summaryrefslogtreecommitdiff
path: root/src/departures_gui.cpp
diff options
context:
space:
mode:
authorErich Eckner <git@eckner.net>2018-10-30 11:13:12 +0100
committerErich Eckner <git@eckner.net>2018-10-30 15:28:03 +0100
commita34d095259409cf1454d9777deabbc00bcdb9407 (patch)
tree9e7c37ed33f5bd6b3c2f41cf248dcb656b2a4afa /src/departures_gui.cpp
parent6647cb917963c4e0d6d633b7a92af78167050893 (diff)
downloadopenttd-a34d095259409cf1454d9777deabbc00bcdb9407.tar.xz
underground patch appliedunderground-plus-others-original
Diffstat (limited to 'src/departures_gui.cpp')
-rw-r--r--src/departures_gui.cpp856
1 files changed, 856 insertions, 0 deletions
diff --git a/src/departures_gui.cpp b/src/departures_gui.cpp
new file mode 100644
index 000000000..6ccc29f89
--- /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 <http://www.gnu.org/licenses/>.
+ */
+
+/** @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, 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<bool Twaypoint = false>
+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(const WindowDesc *desc, WindowNumber window_number) : Window(),
+ 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(desc);
+ this->vscroll = this->GetScrollbar(WID_DB_SCROLLBAR);
+ this->FinishInitNested(desc, 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<NWidgetCore>(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<NWidgetBase>(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<NWidgetCore>(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<NWidgetCore>(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<DeparturesWindow<> >(&_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<DeparturesWindow<true> >(&_departures_desc, waypoint);
+}
+
+template<bool Twaypoint>
+void DeparturesWindow<Twaypoint>::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<bool Twaypoint>
+uint DeparturesWindow<Twaypoint>::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<bool Twaypoint>
+void DeparturesWindow<Twaypoint>::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<bool Twaypoint>
+void DeparturesWindow<Twaypoint>::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 <time>" fields. */
+ int time_width = cached_date_width;
+
+ if (!_settings_client.gui.departure_show_both) {
+ time_width += (departure_types[0] && departure_types[1] ? cached_date_arrow_width : 0);
+ }
+
+ /* Vehicle type icon */
+ int type_width = _settings_client.gui.departure_show_vehicle_type ? (GetStringBoundingBox(STR_DEPARTURES_TYPE_PLANE)).width : 0;
+
+ /* Find the maximum width of the status field */
+ int status_width = cached_status_width;
+
+ /* Find the width of the "Calling at:" field. */
+ int calling_at_width = (GetStringBoundingBox(_settings_client.gui.departure_larger_font ? STR_DEPARTURES_CALLING_AT_LARGE : STR_DEPARTURES_CALLING_AT)).width;
+
+ /* Find the maximum company name width. */
+ int toc_width = 0;
+
+ /* Find the maximum group 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;
+ }
+ }
+ }
+
+ uint departure = 0;
+ uint arrival = 0;
+
+ /* Draw each departure. */
+ for (uint i = 0; i < max_departures; ++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 < this->vscroll->GetPosition()) {
+ continue;
+ }
+
+ /* If for some reason the departure is too far in the future or is at a negative time, skip it. */
+ if ((d->scheduled_date / DAY_TICKS) > (_date + _settings_client.gui.max_departure_time) ||
+ d->scheduled_date < 0) {
+ continue;
+ }
+
+ if (d->terminus == INVALID_STATION) continue;
+
+ StringID time_str = (departure_types[0] && departure_types[1]) ? (d->type == D_DEPARTURE ? STR_DEPARTURES_TIME_DEP : STR_DEPARTURES_TIME_ARR) : STR_DEPARTURES_TIME;
+
+ if (_settings_client.gui.departure_show_both) time_str = STR_DEPARTURES_TIME_BOTH;
+
+ /* Time */
+ SetDParam(0, d->scheduled_date);
+ SetDParam(1, d->scheduled_date - d->order->wait_time);
+ ltr ? DrawString( text_left, text_left + time_width, y + 1, time_str)
+ : DrawString(text_right - time_width, text_right, y + 1, time_str);
+
+ /* Vehicle type icon, with thanks to sph */
+ if (_settings_client.gui.departure_show_vehicle_type) {
+ StringID type = STR_DEPARTURES_TYPE_TRAIN;
+ int offset = (_settings_client.gui.departure_show_vehicle_color ? 1 : 0);
+
+ switch (d->vehicle->type) {
+ case VEH_TRAIN:
+ type = STR_DEPARTURES_TYPE_TRAIN;
+ break;
+ case VEH_ROAD:
+ type = IsCargoInClass(d->vehicle->cargo_type, CC_PASSENGERS) ? STR_DEPARTURES_TYPE_BUS : STR_DEPARTURES_TYPE_LORRY;
+ break;
+ case VEH_SHIP:
+ type = STR_DEPARTURES_TYPE_SHIP;
+ break;
+ case VEH_AIRCRAFT:
+ type = STR_DEPARTURES_TYPE_PLANE;
+ break;
+ default:
+ break;
+ }
+
+ type += offset;
+
+ DrawString(text_left + time_width + 3, text_left + time_width + type_width + 3, y, type);
+ }
+
+ /* The icons to show with the destination and via stations. */
+ StringID icon = STR_DEPARTURES_STATION_NONE;
+ StringID icon_via = STR_DEPARTURES_STATION_NONE;
+
+ if (_settings_client.gui.departure_destination_type) {
+ Station *t = Station::Get(d->terminus.station);
+
+ if (t->facilities & FACIL_DOCK &&
+ t->facilities & FACIL_AIRPORT &&
+ d->vehicle->type != VEH_SHIP &&
+ d->vehicle->type != VEH_AIRCRAFT) {
+ icon = STR_DEPARTURES_STATION_PORTAIRPORT;
+ } else if (t->facilities & FACIL_DOCK &&
+ d->vehicle->type != VEH_SHIP) {
+ icon = STR_DEPARTURES_STATION_PORT;
+ } else if (t->facilities & FACIL_AIRPORT &&
+ d->vehicle->type != VEH_AIRCRAFT) {
+ icon = STR_DEPARTURES_STATION_AIRPORT;
+ }
+ }
+
+ if (_settings_client.gui.departure_destination_type && d->via != INVALID_STATION) {
+ Station *t = Station::Get(d->via);
+
+ if (t->facilities & FACIL_DOCK &&
+ t->facilities & FACIL_AIRPORT &&
+ d->vehicle->type != VEH_SHIP &&
+ d->vehicle->type != VEH_AIRCRAFT) {
+ icon_via = STR_DEPARTURES_STATION_PORTAIRPORT;
+ } else if (t->facilities & FACIL_DOCK &&
+ d->vehicle->type != VEH_SHIP) {
+ icon_via = STR_DEPARTURES_STATION_PORT;
+ } else if (t->facilities & FACIL_AIRPORT &&
+ d->vehicle->type != VEH_AIRCRAFT) {
+ icon_via = STR_DEPARTURES_STATION_AIRPORT;
+ }
+ }
+
+ /* Destination */
+ if (d->via == INVALID_STATION) {
+ /* Only show the terminus. */
+ SetDParam(0, d->terminus.station);
+ SetDParam(1, icon);
+ ltr ? DrawString( text_left + time_width + type_width + 6, text_right - status_width - (toc_width + veh_width + group_width + 2) - 2, y + 1, STR_DEPARTURES_TERMINUS)
+ : DrawString(text_left + status_width + (toc_width + veh_width + group_width + 2) + 2, text_right - time_width - type_width - 6, y + 1, STR_DEPARTURES_TERMINUS);
+ } else {
+ /* Show the terminus and the via station. */
+ SetDParam(0, d->terminus.station);
+ SetDParam(1, icon);
+ SetDParam(2, d->via);
+ SetDParam(3, icon_via);
+ int text_width = (GetStringBoundingBox(STR_DEPARTURES_TERMINUS_VIA_STATION)).width;
+
+ if (text_width < text_right - status_width - (toc_width + veh_width + group_width + 2) - 2 - (text_left + time_width + type_width + 6)) {
+ /* They will both fit, so show them both. */
+ SetDParam(0, d->terminus.station);
+ SetDParam(1, icon);
+ SetDParam(2, d->via);
+ SetDParam(3, icon_via);
+ ltr ? DrawString( text_left + time_width + type_width + 6, text_right - status_width - (toc_width + veh_width + group_width + 2) - 2, y + 1, STR_DEPARTURES_TERMINUS_VIA_STATION)
+ : DrawString(text_left + status_width + (toc_width + veh_width + group_width + 2) + 2, text_right - time_width - type_width - 6, y + 1, STR_DEPARTURES_TERMINUS_VIA_STATION);
+ } else {
+ /* They won't both fit, so switch between showing the terminus and the via station approximately every 4 seconds. */
+ if (this->tick_count & (1 << 7)) {
+ SetDParam(0, d->via);
+ SetDParam(1, icon_via);
+ ltr ? DrawString( text_left + time_width + type_width + 6, text_right - status_width - (toc_width + veh_width + group_width + 2) - 2, y + 1, STR_DEPARTURES_VIA)
+ : DrawString(text_left + status_width + (toc_width + veh_width + group_width + 2) + 2, text_right - time_width - type_width - 6, y + 1, STR_DEPARTURES_VIA);
+ } else {
+ SetDParam(0, d->terminus.station);
+ SetDParam(1, icon);
+ ltr ? DrawString( text_left + time_width + type_width + 6, text_right - status_width - (toc_width + veh_width + group_width + 2) - 2, y + 1, STR_DEPARTURES_TERMINUS_VIA)
+ : DrawString(text_left + status_width + (toc_width + veh_width + group_width + 2) + 2, text_right - time_width - type_width - 6, y + 1, STR_DEPARTURES_TERMINUS_VIA);
+ }
+ }
+ }
+
+ /* Status */
+ {
+ int status_left = ltr ? text_right - status_width - 2 - (toc_width + veh_width + group_width + 2) : text_left + (toc_width + veh_width + group_width + 2) + 2;
+ int status_right = ltr ? text_right - (toc_width + veh_width + group_width + 2) + 2 : text_left + status_width + 2 + (toc_width + veh_width + group_width + 2);
+
+ if (d->status == D_ARRIVED) {
+ /* The vehicle has arrived. */
+ DrawString(status_left, status_right, y + 1, STR_DEPARTURES_ARRIVED);
+ } else if(d->status == D_CANCELLED) {
+ /* The vehicle has been cancelled. */
+ DrawString(status_left, status_right, y + 1, STR_DEPARTURES_CANCELLED);
+ } else{
+ if (d->lateness <= DAY_TICKS && d->scheduled_date > ((_date * DAY_TICKS) + _date_fract)) {
+ /* We have no evidence that the vehicle is late, so assume it is on time. */
+ DrawString(status_left, status_right, y + 1, STR_DEPARTURES_ON_TIME);
+ } else {
+ if ((d->scheduled_date + d->lateness) < ((_date * DAY_TICKS) + _date_fract)) {
+ /* The vehicle was expected to have arrived by now, even if we knew it was going to be late. */
+ /* We assume that the train stays at least a day at a station so it won't accidentally be marked as delayed for a fraction of a day. */
+ DrawString(status_left, status_right, y + 1, STR_DEPARTURES_DELAYED);
+ } else {
+ /* The vehicle is expected to be late and is not yet due to arrive. */
+ SetDParam(0, d->scheduled_date + d->lateness);
+ DrawString(status_left, status_right, y + 1, STR_DEPARTURES_EXPECTED);
+ }
+ }
+ }
+ }
+
+ /* Vehicle name */
+
+ if (_settings_client.gui.departure_show_vehicle) {
+ SetDParam(0, (uint64)(d->vehicle->index));
+ ltr ? DrawString(text_right - (toc_width + veh_width + group_width + 2), text_right - toc_width - group_width - 2, y + 1, STR_DEPARTURES_VEH)
+ : DrawString( text_left + toc_width + group_width + 2, text_left + (toc_width + veh_width + group_width + 2), y + 1, STR_DEPARTURES_VEH);
+ }
+
+ /* Group name */
+
+ if (_settings_client.gui.departure_show_group && d->vehicle->group_id != INVALID_GROUP && d->vehicle->group_id != DEFAULT_GROUP) {
+ SetDParam(0, (uint64)(d->vehicle->group_id));
+ ltr ? DrawString(text_right - (toc_width + group_width + 2), text_right - toc_width - 2, y + 1, STR_DEPARTURES_GROUP)
+ : DrawString( text_left + toc_width + 2, text_left + (toc_width + group_width + 2), y + 1, STR_DEPARTURES_GROUP);
+ }
+
+ /* Operating company */
+ if (_settings_client.gui.departure_show_company) {
+ SetDParam(0, (uint64)(d->vehicle->owner));
+ ltr ? DrawString(text_right - toc_width, text_right, y + 1, STR_DEPARTURES_TOC, TC_FROMSTRING, SA_RIGHT)
+ : DrawString( text_left, text_left + toc_width, y + 1, STR_DEPARTURES_TOC, TC_FROMSTRING, SA_LEFT);
+ }
+
+ int bottom_y = y + this->entry_height - small_font_size - (_settings_client.gui.departure_larger_font ? 1 : 3);
+
+ /* Calling at */
+ ltr ? DrawString( text_left, text_left + calling_at_width, bottom_y, _settings_client.gui.departure_larger_font ? STR_DEPARTURES_CALLING_AT_LARGE : STR_DEPARTURES_CALLING_AT)
+ : DrawString(text_right - calling_at_width, text_right, bottom_y, _settings_client.gui.departure_larger_font ? STR_DEPARTURES_CALLING_AT_LARGE : STR_DEPARTURES_CALLING_AT);
+
+ /* List of stations */
+ /* RTL languages can be handled in the language file, e.g. by having the following: */
+ /* STR_DEPARTURES_CALLING_AT_STATION :{STATION}, {RAW_STRING} */
+ /* STR_DEPARTURES_CALLING_AT_LAST_STATION :{STATION} & {RAW_STRING}*/
+ char buffer[512], scratch[512];
+
+ if (d->calling_at.Length() != 0) {
+ SetDParam(0, (uint64)(*d->calling_at.Get(0)).station);
+ GetString(scratch, STR_DEPARTURES_CALLING_AT_FIRST_STATION, lastof(scratch));
+
+ StationID continuesTo = INVALID_STATION;
+
+ if (d->calling_at.Get(0)->station == d->terminus.station && d->calling_at.Length() > 1) {
+ continuesTo = d->calling_at.Get(d->calling_at.Length() - 1)->station;
+ } else if (d->calling_at.Length() > 1) {
+ /* There's more than one stop. */
+
+ uint i;
+ /* For all but the last station, write out ", <station>". */
+ for (i = 1; i < d->calling_at.Length() - 1; ++i) {
+ StationID s = d->calling_at.Get(i)->station;
+ if (s == d->terminus.station) {
+ continuesTo = d->calling_at.Get(d->calling_at.Length() - 1)->station;
+ break;
+ }
+ SetDParam(0, (uint64)scratch);
+ SetDParam(1, (uint64)s);
+ GetString(buffer, STR_DEPARTURES_CALLING_AT_STATION, lastof(buffer));
+ strncpy(scratch, buffer, sizeof(scratch));
+ }
+
+ /* Finally, finish off with " and <station>". */
+ SetDParam(0, (uint64)scratch);
+ SetDParam(1, (uint64)d->calling_at.Get(i)->station);
+ GetString(buffer, STR_DEPARTURES_CALLING_AT_LAST_STATION, lastof(buffer));
+ strncpy(scratch, buffer, sizeof(scratch));
+ }
+
+ SetDParam(0, (uint64)scratch);
+ StringID string;
+ if (continuesTo == INVALID_STATION) {
+ string = _settings_client.gui.departure_larger_font ? STR_DEPARTURES_CALLING_AT_LIST_LARGE : STR_DEPARTURES_CALLING_AT_LIST;
+ } else {
+ SetDParam(1, continuesTo);
+ string = _settings_client.gui.departure_larger_font ? STR_DEPARTURES_CALLING_AT_LIST_SMART_TERMINUS_LARGE : STR_DEPARTURES_CALLING_AT_LIST_SMART_TERMINUS;
+ }
+ GetString(buffer, string, lastof(buffer));
+ } else {
+ buffer[0] = 0;
+ //SetDParam(0, d->terminus);
+ //GetString(scratch, STR_DEPARTURES_CALLING_AT_FIRST_STATION, lastof(scratch));
+ }
+
+ int list_width = (GetStringBoundingBox(buffer, _settings_client.gui.departure_larger_font ? FS_NORMAL : FS_SMALL)).width;
+
+ /* Draw the whole list if it will fit. Otherwise scroll it. */
+ if (list_width < text_right - (text_left + calling_at_width + 2)) {
+ ltr ? DrawString(text_left + calling_at_width + 2, text_right, bottom_y, buffer)
+ : DrawString( text_left, text_right - calling_at_width - 2, bottom_y, buffer);
+ } else {
+ DrawPixelInfo tmp_dpi;
+ if (ltr
+ ? !FillDrawPixelInfo(&tmp_dpi, text_left + calling_at_width + 2, bottom_y, text_right - (text_left + calling_at_width + 2), small_font_size + 3)
+ : !FillDrawPixelInfo(&tmp_dpi, text_left , bottom_y, text_right - (text_left + calling_at_width + 2), small_font_size + 3)) {
+ y += this->entry_height;
+ continue;
+ }
+ DrawPixelInfo *old_dpi = _cur_dpi;
+ _cur_dpi = &tmp_dpi;
+
+ /* The scrolling text starts out of view at the right of the screen and finishes when it is out of view at the left of the screen. */
+ int pos = ltr
+ ? text_right - (this->tick_count % (list_width + text_right - text_left))
+ : text_left + (this->tick_count % (list_width + text_right - text_left));
+
+ ltr ? DrawString( pos, INT16_MAX, 0, buffer, TC_FROMSTRING, SA_LEFT | SA_FORCE)
+ : DrawString(-INT16_MAX, pos, 0, buffer, TC_FROMSTRING, SA_RIGHT | SA_FORCE);
+
+ _cur_dpi = old_dpi;
+ }
+
+ y += this->entry_height;
+ }
+}