changeset 9179:3b7b689031d6 draft

(svn r13042) -Codechange: make a class of CreateScenarioWindow, GenerateLandscapeWindow, NetworkChatWindow, NetworkCompanyPasswordWindow, NetworkGameWindow, NetworkStartServerWindow, QueryStringWindow, SaveLoadWindow. All these classes depended on the 'querystr_d' object which is now put into QueryStringBaseWindow. As a side effect this removes quite a lot of WP macro usages and a few global variables.
author rubidium <rubidium@openttd.org>
date Sun, 11 May 2008 12:26:20 +0000
parents 708347596d9a
children 50eedf1d551b
files src/genworld_gui.cpp src/misc_gui.cpp src/network/network_gui.cpp src/osk_gui.cpp src/querystring_gui.h src/signs_gui.cpp src/textbuf_gui.h
diffstat 7 files changed, 1894 insertions(+), 1823 deletions(-) [+]
line wrap: on
line diff
--- a/src/genworld_gui.cpp
+++ b/src/genworld_gui.cpp
@@ -29,6 +29,7 @@
 #include "widgets/dropdown_func.h"
 #include "core/random_func.hpp"
 #include "landscape_type.h"
+#include "querystring_gui.h"
 
 #include "table/strings.h"
 #include "table/sprites.h"
@@ -43,14 +44,6 @@
 	GLWP_END
 };
 
-struct generate_d {
-	uint widget_id;
-	uint x;
-	uint y;
-	char name[64];
-};
-assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(generate_d));
-
 extern void SwitchMode(int new_mode);
 
 static inline void SetNewLandscapeType(byte landscape)
@@ -244,314 +237,324 @@
 	return list;
 }
 
