diff --git a/projects/openttd_vs80.vcproj b/projects/openttd_vs80.vcproj
index 17c2d8d4f1..f44e7cff6d 100644
--- a/projects/openttd_vs80.vcproj
+++ b/projects/openttd_vs80.vcproj
@@ -510,6 +510,10 @@
RelativePath=".\..\src\dedicated.cpp"
>
+
+
@@ -1002,6 +1006,18 @@
RelativePath=".\..\src\video\dedicated_v.h"
>
+
+
+
+
+
+
@@ -2074,6 +2090,10 @@
RelativePath=".\..\src\date_gui.cpp"
>
+
+
diff --git a/projects/openttd_vs90.vcproj b/projects/openttd_vs90.vcproj
index 666760ce78..dca08811be 100644
--- a/projects/openttd_vs90.vcproj
+++ b/projects/openttd_vs90.vcproj
@@ -507,6 +507,10 @@
RelativePath=".\..\src\dedicated.cpp"
>
+
+
@@ -999,6 +1003,18 @@
RelativePath=".\..\src\video\dedicated_v.h"
>
+
+
+
+
+
+
@@ -2071,6 +2087,10 @@
RelativePath=".\..\src\date_gui.cpp"
>
+
+
diff --git a/source.list b/source.list
index 3b66ab9fca..51fc5e5257 100644
--- a/source.list
+++ b/source.list
@@ -18,6 +18,7 @@ currency.cpp
date.cpp
debug.cpp
dedicated.cpp
+departures.cpp
depot.cpp
disaster_vehicle.cpp
driver.cpp
@@ -171,6 +172,9 @@ date_gui.h
date_type.h
debug.h
video/dedicated_v.h
+departures_func.h
+departures_gui.h
+departures_type.h
depot_base.h
depot_func.h
depot_map.h
@@ -456,6 +460,7 @@ cheat_gui.cpp
company_gui.cpp
console_gui.cpp
date_gui.cpp
+departures_gui.cpp
depot_gui.cpp
dock_gui.cpp
engine_gui.cpp
diff --git a/src/base_consist.h b/src/base_consist.h
index 3679afd351..474ab97415 100644
--- a/src/base_consist.h
+++ b/src/base_consist.h
@@ -14,6 +14,7 @@
#include "order_type.h"
#include "date_type.h"
+#include "timetable.h"
/** Various front vehicle properties that are preserved when autoreplacing, using order-backup or switching front engines within a consist. */
struct BaseConsist {
@@ -22,7 +23,11 @@ struct BaseConsist {
/* Used for timetabling. */
uint32 current_order_time; ///< How many ticks have passed since this order started.
int32 lateness_counter; ///< How many ticks late (or early if negative) this vehicle is.
+#if WALLCLOCK_NETWORK_COMPATIBLE
Date timetable_start; ///< When the vehicle is supposed to start the timetable.
+#else
+ DateTicks timetable_start; ///< When the vehicle is supposed to start the timetable.
+#endif
uint16 service_interval; ///< The interval for (automatic) servicing; either in days or %.
diff --git a/src/date_func.h b/src/date_func.h
index 6bbde59556..9ac87af61b 100644
--- a/src/date_func.h
+++ b/src/date_func.h
@@ -24,6 +24,8 @@ void SetDate(Date date, DateFract fract);
void ConvertDateToYMD(Date date, YearMonthDay *ymd);
Date ConvertYMDToDate(Year year, Month month, Day day);
+#define YMD_TO_DATE(ymd) (ConvertYMDToDate(ymd.year, ymd.month, ymd.day))
+
/**
* Checks whether the given year is a leap year or not.
* @param yr The year to check.
diff --git a/src/date_gui.cpp b/src/date_gui.cpp
index 468a74db99..b5b5bce4a2 100644
--- a/src/date_gui.cpp
+++ b/src/date_gui.cpp
@@ -16,6 +16,7 @@
#include "window_gui.h"
#include "date_gui.h"
#include "core/geometry_func.hpp"
+#include "settings_type.h"
#include "widgets/dropdown_type.h"
#include "widgets/date_widget.h"
@@ -65,7 +66,7 @@ struct SetDateWindow : Window {
* Helper function to construct the dropdown.
* @param widget the dropdown widget to create the dropdown for
*/
- void ShowDateDropDown(int widget)
+ virtual void ShowDateDropDown(int widget)
{
int selected;
DropDownList *list = new DropDownList();
@@ -146,9 +147,8 @@ struct SetDateWindow : Window {
case WID_SD_YEAR:
ShowDateDropDown(widget);
break;
-
case WID_SD_SET_DATE:
- if (this->callback != NULL) this->callback(this, ConvertYMDToDate(this->date.year, this->date.month, this->date.day));
+ if (this->callback != NULL) this->callback(this, ConvertYMDToDate(this->date.year, this->date.month, this->date.day) * DAY_TICKS);
delete this;
break;
}
@@ -173,6 +173,122 @@ struct SetDateWindow : Window {
}
};
+struct SetMinutesWindow : SetDateWindow
+{
+ Minutes minutes;
+
+ /** Constructor. */
+ SetMinutesWindow(WindowDesc *desc, WindowNumber window_number, Window *parent, DateTicks initial_date, Year min_year, Year max_year, SetDateCallback *callback) :
+ SetDateWindow(desc, window_number, parent, initial_date, min_year, max_year, callback),
+ minutes(initial_date / _settings_client.gui.ticks_per_minute)
+ {
+ }
+
+ /**
+ * Helper function to construct the dropdown.
+ * @param widget the dropdown widget to create the dropdown for
+ */
+ virtual void ShowDateDropDown(int widget)
+ {
+ int selected;
+ DropDownList *list = new DropDownList();
+
+ switch (widget) {
+ default: NOT_REACHED();
+
+ case WID_SD_DAY:
+ for (uint i = 0; i < 60; i++) {
+ DropDownListParamStringItem *item = new DropDownListParamStringItem(STR_JUST_INT, i, false);
+ item->SetParam(0, i);
+ *list->Append() = item;
+ }
+ selected = MINUTES_MINUTE(minutes);
+ break;
+
+ case WID_SD_MONTH:
+ for (uint i = 0; i < 24; i++) {
+ DropDownListParamStringItem *item = new DropDownListParamStringItem(STR_JUST_INT, i, false);
+ item->SetParam(0, i);
+ *list->Append() = item;
+ }
+ selected = MINUTES_HOUR(minutes);
+
+ break;
+ }
+
+ ShowDropDownList(this, list, selected, widget);
+ }
+
+ virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
+ {
+ Dimension d = {0, 0};
+ switch (widget) {
+ default: return;
+
+ case WID_SD_DAY:
+ for (uint i = 0; i < 60; i++) {
+ SetDParam(0, i);
+ d = maxdim(d, GetStringBoundingBox(STR_JUST_INT));
+ }
+ break;
+
+ case WID_SD_MONTH:
+ for (uint i = 0; i < 24; i++) {
+ SetDParam(0, i);
+ d = maxdim(d, GetStringBoundingBox(STR_JUST_INT));
+ }
+ break;
+ }
+
+ d.width += padding.width;
+ d.height += padding.height;
+ *size = d;
+ }
+
+ virtual void SetStringParameters(int widget) const
+ {
+ switch (widget) {
+ case WID_SD_DAY: SetDParam(0, MINUTES_MINUTE(minutes)); break;
+ case WID_SD_MONTH: SetDParam(0, MINUTES_HOUR(minutes)); break;
+ }
+ }
+
+ virtual void OnClick(Point pt, int widget, int click_count)
+ {
+ switch (widget) {
+ case WID_SD_DAY:
+ case WID_SD_MONTH:
+ case WID_SD_YEAR:
+ ShowDateDropDown(widget);
+ break;
+
+ case WID_SD_SET_DATE:
+ if (this->callback != NULL) this->callback(this->parent, ((DateTicks)minutes - _settings_client.gui.clock_offset) * _settings_client.gui.ticks_per_minute);
+ delete this;
+ break;
+ }
+ }
+
+ virtual void OnDropdownSelect(int widget, int index)
+ {
+ Minutes current = 0;
+ switch (widget) {
+ case WID_SD_DAY:
+ current = MINUTES_DATE(MINUTES_DAY(CURRENT_MINUTE), MINUTES_HOUR(minutes), index);
+ break;
+
+ case WID_SD_MONTH:
+ current = MINUTES_DATE(MINUTES_DAY(CURRENT_MINUTE), index, MINUTES_MINUTE(minutes));
+ break;
+ }
+
+ if (current < (CURRENT_MINUTE - 60)) current += 60 * 24;
+ minutes = current;
+
+ this->SetDirty();
+ }
+};
+
/** Widgets for the date setting window. */
static const NWidgetPart _nested_set_date_widgets[] = {
NWidget(NWID_HORIZONTAL),
@@ -195,6 +311,26 @@ static const NWidgetPart _nested_set_date_widgets[] = {
EndContainer()
};
+static const NWidgetPart _nested_set_minutes_widgets[] = {
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
+ NWidget(WWT_CAPTION, COLOUR_BROWN), SetDataTip(STR_DATE_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
+ EndContainer(),
+ NWidget(WWT_PANEL, COLOUR_BROWN),
+ NWidget(NWID_VERTICAL), SetPIP(6, 6, 6),
+ NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(6, 6, 6),
+ NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_SD_MONTH), SetFill(1, 0), SetDataTip(STR_JUST_INT, STR_DATE_MINUTES_MONTH_TOOLTIP),
+ NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_SD_DAY), SetFill(1, 0), SetDataTip(STR_JUST_INT, STR_DATE_MINUTES_DAY_TOOLTIP),
+ EndContainer(),
+ NWidget(NWID_HORIZONTAL),
+ NWidget(NWID_SPACER), SetFill(1, 0),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_SD_SET_DATE), SetMinimalSize(100, 12), SetDataTip(STR_DATE_SET_DATE, STR_DATE_SET_DATE_TOOLTIP),
+ NWidget(NWID_SPACER), SetFill(1, 0),
+ EndContainer(),
+ EndContainer(),
+ EndContainer()
+};
+
/** Description of the date setting window. */
static WindowDesc _set_date_desc(
WDP_CENTER, NULL, 0, 0,
@@ -203,6 +339,13 @@ static WindowDesc _set_date_desc(
_nested_set_date_widgets, lengthof(_nested_set_date_widgets)
);
+static WindowDesc _set_minutes_desc(
+ WDP_CENTER, NULL, 0, 0,
+ WC_SET_DATE, WC_NONE,
+ 0,
+ _nested_set_minutes_widgets, lengthof(_nested_set_minutes_widgets)
+);
+
/**
* Create the new 'set date' window
* @param window_number number for the window
@@ -212,8 +355,13 @@ static WindowDesc _set_date_desc(
* @param max_year the maximum year (inclusive) to show in the year dropdown
* @param callback the callback to call once a date has been selected
*/
-void ShowSetDateWindow(Window *parent, int window_number, Date initial_date, Year min_year, Year max_year, SetDateCallback *callback)
+void ShowSetDateWindow(Window *parent, int window_number, DateTicks initial_date, Year min_year, Year max_year, SetDateCallback *callback)
{
DeleteWindowByClass(WC_SET_DATE);
- new SetDateWindow(&_set_date_desc, window_number, parent, initial_date, min_year, max_year, callback);
+
+ if (!_settings_client.gui.time_in_minutes) {
+ new SetDateWindow(&_set_date_desc, window_number, parent, initial_date / DAY_TICKS, min_year, max_year, callback);
+ } else {
+ new SetMinutesWindow(&_set_minutes_desc, window_number, parent, initial_date + (_settings_client.gui.clock_offset * _settings_client.gui.ticks_per_minute), min_year, max_year, callback);
+ }
}
diff --git a/src/date_gui.h b/src/date_gui.h
index 314baba3ca..0c8dcba865 100644
--- a/src/date_gui.h
+++ b/src/date_gui.h
@@ -20,8 +20,8 @@
* @param w the window that sends the callback
* @param date the date that has been chosen
*/
-typedef void SetDateCallback(const Window *w, Date date);
+typedef void SetDateCallback(const Window *w, DateTicks date);
-void ShowSetDateWindow(Window *parent, int window_number, Date initial_date, Year min_year, Year max_year, SetDateCallback *callback);
+void ShowSetDateWindow(Window *parent, int window_number, DateTicks initial_date, Year min_year, Year max_year, SetDateCallback *callback);
#endif /* DATE_GUI_H */
diff --git a/src/date_type.h b/src/date_type.h
index b20ace91ec..505b289605 100644
--- a/src/date_type.h
+++ b/src/date_type.h
@@ -12,10 +12,11 @@
#ifndef DATE_TYPE_H
#define DATE_TYPE_H
-
typedef int32 Date; ///< The type to store our dates in
typedef uint16 DateFract; ///< The fraction of a date we're in, i.e. the number of ticks since the last date changeover
typedef int32 Ticks; ///< The type to store ticks in
+typedef int32 DateTicks; ///< The type to store dates in when tick-precision is required
+typedef int32 Minutes; ///< The type to store minutes in
typedef int32 Year; ///< Type for the year, note: 0 based, i.e. starts at the year 0.
typedef uint8 Month; ///< Type for the month, note: 0 based, i.e. 0 = January, 11 = December.
@@ -31,6 +32,8 @@ static const int DAY_TICKS = 74; ///< ticks per day
static const int DAYS_IN_YEAR = 365; ///< days per year
static const int DAYS_IN_LEAP_YEAR = 366; ///< sometimes, you need one day more...
+#define DATE_UNIT_SIZE (_settings_client.gui.time_in_minutes ? _settings_client.gui.ticks_per_minute : DAY_TICKS)
+
static const int STATION_RATING_TICKS = 185; ///< cycle duration for updating station rating
static const int STATION_ACCEPTANCE_TICKS = 250; ///< cycle duration for updating station acceptance
static const int STATION_LINKGRAPH_TICKS = 504; ///< cycle duration for cleaning dead links
@@ -39,7 +42,6 @@ static const int INDUSTRY_PRODUCE_TICKS = 256; ///< cycle duration for industr
static const int TOWN_GROWTH_TICKS = 70; ///< cycle duration for towns trying to grow. (this originates from the size of the town array in TTD
static const int INDUSTRY_CUT_TREE_TICKS = INDUSTRY_PRODUCE_TICKS * 2; ///< cycle duration for lumber mill's extra action
-
/*
* ORIGINAL_BASE_YEAR, ORIGINAL_MAX_YEAR and DAYS_TILL_ORIGINAL_BASE_YEAR are
* primarily used for loading newgrf and savegame data and returning some
@@ -96,6 +98,21 @@ static const Year MAX_YEAR = 5000000;
/** The number of days till the last day */
#define MAX_DAY (DAYS_TILL(MAX_YEAR + 1) - 1)
+/** The day when converting to minutes */
+#define MINUTES_DAY(minutes) (minutes / 1440)
+
+/** The hour when converting to minutes */
+#define MINUTES_HOUR(minutes) ((minutes / 60) % 24)
+
+/** The day when converting to minutes */
+#define MINUTES_MINUTE(minutes) (minutes % 60)
+
+/** Convert minutes to a date */
+#define MINUTES_DATE(day, hour, minute) ((day * 1440) + (hour * 60) + minute)
+
+/** Get the current date in minutes */
+#define CURRENT_MINUTE ((((DateTicks)_date * DAY_TICKS) + _date_fract) / _settings_client.gui.ticks_per_minute)
+
/**
* Data structure to convert between Date and triplet (year, month, and day).
* @see ConvertDateToYMD(), ConvertYMDToDate()
diff --git a/src/departures.cpp b/src/departures.cpp
new file mode 100644
index 0000000000..ab0ebb231c
--- /dev/null
+++ b/src/departures.cpp
@@ -0,0 +1,644 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * 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.
+ * 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.
+ * 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 .
+ */
+
+/** @file departures.cpp Scheduled departures from a station. */
+
+#include "stdafx.h"
+#include "debug.h"
+#include "gui.h"
+#include "textbuf_gui.h"
+#include "strings_func.h"
+#include "window_func.h"
+#include "vehicle_func.h"
+#include "string_func.h"
+#include "window_gui.h"
+#include "timetable.h"
+#include "vehiclelist.h"
+#include "company_base.h"
+#include "date_func.h"
+#include "departures_gui.h"
+#include "station_base.h"
+#include "vehicle_gui_base.h"
+#include "vehicle_base.h"
+#include "vehicle_gui.h"
+#include "order_base.h"
+#include "settings_type.h"
+#include "core/smallvec_type.hpp"
+#include "date_type.h"
+#include "company_type.h"
+#include "cargo_type.h"
+#include "departures_func.h"
+#include "departures_type.h"
+
+/** A scheduled order. */
+typedef struct OrderDate
+{
+ const Order *order; ///< The order
+ const Vehicle *v; ///< The vehicle carrying out the order
+ DateTicks expected_date;///< The date on which the order is expected to complete
+ Ticks lateness; ///< How late this order is expected to finish
+ DepartureStatus status; ///< Whether the vehicle has arrived to carry out the order yet
+} OrderDate;
+
+static bool IsDeparture(const Order *order, StationID station) {
+ return (order->GetType() == OT_GOTO_STATION &&
+ (StationID)order->GetDestination() == station &&
+ (order->GetLoadType() != OLFB_NO_LOAD ||
+ _settings_client.gui.departure_show_all_stops) &&
+ order->GetWaitTime() != 0);
+}
+
+static bool IsVia(const Order *order, StationID station) {
+ return ((order->GetType() == OT_GOTO_STATION ||
+ order->GetType() == OT_GOTO_WAYPOINT) &&
+ (StationID)order->GetDestination() == station &&
+ (order->GetNonStopType() == ONSF_NO_STOP_AT_ANY_STATION ||
+ order->GetNonStopType() == ONSF_NO_STOP_AT_DESTINATION_STATION));
+}
+
+static bool IsArrival(const Order *order, StationID station) {
+ return (order->GetType() == OT_GOTO_STATION &&
+ (StationID)order->GetDestination() == station &&
+ (order->GetUnloadType() != OUFB_NO_UNLOAD ||
+ _settings_client.gui.departure_show_all_stops) &&
+ order->GetWaitTime() != 0);
+}
+
+/**
+ * Compute an up-to-date list of departures for a station.
+ * @param station the station to compute the departures of
+ * @param show_vehicle_types the types of vehicles to include in the departure list
+ * @param type the type of departures to get (departures or arrivals)
+ * @param show_vehicles_via whether to include vehicles that have this station in their orders but do not stop at it
+ * @return a list of departures, which is empty if an error occurred
+ */
+DepartureList* MakeDepartureList(StationID station, bool show_vehicle_types[5], DepartureType type, bool show_vehicles_via)
+{
+ /* This function is the meat of the departure boards functionality. */
+ /* As an overview, it works by repeatedly considering the best possible next departure to show. */
+ /* By best possible we mean the one expected to arrive at the station first. */
+ /* However, we do not consider departures whose scheduled time is too far in the future, even if they are expected before some delayed ones. */
+ /* This code can probably be made more efficient. I haven't done so in order to keep both its (relative) simplicity and my (relative) sanity. */
+ /* Having written that, it's not exactly slow at the moment. */
+
+ /* The list of departures which will be returned as a result. */
+ SmallVector *result = new SmallVector();
+
+ /* A list of the next scheduled orders to be considered for inclusion in the departure list. */
+ SmallVector next_orders;
+
+ /* The maximum possible date for departures to be scheduled to occur. */
+ DateTicks max_date = _settings_client.gui.max_departure_time * DAY_TICKS;
+
+ /* The scheduled order in next_orders with the earliest expected_date field. */
+ OrderDate *least_order = NULL;
+
+ /* Get all the vehicles stopping at this station. */
+ /* We do this to get the order which is the first time they will stop at this station. */
+ /* This order is stored along with some more information. */
+ /* We keep a pointer to the `least' order (the one with the soonest expected completion time). */
+ for (uint i = 0; i < 4; ++i) {
+ VehicleList vehicles;
+
+ if (!show_vehicle_types[i]) {
+ /* Don't show vehicles whose type we're not interested in. */
+ continue;
+ }
+
+ /* MAX_COMPANIES is probably the wrong thing to put here, but it works. GenerateVehicleSortList doesn't check the company when the type of list is VL_STATION_LIST (r20801). */
+ if (!GenerateVehicleSortList(&vehicles, VehicleListIdentifier(VL_STATION_LIST, (VehicleType)(VEH_TRAIN + i), MAX_COMPANIES, station).Pack())) {
+ /* Something went wrong: panic! */
+ return result;
+ }
+
+ /* Get the first order for each vehicle for the station we're interested in that doesn't have No Loading set. */
+ /* We find the least order while we're at it. */
+ for (const Vehicle **v = vehicles.Begin(); v != vehicles.End(); v++) {
+ if (_settings_client.gui.departure_only_passengers) {
+ bool carries_passengers = false;
+
+ const Vehicle *u = *v;
+ while (u != NULL) {
+ if (u->cargo_type == CT_PASSENGERS && u->cargo_cap > 0) {
+ carries_passengers = true;
+ break;
+ }
+ u = u->Next();
+ }
+
+ if (carries_passengers == false) {
+ continue;
+ }
+ }
+
+ const Order *order = (*v)->GetOrder((*v)->cur_implicit_order_index % (*v)->GetNumOrders());
+ DateTicks start_date = (DateTicks)_date_fract - (*v)->current_order_time;
+ DepartureStatus status = D_TRAVELLING;
+
+ /* If the vehicle is stopped in a depot, ignore it. */
+ if ((*v)->IsStoppedInDepot()) {
+ continue;
+ }
+
+ /* If the vehicle is heading for a depot to stop there, then its departures are cancelled. */
+ if ((*v)->current_order.IsType(OT_GOTO_DEPOT) && (*v)->current_order.GetDepotActionType() & ODATFB_HALT) {
+ status = D_CANCELLED;
+ }
+
+ if ((*v)->current_order.IsType(OT_LOADING)) {
+ /* Account for the vehicle having reached the current order and being in the loading phase. */
+ status = D_ARRIVED;
+ start_date -= order->GetTravelTime() + (((*v)->lateness_counter < 0) ? (*v)->lateness_counter : 0);
+ }
+
+ /* Loop through the vehicle's orders until we've found a suitable order or we've determined that no such order exists. */
+ /* We only need to consider each order at most once. */
+ for (int i = (*v)->GetNumOrders(); i > 0; --i) {
+ start_date += order->GetTravelTime() + order->GetWaitTime();
+
+ /* If the scheduled departure date is too far in the future, stop. */
+ if (start_date - (*v)->lateness_counter > max_date) {
+ break;
+ }
+
+ /* If the order is a conditional branch, handle it. */
+ if (order->IsType(OT_CONDITIONAL)) {
+ switch(_settings_client.gui.departure_conditionals) {
+ case 0: {
+ /* Give up */
+ break;
+ }
+ case 1: {
+ /* Take the branch */
+ if (status != D_CANCELLED) {
+ status = D_TRAVELLING;
+ }
+ order = (*v)->GetOrder(order->GetConditionSkipToOrder());
+ if (order == NULL) {
+ break;
+ }
+
+ start_date -= order->GetTravelTime();
+
+ continue;
+ }
+ case 2: {
+ /* Do not take the branch */
+ if (status != D_CANCELLED) {
+ status = D_TRAVELLING;
+ }
+ order = (order->next == NULL) ? (*v)->GetFirstOrder() : order->next;
+ continue;
+ }
+ }
+ }
+
+ /* Skip it if it's an automatic order. */
+ if (order->IsType(OT_IMPLICIT)) {
+ order = (order->next == NULL) ? (*v)->GetFirstOrder() : order->next;
+ continue;
+ }
+
+ /* If an order doesn't have a travel time set, then stop. */
+ if (order->GetTravelTime() == 0) {
+ break;
+ }
+
+ /* If the vehicle will be stopping at and loading from this station, and its wait time is not zero, then it is a departure. */
+ /* If the vehicle will be stopping at and unloading at this station, and its wait time is not zero, then it is an arrival. */
+ if ((type == D_DEPARTURE && IsDeparture(order, station)) ||
+ (type == D_DEPARTURE && show_vehicles_via && IsVia(order, station)) ||
+ (type == D_ARRIVAL && IsArrival(order, station))) {
+ /* If the departure was scheduled to have already begun and has been cancelled, do not show it. */
+ if (start_date < 0 && status == D_CANCELLED) {
+ break;
+ }
+
+ OrderDate *od = new OrderDate();
+ od->order = order;
+ od->v = *v;
+ /* We store the expected date for now, so that vehicles will be shown in order of expected time. */
+ od->expected_date = start_date;
+ od->lateness = (*v)->lateness_counter > 0 ? (*v)->lateness_counter : 0;
+ od->status = status;
+
+ /* If we are early, use the scheduled date as the expected date. We also take lateness to be zero. */
+ if ((*v)->lateness_counter < 0 && !(*v)->current_order.IsType(OT_LOADING)) {
+ od->expected_date -= (*v)->lateness_counter;
+ }
+
+ /* Update least_order if this is the current least order. */
+ if (least_order == NULL) {
+ least_order = od;
+ } else if (least_order->expected_date - least_order->lateness - (type == D_ARRIVAL ? least_order->order->GetWaitTime() : 0) > od->expected_date - od->lateness - (type == D_ARRIVAL ? od->order->GetWaitTime() : 0)) {
+ least_order = od;
+ }
+
+ *(next_orders.Append(1)) = od;
+
+ /* We're done with this vehicle. */
+ break;
+ } else {
+ /* Go to the next order in the list. */
+ if (status != D_CANCELLED) {
+ status = D_TRAVELLING;
+ }
+ order = (order->next == NULL) ? (*v)->GetFirstOrder() : order->next;
+ }
+ }
+ }
+ }
+
+ /* No suitable orders found? Then stop. */
+ if (next_orders.Length() == 0) {
+ return result;
+ }
+
+ /* We now find as many departures as we can. It's a little involved so I'll try to explain each major step. */
+ /* The countdown from 10000 is a safeguard just in case something nasty happens. 10000 seemed large enough. */
+ for(int i = 10000; i > 0; --i) {
+ /* I should probably try to convince you that this loop always terminates regardless of the safeguard. */
+ /* 1. next_orders contains at least one element. */
+ /* 2. The loop terminates if result->Length() exceeds a fixed (for this loop) value, or if the least order's scheduled date is later than max_date. */
+ /* (We ignore the case that the least order's scheduled date has overflown, as it is a relative rather than absolute date.) */
+ /* 3. Every time we loop round, either result->Length() will have increased -OR- we will have increased the expected_date of one of the elements of next_orders. */
+ /* 4. Therefore the loop must eventually terminate. */
+
+ /* least_order is the best candidate for the next departure. */
+
+ /* First, we check if we can stop looking for departures yet. */
+ if (result->Length() >= _settings_client.gui.max_departures ||
+ least_order->expected_date - least_order->lateness > max_date) {
+ break;
+ }
+
+ /* We already know the least order and that it's a suitable departure, so make it into a departure. */
+ Departure *d = new Departure();
+ d->scheduled_date = (DateTicks)_date * DAY_TICKS + least_order->expected_date - least_order->lateness;
+ d->lateness = least_order->lateness;
+ d->status = least_order->status;
+ d->vehicle = least_order->v;
+ d->type = type;
+ d->order = least_order->order;
+
+ /* We'll be going through the order list later, so we need a separate variable for it. */
+ const Order *order = least_order->order;
+
+ if (type == D_DEPARTURE) {
+ /* Computing departures: */
+ /* We want to find out where it will terminate, making a list of the stations it calls at along the way. */
+ /* We only count stations where unloading happens as being called at - i.e. pickup-only stations are ignored. */
+ /* Where the vehicle terminates is defined as the last unique station called at by the vehicle from the current order. */
+
+ /* If the vehicle loops round to the current order without a terminus being found, then it terminates upon reaching its current order again. */
+
+ /* We also determine which station this departure is going via, if any. */
+ /* A departure goes via a station if it is the first station for which the vehicle has an order to go via or non-stop via. */
+ /* Multiple departures on the same journey may go via different stations. That a departure can go via at most one station is intentional. */
+
+ /* We keep track of potential via stations along the way. If we call at a station immediately after going via it, then it is the via station. */
+ StationID candidate_via = INVALID_STATION;
+
+ /* Go through the order list, looping if necessary, to find a terminus. */
+ /* Get the next order, which may be the vehicle's first order. */
+ order = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next;
+ /* We only need to consider each order at most once. */
+ bool found_terminus = false;
+ CallAt c = CallAt((StationID)order->GetDestination(), d->scheduled_date);
+ for (int i = least_order->v->GetNumOrders(); i > 0; --i) {
+ /* If we reach the order at which the departure occurs again, then use the departure station as the terminus. */
+ if (order == least_order->order) {
+ /* If we're not calling anywhere, then skip this departure. */
+ found_terminus = (d->calling_at.Length() > 0);
+ break;
+ }
+
+ /* If the order is a conditional branch, handle it. */
+ if (order->IsType(OT_CONDITIONAL)) {
+ switch(_settings_client.gui.departure_conditionals) {
+ case 0: {
+ /* Give up */
+ break;
+ }
+ case 1: {
+ /* Take the branch */
+ order = least_order->v->GetOrder(order->GetConditionSkipToOrder());
+ if (order == NULL) {
+ break;
+ }
+ continue;
+ }
+ case 2: {
+ /* Do not take the branch */
+ order = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next;
+ continue;
+ }
+ }
+ }
+
+ /* If we reach the original station again, then use it as the terminus. */
+ if (order->GetType() == OT_GOTO_STATION &&
+ (StationID)order->GetDestination() == station &&
+ (order->GetUnloadType() != OUFB_NO_UNLOAD ||
+ _settings_client.gui.departure_show_all_stops) &&
+ order->GetNonStopType() != ONSF_NO_STOP_AT_ANY_STATION &&
+ order->GetNonStopType() != ONSF_NO_STOP_AT_DESTINATION_STATION) {
+ /* If we're not calling anywhere, then skip this departure. */
+ found_terminus = (d->calling_at.Length() > 0);
+ break;
+ }
+
+ /* Check if we're going via this station. */
+ if ((order->GetNonStopType() == ONSF_NO_STOP_AT_ANY_STATION ||
+ order->GetNonStopType() == ONSF_NO_STOP_AT_DESTINATION_STATION) &&
+ order->GetType() == OT_GOTO_STATION &&
+ d->via == INVALID_STATION) {
+ candidate_via = (StationID)order->GetDestination();
+ }
+
+ if (c.scheduled_date != 0 && order->GetTravelTime() != 0) {
+ c.scheduled_date += order->GetTravelTime();
+ } else {
+ c.scheduled_date = 0;
+ }
+
+ c.station = (StationID)order->GetDestination();
+
+ /* We're not interested in this order any further if we're not calling at it. */
+ if ((order->GetUnloadType() == OUFB_NO_UNLOAD &&
+ !_settings_client.gui.departure_show_all_stops) ||
+ (order->GetType() != OT_GOTO_STATION &&
+ order->GetType() != OT_IMPLICIT) ||
+ order->GetNonStopType() == ONSF_NO_STOP_AT_ANY_STATION ||
+ order->GetNonStopType() == ONSF_NO_STOP_AT_DESTINATION_STATION) {
+ c.scheduled_date += order->GetWaitTime();
+ order = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next;
+ continue;
+ }
+
+ /* If this order's station is already in the calling, then the previous called at station is the terminus. */
+ if (d->calling_at.Contains(c)) {
+ found_terminus = true;
+ break;
+ }
+
+ /* If appropriate, add the station to the calling at list and make it the candidate terminus. */
+ if ((order->GetType() == OT_GOTO_STATION ||
+ order->GetType() == OT_IMPLICIT) &&
+ order->GetNonStopType() != ONSF_NO_STOP_AT_ANY_STATION &&
+ order->GetNonStopType() != ONSF_NO_STOP_AT_DESTINATION_STATION) {
+ if (d->via == INVALID_STATION && candidate_via == (StationID)order->GetDestination()) {
+ d->via = (StationID)order->GetDestination();
+ }
+ d->terminus = c;
+ *(d->calling_at.Append(1)) = c;
+ }
+
+ /* If we unload all at this station, then it is the terminus. */
+ if (order->GetType() == OT_GOTO_STATION &&
+ order->GetUnloadType() == OUFB_UNLOAD) {
+ if (d->calling_at.Length() > 0) {
+ found_terminus = true;
+ }
+ break;
+ }
+
+ c.scheduled_date += order->GetWaitTime();
+
+ /* Get the next order, which may be the vehicle's first order. */
+ order = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next;
+ }
+
+ if (found_terminus) {
+ /* Add the departure to the result list. */
+ bool duplicate = false;
+
+ if (_settings_client.gui.departure_merge_identical) {
+ for (uint i = 0; i < result->Length(); ++i) {
+ if (*d == **(result->Get(i))) {
+ duplicate = true;
+ break;
+ }
+ }
+ }
+
+ if (!duplicate) {
+ *(result->Append(1)) = d;
+
+ if (_settings_client.gui.departure_smart_terminus && type == D_DEPARTURE) {
+ for (uint i = 0; i < result->Length()-1; ++i) {
+ Departure *d_first = *(result->Get(i));
+ uint k = d_first->calling_at.Length()-2;
+ for (uint j = d->calling_at.Length(); j > 0; --j) {
+ CallAt c = CallAt(*(d->calling_at.Get(j-1)));
+
+ if (d_first->terminus >= c && d_first->calling_at.Length() >= 2) {
+ d_first->terminus = CallAt(*(d_first->calling_at.Get(k)));
+
+ if (k == 0) break;
+
+ k--;
+ }
+ }
+ }
+ }
+
+ /* If the vehicle is expected to be late, we want to know what time it will arrive rather than depart. */
+ /* This is done because it looked silly to me to have a vehicle not be expected for another few days, yet it be at the same time pulling into the station. */
+ if (d->status != D_ARRIVED &&
+ d->lateness > 0) {
+ d->lateness -= least_order->order->GetWaitTime();
+ }
+ }
+ }
+ } else {
+ /* Computing arrivals: */
+ /* First we need to find the origin of the order. This is somewhat like finding a terminus, but a little more involved since order lists are singly linked. */
+ /* The next stage is simpler. We just need to add all the stations called at on the way to the current station. */
+ /* Again, we define a station as being called at if the vehicle loads from it. */
+
+ /* However, the very first thing we do is use the arrival time as the scheduled time instead of the departure time. */
+ d->scheduled_date -= order->GetWaitTime();
+
+ const Order *candidate_origin = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next;
+ bool found_origin = false;
+
+ while (candidate_origin != least_order->order) {
+ if ((candidate_origin->GetLoadType() != OLFB_NO_LOAD ||
+ _settings_client.gui.departure_show_all_stops) &&
+ (candidate_origin->GetType() == OT_GOTO_STATION ||
+ candidate_origin->GetType() == OT_IMPLICIT) &&
+ candidate_origin->GetDestination() != station) {
+ const Order *o = (candidate_origin->next == NULL) ? least_order->v->GetFirstOrder() : candidate_origin->next;
+ bool found_collision = false;
+
+ /* Check if the candidate origin's destination appears again before the original order or the station does. */
+ while (o != least_order->order) {
+ if (o->GetUnloadType() == OUFB_UNLOAD) {
+ found_collision = true;
+ break;
+ }
+
+ if ((o->GetType() == OT_GOTO_STATION ||
+ o->GetType() == OT_IMPLICIT) &&
+ (o->GetDestination() == candidate_origin->GetDestination() ||
+ o->GetDestination() == station)) {
+ found_collision = true;
+ break;
+ }
+
+ o = (o->next == NULL) ? least_order->v->GetFirstOrder() : o->next;
+ }
+
+ /* If it doesn't, then we have found the origin. */
+ if (!found_collision) {
+ found_origin = true;
+ break;
+ }
+ }
+
+ candidate_origin = (candidate_origin->next == NULL) ? least_order->v->GetFirstOrder() : candidate_origin->next;
+ }
+
+ order = (candidate_origin->next == NULL) ? least_order->v->GetFirstOrder() : candidate_origin->next;
+
+ while (order != least_order->order) {
+ if (order->GetType() == OT_GOTO_STATION &&
+ (order->GetLoadType() != OLFB_NO_LOAD ||
+ _settings_client.gui.departure_show_all_stops)) {
+ *(d->calling_at.Append(1)) = CallAt((StationID)order->GetDestination());
+ }
+
+ order = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next;
+ }
+
+ d->terminus = CallAt((StationID)candidate_origin->GetDestination());
+
+ if (found_origin) {
+ bool duplicate = false;
+
+ if (_settings_client.gui.departure_merge_identical) {
+ for (uint i = 0; i < result->Length(); ++i) {
+ if (*d == **(result->Get(i))) {
+ duplicate = true;
+ break;
+ }
+ }
+ }
+
+ if (!duplicate) {
+ *(result->Append(1)) = d;
+ }
+ }
+ }
+
+ /* Save on pointer dereferences in the coming loop. */
+ order = least_order->order;
+
+ /* Now we find the next suitable order for being a departure for this vehicle. */
+ /* We do this in a similar way to finding the first suitable order for the vehicle. */
+
+ /* Go to the next order so we don't add the current order again. */
+ order = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next;
+ least_order->expected_date += order->GetTravelTime() + order->GetWaitTime();
+
+ /* Go through the order list to find the next candidate departure. */
+ /* We only need to consider each order at most once. */
+ bool found_next_order = false;
+ for (int i = least_order->v->GetNumOrders(); i > 0; --i) {
+ /* If the order is a conditional branch, handle it. */
+ if (order->IsType(OT_CONDITIONAL)) {
+ switch(_settings_client.gui.departure_conditionals) {
+ case 0: {
+ /* Give up */
+ break;
+ }
+ case 1: {
+ /* Take the branch */
+ order = least_order->v->GetOrder(order->GetConditionSkipToOrder());
+ if (order == NULL) {
+ break;
+ }
+
+ least_order->expected_date += order->GetWaitTime();
+
+ continue;
+ }
+ case 2: {
+ /* Do not take the branch */
+ order = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next;
+ least_order->expected_date += order->GetTravelTime() + order->GetWaitTime();
+ continue;
+ }
+ }
+ }
+
+ /* Skip it if it's an automatic order. */
+ if (order->IsType(OT_IMPLICIT)) {
+ order = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next;
+ continue;
+ }
+
+ /* If an order doesn't have a travel time set, then stop. */
+ if (order->GetTravelTime() == 0) {
+ break;
+ }
+
+ /* If the departure is scheduled to be too late, then stop. */
+ if (least_order->expected_date - least_order->lateness > max_date) {
+ break;
+ }
+
+ /* If the order loads from this station (or unloads if we're computing arrivals) and has a wait time set, then it is suitable for being a departure. */
+ if ((type == D_DEPARTURE && IsDeparture(order, station)) ||
+ (type == D_DEPARTURE && show_vehicles_via && IsVia(order, station)) ||
+ (type == D_ARRIVAL && IsArrival(order, station))) {
+ least_order->order = order;
+ found_next_order = true;
+ break;
+ }
+
+ order = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next;
+ least_order->expected_date += order->GetTravelTime() + order->GetWaitTime();
+ }
+
+ /* If we didn't find a suitable order for being a departure, then we can ignore this vehicle from now on. */
+ if (!found_next_order) {
+ /* Make sure we don't try to get departures out of this order. */
+ /* This is cheaper than deleting it from next_orders. */
+ /* If we ever get to a state where _date * DAY_TICKS is close to INT_MAX, then we'll have other problems anyway as departures' scheduled dates will wrap around. */
+ least_order->expected_date = INT32_MAX;
+ }
+
+ /* The vehicle can't possibly have arrived at its next candidate departure yet. */
+ if (least_order->status == D_ARRIVED) {
+ least_order->status = D_TRAVELLING;
+ }
+
+ /* Find the new least order. */
+ for (uint i = 0; i < next_orders.Length(); ++i) {
+ OrderDate *od = *(next_orders.Get(i));
+
+ DateTicks lod = least_order->expected_date - least_order->lateness;
+ DateTicks odd = od->expected_date - od->lateness;
+
+ if (type == D_ARRIVAL) {
+ lod -= least_order->order->GetWaitTime();
+ odd -= od->order->GetWaitTime();
+ }
+
+ if (lod > odd && od->expected_date - od->lateness < max_date) {
+ least_order = od;
+ }
+ }
+ }
+
+ /* Done. Phew! */
+ return result;
+}
diff --git a/src/departures_func.h b/src/departures_func.h
new file mode 100644
index 0000000000..a5fb42bb9f
--- /dev/null
+++ b/src/departures_func.h
@@ -0,0 +1,21 @@
+/* $Id: departures_func.h $ */
+
+/*
+ * This file is part of OpenTTD.
+ * 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.
+ * 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.
+ * 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 .
+ */
+
+/** @file departures_func.h Functions related to departures. */
+
+#ifndef DEPARTURES_FUNC_H
+#define DEPARTURES_FUNC_H
+
+#include "station_base.h"
+#include "core/smallvec_type.hpp"
+#include "departures_type.h"
+
+DepartureList* MakeDepartureList(StationID station, bool show_vehicle_types[4], DepartureType type = D_DEPARTURE, bool show_vehicles_via = false);
+
+#endif /* DEPARTURES_FUNC_H */
diff --git a/src/departures_gui.cpp b/src/departures_gui.cpp
new file mode 100644
index 0000000000..c88326d2f3
--- /dev/null
+++ b/src/departures_gui.cpp
@@ -0,0 +1,856 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * 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.
+ * 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.
+ * 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 .
+ */
+
+/** @file departures_gui.cpp Scheduled departures from a station. */
+
+#include "stdafx.h"
+#include "debug.h"
+#include "gui.h"
+#include "textbuf_gui.h"
+#include "strings_func.h"
+#include "window_func.h"
+#include "vehicle_func.h"
+#include "string_func.h"
+#include "window_gui.h"
+#include "timetable.h"
+#include "vehiclelist.h"
+#include "company_base.h"
+#include "date_func.h"
+#include "departures_gui.h"
+#include "station_base.h"
+#include "vehicle_gui_base.h"
+#include "vehicle_base.h"
+#include "vehicle_gui.h"
+#include "order_base.h"
+#include "settings_type.h"
+#include "core/smallvec_type.hpp"
+#include "date_type.h"
+#include "company_type.h"
+#include "departures_func.h"
+#include "cargotype.h"
+
+#include "table/sprites.h"
+#include "table/strings.h"
+
+static const NWidgetPart _nested_departures_list[] = {
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_CLOSEBOX, COLOUR_GREY),
+ NWidget(WWT_CAPTION, COLOUR_GREY, WID_DB_CAPTION), SetDataTip(STR_DEPARTURES_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
+ NWidget(WWT_SHADEBOX, COLOUR_GREY),
+ NWidget(WWT_STICKYBOX, COLOUR_GREY),
+ EndContainer(),
+
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_MATRIX, COLOUR_GREY, WID_DB_LIST), SetMinimalSize(0, 0), SetFill(1, 0), SetResize(1, 1),
+ NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_DB_SCROLLBAR),
+ EndContainer(),
+
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(0, 12), SetResize(1, 0), SetFill(1, 1), EndContainer(),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_DB_SHOW_ARRS), SetMinimalSize(6, 12), SetFill(0, 1), SetDataTip(STR_DEPARTURES_ARRIVALS, STR_DEPARTURES_ARRIVALS_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_DB_SHOW_DEPS), SetMinimalSize(6, 12), SetFill(0, 1), SetDataTip(STR_DEPARTURES_DEPARTURES, STR_DEPARTURES_DEPARTURES_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_DB_SHOW_VIA), SetMinimalSize(11, 12), SetFill(0, 1), SetDataTip(STR_DEPARTURES_VIA_BUTTON, STR_DEPARTURES_VIA_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_DB_SHOW_TRAINS), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_TRAIN, STR_STATION_VIEW_SCHEDULED_TRAINS_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_DB_SHOW_ROADVEHS), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_LORRY, STR_STATION_VIEW_SCHEDULED_ROAD_VEHICLES_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_DB_SHOW_SHIPS), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_SHIP, STR_STATION_VIEW_SCHEDULED_SHIPS_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_DB_SHOW_PLANES), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_PLANE, STR_STATION_VIEW_SCHEDULED_AIRCRAFT_TOOLTIP),
+ NWidget(WWT_RESIZEBOX, COLOUR_GREY),
+ EndContainer(),
+};
+
+static WindowDesc _departures_desc(
+ WDP_AUTO, NULL, 260, 246,
+ WC_DEPARTURES_BOARD, WC_NONE,
+ 0,
+ _nested_departures_list, lengthof(_nested_departures_list)
+);
+
+static uint cached_date_width = 0; ///< The cached maximum width required to display a date.
+static uint cached_status_width = 0; ///< The cached maximum width required to show the status field.
+static uint cached_date_arrow_width = 0; ///< The cached width of the red/green arrows that may be displayed alongside times.
+static bool cached_date_display_method; ///< Whether the above cached values refers to original (d,m,y) dates or the 24h clock.
+static bool cached_arr_dep_display_method; ///< Whether to show departures and arrivals on a single line.
+
+template
+struct DeparturesWindow : public Window {
+protected:
+ StationID station; ///< The station whose departures we're showing.
+ DepartureList *departures; ///< The current list of departures from this station.
+ DepartureList *arrivals; ///< The current list of arrivals from this station.
+ uint entry_height; ///< The height of an entry in the departures list.
+ uint tick_count; ///< The number of ticks that have elapsed since the window was created. Used for scrolling text.
+ int calc_tick_countdown; ///< The number of ticks to wait until recomputing the departure list. Signed in case it goes below zero.
+ bool show_types[4]; ///< The vehicle types to show in the departure list.
+ bool departure_types[3]; ///< The types of departure to show in the departure list.
+ uint min_width; ///< The minimum width of this window.
+ Scrollbar *vscroll;
+
+ virtual uint GetMinWidth() const;
+ static void RecomputeDateWidth();
+ virtual void DrawDeparturesListItems(const Rect &r) const;
+ void DeleteDeparturesList(DepartureList* list);
+public:
+
+ DeparturesWindow(WindowDesc *desc, WindowNumber window_number) : Window(desc),
+ station(window_number),
+ departures(new DepartureList()),
+ arrivals(new DepartureList()),
+ entry_height(1 + FONT_HEIGHT_NORMAL + 1 + (_settings_client.gui.departure_larger_font ? FONT_HEIGHT_NORMAL : FONT_HEIGHT_SMALL) + 1 + 1),
+ tick_count(0),
+ calc_tick_countdown(0),
+ min_width(400)
+ {
+ this->CreateNestedTree();
+ this->vscroll = this->GetScrollbar(WID_DB_SCROLLBAR);
+ this->FinishInitNested(window_number);
+
+ /* By default, only show departures. */
+ departure_types[0] = true;
+ departure_types[1] = false;
+ departure_types[2] = false;
+ this->LowerWidget(WID_DB_SHOW_DEPS);
+ this->RaiseWidget(WID_DB_SHOW_ARRS);
+ this->RaiseWidget(WID_DB_SHOW_VIA);
+
+ for (uint i = 0; i < 4; ++i) {
+ show_types[i] = true;
+ this->LowerWidget(WID_DB_SHOW_TRAINS + i);
+ }
+
+ if (Twaypoint) {
+ this->GetWidget(WID_DB_CAPTION)->SetDataTip(STR_DEPARTURES_CAPTION_WAYPOINT, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS);
+
+ for (uint i = 0; i < 4; ++i) {
+ this->DisableWidget(WID_DB_SHOW_TRAINS + i);
+ }
+
+ this->DisableWidget(WID_DB_SHOW_ARRS);
+ this->DisableWidget(WID_DB_SHOW_DEPS);
+ this->DisableWidget(WID_DB_SHOW_VIA);
+
+ departure_types[2] = true;
+
+ this->LowerWidget(WID_DB_SHOW_VIA);
+ }
+ }
+
+ virtual ~DeparturesWindow()
+ {
+ this->DeleteDeparturesList(departures);
+ this->DeleteDeparturesList(this->arrivals);
+ }
+
+ virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
+ {
+ switch (widget) {
+ case WID_DB_LIST:
+ resize->height = DeparturesWindow::entry_height;
+ size->height = 2 * resize->height;
+ break;
+ }
+ }
+
+ virtual void SetStringParameters(int widget) const
+ {
+ if (widget == WID_DB_CAPTION) {
+ const Station *st = Station::Get(this->station);
+ SetDParam(0, st->index);
+ }
+ }
+
+ virtual void OnClick(Point pt, int widget, int click_count)
+ {
+ switch (widget) {
+ case WID_DB_SHOW_TRAINS: // Show trains to this station
+ case WID_DB_SHOW_ROADVEHS: // Show road vehicles to this station
+ case WID_DB_SHOW_SHIPS: // Show ships to this station
+ case WID_DB_SHOW_PLANES: // Show aircraft to this station
+ this->show_types[widget - WID_DB_SHOW_TRAINS] = !this->show_types[widget - WID_DB_SHOW_TRAINS];
+ if (this->show_types[widget - WID_DB_SHOW_TRAINS]) {
+ this->LowerWidget(widget);
+ } else {
+ this->RaiseWidget(widget);
+ }
+ /* We need to recompute the departures list. */
+ this->calc_tick_countdown = 0;
+ /* We need to redraw the button that was pressed. */
+ this->SetWidgetDirty(widget);
+ break;
+
+ case WID_DB_SHOW_DEPS:
+ case WID_DB_SHOW_ARRS:
+ if (_settings_client.gui.departure_show_both) break;
+ /* FALL THROUGH */
+
+ case WID_DB_SHOW_VIA:
+
+ this->departure_types[widget - WID_DB_SHOW_DEPS] = !this->departure_types[widget - WID_DB_SHOW_DEPS];
+ if (this->departure_types[widget - WID_DB_SHOW_DEPS]) {
+ this->LowerWidget(widget);
+ } else {
+ this->RaiseWidget(widget);
+ }
+
+ if (!this->departure_types[0]) {
+ this->RaiseWidget(WID_DB_SHOW_VIA);
+ this->DisableWidget(WID_DB_SHOW_VIA);
+ } else {
+ this->EnableWidget(WID_DB_SHOW_VIA);
+
+ if (this->departure_types[2]) {
+ this->LowerWidget(WID_DB_SHOW_VIA);
+ }
+ }
+ /* We need to recompute the departures list. */
+ this->calc_tick_countdown = 0;
+ /* We need to redraw the button that was pressed. */
+ this->SetWidgetDirty(widget);
+ break;
+
+ case WID_DB_LIST: // Matrix to show departures
+ /* We need to find the departure corresponding to where the user clicked. */
+ uint32 id_v = (pt.y - this->GetWidget(WID_DB_LIST)->pos_y) / this->entry_height;
+
+ if (id_v >= this->vscroll->GetCapacity()) return; // click out of bounds
+
+ id_v += this->vscroll->GetPosition();
+
+ if (id_v >= (this->departures->Length() + this->arrivals->Length())) return; // click out of list bound
+
+ uint departure = 0;
+ uint arrival = 0;
+
+ /* Draw each departure. */
+ for (uint i = 0; i <= id_v; ++i) {
+ const Departure *d;
+
+ if (arrival == this->arrivals->Length()) {
+ d = (*(this->departures))[departure++];
+ } else if (departure == this->departures->Length()) {
+ d = (*(this->arrivals))[arrival++];
+ } else {
+ d = (*(this->departures))[departure];
+ const Departure *a = (*(this->arrivals))[arrival];
+
+ if (a->scheduled_date < d->scheduled_date) {
+ d = a;
+ arrival++;
+ } else {
+ departure++;
+ }
+ }
+
+ if (i == id_v) {
+ ShowVehicleViewWindow(d->vehicle);
+ break;
+ }
+ }
+
+ break;
+ }
+ }
+
+ virtual void OnTick()
+ {
+ if (_pause_mode == PM_UNPAUSED) {
+ this->tick_count += 1;
+ this->calc_tick_countdown -= 1;
+ }
+
+ /* Recompute the minimum date display width if the cached one is no longer valid. */
+ if (cached_date_width == 0 ||
+ _settings_client.gui.time_in_minutes != cached_date_display_method ||
+ _settings_client.gui.departure_show_both != cached_arr_dep_display_method) {
+ this->RecomputeDateWidth();
+ }
+
+ /* We need to redraw the scrolling text in its new position. */
+ this->SetWidgetDirty(WID_DB_LIST);
+
+ /* Recompute the list of departures if we're due to. */
+ if (this->calc_tick_countdown <= 0) {
+ this->calc_tick_countdown = _settings_client.gui.departure_calc_frequency;
+ this->DeleteDeparturesList(this->departures);
+ this->DeleteDeparturesList(this->arrivals);
+ this->departures = (this->departure_types[0] ? MakeDepartureList(this->station, this->show_types, D_DEPARTURE, Twaypoint || this->departure_types[2]) : new DepartureList());
+ this->arrivals = (this->departure_types[1] && !_settings_client.gui.departure_show_both ? MakeDepartureList(this->station, this->show_types, D_ARRIVAL ) : new DepartureList());
+ this->SetWidgetDirty(WID_DB_LIST);
+ }
+
+ uint new_width = this->GetMinWidth();
+
+ if (new_width != this->min_width) {
+ NWidgetCore *n = this->GetWidget(WID_DB_LIST);
+ n->SetMinimalSize(new_width, 0);
+ this->ReInit();
+ this->min_width = new_width;
+ }
+
+ uint new_height = 1 + FONT_HEIGHT_NORMAL + 1 + (_settings_client.gui.departure_larger_font ? FONT_HEIGHT_NORMAL : FONT_HEIGHT_SMALL) + 1 + 1;
+
+ if (new_height != this->entry_height) {
+ this->entry_height = new_height;
+ this->SetWidgetDirty(WID_DB_LIST);
+ this->ReInit();
+ }
+ }
+
+ virtual void OnPaint()
+ {
+ if (Twaypoint || _settings_client.gui.departure_show_both) {
+ this->DisableWidget(WID_DB_SHOW_ARRS);
+ this->DisableWidget(WID_DB_SHOW_DEPS);
+ } else {
+ this->EnableWidget(WID_DB_SHOW_ARRS);
+ this->EnableWidget(WID_DB_SHOW_DEPS);
+ }
+
+ this->vscroll->SetCount(min(_settings_client.gui.max_departures, this->departures->Length() + this->arrivals->Length()));
+ this->DrawWidgets();
+ }
+
+ virtual void DrawWidget(const Rect &r, int widget) const
+ {
+ switch (widget) {
+ case WID_DB_LIST:
+ this->DrawDeparturesListItems(r);
+ break;
+ }
+ }
+
+ virtual void OnResize()
+ {
+ this->vscroll->SetCapacityFromWidget(this, WID_DB_LIST);
+ this->GetWidget(WID_DB_LIST)->widget_data = (this->vscroll->GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START);
+ }
+};
+
+/**
+ * Shows a window of scheduled departures for a station.
+ * @param station the station to show a departures window for
+ */
+void ShowStationDepartures(StationID station)
+{
+ AllocateWindowDescFront >(&_departures_desc, station);
+}
+
+/**
+ * Shows a window of scheduled departures for a station.
+ * @param station the station to show a departures window for
+ */
+void ShowWaypointDepartures(StationID waypoint)
+{
+ AllocateWindowDescFront >(&_departures_desc, waypoint);
+}
+
+template
+void DeparturesWindow::RecomputeDateWidth()
+{
+ cached_date_width = 0;
+ cached_status_width = 0;
+ cached_date_display_method = _settings_client.gui.time_in_minutes;
+ cached_arr_dep_display_method = _settings_client.gui.departure_show_both;
+
+ cached_status_width = max((GetStringBoundingBox(STR_DEPARTURES_ON_TIME)).width, cached_status_width);
+ cached_status_width = max((GetStringBoundingBox(STR_DEPARTURES_DELAYED)).width, cached_status_width);
+ cached_status_width = max((GetStringBoundingBox(STR_DEPARTURES_CANCELLED)).width, cached_status_width);
+
+ uint interval = cached_date_display_method ? _settings_client.gui.ticks_per_minute : DAY_TICKS;
+ uint count = cached_date_display_method ? 24*60 : 365;
+
+ for (uint i = 0; i < count; ++i) {
+ SetDParam(0, INT_MAX - (i*interval));
+ SetDParam(1, INT_MAX - (i*interval));
+ cached_date_width = max(GetStringBoundingBox(cached_arr_dep_display_method ? STR_DEPARTURES_TIME_BOTH : STR_DEPARTURES_TIME_DEP).width, cached_date_width);
+ cached_status_width = max((GetStringBoundingBox(STR_DEPARTURES_EXPECTED)).width, cached_status_width);
+ }
+
+ SetDParam(0, 0);
+ cached_date_arrow_width = GetStringBoundingBox(STR_DEPARTURES_TIME_DEP).width - GetStringBoundingBox(STR_DEPARTURES_TIME).width;
+
+ if (!_settings_client.gui.departure_show_both) {
+ cached_date_width -= cached_date_arrow_width;
+ }
+}
+
+template
+uint DeparturesWindow::GetMinWidth() const
+{
+ uint result = 0;
+
+ /* Time */
+ result = cached_date_width;
+
+ /* Vehicle type icon */
+ result += _settings_client.gui.departure_show_vehicle_type ? (GetStringBoundingBox(STR_DEPARTURES_TYPE_PLANE)).width : 0;
+
+ /* Status */
+ result += cached_status_width;
+
+ /* Find the maximum company name width. */
+ int toc_width = 0;
+
+ /* Find the maximum company name width. */
+ int group_width = 0;
+
+ /* Find the maximum vehicle name width. */
+ int veh_width = 0;
+
+ if (_settings_client.gui.departure_show_vehicle || _settings_client.gui.departure_show_company || _settings_client.gui.departure_show_group) {
+ for (uint i = 0; i < 4; ++i) {
+ VehicleList vehicles;
+
+ /* MAX_COMPANIES is probably the wrong thing to put here, but it works. GenerateVehicleSortList doesn't check the company when the type of list is VL_STATION_LIST (r20801). */
+ if (!GenerateVehicleSortList(&vehicles, VehicleListIdentifier(VL_STATION_LIST, (VehicleType)(VEH_TRAIN + i), MAX_COMPANIES, station).Pack())) {
+ /* Something went wrong: panic! */
+ continue;
+ }
+
+ for (const Vehicle **v = vehicles.Begin(); v != vehicles.End(); v++) {
+ SetDParam(0, (uint64)((*v)->index));
+ int width = (GetStringBoundingBox(STR_DEPARTURES_VEH)).width;
+ if (_settings_client.gui.departure_show_vehicle && width > veh_width) veh_width = width;
+
+ if ((*v)->group_id != INVALID_GROUP && (*v)->group_id != DEFAULT_GROUP) {
+ SetDParam(0, (uint64)((*v)->group_id));
+ width = (GetStringBoundingBox(STR_DEPARTURES_GROUP)).width;
+ if (_settings_client.gui.departure_show_group && width > group_width) group_width = width;
+ }
+
+ SetDParam(0, (uint64)((*v)->owner));
+ width = (GetStringBoundingBox(STR_DEPARTURES_TOC)).width;
+ if (_settings_client.gui.departure_show_company && width > toc_width) toc_width = width;
+ }
+ }
+ }
+
+ result += toc_width + veh_width + group_width;
+
+ return result + 140;
+}
+
+/**
+ * Deletes this window's departure list.
+ */
+template
+void DeparturesWindow::DeleteDeparturesList(DepartureList *list)
+{
+ /* SmallVector uses free rather than delete on its contents (which doesn't invoke the destructor), so we need to delete each departure manually. */
+ for (uint i = 0; i < list->Length(); ++i) {
+ Departure **d = list->Get(i);
+ delete *d;
+ /* Make sure a double free doesn't happen. */
+ *d = NULL;
+ }
+ list->Reset();
+ delete list;
+ list = NULL;
+}
+
+/**
+ * Draws a list of departures.
+ */
+template
+void DeparturesWindow::DrawDeparturesListItems(const Rect &r) const
+{
+ int left = r.left + WD_MATRIX_LEFT;
+ int right = r.right - WD_MATRIX_RIGHT;
+
+ bool rtl = _current_text_dir == TD_RTL;
+ bool ltr = !rtl;
+
+ int text_offset = WD_FRAMERECT_RIGHT;
+ int text_left = left + (rtl ? 0 : text_offset);
+ int text_right = right - (rtl ? text_offset : 0);
+
+ int y = r.top + 1;
+ uint max_departures = min(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), this->departures->Length() + this->arrivals->Length());
+
+ if (max_departures > _settings_client.gui.max_departures) {
+ max_departures = _settings_client.gui.max_departures;
+ }
+
+ byte small_font_size = _settings_client.gui.departure_larger_font ? FONT_HEIGHT_NORMAL : FONT_HEIGHT_SMALL;
+
+ /* Draw the black background. */
+ GfxFillRect(r.left + 1, r.top, r.right - 1, r.bottom, PC_BLACK);
+
+ /* Nothing selected? Then display the information text. */
+ bool none_selected[2] = {true, true};
+ for (uint i = 0; i < 4; ++i)
+ {
+ if (this->show_types[i]) {
+ none_selected[0] = false;
+ break;
+ }
+ }
+
+ for (uint i = 0; i < 2; ++i)
+ {
+ if (this->departure_types[i]) {
+ none_selected[1] = false;
+ break;
+ }
+ }
+
+ if (none_selected[0] || none_selected[1]) {
+ DrawString(text_left, text_right, y + 1, STR_DEPARTURES_NONE_SELECTED);
+ return;
+ }
+
+ /* No scheduled departures? Then display the information text. */
+ if (max_departures == 0) {
+ DrawString(text_left, text_right, y + 1, STR_DEPARTURES_EMPTY);
+ return;
+ }
+
+ /* Find the maximum possible width of the departure time and "Expt