diff options
Diffstat (limited to 'src/departures_gui.cpp')
-rw-r--r-- | src/departures_gui.cpp | 856 |
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; + } +} |