src/misc_gui.cpp
author maedhros
Mon, 19 Mar 2007 11:27:30 +0000
changeset 6658 59048224be55
parent 6574 e1d1a12faaf7
child 6676 30aea9ac89bc
permissions -rw-r--r--
(svn r9315) -Merge: The newhouses branch. With this merge comes almost complete support for
the newhouses grf specs, so all newhouses grfs will be playable in the game.

Many thanks to everyone who contributed code and ideas, and all the testers
who found things we missed.
/* $Id$ */

/** @file misc_gui.cpp */

#include "stdafx.h"
#include "openttd.h"
#include "hal.h"
#include "heightmap.h"
#include "debug.h"
#include "functions.h"
#include "newgrf.h"
#include "saveload.h"
#include "strings.h"
#include "table/sprites.h"
#include "table/strings.h"
#include "table/tree_land.h"
#include "map.h"
#include "window.h"
#include "gui.h"
#include "viewport.h"
#include "gfx.h"
#include "station.h"
#include "command.h"
#include "player.h"
#include "town.h"
#include "sound.h"
#include "network/network.h"
#include "string.h"
#include "variables.h"
#include "vehicle.h"
#include "train.h"
#include "tgp.h"
#include "settings.h"
#include "date.h"
#include "cargotype.h"
#include "player_face.h"

#include "fios.h"
/* Variables to display file lists */
FiosItem *_fios_list;
int _saveload_mode;

extern void GenerateLandscape(byte mode);
extern void SwitchMode(int new_mode);

static bool _fios_path_changed;
static bool _savegame_sort_dirty;

enum {
	LAND_INFO_LINES          =   7,
	LAND_INFO_LINE_BUFF_SIZE = 512,
};

static char _landinfo_data[LAND_INFO_LINES][LAND_INFO_LINE_BUFF_SIZE];

static void LandInfoWndProc(Window *w, WindowEvent *e)
{
	if (e->event == WE_PAINT) {
		DrawWindowWidgets(w);

		DoDrawStringCentered(140, 16, _landinfo_data[0], 13);
		DoDrawStringCentered(140, 27, _landinfo_data[1], 0);
		DoDrawStringCentered(140, 38, _landinfo_data[2], 0);
		DoDrawStringCentered(140, 49, _landinfo_data[3], 0);
		DoDrawStringCentered(140, 60, _landinfo_data[4], 0);
		if (_landinfo_data[5][0] != '\0') DrawStringMultiCenter(140, 76, BindCString(_landinfo_data[5]), w->width - 4);
		if (_landinfo_data[6][0] != '\0') DoDrawStringCentered(140, 71, _landinfo_data[6], 0);
	}
}

static const Widget _land_info_widgets[] = {
{   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,                       STR_018B_CLOSE_WINDOW},
{    WWT_CAPTION,   RESIZE_NONE,    14,    11,   279,     0,    13, STR_01A3_LAND_AREA_INFORMATION, STR_018C_WINDOW_TITLE_DRAG_THIS},
{      WWT_PANEL,   RESIZE_NONE,    14,     0,   279,    14,    92, 0x0,                            STR_NULL},
{    WIDGETS_END},
};

static const WindowDesc _land_info_desc = {
	WDP_AUTO, WDP_AUTO, 280, 93,
	WC_LAND_INFO, WC_NONE,
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
	_land_info_widgets,
	LandInfoWndProc
};

static void Place_LandInfo(TileIndex tile)
{
	Player *p;
	Window *w;
	Town *t;
	int64 old_money;
	int64 costclear;
	AcceptedCargo ac;
	TileDesc td;
	StringID str;

	DeleteWindowById(WC_LAND_INFO, 0);

	w = AllocateWindowDesc(&_land_info_desc);
	WP(w, void_d).data = &_landinfo_data;

	p = GetPlayer(IsValidPlayer(_local_player) ? _local_player : PLAYER_FIRST);
	t = ClosestTownFromTile(tile, _patches.dist_local_authority);

	old_money = p->money64;
	p->money64 = p->player_money = 0x7fffffff;
	costclear = DoCommand(tile, 0, 0, 0, CMD_LANDSCAPE_CLEAR);
	p->money64 = old_money;
	UpdatePlayerMoney32(p);

	/* Because build_date is not set yet in every TileDesc, we make sure it is empty */
	td.build_date = 0;
	GetAcceptedCargo(tile, ac);
	GetTileDesc(tile, &td);

	SetDParam(0, td.dparam[0]);
	GetString(_landinfo_data[0], td.str, lastof(_landinfo_data[0]));

	SetDParam(0, STR_01A6_N_A);
	if (td.owner != OWNER_NONE && td.owner != OWNER_WATER) GetNameOfOwner(td.owner, tile);
	GetString(_landinfo_data[1], STR_01A7_OWNER, lastof(_landinfo_data[1]));

	str = STR_01A4_COST_TO_CLEAR_N_A;
	if (!CmdFailed(costclear)) {
		SetDParam(0, costclear);
		str = STR_01A5_COST_TO_CLEAR;
	}
	GetString(_landinfo_data[2], str, lastof(_landinfo_data[2]));

	snprintf(_userstring, lengthof(_userstring), "0x%.4X", tile);
	SetDParam(0, TileX(tile));
	SetDParam(1, TileY(tile));
	SetDParam(2, STR_SPEC_USERSTRING);
	GetString(_landinfo_data[3], STR_LANDINFO_COORDS, lastof(_landinfo_data[3]));

	SetDParam(0, STR_01A9_NONE);
	if (t != NULL && IsValidTown(t)) {
		SetDParam(0, STR_TOWN);
		SetDParam(1, t->index);
	}
	GetString(_landinfo_data[4], STR_01A8_LOCAL_AUTHORITY, lastof(_landinfo_data[4]));

	{
		int i;
		char *p = GetString(_landinfo_data[5], STR_01CE_CARGO_ACCEPTED, lastof(_landinfo_data[5]));
		bool found = false;

		for (i = 0; i < NUM_CARGO; ++i) {
			if (ac[i] > 0) {
				/* Add a comma between each item. */
				if (found) {
					*p++ = ',';
					*p++ = ' ';
				}
				found = true;

				/* If the accepted value is less than 8, show it in 1/8:ths */
				if (ac[i] < 8) {
					SetDParam(0, ac[i]);
					SetDParam(1, GetCargo(i)->name);
					p = GetString(p, STR_01D1_8, lastof(_landinfo_data[5]));
				} else {
					p = GetString(p, GetCargo(i)->name, lastof(_landinfo_data[5]));
				}
			}
		}

		if (!found) _landinfo_data[5][0] = '\0';
	}

	if (td.build_date != 0) {
		SetDParam(0, td.build_date);
	GetString(_landinfo_data[6], STR_BUILD_DATE, lastof(_landinfo_data[6]));
	} else {
		_landinfo_data[6][0] = '\0';
	}

#if defined(_DEBUG)
#	define LANDINFOD_LEVEL 0
#else
#	define LANDINFOD_LEVEL 1
#endif
	DEBUG(misc, LANDINFOD_LEVEL, "TILE: %#x (%i,%i)", tile, TileX(tile), TileY(tile));
	DEBUG(misc, LANDINFOD_LEVEL, "type_height  = %#x", _m[tile].type_height);
	DEBUG(misc, LANDINFOD_LEVEL, "m1           = %#x", _m[tile].m1);
	DEBUG(misc, LANDINFOD_LEVEL, "m2           = %#x", _m[tile].m2);
	DEBUG(misc, LANDINFOD_LEVEL, "m3           = %#x", _m[tile].m3);
	DEBUG(misc, LANDINFOD_LEVEL, "m4           = %#x", _m[tile].m4);
	DEBUG(misc, LANDINFOD_LEVEL, "m5           = %#x", _m[tile].m5);
	DEBUG(misc, LANDINFOD_LEVEL, "m6           = %#x", _m[tile].m6);
	DEBUG(misc, LANDINFOD_LEVEL, "m7           = %#x", _me[tile].m7);
#undef LANDINFOD_LEVEL
}

void PlaceLandBlockInfo()
{
	if (_cursor.sprite == SPR_CURSOR_QUERY) {
		ResetObjectToPlace();
	} else {
		_place_proc = Place_LandInfo;
		SetObjectToPlace(SPR_CURSOR_QUERY, PAL_NONE, 1, WC_MAIN_TOOLBAR, 0);
	}
}

static const char *credits[] = {
	/*************************************************************************
	 *                      maximum length of string which fits in window   -^*/
	"Original design by Chris Sawyer",
	"Original graphics by Simon Foster",
	"",
	"The OpenTTD team (in alphabetical order):",
	"  Jean-Francois Claeys (Belugas) - In training, not yet specialized",
	"  Bjarni Corfitzen (Bjarni) - MacOSX port, coder",
	"  Matthijs Kooijman (blathijs) - Pathfinder-guru",
	"  Victor Fischer (Celestar) - Programming everywhere you need him to",
	"  Tamás Faragó (Darkvater) - Lead coder",
	"  Loïc Guilloux (glx) - In training, not yet specialized",
	"  Jaroslav Mazanec (KUDr) - YAPG (Yet Another Pathfinder God) ;)",
	"  Jonathan Coome (Maedhros) - High priest of the newGRF Temple",
	"  Attila Bán (MiHaMiX) - WebTranslator, Nightlies, Wiki and bugtracker host",
	"  Peter Nelson (peter1138) - Spiritual descendant from newgrf gods",
	"  Remko Bijker (Rubidium) - THE desync hunter",
	"  Christoph Mallon (Tron) - Programmer, code correctness police",
	"  Patric Stout (TrueLight) - Coder, network guru, SVN- and website host",
	"",
	"Retired Developers:",
	"  Ludvig Strigeus (ludde) - OpenTTD author, main coder (0.1 - 0.3.3)",
	"  Serge Paquet (vurlix) - Assistant project manager, coder (0.1 - 0.3.3)",
	"  Dominik Scherer (dominik81) - Lead programmer, GUI expert (0.3.0 - 0.3.6)",
	"  Owen Rudge (orudge) - Forum- and masterserver host, OS/2 port (0.1 - 0.4.8)",
	"",
	"Special thanks go out to:",
	"  Josef Drexler - For his great work on TTDPatch",
	"  Marcin Grzegorczyk - For his documentation of TTD internals",
	"  Petr Baudis (pasky) - Many patches, newgrf support",
	"  Stefan Meißner (sign_de) - For his work on the console",
	"  Simon Sasburg (HackyKid) - Many bugfixes he has blessed us with (and PBS)",
	"  Cian Duffy (MYOB) - BeOS port / manual writing",
	"  Christian Rosentreter (tokai) - MorphOS / AmigaOS port",
	"  Richard Kempton (richK) - additional airports, initial TGP implementation",
	"",
	"  Michael Blunck - Pre-Signals and Semaphores © 2003",
	"  George - Canal/Lock graphics © 2003-2004",
	"  Marcin Grzegorczyk - Foundations for Tracks on Slopes",
	"  All Translators - Who made OpenTTD a truly international game",
	"  Bug Reporters - Without whom OpenTTD would still be full of bugs!",
	"",
	"",
	"And last but not least:",
	"  Chris Sawyer - For an amazing game!"
};