-static void GenerateLandscapeWndProc(Window *w, WindowEvent *e)
-{
-	static const StringID elevations[]  = {STR_682A_VERY_FLAT, STR_682B_FLAT, STR_682C_HILLY, STR_682D_MOUNTAINOUS, INVALID_STRING_ID};
-	static const StringID sea_lakes[]   = {STR_VERY_LOW, STR_6820_LOW, STR_6821_MEDIUM, STR_6822_HIGH, INVALID_STRING_ID};
-	static const StringID smoothness[]  = {STR_CONFIG_PATCHES_ROUGHNESS_OF_TERRAIN_VERY_SMOOTH, STR_CONFIG_PATCHES_ROUGHNESS_OF_TERRAIN_SMOOTH, STR_CONFIG_PATCHES_ROUGHNESS_OF_TERRAIN_ROUGH, STR_CONFIG_PATCHES_ROUGHNESS_OF_TERRAIN_VERY_ROUGH, INVALID_STRING_ID};
-	static const StringID tree_placer[] = {STR_CONFIG_PATCHES_TREE_PLACER_NONE, STR_CONFIG_PATCHES_TREE_PLACER_ORIGINAL, STR_CONFIG_PATCHES_TREE_PLACER_IMPROVED, INVALID_STRING_ID};
-	static const StringID rotation[]    = {STR_CONFIG_PATCHES_HEIGHTMAP_ROTATION_COUNTER_CLOCKWISE, STR_CONFIG_PATCHES_HEIGHTMAP_ROTATION_CLOCKWISE, INVALID_STRING_ID};
-	static const StringID landscape[]   = {STR_CONFIG_PATCHES_LAND_GENERATOR_ORIGINAL, STR_CONFIG_PATCHES_LAND_GENERATOR_TERRA_GENESIS, INVALID_STRING_ID};
-	static const StringID num_towns[]   = {STR_NUM_VERY_LOW, STR_6816_LOW, STR_6817_NORMAL, STR_6818_HIGH, INVALID_STRING_ID};
-	static const StringID num_inds[]    = {STR_NONE, STR_NUM_VERY_LOW, STR_6816_LOW, STR_6817_NORMAL, STR_6818_HIGH, INVALID_STRING_ID};
+static const StringID _elevations[]  = {STR_682A_VERY_FLAT, STR_682B_FLAT, STR_682C_HILLY, STR_682D_MOUNTAINOUS, INVALID_STRING_ID};
+static const StringID _sea_lakes[]   = {STR_VERY_LOW, STR_6820_LOW, STR_6821_MEDIUM, STR_6822_HIGH, INVALID_STRING_ID};
+static const StringID _smoothness[]  = {STR_CONFIG_PATCHES_ROUGHNESS_OF_TERRAIN_VERY_SMOOTH, STR_CONFIG_PATCHES_ROUGHNESS_OF_TERRAIN_SMOOTH, STR_CONFIG_PATCHES_ROUGHNESS_OF_TERRAIN_ROUGH, STR_CONFIG_PATCHES_ROUGHNESS_OF_TERRAIN_VERY_ROUGH, INVALID_STRING_ID};
+static const StringID _tree_placer[] = {STR_CONFIG_PATCHES_TREE_PLACER_NONE, STR_CONFIG_PATCHES_TREE_PLACER_ORIGINAL, STR_CONFIG_PATCHES_TREE_PLACER_IMPROVED, INVALID_STRING_ID};
+static const StringID _rotation[]    = {STR_CONFIG_PATCHES_HEIGHTMAP_ROTATION_COUNTER_CLOCKWISE, STR_CONFIG_PATCHES_HEIGHTMAP_ROTATION_CLOCKWISE, INVALID_STRING_ID};
+static const StringID _landscape[]   = {STR_CONFIG_PATCHES_LAND_GENERATOR_ORIGINAL, STR_CONFIG_PATCHES_LAND_GENERATOR_TERRA_GENESIS, INVALID_STRING_ID};
+static const StringID _num_towns[]   = {STR_NUM_VERY_LOW, STR_6816_LOW, STR_6817_NORMAL, STR_6818_HIGH, INVALID_STRING_ID};
+static const StringID _num_inds[]    = {STR_NONE, STR_NUM_VERY_LOW, STR_6816_LOW, STR_6817_NORMAL, STR_6818_HIGH, INVALID_STRING_ID};
+
+struct GenerateLandscapeWindow : public QueryStringBaseWindow {
+	uint widget_id;
+	uint x;
+	uint y;
+	char name[64];
+	glwp_modes mode;
+
+	GenerateLandscapeWindow(const WindowDesc *desc, void *data = NULL, WindowNumber number = 0) : QueryStringBaseWindow(desc, NULL, number)
+	{
+		this->LowerWidget(_opt_newgame.landscape + GLAND_TEMPERATE);
 
-	/* Data used for the generate seed edit box */
-	static querystr_d _genseed_query;
-	static char _genseed_buffer[11];
+		snprintf(this->edit_str_buf, sizeof(this->edit_str_buf), "%u", _patches_newgame.generation_seed);
+		InitializeTextBuffer(&this->text, this->edit_str_buf, lengthof(this->edit_str_buf), 120);
+		this->caption = STR_NULL;
+		this->afilter = CS_NUMERAL;
 
-	glwp_modes mode = (glwp_modes)w->window_number;
+		this->mode = (glwp_modes)this->window_number;
+
+		this->FindWindowPlacementAndResize(desc);
+	}
 
-	switch (e->event) {
-		case WE_CREATE:
-			w->LowerWidget(_opt_newgame.landscape + GLAND_TEMPERATE);
-
-			snprintf(_genseed_buffer, sizeof(_genseed_buffer), "%u", _patches_newgame.generation_seed);
-			InitializeTextBuffer(&_genseed_query.text, _genseed_buffer, lengthof(_genseed_buffer), 120);
-			_genseed_query.caption = STR_NULL;
-			_genseed_query.afilter = CS_NUMERAL;
-			break;
+	virtual void OnPaint()
+	{
+		/* You can't select smoothness if not terragenesis */
+		if (mode == GLWP_GENERATE) {
+			this->SetWidgetDisabledState(GLAND_SMOOTHNESS_PULLDOWN, _patches_newgame.land_generator == 0);
+		}
+		/* Disable snowline if not hilly */
+		this->SetWidgetDisabledState(GLAND_SNOW_LEVEL_TEXT, _opt_newgame.landscape != LT_ARCTIC);
+		/* Disable town, industry and trees in SE */
+		this->SetWidgetDisabledState(GLAND_TOWN_PULLDOWN,     _game_mode == GM_EDITOR);
+		this->SetWidgetDisabledState(GLAND_INDUSTRY_PULLDOWN, _game_mode == GM_EDITOR);
+		this->SetWidgetDisabledState(GLAND_TREE_PULLDOWN,     _game_mode == GM_EDITOR);
 
-		case WE_PAINT:
-			/* You can't select smoothness if not terragenesis */
-			if (mode == GLWP_GENERATE) {
-				w->SetWidgetDisabledState(GLAND_SMOOTHNESS_PULLDOWN, _patches_newgame.land_generator == 0);
-			}
-			/* Disable snowline if not hilly */
-			w->SetWidgetDisabledState(GLAND_SNOW_LEVEL_TEXT, _opt_newgame.landscape != LT_ARCTIC);
-			/* Disable town, industry and trees in SE */
-			w->SetWidgetDisabledState(GLAND_TOWN_PULLDOWN,     _game_mode == GM_EDITOR);
-			w->SetWidgetDisabledState(GLAND_INDUSTRY_PULLDOWN, _game_mode == GM_EDITOR);
-			w->SetWidgetDisabledState(GLAND_TREE_PULLDOWN,     _game_mode == GM_EDITOR);
+		this->SetWidgetDisabledState(GLAND_START_DATE_DOWN, _patches_newgame.starting_year <= MIN_YEAR);
+		this->SetWidgetDisabledState(GLAND_START_DATE_UP,   _patches_newgame.starting_year >= MAX_YEAR);
+		this->SetWidgetDisabledState(GLAND_SNOW_LEVEL_DOWN, _patches_newgame.snow_line_height <= 2 || _opt_newgame.landscape != LT_ARCTIC);
+		this->SetWidgetDisabledState(GLAND_SNOW_LEVEL_UP,   _patches_newgame.snow_line_height >= MAX_SNOWLINE_HEIGHT || _opt_newgame.landscape != LT_ARCTIC);
 
-			w->SetWidgetDisabledState(GLAND_START_DATE_DOWN, _patches_newgame.starting_year <= MIN_YEAR);
-			w->SetWidgetDisabledState(GLAND_START_DATE_UP,   _patches_newgame.starting_year >= MAX_YEAR);
-			w->SetWidgetDisabledState(GLAND_SNOW_LEVEL_DOWN, _patches_newgame.snow_line_height <= 2 || _opt_newgame.landscape != LT_ARCTIC);
-			w->SetWidgetDisabledState(GLAND_SNOW_LEVEL_UP,   _patches_newgame.snow_line_height >= MAX_SNOWLINE_HEIGHT || _opt_newgame.landscape != LT_ARCTIC);
+		this->SetWidgetLoweredState(GLAND_TEMPERATE, _opt_newgame.landscape == LT_TEMPERATE);
+		this->SetWidgetLoweredState(GLAND_ARCTIC,    _opt_newgame.landscape == LT_ARCTIC);
+		this->SetWidgetLoweredState(GLAND_TROPICAL,  _opt_newgame.landscape == LT_TROPIC);
+		this->SetWidgetLoweredState(GLAND_TOYLAND,   _opt_newgame.landscape == LT_TOYLAND);
+
+		if (_game_mode == GM_EDITOR) {
+			this->widget[GLAND_TOWN_PULLDOWN].data     = STR_6836_OFF;
+			this->widget[GLAND_INDUSTRY_PULLDOWN].data = STR_6836_OFF;
+		} else {
+			this->widget[GLAND_TOWN_PULLDOWN].data     = _num_towns[_opt_newgame.diff.number_towns];
+			this->widget[GLAND_INDUSTRY_PULLDOWN].data = _num_inds[_opt_newgame.diff.number_industries];
+		}
 
-			w->SetWidgetLoweredState(GLAND_TEMPERATE, _opt_newgame.landscape == LT_TEMPERATE);
-			w->SetWidgetLoweredState(GLAND_ARCTIC,    _opt_newgame.landscape == LT_ARCTIC);
-			w->SetWidgetLoweredState(GLAND_TROPICAL,  _opt_newgame.landscape == LT_TROPIC);
-			w->SetWidgetLoweredState(GLAND_TOYLAND,   _opt_newgame.landscape == LT_TOYLAND);
+		if (mode == GLWP_GENERATE) {
+			this->widget[GLAND_LANDSCAPE_PULLDOWN].data  = _landscape[_patches_newgame.land_generator];
+			this->widget[GLAND_TREE_PULLDOWN].data       = _tree_placer[_patches_newgame.tree_placer];
+			this->widget[GLAND_TERRAIN_PULLDOWN].data    = _elevations[_opt_newgame.diff.terrain_type];
+			this->widget[GLAND_WATER_PULLDOWN].data      = _sea_lakes[_opt_newgame.diff.quantity_sea_lakes];
+			this->widget[GLAND_SMOOTHNESS_PULLDOWN].data = _smoothness[_patches_newgame.tgen_smoothness];
+		} else {
+			this->widget[GLAND_TREE_PULLDOWN].data               = _tree_placer[_patches_newgame.tree_placer];
+			this->widget[GLAND_HEIGHTMAP_ROTATION_PULLDOWN].data = _rotation[_patches_newgame.heightmap_rotation];
+		}
 
-			if (_game_mode == GM_EDITOR) {
-				w->widget[GLAND_TOWN_PULLDOWN].data     = STR_6836_OFF;
-				w->widget[GLAND_INDUSTRY_PULLDOWN].data = STR_6836_OFF;
+		/* Set parameters for widget text that requires them. */
+		SetDParam(0, ConvertYMDToDate(_patches_newgame.starting_year, 0, 1)); // GLAND_START_DATE_TEXT
+		SetDParam(1, 1 << _patches_newgame.map_x); // GLAND_MAPSIZE_X_PULLDOWN
+		SetDParam(2, 1 << _patches_newgame.map_y); // GLAND_MAPSIZE_Y_PULLDOWN
+		SetDParam(3, _patches_newgame.snow_line_height); // GLAND_SNOW_LEVEL_TEXT
+
+		DrawWindowWidgets(this);
+
+		this->DrawEditBox(GLAND_RANDOM_EDITBOX);
+
+		if (mode != GLWP_GENERATE) {
+			char buffer[512];
+
+			if (_patches_newgame.heightmap_rotation == HM_CLOCKWISE) {
+				SetDParam(0, this->y);
+				SetDParam(1, this->x);
 			} else {
-				w->widget[GLAND_TOWN_PULLDOWN].data     = num_towns[_opt_newgame.diff.number_towns];
-				w->widget[GLAND_INDUSTRY_PULLDOWN].data = num_inds[_opt_newgame.diff.number_industries];
-			}
-
-			if (mode == GLWP_GENERATE) {
-				w->widget[GLAND_LANDSCAPE_PULLDOWN].data  = landscape[_patches_newgame.land_generator];
-				w->widget[GLAND_TREE_PULLDOWN].data       = tree_placer[_patches_newgame.tree_placer];
-				w->widget[GLAND_TERRAIN_PULLDOWN].data    = elevations[_opt_newgame.diff.terrain_type];
-				w->widget[GLAND_WATER_PULLDOWN].data      = sea_lakes[_opt_newgame.diff.quantity_sea_lakes];
-				w->widget[GLAND_SMOOTHNESS_PULLDOWN].data = smoothness[_patches_newgame.tgen_smoothness];
-			} else {
-				w->widget[GLAND_TREE_PULLDOWN].data               = tree_placer[_patches_newgame.tree_placer];
-				w->widget[GLAND_HEIGHTMAP_ROTATION_PULLDOWN].data = rotation[_patches_newgame.heightmap_rotation];
+				SetDParam(0, this->x);
+				SetDParam(1, this->y);
 			}
+			GetString(buffer, STR_HEIGHTMAP_SIZE, lastof(buffer));
+			DrawStringRightAligned(326, 91, STR_HEIGHTMAP_SIZE, TC_BLACK);
 
-			/* Set parameters for widget text that requires them. */
-			SetDParam(0, ConvertYMDToDate(_patches_newgame.starting_year, 0, 1)); // GLAND_START_DATE_TEXT
-			SetDParam(1, 1 << _patches_newgame.map_x); // GLAND_MAPSIZE_X_PULLDOWN
-			SetDParam(2, 1 << _patches_newgame.map_y); // GLAND_MAPSIZE_Y_PULLDOWN
-			SetDParam(3, _patches_newgame.snow_line_height); // GLAND_SNOW_LEVEL_TEXT
+			DrawString( 12,  91, STR_HEIGHTMAP_NAME, TC_BLACK);
+			SetDParamStr(0, this->name);
+			DrawStringTruncated(114,  91, STR_ORANGE, TC_BLACK, 326 - 114 - GetStringBoundingBox(buffer).width - 5);
+		}
+	}
+
+	virtual void OnClick(Point pt, int widget)
+	{
+		switch (widget) {
+			case 0: delete this; break;
+
+			case GLAND_TEMPERATE:
+			case GLAND_ARCTIC:
+			case GLAND_TROPICAL:
+			case GLAND_TOYLAND:
+				this->RaiseWidget(_opt_newgame.landscape + GLAND_TEMPERATE);
+				SetNewLandscapeType(widget - GLAND_TEMPERATE);
+				break;
 
-			DrawWindowWidgets(w);
+			case GLAND_MAPSIZE_X_PULLDOWN: // Mapsize X
+				ShowDropDownList(this, BuildMapsizeDropDown(), _patches_newgame.map_x, GLAND_MAPSIZE_X_PULLDOWN);
+				break;
+
+			case GLAND_MAPSIZE_Y_PULLDOWN: // Mapsize Y
+				ShowDropDownList(this, BuildMapsizeDropDown(), _patches_newgame.map_y, GLAND_MAPSIZE_Y_PULLDOWN);
+				break;
+
+			case GLAND_TOWN_PULLDOWN: // Number of towns
+				ShowDropDownMenu(this, _num_towns, _opt_newgame.diff.number_towns, GLAND_TOWN_PULLDOWN, 0, 0);
+				break;
 
-			DrawEditBox(w, &_genseed_query, GLAND_RANDOM_EDITBOX);
+			case GLAND_INDUSTRY_PULLDOWN: // Number of industries
+				ShowDropDownMenu(this, _num_inds, _opt_newgame.diff.number_industries, GLAND_INDUSTRY_PULLDOWN, 0, 0);
+				break;
 
-			if (mode != GLWP_GENERATE) {
-				char buffer[512];
+			case GLAND_RANDOM_BUTTON: // Random seed
+				_patches_newgame.generation_seed = InteractiveRandom();
+				snprintf(this->edit_str_buf, lengthof(this->edit_str_buf), "%u", _patches_newgame.generation_seed);
+				UpdateTextBufferSize(&this->text);
+				this->SetDirty();
+				break;
+
+			case GLAND_RANDOM_EDITBOX: // edit box for random seed
+				ShowOnScreenKeyboard(this, GLAND_RANDOM_EDITBOX, 0, 0);
+				break;
 
-				if (_patches_newgame.heightmap_rotation == HM_CLOCKWISE) {
-					SetDParam(0, WP(w, generate_d).y);
-					SetDParam(1, WP(w, generate_d).x);
+			case GLAND_GENERATE_BUTTON: // Generate
+				UpdatePatches();
+
+				if (_patches.town_layout == TL_NO_ROADS) {
+					ShowQuery(
+						STR_TOWN_LAYOUT_WARNING_CAPTION,
+						STR_TOWN_LAYOUT_WARNING_MESSAGE,
+						this,
+						LandscapeGenerationCallback);
+				} else if (mode == GLWP_HEIGHTMAP &&
+						(this->x * 2 < (1U << _patches_newgame.map_x) ||
+						this->x / 2 > (1U << _patches_newgame.map_x) ||
+						this->y * 2 < (1U << _patches_newgame.map_y) ||
+						this->y / 2 > (1U << _patches_newgame.map_y))) {
+					ShowQuery(
+						STR_HEIGHTMAP_SCALE_WARNING_CAPTION,
+						STR_HEIGHTMAP_SCALE_WARNING_MESSAGE,
+						this,
+						LandscapeGenerationCallback);
 				} else {
-					SetDParam(0, WP(w, generate_d).x);
-					SetDParam(1, WP(w, generate_d).y);
+					StartGeneratingLandscape(mode);
 				}
-				GetString(buffer, STR_HEIGHTMAP_SIZE, lastof(buffer));
-				DrawStringRightAligned(326, 91, STR_HEIGHTMAP_SIZE, TC_BLACK);
+				break;
 
-				DrawString( 12,  91, STR_HEIGHTMAP_NAME, TC_BLACK);
-				SetDParamStr(0, WP(w, generate_d).name);
-				DrawStringTruncated(114,  91, STR_ORANGE, TC_BLACK, 326 - 114 - GetStringBoundingBox(buffer).width - 5);
-			}
-			break;
+			case GLAND_START_DATE_DOWN:
+			case GLAND_START_DATE_UP: // Year buttons
+				/* Don't allow too fast scrolling */
+				if ((this->flags4 & WF_TIMEOUT_MASK) <= 2 << WF_TIMEOUT_SHL) {
+					this->HandleButtonClick(widget);
+					this->SetDirty();
+
+					_patches_newgame.starting_year = Clamp(_patches_newgame.starting_year + widget - GLAND_START_DATE_TEXT, MIN_YEAR, MAX_YEAR);
+				}
+				_left_button_clicked = false;
+				break;
 
-		case WE_CLICK:
-			switch (e->we.click.widget) {
-				case 0: delete w; break;
+			case GLAND_START_DATE_TEXT: // Year text
+				this->widget_id = GLAND_START_DATE_TEXT;
+				SetDParam(0, _patches_newgame.starting_year);
+				ShowQueryString(STR_CONFIG_PATCHES_INT32, STR_START_DATE_QUERY_CAPT, 8, 100, this, CS_NUMERAL);
+				break;
+
+			case GLAND_SNOW_LEVEL_DOWN:
+			case GLAND_SNOW_LEVEL_UP: // Snow line buttons
+				/* Don't allow too fast scrolling */
+				if ((this->flags4 & WF_TIMEOUT_MASK) <= 2 << WF_TIMEOUT_SHL) {
+					this->HandleButtonClick(widget);
+					this->SetDirty();
+
+					_patches_newgame.snow_line_height = Clamp(_patches_newgame.snow_line_height + widget - GLAND_SNOW_LEVEL_TEXT, 2, MAX_SNOWLINE_HEIGHT);
+				}
+				_left_button_clicked = false;
+				break;
 
-				case GLAND_TEMPERATE:
-				case GLAND_ARCTIC:
-				case GLAND_TROPICAL:
-				case GLAND_TOYLAND:
-					w->RaiseWidget(_opt_newgame.landscape + GLAND_TEMPERATE);
-					SetNewLandscapeType(e->we.click.widget - GLAND_TEMPERATE);
-					break;
+			case GLAND_SNOW_LEVEL_TEXT: // Snow line text
+				this->widget_id = GLAND_SNOW_LEVEL_TEXT;
+				SetDParam(0, _patches_newgame.snow_line_height);
+				ShowQueryString(STR_CONFIG_PATCHES_INT32, STR_SNOW_LINE_QUERY_CAPT, 3, 100, this, CS_NUMERAL);
+				break;
+
+			case GLAND_TREE_PULLDOWN: // Tree placer
+				ShowDropDownMenu(this, _tree_placer, _patches_newgame.tree_placer, GLAND_TREE_PULLDOWN, 0, 0);
+				break;
+
+			case GLAND_LANDSCAPE_PULLDOWN: // Landscape generator OR Heightmap rotation
+			/* case GLAND_HEIGHTMAP_ROTATION_TEXT: case GLAND_HEIGHTMAP_ROTATION_PULLDOWN:*/
+				if (mode == GLWP_HEIGHTMAP) {
+					ShowDropDownMenu(this, _rotation, _patches_newgame.heightmap_rotation, GLAND_HEIGHTMAP_ROTATION_PULLDOWN, 0, 0);
+				} else {
+					ShowDropDownMenu(this, _landscape, _patches_newgame.land_generator, GLAND_LANDSCAPE_PULLDOWN, 0, 0);
+				}
+				break;
+
+			case GLAND_TERRAIN_PULLDOWN: // Terrain type
+				ShowDropDownMenu(this, _elevations, _opt_newgame.diff.terrain_type, GLAND_TERRAIN_PULLDOWN, 0, 0);
+				break;
 
-				case GLAND_MAPSIZE_X_PULLDOWN: // Mapsize X
-					ShowDropDownList(w, BuildMapsizeDropDown(), _patches_newgame.map_x, GLAND_MAPSIZE_X_PULLDOWN);
-					break;
+			case GLAND_WATER_PULLDOWN: // Water quantity
+				ShowDropDownMenu(this, _sea_lakes, _opt_newgame.diff.quantity_sea_lakes, GLAND_WATER_PULLDOWN, 0, 0);
+				break;
+
+			case GLAND_SMOOTHNESS_PULLDOWN: // Map smoothness
+				ShowDropDownMenu(this, _smoothness, _patches_newgame.tgen_smoothness, GLAND_SMOOTHNESS_PULLDOWN, 0, 0);
+				break;
+		}
+	}
 
-				case GLAND_MAPSIZE_Y_PULLDOWN: // Mapsize Y
-					ShowDropDownList(w, BuildMapsizeDropDown(), _patches_newgame.map_y, GLAND_MAPSIZE_Y_PULLDOWN);
-					break;
+	virtual void OnMouseLoop()
+	{
+		this->HandleEditBox(GLAND_RANDOM_EDITBOX);
+	}
+
+	virtual bool OnKeyPress(uint16 key, uint16 keycode)
+	{
+		bool cont;
+		this->HandleEditBoxKey(GLAND_RANDOM_EDITBOX, key, keycode, cont);
+		/* the seed is unsigned, therefore atoi cannot be used.
+			* As 2^32 - 1 (MAX_UVALUE(uint32)) is a 'magic' value
+			* (use random seed) it should not be possible to be
+			* entered into the input field; the generate seed
+			* button can be used instead. */
+		_patches_newgame.generation_seed = minu(strtoul(this->edit_str_buf, NULL, sizeof(this->edit_str_buf) - 1), MAX_UVALUE(uint32) - 1);
+		return cont;
+	}
 
-				case GLAND_TOWN_PULLDOWN: // Number of towns
-					ShowDropDownMenu(w, num_towns, _opt_newgame.diff.number_towns, GLAND_TOWN_PULLDOWN, 0, 0);
-					break;
+	virtual void OnDropdownSelect(int widget, int index)
+	{
+		switch (widget) {
+			case GLAND_MAPSIZE_X_PULLDOWN:  _patches_newgame.map_x = index; break;
+			case GLAND_MAPSIZE_Y_PULLDOWN:  _patches_newgame.map_y = index; break;
+			case GLAND_TREE_PULLDOWN:       _patches_newgame.tree_placer = index; break;
+			case GLAND_SMOOTHNESS_PULLDOWN: _patches_newgame.tgen_smoothness = index;  break;
 
-				case GLAND_INDUSTRY_PULLDOWN: // Number of industries
-					ShowDropDownMenu(w, num_inds, _opt_newgame.diff.number_industries, GLAND_INDUSTRY_PULLDOWN, 0, 0);
-					break;
+			case GLAND_TOWN_PULLDOWN:
+				_opt_newgame.diff.number_towns = index;
+				if (_opt_newgame.diff_level != 3) ShowErrorMessage(INVALID_STRING_ID, STR_DIFFICULTY_TO_CUSTOM, 0, 0);
+				DoCommandP(0, 2, _opt_newgame.diff.number_towns, NULL, CMD_CHANGE_DIFFICULTY_LEVEL);
+				break;
+
+			case GLAND_INDUSTRY_PULLDOWN:
+				_opt_newgame.diff.number_industries = index;
+				if (_opt_newgame.diff_level != 3) ShowErrorMessage(INVALID_STRING_ID, STR_DIFFICULTY_TO_CUSTOM, 0, 0);
+				DoCommandP(0, 3, _opt_newgame.diff.number_industries, NULL, CMD_CHANGE_DIFFICULTY_LEVEL);
+				break;
 
-				case GLAND_RANDOM_BUTTON: // Random seed
-					_patches_newgame.generation_seed = InteractiveRandom();
-					snprintf(_genseed_buffer, lengthof(_genseed_buffer), "%u", _patches_newgame.generation_seed);
-					UpdateTextBufferSize(&_genseed_query.text);
-					w->SetDirty();
-					break;
+			case GLAND_LANDSCAPE_PULLDOWN:
+			/* case GLAND_HEIGHTMAP_PULLDOWN: */
+				if (mode == GLWP_HEIGHTMAP) {
+					_patches_newgame.heightmap_rotation = index;
+				} else {
+					_patches_newgame.land_generator = index;
+				}
+				break;
+
+			case GLAND_TERRAIN_PULLDOWN:
+				_opt_newgame.diff.terrain_type = index;
+				if (_opt_newgame.diff_level != 3) ShowErrorMessage(INVALID_STRING_ID, STR_DIFFICULTY_TO_CUSTOM, 0, 0);
+				DoCommandP(0, 12, _opt_newgame.diff.terrain_type, NULL, CMD_CHANGE_DIFFICULTY_LEVEL);
+				break;
 
-				case GLAND_RANDOM_EDITBOX: // edit box for random seed
-					ShowOnScreenKeyboard(w, & _genseed_query, GLAND_RANDOM_EDITBOX, 0, 0);
+			case GLAND_WATER_PULLDOWN:
+				_opt_newgame.diff.quantity_sea_lakes = index;
+				if (_opt_newgame.diff_level != 3) ShowErrorMessage(INVALID_STRING_ID, STR_DIFFICULTY_TO_CUSTOM, 0, 0);
+				DoCommandP(0, 13, _opt_newgame.diff.quantity_sea_lakes, NULL, CMD_CHANGE_DIFFICULTY_LEVEL);
+				break;
+		}
+		this->SetDirty();
+	}
+
+	virtual void OnQueryTextFinished(char *str)
+	{
+		if (!StrEmpty(str)) {
+			int32 value = atoi(str);
+
+			switch (this->widget_id) {
+				case GLAND_START_DATE_TEXT:
+					this->InvalidateWidget(GLAND_START_DATE_TEXT);
+					_patches_newgame.starting_year = Clamp(value, MIN_YEAR, MAX_YEAR);
 					break;
 
-				case GLAND_GENERATE_BUTTON: // Generate
-					UpdatePatches();
-
-					if (_patches.town_layout == TL_NO_ROADS) {
-						ShowQuery(
-							STR_TOWN_LAYOUT_WARNING_CAPTION,
-							STR_TOWN_LAYOUT_WARNING_MESSAGE,
-							w,
-							LandscapeGenerationCallback);
-					} else if (mode == GLWP_HEIGHTMAP &&
-							(WP(w, generate_d).x * 2 < (1U << _patches_newgame.map_x) ||
-							WP(w, generate_d).x / 2 > (1U << _patches_newgame.map_x) ||
-							WP(w, generate_d).y * 2 < (1U << _patches_newgame.map_y) ||
-							WP(w, generate_d).y / 2 > (1U << _patches_newgame.map_y))) {
-						ShowQuery(
-							STR_HEIGHTMAP_SCALE_WARNING_CAPTION,
-							STR_HEIGHTMAP_SCALE_WARNING_MESSAGE,
-							w,
-							LandscapeGenerationCallback);
-					} else {
-						StartGeneratingLandscape(mode);
-					}
-					break;
-
-				case GLAND_START_DATE_DOWN:
-				case GLAND_START_DATE_UP: // Year buttons
-					/* Don't allow too fast scrolling */
-					if ((w->flags4 & WF_TIMEOUT_MASK) <= 2 << WF_TIMEOUT_SHL) {
-						w->HandleButtonClick(e->we.click.widget);
-						w->SetDirty();
-
-						_patches_newgame.starting_year = Clamp(_patches_newgame.starting_year + e->we.click.widget - GLAND_START_DATE_TEXT, MIN_YEAR, MAX_YEAR);
-					}
-					_left_button_clicked = false;
-					break;
-
-				case GLAND_START_DATE_TEXT: // Year text
-					WP(w, generate_d).widget_id = GLAND_START_DATE_TEXT;
-					SetDParam(0, _patches_newgame.starting_year);
-					ShowQueryString(STR_CONFIG_PATCHES_INT32, STR_START_DATE_QUERY_CAPT, 8, 100, w, CS_NUMERAL);
-					break;
-
-				case GLAND_SNOW_LEVEL_DOWN:
-				case GLAND_SNOW_LEVEL_UP: // Snow line buttons
-					/* Don't allow too fast scrolling */
-					if ((w->flags4 & WF_TIMEOUT_MASK) <= 2 << WF_TIMEOUT_SHL) {
-						w->HandleButtonClick(e->we.click.widget);
-						w->SetDirty();
-
-						_patches_newgame.snow_line_height = Clamp(_patches_newgame.snow_line_height + e->we.click.widget - GLAND_SNOW_LEVEL_TEXT, 2, MAX_SNOWLINE_HEIGHT);
-					}
-					_left_button_clicked = false;
-					break;
-
-				case GLAND_SNOW_LEVEL_TEXT: // Snow line text
-					WP(w, generate_d).widget_id = GLAND_SNOW_LEVEL_TEXT;
-					SetDParam(0, _patches_newgame.snow_line_height);
-					ShowQueryString(STR_CONFIG_PATCHES_INT32, STR_SNOW_LINE_QUERY_CAPT, 3, 100, w, CS_NUMERAL);
-					break;
-
-				case GLAND_TREE_PULLDOWN: // Tree placer
-					ShowDropDownMenu(w, tree_placer, _patches_newgame.tree_placer, GLAND_TREE_PULLDOWN, 0, 0);
-					break;
-
-				case GLAND_LANDSCAPE_PULLDOWN: // Landscape generator OR Heightmap rotation
-				/* case GLAND_HEIGHTMAP_ROTATION_TEXT: case GLAND_HEIGHTMAP_ROTATION_PULLDOWN:*/
-					if (mode == GLWP_HEIGHTMAP) {
-						ShowDropDownMenu(w, rotation, _patches_newgame.heightmap_rotation, GLAND_HEIGHTMAP_ROTATION_PULLDOWN, 0, 0);
-					} else {
-						ShowDropDownMenu(w, landscape, _patches_newgame.land_generator, GLAND_LANDSCAPE_PULLDOWN, 0, 0);
-					}
-					break;
-
-				case GLAND_TERRAIN_PULLDOWN: // Terrain type
-					ShowDropDownMenu(w, elevations, _opt_newgame.diff.terrain_type, GLAND_TERRAIN_PULLDOWN, 0, 0);
-					break;
-
-				case GLAND_WATER_PULLDOWN: // Water quantity
-					ShowDropDownMenu(w, sea_lakes, _opt_newgame.diff.quantity_sea_lakes, GLAND_WATER_PULLDOWN, 0, 0);
-					break;
-
-				case GLAND_SMOOTHNESS_PULLDOWN: // Map smoothness
-					ShowDropDownMenu(w, smoothness, _patches_newgame.tgen_smoothness, GLAND_SMOOTHNESS_PULLDOWN, 0, 0);
+				case GLAND_SNOW_LEVEL_TEXT:
+					this->InvalidateWidget(GLAND_SNOW_LEVEL_TEXT);
+					_patches_newgame.snow_line_height = Clamp(value, 2, MAX_SNOWLINE_HEIGHT);
 					break;
 			}
-			break;
 
-		case WE_MOUSELOOP:
-			HandleEditBox(w, &_genseed_query, GLAND_RANDOM_EDITBOX);
-			break;
-
-		case WE_KEYPRESS:
-			HandleEditBoxKey(w, &_genseed_query, GLAND_RANDOM_EDITBOX, e);
-			/* the seed is unsigned, therefore atoi cannot be used.
-			 * As 2^32 - 1 (MAX_UVALUE(uint32)) is a 'magic' value
-			 * (use random seed) it should not be possible to be
-			 * entered into the input field; the generate seed
-			 * button can be used instead. */
-			_patches_newgame.generation_seed = minu(strtoul(_genseed_buffer, NULL, sizeof(_genseed_buffer) - 1), MAX_UVALUE(uint32) - 1);
-			break;
-
-		case WE_DROPDOWN_SELECT:
-			switch (e->we.dropdown.button) {
-				case GLAND_MAPSIZE_X_PULLDOWN:  _patches_newgame.map_x = e->we.dropdown.index; break;
-				case GLAND_MAPSIZE_Y_PULLDOWN:  _patches_newgame.map_y = e->we.dropdown.index; break;
-				case GLAND_TREE_PULLDOWN:       _patches_newgame.tree_placer = e->we.dropdown.index; break;
-				case GLAND_SMOOTHNESS_PULLDOWN: _patches_newgame.tgen_smoothness = e->we.dropdown.index;  break;
-
-				case GLAND_TOWN_PULLDOWN:
-					_opt_newgame.diff.number_towns = e->we.dropdown.index;
-					if (_opt_newgame.diff_level != 3) ShowErrorMessage(INVALID_STRING_ID, STR_DIFFICULTY_TO_CUSTOM, 0, 0);
-					DoCommandP(0, 2, _opt_newgame.diff.number_towns, NULL, CMD_CHANGE_DIFFICULTY_LEVEL);
-					break;
-
-				case GLAND_INDUSTRY_PULLDOWN:
-					_opt_newgame.diff.number_industries = e->we.dropdown.index;
-					if (_opt_newgame.diff_level != 3) ShowErrorMessage(INVALID_STRING_ID, STR_DIFFICULTY_TO_CUSTOM, 0, 0);
-					DoCommandP(0, 3, _opt_newgame.diff.number_industries, NULL, CMD_CHANGE_DIFFICULTY_LEVEL);
-					break;
-
-				case GLAND_LANDSCAPE_PULLDOWN:
-				/* case GLAND_HEIGHTMAP_PULLDOWN: */
-					if (mode == GLWP_HEIGHTMAP) {
-						_patches_newgame.heightmap_rotation = e->we.dropdown.index;
-					} else {
-						_patches_newgame.land_generator = e->we.dropdown.index;
-					}
-					break;
-
-				case GLAND_TERRAIN_PULLDOWN:
-					_opt_newgame.diff.terrain_type = e->we.dropdown.index;
-					if (_opt_newgame.diff_level != 3) ShowErrorMessage(INVALID_STRING_ID, STR_DIFFICULTY_TO_CUSTOM, 0, 0);
-					DoCommandP(0, 12, _opt_newgame.diff.terrain_type, NULL, CMD_CHANGE_DIFFICULTY_LEVEL);
-					break;
-
-				case GLAND_WATER_PULLDOWN:
-					_opt_newgame.diff.quantity_sea_lakes = e->we.dropdown.index;
-					if (_opt_newgame.diff_level != 3) ShowErrorMessage(INVALID_STRING_ID, STR_DIFFICULTY_TO_CUSTOM, 0, 0);
-					DoCommandP(0, 13, _opt_newgame.diff.quantity_sea_lakes, NULL, CMD_CHANGE_DIFFICULTY_LEVEL);
-					break;
-			}
-			w->SetDirty();
-			break;
-
-		case WE_ON_EDIT_TEXT:
-			if (!StrEmpty(e->we.edittext.str)) {
-				int32 value = atoi(e->we.edittext.str);
-
-				switch (WP(w, generate_d).widget_id) {
-					case GLAND_START_DATE_TEXT:
-						w->InvalidateWidget(GLAND_START_DATE_TEXT);
-						_patches_newgame.starting_year = Clamp(value, MIN_YEAR, MAX_YEAR);
-						break;
-
-					case GLAND_SNOW_LEVEL_TEXT:
-						w->InvalidateWidget(GLAND_SNOW_LEVEL_TEXT);
-						_patches_newgame.snow_line_height = Clamp(value, 2, MAX_SNOWLINE_HEIGHT);
-						break;
-				}
-
-				w->SetDirty();
-			}
-			break;
+			this->SetDirty();
+		}
 	}
-}
+};
 
 static const WindowDesc _generate_landscape_desc = {
 	WDP_CENTER, WDP_CENTER, 338, 268, 338, 268,
 	WC_GENERATE_LANDSCAPE, WC_NONE,
 	WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
 	_generate_landscape_widgets,
-	GenerateLandscapeWndProc,
+	NULL,
 };
 
 static const WindowDesc _heightmap_load_desc = {
@@ -559,7 +562,7 @@
 	WC_GENERATE_LANDSCAPE, WC_NONE,
 	WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_STD_BTN | WDF_UNCLICK_BUTTONS,
 	_heightmap_load_widgets,
-	GenerateLandscapeWndProc,
+	NULL,
 };
 
 static void _ShowGenerateLandscape(glwp_modes mode)
@@ -577,14 +580,12 @@
 		if (!GetHeightmapDimensions(_file_to_saveload.name, &x, &y)) return;
 	}
 
-	Window *w = AllocateWindowDescFront<Window>((mode == GLWP_HEIGHTMAP) ? &_heightmap_load_desc : &_generate_landscape_desc, mode);
-
-	if (w == NULL) return;
+	GenerateLandscapeWindow *w = AllocateWindowDescFront<GenerateLandscapeWindow>((mode == GLWP_HEIGHTMAP) ? &_heightmap_load_desc : &_generate_landscape_desc, mode);
 
 	if (mode == GLWP_HEIGHTMAP) {
-		WP(w, generate_d).x = x;
-		WP(w, generate_d).y = y;
-		strecpy(WP(w, generate_d).name, _file_to_saveload.title, lastof(WP(w, generate_d).name));
+		w->x = x;
+		w->y = y;
+		strecpy(w->name, _file_to_saveload.title, lastof(w->name));
 	}
 
 	InvalidateWindow(WC_GENERATE_LANDSCAPE, mode);
@@ -639,127 +640,131 @@
 };
 
 
-static void CreateScenarioWndProc(Window *w, WindowEvent *e)
+struct CreateScenarioWindow : public Window
 {
-	switch (e->event) {
-		case WE_CREATE:
-			w->LowerWidget(_opt_newgame.landscape + CSCEN_TEMPERATE);
-			break;
+	uint widget_id;
+
+	CreateScenarioWindow(const WindowDesc *desc, WindowNumber window_number) : Window(desc, NULL, window_number)
+	{
+		this->LowerWidget(_opt_newgame.landscape + CSCEN_TEMPERATE);
+	}
 
-		case WE_PAINT:
-			w->SetWidgetDisabledState(CSCEN_START_DATE_DOWN,       _patches_newgame.starting_year <= MIN_YEAR);
-			w->SetWidgetDisabledState(CSCEN_START_DATE_UP,         _patches_newgame.starting_year >= MAX_YEAR);
-			w->SetWidgetDisabledState(CSCEN_FLAT_LAND_HEIGHT_DOWN, _patches_newgame.se_flat_world_height <= 0);
-			w->SetWidgetDisabledState(CSCEN_FLAT_LAND_HEIGHT_UP,   _patches_newgame.se_flat_world_height >= MAX_TILE_HEIGHT);
+	virtual void OnPaint()
+	{
+		this->SetWidgetDisabledState(CSCEN_START_DATE_DOWN,       _patches_newgame.starting_year <= MIN_YEAR);
+		this->SetWidgetDisabledState(CSCEN_START_DATE_UP,         _patches_newgame.starting_year >= MAX_YEAR);
+		this->SetWidgetDisabledState(CSCEN_FLAT_LAND_HEIGHT_DOWN, _patches_newgame.se_flat_world_height <= 0);
+		this->SetWidgetDisabledState(CSCEN_FLAT_LAND_HEIGHT_UP,   _patches_newgame.se_flat_world_height >= MAX_TILE_HEIGHT);
+
+		this->SetWidgetLoweredState(CSCEN_TEMPERATE, _opt_newgame.landscape == LT_TEMPERATE);
+		this->SetWidgetLoweredState(CSCEN_ARCTIC,    _opt_newgame.landscape == LT_ARCTIC);
+		this->SetWidgetLoweredState(CSCEN_TROPICAL,  _opt_newgame.landscape == LT_TROPIC);
+		this->SetWidgetLoweredState(CSCEN_TOYLAND,   _opt_newgame.landscape == LT_TOYLAND);
+
+		/* Set parameters for widget text that requires them */
+		SetDParam(0, ConvertYMDToDate(_patches_newgame.starting_year, 0, 1)); // CSCEN_START_DATE_TEXT
+		SetDParam(1, 1 << _patches_newgame.map_x); // CSCEN_MAPSIZE_X_PULLDOWN
+		SetDParam(2, 1 << _patches_newgame.map_y); // CSCEN_MAPSIZE_Y_PULLDOWN
+		SetDParam(3, _patches_newgame.se_flat_world_height); // CSCEN_FLAT_LAND_HEIGHT_TEXT
 
-			w->SetWidgetLoweredState(CSCEN_TEMPERATE, _opt_newgame.landscape == LT_TEMPERATE);
-			w->SetWidgetLoweredState(CSCEN_ARCTIC,    _opt_newgame.landscape == LT_ARCTIC);
-			w->SetWidgetLoweredState(CSCEN_TROPICAL,  _opt_newgame.landscape == LT_TROPIC);
-			w->SetWidgetLoweredState(CSCEN_TOYLAND,   _opt_newgame.landscape == LT_TOYLAND);
+		DrawWindowWidgets(this);
+	}
+
+	virtual void OnClick(Point pt, int widget)
+	{
+		switch (widget) {
+			case CSCEN_TEMPERATE:
+			case CSCEN_ARCTIC:
+			case CSCEN_TROPICAL:
+			case CSCEN_TOYLAND:
+				this->RaiseWidget(_opt_newgame.landscape + CSCEN_TEMPERATE);
+				SetNewLandscapeType(widget - CSCEN_TEMPERATE);
+				break;
 
-			/* Set parameters for widget text that requires them */
-			SetDParam(0, ConvertYMDToDate(_patches_newgame.starting_year, 0, 1)); // CSCEN_START_DATE_TEXT
-			SetDParam(1, 1 << _patches_newgame.map_x); // CSCEN_MAPSIZE_X_PULLDOWN
-			SetDParam(2, 1 << _patches_newgame.map_y); // CSCEN_MAPSIZE_Y_PULLDOWN
-			SetDParam(3, _patches_newgame.se_flat_world_height); // CSCEN_FLAT_LAND_HEIGHT_TEXT
+			case CSCEN_MAPSIZE_X_PULLDOWN: // Mapsize X
+				ShowDropDownList(this, BuildMapsizeDropDown(), _patches_newgame.map_x, CSCEN_MAPSIZE_X_PULLDOWN);
+				break;
+
+			case CSCEN_MAPSIZE_Y_PULLDOWN: // Mapsize Y
+				ShowDropDownList(this, BuildMapsizeDropDown(), _patches_newgame.map_y, CSCEN_MAPSIZE_Y_PULLDOWN);
+				break;
 
-			DrawWindowWidgets(w);
+			case CSCEN_EMPTY_WORLD: // Empty world / flat world
+				StartGeneratingLandscape(GLWP_SCENARIO);
+				break;
+
+			case CSCEN_RANDOM_WORLD: // Generate
+				ShowGenerateLandscape();
+				break;
 
-			break;
+			case CSCEN_START_DATE_DOWN:
+			case CSCEN_START_DATE_UP: // Year buttons
+				/* Don't allow too fast scrolling */
+				if ((this->flags4 & WF_TIMEOUT_MASK) <= 2 << WF_TIMEOUT_SHL) {
+					this->HandleButtonClick(widget);
+					this->SetDirty();
+
+					_patches_newgame.starting_year = Clamp(_patches_newgame.starting_year + widget - CSCEN_START_DATE_TEXT, MIN_YEAR, MAX_YEAR);
+				}
+				_left_button_clicked = false;
+				break;
 
-		case WE_CLICK:
-			switch (e->we.click.widget) {
-				case CSCEN_TEMPERATE:
-				case CSCEN_ARCTIC:
-				case CSCEN_TROPICAL:
-				case CSCEN_TOYLAND:
-					w->RaiseWidget(_opt_newgame.landscape + CSCEN_TEMPERATE);
-					SetNewLandscapeType(e->we.click.widget - CSCEN_TEMPERATE);
-					break;
+			case CSCEN_START_DATE_TEXT: // Year text
+				this->widget_id = CSCEN_START_DATE_TEXT;
+				SetDParam(0, _patches_newgame.starting_year);
+				ShowQueryString(STR_CONFIG_PATCHES_INT32, STR_START_DATE_QUERY_CAPT, 8, 100, this, CS_NUMERAL);
+				break;
+
+			case CSCEN_FLAT_LAND_HEIGHT_DOWN:
+			case CSCEN_FLAT_LAND_HEIGHT_UP: // Height level buttons
+				/* Don't allow too fast scrolling */
+				if ((this->flags4 & WF_TIMEOUT_MASK) <= 2 << WF_TIMEOUT_SHL) {
+					this->HandleButtonClick(widget);
+					this->SetDirty();
 
-				case CSCEN_MAPSIZE_X_PULLDOWN: // Mapsize X
-					ShowDropDownList(w, BuildMapsizeDropDown(), _patches_newgame.map_x, CSCEN_MAPSIZE_X_PULLDOWN);
-					break;
+					_patches_newgame.se_flat_world_height = Clamp(_patches_newgame.se_flat_world_height + widget - CSCEN_FLAT_LAND_HEIGHT_TEXT, 0, MAX_TILE_HEIGHT);
+				}
+				_left_button_clicked = false;
+				break;
+
+			case CSCEN_FLAT_LAND_HEIGHT_TEXT: // Height level text
+				this->widget_id = CSCEN_FLAT_LAND_HEIGHT_TEXT;
+				SetDParam(0, _patches_newgame.se_flat_world_height);
+				ShowQueryString(STR_CONFIG_PATCHES_INT32, STR_FLAT_WORLD_HEIGHT_QUERY_CAPT, 3, 100, this, CS_NUMERAL);
+				break;
+		}
+	}
 
-				case CSCEN_MAPSIZE_Y_PULLDOWN: // Mapsize Y
-					ShowDropDownList(w, BuildMapsizeDropDown(), _patches_newgame.map_y, CSCEN_MAPSIZE_Y_PULLDOWN);
-					break;
+	virtual void OnDropdownSelect(int widget, int index)
+	{
+		switch (widget) {
+			case CSCEN_MAPSIZE_X_PULLDOWN: _patches_newgame.map_x = index; break;
+			case CSCEN_MAPSIZE_Y_PULLDOWN: _patches_newgame.map_y = index; break;
+		}
+		this->SetDirty();
+	}
 
-				case CSCEN_EMPTY_WORLD: // Empty world / flat world
-					StartGeneratingLandscape(GLWP_SCENARIO);
-					break;
+	virtual void OnQueryTextFinished(char *str)
+	{
+		if (!StrEmpty(str)) {
+			int32 value = atoi(str);
 
-				case CSCEN_RANDOM_WORLD: // Generate
-					ShowGenerateLandscape();
+			switch (this->widget_id) {
+				case CSCEN_START_DATE_TEXT:
+					this->InvalidateWidget(CSCEN_START_DATE_TEXT);
+					_patches_newgame.starting_year = Clamp(value, MIN_YEAR, MAX_YEAR);
 					break;
 
-				case CSCEN_START_DATE_DOWN:
-				case CSCEN_START_DATE_UP: // Year buttons
-					/* Don't allow too fast scrolling */
-					if ((w->flags4 & WF_TIMEOUT_MASK) <= 2 << WF_TIMEOUT_SHL) {
-						w->HandleButtonClick(e->we.click.widget);
-						w->SetDirty();
-
-						_patches_newgame.starting_year = Clamp(_patches_newgame.starting_year + e->we.click.widget - CSCEN_START_DATE_TEXT, MIN_YEAR, MAX_YEAR);
-					}
-					_left_button_clicked = false;
-					break;
-
-				case CSCEN_START_DATE_TEXT: // Year text
-					WP(w, generate_d).widget_id = CSCEN_START_DATE_TEXT;
-					SetDParam(0, _patches_newgame.starting_year);
-					ShowQueryString(STR_CONFIG_PATCHES_INT32, STR_START_DATE_QUERY_CAPT, 8, 100, w, CS_NUMERAL);
-					break;
-
-				case CSCEN_FLAT_LAND_HEIGHT_DOWN:
-				case CSCEN_FLAT_LAND_HEIGHT_UP: // Height level buttons
-					/* Don't allow too fast scrolling */
-					if ((w->flags4 & WF_TIMEOUT_MASK) <= 2 << WF_TIMEOUT_SHL) {
-						w->HandleButtonClick(e->we.click.widget);
-						w->SetDirty();
-
-						_patches_newgame.se_flat_world_height = Clamp(_patches_newgame.se_flat_world_height + e->we.click.widget - CSCEN_FLAT_LAND_HEIGHT_TEXT, 0, MAX_TILE_HEIGHT);
-					}
-					_left_button_clicked = false;
-					break;
-
-				case CSCEN_FLAT_LAND_HEIGHT_TEXT: // Height level text
-					WP(w, generate_d).widget_id = CSCEN_FLAT_LAND_HEIGHT_TEXT;
-					SetDParam(0, _patches_newgame.se_flat_world_height);
-					ShowQueryString(STR_CONFIG_PATCHES_INT32, STR_FLAT_WORLD_HEIGHT_QUERY_CAPT, 3, 100, w, CS_NUMERAL);
+				case CSCEN_FLAT_LAND_HEIGHT_TEXT:
+					this->InvalidateWidget(CSCEN_FLAT_LAND_HEIGHT_TEXT);
+					_patches_newgame.se_flat_world_height = Clamp(value, 0, MAX_TILE_HEIGHT);
 					break;
 			}
-			break;
 
-		case WE_DROPDOWN_SELECT:
-			switch (e->we.dropdown.button) {
-				case CSCEN_MAPSIZE_X_PULLDOWN: _patches_newgame.map_x = e->we.dropdown.index; break;
-				case CSCEN_MAPSIZE_Y_PULLDOWN: _patches_newgame.map_y = e->we.dropdown.index; break;
-			}
-			w->SetDirty();
-			break;
-
-		case WE_ON_EDIT_TEXT:
-			if (!StrEmpty(e->we.edittext.str)) {
-				int32 value = atoi(e->we.edittext.str);
-
-				switch (WP(w, generate_d).widget_id) {
-					case CSCEN_START_DATE_TEXT:
-						w->InvalidateWidget(CSCEN_START_DATE_TEXT);
-						_patches_newgame.starting_year = Clamp(value, MIN_YEAR, MAX_YEAR);
-						break;
-
-					case CSCEN_FLAT_LAND_HEIGHT_TEXT:
-						w->InvalidateWidget(CSCEN_FLAT_LAND_HEIGHT_TEXT);
-						_patches_newgame.se_flat_world_height = Clamp(value, 0, MAX_TILE_HEIGHT);
-						break;
-				}
-
-				w->SetDirty();
-			}
-			break;
+			this->SetDirty();
+		}
 	}
-}
+};
 
 static const Widget _create_scenario_widgets[] = {
 {   WWT_CLOSEBOX, RESIZE_NONE, 13,   0,  10,   0,  13, STR_00C5,                STR_018B_CLOSE_WINDOW},
@@ -796,13 +801,13 @@
 	WC_GENERATE_LANDSCAPE, WC_NONE,
 	WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_STD_BTN | WDF_UNCLICK_BUTTONS,
 	_create_scenario_widgets,
-	CreateScenarioWndProc,
+	NULL,
 };
 
 void ShowCreateScenario()
 {
 	DeleteWindowByClass(WC_GENERATE_LANDSCAPE);
-	AllocateWindowDescFront<Window>(&_create_scenario_desc, GLWP_SCENARIO);
+	new CreateScenarioWindow(&_create_scenario_desc, GLWP_SCENARIO);
 }
 
 
--- a/src/misc_gui.cpp
+++ b/src/misc_gui.cpp
@@ -44,6 +44,7 @@
 #include "newgrf_cargo.h"
 #include "rail_gui.h"
 #include "tilehighlight_func.h"
+#include "querystring_gui.h"
 
 #include "table/sprites.h"
 #include "table/strings.h"
@@ -878,43 +879,6 @@
 	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)) w->InvalidateWidget(wid);
-			break;
-
-		case (WKC_CTRL | 'U'):
-			DeleteTextBufferAll(&string->text);
-			w->InvalidateWidget(wid);
-			break;
-
-		case WKC_BACKSPACE: case WKC_DELETE:
-			if (DeleteTextBufferChar(&string->text, e->we.keypress.keycode)) w->InvalidateWidget(wid);
-			break;
-
-		case WKC_LEFT: case WKC_RIGHT: case WKC_END: case WKC_HOME:
-			if (MoveTextBufferPos(&string->text, e->we.keypress.keycode)) w->InvalidateWidget(wid);
-			break;
-
-		default:
-			if (IsValidChar(e->we.keypress.key, string->afilter)) {
-				if (InsertTextBufferChar(&string->text, e->we.keypress.key)) w->InvalidateWidget(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? */
@@ -927,12 +891,49 @@
 	return false;
 }
 
-void HandleEditBox(Window *w, querystr_d *string, int wid)
+int QueryString::HandleEditBoxKey(Window *w, int wid, uint16 key, uint16 keycode, bool &cont)
 {
-	if (HandleCaret(&string->text)) w->InvalidateWidget(wid);
+	cont = false;
+
+	switch (keycode) {
+		case WKC_ESC: return 2;
+
+		case WKC_RETURN: case WKC_NUM_ENTER: return 1;
+
+		case (WKC_CTRL | 'V'):
+			if (InsertTextBufferClipboard(&this->text)) w->InvalidateWidget(wid);
+			break;
+
+		case (WKC_CTRL | 'U'):
+			DeleteTextBufferAll(&this->text);
+			w->InvalidateWidget(wid);
+			break;
+
+		case WKC_BACKSPACE: case WKC_DELETE:
+			if (DeleteTextBufferChar(&this->text, keycode)) w->InvalidateWidget(wid);
+			break;
+
+		case WKC_LEFT: case WKC_RIGHT: case WKC_END: case WKC_HOME:
+			if (MoveTextBufferPos(&this->text, keycode)) w->InvalidateWidget(wid);
+			break;
+
+		default:
+			if (IsValidChar(key, this->afilter)) {
+				if (InsertTextBufferChar(&this->text, key)) w->InvalidateWidget(wid);
+			} else { // key wasn't caught. Continue only if standard entry specified
+				cont = (this->afilter == CS_ALPHANUMERAL);
+			}
+	}
+
+	return 0;
 }
 
-void DrawEditBox(Window *w, querystr_d *string, int wid)
+void QueryString::HandleEditBox(Window *w, int wid)
+{
+	if (HandleCaret(&this->text)) w->InvalidateWidget(wid);
+}
+
+void QueryString::DrawEditBox(Window *w, int wid)
 {
 	const Widget *wi = &w->widget[wid];
 
@@ -957,7 +958,7 @@
 
 	/* We will take the current widget length as maximum width, with a small
 	 * space reserved at the end for the caret to show */
-	const Textbuf *tb = &string->text;
+	const Textbuf *tb = &this->text;
 
 	delta = (wi->right - wi->left) - tb->width - 10;
 	if (delta > 0) delta = 0;
@@ -970,6 +971,21 @@
 	_cur_dpi = old_dpi;
 }
 
+int QueryStringBaseWindow::HandleEditBoxKey(int wid, uint16 key, uint16 keycode, bool &cont)
+{
+	return this->QueryString::HandleEditBoxKey(this, wid, key, keycode, cont);
+}
+
+void QueryStringBaseWindow::HandleEditBox(int wid)
+{
+	this->QueryString::HandleEditBox(this, wid);
+}
+
+void QueryStringBaseWindow::DrawEditBox(int wid)
+{
+	this->QueryString::DrawEditBox(this, wid);
+}
+
 enum QueryStringWidgets {
 	QUERY_STR_WIDGET_TEXT = 3,
 	QUERY_STR_WIDGET_CANCEL,
@@ -977,69 +993,79 @@
 };
 
 
-static void QueryStringWndProc(Window *w, WindowEvent *e)
+struct QueryStringWindow : public QueryStringBaseWindow
 {
-	querystr_d *qs = &WP(w, querystr_d);
+	Window *parent;
+
+	QueryStringWindow(const WindowDesc *desc, Window *parent) : QueryStringBaseWindow(desc), parent(parent)
+	{
+		SetBit(_no_scroll, SCROLL_EDIT);
 
-	switch (e->event) {
-		case WE_CREATE:
-			SetBit(_no_scroll, SCROLL_EDIT);
-			break;
+		this->FindWindowPlacementAndResize(desc);
+	}
 
-		case WE_PAINT:
-			SetDParam(0, qs->caption);
-			DrawWindowWidgets(w);
+	virtual void OnPaint()
+	{
+		SetDParam(0, this->caption);
+		DrawWindowWidgets(this);
 
-			DrawEditBox(w, qs, QUERY_STR_WIDGET_TEXT);
-			break;
+		this->DrawEditBox(QUERY_STR_WIDGET_TEXT);
+	}
 
-		case WE_CLICK:
-			switch (e->we.click.widget) {
-				case QUERY_STR_WIDGET_TEXT:
-					ShowOnScreenKeyboard(w, &WP(w, querystr_d), QUERY_STR_WIDGET_TEXT, QUERY_STR_WIDGET_CANCEL, QUERY_STR_WIDGET_OK);
-					break;
-
-				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;
+	void OnOk()
+	{
+		if (this->orig == NULL || strcmp(this->text.buf, this->orig) != 0) {
+			/* If the parent is NULL, the editbox is handled by general function
+				* HandleOnEditText */
+			if (this->parent != NULL) {
+				this->parent->OnQueryTextFinished(this->text.buf);
+			} else {
+				HandleOnEditText(this->text.buf);
+			}
+		}
+	}
 
-						/* If the parent is NULL, the editbox is handled by general function
-						 * HandleOnEditText */
-						if (parent != NULL) {
-							parent->OnQueryTextFinished(qs->text.buf);
-						} else {
-							HandleOnEditText(qs->text.buf);
-						}
-					}
-					/* Fallthrough */
-				case QUERY_STR_WIDGET_CANCEL:
-					delete w;
-					break;
-			}
-			break;
+	virtual void OnClick(Point pt, int widget)
+	{
+		switch (widget) {
+			case QUERY_STR_WIDGET_TEXT:
+				ShowOnScreenKeyboard(this, QUERY_STR_WIDGET_TEXT, QUERY_STR_WIDGET_CANCEL, QUERY_STR_WIDGET_OK);
+				break;
+
+			case QUERY_STR_WIDGET_OK:
+				this->OnOk();
+				/* Fallthrough */
+			case QUERY_STR_WIDGET_CANCEL:
+				delete this;
+				break;
+		}
+	}
+
+	virtual void OnMouseLoop()
+	{
+		this->HandleEditBox(QUERY_STR_WIDGET_TEXT);
+	}
 
-		case WE_MOUSELOOP:
-			HandleEditBox(w, qs, QUERY_STR_WIDGET_TEXT);
-			break;
+	virtual bool OnKeyPress(uint16 key, uint16 keycode)
+	{
+		bool cont;
+		switch (this->HandleEditBoxKey(QUERY_STR_WIDGET_TEXT, key, keycode, cont)) {
+			case 1: this->OnOk(); // Enter pressed, confirms change
+			/* FALL THROUGH */
+			case 2: delete this; break; // ESC pressed, closes window, abandons changes
+		}
+		return cont;
+	}
 
-		case WE_KEYPRESS:
-			switch (HandleEditBoxKey(w, qs, QUERY_STR_WIDGET_TEXT, e)) {
-				case 1: goto press_ok; // Enter pressed, confirms change
-				case 2: delete 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) {
-				qs->handled = true;
-				w->parent->OnQueryTextFinished(NULL);
-			}
-			ClrBit(_no_scroll, SCROLL_EDIT);
-			break;
+	~QueryStringWindow()
+	{
+		if (!this->handled && this->parent != NULL) {
+			this->handled = true;
+			this->parent->OnQueryTextFinished(NULL);
+		}
+		ClrBit(_no_scroll, SCROLL_EDIT);
 	}
-}
+};
 
 static const Widget _query_string_widgets[] = {
 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,        STR_018B_CLOSE_WINDOW},
@@ -1056,12 +1082,9 @@
 	WC_QUERY_STRING, WC_NONE,
 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
 	_query_string_widgets,
-	QueryStringWndProc
+	NULL
 };
 
