changeset 8284:dbb7bfe0e95c draft

(svn r11848) -Codechange: New class-based drop down list functionality. Lists are now dynamically generated, and can include parameters, or be extended however needed.
author peter1138 <peter1138@openttd.org>
date Mon, 14 Jan 2008 16:10:58 +0000
parents dca081896c52
children e72a954df46c
files source.list src/autoreplace_gui.cpp src/build_vehicle_gui.cpp src/genworld_gui.cpp src/group_gui.cpp src/network/network_gui.cpp src/news_gui.cpp src/player_gui.cpp src/rail_gui.cpp src/settings_gui.cpp src/station_gui.cpp src/vehicle_gui.cpp src/widget.cpp src/widgets/dropdown.cpp src/widgets/dropdown_func.h src/widgets/dropdown_type.h src/window_gui.h
diffstat 17 files changed, 379 insertions(+), 252 deletions(-) [+]
line wrap: on
line diff
--- a/source.list
+++ b/source.list
@@ -93,6 +93,7 @@
 viewport.cpp
 waypoint.cpp
 widget.cpp
+widgets/dropdown.cpp
 #if WIN32
 	win32.cpp
 #end
@@ -210,6 +211,7 @@
 sound/win32_s.h
 video/win32_v.h
 window.h
+widgets/dropdown.h
 zoom.hpp
 
 # GUI Source Code
--- a/src/autoreplace_gui.cpp
+++ b/src/autoreplace_gui.cpp
@@ -18,6 +18,7 @@
 #include "autoreplace_func.h"
 #include "gfx_func.h"
 #include "player_func.h"
+#include "widgets/dropdown_func.h"
 
 #include "table/sprites.h"
 #include "table/strings.h"
--- a/src/build_vehicle_gui.cpp
+++ b/src/build_vehicle_gui.cpp
@@ -29,6 +29,7 @@
 #include "vehicle_func.h"
 #include "settings_type.h"
 #include "gfx_func.h"
+#include "widgets/dropdown_func.h"
 
 #include "table/sprites.h"
 #include "table/strings.h"
--- a/src/genworld_gui.cpp
+++ b/src/genworld_gui.cpp
@@ -25,6 +25,7 @@
 #include "string_func.h"
 #include "gfx_func.h"
 #include "settings_type.h"
+#include "widgets/dropdown_func.h"
 
 #include "table/strings.h"
 #include "table/sprites.h"
--- a/src/group_gui.cpp
+++ b/src/group_gui.cpp
@@ -23,6 +23,7 @@
 #include "viewport_func.h"
 #include "gfx_func.h"
 #include "player_func.h"
+#include "widgets/dropdown_func.h"
 
 #include "table/strings.h"
 #include "table/sprites.h"
@@ -328,14 +329,7 @@
 			/* The drop down menu is out, *but* it may not be used, retract it. */
 			if (gv->l.list_length == 0 && w->IsWidgetLowered(GRP_WIDGET_MANAGE_VEHICLES_DROPDOWN)) {
 				w->RaiseWidget(GRP_WIDGET_MANAGE_VEHICLES_DROPDOWN);
-				Window **w2;
-				FOR_ALL_WINDOWS(w2) {
-					if (w->window_class  == WP(*w2, dropdown_d).parent_wnd_class &&
-							w->window_number == WP(*w2, dropdown_d).parent_wnd_num) {
-						DeleteWindow(*w2);
-						break;
-					}
-				}
+				HideDropDownMenu(w);
 			}
 
 			/* Disable all lists management button when the list is empty */
--- a/src/network/network_gui.cpp
+++ b/src/network/network_gui.cpp
@@ -26,6 +26,7 @@
 #include "../gfx_func.h"
 #include "../player_func.h"
 #include "../settings_type.h"
+#include "../widgets/dropdown_func.h"
 
 #include "table/strings.h"
 #include "../table/sprites.h"
--- a/src/news_gui.cpp
+++ b/src/news_gui.cpp
@@ -15,6 +15,7 @@
 #include "vehicle_base.h"
 #include "sound_func.h"
 #include "string_func.h"
+#include "widgets/dropdown_func.h"
 
 #include "table/sprites.h"
 #include "table/strings.h"
