src/misc_gui.cpp
branchcustombridgeheads
changeset 5649 55c8267c933f
parent 5648 1608018c5ff2
child 5650 aefc131bf5ce
equal deleted inserted replaced
5648:1608018c5ff2 5649:55c8267c933f
       
     1 /* $Id$ */
       
     2 
       
     3 #include "stdafx.h"
       
     4 #include "openttd.h"
       
     5 #include "hal.h"
       
     6 #include "heightmap.h"
       
     7 #include "debug.h"
       
     8 #include "functions.h"
       
     9 #include "newgrf.h"
       
    10 #include "saveload.h"
       
    11 #include "strings.h"
       
    12 #include "table/sprites.h"
       
    13 #include "table/strings.h"
       
    14 #include "table/tree_land.h"
       
    15 #include "map.h"
       
    16 #include "window.h"
       
    17 #include "gui.h"
       
    18 #include "viewport.h"
       
    19 #include "gfx.h"
       
    20 #include "station.h"
       
    21 #include "command.h"
       
    22 #include "player.h"
       
    23 #include "town.h"
       
    24 #include "sound.h"
       
    25 #include "network/network.h"
       
    26 #include "string.h"
       
    27 #include "variables.h"
       
    28 #include "vehicle.h"
       
    29 #include "train.h"
       
    30 #include "tgp.h"
       
    31 #include "settings.h"
       
    32 #include "date.h"
       
    33 
       
    34 #include "fios.h"
       
    35 /* Variables to display file lists */
       
    36 FiosItem *_fios_list;
       
    37 int _saveload_mode;
       
    38 
       
    39 extern void GenerateLandscape(byte mode);
       
    40 extern void SwitchMode(int new_mode);
       
    41 
       
    42 static bool _fios_path_changed;
       
    43 static bool _savegame_sort_dirty;
       
    44 
       
    45 enum {
       
    46 	LAND_INFO_LINES          =   7,
       
    47 	LAND_INFO_LINE_BUFF_SIZE = 512,
       
    48 };
       
    49 
       
    50 static char _landinfo_data[LAND_INFO_LINES][LAND_INFO_LINE_BUFF_SIZE];
       
    51 
       
    52 static void LandInfoWndProc(Window *w, WindowEvent *e)
       
    53 {
       
    54 	if (e->event == WE_PAINT) {
       
    55 		DrawWindowWidgets(w);
       
    56 
       
    57 		DoDrawStringCentered(140, 16, _landinfo_data[0], 13);
       
    58 		DoDrawStringCentered(140, 27, _landinfo_data[1], 0);
       
    59 		DoDrawStringCentered(140, 38, _landinfo_data[2], 0);
       
    60 		DoDrawStringCentered(140, 49, _landinfo_data[3], 0);
       
    61 		DoDrawStringCentered(140, 60, _landinfo_data[4], 0);
       
    62 		if (_landinfo_data[5][0] != '\0') DrawStringMultiCenter(140, 76, BindCString(_landinfo_data[5]), 276);
       
    63 		if (_landinfo_data[6][0] != '\0') DoDrawStringCentered(140, 71, _landinfo_data[6], 0);
       
    64 	}
       
    65 }
       
    66 
       
    67 static const Widget _land_info_widgets[] = {
       
    68 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,                       STR_018B_CLOSE_WINDOW},
       
    69 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   279,     0,    13, STR_01A3_LAND_AREA_INFORMATION, STR_018C_WINDOW_TITLE_DRAG_THIS},
       
    70 {      WWT_PANEL,   RESIZE_NONE,    14,     0,   279,    14,    92, 0x0,                            STR_NULL},
       
    71 {    WIDGETS_END},
       
    72 };
       
    73 
       
    74 static const WindowDesc _land_info_desc = {
       
    75 	WDP_AUTO, WDP_AUTO, 280, 93,
       
    76 	WC_LAND_INFO,0,
       
    77 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
       
    78 	_land_info_widgets,
       
    79 	LandInfoWndProc
       
    80 };
       
    81 
       
    82 static void Place_LandInfo(TileIndex tile)
       
    83 {
       
    84 	Player *p;
       
    85 	Window *w;
       
    86 	Town *t;
       
    87 	int64 old_money;
       
    88 	int64 costclear;
       
    89 	AcceptedCargo ac;
       
    90 	TileDesc td;
       
    91 	StringID str;
       
    92 
       
    93 	DeleteWindowById(WC_LAND_INFO, 0);
       
    94 
       
    95 	w = AllocateWindowDesc(&_land_info_desc);
       
    96 	WP(w, void_d).data = &_landinfo_data;
       
    97 
       
    98 	p = GetPlayer(IsValidPlayer(_local_player) ? _local_player : 0);
       
    99 	t = ClosestTownFromTile(tile, _patches.dist_local_authority);
       
   100 
       
   101 	old_money = p->money64;
       
   102 	p->money64 = p->player_money = 0x7fffffff;
       
   103 	costclear = DoCommand(tile, 0, 0, 0, CMD_LANDSCAPE_CLEAR);
       
   104 	p->money64 = old_money;
       
   105 	UpdatePlayerMoney32(p);
       
   106 
       
   107 	/* Because build_date is not set yet in every TileDesc, we make sure it is empty */
       
   108 	td.build_date = 0;
       
   109 	GetAcceptedCargo(tile, ac);
       
   110 	GetTileDesc(tile, &td);
       
   111 
       
   112 	SetDParam(0, td.dparam[0]);
       
   113 	GetString(_landinfo_data[0], td.str, lastof(_landinfo_data[0]));
       
   114 
       
   115 	SetDParam(0, STR_01A6_N_A);
       
   116 	if (td.owner != OWNER_NONE && td.owner != OWNER_WATER) GetNameOfOwner(td.owner, tile);
       
   117 	GetString(_landinfo_data[1], STR_01A7_OWNER, lastof(_landinfo_data[1]));
       
   118 
       
   119 	str = STR_01A4_COST_TO_CLEAR_N_A;
       
   120 	if (!CmdFailed(costclear)) {
       
   121 		SetDParam(0, costclear);
       
   122 		str = STR_01A5_COST_TO_CLEAR;
       
   123 	}
       
   124 	GetString(_landinfo_data[2], str, lastof(_landinfo_data[2]));
       
   125 
       
   126 	snprintf(_userstring, lengthof(_userstring), "0x%.4X", tile);
       
   127 	SetDParam(0, TileX(tile));
       
   128 	SetDParam(1, TileY(tile));
       
   129 	SetDParam(2, STR_SPEC_USERSTRING);
       
   130 	GetString(_landinfo_data[3], STR_LANDINFO_COORDS, lastof(_landinfo_data[3]));
       
   131 
       
   132 	SetDParam(0, STR_01A9_NONE);
       
   133 	if (t != NULL && IsValidTown(t)) {
       
   134 		SetDParam(0, STR_TOWN);
       
   135 		SetDParam(1, t->index);
       
   136 	}
       
   137 	GetString(_landinfo_data[4], STR_01A8_LOCAL_AUTHORITY, lastof(_landinfo_data[4]));
       
   138 
       
   139 	{
       
   140 		int i;
       
   141 		char *p = GetString(_landinfo_data[5], STR_01CE_CARGO_ACCEPTED, lastof(_landinfo_data[5]));
       
   142 		bool found = false;
       
   143 
       
   144 		for (i = 0; i < NUM_CARGO; ++i) {
       
   145 			if (ac[i] > 0) {
       
   146 				/* Add a comma between each item. */
       
   147 				if (found) {
       
   148 					*p++ = ',';
       
   149 					*p++ = ' ';
       
   150 				}
       
   151 				found = true;
       
   152 
       
   153 				/* If the accepted value is less than 8, show it in 1/8:ths */
       
   154 				if (ac[i] < 8) {
       
   155 					SetDParam(0, ac[i]);
       
   156 					SetDParam(1, _cargoc.names_s[i]);
       
   157 					p = GetString(p, STR_01D1_8, lastof(_landinfo_data[5]));
       
   158 				} else {
       
   159 					p = GetString(p, _cargoc.names_s[i], lastof(_landinfo_data[5]));
       
   160 				}
       
   161 			}
       
   162 		}
       
   163 
       
   164 		if (!found) _landinfo_data[5][0] = '\0';
       
   165 	}
       
   166 
       
   167 	if (td.build_date != 0) {
       
   168 		SetDParam(0, td.build_date);
       
   169 		GetString(_landinfo_data[6], STR_BUILD_DATE, lastof(_landinfo_data[6]));
       
   170 	} else {
       
   171 		_landinfo_data[6][0] = '\0';
       
   172 	}
       
   173 
       
   174 #if defined(_DEBUG)
       
   175 #	define LANDINFOD_LEVEL 0
       
   176 #else
       
   177 #	define LANDINFOD_LEVEL 1
       
   178 #endif
       
   179 	DEBUG(misc, LANDINFOD_LEVEL, "TILE: %#x (%i,%i)", tile, TileX(tile), TileY(tile));
       
   180 	DEBUG(misc, LANDINFOD_LEVEL, "type_height  = %#x", _m[tile].type_height);
       
   181 	DEBUG(misc, LANDINFOD_LEVEL, "m1           = %#x", _m[tile].m1);
       
   182 	DEBUG(misc, LANDINFOD_LEVEL, "m2           = %#x", _m[tile].m2);
       
   183 	DEBUG(misc, LANDINFOD_LEVEL, "m3           = %#x", _m[tile].m3);
       
   184 	DEBUG(misc, LANDINFOD_LEVEL, "m4           = %#x", _m[tile].m4);
       
   185 	DEBUG(misc, LANDINFOD_LEVEL, "m5           = %#x", _m[tile].m5);
       
   186 	DEBUG(misc, LANDINFOD_LEVEL, "extra        = %#x", _m[tile].extra);
       
   187 #undef LANDINFOD_LEVEL
       
   188 }
       
   189 
       
   190 void PlaceLandBlockInfo(void)
       
   191 {
       
   192 	if (_cursor.sprite == SPR_CURSOR_QUERY) {
       
   193 		ResetObjectToPlace();
       
   194 	} else {
       
   195 		_place_proc = Place_LandInfo;
       
   196 		SetObjectToPlace(SPR_CURSOR_QUERY, 1, 1, 0);
       
   197 	}
       
   198 }
       
   199 
       
   200 static const char *credits[] = {
       
   201 	/*************************************************************************
       
   202 	 *                      maximum length of string which fits in window   -^*/
       
   203 	"Original design by Chris Sawyer",
       
   204 	"Original graphics by Simon Foster",
       
   205 	"",
       
   206 	"The OpenTTD team (in alphabetical order):",
       
   207 	"  Jean-Francois Claeys (Belugas) - In training, not yet specialized",
       
   208 	"  Bjarni Corfitzen (Bjarni) - MacOSX port, coder",
       
   209 	"  Matthijs Kooijman (blathijs) - Pathfinder-guru",
       
   210 	"  Victor Fischer (Celestar) - Programming everywhere you need him to",
       
   211 	"  Tamás Faragó (Darkvater) - Lead coder",
       
   212 	"  Loïc Guilloux (glx) - In training, not yet specialized",
       
   213 	"  Jaroslav Mazanec (KUDr) - YAPG (Yet Another Pathfinder God) ;)",
       
   214 	"  Attila Bán (MiHaMiX) - WebTranslator, Nightlies, Wiki and bugtracker host",
       
   215 	"  Peter Nelson (peter1138) - Spiritual descendant from newgrf gods",
       
   216 	"  Remko Bijker (Rubidium) - Belugas code scrutinizer",
       
   217 	"  Christoph Mallon (Tron) - Programmer, code correctness police",
       
   218 	"  Patric Stout (TrueLight) - Coder, network guru, SVN- and website host",
       
   219 	"",
       
   220 	"Retired Developers:",
       
   221 	"  Ludvig Strigeus (ludde) - OpenTTD author, main coder (0.1 - 0.3.3)",
       
   222 	"  Serge Paquet (vurlix) - Assistant project manager, coder (0.1 - 0.3.3)",
       
   223 	"  Dominik Scherer (dominik81) - Lead programmer, GUI expert (0.3.0 - 0.3.6)",
       
   224 	"  Owen Rudge (orudge) - Forum- and masterserver host, OS/2 port (0.1 - 0.4.8)",
       
   225 	"",
       
   226 	"Special thanks go out to:",
       
   227 	"  Josef Drexler - For his great work on TTDPatch",
       
   228 	"  Marcin Grzegorczyk - For his documentation of TTD internals",
       
   229 	"  Petr Baudis (pasky) - Many patches, newgrf support",
       
   230 	"  Stefan Meißner (sign_de) - For his work on the console",
       
   231 	"  Simon Sasburg (HackyKid) - Many bugfixes he has blessed us with (and PBS)",
       
   232 	"  Cian Duffy (MYOB) - BeOS port / manual writing",
       
   233 	"  Christian Rosentreter (tokai) - MorphOS / AmigaOS port",
       
   234 	"  Richard Kempton (richK) - additional airports, initial TGP implementation",
       
   235 	"",
       
   236 	"  Michael Blunck - Pre-Signals and Semaphores © 2003",
       
   237 	"  George - Canal/Lock graphics © 2003-2004",
       
   238 	"  Marcin Grzegorczyk - Foundations for Tracks on Slopes",
       
   239 	"  All Translators - Who made OpenTTD a truly international game",
       
   240 	"  Bug Reporters - Without whom OpenTTD would still be full of bugs!",
       
   241 	"",
       
   242 	"",
       
   243 	"And last but not least:",
       
   244 	"  Chris Sawyer - For an amazing game!"
       
   245 };
       
   246 
       
   247 static void AboutWindowProc(Window *w, WindowEvent *e)
       
   248 {
       
   249 	switch (e->event) {
       
   250 	case WE_CREATE: /* Set up window counter and start position of scroller */
       
   251 		WP(w, scroller_d).counter = 0;
       
   252 		WP(w, scroller_d).height = w->height - 40;
       
   253 		break;
       
   254 	case WE_PAINT: {
       
   255 		uint i;
       
   256 		int y = WP(w, scroller_d).height;
       
   257 		DrawWindowWidgets(w);
       
   258 
       
   259 		// Show original copyright and revision version
       
   260 		DrawStringCentered(210, 17, STR_00B6_ORIGINAL_COPYRIGHT, 0);
       
   261 		DrawStringCentered(210, 17 + 10, STR_00B7_VERSION, 0);
       
   262 
       
   263 		// Show all scrolling credits
       
   264 		for (i = 0; i < lengthof(credits); i++) {
       
   265 			if (y >= 50 && y < (w->height - 40)) {
       
   266 				DoDrawString(credits[i], 10, y, 0x10);
       
   267 			}
       
   268 			y += 10;
       
   269 		}
       
   270 
       
   271 		// If the last text has scrolled start anew from the start
       
   272 		if (y < 50) WP(w, scroller_d).height = w->height - 40;
       
   273 
       
   274 		DoDrawStringCentered(210, w->height - 25, "Website: http://www.openttd.org", 16);
       
   275 		DrawStringCentered(210, w->height - 15, STR_00BA_COPYRIGHT_OPENTTD, 0);
       
   276 	}	break;
       
   277 	case WE_MOUSELOOP: /* Timer to scroll the text and adjust the new top */
       
   278 		if (WP(w, scroller_d).counter++ % 3 == 0) {
       
   279 			WP(w, scroller_d).height--;
       
   280 			SetWindowDirty(w);
       
   281 		}
       
   282 		break;
       
   283 	}
       
   284 }
       
   285 
       
   286 static const Widget _about_widgets[] = {
       
   287 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,         STR_018B_CLOSE_WINDOW},
       
   288 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   419,     0,    13, STR_015B_OPENTTD, STR_NULL},
       
   289 {      WWT_PANEL,   RESIZE_NONE,    14,     0,   419,    14,   271, 0x0,              STR_NULL},
       
   290 {      WWT_FRAME,   RESIZE_NONE,    14,     5,   414,    40,   245, STR_NULL,         STR_NULL},
       
   291 {    WIDGETS_END},
       
   292 };
       
   293 
       
   294 static const WindowDesc _about_desc = {
       
   295 	WDP_CENTER, WDP_CENTER, 420, 272,
       
   296 	WC_GAME_OPTIONS,0,
       
   297 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
       
   298 	_about_widgets,
       
   299 	AboutWindowProc
       
   300 };
       
   301 
       
   302 
       
   303 void ShowAboutWindow(void)
       
   304 {
       
   305 	DeleteWindowById(WC_GAME_OPTIONS, 0);
       
   306 	AllocateWindowDesc(&_about_desc);
       
   307 }
       
   308 
       
   309 static int _tree_to_plant;
       
   310 
       
   311 static const uint32 _tree_sprites[] = {
       
   312 	0x655, 0x663, 0x678, 0x62B, 0x647, 0x639, 0x64E, 0x632, 0x67F, 0x68D, 0x69B, 0x6A9,
       
   313 	0x6AF, 0x6D2, 0x6D9, 0x6C4, 0x6CB, 0x6B6, 0x6BD, 0x6E0,
       
   314 	0x72E, 0x734, 0x74A, 0x74F, 0x76B, 0x78F, 0x788, 0x77B, 0x75F, 0x774, 0x720, 0x797,
       
   315 	0x79E, 0x7A5 | PALETTE_TO_GREEN, 0x7AC | PALETTE_TO_RED, 0x7B3, 0x7BA, 0x7C1 | PALETTE_TO_RED, 0x7C8 | PALETTE_TO_PALE_GREEN, 0x7CF | PALETTE_TO_YELLOW, 0x7D6 | PALETTE_TO_RED
       
   316 };
       
   317 
       
   318 static void BuildTreesWndProc(Window *w, WindowEvent *e)
       
   319 {
       
   320 	switch (e->event) {
       
   321 	case WE_PAINT: {
       
   322 		int x,y;
       
   323 		int i, count;
       
   324 
       
   325 		DrawWindowWidgets(w);
       
   326 
       
   327 		WP(w,tree_d).base = i = _tree_base_by_landscape[_opt.landscape];
       
   328 		WP(w,tree_d).count = count = _tree_count_by_landscape[_opt.landscape];
       
   329 
       
   330 		x = 18;
       
   331 		y = 54;
       
   332 		do {
       
   333 			DrawSprite(_tree_sprites[i], x, y);
       
   334 			x += 35;
       
   335 			if (!(++i & 3)) {
       
   336 				x -= 35 * 4;
       
   337 				y += 47;
       
   338 			}
       
   339 		} while (--count);
       
   340 	} break;
       
   341 
       
   342 	case WE_CLICK: {
       
   343 		int wid = e->we.click.widget;
       
   344 
       
   345 		switch (wid) {
       
   346 		case 0:
       
   347 			ResetObjectToPlace();
       
   348 			break;
       
   349 
       
   350 		case 3: case 4: case 5: case 6:
       
   351 		case 7: case 8: case 9: case 10:
       
   352 		case 11:case 12: case 13: case 14:
       
   353 			if (wid - 3 >= WP(w,tree_d).count) break;
       
   354 
       
   355 			if (HandlePlacePushButton(w, wid, SPR_CURSOR_TREE, 1, NULL))
       
   356 				_tree_to_plant = WP(w,tree_d).base + wid - 3;
       
   357 			break;
       
   358 
       
   359 		case 15: // tree of random type.
       
   360 			if (HandlePlacePushButton(w, 15, SPR_CURSOR_TREE, 1, NULL))
       
   361 				_tree_to_plant = -1;
       
   362 			break;
       
   363 
       
   364 		case 16: /* place trees randomly over the landscape*/
       
   365 			LowerWindowWidget(w, 16);
       
   366 			w->flags4 |= 5 << WF_TIMEOUT_SHL;
       
   367 			SndPlayFx(SND_15_BEEP);
       
   368 			PlaceTreesRandomly();
       
   369 			MarkWholeScreenDirty();
       
   370 			break;
       
   371 		}
       
   372 	} break;
       
   373 
       
   374 	case WE_PLACE_OBJ:
       
   375 		VpStartPlaceSizing(e->we.place.tile, VPM_X_AND_Y_LIMITED);
       
   376 		VpSetPlaceSizingLimit(20);
       
   377 		break;
       
   378 
       
   379 	case WE_PLACE_DRAG:
       
   380 		VpSelectTilesWithMethod(e->we.place.pt.x, e->we.place.pt.y, e->we.place.userdata);
       
   381 		return;
       
   382 
       
   383 	case WE_PLACE_MOUSEUP:
       
   384 		if (e->we.place.pt.x != -1) {
       
   385 			DoCommandP(e->we.place.tile, _tree_to_plant, e->we.place.starttile, NULL,
       
   386 				CMD_PLANT_TREE | CMD_AUTO | CMD_MSG(STR_2805_CAN_T_PLANT_TREE_HERE));
       
   387 		}
       
   388 		break;
       
   389 
       
   390 	case WE_TIMEOUT:
       
   391 		RaiseWindowWidget(w, 16);
       
   392 		break;
       
   393 
       
   394 	case WE_ABORT_PLACE_OBJ:
       
   395 		RaiseWindowButtons(w);
       
   396 		break;
       
   397 	}
       
   398 }
       
   399 
       
   400 static const Widget _build_trees_widgets[] = {
       
   401 {   WWT_CLOSEBOX,   RESIZE_NONE,     7,     0,    10,     0,    13, STR_00C5,              STR_018B_CLOSE_WINDOW},
       
   402 {    WWT_CAPTION,   RESIZE_NONE,     7,    11,   142,     0,    13, STR_2802_TREES,        STR_018C_WINDOW_TITLE_DRAG_THIS},
       
   403 {      WWT_PANEL,   RESIZE_NONE,     7,     0,   142,    14,   170, 0x0,                   STR_NULL},
       
   404 {      WWT_PANEL,   RESIZE_NONE,    14,     2,    35,    16,    61, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
       
   405 {      WWT_PANEL,   RESIZE_NONE,    14,    37,    70,    16,    61, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
       
   406 {      WWT_PANEL,   RESIZE_NONE,    14,    72,   105,    16,    61, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
       
   407 {      WWT_PANEL,   RESIZE_NONE,    14,   107,   140,    16,    61, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
       
   408 {      WWT_PANEL,   RESIZE_NONE,    14,     2,    35,    63,   108, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
       
   409 {      WWT_PANEL,   RESIZE_NONE,    14,    37,    70,    63,   108, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
       
   410 {      WWT_PANEL,   RESIZE_NONE,    14,    72,   105,    63,   108, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
       
   411 {      WWT_PANEL,   RESIZE_NONE,    14,   107,   140,    63,   108, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
       
   412 {      WWT_PANEL,   RESIZE_NONE,    14,     2,    35,   110,   155, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
       
   413 {      WWT_PANEL,   RESIZE_NONE,    14,    37,    70,   110,   155, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
       
   414 {      WWT_PANEL,   RESIZE_NONE,    14,    72,   105,   110,   155, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
       
   415 {      WWT_PANEL,   RESIZE_NONE,    14,   107,   140,   110,   155, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
       
   416 {    WWT_TEXTBTN,   RESIZE_NONE,    14,     2,   140,   157,   168, STR_TREES_RANDOM_TYPE, STR_TREES_RANDOM_TYPE_TIP},
       
   417 {    WIDGETS_END},
       
   418 };
       
   419 
       
   420 static const WindowDesc _build_trees_desc = {
       
   421 	497, 22, 143, 171,
       
   422 	WC_BUILD_TREES, WC_SCEN_LAND_GEN,
       
   423 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
       
   424 	_build_trees_widgets,
       
   425 	BuildTreesWndProc
       
   426 };
       
   427 
       
   428 static const Widget _build_trees_scen_widgets[] = {
       
   429 {   WWT_CLOSEBOX,   RESIZE_NONE,     7,     0,    10,     0,    13, STR_00C5,              STR_018B_CLOSE_WINDOW},
       
   430 {    WWT_CAPTION,   RESIZE_NONE,     7,    11,   142,     0,    13, STR_2802_TREES,        STR_018C_WINDOW_TITLE_DRAG_THIS},
       
   431 {      WWT_PANEL,   RESIZE_NONE,     7,     0,   142,    14,   183, 0x0,                   STR_NULL},
       
   432 {      WWT_PANEL,   RESIZE_NONE,    14,     2,    35,    16,    61, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
       
   433 {      WWT_PANEL,   RESIZE_NONE,    14,    37,    70,    16,    61, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
       
   434 {      WWT_PANEL,   RESIZE_NONE,    14,    72,   105,    16,    61, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
       
   435 {      WWT_PANEL,   RESIZE_NONE,    14,   107,   140,    16,    61, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
       
   436 {      WWT_PANEL,   RESIZE_NONE,    14,     2,    35,    63,   108, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
       
   437 {      WWT_PANEL,   RESIZE_NONE,    14,    37,    70,    63,   108, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
       
   438 {      WWT_PANEL,   RESIZE_NONE,    14,    72,   105,    63,   108, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
       
   439 {      WWT_PANEL,   RESIZE_NONE,    14,   107,   140,    63,   108, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
       
   440 {      WWT_PANEL,   RESIZE_NONE,    14,     2,    35,   110,   155, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
       
   441 {      WWT_PANEL,   RESIZE_NONE,    14,    37,    70,   110,   155, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
       
   442 {      WWT_PANEL,   RESIZE_NONE,    14,    72,   105,   110,   155, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
       
   443 {      WWT_PANEL,   RESIZE_NONE,    14,   107,   140,   110,   155, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
       
   444 {    WWT_TEXTBTN,   RESIZE_NONE,    14,     2,   140,   157,   168, STR_TREES_RANDOM_TYPE, STR_TREES_RANDOM_TYPE_TIP},
       
   445 {    WWT_TEXTBTN,   RESIZE_NONE,    14,     2,   140,   170,   181, STR_028A_RANDOM_TREES, STR_028B_PLANT_TREES_RANDOMLY_OVER},
       
   446 {    WIDGETS_END},
       
   447 };
       
   448 
       
   449 static const WindowDesc _build_trees_scen_desc = {
       
   450 	WDP_AUTO, WDP_AUTO, 143, 184,
       
   451 	WC_BUILD_TREES,0,
       
   452 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
       
   453 	_build_trees_scen_widgets,
       
   454 	BuildTreesWndProc
       
   455 };
       
   456 
       
   457 
       
   458 void ShowBuildTreesToolbar(void)
       
   459 {
       
   460 	if (!IsValidPlayer(_current_player)) return;
       
   461 	AllocateWindowDescFront(&_build_trees_desc, 0);
       
   462 }
       
   463 
       
   464 void ShowBuildTreesScenToolbar(void)
       
   465 {
       
   466 	AllocateWindowDescFront(&_build_trees_scen_desc, 0);
       
   467 }
       
   468 
       
   469 static uint32 _errmsg_decode_params[20];
       
   470 static StringID _errmsg_message_1, _errmsg_message_2;
       
   471 static uint _errmsg_duration;
       
   472 
       
   473 
       
   474 static const Widget _errmsg_widgets[] = {
       
   475 {   WWT_CLOSEBOX,   RESIZE_NONE,     4,     0,    10,     0,    13, STR_00C5,         STR_018B_CLOSE_WINDOW},
       
   476 {    WWT_CAPTION,   RESIZE_NONE,     4,    11,   239,     0,    13, STR_00B2_MESSAGE, STR_NULL},
       
   477 {      WWT_PANEL,   RESIZE_NONE,     4,     0,   239,    14,    45, 0x0,              STR_NULL},
       
   478 {    WIDGETS_END},
       
   479 };
       
   480 
       
   481 static const Widget _errmsg_face_widgets[] = {
       
   482 {   WWT_CLOSEBOX,   RESIZE_NONE,     4,     0,    10,     0,    13, STR_00C5,              STR_018B_CLOSE_WINDOW},
       
   483 {    WWT_CAPTION,   RESIZE_NONE,     4,    11,   333,     0,    13, STR_00B3_MESSAGE_FROM, STR_NULL},
       
   484 {      WWT_PANEL,   RESIZE_NONE,     4,     0,   333,    14,   136, 0x0,                   STR_NULL},
       
   485 {   WIDGETS_END},
       
   486 };
       
   487 
       
   488 static void ErrmsgWndProc(Window *w, WindowEvent *e)
       
   489 {
       
   490 	switch (e->event) {
       
   491 	case WE_PAINT:
       
   492 		COPY_IN_DPARAM(0, _errmsg_decode_params, lengthof(_errmsg_decode_params));
       
   493 		DrawWindowWidgets(w);
       
   494 		COPY_IN_DPARAM(0, _errmsg_decode_params, lengthof(_errmsg_decode_params));
       
   495 		if (!IsWindowOfPrototype(w, _errmsg_face_widgets)) {
       
   496 			DrawStringMultiCenter(
       
   497 				120,
       
   498 				(_errmsg_message_1 == INVALID_STRING_ID ? 25 : 15),
       
   499 				_errmsg_message_2,
       
   500 				238);
       
   501 			if (_errmsg_message_1 != INVALID_STRING_ID)
       
   502 				DrawStringMultiCenter(
       
   503 					120,
       
   504 					30,
       
   505 					_errmsg_message_1,
       
   506 					238);
       
   507 		} else {
       
   508 			const Player *p = GetPlayer(GetDParamX(_errmsg_decode_params,2));
       
   509 			DrawPlayerFace(p->face, p->player_color, 2, 16);
       
   510 
       
   511 			DrawStringMultiCenter(
       
   512 				214,
       
   513 				(_errmsg_message_1 == INVALID_STRING_ID ? 65 : 45),
       
   514 				_errmsg_message_2,
       
   515 				238);
       
   516 			if (_errmsg_message_1 != INVALID_STRING_ID)
       
   517 				DrawStringMultiCenter(
       
   518 					214,
       
   519 					90,
       
   520 					_errmsg_message_1,
       
   521 					238);
       
   522 		}
       
   523 		break;
       
   524 
       
   525 	case WE_MOUSELOOP:
       
   526 		if (_right_button_down) DeleteWindow(w);
       
   527 		break;
       
   528 
       
   529 	case WE_4:
       
   530 		if (--_errmsg_duration == 0) DeleteWindow(w);
       
   531 		break;
       
   532 
       
   533 	case WE_DESTROY:
       
   534 		SetRedErrorSquare(0);
       
   535 		_switch_mode_errorstr = INVALID_STRING_ID;
       
   536 		break;
       
   537 
       
   538 	case WE_KEYPRESS:
       
   539 		if (e->we.keypress.keycode == WKC_SPACE) {
       
   540 			// Don't continue.
       
   541 			e->we.keypress.cont = false;
       
   542 			DeleteWindow(w);
       
   543 		}
       
   544 		break;
       
   545 	}
       
   546 }
       
   547 
       
   548 void ShowErrorMessage(StringID msg_1, StringID msg_2, int x, int y)
       
   549 {
       
   550 	Window *w;
       
   551 	const ViewPort *vp;
       
   552 	Point pt;
       
   553 
       
   554 	DeleteWindowById(WC_ERRMSG, 0);
       
   555 
       
   556 	//assert(msg_2);
       
   557 	if (msg_2 == 0) msg_2 = STR_EMPTY;
       
   558 
       
   559 	_errmsg_message_1 = msg_1;
       
   560 	_errmsg_message_2 = msg_2;
       
   561 	COPY_OUT_DPARAM(_errmsg_decode_params, 0, lengthof(_errmsg_decode_params));
       
   562 	_errmsg_duration = _patches.errmsg_duration;
       
   563 	if (!_errmsg_duration) return;
       
   564 
       
   565 	if (_errmsg_message_1 != STR_013B_OWNED_BY || GetDParamX(_errmsg_decode_params,2) >= 8) {
       
   566 
       
   567 		if ( (x|y) != 0) {
       
   568 			pt = RemapCoords2(x, y);
       
   569 			vp = FindWindowById(WC_MAIN_WINDOW, 0)->viewport;
       
   570 
       
   571 			// move x pos to opposite corner
       
   572 			pt.x = ((pt.x - vp->virtual_left) >> vp->zoom) + vp->left;
       
   573 			pt.x = (pt.x < (_screen.width >> 1)) ? _screen.width - 260 : 20;
       
   574 
       
   575 			// move y pos to opposite corner
       
   576 			pt.y = ((pt.y - vp->virtual_top) >> vp->zoom) + vp->top;
       
   577 			pt.y = (pt.y < (_screen.height >> 1)) ? _screen.height - 80 : 100;
       
   578 
       
   579 		} else {
       
   580 			pt.x = (_screen.width - 240) >> 1;
       
   581 			pt.y = (_screen.height - 46) >> 1;
       
   582 		}
       
   583 		w = AllocateWindow(pt.x, pt.y, 240, 46, ErrmsgWndProc, WC_ERRMSG, _errmsg_widgets);
       
   584 	} else {
       
   585 		if ( (x|y) != 0) {
       
   586 			pt = RemapCoords2(x, y);
       
   587 			vp = FindWindowById(WC_MAIN_WINDOW, 0)->viewport;
       
   588 			pt.x = clamp(((pt.x - vp->virtual_left) >> vp->zoom) + vp->left - (334/2), 0, _screen.width - 334);
       
   589 			pt.y = clamp(((pt.y - vp->virtual_top) >> vp->zoom) + vp->top - (137/2), 22, _screen.height - 137);
       
   590 		} else {
       
   591 			pt.x = (_screen.width - 334) >> 1;
       
   592 			pt.y = (_screen.height - 137) >> 1;
       
   593 		}
       
   594 		w = AllocateWindow(pt.x, pt.y, 334, 137, ErrmsgWndProc, WC_ERRMSG, _errmsg_face_widgets);
       
   595 	}
       
   596 
       
   597 	w->desc_flags = WDF_STD_BTN | WDF_DEF_WIDGET;
       
   598 }
       
   599 
       
   600 
       
   601 void ShowEstimatedCostOrIncome(int32 cost, int x, int y)
       
   602 {
       
   603 	StringID msg = STR_0805_ESTIMATED_COST;
       
   604 
       
   605 	if (cost < 0) {
       
   606 		cost = -cost;
       
   607 		msg = STR_0807_ESTIMATED_INCOME;
       
   608 	}
       
   609 	SetDParam(0, cost);
       
   610 	ShowErrorMessage(INVALID_STRING_ID, msg, x, y);
       
   611 }
       
   612 
       
   613 void ShowCostOrIncomeAnimation(int x, int y, int z, int32 cost)
       
   614 {
       
   615 	StringID msg;
       
   616 	Point pt = RemapCoords(x,y,z);
       
   617 
       
   618 	msg = STR_0801_COST;
       
   619 	if (cost < 0) {
       
   620 		cost = -cost;
       
   621 		msg = STR_0803_INCOME;
       
   622 	}
       
   623 	SetDParam(0, cost);
       
   624 	AddTextEffect(msg, pt.x, pt.y, 0x250);
       
   625 }
       
   626 
       
   627 void ShowFeederIncomeAnimation(int x, int y, int z, int32 cost)
       
   628 {
       
   629 	Point pt = RemapCoords(x,y,z);
       
   630 
       
   631 	SetDParam(0, cost);
       
   632 	AddTextEffect(STR_FEEDER, pt.x, pt.y, 0x250);
       
   633 }
       
   634 
       
   635 static const Widget _tooltips_widgets[] = {
       
   636 {      WWT_PANEL,   RESIZE_NONE,    14,     0,   199,     0,    31, 0x0, STR_NULL},
       
   637 {   WIDGETS_END},
       
   638 };
       
   639 
       
   640 
       
   641 static void TooltipsWndProc(Window *w, WindowEvent *e)
       
   642 {
       
   643 	switch (e->event) {
       
   644 		case WE_PAINT: {
       
   645 			uint arg;
       
   646 			GfxFillRect(0, 0, w->width - 1, w->height - 1, 0);
       
   647 			GfxFillRect(1, 1, w->width - 2, w->height - 2, 0x44);
       
   648 
       
   649 			for (arg = 0; arg < WP(w, tooltips_d).paramcount; arg++) {
       
   650 				SetDParam(arg, WP(w, tooltips_d).params[arg]);
       
   651 			}
       
   652 			DrawStringMultiCenter((w->width >> 1), (w->height >> 1) - 5, WP(w, tooltips_d).string_id, 197);
       
   653 			break;
       
   654 		}
       
   655 
       
   656 		case WE_MOUSELOOP:
       
   657 			/* We can show tooltips while dragging tools. These are shown as long as
       
   658 			 * we are dragging the tool. Normal tooltips work with rmb */
       
   659 			if (WP(w, tooltips_d).paramcount == 0 ) {
       
   660 				if (!_right_button_down) DeleteWindow(w);
       
   661 			} else {
       
   662 				if (!_left_button_down) DeleteWindow(w);
       
   663 			}
       
   664 
       
   665 			break;
       
   666 	}
       
   667 }
       
   668 
       
   669 /** Shows a tooltip
       
   670 * @param str String to be displayed
       
   671 * @param params (optional) up to 5 pieces of additional information that may be
       
   672 * added to a tooltip; currently only supports parameters of {NUM} (integer) */
       
   673 void GuiShowTooltipsWithArgs(StringID str, uint paramcount, const uint32 params[])
       
   674 {
       
   675 	char buffer[512];
       
   676 	BoundingRect br;
       
   677 	Window *w;
       
   678 	uint i;
       
   679 	int x, y;
       
   680 
       
   681 	DeleteWindowById(WC_TOOLTIPS, 0);
       
   682 
       
   683 	/* We only show measurement tooltips with patch setting on */
       
   684 	if (str == STR_NULL || (paramcount != 0 && !_patches.measure_tooltip)) return;
       
   685 
       
   686 	for (i = 0; i != paramcount; i++) SetDParam(i, params[i]);
       
   687 	GetString(buffer, str, lastof(buffer));
       
   688 
       
   689 	br = GetStringBoundingBox(buffer);
       
   690 	br.width += 6; br.height += 4; // increase slightly to have some space around the box
       
   691 
       
   692 	/* Cut tooltip length to 200 pixels max, wrap to new line if longer */
       
   693 	if (br.width > 200) {
       
   694 		br.height += ((br.width - 4) / 176) * 10;
       
   695 		br.width = 200;
       
   696 	}
       
   697 
       
   698 	/* Correctly position the tooltip position, watch out for window and cursor size
       
   699 	 * Clamp value to below main toolbar and above statusbar. If tooltip would
       
   700 	 * go below window, flip it so it is shown above the cursor */
       
   701 	y = clamp(_cursor.pos.y + _cursor.size.y + _cursor.offs.y + 5, 22, _screen.height - 12);
       
   702 	if (y + br.height > _screen.height - 12) y = _cursor.pos.y + _cursor.offs.y - br.height - 5;
       
   703 	x = clamp(_cursor.pos.x - (br.width >> 1), 0, _screen.width - br.width);
       
   704 
       
   705 	w = AllocateWindow(x, y, br.width, br.height, TooltipsWndProc, WC_TOOLTIPS, _tooltips_widgets);
       
   706 
       
   707 	WP(w, tooltips_d).string_id = str;
       
   708 	assert(sizeof(WP(w, tooltips_d).params[0]) == sizeof(params[0]));
       
   709 	memcpy(WP(w, tooltips_d).params, params, sizeof(WP(w, tooltips_d).params[0]) * paramcount);
       
   710 	WP(w, tooltips_d).paramcount = paramcount;
       
   711 
       
   712 	w->flags4 &= ~WF_WHITE_BORDER_MASK; // remove white-border from tooltip
       
   713 	w->widget[0].right = br.width;
       
   714 	w->widget[0].bottom = br.height;
       
   715 }
       
   716 
       
   717 
       
   718 static void DrawStationCoverageText(const AcceptedCargo accepts,
       
   719 	int str_x, int str_y, uint mask)
       
   720 {
       
   721 	char *b = _userstring;
       
   722 	bool first = true;
       
   723 	int i;
       
   724 
       
   725 	b = InlineString(b, STR_000D_ACCEPTS);
       
   726 
       
   727 	for (i = 0; i != NUM_CARGO; i++, mask >>= 1) {
       
   728 		if (b >= lastof(_userstring) - 5) break;
       
   729 		if (accepts[i] >= 8 && mask & 1) {
       
   730 			if (first) {
       
   731 				first = false;
       
   732 			} else {
       
   733 				/* Add a comma if this is not the first item */
       
   734 				*b++ = ',';
       
   735 				*b++ = ' ';
       
   736 			}
       
   737 			b = InlineString(b, _cargoc.names_s[i]);
       
   738 		}
       
   739 	}
       
   740 
       
   741 	/* If first is still true then no cargo is accepted */
       
   742 	if (first) b = InlineString(b, STR_00D0_NOTHING);
       
   743 
       
   744 	*b = '\0';
       
   745 	DrawStringMultiLine(str_x, str_y, STR_SPEC_USERSTRING, 144);
       
   746 }
       
   747 
       
   748 void DrawStationCoverageAreaText(int sx, int sy, uint mask, int rad) {
       
   749 	TileIndex tile = TileVirtXY(_thd.pos.x, _thd.pos.y);
       
   750 	AcceptedCargo accepts;
       
   751 	if (tile < MapSize()) {
       
   752 		GetAcceptanceAroundTiles(accepts, tile, _thd.size.x / TILE_SIZE, _thd.size.y / TILE_SIZE , rad);
       
   753 		DrawStationCoverageText(accepts, sx, sy, mask);
       
   754 	}
       
   755 }
       
   756 
       
   757 void CheckRedrawStationCoverage(const Window *w)
       
   758 {
       
   759 	if (_thd.dirty & 1) {
       
   760 		_thd.dirty &= ~1;
       
   761 		SetWindowDirty(w);
       
   762 	}
       
   763 }
       
   764 
       
   765 void SetVScrollCount(Window *w, int num)
       
   766 {
       
   767 	w->vscroll.count = num;
       
   768 	num -= w->vscroll.cap;
       
   769 	if (num < 0) num = 0;
       
   770 	if (num < w->vscroll.pos) w->vscroll.pos = num;
       
   771 }
       
   772 
       
   773 void SetVScroll2Count(Window *w, int num)
       
   774 {
       
   775 	w->vscroll2.count = num;
       
   776 	num -= w->vscroll2.cap;
       
   777 	if (num < 0) num = 0;
       
   778 	if (num < w->vscroll2.pos) w->vscroll2.pos = num;
       
   779 }
       
   780 
       
   781 void SetHScrollCount(Window *w, int num)
       
   782 {
       
   783 	w->hscroll.count = num;
       
   784 	num -= w->hscroll.cap;
       
   785 	if (num < 0) num = 0;
       
   786 	if (num < w->hscroll.pos) w->hscroll.pos = num;
       
   787 }
       
   788 
       
   789 /* Delete a character at the caret position in a text buf.
       
   790  * If backspace is set, delete the character before the caret,
       
   791  * else delete the character after it. */
       
   792 static void DelChar(Textbuf *tb, bool backspace)
       
   793 {
       
   794 	WChar c;
       
   795 	uint width;
       
   796 	size_t len;
       
   797 
       
   798 	if (backspace) {
       
   799 		do {
       
   800 			tb->caretpos--;
       
   801 		} while (IsUtf8Part(*(tb->buf + tb->caretpos)));
       
   802 	}
       
   803 
       
   804 	len = Utf8Decode(&c, tb->buf + tb->caretpos);
       
   805 	width = GetCharacterWidth(FS_NORMAL, c);
       
   806 
       
   807 	tb->width  -= width;
       
   808 	if (backspace) tb->caretxoffs -= width;
       
   809 
       
   810 	/* Move the remaining characters over the marker */
       
   811 	memmove(tb->buf + tb->caretpos, tb->buf + tb->caretpos + len, tb->length - tb->caretpos - len + 1);
       
   812 	tb->length -= len;
       
   813 }
       
   814 
       
   815 /**
       
   816  * Delete a character from a textbuffer, either with 'Delete' or 'Backspace'
       
   817  * The character is delete from the position the caret is at
       
   818  * @param tb @Textbuf type to be changed
       
   819  * @param delmode Type of deletion, either @WKC_BACKSPACE or @WKC_DELETE
       
   820  * @return Return true on successfull change of Textbuf, or false otherwise
       
   821  */
       
   822 bool DeleteTextBufferChar(Textbuf *tb, int delmode)
       
   823 {
       
   824 	if (delmode == WKC_BACKSPACE && tb->caretpos != 0) {
       
   825 		DelChar(tb, true);
       
   826 		return true;
       
   827 	} else if (delmode == WKC_DELETE && tb->caretpos < tb->length) {
       
   828 		DelChar(tb, false);
       
   829 		return true;
       
   830 	}
       
   831 
       
   832 	return false;
       
   833 }
       
   834 
       
   835 /**
       
   836  * Delete every character in the textbuffer
       
   837  * @param tb @Textbuf buffer to be emptied
       
   838  */
       
   839 void DeleteTextBufferAll(Textbuf *tb)
       
   840 {
       
   841 	memset(tb->buf, 0, tb->maxlength);
       
   842 	tb->length = tb->width = 0;
       
   843 	tb->caretpos = tb->caretxoffs = 0;
       
   844 }
       
   845 
       
   846 /**
       
   847  * Insert a character to a textbuffer. If maxwidth of the Textbuf is zero,
       
   848  * we don't care about the visual-length but only about the physical
       
   849  * length of the string
       
   850  * @param tb @Textbuf type to be changed
       
   851  * @param key Character to be inserted
       
   852  * @return Return true on successfull change of Textbuf, or false otherwise
       
   853  */
       
   854 bool InsertTextBufferChar(Textbuf *tb, WChar key)
       
   855 {
       
   856 	const byte charwidth = GetCharacterWidth(FS_NORMAL, key);
       
   857 	size_t len = Utf8CharLen(key);
       
   858 	if (tb->length < (tb->maxlength - len) && (tb->maxwidth == 0 || tb->width + charwidth <= tb->maxwidth)) {
       
   859 		memmove(tb->buf + tb->caretpos + len, tb->buf + tb->caretpos, tb->length - tb->caretpos + 1);
       
   860 		Utf8Encode(tb->buf + tb->caretpos, key);
       
   861 		tb->length += len;
       
   862 		tb->width  += charwidth;
       
   863 
       
   864 		tb->caretpos   += len;
       
   865 		tb->caretxoffs += charwidth;
       
   866 		return true;
       
   867 	}
       
   868 	return false;
       
   869 }
       
   870 
       
   871 /**
       
   872  * Handle text navigation with arrow keys left/right.
       
   873  * This defines where the caret will blink and the next characer interaction will occur
       
   874  * @param tb @Textbuf type where navigation occurs
       
   875  * @param navmode Direction in which navigation occurs @WKC_LEFT, @WKC_RIGHT, @WKC_END, @WKC_HOME
       
   876  * @return Return true on successfull change of Textbuf, or false otherwise
       
   877  */
       
   878 bool MoveTextBufferPos(Textbuf *tb, int navmode)
       
   879 {
       
   880 	switch (navmode) {
       
   881 	case WKC_LEFT:
       
   882 		if (tb->caretpos != 0) {
       
   883 			WChar c;
       
   884 
       
   885 			do {
       
   886 				tb->caretpos--;
       
   887 			} while (IsUtf8Part(*(tb->buf + tb->caretpos)));
       
   888 
       
   889 			Utf8Decode(&c, tb->buf + tb->caretpos);
       
   890 			tb->caretxoffs -= GetCharacterWidth(FS_NORMAL, c);
       
   891 
       
   892 			return true;
       
   893 		}
       
   894 		break;
       
   895 	case WKC_RIGHT:
       
   896 		if (tb->caretpos < tb->length) {
       
   897 			WChar c;
       
   898 
       
   899 			tb->caretpos   += Utf8Decode(&c, tb->buf + tb->caretpos);
       
   900 			tb->caretxoffs += GetCharacterWidth(FS_NORMAL, c);
       
   901 
       
   902 			return true;
       
   903 		}
       
   904 		break;
       
   905 	case WKC_HOME:
       
   906 		tb->caretpos = 0;
       
   907 		tb->caretxoffs = 0;
       
   908 		return true;
       
   909 	case WKC_END:
       
   910 		tb->caretpos = tb->length;
       
   911 		tb->caretxoffs = tb->width;
       
   912 		return true;
       
   913 	}
       
   914 
       
   915 	return false;
       
   916 }
       
   917 
       
   918 /**
       
   919  * Initialize the textbuffer by supplying it the buffer to write into
       
   920  * and the maximum length of this buffer
       
   921  * @param tb @Textbuf type which is getting initialized
       
   922  * @param buf the buffer that will be holding the data for input
       
   923  * @param maxlength maximum length in characters of this buffer
       
   924  * @param maxwidth maximum length in pixels of this buffer. If reached, buffer
       
   925  * cannot grow, even if maxlength would allow because there is space. A length
       
   926  * of zero '0' means the buffer is only restricted by maxlength */
       
   927 void InitializeTextBuffer(Textbuf *tb, const char *buf, uint16 maxlength, uint16 maxwidth)
       
   928 {
       
   929 	tb->buf = (char*)buf;
       
   930 	tb->maxlength = maxlength;
       
   931 	tb->maxwidth  = maxwidth;
       
   932 	tb->caret = true;
       
   933 	UpdateTextBufferSize(tb);
       
   934 }
       
   935 
       
   936 /**
       
   937  * Update @Textbuf type with its actual physical character and screenlength
       
   938  * Get the count of characters in the string as well as the width in pixels.
       
   939  * Useful when copying in a larger amount of text at once
       
   940  * @param tb @Textbuf type which length is calculated
       
   941  */
       
   942 void UpdateTextBufferSize(Textbuf *tb)
       
   943 {
       
   944 	const char *buf = tb->buf;
       
   945 	WChar c = Utf8Consume(&buf);
       
   946 
       
   947 	tb->width = 0;
       
   948 	tb->length = 0;
       
   949 
       
   950 	for (; c != '\0' && tb->length < (tb->maxlength - 1); c = Utf8Consume(&buf)) {
       
   951 		tb->width += GetCharacterWidth(FS_NORMAL, c);
       
   952 		tb->length += Utf8CharLen(c);
       
   953 	}
       
   954 
       
   955 	tb->caretpos = tb->length;
       
   956 	tb->caretxoffs = tb->width;
       
   957 }
       
   958 
       
   959 int HandleEditBoxKey(Window *w, querystr_d *string, int wid, WindowEvent *e)
       
   960 {
       
   961 	e->we.keypress.cont = false;
       
   962 
       
   963 	switch (e->we.keypress.keycode) {
       
   964 	case WKC_ESC: return 2;
       
   965 	case WKC_RETURN: case WKC_NUM_ENTER: return 1;
       
   966 	case (WKC_CTRL | 'V'):
       
   967 		if (InsertTextBufferClipboard(&string->text))
       
   968 			InvalidateWidget(w, wid);
       
   969 		break;
       
   970 	case (WKC_CTRL | 'U'):
       
   971 		DeleteTextBufferAll(&string->text);
       
   972 		InvalidateWidget(w, wid);
       
   973 		break;
       
   974 	case WKC_BACKSPACE: case WKC_DELETE:
       
   975 		if (DeleteTextBufferChar(&string->text, e->we.keypress.keycode))
       
   976 			InvalidateWidget(w, wid);
       
   977 		break;
       
   978 	case WKC_LEFT: case WKC_RIGHT: case WKC_END: case WKC_HOME:
       
   979 		if (MoveTextBufferPos(&string->text, e->we.keypress.keycode))
       
   980 			InvalidateWidget(w, wid);
       
   981 		break;
       
   982 	default:
       
   983 		if (IsValidChar(e->we.keypress.key, string->afilter)) {
       
   984 			if (InsertTextBufferChar(&string->text, e->we.keypress.key)) {
       
   985 				InvalidateWidget(w, wid);
       
   986 			}
       
   987 		} else { // key wasn't caught. Continue only if standard entry specified
       
   988 			e->we.keypress.cont = (string->afilter == CS_ALPHANUMERAL);
       
   989 		}
       
   990 	}
       
   991 
       
   992 	return 0;
       
   993 }
       
   994 
       
   995 bool HandleCaret(Textbuf *tb)
       
   996 {
       
   997 	/* caret changed? */
       
   998 	bool b = !!(_caret_timer & 0x20);
       
   999 
       
  1000 	if (b != tb->caret) {
       
  1001 		tb->caret = b;
       
  1002 		return true;
       
  1003 	}
       
  1004 	return false;
       
  1005 }
       
  1006 
       
  1007 void HandleEditBox(Window *w, querystr_d *string, int wid)
       
  1008 {
       
  1009 	if (HandleCaret(&string->text)) InvalidateWidget(w, wid);
       
  1010 }
       
  1011 
       
  1012 void DrawEditBox(Window *w, querystr_d *string, int wid)
       
  1013 {
       
  1014 	DrawPixelInfo dpi, *old_dpi;
       
  1015 	int delta;
       
  1016 	const Widget *wi = &w->widget[wid];
       
  1017 	const Textbuf *tb = &string->text;
       
  1018 
       
  1019 	/* Limit the drawing of the string inside the widget boundaries */
       
  1020 	if (!FillDrawPixelInfo(&dpi,
       
  1021 	      wi->left + 4,
       
  1022 	      wi->top + 1,
       
  1023 	      wi->right - wi->left - 4,
       
  1024 	      wi->bottom - wi->top - 1)
       
  1025 	) return;
       
  1026 
       
  1027 	GfxFillRect(wi->left + 1, wi->top + 1, wi->right - 1, wi->bottom - 1, 215);
       
  1028 
       
  1029 	old_dpi = _cur_dpi;
       
  1030 	_cur_dpi = &dpi;
       
  1031 
       
  1032 	/* We will take the current widget length as maximum width, with a small
       
  1033 	 * space reserved at the end for the caret to show */
       
  1034 	delta = (wi->right - wi->left) - tb->width - 10;
       
  1035 	if (delta > 0) delta = 0;
       
  1036 
       
  1037 	if (tb->caretxoffs + delta < 0) delta = -tb->caretxoffs;
       
  1038 
       
  1039 	DoDrawString(tb->buf, delta, 0, 8);
       
  1040 	if (tb->caret) DoDrawString("_", tb->caretxoffs + delta, 0, 12);
       
  1041 
       
  1042 	_cur_dpi = old_dpi;
       
  1043 }
       
  1044 
       
  1045 enum QueryStringWidgets {
       
  1046 	QUERY_STR_WIDGET_TEXT = 3,
       
  1047 	QUERY_STR_WIDGET_CANCEL,
       
  1048 	QUERY_STR_WIDGET_OK
       
  1049 };
       
  1050 
       
  1051 
       
  1052 static void QueryStringWndProc(Window *w, WindowEvent *e)
       
  1053 {
       
  1054 	querystr_d *qs = &WP(w, querystr_d);
       
  1055 
       
  1056 	switch (e->event) {
       
  1057 		case WE_CREATE:
       
  1058 			SETBIT(_no_scroll, SCROLL_EDIT);
       
  1059 			break;
       
  1060 
       
  1061 		case WE_PAINT:
       
  1062 			SetDParam(0, qs->caption);
       
  1063 			DrawWindowWidgets(w);
       
  1064 
       
  1065 			DrawEditBox(w, qs, QUERY_STR_WIDGET_TEXT);
       
  1066 			break;
       
  1067 
       
  1068 		case WE_CLICK:
       
  1069 			switch (e->we.click.widget) {
       
  1070 				case QUERY_STR_WIDGET_OK:
       
  1071 		press_ok:;
       
  1072 					if (qs->orig == NULL || strcmp(qs->text.buf, qs->orig) != 0) {
       
  1073 						Window *parent = w->parent;
       
  1074 						qs->handled = true;
       
  1075 
       
  1076 						/* If the parent is NULL, the editbox is handled by general function
       
  1077 						 * HandleOnEditText */
       
  1078 						if (parent != NULL) {
       
  1079 							WindowEvent e;
       
  1080 							e.event = WE_ON_EDIT_TEXT;
       
  1081 							e.we.edittext.str = qs->text.buf;
       
  1082 							parent->wndproc(parent, &e);
       
  1083 						} else {
       
  1084 							HandleOnEditText(qs->text.buf);
       
  1085 						}
       
  1086 					}
       
  1087 					/* Fallthrough */
       
  1088 				case QUERY_STR_WIDGET_CANCEL:
       
  1089 					DeleteWindow(w);
       
  1090 					break;
       
  1091 			}
       
  1092 			break;
       
  1093 
       
  1094 		case WE_MOUSELOOP:
       
  1095 			HandleEditBox(w, qs, QUERY_STR_WIDGET_TEXT);
       
  1096 			break;
       
  1097 
       
  1098 		case WE_KEYPRESS:
       
  1099 			switch (HandleEditBoxKey(w, qs, QUERY_STR_WIDGET_TEXT, e)) {
       
  1100 				case 1: goto press_ok; /* Enter pressed, confirms change */
       
  1101 				case 2: DeleteWindow(w); break; /* ESC pressed, closes window, abandons changes */
       
  1102 			}
       
  1103 			break;
       
  1104 
       
  1105 		case WE_DESTROY: /* Call cancellation of query, if we have not handled it before */
       
  1106 			if (!qs->handled && w->parent != NULL) {
       
  1107 				WindowEvent e;
       
  1108 				Window *parent = w->parent;
       
  1109 
       
  1110 				qs->handled = true;
       
  1111 				e.event = WE_ON_EDIT_TEXT_CANCEL;
       
  1112 				parent->wndproc(parent, &e);
       
  1113 			}
       
  1114 			CLRBIT(_no_scroll, SCROLL_EDIT);
       
  1115 			break;
       
  1116 		}
       
  1117 }
       
  1118 
       
  1119 static const Widget _query_string_widgets[] = {
       
  1120 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,        STR_018B_CLOSE_WINDOW},
       
  1121 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   259,     0,    13, STR_012D,        STR_NULL},
       
  1122 {      WWT_PANEL,   RESIZE_NONE,    14,     0,   259,    14,    29, 0x0,             STR_NULL},
       
  1123 {      WWT_PANEL,   RESIZE_NONE,    14,     2,   257,    16,    27, 0x0,             STR_NULL},
       
  1124 {    WWT_TEXTBTN,   RESIZE_NONE,    14,     0,   129,    30,    41, STR_012E_CANCEL, STR_NULL},
       
  1125 {    WWT_TEXTBTN,   RESIZE_NONE,    14,   130,   259,    30,    41, STR_012F_OK,     STR_NULL},
       
  1126 {   WIDGETS_END},
       
  1127 };
       
  1128 
       
  1129 static const WindowDesc _query_string_desc = {
       
  1130 	190, 219, 260, 42,
       
  1131 	WC_QUERY_STRING,0,
       
  1132 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
       
  1133 	_query_string_widgets,
       
  1134 	QueryStringWndProc
       
  1135 };
       
  1136 
       
  1137 static char _edit_str_buf[64];
       
  1138 
       
  1139 /** Show a query popup window with a textbox in it.
       
  1140  * @param str StringID for the text shown in the textbox
       
  1141  * @param caption StringID of text shown in caption of querywindow
       
  1142  * @param maxlen maximum length in characters allowed. If bit 12 is set we
       
  1143  * will not check the resulting string against to original string to return success
       
  1144  * @param maxwidth maximum width in pixels allowed
       
  1145  * @param parent pointer to a Window that will handle the events (ok/cancel) of this
       
  1146  * window. If NULL, results are handled by global function HandleOnEditText
       
  1147  * @param afilter filters out unwanted character input */
       
  1148 void ShowQueryString(StringID str, StringID caption, uint maxlen, uint maxwidth, Window *parent, CharSetFilter afilter)
       
  1149 {
       
  1150 	static char orig_str_buf[lengthof(_edit_str_buf)];
       
  1151 	Window *w;
       
  1152 	uint realmaxlen = maxlen & ~0x1000;
       
  1153 
       
  1154 	assert(realmaxlen < lengthof(_edit_str_buf));
       
  1155 
       
  1156 	DeleteWindowById(WC_QUERY_STRING, 0);
       
  1157 	DeleteWindowById(WC_SAVELOAD, 0);
       
  1158 
       
  1159 	w = AllocateWindowDesc(&_query_string_desc);
       
  1160 	w->parent = parent;
       
  1161 
       
  1162 	GetString(_edit_str_buf, str, lastof(_edit_str_buf));
       
  1163 	_edit_str_buf[realmaxlen - 1] = '\0';
       
  1164 
       
  1165 	if (maxlen & 0x1000) {
       
  1166 		WP(w, querystr_d).orig = NULL;
       
  1167 	} else {
       
  1168 		strecpy(orig_str_buf, _edit_str_buf, lastof(orig_str_buf));
       
  1169 		WP(w, querystr_d).orig = orig_str_buf;
       
  1170 	}
       
  1171 
       
  1172 	LowerWindowWidget(w, QUERY_STR_WIDGET_TEXT);
       
  1173 	WP(w, querystr_d).caption = caption;
       
  1174 	WP(w, querystr_d).afilter = afilter;
       
  1175 	InitializeTextBuffer(&WP(w, querystr_d).text, _edit_str_buf, realmaxlen, maxwidth);
       
  1176 }
       
  1177 
       
  1178 
       
  1179 enum QueryWidgets {
       
  1180 	QUERY_WIDGET_CAPTION = 1,
       
  1181 	QUERY_WIDGET_NO = 3,
       
  1182 	QUERY_WIDGET_YES
       
  1183 };
       
  1184 
       
  1185 
       
  1186 typedef struct query_d {
       
  1187 	void (*proc)(Window*, bool); ///< callback function executed on closing of popup. Window* points to parent, bool is true if 'yes' clicked, false otherwise
       
  1188 	StringID message;            ///< message shown for query window
       
  1189 	uint32 params[20];           ///< local copy of _decode_parameters
       
  1190 	bool calledback;             ///< has callback been executed already (internal usage for WE_DESTROY event)
       
  1191 } query_d;
       
  1192 assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(query_d));
       
  1193 
       
  1194 
       
  1195 static void QueryWndProc(Window *w, WindowEvent *e)
       
  1196 {
       
  1197 	query_d *q = &WP(w, query_d);
       
  1198 
       
  1199 	switch (e->event) {
       
  1200 		case WE_PAINT:
       
  1201 			COPY_IN_DPARAM(0, q->params, lengthof(q->params));
       
  1202 			DrawWindowWidgets(w);
       
  1203 			COPY_IN_DPARAM(0, q->params, lengthof(q->params));
       
  1204 
       
  1205 			DrawStringMultiCenter(w->width / 2, (w->height / 2) - 10, q->message, w->width);
       
  1206 			break;
       
  1207 
       
  1208 		case WE_CLICK:
       
  1209 			switch (e->we.click.widget) {
       
  1210 				case QUERY_WIDGET_YES:
       
  1211 					q->calledback = true;
       
  1212 					if (q->proc != NULL) q->proc(w->parent, true);
       
  1213 					/* Fallthrough */
       
  1214 				case QUERY_WIDGET_NO:
       
  1215 					DeleteWindow(w);
       
  1216 					break;
       
  1217 				}
       
  1218 			break;
       
  1219 
       
  1220 		case WE_KEYPRESS: /* ESC closes the window, Enter confirms the action */
       
  1221 			switch (e->we.keypress.keycode) {
       
  1222 				case WKC_RETURN:
       
  1223 				case WKC_NUM_ENTER:
       
  1224 					q->calledback = true;
       
  1225 					if (q->proc != NULL) q->proc(w->parent, true);
       
  1226 					/* Fallthrough */
       
  1227 				case WKC_ESC:
       
  1228 					e->we.keypress.cont = false;
       
  1229 					DeleteWindow(w);
       
  1230 					break;
       
  1231 			}
       
  1232 			break;
       
  1233 
       
  1234 		case WE_DESTROY: /* Call callback function (if any) on window close if not yet called */
       
  1235 			if (!q->calledback && q->proc != NULL) {
       
  1236 				q->calledback = true;
       
  1237 				q->proc(w->parent, false);
       
  1238 			}
       
  1239 			break;
       
  1240 	}
       
  1241 }
       
  1242 
       
  1243 
       
  1244 static const Widget _query_widgets[] = {
       
  1245 {  WWT_CLOSEBOX, RESIZE_NONE,  4,   0,  10,   0,  13, STR_00C5,        STR_018B_CLOSE_WINDOW},
       
  1246 {   WWT_CAPTION, RESIZE_NONE,  4,  11, 209,   0,  13, STR_NULL,        STR_NULL},
       
  1247 {     WWT_PANEL, RESIZE_NONE,  4,   0, 209,  14,  81, 0x0, /*OVERRIDE*/STR_NULL},
       
  1248 {WWT_PUSHTXTBTN, RESIZE_NONE,  3,  20,  90,  62,  73, STR_00C9_NO,     STR_NULL},
       
  1249 {WWT_PUSHTXTBTN, RESIZE_NONE,  3, 120, 190,  62,  73, STR_00C8_YES,    STR_NULL},
       
  1250 {   WIDGETS_END },
       
  1251 };
       
  1252 
       
  1253 static const WindowDesc _query_desc = {
       
  1254 	WDP_CENTER, WDP_CENTER, 210, 82,
       
  1255 	WC_CONFIRM_POPUP_QUERY, 0,
       
  1256 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_UNCLICK_BUTTONS | WDF_DEF_WIDGET | WDF_MODAL,
       
  1257 	_query_widgets,
       
  1258 	QueryWndProc
       
  1259 };
       
  1260 
       
  1261 /** Show a modal confirmation window with standard 'yes' and 'no' buttons
       
  1262  * The window is aligned to the centre of its parent.
       
  1263  * NOTE: You cannot use BindCString as parameter for this window!
       
  1264  * @param caption string shown as window caption
       
  1265  * @param message string that will be shown for the window
       
  1266  * @param parent pointer to parent window, if this pointer is NULL the parent becomes
       
  1267  * the main window WC_MAIN_WINDOW
       
  1268  * @param x,y coordinates to show the window at
       
  1269  * @param yes_no_callback callback function called when window is closed through any button */
       
  1270 void ShowQuery(StringID caption, StringID message, Window *parent, void (*callback)(Window*, bool))
       
  1271 {
       
  1272 	Window *w = AllocateWindowDesc(&_query_desc);
       
  1273 	if (w == NULL) return;
       
  1274 
       
  1275 	if (parent == NULL) parent = FindWindowById(WC_MAIN_WINDOW, 0);
       
  1276 	w->parent = parent;
       
  1277 	w->left = parent->left + (parent->width / 2) - (w->width / 2);
       
  1278 	w->top = parent->top + (parent->height / 2) - (w->height / 2);
       
  1279 
       
  1280 	/* Create a backup of the variadic arguments to strings because it will be
       
  1281 	 * overridden pretty often. We will copy these back for drawing */
       
  1282 	COPY_OUT_DPARAM(WP(w, query_d).params, 0, lengthof(WP(w, query_d).params));
       
  1283 	w->widget[QUERY_WIDGET_CAPTION].data = caption;
       
  1284 	WP(w, query_d).message    = message;
       
  1285 	WP(w, query_d).proc       = callback;
       
  1286 	WP(w, query_d).calledback = false;
       
  1287 }
       
  1288 
       
  1289 
       
  1290 static const Widget _load_dialog_widgets[] = {
       
  1291 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,         STR_018B_CLOSE_WINDOW},
       
  1292 {    WWT_CAPTION,  RESIZE_RIGHT,    14,    11,   256,     0,    13, STR_NULL,         STR_018C_WINDOW_TITLE_DRAG_THIS},
       
  1293 { WWT_PUSHTXTBTN,   RESIZE_NONE,    14,     0,   127,    14,    25, STR_SORT_BY_NAME, STR_SORT_ORDER_TIP},
       
  1294 { WWT_PUSHTXTBTN,   RESIZE_NONE,    14,   128,   256,    14,    25, STR_SORT_BY_DATE, STR_SORT_ORDER_TIP},
       
  1295 {      WWT_PANEL,  RESIZE_RIGHT,    14,     0,   256,    26,    47, 0x0,              STR_NULL},
       
  1296 {      WWT_PANEL,     RESIZE_RB,    14,     0,   256,    48,   293, 0x0,              STR_NULL},
       
  1297 { WWT_PUSHIMGBTN,     RESIZE_LR,    14,   245,   256,    48,    59, SPR_HOUSE_ICON,   STR_SAVELOAD_HOME_BUTTON},
       
  1298 {      WWT_INSET,     RESIZE_RB,    14,     2,   243,    50,   291, 0x0,              STR_400A_LIST_OF_DRIVES_DIRECTORIES},
       
  1299 {  WWT_SCROLLBAR,    RESIZE_LRB,    14,   245,   256,    60,   281, 0x0,              STR_0190_SCROLL_BAR_SCROLLS_LIST},
       
  1300 {  WWT_RESIZEBOX,   RESIZE_LRTB,    14,   245,   256,   282,   293, 0x0,              STR_RESIZE_BUTTON},
       
  1301 {   WIDGETS_END},
       
  1302 };
       
  1303 
       
  1304 static const Widget _save_dialog_widgets[] = {
       
  1305 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,         STR_018B_CLOSE_WINDOW},
       
  1306 {    WWT_CAPTION,  RESIZE_RIGHT,    14,    11,   256,     0,    13, STR_NULL,         STR_018C_WINDOW_TITLE_DRAG_THIS},
       
  1307 { WWT_PUSHTXTBTN,   RESIZE_NONE,    14,     0,   127,    14,    25, STR_SORT_BY_NAME, STR_SORT_ORDER_TIP},
       
  1308 { WWT_PUSHTXTBTN,   RESIZE_NONE,    14,   128,   256,    14,    25, STR_SORT_BY_DATE, STR_SORT_ORDER_TIP},
       
  1309 {      WWT_PANEL,  RESIZE_RIGHT,    14,     0,   256,    26,    47, 0x0,              STR_NULL},
       
  1310 {      WWT_PANEL,     RESIZE_RB,    14,     0,   256,    48,   291, 0x0,              STR_NULL},
       
  1311 { WWT_PUSHIMGBTN,     RESIZE_LR,    14,   245,   256,    48,    59, SPR_HOUSE_ICON,   STR_SAVELOAD_HOME_BUTTON},
       
  1312 {      WWT_INSET,     RESIZE_RB,    14,     2,   243,    50,   290, 0x0,              STR_400A_LIST_OF_DRIVES_DIRECTORIES},
       
  1313 {  WWT_SCROLLBAR,    RESIZE_LRB,    14,   245,   256,    60,   291, 0x0,              STR_0190_SCROLL_BAR_SCROLLS_LIST},
       
  1314 {      WWT_PANEL,    RESIZE_RTB,    14,     0,   256,   292,   307, 0x0,              STR_NULL},
       
  1315 {      WWT_PANEL,    RESIZE_RTB,    14,     2,   254,   294,   305, 0x0,              STR_400B_CURRENTLY_SELECTED_NAME},
       
  1316 { WWT_PUSHTXTBTN,     RESIZE_TB,    14,     0,   127,   308,   319, STR_4003_DELETE,  STR_400C_DELETE_THE_CURRENTLY_SELECTED},
       
  1317 { WWT_PUSHTXTBTN,     RESIZE_TB,    14,   128,   244,   308,   319, STR_4002_SAVE,    STR_400D_SAVE_THE_CURRENT_GAME_USING},
       
  1318 {  WWT_RESIZEBOX,   RESIZE_LRTB,    14,   245,   256,   308,   319, 0x0,              STR_RESIZE_BUTTON},
       
  1319 {   WIDGETS_END},
       
  1320 };
       
  1321 
       
  1322 // Colors for fios types
       
  1323 const byte _fios_colors[] = {13, 9, 9, 6, 5, 6, 5, 6, 6, 8};
       
  1324 
       
  1325 void BuildFileList(void)
       
  1326 {
       
  1327 	_fios_path_changed = true;
       
  1328 	FiosFreeSavegameList();
       
  1329 
       
  1330 	switch (_saveload_mode) {
       
  1331 		case SLD_NEW_GAME:
       
  1332 		case SLD_LOAD_SCENARIO:
       
  1333 		case SLD_SAVE_SCENARIO:
       
  1334 			_fios_list = FiosGetScenarioList(_saveload_mode); break;
       
  1335 		case SLD_LOAD_HEIGHTMAP:
       
  1336 			_fios_list = FiosGetHeightmapList(_saveload_mode); break;
       
  1337 
       
  1338 		default: _fios_list = FiosGetSavegameList(_saveload_mode); break;
       
  1339 	}
       
  1340 }
       
  1341 
       
  1342 static void DrawFiosTexts(uint maxw)
       
  1343 {
       
  1344 	static const char *path = NULL;
       
  1345 	static StringID str = STR_4006_UNABLE_TO_READ_DRIVE;
       
  1346 	static uint32 tot = 0;
       
  1347 
       
  1348 	if (_fios_path_changed) {
       
  1349 		str = FiosGetDescText(&path, &tot);
       
  1350 		_fios_path_changed = false;
       
  1351 	}
       
  1352 
       
  1353 	if (str != STR_4006_UNABLE_TO_READ_DRIVE) SetDParam(0, tot);
       
  1354 	DrawString(2, 37, str, 0);
       
  1355 	DoDrawStringTruncated(path, 2, 27, 16, maxw);
       
  1356 }
       
  1357 
       
  1358 static void MakeSortedSaveGameList(void)
       
  1359 {
       
  1360 	uint sort_start = 0;
       
  1361 	uint sort_end = 0;
       
  1362 	uint s_amount;
       
  1363 	int i;
       
  1364 
       
  1365 	/* Directories are always above the files (FIOS_TYPE_DIR)
       
  1366 	 * Drives (A:\ (windows only) are always under the files (FIOS_TYPE_DRIVE)
       
  1367 	 * Only sort savegames/scenarios, not directories
       
  1368 	 */
       
  1369 	for (i = 0; i < _fios_num; i++) {
       
  1370 		switch (_fios_list[i].type) {
       
  1371 			case FIOS_TYPE_DIR:    sort_start++; break;
       
  1372 			case FIOS_TYPE_PARENT: sort_start++; break;
       
  1373 			case FIOS_TYPE_DRIVE:  sort_end++;   break;
       
  1374 		}
       
  1375 	}
       
  1376 
       
  1377 	s_amount = _fios_num - sort_start - sort_end;
       
  1378 	if (s_amount > 0)
       
  1379 		qsort(_fios_list + sort_start, s_amount, sizeof(FiosItem), compare_FiosItems);
       
  1380 }
       
  1381 
       
  1382 static void GenerateFileName(void)
       
  1383 {
       
  1384 	/* Check if we are not a specatator who wants to generate a name..
       
  1385 	    Let's use the name of player #0 for now. */
       
  1386 	const Player *p = GetPlayer(IsValidPlayer(_local_player) ? _local_player : 0);
       
  1387 
       
  1388 	SetDParam(0, p->name_1);
       
  1389 	SetDParam(1, p->name_2);
       
  1390 	SetDParam(2, _date);
       
  1391 	GetString(_edit_str_buf, STR_4004, lastof(_edit_str_buf));
       
  1392 }
       
  1393 
       
  1394 extern void StartupEngines(void);
       
  1395 
       
  1396 static void SaveLoadDlgWndProc(Window *w, WindowEvent *e)
       
  1397 {
       
  1398 	static FiosItem o_dir;
       
  1399 
       
  1400 	switch (e->event) {
       
  1401 	case WE_CREATE: { /* Set up OPENTTD button */
       
  1402 		o_dir.type = FIOS_TYPE_DIRECT;
       
  1403 		switch (_saveload_mode) {
       
  1404 			case SLD_SAVE_GAME:
       
  1405 			case SLD_LOAD_GAME:
       
  1406 				ttd_strlcpy(&o_dir.name[0], _paths.save_dir, sizeof(o_dir.name));
       
  1407 				break;
       
  1408 
       
  1409 			case SLD_SAVE_SCENARIO:
       
  1410 			case SLD_LOAD_SCENARIO:
       
  1411 				ttd_strlcpy(&o_dir.name[0], _paths.scenario_dir, sizeof(o_dir.name));
       
  1412 				break;
       
  1413 
       
  1414 			case SLD_LOAD_HEIGHTMAP:
       
  1415 				ttd_strlcpy(&o_dir.name[0], _paths.heightmap_dir, sizeof(o_dir.name));
       
  1416 				break;
       
  1417 
       
  1418 			default:
       
  1419 				ttd_strlcpy(&o_dir.name[0], _paths.personal_dir, sizeof(o_dir.name));
       
  1420 		}
       
  1421 		break;
       
  1422 		}
       
  1423 
       
  1424 	case WE_PAINT: {
       
  1425 		int pos;
       
  1426 		int y;
       
  1427 
       
  1428 		SetVScrollCount(w, _fios_num);
       
  1429 		DrawWindowWidgets(w);
       
  1430 		DrawFiosTexts(w->width);
       
  1431 
       
  1432 		if (_savegame_sort_dirty) {
       
  1433 			_savegame_sort_dirty = false;
       
  1434 			MakeSortedSaveGameList();
       
  1435 		}
       
  1436 
       
  1437 		GfxFillRect(w->widget[7].left + 1, w->widget[7].top + 1, w->widget[7].right, w->widget[7].bottom, 0xD7);
       
  1438 		DoDrawString(
       
  1439 			_savegame_sort_order & SORT_DESCENDING ? DOWNARROW : UPARROW,
       
  1440 			_savegame_sort_order & SORT_BY_NAME ? w->widget[2].right - 9 : w->widget[3].right - 9,
       
  1441 			15, 16
       
  1442 		);
       
  1443 
       
  1444 		y = w->widget[7].top + 1;
       
  1445 		for (pos = w->vscroll.pos; pos < _fios_num; pos++) {
       
  1446 			const FiosItem *item = _fios_list + pos;
       
  1447 
       
  1448 			DoDrawStringTruncated(item->title, 4, y, _fios_colors[item->type], w->width - 18);
       
  1449 			y += 10;
       
  1450 			if (y >= w->vscroll.cap * 10 + w->widget[7].top + 1) break;
       
  1451 		}
       
  1452 
       
  1453 		if (_saveload_mode == SLD_SAVE_GAME || _saveload_mode == SLD_SAVE_SCENARIO) {
       
  1454 			DrawEditBox(w, &WP(w,querystr_d), 10);
       
  1455 		}
       
  1456 		break;
       
  1457 	}
       
  1458 
       
  1459 	case WE_CLICK:
       
  1460 		switch (e->we.click.widget) {
       
  1461 		case 2: /* Sort save names by name */
       
  1462 			_savegame_sort_order = (_savegame_sort_order == SORT_BY_NAME) ?
       
  1463 				SORT_BY_NAME | SORT_DESCENDING : SORT_BY_NAME;
       
  1464 			_savegame_sort_dirty = true;
       
  1465 			SetWindowDirty(w);
       
  1466 			break;
       
  1467 
       
  1468 		case 3: /* Sort save names by date */
       
  1469 			_savegame_sort_order = (_savegame_sort_order == SORT_BY_DATE) ?
       
  1470 				SORT_BY_DATE | SORT_DESCENDING : SORT_BY_DATE;
       
  1471 			_savegame_sort_dirty = true;
       
  1472 			SetWindowDirty(w);
       
  1473 			break;
       
  1474 
       
  1475 		case 6: /* OpenTTD 'button', jumps to OpenTTD directory */
       
  1476 			FiosBrowseTo(&o_dir);
       
  1477 			SetWindowDirty(w);
       
  1478 			BuildFileList();
       
  1479 			break;
       
  1480 
       
  1481 		case 7: { /* Click the listbox */
       
  1482 			int y = (e->we.click.pt.y - w->widget[e->we.click.widget].top - 1) / 10;
       
  1483 			char *name;
       
  1484 			const FiosItem *file;
       
  1485 
       
  1486 			if (y < 0 || (y += w->vscroll.pos) >= w->vscroll.count) return;
       
  1487 
       
  1488 			file = _fios_list + y;
       
  1489 
       
  1490 			name = FiosBrowseTo(file);
       
  1491 			if (name != NULL) {
       
  1492 				if (_saveload_mode == SLD_LOAD_GAME || _saveload_mode == SLD_LOAD_SCENARIO) {
       
  1493 					_switch_mode = (_game_mode == GM_EDITOR) ? SM_LOAD_SCENARIO : SM_LOAD;
       
  1494 
       
  1495 					SetFiosType(file->type);
       
  1496 					ttd_strlcpy(_file_to_saveload.name, name, sizeof(_file_to_saveload.name));
       
  1497 					ttd_strlcpy(_file_to_saveload.title, file->title, sizeof(_file_to_saveload.title));
       
  1498 
       
  1499 					DeleteWindow(w);
       
  1500 				} else if (_saveload_mode == SLD_LOAD_HEIGHTMAP) {
       
  1501 					SetFiosType(file->type);
       
  1502 					ttd_strlcpy(_file_to_saveload.name, name, sizeof(_file_to_saveload.name));
       
  1503 					ttd_strlcpy(_file_to_saveload.title, file->title, sizeof(_file_to_saveload.title));
       
  1504 
       
  1505 					DeleteWindow(w);
       
  1506 					ShowHeightmapLoad();
       
  1507 				} else {
       
  1508 					// SLD_SAVE_GAME, SLD_SAVE_SCENARIO copy clicked name to editbox
       
  1509 					ttd_strlcpy(WP(w, querystr_d).text.buf, file->title, WP(w, querystr_d).text.maxlength);
       
  1510 					UpdateTextBufferSize(&WP(w, querystr_d).text);
       
  1511 					InvalidateWidget(w, 10);
       
  1512 				}
       
  1513 			} else {
       
  1514 				// Changed directory, need repaint.
       
  1515 				SetWindowDirty(w);
       
  1516 				BuildFileList();
       
  1517 			}
       
  1518 			break;
       
  1519 		}
       
  1520 
       
  1521 		case 11: case 12: /* Delete, Save game */
       
  1522 			break;
       
  1523 		}
       
  1524 		break;
       
  1525 	case WE_MOUSELOOP:
       
  1526 		if (_saveload_mode == SLD_SAVE_GAME || _saveload_mode == SLD_SAVE_SCENARIO) {
       
  1527 			HandleEditBox(w, &WP(w, querystr_d), 10);
       
  1528 		}
       
  1529 		break;
       
  1530 	case WE_KEYPRESS:
       
  1531 		if (e->we.keypress.keycode == WKC_ESC) {
       
  1532 			DeleteWindow(w);
       
  1533 			return;
       
  1534 		}
       
  1535 
       
  1536 		if (_saveload_mode == SLD_SAVE_GAME || _saveload_mode == SLD_SAVE_SCENARIO) {
       
  1537 			if (HandleEditBoxKey(w, &WP(w, querystr_d), 10, e) == 1) /* Press Enter */
       
  1538 					HandleButtonClick(w, 12);
       
  1539 		}
       
  1540 		break;
       
  1541 	case WE_TIMEOUT:
       
  1542 		/* This test protects against using widgets 11 and 12 which are only available
       
  1543 		 * in those two saveload mode  */
       
  1544 		if (!(_saveload_mode == SLD_SAVE_GAME || _saveload_mode == SLD_SAVE_SCENARIO)) break;
       
  1545 
       
  1546 		if (IsWindowWidgetLowered(w, 11)) { /* Delete button clicked */
       
  1547 			if (!FiosDelete(WP(w,querystr_d).text.buf)) {
       
  1548 				ShowErrorMessage(INVALID_STRING_ID, STR_4008_UNABLE_TO_DELETE_FILE, 0, 0);
       
  1549 			} else {
       
  1550 				BuildFileList();
       
  1551 				/* Reset file name to current date on successfull delete */
       
  1552 				if (_saveload_mode == SLD_SAVE_GAME) GenerateFileName();
       
  1553 			}
       
  1554 
       
  1555 			UpdateTextBufferSize(&WP(w, querystr_d).text);
       
  1556 			SetWindowDirty(w);
       
  1557 		} else if (IsWindowWidgetLowered(w, 12)) { /* Save button clicked */
       
  1558 			_switch_mode = SM_SAVE;
       
  1559 			FiosMakeSavegameName(_file_to_saveload.name, WP(w,querystr_d).text.buf, sizeof(_file_to_saveload.name));
       
  1560 
       
  1561 			/* In the editor set up the vehicle engines correctly (date might have changed) */
       
  1562 			if (_game_mode == GM_EDITOR) StartupEngines();
       
  1563 		}
       
  1564 		break;
       
  1565 	case WE_DESTROY:
       
  1566 		// pause is only used in single-player, non-editor mode, non menu mode
       
  1567 		if (!_networking && _game_mode != GM_EDITOR && _game_mode != GM_MENU) {
       
  1568 			DoCommandP(0, 0, 0, NULL, CMD_PAUSE);
       
  1569 		}
       
  1570 		FiosFreeSavegameList();
       
  1571 		CLRBIT(_no_scroll, SCROLL_SAVE);
       
  1572 		break;
       
  1573 	case WE_RESIZE: {
       
  1574 		/* Widget 2 and 3 have to go with halve speed, make it so obiwan */
       
  1575 		uint diff = e->we.sizing.diff.x / 2;
       
  1576 		w->widget[2].right += diff;
       
  1577 		w->widget[3].left  += diff;
       
  1578 		w->widget[3].right += e->we.sizing.diff.x;
       
  1579 
       
  1580 		/* Same for widget 11 and 12 in save-dialog */
       
  1581 		if (_saveload_mode == SLD_SAVE_GAME || _saveload_mode == SLD_SAVE_SCENARIO) {
       
  1582 			w->widget[11].right += diff;
       
  1583 			w->widget[12].left  += diff;
       
  1584 			w->widget[12].right += e->we.sizing.diff.x;
       
  1585 		}
       
  1586 
       
  1587 		w->vscroll.cap += e->we.sizing.diff.y / 10;
       
  1588 		} break;
       
  1589 	}
       
  1590 }
       
  1591 
       
  1592 static const WindowDesc _load_dialog_desc = {
       
  1593 	WDP_CENTER, WDP_CENTER, 257, 294,
       
  1594 	WC_SAVELOAD,0,
       
  1595 	WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_STD_BTN | WDF_UNCLICK_BUTTONS | WDF_RESIZABLE,
       
  1596 	_load_dialog_widgets,
       
  1597 	SaveLoadDlgWndProc,
       
  1598 };
       
  1599 
       
  1600 static const WindowDesc _save_dialog_desc = {
       
  1601 	WDP_CENTER, WDP_CENTER, 257, 320,
       
  1602 	WC_SAVELOAD,0,
       
  1603 	WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_STD_BTN | WDF_UNCLICK_BUTTONS | WDF_RESIZABLE,
       
  1604 	_save_dialog_widgets,
       
  1605 	SaveLoadDlgWndProc,
       
  1606 };
       
  1607 
       
  1608 void ShowSaveLoadDialog(int mode)
       
  1609 {
       
  1610 	static const StringID saveload_captions[] = {
       
  1611 		STR_4001_LOAD_GAME,
       
  1612 		STR_0298_LOAD_SCENARIO,
       
  1613 		STR_4000_SAVE_GAME,
       
  1614 		STR_0299_SAVE_SCENARIO,
       
  1615 		STR_4011_LOAD_HEIGHTMAP,
       
  1616 	};
       
  1617 
       
  1618 	Window *w;
       
  1619 	const WindowDesc *sld = &_save_dialog_desc;
       
  1620 
       
  1621 
       
  1622 	SetObjectToPlace(SPR_CURSOR_ZZZ, 0, 0, 0);
       
  1623 	DeleteWindowById(WC_QUERY_STRING, 0);
       
  1624 	DeleteWindowById(WC_SAVELOAD, 0);
       
  1625 
       
  1626 	_saveload_mode = mode;
       
  1627 	SETBIT(_no_scroll, SCROLL_SAVE);
       
  1628 
       
  1629 	switch (mode) {
       
  1630 		case SLD_SAVE_GAME:     GenerateFileName(); break;
       
  1631 		case SLD_SAVE_SCENARIO: strcpy(_edit_str_buf, "UNNAMED"); break;
       
  1632 		default:                sld = &_load_dialog_desc; break;
       
  1633 	}
       
  1634 
       
  1635 	assert((uint)mode < lengthof(saveload_captions));
       
  1636 	w = AllocateWindowDesc(sld);
       
  1637 	w->widget[1].data = saveload_captions[mode];
       
  1638 	w->vscroll.cap = 24;
       
  1639 	w->resize.step_width = 2;
       
  1640 	w->resize.step_height = 10;
       
  1641 	w->resize.height = w->height - 14 * 10; // Minimum of 10 items
       
  1642 	LowerWindowWidget(w, 7);
       
  1643 
       
  1644 	WP(w, querystr_d).afilter = CS_ALPHANUMERAL;
       
  1645 	InitializeTextBuffer(&WP(w, querystr_d).text, _edit_str_buf, lengthof(_edit_str_buf), 240);
       
  1646 
       
  1647 	// pause is only used in single-player, non-editor mode, non-menu mode. It
       
  1648 	// will be unpaused in the WE_DESTROY event handler.
       
  1649 	if (_game_mode != GM_MENU && !_networking && _game_mode != GM_EDITOR) {
       
  1650 		DoCommandP(0, 1, 0, NULL, CMD_PAUSE);
       
  1651 	}
       
  1652 
       
  1653 	BuildFileList();
       
  1654 
       
  1655 	ResetObjectToPlace();
       
  1656 }
       
  1657 
       
  1658 void RedrawAutosave(void)
       
  1659 {
       
  1660 	SetWindowDirty(FindWindowById(WC_STATUS_BAR, 0));
       
  1661 }
       
  1662 
       
  1663 void SetFiosType(const byte fiostype)
       
  1664 {
       
  1665 	switch (fiostype) {
       
  1666 		case FIOS_TYPE_FILE:
       
  1667 		case FIOS_TYPE_SCENARIO:
       
  1668 			_file_to_saveload.mode = SL_LOAD;
       
  1669 			break;
       
  1670 
       
  1671 		case FIOS_TYPE_OLDFILE:
       
  1672 		case FIOS_TYPE_OLD_SCENARIO:
       
  1673 			_file_to_saveload.mode = SL_OLD_LOAD;
       
  1674 			break;
       
  1675 
       
  1676 #ifdef WITH_PNG
       
  1677 		case FIOS_TYPE_PNG:
       
  1678 			_file_to_saveload.mode = SL_PNG;
       
  1679 			break;
       
  1680 #endif /* WITH_PNG */
       
  1681 
       
  1682 		case FIOS_TYPE_BMP:
       
  1683 			_file_to_saveload.mode = SL_BMP;
       
  1684 			break;
       
  1685 
       
  1686 		default:
       
  1687 			_file_to_saveload.mode = SL_INVALID;
       
  1688 			break;
       
  1689 	}
       
  1690 }
       
  1691 
       
  1692 static int32 ClickMoneyCheat(int32 p1, int32 p2)
       
  1693 {
       
  1694 		DoCommandP(0, -10000000, 0, NULL, CMD_MONEY_CHEAT);
       
  1695 		return true;
       
  1696 }
       
  1697 
       
  1698 // p1 player to set to, p2 is -1 or +1 (down/up)
       
  1699 static int32 ClickChangePlayerCheat(int32 p1, int32 p2)
       
  1700 {
       
  1701 	while (IsValidPlayer((PlayerID)p1)) {
       
  1702 		if (_players[p1].is_active) {
       
  1703 			SetLocalPlayer((PlayerID)p1);
       
  1704 
       
  1705 			MarkWholeScreenDirty();
       
  1706 			return _local_player;
       
  1707 		}
       
  1708 		p1 += p2;
       
  1709 	}
       
  1710 
       
  1711 	return _local_player;
       
  1712 }
       
  1713 
       
  1714 // p1 -1 or +1 (down/up)
       
  1715 static int32 ClickChangeClimateCheat(int32 p1, int32 p2)
       
  1716 {
       
  1717 	if (p1 == -1) p1 = 3;
       
  1718 	if (p1 ==  4) p1 = 0;
       
  1719 	_opt.landscape = p1;
       
  1720 	ReloadNewGRFData();
       
  1721 	return _opt.landscape;
       
  1722 }
       
  1723 
       
  1724 extern void EnginesMonthlyLoop(void);
       
  1725 
       
  1726 // p2 1 (increase) or -1 (decrease)
       
  1727 static int32 ClickChangeDateCheat(int32 p1, int32 p2)
       
  1728 {
       
  1729 	YearMonthDay ymd;
       
  1730 	ConvertDateToYMD(_date, &ymd);
       
  1731 
       
  1732 	if ((ymd.year == MIN_YEAR && p2 == -1) || (ymd.year == MAX_YEAR && p2 == 1)) return _cur_year;
       
  1733 
       
  1734 	SetDate(ConvertYMDToDate(_cur_year + p2, ymd.month, ymd.day));
       
  1735 	EnginesMonthlyLoop();
       
  1736 	SetWindowDirty(FindWindowById(WC_STATUS_BAR, 0));
       
  1737 	return _cur_year;
       
  1738 }
       
  1739 
       
  1740 typedef int32 CheckButtonClick(int32, int32);
       
  1741 
       
  1742 enum ce_flags {CE_CLICK = 1 << 0};
       
  1743 
       
  1744 typedef byte ce_flags;
       
  1745 
       
  1746 typedef struct CheatEntry {
       
  1747 	VarType type;          // type of selector
       
  1748 	ce_flags flags;        // selector flags
       
  1749 	StringID str;          // string with descriptive text
       
  1750 	void *variable;        // pointer to the variable
       
  1751 	bool *been_used;       // has this cheat been used before?
       
  1752 	CheckButtonClick *proc;// procedure
       
  1753 	int16 min, max;        // range for spinbox setting
       
  1754 } CheatEntry;
       
  1755 
       
  1756 static const CheatEntry _cheats_ui[] = {
       
  1757 	{SLE_BOOL,CE_CLICK, STR_CHEAT_MONEY,          &_cheats.money.value,           &_cheats.money.been_used,           &ClickMoneyCheat,         0,  0},
       
  1758 	{SLE_UINT8,      0, STR_CHEAT_CHANGE_PLAYER,  &_local_player,                 &_cheats.switch_player.been_used,   &ClickChangePlayerCheat,  0, 11},
       
  1759 	{SLE_BOOL,       0, STR_CHEAT_EXTRA_DYNAMITE, &_cheats.magic_bulldozer.value, &_cheats.magic_bulldozer.been_used, NULL,                     0,  0},
       
  1760 	{SLE_BOOL,       0, STR_CHEAT_CROSSINGTUNNELS,&_cheats.crossing_tunnels.value,&_cheats.crossing_tunnels.been_used,NULL,                     0,  0},
       
  1761 	{SLE_BOOL,       0, STR_CHEAT_BUILD_IN_PAUSE, &_cheats.build_in_pause.value,  &_cheats.build_in_pause.been_used,  NULL,                     0,  0},
       
  1762 	{SLE_BOOL,       0, STR_CHEAT_NO_JETCRASH,    &_cheats.no_jetcrash.value,     &_cheats.no_jetcrash.been_used,     NULL,                     0,  0},
       
  1763 	{SLE_BOOL,       0, STR_CHEAT_SETUP_PROD,     &_cheats.setup_prod.value,      &_cheats.setup_prod.been_used,      NULL,                     0,  0},
       
  1764 	{SLE_UINT8,      0, STR_CHEAT_SWITCH_CLIMATE, &_opt.landscape,                &_cheats.switch_climate.been_used,  &ClickChangeClimateCheat,-1,  4},
       
  1765 	{SLE_INT32,      0, STR_CHEAT_CHANGE_DATE,    &_cur_year,                     &_cheats.change_date.been_used,     &ClickChangeDateCheat,   -1,  1},
       
  1766 };
       
  1767 
       
  1768 
       
  1769 static const Widget _cheat_widgets[] = {
       
  1770 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,   STR_018B_CLOSE_WINDOW},
       
  1771 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   399,     0,    13, STR_CHEATS, STR_018C_WINDOW_TITLE_DRAG_THIS},
       
  1772 {      WWT_PANEL,   RESIZE_NONE,    14,     0,   399,    14,   169, 0x0,        STR_NULL},
       
  1773 {      WWT_PANEL,   RESIZE_NONE,    14,     0,   399,    14,   169, 0x0,        STR_CHEATS_TIP},
       
  1774 {   WIDGETS_END},
       
  1775 };
       
  1776 
       
  1777 extern void DrawPlayerIcon(PlayerID pid, int x, int y);
       
  1778 
       
  1779 static void CheatsWndProc(Window *w, WindowEvent *e)
       
  1780 {
       
  1781 	switch (e->event) {
       
  1782 	case WE_PAINT: {
       
  1783 		int clk = WP(w,def_d).data_1;
       
  1784 		int x, y;
       
  1785 		int i;
       
  1786 
       
  1787 		DrawWindowWidgets(w);
       
  1788 
       
  1789 		DrawStringMultiCenter(200, 25, STR_CHEATS_WARNING, 350);
       
  1790 
       
  1791 		x = 0;
       
  1792 		y = 45;
       
  1793 
       
  1794 		for (i = 0; i != lengthof(_cheats_ui); i++) {
       
  1795 			const CheatEntry *ce = &_cheats_ui[i];
       
  1796 
       
  1797 			DrawSprite((*ce->been_used) ? SPR_BOX_CHECKED : SPR_BOX_EMPTY, x + 5, y + 2);
       
  1798 
       
  1799 			switch (ce->type) {
       
  1800 			case SLE_BOOL: {
       
  1801 				bool on = (*(bool*)ce->variable);
       
  1802 
       
  1803 				if (ce->flags & CE_CLICK) {
       
  1804 					DrawFrameRect(x + 20, y + 1, x + 30 + 9, y + 9, 0, (clk - (i * 2) == 1) ? FR_LOWERED : 0);
       
  1805 					if (i == 0) { // XXX - hack/hack for first element which is increase money. Told ya it's a mess
       
  1806 						SetDParam64(0, 10000000);
       
  1807 					} else {
       
  1808 						SetDParam(0, false);
       
  1809 					}
       
  1810 				} else {
       
  1811 					DrawFrameRect(x + 20, y + 1, x + 30 + 9, y + 9, on ? 6 : 4, on ? FR_LOWERED : 0);
       
  1812 					SetDParam(0, on ? STR_CONFIG_PATCHES_ON : STR_CONFIG_PATCHES_OFF);
       
  1813 				}
       
  1814 			} break;
       
  1815 			default: {
       
  1816 				int32 val = (int32)ReadValue(ce->variable, ce->type);
       
  1817 				char buf[512];
       
  1818 
       
  1819 				/* Draw [<][>] boxes for settings of an integer-type */
       
  1820 				DrawArrowButtons(x + 20, y, 3, clk - (i * 2), true, true);
       
  1821 
       
  1822 				switch (ce->str) {
       
  1823 				/* Display date for change date cheat */
       
  1824 				case STR_CHEAT_CHANGE_DATE: SetDParam(0, _date); break;
       
  1825 				/* Draw colored flag for change player cheat */
       
  1826 				case STR_CHEAT_CHANGE_PLAYER:
       
  1827 					SetDParam(0, val);
       
  1828 					GetString(buf, STR_CHEAT_CHANGE_PLAYER, lastof(buf));
       
  1829 					DrawPlayerIcon(_current_player, 60 + GetStringBoundingBox(buf).width, y + 2);
       
  1830 					break;
       
  1831 				/* Set correct string for switch climate cheat */
       
  1832 				case STR_CHEAT_SWITCH_CLIMATE: val += STR_TEMPERATE_LANDSCAPE;
       
  1833 				/* Fallthrough */
       
  1834 				default: SetDParam(0, val);
       
  1835 				}
       
  1836 			} break;
       
  1837 			}
       
  1838 
       
  1839 			DrawString(50, y + 1, ce->str, 0);
       
  1840 
       
  1841 			y += 12;
       
  1842 		}
       
  1843 		break;
       
  1844 	}
       
  1845 
       
  1846 	case WE_CLICK: {
       
  1847 			const CheatEntry *ce;
       
  1848 			uint btn = (e->we.click.pt.y - 46) / 12;
       
  1849 			int32 value, oldvalue;
       
  1850 			uint x = e->we.click.pt.x;
       
  1851 
       
  1852 			// not clicking a button?
       
  1853 			if (!IS_INT_INSIDE(x, 20, 40) || btn >= lengthof(_cheats_ui)) break;
       
  1854 
       
  1855 			ce = &_cheats_ui[btn];
       
  1856 			oldvalue = value = (int32)ReadValue(ce->variable, ce->type);
       
  1857 
       
  1858 			*ce->been_used = true;
       
  1859 
       
  1860 			switch (ce->type) {
       
  1861 			case SLE_BOOL:
       
  1862 				if (ce->flags & CE_CLICK) WP(w,def_d).data_1 = btn * 2 + 1;
       
  1863 				value ^= 1;
       
  1864 				if (ce->proc != NULL) ce->proc(value, 0);
       
  1865 				break;
       
  1866 			default: {
       
  1867 				/* Add a dynamic step-size to the scroller. In a maximum of
       
  1868 				 * 50-steps you should be able to get from min to max */
       
  1869 				uint16 step = ((ce->max - ce->min) / 20);
       
  1870 				if (step == 0) step = 1;
       
  1871 
       
  1872 				/* Increase or decrease the value and clamp it to extremes */
       
  1873 				value += (x >= 30) ? step : -step;
       
  1874 				clamp(value, ce->min, ce->max);
       
  1875 
       
  1876 				// take whatever the function returns
       
  1877 				value = ce->proc(value, (x >= 30) ? 1 : -1);
       
  1878 
       
  1879 				if (value != oldvalue) {
       
  1880 					WP(w,def_d).data_1 = btn * 2 + 1 + ((x >= 30) ? 1 : 0);
       
  1881 				}
       
  1882 			} break;
       
  1883 			}
       
  1884 
       
  1885 			if (value != oldvalue) {
       
  1886 				WriteValue(ce->variable, ce->type, (int64)value);
       
  1887 				SetWindowDirty(w);
       
  1888 			}
       
  1889 
       
  1890 			w->flags4 |= 5 << WF_TIMEOUT_SHL;
       
  1891 
       
  1892 			SetWindowDirty(w);
       
  1893 		}
       
  1894 		break;
       
  1895 	case WE_TIMEOUT:
       
  1896 		WP(w,def_d).data_1 = 0;
       
  1897 		SetWindowDirty(w);
       
  1898 		break;
       
  1899 	}
       
  1900 }
       
  1901 
       
  1902 static const WindowDesc _cheats_desc = {
       
  1903 	240, 22, 400, 170,
       
  1904 	WC_CHEATS,0,
       
  1905 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
       
  1906 	_cheat_widgets,
       
  1907 	CheatsWndProc
       
  1908 };
       
  1909 
       
  1910 
       
  1911 void ShowCheatWindow(void)
       
  1912 {
       
  1913 	DeleteWindowById(WC_CHEATS, 0);
       
  1914 	AllocateWindowDesc(&_cheats_desc);
       
  1915 }