# misc_gui.cpp

Go to the documentation of this file.

    00001 /* $Id: misc_gui.cpp 14385 2008-09-22 19:57:31Z rubidium $ */
    00002
    00005 #include "stdafx.h"
    00006 #include "openttd.h"
    00007 #include "heightmap.h"
    00008 #include "debug.h"
    00009 #include "landscape.h"
    00010 #include "newgrf.h"
    00011 #include "newgrf_text.h"
    00012 #include "saveload.h"
    00013 #include "tile_map.h"
    00014 #include "gui.h"
    00015 #include "window_gui.h"
    00016 #include "station_gui.h"
    00017 #include "textbuf_gui.h"
    00018 #include "viewport_func.h"
    00019 #include "gfx_func.h"
    00020 #include "station.h"
    00021 #include "command_func.h"
    00022 #include "player_func.h"
    00023 #include "player_base.h"
    00024 #include "town.h"
    00025 #include "network/network.h"
    00026 #include "variables.h"
    00027 #include "train.h"
    00028 #include "tgp.h"
    00029 #include "cargotype.h"
    00030 #include "player_face.h"
    00031 #include "strings_func.h"
    00032 #include "fileio.h"
    00033 #include "fios.h"
    00034 #include "tile_cmd.h"
    00035 #include "zoom_func.h"
    00036 #include "functions.h"
    00037 #include "window_func.h"
    00038 #include "date_func.h"
    00039 #include "sound_func.h"
    00040 #include "string_func.h"
    00041 #include "player_gui.h"
    00042 #include "settings_type.h"
    00043
    00044 #include "table/sprites.h"
    00045 #include "table/strings.h"
    00046 #include "table/tree_land.h"
    00047
    00048 /* Variables to display file lists */
    00049 FiosItem *_fios_list;
    00050 SaveLoadDialogMode _saveload_mode;
    00051
    00052
    00053 static bool _fios_path_changed;
    00054 static bool _savegame_sort_dirty;
    00055
    00056 enum {
    00057   LAND_INFO_LINES          =   7,
    00058   LAND_INFO_LINE_BUFF_SIZE = 512,
    00059 };
    00060
    00061 static char _landinfo_data[LAND_INFO_LINES][LAND_INFO_LINE_BUFF_SIZE];
    00062
    00063 static void LandInfoWndProc(Window *w, WindowEvent *e)
    00064 {
    00065   if (e->event == WE_PAINT) {
    00066     DrawWindowWidgets(w);
    00067
    00068     DoDrawStringCentered(140, 16, _landinfo_data[0], TC_LIGHT_BLUE);
    00069     DoDrawStringCentered(140, 27, _landinfo_data[1], TC_FROMSTRING);
    00070     DoDrawStringCentered(140, 38, _landinfo_data[2], TC_FROMSTRING);
    00071     DoDrawStringCentered(140, 49, _landinfo_data[3], TC_FROMSTRING);
    00072     DoDrawStringCentered(140, 60, _landinfo_data[4], TC_FROMSTRING);
    00073     if (_landinfo_data[5][0] != '\0') DrawStringMultiCenter(140, 76, BindCString(_landinfo_data[5]), w->width - 4);
    00074     if (_landinfo_data[6][0] != '\0') DoDrawStringCentered(140, 71, _landinfo_data[6], TC_FROMSTRING);
    00075   }
    00076 }
    00077
    00078 static const Widget _land_info_widgets[] = {
    00079 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,                       STR_018B_CLOSE_WINDOW},
    00080 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   279,     0,    13, STR_01A3_LAND_AREA_INFORMATION, STR_018C_WINDOW_TITLE_DRAG_THIS},
    00081 {      WWT_PANEL,   RESIZE_NONE,    14,     0,   279,    14,    92, 0x0,                            STR_NULL},
    00082 {    WIDGETS_END},
    00083 };
    00084
    00085 static const WindowDesc _land_info_desc = {
    00086   WDP_AUTO, WDP_AUTO, 280, 93, 280, 93,
    00087   WC_LAND_INFO, WC_NONE,
    00088   WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
    00089   _land_info_widgets,
    00090   LandInfoWndProc
    00091 };
    00092
    00093 static void Place_LandInfo(TileIndex tile)
    00094 {
    00095   Player *p;
    00096   Window *w;
    00097   Town *t;
    00098   Money old_money;
    00099   CommandCost costclear;
    00100   AcceptedCargo ac;
    00101   TileDesc td;
    00102   StringID str;
    00103
    00104   DeleteWindowById(WC_LAND_INFO, 0);
    00105
    00106   w = AllocateWindowDesc(&_land_info_desc);
    00107   WP(w, void_d).data = &_landinfo_data;
    00108
    00109   p = GetPlayer(IsValidPlayer(_local_player) ? _local_player : PLAYER_FIRST);
    00110   t = ClosestTownFromTile(tile, _patches.dist_local_authority);
    00111
    00112   old_money = p->player_money;
    00113   p->player_money = INT64_MAX;
    00114   costclear = DoCommand(tile, 0, 0, 0, CMD_LANDSCAPE_CLEAR);
    00115   p->player_money = old_money;
    00116
    00117   /* Because build_date is not set yet in every TileDesc, we make sure it is empty */
    00118   td.build_date = 0;
    00119   GetAcceptedCargo(tile, ac);
    00120   GetTileDesc(tile, &td);
    00121
    00122   SetDParam(0, td.dparam[0]);
    00123   GetString(_landinfo_data[0], td.str, lastof(_landinfo_data[0]));
    00124
    00125   SetDParam(0, STR_01A6_N_A);
    00126   if (td.owner != OWNER_NONE && td.owner != OWNER_WATER) GetNameOfOwner(td.owner, tile);
    00127   GetString(_landinfo_data[1], STR_01A7_OWNER, lastof(_landinfo_data[1]));
    00128
    00129   str = STR_01A4_COST_TO_CLEAR_N_A;
    00130   if (CmdSucceeded(costclear)) {
    00131     SetDParam(0, costclear.GetCost());
    00132     str = STR_01A5_COST_TO_CLEAR;
    00133   }
    00134   GetString(_landinfo_data[2], str, lastof(_landinfo_data[2]));
    00135
    00136   snprintf(_userstring, lengthof(_userstring), "0x%.4X", tile);
    00137   SetDParam(0, TileX(tile));
    00138   SetDParam(1, TileY(tile));
    00139   SetDParam(2, TileHeight(tile));
    00140   SetDParam(3, STR_SPEC_USERSTRING);
    00141   GetString(_landinfo_data[3], STR_LANDINFO_COORDS, lastof(_landinfo_data[3]));
    00142
    00143   SetDParam(0, STR_01A9_NONE);
    00144   if (t != NULL && t->IsValid()) {
    00145     SetDParam(0, STR_TOWN);
    00146     SetDParam(1, t->index);
    00147   }
    00148   GetString(_landinfo_data[4], STR_01A8_LOCAL_AUTHORITY, lastof(_landinfo_data[4]));
    00149
    00150   {
    00151     char *p = GetString(_landinfo_data[5], STR_01CE_CARGO_ACCEPTED, lastof(_landinfo_data[5]));
    00152     bool found = false;
    00153
    00154     for (CargoID i = 0; i < NUM_CARGO; ++i) {
    00155       if (ac[i] > 0) {
    00156         /* Add a comma between each item. */
    00157         if (found) {
    00158           *p++ = ',';
    00159           *p++ = ' ';
    00160         }
    00161         found = true;
    00162
    00163         /* If the accepted value is less than 8, show it in 1/8:ths */
    00164         if (ac[i] < 8) {
    00165           SetDParam(0, ac[i]);
    00166           SetDParam(1, GetCargo(i)->name);
    00167           p = GetString(p, STR_01D1_8, lastof(_landinfo_data[5]));
    00168         } else {
    00169           p = GetString(p, GetCargo(i)->name, lastof(_landinfo_data[5]));
    00170         }
    00171       }
    00172     }
    00173
    00174     if (!found) _landinfo_data[5][0] = '\0';
    00175   }
    00176
    00177   if (td.build_date != 0) {
    00178     SetDParam(0, td.build_date);
    00179   GetString(_landinfo_data[6], STR_BUILD_DATE, lastof(_landinfo_data[6]));
    00180   } else {
    00181     _landinfo_data[6][0] = '\0';
    00182   }
    00183
    00184 #if defined(_DEBUG)
    00185 # define LANDINFOD_LEVEL 0
    00186 #else
    00187 # define LANDINFOD_LEVEL 1
    00188 #endif
    00189   DEBUG(misc, LANDINFOD_LEVEL, "TILE: %#x (%i,%i)", tile, TileX(tile), TileY(tile));
    00190   DEBUG(misc, LANDINFOD_LEVEL, "type_height  = %#x", _m[tile].type_height);
    00191   DEBUG(misc, LANDINFOD_LEVEL, "m1           = %#x", _m[tile].m1);
    00192   DEBUG(misc, LANDINFOD_LEVEL, "m2           = %#x", _m[tile].m2);
    00193   DEBUG(misc, LANDINFOD_LEVEL, "m3           = %#x", _m[tile].m3);
    00194   DEBUG(misc, LANDINFOD_LEVEL, "m4           = %#x", _m[tile].m4);
    00195   DEBUG(misc, LANDINFOD_LEVEL, "m5           = %#x", _m[tile].m5);
    00196   DEBUG(misc, LANDINFOD_LEVEL, "m6           = %#x", _m[tile].m6);
    00197   DEBUG(misc, LANDINFOD_LEVEL, "m7           = %#x", _me[tile].m7);
    00198 #undef LANDINFOD_LEVEL
    00199 }
    00200
    00201 void PlaceLandBlockInfo()
    00202 {
    00203   if (_cursor.sprite == SPR_CURSOR_QUERY) {
    00204     ResetObjectToPlace();
    00205   } else {
    00206     _place_proc = Place_LandInfo;
    00207     SetObjectToPlace(SPR_CURSOR_QUERY, PAL_NONE, VHM_RECT, WC_MAIN_TOOLBAR, 0);
    00208   }
    00209 }
    00210
    00211 static const char *credits[] = {
    00212   /*************************************************************************
    00213    *                      maximum length of string which fits in window   -^*/
    00214   "Original design by Chris Sawyer",
    00215   "Original graphics by Simon Foster",
    00216   "",
    00217   "The OpenTTD team (in alphabetical order):",
    00218   "  Jean-Francois Claeys (Belugas) - GUI, newindustries and more",
    00219   "  Bjarni Corfitzen (Bjarni) - MacOSX port, coder and vehicles",
    00220   "  Matthijs Kooijman (blathijs) - Pathfinder-guru, pool rework",
    00221   "  Loïc Guilloux (glx) - General coding",
    00222   "  Christoph Elsenhans (frosch) - General coding",
    00223   "  Jaroslav Mazanec (KUDr) - YAPG (Yet Another Pathfinder God) ;)",
    00224   "  Jonathan Coome (Maedhros) - High priest of the newGRF Temple",
    00225   "  Attila Bán (MiHaMiX) - WebTranslator, Nightlies, Wiki and bugtracker host",
    00226   "  Owen Rudge (orudge) - Forum host, OS/2 port",
    00227   "  Peter Nelson (peter1138) - Spiritual descendant from newGRF gods",
    00228   "  Remko Bijker (Rubidium) - Lead coder and way more",
    00229   "  Benedikt Brüggemeier (skidd13) - Bug fixer and code reworker",
    00230   "  Zdenek Sojka (SmatZ) - Bug finder and fixer",
    00231   "",
    00232   "Inactive Developers:",
    00233   "  Victor Fischer (Celestar) - Programming everywhere you need him to",
    00234   "  Tamás Faragó (Darkvater) - Ex-Lead coder",
    00235   "  Christoph Mallon (Tron) - Programmer, code correctness police",
    00236   "",
    00237   "Retired Developers:",
    00238   "  Ludvig Strigeus (ludde) - OpenTTD author, main coder (0.1 - 0.3.3)",
    00239   "  Serge Paquet (vurlix) - Assistant project manager, coder (0.1 - 0.3.3)",
    00240   "  Dominik Scherer (dominik81) - Lead programmer, GUI expert (0.3.0 - 0.3.6)",
    00241   "  Patric Stout (TrueLight) - Programmer, webhoster (0.3 - pre0.6)",
    00242   "",
    00243   "Special thanks go out to:",
    00244   "  Josef Drexler - For his great work on TTDPatch",
    00245   "  Marcin Grzegorczyk - For his documentation of TTD internals",
    00246   "  Petr Baudis (pasky) - Many patches, newGRF support",
    00247   "  Stefan Meißner (sign_de) - For his work on the console",
    00248   "  Simon Sasburg (HackyKid) - Many bugfixes he has blessed us with",
    00249   "  Cian Duffy (MYOB) - BeOS port / manual writing",
    00250   "  Christian Rosentreter (tokai) - MorphOS / AmigaOS port",
    00251   "  Richard Kempton (richK) - additional airports, initial TGP implementation",
    00252   "",
    00253   "  Michael Blunck - Pre-Signals and Semaphores © 2003",
    00254   "  George - Canal/Lock graphics © 2003-2004",
    00255   "  David Dallaston - Tram tracks",
    00256   "  Marcin Grzegorczyk - Foundations for Tracks on Slopes",
    00257   "  All Translators - Who made OpenTTD a truly international game",
    00258   "  Bug Reporters - Without whom OpenTTD would still be full of bugs!",
    00259   "",
    00260   "",
    00261   "And last but not least:",
    00262   "  Chris Sawyer - For an amazing game!"
    00263 };
    00264
    00265 static void AboutWindowProc(Window *w, WindowEvent *e)
    00266 {
    00267   switch (e->event) {
    00268   case WE_CREATE: // Set up window counter and start position of scroller
    00269     WP(w, scroller_d).counter = 5;
    00270     WP(w, scroller_d).height = w->height - 40;
    00271     break;
    00272   case WE_PAINT: {
    00273     uint i;
    00274     int y = WP(w, scroller_d).height;
    00275     DrawWindowWidgets(w);
    00276
    00277     /* Show original copyright and revision version */
    00278     DrawStringCentered(210, 17, STR_00B6_ORIGINAL_COPYRIGHT, TC_FROMSTRING);
    00279     DrawStringCentered(210, 17 + 10, STR_00B7_VERSION, TC_FROMSTRING);
    00280
    00281     /* Show all scrolling credits */
    00282     for (i = 0; i < lengthof(credits); i++) {
    00283       if (y >= 50 && y < (w->height - 40)) {
    00284         DoDrawString(credits[i], 10, y, TC_BLACK);
    00285       }
    00286       y += 10;
    00287     }
    00288
    00289     /* If the last text has scrolled start anew from the start */
    00290     if (y < 50) WP(w, scroller_d).height = w->height - 40;
    00291
    00292     DoDrawStringCentered(210, w->height - 25, "Website: http://www.openttd.org", TC_BLACK);
    00293     DrawStringCentered(210, w->height - 15, STR_00BA_COPYRIGHT_OPENTTD, TC_FROMSTRING);
    00294   } break;
    00295   case WE_TICK: // Timer to scroll the text and adjust the new top
    00296     if (--WP(w, scroller_d).counter == 0) {
    00297       WP(w, scroller_d).counter = 5;
    00298       WP(w, scroller_d).height--;
    00299       SetWindowDirty(w);
    00300     }
    00301     break;
    00302   }
    00303 }
    00304
    00305 static const Widget _about_widgets[] = {
    00306 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,         STR_018B_CLOSE_WINDOW},
    00307 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   419,     0,    13, STR_015B_OPENTTD, STR_NULL},
    00308 {      WWT_PANEL,   RESIZE_NONE,    14,     0,   419,    14,   271, 0x0,              STR_NULL},
    00309 {      WWT_FRAME,   RESIZE_NONE,    14,     5,   414,    40,   245, STR_NULL,         STR_NULL},
    00310 {    WIDGETS_END},
    00311 };
    00312
    00313 static const WindowDesc _about_desc = {
    00314   WDP_CENTER, WDP_CENTER, 420, 272, 420, 272,
    00315   WC_GAME_OPTIONS, WC_NONE,
    00316   WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
    00317   _about_widgets,
    00318   AboutWindowProc
    00319 };
    00320
    00321
    00322 void ShowAboutWindow()
    00323 {
    00324   DeleteWindowById(WC_GAME_OPTIONS, 0);
    00325   AllocateWindowDesc(&_about_desc);
    00326 }
    00327
    00328 static int _tree_to_plant;
    00329
    00330 static const PalSpriteID _tree_sprites[] = {
    00331   { 0x655, PAL_NONE }, { 0x663, PAL_NONE }, { 0x678, PAL_NONE }, { 0x62B, PAL_NONE },
    00332   { 0x647, PAL_NONE }, { 0x639, PAL_NONE }, { 0x64E, PAL_NONE }, { 0x632, PAL_NONE },
    00333   { 0x67F, PAL_NONE }, { 0x68D, PAL_NONE }, { 0x69B, PAL_NONE }, { 0x6A9, PAL_NONE },
    00334   { 0x6AF, PAL_NONE }, { 0x6D2, PAL_NONE }, { 0x6D9, PAL_NONE }, { 0x6C4, PAL_NONE },
    00335   { 0x6CB, PAL_NONE }, { 0x6B6, PAL_NONE }, { 0x6BD, PAL_NONE }, { 0x6E0, PAL_NONE },
    00336   { 0x72E, PAL_NONE }, { 0x734, PAL_NONE }, { 0x74A, PAL_NONE }, { 0x74F, PAL_NONE },
    00337   { 0x76B, PAL_NONE }, { 0x78F, PAL_NONE }, { 0x788, PAL_NONE }, { 0x77B, PAL_NONE },
    00338   { 0x75F, PAL_NONE }, { 0x774, PAL_NONE }, { 0x720, PAL_NONE }, { 0x797, PAL_NONE },
    00339   { 0x79E, PAL_NONE }, { 0x7A5, PALETTE_TO_GREEN }, { 0x7AC, PALETTE_TO_RED }, { 0x7B3, PAL_NONE },
    00340   { 0x7BA, PAL_NONE }, { 0x7C1, PALETTE_TO_RED, }, { 0x7C8, PALETTE_TO_PALE_GREEN }, { 0x7CF, PALETTE_TO_YELLOW }, { 0x7D6, PALETTE_TO_RED }
    00341 };
    00342
    00343 static void BuildTreesWndProc(Window *w, WindowEvent *e)
    00344 {
    00345   switch (e->event) {
    00346   case WE_PAINT: {
    00347     int x,y;
    00348     int i, count;
    00349
    00350     DrawWindowWidgets(w);
    00351
    00352     WP(w, tree_d).base = i = _tree_base_by_landscape[_opt.landscape];
    00353     WP(w, tree_d).count = count = _tree_count_by_landscape[_opt.landscape];
    00354
    00355     x = 18;
    00356     y = 54;
    00357     do {
    00358       DrawSprite(_tree_sprites[i].sprite, _tree_sprites[i].pal, x, y);
    00359       x += 35;
    00360       if (!(++i & 3)) {
    00361         x -= 35 * 4;
    00362         y += 47;
    00363       }
    00364     } while (--count);
    00365   } break;
    00366
    00367   case WE_CLICK: {
    00368     int wid = e->we.click.widget;
    00369
    00370     switch (wid) {
    00371     case 0:
    00372       ResetObjectToPlace();
    00373       break;
    00374
    00375     case 3: case 4: case 5: case 6:
    00376     case 7: case 8: case 9: case 10:
    00377     case 11:case 12: case 13: case 14:
    00378       if (wid - 3 >= WP(w, tree_d).count) break;
    00379
    00380       if (HandlePlacePushButton(w, wid, SPR_CURSOR_TREE, VHM_RECT, NULL))
    00381         _tree_to_plant = WP(w, tree_d).base + wid - 3;
    00382       break;
    00383
    00384     case 15: // tree of random type.
    00385       if (HandlePlacePushButton(w, 15, SPR_CURSOR_TREE, VHM_RECT, NULL))
    00386         _tree_to_plant = -1;
    00387       break;
    00388
    00389     case 16: // place trees randomly over the landscape
    00390       w->LowerWidget(16);
    00391       w->flags4 |= 5 << WF_TIMEOUT_SHL;
    00392       SndPlayFx(SND_15_BEEP);
    00393       PlaceTreesRandomly();
    00394       MarkWholeScreenDirty();
    00395       break;
    00396     }
    00397   } break;
    00398
    00399   case WE_PLACE_OBJ:
    00400     VpStartPlaceSizing(e->we.place.tile, VPM_X_AND_Y_LIMITED, DDSP_PLANT_TREES);
    00401     VpSetPlaceSizingLimit(20);
    00402     break;
    00403
    00404   case WE_PLACE_DRAG:
    00405     VpSelectTilesWithMethod(e->we.place.pt.x, e->we.place.pt.y, e->we.place.select_method);
    00406     return;
    00407
    00408   case WE_PLACE_MOUSEUP:
    00409     if (e->we.place.pt.x != -1 && e->we.place.select_proc == DDSP_PLANT_TREES) {
    00410       DoCommandP(e->we.place.tile, _tree_to_plant, e->we.place.starttile, NULL,
    00411         CMD_PLANT_TREE | CMD_MSG(STR_2805_CAN_T_PLANT_TREE_HERE));
    00412     }
    00413     break;
    00414
    00415   case WE_TIMEOUT:
    00416     w->RaiseWidget(16);
    00417     break;
    00418
    00419   case WE_ABORT_PLACE_OBJ:
    00420     w->RaiseButtons();
    00421     break;
    00422   }
    00423 }
    00424
    00425 static const Widget _build_trees_widgets[] = {
    00426 {   WWT_CLOSEBOX,   RESIZE_NONE,     7,     0,    10,     0,    13, STR_00C5,              STR_018B_CLOSE_WINDOW},
    00427 {    WWT_CAPTION,   RESIZE_NONE,     7,    11,   142,     0,    13, STR_2802_TREES,        STR_018C_WINDOW_TITLE_DRAG_THIS},
    00428 {      WWT_PANEL,   RESIZE_NONE,     7,     0,   142,    14,   170, 0x0,                   STR_NULL},
    00429 {      WWT_PANEL,   RESIZE_NONE,    14,     2,    35,    16,    61, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
    00430 {      WWT_PANEL,   RESIZE_NONE,    14,    37,    70,    16,    61, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
    00431 {      WWT_PANEL,   RESIZE_NONE,    14,    72,   105,    16,    61, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
    00432 {      WWT_PANEL,   RESIZE_NONE,    14,   107,   140,    16,    61, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
    00433 {      WWT_PANEL,   RESIZE_NONE,    14,     2,    35,    63,   108, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
    00434 {      WWT_PANEL,   RESIZE_NONE,    14,    37,    70,    63,   108, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
    00435 {      WWT_PANEL,   RESIZE_NONE,    14,    72,   105,    63,   108, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
    00436 {      WWT_PANEL,   RESIZE_NONE,    14,   107,   140,    63,   108, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
    00437 {      WWT_PANEL,   RESIZE_NONE,    14,     2,    35,   110,   155, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
    00438 {      WWT_PANEL,   RESIZE_NONE,    14,    37,    70,   110,   155, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
    00439 {      WWT_PANEL,   RESIZE_NONE,    14,    72,   105,   110,   155, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
    00440 {      WWT_PANEL,   RESIZE_NONE,    14,   107,   140,   110,   155, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
    00441 {    WWT_TEXTBTN,   RESIZE_NONE,    14,     2,   140,   157,   168, STR_TREES_RANDOM_TYPE, STR_TREES_RANDOM_TYPE_TIP},
    00442 {    WIDGETS_END},
    00443 };
    00444
    00445 static const WindowDesc _build_trees_desc = {
    00446   497, 22, 143, 171, 143, 171,
    00447   WC_BUILD_TREES, WC_SCEN_LAND_GEN,
    00448   WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
    00449   _build_trees_widgets,
    00450   BuildTreesWndProc
    00451 };
    00452
    00453 static const Widget _build_trees_scen_widgets[] = {
    00454 {   WWT_CLOSEBOX,   RESIZE_NONE,     7,     0,    10,     0,    13, STR_00C5,              STR_018B_CLOSE_WINDOW},
    00455 {    WWT_CAPTION,   RESIZE_NONE,     7,    11,   142,     0,    13, STR_2802_TREES,        STR_018C_WINDOW_TITLE_DRAG_THIS},
    00456 {      WWT_PANEL,   RESIZE_NONE,     7,     0,   142,    14,   183, 0x0,                   STR_NULL},
    00457 {      WWT_PANEL,   RESIZE_NONE,    14,     2,    35,    16,    61, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
    00458 {      WWT_PANEL,   RESIZE_NONE,    14,    37,    70,    16,    61, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
    00459 {      WWT_PANEL,   RESIZE_NONE,    14,    72,   105,    16,    61, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
    00460 {      WWT_PANEL,   RESIZE_NONE,    14,   107,   140,    16,    61, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
    00461 {      WWT_PANEL,   RESIZE_NONE,    14,     2,    35,    63,   108, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
    00462 {      WWT_PANEL,   RESIZE_NONE,    14,    37,    70,    63,   108, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
    00463 {      WWT_PANEL,   RESIZE_NONE,    14,    72,   105,    63,   108, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
    00464 {      WWT_PANEL,   RESIZE_NONE,    14,   107,   140,    63,   108, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
    00465 {      WWT_PANEL,   RESIZE_NONE,    14,     2,    35,   110,   155, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
    00466 {      WWT_PANEL,   RESIZE_NONE,    14,    37,    70,   110,   155, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
    00467 {      WWT_PANEL,   RESIZE_NONE,    14,    72,   105,   110,   155, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
    00468 {      WWT_PANEL,   RESIZE_NONE,    14,   107,   140,   110,   155, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
    00469 {    WWT_TEXTBTN,   RESIZE_NONE,    14,     2,   140,   157,   168, STR_TREES_RANDOM_TYPE, STR_TREES_RANDOM_TYPE_TIP},
    00470 {    WWT_TEXTBTN,   RESIZE_NONE,    14,     2,   140,   170,   181, STR_028A_RANDOM_TREES, STR_028B_PLANT_TREES_RANDOMLY_OVER},
    00471 {    WIDGETS_END},
    00472 };
    00473
    00474 static const WindowDesc _build_trees_scen_desc = {
    00475   WDP_AUTO, WDP_AUTO, 143, 184, 143, 184,
    00476   WC_BUILD_TREES, WC_NONE,
    00477   WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
    00478   _build_trees_scen_widgets,
    00479   BuildTreesWndProc
    00480 };
    00481
    00482
    00483 void ShowBuildTreesToolbar()
    00484 {
    00485   if (!IsValidPlayer(_current_player)) return;
    00486   AllocateWindowDescFront(&_build_trees_desc, 0);
    00487 }
    00488
    00489 void ShowBuildTreesScenToolbar()
    00490 {
    00491   AllocateWindowDescFront(&_build_trees_scen_desc, 0);
    00492 }
    00493
    00494 static uint64 _errmsg_decode_params[20];
    00495 static StringID _errmsg_message_1, _errmsg_message_2;
    00496 static uint _errmsg_duration;
    00497
    00498
    00499 static const Widget _errmsg_widgets[] = {
    00500 {   WWT_CLOSEBOX,   RESIZE_NONE,     4,     0,    10,     0,    13, STR_00C5,         STR_018B_CLOSE_WINDOW},
    00501 {    WWT_CAPTION,   RESIZE_NONE,     4,    11,   239,     0,    13, STR_00B2_MESSAGE, STR_NULL},
    00502 {      WWT_PANEL,   RESIZE_NONE,     4,     0,   239,    14,    45, 0x0,              STR_NULL},
    00503 {    WIDGETS_END},
    00504 };
    00505
    00506 static const Widget _errmsg_face_widgets[] = {
    00507 {   WWT_CLOSEBOX,   RESIZE_NONE,     4,     0,    10,     0,    13, STR_00C5,              STR_018B_CLOSE_WINDOW},
    00508 {    WWT_CAPTION,   RESIZE_NONE,     4,    11,   333,     0,    13, STR_00B3_MESSAGE_FROM, STR_NULL},
    00509 {      WWT_PANEL,   RESIZE_NONE,     4,     0,   333,    14,   136, 0x0,                   STR_NULL},
    00510 {   WIDGETS_END},
    00511 };
    00512
    00513 static void ErrmsgWndProc(Window *w, WindowEvent *e)
    00514 {
    00515   switch (e->event) {
    00516   case WE_PAINT:
    00517     CopyInDParam(0, _errmsg_decode_params, lengthof(_errmsg_decode_params));
    00518     DrawWindowWidgets(w);
    00519     CopyInDParam(0, _errmsg_decode_params, lengthof(_errmsg_decode_params));
    00520
    00521     /* If the error message comes from a NewGRF, we must use the text ref. stack reserved for error messages.
    00522      * If the message doesn't come from a NewGRF, it won't use the TTDP-style text ref. stack, so we won't hurt anything
    00523      */
    00524     SwitchToErrorRefStack();
    00525     RewindTextRefStack();
    00526
    00527     if (!IsWindowOfPrototype(w, _errmsg_face_widgets)) {
    00528       DrawStringMultiCenter(
    00529         120,
    00530         (_errmsg_message_1 == INVALID_STRING_ID ? 25 : 15),
    00531         _errmsg_message_2,
    00532         w->width - 2);
    00533       if (_errmsg_message_1 != INVALID_STRING_ID)
    00534         DrawStringMultiCenter(
    00535           120,
    00536           30,
    00537           _errmsg_message_1,
    00538           w->width - 2);
    00539     } else {
    00540       const Player *p = GetPlayer((PlayerID)GetDParamX(_errmsg_decode_params,2));
    00541       DrawPlayerFace(p->face, p->player_color, 2, 16);
    00542
    00543       DrawStringMultiCenter(
    00544         214,
    00545         (_errmsg_message_1 == INVALID_STRING_ID ? 65 : 45),
    00546         _errmsg_message_2,
    00547         w->width - 2);
    00548       if (_errmsg_message_1 != INVALID_STRING_ID)
    00549         DrawStringMultiCenter(
    00550           214,
    00551           90,
    00552           _errmsg_message_1,
    00553           w->width - 2);
    00554     }
    00555
    00556     /* Switch back to the normal text ref. stack for NewGRF texts */
    00557     SwitchToNormalRefStack();
    00558     break;
    00559
    00560   case WE_MOUSELOOP:
    00561     if (_right_button_down) DeleteWindow(w);
    00562     break;
    00563
    00564   case WE_4:
    00565     if (--_errmsg_duration == 0) DeleteWindow(w);
    00566     break;
    00567
    00568   case WE_DESTROY:
    00569     SetRedErrorSquare(0);
    00570     _switch_mode_errorstr = INVALID_STRING_ID;
    00571     break;
    00572
    00573   case WE_KEYPRESS:
    00574     if (e->we.keypress.keycode == WKC_SPACE) {
    00575       /* Don't continue. */
    00576       e->we.keypress.cont = false;
    00577       DeleteWindow(w);
    00578     }
    00579     break;
    00580   }
    00581 }
    00582
    00583 void ShowErrorMessage(StringID msg_1, StringID msg_2, int x, int y)
    00584 {
    00585   Window *w;
    00586   const ViewPort *vp;
    00587   Point pt;
    00588
    00589   DeleteWindowById(WC_ERRMSG, 0);
    00590
    00591   //assert(msg_2);
    00592   if (msg_2 == 0) msg_2 = STR_EMPTY;
    00593
    00594   _errmsg_message_1 = msg_1;
    00595   _errmsg_message_2 = msg_2;
    00596   CopyOutDParam(_errmsg_decode_params, 0, lengthof(_errmsg_decode_params));
    00597   _errmsg_duration = _patches.errmsg_duration;
    00598   if (!_errmsg_duration) return;
    00599
    00600   if (_errmsg_message_1 != STR_013B_OWNED_BY || GetDParamX(_errmsg_decode_params,2) >= 8) {
    00601
    00602     if ( (x|y) != 0) {
    00603       pt = RemapCoords2(x, y);
    00604       vp = FindWindowById(WC_MAIN_WINDOW, 0)->viewport;
    00605
    00606       /* move x pos to opposite corner */
    00607       pt.x = UnScaleByZoom(pt.x - vp->virtual_left, vp->zoom) + vp->left;
    00608       pt.x = (pt.x < (_screen.width >> 1)) ? _screen.width - 260 : 20;
    00609
    00610       /* move y pos to opposite corner */
    00611       pt.y = UnScaleByZoom(pt.y - vp->virtual_top, vp->zoom) + vp->top;
    00612       pt.y = (pt.y < (_screen.height >> 1)) ? _screen.height - 80 : 100;
    00613
    00614     } else {
    00615       pt.x = (_screen.width - 240) >> 1;
    00616       pt.y = (_screen.height - 46) >> 1;
    00617     }
    00618     w = AllocateWindow(pt.x, pt.y, 240, 46, ErrmsgWndProc, WC_ERRMSG, _errmsg_widgets);
    00619   } else {
    00620     if ( (x|y) != 0) {
    00621       pt = RemapCoords2(x, y);
    00622       vp = FindWindowById(WC_MAIN_WINDOW, 0)->viewport;
    00623       pt.x = Clamp(UnScaleByZoom(pt.x - vp->virtual_left, vp->zoom) + vp->left - (334/2), 0, _screen.width - 334);
    00624       pt.y = Clamp(UnScaleByZoom(pt.y - vp->virtual_top, vp->zoom) + vp->top - (137/2), 22, _screen.height - 137);
    00625     } else {
    00626       pt.x = (_screen.width - 334) >> 1;
    00627       pt.y = (_screen.height - 137) >> 1;
    00628     }
    00629     w = AllocateWindow(pt.x, pt.y, 334, 137, ErrmsgWndProc, WC_ERRMSG, _errmsg_face_widgets);
    00630   }
    00631
    00632   w->desc_flags = WDF_STD_BTN | WDF_DEF_WIDGET;
    00633 }
    00634
    00635
    00636 void ShowEstimatedCostOrIncome(Money cost, int x, int y)
    00637 {
    00638   StringID msg = STR_0805_ESTIMATED_COST;
    00639
    00640   if (cost < 0) {
    00641     cost = -cost;
    00642     msg = STR_0807_ESTIMATED_INCOME;
    00643   }
    00644   SetDParam(0, cost);
    00645   ShowErrorMessage(INVALID_STRING_ID, msg, x, y);
    00646 }
    00647
    00648 void ShowCostOrIncomeAnimation(int x, int y, int z, Money cost)
    00649 {
    00650   StringID msg;
    00651   Point pt = RemapCoords(x,y,z);
    00652
    00653   msg = STR_0801_COST;
    00654   if (cost < 0) {
    00655     cost = -cost;
    00656     msg = STR_0803_INCOME;
    00657   }
    00658   SetDParam(0, cost);
    00659   AddTextEffect(msg, pt.x, pt.y, 0x250, TE_RISING);
    00660 }
    00661
    00662 void ShowFeederIncomeAnimation(int x, int y, int z, Money cost)
    00663 {
    00664   Point pt = RemapCoords(x,y,z);
    00665
    00666   SetDParam(0, cost);
    00667   AddTextEffect(STR_FEEDER, pt.x, pt.y, 0x250, TE_RISING);
    00668 }
    00669
    00670 TextEffectID ShowFillingPercent(int x, int y, int z, uint8 percent, StringID string)
    00671 {
    00672   Point pt = RemapCoords(x, y, z);
    00673
    00674   assert(string != STR_NULL);
    00675
    00676   SetDParam(0, percent);
    00677   return AddTextEffect(string, pt.x, pt.y, 0xFFFF, TE_STATIC);
    00678 }
    00679
    00680 void UpdateFillingPercent(TextEffectID te_id, uint8 percent, StringID string)
    00681 {
    00682   assert(string != STR_NULL);
    00683
    00684   SetDParam(0, percent);
    00685   UpdateTextEffect(te_id, string);
    00686 }
    00687
    00688 void HideFillingPercent(TextEffectID *te_id)
    00689 {
    00690   if (*te_id == INVALID_TE_ID) return;
    00691
    00692   RemoveTextEffect(*te_id);
    00693   *te_id = INVALID_TE_ID;
    00694 }
    00695
    00696 static const Widget _tooltips_widgets[] = {
    00697 {      WWT_PANEL,   RESIZE_NONE,    14,     0,   199,     0,    31, 0x0, STR_NULL},
    00698 {   WIDGETS_END},
    00699 };
    00700
    00701
    00702 static void TooltipsWndProc(Window *w, WindowEvent *e)
    00703 {
    00704   switch (e->event) {
    00705     case WE_PAINT: {
    00706       uint arg;
    00707       GfxFillRect(0, 0, w->width - 1, w->height - 1, 0);
    00708       GfxFillRect(1, 1, w->width - 2, w->height - 2, 0x44);
    00709
    00710       for (arg = 0; arg < WP(w, tooltips_d).paramcount; arg++) {
    00711         SetDParam(arg, WP(w, tooltips_d).params[arg]);
    00712       }
    00713       DrawStringMultiCenter((w->width >> 1), (w->height >> 1) - 5, WP(w, tooltips_d).string_id, w->width - 2);
    00714       break;
    00715     }
    00716
    00717     case WE_MOUSELOOP:
    00718       /* We can show tooltips while dragging tools. These are shown as long as
    00719        * we are dragging the tool. Normal tooltips work with rmb */
    00720       if (WP(w, tooltips_d).paramcount == 0 ) {
    00721         if (!_right_button_down) DeleteWindow(w);
    00722       } else {
    00723         if (!_left_button_down) DeleteWindow(w);
    00724       }
    00725
    00726       break;
    00727   }
    00728 }
    00729
    00735 void GuiShowTooltipsWithArgs(StringID str, uint paramcount, const uint64 params[])
    00736 {
    00737   char buffer[512];
    00738   Dimension br;
    00739   Window *w;
    00740   uint i;
    00741   int x, y;
    00742
    00743   DeleteWindowById(WC_TOOLTIPS, 0);
    00744
    00745   /* We only show measurement tooltips with patch setting on */
    00746   if (str == STR_NULL || (paramcount != 0 && !_patches.measure_tooltip)) return;
    00747
    00748   for (i = 0; i != paramcount; i++) SetDParam(i, params[i]);
    00749   GetString(buffer, str, lastof(buffer));
    00750
    00751   br = GetStringBoundingBox(buffer);
    00752   br.width += 6; br.height += 4; // increase slightly to have some space around the box
    00753
    00754   /* Cut tooltip length to 200 pixels max, wrap to new line if longer */
    00755   if (br.width > 200) {
    00756     br.height += ((br.width - 4) / 176) * 10;
    00757     br.width = 200;
    00758   }
    00759
    00760   /* Correctly position the tooltip position, watch out for window and cursor size
    00761    * Clamp value to below main toolbar and above statusbar. If tooltip would
    00762    * go below window, flip it so it is shown above the cursor */
    00763   y = Clamp(_cursor.pos.y + _cursor.size.y + _cursor.offs.y + 5, 22, _screen.height - 12);
    00764   if (y + br.height > _screen.height - 12) y = _cursor.pos.y + _cursor.offs.y - br.height - 5;
    00765   x = Clamp(_cursor.pos.x - (br.width >> 1), 0, _screen.width - br.width);
    00766
    00767   w = AllocateWindow(x, y, br.width, br.height, TooltipsWndProc, WC_TOOLTIPS, _tooltips_widgets);
    00768
    00769   WP(w, tooltips_d).string_id = str;
    00770   assert(sizeof(WP(w, tooltips_d).params[0]) == sizeof(params[0]));
    00771   memcpy(WP(w, tooltips_d).params, params, sizeof(WP(w, tooltips_d).params[0]) * paramcount);
    00772   WP(w, tooltips_d).paramcount = paramcount;
    00773
    00774   w->flags4 &= ~WF_WHITE_BORDER_MASK; // remove white-border from tooltip
    00775   w->widget[0].right = br.width;
    00776   w->widget[0].bottom = br.height;
    00777 }
    00778
    00779
    00780 static int DrawStationCoverageText(const AcceptedCargo accepts,
    00781   int str_x, int str_y, StationCoverageType sct)
    00782 {
    00783   char *b = _userstring;
    00784   bool first = true;
    00785
    00786   b = InlineString(b, STR_000D_ACCEPTS);
    00787
    00788   for (CargoID i = 0; i < NUM_CARGO; i++) {
    00789     if (b >= lastof(_userstring) - (1 + 2 * 4)) break; // ',' or ' ' and two calls to Utf8Encode()
    00790     switch (sct) {
    00791       case SCT_PASSENGERS_ONLY: if (!IsCargoInClass(i, CC_PASSENGERS)) continue; break;
    00792       case SCT_NON_PASSENGERS_ONLY: if (IsCargoInClass(i, CC_PASSENGERS)) continue; break;
    00793       case SCT_ALL: break;
    00794       default: NOT_REACHED();
    00795     }
    00796     if (accepts[i] >= 8) {
    00797       if (first) {
    00798         first = false;
    00799       } else {
    00800         /* Add a comma if this is not the first item */
    00801         *b++ = ',';
    00802         *b++ = ' ';
    00803       }
    00804       b = InlineString(b, GetCargo(i)->name);
    00805     }
    00806   }
    00807
    00808   /* If first is still true then no cargo is accepted */
    00809   if (first) b = InlineString(b, STR_00D0_NOTHING);
    00810
    00811   *b = '\0';
    00812
    00813   /* Make sure we detect any buffer overflow */
    00814   assert(b < endof(_userstring));
    00815
    00816   return DrawStringMultiLine(str_x, str_y, STR_SPEC_USERSTRING, 144);
    00817 }
    00818
    00819 int DrawStationCoverageAreaText(int sx, int sy, StationCoverageType sct, int rad)
    00820 {
    00821   TileIndex tile = TileVirtXY(_thd.pos.x, _thd.pos.y);
    00822   AcceptedCargo accepts;
    00823   if (tile < MapSize()) {
    00824     GetAcceptanceAroundTiles(accepts, tile, _thd.size.x / TILE_SIZE, _thd.size.y / TILE_SIZE , rad);
    00825     return sy + DrawStationCoverageText(accepts, sx, sy, sct);
    00826   }
    00827
    00828   return sy;
    00829 }
    00830
    00831 void CheckRedrawStationCoverage(const Window *w)
    00832 {
    00833   if (_thd.dirty & 1) {
    00834     _thd.dirty &= ~1;
    00835     SetWindowDirty(w);
    00836   }
    00837 }
    00838
    00839 void SetVScrollCount(Window *w, int num)
    00840 {
    00841   w->vscroll.count = num;
    00842   num -= w->vscroll.cap;
    00843   if (num < 0) num = 0;
    00844   if (num < w->vscroll.pos) w->vscroll.pos = num;
    00845 }
    00846
    00847 void SetVScroll2Count(Window *w, int num)
    00848 {
    00849   w->vscroll2.count = num;
    00850   num -= w->vscroll2.cap;
    00851   if (num < 0) num = 0;
    00852   if (num < w->vscroll2.pos) w->vscroll2.pos = num;
    00853 }
    00854
    00855 void SetHScrollCount(Window *w, int num)
    00856 {
    00857   w->hscroll.count = num;
    00858   num -= w->hscroll.cap;
    00859   if (num < 0) num = 0;
    00860   if (num < w->hscroll.pos) w->hscroll.pos = num;
    00861 }
    00862
    00863 /* Delete a character at the caret position in a text buf.
    00864  * If backspace is set, delete the character before the caret,
    00865  * else delete the character after it. */
    00866 static void DelChar(Textbuf *tb, bool backspace)
    00867 {
    00868   WChar c;
    00869   uint width;
    00870   size_t len;
    00871   char *s = tb->buf + tb->caretpos;
    00872
    00873   if (backspace) s = Utf8PrevChar(s);
    00874
    00875   len = Utf8Decode(&c, s);
    00876   width = GetCharacterWidth(FS_NORMAL, c);
    00877
    00878   tb->width  -= width;
    00879   if (backspace) {
    00880     tb->caretpos   -= len;
    00881     tb->caretxoffs -= width;
    00882   }
    00883
    00884   /* Move the remaining characters over the marker */
    00885   memmove(s, s + len, tb->length - (s - tb->buf) - len + 1);
    00886   tb->length -= len;
    00887 }
    00888
    00896 bool DeleteTextBufferChar(Textbuf *tb, int delmode)
    00897 {
    00898   if (delmode == WKC_BACKSPACE && tb->caretpos != 0) {
    00899     DelChar(tb, true);
    00900     return true;
    00901   } else if (delmode == WKC_DELETE && tb->caretpos < tb->length) {
    00902     DelChar(tb, false);
    00903     return true;
    00904   }
    00905
    00906   return false;
    00907 }
    00908
    00913 void DeleteTextBufferAll(Textbuf *tb)
    00914 {
    00915   memset(tb->buf, 0, tb->maxlength);
    00916   tb->length = tb->width = 0;
    00917   tb->caretpos = tb->caretxoffs = 0;
    00918 }
    00919
    00928 bool InsertTextBufferChar(Textbuf *tb, WChar key)
    00929 {
    00930   const byte charwidth = GetCharacterWidth(FS_NORMAL, key);
    00931   size_t len = Utf8CharLen(key);
    00932   if (tb->length < (tb->maxlength - len) && (tb->maxwidth == 0 || tb->width + charwidth <= tb->maxwidth)) {
    00933     memmove(tb->buf + tb->caretpos + len, tb->buf + tb->caretpos, tb->length - tb->caretpos + 1);
    00934     Utf8Encode(tb->buf + tb->caretpos, key);
    00935     tb->length += len;
    00936     tb->width  += charwidth;
    00937
    00938     tb->caretpos   += len;
    00939     tb->caretxoffs += charwidth;
    00940     return true;
    00941   }
    00942   return false;
    00943 }
    00944
    00952 bool MoveTextBufferPos(Textbuf *tb, int navmode)
    00953 {
    00954   switch (navmode) {
    00955   case WKC_LEFT:
    00956     if (tb->caretpos != 0) {
    00957       WChar c;
    00958       const char *s = Utf8PrevChar(tb->buf + tb->caretpos);
    00959       Utf8Decode(&c, s);
    00960       tb->caretpos    = s - tb->buf; // -= (tb->buf + tb->caretpos - s)
    00961       tb->caretxoffs -= GetCharacterWidth(FS_NORMAL, c);
    00962
    00963       return true;
    00964     }
    00965     break;
    00966   case WKC_RIGHT:
    00967     if (tb->caretpos < tb->length) {
    00968       WChar c;
    00969
    00970       tb->caretpos   += Utf8Decode(&c, tb->buf + tb->caretpos);
    00971       tb->caretxoffs += GetCharacterWidth(FS_NORMAL, c);
    00972
    00973       return true;
    00974     }
    00975     break;
    00976   case WKC_HOME:
    00977     tb->caretpos = 0;
    00978     tb->caretxoffs = 0;
    00979     return true;
    00980   case WKC_END:
    00981     tb->caretpos = tb->length;
    00982     tb->caretxoffs = tb->width;
    00983     return true;
    00984   }
    00985
    00986   return false;
    00987 }
    00988
    00998 void InitializeTextBuffer(Textbuf *tb, const char *buf, uint16 maxlength, uint16 maxwidth)
    00999 {
    01000   tb->buf = (char*)buf;
    01001   tb->maxlength = maxlength;
    01002   tb->maxwidth  = maxwidth;
    01003   tb->caret = true;
    01004   UpdateTextBufferSize(tb);
    01005 }
    01006
    01013 void UpdateTextBufferSize(Textbuf *tb)
    01014 {
    01015   const char *buf = tb->buf;
    01016   WChar c = Utf8Consume(&buf);
    01017
    01018   tb->width = 0;
    01019   tb->length = 0;
    01020
    01021   for (; c != '\0' && tb->length < (tb->maxlength - 1); c = Utf8Consume(&buf)) {
    01022     tb->width += GetCharacterWidth(FS_NORMAL, c);
    01023     tb->length += Utf8CharLen(c);
    01024   }
    01025
    01026   tb->caretpos = tb->length;
    01027   tb->caretxoffs = tb->width;
    01028 }
    01029
    01030 int HandleEditBoxKey(Window *w, querystr_d *string, int wid, WindowEvent *e)
    01031 {
    01032   e->we.keypress.cont = false;
    01033
    01034   switch (e->we.keypress.keycode) {
    01035   case WKC_ESC: return 2;
    01036   case WKC_RETURN: case WKC_NUM_ENTER: return 1;
    01037   case (WKC_CTRL | 'V'):
    01038     if (InsertTextBufferClipboard(&string->text))
    01039       w->InvalidateWidget(wid);
    01040     break;
    01041   case (WKC_CTRL | 'U'):
    01042     DeleteTextBufferAll(&string->text);
    01043     w->InvalidateWidget(wid);
    01044     break;
    01045   case WKC_BACKSPACE: case WKC_DELETE:
    01046     if (DeleteTextBufferChar(&string->text, e->we.keypress.keycode))
    01047       w->InvalidateWidget(wid);
    01048     break;
    01049   case WKC_LEFT: case WKC_RIGHT: case WKC_END: case WKC_HOME:
    01050     if (MoveTextBufferPos(&string->text, e->we.keypress.keycode))
    01051       w->InvalidateWidget(wid);
    01052     break;
    01053   default:
    01054     if (IsValidChar(e->we.keypress.key, string->afilter)) {
    01055       if (InsertTextBufferChar(&string->text, e->we.keypress.key)) {
    01056         w->InvalidateWidget(wid);
    01057       }
    01058     } else { // key wasn't caught. Continue only if standard entry specified
    01059       e->we.keypress.cont = (string->afilter == CS_ALPHANUMERAL);
    01060     }
    01061   }
    01062
    01063   return 0;
    01064 }
    01065
    01066 bool HandleCaret(Textbuf *tb)
    01067 {
    01068   /* caret changed? */
    01069   bool b = !!(_caret_timer & 0x20);
    01070
    01071   if (b != tb->caret) {
    01072     tb->caret = b;
    01073     return true;
    01074   }
    01075   return false;
    01076 }
    01077
    01078 void HandleEditBox(Window *w, querystr_d *string, int wid)
    01079 {
    01080   if (HandleCaret(&string->text)) w->InvalidateWidget(wid);
    01081 }
    01082
    01083 void DrawEditBox(Window *w, querystr_d *string, int wid)
    01084 {
    01085   DrawPixelInfo dpi, *old_dpi;
    01086   int delta;
    01087   const Widget *wi = &w->widget[wid];
    01088   const Textbuf *tb = &string->text;
    01089
    01090   GfxFillRect(wi->left + 1, wi->top + 1, wi->right - 1, wi->bottom - 1, 215);
    01091
    01092   /* Limit the drawing of the string inside the widget boundaries */
    01093   if (!FillDrawPixelInfo(&dpi,
    01094         wi->left + 4,
    01095         wi->top + 1,
    01096         wi->right - wi->left - 4,
    01097         wi->bottom - wi->top - 1)
    01098   ) return;
    01099
    01100   old_dpi = _cur_dpi;
    01101   _cur_dpi = &dpi;
    01102
    01103   /* We will take the current widget length as maximum width, with a small
    01104    * space reserved at the end for the caret to show */
    01105   delta = (wi->right - wi->left) - tb->width - 10;
    01106   if (delta > 0) delta = 0;
    01107
    01108   if (tb->caretxoffs + delta < 0) delta = -tb->caretxoffs;
    01109
    01110   DoDrawString(tb->buf, delta, 0, TC_YELLOW);
    01111   if (tb->caret) DoDrawString("_", tb->caretxoffs + delta, 0, TC_WHITE);
    01112
    01113   _cur_dpi = old_dpi;
    01114 }
    01115
    01116 enum QueryStringWidgets {
    01117   QUERY_STR_WIDGET_TEXT = 3,
    01118   QUERY_STR_WIDGET_CANCEL,
    01119   QUERY_STR_WIDGET_OK
    01120 };
    01121
    01122
    01123 static void QueryStringWndProc(Window *w, WindowEvent *e)
    01124 {
    01125   querystr_d *qs = &WP(w, querystr_d);
    01126
    01127   switch (e->event) {
    01128     case WE_CREATE:
    01129       SetBit(_no_scroll, SCROLL_EDIT);
    01130       break;
    01131
    01132     case WE_PAINT:
    01133       SetDParam(0, qs->caption);
    01134       DrawWindowWidgets(w);
    01135
    01136       DrawEditBox(w, qs, QUERY_STR_WIDGET_TEXT);
    01137       break;
    01138
    01139     case WE_CLICK:
    01140       switch (e->we.click.widget) {
    01141         case QUERY_STR_WIDGET_OK:
    01142     press_ok:;
    01143           if (qs->orig == NULL || strcmp(qs->text.buf, qs->orig) != 0) {
    01144             Window *parent = w->parent;
    01145             qs->handled = true;
    01146
    01147             /* If the parent is NULL, the editbox is handled by general function
    01148              * HandleOnEditText */
    01149             if (parent != NULL) {
    01150               WindowEvent e;
    01151               e.event = WE_ON_EDIT_TEXT;
    01152               e.we.edittext.str = qs->text.buf;
    01153               parent->wndproc(parent, &e);
    01154             } else {
    01155               HandleOnEditText(qs->text.buf);
    01156             }
    01157           }
    01158           /* Fallthrough */
    01159         case QUERY_STR_WIDGET_CANCEL:
    01160           DeleteWindow(w);
    01161           break;
    01162       }
    01163       break;
    01164
    01165     case WE_MOUSELOOP:
    01166       HandleEditBox(w, qs, QUERY_STR_WIDGET_TEXT);
    01167       break;
    01168
    01169     case WE_KEYPRESS:
    01170       switch (HandleEditBoxKey(w, qs, QUERY_STR_WIDGET_TEXT, e)) {
    01171         case 1: goto press_ok; // Enter pressed, confirms change
    01172         case 2: DeleteWindow(w); break; // ESC pressed, closes window, abandons changes
    01173       }
    01174       break;
    01175
    01176     case WE_DESTROY: // Call cancellation of query, if we have not handled it before
    01177       if (!qs->handled && w->parent != NULL) {
    01178         WindowEvent e;
    01179         Window *parent = w->parent;
    01180
    01181         qs->handled = true;
    01182         e.event = WE_ON_EDIT_TEXT_CANCEL;
    01183         parent->wndproc(parent, &e);
    01184       }
    01185       ClrBit(_no_scroll, SCROLL_EDIT);
    01186       break;
    01187     }
    01188 }
    01189
    01190 static const Widget _query_string_widgets[] = {
    01191 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,        STR_018B_CLOSE_WINDOW},
    01192 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   259,     0,    13, STR_012D,        STR_NULL},
    01193 {      WWT_PANEL,   RESIZE_NONE,    14,     0,   259,    14,    29, 0x0,             STR_NULL},
    01194 {      WWT_PANEL,   RESIZE_NONE,    14,     2,   257,    16,    27, 0x0,             STR_NULL},
    01195 {    WWT_TEXTBTN,   RESIZE_NONE,    14,     0,   129,    30,    41, STR_012E_CANCEL, STR_NULL},
    01196 {    WWT_TEXTBTN,   RESIZE_NONE,    14,   130,   259,    30,    41, STR_012F_OK,     STR_NULL},
    01197 {   WIDGETS_END},
    01198 };
    01199
    01200 static const WindowDesc _query_string_desc = {
    01201   190, 219, 260, 42, 260, 42,
    01202   WC_QUERY_STRING, WC_NONE,
    01203   WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
    01204   _query_string_widgets,
    01205   QueryStringWndProc
    01206 };
    01207
    01208 static char _edit_str_buf[64];
    01209
    01219 void ShowQueryString(StringID str, StringID caption, uint maxlen, uint maxwidth, Window *parent, CharSetFilter afilter)
    01220 {
    01221   static char orig_str_buf[lengthof(_edit_str_buf)];
    01222   Window *w;
    01223   uint realmaxlen = maxlen & ~0x1000;
    01224
    01225   assert(realmaxlen < lengthof(_edit_str_buf));
    01226
    01227   DeleteWindowById(WC_QUERY_STRING, 0);
    01228   DeleteWindowById(WC_SAVELOAD, 0);
    01229
    01230   w = AllocateWindowDesc(&_query_string_desc);
    01231   w->parent = parent;
    01232
    01233   GetString(_edit_str_buf, str, lastof(_edit_str_buf));
    01234   _edit_str_buf[realmaxlen - 1] = '\0';
    01235
    01236   if (maxlen & 0x1000) {
    01237     WP(w, querystr_d).orig = NULL;
    01238   } else {
    01239     strecpy(orig_str_buf, _edit_str_buf, lastof(orig_str_buf));
    01240     WP(w, querystr_d).orig = orig_str_buf;
    01241   }
    01242
    01243   w->LowerWidget(QUERY_STR_WIDGET_TEXT);
    01244   WP(w, querystr_d).caption = caption;
    01245   WP(w, querystr_d).afilter = afilter;
    01246   InitializeTextBuffer(&WP(w, querystr_d).text, _edit_str_buf, realmaxlen, maxwidth);
    01247 }
    01248
    01249
    01250 enum QueryWidgets {
    01251   QUERY_WIDGET_CAPTION = 1,
    01252   QUERY_WIDGET_NO = 3,
    01253   QUERY_WIDGET_YES
    01254 };
    01255
    01256
    01257 struct query_d {
    01258   void (*proc)(Window*, bool);
    01259   uint64 params[10];
    01260   StringID message;
    01261   bool calledback;
    01262 };
    01263 assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(query_d));
    01264
    01265
    01266 static void QueryWndProc(Window *w, WindowEvent *e)
    01267 {
    01268   query_d *q = &WP(w, query_d);
    01269
    01270   switch (e->event) {
    01271     case WE_PAINT:
    01272       CopyInDParam(0, q->params, lengthof(q->params));
    01273       DrawWindowWidgets(w);
    01274       CopyInDParam(0, q->params, lengthof(q->params));
    01275
    01276       DrawStringMultiCenter(w->width / 2, (w->height / 2) - 10, q->message, w->width - 2);
    01277       break;
    01278
    01279     case WE_CLICK:
    01280       switch (e->we.click.widget) {
    01281         case QUERY_WIDGET_YES:
    01282           q->calledback = true;
    01283           if (q->proc != NULL) q->proc(w->parent, true);
    01284           /* Fallthrough */
    01285         case QUERY_WIDGET_NO:
    01286           DeleteWindow(w);
    01287           break;
    01288         }
    01289       break;
    01290
    01291     case WE_KEYPRESS: // ESC closes the window, Enter confirms the action
    01292       switch (e->we.keypress.keycode) {
    01293         case WKC_RETURN:
    01294         case WKC_NUM_ENTER:
    01295           q->calledback = true;
    01296           if (q->proc != NULL) q->proc(w->parent, true);
    01297           /* Fallthrough */
    01298         case WKC_ESC:
    01299           e->we.keypress.cont = false;
    01300           DeleteWindow(w);
    01301           break;
    01302       }
    01303       break;
    01304
    01305     case WE_DESTROY: // Call callback function (if any) on window close if not yet called
    01306       if (!q->calledback && q->proc != NULL) {
    01307         q->calledback = true;
    01308         q->proc(w->parent, false);
    01309       }
    01310       break;
    01311   }
    01312 }
    01313
    01314
    01315 static const Widget _query_widgets[] = {
    01316 {  WWT_CLOSEBOX, RESIZE_NONE,  4,   0,  10,   0,  13, STR_00C5,        STR_018B_CLOSE_WINDOW},
    01317 {   WWT_CAPTION, RESIZE_NONE,  4,  11, 209,   0,  13, STR_NULL,        STR_NULL},
    01318 {     WWT_PANEL, RESIZE_NONE,  4,   0, 209,  14,  81, 0x0, /*OVERRIDE*/STR_NULL},
    01319 {WWT_PUSHTXTBTN, RESIZE_NONE,  3,  20,  90,  62,  73, STR_00C9_NO,     STR_NULL},
    01320 {WWT_PUSHTXTBTN, RESIZE_NONE,  3, 120, 190,  62,  73, STR_00C8_YES,    STR_NULL},
    01321 {   WIDGETS_END },
    01322 };
    01323
    01324 static const WindowDesc _query_desc = {
    01325   WDP_CENTER, WDP_CENTER, 210, 82, 210, 82,
    01326   WC_CONFIRM_POPUP_QUERY, WC_NONE,
    01327   WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_UNCLICK_BUTTONS | WDF_DEF_WIDGET | WDF_MODAL,
    01328   _query_widgets,
    01329   QueryWndProc
    01330 };
    01331
    01340 void ShowQuery(StringID caption, StringID message, Window *parent, void (*callback)(Window*, bool))
    01341 {
    01342   Window *w = AllocateWindowDesc(&_query_desc);
    01343   if (w == NULL) return;
    01344
    01345   if (parent == NULL) parent = FindWindowById(WC_MAIN_WINDOW, 0);
    01346   w->parent = parent;
    01347   w->left = parent->left + (parent->width / 2) - (w->width / 2);
    01348   w->top = parent->top + (parent->height / 2) - (w->height / 2);
    01349
    01350   /* Create a backup of the variadic arguments to strings because it will be
    01351    * overridden pretty often. We will copy these back for drawing */
    01352   CopyOutDParam(WP(w, query_d).params, 0, lengthof(WP(w, query_d).params));
    01353   w->widget[QUERY_WIDGET_CAPTION].data = caption;
    01354   WP(w, query_d).message    = message;
    01355   WP(w, query_d).proc       = callback;
    01356   WP(w, query_d).calledback = false;
    01357 }
    01358
    01359
    01360 static const Widget _load_dialog_widgets[] = {
    01361 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,         STR_018B_CLOSE_WINDOW},
    01362 {    WWT_CAPTION,  RESIZE_RIGHT,    14,    11,   256,     0,    13, STR_NULL,         STR_018C_WINDOW_TITLE_DRAG_THIS},
    01363 { WWT_PUSHTXTBTN,   RESIZE_NONE,    14,     0,   127,    14,    25, STR_SORT_BY_NAME, STR_SORT_ORDER_TIP},
    01364 { WWT_PUSHTXTBTN,   RESIZE_NONE,    14,   128,   256,    14,    25, STR_SORT_BY_DATE, STR_SORT_ORDER_TIP},
    01365 {      WWT_PANEL,  RESIZE_RIGHT,    14,     0,   256,    26,    47, 0x0,              STR_NULL},
    01366 {      WWT_PANEL,     RESIZE_RB,    14,     0,   256,    48,   153, 0x0,              STR_NULL},
    01367 { WWT_PUSHIMGBTN,     RESIZE_LR,    14,   245,   256,    48,    59, SPR_HOUSE_ICON,   STR_SAVELOAD_HOME_BUTTON},
    01368 {      WWT_INSET,     RESIZE_RB,    14,     2,   243,    50,   151, 0x0,              STR_400A_LIST_OF_DRIVES_DIRECTORIES},
    01369 {  WWT_SCROLLBAR,    RESIZE_LRB,    14,   245,   256,    60,   141, 0x0,              STR_0190_SCROLL_BAR_SCROLLS_LIST},
    01370 {  WWT_RESIZEBOX,   RESIZE_LRTB,    14,   245,   256,   142,   153, 0x0,              STR_RESIZE_BUTTON},
    01371 {   WIDGETS_END},
    01372 };
    01373
    01374 static const Widget _save_dialog_widgets[] = {
    01375 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,         STR_018B_CLOSE_WINDOW},
    01376 {    WWT_CAPTION,  RESIZE_RIGHT,    14,    11,   256,     0,    13, STR_NULL,         STR_018C_WINDOW_TITLE_DRAG_THIS},
    01377 { WWT_PUSHTXTBTN,   RESIZE_NONE,    14,     0,   127,    14,    25, STR_SORT_BY_NAME, STR_SORT_ORDER_TIP},
    01378 { WWT_PUSHTXTBTN,   RESIZE_NONE,    14,   128,   256,    14,    25, STR_SORT_BY_DATE, STR_SORT_ORDER_TIP},
    01379 {      WWT_PANEL,  RESIZE_RIGHT,    14,     0,   256,    26,    47, 0x0,              STR_NULL},
    01380 {      WWT_PANEL,     RESIZE_RB,    14,     0,   256,    48,   151, 0x0,              STR_NULL},
    01381 { WWT_PUSHIMGBTN,     RESIZE_LR,    14,   245,   256,    48,    59, SPR_HOUSE_ICON,   STR_SAVELOAD_HOME_BUTTON},
    01382 {      WWT_INSET,     RESIZE_RB,    14,     2,   243,    50,   150, 0x0,              STR_400A_LIST_OF_DRIVES_DIRECTORIES},
    01383 {  WWT_SCROLLBAR,    RESIZE_LRB,    14,   245,   256,    60,   151, 0x0,              STR_0190_SCROLL_BAR_SCROLLS_LIST},
    01384 {      WWT_PANEL,    RESIZE_RTB,    14,     0,   256,   152,   167, 0x0,              STR_NULL},
    01385 {      WWT_PANEL,    RESIZE_RTB,    14,     2,   254,   154,   165, 0x0,              STR_400B_CURRENTLY_SELECTED_NAME},
    01386 { WWT_PUSHTXTBTN,     RESIZE_TB,    14,     0,   127,   168,   179, STR_4003_DELETE,  STR_400C_DELETE_THE_CURRENTLY_SELECTED},
    01387 { WWT_PUSHTXTBTN,     RESIZE_TB,    14,   128,   244,   168,   179, STR_4002_SAVE,    STR_400D_SAVE_THE_CURRENT_GAME_USING},
    01388 {  WWT_RESIZEBOX,   RESIZE_LRTB,    14,   245,   256,   168,   179, 0x0,              STR_RESIZE_BUTTON},
    01389 {   WIDGETS_END},
    01390 };
    01391
    01392 /* Colors for fios types */
    01393 const TextColour _fios_colors[] = {
    01394   TC_LIGHT_BLUE, TC_DARK_GREEN,  TC_DARK_GREEN, TC_ORANGE, TC_LIGHT_BROWN,
    01395   TC_ORANGE,     TC_LIGHT_BROWN, TC_ORANGE,     TC_ORANGE, TC_YELLOW
    01396 };
    01397
    01398 void BuildFileList()
    01399 {
    01400   _fios_path_changed = true;
    01401   FiosFreeSavegameList();
    01402
    01403   switch (_saveload_mode) {
    01404     case SLD_NEW_GAME:
    01405     case SLD_LOAD_SCENARIO:
    01406     case SLD_SAVE_SCENARIO:
    01407       _fios_list = FiosGetScenarioList(_saveload_mode); break;
    01408     case SLD_LOAD_HEIGHTMAP:
    01409       _fios_list = FiosGetHeightmapList(_saveload_mode); break;
    01410
    01411     default: _fios_list = FiosGetSavegameList(_saveload_mode); break;
    01412   }
    01413 }
    01414
    01415 static void DrawFiosTexts(uint maxw)
    01416 {
    01417   static const char *path = NULL;
    01418   static StringID str = STR_4006_UNABLE_TO_READ_DRIVE;
    01419   static uint32 tot = 0;
    01420
    01421   if (_fios_path_changed) {
    01422     str = FiosGetDescText(&path, &tot);
    01423     _fios_path_changed = false;
    01424   }
    01425
    01426   if (str != STR_4006_UNABLE_TO_READ_DRIVE) SetDParam(0, tot);
    01427   DrawString(2, 37, str, TC_FROMSTRING);
    01428   DoDrawStringTruncated(path, 2, 27, TC_BLACK, maxw);
    01429 }
    01430
    01431 static void MakeSortedSaveGameList()
    01432 {
    01433   uint sort_start = 0;
    01434   uint sort_end = 0;
    01435   uint s_amount;
    01436   int i;
    01437
    01438   /* Directories are always above the files (FIOS_TYPE_DIR)
    01439    * Drives (A:\ (windows only) are always under the files (FIOS_TYPE_DRIVE)
    01440    * Only sort savegames/scenarios, not directories
    01441    */
    01442   for (i = 0; i < _fios_num; i++) {
    01443     switch (_fios_list[i].type) {
    01444       case FIOS_TYPE_DIR:    sort_start++; break;
    01445       case FIOS_TYPE_PARENT: sort_start++; break;
    01446       case FIOS_TYPE_DRIVE:  sort_end++;   break;
    01447     }
    01448   }
    01449
    01450   s_amount = _fios_num - sort_start - sort_end;
    01451   if (s_amount > 0)
    01452     qsort(_fios_list + sort_start, s_amount, sizeof(FiosItem), compare_FiosItems);
    01453 }
    01454
    01455 static void GenerateFileName()
    01456 {
    01457   /* Check if we are not a specatator who wants to generate a name..
    01458       Let's use the name of player #0 for now. */
    01459   const Player *p = GetPlayer(IsValidPlayer(_local_player) ? _local_player : PLAYER_FIRST);
    01460
    01461   SetDParam(0, p->index);
    01462   SetDParam(1, _date);
    01463   GetString(_edit_str_buf, STR_4004, lastof(_edit_str_buf));
    01464   SanitizeFilename(_edit_str_buf);
    01465 }
    01466
    01467 extern void StartupEngines();
    01468
    01469 static void SaveLoadDlgWndProc(Window *w, WindowEvent *e)
    01470 {
    01471   static FiosItem o_dir;
    01472
    01473   switch (e->event) {
    01474   case WE_CREATE: // Set up OPENTTD button
    01475     w->vscroll.cap = 10;
    01476     w->resize.step_width = 2;
    01477     w->resize.step_height = 10;
    01478
    01479     o_dir.type = FIOS_TYPE_DIRECT;
    01480     switch (_saveload_mode) {
    01481       case SLD_SAVE_GAME:
    01482       case SLD_LOAD_GAME:
    01483         FioGetDirectory(o_dir.name, lengthof(o_dir.name), SAVE_DIR);
    01484         break;
    01485
    01486       case SLD_SAVE_SCENARIO:
    01487       case SLD_LOAD_SCENARIO:
    01488         FioGetDirectory(o_dir.name, lengthof(o_dir.name), SCENARIO_DIR);
    01489         break;
    01490
    01491       case SLD_LOAD_HEIGHTMAP:
    01492         FioGetDirectory(o_dir.name, lengthof(o_dir.name), HEIGHTMAP_DIR);
    01493         break;
    01494
    01495       default:
    01496         ttd_strlcpy(o_dir.name, _personal_dir, lengthof(o_dir.name));
    01497     }
    01498     break;
    01499
    01500   case WE_PAINT: {
    01501     int pos;
    01502     int y;
    01503
    01504     SetVScrollCount(w, _fios_num);
    01505     DrawWindowWidgets(w);
    01506     DrawFiosTexts(w->width);
    01507
    01508     if (_savegame_sort_dirty) {
    01509       _savegame_sort_dirty = false;
    01510       MakeSortedSaveGameList();
    01511     }
    01512
    01513     GfxFillRect(w->widget[7].left + 1, w->widget[7].top + 1, w->widget[7].right, w->widget[7].bottom, 0xD7);
    01514     DrawSortButtonState(w, _savegame_sort_order & SORT_BY_NAME ? 2 : 3, _savegame_sort_order & SORT_DESCENDING ? SBS_DOWN : SBS_UP);
    01515
    01516     y = w->widget[7].top + 1;
    01517     for (pos = w->vscroll.pos; pos < _fios_num; pos++) {
    01518       const FiosItem *item = _fios_list + pos;
    01519
    01520       DoDrawStringTruncated(item->title, 4, y, _fios_colors[item->type], w->width - 18);
    01521       y += 10;
    01522       if (y >= w->vscroll.cap * 10 + w->widget[7].top + 1) break;
    01523     }
    01524
    01525     if (_saveload_mode == SLD_SAVE_GAME || _saveload_mode == SLD_SAVE_SCENARIO) {
    01526       DrawEditBox(w, &WP(w, querystr_d), 10);
    01527     }
    01528     break;
    01529   }
    01530
    01531   case WE_CLICK:
    01532     switch (e->we.click.widget) {
    01533     case 2: // Sort save names by name
    01534       _savegame_sort_order = (_savegame_sort_order == SORT_BY_NAME) ?
    01535         SORT_BY_NAME | SORT_DESCENDING : SORT_BY_NAME;
    01536       _savegame_sort_dirty = true;
    01537       SetWindowDirty(w);
    01538       break;
    01539
    01540     case 3: // Sort save names by date
    01541       _savegame_sort_order = (_savegame_sort_order == SORT_BY_DATE) ?
    01542         SORT_BY_DATE | SORT_DESCENDING : SORT_BY_DATE;
    01543       _savegame_sort_dirty = true;
    01544       SetWindowDirty(w);
    01545       break;
    01546
    01547     case 6: // OpenTTD 'button', jumps to OpenTTD directory
    01548       FiosBrowseTo(&o_dir);
    01549       SetWindowDirty(w);
    01550       BuildFileList();
    01551       break;
    01552
    01553     case 7: { // Click the listbox
    01554       int y = (e->we.click.pt.y - w->widget[e->we.click.widget].top - 1) / 10;
    01555       char *name;
    01556       const FiosItem *file;
    01557
    01558       if (y < 0 || (y += w->vscroll.pos) >= w->vscroll.count) return;
    01559
    01560       file = _fios_list + y;
    01561
    01562       name = FiosBrowseTo(file);
    01563       if (name != NULL) {
    01564         if (_saveload_mode == SLD_LOAD_GAME || _saveload_mode == SLD_LOAD_SCENARIO) {
    01565           _switch_mode = (_game_mode == GM_EDITOR) ? SM_LOAD_SCENARIO : SM_LOAD;
    01566
    01567           SetFiosType(file->type);
    01568           ttd_strlcpy(_file_to_saveload.name, name, sizeof(_file_to_saveload.name));
    01569           ttd_strlcpy(_file_to_saveload.title, file->title, sizeof(_file_to_saveload.title));
    01570
    01571           DeleteWindow(w);
    01572         } else if (_saveload_mode == SLD_LOAD_HEIGHTMAP) {
    01573           SetFiosType(file->type);
    01574           ttd_strlcpy(_file_to_saveload.name, name, sizeof(_file_to_saveload.name));
    01575           ttd_strlcpy(_file_to_saveload.title, file->title, sizeof(_file_to_saveload.title));
    01576
    01577           DeleteWindow(w);
    01578           ShowHeightmapLoad();
    01579         } else {
    01580           /* SLD_SAVE_GAME, SLD_SAVE_SCENARIO copy clicked name to editbox */
    01581           ttd_strlcpy(WP(w, querystr_d).text.buf, file->title, WP(w, querystr_d).text.maxlength);
    01582           UpdateTextBufferSize(&WP(w, querystr_d).text);
    01583           w->InvalidateWidget(10);
    01584         }
    01585       } else {
    01586         /* Changed directory, need repaint. */
    01587         SetWindowDirty(w);
    01588         BuildFileList();
    01589       }
    01590       break;
    01591     }
    01592
    01593     case 11: case 12: // Delete, Save game
    01594       break;
    01595     }
    01596     break;
    01597   case WE_MOUSELOOP:
    01598     if (_saveload_mode == SLD_SAVE_GAME || _saveload_mode == SLD_SAVE_SCENARIO) {
    01599       HandleEditBox(w, &WP(w, querystr_d), 10);
    01600     }
    01601     break;
    01602   case WE_KEYPRESS:
    01603     if (e->we.keypress.keycode == WKC_ESC) {
    01604       DeleteWindow(w);
    01605       return;
    01606     }
    01607
    01608     if (_saveload_mode == SLD_SAVE_GAME || _saveload_mode == SLD_SAVE_SCENARIO) {
    01609       if (HandleEditBoxKey(w, &WP(w, querystr_d), 10, e) == 1) // Press Enter
    01610           w->HandleButtonClick(12);
    01611     }
    01612     break;
    01613   case WE_TIMEOUT:
    01614     /* This test protects against using widgets 11 and 12 which are only available
    01615      * in those two saveload mode  */
    01616     if (!(_saveload_mode == SLD_SAVE_GAME || _saveload_mode == SLD_SAVE_SCENARIO)) break;
    01617
    01618     if (w->IsWidgetLowered(11)) { // Delete button clicked
    01619       if (!FiosDelete(WP(w, querystr_d).text.buf)) {
    01620         ShowErrorMessage(INVALID_STRING_ID, STR_4008_UNABLE_TO_DELETE_FILE, 0, 0);
    01621       } else {
    01622         BuildFileList();
    01623         /* Reset file name to current date on successful delete */
    01624         if (_saveload_mode == SLD_SAVE_GAME) GenerateFileName();
    01625       }
    01626
    01627       UpdateTextBufferSize(&WP(w, querystr_d).text);
    01628       SetWindowDirty(w);
    01629     } else if (w->IsWidgetLowered(12)) { // Save button clicked
    01630       _switch_mode = SM_SAVE;
    01631       FiosMakeSavegameName(_file_to_saveload.name, WP(w, querystr_d).text.buf, sizeof(_file_to_saveload.name));
    01632
    01633       /* In the editor set up the vehicle engines correctly (date might have changed) */
    01634       if (_game_mode == GM_EDITOR) StartupEngines();
    01635     }
    01636     break;
    01637   case WE_DESTROY:
    01638     /* pause is only used in single-player, non-editor mode, non menu mode */
    01639     if (!_networking && _game_mode != GM_EDITOR && _game_mode != GM_MENU) {
    01640       if (_pause_game >= 0) DoCommandP(0, 0, 0, NULL, CMD_PAUSE);
    01641     }
    01642     FiosFreeSavegameList();
    01643     ClrBit(_no_scroll, SCROLL_SAVE);
    01644     break;
    01645   case WE_RESIZE: {
    01646     /* Widget 2 and 3 have to go with halve speed, make it so obiwan */
    01647     uint diff = e->we.sizing.diff.x / 2;
    01648     w->widget[2].right += diff;
    01649     w->widget[3].left  += diff;
    01650     w->widget[3].right += e->we.sizing.diff.x;
    01651
    01652     /* Same for widget 11 and 12 in save-dialog */
    01653     if (_saveload_mode == SLD_SAVE_GAME || _saveload_mode == SLD_SAVE_SCENARIO) {
    01654       w->widget[11].right += diff;
    01655       w->widget[12].left  += diff;
    01656       w->widget[12].right += e->we.sizing.diff.x;
    01657     }
    01658
    01659     w->vscroll.cap += e->we.sizing.diff.y / 10;
    01660     } break;
    01661   }
    01662 }
    01663
    01664 static const WindowDesc _load_dialog_desc = {
    01665   WDP_CENTER, WDP_CENTER, 257, 154, 257, 294,
    01666   WC_SAVELOAD, WC_NONE,
    01667   WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_STD_BTN | WDF_UNCLICK_BUTTONS | WDF_RESIZABLE,
    01668   _load_dialog_widgets,
    01669   SaveLoadDlgWndProc,
    01670 };
    01671
    01672 static const WindowDesc _save_dialog_desc = {
    01673   WDP_CENTER, WDP_CENTER, 257, 180, 257, 320,
    01674   WC_SAVELOAD, WC_NONE,
    01675   WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_STD_BTN | WDF_UNCLICK_BUTTONS | WDF_RESIZABLE,
    01676   _save_dialog_widgets,
    01677   SaveLoadDlgWndProc,
    01678 };
    01679
    01682 static const FileType _file_modetotype[] = {
    01683   FT_SAVEGAME,
    01684   FT_SCENARIO,
    01685   FT_SAVEGAME,
    01686   FT_SCENARIO,
    01687   FT_HEIGHTMAP,
    01688   FT_SAVEGAME,
    01689 };
    01690
    01691 void ShowSaveLoadDialog(SaveLoadDialogMode mode)
    01692 {
    01693   static const StringID saveload_captions[] = {
    01694     STR_4001_LOAD_GAME,
    01695     STR_0298_LOAD_SCENARIO,
    01696     STR_4000_SAVE_GAME,
    01697     STR_0299_SAVE_SCENARIO,
    01698     STR_LOAD_HEIGHTMAP,
    01699   };
    01700
    01701   Window *w;
    01702   const WindowDesc *sld = &_save_dialog_desc;
    01703
    01704
    01705   SetObjectToPlace(SPR_CURSOR_ZZZ, PAL_NONE, VHM_NONE, WC_MAIN_WINDOW, 0);
    01706   DeleteWindowById(WC_QUERY_STRING, 0);
    01707   DeleteWindowById(WC_SAVELOAD, 0);
    01708
    01709   _saveload_mode = mode;
    01710   SetBit(_no_scroll, SCROLL_SAVE);
    01711
    01712   /* Use an array to define what will be the current file type being handled
    01713    * by current file mode */
    01714   _file_to_saveload.filetype = _file_modetotype[mode];
    01715   switch (mode) {
    01716     case SLD_SAVE_GAME:     GenerateFileName(); break;
    01717     case SLD_SAVE_SCENARIO: strcpy(_edit_str_buf, "UNNAMED"); break;
    01718     default:                sld = &_load_dialog_desc; break;
    01719   }
    01720
    01721   assert((uint)mode < lengthof(saveload_captions));
    01722   w = AllocateWindowDesc(sld);
    01723   w->widget[1].data = saveload_captions[mode];
    01724   w->LowerWidget(7);
    01725
    01726   WP(w, querystr_d).afilter = CS_ALPHANUMERAL;
    01727   InitializeTextBuffer(&WP(w, querystr_d).text, _edit_str_buf, lengthof(_edit_str_buf), 240);
    01728
    01729   /* pause is only used in single-player, non-editor mode, non-menu mode. It
    01730    * will be unpaused in the WE_DESTROY event handler. */
    01731   if (_game_mode != GM_MENU && !_networking && _game_mode != GM_EDITOR) {
    01732     if (_pause_game >= 0) DoCommandP(0, 1, 0, NULL, CMD_PAUSE);
    01733   }
    01734
    01735   BuildFileList();
    01736
    01737   ResetObjectToPlace();
    01738 }
    01739
    01740 void RedrawAutosave()
    01741 {
    01742   SetWindowDirty(FindWindowById(WC_STATUS_BAR, 0));
    01743 }
    01744
    01745 void SetFiosType(const byte fiostype)
    01746 {
    01747   switch (fiostype) {
    01748     case FIOS_TYPE_FILE:
    01749     case FIOS_TYPE_SCENARIO:
    01750       _file_to_saveload.mode = SL_LOAD;
    01751       break;
    01752
    01753     case FIOS_TYPE_OLDFILE:
    01754     case FIOS_TYPE_OLD_SCENARIO:
    01755       _file_to_saveload.mode = SL_OLD_LOAD;
    01756       break;
    01757
    01758 #ifdef WITH_PNG
    01759     case FIOS_TYPE_PNG:
    01760       _file_to_saveload.mode = SL_PNG;
    01761       break;
    01762 #endif /* WITH_PNG */
    01763
    01764     case FIOS_TYPE_BMP:
    01765       _file_to_saveload.mode = SL_BMP;
    01766       break;
    01767
    01768     default:
    01769       _file_to_saveload.mode = SL_INVALID;
    01770       break;
    01771   }
    01772 }
    01773
    01779 static int32 _money_cheat_amount = 10000000;
    01780
    01781 static int32 ClickMoneyCheat(int32 p1, int32 p2)
    01782 {
    01783   DoCommandP(0, (uint32)(p2 * _money_cheat_amount), 0, NULL, CMD_MONEY_CHEAT);
    01784   return _money_cheat_amount;
    01785 }
    01786
    01791 static int32 ClickChangePlayerCheat(int32 p1, int32 p2)
    01792 {
    01793   while (IsValidPlayer((PlayerID)p1)) {
    01794     if (_players[p1].is_active) {
    01795       SetLocalPlayer((PlayerID)p1);
    01796
    01797       MarkWholeScreenDirty();
    01798       return _local_player;
    01799     }
    01800     p1 += p2;
    01801   }
    01802
    01803   return _local_player;
    01804 }
    01805
    01810 static int32 ClickChangeClimateCheat(int32 p1, int32 p2)
    01811 {
    01812   if (p1 == -1) p1 = 3;
    01813   if (p1 ==  4) p1 = 0;
    01814   _opt.landscape = p1;
    01815   ReloadNewGRFData();
    01816   return _opt.landscape;
    01817 }
    01818
    01819 extern void EnginesMonthlyLoop();
    01820
    01825 static int32 ClickChangeDateCheat(int32 p1, int32 p2)
    01826 {
    01827   YearMonthDay ymd;
    01828   ConvertDateToYMD(_date, &ymd);
    01829
    01830   if ((ymd.year == MIN_YEAR && p2 == -1) || (ymd.year == MAX_YEAR && p2 == 1)) return _cur_year;
    01831
    01832   SetDate(ConvertYMDToDate(_cur_year + p2, ymd.month, ymd.day));
    01833   EnginesMonthlyLoop();
    01834   SetWindowDirty(FindWindowById(WC_STATUS_BAR, 0));
    01835   return _cur_year;
    01836 }
    01837
    01838 typedef int32 CheckButtonClick(int32, int32);
    01839
    01840 struct CheatEntry {
    01841   VarType type;
    01842   StringID str;
    01843   void *variable;
    01844   bool *been_used;
    01845   CheckButtonClick *proc;
    01846 };
    01847
    01848 static const CheatEntry _cheats_ui[] = {
    01849   {SLE_INT32, STR_CHEAT_MONEY,           &_money_cheat_amount,            &_cheats.money.been_used,            &ClickMoneyCheat        },
    01850   {SLE_UINT8, STR_CHEAT_CHANGE_PLAYER,   &_local_player,                  &_cheats.switch_player.been_used,    &ClickChangePlayerCheat },
    01851   {SLE_BOOL,  STR_CHEAT_EXTRA_DYNAMITE,  &_cheats.magic_bulldozer.value,  &_cheats.magic_bulldozer.been_used,  NULL                    },
    01852   {SLE_BOOL,  STR_CHEAT_CROSSINGTUNNELS, &_cheats.crossing_tunnels.value, &_cheats.crossing_tunnels.been_used, NULL                    },
    01853   {SLE_BOOL,  STR_CHEAT_BUILD_IN_PAUSE,  &_cheats.build_in_pause.value,   &_cheats.build_in_pause.been_used,   NULL                    },
    01854   {SLE_BOOL,  STR_CHEAT_NO_JETCRASH,     &_cheats.no_jetcrash.value,      &_cheats.no_jetcrash.been_used,      NULL                    },
    01855   {SLE_BOOL,  STR_CHEAT_SETUP_PROD,      &_cheats.setup_prod.value,       &_cheats.setup_prod.been_used,       NULL                    },
    01856   {SLE_UINT8, STR_CHEAT_SWITCH_CLIMATE,  &_opt.landscape,                 &_cheats.switch_climate.been_used,   &ClickChangeClimateCheat},
    01857   {SLE_INT32, STR_CHEAT_CHANGE_DATE,     &_cur_year,                      &_cheats.change_date.been_used,      &ClickChangeDateCheat   },
    01858 };
    01859
    01860
    01861 static const Widget _cheat_widgets[] = {
    01862 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,   STR_018B_CLOSE_WINDOW},
    01863 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   399,     0,    13, STR_CHEATS, STR_018C_WINDOW_TITLE_DRAG_THIS},
    01864 {      WWT_PANEL,   RESIZE_NONE,    14,     0,   399,    14,   169, 0x0,        STR_NULL},
    01865 {      WWT_PANEL,   RESIZE_NONE,    14,     0,   399,    14,   169, 0x0,        STR_CHEATS_TIP},
    01866 {   WIDGETS_END},
    01867 };
    01868
    01869 static void CheatsWndProc(Window *w, WindowEvent *e)
    01870 {
    01871   switch (e->event) {
    01872     case WE_PAINT: {
    01873       int clk = WP(w, def_d).data_1;
    01874
    01875       DrawWindowWidgets(w);
    01876       DrawStringMultiCenter(200, 25, STR_CHEATS_WARNING, w->width - 50);
    01877
    01878       for (int i = 0, x = 0, y = 45; i != lengthof(_cheats_ui); i++) {
    01879         const CheatEntry *ce = &_cheats_ui[i];
    01880
    01881         DrawSprite((*ce->been_used) ? SPR_BOX_CHECKED : SPR_BOX_EMPTY, PAL_NONE, x + 5, y + 2);
    01882
    01883         switch (ce->type) {
    01884           case SLE_BOOL: {
    01885             bool on = (*(bool*)ce->variable);
    01886
    01887             DrawFrameRect(x + 20, y + 1, x + 30 + 9, y + 9, on ? 6 : 4, on ? FR_LOWERED : FR_NONE);
    01888             SetDParam(0, on ? STR_CONFIG_PATCHES_ON : STR_CONFIG_PATCHES_OFF);
    01889           } break;
    01890
    01891           default: {
    01892             int32 val = (int32)ReadValue(ce->variable, ce->type);
    01893             char buf[512];
    01894
    01895             /* Draw [<][>] boxes for settings of an integer-type */
    01896             DrawArrowButtons(x + 20, y, 3, clk - (i * 2), true, true);
    01897
    01898             switch (ce->str) {
    01899               /* Display date for change date cheat */
    01900               case STR_CHEAT_CHANGE_DATE: SetDParam(0, _date); break;
    01901
    01902               /* Draw colored flag for change player cheat */
    01903               case STR_CHEAT_CHANGE_PLAYER:
    01904                 SetDParam(0, val);
    01905                 GetString(buf, STR_CHEAT_CHANGE_PLAYER, lastof(buf));
    01906                 DrawPlayerIcon(_current_player, 60 + GetStringBoundingBox(buf).width, y + 2);
    01907                 break;
    01908
    01909               /* Set correct string for switch climate cheat */
    01910               case STR_CHEAT_SWITCH_CLIMATE: val += STR_TEMPERATE_LANDSCAPE;
    01911
    01912               /* Fallthrough */
    01913               default: SetDParam(0, val);
    01914             }
    01915           } break;
    01916         }
    01917
    01918         DrawString(50, y + 1, ce->str, TC_FROMSTRING);
    01919
    01920         y += 12;
    01921       }
    01922       break;
    01923     }
    01924
    01925     case WE_CLICK: {
    01926       uint btn = (e->we.click.pt.y - 46) / 12;
    01927       uint x = e->we.click.pt.x;
    01928
    01929       /* Not clicking a button? */
    01930       if (!IsInsideMM(x, 20, 40) || btn >= lengthof(_cheats_ui)) break;
    01931
    01932       const CheatEntry *ce = &_cheats_ui[btn];
    01933       int value = (int32)ReadValue(ce->variable, ce->type);
    01934       int oldvalue = value;
    01935
    01936       *ce->been_used = true;
    01937
    01938       switch (ce->type) {
    01939         case SLE_BOOL:
    01940           value ^= 1;
    01941           if (ce->proc != NULL) ce->proc(value, 0);
    01942           break;
    01943
    01944         default:
    01945           /* Take whatever the function returns */
    01946           value = ce->proc(value + ((x >= 30) ? 1 : -1), (x >= 30) ? 1 : -1);
    01947
    01948           if (value != oldvalue) WP(w, def_d).data_1 = btn * 2 + 1 + ((x >= 30) ? 1 : 0);
    01949           break;
    01950       }
    01951
    01952       if (value != oldvalue) WriteValue(ce->variable, ce->type, (int64)value);
    01953
    01954       w->flags4 |= 5 << WF_TIMEOUT_SHL;
    01955
    01956       SetWindowDirty(w);
    01957     } break;
    01958
    01959     case WE_TIMEOUT:
    01960       WP(w, def_d).data_1 = 0;
    01961       SetWindowDirty(w);
    01962       break;
    01963   }
    01964 }
    01965
    01966 static const WindowDesc _cheats_desc = {
    01967   240, 22, 400, 170, 400, 170,
    01968   WC_CHEATS, WC_NONE,
    01969   WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
    01970   _cheat_widgets,
    01971   CheatsWndProc
    01972 };
    01973
    01974
    01975 void ShowCheatWindow()
    01976 {
    01977   DeleteWindowById(WC_CHEATS, 0);
    01978   AllocateWindowDesc(&_cheats_desc);
    01979 }