--- a/src/player_gui.cpp
+++ b/src/player_gui.cpp
@@ -28,6 +28,7 @@
 #include "date_func.h"
 #include "string_func.h"
 #include "settings_type.h"
+#include "widgets/dropdown_func.h"
 
 #include "table/sprites.h"
 #include "table/strings.h"
--- a/src/rail_gui.cpp
+++ b/src/rail_gui.cpp
@@ -27,6 +27,7 @@
 #include "sound_func.h"
 #include "player_func.h"
 #include "settings_type.h"
+#include "widgets/dropdown_func.h"
 
 #include "bridge_map.h"
 #include "rail_map.h"
--- a/src/settings_gui.cpp
+++ b/src/settings_gui.cpp
@@ -24,6 +24,7 @@
 #include "core/alloc_func.hpp"
 #include "string_func.h"
 #include "gfx_func.h"
+#include "widgets/dropdown_func.h"
 
 #include "table/sprites.h"
 #include "table/strings.h"
--- a/src/station_gui.cpp
+++ b/src/station_gui.cpp
@@ -23,6 +23,7 @@
 #include "window_func.h"
 #include "viewport_func.h"
 #include "gfx_func.h"
+#include "widgets/dropdown_func.h"
 
 #include "table/strings.h"
 #include "table/sprites.h"
--- a/src/vehicle_gui.cpp
+++ b/src/vehicle_gui.cpp
@@ -35,6 +35,7 @@
 #include "core/alloc_func.hpp"
 #include "string_func.h"
 #include "settings_type.h"
+#include "widgets/dropdown_func.h"
 
 #include "table/sprites.h"
 #include "table/strings.h"
--- a/src/widget.cpp
+++ b/src/widget.cpp
@@ -9,6 +9,7 @@
 #include "gfx_func.h"
 #include "window_gui.h"
 #include "window_func.h"
+#include "widgets/dropdown_func.h"
 
 #include "table/sprites.h"
 #include "table/strings.h"
@@ -484,235 +485,6 @@
 
 }
 