-char _edit_str_buf[64];
-char _orig_str_buf[lengthof(_edit_str_buf)];
-
 /** 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
@@ -1075,28 +1098,27 @@
 {
 	uint realmaxlen = maxlen & ~0x1000;
 
-	assert(realmaxlen < lengthof(_edit_str_buf));
-
 	DeleteWindowById(WC_QUERY_STRING, 0);
 	DeleteWindowById(WC_SAVELOAD, 0);
 
-	Window *w = new Window(&_query_string_desc);
-	w->parent = parent;
+	QueryStringWindow *w = new QueryStringWindow(&_query_string_desc, parent);
 
-	GetString(_edit_str_buf, str, lastof(_edit_str_buf));
-	_edit_str_buf[realmaxlen - 1] = '\0';
+	assert(realmaxlen < lengthof(w->edit_str_buf));
+
+	GetString(w->edit_str_buf, str, lastof(w->edit_str_buf));
+	w->edit_str_buf[realmaxlen - 1] = '\0';
 
 	if (maxlen & 0x1000) {
-		WP(w, querystr_d).orig = NULL;
+		w->orig = NULL;
 	} else {
-		strecpy(_orig_str_buf, _edit_str_buf, lastof(_orig_str_buf));
-		WP(w, querystr_d).orig = _orig_str_buf;
+		strecpy(w->orig_str_buf, w->edit_str_buf, lastof(w->orig_str_buf));
+		w->orig = w->orig_str_buf;
 	}
 
 	w->LowerWidget(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);
+	w->caption = caption;
+	w->afilter = afilter;
+	InitializeTextBuffer(&w->text, w->edit_str_buf, realmaxlen, maxwidth);
 }
 
 
@@ -1306,230 +1328,276 @@
 	}
 }
 
-static void GenerateFileName()
-{
-	/* Check if we are not a spectator 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->index);
-	SetDParam(1, _date);
-	GetString(_edit_str_buf, STR_4004, lastof(_edit_str_buf));
-	SanitizeFilename(_edit_str_buf);
-}
-
 extern void StartupEngines();
 
-static void SaveLoadDlgWndProc(Window *w, WindowEvent *e)
-{
-	static FiosItem o_dir;
+struct SaveLoadWindow : public QueryStringBaseWindow {
+	FiosItem o_dir;
+
+	void GenerateFileName()
+	{
+		/* Check if we are not a spectator 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);
 
-	switch (e->event) {
-		case WE_CREATE: // Set up OPENTTD button
-			w->vscroll.cap = 10;
-			w->resize.step_width = 2;
-			w->resize.step_height = 10;
+		SetDParam(0, p->index);
+		SetDParam(1, _date);
+		GetString(this->edit_str_buf, STR_4004, lastof(this->edit_str_buf));
+		SanitizeFilename(this->edit_str_buf);
+	}
+
+	SaveLoadWindow(const WindowDesc *desc, SaveLoadDialogMode mode) : QueryStringBaseWindow(desc)
+	{
+		static const StringID saveload_captions[] = {
+			STR_4001_LOAD_GAME,
+			STR_0298_LOAD_SCENARIO,
+			STR_4000_SAVE_GAME,
+			STR_0299_SAVE_SCENARIO,
+			STR_LOAD_HEIGHTMAP,
+		};
 
-			o_dir.type = FIOS_TYPE_DIRECT;
-			switch (_saveload_mode) {
-				case SLD_SAVE_GAME:
-				case SLD_LOAD_GAME:
-					FioGetDirectory(o_dir.name, lengthof(o_dir.name), SAVE_DIR);
-					break;
+		SetObjectToPlace(SPR_CURSOR_ZZZ, PAL_NONE, VHM_NONE, WC_MAIN_WINDOW, 0);
+		SetBit(_no_scroll, SCROLL_SAVE);
+
+		/* Use an array to define what will be the current file type being handled
+		 * by current file mode */
+		switch (mode) {
+			case SLD_SAVE_GAME:     this->GenerateFileName(); break;
+			case SLD_SAVE_SCENARIO: strcpy(this->edit_str_buf, "UNNAMED"); break;
+			default:                break;
+		}
+
+		assert((uint)mode < lengthof(saveload_captions));
 
-				case SLD_SAVE_SCENARIO:
-				case SLD_LOAD_SCENARIO:
-					FioGetDirectory(o_dir.name, lengthof(o_dir.name), SCENARIO_DIR);
-					break;
+		this->widget[1].data = saveload_captions[mode];
+		this->LowerWidget(7);
+
+		this->afilter = CS_ALPHANUMERAL;
+		InitializeTextBuffer(&this->text, this->edit_str_buf, lengthof(this->edit_str_buf), 240);
 
-				case SLD_LOAD_HEIGHTMAP:
-					FioGetDirectory(o_dir.name, lengthof(o_dir.name), HEIGHTMAP_DIR);
-					break;
+		/* 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) {
+			if (_pause_game >= 0) DoCommandP(0, 1, 0, NULL, CMD_PAUSE);
+		}
+
+		BuildFileList();
+
+		ResetObjectToPlace();
 
-				default:
-					ttd_strlcpy(o_dir.name, _personal_dir, lengthof(o_dir.name));
-			}
-			break;
+		o_dir.type = FIOS_TYPE_DIRECT;
+		switch (_saveload_mode) {
+			case SLD_SAVE_GAME:
+			case SLD_LOAD_GAME:
+				FioGetDirectory(o_dir.name, lengthof(o_dir.name), SAVE_DIR);
+				break;
+
+			case SLD_SAVE_SCENARIO:
+			case SLD_LOAD_SCENARIO:
+				FioGetDirectory(o_dir.name, lengthof(o_dir.name), SCENARIO_DIR);
+				break;
 
-		case WE_PAINT: {
-			int pos;
-			int y;
+			case SLD_LOAD_HEIGHTMAP:
+				FioGetDirectory(o_dir.name, lengthof(o_dir.name), HEIGHTMAP_DIR);
+				break;
 
-			SetVScrollCount(w, _fios_num);
-			DrawWindowWidgets(w);
-			DrawFiosTexts(w->width);
+			default:
+				ttd_strlcpy(o_dir.name, _personal_dir, lengthof(o_dir.name));
+		}
+
+		this->vscroll.cap = 10;
+		this->resize.step_width = 2;
+		this->resize.step_height = 10;
+
+		this->FindWindowPlacementAndResize(desc);
+	}
 
-			if (_savegame_sort_dirty) {
-				_savegame_sort_dirty = false;
-				MakeSortedSaveGameList();
-			}
+	virtual ~SaveLoadWindow()
+	{
+		/* pause is only used in single-player, non-editor mode, non menu mode */
+		if (!_networking && _game_mode != GM_EDITOR && _game_mode != GM_MENU) {
+			if (_pause_game >= 0) DoCommandP(0, 0, 0, NULL, CMD_PAUSE);
+		}
+		FiosFreeSavegameList();
+		ClrBit(_no_scroll, SCROLL_SAVE);
+	}
 
-			GfxFillRect(w->widget[7].left + 1, w->widget[7].top + 1, w->widget[7].right, w->widget[7].bottom, 0xD7);
-			DrawSortButtonState(w, _savegame_sort_order & SORT_BY_NAME ? 2 : 3, _savegame_sort_order & SORT_DESCENDING ? SBS_DOWN : SBS_UP);
+	virtual void OnPaint()
+	{
+		int pos;
+		int y;
 
-			y = w->widget[7].top + 1;
-			for (pos = w->vscroll.pos; pos < _fios_num; pos++) {
-				const FiosItem *item = _fios_list + pos;
+		SetVScrollCount(this, _fios_num);
+		DrawWindowWidgets(this);
+		DrawFiosTexts(this->width);
+
+		if (_savegame_sort_dirty) {
+			_savegame_sort_dirty = false;
+			MakeSortedSaveGameList();
+		}
 
-				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;
-			}
+		GfxFillRect(this->widget[7].left + 1, this->widget[7].top + 1, this->widget[7].right, this->widget[7].bottom, 0xD7);
+		DrawSortButtonState(this, _savegame_sort_order & SORT_BY_NAME ? 2 : 3, _savegame_sort_order & SORT_DESCENDING ? SBS_DOWN : SBS_UP);
 
-			if (_saveload_mode == SLD_SAVE_GAME || _saveload_mode == SLD_SAVE_SCENARIO) {
-				DrawEditBox(w, &WP(w, querystr_d), 10);
-			}
-			break;
+		y = this->widget[7].top + 1;
+		for (pos = this->vscroll.pos; pos < _fios_num; pos++) {
+			const FiosItem *item = _fios_list + pos;
+
+			DoDrawStringTruncated(item->title, 4, y, _fios_colors[item->type], this->width - 18);
+			y += 10;
+			if (y >= this->vscroll.cap * 10 + this->widget[7].top + 1) 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;
-					w->SetDirty();
-					break;
+		if (_saveload_mode == SLD_SAVE_GAME || _saveload_mode == SLD_SAVE_SCENARIO) {
+			this->DrawEditBox(10);
+		}
+	}
 
-				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;
-					w->SetDirty();
-					break;
+	virtual void OnClick(Point pt, int widget)
+	{
+		switch (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;
+				this->SetDirty();
+				break;
 
-				case 6: // OpenTTD 'button', jumps to OpenTTD directory
-					FiosBrowseTo(&o_dir);
-					w->SetDirty();
-					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;
+			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;
+				this->SetDirty();
+				break;
 
-					if (y < 0 || (y += w->vscroll.pos) >= w->vscroll.count) return;
-
-					file = _fios_list + y;
+			case 6: // OpenTTD 'button', jumps to OpenTTD directory
+				FiosBrowseTo(&o_dir);
+				this->SetDirty();
+				BuildFileList();
+				break;
 
-					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));
+			case 7: { // Click the listbox
+				int y = (pt.y - this->widget[widget].top - 1) / 10;
+				char *name;
+				const FiosItem *file;
 
-							delete 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));
+				if (y < 0 || (y += this->vscroll.pos) >= this->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));
 
