# station_gui.cpp

Go to the documentation of this file.

    00001 /* $Id: station_gui.cpp 14268 2008-09-07 22:04:39Z 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 "station.h"
    00012 #include "player_func.h"
    00013 #include "economy_func.h"
    00014 #include "town.h"
    00015 #include "command_func.h"
    00016 #include "variables.h"
    00017 #include "vehicle_gui.h"
    00018 #include "cargotype.h"
    00019 #include "station_gui.h"
    00020 #include "station.h"
    00021 #include "strings_func.h"
    00022 #include "core/alloc_func.hpp"
    00023 #include "window_func.h"
    00024 #include "viewport_func.h"
    00025 #include "gfx_func.h"
    00026 #include "widgets/dropdown_func.h"
    00027
    00028 #include "table/strings.h"
    00029 #include "table/sprites.h"
    00030
    00031 typedef int CDECL StationSortListingTypeFunction(const void*, const void*);
    00032
    00033 static StationSortListingTypeFunction StationNameSorter;
    00034 static StationSortListingTypeFunction StationTypeSorter;
    00035 static StationSortListingTypeFunction StationWaitingSorter;
    00036 static StationSortListingTypeFunction StationRatingMaxSorter;
    00037
    00038 bool _station_show_coverage;
    00039
    00054 static void StationsWndShowStationRating(int x, int y, CargoID type, uint amount, byte rating)
    00055 {
    00056   static const uint units_full  = 576;
    00057   static const uint rating_full = 224;
    00058
    00059   const CargoSpec *cs = GetCargo(type);
    00060   if (!cs->IsValid()) return;
    00061
    00062   int colour = cs->rating_colour;
    00063   uint w = (minu(amount, units_full) + 5) / 36;
    00064
    00065   /* Draw total cargo (limited) on station (fits into 16 pixels) */
    00066   if (w != 0) GfxFillRect(x, y, x + w - 1, y + 6, colour);
    00067
    00068   /* Draw a one pixel-wide bar of additional cargo meter, useful
    00069    * for stations with only a small amount (<=30) */
    00070   if (w == 0) {
    00071     uint rest = amount / 5;
    00072     if (rest != 0) {
    00073       w += x;
    00074       GfxFillRect(w, y + 6 - rest, w, y + 6, colour);
    00075     }
    00076   }
    00077
    00078   DrawString(x + 1, y, cs->abbrev, TC_BLACK);
    00079
    00080   /* Draw green/red ratings bar (fits into 14 pixels) */
    00081   y += 8;
    00082   GfxFillRect(x + 1, y, x + 14, y, 0xB8);
    00083   rating = minu(rating, rating_full) / 16;
    00084   if (rating != 0) GfxFillRect(x + 1, y, x + rating, y, 0xD0);
    00085 }
    00086
    00087 const StringID _station_sort_listing[] = {
    00088   STR_SORT_BY_DROPDOWN_NAME,
    00089   STR_SORT_BY_FACILITY,
    00090   STR_SORT_BY_WAITING,
    00091   STR_SORT_BY_RATING_MAX,
    00092   INVALID_STRING_ID
    00093 };
    00094
    00095 static char _bufcache[64];
    00096 static const Station* _last_station;
    00097 static int _internal_sort_order;
    00098
    00099 static int CDECL StationNameSorter(const void *a, const void *b)
    00100 {
    00101   const Station* st1 = *(const Station**)a;
    00102   const Station* st2 = *(const Station**)b;
    00103   char buf1[64];
    00104   int r;
    00105
    00106   SetDParam(0, st1->index);
    00107   GetString(buf1, STR_STATION, lastof(buf1));
    00108
    00109   if (st2 != _last_station) {
    00110     _last_station = st2;
    00111     SetDParam(0, st2->index);
    00112     GetString(_bufcache, STR_STATION, lastof(_bufcache));
    00113   }
    00114
    00115   r = strcmp(buf1, _bufcache); // sort by name
    00116   return (_internal_sort_order & 1) ? -r : r;
    00117 }
    00118
    00119 static int CDECL StationTypeSorter(const void *a, const void *b)
    00120 {
    00121   const Station* st1 = *(const Station**)a;
    00122   const Station* st2 = *(const Station**)b;
    00123   return (_internal_sort_order & 1) ? st2->facilities - st1->facilities : st1->facilities - st2->facilities;
    00124 }
    00125
    00126 static const uint32 _cargo_filter_max = ~0;
    00127 static uint32 _cargo_filter = _cargo_filter_max;
    00128
    00129 static int CDECL StationWaitingSorter(const void *a, const void *b)
    00130 {
    00131   const Station* st1 = *(const Station**)a;
    00132   const Station* st2 = *(const Station**)b;
    00133   Money sum1 = 0, sum2 = 0;
    00134
    00135   for (CargoID j = 0; j < NUM_CARGO; j++) {
    00136     if (!HasBit(_cargo_filter, j)) continue;
    00137     if (!st1->goods[j].cargo.Empty()) sum1 += GetTransportedGoodsIncome(st1->goods[j].cargo.Count(), 20, 50, j);
    00138     if (!st2->goods[j].cargo.Empty()) sum2 += GetTransportedGoodsIncome(st2->goods[j].cargo.Count(), 20, 50, j);
    00139   }
    00140
    00141   return (_internal_sort_order & 1) ? ClampToI32(sum2 - sum1) : ClampToI32(sum1 - sum2);
    00142 }
    00143
    00152 static int CDECL StationRatingMaxSorter(const void *a, const void *b)
    00153 {
    00154   const Station* st1 = *(const Station**)a;
    00155   const Station* st2 = *(const Station**)b;
    00156   byte maxr1 = 0;
    00157   byte maxr2 = 0;
    00158
    00159   for (CargoID j = 0; j < NUM_CARGO; j++) {
    00160     if (HasBit(st1->goods[j].acceptance_pickup, GoodsEntry::PICKUP)) maxr1 = max(maxr1, st1->goods[j].rating);
    00161     if (HasBit(st2->goods[j].acceptance_pickup, GoodsEntry::PICKUP)) maxr2 = max(maxr2, st2->goods[j].rating);
    00162   }
    00163
    00164   return (_internal_sort_order & 1) ? maxr2 - maxr1 : maxr1 - maxr2;
    00165 }
    00166
    00168 enum StationListFlags {
    00169   SL_ORDER   = 1 << 0,
    00170   SL_RESORT  = 1 << 1,
    00171   SL_REBUILD = 1 << 2,
    00172 };
    00173
    00174 DECLARE_ENUM_AS_BIT_SET(StationListFlags);
    00175
    00177 struct plstations_d {
    00178   const Station** sort_list;
    00179   uint16 list_length;
    00180   uint16 resort_timer;
    00181   byte sort_type;
    00182   byte flags;
    00183 };
    00184 assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(plstations_d));
    00185
    00189 void RebuildStationLists()
    00190 {
    00191   Window* const *wz;
    00192
    00193   FOR_ALL_WINDOWS(wz) {
    00194     Window *w = *wz;
    00195     if (w->window_class == WC_STATION_LIST) {
    00196       WP(w, plstations_d).flags |= SL_REBUILD;
    00197       SetWindowDirty(w);
    00198     }
    00199   }
    00200 }
    00201
    00205 void ResortStationLists()
    00206 {
    00207   Window* const *wz;
    00208
    00209   FOR_ALL_WINDOWS(wz) {
    00210     Window *w = *wz;
    00211     if (w->window_class == WC_STATION_LIST) {
    00212       WP(w, plstations_d).flags |= SL_RESORT;
    00213       SetWindowDirty(w);
    00214     }
    00215   }
    00216 }
    00217
    00227 static void BuildStationsList(plstations_d* sl, PlayerID owner, byte facilities, uint32 cargo_filter, bool include_empty)
    00228 {
    00229   uint n = 0;
    00230   const Station *st;
    00231
    00232   if (!(sl->flags & SL_REBUILD)) return;
    00233
    00234   /* Create array for sorting */
    00235   const Station** station_sort = MallocT<const Station*>(GetMaxStationIndex() + 1);
    00236
    00237   DEBUG(misc, 3, "Building station list for player %d", owner);
    00238
    00239   FOR_ALL_STATIONS(st) {
    00240     if (st->owner == owner || (st->owner == OWNER_NONE && !st->IsBuoy() && HasStationInUse(st->index, owner))) {
    00241       if (facilities & st->facilities) { //only stations with selected facilities
    00242         int num_waiting_cargo = 0;
    00243         for (CargoID j = 0; j < NUM_CARGO; j++) {
    00244           if (!st->goods[j].cargo.Empty()) {
    00245             num_waiting_cargo++; //count number of waiting cargo
    00246             if (HasBit(cargo_filter, j)) {
    00247               station_sort[n++] = st;
    00248               break;
    00249             }
    00250           }
    00251         }
    00252         /* stations without waiting cargo */
    00253         if (num_waiting_cargo == 0 && include_empty) {
    00254           station_sort[n++] = st;
    00255         }
    00256       }
    00257     }
    00258   }
    00259
    00260   free((void*)sl->sort_list);
    00261   sl->sort_list = MallocT<const Station*>(n);
    00262   sl->list_length = n;
    00263
    00264   for (uint i = 0; i < n; ++i) sl->sort_list[i] = station_sort[i];
    00265
    00266   sl->flags &= ~SL_REBUILD;
    00267   sl->flags |= SL_RESORT;
    00268   free((void*)station_sort);
    00269 }
    00270
    00271
    00277 static void SortStationsList(plstations_d *sl)
    00278 {
    00279   static StationSortListingTypeFunction* const _station_sorter[] = {
    00280     &StationNameSorter,
    00281     &StationTypeSorter,
    00282     &StationWaitingSorter,
    00283     &StationRatingMaxSorter
    00284   };
    00285
    00286   if (!(sl->flags & SL_RESORT)) return;
    00287
    00288   _internal_sort_order = sl->flags & SL_ORDER;
    00289   _last_station = NULL; // used for "cache" in namesorting
    00290   qsort((void*)sl->sort_list, sl->list_length, sizeof(sl->sort_list[0]), _station_sorter[sl->sort_type]);
    00291
    00292   sl->resort_timer = DAY_TICKS * PERIODIC_RESORT_DAYS;
    00293   sl->flags &= ~SL_RESORT;
    00294 }
    00295
    00302 static void PlayerStationsWndProc(Window *w, WindowEvent *e)
    00303 {
    00304   const PlayerID owner = (PlayerID)w->window_number;
    00305   static byte facilities = FACIL_TRAIN | FACIL_TRUCK_STOP | FACIL_BUS_STOP | FACIL_AIRPORT | FACIL_DOCK;
    00306   static Listing station_sort = {0, 0};
    00307   static bool include_empty = true;
    00308
    00309   plstations_d *sl = &WP(w, plstations_d);
    00310
    00311   switch (e->event) {
    00312     case WE_CREATE:
    00313       if (_cargo_filter == _cargo_filter_max) _cargo_filter = _cargo_mask;
    00314
    00315       for (uint i = 0; i < 5; i++) {
    00316         if (HasBit(facilities, i)) w->LowerWidget(i + SLW_TRAIN);
    00317       }
    00318       w->SetWidgetLoweredState(SLW_FACILALL, facilities == (FACIL_TRAIN | FACIL_TRUCK_STOP | FACIL_BUS_STOP | FACIL_AIRPORT | FACIL_DOCK));
    00319       w->SetWidgetLoweredState(SLW_CARGOALL, _cargo_filter == _cargo_mask && include_empty);
    00320       w->SetWidgetLoweredState(SLW_NOCARGOWAITING, include_empty);
    00321
    00322       sl->sort_list = NULL;
    00323       sl->flags = SL_REBUILD;
    00324       sl->sort_type = station_sort.criteria;
    00325       if (station_sort.order) sl->flags |= SL_ORDER;
    00326
    00327       /* set up resort timer */
    00328       sl->resort_timer = DAY_TICKS * PERIODIC_RESORT_DAYS;
    00329       break;
    00330
    00331     case WE_PAINT: {
    00332       BuildStationsList(sl, owner, facilities, _cargo_filter, include_empty);
    00333       SortStationsList(sl);
    00334
    00335       SetVScrollCount(w, sl->list_length);
    00336
    00337       /* draw widgets, with player's name in the caption */
    00338       SetDParam(0, owner);
    00339       SetDParam(1, w->vscroll.count);
    00340
    00341       /* Set text of sort by dropdown */
    00342       w->widget[SLW_SORTDROPBTN].data = _station_sort_listing[sl->sort_type];
    00343
    00344       DrawWindowWidgets(w);
    00345
    00346       /* draw arrow pointing up/down for ascending/descending sorting */
    00347       DrawSortButtonState(w, SLW_SORTBY, sl->flags & SL_ORDER ? SBS_DOWN : SBS_UP);
    00348
    00349       int cg_ofst;
    00350       int x = 89;
    00351       int y = 14;
    00352       int xb = 2;
    00353
    00354       uint i = 0;
    00355       for (CargoID c = 0; c < NUM_CARGO; c++) {
    00356         const CargoSpec *cs = GetCargo(c);
    00357         if (!cs->IsValid()) continue;
    00358
    00359         cg_ofst = HasBit(_cargo_filter, c) ? 2 : 1;
    00360         GfxFillRect(x + cg_ofst, y + cg_ofst, x + cg_ofst + 10 , y + cg_ofst + 7, cs->rating_colour);
    00361         DrawStringCentered(x + 6 + cg_ofst, y + cg_ofst, cs->abbrev, TC_BLACK);
    00362         x += 14;
    00363         i++;
    00364       }
    00365
    00366       x += 6;
    00367       cg_ofst = w->IsWidgetLowered(SLW_NOCARGOWAITING) ? 2 : 1;
    00368       DrawStringCentered(x + cg_ofst, y + cg_ofst, STR_ABBREV_NONE, TC_BLACK);
    00369       x += 14;
    00370       cg_ofst = w->IsWidgetLowered(SLW_CARGOALL) ? 2 : 1;
    00371       DrawStringCentered(x + cg_ofst, y + cg_ofst, STR_ABBREV_ALL, TC_BLACK);
    00372
    00373       cg_ofst = w->IsWidgetLowered(SLW_FACILALL) ? 2 : 1;
    00374       DrawString(71 + cg_ofst, y + cg_ofst, STR_ABBREV_ALL, TC_BLACK);
    00375
    00376       if (w->vscroll.count == 0) { // player has no stations
    00377         DrawString(xb, 40, STR_304A_NONE, TC_FROMSTRING);
    00378         return;
    00379       }
    00380
    00381       int max = min(w->vscroll.pos + w->vscroll.cap, sl->list_length);
    00382       y = 40; // start of the list-widget
    00383
    00384       for (int i = w->vscroll.pos; i < max; ++i) { // do until max number of stations of owner
    00385         const Station *st = sl->sort_list[i];
    00386         int x;
    00387
    00388         assert(st->xy != 0);
    00389
    00390         /* Do not do the complex check HasStationInUse here, it may be even false
    00391          * when the order had been removed and the station list hasn't been removed yet */
    00392         assert(st->owner == owner || (st->owner == OWNER_NONE && !st->IsBuoy()));
    00393
    00394         SetDParam(0, st->index);
    00395         SetDParam(1, st->facilities);
    00396         x = DrawString(xb, y, STR_3049_0, TC_FROMSTRING) + 5;
    00397
    00398         /* show cargo waiting and station ratings */
    00399         for (CargoID j = 0; j < NUM_CARGO; j++) {
    00400           if (!st->goods[j].cargo.Empty()) {
    00401             StationsWndShowStationRating(x, y, j, st->goods[j].cargo.Count(), st->goods[j].rating);
    00402             x += 20;
    00403           }
    00404         }
    00405         y += 10;
    00406       }
    00407       break;
    00408     }
    00409
    00410     case WE_CLICK:
    00411       switch (e->we.click.widget) {
    00412         case SLW_LIST: {
    00413           uint32 id_v = (e->we.click.pt.y - 41) / 10;
    00414
    00415           if (id_v >= w->vscroll.cap) return; // click out of bounds
    00416
    00417           id_v += w->vscroll.pos;
    00418
    00419           if (id_v >= sl->list_length) return; // click out of list bound
    00420
    00421           const Station *st = sl->sort_list[id_v];
    00422           /* do not check HasStationInUse - it is slow and may be invalid */
    00423           assert(st->owner == owner || (st->owner == OWNER_NONE && !st->IsBuoy()));
    00424           ScrollMainWindowToTile(st->xy);
    00425           break;
    00426         }
    00427
    00428         case SLW_TRAIN:
    00429         case SLW_TRUCK:
    00430         case SLW_BUS:
    00431         case SLW_AIRPLANE:
    00432         case SLW_SHIP:
    00433           if (_ctrl_pressed) {
    00434             ToggleBit(facilities, e->we.click.widget - SLW_TRAIN);
    00435             w->ToggleWidgetLoweredState(e->we.click.widget);
    00436           } else {
    00437             uint i;
    00438             FOR_EACH_SET_BIT(i, facilities) {
    00439               w->RaiseWidget(i + SLW_TRAIN);
    00440             }
    00441             SetBit(facilities, e->we.click.widget - SLW_TRAIN);
    00442             w->LowerWidget(e->we.click.widget);
    00443           }
    00444           w->SetWidgetLoweredState(SLW_FACILALL, facilities == (FACIL_TRAIN | FACIL_TRUCK_STOP | FACIL_BUS_STOP | FACIL_AIRPORT | FACIL_DOCK));
    00445           sl->flags |= SL_REBUILD;
    00446           SetWindowDirty(w);
    00447           break;
    00448
    00449         case SLW_FACILALL:
    00450           for (uint i = 0; i < 5; i++) {
    00451             w->LowerWidget(i + SLW_TRAIN);
    00452           }
    00453           w->LowerWidget(SLW_FACILALL);
    00454
    00455           facilities = FACIL_TRAIN | FACIL_TRUCK_STOP | FACIL_BUS_STOP | FACIL_AIRPORT | FACIL_DOCK;
    00456           sl->flags |= SL_REBUILD;
    00457           SetWindowDirty(w);
    00458           break;
    00459
    00460         case SLW_CARGOALL: {
    00461           uint i = 0;
    00462           for (CargoID c = 0; c < NUM_CARGO; c++) {
    00463             if (!GetCargo(c)->IsValid()) continue;
    00464             w->LowerWidget(i + SLW_CARGOSTART);
    00465             i++;
    00466           }
    00467           w->LowerWidget(SLW_NOCARGOWAITING);
    00468           w->LowerWidget(SLW_CARGOALL);
    00469
    00470           _cargo_filter = _cargo_mask;
    00471           include_empty = true;
    00472           sl->flags |= SL_REBUILD;
    00473           SetWindowDirty(w);
    00474           break;
    00475         }
    00476
    00477         case SLW_SORTBY: // flip sorting method asc/desc
    00478           sl->flags ^= SL_ORDER; //DESC-flag
    00479           station_sort.order = HasBit(sl->flags, 0);
    00480           sl->flags |= SL_RESORT;
    00481           w->flags4 |= 5 << WF_TIMEOUT_SHL;
    00482           w->LowerWidget(SLW_SORTBY);
    00483           SetWindowDirty(w);
    00484           break;
    00485
    00486         case SLW_SORTDROPBTN: // select sorting criteria dropdown menu
    00487           ShowDropDownMenu(w, _station_sort_listing, sl->sort_type, SLW_SORTDROPBTN, 0, 0);
    00488           break;
    00489
    00490         case SLW_NOCARGOWAITING:
    00491           if (_ctrl_pressed) {
    00492             include_empty = !include_empty;
    00493             w->ToggleWidgetLoweredState(SLW_NOCARGOWAITING);
    00494           } else {
    00495             for (uint i = SLW_CARGOSTART; i < w->widget_count; i++) {
    00496               w->RaiseWidget(i);
    00497             }
    00498
    00499             _cargo_filter = 0;
    00500             include_empty = true;
    00501
    00502             w->LowerWidget(SLW_NOCARGOWAITING);
    00503           }
    00504           sl->flags |= SL_REBUILD;
    00505           w->SetWidgetLoweredState(SLW_CARGOALL, _cargo_filter == _cargo_mask && include_empty);
    00506           SetWindowDirty(w);
    00507           break;
    00508
    00509         default:
    00510           if (e->we.click.widget >= SLW_CARGOSTART) { // change cargo_filter
    00511             /* Determine the selected cargo type */
    00512             CargoID c;
    00513             int i = 0;
    00514             for (c = 0; c < NUM_CARGO; c++) {
    00515               if (!GetCargo(c)->IsValid()) continue;
    00516               if (e->we.click.widget - SLW_CARGOSTART == i) break;
    00517               i++;
    00518             }
    00519
    00520             if (_ctrl_pressed) {
    00521               ToggleBit(_cargo_filter, c);
    00522               w->ToggleWidgetLoweredState(e->we.click.widget);
    00523             } else {
    00524               for (uint i = SLW_CARGOSTART; i < w->widget_count; i++) {
    00525                 w->RaiseWidget(i);
    00526               }
    00527               w->RaiseWidget(SLW_NOCARGOWAITING);
    00528
    00529               _cargo_filter = 0;
    00530               include_empty = false;
    00531
    00532               SetBit(_cargo_filter, c);
    00533               w->LowerWidget(e->we.click.widget);
    00534             }
    00535             sl->flags |= SL_REBUILD;
    00536             w->SetWidgetLoweredState(SLW_CARGOALL, _cargo_filter == _cargo_mask && include_empty);
    00537             SetWindowDirty(w);
    00538           }
    00539           break;
    00540       }
    00541       break;
    00542
    00543     case WE_DROPDOWN_SELECT: // we have selected a dropdown item in the list
    00544       if (sl->sort_type != e->we.dropdown.index) {
    00545         /* value has changed -> resort */
    00546         sl->sort_type = e->we.dropdown.index;
    00547         station_sort.criteria = sl->sort_type;
    00548         sl->flags |= SL_RESORT;
    00549       }
    00550       SetWindowDirty(w);
    00551       break;
    00552
    00553     case WE_TICK:
    00554       if (_pause_game != 0) break;
    00555       if (--sl->resort_timer == 0) {
    00556         DEBUG(misc, 3, "Periodic rebuild station list player %d", owner);
    00557         sl->resort_timer = DAY_TICKS * PERIODIC_RESORT_DAYS;
    00558         sl->flags |= SL_REBUILD;
    00559         SetWindowDirty(w);
    00560       }
    00561       break;
    00562
    00563     case WE_TIMEOUT:
    00564       w->RaiseWidget(SLW_SORTBY);
    00565       SetWindowDirty(w);
    00566       break;
    00567
    00568     case WE_RESIZE:
    00569       w->vscroll.cap += e->we.sizing.diff.y / 10;
    00570       break;
    00571   }
    00572 }
    00573
    00574 static const Widget _player_stations_widgets[] = {
    00575 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,          STR_018B_CLOSE_WINDOW},            // SLW_CLOSEBOX
    00576 {    WWT_CAPTION,  RESIZE_RIGHT,    14,    11,   345,     0,    13, STR_3048_STATIONS, STR_018C_WINDOW_TITLE_DRAG_THIS},
    00577 {  WWT_STICKYBOX,     RESIZE_LR,    14,   346,   357,     0,    13, 0x0,               STR_STICKY_BUTTON},
    00578 {      WWT_PANEL,     RESIZE_RB,    14,     0,   345,    37,   161, 0x0,               STR_3057_STATION_NAMES_CLICK_ON},  // SLW_LIST
    00579 {  WWT_SCROLLBAR,    RESIZE_LRB,    14,   346,   357,    37,   149, 0x0,               STR_0190_SCROLL_BAR_SCROLLS_LIST},
    00580 {  WWT_RESIZEBOX,   RESIZE_LRTB,    14,   346,   357,   150,   161, 0x0,               STR_RESIZE_BUTTON},
    00581
    00582 {    WWT_TEXTBTN,   RESIZE_NONE,    14,     0,    13,    14,    24, STR_TRAIN,         STR_USE_CTRL_TO_SELECT_MORE},      // SLW_TRAIN
    00583 {    WWT_TEXTBTN,   RESIZE_NONE,    14,    14,    27,    14,    24, STR_LORRY,         STR_USE_CTRL_TO_SELECT_MORE},      // SLW_TRUCK
    00584 {    WWT_TEXTBTN,   RESIZE_NONE,    14,    28,    41,    14,    24, STR_BUS,           STR_USE_CTRL_TO_SELECT_MORE},      // SLW_BUS
    00585 {    WWT_TEXTBTN,   RESIZE_NONE,    14,    42,    55,    14,    24, STR_PLANE,         STR_USE_CTRL_TO_SELECT_MORE},      // SLW_AIRPLANE
    00586 {    WWT_TEXTBTN,   RESIZE_NONE,    14,    56,    69,    14,    24, STR_SHIP,          STR_USE_CTRL_TO_SELECT_MORE},      // SLW_SHIP
    00587 {      WWT_PANEL,   RESIZE_NONE,    14,    70,    83,    14,    24, 0x0,               STR_SELECT_ALL_FACILITIES},        // SLW_FACILALL
    00588
    00589 {      WWT_PANEL,   RESIZE_NONE,    14,    83,    88,    14,    24, 0x0,               STR_NULL},                         // SLW_PAN_BETWEEN
    00590 {      WWT_PANEL,   RESIZE_NONE,    14,    89,   102,    14,    24, 0x0,               STR_NO_WAITING_CARGO},             // SLW_NOCARGOWAITING
    00591 {      WWT_PANEL,   RESIZE_NONE,    14,   103,   116,    14,    24, 0x0,               STR_SELECT_ALL_TYPES},             // SLW_CARGOALL
    00592 {      WWT_PANEL,  RESIZE_RIGHT,    14,   117,   357,    14,    24, 0x0,               STR_NULL},                         // SLW_PAN_RIGHT
    00593
    00594 {    WWT_TEXTBTN,   RESIZE_NONE,    14,     0,    80,    25,    36, STR_SORT_BY,       STR_SORT_ORDER_TIP},               // SLW_SORTBY
    00595 {   WWT_DROPDOWN,   RESIZE_NONE,    14,    81,   243,    25,    36, 0x0,               STR_SORT_CRITERIA_TIP},            // SLW_SORTDROPBTN
    00596 {      WWT_PANEL,  RESIZE_RIGHT,    14,   244,   357,    25,    36, 0x0,               STR_NULL},                         // SLW_PAN_SORT_RIGHT
    00597 {   WIDGETS_END},
    00598 };
    00599
    00600 static const WindowDesc _player_stations_desc = {
    00601   WDP_AUTO, WDP_AUTO, 358, 162, 358, 162,
    00602   WC_STATION_LIST, WC_NONE,
    00603   WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_STICKY_BUTTON | WDF_RESIZABLE,
    00604   _player_stations_widgets,
    00605   PlayerStationsWndProc
    00606 };
    00607
    00613 void ShowPlayerStations(PlayerID player)
    00614 {
    00615   if (!IsValidPlayer(player)) return;
    00616
    00617   Window *w = AllocateWindowDescFront(&_player_stations_desc, player);
    00618   if (w == NULL) return;
    00619
    00620   w->caption_color = (byte)w->window_number;
    00621   w->vscroll.cap = 12;
    00622   w->resize.step_height = 10;
    00623   w->resize.height = w->height - 10 * 7; // minimum if 5 in the list
    00624
    00625   /* Add cargo filter buttons */
    00626   uint num_active = 0;
    00627   for (CargoID c = 0; c < NUM_CARGO; c++) {
    00628     if (GetCargo(c)->IsValid()) num_active++;
    00629   }
    00630
    00631   w->widget_count += num_active;
    00632   w->widget = ReallocT(w->widget, w->widget_count + 1);
    00633   w->widget[w->widget_count].type = WWT_LAST;
    00634
    00635   uint i = 0;
    00636   for (CargoID c = 0; c < NUM_CARGO; c++) {
    00637     if (!GetCargo(c)->IsValid()) continue;
    00638
    00639     Widget *wi = &w->widget[SLW_CARGOSTART + i];
    00640     wi->type     = WWT_PANEL;
    00641     wi->display_flags = RESIZE_NONE;
    00642     wi->color    = 14;
    00643     wi->left     = 89 + i * 14;
    00644     wi->right    = wi->left + 13;
    00645     wi->top      = 14;
    00646     wi->bottom   = 24;
    00647     wi->data     = 0;
    00648     wi->tooltips = STR_USE_CTRL_TO_SELECT_MORE;
    00649
    00650     if (HasBit(_cargo_filter, c)) w->LowerWidget(SLW_CARGOSTART + i);
    00651     i++;
    00652   }
    00653
    00654   w->widget[SLW_NOCARGOWAITING].left += num_active * 14;
    00655   w->widget[SLW_NOCARGOWAITING].right += num_active * 14;
    00656   w->widget[SLW_CARGOALL].left += num_active * 14;
    00657   w->widget[SLW_CARGOALL].right += num_active * 14;
    00658   w->widget[SLW_PAN_RIGHT].left += num_active * 14;
    00659
    00660   if (num_active > 15) {
    00661     /* Resize and fix the minimum width, if necessary */
    00662     ResizeWindow(w, (num_active - 15) * 14, 0);
    00663     w->resize.width = w->width;
    00664   }
    00665 }
    00666
    00667 static const Widget _station_view_widgets[] = {
    00668 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,          STR_018B_CLOSE_WINDOW},                // SVW_CLOSEBOX
    00669 {    WWT_CAPTION,  RESIZE_RIGHT,    14,    11,   236,     0,    13, STR_300A_0,        STR_018C_WINDOW_TITLE_DRAG_THIS},
    00670 {  WWT_STICKYBOX,     RESIZE_LR,    14,   237,   248,     0,    13, 0x0,               STR_STICKY_BUTTON},
    00671 {      WWT_PANEL,     RESIZE_RB,    14,     0,   236,    14,    65, 0x0,               STR_NULL},                             // SVW_WAITING
    00672 {  WWT_SCROLLBAR,    RESIZE_LRB,    14,   237,   248,    14,    65, 0x0,               STR_0190_SCROLL_BAR_SCROLLS_LIST},
    00673 {      WWT_PANEL,    RESIZE_RTB,    14,     0,   248,    66,    97, 0x0,               STR_NULL},                             // SVW_ACCEPTLIST / SVW_RATINGLIST
    00674 { WWT_PUSHTXTBTN,     RESIZE_TB,    14,     0,    59,    98,   109, STR_00E4_LOCATION, STR_3053_CENTER_MAIN_VIEW_ON_STATION}, // SVW_LOCATION
    00675 { WWT_PUSHTXTBTN,     RESIZE_TB,    14,    60,   120,    98,   109, STR_3032_RATINGS,  STR_3054_SHOW_STATION_RATINGS},        // SVW_RATINGS / SVW_ACCEPTS
    00676 { WWT_PUSHTXTBTN,    RESIZE_RTB,    14,   121,   180,    98,   109, STR_0130_RENAME,   STR_3055_CHANGE_NAME_OF_STATION},      // SVW_RENAME
    00677 { WWT_PUSHTXTBTN,   RESIZE_LRTB,    14,   181,   194,    98,   109, STR_TRAIN,         STR_SCHEDULED_TRAINS_TIP },            // SVW_TRAINS
    00678 { WWT_PUSHTXTBTN,   RESIZE_LRTB,    14,   195,   208,    98,   109, STR_LORRY,         STR_SCHEDULED_ROAD_VEHICLES_TIP },     // SVW_ROADVEHS
    00679 { WWT_PUSHTXTBTN,   RESIZE_LRTB,    14,   209,   222,    98,   109, STR_PLANE,         STR_SCHEDULED_AIRCRAFT_TIP },          // SVW_PLANES
    00680 { WWT_PUSHTXTBTN,   RESIZE_LRTB,    14,   223,   236,    98,   109, STR_SHIP,          STR_SCHEDULED_SHIPS_TIP },             // SVW_SHIPS
    00681 {  WWT_RESIZEBOX,   RESIZE_LRTB,    14,   237,   248,    98,   109, 0x0,               STR_RESIZE_BUTTON},
    00682 {   WIDGETS_END},
    00683 };
    00684
    00693 static void DrawCargoIcons(CargoID i, uint waiting, int x, int y, uint width)
    00694 {
    00695   uint num = min((waiting + 5) / 10, width / 10); // maximum is width / 10 icons so it won't overflow
    00696   if (num == 0) return;
    00697
    00698   const CargoSpec *cs = GetCargo(i);
    00699   SpriteID sprite;
    00700
    00701   if (cs->sprite == 0xFFFF) {
    00702     /* A value of 0xFFFF indicates we should draw a custom icon */
    00703     sprite = GetCustomCargoSprite(cs);
    00704   } else {
    00705     sprite = cs->sprite;
    00706   }
    00707
    00708   if (sprite == 0) sprite = SPR_CARGO_GOODS;
    00709
    00710   do {
    00711     DrawSprite(sprite, PAL_NONE, x, y);
    00712     x += 10;
    00713   } while (--num);
    00714 }
    00715
    00716 struct CargoData {
    00717   CargoID cargo;
    00718   StationID source;
    00719   uint count;
    00720
    00721   CargoData(CargoID cargo, StationID source, uint count) :
    00722     cargo(cargo),
    00723     source(source),
    00724     count(count)
    00725   { }
    00726 };
    00727
    00728 typedef std::list<CargoData> CargoDataList;
    00729
    00735 static void DrawStationViewWindow(Window *w)
    00736 {
    00737   StationID station_id = w->window_number;
    00738   const Station* st = GetStation(station_id);
    00739   int x, y;
    00740   int pos;
    00741   StringID str;
    00742   CargoDataList cargolist;
    00743
    00744   /* count types of cargos waiting in station */
    00745   for (CargoID i = 0; i < NUM_CARGO; i++) {
    00746     if (!st->goods[i].cargo.Empty()) {
    00747       /* Add an entry for total amount of cargo of this type waiting. */
    00748       cargolist.push_back(CargoData(i, INVALID_STATION, st->goods[i].cargo.Count()));
    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           /* Check if we already have this source in the list */
    00758           for (CargoDataList::iterator jt = cargolist.begin(); jt != cargolist.end(); jt++) {
    00759             CargoData *cd = &(*jt);
    00760             if (cd->cargo == i && cd->source == cp->source) {
    00761               cd->count += cp->count;
    00762               added = true;
    00763               break;
    00764             }
    00765           }
    00766
    00767           if (!added) cargolist.push_back(CargoData(i, cp->source, cp->count));
    00768         }
    00769       }
    00770     }
    00771   }
    00772   SetVScrollCount(w, cargolist.size() + 1); // update scrollbar
    00773
    00774   /* disable some buttons */
    00775   w->SetWidgetDisabledState(SVW_RENAME,   st->owner != _local_player);
    00776   w->SetWidgetDisabledState(SVW_TRAINS,   !(st->facilities & FACIL_TRAIN));
    00777   w->SetWidgetDisabledState(SVW_ROADVEHS, !(st->facilities & FACIL_TRUCK_STOP) && !(st->facilities & FACIL_BUS_STOP));
    00778   w->SetWidgetDisabledState(SVW_PLANES,   !(st->facilities & FACIL_AIRPORT));
    00779   w->SetWidgetDisabledState(SVW_SHIPS,    !(st->facilities & FACIL_DOCK));
    00780
    00781   SetDParam(0, st->index);
    00782   SetDParam(1, st->facilities);
    00783   DrawWindowWidgets(w);
    00784
    00785   x = 2;
    00786   y = 15;
    00787   pos = w->vscroll.pos;
    00788
    00789   uint width = w->widget[SVW_WAITING].right - w->widget[SVW_WAITING].left - 4;
    00790   int maxrows = w->vscroll.cap;
    00791
    00792   if (--pos < 0) {
    00793     str = STR_00D0_NOTHING;
    00794     for (CargoID i = 0; i < NUM_CARGO; i++) {
    00795       if (!st->goods[i].cargo.Empty()) str = STR_EMPTY;
    00796     }
    00797     SetDParam(0, str);
    00798     DrawString(x, y, STR_0008_WAITING, TC_FROMSTRING);
    00799     y += 10;
    00800   }
    00801
    00802   for (CargoDataList::const_iterator it = cargolist.begin(); it != cargolist.end() && pos > -maxrows; ++it) {
    00803     if (--pos < 0) {
    00804       const CargoData *cd = &(*it);
    00805       if (cd->source == INVALID_STATION) {
    00806         /* Heading */
    00807         DrawCargoIcons(cd->cargo, cd->count, x, y, width);
    00808         SetDParam(0, cd->cargo);
    00809         SetDParam(1, cd->count);
    00810         DrawStringRightAligned(x + width, y, STR_0009, TC_FROMSTRING);
    00811       } else {
    00812         SetDParam(0, cd->cargo);
    00813         SetDParam(1, cd->count);
    00814         SetDParam(2, cd->source);
    00815         DrawStringRightAlignedTruncated(x + width, y, STR_EN_ROUTE_FROM, TC_FROMSTRING, width);
    00816       }
    00817
    00818       y += 10;
    00819     }
    00820   }
    00821
    00822   if (w->widget[SVW_ACCEPTS].data == STR_3032_RATINGS) { // small window with list of accepted cargo
    00823     char *b = _userstring;
    00824     bool first = true;
    00825
    00826     b = InlineString(b, STR_000C_ACCEPTS);
    00827
    00828     for (CargoID i = 0; i < NUM_CARGO; i++) {
    00829       if (b >= lastof(_userstring) - (1 + 2 * 4)) break; // ',' or ' ' and two calls to Utf8Encode()
    00830       if (HasBit(st->goods[i].acceptance_pickup, GoodsEntry::ACCEPTANCE)) {
    00831         if (first) {
    00832           first = false;
    00833         } else {
    00834           /* Add a comma if this is not the first item */
    00835           *b++ = ',';
    00836           *b++ = ' ';
    00837         }
    00838         b = InlineString(b, GetCargo(i)->name);
    00839       }
    00840     }
    00841
    00842     /* If first is still true then no cargo is accepted */
    00843     if (first) b = InlineString(b, STR_00D0_NOTHING);
    00844
    00845     *b = '\0';
    00846
    00847     /* Make sure we detect any buffer overflow */
    00848     assert(b < endof(_userstring));
    00849
    00850     DrawStringMultiLine(2, w->widget[SVW_ACCEPTLIST].top + 1, STR_SPEC_USERSTRING, w->widget[SVW_ACCEPTLIST].right - w->widget[SVW_ACCEPTLIST].left);
    00851   } else { // extended window with list of cargo ratings
    00852     y = w->widget[SVW_RATINGLIST].top + 1;
    00853
    00854     DrawString(2, y, STR_3034_LOCAL_RATING_OF_TRANSPORT, TC_FROMSTRING);
    00855     y += 10;
    00856
    00857     for (CargoID i = 0; i < NUM_CARGO; i++) {
    00858       const CargoSpec *cs = GetCargo(i);
    00859       if (!cs->IsValid()) continue;
    00860
    00861       const GoodsEntry *ge = &st->goods[i];
    00862       if (!HasBit(ge->acceptance_pickup, GoodsEntry::PICKUP)) continue;
    00863
    00864       SetDParam(0, cs->name);
    00865       SetDParam(2, ge->rating * 101 >> 8);
    00866       SetDParam(1, STR_3035_APPALLING + (ge->rating >> 5));
    00867       DrawString(8, y, STR_303D, TC_FROMSTRING);
    00868       y += 10;
    00869     }
    00870   }
    00871 }
    00872
    00873
    00880 static void StationViewWndProc(Window *w, WindowEvent *e)
    00881 {
    00882   switch (e->event) {
    00883     case WE_PAINT:
    00884       DrawStationViewWindow(w);
    00885       break;
    00886
    00887     case WE_CLICK:
    00888       switch (e->we.click.widget) {
    00889         case SVW_LOCATION:
    00890           ScrollMainWindowToTile(GetStation(w->window_number)->xy);
    00891           break;
    00892
    00893         case SVW_RATINGS:
    00894           SetWindowDirty(w);
    00895
    00896           if (w->widget[SVW_RATINGS].data == STR_3032_RATINGS) {
    00897             /* Switch to ratings view */
    00898             w->widget[SVW_RATINGS].data = STR_3033_ACCEPTS;
    00899             w->widget[SVW_RATINGS].tooltips = STR_3056_SHOW_LIST_OF_ACCEPTED_CARGO;
    00900             ResizeWindowForWidget(w, SVW_ACCEPTLIST, 0, 100);
    00901           } else {
    00902             /* Switch to accepts view */
    00903             w->widget[SVW_RATINGS].data = STR_3032_RATINGS;
    00904             w->widget[SVW_RATINGS].tooltips = STR_3054_SHOW_STATION_RATINGS;
    00905             ResizeWindowForWidget(w, SVW_ACCEPTLIST, 0, -100);
    00906           }
    00907
    00908           SetWindowDirty(w);
    00909           break;
    00910
    00911         case SVW_RENAME:
    00912           SetDParam(0, w->window_number);
    00913           ShowQueryString(STR_STATION, STR_3030_RENAME_STATION_LOADING, 31, 180, w, CS_ALPHANUMERAL);
    00914           break;
    00915
    00916         case SVW_TRAINS: { // Show a list of scheduled trains to this station
    00917           const Station *st = GetStation(w->window_number);
    00918           ShowVehicleListWindow(st->owner, VEH_TRAIN, (StationID)w->window_number);
    00919           break;
    00920         }
    00921
    00922         case SVW_ROADVEHS: { // Show a list of scheduled road-vehicles to this station
    00923           const Station *st = GetStation(w->window_number);
    00924           ShowVehicleListWindow(st->owner, VEH_ROAD, (StationID)w->window_number);
    00925           break;
    00926         }
    00927
    00928         case SVW_PLANES: { // Show a list of scheduled aircraft to this station
    00929           const Station *st = GetStation(w->window_number);
    00930           /* Since oilrigs have no owners, show the scheduled aircraft of current player */
    00931           PlayerID owner = (st->owner == OWNER_NONE) ? _current_player : st->owner;
    00932           ShowVehicleListWindow(owner, VEH_AIRCRAFT, (StationID)w->window_number);
    00933           break;
    00934         }
    00935
    00936         case SVW_SHIPS: { // Show a list of scheduled ships to this station
    00937           const Station *st = GetStation(w->window_number);
    00938           /* Since oilrigs/bouys have no owners, show the scheduled ships of current player */
    00939           PlayerID owner = (st->owner == OWNER_NONE) ? _current_player : st->owner;
    00940           ShowVehicleListWindow(owner, VEH_SHIP, (StationID)w->window_number);
    00941           break;
    00942         }
    00943       }
    00944       break;
    00945
    00946     case WE_ON_EDIT_TEXT:
    00947       if (e->we.edittext.str[0] != '\0') {
    00948         _cmd_text = e->we.edittext.str;
    00949         DoCommandP(0, w->window_number, 0, NULL,
    00950           CMD_RENAME_STATION | CMD_MSG(STR_3031_CAN_T_RENAME_STATION));
    00951       }
    00952       break;
    00953
    00954     case WE_DESTROY: {
    00955       WindowNumber wno =
    00956         (w->window_number << 16) | VLW_STATION_LIST | GetStation(w->window_number)->owner;
    00957
    00958       DeleteWindowById(WC_TRAINS_LIST, wno | (VEH_TRAIN << 11));
    00959       DeleteWindowById(WC_ROADVEH_LIST, wno | (VEH_ROAD << 11));
    00960       DeleteWindowById(WC_SHIPS_LIST, wno | (VEH_SHIP << 11));
    00961       DeleteWindowById(WC_AIRCRAFT_LIST, wno | (VEH_AIRCRAFT << 11));
    00962       break;
    00963     }
    00964
    00965     case WE_RESIZE:
    00966       if (e->we.sizing.diff.x != 0) ResizeButtons(w, SVW_LOCATION, SVW_RENAME);
    00967       w->vscroll.cap += e->we.sizing.diff.y / (int)w->resize.step_height;
    00968       break;
    00969   }
    00970 }
    00971
    00972
    00973 static const WindowDesc _station_view_desc = {
    00974   WDP_AUTO, WDP_AUTO, 249, 110, 249, 110,
    00975   WC_STATION_VIEW, WC_NONE,
    00976   WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE,
    00977   _station_view_widgets,
    00978   StationViewWndProc
    00979 };
    00980
    00986 void ShowStationViewWindow(StationID station)
    00987 {
    00988   Window *w = AllocateWindowDescFront(&_station_view_desc, station);
    00989   if (w == NULL) return;
    00990
    00991   PlayerID owner = GetStation(w->window_number)->owner;
    00992   if (owner != OWNER_NONE) w->caption_color = owner;
    00993   w->vscroll.cap = 5;
    00994   w->resize.step_height = 10;
    00995 }