maedhros@7477: /* $Id$ */ maedhros@7477: maedhros@7477: /** @file timetable_gui.cpp */ maedhros@7477: maedhros@7477: #include "stdafx.h" maedhros@7477: #include "openttd.h" maedhros@7477: #include "variables.h" rubidium@8612: #include "command_func.h" maedhros@7477: #include "engine.h" maedhros@7477: #include "gui.h" rubidium@8603: #include "window_gui.h" rubidium@8603: #include "textbuf_gui.h" maedhros@7477: #include "cargotype.h" maedhros@7477: #include "depot.h" rubidium@8610: #include "strings_func.h" rubidium@8640: #include "vehicle_base.h" rubidium@8710: #include "string_func.h" rubidium@8720: #include "gfx_func.h" rubidium@8750: #include "player_func.h" rubidium@8766: #include "settings_type.h" maedhros@7477: rubidium@8760: #include "table/strings.h" rubidium@8760: maedhros@7477: static int GetOrderFromTimetableWndPt(Window *w, int y, const Vehicle *v) maedhros@7477: { maedhros@7477: /* maedhros@7477: * Calculation description: maedhros@7477: * 15 = 14 (w->widget[ORDER_WIDGET_ORDER_LIST].top) + 1 (frame-line) maedhros@7477: * 10 = order text hight maedhros@7477: */ maedhros@7477: int sel = (y - 15) / 10; maedhros@7477: maedhros@7477: if ((uint)sel >= w->vscroll.cap) return INVALID_ORDER; maedhros@7477: maedhros@7477: sel += w->vscroll.pos; maedhros@7477: maedhros@7477: return (sel <= v->num_orders * 2 && sel >= 0) ? sel : INVALID_ORDER; maedhros@7477: } maedhros@7477: maedhros@7477: static inline void SetTimetableParams(int param1, int param2, uint32 time) maedhros@7477: { maedhros@7477: if (_patches.timetable_in_ticks) { maedhros@7477: SetDParam(param1, STR_TIMETABLE_TICKS); maedhros@7477: SetDParam(param2, time); maedhros@7477: } else { maedhros@7477: SetDParam(param1, STR_TIMETABLE_DAYS); maedhros@7477: SetDParam(param2, time / DAY_TICKS); maedhros@7477: } maedhros@7477: } maedhros@7477: maedhros@7477: static void DrawTimetableWindow(Window *w) maedhros@7477: { maedhros@7477: const Vehicle *v = GetVehicle(w->window_number); rubidium@8578: int selected = WP(w, order_d).sel; maedhros@7477: maedhros@7477: SetVScrollCount(w, v->num_orders * 2); maedhros@7477: maedhros@7477: if (v->owner == _local_player) { maedhros@7477: if (selected == -1) { rubidium@8493: w->DisableWidget(6); rubidium@8493: w->DisableWidget(7); maedhros@7477: } else if (selected % 2 == 1) { rubidium@8493: w->EnableWidget(6); rubidium@8493: w->EnableWidget(7); maedhros@7477: } else { maedhros@7477: const Order *order = GetVehicleOrder(v, (selected + 1) / 2); rubidium@8798: bool disable = order == NULL || order->type != OT_GOTO_STATION || (_patches.new_nonstop && (order->flags & OFB_NON_STOP)); maedhros@7477: rubidium@8493: w->SetWidgetDisabledState(6, disable); rubidium@8493: w->SetWidgetDisabledState(7, disable); maedhros@7477: } maedhros@7477: rubidium@8493: w->EnableWidget(8); rubidium@8493: w->EnableWidget(9); maedhros@7477: } else { rubidium@8493: w->DisableWidget(6); rubidium@8493: w->DisableWidget(7); rubidium@8493: w->DisableWidget(8); rubidium@8493: w->DisableWidget(9); maedhros@7477: } maedhros@7477: rubidium@8493: w->SetWidgetLoweredState(9, HasBit(v->vehicle_flags, VF_AUTOFILL_TIMETABLE)); maedhros@7562: peter1138@7545: SetDParam(0, v->index); maedhros@7477: DrawWindowWidgets(w); maedhros@7477: maedhros@7477: int y = 15; maedhros@7477: int i = w->vscroll.pos; maedhros@7477: VehicleOrderID order_id = (i + 1) / 2; maedhros@7477: bool final_order = false; maedhros@7477: maedhros@7477: const Order *order = GetVehicleOrder(v, order_id); maedhros@7477: maedhros@7477: while (order != NULL) { maedhros@7477: /* Don't draw anything if it extends past the end of the window. */ maedhros@7477: if (i - w->vscroll.pos >= w->vscroll.cap) break; maedhros@7477: maedhros@7477: if (i % 2 == 0) { maedhros@7477: SetDParam(2, STR_EMPTY); maedhros@7477: maedhros@7477: switch (order->type) { truelight@7808: case OT_DUMMY: truelight@7808: SetDParam(0, STR_INVALID_ORDER); truelight@7808: break; truelight@7808: maedhros@7477: case OT_GOTO_STATION: maedhros@8920: SetDParam(0, (order->flags & OFB_NON_STOP) ? STR_880A_GO_NON_STOP_TO : STR_8806_GO_TO); maedhros@7477: SetDParam(1, order->dest); maedhros@7477: maedhros@7477: if (order->wait_time > 0) { maedhros@7477: SetDParam(2, STR_TIMETABLE_STAY_FOR); maedhros@7477: SetTimetableParams(3, 4, order->wait_time); maedhros@7477: } maedhros@7477: maedhros@7477: break; maedhros@7477: maedhros@7477: case OT_GOTO_DEPOT: { maedhros@7477: StringID string = STR_EMPTY; maedhros@7477: maedhros@7477: if (v->type == VEH_AIRCRAFT) { maedhros@7477: string = STR_GO_TO_AIRPORT_HANGAR; maedhros@7477: SetDParam(1, order->dest); maedhros@7477: } else { maedhros@7477: SetDParam(1, GetDepot(order->dest)->town_index); maedhros@7477: maedhros@7477: switch (v->type) { rubidium@8798: case VEH_TRAIN: string = (order->flags & OFB_NON_STOP) ? STR_880F_GO_NON_STOP_TO_TRAIN_DEPOT : STR_GO_TO_TRAIN_DEPOT; break; maedhros@8920: case VEH_ROAD: string = STR_GO_TO_ROADVEH_DEPOT; break; maedhros@7477: case VEH_SHIP: string = STR_GO_TO_SHIP_DEPOT; break; maedhros@7477: default: break; maedhros@7477: } maedhros@7477: } maedhros@7477: rubidium@8798: if (order->flags & OFB_FULL_LOAD) string++; // Service at orders maedhros@7477: maedhros@7477: SetDParam(0, string); maedhros@7477: } break; maedhros@7477: maedhros@7477: case OT_GOTO_WAYPOINT: rubidium@8798: SetDParam(0, (order->flags & OFB_NON_STOP) ? STR_GO_NON_STOP_TO_WAYPOINT : STR_GO_TO_WAYPOINT); maedhros@7477: SetDParam(1, order->dest); maedhros@7477: break; maedhros@7477: maedhros@7477: default: break; maedhros@7477: } maedhros@7477: belugas@8320: DrawString(2, y, STR_TIMETABLE_GO_TO, (i == selected) ? TC_WHITE : TC_BLACK); maedhros@7477: maedhros@7477: order_id++; maedhros@7477: maedhros@7477: if (order_id >= v->num_orders) { maedhros@7477: order = GetVehicleOrder(v, 0); maedhros@7477: final_order = true; maedhros@7477: } else { maedhros@7477: order = order->next; maedhros@7477: } maedhros@7477: } else { maedhros@7477: StringID string; maedhros@7477: maedhros@7477: if (order->travel_time == 0) { maedhros@7477: string = STR_TIMETABLE_TRAVEL_NOT_TIMETABLED; maedhros@7477: } else { maedhros@7477: SetTimetableParams(0, 1, order->travel_time); maedhros@7477: string = STR_TIMETABLE_TRAVEL_FOR; maedhros@7477: } maedhros@7477: belugas@8320: DrawString(12, y, string, (i == selected) ? TC_WHITE : TC_BLACK); maedhros@7477: maedhros@7477: if (final_order) break; maedhros@7477: } maedhros@7477: maedhros@7477: i++; maedhros@7477: y += 10; maedhros@7477: } maedhros@7477: maedhros@7477: y = w->widget[5].top + 1; maedhros@7477: maedhros@7569: { maedhros@7569: uint total_time = 0; maedhros@7569: bool complete = true; maedhros@7569: maedhros@7569: for (const Order *order = GetVehicleOrder(v, 0); order != NULL; order = order->next) { maedhros@7569: total_time += order->travel_time + order->wait_time; maedhros@7569: if (order->travel_time == 0) complete = false; rubidium@8798: if (order->wait_time == 0 && order->type == OT_GOTO_STATION && !(_patches.new_nonstop && (order->flags & OFB_NON_STOP))) complete = false; maedhros@7569: } maedhros@7569: maedhros@7569: if (total_time != 0) { maedhros@7569: SetTimetableParams(0, 1, total_time); belugas@8320: DrawString(2, y, complete ? STR_TIMETABLE_TOTAL_TIME : STR_TIMETABLE_TOTAL_TIME_INCOMPLETE, TC_BLACK); maedhros@7569: } maedhros@7569: } maedhros@7569: y += 10; maedhros@7569: maedhros@7477: if (v->lateness_counter == 0 || (!_patches.timetable_in_ticks && v->lateness_counter / DAY_TICKS == 0)) { belugas@8320: DrawString(2, y, STR_TIMETABLE_STATUS_ON_TIME, TC_BLACK); maedhros@7477: } else { maedhros@7477: SetTimetableParams(0, 1, abs(v->lateness_counter)); belugas@8320: DrawString(2, y, v->lateness_counter < 0 ? STR_TIMETABLE_STATUS_EARLY : STR_TIMETABLE_STATUS_LATE, TC_BLACK); maedhros@7477: } maedhros@7477: } maedhros@7477: maedhros@7477: static inline uint32 PackTimetableArgs(const Vehicle *v, uint selected) maedhros@7477: { maedhros@7477: uint order_number = (selected + 1) / 2; maedhros@7477: uint is_journey = (selected % 2 == 1) ? 1 : 0; maedhros@7477: maedhros@7477: if (order_number >= v->num_orders) order_number = 0; maedhros@7477: maedhros@7477: return v->index | (order_number << 16) | (is_journey << 24); maedhros@7477: } maedhros@7477: maedhros@7477: static void TimetableWndProc(Window *w, WindowEvent *we) maedhros@7477: { maedhros@7477: switch (we->event) { maedhros@7477: case WE_PAINT: maedhros@7477: DrawTimetableWindow(w); maedhros@7477: break; maedhros@7477: maedhros@7477: case WE_CLICK: { maedhros@7477: const Vehicle *v = GetVehicle(w->window_number); maedhros@7477: maedhros@7477: switch (we->we.click.widget) { maedhros@7477: case 3: { /* Main panel. */ maedhros@7477: int selected = GetOrderFromTimetableWndPt(w, we->we.click.pt.y, v); maedhros@7477: rubidium@8578: if (selected == INVALID_ORDER || selected == WP(w, order_d).sel) { maedhros@7477: /* Deselect clicked order */ rubidium@8578: WP(w, order_d).sel = -1; maedhros@7477: } else { maedhros@7477: /* Select clicked order */ rubidium@8578: WP(w, order_d).sel = selected; maedhros@7477: } maedhros@7477: } break; maedhros@7477: maedhros@7477: case 6: { /* "Wait For" button. */ rubidium@8578: int selected = WP(w, order_d).sel; maedhros@7477: VehicleOrderID real = (selected + 1) / 2; maedhros@7477: maedhros@7477: if (real >= v->num_orders) real = 0; maedhros@7477: maedhros@7477: const Order *order = GetVehicleOrder(v, real); maedhros@7477: StringID current = STR_EMPTY; maedhros@7477: maedhros@7477: if (order != NULL) { maedhros@7477: uint time = (selected % 2 == 1) ? order->travel_time : order->wait_time; maedhros@7477: if (!_patches.timetable_in_ticks) time /= DAY_TICKS; maedhros@7477: maedhros@7477: if (time != 0) { maedhros@7477: SetDParam(0, time); maedhros@7477: current = STR_CONFIG_PATCHES_INT32; maedhros@7477: } maedhros@7477: } maedhros@7477: maedhros@7477: ShowQueryString(current, STR_TIMETABLE_CHANGE_TIME, 31, 150, w, CS_NUMERAL); maedhros@7477: } break; maedhros@7477: maedhros@7477: case 7: { /* Clear waiting time button. */ rubidium@8578: uint32 p1 = PackTimetableArgs(v, WP(w, order_d).sel); maedhros@7477: DoCommandP(0, p1, 0, NULL, CMD_CHANGE_TIMETABLE | CMD_MSG(STR_CAN_T_TIMETABLE_VEHICLE)); maedhros@7477: } break; maedhros@7477: maedhros@7477: case 8: /* Reset the vehicle's late counter. */ maedhros@7477: DoCommandP(0, v->index, 0, NULL, CMD_SET_VEHICLE_ON_TIME | CMD_MSG(STR_CAN_T_TIMETABLE_VEHICLE)); maedhros@7477: break; maedhros@7562: maedhros@7562: case 9: /* Autofill the timetable. */ skidd13@8424: DoCommandP(0, v->index, HasBit(v->vehicle_flags, VF_AUTOFILL_TIMETABLE) ? 0 : 1, NULL, CMD_AUTOFILL_TIMETABLE | CMD_MSG(STR_CAN_T_TIMETABLE_VEHICLE)); maedhros@7562: break; maedhros@7477: } maedhros@7477: maedhros@7477: SetWindowDirty(w); maedhros@7477: } break; maedhros@7477: maedhros@7477: case WE_ON_EDIT_TEXT: { maedhros@7477: const Vehicle *v = GetVehicle(w->window_number); maedhros@7477: rubidium@8578: uint32 p1 = PackTimetableArgs(v, WP(w, order_d).sel); maedhros@7477: maedhros@7477: uint64 time = StrEmpty(we->we.edittext.str) ? 0 : strtoul(we->we.edittext.str, NULL, 10); maedhros@7477: if (!_patches.timetable_in_ticks) time *= DAY_TICKS; maedhros@7477: maedhros@7477: uint32 p2 = minu(time, MAX_UVALUE(uint16)); maedhros@7477: maedhros@7477: DoCommandP(0, p1, p2, NULL, CMD_CHANGE_TIMETABLE | CMD_MSG(STR_CAN_T_TIMETABLE_VEHICLE)); maedhros@7477: } break; maedhros@7477: maedhros@7477: case WE_RESIZE: maedhros@7477: /* Update the scroll + matrix */ maedhros@7478: w->vscroll.cap = (w->widget[3].bottom - w->widget[3].top) / 10; maedhros@7477: break; maedhros@7477: maedhros@7477: } maedhros@7477: } maedhros@7477: maedhros@7477: static const Widget _timetable_widgets[] = { maedhros@7477: { WWT_CLOSEBOX, RESIZE_NONE, 14, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, maedhros@7562: { WWT_CAPTION, RESIZE_RIGHT, 14, 11, 387, 0, 13, STR_TIMETABLE_TITLE, STR_018C_WINDOW_TITLE_DRAG_THIS}, maedhros@7562: { WWT_STICKYBOX, RESIZE_LR, 14, 388, 399, 0, 13, STR_NULL, STR_STICKY_BUTTON}, maedhros@7477: maedhros@7562: { WWT_PANEL, RESIZE_RB, 14, 0, 387, 14, 95, STR_NULL, STR_TIMETABLE_TOOLTIP}, maedhros@7562: { WWT_SCROLLBAR, RESIZE_LRB, 14, 388, 399, 14, 95, STR_NULL, STR_0190_SCROLL_BAR_SCROLLS_LIST}, maedhros@7477: maedhros@7569: { WWT_PANEL, RESIZE_RTB, 14, 0, 399, 96, 117, STR_NULL, STR_NULL}, maedhros@7477: maedhros@7569: { WWT_PUSHTXTBTN, RESIZE_TB, 14, 0, 109, 118, 129, STR_TIMETABLE_CHANGE_TIME, STR_TIMETABLE_WAIT_TIME_TOOLTIP}, maedhros@7569: { WWT_PUSHTXTBTN, RESIZE_TB, 14, 110, 219, 118, 129, STR_CLEAR_TIME, STR_TIMETABLE_CLEAR_TIME_TOOLTIP}, maedhros@7569: { WWT_PUSHTXTBTN, RESIZE_TB, 14, 220, 337, 118, 129, STR_RESET_LATENESS, STR_TIMETABLE_RESET_LATENESS_TOOLTIP}, maedhros@7569: { WWT_PUSHTXTBTN, RESIZE_TB, 14, 338, 387, 118, 129, STR_TIMETABLE_AUTOFILL, STR_TIMETABLE_AUTOFILL_TOOLTIP}, maedhros@7553: maedhros@7569: { WWT_PANEL, RESIZE_RTB, 14, 388, 387, 118, 129, STR_NULL, STR_NULL}, maedhros@7569: { WWT_RESIZEBOX, RESIZE_LRTB, 14, 388, 399, 118, 129, STR_NULL, STR_RESIZE_BUTTON}, maedhros@7477: maedhros@7477: { WIDGETS_END } maedhros@7477: }; maedhros@7477: maedhros@7477: static const WindowDesc _timetable_desc = { rubidium@7837: WDP_AUTO, WDP_AUTO, 400, 130, 400, 130, maedhros@7477: WC_VEHICLE_TIMETABLE, WC_VEHICLE_VIEW, maedhros@7477: WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE, maedhros@7477: _timetable_widgets, maedhros@7477: TimetableWndProc maedhros@7477: }; maedhros@7477: maedhros@7477: void ShowTimetableWindow(const Vehicle *v) maedhros@7477: { maedhros@7477: Window *w = AllocateWindowDescFront(&_timetable_desc, v->index); maedhros@7477: maedhros@7477: if (w != NULL) { maedhros@7477: w->caption_color = v->owner; maedhros@7553: w->vscroll.cap = 8; maedhros@7477: w->resize.step_height = 10; rubidium@8578: WP(w, order_d).sel = -1; maedhros@7477: } maedhros@7477: }