# town_gui.cpp

Go to the documentation of this file.

    00001 /* $Id: town_gui.cpp 15723 2009-03-15 15:12:06Z rubidium $ */
    00002
    00005 #include "stdafx.h"
    00006 #include "openttd.h"
    00007 #include "town.h"
    00008 #include "viewport_func.h"
    00009 #include "gfx_func.h"
    00010 #include "gui.h"
    00011 #include "window_gui.h"
    00012 #include "textbuf_gui.h"
    00013 #include "command_func.h"
    00014 #include "company_func.h"
    00015 #include "company_base.h"
    00016 #include "company_gui.h"
    00017 #include "network/network.h"
    00018 #include "variables.h"
    00019 #include "strings_func.h"
    00020 #include "sound_func.h"
    00021 #include "economy_func.h"
    00022 #include "tilehighlight_func.h"
    00023 #include "sortlist_type.h"
    00024 #include "road_cmd.h"
    00025 #include "landscape_type.h"
    00026 #include "landscape.h"
    00027 #include "cargotype.h"
    00028 #include "tile_map.h"
    00029
    00030 #include "table/sprites.h"
    00031 #include "table/strings.h"
    00032
    00033 typedef GUIList<const Town*> GUITownList;
    00034
    00035 static const Widget _town_authority_widgets[] = {
    00036 {   WWT_CLOSEBOX,   RESIZE_NONE,  COLOUR_BROWN,     0,    10,     0,    13, STR_00C5,                 STR_018B_CLOSE_WINDOW},              // TWA_CLOSEBOX
    00037 {    WWT_CAPTION,   RESIZE_NONE,  COLOUR_BROWN,    11,   316,     0,    13, STR_2022_LOCAL_AUTHORITY, STR_018C_WINDOW_TITLE_DRAG_THIS},    // TWA_CAPTION
    00038 {      WWT_PANEL,   RESIZE_NONE,  COLOUR_BROWN,     0,   316,    14,   105, 0x0,                      STR_NULL},                           // TWA_RATING_INFO
    00039 {      WWT_PANEL,   RESIZE_NONE,  COLOUR_BROWN,     0,   304,   106,   157, 0x0,                      STR_2043_LIST_OF_THINGS_TO_DO_AT},   // TWA_COMMAND_LIST
    00040 {  WWT_SCROLLBAR,   RESIZE_NONE,  COLOUR_BROWN,   305,   316,   106,   157, 0x0,                      STR_0190_SCROLL_BAR_SCROLLS_LIST},   // TWA_SCROLLBAR
    00041 {      WWT_PANEL,   RESIZE_NONE,  COLOUR_BROWN,     0,   316,   158,   209, 0x0,                      STR_NULL},                           // TWA_ACTION_INFO
    00042 { WWT_PUSHTXTBTN,   RESIZE_NONE,  COLOUR_BROWN,     0,   316,   210,   221, STR_2042_DO_IT,           STR_2044_CARRY_OUT_THE_HIGHLIGHTED}, // TWA_EXECUTE
    00043 {   WIDGETS_END},
    00044 };
    00045
    00046 extern const byte _town_action_costs[8];
    00047
    00048 struct TownAuthorityWindow : Window {
    00049 private:
    00050   Town *town;
    00051   int sel_index;
    00052
    00053   enum TownAuthorityWidget {
    00054     TWA_CLOSEBOX = 0,
    00055     TWA_CAPTION,
    00056     TWA_RATING_INFO,
    00057     TWA_COMMAND_LIST,
    00058     TWA_SCROLLBAR,
    00059     TWA_ACTION_INFO,
    00060     TWA_EXECUTE,
    00061   };
    00062
    00072   static int GetNthSetBit(uint32 bits, int n)
    00073   {
    00074     if (n >= 0) {
    00075       uint i;
    00076       FOR_EACH_SET_BIT(i, bits) {
    00077         n--;
    00078         if (n < 0) return i;
    00079       }
    00080     }
    00081     return -1;
    00082   }
    00083
    00084 public:
    00085   TownAuthorityWindow(const WindowDesc *desc, WindowNumber window_number) :
    00086       Window(desc, window_number), sel_index(-1)
    00087   {
    00088     this->town = GetTown(this->window_number);
    00089     this->vscroll.cap = 5;
    00090
    00091     this->FindWindowPlacementAndResize(desc);
    00092   }
    00093
    00094   virtual void OnPaint()
    00095   {
    00096     int numact;
    00097     uint buttons = GetMaskOfTownActions(&numact, _local_company, this->town);
    00098
    00099     SetVScrollCount(this, numact + 1);
    00100
    00101     if (this->sel_index != -1 && !HasBit(buttons, this->sel_index)) {
    00102       this->sel_index = -1;
    00103     }
    00104
    00105     this->SetWidgetDisabledState(6, this->sel_index == -1);
    00106
    00107     SetDParam(0, this->window_number);
    00108     this->DrawWidgets();
    00109
    00110     int y = this->widget[TWA_RATING_INFO].top + 1;
    00111
    00112     DrawString(2, y, STR_2023_TRANSPORT_COMPANY_RATINGS, TC_FROMSTRING);
    00113     y += 10;
    00114
    00115     /* Draw list of companies */
    00116     const Company *c;
    00117     FOR_ALL_COMPANIES(c) {
    00118       if ((HasBit(this->town->have_ratings, c->index) || this->town->exclusivity == c->index)) {
    00119         DrawCompanyIcon(c->index, 2, y);
    00120
    00121         SetDParam(0, c->index);
    00122         SetDParam(1, c->index);
    00123
    00124         int r = this->town->ratings[c->index];
    00125         StringID str;
    00126         (str = STR_3035_APPALLING, r <= RATING_APPALLING) || // Apalling
    00127         (str++,                    r <= RATING_VERYPOOR)  || // Very Poor
    00128         (str++,                    r <= RATING_POOR)      || // Poor
    00129         (str++,                    r <= RATING_MEDIOCRE)  || // Mediocore
    00130         (str++,                    r <= RATING_GOOD)      || // Good
    00131         (str++,                    r <= RATING_VERYGOOD)  || // Very Good
    00132         (str++,                    r <= RATING_EXCELLENT) || // Excellent
    00133         (str++,                    true);                    // Outstanding
    00134
    00135         SetDParam(2, str);
    00136         if (this->town->exclusivity == c->index) { // red icon for company with exclusive rights
    00137           DrawSprite(SPR_BLOT, PALETTE_TO_RED, 18, y);
    00138         }
    00139
    00140         DrawString(28, y, STR_2024, TC_FROMSTRING);
    00141         y += 10;
    00142       }
    00143     }
    00144
    00145     if (y > this->widget[TWA_RATING_INFO].bottom) {
    00146       /* If the company list is too big to fit, mark ourself dirty and draw again. */
    00147       ResizeWindowForWidget(this, TWA_RATING_INFO, 0, y - this->widget[TWA_RATING_INFO].bottom);
    00148       this->SetDirty();
    00149       return;
    00150     }
    00151
    00152     y = this->widget[TWA_COMMAND_LIST].top + 1;
    00153     int pos = this->vscroll.pos;
    00154
    00155     if (--pos < 0) {
    00156       DrawString(2, y, STR_2045_ACTIONS_AVAILABLE, TC_FROMSTRING);
    00157       y += 10;
    00158     }
    00159
    00160     for (int i = 0; buttons; i++, buttons >>= 1) {
    00161       if (pos <= -5) break;
    00162
    00163       if ((buttons & 1) && --pos < 0) {
    00164         DrawString(3, y, STR_2046_SMALL_ADVERTISING_CAMPAIGN + i, TC_ORANGE);
    00165         y += 10;
    00166       }
    00167     }
    00168
    00169     if (this->sel_index != -1) {
    00170       SetDParam(1, (_price.build_industry >> 8) * _town_action_costs[this->sel_index]);
    00171       SetDParam(0, STR_2046_SMALL_ADVERTISING_CAMPAIGN + this->sel_index);
    00172       DrawStringMultiLine(2, this->widget[TWA_ACTION_INFO].top + 1, STR_204D_INITIATE_A_SMALL_LOCAL + this->sel_index, 313);
    00173     }
    00174   }
    00175
    00176   virtual void OnDoubleClick(Point pt, int widget) { HandleClick(pt, widget, true); }
    00177   virtual void OnClick(Point pt, int widget) { HandleClick(pt, widget, false); }
    00178
    00179   void HandleClick(Point pt, int widget, bool double_click)
    00180   {
    00181     switch (widget) {
    00182       case TWA_COMMAND_LIST: {
    00183         int y = (pt.y - this->widget[TWA_COMMAND_LIST].top - 1) / 10;
    00184
    00185         if (!IsInsideMM(y, 0, 5)) return;
    00186
    00187         y = GetNthSetBit(GetMaskOfTownActions(NULL, _local_company, this->town), y + this->vscroll.pos - 1);
    00188         if (y >= 0) {
    00189           this->sel_index = y;
    00190           this->SetDirty();
    00191         }
    00192         /* Fall through to clicking in case we are double-clicked */
    00193         if (!double_click || y < 0) break;
    00194       }
    00195
    00196       case TWA_EXECUTE:
    00197         DoCommandP(this->town->xy, this->window_number, this->sel_index, CMD_DO_TOWN_ACTION | CMD_MSG(STR_00B4_CAN_T_DO_THIS));
    00198         break;
    00199     }
    00200   }
    00201
    00202   virtual void OnHundredthTick()
    00203   {
    00204     this->SetDirty();
    00205   }
    00206 };
    00207
    00208 static const WindowDesc _town_authority_desc(
    00209   WDP_AUTO, WDP_AUTO, 317, 222, 317, 222,
    00210   WC_TOWN_AUTHORITY, WC_NONE,
    00211   WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
    00212   _town_authority_widgets
    00213 );
    00214
    00215 static void ShowTownAuthorityWindow(uint town)
    00216 {
    00217   AllocateWindowDescFront<TownAuthorityWindow>(&_town_authority_desc, town);
    00218 }
    00219
    00220 struct TownViewWindow : Window {
    00221 private:
    00222   Town *town;
    00223
    00224   enum TownViewWidget {
    00225     TVW_CAPTION = 1,
    00226     TVW_STICKY,
    00227     TVW_VIEWPORTPANEL,
    00228     TVW_INFOPANEL = 5,
    00229     TVW_CENTERVIEW,
    00230     TVW_SHOWAUTORITY,
    00231     TVW_CHANGENAME,
    00232     TVW_EXPAND,
    00233     TVW_DELETE,
    00234   };
    00235
    00236 public:
    00237   enum {
    00238     TVW_HEIGHT_NORMAL = 150,
    00239   };
    00240
    00241   TownViewWindow(const WindowDesc *desc, WindowNumber window_number) : Window(desc, window_number)
    00242   {
    00243     this->town = GetTown(this->window_number);
    00244     bool ingame = _game_mode != GM_EDITOR;
    00245
    00246     this->flags4 |= WF_DISABLE_VP_SCROLL;
    00247     InitializeWindowViewport(this, 3, 17, 254, 86, this->town->xy, ZOOM_LVL_TOWN);
    00248
    00249     if (this->town->larger_town) this->widget[TVW_CAPTION].data = STR_CITY;
    00250     this->SetWidgetHiddenState(TVW_DELETE, ingame);  // hide delete button on game mode
    00251     this->SetWidgetHiddenState(TVW_EXPAND, ingame);  // hide expand button on game mode
    00252     this->SetWidgetHiddenState(TVW_SHOWAUTORITY, !ingame); // hide autority button on editor mode
    00253
    00254     if (ingame) {
    00255       /* resize caption bar */
    00256       this->widget[TVW_CAPTION].right = this->widget[TVW_STICKY].left -1;
    00257       /* move the rename from top on scenario to bottom in game */
    00258       this->widget[TVW_CHANGENAME].top = this->widget[TVW_EXPAND].top;
    00259       this->widget[TVW_CHANGENAME].bottom = this->widget[TVW_EXPAND].bottom;
    00260       this->widget[TVW_CHANGENAME].right = this->widget[TVW_STICKY].right;
    00261     }
    00262
    00263     this->ResizeWindowAsNeeded();
    00264
    00265     this->FindWindowPlacementAndResize(desc);
    00266   }
    00267
    00268   virtual void OnPaint()
    00269   {
    00270     /* disable renaming town in network games if you are not the server */
    00271     this->SetWidgetDisabledState(TVW_CHANGENAME, _networking && !_network_server);
    00272
    00273     SetDParam(0, this->town->index);
    00274     this->DrawWidgets();
    00275
    00276     uint y = 107;
    00277
    00278     SetDParam(0, this->town->population);
    00279     SetDParam(1, this->town->num_houses);
    00280     DrawString(2, y, STR_2006_POPULATION, TC_FROMSTRING);
    00281
    00282     SetDParam(0, this->town->act_pass);
    00283     SetDParam(1, this->town->max_pass);
    00284     DrawString(2, y += 10, STR_200D_PASSENGERS_LAST_MONTH_MAX, TC_FROMSTRING);
    00285
    00286     SetDParam(0, this->town->act_mail);
    00287     SetDParam(1, this->town->max_mail);
    00288     DrawString(2, y += 10, STR_200E_MAIL_LAST_MONTH_MAX, TC_FROMSTRING);
    00289
    00290     uint cargo_needed_for_growth = 0;
    00291     switch (_settings_game.game_creation.landscape) {
    00292       case LT_ARCTIC:
    00293         if (TilePixelHeight(this->town->xy) >= LowestSnowLine()) cargo_needed_for_growth = 1;
    00294         break;
    00295
    00296       case LT_TROPIC:
    00297         if (GetTropicZone(this->town->xy) == TROPICZONE_DESERT) cargo_needed_for_growth = 2;
    00298         break;
    00299
    00300       default: break;
    00301     }
    00302
    00303     if (cargo_needed_for_growth > 0) {
    00304       DrawString(2, y += 10, STR_CARGO_FOR_TOWNGROWTH, TC_FROMSTRING);
    00305
    00306       CargoID first_food_cargo = CT_INVALID;
    00307       StringID food_name = STR_001E_FOOD;
    00308       CargoID first_water_cargo = CT_INVALID;
    00309       StringID water_name = STR_0021_WATER;
    00310       for (CargoID cid = 0; cid < NUM_CARGO; cid++) {
    00311         const CargoSpec *cs = GetCargo(cid);
    00312         if (first_food_cargo == CT_INVALID && cs->town_effect == TE_FOOD) {
    00313           first_food_cargo = cid;
    00314           food_name = cs->name;
    00315         }
    00316         if (first_water_cargo == CT_INVALID && cs->town_effect == TE_WATER) {
    00317           first_water_cargo = cid;
    00318           water_name = cs->name;
    00319         }
    00320       }
    00321
    00322       if (first_food_cargo != CT_INVALID && this->town->act_food > 0) {
    00323         SetDParam(0, first_food_cargo);
    00324         SetDParam(1, this->town->act_food);
    00325         DrawString(2, y += 10, STR_CARGO_FOR_TOWNGROWTH_LAST_MONTH, TC_FROMSTRING);
    00326       } else {
    00327         SetDParam(0, food_name);
    00328         DrawString(2, y += 10, STR_CARGO_FOR_TOWNGROWTH_REQUIRED, TC_FROMSTRING);
    00329       }
    00330
    00331       if (cargo_needed_for_growth > 1) {
    00332         if (first_water_cargo != CT_INVALID && this->town->act_water > 0) {
    00333           SetDParam(0, first_water_cargo);
    00334           SetDParam(1, this->town->act_water);
    00335           DrawString(2, y += 10, STR_CARGO_FOR_TOWNGROWTH_LAST_MONTH, TC_FROMSTRING);
    00336         } else {
    00337           SetDParam(0, water_name);
    00338           DrawString(2, y += 10, STR_CARGO_FOR_TOWNGROWTH_REQUIRED, TC_FROMSTRING);
    00339         }
    00340       }
    00341     }
    00342
    00343     this->DrawViewport();
    00344
    00345     /* only show the town noise, if the noise option is activated. */
    00346     if (_settings_game.economy.station_noise_level) {
    00347       SetDParam(0, this->town->noise_reached);
    00348       SetDParam(1, this->town->MaxTownNoise());
    00349       DrawString(2, y += 10, STR_NOISE_IN_TOWN, TC_FROMSTRING);
    00350     }
    00351   }
    00352
    00353   virtual void OnClick(Point pt, int widget)
    00354   {
    00355     switch (widget) {
    00356       case TVW_CENTERVIEW: // scroll to location
    00357         if (_ctrl_pressed) {
    00358           ShowExtraViewPortWindow(this->town->xy);
    00359         } else {
    00360           ScrollMainWindowToTile(this->town->xy);
    00361         }
    00362         break;
    00363
    00364       case TVW_SHOWAUTORITY: // town authority
    00365         ShowTownAuthorityWindow(this->window_number);
    00366         break;
    00367
    00368       case TVW_CHANGENAME: // rename
    00369         SetDParam(0, this->window_number);
    00370         ShowQueryString(STR_TOWN, STR_2007_RENAME_TOWN, MAX_LENGTH_TOWN_NAME_BYTES, MAX_LENGTH_TOWN_NAME_PIXELS, this, CS_ALPHANUMERAL, QSF_ENABLE_DEFAULT);
    00371         break;
    00372
    00373       case TVW_EXPAND: // expand town - only available on Scenario editor
    00374         ExpandTown(this->town);
    00375         break;
    00376
    00377       case TVW_DELETE: // delete town - only available on Scenario editor
    00378         delete this->town;
    00379         break;
    00380     }
    00381   }
    00382
    00383   void ResizeWindowAsNeeded()
    00384   {
    00385     int aimed_height = TVW_HEIGHT_NORMAL;
    00386
    00387     switch (_settings_game.game_creation.landscape) {
    00388       case LT_ARCTIC:
    00389         if (TilePixelHeight(this->town->xy) >= LowestSnowLine()) aimed_height += 20;
    00390         break;
    00391
    00392       case LT_TROPIC:
    00393         if (GetTropicZone(this->town->xy) == TROPICZONE_DESERT) aimed_height += 30;
    00394         break;
    00395
    00396       default: break;
    00397     }
    00398
    00399     if (_settings_game.economy.station_noise_level) aimed_height += 10;
    00400
    00401     if (this->height != aimed_height) ResizeWindowForWidget(this, TVW_INFOPANEL, 0, aimed_height - this->height);
    00402   }
    00403
    00404   virtual void OnInvalidateData(int data = 0)
    00405   {
    00406     /* Called when setting station noise have changed, in order to resize the window */
    00407     this->SetDirty(); // refresh display for current size. This will allow to avoid glitches when downgrading
    00408     this->ResizeWindowAsNeeded();
    00409   }
    00410
    00411   virtual void OnQueryTextFinished(char *str)
    00412   {
    00413     if (str == NULL) return;
    00414
    00415     DoCommandP(0, this->window_number, 0, CMD_RENAME_TOWN | CMD_MSG(STR_2008_CAN_T_RENAME_TOWN), NULL, str);
    00416   }
    00417 };
    00418
    00419
    00420 static const Widget _town_view_widgets[] = {
    00421 {   WWT_CLOSEBOX,   RESIZE_NONE,  COLOUR_BROWN,     0,    10,     0,    13, STR_00C5,                 STR_018B_CLOSE_WINDOW},
    00422 {    WWT_CAPTION,   RESIZE_NONE,  COLOUR_BROWN,    11,   172,     0,    13, STR_2005,                 STR_018C_WINDOW_TITLE_DRAG_THIS},
    00423 {  WWT_STICKYBOX,   RESIZE_NONE,  COLOUR_BROWN,   248,   259,     0,    13, 0x0,                      STR_STICKY_BUTTON},
    00424 {      WWT_PANEL,   RESIZE_NONE,  COLOUR_BROWN,     0,   259,    14,   105, 0x0,                      STR_NULL},
    00425 {      WWT_INSET,   RESIZE_NONE,  COLOUR_BROWN,     2,   257,    16,   103, 0x0,                      STR_NULL},
    00426 {      WWT_PANEL,   RESIZE_NONE,  COLOUR_BROWN,     0,   259,   106,   137, 0x0,                      STR_NULL},
    00427 { WWT_PUSHTXTBTN,   RESIZE_NONE,  COLOUR_BROWN,     0,    85,   138,   149, STR_00E4_LOCATION,        STR_200B_CENTER_THE_MAIN_VIEW_ON},
    00428 { WWT_PUSHTXTBTN,   RESIZE_NONE,  COLOUR_BROWN,    86,   171,   138,   149, STR_2020_LOCAL_AUTHORITY, STR_2021_SHOW_INFORMATION_ON_LOCAL},
    00429 { WWT_PUSHTXTBTN,   RESIZE_NONE,  COLOUR_BROWN,   172,   247,     0,    13, STR_0130_RENAME,          STR_200C_CHANGE_TOWN_NAME},
    00430 { WWT_PUSHTXTBTN,   RESIZE_NONE,  COLOUR_BROWN,    86,   171,   138,   149, STR_023C_EXPAND,          STR_023B_INCREASE_SIZE_OF_TOWN},
    00431 { WWT_PUSHTXTBTN,   RESIZE_NONE,  COLOUR_BROWN,   172,   259,   138,   149, STR_0290_DELETE,          STR_0291_DELETE_THIS_TOWN_COMPLETELY},
    00432 {   WIDGETS_END},
    00433 };
    00434
    00435 static const WindowDesc _town_view_desc(
    00436   WDP_AUTO, WDP_AUTO, 260, TownViewWindow::TVW_HEIGHT_NORMAL, 260, TownViewWindow::TVW_HEIGHT_NORMAL,
    00437   WC_TOWN_VIEW, WC_NONE,
    00438   WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON,
    00439   _town_view_widgets
    00440 );
    00441
    00442 void ShowTownViewWindow(TownID town)
    00443 {
    00444   AllocateWindowDescFront<TownViewWindow>(&_town_view_desc, town);
    00445 }
    00446
    00447 static const Widget _town_directory_widgets[] = {
    00448 {   WWT_CLOSEBOX,   RESIZE_NONE,  COLOUR_BROWN,     0,    10,     0,    13, STR_00C5,               STR_018B_CLOSE_WINDOW},
    00449 {    WWT_CAPTION,   RESIZE_NONE,  COLOUR_BROWN,    11,   195,     0,    13, STR_2000_TOWNS,         STR_018C_WINDOW_TITLE_DRAG_THIS},
    00450 {  WWT_STICKYBOX,   RESIZE_NONE,  COLOUR_BROWN,   196,   207,     0,    13, 0x0,                    STR_STICKY_BUTTON},
    00451 { WWT_PUSHTXTBTN,   RESIZE_NONE,  COLOUR_BROWN,     0,    98,    14,    25, STR_SORT_BY_NAME,       STR_SORT_ORDER_TIP},
    00452 { WWT_PUSHTXTBTN,   RESIZE_NONE,  COLOUR_BROWN,    99,   195,    14,    25, STR_SORT_BY_POPULATION, STR_SORT_ORDER_TIP},
    00453 {      WWT_PANEL, RESIZE_BOTTOM,  COLOUR_BROWN,     0,   195,    26,   189, 0x0,                    STR_200A_TOWN_NAMES_CLICK_ON_NAME},
    00454 {  WWT_SCROLLBAR, RESIZE_BOTTOM,  COLOUR_BROWN,   196,   207,    14,   189, 0x0,                    STR_0190_SCROLL_BAR_SCROLLS_LIST},
    00455 {      WWT_PANEL,     RESIZE_TB,  COLOUR_BROWN,     0,   195,   190,   201, 0x0,                    STR_NULL},
    00456 {  WWT_RESIZEBOX,     RESIZE_TB,  COLOUR_BROWN,   196,   207,   190,   201, 0x0,                    STR_RESIZE_BUTTON},
    00457 {   WIDGETS_END},
    00458 };
    00459
    00460
    00461 struct TownDirectoryWindow : public Window {
    00462 private:
    00463   enum TownDirectoryWidget {
    00464     TDW_SORTNAME = 3,
    00465     TDW_SORTPOPULATION,
    00466     TDW_CENTERTOWN,
    00467   };
    00468
    00469   /* Runtime saved values */
    00470   static Listing last_sorting;
    00471   static const Town *last_town;
    00472
    00473   /* Constants for sorting towns */
    00474   static GUITownList::SortFunction * const sorter_funcs[];
    00475
    00476   GUITownList towns;
    00477
    00478   void BuildTownList()
    00479   {
    00480     if (!this->towns.NeedRebuild()) return;
    00481
    00482     this->towns.Clear();
    00483
    00484     const Town *t;
    00485     FOR_ALL_TOWNS(t) {
    00486       *this->towns.Append() = t;
    00487     }
    00488
    00489     this->towns.Compact();
    00490     this->towns.RebuildDone();
    00491   }
    00492
    00493   void SortTownList()
    00494   {
    00495     last_town = NULL;
    00496     this->towns.Sort();
    00497   }
    00498
    00500   static int CDECL TownNameSorter(const Town * const *a, const Town * const *b)
    00501   {
    00502     static char buf_cache[64];
    00503     const Town *ta = *a;
    00504     const Town *tb = *b;
    00505     char buf[64];
    00506
    00507     SetDParam(0, ta->index);
    00508     GetString(buf, STR_TOWN, lastof(buf));
    00509
    00510     /* If 'b' is the same town as in the last round, use the cached value
    00511      * We do this to speed stuff up ('b' is called with the same value a lot of
    00512      * times after eachother) */
    00513     if (tb != last_town) {
    00514       last_town = tb;
    00515       SetDParam(0, tb->index);
    00516       GetString(buf_cache, STR_TOWN, lastof(buf_cache));
    00517     }
    00518
    00519     return strcmp(buf, buf_cache);
    00520   }
    00521
    00523   static int CDECL TownPopulationSorter(const Town * const *a, const Town * const *b)
    00524   {
    00525     return (*a)->population - (*b)->population;
    00526   }
    00527
    00528 public:
    00529   TownDirectoryWindow(const WindowDesc *desc) : Window(desc, 0)
    00530   {
    00531     this->vscroll.cap = 16;
    00532     this->resize.step_height = 10;
    00533     this->resize.height = this->height - 10 * 6; // minimum of 10 items in the list, each item 10 high
    00534
    00535     this->towns.SetListing(this->last_sorting);
    00536     this->towns.SetSortFuncs(this->sorter_funcs);
    00537     this->towns.ForceRebuild();
    00538
    00539     this->FindWindowPlacementAndResize(desc);
    00540   }
    00541
    00542   ~TownDirectoryWindow()
    00543   {
    00544     this->last_sorting = this->towns.GetListing();
    00545   }
    00546
    00547   virtual void OnPaint()
    00548   {
    00549     this->BuildTownList();
    00550     this->SortTownList();
    00551
    00552     SetVScrollCount(this, this->towns.Length());
    00553
    00554     this->DrawWidgets();
    00555     this->DrawSortButtonState(this->towns.SortType() == 0 ? TDW_SORTNAME : TDW_SORTPOPULATION, this->towns.IsDescSortOrder() ? SBS_DOWN : SBS_UP);
    00556
    00557     {
    00558       int n = 0;
    00559       uint16 i = this->vscroll.pos;
    00560       int y = 28;
    00561
    00562       while (i < this->towns.Length()) {
    00563         const Town *t = this->towns[i];
    00564
    00565         assert(t->xy != INVALID_TILE);
    00566
    00567         SetDParam(0, t->index);
    00568         SetDParam(1, t->population);
    00569         DrawString(2, y, STR_2057, TC_FROMSTRING);
    00570
    00571         y += 10;
    00572         i++;
    00573         if (++n == this->vscroll.cap) break; // max number of towns in 1 window
    00574       }
    00575
    00576       SetDParam(0, GetWorldPopulation());
    00577       DrawString(3, this->height - 12 + 2, STR_TOWN_POPULATION, TC_FROMSTRING);
    00578     }
    00579   }
    00580
    00581   virtual void OnClick(Point pt, int widget)
    00582   {
    00583     switch (widget) {
    00584       case TDW_SORTNAME: // Sort by Name ascending/descending
    00585         if (this->towns.SortType() == 0) {
    00586           this->towns.ToggleSortOrder();
    00587         } else {
    00588           this->towns.SetSortType(0);
    00589         }
    00590         this->SetDirty();
    00591         break;
    00592
    00593       case TDW_SORTPOPULATION: // Sort by Population ascending/descending
    00594         if (this->towns.SortType() == 1) {
    00595           this->towns.ToggleSortOrder();
    00596         } else {
    00597           this->towns.SetSortType(1);
    00598         }
    00599         this->SetDirty();
    00600         break;
    00601
    00602       case TDW_CENTERTOWN: { // Click on Town Matrix
    00603         uint16 id_v = (pt.y - 28) / 10;
    00604
    00605         if (id_v >= this->vscroll.cap) return; // click out of bounds
    00606
    00607         id_v += this->vscroll.pos;
    00608
    00609         if (id_v >= this->towns.Length()) return; // click out of town bounds
    00610
    00611         const Town *t = this->towns[id_v];
    00612         assert(t->xy != INVALID_TILE);
    00613         if (_ctrl_pressed) {
    00614           ShowExtraViewPortWindow(t->xy);
    00615         } else {
    00616           ScrollMainWindowToTile(t->xy);
    00617         }
    00618         break;
    00619       }
    00620     }
    00621   }
    00622
    00623   virtual void OnHundredthTick()
    00624   {
    00625     this->SetDirty();
    00626   }
    00627
    00628   virtual void OnResize(Point new_size, Point delta)
    00629   {
    00630     this->vscroll.cap += delta.y / 10;
    00631   }
    00632
    00633   virtual void OnInvalidateData(int data)
    00634   {
    00635     if (data == 0) {
    00636       this->towns.ForceRebuild();
    00637     } else {
    00638       this->towns.ForceResort();
    00639     }
    00640   }
    00641 };
    00642
    00643 Listing TownDirectoryWindow::last_sorting = {false, 0};
    00644 const Town *TownDirectoryWindow::last_town = NULL;
    00645
    00646 /* Available town directory sorting functions */
    00647 GUITownList::SortFunction * const TownDirectoryWindow::sorter_funcs[] = {
    00648   &TownNameSorter,
    00649   &TownPopulationSorter,
    00650 };
    00651
    00652 static const WindowDesc _town_directory_desc(
    00653   WDP_AUTO, WDP_AUTO, 208, 202, 208, 202,
    00654   WC_TOWN_DIRECTORY, WC_NONE,
    00655   WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE,
    00656   _town_directory_widgets
    00657 );
    00658
    00659 void ShowTownDirectory()
    00660 {
    00661   if (BringWindowToFrontById(WC_TOWN_DIRECTORY, 0)) return;
    00662   new TownDirectoryWindow(&_town_directory_desc);
    00663 }
    00664
    00665 void CcBuildTown(bool success, TileIndex tile, uint32 p1, uint32 p2)
    00666 {
    00667   if (success) {
    00668     SndPlayTileFx(SND_1F_SPLAT, tile);
    00669     if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace();
    00670   }
    00671 }
    00672
    00673 static const Widget _found_town_widgets[] = {
    00674 {   WWT_CLOSEBOX,   RESIZE_NONE,  COLOUR_DARK_GREEN,    0,    10,     0,    13, STR_00C5,                 STR_018B_CLOSE_WINDOW},
    00675 {    WWT_CAPTION,   RESIZE_NONE,  COLOUR_DARK_GREEN,   11,   147,     0,    13, STR_0233_TOWN_GENERATION, STR_018C_WINDOW_TITLE_DRAG_THIS},
    00676 {  WWT_STICKYBOX,   RESIZE_NONE,  COLOUR_DARK_GREEN,  148,   159,     0,    13, 0x0,                      STR_STICKY_BUTTON},
    00677 {      WWT_PANEL,   RESIZE_NONE,  COLOUR_DARK_GREEN,    0,   159,    14,   161, 0x0,                      STR_NULL},
    00678
    00679 {    WWT_TEXTBTN,   RESIZE_NONE,  COLOUR_GREY,          2,   157,    16,    27, STR_0234_NEW_TOWN,        STR_0235_CONSTRUCT_NEW_TOWN},
    00680 {    WWT_TEXTBTN,   RESIZE_NONE,  COLOUR_GREY,          2,   157,    29,    40, STR_023D_RANDOM_TOWN,     STR_023E_BUILD_TOWN_IN_RANDOM_LOCATION},
    00681 {    WWT_TEXTBTN,   RESIZE_NONE,  COLOUR_GREY,          2,   157,    42,    53, STR_MANY_RANDOM_TOWNS,    STR_RANDOM_TOWNS_TIP},
    00682
    00683 {      WWT_LABEL,   RESIZE_NONE,  COLOUR_DARK_GREEN,    0,   147,    54,    67, STR_02A5_TOWN_SIZE,          STR_NULL},
    00684 {    WWT_TEXTBTN,   RESIZE_NONE,  COLOUR_GREY,          2,    79,    68,    79, STR_02A1_SMALL,              STR_02A4_SELECT_TOWN_SIZE},
    00685 {    WWT_TEXTBTN,   RESIZE_NONE,  COLOUR_GREY,         80,   157,    68,    79, STR_02A2_MEDIUM,             STR_02A4_SELECT_TOWN_SIZE},
    00686 {    WWT_TEXTBTN,   RESIZE_NONE,  COLOUR_GREY,          2,    79,    81,    92, STR_02A3_LARGE,              STR_02A4_SELECT_TOWN_SIZE},
    00687 {    WWT_TEXTBTN,   RESIZE_NONE,  COLOUR_GREY,         80,   157,    81,    92, STR_SELECT_TOWN_SIZE_RANDOM, STR_02A4_SELECT_TOWN_SIZE},
    00688 {    WWT_TEXTBTN,   RESIZE_NONE,  COLOUR_GREY,          2,   157,    96,   107, STR_FOUND_TOWN_CITY,         STR_FOUND_TOWN_CITY_TOOLTIP},
    00689
    00690 {      WWT_LABEL,   RESIZE_NONE,  COLOUR_DARK_GREEN,    0,   147,   108,   121, STR_TOWN_ROAD_LAYOUT,           STR_NULL},
    00691 {    WWT_TEXTBTN,   RESIZE_NONE,  COLOUR_GREY,          2,    79,   122,   133, STR_SELECT_LAYOUT_ORIGINAL,     STR_SELECT_TOWN_ROAD_LAYOUT},
    00692 {    WWT_TEXTBTN,   RESIZE_NONE,  COLOUR_GREY,         80,   157,   122,   133, STR_SELECT_LAYOUT_BETTER_ROADS, STR_SELECT_TOWN_ROAD_LAYOUT},
    00693 {    WWT_TEXTBTN,   RESIZE_NONE,  COLOUR_GREY,          2,    79,   135,   146, STR_SELECT_LAYOUT_2X2_GRID,     STR_SELECT_TOWN_ROAD_LAYOUT},
    00694 {    WWT_TEXTBTN,   RESIZE_NONE,  COLOUR_GREY,         80,   157,   135,   146, STR_SELECT_LAYOUT_3X3_GRID,     STR_SELECT_TOWN_ROAD_LAYOUT},
    00695 {    WWT_TEXTBTN,   RESIZE_NONE,  COLOUR_GREY,          2,   157,   148,   159, STR_SELECT_LAYOUT_RANDOM,       STR_SELECT_TOWN_ROAD_LAYOUT},
    00696
    00697 {   WIDGETS_END},
    00698 };
    00699
    00700 struct FoundTownWindow : Window
    00701 {
    00702 private:
    00703   enum TownScenarioEditorWidget {
    00704     TSEW_NEWTOWN = 4,
    00705     TSEW_RANDOMTOWN,
    00706     TSEW_MANYRANDOMTOWNS,
    00707     TSEW_TOWNSIZE,
    00708     TSEW_SIZE_SMALL,
    00709     TSEW_SIZE_MEDIUM,
    00710     TSEW_SIZE_LARGE,
    00711     TSEW_SIZE_RANDOM,
    00712     TSEW_CITY,
    00713     TSEW_TOWNLAYOUT,
    00714     TSEW_LAYOUT_ORIGINAL,
    00715     TSEW_LAYOUT_BETTER,
    00716     TSEW_LAYOUT_GRID2,
    00717     TSEW_LAYOUT_GRID3,
    00718     TSEW_LAYOUT_RANDOM,
    00719   };
    00720
    00721   static TownSize town_size;
    00722   static bool city;
    00723   static TownLayout town_layout;
    00724
    00725 public:
    00726   FoundTownWindow(const WindowDesc *desc, WindowNumber window_number) : Window(desc, window_number)
    00727   {
    00728     this->FindWindowPlacementAndResize(desc);
    00729     town_layout = _settings_game.economy.town_layout;
    00730     city = false;
    00731     this->UpdateButtons();
    00732   }
    00733
    00734   void UpdateButtons()
    00735   {
    00736     for (int i = TSEW_SIZE_SMALL; i <= TSEW_SIZE_RANDOM; i++) {
    00737       this->SetWidgetLoweredState(i, i == TSEW_SIZE_SMALL + town_size);
    00738     }
    00739
    00740     this->SetWidgetLoweredState(TSEW_CITY, city);
    00741
    00742     for (int i = TSEW_LAYOUT_ORIGINAL; i <= TSEW_LAYOUT_RANDOM; i++) {
    00743       this->SetWidgetLoweredState(i, i == TSEW_LAYOUT_ORIGINAL + town_layout);
    00744     }
    00745
    00746     this->SetDirty();
    00747   }
    00748
    00749   virtual void OnPaint()
    00750   {
    00751     this->DrawWidgets();
    00752   }
    00753
    00754   virtual void OnClick(Point pt, int widget)
    00755   {
    00756     switch (widget) {
    00757       case TSEW_NEWTOWN:
    00758         HandlePlacePushButton(this, TSEW_NEWTOWN, SPR_CURSOR_TOWN, VHM_RECT, PlaceProc_Town);
    00759         break;
    00760
    00761       case TSEW_RANDOMTOWN: {
    00762         this->HandleButtonClick(TSEW_RANDOMTOWN);
    00763         _generating_world = true;
    00764         UpdateNearestTownForRoadTiles(true);
    00765         const Town *t = CreateRandomTown(20, town_size, city, town_layout);
    00766         UpdateNearestTownForRoadTiles(false);
    00767         _generating_world = false;
    00768
    00769         if (t == NULL) {
    00770           ShowErrorMessage(STR_NO_SPACE_FOR_TOWN, STR_CANNOT_GENERATE_TOWN, 0, 0);
    00771         } else {
    00772           ScrollMainWindowToTile(t->xy);
    00773         }
    00774       } break;
    00775
    00776       case TSEW_MANYRANDOMTOWNS:
    00777         this->HandleButtonClick(TSEW_MANYRANDOMTOWNS);
    00778
    00779         _generating_world = true;
    00780         UpdateNearestTownForRoadTiles(true);
    00781         if (!GenerateTowns(town_layout)) {
    00782           ShowErrorMessage(STR_NO_SPACE_FOR_TOWN, STR_CANNOT_GENERATE_TOWN, 0, 0);
    00783         }
    00784         UpdateNearestTownForRoadTiles(false);
    00785         _generating_world = false;
    00786         break;
    00787
    00788       case TSEW_SIZE_SMALL: case TSEW_SIZE_MEDIUM: case TSEW_SIZE_LARGE: case TSEW_SIZE_RANDOM:
    00789         town_size = (TownSize)(widget - TSEW_SIZE_SMALL);
    00790         this->UpdateButtons();
    00791         break;
    00792
    00793       case TSEW_CITY:
    00794         city ^= true;
    00795         this->SetWidgetLoweredState(TSEW_CITY, city);
    00796         this->SetDirty();
    00797         break;
    00798
    00799       case TSEW_LAYOUT_ORIGINAL: case TSEW_LAYOUT_BETTER: case TSEW_LAYOUT_GRID2:
    00800       case TSEW_LAYOUT_GRID3: case TSEW_LAYOUT_RANDOM:
    00801         town_layout = (TownLayout)(widget - TSEW_LAYOUT_ORIGINAL);
    00802         this->UpdateButtons();
    00803         break;
    00804     }
    00805   }
    00806
    00807   virtual void OnTimeout()
    00808   {
    00809     this->RaiseWidget(TSEW_RANDOMTOWN);
    00810     this->RaiseWidget(TSEW_MANYRANDOMTOWNS);
    00811     this->SetDirty();
    00812   }
    00813
    00814   virtual void OnPlaceObject(Point pt, TileIndex tile)
    00815   {
    00816     _place_proc(tile);
    00817   }
    00818
    00819   virtual void OnPlaceObjectAbort()
    00820   {
    00821     this->RaiseButtons();
    00822     this->UpdateButtons();
    00823   }
    00824
    00825   static void PlaceProc_Town(TileIndex tile)
    00826   {
    00827     uint32 townnameparts;
    00828     if (!GenerateTownName(&townnameparts)) {
    00829       ShowErrorMessage(STR_023A_TOO_MANY_TOWNS, STR_0236_CAN_T_BUILD_TOWN_HERE, 0, 0);
    00830       return;
    00831     }
    00832
    00833     DoCommandP(tile, town_size | city << 2 | town_layout << 3, townnameparts, CMD_BUILD_TOWN | CMD_MSG(STR_0236_CAN_T_BUILD_TOWN_HERE), CcBuildTown);
    00834   }
    00835 };
    00836
    00837 TownSize FoundTownWindow::town_size = TS_MEDIUM; // select medium-sized towns per default;
    00838 bool FoundTownWindow::city;
    00839 TownLayout FoundTownWindow::town_layout;
    00840
    00841 static const WindowDesc _found_town_desc(
    00842   WDP_AUTO, WDP_AUTO, 160, 162, 160, 162,
    00843   WC_FOUND_TOWN, WC_NONE,
    00844   WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_STICKY_BUTTON | WDF_CONSTRUCTION,
    00845   _found_town_widgets
    00846 );
    00847
    00848 void ShowBuildTownWindow()
    00849 {
    00850   if (_game_mode != GM_EDITOR && !IsValidCompanyID(_local_company)) return;
    00851   AllocateWindowDescFront<FoundTownWindow>(&_found_town_desc, 0);
    00852 }