# station_gui.cpp

Go to the documentation of this file.

    00001 /* $Id: station_gui.cpp 15427 2009-02-09 02:33:10Z rubidium $ */
    00002
    00005 #include "stdafx.h"
    00006 #include "openttd.h"
    00007 #include "debug.h"
    00008 #include "gui.h"
    00009 #include "window_gui.h"
    00010 #include "textbuf_gui.h"
    00011 #include "company_func.h"
    00012 #include "command_func.h"
    00013 #include "vehicle_gui.h"
    00014 #include "cargotype.h"
    00015 #include "station_gui.h"
    00016 #include "strings_func.h"
    00017 #include "window_func.h"
    00018 #include "viewport_func.h"
    00019 #include "gfx_func.h"
    00020 #include "widgets/dropdown_func.h"
    00021 #include "newgrf_cargo.h"
    00022 #include "station_map.h"
    00023 #include "tilehighlight_func.h"
    00024 #include "core/smallmap_type.hpp"
    00025 #include "company_base.h"
    00026 #include "sortlist_type.h"
    00027 #include "settings_type.h"
    00028
    00029 #include "table/strings.h"
    00030 #include "table/sprites.h"
    00031
    00046 static void StationsWndShowStationRating(int x, int y, CargoID type, uint amount, byte rating)
    00047 {
    00048   static const uint units_full  = 576;
    00049   static const uint rating_full = 224;
    00050
    00051   const CargoSpec *cs = GetCargo(type);
    00052   if (!cs->IsValid()) return;
    00053
    00054   int colour = cs->rating_colour;
    00055   uint w = (minu(amount, units_full) + 5) / 36;
    00056
    00057   /* Draw total cargo (limited) on station (fits into 16 pixels) */
    00058   if (w != 0) GfxFillRect(x, y, x + w - 1, y + 6, colour);
    00059
    00060   /* Draw a one pixel-wide bar of additional cargo meter, useful
    00061    * for stations with only a small amount (<=30) */
    00062   if (w == 0) {
    00063     uint rest = amount / 5;
    00064     if (rest != 0) {
    00065       w += x;
    00066       GfxFillRect(w, y + 6 - rest, w, y + 6, colour);
    00067     }
    00068   }
    00069
    00070   DrawString(x + 1, y, cs->abbrev, TC_BLACK);
    00071
    00072   /* Draw green/red ratings bar (fits into 14 pixels) */
    00073   y += 8;
    00074   GfxFillRect(x + 1, y, x + 14, y, 0xB8);
    00075   rating = minu(rating, rating_full) / 16;
    00076   if (rating != 0) GfxFillRect(x + 1, y, x + rating, y, 0xD0);
    00077 }
    00078
    00079 typedef GUIList<const Station*> GUIStationList;
    00080
    00084 class CompanyStationsWindow : public Window
    00085 {
    00087   enum StationListWidgets {
    00088     SLW_CLOSEBOX =  0,
    00089     SLW_CAPTION,
    00090     SLW_STICKY,
    00091     SLW_LIST,
    00092     SLW_SCROLLBAR,
    00093     SLW_RESIZE,
    00094
    00095     SLW_TRAIN,
    00096     SLW_TRUCK,
    00097     SLW_BUS,
    00098     SLW_AIRPLANE,
    00099     SLW_SHIP,
    00100     SLW_FACILALL,
    00101
    00102     SLW_PAN_BETWEEN,
    00103     SLW_NOCARGOWAITING,
    00104     SLW_CARGOALL,
    00105     SLW_PAN_RIGHT,
    00106
    00107     SLW_SORTBY,
    00108     SLW_SORTDROPBTN,
    00109     SLW_PAN_SORT_RIGHT,
    00110
    00111     SLW_CARGOSTART,
    00112   };
    00113
    00114 protected:
    00115   /* Runtime saved values */
    00116   static Listing last_sorting;
    00117   static byte facilities;               // types of stations of interest
    00118   static bool include_empty;            // whether we should include stations without waiting cargo
    00119   static const uint32 cargo_filter_max;
    00120   static uint32 cargo_filter;           // bitmap of cargo types to include
    00121   static const Station *last_station;
    00122
    00123   /* Constants for sorting stations */
    00124   static const StringID sorter_names[];
    00125   static GUIStationList::SortFunction *const sorter_funcs[];
    00126
    00127   GUIStationList stations;
    00128
    00129
    00135   void BuildStationsList(const Owner owner)
    00136   {
    00137     if (!this->stations.NeedRebuild()) return;
    00138
    00139     DEBUG(misc, 3, "Building station list for company %d", owner);
    00140
    00141     this->stations.Clear();
    00142
    00143     const Station *st;
    00144     FOR_ALL_STATIONS(st) {
    00145       if (st->owner == owner || (st->owner == OWNER_NONE && !st->IsBuoy() && HasStationInUse(st->index, owner))) {
    00146         if (this->facilities & st->facilities) { // only stations with selected facilities
    00147           int num_waiting_cargo = 0;
    00148           for (CargoID j = 0; j < NUM_CARGO; j++) {
    00149             if (!st->goods[j].cargo.Empty()) {
    00150               num_waiting_cargo++; // count number of waiting cargo
    00151               if (HasBit(this->cargo_filter, j)) {
    00152                 *this->stations.Append() = st;
    00153                 break;
    00154               }
    00155             }
    00156           }
    00157           /* stations without waiting cargo */
    00158           if (num_waiting_cargo == 0 && this->include_empty) {
    00159             *this->stations.Append() = st;
    00160           }
    00161         }
    00162       }
    00163     }
    00164
    00165     this->stations.Compact();
    00166     this->stations.RebuildDone();
    00167   }
    00168
    00170   static int CDECL StationNameSorter(const Station * const *a, const Station * const *b)
    00171   {
    00172     static char buf_cache[64];
    00173     char buf[64];
    00174
    00175     SetDParam(0, (*a)->index);
    00176     GetString(buf, STR_STATION, lastof(buf));
    00177
    00178     if (*b != last_station) {
    00179       last_station = *b;
    00180       SetDParam(0, (*b)->index);
    00181       GetString(buf_cache, STR_STATION, lastof(buf_cache));
    00182     }
    00183
    00184     return strcmp(buf, buf_cache);
    00185   }
    00186
    00188   static int CDECL StationTypeSorter(const Station * const *a, const Station * const *b)
    00189   {
    00190     return (*a)->facilities - (*b)->facilities;
    00191   }
    00192
    00194   static int CDECL StationWaitingSorter(const Station * const *a, const Station * const *b)
    00195   {
    00196     Money diff = 0;
    00197
    00198     for (CargoID j = 0; j < NUM_CARGO; j++) {
    00199       if (!HasBit(cargo_filter, j)) continue;
    00200       if (!(*a)->goods[j].cargo.Empty()) diff += GetTransportedGoodsIncome((*a)->goods[j].cargo.Count(), 20, 50, j);
    00201       if (!(*b)->goods[j].cargo.Empty()) diff -= GetTransportedGoodsIncome((*b)->goods[j].cargo.Count(), 20, 50, j);
    00202     }
    00203
    00204     return ClampToI32(diff);
    00205   }
    00206
    00208   static int CDECL StationRatingMaxSorter(const Station * const *a, const Station * const *b)
    00209   {
    00210     byte maxr1 = 0;
    00211     byte maxr2 = 0;
    00212
    00213     for (CargoID j = 0; j < NUM_CARGO; j++) {
    00214       if (HasBit((*a)->goods[j].acceptance_pickup, GoodsEntry::PICKUP)) maxr1 = max(maxr1, (*a)->goods[j].rating);
    00215       if (HasBit((*b)->goods[j].acceptance_pickup, GoodsEntry::PICKUP)) maxr2 = max(maxr2, (*b)->goods[j].rating);
    00216     }
    00217
    00218     return maxr1 - maxr2;
    00219   }
    00220
    00222   void SortStationsList()
    00223   {
    00224     if (!this->stations.Sort()) return;
    00225
    00226     /* Reset name sorter sort cache */
    00227     this->last_station = NULL;
    00228
    00229     /* Set the modified widget dirty */
    00230     this->InvalidateWidget(SLW_LIST);
    00231   }
    00232
    00233 public:
    00234   CompanyStationsWindow(const WindowDesc *desc, WindowNumber window_number) : Window(desc, window_number)
    00235   {
    00236     this->owner = (Owner)this->window_number;
    00237     this->vscroll.cap = 12;
    00238     this->resize.step_height = 10;
    00239     this->resize.height = this->height - 10 * 7; // minimum if 5 in the list
    00240
    00241     /* Add cargo filter buttons */
    00242     uint num_active = 0;
    00243     for (CargoID c = 0; c < NUM_CARGO; c++) {
    00244       if (GetCargo(c)->IsValid()) num_active++;
    00245     }
    00246
    00247     this->widget_count += num_active;
    00248     this->widget = ReallocT(this->widget, this->widget_count + 1);
    00249     this->widget[this->widget_count].type = WWT_LAST;
    00250
    00251     uint i = 0;
    00252     for (CargoID c = 0; c < NUM_CARGO; c++) {
    00253       if (!GetCargo(c)->IsValid()) continue;
    00254
    00255       Widget *wi = &this->widget[SLW_CARGOSTART + i];
    00256       wi->type     = WWT_PANEL;
    00257       wi->display_flags = RESIZE_NONE;
    00258       wi->colour   = COLOUR_GREY;
    00259       wi->left     = 89 + i * 14;
    00260       wi->right    = wi->left + 13;
    00261       wi->top      = 14;
    00262       wi->bottom   = 24;
    00263       wi->data     = 0;
    00264       wi->tooltips = STR_USE_CTRL_TO_SELECT_MORE;
    00265
    00266       if (HasBit(this->cargo_filter, c)) this->LowerWidget(SLW_CARGOSTART + i);
    00267       i++;
    00268     }
    00269
    00270     this->widget[SLW_NOCARGOWAITING].left += num_active * 14;
    00271     this->widget[SLW_NOCARGOWAITING].right += num_active * 14;
    00272     this->widget[SLW_CARGOALL].left += num_active * 14;
    00273     this->widget[SLW_CARGOALL].right += num_active * 14;
    00274     this->widget[SLW_PAN_RIGHT].left += num_active * 14;
    00275
    00276     if (num_active > 15) {
    00277       /* Resize and fix the minimum width, if necessary */
    00278       ResizeWindow(this, (num_active - 15) * 14, 0);
    00279       this->resize.width = this->width;
    00280     }
    00281
    00282     if (this->cargo_filter == this->cargo_filter_max) this->cargo_filter = _cargo_mask;
    00283
    00284     for (uint i = 0; i < 5; i++) {
    00285       if (HasBit(this->facilities, i)) this->LowerWidget(i + SLW_TRAIN);
    00286     }
    00287     this->SetWidgetLoweredState(SLW_FACILALL, this->facilities == (FACIL_TRAIN | FACIL_TRUCK_STOP | FACIL_BUS_STOP | FACIL_AIRPORT | FACIL_DOCK));
    00288     this->SetWidgetLoweredState(SLW_CARGOALL, this->cargo_filter == _cargo_mask && this->include_empty);
    00289     this->SetWidgetLoweredState(SLW_NOCARGOWAITING, this->include_empty);
    00290
    00291     this->stations.SetListing(this->last_sorting);
    00292     this->stations.SetSortFuncs(this->sorter_funcs);
    00293     this->stations.ForceRebuild();
    00294     this->stations.NeedResort();
    00295     this->SortStationsList();
    00296
    00297     this->widget[SLW_SORTDROPBTN].data = this->sorter_names[this->stations.SortType()];
    00298
    00299     this->FindWindowPlacementAndResize(desc);
    00300   }
    00301
    00302   ~CompanyStationsWindow()
    00303   {
    00304     this->last_sorting = this->stations.GetListing();
    00305   }
    00306
    00307   virtual void OnPaint()
    00308   {
    00309     const Owner owner = (Owner)this->window_number;
    00310
    00311     this->BuildStationsList(owner);
    00312     this->SortStationsList();
    00313
    00314     SetVScrollCount(this, this->stations.Length());
    00315
    00316     /* draw widgets, with company's name in the caption */
    00317     SetDParam(0, owner);
    00318     SetDParam(1, this->vscroll.count);
    00319
    00320     this->DrawWidgets();
    00321
    00322     /* draw arrow pointing up/down for ascending/descending sorting */
    00323     this->DrawSortButtonState(SLW_SORTBY, this->stations.IsDescSortOrder() ? SBS_DOWN : SBS_UP);
    00324
    00325     int cg_ofst;
    00326     int x = 89;
    00327     int y = 14;
    00328     int xb = 2;
    00329
    00330     uint i = 0;
    00331     for (CargoID c = 0; c < NUM_CARGO; c++) {
    00332       const CargoSpec *cs = GetCargo(c);
    00333       if (!cs->IsValid()) continue;
    00334
    00335       cg_ofst = HasBit(this->cargo_filter, c) ? 2 : 1;
    00336       GfxFillRect(x + cg_ofst, y + cg_ofst, x + cg_ofst + 10 , y + cg_ofst + 7, cs->rating_colour);
    00337       DrawStringCentered(x + 6 + cg_ofst, y + cg_ofst, cs->abbrev, TC_BLACK);
    00338       x += 14;
    00339       i++;
    00340     }
    00341
    00342     x += 6;
    00343     cg_ofst = this->IsWidgetLowered(SLW_NOCARGOWAITING) ? 2 : 1;
    00344     DrawStringCentered(x + cg_ofst, y + cg_ofst, STR_ABBREV_NONE, TC_BLACK);
    00345     x += 14;
    00346     cg_ofst = this->IsWidgetLowered(SLW_CARGOALL) ? 2 : 1;
    00347     DrawStringCentered(x + cg_ofst, y + cg_ofst, STR_ABBREV_ALL, TC_BLACK);
    00348
    00349     cg_ofst = this->IsWidgetLowered(SLW_FACILALL) ? 2 : 1;
    00350     DrawString(71 + cg_ofst, y + cg_ofst, STR_ABBREV_ALL, TC_BLACK);
    00351
    00352     if (this->vscroll.count == 0) { // company has no stations
    00353       DrawString(xb, 40, STR_304A_NONE, TC_FROMSTRING);
    00354       return;
    00355     }
    00356
    00357     int max = min(this->vscroll.pos + this->vscroll.cap, this->stations.Length());
    00358     y = 40; // start of the list-widget
    00359
    00360     for (int i = this->vscroll.pos; i < max; ++i) { // do until max number of stations of owner
    00361       const Station *st = this->stations[i];
    00362       int x;
    00363
    00364       assert(st->xy != INVALID_TILE);
    00365
    00366       /* Do not do the complex check HasStationInUse here, it may be even false
    00367         * when the order had been removed and the station list hasn't been removed yet */
    00368       assert(st->owner == owner || (st->owner == OWNER_NONE && !st->IsBuoy()));
    00369
    00370       SetDParam(0, st->index);
    00371       SetDParam(1, st->facilities);
    00372       x = DrawString(xb, y, STR_3049_0, TC_FROMSTRING) + 5;
    00373
    00374       /* show cargo waiting and station ratings */
    00375       for (CargoID j = 0; j < NUM_CARGO; j++) {
    00376         if (!st->goods[j].cargo.Empty()) {
    00377           StationsWndShowStationRating(x, y, j, st->goods[j].cargo.Count(), st->goods[j].rating);
    00378           x += 20;
    00379         }
    00380       }
    00381       y += 10;
    00382     }
    00383   }
    00384
    00385   virtual void OnClick(Point pt, int widget)
    00386   {
    00387     switch (widget) {
    00388       case SLW_LIST: {
    00389         uint32 id_v = (pt.y - 41) / 10;
    00390
    00391         if (id_v >= this->vscroll.cap) return; // click out of bounds
    00392
    00393         id_v += this->vscroll.pos;
    00394
    00395         if (id_v >= this->stations.Length()) return; // click out of list bound
    00396
    00397         const Station *st = this->stations[id_v];
    00398         /* do not check HasStationInUse - it is slow and may be invalid */
    00399         assert(st->owner == (Owner)this->window_number || (st->owner == OWNER_NONE && !st->IsBuoy()));
    00400
    00401         if (_ctrl_pressed) {
    00402           ShowExtraViewPortWindow(st->xy);
    00403         } else {
    00404           ScrollMainWindowToTile(st->xy);
    00405         }
    00406         break;
    00407       }
    00408
    00409       case SLW_TRAIN:
    00410       case SLW_TRUCK:
    00411       case SLW_BUS:
    00412       case SLW_AIRPLANE:
    00413       case SLW_SHIP:
    00414         if (_ctrl_pressed) {
    00415           ToggleBit(this->facilities, widget - SLW_TRAIN);
    00416           this->ToggleWidgetLoweredState(widget);
    00417         } else {
    00418           uint i;
    00419           FOR_EACH_SET_BIT(i, this->facilities) {
    00420             this->RaiseWidget(i + SLW_TRAIN);
    00421           }
    00422           SetBit(this->facilities, widget - SLW_TRAIN);
    00423           this->LowerWidget(widget);
    00424         }
    00425         this->SetWidgetLoweredState(SLW_FACILALL, this->facilities == (FACIL_TRAIN | FACIL_TRUCK_STOP | FACIL_BUS_STOP | FACIL_AIRPORT | FACIL_DOCK));
    00426         this->stations.ForceRebuild();
    00427         this->SetDirty();
    00428         break;
    00429
    00430       case SLW_FACILALL:
    00431         for (uint i = 0; i < 5; i++) {
    00432           this->LowerWidget(i + SLW_TRAIN);
    00433         }
    00434         this->LowerWidget(SLW_FACILALL);
    00435
    00436         this->facilities = FACIL_TRAIN | FACIL_TRUCK_STOP | FACIL_BUS_STOP | FACIL_AIRPORT | FACIL_DOCK;
    00437         this->stations.ForceRebuild();
    00438         this->SetDirty();
    00439         break;
    00440
    00441       case SLW_CARGOALL: {
    00442         uint i = 0;
    00443         for (CargoID c = 0; c < NUM_CARGO; c++) {
    00444           if (!GetCargo(c)->IsValid()) continue;
    00445           this->LowerWidget(i + SLW_CARGOSTART);
    00446           i++;
    00447         }
    00448         this->LowerWidget(SLW_NOCARGOWAITING);
    00449         this->LowerWidget(SLW_CARGOALL);
    00450
    00451         this->cargo_filter = _cargo_mask;
    00452         this->include_empty = true;
    00453         this->stations.ForceRebuild();
    00454         this->SetDirty();
    00455         break;
    00456       }
    00457
    00458       case SLW_SORTBY: // flip sorting method asc/desc
    00459         this->stations.ToggleSortOrder();
    00460         this->flags4 |= WF_TIMEOUT_BEGIN;
    00461         this->LowerWidget(SLW_SORTBY);
    00462         this->SetDirty();
    00463         break;
    00464
    00465       case SLW_SORTDROPBTN: // select sorting criteria dropdown menu
    00466         ShowDropDownMenu(this, this->sorter_names, this->stations.SortType(), SLW_SORTDROPBTN, 0, 0);
    00467         break;
    00468
    00469       case SLW_NOCARGOWAITING:
    00470         if (_ctrl_pressed) {
    00471           this->include_empty = !this->include_empty;
    00472           this->ToggleWidgetLoweredState(SLW_NOCARGOWAITING);
    00473         } else {
    00474           for (uint i = SLW_CARGOSTART; i < this->widget_count; i++) {
    00475             this->RaiseWidget(i);
    00476           }
    00477
    00478           this->cargo_filter = 0;
    00479           this->include_empty = true;
    00480
    00481           this->LowerWidget(SLW_NOCARGOWAITING);
    00482         }
    00483         this->SetWidgetLoweredState(SLW_CARGOALL, this->cargo_filter == _cargo_mask && this->include_empty);
    00484         this->stations.ForceRebuild();
    00485         this->SetDirty();
    00486         break;
    00487
    00488       default:
    00489         if (widget >= SLW_CARGOSTART) { // change cargo_filter
    00490           /* Determine the selected cargo type */
    00491           CargoID c;
    00492           int i = 0;
    00493           for (c = 0; c < NUM_CARGO; c++) {
    00494             if (!GetCargo(c)->IsValid()) continue;
    00495             if (widget - SLW_CARGOSTART == i) break;
    00496             i++;
    00497           }
    00498
    00499           if (_ctrl_pressed) {
    00500             ToggleBit(this->cargo_filter, c);
    00501             this->ToggleWidgetLoweredState(widget);
    00502           } else {
    00503             for (uint i = SLW_CARGOSTART; i < this->widget_count; i++) {
    00504               this->RaiseWidget(i);
    00505             }
    00506             this->RaiseWidget(SLW_NOCARGOWAITING);
    00507
    00508             this->cargo_filter = 0;
    00509             this->include_empty = false;
    00510
    00511             SetBit(this->cargo_filter, c);
    00512             this->LowerWidget(widget);
    00513           }
    00514           this->SetWidgetLoweredState(SLW_CARGOALL, this->cargo_filter == _cargo_mask && this->include_empty);
    00515           this->stations.ForceRebuild();
    00516           this->SetDirty();
    00517         }
    00518         break;
    00519     }
    00520   }
    00521
    00522   virtual void OnDropdownSelect(int widget, int index)
    00523   {
    00524     if (this->stations.SortType() != index) {
    00525       this->stations.SetSortType(index);
    00526
    00527       /* Display the current sort variant */
    00528       this->widget[SLW_SORTDROPBTN].data = this->sorter_names[this->stations.SortType()];
    00529
    00530       this->SetDirty();
    00531     }
    00532   }
    00533
    00534   virtual void OnTick()
    00535   {
    00536     if (_pause_game != 0) return;
    00537     if (this->stations.NeedResort()) {
    00538       DEBUG(misc, 3, "Periodic rebuild station list company %d", this->window_number);
    00539       this->SetDirty();
    00540     }
    00541   }
    00542
    00543   virtual void OnTimeout()
    00544   {
    00545     this->RaiseWidget(SLW_SORTBY);
    00546     this->SetDirty();
    00547   }
    00548
    00549   virtual void OnResize(Point new_size, Point delta)
    00550   {
    00551     this->vscroll.cap += delta.y / 10;
    00552   }
    00553
    00554   virtual void OnInvalidateData(int data)
    00555   {
    00556     if (data == 0) {
    00557       this->stations.ForceRebuild();
    00558     } else {
    00559       this->stations.ForceResort();
    00560     }
    00561   }
    00562 };
    00563
    00564 Listing CompanyStationsWindow::last_sorting = {false, 0};
    00565 byte CompanyStationsWindow::facilities = FACIL_TRAIN | FACIL_TRUCK_STOP | FACIL_BUS_STOP | FACIL_AIRPORT | FACIL_DOCK;
    00566 bool CompanyStationsWindow::include_empty = true;
    00567 const uint32 CompanyStationsWindow::cargo_filter_max = UINT32_MAX;
    00568 uint32 CompanyStationsWindow::cargo_filter = UINT32_MAX;
    00569 const Station *CompanyStationsWindow::last_station = NULL;
    00570
    00571 /* Availible station sorting functions */
    00572 GUIStationList::SortFunction *const CompanyStationsWindow::sorter_funcs[] = {
    00573   &StationNameSorter,
    00574   &StationTypeSorter,
    00575   &StationWaitingSorter,
    00576   &StationRatingMaxSorter
    00577 };
    00578
    00579 /* Names of the sorting functions */
    00580 const StringID CompanyStationsWindow::sorter_names[] = {
    00581   STR_SORT_BY_DROPDOWN_NAME,
    00582   STR_SORT_BY_FACILITY,
    00583   STR_SORT_BY_WAITING,
    00584   STR_SORT_BY_RATING_MAX,
    00585   INVALID_STRING_ID
    00586 };
    00587
    00588
    00589 static const Widget _company_stations_widgets[] = {
    00590 {   WWT_CLOSEBOX,   RESIZE_NONE,  COLOUR_GREY,     0,    10,     0,    13, STR_00C5,          STR_018B_CLOSE_WINDOW},            // SLW_CLOSEBOX
    00591 {    WWT_CAPTION,  RESIZE_RIGHT,  COLOUR_GREY,    11,   345,     0,    13, STR_3048_STATIONS, STR_018C_WINDOW_TITLE_DRAG_THIS},  // SLW_CAPTION
    00592 {  WWT_STICKYBOX,     RESIZE_LR,  COLOUR_GREY,   346,   357,     0,    13, 0x0,               STR_STICKY_BUTTON},                // SLW_STICKY
    00593 {      WWT_PANEL,     RESIZE_RB,  COLOUR_GREY,     0,   345,    37,   161, 0x0,               STR_3057_STATION_NAMES_CLICK_ON},  // SLW_LIST
    00594 {  WWT_SCROLLBAR,    RESIZE_LRB,  COLOUR_GREY,   346,   357,    37,   149, 0x0,               STR_0190_SCROLL_BAR_SCROLLS_LIST}, // SLW_SCROLLBAR
    00595 {  WWT_RESIZEBOX,   RESIZE_LRTB,  COLOUR_GREY,   346,   357,   150,   161, 0x0,               STR_RESIZE_BUTTON},                // SLW_RESIZE
    00596
    00597 {    WWT_TEXTBTN,   RESIZE_NONE,  COLOUR_GREY,     0,    13,    14,    24, STR_TRAIN,         STR_USE_CTRL_TO_SELECT_MORE},      // SLW_TRAIN
    00598 {    WWT_TEXTBTN,   RESIZE_NONE,  COLOUR_GREY,    14,    27,    14,    24, STR_LORRY,         STR_USE_CTRL_TO_SELECT_MORE},      // SLW_TRUCK
    00599 {    WWT_TEXTBTN,   RESIZE_NONE,  COLOUR_GREY,    28,    41,    14,    24, STR_BUS,           STR_USE_CTRL_TO_SELECT_MORE},      // SLW_BUS
    00600 {    WWT_TEXTBTN,   RESIZE_NONE,  COLOUR_GREY,    42,    55,    14,    24, STR_PLANE,         STR_USE_CTRL_TO_SELECT_MORE},      // SLW_AIRPLANE
    00601 {    WWT_TEXTBTN,   RESIZE_NONE,  COLOUR_GREY,    56,    69,    14,    24, STR_SHIP,          STR_USE_CTRL_TO_SELECT_MORE},      // SLW_SHIP
    00602 {      WWT_PANEL,   RESIZE_NONE,  COLOUR_GREY,    70,    83,    14,    24, 0x0,               STR_SELECT_ALL_FACILITIES},        // SLW_FACILALL
    00603
    00604 {      WWT_PANEL,   RESIZE_NONE,  COLOUR_GREY,    83,    88,    14,    24, 0x0,               STR_NULL},                         // SLW_PAN_BETWEEN
    00605 {      WWT_PANEL,   RESIZE_NONE,  COLOUR_GREY,    89,   102,    14,    24, 0x0,               STR_NO_WAITING_CARGO},             // SLW_NOCARGOWAITING
    00606 {      WWT_PANEL,   RESIZE_NONE,  COLOUR_GREY,   103,   116,    14,    24, 0x0,               STR_SELECT_ALL_TYPES},             // SLW_CARGOALL
    00607 {      WWT_PANEL,  RESIZE_RIGHT,  COLOUR_GREY,   117,   357,    14,    24, 0x0,               STR_NULL},                         // SLW_PAN_RIGHT
    00608
    00609 {    WWT_TEXTBTN,   RESIZE_NONE,  COLOUR_GREY,     0,    80,    25,    36, STR_SORT_BY,       STR_SORT_ORDER_TIP},               // SLW_SORTBY
    00610 {   WWT_DROPDOWN,   RESIZE_NONE,  COLOUR_GREY,    81,   243,    25,    36, 0x0,               STR_SORT_CRITERIA_TIP},            // SLW_SORTDROPBTN
    00611 {      WWT_PANEL,  RESIZE_RIGHT,  COLOUR_GREY,   244,   357,    25,    36, 0x0,               STR_NULL},                         // SLW_PAN_SORT_RIGHT
    00612 {   WIDGETS_END},
    00613 };
    00614
    00615 static const WindowDesc _company_stations_desc = {
    00616   WDP_AUTO, WDP_AUTO, 358, 162, 358, 162,
    00617   WC_STATION_LIST, WC_NONE,
    00618   WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_STICKY_BUTTON | WDF_RESIZABLE,
    00619   _company_stations_widgets,
    00620 };
    00621
    00627 void ShowCompanyStations(CompanyID company)
    00628 {
    00629   if (!IsValidCompanyID(company)) return;
    00630
    00631   AllocateWindowDescFront<CompanyStationsWindow>(&_company_stations_desc, company);
    00632 }
    00633
    00634 static const Widget _station_view_widgets[] = {
    00635 {   WWT_CLOSEBOX,   RESIZE_NONE,  COLOUR_GREY,     0,    10,     0,    13, STR_00C5,          STR_018B_CLOSE_WINDOW},                // SVW_CLOSEBOX
    00636 {    WWT_CAPTION,  RESIZE_RIGHT,  COLOUR_GREY,    11,   236,     0,    13, STR_300A_0,        STR_018C_WINDOW_TITLE_DRAG_THIS},
    00637 {  WWT_STICKYBOX,     RESIZE_LR,  COLOUR_GREY,   237,   248,     0,    13, 0x0,               STR_STICKY_BUTTON},
    00638 {      WWT_PANEL,     RESIZE_RB,  COLOUR_GREY,     0,   236,    14,    65, 0x0,               STR_NULL},                             // SVW_WAITING
    00639 {  WWT_SCROLLBAR,    RESIZE_LRB,  COLOUR_GREY,   237,   248,    14,    65, 0x0,               STR_0190_SCROLL_BAR_SCROLLS_LIST},
    00640 {      WWT_PANEL,    RESIZE_RTB,  COLOUR_GREY,     0,   248,    66,    97, 0x0,               STR_NULL},                             // SVW_ACCEPTLIST / SVW_RATINGLIST
    00641 { WWT_PUSHTXTBTN,     RESIZE_TB,  COLOUR_GREY,     0,    59,    98,   109, STR_00E4_LOCATION, STR_3053_CENTER_MAIN_VIEW_ON_STATION}, // SVW_LOCATION
    00642 { WWT_PUSHTXTBTN,     RESIZE_TB,  COLOUR_GREY,    60,   120,    98,   109, STR_3032_RATINGS,  STR_3054_SHOW_STATION_RATINGS},        // SVW_RATINGS / SVW_ACCEPTS
    00643 { WWT_PUSHTXTBTN,    RESIZE_RTB,  COLOUR_GREY,   121,   180,    98,   109, STR_0130_RENAME,   STR_3055_CHANGE_NAME_OF_STATION},      // SVW_RENAME
    00644 { WWT_PUSHTXTBTN,   RESIZE_LRTB,  COLOUR_GREY,   181,   194,    98,   109, STR_TRAIN,         STR_SCHEDULED_TRAINS_TIP },            // SVW_TRAINS
    00645 { WWT_PUSHTXTBTN,   RESIZE_LRTB,  COLOUR_GREY,   195,   208,    98,   109, STR_LORRY,         STR_SCHEDULED_ROAD_VEHICLES_TIP },     // SVW_ROADVEHS
    00646 { WWT_PUSHTXTBTN,   RESIZE_LRTB,  COLOUR_GREY,   209,   222,    98,   109, STR_PLANE,         STR_SCHEDULED_AIRCRAFT_TIP },          // SVW_PLANES
    00647 { WWT_PUSHTXTBTN,   RESIZE_LRTB,  COLOUR_GREY,   223,   236,    98,   109, STR_SHIP,          STR_SCHEDULED_SHIPS_TIP },             // SVW_SHIPS
    00648 {  WWT_RESIZEBOX,   RESIZE_LRTB,  COLOUR_GREY,   237,   248,    98,   109, 0x0,               STR_RESIZE_BUTTON},
    00649 {   WIDGETS_END},
    00650 };
    00651
    00652 SpriteID GetCargoSprite(CargoID i)
    00653 {
    00654   const CargoSpec *cs = GetCargo(i);
    00655   SpriteID sprite;
    00656
    00657   if (cs->sprite == 0xFFFF) {
    00658     /* A value of 0xFFFF indicates we should draw a custom icon */
    00659     sprite = GetCustomCargoSprite(cs);
    00660   } else {
    00661     sprite = cs->sprite;
    00662   }
    00663
    00664   if (sprite == 0) sprite = SPR_CARGO_GOODS;
    00665
    00666   return sprite;
    00667 }
    00668
    00677 static void DrawCargoIcons(CargoID i, uint waiting, int x, int y, uint width)
    00678 {
    00679   uint num = min((waiting + 5) / 10, width / 10); // maximum is width / 10 icons so it won't overflow
    00680   if (num == 0) return;
    00681
    00682   SpriteID sprite = GetCargoSprite(i);
    00683
    00684   do {
    00685     DrawSprite(sprite, PAL_NONE, x, y);
    00686     x += 10;
    00687   } while (--num);
    00688 }
    00689
    00690 struct CargoData {
    00691   CargoID cargo;
    00692   StationID source;
    00693   uint count;
    00694
    00695   CargoData(CargoID cargo, StationID source, uint count) :
    00696     cargo(cargo),
    00697     source(source),
    00698     count(count)
    00699   { }
    00700 };
    00701
    00702 typedef std::list<CargoData> CargoDataList;
    00703
    00707 struct StationViewWindow : public Window {
    00708   uint32 cargo;
    00709   uint16 cargo_rows[NUM_CARGO];
    00710
    00711   StationViewWindow(const WindowDesc *desc, WindowNumber window_number) : Window(desc, window_number)
    00712   {
    00713     Owner owner = GetStation(window_number)->owner;
    00714     if (owner != OWNER_NONE) this->owner = owner;
    00715     this->vscroll.cap = 5;
    00716     this->resize.step_height = 10;
    00717
    00718     this->FindWindowPlacementAndResize(desc);
    00719   }
    00720
    00721   ~StationViewWindow()
    00722   {
    00723     WindowNumber wno =
    00724       (this->window_number << 16) | VLW_STATION_LIST | GetStation(this->window_number)->owner;
    00725
    00726     DeleteWindowById(WC_TRAINS_LIST, wno | (VEH_TRAIN << 11), false);
    00727     DeleteWindowById(WC_ROADVEH_LIST, wno | (VEH_ROAD << 11), false);
    00728     DeleteWindowById(WC_SHIPS_LIST, wno | (VEH_SHIP << 11), false);
    00729     DeleteWindowById(WC_AIRCRAFT_LIST, wno | (VEH_AIRCRAFT << 11), false);
    00730   }
    00731
    00732   virtual void OnPaint()
    00733   {
    00734     StationID station_id = this->window_number;
    00735     const Station *st = GetStation(station_id);
    00736     CargoDataList cargolist;
    00737     uint32 transfers = 0;
    00738
    00739     /* count types of cargos waiting in station */
    00740     for (CargoID i = 0; i < NUM_CARGO; i++) {
    00741       if (st->goods[i].cargo.Empty()) {
    00742         this->cargo_rows[i] = 0;
    00743       } else {
    00744         /* Add an entry for total amount of cargo of this type waiting. */
    00745         cargolist.push_back(CargoData(i, INVALID_STATION, st->goods[i].cargo.Count()));
    00746
    00747         /* Set the row for this cargo entry for the expand/hide button */
    00748         this->cargo_rows[i] = (uint16)cargolist.size();
    00749
    00750         /* Add an entry for each distinct cargo source. */
    00751         const CargoList::List *packets = st->goods[i].cargo.Packets();
    00752         for (CargoList::List::const_iterator it = packets->begin(); it != packets->end(); it++) {
    00753           const CargoPacket *cp = *it;
    00754           if (cp->source != station_id) {
    00755             bool added = false;
    00756
    00757             /* Enable the expand/hide button for this cargo type */
    00758             SetBit(transfers, i);
    00759
    00760             /* Don't add cargo lines if not expanded */
    00761             if (!HasBit(this->cargo, i)) break;
    00762
    00763             /* Check if we already have this source in the list */
    00764             for (CargoDataList::iterator jt = cargolist.begin(); jt != cargolist.end(); jt++) {
    00765               CargoData *cd = &(*jt);
    00766               if (cd->cargo == i && cd->source == cp->source) {
    00767                 cd->count += cp->count;
    00768                 added = true;
    00769                 break;
    00770               }
    00771             }
    00772
    00773             if (!added) cargolist.push_back(CargoData(i, cp->source, cp->count));
    00774           }
    00775         }
    00776       }
    00777     }
    00778     SetVScrollCount(this, (int)cargolist.size() + 1); // update scrollbar
    00779
    00780     /* disable some buttons */
    00781     this->SetWidgetDisabledState(SVW_RENAME,   st->owner != _local_company);
    00782     this->SetWidgetDisabledState(SVW_TRAINS,   !(st->facilities & FACIL_TRAIN));
    00783     this->SetWidgetDisabledState(SVW_ROADVEHS, !(st->facilities & FACIL_TRUCK_STOP) && !(st->facilities & FACIL_BUS_STOP));
    00784     this->SetWidgetDisabledState(SVW_PLANES,   !(st->facilities & FACIL_AIRPORT));
    00785     this->SetWidgetDisabledState(SVW_SHIPS,    !(st->facilities & FACIL_DOCK));
    00786
    00787     SetDParam(0, st->index);
    00788     SetDParam(1, st->facilities);
    00789     this->DrawWidgets();
    00790
    00791     int x = 2;
    00792     int y = 15;
    00793     int pos = this->vscroll.pos;
    00794
    00795     uint width = this->widget[SVW_WAITING].right - this->widget[SVW_WAITING].left - 4;
    00796     int maxrows = this->vscroll.cap;
    00797
    00798     StringID str;
    00799
    00800     if (--pos < 0) {
    00801       str = STR_00D0_NOTHING;
    00802       for (CargoID i = 0; i < NUM_CARGO; i++) {
    00803         if (!st->goods[i].cargo.Empty()) str = STR_EMPTY;
    00804       }
    00805       SetDParam(0, str);
    00806       DrawString(x, y, STR_0008_WAITING, TC_FROMSTRING);
    00807       y += 10;
    00808     }
    00809
    00810     for (CargoDataList::const_iterator it = cargolist.begin(); it != cargolist.end() && pos > -maxrows; ++it) {
    00811       if (--pos < 0) {
    00812         const CargoData *cd = &(*it);
    00813         if (cd->source == INVALID_STATION) {
    00814           /* Heading */
    00815           DrawCargoIcons(cd->cargo, cd->count, x, y, width);
    00816           SetDParam(0, cd->cargo);
    00817           SetDParam(1, cd->count);
    00818           if (HasBit(transfers, cd->cargo)) {
    00819             /* This cargo has transfers waiting so show the expand or shrink 'button' */
    00820             const char *sym = HasBit(this->cargo, cd->cargo) ? "-" : "+";
    00821             DrawStringRightAligned(x + width - 8, y, STR_0009, TC_FROMSTRING);
    00822             DoDrawString(sym, x + width - 6, y, TC_YELLOW);
    00823           } else {
    00824             DrawStringRightAligned(x + width, y, STR_0009, TC_FROMSTRING);
    00825           }
    00826         } else {
    00827           SetDParam(0, cd->cargo);
    00828           SetDParam(1, cd->count);
    00829           SetDParam(2, cd->source);
    00830           DrawStringRightAlignedTruncated(x + width, y, STR_EN_ROUTE_FROM, TC_FROMSTRING, width);
    00831         }
    00832
    00833         y += 10;
    00834       }
    00835     }
    00836
    00837     if (this->widget[SVW_ACCEPTS].data == STR_3032_RATINGS) { // small window with list of accepted cargo
    00838       char string[512];
    00839       char *b = string;
    00840       bool first = true;
    00841
    00842       b = InlineString(b, STR_000C_ACCEPTS);
    00843
    00844       for (CargoID i = 0; i < NUM_CARGO; i++) {
    00845         if (b >= lastof(string) - (1 + 2 * 4)) break; // ',' or ' ' and two calls to Utf8Encode()
    00846         if (HasBit(st->goods[i].acceptance_pickup, GoodsEntry::ACCEPTANCE)) {
    00847           if (first) {
    00848             first = false;
    00849           } else {
    00850             /* Add a comma if this is not the first item */
    00851             *b++ = ',';
    00852             *b++ = ' ';
    00853           }
    00854           b = InlineString(b, GetCargo(i)->name);
    00855         }
    00856       }
    00857
    00858       /* If first is still true then no cargo is accepted */
    00859       if (first) b = InlineString(b, STR_00D0_NOTHING);
    00860
    00861       *b = '\0';
    00862
    00863       /* Make sure we detect any buffer overflow */
    00864       assert(b < endof(string));
    00865
    00866       SetDParamStr(0, string);
    00867       DrawStringMultiLine(2, this->widget[SVW_ACCEPTLIST].top + 1, STR_JUST_RAW_STRING, this->widget[SVW_ACCEPTLIST].right - this->widget[SVW_ACCEPTLIST].left);
    00868     } else { // extended window with list of cargo ratings
    00869       y = this->widget[SVW_RATINGLIST].top + 1;
    00870
    00871       DrawString(2, y, STR_3034_LOCAL_RATING_OF_TRANSPORT, TC_FROMSTRING);
    00872       y += 10;
    00873
    00874       for (CargoID i = 0; i < NUM_CARGO; i++) {
    00875         const CargoSpec *cs = GetCargo(i);
    00876         if (!cs->IsValid()) continue;
    00877
    00878         const GoodsEntry *ge = &st->goods[i];
    00879         if (!HasBit(ge->acceptance_pickup, GoodsEntry::PICKUP)) continue;
    00880
    00881         SetDParam(0, cs->name);
    00882         SetDParam(2, ge->rating * 101 >> 8);
    00883         SetDParam(1, STR_3035_APPALLING + (ge->rating >> 5));
    00884         DrawString(8, y, STR_303D, TC_FROMSTRING);
    00885         y += 10;
    00886       }
    00887     }
    00888   }
    00889
    00890   void HandleCargoWaitingClick(int row)
    00891   {
    00892     if (row == 0) return;
    00893
    00894     for (CargoID c = 0; c < NUM_CARGO; c++) {
    00895       if (this->cargo_rows[c] == row) {
    00896         ToggleBit(this->cargo, c);
    00897         this->InvalidateWidget(SVW_WAITING);
    00898         break;
    00899       }
    00900     }
    00901   }
    00902
    00903   virtual void OnClick(Point pt, int widget)
    00904   {
    00905     switch (widget) {
    00906       case SVW_WAITING:
    00907         this->HandleCargoWaitingClick((pt.y - this->widget[SVW_WAITING].top) / 10 + this->vscroll.pos);
    00908         break;
    00909
    00910       case SVW_LOCATION:
    00911         if (_ctrl_pressed) {
    00912           ShowExtraViewPortWindow(GetStation(this->window_number)->xy);
    00913         } else {
    00914           ScrollMainWindowToTile(GetStation(this->window_number)->xy);
    00915         }
    00916         break;
    00917
    00918       case SVW_RATINGS:
    00919         this->SetDirty();
    00920
    00921         if (this->widget[SVW_RATINGS].data == STR_3032_RATINGS) {
    00922           /* Switch to ratings view */
    00923           this->widget[SVW_RATINGS].data = STR_3033_ACCEPTS;
    00924           this->widget[SVW_RATINGS].tooltips = STR_3056_SHOW_LIST_OF_ACCEPTED_CARGO;
    00925           ResizeWindowForWidget(this, SVW_ACCEPTLIST, 0, 100);
    00926         } else {
    00927           /* Switch to accepts view */
    00928           this->widget[SVW_RATINGS].data = STR_3032_RATINGS;
    00929           this->widget[SVW_RATINGS].tooltips = STR_3054_SHOW_STATION_RATINGS;
    00930           ResizeWindowForWidget(this, SVW_ACCEPTLIST, 0, -100);
    00931         }
    00932
    00933         this->SetDirty();
    00934         break;
    00935
    00936       case SVW_RENAME:
    00937         SetDParam(0, this->window_number);
    00938         ShowQueryString(STR_STATION, STR_3030_RENAME_STATION_LOADING, MAX_LENGTH_STATION_NAME_BYTES, MAX_LENGTH_STATION_NAME_PIXELS, this, CS_ALPHANUMERAL, QSF_ENABLE_DEFAULT);
    00939         break;
    00940
    00941       case SVW_TRAINS: { // Show a list of scheduled trains to this station
    00942         const Station *st = GetStation(this->window_number);
    00943         ShowVehicleListWindow(st->owner, VEH_TRAIN, (StationID)this->window_number);
    00944         break;
    00945       }
    00946
    00947       case SVW_ROADVEHS: { // Show a list of scheduled road-vehicles to this station
    00948         const Station *st = GetStation(this->window_number);
    00949         ShowVehicleListWindow(st->owner, VEH_ROAD, (StationID)this->window_number);
    00950         break;
    00951       }
    00952
    00953       case SVW_PLANES: { // Show a list of scheduled aircraft to this station
    00954         const Station *st = GetStation(this->window_number);
    00955         /* Since oilrigs have no owners, show the scheduled aircraft of local company */
    00956         Owner owner = (st->owner == OWNER_NONE) ? _local_company : st->owner;
    00957         ShowVehicleListWindow(owner, VEH_AIRCRAFT, (StationID)this->window_number);
    00958         break;
    00959       }
    00960
    00961       case SVW_SHIPS: { // Show a list of scheduled ships to this station
    00962         const Station *st = GetStation(this->window_number);
    00963         /* Since oilrigs/bouys have no owners, show the scheduled ships of local company */
    00964         Owner owner = (st->owner == OWNER_NONE) ? _local_company : st->owner;
    00965         ShowVehicleListWindow(owner, VEH_SHIP, (StationID)this->window_number);
    00966         break;
    00967       }
    00968     }
    00969   }
    00970
    00971   virtual void OnQueryTextFinished(char *str)
    00972   {
    00973     if (str == NULL) return;
    00974
    00975     DoCommandP(0, this->window_number, 0, CMD_RENAME_STATION | CMD_MSG(STR_3031_CAN_T_RENAME_STATION), NULL, str);
    00976   }
    00977
    00978   virtual void OnResize(Point new_size, Point delta)
    00979   {
    00980     if (delta.x != 0) ResizeButtons(this, SVW_LOCATION, SVW_RENAME);
    00981     this->vscroll.cap += delta.y / (int)this->resize.step_height;
    00982   }
    00983 };
    00984
    00985
    00986 static const WindowDesc _station_view_desc = {
    00987   WDP_AUTO, WDP_AUTO, 249, 110, 249, 110,
    00988   WC_STATION_VIEW, WC_NONE,
    00989   WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE,
    00990   _station_view_widgets,
    00991 };
    00992
    00998 void ShowStationViewWindow(StationID station)
    00999 {
    01000   AllocateWindowDescFront<StationViewWindow>(&_station_view_desc, station);
    01001 }
    01002
    01003 static SmallVector<StationID, 8> _stations_nearby_list;
    01004 static SmallMap<TileIndex, StationID, 8> _deleted_stations_nearby;
    01005
    01007 struct FindNearbyStationContext {
    01008   TileIndex tile;
    01009   uint      w;
    01010   uint      h;
    01011 };
    01012
    01019 static bool AddNearbyStation(TileIndex tile, void *user_data)
    01020 {
    01021   FindNearbyStationContext *ctx = (FindNearbyStationContext *)user_data;
    01022
    01023   /* First check if there was a deleted station here */
    01024   SmallPair<TileIndex, StationID> *dst = _deleted_stations_nearby.Find(tile);
    01025   if (dst != _deleted_stations_nearby.End()) {
    01026     _stations_nearby_list.Include(dst->second);
    01027     return false;
    01028   }
    01029
    01030   /* Check if own station and if we stay within station spread */
    01031   if (!IsTileType(tile, MP_STATION)) return false;
    01032
    01033   StationID sid = GetStationIndex(tile);
    01034   Station *st = GetStation(sid);
    01035   if (st->owner != _local_company || _stations_nearby_list.Contains(sid)) return false;
    01036
    01037   if (st->rect.BeforeAddRect(ctx->tile, ctx->w, ctx->h, StationRect::ADD_TEST)) {
    01038     *_stations_nearby_list.Append() = sid;
    01039   }
    01040
    01041   return false; // We want to include *all* nearby stations
    01042 }
    01043
    01054 static const Station *FindStationsNearby(TileIndex tile, int w, int h, bool distant_join)
    01055 {
    01056   FindNearbyStationContext ctx;
    01057   ctx.tile = tile;
    01058   ctx.w = w;
    01059   ctx.h = h;
    01060
    01061   _stations_nearby_list.Clear();
    01062   _deleted_stations_nearby.Clear();
    01063
    01064   /* Check the inside, to return, if we sit on another station */
    01065   BEGIN_TILE_LOOP(t, w, h, tile)
    01066     if (t < MapSize() && IsTileType(t, MP_STATION)) return GetStationByTile(t);
    01067   END_TILE_LOOP(t, w, h, tile)
    01068
    01069   /* Look for deleted stations */
    01070   const Station *st;
    01071   FOR_ALL_STATIONS(st) {
    01072     if (st->facilities == 0 && st->owner == _local_company) {
    01073       /* Include only within station spread (yes, it is strictly less than) */
    01074       if (max(DistanceMax(tile, st->xy), DistanceMax(TILE_ADDXY(tile, w - 1, h - 1), st->xy)) < _settings_game.station.station_spread) {
    01075         _deleted_stations_nearby.Insert(st->xy, st->index);
    01076
    01077         /* Add the station when it's within where we're going to build */
    01078         if (IsInsideBS(TileX(st->xy), TileX(ctx.tile), ctx.w) &&
    01079             IsInsideBS(TileY(st->xy), TileY(ctx.tile), ctx.h)) {
    01080           AddNearbyStation(st->xy, &ctx);
    01081         }
    01082       }
    01083     }
    01084   }
    01085
    01086   /* Only search tiles where we have a chance to stay within the station spread.
    01087    * The complete check needs to be done in the callback as we don't know the
    01088    * extent of the found station, yet. */
    01089   if (distant_join && min(w, h) >= _settings_game.station.station_spread) return NULL;
    01090   uint max_dist = distant_join ? _settings_game.station.station_spread - min(w, h) : 1;
    01091
    01092   tile = TILE_ADD(ctx.tile, TileOffsByDir(DIR_N));
    01093   CircularTileSearch(&tile, max_dist, w, h, AddNearbyStation, &ctx);
    01094
    01095   return NULL;
    01096 }
    01097
    01098 enum JoinStationWidgets {
    01099   JSW_WIDGET_CLOSEBOX = 0,
    01100   JSW_WIDGET_CAPTION,
    01101   JSW_PANEL,
    01102   JSW_SCROLLBAR,
    01103   JSW_EMPTY,
    01104   JSW_RESIZEBOX,
    01105 };
    01106
    01107 static const Widget _select_station_widgets[] = {
    01108 {   WWT_CLOSEBOX,   RESIZE_NONE, COLOUR_DARK_GREEN,     0,    10,     0,    13, STR_00C5,                        STR_018B_CLOSE_WINDOW},
    01109 {    WWT_CAPTION,  RESIZE_RIGHT, COLOUR_DARK_GREEN,    11,   199,     0,    13, STR_SELECT_STATION_TO_JOIN,      STR_018C_WINDOW_TITLE_DRAG_THIS},
    01110 {      WWT_PANEL,     RESIZE_RB, COLOUR_DARK_GREEN,     0,   187,    14,    79, 0x0,                             STR_NULL},
    01111 {  WWT_SCROLLBAR,    RESIZE_LRB, COLOUR_DARK_GREEN,   188,   199,    14,    79, 0x0,                             STR_0190_SCROLL_BAR_SCROLLS_LIST},
    01112 {      WWT_PANEL,    RESIZE_RTB, COLOUR_DARK_GREEN,     0,   187,    80,    91, 0x0,                             STR_NULL},
    01113 {  WWT_RESIZEBOX,   RESIZE_LRTB, COLOUR_DARK_GREEN,   188,   199,    80,    91, 0x0,                             STR_RESIZE_BUTTON},
    01114 {   WIDGETS_END},
    01115 };
    01116
    01117 struct SelectStationWindow : Window {
    01118   CommandContainer select_station_cmd;
    01119   TileIndex tile;
    01120   int size_x;
    01121   int size_y;
    01122
    01123   SelectStationWindow(const WindowDesc *desc, CommandContainer cmd, int w, int h) :
    01124     Window(desc, 0),
    01125     select_station_cmd(cmd),
    01126     tile(cmd.tile),
    01127     size_x(w),
    01128     size_y(h)
    01129   {
    01130     this->vscroll.cap = 6;
    01131     this->resize.step_height = 10;
    01132
    01133     FindStationsNearby(this->tile, this->size_x, this->size_y, true);
    01134
    01135     this->FindWindowPlacementAndResize(desc);
    01136   }
    01137
    01138   virtual void OnPaint()
    01139   {
    01140     SetVScrollCount(this, _stations_nearby_list.Length() + 1);
    01141
    01142     this->DrawWidgets();
    01143
    01144     uint y = 17;
    01145     if (this->vscroll.pos == 0) {
    01146       DrawStringTruncated(3, y, STR_CREATE_SPLITTED_STATION, TC_FROMSTRING, this->widget[JSW_PANEL].right - 5);
    01147       y += 10;
    01148     }
    01149
    01150     for (uint i = max<uint>(1, this->vscroll.pos); i <= _stations_nearby_list.Length(); ++i, y += 10) {
    01151       /* Don't draw anything if it extends past the end of the window. */
    01152       if (i - this->vscroll.pos >= this->vscroll.cap) break;
    01153
    01154       const Station *st = GetStation(_stations_nearby_list[i - 1]);
    01155       SetDParam(0, st->index);
    01156       SetDParam(1, st->facilities);
    01157       DrawStringTruncated(3, y, STR_3049_0, TC_FROMSTRING, this->widget[JSW_PANEL].right - 5);
    01158     }
    01159   }
    01160
    01161   virtual void OnClick(Point pt, int widget)
    01162   {
    01163     if (widget != JSW_PANEL) return;
    01164
    01165     uint32 st_index = (pt.y - 16) / 10 + this->vscroll.pos;
    01166     bool distant_join = (st_index > 0);
    01167     if (distant_join) st_index--;
    01168
    01169     if (distant_join && st_index >= _stations_nearby_list.Length()) return;
    01170
    01171     /* Insert station to be joined into stored command */
    01172     SB(this->select_station_cmd.p2, 16, 16,
    01173        (distant_join ? _stations_nearby_list[st_index] : INVALID_STATION));
    01174
    01175     /* Execute stored Command */
    01176     DoCommandP(&this->select_station_cmd);
    01177
    01178     /* Close Window; this might cause double frees! */
    01179     DeleteWindowById(WC_SELECT_STATION, 0);
    01180   }
    01181
    01182   virtual void OnTick()
    01183   {
    01184     if (_thd.dirty & 2) {
    01185       _thd.dirty &= ~2;
    01186       this->SetDirty();
    01187     }
    01188   }
    01189
    01190   virtual void OnResize(Point new_size, Point delta)
    01191   {
    01192     this->vscroll.cap = (this->widget[JSW_PANEL].bottom - this->widget[JSW_PANEL].top) / 10;
    01193   }
    01194
    01195   virtual void OnInvalidateData(int data)
    01196   {
    01197     FindStationsNearby(this->tile, this->size_x, this->size_y, true);
    01198     this->SetDirty();
    01199   }
    01200 };
    01201
    01202 static const WindowDesc _select_station_desc = {
    01203   WDP_AUTO, WDP_AUTO, 200, 92, 200, 182,
    01204   WC_SELECT_STATION, WC_NONE,
    01205   WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_RESIZABLE | WDF_CONSTRUCTION,
    01206   _select_station_widgets,
    01207 };
    01208
    01209
    01217 static bool StationJoinerNeeded(CommandContainer cmd, int w, int h)
    01218 {
    01219   /* Only show selection if distant join is enabled in the settings */
    01220   if (!_settings_game.station.distant_join_stations) return false;
    01221
    01222   /* If a window is already opened and we didn't ctrl-click,
    01223    * return true (i.e. just flash the old window) */
    01224   Window *selection_window = FindWindowById(WC_SELECT_STATION, 0);
    01225   if (selection_window != NULL) {
    01226     if (!_ctrl_pressed) return true;
    01227
    01228     /* Abort current distant-join and start new one */
    01229     delete selection_window;
    01230     UpdateTileSelection();
    01231   }
    01232
    01233   /* only show the popup, if we press ctrl */
    01234   if (!_ctrl_pressed) return false;
    01235
    01236   /* Now check if we could build there */
    01237   if (CmdFailed(DoCommand(&cmd, CommandFlagsToDCFlags(GetCommandFlags(cmd.cmd))))) return false;
    01238
    01239   /* Test for adjacent station or station below selection.
    01240    * If adjacent-stations is disabled and we are building next to a station, do not show the selection window.
    01241    * but join the other station immediatelly. */
    01242   const Station *st = FindStationsNearby(cmd.tile, w, h, false);
    01243   return st == NULL && (_settings_game.station.adjacent_stations || _stations_nearby_list.Length() == 0);
    01244 }
    01245
    01252 void ShowSelectStationIfNeeded(CommandContainer cmd, int w, int h)
    01253 {
    01254   if (StationJoinerNeeded(cmd, w, h)) {
    01255     if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace();
    01256     if (BringWindowToFrontById(WC_SELECT_STATION, 0)) return;
    01257     new SelectStationWindow(&_select_station_desc, cmd, w, h);
    01258   } else {
    01259     DoCommandP(&cmd);
    01260   }
    01261 }