-							delete 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);
-							w->InvalidateWidget(10);
-						}
+						delete this;
+					} 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));
+
+						delete this;
+						ShowHeightmapLoad();
 					} else {
-						/* Changed directory, need repaint. */
-						w->SetDirty();
-						BuildFileList();
+						/* SLD_SAVE_GAME, SLD_SAVE_SCENARIO copy clicked name to editbox */
+						ttd_strlcpy(this->text.buf, file->title, this->text.maxlength);
+						UpdateTextBufferSize(&this->text);
+						this->InvalidateWidget(10);
 					}
-					break;
+				} else {
+					/* Changed directory, need repaint. */
+					this->SetDirty();
+					BuildFileList();
 				}
-
-				case 10: // edit box
-					ShowOnScreenKeyboard(w, &WP(w, querystr_d), e->we.click.widget, 0, 0);
-					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) {
-				delete w;
-				return;
+				break;
 			}
 
-			if ((_saveload_mode == SLD_SAVE_GAME || _saveload_mode == SLD_SAVE_SCENARIO) &&
-					HandleEditBoxKey(w, &WP(w, querystr_d), 10, e) == 1) { // Press Enter
-				w->HandleButtonClick(12);
-			}
-			break;
+			case 10: // edit box
+				ShowOnScreenKeyboard(this, widget, 0, 0);
+				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;
+			case 11: case 12: // Delete, Save game
+				break;
+		}
+	}
 
-			if (w->IsWidgetLowered(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 successful delete */
-					if (_saveload_mode == SLD_SAVE_GAME) GenerateFileName();
-				}
+	virtual void OnMouseLoop()
+	{
+		if (_saveload_mode == SLD_SAVE_GAME || _saveload_mode == SLD_SAVE_SCENARIO) {
+			this->HandleEditBox(10);
+		}
+	}
 
-				UpdateTextBufferSize(&WP(w, querystr_d).text);
-				w->SetDirty();
-			} else if (w->IsWidgetLowered(12)) { // Save button clicked
-				_switch_mode = SM_SAVE;
-				FiosMakeSavegameName(_file_to_saveload.name, WP(w, querystr_d).text.buf, sizeof(_file_to_saveload.name));
+	virtual bool OnKeyPress(uint16 key, uint16 keycode)
+	{
+		if (keycode == WKC_ESC) {
+			delete this;
+			return false;
+		}
 
-				/* In the editor set up the vehicle engines correctly (date might have changed) */
-				if (_game_mode == GM_EDITOR) StartupEngines();
-			}
-			break;
+		bool cont = true;
+		if ((_saveload_mode == SLD_SAVE_GAME || _saveload_mode == SLD_SAVE_SCENARIO) &&
+				this->HandleEditBoxKey(10, key, keycode, cont) == 1) { // Press Enter
+			this->HandleButtonClick(12);
+		}
+
+		return cont;
+	}
 
-		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) {
-				if (_pause_game >= 0) DoCommandP(0, 0, 0, NULL, CMD_PAUSE);
-			}
-			FiosFreeSavegameList();
-			ClrBit(_no_scroll, SCROLL_SAVE);
-			break;
+	virtual void OnTimeout()
+	{
+		/* 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)) return;
 
-		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;
+		if (this->IsWidgetLowered(11)) { // Delete button clicked
+			if (!FiosDelete(this->text.buf)) {
+				ShowErrorMessage(INVALID_STRING_ID, STR_4008_UNABLE_TO_DELETE_FILE, 0, 0);
+			} else {
+				BuildFileList();
+				/* Reset file name to current date on successful delete */
+				if (_saveload_mode == SLD_SAVE_GAME) GenerateFileName();
 			}
 
-			w->vscroll.cap += e->we.sizing.diff.y / 10;
-		} break;
+			UpdateTextBufferSize(&this->text);
+			this->SetDirty();
+		} else if (this->IsWidgetLowered(12)) { // Save button clicked
+			_switch_mode = SM_SAVE;
+			FiosMakeSavegameName(_file_to_saveload.name, this->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();
+		}
 	}
-}
+
+	virtual void OnResize(Point new_size, Point delta)
+	{
+		/* Widget 2 and 3 have to go with halve speed, make it so obiwan */
+		uint diff = delta.x / 2;
+		this->widget[2].right += diff;
+		this->widget[3].left  += diff;
+		this->widget[3].right += delta.x;
+
+		/* Same for widget 11 and 12 in save-dialog */
+		if (_saveload_mode == SLD_SAVE_GAME || _saveload_mode == SLD_SAVE_SCENARIO) {
+			this->widget[11].right += diff;
+			this->widget[12].left  += diff;
+			this->widget[12].right += delta.x;
+		}
+
+		this->vscroll.cap += delta.y / 10;
+	}
+};
 
 static const WindowDesc _load_dialog_desc = {
 	WDP_CENTER, WDP_CENTER, 257, 154, 257, 294,
 	WC_SAVELOAD, WC_NONE,
 	WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_STD_BTN | WDF_UNCLICK_BUTTONS | WDF_RESIZABLE,
 	_load_dialog_widgets,
-	SaveLoadDlgWndProc,
+	NULL,
 };
 
 static const WindowDesc _save_dialog_desc = {
@@ -1537,7 +1605,7 @@
 	WC_SAVELOAD, WC_NONE,
 	WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_STD_BTN | WDF_UNCLICK_BUTTONS | WDF_RESIZABLE,
 	_save_dialog_widgets,
-	SaveLoadDlgWndProc,
+	NULL,
 };
 
 /** These values are used to convert the file/operations mode into a corresponding file type.
@@ -1553,50 +1621,22 @@
 
 void ShowSaveLoadDialog(SaveLoadDialogMode mode)
 {
-	static const StringID saveload_captions[] = {
-		STR_4001_LOAD_GAME,
-		STR_0298_LOAD_SCENARIO,
-		STR_4000_SAVE_GAME,
-		STR_0299_SAVE_SCENARIO,
-		STR_LOAD_HEIGHTMAP,
-	};
-
-	const WindowDesc *sld = &_save_dialog_desc;
-
-	SetObjectToPlace(SPR_CURSOR_ZZZ, PAL_NONE, VHM_NONE, WC_MAIN_WINDOW, 0);
 	DeleteWindowById(WC_QUERY_STRING, 0);
 	DeleteWindowById(WC_SAVELOAD, 0);
 
-	_saveload_mode = mode;
-	SetBit(_no_scroll, SCROLL_SAVE);
-
-	/* Use an array to define what will be the current file type being handled
-	 * by current file mode */
-	_file_to_saveload.filetype = _file_modetotype[mode];
+	const WindowDesc *sld;
 	switch (mode) {
-		case SLD_SAVE_GAME:     GenerateFileName(); break;
-		case SLD_SAVE_SCENARIO: strcpy(_edit_str_buf, "UNNAMED"); break;
-		default:                sld = &_load_dialog_desc; break;
+		case SLD_SAVE_GAME:
+		case SLD_SAVE_SCENARIO:
+			sld = &_save_dialog_desc; break;
+		default:
+			sld = &_load_dialog_desc; break;
 	}
 
-	assert((uint)mode < lengthof(saveload_captions));
-
-	Window *w = new Window(sld);
-	w->widget[1].data = saveload_captions[mode];
-	w->LowerWidget(7);
-
-	WP(w, querystr_d).afilter = CS_ALPHANUMERAL;
-	InitializeTextBuffer(&WP(w, querystr_d).text, _edit_str_buf, lengthof(_edit_str_buf), 240);
+	_saveload_mode = mode;
+	_file_to_saveload.filetype = _file_modetotype[mode];
 
-	/* 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) {
-		if (_pause_game >= 0) DoCommandP(0, 1, 0, NULL, CMD_PAUSE);
-	}
-
-	BuildFileList();
-
-	ResetObjectToPlace();
+	new SaveLoadWindow(sld, mode);
 }
 
 void RedrawAutosave()
--- a/src/network/network_gui.cpp
+++ b/src/network/network_gui.cpp
@@ -29,6 +29,7 @@
 #include "../player_func.h"
 #include "../settings_type.h"
 #include "../widgets/dropdown_func.h"
+#include "../querystring_gui.h"
 
 #include "table/strings.h"
 #include "../table/sprites.h"
@@ -36,15 +37,11 @@
 #define BGC 5
 #define BTC 15
 
-struct chatquerystr_d : public querystr_d {
-	DestType dtype;
-	int dest;
-};
-assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(chatquerystr_d));
-
+/*
 struct network_d {
 	byte field;              // select text-field in start-server and game-listing
 	byte widget_id;          ///< The widget that has the pop-up input menu
+	byte field;              // select text-field in start-server and game-listing
 	NetworkGameList *server; // selected server in lobby and game-listing
 	FiosItem *map;           // selected map in start-server
 };
@@ -53,15 +50,14 @@
 struct network_ql_d {
 	network_d n;                 // see above; general stuff
 	querystr_d q;                // text-input in start-server and game-listing
-	NetworkGameList **sort_list; // list of games (sorted)
 	list_d l;                    // accompanying list-administration
 };
 assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(network_ql_d));
+*/
 
 /* Global to remember sorting after window has been closed */
 static Listing _ng_sorting;
 
-static char _edit_str_net_buf[150];
 static bool _chat_tab_completion_active;
 
 static void ShowNetworkStartServerWindow();
@@ -159,64 +155,6 @@
 	return _internal_sort_order ? -r : r;
 }
 
-/** (Re)build the network game list as its amount has changed because
- * an item has been added or deleted for example
- * @param ngl list_d struct that contains all necessary information for sorting */
-static void BuildNetworkGameList(network_ql_d *nqld)
-{
-	NetworkGameList *ngl_temp;
-	uint n = 0;
-
-	if (!(nqld->l.flags & VL_REBUILD)) return;
-
-	/* Count the number of games in the list */
-	for (ngl_temp = _network_game_list; ngl_temp != NULL; ngl_temp = ngl_temp->next) n++;
-	if (n == 0) return;
-
-	/* Create temporary array of games to use for listing */
-	free(nqld->sort_list);
-	nqld->sort_list = MallocT<NetworkGameList*>(n);
-	nqld->l.list_length = n;
-
-	for (n = 0, ngl_temp = _network_game_list; ngl_temp != NULL; ngl_temp = ngl_temp->next) {
-		nqld->sort_list[n++] = ngl_temp;
-	}
-
-	/* Force resort */
-	nqld->l.flags &= ~VL_REBUILD;
-	nqld->l.flags |= VL_RESORT;
-}
-
-static void SortNetworkGameList(network_ql_d *nqld)
-{
-	static NGameNameSortFunction * const ngame_sorter[] = {
-		&NGameNameSorter,
-		&NGameClientSorter,
-		&NGameAllowedSorter
-	};
-
-	NetworkGameList *item;
-	uint i;
-
-	if (!(nqld->l.flags & VL_RESORT)) return;
-	if (nqld->l.list_length == 0) return;
-
-	_internal_sort_order = !!(nqld->l.flags & VL_DESC);
-	qsort(nqld->sort_list, nqld->l.list_length, sizeof(nqld->sort_list[0]), ngame_sorter[nqld->l.sort_type]);
-
-	/* After sorting ngl->sort_list contains the sorted items. Put these back
-	 * into the original list. Basically nothing has changed, we are only
-	 * shuffling the ->next pointers */
-	_network_game_list = nqld->sort_list[0];
-	for (item = _network_game_list, i = 1; i != nqld->l.list_length; i++) {
-		item->next = nqld->sort_list[i];
-		item = item->next;
-	}
-	item->next = NULL;
-
-	nqld->l.flags &= ~VL_RESORT;
-}
-
 /** Enum for NetworkGameWindow, referring to _network_game_window_widgets */
 enum NetworkGameWindowWidgets {
 	NGWW_CLOSE,         ///< Close 'X' button
@@ -248,366 +186,432 @@
 	NGWW_CANCEL,        ///< 'Cancel' button
 };
 