static void AboutWindowProc(Window *w, WindowEvent *e)
{
	switch (e->event) {
	case WE_CREATE: // Set up window counter and start position of scroller
		WP(w, scroller_d).counter = 0;
		WP(w, scroller_d).height = w->height - 40;
		break;
	case WE_PAINT: {
		uint i;
		int y = WP(w, scroller_d).height;
		DrawWindowWidgets(w);

		/* Show original copyright and revision version */
		DrawStringCentered(210, 17, STR_00B6_ORIGINAL_COPYRIGHT, 0);
		DrawStringCentered(210, 17 + 10, STR_00B7_VERSION, 0);

		/* Show all scrolling credits */
		for (i = 0; i < lengthof(credits); i++) {
			if (y >= 50 && y < (w->height - 40)) {
				DoDrawString(credits[i], 10, y, 0x10);
			}
			y += 10;
		}

		/* If the last text has scrolled start anew from the start */
		if (y < 50) WP(w, scroller_d).height = w->height - 40;

		DoDrawStringCentered(210, w->height - 25, "Website: http://www.openttd.org", 16);
		DrawStringCentered(210, w->height - 15, STR_00BA_COPYRIGHT_OPENTTD, 0);
	}	break;
	case WE_MOUSELOOP: // Timer to scroll the text and adjust the new top
		if (WP(w, scroller_d).counter++ % 3 == 0) {
			WP(w, scroller_d).height--;
			SetWindowDirty(w);
		}
		break;
	}
}

static const Widget _about_widgets[] = {
{   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,         STR_018B_CLOSE_WINDOW},
{    WWT_CAPTION,   RESIZE_NONE,    14,    11,   419,     0,    13, STR_015B_OPENTTD, STR_NULL},
{      WWT_PANEL,   RESIZE_NONE,    14,     0,   419,    14,   271, 0x0,              STR_NULL},
{      WWT_FRAME,   RESIZE_NONE,    14,     5,   414,    40,   245, STR_NULL,         STR_NULL},
{    WIDGETS_END},
};

static const WindowDesc _about_desc = {
	WDP_CENTER, WDP_CENTER, 420, 272,
	WC_GAME_OPTIONS, WC_NONE,
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
	_about_widgets,
	AboutWindowProc
};


void ShowAboutWindow()
{
	DeleteWindowById(WC_GAME_OPTIONS, 0);
	AllocateWindowDesc(&_about_desc);
}

static int _tree_to_plant;

static const PalSpriteID _tree_sprites[] = {
	{ 0x655, PAL_NONE }, { 0x663, PAL_NONE }, { 0x678, PAL_NONE }, { 0x62B, PAL_NONE },
	{ 0x647, PAL_NONE }, { 0x639, PAL_NONE }, { 0x64E, PAL_NONE }, { 0x632, PAL_NONE },
	{ 0x67F, PAL_NONE }, { 0x68D, PAL_NONE }, { 0x69B, PAL_NONE }, { 0x6A9, PAL_NONE },
	{ 0x6AF, PAL_NONE }, { 0x6D2, PAL_NONE }, { 0x6D9, PAL_NONE }, { 0x6C4, PAL_NONE },
	{ 0x6CB, PAL_NONE }, { 0x6B6, PAL_NONE }, { 0x6BD, PAL_NONE }, { 0x6E0, PAL_NONE },
	{ 0x72E, PAL_NONE }, { 0x734, PAL_NONE }, { 0x74A, PAL_NONE }, { 0x74F, PAL_NONE },
	{ 0x76B, PAL_NONE }, { 0x78F, PAL_NONE }, { 0x788, PAL_NONE }, { 0x77B, PAL_NONE },
	{ 0x75F, PAL_NONE }, { 0x774, PAL_NONE }, { 0x720, PAL_NONE }, { 0x797, PAL_NONE },
	{ 0x79E, PAL_NONE }, { 0x7A5, PALETTE_TO_GREEN }, { 0x7AC, PALETTE_TO_RED }, { 0x7B3, PAL_NONE },
	{ 0x7BA, PAL_NONE }, { 0x7C1, PALETTE_TO_RED, }, { 0x7C8, PALETTE_TO_PALE_GREEN }, { 0x7CF, PALETTE_TO_YELLOW }, { 0x7D6, PALETTE_TO_RED }
};

static void BuildTreesWndProc(Window *w, WindowEvent *e)
{
	switch (e->event) {
	case WE_PAINT: {
		int x,y;
		int i, count;

		DrawWindowWidgets(w);

		WP(w,tree_d).base = i = _tree_base_by_landscape[_opt.landscape];
		WP(w,tree_d).count = count = _tree_count_by_landscape[_opt.landscape];

		x = 18;
		y = 54;
		do {
			DrawSprite(_tree_sprites[i].sprite, _tree_sprites[i].pal, x, y);
			x += 35;
			if (!(++i & 3)) {
				x -= 35 * 4;
				y += 47;
			}
		} while (--count);
	} break;

	case WE_CLICK: {
		int wid = e->we.click.widget;

		switch (wid) {
		case 0:
			ResetObjectToPlace();
			break;

		case 3: case 4: case 5: case 6:
		case 7: case 8: case 9: case 10:
		case 11:case 12: case 13: case 14:
			if (wid - 3 >= WP(w,tree_d).count) break;

			if (HandlePlacePushButton(w, wid, SPR_CURSOR_TREE, 1, NULL))
				_tree_to_plant = WP(w,tree_d).base + wid - 3;
			break;

		case 15: // tree of random type.
			if (HandlePlacePushButton(w, 15, SPR_CURSOR_TREE, 1, NULL))
				_tree_to_plant = -1;
			break;

		case 16: // place trees randomly over the landscape
			LowerWindowWidget(w, 16);
			w->flags4 |= 5 << WF_TIMEOUT_SHL;
			SndPlayFx(SND_15_BEEP);
			PlaceTreesRandomly();
			MarkWholeScreenDirty();
			break;
		}
	} break;

	case WE_PLACE_OBJ:
		VpStartPlaceSizing(e->we.place.tile, VPM_X_AND_Y_LIMITED);
		VpSetPlaceSizingLimit(20);
		break;

	case WE_PLACE_DRAG:
		VpSelectTilesWithMethod(e->we.place.pt.x, e->we.place.pt.y, e->we.place.userdata);
		return;

	case WE_PLACE_MOUSEUP:
		if (e->we.place.pt.x != -1) {
			DoCommandP(e->we.place.tile, _tree_to_plant, e->we.place.starttile, NULL,
				CMD_PLANT_TREE | CMD_AUTO | CMD_MSG(STR_2805_CAN_T_PLANT_TREE_HERE));
		}
		break;

	case WE_TIMEOUT:
		RaiseWindowWidget(w, 16);
		break;

	case WE_ABORT_PLACE_OBJ:
		RaiseWindowButtons(w);
		break;
	}
}

