|
@@ -20,6 +20,7 @@
|
|
|
#include "string_func.h"
|
|
|
#include "gfx_func.h"
|
|
|
#include "company_func.h"
|
|
|
#include "date_func.h"
|
|
|
|
|
|
#include "table/strings.h"
|
|
|
|
|
@@ -27,12 +28,23 @@ enum TimetableViewWindowWidgets {
|
|
|
TTV_CAPTION,
|
|
|
TTV_ORDER_VIEW,
|
|
|
TTV_TIMETABLE_PANEL,
|
|
|
TTV_FAKE_SCROLLBAR, ///< So the timetable panel 'sees' the scrollbar too
|
|
|
TTV_ARRIVAL_DEPARTURE_PANEL, ///< Panel with the expected/scheduled arrivals
|
|
|
TTV_SCROLLBAR,
|
|
|
TTV_SUMMARY_PANEL,
|
|
|
TTV_CHANGE_TIME,
|
|
|
TTV_CLEAR_TIME,
|
|
|
TTV_RESET_LATENESS,
|
|
|
TTV_AUTOFILL,
|
|
|
TTV_EXPECTED, ///< Toggle between expected and scheduled arrivals
|
|
|
TTV_ARRIVAL_DEPARTURE_SELECTION, ///< Disable/hide the arrival departure panel
|
|
|
TTV_EXPECTED_SELECTION, ///< Disable/hide the expected selection button
|
|
|
};
|
|
|
|
|
|
/** Container for the arrival/departure dates of a vehicle */
|
|
|
struct TimetableArrivalDeparture {
|
|
|
Ticks arrival; ///< The arrival time
|
|
|
Ticks departure; ///< The departure time
|
|
|
};
|
|
|
|
|
|
/**
|
|
@@ -52,21 +64,137 @@ void SetTimetableParams(int param1, int
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Sets the arrival or departure string and parameters.
|
|
|
* @param param1 the first DParam to fill
|
|
|
* @param param2 the second DParam to fill
|
|
|
* @param ticks the number of ticks to 'draw'
|
|
|
*/
|
|
|
static void SetArrivalDepartParams(int param1, int param2, Ticks ticks)
|
|
|
{
|
|
|
SetDParam(param1, STR_JUST_DATE_TINY);
|
|
|
SetDParam(param2, _date + (ticks / DAY_TICKS));
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Check whether it is possible to determine how long the order takes.
|
|
|
* @param order the order to check.
|
|
|
* @param travelling whether we are interested in the travel or the wait part.
|
|
|
* @return true if the travel/wait time can be used.
|
|
|
*/
|
|
|
static bool CanDetermineTimeTaken(const Order *order, bool travelling)
|
|
|
{
|
|
|
/* Current order is conditional */
|
|
|
if (order->IsType(OT_CONDITIONAL)) return false;
|
|
|
/* No travel time and we have not already finished travelling */
|
|
|
if (travelling && order->travel_time == 0) return false;
|
|
|
/* No wait time but we are loading at this timetabled station */
|
|
|
if (!travelling && order->wait_time == 0 && order->IsType(OT_GOTO_STATION) && !(order->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION)) return false;
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Fill the table with arrivals and departures
|
|
|
* @param v Vehicle which must have at least 2 orders.
|
|
|
* @param start order index to start at
|
|
|
* @param travelling Are we still in the travelling part of the start order
|
|
|
* @param table Fill in arrival and departures including intermediate orders
|
|
|
* @param offset Add this value to result and all arrivals and departures
|
|
|
*/
|
|
|
static void FillTimetableArrivalDepartureTable(const Vehicle *v, VehicleOrderID start, bool travelling, TimetableArrivalDeparture *table, Ticks offset)
|
|
|
{
|
|
|
assert(table != NULL);
|
|
|
assert(v->GetNumOrders() >= 2);
|
|
|
assert(start < v->GetNumOrders());
|
|
|
|
|
|
Ticks sum = offset;
|
|
|
VehicleOrderID i = start;
|
|
|
const Order *order = v->GetOrder(i);
|
|
|
|
|
|
/* Pre-initialize with unknown time */
|
|
|
for (int i = 0; i < v->GetNumOrders(); ++i) {
|
|
|
table[i].arrival = table[i].departure = INVALID_TICKS;
|
|
|
}
|
|
|
|
|
|
/* Cyclically loop over all orders until we reach the current one again.
|
|
|
* As we may start at the current order, do a post-checking loop */
|
|
|
do {
|
|
|
if (travelling || i != start) {
|
|
|
if (!CanDetermineTimeTaken(order, true)) return;
|
|
|
sum += order->travel_time;
|
|
|
table[i].arrival = sum;
|
|
|
}
|
|
|
|
|
|
if (!CanDetermineTimeTaken(order, false)) return;
|
|
|
sum += order->wait_time;
|
|
|
table[i].departure = sum;
|
|
|
|
|
|
++i;
|
|
|
order = order->next;
|
|
|
if (i >= v->GetNumOrders()) {
|
|
|
i = 0;
|
|
|
assert(order == NULL);
|
|
|
order = v->orders.list->GetFirstOrder();
|
|
|
}
|
|
|
} while (i != start);
|
|
|
|
|
|
/* When loading at a scheduled station we still have to treat the
|
|
|
* travelling part of the first order. */
|
|
|
if (!travelling) {
|
|
|
if (!CanDetermineTimeTaken(order, true)) return;
|
|
|
sum += order->travel_time;
|
|
|
table[i].arrival = sum;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
struct TimetableWindow : Window {
|
|
|
int sel_index;
|
|
|
const Vehicle *vehicle; ///< Vehicle monitored by the window.
|
|
|
bool show_expected; ///< Whether we show expected arrival or scheduled
|
|
|
|
|
|
TimetableWindow(const WindowDesc *desc, WindowNumber window_number) : Window()
|
|
|
TimetableWindow(const WindowDesc *desc, WindowNumber window_number) :
|
|
|
Window(),
|
|
|
sel_index(-1),
|
|
|
vehicle(Vehicle::Get(window_number)),
|
|
|
show_expected(true)
|
|
|
{
|
|
|
this->vehicle = Vehicle::Get(window_number);
|
|
|
this->InitNested(desc, window_number);
|
|
|
this->CreateNestedTree(desc);
|
|
|
this->UpdateSelectionStates();
|
|
|
this->FinishInitNested(desc, window_number);
|
|
|
|
|
|
this->owner = this->vehicle->owner;
|
|
|
this->sel_index = -1;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Build the arrival-departure list for a given vehicle
|
|
|
* @param v the vehicle to make the list for
|
|
|
* @param table the table to fill
|
|
|
* @return if next arrival will be early
|
|
|
*/
|
|
|
static bool BuildArrivalDepartureList(const Vehicle *v, TimetableArrivalDeparture *table)
|
|
|
{
|
|
|
assert(HasBit(v->vehicle_flags, VF_TIMETABLE_STARTED));
|
|
|
|
|
|
bool travelling = (!v->current_order.IsType(OT_LOADING) || v->current_order.GetNonStopType() == ONSF_STOP_EVERYWHERE);
|
|
|
Ticks start_time = _date_fract - v->current_order_time;
|
|
|
|
|
|
FillTimetableArrivalDepartureTable(v, v->cur_order_index % v->GetNumOrders(), travelling, table, start_time);
|
|
|
|
|
|
return (travelling && v->lateness_counter < 0);
|
|
|
}
|
|
|
|
|
|
virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
|
|
|
{
|
|
|
switch (widget) {
|
|
|
case TTV_ARRIVAL_DEPARTURE_PANEL:
|
|
|
SetDParam(0, STR_JUST_DATE_TINY);
|
|
|
SetDParam(1, MAX_YEAR * DAYS_IN_YEAR);
|
|
|
size->width = GetStringBoundingBox(STR_TIMETABLE_ARRIVAL).width + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
|
|
|
/* fall through */
|
|
|
case TTV_ARRIVAL_DEPARTURE_SELECTION:
|
|
|
case TTV_TIMETABLE_PANEL:
|
|
|
resize->height = FONT_HEIGHT_NORMAL;
|
|
|
size->height = WD_FRAMERECT_TOP + 8 * resize->height + WD_FRAMERECT_BOTTOM;
|
|
@@ -105,6 +233,11 @@ struct TimetableWindow : Window {
|
|
|
this->sel_index = -1;
|
|
|
break;
|
|
|
|
|
|
case -2:
|
|
|
this->UpdateSelectionStates();
|
|
|
this->ReInit();
|
|
|
break;
|
|
|
|
|
|
default: {
|
|
|
/* Moving an order. If one of these is INVALID_VEH_ORDER_ID, then
|
|
|
* the order is being created / removed */
|
|
@@ -187,7 +320,10 @@ struct TimetableWindow : Window {
|
|
|
|
|
|
virtual void SetStringParameters(int widget) const
|
|
|
{
|
|
|
if (widget == TTV_CAPTION) SetDParam(0, this->vehicle->index);
|
|
|
switch (widget) {
|
|
|
case TTV_CAPTION: SetDParam(0, this->vehicle->index); break;
|
|
|
case TTV_EXPECTED: SetDParam(0, this->show_expected ? STR_TIMETABLE_EXPECTED : STR_TIMETABLE_SCHEDULED); break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
virtual void DrawWidget(const Rect &r, int widget) const
|
|
@@ -241,6 +377,55 @@ struct TimetableWindow : Window {
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
case TTV_ARRIVAL_DEPARTURE_PANEL: {
|
|
|
/* Arrival and departure times are handled in an all-or-nothing approach,
|
|
|
* i.e. are only shown if we can calculate all times.
|
|
|
* Excluding order lists with only one order makes some things easier.
|
|
|
*/
|
|
|
Ticks total_time = v->orders.list != NULL ? v->orders.list->GetTimetableDurationIncomplete() : 0;
|
|
|
if (total_time <= 0 || v->GetNumOrders() <= 1 || !HasBit(v->vehicle_flags, VF_TIMETABLE_STARTED)) break;
|
|
|
|
|
|
TimetableArrivalDeparture *arr_dep = AllocaM(TimetableArrivalDeparture, v->GetNumOrders());
|
|
|
const VehicleOrderID cur_order = v->cur_order_index % v->GetNumOrders();
|
|
|
|
|
|
VehicleOrderID earlyID = BuildArrivalDepartureList(v, arr_dep) ? cur_order : (VehicleOrderID)INVALID_VEH_ORDER_ID;
|
|
|
|
|
|
int y = r.top + WD_FRAMERECT_TOP;
|
|
|
|
|
|
Ticks offset;
|
|
|
StringID str_offset;
|
|
|
if (this->show_expected && v->lateness_counter > DAY_TICKS) {
|
|
|
offset = 0;
|
|
|
str_offset = STR_TIMETABLE_ARRIVAL_LATE - STR_TIMETABLE_ARRIVAL;
|
|
|
} else {
|
|
|
offset = -v->lateness_counter;
|
|
|
str_offset = 0;
|
|
|
}
|
|
|
|
|
|
for (int i = this->vscroll.GetPosition(); i / 2 < v->GetNumOrders(); ++i) { // note: i is also incremented in the loop
|
|
|
/* Don't draw anything if it extends past the end of the window. */
|
|
|
if (!this->vscroll.IsVisible(i)) break;
|
|
|
|
|
|
if (i % 2 == 0) {
|
|
|
if (arr_dep[i / 2].arrival != INVALID_TICKS) {
|
|
|
if (this->show_expected && i / 2 == earlyID) {
|
|
|
SetArrivalDepartParams(0, 1, arr_dep[i / 2].arrival);
|
|
|
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_TIMETABLE_ARRIVAL_EARLY, i == selected ? TC_WHITE : TC_BLACK);
|
|
|
} else {
|
|
|
SetArrivalDepartParams(0, 1, arr_dep[i / 2].arrival + offset);
|
|
|
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_TIMETABLE_ARRIVAL + str_offset, i == selected ? TC_WHITE : TC_BLACK);
|
|
|
}
|
|
|
}
|
|
|
} else {
|
|
|
if (arr_dep[i / 2].departure != INVALID_TICKS) {
|
|
|
SetArrivalDepartParams(0, 1, arr_dep[i/2].departure + offset);
|
|
|
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_TIMETABLE_DEPARTURE + str_offset, i == selected ? TC_WHITE : TC_BLACK);
|
|
|
}
|
|
|
}
|
|
|
y += FONT_HEIGHT_NORMAL;
|
|
|
}
|
|
|
} break;
|
|
|
|
|
|
case TTV_SUMMARY_PANEL: {
|
|
|
int y = r.top + WD_FRAMERECT_TOP;
|
|
|
|
|
@@ -325,6 +510,10 @@ struct TimetableWindow : Window {
|
|
|
if (_ctrl_pressed) SetBit(p2, 1);
|
|
|
DoCommandP(0, v->index, p2, CMD_AUTOFILL_TIMETABLE | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
|
|
|
} break;
|
|
|
|
|
|
case TTV_EXPECTED:
|
|
|
this->show_expected = !this->show_expected;
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
this->SetDirty();
|
|
@@ -351,6 +540,16 @@ struct TimetableWindow : Window {
|
|
|
/* Update the scroll bar */
|
|
|
this->vscroll.SetCapacity((this->GetWidget<NWidgetBase>(TTV_TIMETABLE_PANEL)->current_y - WD_FRAMERECT_TOP - WD_FRAMERECT_BOTTOM) / this->resize.step_height);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Update the selection state of the arrival/departure data
|
|
|
*/
|
|
|
void UpdateSelectionStates()
|
|
|
{
|
|
|
int plane = _settings_client.gui.timetable_arrival_departure ? 0 : STACKED_SELECTION_ZERO_SIZE;
|
|
|
this->GetWidget<NWidgetStacked>(TTV_ARRIVAL_DEPARTURE_SELECTION)->SetDisplayedPlane(plane);
|
|
|
this->GetWidget<NWidgetStacked>(TTV_EXPECTED_SELECTION)->SetDisplayedPlane(plane);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
static const NWidgetPart _nested_timetable_widgets[] = {
|
|
@@ -362,15 +561,21 @@ static const NWidgetPart _nested_timetab
|
|
|
EndContainer(),
|
|
|
NWidget(NWID_HORIZONTAL),
|
|
|
NWidget(WWT_PANEL, COLOUR_GREY, TTV_TIMETABLE_PANEL), SetMinimalSize(388, 82), SetResize(1, 10), SetDataTip(STR_NULL, STR_TIMETABLE_TOOLTIP), EndContainer(),
|
|
|
NWidget(WWT_SCROLLBAR, COLOUR_GREY, TTV_FAKE_SCROLLBAR), SetMinimalSize(0, 0), // Hack so the timetable panel can 'use' the scrollbar too
|
|
|
NWidget(NWID_SELECTION, INVALID_COLOUR, TTV_ARRIVAL_DEPARTURE_SELECTION),
|
|
|
NWidget(WWT_PANEL, COLOUR_GREY, TTV_ARRIVAL_DEPARTURE_PANEL), SetMinimalSize(110, 0), SetFill(0, 1), SetDataTip(STR_NULL, STR_TIMETABLE_TOOLTIP), EndContainer(),
|
|
|
EndContainer(),
|
|
|
NWidget(WWT_SCROLLBAR, COLOUR_GREY, TTV_SCROLLBAR),
|
|
|
EndContainer(),
|
|
|
NWidget(WWT_PANEL, COLOUR_GREY, TTV_SUMMARY_PANEL), SetMinimalSize(400, 22), SetResize(1, 0), EndContainer(),
|
|
|
NWidget(NWID_HORIZONTAL),
|
|
|
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TTV_CHANGE_TIME), SetMinimalSize(110, 12), SetDataTip(STR_TIMETABLE_CHANGE_TIME, STR_TIMETABLE_WAIT_TIME_TOOLTIP),
|
|
|
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TTV_CLEAR_TIME), SetMinimalSize(110, 12), SetDataTip(STR_TIMETABLE_CLEAR_TIME, STR_TIMETABLE_CLEAR_TIME_TOOLTIP),
|
|
|
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TTV_RESET_LATENESS), SetMinimalSize(118, 12), SetDataTip(STR_TIMETABLE_RESET_LATENESS, STR_TIMETABLE_RESET_LATENESS_TOOLTIP),
|
|
|
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TTV_AUTOFILL), SetMinimalSize(50, 12), SetDataTip(STR_TIMETABLE_AUTOFILL, STR_TIMETABLE_AUTOFILL_TOOLTIP),
|
|
|
NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(0, 12), SetResize(1, 0), EndContainer(),
|
|
|
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TTV_CHANGE_TIME), SetMinimalSize(110, 12), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_TIMETABLE_CHANGE_TIME, STR_TIMETABLE_WAIT_TIME_TOOLTIP),
|
|
|
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TTV_CLEAR_TIME), SetMinimalSize(110, 12), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_TIMETABLE_CLEAR_TIME, STR_TIMETABLE_CLEAR_TIME_TOOLTIP),
|
|
|
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TTV_RESET_LATENESS), SetMinimalSize(118, 12), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_TIMETABLE_RESET_LATENESS, STR_TIMETABLE_RESET_LATENESS_TOOLTIP),
|
|
|
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TTV_AUTOFILL), SetMinimalSize(50, 12), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_TIMETABLE_AUTOFILL, STR_TIMETABLE_AUTOFILL_TOOLTIP),
|
|
|
NWidget(NWID_SELECTION, INVALID_COLOUR, TTV_EXPECTED_SELECTION),
|
|
|
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TTV_EXPECTED), SetMinimalSize(50, 12), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_BLACK_STRING, STR_TIMETABLE_EXPECTED_TOOLTIP),
|
|
|
EndContainer(),
|
|
|
NWidget(WWT_RESIZEBOX,COLOUR_GREY),
|
|
|
EndContainer(),
|
|
|
};
|