-/**
- * Draw a single server line.
- * @param cur_item  the server to draw.
- * @param y         from where to draw?
- * @param highlight does the line need to be highlighted?
- */
-static void DrawServerLine(const Window *w, const NetworkGameList *cur_item, uint y, bool highlight)
-{
-	/* show highlighted item with a different colour */
-	if (highlight) GfxFillRect(w->widget[NGWW_NAME].left + 1, y - 2, w->widget[NGWW_INFO].right - 1, y + 9, 10);
+struct NetworkGameWindow : public QueryStringBaseWindow {
+	byte field;                  ///< selected text-field
+	NetworkGameList *server;     ///< selected server
+	NetworkGameList **sort_list; ///< list of games (sorted)
+	list_d ld;                   ///< accompanying list-administration
 
-	SetDParamStr(0, cur_item->info.server_name);
-	DrawStringTruncated(w->widget[NGWW_NAME].left + 5, y, STR_02BD, TC_BLACK, w->widget[NGWW_NAME].right - w->widget[NGWW_NAME].left - 5);
+	NetworkGameWindow(const WindowDesc *desc) : QueryStringBaseWindow(desc)
+	{
+		ttd_strlcpy(this->edit_str_buf, _network_player_name, lengthof(this->edit_str_buf));
+		this->afilter = CS_ALPHANUMERAL;
+		InitializeTextBuffer(&this->text, this->edit_str_buf, lengthof(this->edit_str_buf), 120);
+
+		UpdateNetworkGameWindow(true);
 
-	SetDParam(0, cur_item->info.clients_on);
-	SetDParam(1, cur_item->info.clients_max);
-	SetDParam(2, cur_item->info.companies_on);
-	SetDParam(3, cur_item->info.companies_max);
-	DrawStringCentered(w->widget[NGWW_CLIENTS].left + 39, y, STR_NETWORK_GENERAL_ONLINE, TC_GOLD);
+		this->vscroll.cap = 11;
+		this->resize.step_height = NET_PRC__SIZE_OF_ROW;
+
+		this->field = NGWW_PLAYER;
+		this->server = NULL;
 
-	/* only draw icons if the server is online */
-	if (cur_item->online) {
-		/* draw a lock if the server is password protected */
-		if (cur_item->info.use_password) DrawSprite(SPR_LOCK, PAL_NONE, w->widget[NGWW_INFO].left + 5, y - 1);
+		this->sort_list = NULL;
+		this->ld.flags = VL_REBUILD | (_ng_sorting.order ? VL_DESC : VL_NONE);
+		this->ld.sort_type = _ng_sorting.criteria;
 
-		/* draw red or green icon, depending on compatibility with server */
-		DrawSprite(SPR_BLOT, (cur_item->info.compatible ? PALETTE_TO_GREEN : (cur_item->info.version_compatible ? PALETTE_TO_YELLOW : PALETTE_TO_RED)), w->widget[NGWW_INFO].left + 15, y);
+		this->FindWindowPlacementAndResize(desc);
+	}
 
-		/* draw flag according to server language */
-		DrawSprite(SPR_FLAGS_BASE + cur_item->info.server_lang, PAL_NONE, w->widget[NGWW_INFO].left + 25, y);
+	~NetworkGameWindow()
+	{
+		free(this->sort_list);
 	}
-}
+
+	/**
+	 * (Re)build the network game list as its amount has changed because
+	 * an item has been added or deleted for example
+	 */
+	void BuildNetworkGameList()
+	{
+		NetworkGameList *ngl_temp;
+		uint n = 0;
+
+		if (!(this->ld.flags & VL_REBUILD)) return;
 
-/**
- * Handler of actions done in the NetworkStartServer window
- *
- * @param w pointer to the Window structure
- * @param e pointer to window event
- * @note    Uses network_ql_d (network_d, querystr_d and list_d) WP macro
- * @see     struct _network_game_window_widgets
- * @see     enum NetworkGameWindowWidgets
- */
+		/* Count the number of games in the list */
+		for (ngl_temp = _network_game_list; ngl_temp != NULL; ngl_temp = ngl_temp->next) n++;
+		if (n == 0) return;
+
+		/* Create temporary array of games to use for listing */
+		this->sort_list = ReallocT(this->sort_list, n);
+		this->ld.list_length = n;
 
-static void NetworkGameWindowWndProc(Window *w, WindowEvent *e)
-{
-	network_d *nd = &WP(w, network_ql_d).n;
-	list_d *ld = &WP(w, network_ql_d).l;
+		for (n = 0, ngl_temp = _network_game_list; ngl_temp != NULL; ngl_temp = ngl_temp->next) {
+			this->sort_list[n++] = ngl_temp;
+		}
+
+		/* Force resort */
+		this->ld.flags &= ~VL_REBUILD;
+		this->ld.flags |= VL_RESORT;
+	}
 
-	switch (e->event) {
-		case WE_CREATE: // Focus input box
-			w->vscroll.cap = 11;
-			w->resize.step_height = NET_PRC__SIZE_OF_ROW;
+	void SortNetworkGameList()
+	{
+		static NGameNameSortFunction * const ngame_sorter[] = {
+			&NGameNameSorter,
+			&NGameClientSorter,
+			&NGameAllowedSorter
+		};
 
-			nd->field = NGWW_PLAYER;
-			nd->server = NULL;
+		NetworkGameList *item;
+		uint i;
+
+		if (!(this->ld.flags & VL_RESORT)) return;
+		if (this->ld.list_length == 0) return;
 
-			WP(w, network_ql_d).sort_list = NULL;
-			ld->flags = VL_REBUILD | (_ng_sorting.order ? VL_DESC : VL_NONE);
-			ld->sort_type = _ng_sorting.criteria;
-			break;
+		_internal_sort_order = !!(this->ld.flags & VL_DESC);
+		qsort(this->sort_list, this->ld.list_length, sizeof(this->sort_list[0]), ngame_sorter[this->ld.sort_type]);
 
-		case WE_PAINT: {
-			const NetworkGameList *sel = nd->server;
-			const SortButtonState arrow = (ld->flags & VL_DESC) ? SBS_DOWN : SBS_UP;
+		/* After sorting ngl->sort_list contains the sorted items. Put these back
+		 * into the original list. Basically nothing has changed, we are only
+		 * shuffling the ->next pointers */
+		_network_game_list = this->sort_list[0];
+		for (item = _network_game_list, i = 1; i != this->ld.list_length; i++) {
+			item->next = this->sort_list[i];
+			item = item->next;
+		}
+		item->next = NULL;
+
+		this->ld.flags &= ~VL_RESORT;
+	}
 
-			if (ld->flags & VL_REBUILD) {
-				BuildNetworkGameList(&WP(w, network_ql_d));
-				SetVScrollCount(w, ld->list_length);
-			}
-			if (ld->flags & VL_RESORT) SortNetworkGameList(&WP(w, network_ql_d));
+	/**
+	 * Draw a single server line.
+	 * @param cur_item  the server to draw.
+	 * @param y         from where to draw?
+	 * @param highlight does the line need to be highlighted?
+	 */
+	void DrawServerLine(const NetworkGameList *cur_item, uint y, bool highlight)
+	{
+		/* show highlighted item with a different colour */
+		if (highlight) GfxFillRect(this->widget[NGWW_NAME].left + 1, y - 2, this->widget[NGWW_INFO].right - 1, y + 9, 10);
 
-			/* 'Refresh' button invisible if no server selected */
-			w->SetWidgetDisabledState(NGWW_REFRESH, sel == NULL);
-			/* 'Join' button disabling conditions */
-			w->SetWidgetDisabledState(NGWW_JOIN, sel == NULL || // no Selected Server
-					!sel->online || // Server offline
-					sel->info.clients_on >= sel->info.clients_max || // Server full
-					!sel->info.compatible); // Revision mismatch
+		SetDParamStr(0, cur_item->info.server_name);
+		DrawStringTruncated(this->widget[NGWW_NAME].left + 5, y, STR_02BD, TC_BLACK, this->widget[NGWW_NAME].right - this->widget[NGWW_NAME].left - 5);
+
+		SetDParam(0, cur_item->info.clients_on);
+		SetDParam(1, cur_item->info.clients_max);
+		SetDParam(2, cur_item->info.companies_on);
+		SetDParam(3, cur_item->info.companies_max);
+		DrawStringCentered(this->widget[NGWW_CLIENTS].left + 39, y, STR_NETWORK_GENERAL_ONLINE, TC_GOLD);
+
+		/* only draw icons if the server is online */
+		if (cur_item->online) {
+			/* draw a lock if the server is password protected */
+			if (cur_item->info.use_password) DrawSprite(SPR_LOCK, PAL_NONE, this->widget[NGWW_INFO].left + 5, y - 1);
 
-			/* 'NewGRF Settings' button invisible if no NewGRF is used */
-			w->SetWidgetHiddenState(NGWW_NEWGRF, sel == NULL ||
-					!sel->online ||
-					sel->info.grfconfig == NULL);
+			/* draw red or green icon, depending on compatibility with server */
+			DrawSprite(SPR_BLOT, (cur_item->info.compatible ? PALETTE_TO_GREEN : (cur_item->info.version_compatible ? PALETTE_TO_YELLOW : PALETTE_TO_RED)), this->widget[NGWW_INFO].left + 15, y);
 
-			SetDParam(0, 0x00);
-			SetDParam(1, _lan_internet_types_dropdown[_network_lan_internet]);
-			DrawWindowWidgets(w);
+			/* draw flag according to server language */
+			DrawSprite(SPR_FLAGS_BASE + cur_item->info.server_lang, PAL_NONE, this->widget[NGWW_INFO].left + 25, y);
+		}
+	}
+
+	virtual void OnPaint()
+	{
+		const NetworkGameList *sel = this->server;
+		const SortButtonState arrow = (this->ld.flags & VL_DESC) ? SBS_DOWN : SBS_UP;
 
-			/* Edit box to set player name */
-			DrawEditBox(w, &WP(w, network_ql_d).q, NGWW_PLAYER);
-
-			DrawString(w->widget[NGWW_PLAYER].left - 100, 23, STR_NETWORK_PLAYER_NAME, TC_GOLD);
+		if (this->ld.flags & VL_REBUILD) {
+			this->BuildNetworkGameList();
+			SetVScrollCount(this, this->ld.list_length);
+		}
+		if (this->ld.flags & VL_RESORT) this->SortNetworkGameList();
 
-			/* Sort based on widgets: name, clients, compatibility */
-			switch (ld->sort_type) {
-				case NGWW_NAME    - NGWW_NAME: DrawSortButtonState(w, NGWW_NAME,    arrow); break;
-				case NGWW_CLIENTS - NGWW_NAME: DrawSortButtonState(w, NGWW_CLIENTS, arrow); break;
-				case NGWW_INFO    - NGWW_NAME: DrawSortButtonState(w, NGWW_INFO,    arrow); break;
-			}
+		/* 'Refresh' button invisible if no server selected */
+		this->SetWidgetDisabledState(NGWW_REFRESH, sel == NULL);
+		/* 'Join' button disabling conditions */
+		this->SetWidgetDisabledState(NGWW_JOIN, sel == NULL || // no Selected Server
+				!sel->online || // Server offline
+				sel->info.clients_on >= sel->info.clients_max || // Server full
+				!sel->info.compatible); // Revision mismatch
 
-			uint16 y = NET_PRC__OFFSET_TOP_WIDGET + 3;
-			int32 n = 0;
-			int32 pos = w->vscroll.pos;
-			const NetworkGameList *cur_item = _network_game_list;
+		/* 'NewGRF Settings' button invisible if no NewGRF is used */
+		this->SetWidgetHiddenState(NGWW_NEWGRF, sel == NULL ||
+				!sel->online ||
+				sel->info.grfconfig == NULL);
+
+		SetDParam(0, 0x00);
+		SetDParam(1, _lan_internet_types_dropdown[_network_lan_internet]);
+		DrawWindowWidgets(this);
+
+		/* Edit box to set player name */
+		this->DrawEditBox(NGWW_PLAYER);
 
-			while (pos > 0 && cur_item != NULL) {
-				pos--;
-				cur_item = cur_item->next;
-			}
+		DrawString(this->widget[NGWW_PLAYER].left - 100, 23, STR_NETWORK_PLAYER_NAME, TC_GOLD);
 
-			while (cur_item != NULL) {
-				DrawServerLine(w, cur_item, y, cur_item == sel);
+		/* Sort based on widgets: name, clients, compatibility */
+		switch (this->ld.sort_type) {
+			case NGWW_NAME    - NGWW_NAME: DrawSortButtonState(this, NGWW_NAME,    arrow); break;
+			case NGWW_CLIENTS - NGWW_NAME: DrawSortButtonState(this, NGWW_CLIENTS, arrow); break;
+			case NGWW_INFO    - NGWW_NAME: DrawSortButtonState(this, NGWW_INFO,    arrow); break;
+		}
 
-				cur_item = cur_item->next;
-				y += NET_PRC__SIZE_OF_ROW;
-				if (++n == w->vscroll.cap) break; // max number of games in the window
-			}
+		uint16 y = NET_PRC__OFFSET_TOP_WIDGET + 3;
+		int32 n = 0;
+		int32 pos = this->vscroll.pos;
+		const NetworkGameList *cur_item = _network_game_list;
 
-			const NetworkGameList *last_joined = NetworkGameListAddItem(inet_addr(_network_last_host), _network_last_port);
-			/* Draw the last joined server, if any */
-			if (last_joined != NULL) DrawServerLine(w, last_joined, y = w->widget[NGWW_LASTJOINED].top + 3, last_joined == sel);
+		while (pos > 0 && cur_item != NULL) {
+			pos--;
+			cur_item = cur_item->next;
+		}
+
+		while (cur_item != NULL) {
+			this->DrawServerLine(cur_item, y, cur_item == sel);
+
+			cur_item = cur_item->next;
+			y += NET_PRC__SIZE_OF_ROW;
+			if (++n == this->vscroll.cap) break; // max number of games in the window
+		}
 
-			/* Draw the right menu */
-			GfxFillRect(w->widget[NGWW_DETAILS].left + 1, 43, w->widget[NGWW_DETAILS].right - 1, 92, 157);
-			if (sel == NULL) {
-				DrawStringCentered(w->widget[NGWW_DETAILS].left + 115, 58, STR_NETWORK_GAME_INFO, TC_FROMSTRING);
-			} else if (!sel->online) {
-				SetDParamStr(0, sel->info.server_name);
-				DrawStringCentered(w->widget[NGWW_DETAILS].left + 115, 68, STR_ORANGE, TC_FROMSTRING); // game name
+		const NetworkGameList *last_joined = NetworkGameListAddItem(inet_addr(_network_last_host), _network_last_port);
+		/* Draw the last joined server, if any */
+		if (last_joined != NULL) this->DrawServerLine(last_joined, y = this->widget[NGWW_LASTJOINED].top + 3, last_joined == sel);
 
-				DrawStringCentered(w->widget[NGWW_DETAILS].left + 115, 132, STR_NETWORK_SERVER_OFFLINE, TC_FROMSTRING); // server offline
-			} else { // show game info
-				uint16 y = 100;
-				const uint16 x = w->widget[NGWW_DETAILS].left + 5;
+		/* Draw the right menu */
+		GfxFillRect(this->widget[NGWW_DETAILS].left + 1, 43, this->widget[NGWW_DETAILS].right - 1, 92, 157);
+		if (sel == NULL) {
+			DrawStringCentered(this->widget[NGWW_DETAILS].left + 115, 58, STR_NETWORK_GAME_INFO, TC_FROMSTRING);
+		} else if (!sel->online) {
+			SetDParamStr(0, sel->info.server_name);
+			DrawStringCentered(this->widget[NGWW_DETAILS].left + 115, 68, STR_ORANGE, TC_FROMSTRING); // game name
 
-				DrawStringCentered(w->widget[NGWW_DETAILS].left + 115, 48, STR_NETWORK_GAME_INFO, TC_FROMSTRING);
+			DrawStringCentered(this->widget[NGWW_DETAILS].left + 115, 132, STR_NETWORK_SERVER_OFFLINE, TC_FROMSTRING); // server offline
+		} else { // show game info
+			uint16 y = 100;
+			const uint16 x = this->widget[NGWW_DETAILS].left + 5;
+
+			DrawStringCentered(this->widget[NGWW_DETAILS].left + 115, 48, STR_NETWORK_GAME_INFO, TC_FROMSTRING);
 
 
-				SetDParamStr(0, sel->info.server_name);
-				DrawStringCenteredTruncated(w->widget[NGWW_DETAILS].left, w->widget[NGWW_DETAILS].right, 62, STR_ORANGE, TC_BLACK); // game name
-
-				SetDParamStr(0, sel->info.map_name);
-				DrawStringCenteredTruncated(w->widget[NGWW_DETAILS].left, w->widget[NGWW_DETAILS].right, 74, STR_02BD, TC_BLACK); // map name
+			SetDParamStr(0, sel->info.server_name);
+			DrawStringCenteredTruncated(this->widget[NGWW_DETAILS].left, this->widget[NGWW_DETAILS].right, 62, STR_ORANGE, TC_BLACK); // game name
 
-				SetDParam(0, sel->info.clients_on);
-				SetDParam(1, sel->info.clients_max);
-				SetDParam(2, sel->info.companies_on);
-				SetDParam(3, sel->info.companies_max);
-				DrawString(x, y, STR_NETWORK_CLIENTS, TC_GOLD);
-				y += 10;
-
-				SetDParam(0, STR_NETWORK_LANG_ANY + sel->info.server_lang);
-				DrawString(x, y, STR_NETWORK_LANGUAGE, TC_GOLD); // server language
-				y += 10;
+			SetDParamStr(0, sel->info.map_name);
+			DrawStringCenteredTruncated(this->widget[NGWW_DETAILS].left, this->widget[NGWW_DETAILS].right, 74, STR_02BD, TC_BLACK); // map name
 
-				SetDParam(0, STR_TEMPERATE_LANDSCAPE + sel->info.map_set);
-				DrawString(x, y, STR_NETWORK_TILESET, TC_GOLD); // tileset
-				y += 10;
-
-				SetDParam(0, sel->info.map_width);
-				SetDParam(1, sel->info.map_height);
-				DrawString(x, y, STR_NETWORK_MAP_SIZE, TC_GOLD); // map size
-				y += 10;
-
-				SetDParamStr(0, sel->info.server_revision);
-				DrawString(x, y, STR_NETWORK_SERVER_VERSION, TC_GOLD); // server version
-				y += 10;
-
-				SetDParamStr(0, sel->info.hostname);
-				SetDParam(1, sel->port);
-				DrawString(x, y, STR_NETWORK_SERVER_ADDRESS, TC_GOLD); // server address
-				y += 10;
+			SetDParam(0, sel->info.clients_on);
+			SetDParam(1, sel->info.clients_max);
+			SetDParam(2, sel->info.companies_on);
+			SetDParam(3, sel->info.companies_max);
+			DrawString(x, y, STR_NETWORK_CLIENTS, TC_GOLD);
+			y += 10;
 
-				SetDParam(0, sel->info.start_date);
-				DrawString(x, y, STR_NETWORK_START_DATE, TC_GOLD); // start date
-				y += 10;
-
-				SetDParam(0, sel->info.game_date);
-				DrawString(x, y, STR_NETWORK_CURRENT_DATE, TC_GOLD); // current date
-				y += 10;
-
-				y += 2;
-
-				if (!sel->info.compatible) {
-					DrawStringCentered(w->widget[NGWW_DETAILS].left + 115, y, sel->info.version_compatible ? STR_NETWORK_GRF_MISMATCH : STR_NETWORK_VERSION_MISMATCH, TC_FROMSTRING); // server mismatch
-				} else if (sel->info.clients_on == sel->info.clients_max) {
-					/* Show: server full, when clients_on == clients_max */
-					DrawStringCentered(w->widget[NGWW_DETAILS].left + 115, y, STR_NETWORK_SERVER_FULL, TC_FROMSTRING); // server full
-				} else if (sel->info.use_password) {
-					DrawStringCentered(w->widget[NGWW_DETAILS].left + 115, y, STR_NETWORK_PASSWORD, TC_FROMSTRING); // password warning
-				}
+			SetDParam(0, STR_NETWORK_LANG_ANY + sel->info.server_lang);
+			DrawString(x, y, STR_NETWORK_LANGUAGE, TC_GOLD); // server language
+			y += 10;
 
-				y += 10;
-			}
-		} break;
+			SetDParam(0, STR_TEMPERATE_LANDSCAPE + sel->info.map_set);
+			DrawString(x, y, STR_NETWORK_TILESET, TC_GOLD); // tileset
+			y += 10;
 
-		case WE_CLICK:
-			nd->field = e->we.click.widget;
-			switch (e->we.click.widget) {
-				case NGWW_PLAYER:
-					ShowOnScreenKeyboard(w, &WP(w, network_ql_d).q,  NGWW_PLAYER, 0, 0);
-					break;
-
-				case NGWW_CANCEL: // Cancel button
-					DeleteWindowById(WC_NETWORK_WINDOW, 0);
-					break;
-
-				case NGWW_CONN_BTN: // 'Connection' droplist
-					ShowDropDownMenu(w, _lan_internet_types_dropdown, _network_lan_internet, NGWW_CONN_BTN, 0, 0); // do it for widget NSSW_CONN_BTN
-					break;
+			SetDParam(0, sel->info.map_width);
+			SetDParam(1, sel->info.map_height);
+			DrawString(x, y, STR_NETWORK_MAP_SIZE, TC_GOLD); // map size
+			y += 10;
 
-				case NGWW_NAME: // Sort by name
-				case NGWW_CLIENTS: // Sort by connected clients
-				case NGWW_INFO: // Connectivity (green dot)
-					if (ld->sort_type == e->we.click.widget - NGWW_NAME) ld->flags ^= VL_DESC;
-					ld->flags |= VL_RESORT;
-					ld->sort_type = e->we.click.widget - NGWW_NAME;
-
-					_ng_sorting.order = !!(ld->flags & VL_DESC);
-					_ng_sorting.criteria = ld->sort_type;
-					SetWindowDirty(w);
-					break;
-
-				case NGWW_MATRIX: { // Matrix to show networkgames
-					NetworkGameList *cur_item;
-					uint32 id_v = (e->we.click.pt.y - NET_PRC__OFFSET_TOP_WIDGET) / NET_PRC__SIZE_OF_ROW;
+			SetDParamStr(0, sel->info.server_revision);
+			DrawString(x, y, STR_NETWORK_SERVER_VERSION, TC_GOLD); // server version
+			y += 10;
 
-					if (id_v >= w->vscroll.cap) return; // click out of bounds
-					id_v += w->vscroll.pos;
-
-					cur_item = _network_game_list;
-					for (; id_v > 0 && cur_item != NULL; id_v--) cur_item = cur_item->next;
+			SetDParamStr(0, sel->info.hostname);
+			SetDParam(1, sel->port);
+			DrawString(x, y, STR_NETWORK_SERVER_ADDRESS, TC_GOLD); // server address
+			y += 10;
 
-					nd->server = cur_item;
-					SetWindowDirty(w);
-				} break;
-
-				case NGWW_LASTJOINED: {
-					NetworkGameList *last_joined = NetworkGameListAddItem(inet_addr(_network_last_host), _network_last_port);
-					if (last_joined != NULL) {
-						nd->server = last_joined;
-						SetWindowDirty(w);
-					}
-				} break;
+			SetDParam(0, sel->info.start_date);
+			DrawString(x, y, STR_NETWORK_START_DATE, TC_GOLD); // start date
+			y += 10;
 
-				case NGWW_FIND: // Find server automatically
-					switch (_network_lan_internet) {
-						case 0: NetworkUDPSearchGame(); break;
-						case 1: NetworkUDPQueryMasterServer(); break;
-					}
-					break;
+			SetDParam(0, sel->info.game_date);
+			DrawString(x, y, STR_NETWORK_CURRENT_DATE, TC_GOLD); // current date
+			y += 10;
 
-				case NGWW_ADD: // Add a server
-					ShowQueryString(
-						BindCString(_network_default_ip),
-						STR_NETWORK_ENTER_IP,
-						31 | 0x1000,  // maximum number of characters OR
-						250, // characters up to this width pixels, whichever is satisfied first
-						w, CS_ALPHANUMERAL);
-					break;
-
-				case NGWW_START: // Start server
-					ShowNetworkStartServerWindow();
-					break;
+			y += 2;
 
-				case NGWW_JOIN: // Join Game
-					if (nd->server != NULL) {
-						snprintf(_network_last_host, sizeof(_network_last_host), "%s", inet_ntoa(*(struct in_addr *)&nd->server->ip));
-						_network_last_port = nd->server->port;
-						ShowNetworkLobbyWindow(nd->server);
-					}
-					break;
-
-				case NGWW_REFRESH: // Refresh
-					if (nd->server != NULL) NetworkUDPQueryServer(nd->server->info.hostname, nd->server->port);
-					break;
-
-				case NGWW_NEWGRF: // NewGRF Settings
-					if (nd->server != NULL) ShowNewGRFSettings(false, false, false, &nd->server->info.grfconfig);
-					break;
-			}
-			break;
-
-		case WE_DROPDOWN_SELECT: // we have selected a dropdown item in the list
-			switch (e->we.dropdown.button) {
-				case NGWW_CONN_BTN:
-					_network_lan_internet = e->we.dropdown.index;
-					break;
-
-				default:
-					NOT_REACHED();
+			if (!sel->info.compatible) {
+				DrawStringCentered(this->widget[NGWW_DETAILS].left + 115, y, sel->info.version_compatible ? STR_NETWORK_GRF_MISMATCH : STR_NETWORK_VERSION_MISMATCH, TC_FROMSTRING); // server mismatch
+			} else if (sel->info.clients_on == sel->info.clients_max) {
+				/* Show: server full, when clients_on == clients_max */
+				DrawStringCentered(this->widget[NGWW_DETAILS].left + 115, y, STR_NETWORK_SERVER_FULL, TC_FROMSTRING); // server full
+			} else if (sel->info.use_password) {
+				DrawStringCentered(this->widget[NGWW_DETAILS].left + 115, y, STR_NETWORK_PASSWORD, TC_FROMSTRING); // password warning
 			}
 
-			SetWindowDirty(w);
-			break;
+			y += 10;
+		}
+	}
+
+	virtual void OnClick(Point pt, int widget)
+	{
+		this->field = widget;
+		switch (widget) {
+			case NGWW_PLAYER:
+				ShowOnScreenKeyboard(this, NGWW_PLAYER, 0, 0);
+				break;
+
+			case NGWW_CANCEL: // Cancel button
+				DeleteWindowById(WC_NETWORK_WINDOW, 0);
+				break;
 
-		case WE_MOUSELOOP:
-			if (nd->field == NGWW_PLAYER) HandleEditBox(w, &WP(w, network_ql_d).q, NGWW_PLAYER);
-			break;
+			case NGWW_CONN_BTN: // 'Connection' droplist
+				ShowDropDownMenu(this, _lan_internet_types_dropdown, _network_lan_internet, NGWW_CONN_BTN, 0, 0); // do it for widget NSSW_CONN_BTN
+				break;
+
+			case NGWW_NAME: // Sort by name
+			case NGWW_CLIENTS: // Sort by connected clients
+			case NGWW_INFO: // Connectivity (green dot)
+				if (this->ld.sort_type == widget - NGWW_NAME) this->ld.flags ^= VL_DESC;
+				this->ld.flags |= VL_RESORT;
+				this->ld.sort_type = widget - NGWW_NAME;
+
+				_ng_sorting.order = !!(this->ld.flags & VL_DESC);
+				_ng_sorting.criteria = this->ld.sort_type;
+				this->SetDirty();
+				break;
+
+			case NGWW_MATRIX: { // Matrix to show networkgames
+				NetworkGameList *cur_item;
+				uint32 id_v = (pt.y - NET_PRC__OFFSET_TOP_WIDGET) / NET_PRC__SIZE_OF_ROW;
+
+				if (id_v >= this->vscroll.cap) return; // click out of bounds
+				id_v += this->vscroll.pos;
 
-		case WE_INVALIDATE_DATA:
-			if (e->we.invalidate.data != 0) nd->server = NULL;
-			ld->flags |= VL_REBUILD;
-			SetWindowDirty(w);
-			break;
+				cur_item = _network_game_list;
+				for (; id_v > 0 && cur_item != NULL; id_v--) cur_item = cur_item->next;
+
+				this->server = cur_item;
+				this->SetDirty();
+			} break;
+
+			case NGWW_LASTJOINED: {
+				NetworkGameList *last_joined = NetworkGameListAddItem(inet_addr(_network_last_host), _network_last_port);
+				if (last_joined != NULL) {
+					this->server = last_joined;
+					this->SetDirty();
+				}
+			} break;
 
-		case WE_KEYPRESS:
-			if (nd->field != NGWW_PLAYER) {
-				if (nd->server != NULL) {
-					if (e->we.keypress.keycode == WKC_DELETE) { // Press 'delete' to remove servers
-						NetworkGameListRemoveItem(nd->server);
-						NetworkRebuildHostList();
-						nd->server = NULL;
-					}
+			case NGWW_FIND: // Find server automatically
+				switch (_network_lan_internet) {
+					case 0: NetworkUDPSearchGame(); break;
+					case 1: NetworkUDPQueryMasterServer(); break;
+				}
+				break;
+
+			case NGWW_ADD: // Add a server
+				ShowQueryString(
+					BindCString(_network_default_ip),
+					STR_NETWORK_ENTER_IP,
+					31 | 0x1000,  // maximum number of characters OR
+					250, // characters up to this width pixels, whichever is satisfied first
+					this, CS_ALPHANUMERAL);
+				break;
+
+			case NGWW_START: // Start server
+				ShowNetworkStartServerWindow();
+				break;
+
+			case NGWW_JOIN: // Join Game
+				if (this->server != NULL) {
+					snprintf(_network_last_host, sizeof(_network_last_host), "%s", inet_ntoa(*(struct in_addr *)&this->server->ip));
+					_network_last_port = this->server->port;
+					ShowNetworkLobbyWindow(this->server);
 				}
 				break;
-			}
+
+			case NGWW_REFRESH: // Refresh
+				if (this->server != NULL) NetworkUDPQueryServer(this->server->info.hostname, this->server->port);
+				break;
 
-			if (HandleEditBoxKey(w, &WP(w, network_ql_d).q, NGWW_PLAYER, e) == 1) break; // enter pressed
+			case NGWW_NEWGRF: // NewGRF Settings
+				if (this->server != NULL) ShowNewGRFSettings(false, false, false, &this->server->info.grfconfig);
+				break;
+		}
+	}
+
+	virtual void OnDropdownSelect(int widget, int index)
+	{
+		switch (widget) {
+			case NGWW_CONN_BTN:
+				_network_lan_internet = index;
+				break;
 
-			/* The name is only allowed when it starts with a letter! */
-			if (_edit_str_net_buf[0] != '\0' && _edit_str_net_buf[0] != ' ') {
-				ttd_strlcpy(_network_player_name, _edit_str_net_buf, lengthof(_network_player_name));
-			} else {
-				ttd_strlcpy(_network_player_name, "Player", lengthof(_network_player_name));
-			}
+			default:
+				NOT_REACHED();
+		}
+
+		this->SetDirty();
+	}
 
-			break;
+	virtual void OnMouseLoop()
+	{
+		if (this->field == NGWW_PLAYER) this->HandleEditBox(NGWW_PLAYER);
+	}
 
-		case WE_ON_EDIT_TEXT:
-			if (!StrEmpty(e->we.edittext.str)) {
-				NetworkAddServer(e->we.edittext.str);
-				NetworkRebuildHostList();
-			}
-			break;
+	virtual void OnInvalidateData(int data)
+	{
+		if (data != 0) this->server = NULL;
+		this->ld.flags |= VL_REBUILD;
+		this->SetDirty();
+	}
 
-		case WE_RESIZE: {
-			w->vscroll.cap += e->we.sizing.diff.y / (int)w->resize.step_height;
-
-			w->widget[NGWW_MATRIX].data = (w->vscroll.cap << 8) + 1;
+	virtual bool OnKeyPress(uint16 key, uint16 keycode)
+	{
+		bool cont = true;
+		if (this->field != NGWW_PLAYER) {
+			if (this->server != NULL) {
+				if (keycode == WKC_DELETE) { // Press 'delete' to remove servers
+					NetworkGameListRemoveItem(this->server);
+					NetworkRebuildHostList();
+					this->server = NULL;
+				}
+			}
+			return cont;
+		}
 
-			SetVScrollCount(w, ld->list_length);
+		if (this->HandleEditBoxKey(NGWW_PLAYER, keycode, key, cont) == 1) return cont; // enter pressed
 
-			int widget_width = w->widget[NGWW_FIND].right - w->widget[NGWW_FIND].left;
-			int space = (w->width - 4 * widget_width - 25) / 3;
+		/* The name is only allowed when it starts with a letter! */
+		if (StrEmpty(this->edit_str_buf) && this->edit_str_buf[0] != ' ') {
+			ttd_strlcpy(_network_player_name, this->edit_str_buf, lengthof(_network_player_name));
+		} else {
+			ttd_strlcpy(_network_player_name, "Player", lengthof(_network_player_name));
+		}
+		return cont;
+	}
 
-			int offset = 10;
-			for (uint i = 0; i < 4; i++) {
-				w->widget[NGWW_FIND + i].left  = offset;
-				offset += widget_width;
-				w->widget[NGWW_FIND + i].right = offset;
-				offset += space;
-			}
-		} break;
+	virtual void OnQueryTextFinished(char *str)
+	{
+		if (!StrEmpty(str)) {
+			NetworkAddServer(str);
+			NetworkRebuildHostList();
+		}
+	}
+
+	virtual void OnResize(Point new_size, Point delta)
+	{
+		this->vscroll.cap += delta.y / (int)this->resize.step_height;
+
+		this->widget[NGWW_MATRIX].data = (this->vscroll.cap << 8) + 1;
 
-		case WE_DESTROY: // Nicely clean up the sort-list
-			free(WP(w, network_ql_d).sort_list);
-			break;
+		SetVScrollCount(this, this->ld.list_length);
+
+		int widget_width = this->widget[NGWW_FIND].right - this->widget[NGWW_FIND].left;
+		int space = (this->width - 4 * widget_width - 25) / 3;
+
+		int offset = 10;
+		for (uint i = 0; i < 4; i++) {
+			this->widget[NGWW_FIND + i].left  = offset;
+			offset += widget_width;
+			this->widget[NGWW_FIND + i].right = offset;
+			offset += space;
+		}
 	}
-}
+};
 
 static const Widget _network_game_window_widgets[] = {
 /* TOP */
@@ -654,7 +658,7 @@
 	WC_NETWORK_WINDOW, WC_NONE,
 	WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_STD_BTN | WDF_UNCLICK_BUTTONS | WDF_RESIZABLE,
 	_network_game_window_widgets,
-	NetworkGameWindowWndProc,
+	NULL,
 };
 
 void ShowNetworkGameWindow()
@@ -676,16 +680,7 @@
 		_ng_sorting.order = 0;    // sort ascending by default
 	}
 
-	Window *w = new Window(&_network_game_window_desc);
-	if (w != NULL) {
-		querystr_d *querystr = &WP(w, network_ql_d).q;
-
-		ttd_strlcpy(_edit_str_net_buf, _network_player_name, lengthof(_edit_str_net_buf));
-		querystr->afilter = CS_ALPHANUMERAL;
-		InitializeTextBuffer(&querystr->text, _edit_str_net_buf, lengthof(_edit_str_net_buf), 120);
-
-		UpdateNetworkGameWindow(true);
-	}
+	new NetworkGameWindow(&_network_game_window_desc);
 }
 
 enum {
@@ -715,225 +710,237 @@
 	NSSW_CANCEL          = 27,   ///< 'Cancel' button
 };
 
-/**
- * Handler of actions done in the NetworkStartServer window
- *
- * @param w pointer to the Window structure
- * @param e pointer to window event
- * @note    Uses network_ql_d (network_d, querystr_d and list_d) WP macro
- * @see     struct _network_start_server_window_widgets
- * @see     enum NetworkStartServerWidgets
- */
-static void NetworkStartServerWindowWndProc(Window *w, WindowEvent *e)
-{
-	network_d *nd = &WP(w, network_ql_d).n;
+struct NetworkStartServerWindow : public QueryStringBaseWindow {
+	byte field;                  ///< Selected text-field
+	FiosItem *map;               ///< Selected map
+	byte widget_id;              ///< The widget that has the pop-up input menu
+
+	NetworkStartServerWindow(const WindowDesc *desc) : QueryStringBaseWindow(desc)
+	{
+		ttd_strlcpy(this->edit_str_buf, _network_server_name, lengthof(this->edit_str_buf));
 
-	switch (e->event) {
-		case WE_CREATE: // focus input box
-			nd->field = NSSW_GAMENAME;
-			_network_game_info.use_password = (_network_server_password[0] != '\0');
-			break;
+		_saveload_mode = SLD_NEW_GAME;
+		BuildFileList();
+		this->vscroll.cap = 12;
+		this->vscroll.count = _fios_num + 1;
 
-		case WE_PAINT: {
-			int y = NSSWND_START, pos;
-			const FiosItem *item;
+		this->afilter = CS_ALPHANUMERAL;
+		InitializeTextBuffer(&this->text, this->edit_str_buf, lengthof(this->edit_str_buf), 160);
+
+		this->field = NSSW_GAMENAME;
+		_network_game_info.use_password = !StrEmpty(_network_server_password);
+
+		this->FindWindowPlacementAndResize(desc);
+	}
 
-			/* draw basic widgets */
-			SetDParam(1, _connection_types_dropdown[_network_advertise]);
-			SetDParam(2, _network_game_info.clients_max);
-			SetDParam(3, _network_game_info.companies_max);
-			SetDParam(4, _network_game_info.spectators_max);
-			SetDParam(5, STR_NETWORK_LANG_ANY + _network_game_info.server_lang);
-			DrawWindowWidgets(w);
+	virtual void OnPaint()
+	{
+		int y = NSSWND_START, pos;
+		const FiosItem *item;
 
-			/* editbox to set game name */
-			DrawEditBox(w, &WP(w, network_ql_d).q, NSSW_GAMENAME);
-
-			/* if password is set, draw red '*' next to 'Set password' button */
-			if (_network_game_info.use_password) DoDrawString("*", 408, 23, TC_RED);
+		/* draw basic widgets */
+		SetDParam(1, _connection_types_dropdown[_network_advertise]);
+		SetDParam(2, _network_game_info.clients_max);
+		SetDParam(3, _network_game_info.companies_max);
+		SetDParam(4, _network_game_info.spectators_max);
+		SetDParam(5, STR_NETWORK_LANG_ANY + _network_game_info.server_lang);
+		DrawWindowWidgets(this);
 
-			/* draw list of maps */
-			GfxFillRect(11, 63, 258, 215, 0xD7);  // black background of maps list
+		/* editbox to set game name */
+		this->DrawEditBox(NSSW_GAMENAME);
+
+		/* if password is set, draw red '*' next to 'Set password' button */
+		if (_network_game_info.use_password) DoDrawString("*", 408, 23, TC_RED);
 
-			pos = w->vscroll.pos;
-			while (pos < _fios_num + 1) {
-				item = _fios_list + pos - 1;
-				if (item == nd->map || (pos == 0 && nd->map == NULL))
-					GfxFillRect(11, y - 1, 258, y + 10, 155); // show highlighted item with a different colour
+		/* draw list of maps */
+		GfxFillRect(11, 63, 258, 215, 0xD7);  // black background of maps list
 
-				if (pos == 0) {
-					DrawString(14, y, STR_4010_GENERATE_RANDOM_NEW_GAME, TC_DARK_GREEN);
-				} else {
-					DoDrawString(item->title, 14, y, _fios_colors[item->type] );
-				}
-				pos++;
-				y += NSSWND_ROWSIZE;
+		pos = this->vscroll.pos;
+		while (pos < _fios_num + 1) {
+			item = _fios_list + pos - 1;
+			if (item == this->map || (pos == 0 && this->map == NULL))
+				GfxFillRect(11, y - 1, 258, y + 10, 155); // show highlighted item with a different colour
 
-				if (y >= w->vscroll.cap * NSSWND_ROWSIZE + NSSWND_START) break;
+			if (pos == 0) {
+				DrawString(14, y, STR_4010_GENERATE_RANDOM_NEW_GAME, TC_DARK_GREEN);
+			} else {
+				DoDrawString(item->title, 14, y, _fios_colors[item->type] );
 			}
-		} break;
+			pos++;
+			y += NSSWND_ROWSIZE;
 
-		case WE_CLICK:
-			if (e->we.click.widget != NSSW_CONNTYPE_BTN && e->we.click.widget != NSSW_LANGUAGE_BTN) HideDropDownMenu(w);
-			nd->field = e->we.click.widget;
-			switch (e->we.click.widget) {
-				case NSSW_CLOSE:  // Close 'X'
-				case NSSW_CANCEL: // Cancel button
-					ShowNetworkGameWindow();
-					break;
+			if (y >= this->vscroll.cap * NSSWND_ROWSIZE + NSSWND_START) break;
+		}
+	}
 
-				case NSSW_GAMENAME:
-					ShowOnScreenKeyboard(w, &WP(w, network_ql_d).q,  NSSW_GAMENAME, 0, 0);
-					break;
+	virtual void OnClick(Point pt, int widget)
+	{
+		if (widget != NSSW_CONNTYPE_BTN && widget != NSSW_LANGUAGE_BTN) HideDropDownMenu(this);
+		this->field = widget;
+		switch (widget) {
+			case NSSW_CLOSE:  // Close 'X'
+			case NSSW_CANCEL: // Cancel button
+				ShowNetworkGameWindow();
+				break;
 
-				case NSSW_SETPWD: // Set password button
-					nd->widget_id = NSSW_SETPWD;
-					ShowQueryString(BindCString(_network_server_password), STR_NETWORK_SET_PASSWORD, 20, 250, w, CS_ALPHANUMERAL);
-					break;
+			case NSSW_GAMENAME:
+				ShowOnScreenKeyboard(this, NSSW_GAMENAME, 0, 0);
+				break;
 
-				case NSSW_SELMAP: { // Select map
-					int y = (e->we.click.pt.y - NSSWND_START) / NSSWND_ROWSIZE;
-
-					y += w->vscroll.pos;
-					if (y >= w->vscroll.count) return;
+			case NSSW_SETPWD: // Set password button
+				this->widget_id = NSSW_SETPWD;
+				ShowQueryString(BindCString(_network_server_password), STR_NETWORK_SET_PASSWORD, 20, 250, this, CS_ALPHANUMERAL);
+				break;
 
-					nd->map = (y == 0) ? NULL : _fios_list + y - 1;
-					SetWindowDirty(w);
-				} break;
+			case NSSW_SELMAP: { // Select map
+				int y = (pt.y - NSSWND_START) / NSSWND_ROWSIZE;
+
+				y += this->vscroll.pos;
+				if (y >= this->vscroll.count) return;
 
-				case NSSW_CONNTYPE_BTN: // Connection type
-					ShowDropDownMenu(w, _connection_types_dropdown, _network_advertise, NSSW_CONNTYPE_BTN, 0, 0); // do it for widget NSSW_CONNTYPE_BTN
-					break;
+				this->map = (y == 0) ? NULL : _fios_list + y - 1;
+				this->SetDirty();
+			} break;
+
+			case NSSW_CONNTYPE_BTN: // Connection type
+				ShowDropDownMenu(this, _connection_types_dropdown, _network_advertise, NSSW_CONNTYPE_BTN, 0, 0); // do it for widget NSSW_CONNTYPE_BTN
+				break;
 
-				case NSSW_CLIENTS_BTND:    case NSSW_CLIENTS_BTNU:    // Click on up/down button for number of clients
-				case NSSW_COMPANIES_BTND:  case NSSW_COMPANIES_BTNU:  // Click on up/down button for number of companies
-				case NSSW_SPECTATORS_BTND: case NSSW_SPECTATORS_BTNU: // Click on up/down button for number of spectators
-					/* Don't allow too fast scrolling */
-					if ((w->flags4 & WF_TIMEOUT_MASK) <= 2 << WF_TIMEOUT_SHL) {
-						w->HandleButtonClick(e->we.click.widget);
-						SetWindowDirty(w);
-						switch (e->we.click.widget) {
-							default: NOT_REACHED();
-							case NSSW_CLIENTS_BTND: case NSSW_CLIENTS_BTNU:
-								_network_game_info.clients_max    = Clamp(_network_game_info.clients_max    + e->we.click.widget - NSSW_CLIENTS_TXT,    2, MAX_CLIENTS);
-								break;
-							case NSSW_COMPANIES_BTND: case NSSW_COMPANIES_BTNU:
-								_network_game_info.companies_max  = Clamp(_network_game_info.companies_max  + e->we.click.widget - NSSW_COMPANIES_TXT,  1, MAX_PLAYERS);
-								break;
-							case NSSW_SPECTATORS_BTND: case NSSW_SPECTATORS_BTNU:
-								_network_game_info.spectators_max = Clamp(_network_game_info.spectators_max + e->we.click.widget - NSSW_SPECTATORS_TXT, 0, MAX_CLIENTS);
-								break;
-						}
+			case NSSW_CLIENTS_BTND:    case NSSW_CLIENTS_BTNU:    // Click on up/down button for number of clients
+			case NSSW_COMPANIES_BTND:  case NSSW_COMPANIES_BTNU:  // Click on up/down button for number of companies
+			case NSSW_SPECTATORS_BTND: case NSSW_SPECTATORS_BTNU: // Click on up/down button for number of spectators
+				/* Don't allow too fast scrolling */
+				if ((this->flags4 & WF_TIMEOUT_MASK) <= 2 << WF_TIMEOUT_SHL) {
+					this->HandleButtonClick(widget);
+					this->SetDirty();
+					switch (widget) {
+						default: NOT_REACHED();
+						case NSSW_CLIENTS_BTND: case NSSW_CLIENTS_BTNU:
+							_network_game_info.clients_max    = Clamp(_network_game_info.clients_max    + widget - NSSW_CLIENTS_TXT,    2, MAX_CLIENTS);
+							break;
+						case NSSW_COMPANIES_BTND: case NSSW_COMPANIES_BTNU:
+							_network_game_info.companies_max  = Clamp(_network_game_info.companies_max  + widget - NSSW_COMPANIES_TXT,  1, MAX_PLAYERS);
+							break;
+						case NSSW_SPECTATORS_BTND: case NSSW_SPECTATORS_BTNU:
+							_network_game_info.spectators_max = Clamp(_network_game_info.spectators_max + widget - NSSW_SPECTATORS_TXT, 0, MAX_CLIENTS);
+							break;
 					}
-					_left_button_clicked = false;
-					break;
+				}
+				_left_button_clicked = false;
+				break;
 
-				case NSSW_CLIENTS_TXT:    // Click on number of players
-					nd->widget_id = NSSW_CLIENTS_TXT;
-					SetDParam(0, _network_game_info.clients_max);
-					ShowQueryString(STR_CONFIG_PATCHES_INT32, STR_NETWORK_NUMBER_OF_CLIENTS,    3, 50, w, CS_NUMERAL);
-					break;
+			case NSSW_CLIENTS_TXT:    // Click on number of players
+				this->widget_id = NSSW_CLIENTS_TXT;
+				SetDParam(0, _network_game_info.clients_max);
+				ShowQueryString(STR_CONFIG_PATCHES_INT32, STR_NETWORK_NUMBER_OF_CLIENTS,    3, 50, this, CS_NUMERAL);
+				break;
 
-				case NSSW_COMPANIES_TXT:  // Click on number of companies
-					nd->widget_id = NSSW_COMPANIES_TXT;
-					SetDParam(0, _network_game_info.companies_max);
-					ShowQueryString(STR_CONFIG_PATCHES_INT32, STR_NETWORK_NUMBER_OF_COMPANIES,  3, 50, w, CS_NUMERAL);
-					break;
+			case NSSW_COMPANIES_TXT:  // Click on number of companies
+				this->widget_id = NSSW_COMPANIES_TXT;
+				SetDParam(0, _network_game_info.companies_max);
+				ShowQueryString(STR_CONFIG_PATCHES_INT32, STR_NETWORK_NUMBER_OF_COMPANIES,  3, 50, this, CS_NUMERAL);
+				break;
 
-				case NSSW_SPECTATORS_TXT: // Click on number of spectators
-					nd->widget_id = NSSW_SPECTATORS_TXT;
-					SetDParam(0, _network_game_info.spectators_max);
-					ShowQueryString(STR_CONFIG_PATCHES_INT32, STR_NETWORK_NUMBER_OF_SPECTATORS, 3, 50, w, CS_NUMERAL);
-					break;
+			case NSSW_SPECTATORS_TXT: // Click on number of spectators
+				this->widget_id = NSSW_SPECTATORS_TXT;
+				SetDParam(0, _network_game_info.spectators_max);
+				ShowQueryString(STR_CONFIG_PATCHES_INT32, STR_NETWORK_NUMBER_OF_SPECTATORS, 3, 50, this, CS_NUMERAL);
+				break;
 
-				case NSSW_LANGUAGE_BTN: { // Language
-					uint sel = 0;
-					for (uint i = 0; i < lengthof(_language_dropdown) - 1; i++) {
-						if (_language_dropdown[i] == STR_NETWORK_LANG_ANY + _network_game_info.server_lang) {
-							sel = i;
-							break;
-						}
+			case NSSW_LANGUAGE_BTN: { // Language
+				uint sel = 0;
+				for (uint i = 0; i < lengthof(_language_dropdown) - 1; i++) {
+					if (_language_dropdown[i] == STR_NETWORK_LANG_ANY + _network_game_info.server_lang) {
+						sel = i;
+						break;
 					}
-					ShowDropDownMenu(w, _language_dropdown, sel, NSSW_LANGUAGE_BTN, 0, 0);
-				} break;
+				}
+				ShowDropDownMenu(this, _language_dropdown, sel, NSSW_LANGUAGE_BTN, 0, 0);
+			} break;
 
-				case NSSW_START: // Start game
-					_is_network_server = true;
+			case NSSW_START: // Start game
+				_is_network_server = true;
 
-					if (nd->map == NULL) { // start random new game
-						ShowGenerateLandscape();
-					} else { // load a scenario
-						char *name = FiosBrowseTo(nd->map);
-						if (name != NULL) {
-							SetFiosType(nd->map->type);
-							_file_to_saveload.filetype = FT_SCENARIO;
-							ttd_strlcpy(_file_to_saveload.name, name, sizeof(_file_to_saveload.name));
-							ttd_strlcpy(_file_to_saveload.title, nd->map->title, sizeof(_file_to_saveload.title));
+				if (this->map == NULL) { // start random new game
+					ShowGenerateLandscape();
+				} else { // load a scenario
+					char *name = FiosBrowseTo(this->map);
+					if (name != NULL) {
+						SetFiosType(this->map->type);
+						_file_to_saveload.filetype = FT_SCENARIO;
+						ttd_strlcpy(_file_to_saveload.name, name, sizeof(_file_to_saveload.name));
+						ttd_strlcpy(_file_to_saveload.title, this->map->title, sizeof(_file_to_saveload.title));
 
-							delete w;
-							SwitchMode(SM_START_SCENARIO);
-						}
+						delete this;
+						SwitchMode(SM_START_SCENARIO);
 					}
-					break;
+				}
+				break;
 
-				case NSSW_LOAD: // Load game
-					_is_network_server = true;
-					/* XXX - WC_NETWORK_WINDOW (this window) should stay, but if it stays, it gets
-					* copied all the elements of 'load game' and upon closing that, it segfaults */
-					delete w;
-					ShowSaveLoadDialog(SLD_LOAD_GAME);
-					break;
-			}
-			break;
+			case NSSW_LOAD: // Load game
+				_is_network_server = true;
+				/* XXX - WC_NETWORK_WINDOW (this window) should stay, but if it stays, it gets
+				* copied all the elements of 'load game' and upon closing that, it segfaults */
+				delete this;
+				ShowSaveLoadDialog(SLD_LOAD_GAME);
+				break;
+		}
+	}
 
-		case WE_DROPDOWN_SELECT: // we have selected a dropdown item in the list
-			switch (e->we.dropdown.button) {
-				case NSSW_CONNTYPE_BTN:
-					_network_advertise = (e->we.dropdown.index != 0);
-					break;
-				case NSSW_LANGUAGE_BTN:
-					_network_game_info.server_lang = _language_dropdown[e->we.dropdown.index] - STR_NETWORK_LANG_ANY;
-					break;
-				default:
-					NOT_REACHED();
-			}
+	virtual void OnDropdownSelect(int widget, int index)
+	{
+		switch (widget) {
+			case NSSW_CONNTYPE_BTN:
+				_network_advertise = (index != 0);
+				break;
+			case NSSW_LANGUAGE_BTN:
+				_network_game_info.server_lang = _language_dropdown[index] - STR_NETWORK_LANG_ANY;
+				break;
+			default:
+				NOT_REACHED();
+		}
 
-			SetWindowDirty(w);
-			break;
+		this->SetDirty();
+	}
 
-		case WE_MOUSELOOP:
-			if (nd->field == NSSW_GAMENAME) HandleEditBox(w, &WP(w, network_ql_d).q, NSSW_GAMENAME);
-			break;
+	virtual void OnMouseLoop()
+	{
+		if (this->field == NSSW_GAMENAME) this->HandleEditBox(NSSW_GAMENAME);
+	}
 
-		case WE_KEYPRESS:
-			if (nd->field == NSSW_GAMENAME) {
-				if (HandleEditBoxKey(w, &WP(w, network_ql_d).q, NSSW_GAMENAME, e) == 1) break; // enter pressed
+	virtual bool OnKeyPress(uint16 key, uint16 keycode)
+	{
+		bool cont = true;
+		if (this->field == NSSW_GAMENAME) {
+			if (this->HandleEditBoxKey(NSSW_GAMENAME, key, keycode, cont) == 1) return cont; // enter pressed
 
-				ttd_strlcpy(_network_server_name, WP(w, network_ql_d).q.text.buf, sizeof(_network_server_name));
-			}
-			break;
+			ttd_strlcpy(_network_server_name, this->text.buf, sizeof(_network_server_name));
+		}
 
-		case WE_ON_EDIT_TEXT:
-			if (e->we.edittext.str == NULL) break;
+		return cont;
+	}
+
+	virtual void OnQueryTextFinished(char *str)
+	{
+		if (str == NULL) return;
 
-			if (nd->widget_id == NSSW_SETPWD) {
-				ttd_strlcpy(_network_server_password, e->we.edittext.str, lengthof(_network_server_password));
-				_network_game_info.use_password = !StrEmpty(_network_server_password);
-			} else {
-				int32 value = atoi(e->we.edittext.str);
-				w->InvalidateWidget(nd->widget_id);
-				switch (nd->widget_id) {
-					default: NOT_REACHED();
-					case NSSW_CLIENTS_TXT:    _network_game_info.clients_max    = Clamp(value, 2, MAX_CLIENTS); break;
-					case NSSW_COMPANIES_TXT:  _network_game_info.companies_max  = Clamp(value, 1, MAX_PLAYERS); break;
-					case NSSW_SPECTATORS_TXT: _network_game_info.spectators_max = Clamp(value, 0, MAX_CLIENTS); break;
-				}
+		if (this->widget_id == NSSW_SETPWD) {
+			ttd_strlcpy(_network_server_password, str, lengthof(_network_server_password));
+			_network_game_info.use_password = !StrEmpty(_network_server_password);
+		} else {
+			int32 value = atoi(str);
+			this->InvalidateWidget(this->widget_id);
+			switch (this->widget_id) {
+				default: NOT_REACHED();
+				case NSSW_CLIENTS_TXT:    _network_game_info.clients_max    = Clamp(value, 2, MAX_CLIENTS); break;
+				case NSSW_COMPANIES_TXT:  _network_game_info.companies_max  = Clamp(value, 1, MAX_PLAYERS); break;
+				case NSSW_SPECTATORS_TXT: _network_game_info.spectators_max = Clamp(value, 0, MAX_CLIENTS); break;
 			}
+		}
 
-			SetWindowDirty(w);
-			break;
+		this->SetDirty();
 	}
-}
+};
 
 static const Widget _network_start_server_window_widgets[] = {
 /* Window decoration and background panel */
@@ -986,23 +993,14 @@
 	WC_NETWORK_WINDOW, WC_NONE,
 	WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
 	_network_start_server_window_widgets,
-	NetworkStartServerWindowWndProc,
+	NULL,
 };
 
 static void ShowNetworkStartServerWindow()
 {
 	DeleteWindowById(WC_NETWORK_WINDOW, 0);
 
-	Window *w = new Window(&_network_start_server_window_desc);
-	ttd_strlcpy(_edit_str_net_buf, _network_server_name, lengthof(_edit_str_net_buf));
-
-	_saveload_mode = SLD_NEW_GAME;
-	BuildFileList();
-	w->vscroll.cap = 12;
-	w->vscroll.count = _fios_num + 1;
-
-	WP(w, network_ql_d).q.afilter = CS_ALPHANUMERAL;
-	InitializeTextBuffer(&WP(w, network_ql_d).q.text, _edit_str_net_buf, lengthof(_edit_str_net_buf), 160);
+	new NetworkStartServerWindow(&_network_start_server_window_desc);
 }
 
 static PlayerID NetworkLobbyFindCompanyIndex(byte pos)
@@ -1036,7 +1034,6 @@
 	NetworkLobbyWindow(const WindowDesc *desc, NetworkGameList *ngl) :
 			Window(desc), company(INVALID_PLAYER), server(ngl)
 	{
-		strcpy(_edit_str_net_buf, "");
 		this->vscroll.cap = 10;
 
 		this->FindWindowPlacementAndResize(desc);
@@ -1370,10 +1367,10 @@
 	/* If height is changed */
 	if (w->height != CLNWND_OFFSET + num + 1) {
 		// XXX - magic unfortunately; (num + 2) has to be one bigger than heigh (num + 1)
-		SetWindowDirty(w);
+		w->SetDirty();
 		w->widget[3].bottom = w->widget[3].top + num + 2;
 		w->height = CLNWND_OFFSET + num + 1;
-		SetWindowDirty(w);
+		w->SetDirty();
 		return false;
 	}
 	return true;
@@ -1508,7 +1505,7 @@
 				if (index == -1 || index == WP(w, menu_d).sel_index) return;
 
 				WP(w, menu_d).sel_index = index;
-				SetWindowDirty(w);
+				w->SetDirty();
 			} else {
 				if (index >= 0 && _cursor.pos.y >= w->top) {
 					HandleClientListPopupClick(index, WP(w, menu_d).main_button);
@@ -1573,7 +1570,7 @@
 			if (e->we.mouseover.pt.y == -1) {
 				_selected_clientlist_y = 0;
 				_selected_clientlist_item = 255;
-				SetWindowDirty(w);
+				w->SetDirty();
 				break;
 			}
 			/* It did not change.. no update! */
@@ -1588,7 +1585,7 @@
 			}
 
 			/* Repaint */
-			SetWindowDirty(w);
+			w->SetDirty();
 			break;
 
 		case WE_DESTROY: case WE_CREATE:
@@ -1706,202 +1703,217 @@
 	}
 }
 
-/**
- * Find the next item of the list of things that can be auto-completed.
- * @param item The current indexed item to return. This function can, and most
- *     likely will, alter item, to skip empty items in the arrays.
- * @return Returns the char that matched to the index.
- */
-static const char *ChatTabCompletionNextItem(uint *item)
-{
-	static char chat_tab_temp_buffer[64];
+
+struct NetworkChatWindow : public QueryStringBaseWindow {
+	DestType dtype;
+	int dest;
+
+	NetworkChatWindow (const WindowDesc *desc, DestType type, int dest) : QueryStringBaseWindow(desc)
+	{
+		this->LowerWidget(2);
+		this->dtype   = type;
+		this->dest    = dest;
+		this->afilter = CS_ALPHANUMERAL;
+		InitializeTextBuffer(&this->text, this->edit_str_buf, lengthof(this->edit_str_buf), 0);
+
+		InvalidateWindowData(WC_NEWS_WINDOW, 0, this->height);
+		SetBit(_no_scroll, SCROLL_CHAT); // do not scroll the game with the arrow-keys
+
+		_chat_tab_completion_active = false;
+
+		this->FindWindowPlacementAndResize(desc);
+	}
+
+	~NetworkChatWindow ()
+	{
+		InvalidateWindowData(WC_NEWS_WINDOW, 0, 0);
+		ClrBit(_no_scroll, SCROLL_CHAT);
+	}
+
+	/**
+	 * Find the next item of the list of things that can be auto-completed.
+	 * @param item The current indexed item to return. This function can, and most
+	 *     likely will, alter item, to skip empty items in the arrays.
+	 * @return Returns the char that matched to the index.
+	 */
+	const char *ChatTabCompletionNextItem(uint *item)
+	{
+		static char chat_tab_temp_buffer[64];
 
-	/* First, try clients */
-	if (*item < MAX_CLIENT_INFO) {
-		/* Skip inactive clients */
-		while (_network_client_info[*item].client_index == NETWORK_EMPTY_INDEX && *item < MAX_CLIENT_INFO) (*item)++;
-		if (*item < MAX_CLIENT_INFO) return _network_client_info[*item].client_name;
+		/* First, try clients */
+		if (*item < MAX_CLIENT_INFO) {
+			/* Skip inactive clients */
+			while (_network_client_info[*item].client_index == NETWORK_EMPTY_INDEX && *item < MAX_CLIENT_INFO) (*item)++;
+			if (*item < MAX_CLIENT_INFO) return _network_client_info[*item].client_name;
+		}
+
+		/* Then, try townnames */
+		/* Not that the following assumes all town indices are adjacent, ie no
+		* towns have been deleted. */
+		if (*item <= (uint)MAX_CLIENT_INFO + GetMaxTownIndex()) {
+			const Town *t;
+
+			FOR_ALL_TOWNS_FROM(t, *item - MAX_CLIENT_INFO) {
+				/* Get the town-name via the string-system */
+				SetDParam(0, t->index);
+				GetString(chat_tab_temp_buffer, STR_TOWN, lastof(chat_tab_temp_buffer));
+				return &chat_tab_temp_buffer[0];
+			}
+		}
+
+		return NULL;
+	}
+
+	/**
+	 * Find what text to complete. It scans for a space from the left and marks
+	 *  the word right from that as to complete. It also writes a \0 at the
+	 *  position of the space (if any). If nothing found, buf is returned.
+	 */
+	static char *ChatTabCompletionFindText(char *buf)
+	{
+		char *p = strrchr(buf, ' ');
+		if (p == NULL) return buf;
+
+		*p = '\0';
+		return p + 1;
 	}
 
-	/* Then, try townnames */
-	/* Not that the following assumes all town indices are adjacent, ie no
-	 * towns have been deleted. */
-	if (*item <= (uint)MAX_CLIENT_INFO + GetMaxTownIndex()) {
-		const Town *t;
+	/**
+	 * See if we can auto-complete the current text of the user.
+	 */
+	void ChatTabCompletion()
+	{
+		static char _chat_tab_completion_buf[lengthof(this->edit_str_buf)];
+		Textbuf *tb = &this->text;
+		size_t len, tb_len;
+		uint item;
+		char *tb_buf, *pre_buf;
+		const char *cur_name;
+		bool second_scan = false;
+
+		item = 0;
+
+		/* Copy the buffer so we can modify it without damaging the real data */
+		pre_buf = (_chat_tab_completion_active) ? strdup(_chat_tab_completion_buf) : strdup(tb->buf);
+
+		tb_buf  = ChatTabCompletionFindText(pre_buf);
+		tb_len  = strlen(tb_buf);
+
+		while ((cur_name = ChatTabCompletionNextItem(&item)) != NULL) {
+			item++;
+
+			if (_chat_tab_completion_active) {
+				/* We are pressing TAB again on the same name, is there an other name
+				*  that starts with this? */
+				if (!second_scan) {
+					size_t offset;
+					size_t length;
+
+					/* If we are completing at the begin of the line, skip the ': ' we added */
+					if (tb_buf == pre_buf) {
+						offset = 0;
+						length = tb->length - 2;
+					} else {
+						/* Else, find the place we are completing at */
+						offset = strlen(pre_buf) + 1;
+						length = tb->length - offset;
+					}
+
+					/* Compare if we have a match */
+					if (strlen(cur_name) == length && strncmp(cur_name, tb->buf + offset, length) == 0) second_scan = true;
+
+					continue;
+				}
+
+				/* Now any match we make on _chat_tab_completion_buf after this, is perfect */
+			}
+
+			len = strlen(cur_name);
+			if (tb_len < len && strncasecmp(cur_name, tb_buf, tb_len) == 0) {
+				/* Save the data it was before completion */
+				if (!second_scan) snprintf(_chat_tab_completion_buf, lengthof(_chat_tab_completion_buf), "%s", tb->buf);
+				_chat_tab_completion_active = true;
 
-		FOR_ALL_TOWNS_FROM(t, *item - MAX_CLIENT_INFO) {
-			/* Get the town-name via the string-system */
-			SetDParam(0, t->index);
-			GetString(chat_tab_temp_buffer, STR_TOWN, lastof(chat_tab_temp_buffer));
-			return &chat_tab_temp_buffer[0];
+				/* Change to the found name. Add ': ' if we are at the start of the line (pretty) */
+				if (pre_buf == tb_buf) {
+					snprintf(tb->buf, lengthof(this->edit_str_buf), "%s: ", cur_name);
+				} else {
+					snprintf(tb->buf, lengthof(this->edit_str_buf), "%s %s", pre_buf, cur_name);
+				}
+
+				/* Update the textbuffer */
+				UpdateTextBufferSize(&this->text);
+
+				this->SetDirty();
+				free(pre_buf);
+				return;
+			}
+		}
+
+		if (second_scan) {
+			/* We walked all posibilities, and the user presses tab again.. revert to original text */
+			strcpy(tb->buf, _chat_tab_completion_buf);
+			_chat_tab_completion_active = false;
+
+			/* Update the textbuffer */
+			UpdateTextBufferSize(&this->text);
+
+			this->SetDirty();
+		}
+		free(pre_buf);
+	}
+
+	virtual void OnPaint()
+	{
+		static const StringID chat_captions[] = {
+			STR_NETWORK_CHAT_ALL_CAPTION,
+			STR_NETWORK_CHAT_COMPANY_CAPTION,
+			STR_NETWORK_CHAT_CLIENT_CAPTION
+		};
+
+		DrawWindowWidgets(this);
+
+		assert((uint)this->dtype < lengthof(chat_captions));
+		DrawStringRightAligned(this->widget[2].left - 2, this->widget[2].top + 1, chat_captions[this->dtype], TC_BLACK);
+		this->DrawEditBox(2);
+	}
+
+	virtual void OnClick(Point pt, int widget)
+	{
+		switch (widget) {
+			case 2:
+				ShowOnScreenKeyboard(this, 2, 0, 3);
+				break;
+
+			case 3: /* Send */
+				SendChat(this->text.buf, this->dtype, this->dest);
+			/* FALLTHROUGH */
+			case 0: /* Cancel */ delete this; break;
 		}
 	}
 
-	return NULL;
-}
-
-/**
- * Find what text to complete. It scans for a space from the left and marks
- *  the word right from that as to complete. It also writes a \0 at the
- *  position of the space (if any). If nothing found, buf is returned.
- */
-static char *ChatTabCompletionFindText(char *buf)
-{
-	char *p = strrchr(buf, ' ');
-	if (p == NULL) return buf;
-
-	*p = '\0';
-	return p + 1;
-}
-
-/**
- * See if we can auto-complete the current text of the user.
- */
-static void ChatTabCompletion(Window *w)
-{
-	static char _chat_tab_completion_buf[lengthof(_edit_str_net_buf)];
-	Textbuf *tb = &WP(w, chatquerystr_d).text;
-	size_t len, tb_len;
-	uint item;
-	char *tb_buf, *pre_buf;
-	const char *cur_name;
-	bool second_scan = false;
-
-	item = 0;
-
-	/* Copy the buffer so we can modify it without damaging the real data */
-	pre_buf = (_chat_tab_completion_active) ? strdup(_chat_tab_completion_buf) : strdup(tb->buf);
-
-	tb_buf  = ChatTabCompletionFindText(pre_buf);
-	tb_len  = strlen(tb_buf);
-
-	while ((cur_name = ChatTabCompletionNextItem(&item)) != NULL) {
-		item++;
-
-		if (_chat_tab_completion_active) {
-			/* We are pressing TAB again on the same name, is there an other name
-			 *  that starts with this? */
-			if (!second_scan) {
-				size_t offset;
-				size_t length;
-
-				/* If we are completing at the begin of the line, skip the ': ' we added */
-				if (tb_buf == pre_buf) {
-					offset = 0;
-					length = tb->length - 2;
-				} else {
-					/* Else, find the place we are completing at */
-					offset = strlen(pre_buf) + 1;
-					length = tb->length - offset;
-				}
-
-				/* Compare if we have a match */
-				if (strlen(cur_name) == length && strncmp(cur_name, tb->buf + offset, length) == 0) second_scan = true;
-
-				continue;
-			}
-
-			/* Now any match we make on _chat_tab_completion_buf after this, is perfect */
-		}
-
-		len = strlen(cur_name);
-		if (tb_len < len && strncasecmp(cur_name, tb_buf, tb_len) == 0) {
-			/* Save the data it was before completion */
-			if (!second_scan) snprintf(_chat_tab_completion_buf, lengthof(_chat_tab_completion_buf), "%s", tb->buf);
-			_chat_tab_completion_active = true;
-
-			/* Change to the found name. Add ': ' if we are at the start of the line (pretty) */
-			if (pre_buf == tb_buf) {
-				snprintf(tb->buf, lengthof(_edit_str_net_buf), "%s: ", cur_name);
-			} else {
-				snprintf(tb->buf, lengthof(_edit_str_net_buf), "%s %s", pre_buf, cur_name);
-			}
-
-			/* Update the textbuffer */
-			UpdateTextBufferSize(&WP(w, chatquerystr_d).text);
-
-			SetWindowDirty(w);
-			free(pre_buf);
-			return;
-		}
+	virtual void OnMouseLoop()
+	{
+		this->HandleEditBox(2);
 	}
 
-	if (second_scan) {
-		/* We walked all posibilities, and the user presses tab again.. revert to original text */
-		strcpy(tb->buf, _chat_tab_completion_buf);
-		_chat_tab_completion_active = false;
-
-		/* Update the textbuffer */
-		UpdateTextBufferSize(&WP(w, chatquerystr_d).text);
-
-		SetWindowDirty(w);
+	virtual bool OnKeyPress(uint16 key, uint16 keycode)
+	{
+		bool cont = true;
+		if (keycode == WKC_TAB) {
+			ChatTabCompletion();
+		} else {
+			_chat_tab_completion_active = false;
+			switch (this->HandleEditBoxKey(2, key, keycode, cont)) {
+				case 1: /* Return */
+					SendChat(this->text.buf, this->dtype, this->dest);
+				/* FALLTHROUGH */
+				case 2: /* Escape */ delete this; break;
+			}
+		}
+		return cont;
 	}
-	free(pre_buf);
-}
-
-/*
- * uses chatquerystr_d WP macro
- * uses chatquerystr_d->dtype to store type of chat message (Private/Team/All)
- */
-static void ChatWindowWndProc(Window *w, WindowEvent *e)
-{
-	switch (e->event) {
-		case WE_CREATE:
-			InvalidateWindowData(WC_NEWS_WINDOW, 0, w->height);
-			SetBit(_no_scroll, SCROLL_CHAT); // do not scroll the game with the arrow-keys
-			break;
-
-		case WE_PAINT: {
-			static const StringID chat_captions[] = {
-				STR_NETWORK_CHAT_ALL_CAPTION,
-				STR_NETWORK_CHAT_COMPANY_CAPTION,
-				STR_NETWORK_CHAT_CLIENT_CAPTION
-			};
-
-			DrawWindowWidgets(w);
-
-			assert((uint)WP(w, chatquerystr_d).dtype < lengthof(chat_captions));
-			DrawStringRightAligned(w->widget[2].left - 2, w->widget[2].top + 1, chat_captions[WP(w, chatquerystr_d).dtype], TC_BLACK);
-			DrawEditBox(w, &WP(w, chatquerystr_d), 2);
-		} break;
-
-		case WE_CLICK:
-			switch (e->we.click.widget) {
-				case 2:
-					ShowOnScreenKeyboard(w, &WP(w, chatquerystr_d), 2, 0, 3);
-					break;
-
-				case 3: /* Send */
-					SendChat(WP(w, chatquerystr_d).text.buf, WP(w, chatquerystr_d).dtype, WP(w, chatquerystr_d).dest);
-				/* FALLTHROUGH */
-				case 0: /* Cancel */ delete w; break;
-			}
-			break;
-
-		case WE_MOUSELOOP:
-			HandleEditBox(w, &WP(w, chatquerystr_d), 2);
-			break;
-
-		case WE_KEYPRESS:
-			if (e->we.keypress.keycode == WKC_TAB) {
-				ChatTabCompletion(w);
-			} else {
-				_chat_tab_completion_active = false;
-				switch (HandleEditBoxKey(w, &WP(w, chatquerystr_d), 2, e)) {
-					case 1: /* Return */
-						SendChat(WP(w, chatquerystr_d).text.buf, WP(w, chatquerystr_d).dtype, WP(w, chatquerystr_d).dest);
-					/* FALLTHROUGH */
-					case 2: /* Escape */ delete w; break;
-				}
-			}
-			break;
-
-		case WE_DESTROY:
-			InvalidateWindowData(WC_NEWS_WINDOW, 0, 0);
-			ClrBit(_no_scroll, SCROLL_CHAT);
-			break;
-	}
-}
+};
 
 static const Widget _chat_window_widgets[] = {
 {   WWT_CLOSEBOX, RESIZE_NONE,  14,   0,  10,  0, 13, STR_00C5,                  STR_018B_CLOSE_WINDOW},
@@ -1916,23 +1928,13 @@
 	WC_SEND_NETWORK_MSG, WC_NONE,
 	WDF_STD_TOOLTIPS | WDF_DEF_WIDGET,
 	_chat_window_widgets,
-	ChatWindowWndProc
+	NULL
 };
 
 void ShowNetworkChatQueryWindow(DestType type, int dest)
 {
 	DeleteWindowById(WC_SEND_NETWORK_MSG, 0);
-
-	_edit_str_net_buf[0] = '\0';
-	_chat_tab_completion_active = false;
-
-	Window *w = new Window(&_chat_window_desc);
-
-	w->LowerWidget(2);
-	WP(w, chatquerystr_d).dtype   = type;
-	WP(w, chatquerystr_d).dest    = dest;
-	WP(w, chatquerystr_d).afilter = CS_ALPHANUMERAL;
-	InitializeTextBuffer(&WP(w, chatquerystr_d).text, _edit_str_net_buf, lengthof(_edit_str_net_buf), 0);
+	new NetworkChatWindow (&_chat_window_desc, type, dest);
 }
 
 /** Enum for NetworkGameWindow, referring to _network_game_window_widgets */
@@ -1947,61 +1949,75 @@
 	NCPWW_OK,                       ///< Safe the password etc.
 };
 
-static void NetworkCompanyPasswordWindowWndProc(Window *w, WindowEvent *e)
-{
-	switch (e->event) {
-		case WE_PAINT:
-			DrawWindowWidgets(w);
-			DrawEditBox(w, &WP(w, chatquerystr_d), 4);
-			break;
+struct NetworkCompanyPasswordWindow : public QueryStringBaseWindow {
+	NetworkCompanyPasswordWindow(const WindowDesc *desc) : QueryStringBaseWindow(desc)
+	{
+		this->afilter = CS_ALPHANUMERAL;
+		InitializeTextBuffer(&this->text, this->edit_str_buf, min(lengthof(_network_default_company_pass), lengthof(this->edit_str_buf)), 0);
+
+		this->FindWindowPlacementAndResize(desc);
+	}
+
+	void OnOk()
+	{
+		if (this->IsWidgetLowered(NCPWW_SAVE_AS_DEFAULT_PASSWORD)) {
+			snprintf(_network_default_company_pass, lengthof(_network_default_company_pass), "%s", this->edit_str_buf);
+		}
 
-		case WE_CLICK:
-			switch (e->we.click.widget) {
-				case NCPWW_OK: {
-					if (w->IsWidgetLowered(NCPWW_SAVE_AS_DEFAULT_PASSWORD)) {
-						snprintf(_network_default_company_pass, lengthof(_network_default_company_pass), "%s", _edit_str_net_buf);
-					}
+		/* empty password is a '*' because of console argument */
+		if (StrEmpty(this->edit_str_buf)) snprintf(this->edit_str_buf, lengthof(this->edit_str_buf), "*");
+		char *password = this->edit_str_buf;
+		NetworkChangeCompanyPassword(1, &password);
+	}
 
-					/* empty password is a '*' because of console argument */
-					if (StrEmpty(_edit_str_net_buf)) snprintf(_edit_str_net_buf, lengthof(_edit_str_net_buf), "*");
-					char *password = _edit_str_net_buf;
-					NetworkChangeCompanyPassword(1, &password);
-				}
+	virtual void OnPaint()
+	{
+		DrawWindowWidgets(this);
+		this->DrawEditBox(4);
+	}
 
-				/* FALL THROUGH */
-				case NCPWW_CANCEL:
-					delete w;
-					break;
+	virtual void OnClick(Point pt, int widget)
+	{
+		switch (widget) {
+			case NCPWW_OK:
+				this->OnOk();
 
-				case NCPWW_SAVE_AS_DEFAULT_PASSWORD:
-					w->ToggleWidgetLoweredState(NCPWW_SAVE_AS_DEFAULT_PASSWORD);
-					SetWindowDirty(w);
-					break;
-				case NCPWW_PASSWORD:
-					ShowOnScreenKeyboard(w, &WP(w, chatquerystr_d), NCPWW_PASSWORD, 2, 1);
-					break;
-			}
-			break;
+			/* FALL THROUGH */
+			case NCPWW_CANCEL:
+				delete this;
+				break;
 
-		case WE_MOUSELOOP:
-			HandleEditBox(w, &WP(w, chatquerystr_d), 4);
-			break;
+			case NCPWW_SAVE_AS_DEFAULT_PASSWORD:
+				this->ToggleWidgetLoweredState(NCPWW_SAVE_AS_DEFAULT_PASSWORD);
+				this->SetDirty();
+				break;
+
+			case NCPWW_PASSWORD:
+				ShowOnScreenKeyboard(this, NCPWW_PASSWORD, 2, 1);
+				break;
+		}
+	}
 
-		case WE_KEYPRESS:
-			switch (HandleEditBoxKey(w, &WP(w, chatquerystr_d), 4, e)) {
-				case 1: // Return
-					e->event = WE_CLICK;
-					e->we.click.widget = NCPWW_OK;
-					NetworkCompanyPasswordWindowWndProc(w, e);
-					break;
+	virtual void OnMouseLoop()
+	{
+		this->HandleEditBox(4);
+	}
 
-				case 2: // Escape
-					delete w;
-					break;
-			}
-			break;
+	virtual bool OnKeyPress(uint16 key, uint16 keycode)
+	{
+		bool cont;
+		switch (this->HandleEditBoxKey(4, key, keycode, cont)) {
+			case 1: // Return
+				this->OnOk();
+				/* FALL THROUGH */
+
+			case 2: // Escape
+				delete this;
+				break;
+		}
+		return cont;
 	}
-}
+};
 
 static const Widget _ncp_window_widgets[] = {
 {   WWT_CLOSEBOX, RESIZE_NONE, 14,   0,  10,  0, 13, STR_00C5,                          STR_018B_CLOSE_WINDOW},
@@ -2020,17 +2036,14 @@
 	WC_COMPANY_PASSWORD_WINDOW, WC_NONE,
 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON,
 	_ncp_window_widgets,
-	NetworkCompanyPasswordWindowWndProc
+	NULL
 };
 
 void ShowNetworkCompanyPasswordWindow()
 {
 	DeleteWindowById(WC_COMPANY_PASSWORD_WINDOW, 0);
 
-	_edit_str_net_buf[0] = '\0';
-	Window *w = new Window(&_ncp_window_desc);
-	WP(w, chatquerystr_d).afilter = CS_ALPHANUMERAL;
-	InitializeTextBuffer(&WP(w, chatquerystr_d).text, _edit_str_net_buf, min(lengthof(_network_default_company_pass), lengthof(_edit_str_net_buf)), 0);
+	new NetworkCompanyPasswordWindow(&_ncp_window_desc);
 }
 
 #endif /* ENABLE_NETWORK */
--- a/src/osk_gui.cpp
+++ b/src/osk_gui.cpp
@@ -12,20 +12,11 @@
 #include "debug.h"
 #include "window_func.h"
 #include "gfx_func.h"
+#include "querystring_gui.h"
 
 #include "table/sprites.h"
 #include "table/strings.h"
 
-struct osk_d {
-	querystr_d *qs; // text-input
-	int text_btn;   // widget number of parent's text field
-	int ok_btn;     // widget number of parent's ok button (=0 when ok shouldn't be passed on)
-	int cancel_btn; // widget number of parent's cancel button (=0 when cancel shouldn't be passed on; text will be reverted to original)
-	Textbuf *text;  // pointer to parent's textbuffer (to update caret position)
-	char *orig;     // the original text, in case we cancel
-};
-assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(osk_d));
-
 enum OskWidgets {
 	OSK_WIDGET_TEXT = 3,
 	OSK_WIDGET_CANCEL = 5,
@@ -50,142 +41,162 @@
 };
 static byte _keystate = KEYS_NONE;
 
-/*
- * Only show valid characters; do not show characters that would
- * only insert a space when we have a spacebar to do that or
- * characters that are not allowed to be entered.
- */
-static void ChangeOskDiabledState(Window *w, const querystr_d *qs, bool shift)
-{
-	for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) {
-		w->SetWidgetDisabledState(OSK_WIDGET_LETTERS + i,
-				!IsValidChar(_keyboard[shift][i], qs->afilter) || _keyboard[shift][i] == ' ');
+struct OskWindow : public Window {
+	QueryString *qs;       ///< text-input
+	int text_btn;          ///< widget number of parent's text field
+	int ok_btn;            ///< widget number of parent's ok button (=0 when ok shouldn't be passed on)
+	int cancel_btn;        ///< widget number of parent's cancel button (=0 when cancel shouldn't be passed on; text will be reverted to original)
+	Textbuf *text;         ///< pointer to parent's textbuffer (to update caret position)
+	char orig_str_buf[64]; ///< Original string.
+
+	OskWindow(const WindowDesc *desc, QueryStringBaseWindow *parent, int button, int cancel, int ok) : Window(desc)
+	{
+		this->parent = parent;
+		assert(parent != NULL);
+
+		if (parent->widget[button].data != 0) parent->caption = parent->widget[button].data;
+
+		this->qs         = parent;
+		this->text_btn   = button;
+		this->cancel_btn = cancel;
+		this->ok_btn     = ok;
+		this->text       = &parent->text;
+
+		/* make a copy in case we need to reset later */
+		strcpy(this->orig_str_buf, this->qs->text.buf);
+
+		SetBit(_no_scroll, SCROLL_EDIT);
+		/* Not needed by default. */
+		this->DisableWidget(OSK_WIDGET_SPECIAL);
+
+		this->FindWindowPlacementAndResize(desc);
 	}
-	w->SetWidgetDisabledState(OSK_WIDGET_SPACE, !IsValidChar(' ', qs->afilter));
-}
 
-/* on screen keyboard */
-static void OskWndProc(Window *w, WindowEvent *e)
-{
-	querystr_d *qs = WP(w, osk_d).qs;
+	/**
+	 * Only show valid characters; do not show characters that would
+	 * only insert a space when we have a spacebar to do that or
+	 * characters that are not allowed to be entered.
+	 */
+	void ChangeOskDiabledState(bool shift)
+	{
+		for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) {
+			this->SetWidgetDisabledState(OSK_WIDGET_LETTERS + i,
+					!IsValidChar(_keyboard[shift][i], this->qs->afilter) || _keyboard[shift][i] == ' ');
+		}
+		this->SetWidgetDisabledState(OSK_WIDGET_SPACE, !IsValidChar(' ', this->qs->afilter));
+	}
+
+	virtual void OnPaint()
+	{
+		bool shift = HasBit(_keystate, KEYS_CAPS) ^ HasBit(_keystate, KEYS_SHIFT);
 
-	switch (e->event) {
-		case WE_CREATE:
-			SetBit(_no_scroll, SCROLL_EDIT);
-			/* Not needed by default. */
-			w->DisableWidget(OSK_WIDGET_SPECIAL);
-			break;
+		this->LowerWidget(OSK_WIDGET_TEXT);
+		this->SetWidgetLoweredState(OSK_WIDGET_SHIFT, HasBit(_keystate, KEYS_SHIFT));
+		this->SetWidgetLoweredState(OSK_WIDGET_CAPS, HasBit(_keystate, KEYS_CAPS));
+
+		this->ChangeOskDiabledState(shift);
+
+		SetDParam(0, this->qs->caption);
+		DrawWindowWidgets(this);
 
-		case WE_PAINT: {
+		for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) {
+			DrawCharCentered(_keyboard[shift][i],
+				this->widget[OSK_WIDGET_LETTERS + i].left + 8,
+				this->widget[OSK_WIDGET_LETTERS + i].top + 3,
+				TC_BLACK);
+		}
+
+		this->qs->DrawEditBox(this, OSK_WIDGET_TEXT);
+	}
+
+	virtual void OnClick(Point pt, int widget)
+	{
+		/* clicked a letter */
+		if (widget >= OSK_WIDGET_LETTERS) {
 			bool shift = HasBit(_keystate, KEYS_CAPS) ^ HasBit(_keystate, KEYS_SHIFT);
 
-			w->LowerWidget(OSK_WIDGET_TEXT);
-			w->SetWidgetLoweredState(OSK_WIDGET_SHIFT, HasBit(_keystate, KEYS_SHIFT));
-			w->SetWidgetLoweredState(OSK_WIDGET_CAPS, HasBit(_keystate, KEYS_CAPS));
+			WChar c = _keyboard[shift][widget - OSK_WIDGET_LETTERS];
 
-			ChangeOskDiabledState(w, qs, shift);
+			if (!IsValidChar(c, this->qs->afilter)) return;
 
-			SetDParam(0, qs->caption);
-			DrawWindowWidgets(w);
+			if (InsertTextBufferChar(&this->qs->text, c)) this->InvalidateWidget(OSK_WIDGET_TEXT);
 
-			for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) {
-				DrawCharCentered(_keyboard[shift][i],
-					w->widget[OSK_WIDGET_LETTERS + i].left + 8,
-					w->widget[OSK_WIDGET_LETTERS + i].top + 3,
-					TC_BLACK);
+			if (HasBit(_keystate, KEYS_SHIFT)) {
+				ToggleBit(_keystate, KEYS_SHIFT);
+				this->widget[OSK_WIDGET_SHIFT].color = HasBit(_keystate, KEYS_SHIFT) ? 15 : 14;
+				this->SetDirty();
 			}
-
-			DrawEditBox(w, qs, OSK_WIDGET_TEXT);
-			break;
+			return;
 		}
 
-		case WE_CLICK:
-			/* clicked a letter */
-			if (e->we.click.widget >= OSK_WIDGET_LETTERS) {
-				bool shift = HasBit(_keystate, KEYS_CAPS) ^ HasBit(_keystate, KEYS_SHIFT);
+		switch (widget) {
+			case OSK_WIDGET_BACKSPACE:
+				if (DeleteTextBufferChar(&this->qs->text, WKC_BACKSPACE)) this->InvalidateWidget(OSK_WIDGET_TEXT);
+				break;
+
+			case OSK_WIDGET_SPECIAL:
+				/*
+				 * Anything device specific can go here.
+				 * The button itself is hidden by default, and when you need it you
+				 * can not hide it in the create event.
+				 */
+				break;
 
-				WChar c = _keyboard[shift][e->we.click.widget - OSK_WIDGET_LETTERS];
+			case OSK_WIDGET_CAPS:
+				ToggleBit(_keystate, KEYS_CAPS);
+				this->SetDirty();
+				break;
+
+			case OSK_WIDGET_SHIFT:
+				ToggleBit(_keystate, KEYS_SHIFT);
+				this->SetDirty();
+				break;
+
+			case OSK_WIDGET_SPACE:
+				if (InsertTextBufferChar(&this->qs->text, ' ')) this->InvalidateWidget(OSK_WIDGET_TEXT);
+				break;
 
-				if (!IsValidChar(c, qs->afilter)) break;
+			case OSK_WIDGET_LEFT:
+				if (MoveTextBufferPos(&this->qs->text, WKC_LEFT)) this->InvalidateWidget(OSK_WIDGET_TEXT);
+				break;
 
-				if (InsertTextBufferChar(&qs->text, c)) w->InvalidateWidget(OSK_WIDGET_TEXT);
+			case OSK_WIDGET_RIGHT:
+				if (MoveTextBufferPos(&this->qs->text, WKC_RIGHT)) this->InvalidateWidget(OSK_WIDGET_TEXT);
+				break;
 
-				if (HasBit(_keystate, KEYS_SHIFT)) {
-					ToggleBit(_keystate, KEYS_SHIFT);
-					w->widget[OSK_WIDGET_SHIFT].color = HasBit(_keystate, KEYS_SHIFT) ? 15 : 14;
-					w->SetDirty();
+			case OSK_WIDGET_OK:
+				if (this->qs->orig == NULL || strcmp(this->qs->text.buf, this->qs->orig) != 0) {
+					/* pass information by simulating a button press on parent window */
+					if (this->ok_btn != 0) {
+						this->parent->OnClick(pt, this->ok_btn);
+					}
+				}
+				delete this;
+				break;
+
+			case OSK_WIDGET_CANCEL:
+				if (this->cancel_btn != 0) { // pass a cancel event to the parent window
+					this->parent->OnClick(pt, this->cancel_btn);
+					/* Window gets deleted when the parent window removes itself. */
+				} else { // or reset to original string
+					strcpy(qs->text.buf, this->orig_str_buf);
+					UpdateTextBufferSize(&qs->text);
+					MoveTextBufferPos(&qs->text, WKC_END);
+					delete this;
 				}
 				break;
-			}
-
-			switch (e->we.click.widget) {
-				case OSK_WIDGET_BACKSPACE:
-					if (DeleteTextBufferChar(&qs->text, WKC_BACKSPACE)) w->InvalidateWidget(OSK_WIDGET_TEXT);
-					break;
-
-				case OSK_WIDGET_SPECIAL:
-					/*
-					 * Anything device specific can go here.
-					 * The button itself is hidden by default, and when you need it you
-					 * can not hide it in the create event.
-					 */
-					break;
-
-				case OSK_WIDGET_CAPS:
-					ToggleBit(_keystate, KEYS_CAPS);
-					w->SetDirty();
-					break;
-
-				case OSK_WIDGET_SHIFT:
-					ToggleBit(_keystate, KEYS_SHIFT);
-					w->SetDirty();
-					break;
-
-				case OSK_WIDGET_SPACE:
-					if (InsertTextBufferChar(&qs->text, ' ')) w->InvalidateWidget(OSK_WIDGET_TEXT);
-					break;
-
-				case OSK_WIDGET_LEFT:
-					if (MoveTextBufferPos(&qs->text, WKC_LEFT)) w->InvalidateWidget(OSK_WIDGET_TEXT);
-					break;
+		}
+		/* make sure that the parent window's textbox also gets updated */
+		if (this->parent != NULL) this->parent->InvalidateWidget(this->text_btn);
+	}
 
-				case OSK_WIDGET_RIGHT:
-					if (MoveTextBufferPos(&qs->text, WKC_RIGHT)) w->InvalidateWidget(OSK_WIDGET_TEXT);
-					break;
-
-				case OSK_WIDGET_OK:
-					if (qs->orig == NULL || strcmp(qs->text.buf, qs->orig) != 0) {
-						/* pass information by simulating a button press on parent window */
-						if (WP(w, osk_d).ok_btn != 0) {
-							w->parent->OnClick(e->we.click.pt, WP(w, osk_d).ok_btn);
-						}
-					}
-					delete w;
-					break;
-
-				case OSK_WIDGET_CANCEL:
-					if (WP(w, osk_d).cancel_btn != 0) { // pass a cancel event to the parent window
-						w->parent->OnClick(e->we.click.pt, WP(w, osk_d).cancel_btn);
-						/* Window gets deleted when the parent window removes itself. */
-					} else { // or reset to original string
-						strcpy(qs->text.buf, WP(w, osk_d).orig);
-						UpdateTextBufferSize(&qs->text);
-						MoveTextBufferPos(&qs->text, WKC_END);
-						delete w;
-					}
-					break;
-			}
-			/* make sure that the parent window's textbox also gets updated */
-			if (w->parent != NULL) w->parent->InvalidateWidget(WP(w, osk_d).text_btn);
-			break;
-
-		case WE_MOUSELOOP:
-			HandleEditBox(w, qs, OSK_WIDGET_TEXT);
-			/* make the caret of the parent window also blink */
-			w->parent->InvalidateWidget(WP(w, osk_d).text_btn);
-			break;
+	virtual void OnMouseLoop()
+	{
+		this->qs->HandleEditBox(this, OSK_WIDGET_TEXT);
+		/* make the caret of the parent window also blink */
+		this->parent->InvalidateWidget(this->text_btn);
 	}
-}
+};
 
 static const Widget _osk_widgets[] = {
 {      WWT_EMPTY, RESIZE_NONE,     0,     0,     0,     0,     0, 0x0,               STR_NULL},
@@ -270,7 +281,7 @@
 	WC_OSK, WC_NONE,
 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
 	_osk_widgets,
-	OskWndProc
+	NULL
 };
 
 /**
@@ -338,26 +349,10 @@
  * @param ok     widget number of parent's ok button  (0 if ok events should not
  *               be passed)
  */
-void ShowOnScreenKeyboard(Window *parent, querystr_d *q, int button, int cancel, int ok)
+void ShowOnScreenKeyboard(QueryStringBaseWindow *parent, int button, int cancel, int ok)
 {
 	DeleteWindowById(WC_OSK, 0);
 
-	Window *w = new Window(&_osk_desc);
-
-	w->parent = parent;
-	assert(parent != NULL);
-
-	if (parent->widget[button].data != 0) q->caption = parent->widget[button].data;
-
-	WP(w, osk_d).qs         = q;
-	WP(w, osk_d).text_btn   = button;
-	WP(w, osk_d).cancel_btn = cancel;
-	WP(w, osk_d).ok_btn     = ok;
-	WP(w, osk_d).text       = &q->text;
-
 	GetKeyboardLayout();
-
-	/* make a copy in case we need to reset later */
-	strcpy(_orig_str_buf, WP(w, osk_d).qs->text.buf);
-	WP(w, osk_d).orig = _orig_str_buf;
+	new OskWindow(&_osk_desc, parent, button, cancel, ok);
 }
new file mode 100644
--- /dev/null
+++ b/src/querystring_gui.h
@@ -0,0 +1,38 @@
+/* $Id$ */
+
+/** @file querystring_gui.h Base for the GUIs that have an edit box in them. */
+
+#ifndef QUERYSTRING_GUI_H
+#define QUERYSTRING_GUI_H
+
+#include "textbuf_gui.h"
+#include "window_gui.h"
+
+struct QueryString {
+	StringID caption;
+	Textbuf text;
+	const char *orig;
+	CharSetFilter afilter;
+	bool handled;
+
+	void DrawEditBox(Window *w, int wid);
+	void HandleEditBox(Window *w, int wid);
+	int HandleEditBoxKey(Window *w, int wid, uint16 key, uint16 keycode, bool &cont);
+};
+
+struct QueryStringBaseWindow : public Window, public QueryString {
+	char edit_str_buf[64];
+	char orig_str_buf[64];
+
+	QueryStringBaseWindow(const WindowDesc *desc, void *data = NULL, WindowNumber window_number = 0) : Window(desc, data, window_number)
+	{
+	}
+
+	void DrawEditBox(int wid);
+	void HandleEditBox(int wid);
+	int HandleEditBoxKey(int wid, uint16 key, uint16 keycode, bool &cont);
+};
+
+void ShowOnScreenKeyboard(QueryStringBaseWindow *parent, int button, int cancel, int ok);
+
+#endif /* QUERYSTRING_GUI_H */
--- a/src/signs_gui.cpp
+++ b/src/signs_gui.cpp
@@ -19,6 +19,7 @@
 #include "map_func.h"
 #include "gfx_func.h"
 #include "viewport_func.h"
+#include "querystring_gui.h"
 
 #include "table/strings.h"
 #include "table/sprites.h"
@@ -143,11 +144,11 @@
 	}
 }
 
-/** Edit sign window stuff */
-struct editsign_d : querystr_d {
-	SignID cur_sign;
-};
-assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(editsign_d));
+static void RenameSign(SignID index, const char *text)
+{
+	_cmd_text = text;
+	DoCommandP(0, index, 0, NULL, CMD_RENAME_SIGN | CMD_MSG(STR_280C_CAN_T_CHANGE_SIGN_NAME));
+}
 
 enum QueryEditSignWidgets {
 	QUERY_EDIT_SIGN_WIDGET_TEXT = 3,
@@ -158,120 +159,122 @@
 	QUERY_EDIT_SIGN_WIDGET_NEXT,
 };
 
-static void UpdateSignEditWindow(Window *w, const Sign *si)
-{
-	/* Display an empty string when the sign hasnt been edited yet */
-	if (si->name != NULL) {
-		SetDParam(0, si->index);
-		GetString(_edit_str_buf, STR_SIGN_NAME, lastof(_edit_str_buf));
-	} else {
-		GetString(_edit_str_buf, STR_EMPTY, lastof(_edit_str_buf));
-	}
-	_edit_str_buf[lengthof(_edit_str_buf) - 1] = '\0';
+struct SignWindow : QueryStringBaseWindow {
+	SignID cur_sign;
 
-	WP(w, editsign_d).cur_sign = si->index;
-	InitializeTextBuffer(&WP(w, querystr_d).text, _edit_str_buf, 31, 255); // Allow 31 characters (including \0)
-
-	w->InvalidateWidget(QUERY_EDIT_SIGN_WIDGET_TEXT);
-}
+	SignWindow(const WindowDesc *desc, const Sign *si) : QueryStringBaseWindow(desc)
+	{
+		SetBit(_no_scroll, SCROLL_EDIT);
+		this->caption = STR_280B_EDIT_SIGN_TEXT;
+		this->afilter = CS_ALPHANUMERAL;
+		this->LowerWidget(QUERY_EDIT_SIGN_WIDGET_TEXT);
 
-static void RenameSign(SignID index, const char *text)
-{
-	_cmd_text = text;
-	DoCommandP(0, index, 0, NULL, CMD_RENAME_SIGN | CMD_MSG(STR_280C_CAN_T_CHANGE_SIGN_NAME));
-}
+		UpdateSignEditWindow(si);
+	}
+
+	~SignWindow()
+	{
+		ClrBit(_no_scroll, SCROLL_EDIT);
+	}
 
-static void QuerySignEditWndProc(Window *w, WindowEvent *e)
-{
-	editsign_d *qs = &WP(w, editsign_d);
-	Sign       *si;
-	uint       sign_index = 0;
+	void UpdateSignEditWindow(const Sign *si)
+	{
+		/* Display an empty string when the sign hasnt been edited yet */
+		if (si->name != NULL) {
+			SetDParam(0, si->index);
+			GetString(this->edit_str_buf, STR_SIGN_NAME, lastof(this->edit_str_buf));
+		} else {
+			GetString(this->edit_str_buf, STR_EMPTY, lastof(this->edit_str_buf));
+		}
+		this->edit_str_buf[lengthof(this->edit_str_buf) - 1] = '\0';
 
-	switch (e->event) {
-		case WE_CREATE:
-			SetBit(_no_scroll, SCROLL_EDIT);
-			break;
+		this->cur_sign = si->index;
+		InitializeTextBuffer(&this->text, this->edit_str_buf, 31, 255); // Allow 31 characters (including \0)
+
+		this->InvalidateWidget(QUERY_EDIT_SIGN_WIDGET_TEXT);
+	}
 
-		case WE_PAINT:
-			SetDParam(0, qs->caption);
-			DrawWindowWidgets(w);
-			DrawEditBox(w, qs, QUERY_EDIT_SIGN_WIDGET_TEXT);
-			break;
+	virtual void OnPaint()
+	{
+		SetDParam(0, this->caption);
+		DrawWindowWidgets(this);
+		this->DrawEditBox(QUERY_EDIT_SIGN_WIDGET_TEXT);
+	}
 
-		case WE_CLICK:
-			switch (e->we.click.widget) {
-				case QUERY_EDIT_SIGN_WIDGET_PREVIOUS:
-					if (_sign_sort_dirty) GlobalSortSignList();
-					sign_index = _sign_sort[_num_sign_sort - 1]->index;
-					for (uint i = 1; i < _num_sign_sort; i++) {
-						if (qs->cur_sign == _sign_sort[i]->index) {
-							sign_index = _sign_sort[i - 1]->index;
-							break;
-						}
+	virtual void OnClick(Point pt, int widget)
+	{
+		switch (widget) {
+			case QUERY_EDIT_SIGN_WIDGET_PREVIOUS: {
+				if (_sign_sort_dirty) GlobalSortSignList();
+				SignID sign_index = _sign_sort[_num_sign_sort - 1]->index;
+				for (uint i = 1; i < _num_sign_sort; i++) {
+					if (this->cur_sign == _sign_sort[i]->index) {
+						sign_index = _sign_sort[i - 1]->index;
+						break;
 					}
-					si = GetSign(sign_index);
+				}
+				const Sign *si = GetSign(sign_index);
 
-					/* Scroll to sign and reopen window */
-					ScrollMainWindowToTile(TileVirtXY(si->x, si->y));
-					UpdateSignEditWindow(w, si);
-					break;
+				/* Scroll to sign and reopen window */
+				ScrollMainWindowToTile(TileVirtXY(si->x, si->y));
+				UpdateSignEditWindow(si);
+			} break;
 
-				case QUERY_EDIT_SIGN_WIDGET_NEXT:
-					if (_sign_sort_dirty) GlobalSortSignList();
-					sign_index = _sign_sort[0]->index;
-					for (uint i = 0; i < _num_sign_sort - 1; i++) {
-						if (qs->cur_sign == _sign_sort[i]->index) {
-							sign_index = _sign_sort[i + 1]->index;
-							break;
-						}
+			case QUERY_EDIT_SIGN_WIDGET_NEXT: {
+				if (_sign_sort_dirty) GlobalSortSignList();
+				SignID sign_index = _sign_sort[0]->index;
+				for (uint i = 0; i < _num_sign_sort - 1; i++) {
+					if (this->cur_sign == _sign_sort[i]->index) {
+						sign_index = _sign_sort[i + 1]->index;
+						break;
 					}
-					si = GetSign(sign_index);
+				}
+				const Sign *si = GetSign(sign_index);
 
-					/* Scroll to sign and reopen window */
-					ScrollMainWindowToTile(TileVirtXY(si->x, si->y));
-					UpdateSignEditWindow(w, si);
-					break;
+				/* Scroll to sign and reopen window */
+				ScrollMainWindowToTile(TileVirtXY(si->x, si->y));
+				UpdateSignEditWindow(si);
+			} break;
 
-				case QUERY_EDIT_SIGN_WIDGET_TEXT:
-					ShowOnScreenKeyboard(w, qs, e->we.click.widget, QUERY_EDIT_SIGN_WIDGET_CANCEL, QUERY_EDIT_SIGN_WIDGET_OK);
-					break;
+			case QUERY_EDIT_SIGN_WIDGET_TEXT:
+				ShowOnScreenKeyboard(this, widget, QUERY_EDIT_SIGN_WIDGET_CANCEL, QUERY_EDIT_SIGN_WIDGET_OK);
+				break;
 
-				case QUERY_EDIT_SIGN_WIDGET_DELETE:
-					/* Only need to set the buffer to null, the rest is handled as the OK button */
-					DeleteTextBufferAll(&qs->text);
-					/* FALL THROUGH */
+			case QUERY_EDIT_SIGN_WIDGET_DELETE:
+				/* Only need to set the buffer to null, the rest is handled as the OK button */
+				DeleteTextBufferAll(&this->text);
+				/* FALL THROUGH */
 
-				case QUERY_EDIT_SIGN_WIDGET_OK:
-					RenameSign(qs->cur_sign, qs->text.buf);
-					/* FALL THROUGH */
+			case QUERY_EDIT_SIGN_WIDGET_OK:
+				RenameSign(this->cur_sign, this->text.buf);
+				/* FALL THROUGH */
 
-				case QUERY_EDIT_SIGN_WIDGET_CANCEL:
-					delete w;
-					break;
-			}
-			break;
-
-		case WE_KEYPRESS:
-			switch (HandleEditBoxKey(w, qs, QUERY_EDIT_SIGN_WIDGET_TEXT, e)) {
-				case 1: // Enter pressed, confirms change
-					RenameSign(qs->cur_sign, qs->text.buf);
-					/* FALL THROUGH */
+			case QUERY_EDIT_SIGN_WIDGET_CANCEL:
+				delete this;
+				break;
+		}
+	}
 
-				case 2: // ESC pressed, closes window, abandons changes
-					delete w;
-					break;
-			}
-			break;
+	virtual bool OnKeyPress(uint16 key, uint16 keycode)
+	{
+		bool cont = true;
+		switch (this->HandleEditBoxKey(QUERY_EDIT_SIGN_WIDGET_TEXT, key, keycode, cont)) {
+			case 1: // Enter pressed, confirms change
+				RenameSign(this->cur_sign, this->text.buf);
+				/* FALL THROUGH */
 
-		case WE_MOUSELOOP:
-			HandleEditBox(w, qs, QUERY_EDIT_SIGN_WIDGET_TEXT);
-			break;
+			case 2: // ESC pressed, closes window, abandons changes
+				delete this;
+				break;
+		}
+		return cont;
+	}
 
-		case WE_DESTROY:
-			ClrBit(_no_scroll, SCROLL_EDIT);
-			break;
+	virtual void OnMouseLoop()
+	{
+		this->HandleEditBox(QUERY_EDIT_SIGN_WIDGET_TEXT);
 	}
-}
+};
 
 static const Widget _query_sign_edit_widgets[] = {
 { WWT_CLOSEBOX, RESIZE_NONE,  14,   0,  10,   0,  13, STR_00C5,          STR_018B_CLOSE_WINDOW},
@@ -292,7 +295,7 @@
 	WC_QUERY_STRING, WC_NONE,
 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
 	_query_sign_edit_widgets,
-	QuerySignEditWndProc
+	NULL
 };
 
 void ShowRenameSignWindow(const Sign *si)
@@ -301,11 +304,5 @@
 	DeleteWindowById(WC_QUERY_STRING, 0);
 	DeleteWindowById(WC_SAVELOAD, 0);
 
-	Window *w = new Window(&_query_sign_edit_desc);
-
-	WP(w, editsign_d).caption = STR_280B_EDIT_SIGN_TEXT;
-	WP(w, editsign_d).afilter = CS_ALPHANUMERAL;
-	w->LowerWidget(QUERY_EDIT_SIGN_WIDGET_TEXT);
-
-	UpdateSignEditWindow(w, si);
+	new SignWindow(&_query_sign_edit_desc, si);
 }
--- a/src/textbuf_gui.h
+++ b/src/textbuf_gui.h
@@ -18,21 +18,6 @@
 	uint16 caretxoffs;          ///< the current position of the caret in pixels
 };
 
-struct querystr_d {
-	StringID caption;
-	Textbuf text;
-	const char *orig;
-	CharSetFilter afilter;
-	bool handled;
-};
-assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(querystr_d));
-
-extern char _edit_str_buf[64];
-extern char _orig_str_buf[lengthof(_edit_str_buf)];
-
-void DrawEditBox(Window *w, querystr_d *string, int wid);
-void HandleEditBox(Window *w, querystr_d *string, int wid);
-int HandleEditBoxKey(Window *w, querystr_d *string, int wid, WindowEvent *we);
 bool HandleCaret(Textbuf *tb);
 
 void DeleteTextBufferAll(Textbuf *tb);
@@ -56,6 +41,4 @@
  */
 extern char _keyboard_opt[2][OSK_KEYBOARD_ENTRIES * 4 + 1];
 
-void ShowOnScreenKeyboard(Window *parent, querystr_d *q, int button, int cancel, int ok);
-
 #endif /* TEXTBUF_GUI_H */