static const Widget _build_trees_widgets[] = {
{   WWT_CLOSEBOX,   RESIZE_NONE,     7,     0,    10,     0,    13, STR_00C5,              STR_018B_CLOSE_WINDOW},
{    WWT_CAPTION,   RESIZE_NONE,     7,    11,   142,     0,    13, STR_2802_TREES,        STR_018C_WINDOW_TITLE_DRAG_THIS},
{      WWT_PANEL,   RESIZE_NONE,     7,     0,   142,    14,   170, 0x0,                   STR_NULL},
{      WWT_PANEL,   RESIZE_NONE,    14,     2,    35,    16,    61, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
{      WWT_PANEL,   RESIZE_NONE,    14,    37,    70,    16,    61, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
{      WWT_PANEL,   RESIZE_NONE,    14,    72,   105,    16,    61, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
{      WWT_PANEL,   RESIZE_NONE,    14,   107,   140,    16,    61, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
{      WWT_PANEL,   RESIZE_NONE,    14,     2,    35,    63,   108, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
{      WWT_PANEL,   RESIZE_NONE,    14,    37,    70,    63,   108, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
{      WWT_PANEL,   RESIZE_NONE,    14,    72,   105,    63,   108, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
{      WWT_PANEL,   RESIZE_NONE,    14,   107,   140,    63,   108, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
{      WWT_PANEL,   RESIZE_NONE,    14,     2,    35,   110,   155, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
{      WWT_PANEL,   RESIZE_NONE,    14,    37,    70,   110,   155, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
{      WWT_PANEL,   RESIZE_NONE,    14,    72,   105,   110,   155, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
{      WWT_PANEL,   RESIZE_NONE,    14,   107,   140,   110,   155, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
{    WWT_TEXTBTN,   RESIZE_NONE,    14,     2,   140,   157,   168, STR_TREES_RANDOM_TYPE, STR_TREES_RANDOM_TYPE_TIP},
{    WIDGETS_END},
};

static const WindowDesc _build_trees_desc = {
	497, 22, 143, 171,
	WC_BUILD_TREES, WC_SCEN_LAND_GEN,
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
	_build_trees_widgets,
	BuildTreesWndProc
};

static const Widget _build_trees_scen_widgets[] = {
{   WWT_CLOSEBOX,   RESIZE_NONE,     7,     0,    10,     0,    13, STR_00C5,              STR_018B_CLOSE_WINDOW},
{    WWT_CAPTION,   RESIZE_NONE,     7,    11,   142,     0,    13, STR_2802_TREES,        STR_018C_WINDOW_TITLE_DRAG_THIS},
{      WWT_PANEL,   RESIZE_NONE,     7,     0,   142,    14,   183, 0x0,                   STR_NULL},
{      WWT_PANEL,   RESIZE_NONE,    14,     2,    35,    16,    61, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
{      WWT_PANEL,   RESIZE_NONE,    14,    37,    70,    16,    61, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
{      WWT_PANEL,   RESIZE_NONE,    14,    72,   105,    16,    61, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
{      WWT_PANEL,   RESIZE_NONE,    14,   107,   140,    16,    61, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
{      WWT_PANEL,   RESIZE_NONE,    14,     2,    35,    63,   108, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
{      WWT_PANEL,   RESIZE_NONE,    14,    37,    70,    63,   108, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
{      WWT_PANEL,   RESIZE_NONE,    14,    72,   105,    63,   108, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
{      WWT_PANEL,   RESIZE_NONE,    14,   107,   140,    63,   108, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
{      WWT_PANEL,   RESIZE_NONE,    14,     2,    35,   110,   155, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
{      WWT_PANEL,   RESIZE_NONE,    14,    37,    70,   110,   155, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
{      WWT_PANEL,   RESIZE_NONE,    14,    72,   105,   110,   155, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
{      WWT_PANEL,   RESIZE_NONE,    14,   107,   140,   110,   155, 0x0,                   STR_280D_SELECT_TREE_TYPE_TO_PLANT},
{    WWT_TEXTBTN,   RESIZE_NONE,    14,     2,   140,   157,   168, STR_TREES_RANDOM_TYPE, STR_TREES_RANDOM_TYPE_TIP},
{    WWT_TEXTBTN,   RESIZE_NONE,    14,     2,   140,   170,   181, STR_028A_RANDOM_TREES, STR_028B_PLANT_TREES_RANDOMLY_OVER},
{    WIDGETS_END},
};

static const WindowDesc _build_trees_scen_desc = {
	WDP_AUTO, WDP_AUTO, 143, 184,
	WC_BUILD_TREES, WC_NONE,
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
	_build_trees_scen_widgets,
	BuildTreesWndProc
};


void ShowBuildTreesToolbar()
{
	if (!IsValidPlayer(_current_player)) return;
	AllocateWindowDescFront(&_build_trees_desc, 0);
}

void ShowBuildTreesScenToolbar()
{
	AllocateWindowDescFront(&_build_trees_scen_desc, 0);
}

static uint32 _errmsg_decode_params[20];
static StringID _errmsg_message_1, _errmsg_message_2;
static uint _errmsg_duration;


static const Widget _errmsg_widgets[] = {
{   WWT_CLOSEBOX,   RESIZE_NONE,     4,     0,    10,     0,    13, STR_00C5,         STR_018B_CLOSE_WINDOW},
{    WWT_CAPTION,   RESIZE_NONE,     4,    11,   239,     0,    13, STR_00B2_MESSAGE, STR_NULL},
{      WWT_PANEL,   RESIZE_NONE,     4,     0,   239,    14,    45, 0x0,              STR_NULL},
{    WIDGETS_END},
};

static const Widget _errmsg_face_widgets[] = {
{   WWT_CLOSEBOX,   RESIZE_NONE,     4,     0,    10,     0,    13, STR_00C5,              STR_018B_CLOSE_WINDOW},
{    WWT_CAPTION,   RESIZE_NONE,     4,    11,   333,     0,    13, STR_00B3_MESSAGE_FROM, STR_NULL},
{      WWT_PANEL,   RESIZE_NONE,     4,     0,   333,    14,   136, 0x0,                   STR_NULL},
{   WIDGETS_END},
};

static void ErrmsgWndProc(Window *w, WindowEvent *e)
{
	switch (e->event) {
	case WE_PAINT:
		COPY_IN_DPARAM(0, _errmsg_decode_params, lengthof(_errmsg_decode_params));
		DrawWindowWidgets(w);
		COPY_IN_DPARAM(0, _errmsg_decode_params, lengthof(_errmsg_decode_params));
		if (!IsWindowOfPrototype(w, _errmsg_face_widgets)) {
			DrawStringMultiCenter(
				120,
				(_errmsg_message_1 == INVALID_STRING_ID ? 25 : 15),
				_errmsg_message_2,
				w->width - 2);
			if (_errmsg_message_1 != INVALID_STRING_ID)
				DrawStringMultiCenter(
					120,
					30,
					_errmsg_message_1,
					w->width - 2);
		} else {
			const Player *p = GetPlayer((PlayerID)GetDParamX(_errmsg_decode_params,2));
			DrawPlayerFace(p->face, p->player_color, 2, 16);

			DrawStringMultiCenter(
				214,
				(_errmsg_message_1 == INVALID_STRING_ID ? 65 : 45),
				_errmsg_message_2,
				w->width - 2);
			if (_errmsg_message_1 != INVALID_STRING_ID)
				DrawStringMultiCenter(
					214,
					90,
					_errmsg_message_1,
					w->width - 2);
		}
		break;

	case WE_MOUSELOOP:
		if (_right_button_down) DeleteWindow(w);
		break;

	case WE_4:
		if (--_errmsg_duration == 0) DeleteWindow(w);
		break;

	case WE_DESTROY:
		SetRedErrorSquare(0);
		_switch_mode_errorstr = INVALID_STRING_ID;
		break;

	case WE_KEYPRESS:
		if (e->we.keypress.keycode == WKC_SPACE) {
			/* Don't continue. */
			e->we.keypress.cont = false;
			DeleteWindow(w);
		}
		break;
	}
}

void ShowErrorMessage(StringID msg_1, StringID msg_2, int x, int y)
{
	Window *w;
	const ViewPort *vp;
	Point pt;

	DeleteWindowById(WC_ERRMSG, 0);

	//assert(msg_2);
	if (msg_2 == 0) msg_2 = STR_EMPTY;

	_errmsg_message_1 = msg_1;
	_errmsg_message_2 = msg_2;
	COPY_OUT_DPARAM(_errmsg_decode_params, 0, lengthof(_errmsg_decode_params));
	_errmsg_duration = _patches.errmsg_duration;
	if (!_errmsg_duration) return;

	if (_errmsg_message_1 != STR_013B_OWNED_BY || GetDParamX(_errmsg_decode_params,2) >= 8) {

		if ( (x|y) != 0) {
			pt = RemapCoords2(x, y);
			vp = FindWindowById(WC_MAIN_WINDOW, 0)->viewport;

			/* move x pos to opposite corner */
			pt.x = ((pt.x - vp->virtual_left) >> vp->zoom) + vp->left;
			pt.x = (pt.x < (_screen.width >> 1)) ? _screen.width - 260 : 20;

			/* move y pos to opposite corner */
			pt.y = ((pt.y - vp->virtual_top) >> vp->zoom) + vp->top;
			pt.y = (pt.y < (_screen.height >> 1)) ? _screen.height - 80 : 100;

		} else {
			pt.x = (_screen.width - 240) >> 1;
			pt.y = (_screen.height - 46) >> 1;
		}
		w = AllocateWindow(pt.x, pt.y, 240, 46, ErrmsgWndProc, WC_ERRMSG, _errmsg_widgets);
	} else {
		if ( (x|y) != 0) {
			pt = RemapCoords2(x, y);
			vp = FindWindowById(WC_MAIN_WINDOW, 0)->viewport;
			pt.x = clamp(((pt.x - vp->virtual_left) >> vp->zoom) + vp->left - (334/2), 0, _screen.width - 334);
			pt.y = clamp(((pt.y - vp->virtual_top) >> vp->zoom) + vp->top - (137/2), 22, _screen.height - 137);
		} else {
			pt.x = (_screen.width - 334) >> 1;
			pt.y = (_screen.height - 137) >> 1;
		}
		w = AllocateWindow(pt.x, pt.y, 334, 137, ErrmsgWndProc, WC_ERRMSG, _errmsg_face_widgets);
	}

	w->desc_flags = WDF_STD_BTN | WDF_DEF_WIDGET;
}


void ShowEstimatedCostOrIncome(int32 cost, int x, int y)
{
	StringID msg = STR_0805_ESTIMATED_COST;

	if (cost < 0) {
		cost = -cost;
		msg = STR_0807_ESTIMATED_INCOME;
	}
	SetDParam(0, cost);
	ShowErrorMessage(INVALID_STRING_ID, msg, x, y);
}

void ShowCostOrIncomeAnimation(int x, int y, int z, int32 cost)
{
	StringID msg;
	Point pt = RemapCoords(x,y,z);

	msg = STR_0801_COST;
	if (cost < 0) {
		cost = -cost;
		msg = STR_0803_INCOME;
	}
	SetDParam(0, cost);
	AddTextEffect(msg, pt.x, pt.y, 0x250);
}

void ShowFeederIncomeAnimation(int x, int y, int z, int32 cost)
{
	Point pt = RemapCoords(x,y,z);

	SetDParam(0, cost);
	AddTextEffect(STR_FEEDER, pt.x, pt.y, 0x250);
}

static const Widget _tooltips_widgets[] = {
{      WWT_PANEL,   RESIZE_NONE,    14,     0,   199,     0,    31, 0x0, STR_NULL},
{   WIDGETS_END},
};


static void TooltipsWndProc(Window *w, WindowEvent *e)
{
	switch (e->event) {
		case WE_PAINT: {
			uint arg;
			GfxFillRect(0, 0, w->width - 1, w->height - 1, 0);
			GfxFillRect(1, 1, w->width - 2, w->height - 2, 0x44);

			for (arg = 0; arg < WP(w, tooltips_d).paramcount; arg++) {
				SetDParam(arg, WP(w, tooltips_d).params[arg]);
			}
			DrawStringMultiCenter((w->width >> 1), (w->height >> 1) - 5, WP(w, tooltips_d).string_id, w->width - 2);
			break;
		}

		case WE_MOUSELOOP:
			/* We can show tooltips while dragging tools. These are shown as long as
			 * we are dragging the tool. Normal tooltips work with rmb */
			if (WP(w, tooltips_d).paramcount == 0 ) {
				if (!_right_button_down) DeleteWindow(w);
			} else {
				if (!_left_button_down) DeleteWindow(w);
			}

			break;
	}
}

/** Shows a tooltip
* @param str String to be displayed
* @param params (optional) up to 5 pieces of additional information that may be
* added to a tooltip; currently only supports parameters of {NUM} (integer) */
void GuiShowTooltipsWithArgs(StringID str, uint paramcount, const uint32 params[])
{
	char buffer[512];
	BoundingRect br;
	Window *w;
	uint i;
	int x, y;

	DeleteWindowById(WC_TOOLTIPS, 0);

	/* We only show measurement tooltips with patch setting on */
	if (str == STR_NULL || (paramcount != 0 && !_patches.measure_tooltip)) return;

	for (i = 0; i != paramcount; i++) SetDParam(i, params[i]);
	GetString(buffer, str, lastof(buffer));

	br = GetStringBoundingBox(buffer);
	br.width += 6; br.height += 4; // increase slightly to have some space around the box

	/* Cut tooltip length to 200 pixels max, wrap to new line if longer */
	if (br.width > 200) {
		br.height += ((br.width - 4) / 176) * 10;
		br.width = 200;
	}

	/* Correctly position the tooltip position, watch out for window and cursor size
	 * Clamp value to below main toolbar and above statusbar. If tooltip would
	 * go below window, flip it so it is shown above the cursor */
	y = clamp(_cursor.pos.y + _cursor.size.y + _cursor.offs.y + 5, 22, _screen.height - 12);
	if (y + br.height > _screen.height - 12) y = _cursor.pos.y + _cursor.offs.y - br.height - 5;
	x = clamp(_cursor.pos.x - (br.width >> 1), 0, _screen.width - br.width);

	w = AllocateWindow(x, y, br.width, br.height, TooltipsWndProc, WC_TOOLTIPS, _tooltips_widgets);

	WP(w, tooltips_d).string_id = str;
	assert(sizeof(WP(w, tooltips_d).params[0]) == sizeof(params[0]));
	memcpy(WP(w, tooltips_d).params, params, sizeof(WP(w, tooltips_d).params[0]) * paramcount);
	WP(w, tooltips_d).paramcount = paramcount;

	w->flags4 &= ~WF_WHITE_BORDER_MASK; // remove white-border from tooltip
	w->widget[0].right = br.width;
	w->widget[0].bottom = br.height;
}


static void DrawStationCoverageText(const AcceptedCargo accepts,
	int str_x, int str_y, uint mask)
{
	char *b = _userstring;
	bool first = true;
	int i;

	b = InlineString(b, STR_000D_ACCEPTS);

	for (i = 0; i != NUM_CARGO; i++, mask >>= 1) {
		if (b >= lastof(_userstring) - 5) break;
		if (accepts[i] >= 8 && mask & 1) {
			if (first) {
				first = false;
			} else {
				/* Add a comma if this is not the first item */
				*b++ = ',';
				*b++ = ' ';
			}
			b = InlineString(b, GetCargo(i)->name);
		}
	}

	/* If first is still true then no cargo is accepted */
	if (first) b = InlineString(b, STR_00D0_NOTHING);

	*b = '\0';
	DrawStringMultiLine(str_x, str_y, STR_SPEC_USERSTRING, 144);
}

void DrawStationCoverageAreaText(int sx, int sy, uint mask, int rad) {
	TileIndex tile = TileVirtXY(_thd.pos.x, _thd.pos.y);
	AcceptedCargo accepts;
	if (tile < MapSize()) {
		GetAcceptanceAroundTiles(accepts, tile, _thd.size.x / TILE_SIZE, _thd.size.y / TILE_SIZE , rad);
		DrawStationCoverageText(accepts, sx, sy, mask);
	}
}

void CheckRedrawStationCoverage(const Window *w)
{
	if (_thd.dirty & 1) {
		_thd.dirty &= ~1;
		SetWindowDirty(w);
	}
}

void SetVScrollCount(Window *w, int num)
{
	w->vscroll.count = num;
	num -= w->vscroll.cap;
	if (num < 0) num = 0;
	if (num < w->vscroll.pos) w->vscroll.pos = num;
}

void SetVScroll2Count(Window *w, int num)
{
	w->vscroll2.count = num;
	num -= w->vscroll2.cap;
	if (num < 0) num = 0;
	if (num < w->vscroll2.pos) w->vscroll2.pos = num;
}

void SetHScrollCount(Window *w, int num)
{
	w->hscroll.count = num;
	num -= w->hscroll.cap;
	if (num < 0) num = 0;
	if (num < w->hscroll.pos) w->hscroll.pos = num;
}

/* Delete a character at the caret position in a text buf.
 * If backspace is set, delete the character before the caret,
 * else delete the character after it. */
static void DelChar(Textbuf *tb, bool backspace)
{
	WChar c;
	uint width;
	size_t len;
	char *s = tb->buf + tb->caretpos;

	if (backspace) s = Utf8PrevChar(s);

	len = Utf8Decode(&c, s);
	width = GetCharacterWidth(FS_NORMAL, c);

	tb->width  -= width;
	if (backspace) {
		tb->caretpos   -= len;
		tb->caretxoffs -= width;
	}

	/* Move the remaining characters over the marker */
	memmove(s, s + len, tb->length - (s - tb->buf) - len + 1);
	tb->length -= len;
}

/**
 * Delete a character from a textbuffer, either with 'Delete' or 'Backspace'
 * The character is delete from the position the caret is at
 * @param tb @Textbuf type to be changed
 * @param delmode Type of deletion, either @WKC_BACKSPACE or @WKC_DELETE
 * @return Return true on successfull change of Textbuf, or false otherwise
 */
bool DeleteTextBufferChar(Textbuf *tb, int delmode)
{
	if (delmode == WKC_BACKSPACE && tb->caretpos != 0) {
		DelChar(tb, true);
		return true;
	} else if (delmode == WKC_DELETE && tb->caretpos < tb->length) {
		DelChar(tb, false);
		return true;
	}

	return false;
}

/**
 * Delete every character in the textbuffer
 * @param tb @Textbuf buffer to be emptied
 */
void DeleteTextBufferAll(Textbuf *tb)
{
	memset(tb->buf, 0, tb->maxlength);
	tb->length = tb->width = 0;
	tb->caretpos = tb->caretxoffs = 0;
}

/**
 * Insert a character to a textbuffer. If maxwidth of the Textbuf is zero,
 * we don't care about the visual-length but only about the physical
 * length of the string
 * @param tb @Textbuf type to be changed
 * @param key Character to be inserted
 * @return Return true on successfull change of Textbuf, or false otherwise
 */
bool InsertTextBufferChar(Textbuf *tb, WChar key)
{
	const byte charwidth = GetCharacterWidth(FS_NORMAL, key);
	size_t len = Utf8CharLen(key);
	if (tb->length < (tb->maxlength - len) && (tb->maxwidth == 0 || tb->width + charwidth <= tb->maxwidth)) {
		memmove(tb->buf + tb->caretpos + len, tb->buf + tb->caretpos, tb->length - tb->caretpos + 1);
		Utf8Encode(tb->buf + tb->caretpos, key);
		tb->length += len;
		tb->width  += charwidth;

		tb->caretpos   += len;
		tb->caretxoffs += charwidth;
		return true;
	}
	return false;
}

/**
 * Handle text navigation with arrow keys left/right.
 * This defines where the caret will blink and the next characer interaction will occur
 * @param tb @Textbuf type where navigation occurs
 * @param navmode Direction in which navigation occurs @WKC_LEFT, @WKC_RIGHT, @WKC_END, @WKC_HOME
 * @return Return true on successfull change of Textbuf, or false otherwise
 */
bool MoveTextBufferPos(Textbuf *tb, int navmode)
{
	switch (navmode) {
	case WKC_LEFT:
		if (tb->caretpos != 0) {
			WChar c;
			const char *s = Utf8PrevChar(tb->buf + tb->caretpos);
			Utf8Decode(&c, s);
			tb->caretpos    = s - tb->buf; // -= (tb->buf + tb->caretpos - s)
			tb->caretxoffs -= GetCharacterWidth(FS_NORMAL, c);

			return true;
		}
		break;
	case WKC_RIGHT:
		if (tb->caretpos < tb->length) {
			WChar c;

			tb->caretpos   += Utf8Decode(&c, tb->buf + tb->caretpos);
			tb->caretxoffs += GetCharacterWidth(FS_NORMAL, c);

			return true;
		}
		break;
	case WKC_HOME:
		tb->caretpos = 0;
		tb->caretxoffs = 0;
		return true;
	case WKC_END:
		tb->caretpos = tb->length;
		tb->caretxoffs = tb->width;
		return true;
	}

	return false;
}

/**
 * Initialize the textbuffer by supplying it the buffer to write into
 * and the maximum length of this buffer
 * @param tb @Textbuf type which is getting initialized
 * @param buf the buffer that will be holding the data for input
 * @param maxlength maximum length in characters of this buffer
 * @param maxwidth maximum length in pixels of this buffer. If reached, buffer
 * cannot grow, even if maxlength would allow because there is space. A length
 * of zero '0' means the buffer is only restricted by maxlength */
void InitializeTextBuffer(Textbuf *tb, const char *buf, uint16 maxlength, uint16 maxwidth)
{
	tb->buf = (char*)buf;
	tb->maxlength = maxlength;
	tb->maxwidth  = maxwidth;
	tb->caret = true;
	UpdateTextBufferSize(tb);
}

/**
 * Update @Textbuf type with its actual physical character and screenlength
 * Get the count of characters in the string as well as the width in pixels.
 * Useful when copying in a larger amount of text at once
 * @param tb @Textbuf type which length is calculated
 */
void UpdateTextBufferSize(Textbuf *tb)
{
	const char *buf = tb->buf;
	WChar c = Utf8Consume(&buf);

	tb->width = 0;
	tb->length = 0;

	for (; c != '\0' && tb->length < (tb->maxlength - 1); c = Utf8Consume(&buf)) {
		tb->width += GetCharacterWidth(FS_NORMAL, c);
		tb->length += Utf8CharLen(c);
	}

	tb->caretpos = tb->length;
	tb->caretxoffs = tb->width;
}

int HandleEditBoxKey(Window *w, querystr_d *string, int wid, WindowEvent *e)
{
	e->we.keypress.cont = false;

	switch (e->we.keypress.keycode) {
	case WKC_ESC: return 2;
	case WKC_RETURN: case WKC_NUM_ENTER: return 1;
	case (WKC_CTRL | 'V'):
		if (InsertTextBufferClipboard(&string->text))
			InvalidateWidget(w, wid);
		break;
	case (WKC_CTRL | 'U'):
		DeleteTextBufferAll(&string->text);
		InvalidateWidget(w, wid);
		break;
	case WKC_BACKSPACE: case WKC_DELETE:
		if (DeleteTextBufferChar(&string->text, e->we.keypress.keycode))
			InvalidateWidget(w, wid);
		break;
	case WKC_LEFT: case WKC_RIGHT: case WKC_END: case WKC_HOME:
		if (MoveTextBufferPos(&string->text, e->we.keypress.keycode))
			InvalidateWidget(w, wid);
		break;
	default:
		if (IsValidChar(e->we.keypress.key, string->afilter)) {
			if (InsertTextBufferChar(&string->text, e->we.keypress.key)) {
				InvalidateWidget(w, wid);
			}
		} else { // key wasn't caught. Continue only if standard entry specified
			e->we.keypress.cont = (string->afilter == CS_ALPHANUMERAL);
		}
	}

	return 0;
}

bool HandleCaret(Textbuf *tb)
{
	/* caret changed? */
	bool b = !!(_caret_timer & 0x20);

	if (b != tb->caret) {
		tb->caret = b;
		return true;
	}
	return false;
}

void HandleEditBox(Window *w, querystr_d *string, int wid)
{
	if (HandleCaret(&string->text)) InvalidateWidget(w, wid);
}

void DrawEditBox(Window *w, querystr_d *string, int wid)
{
	DrawPixelInfo dpi, *old_dpi;
	int delta;
	const Widget *wi = &w->widget[wid];
	const Textbuf *tb = &string->text;

	GfxFillRect(wi->left + 1, wi->top + 1, wi->right - 1, wi->bottom - 1, 215);

	/* Limit the drawing of the string inside the widget boundaries */
	if (!FillDrawPixelInfo(&dpi,
	      wi->left + 4,
	      wi->top + 1,
	      wi->right - wi->left - 4,
	      wi->bottom - wi->top - 1)
	) return;

	old_dpi = _cur_dpi;
	_cur_dpi = &dpi;

	/* We will take the current widget length as maximum width, with a small
	 * space reserved at the end for the caret to show */
	delta = (wi->right - wi->left) - tb->width - 10;
	if (delta > 0) delta = 0;

	if (tb->caretxoffs + delta < 0) delta = -tb->caretxoffs;

	DoDrawString(tb->buf, delta, 0, 8);
	if (tb->caret) DoDrawString("_", tb->caretxoffs + delta, 0, 12);

	_cur_dpi = old_dpi;
}

enum QueryStringWidgets {
	QUERY_STR_WIDGET_TEXT = 3,
	QUERY_STR_WIDGET_CANCEL,
	QUERY_STR_WIDGET_OK
};


static void QueryStringWndProc(Window *w, WindowEvent *e)
{
	querystr_d *qs = &WP(w, querystr_d);

	switch (e->event) {
		case WE_CREATE:
			SETBIT(_no_scroll, SCROLL_EDIT);
			break;

		case WE_PAINT:
			SetDParam(0, qs->caption);
			DrawWindowWidgets(w);

			DrawEditBox(w, qs, QUERY_STR_WIDGET_TEXT);
			break;

		case WE_CLICK:
			switch (e->we.click.widget) {
				case QUERY_STR_WIDGET_OK:
		press_ok:;
					if (qs->orig == NULL || strcmp(qs->text.buf, qs->orig) != 0) {
						Window *parent = w->parent;
						qs->handled = true;

						/* If the parent is NULL, the editbox is handled by general function
						 * HandleOnEditText */
						if (parent != NULL) {
							WindowEvent e;
							e.event = WE_ON_EDIT_TEXT;
							e.we.edittext.str = qs->text.buf;
							parent->wndproc(parent, &e);
						} else {
							HandleOnEditText(qs->text.buf);
						}
					}
					/* Fallthrough */
				case QUERY_STR_WIDGET_CANCEL:
					DeleteWindow(w);
					break;
			}
			break;

		case WE_MOUSELOOP:
			HandleEditBox(w, qs, QUERY_STR_WIDGET_TEXT);
			break;

		case WE_KEYPRESS:
			switch (HandleEditBoxKey(w, qs, QUERY_STR_WIDGET_TEXT, e)) {
				case 1: goto press_ok; // Enter pressed, confirms change
				case 2: DeleteWindow(w); break; // ESC pressed, closes window, abandons changes
			}
			break;

		case WE_DESTROY: // Call cancellation of query, if we have not handled it before
			if (!qs->handled && w->parent != NULL) {
				WindowEvent e;
				Window *parent = w->parent;

				qs->handled = true;
				e.event = WE_ON_EDIT_TEXT_CANCEL;
				parent->wndproc(parent, &e);
			}
			CLRBIT(_no_scroll, SCROLL_EDIT);
			break;
		}
}

static const Widget _query_string_widgets[] = {
{   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,        STR_018B_CLOSE_WINDOW},
{    WWT_CAPTION,   RESIZE_NONE,    14,    11,   259,     0,    13, STR_012D,        STR_NULL},
{      WWT_PANEL,   RESIZE_NONE,    14,     0,   259,    14,    29, 0x0,             STR_NULL},
{      WWT_PANEL,   RESIZE_NONE,    14,     2,   257,    16,    27, 0x0,             STR_NULL},
{    WWT_TEXTBTN,   RESIZE_NONE,    14,     0,   129,    30,    41, STR_012E_CANCEL, STR_NULL},
{    WWT_TEXTBTN,   RESIZE_NONE,    14,   130,   259,    30,    41, STR_012F_OK,     STR_NULL},
{   WIDGETS_END},
};

static const WindowDesc _query_string_desc = {
	190, 219, 260, 42,
	WC_QUERY_STRING, WC_NONE,
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
	_query_string_widgets,
	QueryStringWndProc
};

static char _edit_str_buf[64];

/** Show a query popup window with a textbox in it.
 * @param str StringID for the text shown in the textbox
 * @param caption StringID of text shown in caption of querywindow
 * @param maxlen maximum length in characters allowed. If bit 12 is set we
 * will not check the resulting string against to original string to return success
 * @param maxwidth maximum width in pixels allowed
 * @param parent pointer to a Window that will handle the events (ok/cancel) of this
 * window. If NULL, results are handled by global function HandleOnEditText
 * @param afilter filters out unwanted character input */
void ShowQueryString(StringID str, StringID caption, uint maxlen, uint maxwidth, Window *parent, CharSetFilter afilter)
{
	static char orig_str_buf[lengthof(_edit_str_buf)];
	Window *w;
	uint realmaxlen = maxlen & ~0x1000;

	assert(realmaxlen < lengthof(_edit_str_buf));

	DeleteWindowById(WC_QUERY_STRING, 0);
	DeleteWindowById(WC_SAVELOAD, 0);

	w = AllocateWindowDesc(&_query_string_desc);
	w->parent = parent;

	GetString(_edit_str_buf, str, lastof(_edit_str_buf));
	_edit_str_buf[realmaxlen - 1] = '\0';

	if (maxlen & 0x1000) {
		WP(w, querystr_d).orig = NULL;
	} else {
		strecpy(orig_str_buf, _edit_str_buf, lastof(orig_str_buf));
		WP(w, querystr_d).orig = orig_str_buf;
	}

	LowerWindowWidget(w, QUERY_STR_WIDGET_TEXT);
	WP(w, querystr_d).caption = caption;
	WP(w, querystr_d).afilter = afilter;
	InitializeTextBuffer(&WP(w, querystr_d).text, _edit_str_buf, realmaxlen, maxwidth);
}


enum QueryWidgets {
	QUERY_WIDGET_CAPTION = 1,
	QUERY_WIDGET_NO = 3,
	QUERY_WIDGET_YES
};


struct query_d {
	void (*proc)(Window*, bool); ///< callback function executed on closing of popup. Window* points to parent, bool is true if 'yes' clicked, false otherwise
	StringID message;            ///< message shown for query window
	uint32 params[20];           ///< local copy of _decode_parameters
	bool calledback;             ///< has callback been executed already (internal usage for WE_DESTROY event)
};
assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(query_d));


static void QueryWndProc(Window *w, WindowEvent *e)
{
	query_d *q = &WP(w, query_d);

	switch (e->event) {
		case WE_PAINT:
			COPY_IN_DPARAM(0, q->params, lengthof(q->params));
			DrawWindowWidgets(w);
			COPY_IN_DPARAM(0, q->params, lengthof(q->params));

			DrawStringMultiCenter(w->width / 2, (w->height / 2) - 10, q->message, w->width - 2);
			break;

		case WE_CLICK:
			switch (e->we.click.widget) {
				case QUERY_WIDGET_YES:
					q->calledback = true;
					if (q->proc != NULL) q->proc(w->parent, true);
					/* Fallthrough */
				case QUERY_WIDGET_NO:
					DeleteWindow(w);
					break;
				}
			break;

		case WE_KEYPRESS: // ESC closes the window, Enter confirms the action
			switch (e->we.keypress.keycode) {
				case WKC_RETURN:
				case WKC_NUM_ENTER:
					q->calledback = true;
					if (q->proc != NULL) q->proc(w->parent, true);
					/* Fallthrough */
				case WKC_ESC:
					e->we.keypress.cont = false;
					DeleteWindow(w);
					break;
			}
			break;

		case WE_DESTROY: // Call callback function (if any) on window close if not yet called
			if (!q->calledback && q->proc != NULL) {
				q->calledback = true;
				q->proc(w->parent, false);
			}
			break;
	}
}


static const Widget _query_widgets[] = {
{  WWT_CLOSEBOX, RESIZE_NONE,  4,   0,  10,   0,  13, STR_00C5,        STR_018B_CLOSE_WINDOW},
{   WWT_CAPTION, RESIZE_NONE,  4,  11, 209,   0,  13, STR_NULL,        STR_NULL},
{     WWT_PANEL, RESIZE_NONE,  4,   0, 209,  14,  81, 0x0, /*OVERRIDE*/STR_NULL},
{WWT_PUSHTXTBTN, RESIZE_NONE,  3,  20,  90,  62,  73, STR_00C9_NO,     STR_NULL},
{WWT_PUSHTXTBTN, RESIZE_NONE,  3, 120, 190,  62,  73, STR_00C8_YES,    STR_NULL},
{   WIDGETS_END },
};

static const WindowDesc _query_desc = {
	WDP_CENTER, WDP_CENTER, 210, 82,
	WC_CONFIRM_POPUP_QUERY, WC_NONE,
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_UNCLICK_BUTTONS | WDF_DEF_WIDGET | WDF_MODAL,
	_query_widgets,
	QueryWndProc
};

/** Show a modal confirmation window with standard 'yes' and 'no' buttons
 * The window is aligned to the centre of its parent.
 * NOTE: You cannot use BindCString as parameter for this window!
 * @param caption string shown as window caption
 * @param message string that will be shown for the window
 * @param parent pointer to parent window, if this pointer is NULL the parent becomes
 * the main window WC_MAIN_WINDOW
 * @param x,y coordinates to show the window at
 * @param yes_no_callback callback function called when window is closed through any button */
void ShowQuery(StringID caption, StringID message, Window *parent, void (*callback)(Window*, bool))
{
	Window *w = AllocateWindowDesc(&_query_desc);
	if (w == NULL) return;

	if (parent == NULL) parent = FindWindowById(WC_MAIN_WINDOW, 0);
	w->parent = parent;
	w->left = parent->left + (parent->width / 2) - (w->width / 2);
	w->top = parent->top + (parent->height / 2) - (w->height / 2);

	/* Create a backup of the variadic arguments to strings because it will be
	 * overridden pretty often. We will copy these back for drawing */
	COPY_OUT_DPARAM(WP(w, query_d).params, 0, lengthof(WP(w, query_d).params));
	w->widget[QUERY_WIDGET_CAPTION].data = caption;
	WP(w, query_d).message    = message;
	WP(w, query_d).proc       = callback;
	WP(w, query_d).calledback = false;
}


static const Widget _load_dialog_widgets[] = {
{   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,         STR_018B_CLOSE_WINDOW},
{    WWT_CAPTION,  RESIZE_RIGHT,    14,    11,   256,     0,    13, STR_NULL,         STR_018C_WINDOW_TITLE_DRAG_THIS},
{ WWT_PUSHTXTBTN,   RESIZE_NONE,    14,     0,   127,    14,    25, STR_SORT_BY_NAME, STR_SORT_ORDER_TIP},
{ WWT_PUSHTXTBTN,   RESIZE_NONE,    14,   128,   256,    14,    25, STR_SORT_BY_DATE, STR_SORT_ORDER_TIP},
{      WWT_PANEL,  RESIZE_RIGHT,    14,     0,   256,    26,    47, 0x0,              STR_NULL},
{      WWT_PANEL,     RESIZE_RB,    14,     0,   256,    48,   293, 0x0,              STR_NULL},
{ WWT_PUSHIMGBTN,     RESIZE_LR,    14,   245,   256,    48,    59, SPR_HOUSE_ICON,   STR_SAVELOAD_HOME_BUTTON},
{      WWT_INSET,     RESIZE_RB,    14,     2,   243,    50,   291, 0x0,              STR_400A_LIST_OF_DRIVES_DIRECTORIES},
{  WWT_SCROLLBAR,    RESIZE_LRB,    14,   245,   256,    60,   281, 0x0,              STR_0190_SCROLL_BAR_SCROLLS_LIST},
{  WWT_RESIZEBOX,   RESIZE_LRTB,    14,   245,   256,   282,   293, 0x0,              STR_RESIZE_BUTTON},
{   WIDGETS_END},
};

static const Widget _save_dialog_widgets[] = {
{   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,         STR_018B_CLOSE_WINDOW},
{    WWT_CAPTION,  RESIZE_RIGHT,    14,    11,   256,     0,    13, STR_NULL,         STR_018C_WINDOW_TITLE_DRAG_THIS},
{ WWT_PUSHTXTBTN,   RESIZE_NONE,    14,     0,   127,    14,    25, STR_SORT_BY_NAME, STR_SORT_ORDER_TIP},
{ WWT_PUSHTXTBTN,   RESIZE_NONE,    14,   128,   256,    14,    25, STR_SORT_BY_DATE, STR_SORT_ORDER_TIP},
{      WWT_PANEL,  RESIZE_RIGHT,    14,     0,   256,    26,    47, 0x0,              STR_NULL},
{      WWT_PANEL,     RESIZE_RB,    14,     0,   256,    48,   291, 0x0,              STR_NULL},
{ WWT_PUSHIMGBTN,     RESIZE_LR,    14,   245,   256,    48,    59, SPR_HOUSE_ICON,   STR_SAVELOAD_HOME_BUTTON},
{      WWT_INSET,     RESIZE_RB,    14,     2,   243,    50,   290, 0x0,              STR_400A_LIST_OF_DRIVES_DIRECTORIES},
{  WWT_SCROLLBAR,    RESIZE_LRB,    14,   245,   256,    60,   291, 0x0,              STR_0190_SCROLL_BAR_SCROLLS_LIST},
{      WWT_PANEL,    RESIZE_RTB,    14,     0,   256,   292,   307, 0x0,              STR_NULL},
{      WWT_PANEL,    RESIZE_RTB,    14,     2,   254,   294,   305, 0x0,              STR_400B_CURRENTLY_SELECTED_NAME},
{ WWT_PUSHTXTBTN,     RESIZE_TB,    14,     0,   127,   308,   319, STR_4003_DELETE,  STR_400C_DELETE_THE_CURRENTLY_SELECTED},
{ WWT_PUSHTXTBTN,     RESIZE_TB,    14,   128,   244,   308,   319, STR_4002_SAVE,    STR_400D_SAVE_THE_CURRENT_GAME_USING},
{  WWT_RESIZEBOX,   RESIZE_LRTB,    14,   245,   256,   308,   319, 0x0,              STR_RESIZE_BUTTON},
{   WIDGETS_END},
};

/* Colors for fios types */
const byte _fios_colors[] = {13, 9, 9, 6, 5, 6, 5, 6, 6, 8};

void BuildFileList()
{
	_fios_path_changed = true;
	FiosFreeSavegameList();

	switch (_saveload_mode) {
		case SLD_NEW_GAME:
		case SLD_LOAD_SCENARIO:
		case SLD_SAVE_SCENARIO:
			_fios_list = FiosGetScenarioList(_saveload_mode); break;
		case SLD_LOAD_HEIGHTMAP:
			_fios_list = FiosGetHeightmapList(_saveload_mode); break;

		default: _fios_list = FiosGetSavegameList(_saveload_mode); break;
	}
}

static void DrawFiosTexts(uint maxw)
{
	static const char *path = NULL;
	static StringID str = STR_4006_UNABLE_TO_READ_DRIVE;
	static uint32 tot = 0;

	if (_fios_path_changed) {
		str = FiosGetDescText(&path, &tot);
		_fios_path_changed = false;
	}

	if (str != STR_4006_UNABLE_TO_READ_DRIVE) SetDParam(0, tot);
	DrawString(2, 37, str, 0);
	DoDrawStringTruncated(path, 2, 27, 16, maxw);
}

static void MakeSortedSaveGameList()
{
	uint sort_start = 0;
	uint sort_end = 0;
	uint s_amount;
	int i;

	/* Directories are always above the files (FIOS_TYPE_DIR)
	 * Drives (A:\ (windows only) are always under the files (FIOS_TYPE_DRIVE)
	 * Only sort savegames/scenarios, not directories
	 */
	for (i = 0; i < _fios_num; i++) {
		switch (_fios_list[i].type) {
			case FIOS_TYPE_DIR:    sort_start++; break;
			case FIOS_TYPE_PARENT: sort_start++; break;
			case FIOS_TYPE_DRIVE:  sort_end++;   break;
		}
	}

	s_amount = _fios_num - sort_start - sort_end;
	if (s_amount > 0)
		qsort(_fios_list + sort_start, s_amount, sizeof(FiosItem), compare_FiosItems);
}

static void GenerateFileName()
{
	/* Check if we are not a specatator who wants to generate a name..
	    Let's use the name of player #0 for now. */
	const Player *p = GetPlayer(IsValidPlayer(_local_player) ? _local_player : PLAYER_FIRST);

	SetDParam(0, p->name_1);
	SetDParam(1, p->name_2);
	SetDParam(2, _date);
	GetString(_edit_str_buf, STR_4004, lastof(_edit_str_buf));
}

extern void StartupEngines();

static void SaveLoadDlgWndProc(Window *w, WindowEvent *e)
{
	static FiosItem o_dir;

	switch (e->event) {
	case WE_CREATE: { // Set up OPENTTD button
		o_dir.type = FIOS_TYPE_DIRECT;
		switch (_saveload_mode) {
			case SLD_SAVE_GAME:
			case SLD_LOAD_GAME:
				ttd_strlcpy(&o_dir.name[0], _paths.save_dir, sizeof(o_dir.name));
				break;

			case SLD_SAVE_SCENARIO:
			case SLD_LOAD_SCENARIO:
				ttd_strlcpy(&o_dir.name[0], _paths.scenario_dir, sizeof(o_dir.name));
				break;

			case SLD_LOAD_HEIGHTMAP:
				ttd_strlcpy(&o_dir.name[0], _paths.heightmap_dir, sizeof(o_dir.name));
				break;

			default:
				ttd_strlcpy(&o_dir.name[0], _paths.personal_dir, sizeof(o_dir.name));
		}
		break;
		}

	case WE_PAINT: {
		int pos;
		int y;

		SetVScrollCount(w, _fios_num);
		DrawWindowWidgets(w);
		DrawFiosTexts(w->width);

		if (_savegame_sort_dirty) {
			_savegame_sort_dirty = false;
			MakeSortedSaveGameList();
		}

		GfxFillRect(w->widget[7].left + 1, w->widget[7].top + 1, w->widget[7].right, w->widget[7].bottom, 0xD7);
		DoDrawString(
			_savegame_sort_order & SORT_DESCENDING ? DOWNARROW : UPARROW,
			_savegame_sort_order & SORT_BY_NAME ? w->widget[2].right - 9 : w->widget[3].right - 9,
			15, 16
		);

		y = w->widget[7].top + 1;
		for (pos = w->vscroll.pos; pos < _fios_num; pos++) {
			const FiosItem *item = _fios_list + pos;

			DoDrawStringTruncated(item->title, 4, y, _fios_colors[item->type], w->width - 18);
			y += 10;
			if (y >= w->vscroll.cap * 10 + w->widget[7].top + 1) break;
		}

		if (_saveload_mode == SLD_SAVE_GAME || _saveload_mode == SLD_SAVE_SCENARIO) {
			DrawEditBox(w, &WP(w,querystr_d), 10);
		}
		break;
	}

	case WE_CLICK:
		switch (e->we.click.widget) {
		case 2: // Sort save names by name
			_savegame_sort_order = (_savegame_sort_order == SORT_BY_NAME) ?
				SORT_BY_NAME | SORT_DESCENDING : SORT_BY_NAME;
			_savegame_sort_dirty = true;
			SetWindowDirty(w);
			break;

		case 3: // Sort save names by date
			_savegame_sort_order = (_savegame_sort_order == SORT_BY_DATE) ?
				SORT_BY_DATE | SORT_DESCENDING : SORT_BY_DATE;
			_savegame_sort_dirty = true;
			SetWindowDirty(w);
			break;

		case 6: // OpenTTD 'button', jumps to OpenTTD directory
			FiosBrowseTo(&o_dir);
			SetWindowDirty(w);
			BuildFileList();
			break;

		case 7: { // Click the listbox
			int y = (e->we.click.pt.y - w->widget[e->we.click.widget].top - 1) / 10;
			char *name;
			const FiosItem *file;

			if (y < 0 || (y += w->vscroll.pos) >= w->vscroll.count) return;

			file = _fios_list + y;

			name = FiosBrowseTo(file);
			if (name != NULL) {
				if (_saveload_mode == SLD_LOAD_GAME || _saveload_mode == SLD_LOAD_SCENARIO) {
					_switch_mode = (_game_mode == GM_EDITOR) ? SM_LOAD_SCENARIO : SM_LOAD;

					SetFiosType(file->type);
					ttd_strlcpy(_file_to_saveload.name, name, sizeof(_file_to_saveload.name));
					ttd_strlcpy(_file_to_saveload.title, file->title, sizeof(_file_to_saveload.title));

					DeleteWindow(w);
				} else if (_saveload_mode == SLD_LOAD_HEIGHTMAP) {
					SetFiosType(file->type);
					ttd_strlcpy(_file_to_saveload.name, name, sizeof(_file_to_saveload.name));
					ttd_strlcpy(_file_to_saveload.title, file->title, sizeof(_file_to_saveload.title));

					DeleteWindow(w);
					ShowHeightmapLoad();
				} else {
					/* SLD_SAVE_GAME, SLD_SAVE_SCENARIO copy clicked name to editbox */
					ttd_strlcpy(WP(w, querystr_d).text.buf, file->title, WP(w, querystr_d).text.maxlength);
					UpdateTextBufferSize(&WP(w, querystr_d).text);
					InvalidateWidget(w, 10);
				}
			} else {
				/* Changed directory, need repaint. */
				SetWindowDirty(w);
				BuildFileList();
			}
			break;
		}

		case 11: case 12: // Delete, Save game
			break;
		}
		break;
	case WE_MOUSELOOP:
		if (_saveload_mode == SLD_SAVE_GAME || _saveload_mode == SLD_SAVE_SCENARIO) {
			HandleEditBox(w, &WP(w, querystr_d), 10);
		}
		break;
	case WE_KEYPRESS:
		if (e->we.keypress.keycode == WKC_ESC) {
			DeleteWindow(w);
			return;
		}

		if (_saveload_mode == SLD_SAVE_GAME || _saveload_mode == SLD_SAVE_SCENARIO) {
			if (HandleEditBoxKey(w, &WP(w, querystr_d), 10, e) == 1) // Press Enter
					HandleButtonClick(w, 12);
		}
		break;
	case WE_TIMEOUT:
		/* This test protects against using widgets 11 and 12 which are only available
		 * in those two saveload mode  */
		if (!(_saveload_mode == SLD_SAVE_GAME || _saveload_mode == SLD_SAVE_SCENARIO)) break;

		if (IsWindowWidgetLowered(w, 11)) { // Delete button clicked
			if (!FiosDelete(WP(w,querystr_d).text.buf)) {
				ShowErrorMessage(INVALID_STRING_ID, STR_4008_UNABLE_TO_DELETE_FILE, 0, 0);
			} else {
				BuildFileList();
				/* Reset file name to current date on successfull delete */
				if (_saveload_mode == SLD_SAVE_GAME) GenerateFileName();
			}

			UpdateTextBufferSize(&WP(w, querystr_d).text);
			SetWindowDirty(w);
		} else if (IsWindowWidgetLowered(w, 12)) { // Save button clicked
			_switch_mode = SM_SAVE;
			FiosMakeSavegameName(_file_to_saveload.name, WP(w,querystr_d).text.buf, sizeof(_file_to_saveload.name));

			/* In the editor set up the vehicle engines correctly (date might have changed) */
			if (_game_mode == GM_EDITOR) StartupEngines();
		}
		break;
	case WE_DESTROY:
		/* pause is only used in single-player, non-editor mode, non menu mode */
		if (!_networking && _game_mode != GM_EDITOR && _game_mode != GM_MENU) {
			DoCommandP(0, 0, 0, NULL, CMD_PAUSE);
		}
		FiosFreeSavegameList();
		CLRBIT(_no_scroll, SCROLL_SAVE);
		break;
	case WE_RESIZE: {
		/* Widget 2 and 3 have to go with halve speed, make it so obiwan */
		uint diff = e->we.sizing.diff.x / 2;
		w->widget[2].right += diff;
		w->widget[3].left  += diff;
		w->widget[3].right += e->we.sizing.diff.x;

		/* Same for widget 11 and 12 in save-dialog */
		if (_saveload_mode == SLD_SAVE_GAME || _saveload_mode == SLD_SAVE_SCENARIO) {
			w->widget[11].right += diff;
			w->widget[12].left  += diff;
			w->widget[12].right += e->we.sizing.diff.x;
		}

		w->vscroll.cap += e->we.sizing.diff.y / 10;
		} break;
	}
}

static const WindowDesc _load_dialog_desc = {
	WDP_CENTER, WDP_CENTER, 257, 294,
	WC_SAVELOAD, WC_NONE,
	WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_STD_BTN | WDF_UNCLICK_BUTTONS | WDF_RESIZABLE,
	_load_dialog_widgets,
	SaveLoadDlgWndProc,
};

static const WindowDesc _save_dialog_desc = {
	WDP_CENTER, WDP_CENTER, 257, 320,
	WC_SAVELOAD, WC_NONE,
	WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_STD_BTN | WDF_UNCLICK_BUTTONS | WDF_RESIZABLE,
	_save_dialog_widgets,
	SaveLoadDlgWndProc,
};

void ShowSaveLoadDialog(int mode)
{
	static const StringID saveload_captions[] = {
		STR_4001_LOAD_GAME,
		STR_0298_LOAD_SCENARIO,
		STR_4000_SAVE_GAME,
		STR_0299_SAVE_SCENARIO,
		STR_4011_LOAD_HEIGHTMAP,
	};

	Window *w;
	const WindowDesc *sld = &_save_dialog_desc;


	SetObjectToPlace(SPR_CURSOR_ZZZ, PAL_NONE, 0, WC_MAIN_WINDOW, 0);
	DeleteWindowById(WC_QUERY_STRING, 0);
	DeleteWindowById(WC_SAVELOAD, 0);

	_saveload_mode = mode;
	SETBIT(_no_scroll, SCROLL_SAVE);

	switch (mode) {
		case SLD_SAVE_GAME:     GenerateFileName(); break;
		case SLD_SAVE_SCENARIO: strcpy(_edit_str_buf, "UNNAMED"); break;
		default:                sld = &_load_dialog_desc; break;
	}

	assert((uint)mode < lengthof(saveload_captions));
	w = AllocateWindowDesc(sld);
	w->widget[1].data = saveload_captions[mode];
	w->vscroll.cap = 24;
	w->resize.step_width = 2;
	w->resize.step_height = 10;
	w->resize.height = w->height - 14 * 10; // Minimum of 10 items
	LowerWindowWidget(w, 7);

	WP(w, querystr_d).afilter = CS_ALPHANUMERAL;
	InitializeTextBuffer(&WP(w, querystr_d).text, _edit_str_buf, lengthof(_edit_str_buf), 240);

	/* pause is only used in single-player, non-editor mode, non-menu mode. It
	 * will be unpaused in the WE_DESTROY event handler. */
	if (_game_mode != GM_MENU && !_networking && _game_mode != GM_EDITOR) {
		DoCommandP(0, 1, 0, NULL, CMD_PAUSE);
	}

	BuildFileList();

	ResetObjectToPlace();
}

void RedrawAutosave()
{
	SetWindowDirty(FindWindowById(WC_STATUS_BAR, 0));
}

void SetFiosType(const byte fiostype)
{
	switch (fiostype) {
		case FIOS_TYPE_FILE:
		case FIOS_TYPE_SCENARIO:
			_file_to_saveload.mode = SL_LOAD;
			break;

		case FIOS_TYPE_OLDFILE:
		case FIOS_TYPE_OLD_SCENARIO:
			_file_to_saveload.mode = SL_OLD_LOAD;
			break;

#ifdef WITH_PNG
		case FIOS_TYPE_PNG:
			_file_to_saveload.mode = SL_PNG;
			break;
#endif /* WITH_PNG */

		case FIOS_TYPE_BMP:
			_file_to_saveload.mode = SL_BMP;
			break;

		default:
			_file_to_saveload.mode = SL_INVALID;
			break;
	}
}

static int32 ClickMoneyCheat(int32 p1, int32 p2)
{
		DoCommandP(0, 10000000, 0, NULL, CMD_MONEY_CHEAT);
		return true;
}

/**
 * @param p1 player to set to
 * @param p2 is -1 or +1 (down/up)
 */
static int32 ClickChangePlayerCheat(int32 p1, int32 p2)
{
	while (IsValidPlayer((PlayerID)p1)) {
		if (_players[p1].is_active) {
			SetLocalPlayer((PlayerID)p1);

			MarkWholeScreenDirty();
			return _local_player;
		}
		p1 += p2;
	}

	return _local_player;
}

/**
 * @param p1 -1 or +1 (down/up)
 */
static int32 ClickChangeClimateCheat(int32 p1, int32 p2)
{
	if (p1 == -1) p1 = 3;
	if (p1 ==  4) p1 = 0;
	_opt.landscape = p1;
	ReloadNewGRFData();
	return _opt.landscape;
}

extern void EnginesMonthlyLoop();

/**
 * @param p2 1 (increase) or -1 (decrease)
 */
static int32 ClickChangeDateCheat(int32 p1, int32 p2)
{
	YearMonthDay ymd;
	ConvertDateToYMD(_date, &ymd);

	if ((ymd.year == MIN_YEAR && p2 == -1) || (ymd.year == MAX_YEAR && p2 == 1)) return _cur_year;

	SetDate(ConvertYMDToDate(_cur_year + p2, ymd.month, ymd.day));
	EnginesMonthlyLoop();
	SetWindowDirty(FindWindowById(WC_STATUS_BAR, 0));
	return _cur_year;
}

typedef int32 CheckButtonClick(int32, int32);

enum ce_flags_long
{
	CE_NONE = 0,
	CE_CLICK = 1 << 0,
	CE_END = 1 << 1,
};

/** Define basic enum properties */
template <> struct EnumPropsT<ce_flags_long> : MakeEnumPropsT<ce_flags_long, byte, CE_NONE, CE_END, CE_END> {};
typedef TinyEnumT<ce_flags_long> ce_flags;


struct CheatEntry {
	VarType type;          ///< type of selector
	ce_flags flags;        ///< selector flags
	StringID str;          ///< string with descriptive text
	void *variable;        ///< pointer to the variable
	bool *been_used;       ///< has this cheat been used before?
	CheckButtonClick *proc;///< procedure
	int16 min, max;        ///< range for spinbox setting
};

static const CheatEntry _cheats_ui[] = {
	{SLE_BOOL, {CE_CLICK}, STR_CHEAT_MONEY,          &_cheats.money.value,           &_cheats.money.been_used,           &ClickMoneyCheat,         0,  0},
	{SLE_UINT8, {CE_NONE}, STR_CHEAT_CHANGE_PLAYER,  &_local_player,                 &_cheats.switch_player.been_used,   &ClickChangePlayerCheat,  0, 11},
	{SLE_BOOL,  {CE_NONE}, STR_CHEAT_EXTRA_DYNAMITE, &_cheats.magic_bulldozer.value, &_cheats.magic_bulldozer.been_used, NULL,                     0,  0},
	{SLE_BOOL,  {CE_NONE}, STR_CHEAT_CROSSINGTUNNELS,&_cheats.crossing_tunnels.value,&_cheats.crossing_tunnels.been_used,NULL,                     0,  0},
	{SLE_BOOL,  {CE_NONE}, STR_CHEAT_BUILD_IN_PAUSE, &_cheats.build_in_pause.value,  &_cheats.build_in_pause.been_used,  NULL,                     0,  0},
	{SLE_BOOL,  {CE_NONE}, STR_CHEAT_NO_JETCRASH,    &_cheats.no_jetcrash.value,     &_cheats.no_jetcrash.been_used,     NULL,                     0,  0},
	{SLE_BOOL,  {CE_NONE}, STR_CHEAT_SETUP_PROD,     &_cheats.setup_prod.value,      &_cheats.setup_prod.been_used,      NULL,                     0,  0},
	{SLE_UINT8, {CE_NONE}, STR_CHEAT_SWITCH_CLIMATE, &_opt.landscape,                &_cheats.switch_climate.been_used,  &ClickChangeClimateCheat,-1,  4},
	{SLE_INT32, {CE_NONE}, STR_CHEAT_CHANGE_DATE,    &_cur_year,                     &_cheats.change_date.been_used,     &ClickChangeDateCheat,   -1,  1},
};


static const Widget _cheat_widgets[] = {
{   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,   STR_018B_CLOSE_WINDOW},
{    WWT_CAPTION,   RESIZE_NONE,    14,    11,   399,     0,    13, STR_CHEATS, STR_018C_WINDOW_TITLE_DRAG_THIS},
{      WWT_PANEL,   RESIZE_NONE,    14,     0,   399,    14,   169, 0x0,        STR_NULL},
{      WWT_PANEL,   RESIZE_NONE,    14,     0,   399,    14,   169, 0x0,        STR_CHEATS_TIP},
{   WIDGETS_END},
};

static void CheatsWndProc(Window *w, WindowEvent *e)
{
	switch (e->event) {
	case WE_PAINT: {
		int clk = WP(w,def_d).data_1;
		int x, y;
		int i;

		DrawWindowWidgets(w);

		DrawStringMultiCenter(200, 25, STR_CHEATS_WARNING, w->width - 50);

		x = 0;
		y = 45;

		for (i = 0; i != lengthof(_cheats_ui); i++) {
			const CheatEntry *ce = &_cheats_ui[i];

			DrawSprite((*ce->been_used) ? SPR_BOX_CHECKED : SPR_BOX_EMPTY, PAL_NONE, x + 5, y + 2);

			switch (ce->type) {
			case SLE_BOOL: {
				bool on = (*(bool*)ce->variable);

				if (ce->flags & CE_CLICK) {
					DrawFrameRect(x + 20, y + 1, x + 30 + 9, y + 9, 0, (clk - (i * 2) == 1) ? FR_LOWERED : FR_NONE);
					if (i == 0) { // XXX - hack/hack for first element which is increase money. Told ya it's a mess
						SetDParam64(0, 10000000);
					} else {
						SetDParam(0, false);
					}
				} else {
					DrawFrameRect(x + 20, y + 1, x + 30 + 9, y + 9, on ? 6 : 4, on ? FR_LOWERED : FR_NONE);
					SetDParam(0, on ? STR_CONFIG_PATCHES_ON : STR_CONFIG_PATCHES_OFF);
				}
			} break;
			default: {
				int32 val = (int32)ReadValue(ce->variable, ce->type);
				char buf[512];

				/* Draw [<][>] boxes for settings of an integer-type */
				DrawArrowButtons(x + 20, y, 3, clk - (i * 2), true, true);

				switch (ce->str) {
				/* Display date for change date cheat */
				case STR_CHEAT_CHANGE_DATE: SetDParam(0, _date); break;
				/* Draw colored flag for change player cheat */
				case STR_CHEAT_CHANGE_PLAYER:
					SetDParam(0, val);
					GetString(buf, STR_CHEAT_CHANGE_PLAYER, lastof(buf));
					DrawPlayerIcon(_current_player, 60 + GetStringBoundingBox(buf).width, y + 2);
					break;
				/* Set correct string for switch climate cheat */
				case STR_CHEAT_SWITCH_CLIMATE: val += STR_TEMPERATE_LANDSCAPE;
				/* Fallthrough */
				default: SetDParam(0, val);
				}
			} break;
			}

			DrawString(50, y + 1, ce->str, 0);

			y += 12;
		}
		break;
	}

	case WE_CLICK: {
			const CheatEntry *ce;
			uint btn = (e->we.click.pt.y - 46) / 12;
			int32 value, oldvalue;
			uint x = e->we.click.pt.x;

			/* not clicking a button? */
			if (!IS_INT_INSIDE(x, 20, 40) || btn >= lengthof(_cheats_ui)) break;

			ce = &_cheats_ui[btn];
			oldvalue = value = (int32)ReadValue(ce->variable, ce->type);

			*ce->been_used = true;

			switch (ce->type) {
			case SLE_BOOL:
				if (ce->flags & CE_CLICK) WP(w,def_d).data_1 = btn * 2 + 1;
				value ^= 1;
				if (ce->proc != NULL) ce->proc(value, 0);
				break;
			default: {
				/* Add a dynamic step-size to the scroller. In a maximum of
				 * 50-steps you should be able to get from min to max */
				uint16 step = ((ce->max - ce->min) / 20);
				if (step == 0) step = 1;

				/* Increase or decrease the value and clamp it to extremes */
				value += (x >= 30) ? step : -step;
				value = clamp(value, ce->min, ce->max);

				/* take whatever the function returns */
				value = ce->proc(value, (x >= 30) ? 1 : -1);

				if (value != oldvalue) {
					WP(w,def_d).data_1 = btn * 2 + 1 + ((x >= 30) ? 1 : 0);
				}
			} break;
			}

			if (value != oldvalue) {
				WriteValue(ce->variable, ce->type, (int64)value);
				SetWindowDirty(w);
			}

			w->flags4 |= 5 << WF_TIMEOUT_SHL;

			SetWindowDirty(w);
		}
		break;
	case WE_TIMEOUT:
		WP(w,def_d).data_1 = 0;
		SetWindowDirty(w);
		break;
	}
}

static const WindowDesc _cheats_desc = {
	240, 22, 400, 170,
	WC_CHEATS, WC_NONE,
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
	_cheat_widgets,
	CheatsWndProc
};


void ShowCheatWindow()
{
	DeleteWindowById(WC_CHEATS, 0);
	AllocateWindowDesc(&_cheats_desc);
}