departures_gui.cpp

Go to the documentation of this file.
00001 /* $Id$ */
00002 
00003 /*
00004  * This file is part of OpenTTD.
00005  * 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.
00006  * 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.
00007  * 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/>.
00008  */
00009 
00012 #include "stdafx.h"
00013 #include "debug.h"
00014 #include "gui.h"
00015 #include "textbuf_gui.h"
00016 #include "strings_func.h"
00017 #include "window_func.h"
00018 #include "vehicle_func.h"
00019 #include "string_func.h"
00020 #include "window_gui.h"
00021 #include "timetable.h"
00022 #include "vehiclelist.h"
00023 #include "company_base.h"
00024 #include "date_func.h"
00025 #include "departures_gui.h"
00026 #include "station_base.h"
00027 #include "vehicle_gui_base.h"
00028 #include "vehicle_base.h"
00029 #include "vehicle_gui.h"
00030 #include "order_base.h"
00031 #include "settings_type.h"
00032 #include "core/smallvec_type.hpp"
00033 #include "date_type.h"
00034 #include "company_type.h"
00035 #include "departures_func.h"
00036 #include "cargotype.h"
00037 
00038 #include "table/sprites.h"
00039 #include "table/strings.h"
00040 
00041 static const NWidgetPart _nested_departures_list[] = {
00042   NWidget(NWID_HORIZONTAL),
00043     NWidget(WWT_CLOSEBOX, COLOUR_GREY),
00044     NWidget(WWT_CAPTION, COLOUR_GREY, DW_WIDGET_CAPTION), SetDataTip(STR_DEPARTURES_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
00045     NWidget(WWT_SHADEBOX, COLOUR_GREY),
00046     NWidget(WWT_STICKYBOX, COLOUR_GREY),
00047   EndContainer(),
00048 
00049   NWidget(NWID_HORIZONTAL),
00050     NWidget(WWT_MATRIX, COLOUR_GREY, DW_WIDGET_LIST), SetMinimalSize(0, 0), SetFill(1, 0), SetResize(1, 1),
00051     NWidget(NWID_VSCROLLBAR, COLOUR_GREY, DW_WIDGET_SCROLLBAR),
00052   EndContainer(),
00053 
00054   NWidget(NWID_HORIZONTAL),
00055     NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(0, 12), SetResize(1, 0), SetFill(1, 1), EndContainer(),
00056     NWidget(WWT_TEXTBTN, COLOUR_GREY, DW_SHOW_ARRS), SetMinimalSize(6, 12), SetFill(0, 1), SetDataTip(STR_DEPARTURES_ARRIVALS, STR_DEPARTURES_ARRIVALS_TOOLTIP),
00057     NWidget(WWT_TEXTBTN, COLOUR_GREY, DW_SHOW_DEPS), SetMinimalSize(6, 12), SetFill(0, 1), SetDataTip(STR_DEPARTURES_DEPARTURES, STR_DEPARTURES_DEPARTURES_TOOLTIP),
00058     NWidget(WWT_TEXTBTN, COLOUR_GREY, DW_SHOW_VIA), SetMinimalSize(11, 12), SetFill(0, 1), SetDataTip(STR_DEPARTURES_VIA_BUTTON, STR_DEPARTURES_VIA_TOOLTIP),
00059     NWidget(WWT_TEXTBTN, COLOUR_GREY, DW_SHOW_TRAINS), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_TRAIN, STR_STATION_VIEW_SCHEDULED_TRAINS_TOOLTIP),
00060     NWidget(WWT_TEXTBTN, COLOUR_GREY, DW_SHOW_ROADVEHS), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_LORRY, STR_STATION_VIEW_SCHEDULED_ROAD_VEHICLES_TOOLTIP),
00061     NWidget(WWT_TEXTBTN, COLOUR_GREY, DW_SHOW_SHIPS), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_SHIP, STR_STATION_VIEW_SCHEDULED_SHIPS_TOOLTIP),
00062     NWidget(WWT_TEXTBTN, COLOUR_GREY, DW_SHOW_PLANES),  SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_PLANE, STR_STATION_VIEW_SCHEDULED_AIRCRAFT_TOOLTIP),
00063     NWidget(WWT_RESIZEBOX, COLOUR_GREY),
00064   EndContainer(),
00065 };
00066 
00067 static WindowDesc _departures_desc(
00068   WDP_AUTO, 260, 246,
00069   WC_DEPARTURES_BOARD, WC_NONE,
00070   WDF_UNCLICK_BUTTONS,
00071   _nested_departures_list, lengthof(_nested_departures_list)
00072 );
00073 
00074 static uint cached_date_width = 0;         
00075 static uint cached_status_width = 0;       
00076 static uint cached_date_arrow_width = 0;   
00077 static bool cached_date_display_method;    
00078 static bool cached_arr_dep_display_method; 
00079 
00080 template<bool Twaypoint = false>
00081 struct DeparturesWindow : public Window {
00082 protected:
00083   StationID station;         
00084   DepartureList *departures; 
00085   DepartureList *arrivals;   
00086   uint entry_height;         
00087   uint tick_count;           
00088   int calc_tick_countdown;   
00089   bool show_types[4];        
00090   bool departure_types[3];   
00091   uint min_width;            
00092   Scrollbar *vscroll;
00093 
00094   virtual uint GetMinWidth() const;
00095   static void RecomputeDateWidth();
00096   virtual void DrawDeparturesListItems(const Rect &r) const;
00097   void DeleteDeparturesList(DepartureList* list);
00098 public:
00099 
00100   DeparturesWindow(const WindowDesc *desc, WindowNumber window_number) : Window(),
00101     station(window_number),
00102     departures(new DepartureList()),
00103     arrivals(new DepartureList()),
00104     entry_height(1 + FONT_HEIGHT_NORMAL + 1 + (_settings_client.gui.departure_larger_font ? FONT_HEIGHT_NORMAL : FONT_HEIGHT_SMALL) + 1 + 1),
00105     tick_count(0),
00106     calc_tick_countdown(0),
00107     min_width(400)
00108   {
00109     this->CreateNestedTree(desc);
00110     this->vscroll = this->GetScrollbar(DW_WIDGET_SCROLLBAR);
00111     this->FinishInitNested(desc, window_number);
00112 
00113     /* By default, only show departures. */
00114     departure_types[0] = true;
00115     departure_types[1] = false;
00116     departure_types[2] = false;
00117     this->LowerWidget(DW_SHOW_DEPS);
00118     this->RaiseWidget(DW_SHOW_ARRS);
00119     this->RaiseWidget(DW_SHOW_VIA);
00120 
00121     for (uint i = 0; i < 4; ++i) {
00122       show_types[i] = true;
00123       this->LowerWidget(DW_SHOW_TRAINS + i);
00124     }
00125 
00126     if (Twaypoint) {
00127       this->GetWidget<NWidgetCore>(DW_WIDGET_CAPTION)->SetDataTip(STR_DEPARTURES_CAPTION_WAYPOINT, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS);
00128 
00129       for (uint i = 0; i < 4; ++i) {
00130         this->DisableWidget(DW_SHOW_TRAINS + i);
00131       }
00132 
00133       this->DisableWidget(DW_SHOW_ARRS);
00134       this->DisableWidget(DW_SHOW_DEPS);
00135       this->DisableWidget(DW_SHOW_VIA);
00136 
00137       departure_types[2] = true;
00138 
00139       this->LowerWidget(DW_SHOW_VIA);
00140     }
00141   }
00142 
00143   virtual ~DeparturesWindow()
00144   {
00145     this->DeleteDeparturesList(departures);
00146     this->DeleteDeparturesList(this->arrivals);
00147   }
00148 
00149   virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
00150   {
00151     switch (widget) {
00152       case DW_WIDGET_LIST:
00153         resize->height = DeparturesWindow::entry_height;
00154         size->height = 2 * resize->height;
00155         break;
00156     }
00157   }
00158 
00159   virtual void SetStringParameters(int widget) const
00160   {
00161     if (widget == DW_WIDGET_CAPTION) {
00162       const Station *st = Station::Get(this->station);
00163       SetDParam(0, st->index);
00164     }
00165   }
00166 
00167   virtual void OnClick(Point pt, int widget, int click_count)
00168   {
00169     switch (widget) {
00170       case DW_SHOW_TRAINS:   // Show trains to this station
00171       case DW_SHOW_ROADVEHS: // Show road vehicles to this station
00172       case DW_SHOW_SHIPS:    // Show ships to this station
00173       case DW_SHOW_PLANES:   // Show aircraft to this station
00174         this->show_types[widget - DW_SHOW_TRAINS] = !this->show_types[widget - DW_SHOW_TRAINS];
00175         if (this->show_types[widget - DW_SHOW_TRAINS]) {
00176           this->LowerWidget(widget);
00177         } else {
00178           this->RaiseWidget(widget);
00179         }
00180         /* We need to recompute the departures list. */
00181         this->calc_tick_countdown = 0;
00182         /* We need to redraw the button that was pressed. */
00183         this->SetWidgetDirty(widget);
00184         break;
00185 
00186       case DW_SHOW_DEPS:
00187       case DW_SHOW_ARRS:
00188         if (_settings_client.gui.departure_show_both) break;
00189         /* FALL THROUGH */
00190 
00191       case DW_SHOW_VIA:
00192 
00193         this->departure_types[widget - DW_SHOW_DEPS] = !this->departure_types[widget - DW_SHOW_DEPS];
00194         if (this->departure_types[widget - DW_SHOW_DEPS]) {
00195           this->LowerWidget(widget);
00196         } else {
00197           this->RaiseWidget(widget);
00198         }
00199 
00200         if (!this->departure_types[0]) {
00201           this->RaiseWidget(DW_SHOW_VIA);
00202           this->DisableWidget(DW_SHOW_VIA);
00203         } else {
00204           this->EnableWidget(DW_SHOW_VIA);
00205 
00206           if (this->departure_types[2]) {
00207             this->LowerWidget(DW_SHOW_VIA);
00208           }
00209         }
00210         /* We need to recompute the departures list. */
00211         this->calc_tick_countdown = 0;
00212         /* We need to redraw the button that was pressed. */
00213         this->SetWidgetDirty(widget);
00214         break;
00215 
00216       case DW_WIDGET_LIST:   // Matrix to show departures
00217         /* We need to find the departure corresponding to where the user clicked. */
00218         uint32 id_v = (pt.y - this->GetWidget<NWidgetBase>(DW_WIDGET_LIST)->pos_y) / this->entry_height;
00219 
00220         if (id_v >= this->vscroll->GetCapacity()) return; // click out of bounds
00221 
00222         id_v += this->vscroll->GetPosition();
00223 
00224         if (id_v >= (this->departures->Length() + this->arrivals->Length())) return; // click out of list bound
00225 
00226         uint departure = 0;
00227         uint arrival = 0;
00228 
00229         /* Draw each departure. */
00230         for (uint i = 0; i <= id_v; ++i) {
00231           const Departure *d;
00232 
00233           if (arrival == this->arrivals->Length()) {
00234             d = (*(this->departures))[departure++];
00235           } else if (departure == this->departures->Length()) {
00236             d = (*(this->arrivals))[arrival++];
00237           } else {
00238             d = (*(this->departures))[departure];
00239             const Departure *a = (*(this->arrivals))[arrival];
00240 
00241             if (a->scheduled_date < d->scheduled_date) {
00242               d = a;
00243               arrival++;
00244             } else {
00245               departure++;
00246             }
00247           }
00248 
00249           if (i == id_v) {
00250             ShowVehicleViewWindow(d->vehicle);
00251             break;
00252           }
00253         }
00254 
00255         break;
00256     }
00257   }
00258 
00259   virtual void OnTick()
00260   {
00261     if (_pause_mode == PM_UNPAUSED) {
00262       this->tick_count += 1;
00263       this->calc_tick_countdown -= 1;
00264     }
00265 
00266     /* Recompute the minimum date display width if the cached one is no longer valid. */
00267     if (cached_date_width == 0 ||
00268         _settings_client.gui.time_in_minutes != cached_date_display_method ||
00269         _settings_client.gui.departure_show_both != cached_arr_dep_display_method) {
00270       this->RecomputeDateWidth();
00271     }
00272 
00273     /* We need to redraw the scrolling text in its new position. */
00274     this->SetWidgetDirty(DW_WIDGET_LIST);
00275 
00276     /* Recompute the list of departures if we're due to. */
00277     if (this->calc_tick_countdown <= 0) {
00278       this->calc_tick_countdown = _settings_client.gui.departure_calc_frequency;
00279       this->DeleteDeparturesList(this->departures);
00280       this->DeleteDeparturesList(this->arrivals);
00281       this->departures = (this->departure_types[0] ? MakeDepartureList(this->station, this->show_types, D_DEPARTURE, Twaypoint || this->departure_types[2]) : new DepartureList());
00282       this->arrivals   = (this->departure_types[1] && !_settings_client.gui.departure_show_both ? MakeDepartureList(this->station, this->show_types, D_ARRIVAL  ) : new DepartureList());
00283       this->SetWidgetDirty(DW_WIDGET_LIST);
00284     }
00285 
00286     uint new_width = this->GetMinWidth();
00287 
00288     if (new_width != this->min_width) {
00289       NWidgetCore *n = this->GetWidget<NWidgetCore>(DW_WIDGET_LIST);
00290       n->SetMinimalSize(new_width, 0);
00291       this->ReInit();
00292       this->min_width = new_width;
00293     }
00294 
00295     uint new_height = 1 + FONT_HEIGHT_NORMAL + 1 + (_settings_client.gui.departure_larger_font ? FONT_HEIGHT_NORMAL : FONT_HEIGHT_SMALL) + 1 + 1;
00296 
00297     if (new_height != this->entry_height) {
00298       this->entry_height = new_height;
00299       this->SetWidgetDirty(DW_WIDGET_LIST);
00300       this->ReInit();
00301     }
00302   }
00303 
00304   virtual void OnPaint()
00305   {
00306     if (Twaypoint || _settings_client.gui.departure_show_both) {
00307       this->DisableWidget(DW_SHOW_ARRS);
00308       this->DisableWidget(DW_SHOW_DEPS);
00309     } else {
00310       this->EnableWidget(DW_SHOW_ARRS);
00311       this->EnableWidget(DW_SHOW_DEPS);
00312     }
00313 
00314     this->vscroll->SetCount(min(_settings_client.gui.max_departures, this->departures->Length() + this->arrivals->Length()));
00315     this->DrawWidgets();
00316   }
00317 
00318   virtual void DrawWidget(const Rect &r, int widget) const
00319   {
00320     switch (widget) {
00321       case DW_WIDGET_LIST:
00322         this->DrawDeparturesListItems(r);
00323         break;
00324     }
00325   }
00326 
00327   virtual void OnResize()
00328   {
00329     this->vscroll->SetCapacityFromWidget(this, DW_WIDGET_LIST);
00330     this->GetWidget<NWidgetCore>(DW_WIDGET_LIST)->widget_data = (this->vscroll->GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START);
00331   }
00332 };
00333 
00338 void ShowStationDepartures(StationID station)
00339 {
00340   AllocateWindowDescFront<DeparturesWindow<> >(&_departures_desc, station);
00341 }
00342 
00347 void ShowWaypointDepartures(StationID waypoint)
00348 {
00349   AllocateWindowDescFront<DeparturesWindow<true> >(&_departures_desc, waypoint);
00350 }
00351 
00352 template<bool Twaypoint>
00353 void DeparturesWindow<Twaypoint>::RecomputeDateWidth()
00354 {
00355   cached_date_width = 0;
00356   cached_status_width = 0;
00357   cached_date_display_method = _settings_client.gui.time_in_minutes;
00358   cached_arr_dep_display_method = _settings_client.gui.departure_show_both;
00359 
00360   cached_status_width = max((GetStringBoundingBox(STR_DEPARTURES_ON_TIME)).width, cached_status_width);
00361   cached_status_width = max((GetStringBoundingBox(STR_DEPARTURES_DELAYED)).width, cached_status_width);
00362   cached_status_width = max((GetStringBoundingBox(STR_DEPARTURES_CANCELLED)).width, cached_status_width);
00363 
00364   uint interval = cached_date_display_method ? _settings_client.gui.ticks_per_minute : DAY_TICKS;
00365   uint count = cached_date_display_method ? 24*60 : 365;
00366 
00367   for (uint i = 0; i < count; ++i) {
00368     SetDParam(0, INT_MAX - (i*interval));
00369     SetDParam(1, INT_MAX - (i*interval));
00370     cached_date_width = max(GetStringBoundingBox(cached_arr_dep_display_method ? STR_DEPARTURES_TIME_BOTH : STR_DEPARTURES_TIME_DEP).width, cached_date_width);
00371     cached_status_width = max((GetStringBoundingBox(STR_DEPARTURES_EXPECTED)).width, cached_status_width);
00372   }
00373 
00374   SetDParam(0, 0);
00375   cached_date_arrow_width = GetStringBoundingBox(STR_DEPARTURES_TIME_DEP).width - GetStringBoundingBox(STR_DEPARTURES_TIME).width;
00376 
00377   if (!_settings_client.gui.departure_show_both) {
00378     cached_date_width -= cached_date_arrow_width;
00379   }
00380 }
00381 
00382 template<bool Twaypoint>
00383 uint DeparturesWindow<Twaypoint>::GetMinWidth() const
00384 {
00385   uint result = 0;
00386 
00387   /* Time */
00388   result = cached_date_width;
00389 
00390   /* Vehicle type icon */
00391   result += _settings_client.gui.departure_show_vehicle_type ? (GetStringBoundingBox(STR_DEPARTURES_TYPE_PLANE)).width : 0;
00392 
00393   /* Status */
00394   result += cached_status_width;
00395 
00396   /* Find the maximum company name width. */
00397   int toc_width = 0;
00398 
00399   /* Find the maximum company name width. */
00400   int group_width = 0;
00401 
00402   /* Find the maximum vehicle name width. */
00403   int veh_width = 0;
00404 
00405   if (_settings_client.gui.departure_show_vehicle || _settings_client.gui.departure_show_company || _settings_client.gui.departure_show_group) {
00406     for (uint i = 0; i < 4; ++i) {
00407       VehicleList vehicles;
00408 
00409       /* 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). */
00410       if (!GenerateVehicleSortList(&vehicles, VehicleListIdentifier(VL_STATION_LIST, (VehicleType)(VEH_TRAIN + i), MAX_COMPANIES, station).Pack())) {
00411         /* Something went wrong: panic! */
00412         continue;
00413       }
00414 
00415       for (const Vehicle **v = vehicles.Begin(); v != vehicles.End(); v++) {
00416         SetDParam(0, (uint64)((*v)->index));
00417         int width = (GetStringBoundingBox(STR_DEPARTURES_VEH)).width;
00418         if (_settings_client.gui.departure_show_vehicle && width > veh_width) veh_width = width;
00419 
00420         if ((*v)->group_id != INVALID_GROUP && (*v)->group_id != DEFAULT_GROUP) {
00421           SetDParam(0, (uint64)((*v)->group_id));
00422           width = (GetStringBoundingBox(STR_DEPARTURES_GROUP)).width;
00423           if (_settings_client.gui.departure_show_group && width > group_width) group_width = width;
00424         }
00425 
00426         SetDParam(0, (uint64)((*v)->owner));
00427         width = (GetStringBoundingBox(STR_DEPARTURES_TOC)).width;
00428         if (_settings_client.gui.departure_show_company && width > toc_width) toc_width = width;
00429       }
00430     }
00431   }
00432 
00433   result += toc_width + veh_width + group_width;
00434 
00435   return result + 140;
00436 }
00437 
00441 template<bool Twaypoint>
00442 void DeparturesWindow<Twaypoint>::DeleteDeparturesList(DepartureList *list)
00443 {
00444   /* SmallVector uses free rather than delete on its contents (which doesn't invoke the destructor), so we need to delete each departure manually. */
00445   for (uint i = 0; i < list->Length(); ++i) {
00446     Departure **d = list->Get(i);
00447     delete *d;
00448     /* Make sure a double free doesn't happen. */
00449     *d = NULL;
00450   }
00451   list->Reset();
00452   delete list;
00453   list = NULL;
00454 }
00455 
00459 template<bool Twaypoint>
00460 void DeparturesWindow<Twaypoint>::DrawDeparturesListItems(const Rect &r) const
00461 {
00462   int left = r.left + WD_MATRIX_LEFT;
00463   int right = r.right - WD_MATRIX_RIGHT;
00464 
00465   bool rtl = _current_text_dir == TD_RTL;
00466   bool ltr = !rtl;
00467 
00468   int text_offset = WD_FRAMERECT_RIGHT;
00469   int text_left  = left  + (rtl ?           0 : text_offset);
00470   int text_right = right - (rtl ? text_offset :           0);
00471 
00472   int y = r.top + 1;
00473   uint max_departures = min(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), this->departures->Length() + this->arrivals->Length());
00474 
00475   if (max_departures > _settings_client.gui.max_departures) {
00476     max_departures = _settings_client.gui.max_departures;
00477   }
00478 
00479   byte small_font_size = _settings_client.gui.departure_larger_font ? FONT_HEIGHT_NORMAL : FONT_HEIGHT_SMALL;
00480 
00481   /* Draw the black background. */
00482   GfxFillRect(r.left + 1, r.top, r.right - 1, r.bottom, PC_BLACK);
00483 
00484   /* Nothing selected? Then display the information text. */
00485   bool none_selected[2] = {true, true};
00486   for (uint i = 0; i < 4; ++i)
00487   {
00488     if (this->show_types[i]) {
00489       none_selected[0] = false;
00490       break;
00491     }
00492   }
00493 
00494   for (uint i = 0; i < 2; ++i)
00495   {
00496     if (this->departure_types[i]) {
00497       none_selected[1] = false;
00498       break;
00499     }
00500   }
00501 
00502   if (none_selected[0] || none_selected[1]) {
00503     DrawString(text_left, text_right, y + 1, STR_DEPARTURES_NONE_SELECTED);
00504     return;
00505   }
00506 
00507   /* No scheduled departures? Then display the information text. */
00508   if (max_departures == 0) {
00509     DrawString(text_left, text_right, y + 1, STR_DEPARTURES_EMPTY);
00510     return;
00511   }
00512 
00513   /* Find the maximum possible width of the departure time and "Expt <time>" fields. */
00514   int time_width = cached_date_width;
00515 
00516   if (!_settings_client.gui.departure_show_both) {
00517     time_width += (departure_types[0] && departure_types[1] ? cached_date_arrow_width : 0);
00518   }
00519 
00520   /* Vehicle type icon */
00521   int type_width = _settings_client.gui.departure_show_vehicle_type ? (GetStringBoundingBox(STR_DEPARTURES_TYPE_PLANE)).width : 0;
00522 
00523   /* Find the maximum width of the status field */
00524   int status_width = cached_status_width;
00525 
00526   /* Find the width of the "Calling at:" field. */
00527   int calling_at_width = (GetStringBoundingBox(_settings_client.gui.departure_larger_font ? STR_DEPARTURES_CALLING_AT_LARGE : STR_DEPARTURES_CALLING_AT)).width;
00528 
00529   /* Find the maximum company name width. */
00530   int toc_width = 0;
00531 
00532   /* Find the maximum group name width. */
00533   int group_width = 0;
00534 
00535   /* Find the maximum vehicle name width. */
00536   int veh_width = 0;
00537 
00538   if (_settings_client.gui.departure_show_vehicle || _settings_client.gui.departure_show_company || _settings_client.gui.departure_show_group) {
00539     for (uint i = 0; i < 4; ++i) {
00540       VehicleList vehicles;
00541 
00542       /* 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). */
00543       if (!GenerateVehicleSortList(&vehicles, VehicleListIdentifier(VL_STATION_LIST, (VehicleType)(VEH_TRAIN + i), MAX_COMPANIES, station).Pack())) {
00544         /* Something went wrong: panic! */
00545         continue;
00546       }
00547 
00548       for (const Vehicle **v = vehicles.Begin(); v != vehicles.End(); v++) {
00549         SetDParam(0, (uint64)((*v)->index));
00550         int width = (GetStringBoundingBox(STR_DEPARTURES_VEH)).width;
00551         if (_settings_client.gui.departure_show_vehicle && width > veh_width) veh_width = width;
00552 
00553         if ((*v)->group_id != INVALID_GROUP && (*v)->group_id != DEFAULT_GROUP) {
00554           SetDParam(0, (uint64)((*v)->group_id));
00555           width = (GetStringBoundingBox(STR_DEPARTURES_GROUP)).width;
00556           if (_settings_client.gui.departure_show_group && width > group_width) group_width = width;
00557         }
00558 
00559         SetDParam(0, (uint64)((*v)->owner));
00560         width = (GetStringBoundingBox(STR_DEPARTURES_TOC)).width;
00561         if (_settings_client.gui.departure_show_company && width > toc_width) toc_width = width;
00562       }
00563     }
00564   }
00565 
00566   uint departure = 0;
00567   uint arrival = 0;
00568 
00569   /* Draw each departure. */
00570   for (uint i = 0; i < max_departures; ++i) {
00571     const Departure *d;
00572 
00573     if (arrival == this->arrivals->Length()) {
00574       d = (*(this->departures))[departure++];
00575     } else if (departure == this->departures->Length()) {
00576       d = (*(this->arrivals))[arrival++];
00577     } else {
00578       d = (*(this->departures))[departure];
00579       const Departure *a = (*(this->arrivals))[arrival];
00580 
00581       if (a->scheduled_date < d->scheduled_date) {
00582         d = a;
00583         arrival++;
00584       } else {
00585         departure++;
00586       }
00587     }
00588 
00589     if (i < this->vscroll->GetPosition()) {
00590       continue;
00591     }
00592 
00593     /* If for some reason the departure is too far in the future or is at a negative time, skip it. */
00594     if ((d->scheduled_date / DAY_TICKS) > (_date + _settings_client.gui.max_departure_time) ||
00595       d->scheduled_date < 0) {
00596       continue;
00597     }
00598 
00599     if (d->terminus == INVALID_STATION) continue;
00600 
00601     StringID time_str = (departure_types[0] && departure_types[1]) ? (d->type == D_DEPARTURE ? STR_DEPARTURES_TIME_DEP : STR_DEPARTURES_TIME_ARR) : STR_DEPARTURES_TIME;
00602 
00603     if (_settings_client.gui.departure_show_both) time_str = STR_DEPARTURES_TIME_BOTH;
00604 
00605     /* Time */
00606     SetDParam(0, d->scheduled_date);
00607     SetDParam(1, d->scheduled_date - d->order->wait_time);
00608     ltr ? DrawString(              text_left, text_left + time_width, y + 1, time_str)
00609       : DrawString(text_right - time_width,             text_right, y + 1, time_str);
00610 
00611     /* Vehicle type icon */
00612     if (_settings_client.gui.departure_show_vehicle_type) {
00613       StringID type = STR_DEPARTURES_TYPE_TRAIN;
00614       int offset = (_settings_client.gui.departure_show_vehicle_color ? 1 : 0);
00615 
00616       switch (d->vehicle->type) {
00617         case VEH_TRAIN:
00618           type = STR_DEPARTURES_TYPE_TRAIN;
00619           break;
00620         case VEH_ROAD:
00621           type = IsCargoInClass(d->vehicle->cargo_type, CC_PASSENGERS) ? STR_DEPARTURES_TYPE_BUS : STR_DEPARTURES_TYPE_LORRY;
00622           break;
00623         case VEH_SHIP:
00624           type = STR_DEPARTURES_TYPE_SHIP;
00625           break;
00626         case VEH_AIRCRAFT:
00627           type = STR_DEPARTURES_TYPE_PLANE;
00628           break;
00629         default:
00630           break;
00631       }
00632 
00633       type += offset;
00634 
00635       DrawString(text_left + time_width + 3, text_left + time_width + type_width + 3, y, type);
00636     }
00637 
00638     /* The icons to show with the destination and via stations. */
00639     StringID icon = STR_DEPARTURES_STATION_NONE;
00640     StringID icon_via = STR_DEPARTURES_STATION_NONE;
00641 
00642     if (_settings_client.gui.departure_destination_type) {
00643       Station *t = Station::Get(d->terminus);
00644 
00645       if (t->facilities & FACIL_DOCK &&
00646           t->facilities & FACIL_AIRPORT &&
00647           d->vehicle->type != VEH_SHIP &&
00648           d->vehicle->type != VEH_AIRCRAFT) {
00649         icon = STR_DEPARTURES_STATION_PORTAIRPORT;
00650       } else if (t->facilities & FACIL_DOCK &&
00651           d->vehicle->type != VEH_SHIP) {
00652         icon = STR_DEPARTURES_STATION_PORT;
00653       } else if (t->facilities & FACIL_AIRPORT &&
00654           d->vehicle->type != VEH_AIRCRAFT) {
00655         icon = STR_DEPARTURES_STATION_AIRPORT;
00656       }
00657     }
00658 
00659     if (_settings_client.gui.departure_destination_type && d->via != INVALID_STATION) {
00660       Station *t = Station::Get(d->via);
00661 
00662       if (t->facilities & FACIL_DOCK &&
00663           t->facilities & FACIL_AIRPORT &&
00664           d->vehicle->type != VEH_SHIP &&
00665           d->vehicle->type != VEH_AIRCRAFT) {
00666         icon_via = STR_DEPARTURES_STATION_PORTAIRPORT;
00667       } else if (t->facilities & FACIL_DOCK &&
00668           d->vehicle->type != VEH_SHIP) {
00669         icon_via = STR_DEPARTURES_STATION_PORT;
00670       } else if (t->facilities & FACIL_AIRPORT &&
00671           d->vehicle->type != VEH_AIRCRAFT) {
00672         icon_via = STR_DEPARTURES_STATION_AIRPORT;
00673       }
00674     }
00675 
00676     /* Destination */
00677     if (d->via == INVALID_STATION) {
00678       /* Only show the terminus. */
00679       SetDParam(0, d->terminus);
00680       SetDParam(1, icon);
00681       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)
00682         : 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);
00683     } else {
00684       /* Show the terminus and the via station. */
00685       SetDParam(0, d->terminus);
00686       SetDParam(1, icon);
00687       SetDParam(2, d->via);
00688       SetDParam(3, icon_via);
00689       int text_width = (GetStringBoundingBox(STR_DEPARTURES_TERMINUS_VIA_STATION)).width;
00690 
00691       if (text_width < text_right - status_width - (toc_width + veh_width + group_width + 2) - 2 - (text_left + time_width + type_width + 6)) {
00692         /* They will both fit, so show them both. */
00693         SetDParam(0, d->terminus);
00694         SetDParam(1, icon);
00695         SetDParam(2, d->via);
00696         SetDParam(3, icon_via);
00697         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)
00698           : 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);
00699       } else {
00700         /* They won't both fit, so switch between showing the terminus and the via station approximately every 4 seconds. */
00701         if (this->tick_count & (1 << 7)) {
00702           SetDParam(0, d->via);
00703           SetDParam(1, icon_via);
00704           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)
00705             : 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);
00706         } else {
00707           SetDParam(0, d->terminus);
00708           SetDParam(1, icon);
00709           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)
00710             : 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);
00711         }
00712       }
00713     }
00714 
00715     /* Status */
00716     {
00717       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;
00718       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);
00719 
00720       if (d->status == D_ARRIVED) {
00721         /* The vehicle has arrived. */
00722         DrawString(status_left, status_right, y + 1, STR_DEPARTURES_ARRIVED);
00723       } else if(d->status == D_CANCELLED) {
00724         /* The vehicle has been cancelled. */
00725         DrawString(status_left, status_right, y + 1, STR_DEPARTURES_CANCELLED);
00726       } else {
00727         if (d->lateness <= DAY_TICKS && d->scheduled_date > ((_date * DAY_TICKS) + _date_fract)) {
00728           /* We have no evidence that the vehicle is late, so assume it is on time. */
00729           DrawString(status_left, status_right, y + 1, STR_DEPARTURES_ON_TIME);
00730         } else {
00731           if ((d->scheduled_date + d->lateness) < ((_date * DAY_TICKS) + _date_fract)) {
00732             /* The vehicle was expected to have arrived by now, even if we knew it was going to be late. */
00733             /* 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. */
00734             DrawString(status_left, status_right, y + 1, STR_DEPARTURES_DELAYED);
00735           } else {
00736             /* The vehicle is expected to be late and is not yet due to arrive. */
00737             SetDParam(0, d->scheduled_date + d->lateness);
00738             DrawString(status_left, status_right, y + 1, STR_DEPARTURES_EXPECTED);
00739           }
00740         }
00741       }
00742     }
00743 
00744     /* Vehicle name */
00745 
00746     if (_settings_client.gui.departure_show_vehicle) {
00747       SetDParam(0, (uint64)(d->vehicle->index));
00748       ltr ? DrawString(text_right - (toc_width + veh_width + group_width + 2),              text_right - toc_width - group_width - 2, y + 1, STR_DEPARTURES_VEH)
00749         : DrawString(               text_left + toc_width + group_width + 2, text_left + (toc_width + veh_width + group_width + 2), y + 1, STR_DEPARTURES_VEH);
00750     }
00751 
00752     /* Group name */
00753 
00754     if (_settings_client.gui.departure_show_group && d->vehicle->group_id != INVALID_GROUP && d->vehicle->group_id != DEFAULT_GROUP) {
00755       SetDParam(0, (uint64)(d->vehicle->group_id));
00756       ltr ? DrawString(text_right - (toc_width + group_width + 2),              text_right - toc_width - 2, y + 1, STR_DEPARTURES_GROUP)
00757         : DrawString(               text_left + toc_width + 2, text_left + (toc_width + group_width + 2), y + 1, STR_DEPARTURES_GROUP);
00758     }
00759 
00760     /* Operating company */
00761     if (_settings_client.gui.departure_show_company) {
00762       SetDParam(0, (uint64)(d->vehicle->owner));
00763       ltr ? DrawString(text_right - toc_width,            text_right, y + 1, STR_DEPARTURES_TOC, TC_FROMSTRING, SA_RIGHT)
00764         : DrawString(             text_left, text_left + toc_width, y + 1, STR_DEPARTURES_TOC, TC_FROMSTRING, SA_LEFT);
00765     }
00766 
00767     int bottom_y = y + this->entry_height - small_font_size - (_settings_client.gui.departure_larger_font ? 1 : 3);
00768 
00769     /* Calling at */
00770     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)
00771       : 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);
00772 
00773     /* List of stations */
00774     /* RTL languages can be handled in the language file, e.g. by having the following: */
00775     /* STR_DEPARTURES_CALLING_AT_STATION      :{STATION}, {RAW_STRING} */
00776     /* STR_DEPARTURES_CALLING_AT_LAST_STATION :{STATION} & {RAW_STRING}*/
00777     char buffer[512], scratch[512];
00778 
00779     if (d->calling_at.Length() != 0) {
00780       SetDParam(0, (uint64)(*d->calling_at.Get(0)));
00781       GetString(scratch, STR_DEPARTURES_CALLING_AT_FIRST_STATION, lastof(scratch));
00782 
00783       if (d->calling_at.Length() > 1) {
00784         /* There's more than one stop. */
00785 
00786         /* For all but the last station, write out ", <station>". */
00787         for (uint i = 1; i < d->calling_at.Length() - 1; ++i) {
00788           SetDParam(0, (uint64)scratch);
00789           SetDParam(1, (uint64)(*d->calling_at.Get(i)));
00790           GetString(buffer, STR_DEPARTURES_CALLING_AT_STATION, lastof(buffer));
00791           strncpy(scratch, buffer, sizeof(scratch)); }
00792 
00793         /* Finally, finish off with " & <station>". */
00794         SetDParam(0, (uint64)scratch);
00795         SetDParam(1, (uint64)(*d->calling_at.Get(d->calling_at.Length()-1)));
00796         GetString(buffer, STR_DEPARTURES_CALLING_AT_LAST_STATION, lastof(buffer));
00797         strncpy(scratch, buffer, sizeof(scratch));
00798       }
00799 
00800       SetDParam(0, (uint64)scratch);
00801       GetString(buffer, _settings_client.gui.departure_larger_font ? STR_DEPARTURES_CALLING_AT_LIST_LARGE : STR_DEPARTURES_CALLING_AT_LIST, lastof(buffer));
00802     } else {
00803       buffer[0] = 0;
00804       //SetDParam(0, d->terminus);
00805       //GetString(scratch, STR_DEPARTURES_CALLING_AT_FIRST_STATION, lastof(scratch));
00806     }
00807 
00808     int list_width = (GetStringBoundingBox(buffer, _settings_client.gui.departure_larger_font ? FS_NORMAL : FS_SMALL)).width;
00809 
00810     /* Draw the whole list if it will fit. Otherwise scroll it. */
00811     if (list_width < text_right - (text_left + calling_at_width + 2)) {
00812       ltr ? DrawString(text_left + calling_at_width + 2,                        text_right, bottom_y, buffer)
00813         : DrawString(                       text_left, text_right - calling_at_width - 2, bottom_y, buffer);
00814     } else {
00815       DrawPixelInfo tmp_dpi;
00816       if (ltr
00817         ? !FillDrawPixelInfo(&tmp_dpi, text_left + calling_at_width + 2, bottom_y, text_right - (text_left + calling_at_width + 2), small_font_size + 3)
00818         : !FillDrawPixelInfo(&tmp_dpi, text_left                       , bottom_y, text_right - (text_left + calling_at_width + 2), small_font_size + 3)) {
00819         y += this->entry_height;
00820         continue;
00821       }
00822       DrawPixelInfo *old_dpi = _cur_dpi;
00823       _cur_dpi = &tmp_dpi;
00824 
00825       /* 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. */
00826       int pos = ltr
00827         ? text_right - (this->tick_count % (list_width + text_right - text_left))
00828         :  text_left + (this->tick_count % (list_width + text_right - text_left));
00829 
00830       ltr ? DrawString(       pos, INT16_MAX, 0, buffer, TC_FROMSTRING,  SA_LEFT | SA_FORCE)
00831         : DrawString(-INT16_MAX,       pos, 0, buffer, TC_FROMSTRING, SA_RIGHT | SA_FORCE);
00832 
00833       _cur_dpi = old_dpi;
00834     }
00835 
00836     y += this->entry_height;
00837   }
00838 }