-static const Widget _dropdown_menu_widgets[] = {
-{      WWT_PANEL,   RESIZE_NONE,     0,     0, 0,     0, 0, 0x0, STR_NULL},
-{  WWT_SCROLLBAR,   RESIZE_NONE,     0,     0, 0,     0, 0, 0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST},
-{   WIDGETS_END},
-};
-
-static int GetDropdownItem(const Window *w)
-{
-	byte item, counter;
-	int y;
-
-	if (GetWidgetFromPos(w, _cursor.pos.x - w->left, _cursor.pos.y - w->top) < 0)
-		return -1;
-
-	y = _cursor.pos.y - w->top - 2 + w->vscroll.pos * 10;
-
-	if (y < 0)
-		return - 1;
-
-	item = y / 10;
-	if (item >= WP(w, dropdown_d).num_items || (HasBit(WP(w,dropdown_d).disabled_state, item) && !HasBit(WP(w,dropdown_d).hidden_state, item)) || WP(w,dropdown_d).items[item] == 0)
-		return - 1;
-
-	/* Skip hidden items -- +1 for each hidden item before the clicked item. */
-	for (counter = 0; item >= counter; ++counter)
-		if (HasBit(WP(w, dropdown_d).hidden_state, counter)) item++;
-
-	return item;
-}
-
-static void DropdownMenuWndProc(Window *w, WindowEvent *e)
-{
-	int item;
-
-	switch (e->event) {
-		case WE_PAINT: {
-			int x,y,i,sel;
-			int width, height;
-
-			DrawWindowWidgets(w);
-
-			x = 1;
-			y = 2 - w->vscroll.pos * 10;
-
-			sel    = WP(w, dropdown_d).selected_index;
-			width  = w->widget[0].right - 3;
-			height = w->widget[0].bottom - 3;
-
-			for (i = 0; WP(w, dropdown_d).items[i] != INVALID_STRING_ID; i++, sel--) {
-				if (HasBit(WP(w, dropdown_d).hidden_state, i)) continue;
-
-				if (y >= 0 && y <= height) {
-					if (WP(w, dropdown_d).items[i] != STR_NULL) {
-						if (sel == 0) GfxFillRect(x + 1, y, x + width, y + 9, 0);
-						DrawStringTruncated(x + 2, y, WP(w, dropdown_d).items[i], sel == 0 ? TC_WHITE : TC_BLACK, x + width);
-
-						if (HasBit(WP(w, dropdown_d).disabled_state, i)) {
-							GfxFillRect(x, y, x + width, y + 9,
-								(1 << PALETTE_MODIFIER_GREYOUT) | _colour_gradient[_dropdown_menu_widgets[0].color][5]
-							);
-						}
-					} else {
-						int c1 = _colour_gradient[_dropdown_menu_widgets[0].color][3];
-						int c2 = _colour_gradient[_dropdown_menu_widgets[0].color][7];
-
-						GfxFillRect(x + 1, y + 3, x + w->width - 5, y + 3, c1);
-						GfxFillRect(x + 1, y + 4, x + w->width - 5, y + 4, c2);
-					}
-				}
-				y += 10;
-			}
-		} break;
-
-		case WE_CLICK: {
-			if (e->we.click.widget != 0) break;
-			item = GetDropdownItem(w);
-			if (item >= 0) {
-				WP(w, dropdown_d).click_delay = 4;
-				WP(w, dropdown_d).selected_index = item;
-				SetWindowDirty(w);
-			}
-		} break;
-
-		case WE_MOUSELOOP: {
-			Window *w2 = FindWindowById(WP(w, dropdown_d).parent_wnd_class, WP(w,dropdown_d).parent_wnd_num);
-			if (w2 == NULL) {
-				DeleteWindow(w);
-				return;
-			}
-
-			if (WP(w, dropdown_d).click_delay != 0 && --WP(w,dropdown_d).click_delay == 0) {
-				WindowEvent e;
-				e.event = WE_DROPDOWN_SELECT;
-				e.we.dropdown.button = WP(w, dropdown_d).parent_button;
-				e.we.dropdown.index  = WP(w, dropdown_d).selected_index;
-				w2->wndproc(w2, &e);
-				DeleteWindow(w);
-				return;
-			}
-
-			if (WP(w, dropdown_d).drag_mode) {
-				item = GetDropdownItem(w);
-
-				if (!_left_button_clicked) {
-					WP(w, dropdown_d).drag_mode = false;
-					if (item < 0) return;
-					WP(w, dropdown_d).click_delay = 2;
-				} else {
-					if (item < 0) return;
-				}
-
-				WP(w, dropdown_d).selected_index = item;
-				SetWindowDirty(w);
-			}
-		} break;
-
-		case WE_DESTROY: {
-			Window *w2 = FindWindowById(WP(w, dropdown_d).parent_wnd_class, WP(w,dropdown_d).parent_wnd_num);
-			if (w2 != NULL) {
-				w2->RaiseWidget(WP(w, dropdown_d).parent_button);
-				w2->InvalidateWidget(WP(w, dropdown_d).parent_button);
-			}
-		} break;
-	}
-}
-
-void ShowDropDownMenu(Window *w, const StringID *strings, int selected, int button, uint32 disabled_mask, uint32 hidden_mask)
-{
-	int i;
-	const Widget *wi;
-	Window *w2;
-	const Window *w3;
-	bool is_dropdown_menu_shown = w->IsWidgetLowered(button);
-	int top, height;
-	int screen_top, screen_bottom;
-	bool scroll = false;
-
-	DeleteWindowById(WC_DROPDOWN_MENU, 0);
-
-	if (is_dropdown_menu_shown) return;
-
-	w->LowerWidget(button);
-
-	w->InvalidateWidget(button);
-
-	for (i = 0; strings[i] != INVALID_STRING_ID; i++) {}
-	if (i == 0) return;
-
-	wi = &w->widget[button];
-
-	if (hidden_mask != 0) {
-		uint j;
-
-		for (j = 0; strings[j] != INVALID_STRING_ID; j++) {
-			if (HasBit(hidden_mask, j)) i--;
-		}
-	}
-
-	/* The preferred position is just below the dropdown calling widget */
-	top = w->top + wi->bottom + 2;
-	height = i * 10 + 4;
-
-	w3 = FindWindowById(WC_STATUS_BAR, 0);
-	screen_bottom = w3 == NULL ? _screen.height : w3->top;
-
-	/* Check if the dropdown will fully fit below the widget */
-	if (top + height >= screen_bottom) {
-		w3 = FindWindowById(WC_MAIN_TOOLBAR, 0);
-		screen_top = w3 == NULL ? 0 : w3->top + w3->height;
-
-		/* If not, check if it will fit above the widget */
-		if (w->top + wi->top - height - 1 > screen_top) {
-			top = w->top + wi->top - height - 1;
-		} else {
-			/* ... and lastly if it won't, enable the scroll bar and fit the
-			 * list in below the widget */
-			int rows = (screen_bottom - 4 - top) / 10;
-			height = rows * 10 + 4;
-			scroll = true;
-		}
-	}
-
-	w2 = AllocateWindow(
-		w->left + wi[-1].left + 1,
-		top,
-		wi->right - wi[-1].left + 1,
-		height,
-		DropdownMenuWndProc,
-		WC_DROPDOWN_MENU,
-		_dropdown_menu_widgets);
-
-	w2->widget[0].color = wi->color;
-	w2->widget[0].right = wi->right - wi[-1].left;
-	w2->widget[0].bottom = height - 1;
-
-	w2->SetWidgetHiddenState(1, !scroll);
-
-	if (scroll) {
-		/* We're scrolling, so enable the scroll bar and shrink the list by
-		 * the scrollbar's width */
-		w2->widget[1].color  = wi->color;
-		w2->widget[1].right  = w2->widget[0].right;
-		w2->widget[1].left   = w2->widget[1].right - 11;
-		w2->widget[1].bottom = height - 1;
-		w2->widget[0].right -= 12;
-
-		w2->vscroll.cap   = (height - 4) / 10;
-		w2->vscroll.count = i;
-	}
-
-	w2->desc_flags = WDF_DEF_WIDGET;
-	w2->flags4 &= ~WF_WHITE_BORDER_MASK;
-
-	WP(w2, dropdown_d).disabled_state = disabled_mask;
-	WP(w2, dropdown_d).hidden_state = hidden_mask;
-
-	WP(w2, dropdown_d).parent_wnd_class = w->window_class;
-	WP(w2, dropdown_d).parent_wnd_num = w->window_number;
-	WP(w2, dropdown_d).parent_button = button;
-
-	WP(w2, dropdown_d).num_items = i;
-	WP(w2, dropdown_d).selected_index = selected;
-	WP(w2, dropdown_d).items = strings;
-
-	WP(w2, dropdown_d).click_delay = 0;
-	WP(w2, dropdown_d).drag_mode = true;
-}
-
-
 static void ResizeWidgets(Window *w, byte a, byte b)
 {
 	int16 offset = w->widget[a].left;
new file mode 100644
--- /dev/null
+++ b/src/widgets/dropdown.cpp
@@ -0,0 +1,287 @@
+/* $Id$ */
+
+#include "../stdafx.h"
+#include "../openttd.h"
+#include "../strings_type.h"
+#include "../window_gui.h"
+#include "../strings_func.h"
+#include "../strings_type.h"
+#include "../gfx_func.h"
+#include "../window_func.h"
+#include "dropdown_type.h"
+#include "dropdown_func.h"
+
+#include "../table/sprites.h"
+#include "table/strings.h"
+
+StringID DropDownListItem::String() const
+{
+	return STR_NULL;
+}
+
+StringID DropDownListStringItem::String() const
+{
+	return this->string;
+}
+
+StringID DropDownListParamStringItem::String() const
+{
+	for (uint i = 0; i < lengthof(this->decode_params); i++) SetDParam(i, this->decode_params[i]);
+	return this->string;
+}
+
+struct dropdown_d {
+	WindowClass parent_wnd_class;
+	WindowNumber parent_wnd_num;
+	byte parent_button;
+	DropDownList *list;
+	byte selected_index;
+	byte click_delay;
+	bool drag_mode;
+};
+assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(dropdown_d));
+
+static const Widget _dropdown_menu_widgets[] = {
+{      WWT_PANEL,   RESIZE_NONE,     0,     0, 0,     0, 0, 0x0, STR_NULL},
+{  WWT_SCROLLBAR,   RESIZE_NONE,     0,     0, 0,     0, 0, 0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST},
+{   WIDGETS_END},
+};
+
+static int GetDropDownItem(const Window *w)
+{
+	if (GetWidgetFromPos(w, _cursor.pos.x - w->left, _cursor.pos.y - w->top) < 0) return -1;
+
+	int y = _cursor.pos.y - w->top - 2 + w->vscroll.pos * 10;
+	if (y < 0) return -1;
+
+	uint selected_row = y / 10;
+	const DropDownList *list = WP(w, dropdown_d).list;
+
+	if (selected_row >= list->size()) return -1;
+
+	for (DropDownList::const_iterator it = list->begin(); it != list->end(); ++it, selected_row--) {
+		if (selected_row == 0) {
+			const DropDownListItem *item = *it;
+			if (item->masked || item->String() == STR_NULL) return -1;
+			return item->result;
+		}
+	}
+
+	return -1;
+}
+
+static void DropDownMenuWndProc(Window *w, WindowEvent *e)
+{
+	switch (e->event) {
+		case WE_PAINT: {
+			DrawWindowWidgets(w);
+
+			int x = 1;
+			int y = 2 - w->vscroll.pos * 10;
+
+			int sel    = WP(w, dropdown_d).selected_index;
+			int width  = w->widget[0].right - 3;
+			int height = w->widget[0].bottom - 3;
+
+			DropDownList *list = WP(w, dropdown_d).list;
+
+			for (DropDownList::const_iterator it = list->begin(); it != list->end(); ++it) {
+				if (y >= 0 && y <= height) {
+					const DropDownListItem *item = *it;
+					if (item->String() != STR_NULL) {
+						if (sel == item->result) GfxFillRect(x + 1, y, x + width, y + 9, 0);
+
+						DrawStringTruncated(x + 2, y, item->String(), sel == item->result ? TC_WHITE : TC_BLACK, x + width);
+
+						if (item->masked) {
+							GfxFillRect(x, y, x + width, y + 9,
+								(1 << PALETTE_MODIFIER_GREYOUT) | _colour_gradient[_dropdown_menu_widgets[0].color][5]
+							);
+						}
+					} else {
+						int c1 = _colour_gradient[_dropdown_menu_widgets[0].color][3];
+						int c2 = _colour_gradient[_dropdown_menu_widgets[0].color][7];
+
+						GfxFillRect(x + 1, y + 3, x + w->width - 5, y + 3, c1);
+						GfxFillRect(x + 1, y + 4, x + w->width - 5, y + 4, c2);
+					}
+				}
+				y += 10;
+			}
+		} break;
+
+		case WE_CLICK: {
+			if (e->we.click.widget != 0) break;
+			int item = GetDropDownItem(w);
+			if (item >= 0) {
+				WP(w, dropdown_d).click_delay = 4;
+				WP(w, dropdown_d).selected_index = item;
+				SetWindowDirty(w);
+			}
+		} break;
+
+		case WE_MOUSELOOP: {
+			Window *w2 = FindWindowById(WP(w, dropdown_d).parent_wnd_class, WP(w,dropdown_d).parent_wnd_num);
+			if (w2 == NULL) {
+				DeleteWindow(w);
+				return;
+			}
+
+			if (WP(w, dropdown_d).click_delay != 0 && --WP(w,dropdown_d).click_delay == 0) {
+				WindowEvent e;
+				e.event = WE_DROPDOWN_SELECT;
+				e.we.dropdown.button = WP(w, dropdown_d).parent_button;
+				e.we.dropdown.index  = WP(w, dropdown_d).selected_index;
+				w2->wndproc(w2, &e);
+				DeleteWindow(w);
+				return;
+			}
+
+			if (WP(w, dropdown_d).drag_mode) {
+				int item = GetDropDownItem(w);
+
+				if (!_left_button_clicked) {
+					WP(w, dropdown_d).drag_mode = false;
+					if (item < 0) return;
+					WP(w, dropdown_d).click_delay = 2;
+				} else {
+					if (item < 0) return;
+				}
+
+				WP(w, dropdown_d).selected_index = item;
+				SetWindowDirty(w);
+			}
+		} break;
+
+		case WE_DESTROY: {
+			Window *w2 = FindWindowById(WP(w, dropdown_d).parent_wnd_class, WP(w,dropdown_d).parent_wnd_num);
+			if (w2 != NULL) {
+				w2->RaiseWidget(WP(w, dropdown_d).parent_button);
+				w2->InvalidateWidget(WP(w, dropdown_d).parent_button);
+			}
+
+			delete WP(w, dropdown_d).list;
+		} break;
+	}
+}
+
+void ShowDropDownList(Window *w, DropDownList *list, int selected, int button)
+{
+	bool is_dropdown_menu_shown = w->IsWidgetLowered(button);
+
+	DeleteWindowById(WC_DROPDOWN_MENU, 0);
+
+	if (is_dropdown_menu_shown) {
+		delete list;
+		return;
+	}
+
+	w->LowerWidget(button);
+	w->InvalidateWidget(button);
+
+	/* Our parent's button widget is used to determine where to place the drop
+	 * down list window. */
+	const Widget *wi = &w->widget[button];
+
+	/* The preferred position is just below the dropdown calling widget */
+	int top = w->top + wi->bottom + 2;
+	int height = list->size() * 10 + 4;
+
+	/* Check if the status bar is visible, as we don't want to draw over it */
+	Window *w3 = FindWindowById(WC_STATUS_BAR, 0);
+	int screen_bottom = w3 == NULL ? _screen.height : w3->top;
+
+	bool scroll = false;
+
+	/* Check if the dropdown will fully fit below the widget */
+	if (top + height >= screen_bottom) {
+		w3 = FindWindowById(WC_MAIN_TOOLBAR, 0);
+		int screen_top = w3 == NULL ? 0 : w3->top + w3->height;
+
+		/* If not, check if it will fit above the widget */
+		if (w->top + wi->top - height - 1 > screen_top) {
+			top = w->top + wi->top - height - 1;
+		} else {
+			/* ... and lastly if it won't, enable the scroll bar and fit the
+			 * list in below the widget */
+			int rows = (screen_bottom - 4 - top) / 10;
+			height = rows * 10 + 4;
+			scroll = true;
+		}
+	}
+
+	Window *dw = AllocateWindow(
+		w->left + wi[-1].left + 1,
+		top,
+		wi->right - wi[-1].left + 1,
+		height,
+		DropDownMenuWndProc,
+		WC_DROPDOWN_MENU,
+		_dropdown_menu_widgets);
+
+	dw->widget[0].color = wi->color;
+	dw->widget[0].right = wi->right - wi[-1].left;
+	dw->widget[0].bottom = height - 1;
+
+	dw->SetWidgetHiddenState(1, !scroll);
+
+	if (scroll) {
+		/* We're scrolling, so enable the scroll bar and shrink the list by
+		 * the scrollbar's width */
+		dw->widget[1].color  = wi->color;
+		dw->widget[1].right  = dw->widget[0].right;
+		dw->widget[1].left   = dw->widget[1].right - 11;
+		dw->widget[1].bottom = height - 1;
+		dw->widget[0].right -= 12;
+
+		dw->vscroll.cap   = (height - 4) / 10;
+		dw->vscroll.count = list->size();
+	}
+
+	dw->desc_flags = WDF_DEF_WIDGET;
+	dw->flags4 &= ~WF_WHITE_BORDER_MASK;
+
+	WP(dw, dropdown_d).parent_wnd_class = w->window_class;
+	WP(dw, dropdown_d).parent_wnd_num   = w->window_number;
+	WP(dw, dropdown_d).parent_button    = button;
+	WP(dw, dropdown_d).list             = list;
+	WP(dw, dropdown_d).selected_index   = selected;
+	WP(dw, dropdown_d).click_delay      = 0;
+	WP(dw, dropdown_d).drag_mode        = true;
+}
+
+void ShowDropDownMenu(Window *w, const StringID *strings, int selected, int button, uint32 disabled_mask, uint32 hidden_mask)
+{
+	uint result = 0;
+	DropDownList *list = new DropDownList();
+
+	for (uint i = 0; strings[i] != INVALID_STRING_ID; i++) {
+		if (!HasBit(hidden_mask, i)) {
+			list->push_back(new DropDownListStringItem(strings[i], result, HasBit(disabled_mask, i)));
+		}
+		result++;
+	}
+
+	/* No entries in the list? */
+	if (list->size() == 0) {
+		delete list;
+		return;
+	}
+
+	ShowDropDownList(w, list, selected, button);
+}
+
+void HideDropDownMenu(Window *pw)
+{
+	Window **wz;
+	FOR_ALL_WINDOWS(wz) {
+		if ((*wz)->window_class != WC_DROPDOWN_MENU) continue;
+
+		if (pw->window_class == WP(*wz, dropdown_d).parent_wnd_class &&
+				pw->window_number == WP(*wz, dropdown_d).parent_wnd_num) {
+			DeleteWindow(*wz);
+			break;
+		}
+	}
+}
+
new file mode 100644
--- /dev/null
+++ b/src/widgets/dropdown_func.h
@@ -0,0 +1,12 @@
+/* $Id$ */
+
+#ifndef WIDGETS_DROPDOWN_FUNC_H
+#define WIDGETS_DROPDOWN_FUNC_H
+
+/* Show drop down menu containing a fixed list of strings */
+void ShowDropDownMenu(Window *w, const StringID *strings, int selected, int button, uint32 disabled_mask, uint32 hidden_mask);
+
+/* Hide drop down menu of a parent window */
+void HideDropDownMenu(Window *pw);
+
+#endif /* WIDGETS_DROPDOWN_FUNC_H */
new file mode 100644
--- /dev/null
+++ b/src/widgets/dropdown_type.h
@@ -0,0 +1,65 @@
+/* $Id$ */
+
+#ifndef WIDGETS_DROPDOWN_TYPE_H
+#define WIDGETS_DROPDOWN_TYPE_H
+
+#include "../window_type.h"
+#include <list>
+
+/**
+ * Base list item class from which others are derived. If placed in a list it
+ * will appear as a horizontal line in the menu.
+ */
+class DropDownListItem {
+public:
+	int result;  ///< Result code to return to window on selection
+	bool masked; ///< Masked and unselectable item
+
+	virtual StringID String() const;
+};
+
+/**
+ * Common string list item.
+ */
+class DropDownListStringItem : public DropDownListItem {
+public:
+	StringID string; ///< String ID of item
+
+	DropDownListStringItem(StringID string, uint result, bool masked)
+	{
+		this->string = string;
+		this->result = result;
+		this->masked = masked;
+	}
+
+	StringID String() const;
+};
+
+/**
+ * String list item with parameters.
+ */
+class DropDownListParamStringItem : public DropDownListStringItem {
+public:
+	uint64 decode_params[10]; ///< Parameters of the string
+
+	StringID String() const;
+	void SetParam(uint index, uint64 value) { decode_params[index] = value; }
+};
+
+/**
+ * A drop down list is a collection of drop down list items.
+ */
+typedef std::list<DropDownListItem *> DropDownList;
+
+/**
+ * Show a drop down list.
+ * @param w        Parent window for the list.
+ * @param list     Prepopulated DropDownList. Will be deleted when the list is
+ *                 closed.
+ * @param selected The initially selected list item.
+ * @param button   The widget within the parent window that is used to determine
+ *                 the list's location.
+ */
+void ShowDropDownList(Window *w, DropDownList *list, int selected, int button);
+
+#endif /* WIDGETS_DROPDOWN_TYPE_H */
--- a/src/window_gui.h
+++ b/src/window_gui.h
@@ -439,20 +439,6 @@
 };
 assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(message_d));
 
-struct dropdown_d {
-	uint32 disabled_state;
-	uint32 hidden_state;
-	WindowClass parent_wnd_class;
-	WindowNumber parent_wnd_num;
-	byte parent_button;
-	byte num_items;
-	byte selected_index;
-	const StringID *items;
-	byte click_delay;
-	bool drag_mode;
-};
-assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(dropdown_d));
-
 struct vehiclelist_d {
 	const Vehicle** sort_list;  // List of vehicles (sorted)
 	Listing *_sorting;          // pointer to the appropiate subcategory of _sorting
@@ -592,7 +578,6 @@
 /* widget.cpp */
 int GetWidgetFromPos(const Window *w, int x, int y);
 void DrawWindowWidgets(const Window *w);
-void ShowDropDownMenu(Window *w, const StringID *strings, int selected, int button, uint32 disabled_mask, uint32 hidden_mask);
 
 
 Window *GetCallbackWnd();