Changeset - r13694:4523784084b5
[Not reviewed]
src/ai/ai_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file ai_gui.cpp Window for configuring the AIs */
 

	
 
#include "../stdafx.h"
 
#include "../gui.h"
 
#include "../window_gui.h"
 
#include "../company_func.h"
 
#include "../company_base.h"
 
#include "../company_gui.h"
 
#include "../strings_func.h"
 
#include "../window_func.h"
 
#include "../gfx_func.h"
 
#include "../command_func.h"
 
#include "../network/network.h"
 
#include "../textbuf_gui.h"
 
#include "../settings_func.h"
 
#include "../network/network_content.h"
 

	
 
#include "ai.hpp"
 
#include "api/ai_log.hpp"
 
#include "ai_config.hpp"
 
#include "ai_instance.hpp"
 

	
 
#include "table/strings.h"
 

	
 
/** Enum referring to the widgets of the AI list window */
 
enum AIListWindowWidgets {
 
	AIL_WIDGET_CLOSEBOX = 0,     ///< Close window button
 
	AIL_WIDGET_CAPTION,          ///< Window caption
 
	AIL_WIDGET_LIST,             ///< The matrix with all available AIs
 
	AIL_WIDGET_SCROLLBAR,        ///< Scrollbar next to the AI list
 
	AIL_WIDGET_INFO_BG,          ///< Panel to draw some AI information on
 
	AIL_WIDGET_ACCEPT,           ///< Accept button
 
	AIL_WIDGET_CANCEL,           ///< Cancel button
 
	AIL_WIDGET_CONTENT_DOWNLOAD, ///< Download content button
 
	AIL_WIDGET_RESIZE,           ///< Resize button
 
};
 

	
 
/**
 
 * Window that let you choose an available AI.
 
 */
 
struct AIListWindow : public Window {
 
	const AIInfoList *ai_info_list;
 
	int selected;
 
	CompanyID slot;
 
	int line_height; // Height of a row in the matrix widget.
 

	
 
	AIListWindow(const WindowDesc *desc, CompanyID slot) : Window(),
 
		slot(slot)
 
	{
 
		this->ai_info_list = AI::GetUniqueInfoList();
 

	
 
		this->InitNested(desc); // Initializes 'this->line_height' as side effect.
 

	
 
		this->vscroll.SetCount((int)this->ai_info_list->size() + 1);
 

	
 
		/* Try if we can find the currently selected AI */
 
		this->selected = -1;
 
		if (AIConfig::GetConfig(slot)->HasAI()) {
 
			AIInfo *info = AIConfig::GetConfig(slot)->GetInfo();
 
			int i = 0;
 
			for (AIInfoList::const_iterator it = this->ai_info_list->begin(); it != this->ai_info_list->end(); it++, i++) {
 
				if ((*it).second == info) {
 
					this->selected = i;
 
					break;
 
				}
 
			}
 
		}
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		if (widget == AIL_WIDGET_LIST) {
 
			this->line_height = FONT_HEIGHT_NORMAL + WD_MATRIX_TOP + WD_MATRIX_BOTTOM;
 

	
 
			resize->width = 1;
 
			resize->height = this->line_height;
 
			size->height = GB(this->GetWidget<NWidgetCore>(widget)->widget_data, MAT_ROW_START, MAT_ROW_BITS) * this->line_height;
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case AIL_WIDGET_LIST: {
 
				/* Draw a list of all available AIs. */
 
				int y = this->GetWidget<NWidgetBase>(AIL_WIDGET_LIST)->pos_y;
 
				/* First AI in the list is hardcoded to random */
 
				if (this->vscroll.IsVisible(0)) {
 
					DrawString(r.left + WD_MATRIX_LEFT, r.right - WD_MATRIX_LEFT, y + WD_MATRIX_TOP, STR_AI_CONFIG_RANDOM_AI, this->selected == -1 ? TC_WHITE : TC_BLACK);
 
					y += this->line_height;
 
				}
 
				AIInfoList::const_iterator it = this->ai_info_list->begin();
 
				for (int i = 1; it != this->ai_info_list->end(); i++, it++) {
 
					if (this->vscroll.IsVisible(i)) {
 
						DrawString(r.left + WD_MATRIX_LEFT, r.right - WD_MATRIX_RIGHT, y + WD_MATRIX_TOP, (*it).second->GetName(), (this->selected == i - 1) ? TC_WHITE : TC_BLACK);
 
						y += this->line_height;
 
					}
 
				}
 
				break;
 
			}
 
			case AIL_WIDGET_INFO_BG: {
 
				AIInfo *selected_info = NULL;
 
				AIInfoList::const_iterator it = this->ai_info_list->begin();
 
				for (int i = 1; selected_info == NULL && it != this->ai_info_list->end(); i++, it++) {
 
					if (this->selected == i - 1) selected_info = (*it).second;
 
				}
 
				/* Some info about the currently selected AI. */
 
				if (selected_info != NULL) {
 
					int y = r.top + WD_FRAMERECT_TOP;
 
					SetDParamStr(0, selected_info->GetAuthor());
 
					DrawString(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, y, STR_AI_LIST_AUTHOR);
 
					y += FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL;
 
					SetDParam(0, selected_info->GetVersion());
 
					DrawString(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, y, STR_AI_LIST_VERSION);
 
					y += FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL;
 
					if (selected_info->GetURL() != NULL) {
 
						SetDParamStr(0, selected_info->GetURL());
 
						DrawString(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, y, STR_AI_LIST_URL);
 
						y += FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL;
 
					}
 
					SetDParamStr(0, selected_info->GetDescription());
 
					DrawStringMultiLine(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, y, r.bottom - WD_FRAMERECT_BOTTOM, STR_JUST_RAW_STRING, TC_BLACK);
 
				}
 
				break;
 
			}
 
		}
 
	}
 

	
 
	void ChangeAI()
 
	{
 
		if (this->selected == -1) {
 
			AIConfig::GetConfig(slot)->ChangeAI(NULL);
 
		} else {
 
			AIInfoList::const_iterator it = this->ai_info_list->begin();
 
			for (int i = 0; i < this->selected; i++) it++;
 
			AIConfig::GetConfig(slot)->ChangeAI((*it).second->GetName(), (*it).second->GetVersion());
 
		}
 
		SetWindowDirty(WC_GAME_OPTIONS, 0);
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case AIL_WIDGET_LIST: { // Select one of the AIs
 
				int sel = (pt.y - this->GetWidget<NWidgetBase>(AIL_WIDGET_LIST)->pos_y) / this->line_height + this->vscroll.GetPosition() - 1;
 
				if (sel < (int)this->ai_info_list->size()) {
 
					this->selected = sel;
 
					this->SetDirty();
 
				}
 
				break;
 
			}
 

	
 
			case AIL_WIDGET_ACCEPT: {
 
				this->ChangeAI();
 
				delete this;
 
				break;
 
			}
 

	
 
			case AIL_WIDGET_CANCEL:
 
				delete this;
 
				break;
 

	
 
			case AIL_WIDGET_CONTENT_DOWNLOAD:
 
				if (!_network_available) {
 
					ShowErrorMessage(STR_NETWORK_ERROR_NOTAVAILABLE, INVALID_STRING_ID, 0, 0);
 
				} else {
 
#if defined(ENABLE_NETWORK)
 
					ShowNetworkContentListWindow(NULL, CONTENT_TYPE_AI);
 
#endif
 
				}
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnDoubleClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case AIL_WIDGET_LIST: {
 
				int sel = (pt.y - this->GetWidget<NWidgetBase>(AIL_WIDGET_LIST)->pos_y) / this->line_height + this->vscroll.GetPosition() - 1;
 
				if (sel < (int)this->ai_info_list->size()) {
 
					this->selected = sel;
 
					this->ChangeAI();
 
					delete this;
 
				}
 
				break;
 
			}
 
		}
 
	}
 

	
 
	virtual void OnResize()
 
	{
 
		NWidgetCore *nwi = this->GetWidget<NWidgetCore>(AIL_WIDGET_LIST);
 
		this->vscroll.SetCapacity(nwi->current_y / this->line_height);
 
		nwi->widget_data = (this->vscroll.GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START);
 
	}
 
};
 

	
 
static const NWidgetPart _nested_ai_list_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_MAUVE, AIL_WIDGET_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_MAUVE, AIL_WIDGET_CAPTION), SetDataTip(STR_AI_LIST_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_MATRIX, COLOUR_MAUVE, AIL_WIDGET_LIST), SetMinimalSize(188, 112), SetResize(1, 1), SetDataTip(0x501, STR_AI_LIST_TOOLTIP),
 
		NWidget(WWT_SCROLLBAR, COLOUR_MAUVE, AIL_WIDGET_SCROLLBAR),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_MAUVE, AIL_WIDGET_INFO_BG), SetMinimalSize(200, 84), SetResize(1, 0),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, AIL_WIDGET_ACCEPT), SetMinimalSize(100, 12), SetResize(1, 0), SetDataTip(STR_AI_LIST_ACCEPT, STR_AI_LIST_ACCEPT_TOOLTIP),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, AIL_WIDGET_CANCEL), SetMinimalSize(100, 12), SetResize(1, 0), SetDataTip(STR_AI_LIST_CANCEL, STR_AI_LIST_CANCEL_TOOLTIP),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, AIL_WIDGET_CONTENT_DOWNLOAD), SetMinimalSize(188, 12), SetResize(1, 0), SetDataTip(STR_INTRO_ONLINE_CONTENT, STR_INTRO_TOOLTIP_ONLINE_CONTENT),
 
		NWidget(WWT_RESIZEBOX, COLOUR_MAUVE, AIL_WIDGET_RESIZE),
 
	EndContainer(),
 
};
 

	
 
/* Window definition for the ai list window. */
 
static const WindowDesc _ai_list_desc(
 
	WDP_CENTER, WDP_CENTER, 200, 234,
 
	WC_AI_LIST, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_RESIZABLE,
 
	_nested_ai_list_widgets, lengthof(_nested_ai_list_widgets)
 
);
 

	
 
static void ShowAIListWindow(CompanyID slot)
 
{
 
	DeleteWindowByClass(WC_AI_LIST);
 
	new AIListWindow(&_ai_list_desc, slot);
 
}
 

	
 
/** Enum referring to the widgets of the AI settings window */
 
enum AISettingsWindowWidgest {
 
	AIS_WIDGET_CLOSEBOX = 0, ///< Close window button
 
	AIS_WIDGET_CAPTION,      ///< Window caption
 
	AIS_WIDGET_BACKGROUND,   ///< Panel to draw the settings on
 
	AIS_WIDGET_SCROLLBAR,    ///< Scrollbar to scroll through all settings
 
	AIS_WIDGET_ACCEPT,       ///< Accept button
 
	AIS_WIDGET_RESET,        ///< Reset button
 
	AIS_WIDGET_RESIZE,       ///< Resize button
 
};
 

	
 
/**
 
 * Window for settings the parameters of an AI.
 
 */
 
struct AISettingsWindow : public Window {
 
	CompanyID slot;
 
	AIConfig *ai_config;
 
	int clicked_button;
 
	bool clicked_increase;
 
	int timeout;
 
	int clicked_row;
 
	int line_height; // Height of a row in the matrix widget.
 

	
 
	AISettingsWindow(const WindowDesc *desc, CompanyID slot) : Window(),
 
		slot(slot),
 
		clicked_button(-1),
 
		timeout(0)
 
	{
 
		this->ai_config = AIConfig::GetConfig(slot);
 

	
 
		this->InitNested(desc);  // Initializes 'this->line_height' as side effect.
 

	
 
		this->vscroll.SetCount((int)this->ai_config->GetConfigList()->size());
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		if (widget == AIS_WIDGET_BACKGROUND) {
 
			this->line_height = FONT_HEIGHT_NORMAL + WD_MATRIX_TOP + WD_MATRIX_BOTTOM;
 

	
 
			resize->width = 1;
 
			resize->height = this->line_height;
 
			size->height = GB(this->GetWidget<NWidgetCore>(widget)->widget_data, MAT_ROW_START, MAT_ROW_BITS) * this->line_height;
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		if (widget != AIS_WIDGET_BACKGROUND) return;
 

	
 
		AIConfig *config = this->ai_config;
 
		AIConfigItemList::const_iterator it = config->GetConfigList()->begin();
 
		int i = 0;
 
		for (; !this->vscroll.IsVisible(i); i++) it++;
 

	
 
		bool rtl = _dynlang.text_dir == TD_RTL;
 
		uint buttons_left = rtl ? r.right - 23 : r.left + 4;
 
		uint value_left   = r.left + (rtl ? WD_FRAMERECT_LEFT : 28);
 
		uint value_right  = r.right - (rtl ? 28 : WD_FRAMERECT_RIGHT);
 
		uint text_left    = r.left + (rtl ? WD_FRAMERECT_LEFT : 54);
 
		uint text_right   = r.right - (rtl ? 54 : WD_FRAMERECT_RIGHT);
 

	
 

	
 
		int y = r.top;
 
		for (; this->vscroll.IsVisible(i) && it != config->GetConfigList()->end(); i++, it++) {
 
			int current_value = config->GetSetting((*it).name);
 

	
 
			int x = rtl ? r.right : r.left;
 
			if (((*it).flags & AICONFIG_BOOLEAN) != 0) {
 
				DrawFrameRect(buttons_left, y  + 2, buttons_left + 19, y + 10, (current_value != 0) ? COLOUR_GREEN : COLOUR_RED, (current_value != 0) ? FR_LOWERED : FR_NONE);
 
			} else {
 
				DrawArrowButtons(buttons_left, y + 2, COLOUR_YELLOW, (this->clicked_button == i) ? 1 + (this->clicked_increase != rtl) : 0, current_value > (*it).min_value, current_value < (*it).max_value);
 
				if (it->labels != NULL && it->labels->Find(current_value) != it->labels->End()) {
 
					x = DrawString(value_left, value_right, y + WD_MATRIX_TOP, it->labels->Find(current_value)->second, TC_ORANGE);
 
				} else {
 
					SetDParam(0, current_value);
 
					x = DrawString(value_left, value_right, y + WD_MATRIX_TOP, STR_JUST_INT, TC_ORANGE);
 
				}
 
			}
 

	
 
			DrawString(max(rtl ? 0U : x + 3, text_left), min(rtl ? x - 3 : r.right, text_right), y + WD_MATRIX_TOP, (*it).description, TC_LIGHT_BLUE);
 
			y += this->line_height;
 
		}
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case AIS_WIDGET_BACKGROUND: {
 
				const NWidgetBase *wid = this->GetWidget<NWidgetBase>(AIS_WIDGET_BACKGROUND);
 
				int num = (pt.y - wid->pos_y) / this->line_height + this->vscroll.GetPosition();
 
				if (num >= (int)this->ai_config->GetConfigList()->size()) break;
 

	
 
				AIConfigItemList::const_iterator it = this->ai_config->GetConfigList()->begin();
 
				for (int i = 0; i < num; i++) it++;
 
				AIConfigItem config_item = *it;
 
				bool bool_item = (config_item.flags & AICONFIG_BOOLEAN) != 0;
 

	
 
				int x = pt.x - wid->pos_x;
 
				if (_dynlang.text_dir == TD_RTL) x = wid->current_x - x;
 
				x -= 4;
 
				/* One of the arrows is clicked (or green/red rect in case of bool value) */
 
				if (IsInsideMM(x, 0, 21)) {
 
					int new_val = this->ai_config->GetSetting(config_item.name);
 
					if (bool_item) {
 
						new_val = !new_val;
 
					} else if (x >= 10) {
 
						/* Increase button clicked */
 
						new_val += config_item.step_size;
 
						if (new_val > config_item.max_value) new_val = config_item.max_value;
 
						this->clicked_increase = true;
 
					} else {
 
						/* Decrease button clicked */
 
						new_val -= config_item.step_size;
 
						if (new_val < config_item.min_value) new_val = config_item.min_value;
 
						this->clicked_increase = false;
 
					}
 

	
 
					this->ai_config->SetSetting(config_item.name, new_val);
 
					this->clicked_button = num;
 
					this->timeout = 5;
 

	
 
					if (_settings_newgame.difficulty.diff_level != 3) {
 
						_settings_newgame.difficulty.diff_level = 3;
 
						ShowErrorMessage(STR_WARNING_DIFFICULTY_TO_CUSTOM, INVALID_STRING_ID, 0, 0);
 
					}
 
				} else if (!bool_item) {
 
					/* Display a query box so users can enter a custom value. */
 
					this->clicked_row = num;
 
					SetDParam(0, this->ai_config->GetSetting(config_item.name));
 
					ShowQueryString(STR_JUST_INT, STR_CONFIG_SETTING_QUERY_CAPTION, 10, 100, this, CS_NUMERAL, QSF_NONE);
 
				}
 

	
 
				this->SetDirty();
 
				break;
 
			}
 

	
 
			case AIS_WIDGET_ACCEPT:
 
				delete this;
 
				break;
 

	
 
			case AIS_WIDGET_RESET:
 
				this->ai_config->ResetSettings();
 
				this->SetDirty();
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnQueryTextFinished(char *str)
 
	{
 
		if (StrEmpty(str)) return;
 
		AIConfigItemList::const_iterator it = this->ai_config->GetConfigList()->begin();
 
		for (int i = 0; i < this->clicked_row; i++) it++;
 
		int32 value = atoi(str);
 
		this->ai_config->SetSetting((*it).name, value);
 
		this->SetDirty();
 
	}
 

	
 
	virtual void OnResize()
 
	{
 
		NWidgetCore *nwi = this->GetWidget<NWidgetCore>(AIS_WIDGET_BACKGROUND);
 
		this->vscroll.SetCapacity(nwi->current_y / this->line_height);
 
		nwi->widget_data = (this->vscroll.GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START);
 
	}
 

	
 
	virtual void OnTick()
 
	{
 
		if (--this->timeout == 0) {
 
			this->clicked_button = -1;
 
			this->SetDirty();
 
		}
 
	}
 
};
 

	
 
static const NWidgetPart _nested_ai_settings_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_MAUVE, AIS_WIDGET_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_MAUVE, AIS_WIDGET_CAPTION), SetDataTip(STR_AI_SETTINGS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_MATRIX, COLOUR_MAUVE, AIS_WIDGET_BACKGROUND), SetMinimalSize(188, 182), SetResize(1, 1), SetDataTip(0x501, STR_NULL),
 
		NWidget(WWT_SCROLLBAR, COLOUR_MAUVE, AIS_WIDGET_SCROLLBAR),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, AIS_WIDGET_ACCEPT), SetMinimalSize(94, 12), SetResize(1, 0), SetDataTip(STR_AI_SETTINGS_CLOSE, STR_NULL),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, AIS_WIDGET_RESET), SetMinimalSize(94, 12), SetResize(1, 0), SetDataTip(STR_AI_SETTINGS_RESET, STR_NULL),
 
		NWidget(WWT_RESIZEBOX, COLOUR_MAUVE, AIS_WIDGET_RESIZE),
 
	EndContainer(),
 
};
 

	
 
/* Window definition for the AI settings window. */
 
static const WindowDesc _ai_settings_desc(
 
	WDP_CENTER, WDP_CENTER, 500, 208,
 
	WC_AI_SETTINGS, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_RESIZABLE,
 
	_nested_ai_settings_widgets, lengthof(_nested_ai_settings_widgets)
 
);
 

	
 
static void ShowAISettingsWindow(CompanyID slot)
 
{
 
	DeleteWindowByClass(WC_AI_LIST);
 
	DeleteWindowByClass(WC_AI_SETTINGS);
 
	new AISettingsWindow(&_ai_settings_desc, slot);
 
}
 

	
 
/** Enum referring to the widgets of the AI config window */
 
enum AIConfigWindowWidgets {
 
	AIC_WIDGET_CLOSEBOX = 0, ///< Close window button
 
	AIC_WIDGET_CAPTION,      ///< Window caption
 
	AIC_WIDGET_BACKGROUND,   ///< Window background
 
	AIC_WIDGET_DECREASE,     ///< Decrease the number of AIs
 
	AIC_WIDGET_INCREASE,     ///< Increase the number of AIs
 
	AIC_WIDGET_NUMBER,       ///< Number of AIs
 
	AIC_WIDGET_LIST,         ///< List with currently selected AIs
 
	AIC_WIDGET_SCROLLBAR,    ///< Scrollbar to scroll through the selected AIs
 
	AIC_WIDGET_CHANGE,       ///< Select another AI button
 
	AIC_WIDGET_CONFIGURE,    ///< Change AI settings button
 
	AIC_WIDGET_CLOSE,        ///< Close window button
 
	AIC_WIDGET_RESIZE,       ///< Resize button
 
};
 

	
 
static const NWidgetPart _nested_ai_config_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_MAUVE, AIC_WIDGET_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_MAUVE, AIC_WIDGET_CAPTION), SetDataTip(STR_AI_CONFIG_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_MAUVE, AIC_WIDGET_BACKGROUND),
 
		NWidget(NWID_VERTICAL), SetPIP(4, 0, 4),
 
			NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 10),
 
				NWidget(NWID_BUTTON_ARROW, COLOUR_YELLOW, AIC_WIDGET_DECREASE), SetFill(false, true), SetDataTip(AWV_DECREASE, STR_NULL),
 
				NWidget(NWID_BUTTON_ARROW, COLOUR_YELLOW, AIC_WIDGET_INCREASE), SetFill(false, true), SetDataTip(AWV_INCREASE, STR_NULL),
 
				NWidget(NWID_BUTTON_ARROW, COLOUR_YELLOW, AIC_WIDGET_DECREASE), SetFill(0, 1), SetDataTip(AWV_DECREASE, STR_NULL),
 
				NWidget(NWID_BUTTON_ARROW, COLOUR_YELLOW, AIC_WIDGET_INCREASE), SetFill(0, 1), SetDataTip(AWV_INCREASE, STR_NULL),
 
				NWidget(NWID_SPACER), SetMinimalSize(6, 0),
 
				NWidget(WWT_TEXT, COLOUR_MAUVE, AIC_WIDGET_NUMBER), SetDataTip(STR_DIFFICULTY_LEVEL_SETTING_MAXIMUM_NO_COMPETITORS, STR_NULL), SetFill(true, false), SetPadding(1, 0, 0, 0),
 
				NWidget(WWT_TEXT, COLOUR_MAUVE, AIC_WIDGET_NUMBER), SetDataTip(STR_DIFFICULTY_LEVEL_SETTING_MAXIMUM_NO_COMPETITORS, STR_NULL), SetFill(1, 0), SetPadding(1, 0, 0, 0),
 
			EndContainer(),
 
		EndContainer(),
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(WWT_MATRIX, COLOUR_MAUVE, AIC_WIDGET_LIST), SetMinimalSize(288, 112), SetFill(true, false), SetDataTip(0x801, STR_AI_CONFIG_LIST_TOOLTIP),
 
			NWidget(WWT_MATRIX, COLOUR_MAUVE, AIC_WIDGET_LIST), SetMinimalSize(288, 112), SetFill(1, 0), SetDataTip(0x801, STR_AI_CONFIG_LIST_TOOLTIP),
 
			NWidget(WWT_SCROLLBAR, COLOUR_MAUVE, AIC_WIDGET_SCROLLBAR),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 9),
 
		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(5, 0, 5),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, AIC_WIDGET_CHANGE), SetFill(true, false), SetMinimalSize(93, 12), SetDataTip(STR_AI_CONFIG_CHANGE, STR_AI_CONFIG_CHANGE_TOOLTIP),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, AIC_WIDGET_CONFIGURE), SetFill(true, false), SetMinimalSize(93, 12), SetDataTip(STR_AI_CONFIG_CONFIGURE, STR_AI_CONFIG_CONFIGURE_TOOLTIP),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, AIC_WIDGET_CLOSE), SetFill(true, false), SetMinimalSize(93, 12), SetDataTip(STR_AI_SETTINGS_CLOSE, STR_NULL),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, AIC_WIDGET_CHANGE), SetFill(1, 0), SetMinimalSize(93, 12), SetDataTip(STR_AI_CONFIG_CHANGE, STR_AI_CONFIG_CHANGE_TOOLTIP),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, AIC_WIDGET_CONFIGURE), SetFill(1, 0), SetMinimalSize(93, 12), SetDataTip(STR_AI_CONFIG_CONFIGURE, STR_AI_CONFIG_CONFIGURE_TOOLTIP),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, AIC_WIDGET_CLOSE), SetFill(1, 0), SetMinimalSize(93, 12), SetDataTip(STR_AI_SETTINGS_CLOSE, STR_NULL),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 9),
 
	EndContainer(),
 
};
 

	
 
/* Window definition for the configure AI window. */
 
static const WindowDesc _ai_config_desc(
 
	WDP_CENTER, WDP_CENTER, 300, 172,
 
	WC_GAME_OPTIONS, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
 
	_nested_ai_config_widgets, lengthof(_nested_ai_config_widgets)
 
);
 

	
 
/**
 
 * Window to configure which AIs will start.
 
 */
 
struct AIConfigWindow : public Window {
 
	CompanyID selected_slot;
 
	bool clicked_button;
 
	bool clicked_increase;
 
	int timeout;
 
	int line_height;
 

	
 
	AIConfigWindow() : Window(),
 
		clicked_button(false),
 
		timeout(0)
 
	{
 
		this->InitNested(&_ai_config_desc); // Initializes 'this->line_height' as a side effect.
 
		this->selected_slot = INVALID_COMPANY;
 
		NWidgetCore *nwi = this->GetWidget<NWidgetCore>(AIC_WIDGET_LIST);
 
		this->vscroll.SetCapacity(nwi->current_y / this->line_height);
 
		this->vscroll.SetCount(MAX_COMPANIES);
 
		nwi->widget_data = (this->vscroll.GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START);
 
		this->OnInvalidateData(0);
 
	}
 

	
 
	~AIConfigWindow()
 
	{
 
		DeleteWindowByClass(WC_AI_LIST);
 
		DeleteWindowByClass(WC_AI_SETTINGS);
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		switch (widget) {
 
			case AIC_WIDGET_NUMBER:
 
				SetDParam(0, _settings_newgame.difficulty.max_no_competitors);
 
				break;
 
		}
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		switch (widget) {
 
			case AIC_WIDGET_LIST:
 
				this->line_height = FONT_HEIGHT_NORMAL + WD_MATRIX_TOP + WD_MATRIX_BOTTOM;
 
				size->height = GB(this->GetWidget<NWidgetCore>(widget)->widget_data, MAT_ROW_START, MAT_ROW_BITS) * this->line_height;
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case AIC_WIDGET_LIST: {
 
				int y = r.top;
 
				for (int i = this->vscroll.GetPosition(); this->vscroll.IsVisible(i) && i < MAX_COMPANIES; i++) {
 
					StringID text;
 

	
 
					if (AIConfig::GetConfig((CompanyID)i)->GetInfo() != NULL) {
 
						SetDParamStr(0, AIConfig::GetConfig((CompanyID)i)->GetInfo()->GetName());
 
						text = STR_JUST_RAW_STRING;
 
					} else if (i == 0) {
 
						text = STR_AI_CONFIG_HUMAN_PLAYER;
 
					} else {
 
						text = STR_AI_CONFIG_RANDOM_AI;
 
					}
 
					DrawString(r.left + 10, r.right - 10, y + WD_MATRIX_TOP, text,
 
							(this->selected_slot == i) ? TC_WHITE : ((i > _settings_newgame.difficulty.max_no_competitors || i == 0) ? TC_SILVER : TC_ORANGE));
 
					y += this->line_height;
 
				}
 
				break;
 
			}
 
		}
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case AIC_WIDGET_DECREASE:
 
			case AIC_WIDGET_INCREASE: {
 
				int new_value;
 
				if (widget == AIC_WIDGET_DECREASE) {
 
					new_value = max(0, _settings_newgame.difficulty.max_no_competitors - 1);
 
				} else {
 
					new_value = min(MAX_COMPANIES - 1, _settings_newgame.difficulty.max_no_competitors + 1);
 
				}
 
				IConsoleSetSetting("difficulty.max_no_competitors", new_value);
 
				this->InvalidateData();
 
				break;
 
			}
 

	
 
			case AIC_WIDGET_LIST: { // Select a slot
 
				uint slot = (pt.y - this->GetWidget<NWidgetBase>(widget)->pos_y) / this->line_height + this->vscroll.GetPosition();
 

	
 
				if (slot == 0 || slot > _settings_newgame.difficulty.max_no_competitors) slot = INVALID_COMPANY;
 
				this->selected_slot = (CompanyID)slot;
 
				this->InvalidateData();
 
				break;
 
			}
 

	
 
			case AIC_WIDGET_CHANGE:  // choose other AI
 
				ShowAIListWindow((CompanyID)this->selected_slot);
 
				break;
 

	
 
			case AIC_WIDGET_CONFIGURE: // change the settings for an AI
 
				ShowAISettingsWindow((CompanyID)this->selected_slot);
 
				break;
 

	
 
			case AIC_WIDGET_CLOSE:
 
				delete this;
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnDoubleClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case AIC_WIDGET_LIST:
 
				this->OnClick(pt, widget);
 
				if (this->selected_slot != INVALID_COMPANY) ShowAIListWindow((CompanyID)this->selected_slot);
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnInvalidateData(int data)
 
	{
 
		this->SetWidgetDisabledState(AIC_WIDGET_DECREASE, _settings_newgame.difficulty.max_no_competitors == 0);
 
		this->SetWidgetDisabledState(AIC_WIDGET_INCREASE, _settings_newgame.difficulty.max_no_competitors == MAX_COMPANIES - 1);
 
		this->SetWidgetDisabledState(AIC_WIDGET_CHANGE, this->selected_slot == INVALID_COMPANY);
 
		this->SetWidgetDisabledState(AIC_WIDGET_CONFIGURE, this->selected_slot == INVALID_COMPANY);
 
	}
 

	
 
	virtual void OnTick()
 
	{
 
		if (--this->timeout == 0) {
 
			this->clicked_button = false;
 
			this->SetDirty();
 
		}
 
	}
 
};
 

	
 
void ShowAIConfigWindow()
 
{
 
	DeleteWindowById(WC_GAME_OPTIONS, 0);
 
	new AIConfigWindow();
 
}
 

	
 
/** Enum referring to the widgets of the AI debug window */
 
enum AIDebugWindowWidgets {
 
	AID_WIDGET_CLOSEBOX = 0,
 
	AID_WIDGET_CAPTION,
 
	AID_WIDGET_STICKY,
 
	AID_WIDGET_VIEW,
 
	AID_WIDGET_NAME_TEXT,
 
	AID_WIDGET_RELOAD_TOGGLE,
 
	AID_WIDGET_LOG_PANEL,
 
	AID_WIDGET_SCROLLBAR,
 
	AID_WIDGET_COMPANY_BUTTON_START,
 
	AID_WIDGET_COMPANY_BUTTON_END = AID_WIDGET_COMPANY_BUTTON_START + 14,
 
	AID_WIDGET_RESIZE,
 
};
 

	
 
/**
 
 * Window with everything an AI prints via AILog.
 
 */
 
struct AIDebugWindow : public Window {
 
	static const int top_offset;    ///< Offset of the text at the top of the #AID_WIDGET_LOG_PANEL.
 
	static const int bottom_offset; ///< Offset of the text at the bottom of the #AID_WIDGET_LOG_PANEL.
 

	
 
	static CompanyID ai_debug_company;
 
	int redraw_timer;
 
	int last_vscroll_pos;
 
	bool autoscroll;
 

	
 
	AIDebugWindow(const WindowDesc *desc, WindowNumber number) : Window()
 
	{
 
		this->InitNested(desc, number);
 
		/* Disable the companies who are not active or not an AI */
 
		for (CompanyID i = COMPANY_FIRST; i < MAX_COMPANIES; i++) {
 
			this->SetWidgetDisabledState(i + AID_WIDGET_COMPANY_BUTTON_START, !Company::IsValidAiID(i));
 
		}
 
		this->DisableWidget(AID_WIDGET_RELOAD_TOGGLE);
 

	
 
		this->last_vscroll_pos = 0;
 
		this->autoscroll = true;
 

	
 
		if (ai_debug_company != INVALID_COMPANY) this->LowerWidget(ai_debug_company + AID_WIDGET_COMPANY_BUTTON_START);
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		if (widget == AID_WIDGET_LOG_PANEL) {
 
			resize->height = FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL;
 
			size->height = 14 * resize->height + this->top_offset + this->bottom_offset;
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		/* Check if the currently selected company is still active. */
 
		if (ai_debug_company == INVALID_COMPANY || !Company::IsValidAiID(ai_debug_company)) {
 
			if (ai_debug_company != INVALID_COMPANY) {
 
				/* Raise and disable the widget for the previous selection. */
 
				this->RaiseWidget(ai_debug_company + AID_WIDGET_COMPANY_BUTTON_START);
 
				this->DisableWidget(ai_debug_company + AID_WIDGET_COMPANY_BUTTON_START);
 

	
 
				ai_debug_company = INVALID_COMPANY;
 
			}
 

	
 
			const Company *c;
 
			FOR_ALL_COMPANIES(c) {
 
				if (c->is_ai) {
 
					/* Lower the widget corresponding to this company. */
 
					this->LowerWidget(c->index + AID_WIDGET_COMPANY_BUTTON_START);
 

	
 
					ai_debug_company = c->index;
 
					break;
 
				}
 
			}
 
		}
 

	
 
		/* Update "Reload AI" button */
 
		this->SetWidgetDisabledState(AID_WIDGET_RELOAD_TOGGLE, ai_debug_company == INVALID_COMPANY);
 

	
 
		/* Draw standard stuff */
 
		this->DrawWidgets();
 

	
 
		/* If there are no active companies, don't display anything else. */
 
		if (ai_debug_company == INVALID_COMPANY) return;
 

	
 
		/* Paint the company icons */
 
		for (CompanyID i = COMPANY_FIRST; i < MAX_COMPANIES; i++) {
 
			/* Background is grey by default, will be changed to red for dead AIs */
 
			this->GetWidget<NWidgetCore>(i + AID_WIDGET_COMPANY_BUTTON_START)->colour = COLOUR_GREY;
 

	
 
			const Company *c = Company::GetIfValid(i);
 
			if (c == NULL || !c->is_ai) {
 
				/* Check if we have the company as an active company */
 
				if (!this->IsWidgetDisabled(i + AID_WIDGET_COMPANY_BUTTON_START)) {
 
					/* Bah, company gone :( */
 
					this->DisableWidget(i + AID_WIDGET_COMPANY_BUTTON_START);
 

	
 
					/* We need a repaint */
 
					this->SetDirty();
 
				}
 
				continue;
 
			}
 

	
 
			/* Mark dead AIs by red background */
 
			if (c->ai_instance->IsDead()) {
 
				this->GetWidget<NWidgetCore>(i + AID_WIDGET_COMPANY_BUTTON_START)->colour = COLOUR_RED;
 
			}
 

	
 
			/* Check if we have the company marked as inactive */
 
			if (this->IsWidgetDisabled(i + AID_WIDGET_COMPANY_BUTTON_START)) {
 
				/* New AI! Yippie :p */
 
				this->EnableWidget(i + AID_WIDGET_COMPANY_BUTTON_START);
 

	
 
				/* We need a repaint */
 
				this->SetDirty();
 
			}
 

	
 
			byte offset = (i == ai_debug_company) ? 1 : 0;
 
			DrawCompanyIcon(i, this->GetWidget<NWidgetBase>(AID_WIDGET_COMPANY_BUTTON_START + i)->pos_x + 11 + offset, this->GetWidget<NWidgetBase>(AID_WIDGET_COMPANY_BUTTON_START + i)->pos_y + 2 + offset);
 
		}
 

	
 
		CompanyID old_company = _current_company;
 
		_current_company = ai_debug_company;
 
		AILog::LogData *log = (AILog::LogData *)AIObject::GetLogPointer();
 
		_current_company = old_company;
 

	
 
		int scroll_count = (log == NULL) ? 0 : log->used;
 
		if (this->vscroll.GetCount() != scroll_count) {
 
			this->vscroll.SetCount(scroll_count);
 

	
 
			/* We need a repaint */
 
			this->SetWidgetDirty(AID_WIDGET_SCROLLBAR);
 
		}
 

	
 
		if (log == NULL) return;
 

	
 
		/* Detect when the user scrolls the window. Enable autoscroll when the
 
		 * bottom-most line becomes visible. */
 
		if (this->last_vscroll_pos != this->vscroll.GetPosition()) {
 
			this->autoscroll = this->vscroll.GetPosition() >= log->used - this->vscroll.GetCapacity();
 
		}
 
		if (this->autoscroll) {
 
			int scroll_pos = max(0, log->used - this->vscroll.GetCapacity());
 
			if (scroll_pos != this->vscroll.GetPosition()) {
 
				this->vscroll.SetPosition(scroll_pos);
 

	
 
				/* We need a repaint */
 
				this->SetWidgetDirty(AID_WIDGET_SCROLLBAR);
 
				this->SetWidgetDirty(AID_WIDGET_LOG_PANEL);
 
			}
 
		}
 
		this->last_vscroll_pos = this->vscroll.GetPosition();
 

	
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		if (ai_debug_company == INVALID_COMPANY) return;
 

	
 
		switch (widget) {
 
			case AID_WIDGET_NAME_TEXT: {
 
				/* Draw the AI name */
 
				AIInfo *info = Company::Get(ai_debug_company)->ai_info;
 
				assert(info != NULL);
 
				char name[1024];
 
				snprintf(name, sizeof(name), "%s (v%d)", info->GetName(), info->GetVersion());
 
				DrawString(r.left + 7, r.right - 7, 47, name, TC_BLACK, SA_CENTER);
 
				break;
 
			}
 
			case AID_WIDGET_LOG_PANEL: {
 
				CompanyID old_company = _current_company;
 
				_current_company = ai_debug_company;
 
				AILog::LogData *log = (AILog::LogData *)AIObject::GetLogPointer();
 
				_current_company = old_company;
 
				if (log == NULL) return;
 

	
 
				int y = this->top_offset;
 
				for (int i = this->vscroll.GetPosition(); this->vscroll.IsVisible(i) && i < log->used; i++) {
 
					uint pos = (i + log->pos + 1 - log->used + log->count) % log->count;
 
					if (log->lines[pos] == NULL) break;
 

	
 
					TextColour colour;
 
					switch (log->type[pos]) {
 
						case AILog::LOG_SQ_INFO:  colour = TC_BLACK;  break;
 
						case AILog::LOG_SQ_ERROR: colour = TC_RED;    break;
 
						case AILog::LOG_INFO:     colour = TC_BLACK;  break;
 
						case AILog::LOG_WARNING:  colour = TC_YELLOW; break;
 
						case AILog::LOG_ERROR:    colour = TC_RED;    break;
 
						default:                  colour = TC_BLACK;  break;
 
					}
 

	
 
					DrawString(r.left + 7, r.right - 7, r.top + y, log->lines[pos], colour, SA_LEFT | SA_FORCE);
 
					y += this->resize.step_height;
 
				}
 
				break;
 
			}
 
		}
 
	}
 

	
 
	void ChangeToAI(CompanyID show_ai)
 
	{
 
		this->RaiseWidget(ai_debug_company + AID_WIDGET_COMPANY_BUTTON_START);
 
		ai_debug_company = show_ai;
 

	
 
		CompanyID old_company = _current_company;
 
		_current_company = ai_debug_company;
 
		AILog::LogData *log = (AILog::LogData *)AIObject::GetLogPointer();
 
		_current_company = old_company;
 
		this->vscroll.SetCount((log == NULL) ? 0 : log->used);
 

	
 
		this->LowerWidget(ai_debug_company + AID_WIDGET_COMPANY_BUTTON_START);
 
		this->autoscroll = true;
 
		this->last_vscroll_pos = this->vscroll.GetPosition();
 
		this->SetDirty();
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		/* Check which button is clicked */
 
		if (IsInsideMM(widget, AID_WIDGET_COMPANY_BUTTON_START, AID_WIDGET_COMPANY_BUTTON_END + 1)) {
 
			/* Is it no on disable? */
 
			if (!this->IsWidgetDisabled(widget)) {
 
				ChangeToAI((CompanyID)(widget - AID_WIDGET_COMPANY_BUTTON_START));
 
			}
 
		}
 
		if (widget == AID_WIDGET_RELOAD_TOGGLE && !this->IsWidgetDisabled(widget)) {
 
			/* First kill the company of the AI, then start a new one. This should start the current AI again */
 
			DoCommandP(0, 2, ai_debug_company, CMD_COMPANY_CTRL);
 
			DoCommandP(0, 1, ai_debug_company, CMD_COMPANY_CTRL);
 
		}
 
	}
 

	
 
	virtual void OnTimeout()
 
	{
 
		this->RaiseWidget(AID_WIDGET_RELOAD_TOGGLE);
 
		this->SetDirty();
 
	}
 

	
 
	virtual void OnInvalidateData(int data = 0)
 
	{
 
		if (data == -1 || ai_debug_company == data) this->SetDirty();
 
	}
 

	
 
	virtual void OnResize()
 
	{
 
		this->vscroll.SetCapacity(this->GetWidget<NWidgetBase>(AID_WIDGET_LOG_PANEL)->current_y / this->resize.step_height);
 
	}
 
};
 

	
 
const int AIDebugWindow::top_offset = WD_FRAMERECT_TOP + 2;
 
const int AIDebugWindow::bottom_offset = WD_FRAMERECT_BOTTOM;
 
CompanyID AIDebugWindow::ai_debug_company = INVALID_COMPANY;
 

	
 
static const NWidgetPart _nested_ai_debug_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, AID_WIDGET_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, AID_WIDGET_CAPTION), SetDataTip(STR_AI_DEBUG, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_STICKYBOX, COLOUR_GREY, AID_WIDGET_STICKY),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, AID_WIDGET_VIEW),
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0),
 
			NWidget(WWT_PANEL, COLOUR_GREY, AID_WIDGET_COMPANY_BUTTON_START), SetMinimalSize(37, 13), SetDataTip(0x0, STR_GRAPH_KEY_COMPANY_SELECTION_TOOLTIP),
 
			EndContainer(),
 
			NWidget(WWT_PANEL, COLOUR_GREY, AID_WIDGET_COMPANY_BUTTON_START + 1), SetMinimalSize(37, 13), SetDataTip(0x0, STR_GRAPH_KEY_COMPANY_SELECTION_TOOLTIP),
 
			EndContainer(),
 
			NWidget(WWT_PANEL, COLOUR_GREY, AID_WIDGET_COMPANY_BUTTON_START + 2), SetMinimalSize(37, 13), SetDataTip(0x0, STR_GRAPH_KEY_COMPANY_SELECTION_TOOLTIP),
 
			EndContainer(),
 
			NWidget(WWT_PANEL, COLOUR_GREY, AID_WIDGET_COMPANY_BUTTON_START + 3), SetMinimalSize(37, 13), SetDataTip(0x0, STR_GRAPH_KEY_COMPANY_SELECTION_TOOLTIP),
 
			EndContainer(),
 
			NWidget(WWT_PANEL, COLOUR_GREY, AID_WIDGET_COMPANY_BUTTON_START + 4), SetMinimalSize(37, 13), SetDataTip(0x0, STR_GRAPH_KEY_COMPANY_SELECTION_TOOLTIP),
 
			EndContainer(),
 
			NWidget(WWT_PANEL, COLOUR_GREY, AID_WIDGET_COMPANY_BUTTON_START + 5), SetMinimalSize(37, 13), SetDataTip(0x0, STR_GRAPH_KEY_COMPANY_SELECTION_TOOLTIP),
 
			EndContainer(),
 
			NWidget(WWT_PANEL, COLOUR_GREY, AID_WIDGET_COMPANY_BUTTON_START + 6), SetMinimalSize(37, 13), SetDataTip(0x0, STR_GRAPH_KEY_COMPANY_SELECTION_TOOLTIP),
 
			EndContainer(),
 
			NWidget(WWT_PANEL, COLOUR_GREY, AID_WIDGET_COMPANY_BUTTON_START + 7), SetMinimalSize(37, 13), SetDataTip(0x0, STR_GRAPH_KEY_COMPANY_SELECTION_TOOLTIP),
 
			EndContainer(),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0), SetResize(1, 0),
 
		EndContainer(),
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0),
 
			NWidget(WWT_PANEL, COLOUR_GREY, AID_WIDGET_COMPANY_BUTTON_START + 8), SetMinimalSize(37, 13), SetDataTip(0x0, STR_GRAPH_KEY_COMPANY_SELECTION_TOOLTIP),
 
			EndContainer(),
 
			NWidget(WWT_PANEL, COLOUR_GREY, AID_WIDGET_COMPANY_BUTTON_START + 9), SetMinimalSize(37, 13), SetDataTip(0x0, STR_GRAPH_KEY_COMPANY_SELECTION_TOOLTIP),
 
			EndContainer(),
 
			NWidget(WWT_PANEL, COLOUR_GREY, AID_WIDGET_COMPANY_BUTTON_START + 10), SetMinimalSize(37, 13), SetDataTip(0x0, STR_GRAPH_KEY_COMPANY_SELECTION_TOOLTIP),
 
			EndContainer(),
 
			NWidget(WWT_PANEL, COLOUR_GREY, AID_WIDGET_COMPANY_BUTTON_START + 11), SetMinimalSize(37, 13), SetDataTip(0x0, STR_GRAPH_KEY_COMPANY_SELECTION_TOOLTIP),
 
			EndContainer(),
 
			NWidget(WWT_PANEL, COLOUR_GREY, AID_WIDGET_COMPANY_BUTTON_START + 12), SetMinimalSize(37, 13), SetDataTip(0x0, STR_GRAPH_KEY_COMPANY_SELECTION_TOOLTIP),
 
			EndContainer(),
 
			NWidget(WWT_PANEL, COLOUR_GREY, AID_WIDGET_COMPANY_BUTTON_START + 13), SetMinimalSize(37, 13), SetDataTip(0x0, STR_GRAPH_KEY_COMPANY_SELECTION_TOOLTIP),
 
			EndContainer(),
 
			NWidget(WWT_PANEL, COLOUR_GREY, AID_WIDGET_COMPANY_BUTTON_START + 14), SetMinimalSize(37, 13), SetDataTip(0x0, STR_GRAPH_KEY_COMPANY_SELECTION_TOOLTIP),
 
			EndContainer(),
 
			NWidget(NWID_SPACER), SetMinimalSize(39, 0), SetResize(1, 0),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 1), SetResize(1, 0),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_PANEL, COLOUR_GREY, AID_WIDGET_NAME_TEXT), SetMinimalSize(150, 20), SetResize(1, 0), SetDataTip(0x0, STR_AI_DEBUG_NAME_TOOLTIP),
 
		EndContainer(),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, AID_WIDGET_RELOAD_TOGGLE), SetMinimalSize(149, 20), SetDataTip(STR_AI_DEBUG_RELOAD, STR_AI_DEBUG_RELOAD_TOOLTIP),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_PANEL, COLOUR_GREY, AID_WIDGET_LOG_PANEL), SetMinimalSize(287, 180), SetResize(1, 1),
 
		EndContainer(),
 
		NWidget(NWID_VERTICAL),
 
			NWidget(WWT_SCROLLBAR, COLOUR_GREY, AID_WIDGET_SCROLLBAR),
 
			NWidget(WWT_RESIZEBOX, COLOUR_GREY, AID_WIDGET_RESIZE),
 
		EndContainer(),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _ai_debug_desc(
 
	WDP_AUTO, WDP_AUTO, 299, 241,
 
	WC_AI_DEBUG, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_STICKY_BUTTON | WDF_RESIZABLE,
 
	_nested_ai_debug_widgets, lengthof(_nested_ai_debug_widgets)
 
);
 

	
 
void ShowAIDebugWindow(CompanyID show_company)
 
{
 
	if (!_networking || _network_server) {
 
		AIDebugWindow *w = (AIDebugWindow *)BringWindowToFrontById(WC_AI_DEBUG, 0);
 
		if (w == NULL) w = new AIDebugWindow(&_ai_debug_desc, 0);
 
		if (show_company != INVALID_COMPANY) w->ChangeToAI(show_company);
 
	} else {
 
		ShowErrorMessage(STR_ERROR_AI_DEBUG_SERVER_ONLY, INVALID_STRING_ID, 0, 0);
 
	}
 
}
src/airport_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file airport_gui.cpp The GUI for airports. */
 

	
 
#include "stdafx.h"
 
#include "window_gui.h"
 
#include "station_gui.h"
 
#include "terraform_gui.h"
 
#include "airport.h"
 
#include "sound_func.h"
 
#include "window_func.h"
 
#include "strings_func.h"
 
#include "viewport_func.h"
 
#include "gfx_func.h"
 
#include "company_func.h"
 
#include "tilehighlight_func.h"
 
#include "company_base.h"
 

	
 
#include "table/sprites.h"
 
#include "table/strings.h"
 

	
 
static byte _selected_airport_type;
 

	
 
static void ShowBuildAirportPicker(Window *parent);
 

	
 

	
 
void CcBuildAirport(bool success, TileIndex tile, uint32 p1, uint32 p2)
 
{
 
	if (success) {
 
		SndPlayTileFx(SND_1F_SPLAT, tile);
 
		if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace();
 
	}
 
}
 

	
 
static void PlaceAirport(TileIndex tile)
 
{
 
	uint32 p2 = _ctrl_pressed;
 
	SB(p2, 16, 16, INVALID_STATION); // no station to join
 

	
 
	CommandContainer cmdcont = { tile, _selected_airport_type, p2, CMD_BUILD_AIRPORT | CMD_MSG(STR_ERROR_CAN_T_BUILD_AIRPORT_HERE), CcBuildAirport, "" };
 
	ShowSelectStationIfNeeded(cmdcont, TileArea(tile, _thd.size.x / TILE_SIZE, _thd.size.y / TILE_SIZE));
 
}
 

	
 
/** Widget number of the airport build window. */
 
enum {
 
	ATW_CLOSEBOX,
 
	ATW_CAPTION,
 
	ATW_STICKYBOX,
 
	ATW_AIRPORT,
 
	ATW_DEMOLISH,
 
	ATW_SPACER,
 
};
 

	
 

	
 
static void BuildAirClick_Airport(Window *w)
 
{
 
	if (HandlePlacePushButton(w, ATW_AIRPORT, SPR_CURSOR_AIRPORT, HT_RECT, PlaceAirport)) ShowBuildAirportPicker(w);
 
}
 

	
 
static void BuildAirClick_Demolish(Window *w)
 
{
 
	HandlePlacePushButton(w, ATW_DEMOLISH, ANIMCURSOR_DEMOLISH, HT_RECT, PlaceProc_DemolishArea);
 
}
 

	
 

	
 
typedef void OnButtonClick(Window *w);
 
static OnButtonClick * const _build_air_button_proc[] = {
 
	BuildAirClick_Airport,
 
	BuildAirClick_Demolish,
 
};
 

	
 
struct BuildAirToolbarWindow : Window {
 
	BuildAirToolbarWindow(const WindowDesc *desc, WindowNumber window_number) : Window()
 
	{
 
		this->InitNested(desc, window_number);
 
		if (_settings_client.gui.link_terraform_toolbar) ShowTerraformToolbar(this);
 
	}
 

	
 
	~BuildAirToolbarWindow()
 
	{
 
		if (_settings_client.gui.link_terraform_toolbar) DeleteWindowById(WC_SCEN_LAND_GEN, 0, false);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		if (!IsInsideBS(widget, ATW_AIRPORT, lengthof(_build_air_button_proc))) return;
 

	
 
		_build_air_button_proc[widget - ATW_AIRPORT](this);
 
	}
 

	
 

	
 
	virtual EventState OnKeyPress(uint16 key, uint16 keycode)
 
	{
 
		switch (keycode) {
 
			case '1': BuildAirClick_Airport(this); break;
 
			case '2': BuildAirClick_Demolish(this); break;
 
			default: return ES_NOT_HANDLED;
 
		}
 
		return ES_HANDLED;
 
	}
 

	
 
	virtual void OnPlaceObject(Point pt, TileIndex tile)
 
	{
 
		_place_proc(tile);
 
	}
 

	
 
	virtual void OnPlaceDrag(ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, Point pt)
 
	{
 
		VpSelectTilesWithMethod(pt.x, pt.y, select_method);
 
	}
 

	
 
	virtual void OnPlaceMouseUp(ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, Point pt, TileIndex start_tile, TileIndex end_tile)
 
	{
 
		if (pt.x != -1 && select_proc == DDSP_DEMOLISH_AREA) {
 
			GUIPlaceProcDragXY(select_proc, start_tile, end_tile);
 
		}
 
	}
 

	
 
	virtual void OnPlaceObjectAbort()
 
	{
 
		this->RaiseButtons();
 

	
 
		DeleteWindowById(WC_BUILD_STATION, TRANSPORT_AIR);
 
		DeleteWindowById(WC_SELECT_STATION, 0);
 
	}
 
};
 

	
 
static const NWidgetPart _nested_air_toolbar_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN, ATW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, ATW_CAPTION), SetDataTip(STR_TOOLBAR_AIRCRAFT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_STICKYBOX, COLOUR_DARK_GREEN, ATW_STICKYBOX),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, ATW_AIRPORT), SetFill(false, true), SetMinimalSize(42, 22), SetDataTip(SPR_IMG_AIRPORT, STR_TOOLBAR_AIRCRAFT_BUILD_AIRPORT_TOOLTIP),
 
		NWidget(WWT_PANEL, COLOUR_DARK_GREEN, ATW_SPACER), SetMinimalSize(4, 22), SetFill(true, true), EndContainer(),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, ATW_DEMOLISH), SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, ATW_AIRPORT), SetFill(0, 1), SetMinimalSize(42, 22), SetDataTip(SPR_IMG_AIRPORT, STR_TOOLBAR_AIRCRAFT_BUILD_AIRPORT_TOOLTIP),
 
		NWidget(WWT_PANEL, COLOUR_DARK_GREEN, ATW_SPACER), SetMinimalSize(4, 22), SetFill(1, 1), EndContainer(),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, ATW_DEMOLISH), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _air_toolbar_desc(
 
	WDP_ALIGN_TBR, 22, 64, 36,
 
	WC_BUILD_TOOLBAR, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_STICKY_BUTTON | WDF_CONSTRUCTION,
 
	_nested_air_toolbar_widgets, lengthof(_nested_air_toolbar_widgets)
 
);
 

	
 
void ShowBuildAirToolbar()
 
{
 
	if (!Company::IsValidID(_local_company)) return;
 

	
 
	DeleteWindowByClass(WC_BUILD_TOOLBAR);
 
	AllocateWindowDescFront<BuildAirToolbarWindow>(&_air_toolbar_desc, TRANSPORT_AIR);
 
}
 

	
 
/** Airport widgets in the airport picker window. */
 
enum AirportPickerWidgets {
 
	BAW_CLOSEBOX,
 
	BAW_CAPTION,
 
	/* Panels and labels. */
 
	BAW_SMALL_AIRPORTS_PANEL,
 
	BAW_SMALL_AIRPORTS_LABEL,
 
	BAW_LARGE_AIRPORTS_PANEL,
 
	BAW_LARGE_AIRPORTS_LABEL,
 
	BAW_HUB_AIRPORTS_PANEL,
 
	BAW_HUB_AIRPORTS_LABEL,
 
	BAW_HELIPORTS_PANEL,
 
	BAW_HELIPORTS_LABEL,
 
	BAW_BOTTOMPANEL,
 
	/* Airport selection buttons. */
 
	BAW_SMALL_AIRPORT,
 
	BAW_CITY_AIRPORT,
 
	BAW_HELIPORT,
 
	BAW_METRO_AIRPORT,
 
	BAW_INTERNATIONAL_AIRPORT,
 
	BAW_COMMUTER_AIRPORT,
 
	BAW_HELIDEPOT,
 
	BAW_INTERCONTINENTAL_AIRPORT,
 
	BAW_HELISTATION,
 
	/* Coverage. */
 
	BAW_BTN_DONTHILIGHT,
 
	BAW_BTN_DOHILIGHT,
 
	BAW_COVERAGE_LABEL,
 

	
 
	BAW_LAST_AIRPORT = BAW_HELISTATION,
 
	BAW_AIRPORT_COUNT = BAW_LAST_AIRPORT - BAW_SMALL_AIRPORT + 1,
 
};
 

	
 
class AirportPickerWindow : public PickerWindowBase {
 
public:
 
	AirportPickerWindow(const WindowDesc *desc, Window *parent) : PickerWindowBase(parent)
 
	{
 
		this->InitNested(desc, TRANSPORT_AIR);
 
		this->SetWidgetLoweredState(BAW_BTN_DONTHILIGHT, !_settings_client.gui.station_show_coverage);
 
		this->SetWidgetLoweredState(BAW_BTN_DOHILIGHT, _settings_client.gui.station_show_coverage);
 
		this->OnInvalidateData();
 
		this->SelectOtherAirport(_selected_airport_type);
 
	}
 

	
 
	virtual ~AirportPickerWindow()
 
	{
 
		DeleteWindowById(WC_SELECT_STATION, 0);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 

	
 
		const AirportFTAClass *airport = GetAirport(_selected_airport_type);
 
		int rad = _settings_game.station.modified_catchment ? airport->catchment : (uint)CA_UNMODIFIED;
 

	
 
		uint16 top = this->GetWidget<NWidgetBase>(BAW_BTN_DOHILIGHT)->pos_y + this->GetWidget<NWidgetBase>(BAW_BTN_DOHILIGHT)->current_y + WD_PAR_VSEP_NORMAL;
 
		NWidgetBase *panel_nwi = this->GetWidget<NWidgetBase>(BAW_BOTTOMPANEL);
 
		int right = panel_nwi->pos_x +  panel_nwi->current_x;
 
		int bottom = panel_nwi->pos_y +  panel_nwi->current_y;
 
		/* only show the station (airport) noise, if the noise option is activated */
 
		if (_settings_game.economy.station_noise_level) {
 
			/* show the noise of the selected airport */
 
			SetDParam(0, airport->noise_level);
 
			DrawString(panel_nwi->pos_x + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, STR_STATION_BUILD_NOISE);
 
			top += FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL;
 
		}
 

	
 
		/* strings such as 'Size' and 'Coverage Area' */
 
		top = DrawStationCoverageAreaText(panel_nwi->pos_x + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, SCT_ALL, rad, false) + WD_PAR_VSEP_NORMAL;
 
		top = DrawStationCoverageAreaText(panel_nwi->pos_x + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, SCT_ALL, rad, true) + WD_PAR_VSEP_NORMAL;
 
		/* Resize background if the text is not equally long as the window. */
 
		if (top > bottom || (top < bottom && panel_nwi->current_y > panel_nwi->smallest_y)) {
 
			ResizeWindow(this, 0, top - bottom);
 
		}
 
	}
 

	
 
	void SelectOtherAirport(byte airport_id)
 
	{
 
		this->RaiseWidget(_selected_airport_type + BAW_SMALL_AIRPORT);
 
		_selected_airport_type = airport_id;
 
		this->LowerWidget(airport_id + BAW_SMALL_AIRPORT);
 

	
 
		const AirportFTAClass *airport = GetAirport(airport_id);
 
		SetTileSelectSize(airport->size_x, airport->size_y);
 

	
 
		int rad = _settings_game.station.modified_catchment ? airport->catchment : (uint)CA_UNMODIFIED;
 
		if (_settings_client.gui.station_show_coverage) SetTileSelectBigSize(-rad, -rad, 2 * rad, 2 * rad);
 

	
 
		this->SetDirty();
 
	}
 

	
 
	virtual void OnInvalidateData(int data = 0)
 
	{
 
		if (!GetAirport(_selected_airport_type)->IsAvailable()) {
 
			for (int i = 0; i < BAW_AIRPORT_COUNT; i++) {
 
				if (GetAirport(i)->IsAvailable()) {
 
					this->SelectOtherAirport(i);
 
					break;
 
				}
 
			}
 
		}
 
		for (int i = 0; i < BAW_AIRPORT_COUNT; i++) {
 
			this->SetWidgetDisabledState(i + BAW_SMALL_AIRPORT, !GetAirport(i)->IsAvailable());
 
		}
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case BAW_SMALL_AIRPORT: case BAW_CITY_AIRPORT: case BAW_HELIPORT: case BAW_METRO_AIRPORT:
 
			case BAW_INTERNATIONAL_AIRPORT: case BAW_COMMUTER_AIRPORT: case BAW_HELIDEPOT:
 
			case BAW_INTERCONTINENTAL_AIRPORT: case BAW_HELISTATION:
 
				this->SelectOtherAirport(widget - BAW_SMALL_AIRPORT);
 
				SndPlayFx(SND_15_BEEP);
 
				DeleteWindowById(WC_SELECT_STATION, 0);
 
				break;
 

	
 
			case BAW_BTN_DONTHILIGHT: case BAW_BTN_DOHILIGHT:
 
				_settings_client.gui.station_show_coverage = (widget != BAW_BTN_DONTHILIGHT);
 
				this->SetWidgetLoweredState(BAW_BTN_DONTHILIGHT, !_settings_client.gui.station_show_coverage);
 
				this->SetWidgetLoweredState(BAW_BTN_DOHILIGHT, _settings_client.gui.station_show_coverage);
 
				SndPlayFx(SND_15_BEEP);
 
				this->SelectOtherAirport(_selected_airport_type);
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnTick()
 
	{
 
		CheckRedrawStationCoverage(this);
 
	}
 
};
 

	
 
static const NWidgetPart _nested_build_airport_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN, BAW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, BAW_CAPTION), SetDataTip(STR_STATION_BUILD_AIRPORT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	/* Small airports. */
 
	NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BAW_SMALL_AIRPORTS_PANEL),
 
		NWidget(WWT_LABEL, COLOUR_DARK_GREEN, BAW_SMALL_AIRPORTS_LABEL), SetMinimalSize(148, 14), SetFill(true, false), SetDataTip(STR_STATION_BUILD_AIRPORT_SMALL_AIRPORTS, STR_NULL),
 
		NWidget(WWT_LABEL, COLOUR_DARK_GREEN, BAW_SMALL_AIRPORTS_LABEL), SetMinimalSize(148, 14), SetFill(1, 0), SetDataTip(STR_STATION_BUILD_AIRPORT_SMALL_AIRPORTS, STR_NULL),
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0),
 
			NWidget(NWID_VERTICAL),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, BAW_SMALL_AIRPORT), SetMinimalSize(144, 12), SetFill(true, false),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, BAW_SMALL_AIRPORT), SetMinimalSize(144, 12), SetFill(1, 0),
 
									SetDataTip(STR_STATION_BUILD_AIRPORT_SMALL_AIRPORT, STR_STATION_BUILD_AIRPORT_TOOLTIP),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, BAW_COMMUTER_AIRPORT), SetMinimalSize(144, 12), SetFill(true, false),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, BAW_COMMUTER_AIRPORT), SetMinimalSize(144, 12), SetFill(1, 0),
 
									SetDataTip(STR_STATION_BUILD_AIRPORT_COMMUTER_AIRPORT, STR_STATION_BUILD_AIRPORT_TOOLTIP),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 1), SetFill(true, false),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 1), SetFill(1, 0),
 
			EndContainer(),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0),
 
		EndContainer(),
 
	EndContainer(),
 
	/* Large airports. */
 
	NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BAW_LARGE_AIRPORTS_PANEL),
 
		NWidget(WWT_LABEL, COLOUR_DARK_GREEN, BAW_LARGE_AIRPORTS_LABEL), SetMinimalSize(148, 14), SetFill(true, false), SetDataTip(STR_STATION_BUILD_AIRPORT_LARGE_AIRPORTS, STR_NULL),
 
		NWidget(WWT_LABEL, COLOUR_DARK_GREEN, BAW_LARGE_AIRPORTS_LABEL), SetMinimalSize(148, 14), SetFill(1, 0), SetDataTip(STR_STATION_BUILD_AIRPORT_LARGE_AIRPORTS, STR_NULL),
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0),
 
			NWidget(NWID_VERTICAL),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, BAW_CITY_AIRPORT), SetMinimalSize(144, 12), SetFill(true, false),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, BAW_CITY_AIRPORT), SetMinimalSize(144, 12), SetFill(1, 0),
 
									SetDataTip(STR_STATION_BUILD_AIRPORT_CITY_AIRPORT, STR_STATION_BUILD_AIRPORT_TOOLTIP),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, BAW_METRO_AIRPORT), SetMinimalSize(144, 12), SetFill(true, false),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, BAW_METRO_AIRPORT), SetMinimalSize(144, 12), SetFill(1, 0),
 
									SetDataTip(STR_STATION_BUILD_AIRPORT_METRO_AIRPORT, STR_STATION_BUILD_AIRPORT_TOOLTIP),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 1), SetFill(true, false),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 1), SetFill(1, 0),
 
			EndContainer(),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0),
 
		EndContainer(),
 
	EndContainer(),
 
	/* Hub airports. */
 
	NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BAW_HUB_AIRPORTS_PANEL),
 
		NWidget(WWT_LABEL, COLOUR_DARK_GREEN, BAW_HUB_AIRPORTS_LABEL), SetMinimalSize(148, 14), SetFill(true, false), SetDataTip(STR_STATION_BUILD_AIRPORT_HUB_AIRPORTS, STR_NULL),
 
		NWidget(WWT_LABEL, COLOUR_DARK_GREEN, BAW_HUB_AIRPORTS_LABEL), SetMinimalSize(148, 14), SetFill(1, 0), SetDataTip(STR_STATION_BUILD_AIRPORT_HUB_AIRPORTS, STR_NULL),
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0),
 
			NWidget(NWID_VERTICAL),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, BAW_INTERNATIONAL_AIRPORT), SetMinimalSize(144, 12), SetFill(true, false),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, BAW_INTERNATIONAL_AIRPORT), SetMinimalSize(144, 12), SetFill(1, 0),
 
									SetDataTip(STR_STATION_BUILD_AIRPORT_INTERNATIONAL_AIRPORT, STR_STATION_BUILD_AIRPORT_TOOLTIP),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, BAW_INTERCONTINENTAL_AIRPORT), SetMinimalSize(144, 12), SetFill(true, false),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, BAW_INTERCONTINENTAL_AIRPORT), SetMinimalSize(144, 12), SetFill(1, 0),
 
									SetDataTip(STR_STATION_BUILD_AIRPORT_INTERCONTINENTAL_AIRPORT, STR_STATION_BUILD_AIRPORT_TOOLTIP),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 1), SetFill(true, false),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 1), SetFill(1, 0),
 
			EndContainer(),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0),
 
		EndContainer(),
 
	EndContainer(),
 
	/* Heliports. */
 
	NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BAW_HELIPORTS_PANEL),
 
		NWidget(WWT_LABEL, COLOUR_DARK_GREEN, BAW_HELIPORTS_LABEL), SetMinimalSize(148, 14), SetFill(true, false), SetDataTip(STR_STATION_BUILD_AIRPORT_HELIPORTS, STR_NULL),
 
		NWidget(WWT_LABEL, COLOUR_DARK_GREEN, BAW_HELIPORTS_LABEL), SetMinimalSize(148, 14), SetFill(1, 0), SetDataTip(STR_STATION_BUILD_AIRPORT_HELIPORTS, STR_NULL),
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0),
 
			NWidget(NWID_VERTICAL),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, BAW_HELIPORT), SetMinimalSize(144, 12), SetFill(true, false),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, BAW_HELIPORT), SetMinimalSize(144, 12), SetFill(1, 0),
 
									SetDataTip(STR_STATION_BUILD_AIRPORT_HELIPORT, STR_STATION_BUILD_AIRPORT_TOOLTIP),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, BAW_HELISTATION), SetMinimalSize(144, 12), SetFill(true, false),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, BAW_HELISTATION), SetMinimalSize(144, 12), SetFill(1, 0),
 
									SetDataTip(STR_STATION_BUILD_AIRPORT_HELISTATION, STR_STATION_BUILD_AIRPORT_TOOLTIP),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, BAW_HELIDEPOT), SetMinimalSize(144, 12), SetFill(true, false),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, BAW_HELIDEPOT), SetMinimalSize(144, 12), SetFill(1, 0),
 
									SetDataTip(STR_STATION_BUILD_AIRPORT_HELIDEPOT, STR_STATION_BUILD_AIRPORT_TOOLTIP),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 1), SetFill(true, false),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 1), SetFill(1, 0),
 
			EndContainer(),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0),
 
		EndContainer(),
 
	EndContainer(),
 
	/* Bottom panel. */
 
	NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BAW_BOTTOMPANEL),
 
		NWidget(WWT_LABEL, COLOUR_DARK_GREEN, BAW_COVERAGE_LABEL), SetMinimalSize(148, 14), SetFill(true, false), SetDataTip(STR_STATION_BUILD_COVERAGE_AREA_TITLE, STR_NULL),
 
		NWidget(WWT_LABEL, COLOUR_DARK_GREEN, BAW_COVERAGE_LABEL), SetMinimalSize(148, 14), SetFill(1, 0), SetDataTip(STR_STATION_BUILD_COVERAGE_AREA_TITLE, STR_NULL),
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(NWID_SPACER), SetMinimalSize(14, 0),
 
			NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, BAW_BTN_DONTHILIGHT), SetMinimalSize(60, 12), SetFill(true, false),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, BAW_BTN_DONTHILIGHT), SetMinimalSize(60, 12), SetFill(1, 0),
 
											SetDataTip(STR_STATION_BUILD_COVERAGE_OFF, STR_STATION_BUILD_COVERAGE_AREA_OFF_TOOLTIP),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, BAW_BTN_DOHILIGHT), SetMinimalSize(60, 12), SetFill(true, false),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, BAW_BTN_DOHILIGHT), SetMinimalSize(60, 12), SetFill(1, 0),
 
											SetDataTip(STR_STATION_BUILD_COVERAGE_ON, STR_STATION_BUILD_COVERAGE_AREA_ON_TOOLTIP),
 
			EndContainer(),
 
			NWidget(NWID_SPACER), SetMinimalSize(14, 0),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 10), SetResize(0, 1), SetFill(true, false),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 10), SetResize(0, 1), SetFill(1, 0),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _build_airport_desc(
 
	WDP_AUTO, WDP_AUTO, 148, 245,
 
	WC_BUILD_STATION, WC_BUILD_TOOLBAR,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_CONSTRUCTION,
 
	_nested_build_airport_widgets, lengthof(_nested_build_airport_widgets)
 
);
 

	
 
static void ShowBuildAirportPicker(Window *parent)
 
{
 
	new AirportPickerWindow(&_build_airport_desc, parent);
 
}
 

	
 
void InitializeAirportGui()
 
{
 
	_selected_airport_type = AT_SMALL;
 
}
src/bridge_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file bridge_gui.cpp Graphical user interface for bridge construction */
 

	
 
#include "stdafx.h"
 
#include "gui.h"
 
#include "window_gui.h"
 
#include "command_func.h"
 
#include "economy_func.h"
 
#include "bridge.h"
 
#include "strings_func.h"
 
#include "window_func.h"
 
#include "sound_func.h"
 
#include "gfx_func.h"
 
#include "tunnelbridge.h"
 
#include "sortlist_type.h"
 
#include "widgets/dropdown_func.h"
 

	
 
#include "table/strings.h"
 

	
 
/** The type of the last built rail bridge */
 
static BridgeType _last_railbridge_type = 0;
 
/** The type of the last built road bridge */
 
static BridgeType _last_roadbridge_type = 0;
 

	
 
/**
 
 * Carriage for the data we need if we want to build a bridge
 
 */
 
struct BuildBridgeData {
 
	BridgeType index;
 
	const BridgeSpec *spec;
 
	Money cost;
 
};
 

	
 
typedef GUIList<BuildBridgeData> GUIBridgeList;
 

	
 
/**
 
 * Callback executed after a build Bridge CMD has been called
 
 *
 
 * @param success True if the build succeded
 
 * @param tile The tile where the command has been executed
 
 * @param p1 not used
 
 * @param p2 not used
 
 */
 
void CcBuildBridge(bool success, TileIndex tile, uint32 p1, uint32 p2)
 
{
 
	if (success) SndPlayTileFx(SND_27_BLACKSMITH_ANVIL, tile);
 
}
 

	
 
/* Names of the build bridge selection window */
 
enum BuildBridgeSelectionWidgets {
 
	BBSW_CLOSEBOX = 0,
 
	BBSW_CAPTION,
 
	BBSW_DROPDOWN_ORDER,
 
	BBSW_DROPDOWN_CRITERIA,
 
	BBSW_BRIDGE_LIST,
 
	BBSW_SCROLLBAR,
 
	BBSW_RESIZEBOX
 
};
 

	
 
class BuildBridgeWindow : public Window {
 
private:
 
	/* Runtime saved values */
 
	static uint16 last_size;
 
	static Listing last_sorting;
 

	
 
	/* Constants for sorting the bridges */
 
	static const StringID sorter_names[];
 
	static GUIBridgeList::SortFunction * const sorter_funcs[];
 

	
 
	/* Internal variables */
 
	TileIndex start_tile;
 
	TileIndex end_tile;
 
	uint32 type;
 
	GUIBridgeList *bridges;
 
	int bridgetext_offset; ///< Horizontal offset of the text describing the bridge properties in #BBSW_BRIDGE_LIST relative to the left edge.
 

	
 
	/** Sort the bridges by their index */
 
	static int CDECL BridgeIndexSorter(const BuildBridgeData *a, const BuildBridgeData *b)
 
	{
 
		return a->index - b->index;
 
	}
 

	
 
	/** Sort the bridges by their price */
 
	static int CDECL BridgePriceSorter(const BuildBridgeData *a, const BuildBridgeData *b)
 
	{
 
		return a->cost - b->cost;
 
	}
 

	
 
	/** Sort the bridges by their maximum speed */
 
	static int CDECL BridgeSpeedSorter(const BuildBridgeData *a, const BuildBridgeData *b)
 
	{
 
		return a->spec->speed - b->spec->speed;
 
	}
 

	
 
	void BuildBridge(uint8 i)
 
	{
 
		switch ((TransportType)(this->type >> 15)) {
 
			case TRANSPORT_RAIL: _last_railbridge_type = this->bridges->Get(i)->index; break;
 
			case TRANSPORT_ROAD: _last_roadbridge_type = this->bridges->Get(i)->index; break;
 
			default: break;
 
		}
 
		DoCommandP(this->end_tile, this->start_tile, this->type | this->bridges->Get(i)->index,
 
					CMD_BUILD_BRIDGE | CMD_MSG(STR_ERROR_CAN_T_BUILD_BRIDGE_HERE), CcBuildBridge);
 
	}
 

	
 
	/** Sort the builable bridges */
 
	void SortBridgeList()
 
	{
 
		this->bridges->Sort();
 

	
 
		/* Display the current sort variant */
 
		this->GetWidget<NWidgetCore>(BBSW_DROPDOWN_CRITERIA)->widget_data = this->sorter_names[this->bridges->SortType()];
 

	
 
		/* Set the modified widgets dirty */
 
		this->SetWidgetDirty(BBSW_DROPDOWN_CRITERIA);
 
		this->SetWidgetDirty(BBSW_BRIDGE_LIST);
 
	}
 

	
 
public:
 
	BuildBridgeWindow(const WindowDesc *desc, TileIndex start, TileIndex end, uint32 br_type, GUIBridgeList *bl) : Window(),
 
		start_tile(start),
 
		end_tile(end),
 
		type(br_type),
 
		bridges(bl)
 
	{
 
		this->CreateNestedTree(desc);
 
		/* Change the data, or the caption of the gui. Set it to road or rail, accordingly. */
 
		this->GetWidget<NWidgetCore>(BBSW_CAPTION)->widget_data = (GB(this->type, 15, 2) == TRANSPORT_ROAD) ? STR_SELECT_ROAD_BRIDGE_CAPTION : STR_SELECT_RAIL_BRIDGE_CAPTION;
 
		this->FinishInitNested(desc, GB(br_type, 15, 2)); // Initializes 'this->bridgetext_offset'.
 

	
 
		this->parent = FindWindowById(WC_BUILD_TOOLBAR, GB(this->type, 15, 2));
 
		this->bridges->SetListing(this->last_sorting);
 
		this->bridges->SetSortFuncs(this->sorter_funcs);
 
		this->bridges->NeedResort();
 
		this->SortBridgeList();
 

	
 
		this->vscroll.SetCount(bl->Length());
 
		if (this->last_size < this->vscroll.GetCapacity()) this->last_size = this->vscroll.GetCapacity();
 
		if (this->last_size > this->vscroll.GetCount()) this->last_size = this->vscroll.GetCount();
 
		/* Resize the bridge selection window if we used a bigger one the last time. */
 
		if (this->last_size > this->vscroll.GetCapacity()) {
 
			ResizeWindow(this, 0, (this->last_size - this->vscroll.GetCapacity()) * this->resize.step_height);
 
		}
 
		this->GetWidget<NWidgetCore>(BBSW_BRIDGE_LIST)->widget_data = (this->vscroll.GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START);
 
	}
 

	
 
	~BuildBridgeWindow()
 
	{
 
		this->last_sorting = this->bridges->GetListing();
 

	
 
		delete bridges;
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		switch (widget) {
 
			case BBSW_DROPDOWN_ORDER: {
 
				Dimension d = GetStringBoundingBox(this->GetWidget<NWidgetCore>(widget)->widget_data);
 
				d.width += padding.width + WD_SORTBUTTON_ARROW_WIDTH * 2; // Doubled since the word is centered, also looks nice.
 
				d.height += padding.height;
 
				*size = maxdim(*size, d);
 
				break;
 
			}
 
			case BBSW_DROPDOWN_CRITERIA: {
 
				Dimension d = {0, 0};
 
				for (const StringID *str = this->sorter_names; *str != INVALID_STRING_ID; str++) {
 
					d = maxdim(d, GetStringBoundingBox(*str));
 
				}
 
				d.width += padding.width;
 
				d.height += padding.height;
 
				*size = maxdim(*size, d);
 
				break;
 
			}
 
			case BBSW_BRIDGE_LIST: {
 
				Dimension sprite_dim = {0, 0}; // Biggest bridge sprite dimension
 
				Dimension text_dim   = {0, 0}; // Biggest text dimension
 
				for (int i = 0; i < (int)this->bridges->Length(); i++) {
 
					const BridgeSpec *b = this->bridges->Get(i)->spec;
 
					sprite_dim = maxdim(sprite_dim, GetSpriteSize(b->sprite));
 

	
 
					SetDParam(2, this->bridges->Get(i)->cost);
 
					SetDParam(1, b->speed);
 
					SetDParam(0, b->material);
 
					text_dim = maxdim(text_dim, GetStringBoundingBox(STR_SELECT_BRIDGE_INFO));
 
				}
 
				sprite_dim.height++; // Sprite is rendered one pixel down in the matrix field.
 
				text_dim.height++; // Allowing the bottom row pixels to be rendered on the edge of the matrix field.
 
				resize->height = max(sprite_dim.height, text_dim.height) + 2; // Max of both sizes + account for matrix edges.
 

	
 
				this->bridgetext_offset = WD_MATRIX_LEFT + sprite_dim.width + 1; // Left edge of text, 1 pixel distance from the sprite.
 
				size->width = this->bridgetext_offset + text_dim.width + WD_MATRIX_RIGHT;
 
				size->height = 4 * resize->height; // Smallest bridge gui is 4 entries high in the matrix.
 
				break;
 
			}
 
		}
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case BBSW_DROPDOWN_ORDER:
 
				this->DrawSortButtonState(widget, this->bridges->IsDescSortOrder() ? SBS_DOWN : SBS_UP);
 
				break;
 

	
 
			case BBSW_BRIDGE_LIST: {
 
				uint y = r.top;
 
				for (int i = this->vscroll.GetPosition(); this->vscroll.IsVisible(i) && i < (int)this->bridges->Length(); i++) {
 
					const BridgeSpec *b = this->bridges->Get(i)->spec;
 

	
 
					SetDParam(2, this->bridges->Get(i)->cost);
 
					SetDParam(1, b->speed);
 
					SetDParam(0, b->material);
 

	
 
					DrawSprite(b->sprite, b->pal, r.left + WD_MATRIX_LEFT, y + this->resize.step_height - 1 - GetSpriteSize(b->sprite).height);
 
					DrawStringMultiLine(r.left + this->bridgetext_offset, r.right, y + 2, y + this->resize.step_height, STR_SELECT_BRIDGE_INFO);
 
					y += this->resize.step_height;
 
				}
 
				break;
 
			}
 
		}
 
	}
 

	
 
	virtual EventState OnKeyPress(uint16 key, uint16 keycode)
 
	{
 
		const uint8 i = keycode - '1';
 
		if (i < 9 && i < this->bridges->Length()) {
 
			/* Build the requested bridge */
 
			this->BuildBridge(i);
 
			delete this;
 
			return ES_HANDLED;
 
		}
 
		return ES_NOT_HANDLED;
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			default: break;
 
			case BBSW_BRIDGE_LIST: {
 
				uint i = ((int)pt.y - this->GetWidget<NWidgetBase>(BBSW_BRIDGE_LIST)->pos_y) / this->resize.step_height;
 
				if (i < this->vscroll.GetCapacity()) {
 
					i += this->vscroll.GetPosition();
 
					if (i < this->bridges->Length()) {
 
						this->BuildBridge(i);
 
						delete this;
 
					}
 
				}
 
			} break;
 

	
 
			case BBSW_DROPDOWN_ORDER:
 
				this->bridges->ToggleSortOrder();
 
				this->SetDirty();
 
				break;
 

	
 
			case BBSW_DROPDOWN_CRITERIA:
 
				ShowDropDownMenu(this, this->sorter_names, this->bridges->SortType(), BBSW_DROPDOWN_CRITERIA, 0, 0);
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnDropdownSelect(int widget, int index)
 
	{
 
		if (widget == BBSW_DROPDOWN_CRITERIA && this->bridges->SortType() != index) {
 
			this->bridges->SetSortType(index);
 

	
 
			this->SortBridgeList();
 
		}
 
	}
 

	
 
	virtual void OnResize()
 
	{
 
		this->vscroll.SetCapacity(this->GetWidget<NWidgetBase>(BBSW_BRIDGE_LIST)->current_y / this->resize.step_height);
 
		this->GetWidget<NWidgetCore>(BBSW_BRIDGE_LIST)->widget_data = (this->vscroll.GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START);
 

	
 
		this->last_size = max(this->vscroll.GetCapacity(), this->last_size);
 
	}
 
};
 

	
 
/* Set the default size of the Build Bridge Window */
 
uint16 BuildBridgeWindow::last_size = 4;
 
/* Set the default sorting for the bridges */
 
Listing BuildBridgeWindow::last_sorting = {false, 0};
 

	
 
/* Availible bridge sorting functions */
 
GUIBridgeList::SortFunction * const BuildBridgeWindow::sorter_funcs[] = {
 
	&BridgeIndexSorter,
 
	&BridgePriceSorter,
 
	&BridgeSpeedSorter
 
};
 

	
 
/* Names of the sorting functions */
 
const StringID BuildBridgeWindow::sorter_names[] = {
 
	STR_SORT_BY_NUMBER,
 
	STR_SORT_BY_COST,
 
	STR_SORT_BY_MAX_SPEED,
 
	INVALID_STRING_ID
 
};
 

	
 
static const NWidgetPart _nested_build_bridge_widgets[] = {
 
	/* Header */
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN, BBSW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, BBSW_CAPTION), SetDataTip(STR_SELECT_RAIL_BRIDGE_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 

	
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(NWID_VERTICAL),
 
			/* Sort order + criteria buttons */
 
			NWidget(NWID_HORIZONTAL),
 
				NWidget(WWT_TEXTBTN, COLOUR_DARK_GREEN, BBSW_DROPDOWN_ORDER), SetFill(true, false), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER),
 
				NWidget(WWT_DROPDOWN, COLOUR_DARK_GREEN, BBSW_DROPDOWN_CRITERIA), SetFill(true, false), SetDataTip(0x0, STR_TOOLTIP_SORT_CRITERIAP),
 
				NWidget(WWT_TEXTBTN, COLOUR_DARK_GREEN, BBSW_DROPDOWN_ORDER), SetFill(1, 0), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER),
 
				NWidget(WWT_DROPDOWN, COLOUR_DARK_GREEN, BBSW_DROPDOWN_CRITERIA), SetFill(1, 0), SetDataTip(0x0, STR_TOOLTIP_SORT_CRITERIAP),
 
			EndContainer(),
 
			/* Matrix. */
 
			NWidget(WWT_MATRIX, COLOUR_DARK_GREEN, BBSW_BRIDGE_LIST), SetFill(true, false), SetResize(0, 22), SetDataTip(0x401, STR_SELECT_BRIDGE_SELECTION_TOOLTIP),
 
			NWidget(WWT_MATRIX, COLOUR_DARK_GREEN, BBSW_BRIDGE_LIST), SetFill(1, 0), SetResize(0, 22), SetDataTip(0x401, STR_SELECT_BRIDGE_SELECTION_TOOLTIP),
 
		EndContainer(),
 

	
 
		/* scrollbar + resize button */
 
		NWidget(NWID_VERTICAL),
 
			NWidget(WWT_SCROLLBAR, COLOUR_DARK_GREEN, BBSW_SCROLLBAR),
 
			NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN, BBSW_RESIZEBOX),
 
		EndContainer(),
 
	EndContainer(),
 
};
 

	
 
/* Window definition for the rail bridge selection window */
 
static const WindowDesc _build_bridge_desc(
 
	WDP_AUTO, WDP_AUTO, 200, 114,
 
	WC_BUILD_BRIDGE, WC_BUILD_TOOLBAR,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_RESIZABLE | WDF_CONSTRUCTION,
 
	_nested_build_bridge_widgets, lengthof(_nested_build_bridge_widgets)
 
);
 

	
 
/**
 
 * Prepare the data for the build a bridge window.
 
 *  If we can't build a bridge under the given conditions
 
 *  show an error message.
 
 *
 
 * @param start The start tile of the bridge
 
 * @param end The end tile of the bridge
 
 * @param transport_type The transport type
 
 * @param road_rail_type The road/rail type
 
 */
 
void ShowBuildBridgeWindow(TileIndex start, TileIndex end, TransportType transport_type, byte road_rail_type)
 
{
 
	DeleteWindowByClass(WC_BUILD_BRIDGE);
 

	
 
	/* Data type for the bridge.
 
	 * Bit 16,15 = transport type,
 
	 *     14..8 = road/rail types,
 
	 *      7..0 = type of bridge */
 
	uint32 type = (transport_type << 15) | (road_rail_type << 8);
 

	
 
	/* The bridge length without ramps. */
 
	const uint bridge_len = GetTunnelBridgeLength(start, end);
 

	
 
	/* If Ctrl is being pressed, check wether the last bridge built is available
 
	 * If so, return this bridge type. Otherwise continue normally.
 
	 * We store bridge types for each transport type, so we have to check for
 
	 * the transport type beforehand.
 
	 */
 
	BridgeType last_bridge_type = 0;
 
	switch (transport_type) {
 
		case TRANSPORT_ROAD: last_bridge_type = _last_roadbridge_type; break;
 
		case TRANSPORT_RAIL: last_bridge_type = _last_railbridge_type; break;
 
		default: break; // water ways and air routes don't have bridge types
 
	}
 
	if (_ctrl_pressed && CheckBridge_Stuff(last_bridge_type, bridge_len)) {
 
		DoCommandP(end, start, type | last_bridge_type, CMD_BUILD_BRIDGE | CMD_MSG(STR_ERROR_CAN_T_BUILD_BRIDGE_HERE), CcBuildBridge);
 
		return;
 
	}
 

	
 
	/* only query bridge building possibility once, result is the same for all bridges!
 
	 * returns CMD_ERROR on failure, and price on success */
 
	StringID errmsg = INVALID_STRING_ID;
 
	CommandCost ret = DoCommand(end, start, type, DC_AUTO | DC_QUERY_COST, CMD_BUILD_BRIDGE);
 

	
 
	GUIBridgeList *bl = NULL;
 
	if (CmdFailed(ret)) {
 
		errmsg = _error_message;
 
	} else {
 
		/* check which bridges can be built */
 
		const uint tot_bridgedata_len = CalcBridgeLenCostFactor(bridge_len + 2);
 

	
 
		bl = new GUIBridgeList();
 

	
 
		/* loop for all bridgetypes */
 
		for (BridgeType brd_type = 0; brd_type != MAX_BRIDGES; brd_type++) {
 
			if (CheckBridge_Stuff(brd_type, bridge_len)) {
 
				/* bridge is accepted, add to list */
 
				BuildBridgeData *item = bl->Append();
 
				item->index = brd_type;
 
				item->spec = GetBridgeSpec(brd_type);
 
				/* Add to terraforming & bulldozing costs the cost of the
 
				 * bridge itself (not computed with DC_QUERY_COST) */
 
				item->cost = ret.GetCost() + (((int64)tot_bridgedata_len * _price[PR_BUILD_BRIDGE] * item->spec->price) >> 8);
 
			}
 
		}
 
	}
 

	
 
	if (bl != NULL && bl->Length() != 0) {
 
		new BuildBridgeWindow(&_build_bridge_desc, start, end, type, bl);
 
	} else {
 
		delete bl;
 
		ShowErrorMessage(STR_ERROR_CAN_T_BUILD_BRIDGE_HERE, errmsg, TileX(end) * TILE_SIZE, TileY(end) * TILE_SIZE);
 
	}
 
}
src/build_vehicle_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file build_vehicle_gui.cpp GUI for building vehicles. */
 

	
 
#include "train.h"
 
#include "roadveh.h"
 
#include "ship.h"
 
#include "aircraft.h"
 
#include "station_base.h"
 
#include "articulated_vehicles.h"
 
#include "textbuf_gui.h"
 
#include "command_func.h"
 
#include "company_func.h"
 
#include "vehicle_gui.h"
 
#include "newgrf_engine.h"
 
#include "newgrf_text.h"
 
#include "group.h"
 
#include "strings_func.h"
 
#include "window_func.h"
 
#include "date_func.h"
 
#include "vehicle_func.h"
 
#include "gfx_func.h"
 
#include "widgets/dropdown_func.h"
 
#include "window_gui.h"
 
#include "engine_gui.h"
 
#include "cargotype.h"
 

	
 
#include "table/sprites.h"
 
#include "table/strings.h"
 

	
 
/**
 
 * Get the height of a single 'entry' in the engine lists.
 
 * @param type the vehicle type to get the height of
 
 * @return the height for the entry
 
 */
 
uint GetEngineListHeight(VehicleType type)
 
{
 
	return max<uint>(FONT_HEIGHT_NORMAL + WD_MATRIX_TOP + WD_MATRIX_BOTTOM, GetVehicleHeight(type));
 
}
 

	
 
enum BuildVehicleWidgets {
 
	BUILD_VEHICLE_WIDGET_CLOSEBOX = 0,
 
	BUILD_VEHICLE_WIDGET_CAPTION,
 
	BUILD_VEHICLE_WIDGET_LIST_CONTROL,
 
	BUILD_VEHICLE_WIDGET_SORT_ASSENDING_DESCENDING,
 
	BUILD_VEHICLE_WIDGET_SORT_DROPDOWN,
 
	BUILD_VEHICLE_WIDGET_CARGO_FILTER_DROPDOWN,
 
	BUILD_VEHICLE_WIDGET_LIST,
 
	BUILD_VEHICLE_WIDGET_SCROLLBAR,
 
	BUILD_VEHICLE_WIDGET_PANEL,
 
	BUILD_VEHICLE_WIDGET_BUILD,
 
	BUILD_VEHICLE_WIDGET_BUILD_SEL,
 
	BUILD_VEHICLE_WIDGET_RENAME,
 
	BUILD_VEHICLE_WIDGET_RESIZE,
 
	BUILD_VEHICLE_WIDGET_END
 
};
 

	
 
static const NWidgetPart _nested_build_vehicle_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, BUILD_VEHICLE_WIDGET_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, BUILD_VEHICLE_WIDGET_CAPTION), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, BUILD_VEHICLE_WIDGET_LIST_CONTROL),
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(NWID_VERTICAL),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, BUILD_VEHICLE_WIDGET_SORT_ASSENDING_DESCENDING), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER), SetFill(true, false),
 
				NWidget(NWID_SPACER), SetFill(true, true),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, BUILD_VEHICLE_WIDGET_SORT_ASSENDING_DESCENDING), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0),
 
				NWidget(NWID_SPACER), SetFill(1, 1),
 
			EndContainer(),
 
			NWidget(NWID_VERTICAL),
 
				NWidget(WWT_DROPDOWN, COLOUR_GREY, BUILD_VEHICLE_WIDGET_SORT_DROPDOWN), SetResize(1, 0), SetFill(true, false), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_SORT_CRITERIAP),
 
				NWidget(WWT_DROPDOWN, COLOUR_GREY, BUILD_VEHICLE_WIDGET_CARGO_FILTER_DROPDOWN), SetResize(1, 0), SetFill(true, false), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_FILTER_CRITERIA),
 
				NWidget(WWT_DROPDOWN, COLOUR_GREY, BUILD_VEHICLE_WIDGET_SORT_DROPDOWN), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_SORT_CRITERIAP),
 
				NWidget(WWT_DROPDOWN, COLOUR_GREY, BUILD_VEHICLE_WIDGET_CARGO_FILTER_DROPDOWN), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_FILTER_CRITERIA),
 
			EndContainer(),
 
		EndContainer(),
 
	EndContainer(),
 
	/* Vehicle list. */
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_MATRIX, COLOUR_GREY, BUILD_VEHICLE_WIDGET_LIST), SetResize(1, 1), SetFill(true, false), SetDataTip(0x101, STR_NULL),
 
		NWidget(WWT_MATRIX, COLOUR_GREY, BUILD_VEHICLE_WIDGET_LIST), SetResize(1, 1), SetFill(1, 0), SetDataTip(0x101, STR_NULL),
 
		NWidget(WWT_SCROLLBAR, COLOUR_GREY, BUILD_VEHICLE_WIDGET_SCROLLBAR),
 
	EndContainer(),
 
	/* Panel with details. */
 
	NWidget(WWT_PANEL, COLOUR_GREY, BUILD_VEHICLE_WIDGET_PANEL), SetMinimalSize(240, 122), SetResize(1, 0), EndContainer(),
 
	/* Build/rename buttons, resize button. */
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(NWID_SELECTION, INVALID_COLOUR, BUILD_VEHICLE_WIDGET_BUILD_SEL),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, BUILD_VEHICLE_WIDGET_BUILD), SetResize(1, 0), SetFill(true, false),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, BUILD_VEHICLE_WIDGET_BUILD), SetResize(1, 0), SetFill(1, 0),
 
		EndContainer(),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, BUILD_VEHICLE_WIDGET_RENAME), SetResize(1, 0), SetFill(true, false),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, BUILD_VEHICLE_WIDGET_RENAME), SetResize(1, 0), SetFill(1, 0),
 
		NWidget(WWT_RESIZEBOX, COLOUR_GREY, BUILD_VEHICLE_WIDGET_RESIZE),
 
	EndContainer(),
 
};
 

	
 
/** Special cargo filter criteria */
 
enum {
 
	CF_ANY  = CT_NO_REFIT, ///< Show all vehicles independent of carried cargo (i.e. no filtering)
 
	CF_NONE = CT_INVALID,  ///< Show only vehicles which do not carry cargo (e.g. train engines)
 
};
 

	
 
static bool _internal_sort_order; // descending/ascending
 
static byte _last_sort_criteria[]    = {0, 0, 0, 0};
 
static bool _last_sort_order[]       = {false, false, false, false};
 
static byte _last_filter_criteria[]  = {0, 0, 0, 0};
 

	
 
static int CDECL EngineNumberSorter(const EngineID *a, const EngineID *b)
 
{
 
	int r = ListPositionOfEngine(*a) - ListPositionOfEngine(*b);
 

	
 
	return _internal_sort_order ? -r : r;
 
}
 

	
 
static int CDECL EngineIntroDateSorter(const EngineID *a, const EngineID *b)
 
{
 
	const int va = Engine::Get(*a)->intro_date;
 
	const int vb = Engine::Get(*b)->intro_date;
 
	const int r = va - vb;
 

	
 
	/* Use EngineID to sort instead since we want consistent sorting */
 
	if (r == 0) return EngineNumberSorter(a, b);
 
	return _internal_sort_order ? -r : r;
 
}
 

	
 
static int CDECL EngineNameSorter(const EngineID *a, const EngineID *b)
 
{
 
	static EngineID last_engine[2] = { INVALID_ENGINE, INVALID_ENGINE };
 
	static char     last_name[2][64] = { "\0", "\0" };
 

	
 
	const EngineID va = *a;
 
	const EngineID vb = *b;
 

	
 
	if (va != last_engine[0]) {
 
		last_engine[0] = va;
 
		SetDParam(0, va);
 
		GetString(last_name[0], STR_ENGINE_NAME, lastof(last_name[0]));
 
	}
 

	
 
	if (vb != last_engine[1]) {
 
		last_engine[1] = vb;
 
		SetDParam(0, vb);
 
		GetString(last_name[1], STR_ENGINE_NAME, lastof(last_name[1]));
 
	}
 

	
 
	int r = strcmp(last_name[0], last_name[1]); // sort by name
 

	
 
	/* Use EngineID to sort instead since we want consistent sorting */
 
	if (r == 0) return EngineNumberSorter(a, b);
 
	return _internal_sort_order ? -r : r;
 
}
 

	
 
static int CDECL EngineReliabilitySorter(const EngineID *a, const EngineID *b)
 
{
 
	const int va = Engine::Get(*a)->reliability;
 
	const int vb = Engine::Get(*b)->reliability;
 
	const int r = va - vb;
 

	
 
	/* Use EngineID to sort instead since we want consistent sorting */
 
	if (r == 0) return EngineNumberSorter(a, b);
 
	return _internal_sort_order ? -r : r;
 
}
 

	
 
static int CDECL EngineCostSorter(const EngineID *a, const EngineID *b)
 
{
 
	Money va = Engine::Get(*a)->GetCost();
 
	Money vb = Engine::Get(*b)->GetCost();
 
	int r = ClampToI32(va - vb);
 

	
 
	/* Use EngineID to sort instead since we want consistent sorting */
 
	if (r == 0) return EngineNumberSorter(a, b);
 
	return _internal_sort_order ? -r : r;
 
}
 

	
 
static int CDECL EngineSpeedSorter(const EngineID *a, const EngineID *b)
 
{
 
	int va = Engine::Get(*a)->GetDisplayMaxSpeed();
 
	int vb = Engine::Get(*b)->GetDisplayMaxSpeed();
 
	int r = va - vb;
 

	
 
	/* Use EngineID to sort instead since we want consistent sorting */
 
	if (r == 0) return EngineNumberSorter(a, b);
 
	return _internal_sort_order ? -r : r;
 
}
 

	
 
static int CDECL EnginePowerSorter(const EngineID *a, const EngineID *b)
 
{
 
	int va = Engine::Get(*a)->GetPower();
 
	int vb = Engine::Get(*b)->GetPower();
 
	int r = va - vb;
 

	
 
	/* Use EngineID to sort instead since we want consistent sorting */
 
	if (r == 0) return EngineNumberSorter(a, b);
 
	return _internal_sort_order ? -r : r;
 
}
 

	
 
static int CDECL EngineRunningCostSorter(const EngineID *a, const EngineID *b)
 
{
 
	Money va = Engine::Get(*a)->GetRunningCost();
 
	Money vb = Engine::Get(*b)->GetRunningCost();
 
	int r = ClampToI32(va - vb);
 

	
 
	/* Use EngineID to sort instead since we want consistent sorting */
 
	if (r == 0) return EngineNumberSorter(a, b);
 
	return _internal_sort_order ? -r : r;
 
}
 

	
 
/* Train sorting functions */
 
static int CDECL TrainEnginePowerVsRunningCostSorter(const EngineID *a, const EngineID *b)
 
{
 
	const Engine *e_a = Engine::Get(*a);
 
	const Engine *e_b = Engine::Get(*b);
 

	
 
	/* Here we are using a few tricks to get the right sort.
 
	 * We want power/running cost, but since we usually got higher running cost than power and we store the result in an int,
 
	 * we will actually calculate cunning cost/power (to make it more than 1).
 
	 * Because of this, the return value have to be reversed as well and we return b - a instead of a - b.
 
	 * Another thing is that both power and running costs should be doubled for multiheaded engines.
 
	 * Since it would be multipling with 2 in both numerator and denumerator, it will even themselves out and we skip checking for multiheaded. */
 
	Money va = (e_a->GetRunningCost()) / max(1U, (uint)e_a->GetPower());
 
	Money vb = (e_b->GetRunningCost()) / max(1U, (uint)e_b->GetPower());
 
	int r = ClampToI32(vb - va);
 

	
 
	/* Use EngineID to sort instead since we want consistent sorting */
 
	if (r == 0) return EngineNumberSorter(a, b);
 
	return _internal_sort_order ? -r : r;
 
}
 

	
 
static int CDECL TrainEngineCapacitySorter(const EngineID *a, const EngineID *b)
 
{
 
	const RailVehicleInfo *rvi_a = RailVehInfo(*a);
 
	const RailVehicleInfo *rvi_b = RailVehInfo(*b);
 

	
 
	int va = GetTotalCapacityOfArticulatedParts(*a) * (rvi_a->railveh_type == RAILVEH_MULTIHEAD ? 2 : 1);
 
	int vb = GetTotalCapacityOfArticulatedParts(*b) * (rvi_b->railveh_type == RAILVEH_MULTIHEAD ? 2 : 1);
 
	int r = va - vb;
 

	
 
	/* Use EngineID to sort instead since we want consistent sorting */
 
	if (r == 0) return EngineNumberSorter(a, b);
 
	return _internal_sort_order ? -r : r;
 
}
 

	
 
static int CDECL TrainEnginesThenWagonsSorter(const EngineID *a, const EngineID *b)
 
{
 
	int val_a = (RailVehInfo(*a)->railveh_type == RAILVEH_WAGON ? 1 : 0);
 
	int val_b = (RailVehInfo(*b)->railveh_type == RAILVEH_WAGON ? 1 : 0);
 
	int r = val_a - val_b;
 

	
 
	/* Use EngineID to sort instead since we want consistent sorting */
 
	if (r == 0) return EngineNumberSorter(a, b);
 
	return _internal_sort_order ? -r : r;
 
}
 

	
 
/* Road vehicle sorting functions */
 
static int CDECL RoadVehEngineCapacitySorter(const EngineID *a, const EngineID *b)
 
{
 
	int va = GetTotalCapacityOfArticulatedParts(*a);
 
	int vb = GetTotalCapacityOfArticulatedParts(*b);
 
	int r = va - vb;
 

	
 
	/* Use EngineID to sort instead since we want consistent sorting */
 
	if (r == 0) return EngineNumberSorter(a, b);
 
	return _internal_sort_order ? -r : r;
 
}
 

	
 
/* Ship vehicle sorting functions */
 
static int CDECL ShipEngineCapacitySorter(const EngineID *a, const EngineID *b)
 
{
 
	const Engine *e_a = Engine::Get(*a);
 
	const Engine *e_b = Engine::Get(*b);
 

	
 
	int va = e_a->GetDisplayDefaultCapacity();
 
	int vb = e_b->GetDisplayDefaultCapacity();
 
	int r = va - vb;
 

	
 
	/* Use EngineID to sort instead since we want consistent sorting */
 
	if (r == 0) return EngineNumberSorter(a, b);
 
	return _internal_sort_order ? -r : r;
 
}
 

	
 
/* Aircraft sorting functions */
 
static int CDECL AircraftEngineCargoSorter(const EngineID *a, const EngineID *b)
 
{
 
	const Engine *e_a = Engine::Get(*a);
 
	const Engine *e_b = Engine::Get(*b);
 

	
 
	uint16 mail_a, mail_b;
 
	int va = e_a->GetDisplayDefaultCapacity(&mail_a);
 
	int vb = e_b->GetDisplayDefaultCapacity(&mail_b);
 
	int r = va - vb;
 

	
 
	if (r == 0) {
 
		/* The planes have the same passenger capacity. Check mail capacity instead */
 
		r = mail_a - mail_b;
 

	
 
		if (r == 0) {
 
			/* Use EngineID to sort instead since we want consistent sorting */
 
			return EngineNumberSorter(a, b);
 
		}
 
	}
 
	return _internal_sort_order ? -r : r;
 
}
 

	
 
static EngList_SortTypeFunction * const _sorter[][10] = {{
 
	/* Trains */
 
	&EngineNumberSorter,
 
	&EngineCostSorter,
 
	&EngineSpeedSorter,
 
	&EnginePowerSorter,
 
	&EngineIntroDateSorter,
 
	&EngineNameSorter,
 
	&EngineRunningCostSorter,
 
	&TrainEnginePowerVsRunningCostSorter,
 
	&EngineReliabilitySorter,
 
	&TrainEngineCapacitySorter,
 
}, {
 
	/* Road vehicles */
 
	&EngineNumberSorter,
 
	&EngineCostSorter,
 
	&EngineSpeedSorter,
 
	&EngineIntroDateSorter,
 
	&EngineNameSorter,
 
	&EngineRunningCostSorter,
 
	&EngineReliabilitySorter,
 
	&RoadVehEngineCapacitySorter,
 
}, {
 
	/* Ships */
 
	&EngineNumberSorter,
 
	&EngineCostSorter,
 
	&EngineSpeedSorter,
 
	&EngineIntroDateSorter,
 
	&EngineNameSorter,
 
	&EngineRunningCostSorter,
 
	&EngineReliabilitySorter,
 
	&ShipEngineCapacitySorter,
 
}, {
 
	/* Aircraft */
 
	&EngineNumberSorter,
 
	&EngineCostSorter,
 
	&EngineSpeedSorter,
 
	&EngineIntroDateSorter,
 
	&EngineNameSorter,
 
	&EngineRunningCostSorter,
 
	&EngineReliabilitySorter,
 
	&AircraftEngineCargoSorter,
 
}};
 

	
 
static const StringID _sort_listing[][11] = {{
 
	/* Trains */
 
	STR_SORT_BY_ENGINE_ID,
 
	STR_SORT_BY_COST,
 
	STR_SORT_BY_MAX_SPEED,
 
	STR_SORT_BY_POWER,
 
	STR_SORT_BY_INTRO_DATE,
 
	STR_SORT_BY_NAME,
 
	STR_SORT_BY_RUNNING_COST,
 
	STR_SORT_BY_POWER_VS_RUNNING_COST,
 
	STR_SORT_BY_RELIABILITY,
 
	STR_SORT_BY_CARGO_CAPACITY,
 
	INVALID_STRING_ID
 
}, {
 
	/* Road vehicles */
 
	STR_SORT_BY_ENGINE_ID,
 
	STR_SORT_BY_COST,
 
	STR_SORT_BY_MAX_SPEED,
 
	STR_SORT_BY_INTRO_DATE,
 
	STR_SORT_BY_NAME,
 
	STR_SORT_BY_RUNNING_COST,
 
	STR_SORT_BY_RELIABILITY,
 
	STR_SORT_BY_CARGO_CAPACITY,
 
	INVALID_STRING_ID
 
}, {
 
	/* Ships */
 
	STR_SORT_BY_ENGINE_ID,
 
	STR_SORT_BY_COST,
 
	STR_SORT_BY_MAX_SPEED,
 
	STR_SORT_BY_INTRO_DATE,
 
	STR_SORT_BY_NAME,
 
	STR_SORT_BY_RUNNING_COST,
 
	STR_SORT_BY_RELIABILITY,
 
	STR_SORT_BY_CARGO_CAPACITY,
 
	INVALID_STRING_ID
 
}, {
 
	/* Aircraft */
 
	STR_SORT_BY_ENGINE_ID,
 
	STR_SORT_BY_COST,
 
	STR_SORT_BY_MAX_SPEED,
 
	STR_SORT_BY_INTRO_DATE,
 
	STR_SORT_BY_NAME,
 
	STR_SORT_BY_RUNNING_COST,
 
	STR_SORT_BY_RELIABILITY,
 
	STR_SORT_BY_CARGO_CAPACITY,
 
	INVALID_STRING_ID
 
}};
 

	
 
/** Cargo filter functions */
 
static bool CDECL CargoFilter(const EngineID *eid, const CargoID cid)
 
{
 
	if (cid == CF_ANY) return true;
 
	uint32 refit_mask = GetUnionOfArticulatedRefitMasks(*eid, true);
 
	return (cid == CF_NONE ? refit_mask == 0 : HasBit(refit_mask, cid));
 
}
 

	
 
static GUIEngineList::FilterFunction * const _filter_funcs[] = {
 
	&CargoFilter,
 
};
 

	
 
static int DrawCargoCapacityInfo(int left, int right, int y, EngineID engine, bool refittable)
 
{
 
	CargoArray cap = GetCapacityOfArticulatedParts(engine);
 

	
 
	for (CargoID c = 0; c < NUM_CARGO; c++) {
 
		if (cap[c] == 0) continue;
 

	
 
		SetDParam(0, c);
 
		SetDParam(1, cap[c]);
 
		SetDParam(2, refittable ? STR_PURCHASE_INFO_REFITTABLE : STR_EMPTY);
 
		DrawString(left, right, y, STR_PURCHASE_INFO_CAPACITY);
 
		y += FONT_HEIGHT_NORMAL;
 

	
 
		/* Only show as refittable once */
 
		refittable = false;
 
	}
 

	
 
	return y;
 
}
 

	
 
/* Draw rail wagon specific details */
 
static int DrawRailWagonPurchaseInfo(int left, int right, int y, EngineID engine_number, const RailVehicleInfo *rvi)
 
{
 
	const Engine *e = Engine::Get(engine_number);
 

	
 
	/* Purchase cost */
 
	SetDParam(0, e->GetCost());
 
	DrawString(left, right, y, STR_PURCHASE_INFO_COST);
 
	y += FONT_HEIGHT_NORMAL;
 

	
 
	/* Wagon weight - (including cargo) */
 
	uint weight = e->GetDisplayWeight();
 
	SetDParam(0, weight);
 
	uint cargo_weight = (e->CanCarryCargo() ? CargoSpec::Get(e->GetDefaultCargoType())->weight * e->GetDisplayDefaultCapacity() >> 4 : 0);
 
	SetDParam(1, cargo_weight + weight);
 
	DrawString(left, right, y, STR_PURCHASE_INFO_WEIGHT_CWEIGHT);
 
	y += FONT_HEIGHT_NORMAL;
 

	
 
	/* Wagon speed limit, displayed if above zero */
 
	if (_settings_game.vehicle.wagon_speed_limits) {
 
		uint max_speed = e->GetDisplayMaxSpeed();
 
		if (max_speed > 0) {
 
			SetDParam(0, max_speed);
 
			DrawString(left, right, y, STR_PURCHASE_INFO_SPEED);
 
			y += FONT_HEIGHT_NORMAL;
 
		}
 
	}
 

	
 
	/* Running cost */
 
	if (rvi->running_cost_class != INVALID_PRICE) {
 
		SetDParam(0, e->GetRunningCost());
 
		DrawString(left, right, y, STR_PURCHASE_INFO_RUNNINGCOST);
 
		y += FONT_HEIGHT_NORMAL;
 
	}
 

	
 
	return y;
 
}
 

	
 
/* Draw locomotive specific details */
 
static int DrawRailEnginePurchaseInfo(int left, int right, int y, EngineID engine_number, const RailVehicleInfo *rvi)
 
{
 
	const Engine *e = Engine::Get(engine_number);
 

	
 
	/* Purchase Cost - Engine weight */
 
	SetDParam(0, e->GetCost());
 
	SetDParam(1, e->GetDisplayWeight());
 
	DrawString(left, right, y, STR_PURCHASE_INFO_COST_WEIGHT);
 
	y += FONT_HEIGHT_NORMAL;
 

	
 
	/* Max speed - Engine power */
 
	SetDParam(0, e->GetDisplayMaxSpeed());
 
	SetDParam(1, e->GetPower());
 
	DrawString(left, right, y, STR_PURCHASE_INFO_SPEED_POWER);
 
	y += FONT_HEIGHT_NORMAL;
 

	
 
	/* Max tractive effort - not applicable if old acceleration or maglev */
 
	if (_settings_game.vehicle.train_acceleration_model != TAM_ORIGINAL && rvi->railtype != RAILTYPE_MAGLEV) {
 
		SetDParam(0, e->GetDisplayMaxTractiveEffort());
 
		DrawString(left, right, y, STR_PURCHASE_INFO_MAX_TE);
 
		y += FONT_HEIGHT_NORMAL;
 
	}
 

	
 
	/* Running cost */
 
	if (rvi->running_cost_class != INVALID_PRICE) {
 
		SetDParam(0, e->GetRunningCost());
 
		DrawString(left, right, y, STR_PURCHASE_INFO_RUNNINGCOST);
 
		y += FONT_HEIGHT_NORMAL;
 
	}
 

	
 
	/* Powered wagons power - Powered wagons extra weight */
 
	if (rvi->pow_wag_power != 0) {
 
		SetDParam(0, rvi->pow_wag_power);
 
		SetDParam(1, rvi->pow_wag_weight);
 
		DrawString(left, right, y, STR_PURCHASE_INFO_PWAGPOWER_PWAGWEIGHT);
 
		y += FONT_HEIGHT_NORMAL;
 
	};
 

	
 
	return y;
 
}
 

	
 
/* Draw road vehicle specific details */
 
static int DrawRoadVehPurchaseInfo(int left, int right, int y, EngineID engine_number)
 
{
 
	const Engine *e = Engine::Get(engine_number);
 

	
 
	/* Purchase cost - Max speed */
 
	SetDParam(0, e->GetCost());
 
	SetDParam(1, e->GetDisplayMaxSpeed());
 
	DrawString(left, right, y, STR_PURCHASE_INFO_COST_SPEED);
 
	y += FONT_HEIGHT_NORMAL;
 

	
 
	/* Running cost */
 
	SetDParam(0, e->GetRunningCost());
 
	DrawString(left, right, y, STR_PURCHASE_INFO_RUNNINGCOST);
 
	y += FONT_HEIGHT_NORMAL;
 

	
 
	return y;
 
}
 

	
 
/* Draw ship specific details */
 
static int DrawShipPurchaseInfo(int left, int right, int y, EngineID engine_number, bool refittable)
 
{
 
	const Engine *e = Engine::Get(engine_number);
 

	
 
	/* Purchase cost - Max speed */
 
	SetDParam(0, e->GetCost());
 
	SetDParam(1, e->GetDisplayMaxSpeed());
 
	DrawString(left, right, y, STR_PURCHASE_INFO_COST_SPEED);
 
	y += FONT_HEIGHT_NORMAL;
 

	
 
	/* Cargo type + capacity */
 
	SetDParam(0, e->GetDefaultCargoType());
 
	SetDParam(1, e->GetDisplayDefaultCapacity());
 
	SetDParam(2, refittable ? STR_PURCHASE_INFO_REFITTABLE : STR_EMPTY);
 
	DrawString(left, right, y, STR_PURCHASE_INFO_CAPACITY);
 
	y += FONT_HEIGHT_NORMAL;
 

	
 
	/* Running cost */
 
	SetDParam(0, e->GetRunningCost());
 
	DrawString(left, right, y, STR_PURCHASE_INFO_RUNNINGCOST);
 
	y += FONT_HEIGHT_NORMAL;
 

	
 
	return y;
 
}
 

	
 
/* Draw aircraft specific details */
 
static int DrawAircraftPurchaseInfo(int left, int right, int y, EngineID engine_number, bool refittable)
 
{
 
	const Engine *e = Engine::Get(engine_number);
 
	CargoID cargo = e->GetDefaultCargoType();
 

	
 
	/* Purchase cost - Max speed */
 
	SetDParam(0, e->GetCost());
 
	SetDParam(1, e->GetDisplayMaxSpeed());
 
	DrawString(left, right, y, STR_PURCHASE_INFO_COST_SPEED);
 
	y += FONT_HEIGHT_NORMAL;
 

	
 
	/* Cargo capacity */
 
	uint16 mail_capacity;
 
	uint capacity = e->GetDisplayDefaultCapacity(&mail_capacity);
 
	if (mail_capacity > 0) {
 
		SetDParam(0, cargo);
 
		SetDParam(1, capacity);
 
		SetDParam(2, CT_MAIL);
 
		SetDParam(3, mail_capacity);
 
		DrawString(left, right, y, STR_PURCHASE_INFO_AIRCRAFT_CAPACITY);
 
	} else {
 
		/* Note, if the default capacity is selected by the refit capacity
 
		 * callback, then the capacity shown is likely to be incorrect. */
 
		SetDParam(0, cargo);
 
		SetDParam(1, capacity);
 
		SetDParam(2, refittable ? STR_PURCHASE_INFO_REFITTABLE : STR_EMPTY);
 
		DrawString(left, right, y, STR_PURCHASE_INFO_CAPACITY);
 
	}
 
	y += FONT_HEIGHT_NORMAL;
 

	
 
	/* Running cost */
 
	SetDParam(0, e->GetRunningCost());
 
	DrawString(left, right, y, STR_PURCHASE_INFO_RUNNINGCOST);
 
	y += FONT_HEIGHT_NORMAL;
 

	
 
	return y;
 
}
 

	
 
/**
 
 * Display additional text from NewGRF in the purchase information window
 
 * @param left   Left border of text bounding box
 
 * @param right  Right border of text bounding box
 
 * @param y      Top border of text bounding box
 
 * @param engine Engine to query the additional purchase information for
 
 * @return       Bottom border of text bounding box
 
 */
 
static uint ShowAdditionalText(int left, int right, int y, EngineID engine)
 
{
 
	uint16 callback = GetVehicleCallback(CBID_VEHICLE_ADDITIONAL_TEXT, 0, 0, engine, NULL);
 
	if (callback == CALLBACK_FAILED) return y;
 

	
 
	/* STR_BLACK_STRING is used to start the string with {BLACK} */
 
	SetDParam(0, GetGRFStringID(GetEngineGRFID(engine), 0xD000 + callback));
 
	PrepareTextRefStackUsage(0);
 
	uint result = DrawStringMultiLine(left, right, y, INT32_MAX, STR_BLACK_STRING);
 
	StopTextRefStackUsage();
 
	return result;
 
}
 

	
 
/**
 
 * Draw the purchase info details of a vehicle at a given location.
 
 * @param left,right,y location where to draw the info
 
 * @param engine_number the engine of which to draw the info of
 
 * @return y after drawing all the text
 
 */
 
int DrawVehiclePurchaseInfo(int left, int right, int y, EngineID engine_number)
 
{
 
	const Engine *e = Engine::Get(engine_number);
 
	YearMonthDay ymd;
 
	ConvertDateToYMD(e->intro_date, &ymd);
 
	bool refittable = IsArticulatedVehicleRefittable(engine_number);
 
	bool articulated_cargo = false;
 

	
 
	switch (e->type) {
 
		default: NOT_REACHED();
 
		case VEH_TRAIN:
 
			if (e->u.rail.railveh_type == RAILVEH_WAGON) {
 
				y = DrawRailWagonPurchaseInfo(left, right, y, engine_number, &e->u.rail);
 
			} else {
 
				y = DrawRailEnginePurchaseInfo(left, right, y, engine_number, &e->u.rail);
 
			}
 
			articulated_cargo = true;
 
			break;
 

	
 
		case VEH_ROAD:
 
			y = DrawRoadVehPurchaseInfo(left, right, y, engine_number);
 
			articulated_cargo = true;
 
			break;
 

	
 
		case VEH_SHIP:
 
			y = DrawShipPurchaseInfo(left, right, y, engine_number, refittable);
 
			break;
 

	
 
		case VEH_AIRCRAFT:
 
			y = DrawAircraftPurchaseInfo(left, right, y, engine_number, refittable);
 
			break;
 
	}
 

	
 
	if (articulated_cargo) {
 
		/* Cargo type + capacity, or N/A */
 
		int new_y = DrawCargoCapacityInfo(left, right, y, engine_number, refittable);
 

	
 
		if (new_y == y) {
 
			SetDParam(0, CT_INVALID);
 
			SetDParam(2, STR_EMPTY);
 
			DrawString(left, right, y, STR_PURCHASE_INFO_CAPACITY);
 
			y += FONT_HEIGHT_NORMAL;
 
		} else {
 
			y = new_y;
 
		}
 
	}
 

	
 
	/* Draw details, that applies to all types except rail wagons */
 
	if (e->type != VEH_TRAIN || e->u.rail.railveh_type != RAILVEH_WAGON) {
 
		/* Design date - Life length */
 
		SetDParam(0, ymd.year);
 
		SetDParam(1, e->GetLifeLengthInDays() / DAYS_IN_LEAP_YEAR);
 
		DrawString(left, right, y, STR_PURCHASE_INFO_DESIGNED_LIFE);
 
		y += FONT_HEIGHT_NORMAL;
 

	
 
		/* Reliability */
 
		SetDParam(0, ToPercent16(e->reliability));
 
		DrawString(left, right, y, STR_PURCHASE_INFO_RELIABILITY);
 
		y += FONT_HEIGHT_NORMAL;
 
	}
 

	
 
	/* Additional text from NewGRF */
 
	y = ShowAdditionalText(left, right, y, engine_number);
 
	if (refittable) y = ShowRefitOptionsList(left, right, y, engine_number);
 

	
 
	return y;
 
}
 

	
 
/** Engine drawing loop
 
 * @param type Type of vehicle (VEH_*)
 
 * @param l The left most location of the list
 
 * @param r The right most location of the list
 
 * @param y The top most location of teh list
 
 * @param eng_list What engines to draw
 
 * @param min where to start in the list
 
 * @param max where in the list to end
 
 * @param selected_id what engine to highlight as selected, if any
 
 * @param show_count Whether to show the amount of engines or not
 
 * @param selected_group the group to list the engines of
 
 */
 
void DrawEngineList(VehicleType type, int l, int r, int y, const GUIEngineList *eng_list, uint16 min, uint16 max, EngineID selected_id, bool show_count, GroupID selected_group)
 
{
 
	static const int sprite_widths[]  = { 60, 60, 76, 67 };
 
	static const int sprite_y_offsets[] = { -1, -1, -2, -2 };
 

	
 
	/* Obligatory sanity checks! */
 
	assert((uint)type < lengthof(sprite_widths));
 
	assert_compile(lengthof(sprite_y_offsets) == lengthof(sprite_widths));
 
	assert(max <= eng_list->Length());
 

	
 
	bool rtl = _dynlang.text_dir == TD_RTL;
 
	int step_size = GetEngineListHeight(type);
 
	int sprite_width = sprite_widths[type];
 

	
 
	int sprite_x        = (rtl ? r - sprite_width / 2 : l + sprite_width / 2) - 1;
 
	int sprite_y_offset = sprite_y_offsets[type] + step_size / 2;
 

	
 
	int text_left  = l + (rtl ? WD_FRAMERECT_LEFT : sprite_width);
 
	int text_right = r - (rtl ? sprite_width : WD_FRAMERECT_RIGHT);
 

	
 
	int normal_text_y_offset = (step_size - FONT_HEIGHT_NORMAL) / 2;
 
	int small_text_y_offset  = step_size - FONT_HEIGHT_SMALL - WD_FRAMERECT_BOTTOM - 1;
 

	
 
	for (; min < max; min++, y += step_size) {
 
		const EngineID engine = (*eng_list)[min];
 
		/* Note: num_engines is only used in the autoreplace GUI, so it is correct to use _local_company here. */
 
		const uint num_engines = GetGroupNumEngines(_local_company, selected_group, engine);
 

	
 
		SetDParam(0, engine);
 
		DrawString(text_left, text_right, y + normal_text_y_offset, STR_ENGINE_NAME, engine == selected_id ? TC_WHITE : TC_BLACK);
 
		DrawVehicleEngine(l, r, sprite_x, y + sprite_y_offset, engine, (show_count && num_engines == 0) ? PALETTE_CRASH : GetEnginePalette(engine, _local_company));
 
		if (show_count) {
 
			SetDParam(0, num_engines);
 
			DrawString(text_left, text_right, y + small_text_y_offset, STR_TINY_BLACK_COMA, TC_FROMSTRING, SA_RIGHT);
 
		}
 
	}
 
}
 

	
 

	
 
struct BuildVehicleWindow : Window {
 
	VehicleType vehicle_type;
 
	union {
 
		RailTypeByte railtype;
 
		AirportFTAClass::Flags flags;
 
		RoadTypes roadtypes;
 
	} filter;
 
	bool descending_sort_order;
 
	byte sort_criteria;
 
	bool listview_mode;
 
	EngineID sel_engine;
 
	EngineID rename_engine;
 
	GUIEngineList eng_list;
 
	CargoID cargo_filter[NUM_CARGO + 2];        ///< Available cargo filters; CargoID or CF_ANY or CF_NONE
 
	StringID cargo_filter_texts[NUM_CARGO + 3]; ///< Texts for filter_cargo, terminated by INVALID_STRING_ID
 
	byte cargo_filter_criteria;                 ///< Selected cargo filter
 
	int details_height;                         ///< Minimal needed height of the details panels (found so far).
 

	
 
	BuildVehicleWindow(const WindowDesc *desc, TileIndex tile, VehicleType type) : Window()
 
	{
 
		this->vehicle_type = type;
 
		this->window_number = tile == INVALID_TILE ? (int)type : tile;
 

	
 
		this->owner = (tile != INVALID_TILE) ? GetTileOwner(tile) : _local_company;
 

	
 
		this->sel_engine      = INVALID_ENGINE;
 

	
 
		this->sort_criteria         = _last_sort_criteria[type];
 
		this->descending_sort_order = _last_sort_order[type];
 
		this->cargo_filter_criteria = _last_filter_criteria[type];
 

	
 
		/* Populate filter list */
 
		uint filter_items = 0;
 

	
 
		/* Add item for disabling filtering */
 
		this->cargo_filter[filter_items] = CF_ANY;
 
		this->cargo_filter_texts[filter_items] = STR_PURCHASE_INFO_ALL_TYPES;
 
		filter_items++;
 

	
 
		/* Add item for vehicles not carrying anything, e.g. train engines.
 
		 * This could also be useful for eyecandy vehicles of other types, but is likely too confusing for joe, */
 
		if (type == VEH_TRAIN) {
 
			this->cargo_filter[filter_items] = CF_NONE;
 
			this->cargo_filter_texts[filter_items] = STR_LAND_AREA_INFORMATION_LOCAL_AUTHORITY_NONE;
 
			filter_items++;
 
		}
 

	
 
		/* Collect available cargo types for filtering */
 
		const CargoSpec *cargo;
 
		FOR_ALL_CARGOSPECS(cargo) {
 
			if (IsCargoInClass(cargo->Index(), CC_SPECIAL)) continue; // exclude fake cargo types
 
			this->cargo_filter[filter_items] = cargo->Index();
 
			this->cargo_filter_texts[filter_items] = cargo->name;
 
			filter_items++;
 
		}
 

	
 
		this->cargo_filter_texts[filter_items] = INVALID_STRING_ID;
 
		if (this->cargo_filter_criteria >= filter_items) this->cargo_filter_criteria = 0;
 

	
 
		this->eng_list.SetFilterFuncs(_filter_funcs);
 
		this->eng_list.SetFilterState(this->cargo_filter[this->cargo_filter_criteria] != CF_ANY);
 

	
 
		switch (type) {
 
			default: NOT_REACHED();
 
			case VEH_TRAIN:
 
				this->filter.railtype = (tile == INVALID_TILE) ? RAILTYPE_END : GetRailType(tile);
 
				break;
 
			case VEH_ROAD:
 
				this->filter.roadtypes = (tile == INVALID_TILE) ? ROADTYPES_ALL : GetRoadTypes(tile);
 
			case VEH_SHIP:
 
				break;
 
			case VEH_AIRCRAFT:
 
				this->filter.flags =
 
					tile == INVALID_TILE ? AirportFTAClass::ALL : Station::GetByTile(tile)->Airport()->flags;
 
				break;
 
		}
 

	
 
		this->listview_mode = (this->window_number <= VEH_END);
 

	
 
		this->CreateNestedTree(desc);
 

	
 
		/* If we are just viewing the list of vehicles, we do not need the Build button.
 
		 * So we just hide it, and enlarge the Rename buton by the now vacant place. */
 
		if (this->listview_mode) this->GetWidget<NWidgetStacked>(BUILD_VEHICLE_WIDGET_BUILD_SEL)->SetDisplayedPlane(STACKED_SELECTION_ZERO_SIZE);
 

	
 
		NWidgetCore *widget = this->GetWidget<NWidgetCore>(BUILD_VEHICLE_WIDGET_LIST);
 
		widget->tool_tip = STR_BUY_VEHICLE_TRAIN_LIST_TOOLTIP + type;
 

	
 
		widget = this->GetWidget<NWidgetCore>(BUILD_VEHICLE_WIDGET_BUILD);
 
		widget->widget_data = STR_BUY_VEHICLE_TRAIN_BUY_VEHICLE_BUTTON + type;
 
		widget->tool_tip    = STR_BUY_VEHICLE_TRAIN_BUY_VEHICLE_TOOLTIP + type;
 

	
 
		widget = this->GetWidget<NWidgetCore>(BUILD_VEHICLE_WIDGET_RENAME);
 
		widget->widget_data = STR_BUY_VEHICLE_TRAIN_RENAME_BUTTON + type;
 
		widget->tool_tip    = STR_BUY_VEHICLE_TRAIN_RENAME_TOOLTIP + type;
 

	
 
		this->details_height = ((this->vehicle_type == VEH_TRAIN) ? 10 : 9) * FONT_HEIGHT_NORMAL + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
 

	
 
		this->FinishInitNested(desc, tile == INVALID_TILE ? (int)type : tile);
 

	
 
		this->eng_list.ForceRebuild();
 
		this->GenerateBuildList(); // generate the list, since we need it in the next line
 
		/* Select the first engine in the list as default when opening the window */
 
		if (this->eng_list.Length() > 0) this->sel_engine = this->eng_list[0];
 
	}
 

	
 
	/** Filter the engine list against the currently selected cargo filter */
 
	void FilterEngineList()
 
	{
 
		this->eng_list.Filter(this->cargo_filter[this->cargo_filter_criteria]);
 
		if (0 == this->eng_list.Length()) { // no engine passed through the filter, invalidate the previously selected engine
 
			this->sel_engine = INVALID_ENGINE;
 
		} else if (!this->eng_list.Contains(this->sel_engine)) { // previously selected engine didn't pass the filter, select the first engine of the list
 
			this->sel_engine = this->eng_list[0];
 
		}
 
	}
 

	
 
	/** Filter a single engine */
 
	bool FilterSingleEngine(EngineID eid)
 
	{
 
		CargoID filter_type = this->cargo_filter[this->cargo_filter_criteria];
 
		return (filter_type == CF_ANY || CargoFilter(&eid, filter_type));
 
	}
src/company_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file company_gui.cpp Company related GUIs. */
 

	
 
#include "stdafx.h"
 
#include "gui.h"
 
#include "window_gui.h"
 
#include "textbuf_gui.h"
 
#include "viewport_func.h"
 
#include "gfx_func.h"
 
#include "company_func.h"
 
#include "command_func.h"
 
#include "network/network.h"
 
#include "network/network_gui.h"
 
#include "network/network_func.h"
 
#include "sprite.h"
 
#include "economy_func.h"
 
#include "vehicle_base.h"
 
#include "newgrf.h"
 
#include "company_manager_face.h"
 
#include "strings_func.h"
 
#include "date_func.h"
 
#include "widgets/dropdown_type.h"
 
#include "tilehighlight_func.h"
 

	
 
#include "table/strings.h"
 

	
 
/** Company GUI constants. */
 
enum {
 
	FIRST_GUI_CALL = INT_MAX,  ///< default value to specify this is the first call of the resizable gui
 

	
 
	EXP_LINESPACE  = 2,        ///< Amount of vertical space for a horizontal (sub-)total line.
 
	EXP_BLOCKSPACE = 10,       ///< Amount of vertical space between two blocks of numbers.
 
};
 

	
 
static void DoSelectCompanyManagerFace(Window *parent, bool show_big, int top =  FIRST_GUI_CALL, int left = FIRST_GUI_CALL);
 

	
 
/** Standard unsorted list of expenses. */
 
static ExpensesType _expenses_list_1[] = {
 
	EXPENSES_CONSTRUCTION,
 
	EXPENSES_NEW_VEHICLES,
 
	EXPENSES_TRAIN_RUN,
 
	EXPENSES_ROADVEH_RUN,
 
	EXPENSES_AIRCRAFT_RUN,
 
	EXPENSES_SHIP_RUN,
 
	EXPENSES_PROPERTY,
 
	EXPENSES_TRAIN_INC,
 
	EXPENSES_ROADVEH_INC,
 
	EXPENSES_AIRCRAFT_INC,
 
	EXPENSES_SHIP_INC,
 
	EXPENSES_LOAN_INT,
 
	EXPENSES_OTHER,
 
};
 

	
 
/** Grouped list of expenses. */
 
static ExpensesType _expenses_list_2[] = {
 
	EXPENSES_TRAIN_INC,
 
	EXPENSES_ROADVEH_INC,
 
	EXPENSES_AIRCRAFT_INC,
 
	EXPENSES_SHIP_INC,
 
	INVALID_EXPENSES,
 
	EXPENSES_TRAIN_RUN,
 
	EXPENSES_ROADVEH_RUN,
 
	EXPENSES_AIRCRAFT_RUN,
 
	EXPENSES_SHIP_RUN,
 
	EXPENSES_PROPERTY,
 
	EXPENSES_LOAN_INT,
 
	INVALID_EXPENSES,
 
	EXPENSES_CONSTRUCTION,
 
	EXPENSES_NEW_VEHICLES,
 
	EXPENSES_OTHER,
 
	INVALID_EXPENSES,
 
};
 

	
 
/** Expense list container. */
 
struct ExpensesList {
 
	const ExpensesType *et;   ///< Expenses items.
 
	const uint length;        ///< Number of items in list.
 
	const uint num_subtotals; ///< Number of sub-totals in the list.
 

	
 
	ExpensesList(ExpensesType *et, int length, int num_subtotals) : et(et), length(length), num_subtotals(num_subtotals)
 
	{
 
	}
 

	
 
	uint GetHeight() const
 
	{
 
		/* heading + line + texts of expenses + sub-totals + total line + total text */
 
		return FONT_HEIGHT_NORMAL + EXP_LINESPACE + this->length * FONT_HEIGHT_NORMAL + num_subtotals * (EXP_BLOCKSPACE + EXP_LINESPACE) + EXP_LINESPACE + FONT_HEIGHT_NORMAL;
 
	}
 

	
 
	/** Compute width of the expenses categories in pixels. */
 
	uint GetCategoriesWidth() const
 
	{
 
		uint width = 0;
 
		bool invalid_expenses_measured = false; // Measure 'Total' width only once.
 
		for (uint i = 0; i < this->length; i++) {
 
			ExpensesType et = this->et[i];
 
			if (et == INVALID_EXPENSES) {
 
				if (!invalid_expenses_measured) {
 
					width = max(width, GetStringBoundingBox(STR_FINANCES_TOTAL_CAPTION).width);
 
					invalid_expenses_measured = true;
 
				}
 
			} else {
 
				width = max(width, GetStringBoundingBox(STR_FINANCES_SECTION_CONSTRUCTION + et).width);
 
			}
 
		}
 
		return width;
 
	}
 
};
 

	
 
static const ExpensesList _expenses_list_types[] = {
 
	ExpensesList(_expenses_list_1, lengthof(_expenses_list_1), 0),
 
	ExpensesList(_expenses_list_2, lengthof(_expenses_list_2), 3),
 
};
 

	
 
/** Widgets of the company finances windows. */
 
enum CompanyFinancesWindowWidgets {
 
	CFW_CLOSEBOX = 0,  ///< Close the window
 
	CFW_CAPTION,       ///< Caption of the window
 
	CFW_TOGGLE_SIZE,   ///< Toggle windows size
 
	CFW_STICKY,        ///< Sticky button
 
	CFW_EXPS_PANEL,    ///< Panel for expenses
 
	CFW_SEL_PANEL,     ///< Select panel or nothing
 
	CFW_EXPS_CATEGORY, ///< Column for expenses category strings
 
	CFW_EXPS_PRICE1,   ///< Column for year Y-2 expenses
 
	CFW_EXPS_PRICE2,   ///< Column for year Y-1 expenses
 
	CFW_EXPS_PRICE3,   ///< Column for year Y expenses
 
	CFW_TOTAL_PANEL,   ///< Panel for totals
 
	CFW_SEL_MAXLOAN,   ///< Selection of maxloan column
 
	CFW_BALANCE_TITLE, ///< 'Bank balance' title
 
	CFW_LOAN_TITLE,    ///< 'Loan' title
 
	CFW_BALANCE_VALUE, ///< Bank balance value
 
	CFW_LOAN_VALUE,    ///< Loan
 
	CFW_LOAN_LINE,     ///< Line for summing bank balance and loan
 
	CFW_TOTAL_VALUE,   ///< Total
 
	CFW_MAXLOAN_GAP,   ///< Gap above max loan widget
 
	CFW_MAXLOAN_VALUE, ///< Max loan widget
 
	CFW_SEL_BUTTONS,   ///< Selection of buttons
 
	CFW_INCREASE_LOAN, ///< Increase loan
 
	CFW_REPAY_LOAN,    ///< Decrease loan
 
};
 

	
 
/** Draw the expenses categories.
 
 * @param r Available space for drawing.
 
 * @note The environment must provide padding at the left and right of \a r.
 
 */
 
static void DrawCategories(const Rect &r)
 
{
 
	int y = r.top;
 

	
 
	DrawString(r.left, r.right, y, STR_FINANCES_EXPENDITURE_INCOME_TITLE, TC_FROMSTRING, SA_CENTER, true);
 
	y += FONT_HEIGHT_NORMAL + EXP_LINESPACE;
 

	
 
	int type = _settings_client.gui.expenses_layout;
 
	for (uint i = 0; i < _expenses_list_types[type].length; i++) {
 
		const ExpensesType et = _expenses_list_types[type].et[i];
 
		if (et == INVALID_EXPENSES) {
 
			y += EXP_LINESPACE;
 
			DrawString(r.left, r.right, y, STR_FINANCES_TOTAL_CAPTION, TC_FROMSTRING, SA_RIGHT);
 
			y += FONT_HEIGHT_NORMAL + EXP_BLOCKSPACE;
 
		} else {
 
			DrawString(r.left, r.right, y, STR_FINANCES_SECTION_CONSTRUCTION + et);
 
			y += FONT_HEIGHT_NORMAL;
 
		}
 
	}
 

	
 
	DrawString(r.left, r.right, y + EXP_LINESPACE, STR_FINANCES_TOTAL_CAPTION, TC_FROMSTRING, SA_RIGHT);
 
}
 

	
 
/** Draw an amount of money.
 
 * @param amount Amount of money to draw,
 
 * @param left   Left coordinate of the space to draw in.
 
 * @param right  Right coordinate of the space to draw in.
 
 * @param top    Top coordinate of the space to draw in.
 
 */
 
static void DrawPrice(Money amount, int left, int right, int top)
 
{
 
	StringID str = STR_FINANCES_NEGATIVE_INCOME;
 
	if (amount < 0) {
 
		amount = -amount;
 
		str++;
 
	}
 
	SetDParam(0, amount);
 
	DrawString(left, right, top, str, TC_FROMSTRING, SA_RIGHT);
 
}
 

	
 
/** Draw a column with prices.
 
 * @param r    Available space for drawing.
 
 * @param year Year being drawn.
 
 * @param tbl  Pointer to table of amounts for \a year.
 
 * @note The environment must provide padding at the left and right of \a r.
 
 */
 
static void DrawYearColumn(const Rect &r, int year, const Money (*tbl)[EXPENSES_END])
 
{
 
	int y = r.top;
 

	
 
	SetDParam(0, year);
 
	DrawString(r.left, r.right, y, STR_FINANCES_YEAR, TC_FROMSTRING, SA_RIGHT, true);
 
	y += FONT_HEIGHT_NORMAL + EXP_LINESPACE;
 

	
 
	Money sum = 0;
 
	Money subtotal = 0;
 
	int type = _settings_client.gui.expenses_layout;
 
	for (uint i = 0; i < _expenses_list_types[type].length; i++) {
 
		const ExpensesType et = _expenses_list_types[type].et[i];
 
		if (et == INVALID_EXPENSES) {
 
			Money cost = subtotal;
 
			subtotal = 0;
 
			GfxFillRect(r.left, y, r.right, y, 215);
 
			y += EXP_LINESPACE;
 
			DrawPrice(cost, r.left, r.right, y);
 
			y += FONT_HEIGHT_NORMAL + EXP_BLOCKSPACE;
 
		} else {
 
			Money cost = (*tbl)[et];
 
			subtotal += cost;
 
			sum += cost;
 
			if (cost != 0) DrawPrice(cost, r.left, r.right, y);
 
			y += FONT_HEIGHT_NORMAL;
 
		}
 
	}
 

	
 
	GfxFillRect(r.left, y, r.right, y, 215);
 
	y += EXP_LINESPACE;
 
	DrawPrice(sum, r.left, r.right, y);
 
}
 

	
 
static const NWidgetPart _nested_company_finances_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, CFW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, CFW_CAPTION), SetDataTip(STR_FINANCES_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_IMGBTN, COLOUR_GREY, CFW_TOGGLE_SIZE), SetDataTip(SPR_LARGE_SMALL_WINDOW, STR_TOOLTIP_TOGGLE_LARGE_SMALL_WINDOW),
 
		NWidget(WWT_STICKYBOX, COLOUR_GREY, CFW_STICKY),
 
	EndContainer(),
 
	NWidget(NWID_SELECTION, INVALID_COLOUR, CFW_SEL_PANEL),
 
		NWidget(WWT_PANEL, COLOUR_GREY, CFW_EXPS_PANEL),
 
			NWidget(NWID_HORIZONTAL), SetPadding(WD_FRAMERECT_TOP, WD_FRAMERECT_RIGHT, WD_FRAMERECT_BOTTOM, WD_FRAMERECT_LEFT), SetPIP(0, 9, 0),
 
				NWidget(WWT_EMPTY, COLOUR_GREY, CFW_EXPS_CATEGORY), SetMinimalSize(120, 0), SetFill(false, false),
 
				NWidget(WWT_EMPTY, COLOUR_GREY, CFW_EXPS_PRICE1), SetMinimalSize(86, 0), SetFill(false, false),
 
				NWidget(WWT_EMPTY, COLOUR_GREY, CFW_EXPS_PRICE2), SetMinimalSize(86, 0), SetFill(false, false),
 
				NWidget(WWT_EMPTY, COLOUR_GREY, CFW_EXPS_PRICE3), SetMinimalSize(86, 0), SetFill(false, false),
 
				NWidget(WWT_EMPTY, COLOUR_GREY, CFW_EXPS_CATEGORY), SetMinimalSize(120, 0), SetFill(0, 0),
 
				NWidget(WWT_EMPTY, COLOUR_GREY, CFW_EXPS_PRICE1), SetMinimalSize(86, 0), SetFill(0, 0),
 
				NWidget(WWT_EMPTY, COLOUR_GREY, CFW_EXPS_PRICE2), SetMinimalSize(86, 0), SetFill(0, 0),
 
				NWidget(WWT_EMPTY, COLOUR_GREY, CFW_EXPS_PRICE3), SetMinimalSize(86, 0), SetFill(0, 0),
 
			EndContainer(),
 
		EndContainer(),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, CFW_TOTAL_PANEL),
 
		NWidget(NWID_HORIZONTAL), SetPadding(WD_FRAMERECT_TOP, WD_FRAMERECT_RIGHT, WD_FRAMERECT_BOTTOM, WD_FRAMERECT_LEFT),
 
			NWidget(NWID_VERTICAL), // Vertical column with 'bank balance', 'loan'
 
				NWidget(WWT_TEXT, COLOUR_GREY, CFW_BALANCE_TITLE), SetDataTip(STR_FINANCES_BANK_BALANCE_TITLE, STR_NULL), SetFill(true, false),
 
				NWidget(WWT_TEXT, COLOUR_GREY, CFW_LOAN_TITLE), SetDataTip(STR_FINANCES_LOAN_TITLE, STR_NULL), SetFill(true, false),
 
				NWidget(NWID_SPACER), SetFill(false, true),
 
				NWidget(WWT_TEXT, COLOUR_GREY, CFW_BALANCE_TITLE), SetDataTip(STR_FINANCES_BANK_BALANCE_TITLE, STR_NULL), SetFill(1, 0),
 
				NWidget(WWT_TEXT, COLOUR_GREY, CFW_LOAN_TITLE), SetDataTip(STR_FINANCES_LOAN_TITLE, STR_NULL), SetFill(1, 0),
 
				NWidget(NWID_SPACER), SetFill(0, 1),
 
			EndContainer(),
 
			NWidget(NWID_SPACER), SetFill(false, false), SetMinimalSize(30, 0),
 
			NWidget(NWID_SPACER), SetFill(0, 0), SetMinimalSize(30, 0),
 
			NWidget(NWID_VERTICAL), // Vertical column with bank balance amount, loan amount, and total.
 
				NWidget(WWT_TEXT, COLOUR_GREY, CFW_BALANCE_VALUE), SetDataTip(STR_NULL, STR_NULL),
 
				NWidget(WWT_TEXT, COLOUR_GREY, CFW_LOAN_VALUE), SetDataTip(STR_NULL, STR_NULL),
 
				NWidget(WWT_EMPTY, COLOUR_GREY, CFW_LOAN_LINE), SetMinimalSize(0, 2), SetFill(true, false),
 
				NWidget(WWT_EMPTY, COLOUR_GREY, CFW_LOAN_LINE), SetMinimalSize(0, 2), SetFill(1, 0),
 
				NWidget(WWT_TEXT, COLOUR_GREY, CFW_TOTAL_VALUE), SetDataTip(STR_NULL, STR_NULL),
 
			EndContainer(),
 
			NWidget(NWID_SELECTION, INVALID_COLOUR, CFW_SEL_MAXLOAN),
 
				NWidget(NWID_HORIZONTAL),
 
					NWidget(NWID_SPACER), SetFill(false, true), SetMinimalSize(25, 0),
 
					NWidget(NWID_SPACER), SetFill(0, 1), SetMinimalSize(25, 0),
 
					NWidget(NWID_VERTICAL), // Max loan information
 
						NWidget(WWT_EMPTY, COLOUR_GREY, CFW_MAXLOAN_GAP), SetFill(false, false),
 
						NWidget(WWT_EMPTY, COLOUR_GREY, CFW_MAXLOAN_GAP), SetFill(0, 0),
 
						NWidget(WWT_TEXT, COLOUR_GREY, CFW_MAXLOAN_VALUE), SetDataTip(STR_FINANCES_MAX_LOAN, STR_NULL),
 
						NWidget(NWID_SPACER), SetFill(false, true),
 
						NWidget(NWID_SPACER), SetFill(0, 1),
 
					EndContainer(),
 
				EndContainer(),
 
			EndContainer(),
 
			NWidget(NWID_SPACER), SetFill(true, true),
 
			NWidget(NWID_SPACER), SetFill(1, 1),
 
		EndContainer(),
 
	EndContainer(),
 
	NWidget(NWID_SELECTION, INVALID_COLOUR, CFW_SEL_BUTTONS),
 
		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, CFW_INCREASE_LOAN), SetFill(true, false), SetDataTip(STR_FINANCES_BORROW_BUTTON, STR_FINANCES_BORROW_TOOLTIP),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, CFW_REPAY_LOAN), SetFill(true, false), SetDataTip(STR_FINANCES_REPAY_BUTTON, STR_FINANCES_REPAY_TOOLTIP),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, CFW_INCREASE_LOAN), SetFill(1, 0), SetDataTip(STR_FINANCES_BORROW_BUTTON, STR_FINANCES_BORROW_TOOLTIP),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, CFW_REPAY_LOAN), SetFill(1, 0), SetDataTip(STR_FINANCES_REPAY_BUTTON, STR_FINANCES_REPAY_TOOLTIP),
 
		EndContainer(),
 
	EndContainer(),
 
};
 

	
 
/** Window class displaying the company finances.
 
 * @todo #money_width should be calculated dynamically.
 
 */
 
struct CompanyFinancesWindow : Window {
 
	bool small;       ///< Window is toggled to 'small'.
 
	uint money_width; ///< Width needed for displaying all amounts.
 

	
 
	CompanyFinancesWindow(const WindowDesc *desc, CompanyID company) : Window()
 
	{
 
		this->small = false;
 
		this->money_width = 86;
 
		this->CreateNestedTree(desc);
 
		this->SetupWidgets();
 
		this->FinishInitNested(desc, company);
 

	
 
		this->owner = (Owner)this->window_number;
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		switch (widget) {
 
			case CFW_CAPTION:
 
				SetDParam(0, (CompanyID)this->window_number);
 
				SetDParam(1, (CompanyID)this->window_number);
 
				break;
 

	
 
			case CFW_MAXLOAN_VALUE:
 
				SetDParam(0, _economy.max_loan);
 
				break;
 

	
 
			case CFW_INCREASE_LOAN:
 
			case CFW_REPAY_LOAN:
 
				SetDParam(2, LOAN_INTERVAL);
 
				break;
 
		}
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		int type = _settings_client.gui.expenses_layout;
 
		switch (widget) {
 
			case CFW_EXPS_CATEGORY:
 
				size->width  = _expenses_list_types[type].GetCategoriesWidth();
 
				size->height = _expenses_list_types[type].GetHeight();
 
				break;
 

	
 
			case CFW_EXPS_PRICE1:
 
			case CFW_EXPS_PRICE2:
 
			case CFW_EXPS_PRICE3:
 
				size->width  = this->money_width;
 
				size->height = _expenses_list_types[type].GetHeight();
 
				break;
 

	
 
			case CFW_BALANCE_VALUE:
 
			case CFW_LOAN_VALUE:
 
			case CFW_TOTAL_VALUE:
 
				size->width  = this->money_width + padding.width;
 
				break;
 

	
 
			case CFW_MAXLOAN_GAP:
 
				size->height = FONT_HEIGHT_NORMAL;
 
				break;
 
		}
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case CFW_EXPS_CATEGORY:
 
				DrawCategories(r);
 
				break;
 

	
 
			case CFW_EXPS_PRICE1:
 
			case CFW_EXPS_PRICE2:
 
			case CFW_EXPS_PRICE3: {
 
				const Company *c = Company::Get((CompanyID)this->window_number);
 
				int age = min(_cur_year - c->inaugurated_year, 2);
 
				int wid_offset = widget - CFW_EXPS_PRICE1;
 
				if (wid_offset <= age) {
 
					DrawYearColumn(r, _cur_year - (age - wid_offset), c->yearly_expenses + (age - wid_offset));
 
				}
 
				break;
 
			}
 

	
 
			case CFW_BALANCE_VALUE: {
 
				const Company *c = Company::Get((CompanyID)this->window_number);
 
				SetDParam(0, c->money);
 
				DrawString(r.left, r.right, r.top, STR_FINANCES_TOTAL_CURRENCY, TC_FROMSTRING, SA_RIGHT);
 
				break;
 
			}
 

	
 
			case CFW_LOAN_VALUE: {
 
				const Company *c = Company::Get((CompanyID)this->window_number);
 
				SetDParam(0, c->current_loan);
 
				DrawString(r.left, r.right, r.top, STR_FINANCES_TOTAL_CURRENCY, TC_FROMSTRING, SA_RIGHT);
 
				break;
 
			}
 

	
 
			case CFW_TOTAL_VALUE: {
 
				const Company *c = Company::Get((CompanyID)this->window_number);
 
				SetDParam(0, c->money - c->current_loan);
 
				DrawString(r.left, r.right, r.top, STR_FINANCES_TOTAL_CURRENCY, TC_FROMSTRING, SA_RIGHT);
 
				break;
 
			}
 

	
 
			case CFW_LOAN_LINE:
 
				GfxFillRect(r.left, r.top, r.right, r.top, 215);
 
				break;
 
		}
 
	}
 

	
 
	/** Setup the widgets in the nested tree, such that the finances window is displayed properly.
 
	 * @note After setup, the window must be (re-)initialized.
 
	 */
 
	void SetupWidgets()
 
	{
 
		int plane = small ? STACKED_SELECTION_ZERO_SIZE : 0;
 
		this->GetWidget<NWidgetStacked>(CFW_SEL_PANEL)->SetDisplayedPlane(plane);
 
		this->GetWidget<NWidgetStacked>(CFW_SEL_MAXLOAN)->SetDisplayedPlane(plane);
 

	
 
		CompanyID company = (CompanyID)this->window_number;
 
		plane = (company != _local_company) ? STACKED_SELECTION_ZERO_SIZE : 0;
 
		this->GetWidget<NWidgetStacked>(CFW_SEL_BUTTONS)->SetDisplayedPlane(plane);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		if (!small) {
 
			/* Check that the expenses panel height matches the height needed for the layout. */
 
			int type = _settings_client.gui.expenses_layout;
 
			if (_expenses_list_types[type].GetHeight() != this->GetWidget<NWidgetBase>(CFW_EXPS_CATEGORY)->current_y) {
 
				this->SetupWidgets();
 
				this->ReInit();
 
				return;
 
			}
 
		}
 

	
 
		/* Check that the loan buttons are shown only when the user owns the company. */
 
		CompanyID company = (CompanyID)this->window_number;
 
		int req_plane = (company != _local_company) ? STACKED_SELECTION_ZERO_SIZE : 0;
 
		if (req_plane != this->GetWidget<NWidgetStacked>(CFW_SEL_BUTTONS)->shown_plane) {
 
			this->SetupWidgets();
 
			this->ReInit();
 
			return;
 
		}
 

	
 
		const Company *c = Company::Get(company);
 
		this->SetWidgetDisabledState(CFW_INCREASE_LOAN, c->current_loan == _economy.max_loan); // Borrow button only shows when there is any more money to loan.
 
		this->SetWidgetDisabledState(CFW_REPAY_LOAN, company != _local_company || c->current_loan == 0); // Repay button only shows when there is any more money to repay.
 

	
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case CFW_TOGGLE_SIZE: // toggle size
 
				this->small = !this->small;
 
				this->SetupWidgets();
 
				this->ReInit();
 
				break;
 

	
 
			case CFW_INCREASE_LOAN: // increase loan
 
				DoCommandP(0, 0, _ctrl_pressed, CMD_INCREASE_LOAN | CMD_MSG(STR_ERROR_CAN_T_BORROW_ANY_MORE_MONEY));
 
				break;
 

	
 
			case CFW_REPAY_LOAN: // repay loan
 
				DoCommandP(0, 0, _ctrl_pressed, CMD_DECREASE_LOAN | CMD_MSG(STR_ERROR_CAN_T_REPAY_LOAN));
 
				break;
 
		}
 
	}
 
};
 

	
 
static const WindowDesc _company_finances_desc(
 
	WDP_AUTO, WDP_AUTO, 0, 0,
 
	WC_FINANCES, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON,
 
	_nested_company_finances_widgets, lengthof(_nested_company_finances_widgets)
 
);
 

	
 
/** Open the finances window of a company.
 
 * @param company Company to show finances of.
 
 * @pre is company a valid company.
 
 */
 
void ShowCompanyFinances(CompanyID company)
 
{
 
	if (!Company::IsValidID(company)) return;
 
	if (BringWindowToFrontById(WC_FINANCES, company)) return;
 

	
 
	new CompanyFinancesWindow(&_company_finances_desc, company);
 
}
 

	
 
/* List of colours for the livery window */
 
static const StringID _colour_dropdown[] = {
 
	STR_COLOUR_DARK_BLUE,
 
	STR_COLOUR_PALE_GREEN,
 
	STR_COLOUR_PINK,
 
	STR_COLOUR_YELLOW,
 
	STR_COLOUR_RED,
 
	STR_COLOUR_LIGHT_BLUE,
 
	STR_COLOUR_GREEN,
 
	STR_COLOUR_DARK_GREEN,
 
	STR_COLOUR_BLUE,
 
	STR_COLOUR_CREAM,
 
	STR_COLOUR_MAUVE,
 
	STR_COLOUR_PURPLE,
 
	STR_COLOUR_ORANGE,
 
	STR_COLOUR_BROWN,
 
	STR_COLOUR_GREY,
 
	STR_COLOUR_WHITE,
 
};
 

	
 
/* Association of liveries to livery classes */
 
static const LiveryClass _livery_class[LS_END] = {
 
	LC_OTHER,
 
	LC_RAIL, LC_RAIL, LC_RAIL, LC_RAIL, LC_RAIL, LC_RAIL, LC_RAIL, LC_RAIL, LC_RAIL, LC_RAIL, LC_RAIL, LC_RAIL, LC_RAIL,
 
	LC_ROAD, LC_ROAD,
 
	LC_SHIP, LC_SHIP,
 
	LC_AIRCRAFT, LC_AIRCRAFT, LC_AIRCRAFT,
 
	LC_ROAD, LC_ROAD,
 
};
 

	
 
class DropDownListColourItem : public DropDownListItem {
 
public:
 
	DropDownListColourItem(int result, bool masked) : DropDownListItem(result, masked) {}
 

	
 
	virtual ~DropDownListColourItem() {}
 

	
 
	StringID String() const
 
	{
 
		return _colour_dropdown[this->result];
 
	}
 

	
 
	uint Height(uint width) const
 
	{
 
		return max(FONT_HEIGHT_NORMAL, (byte)14);
 
	}
 

	
 
	bool Selectable() const
 
	{
 
		return true;
 
	}
 

	
 
	void Draw(int left, int right, int top, int bottom, bool sel, int bg_colour) const
 
	{
 
		bool rtl = _dynlang.text_dir == TD_RTL;
 
		DrawSprite(SPR_VEH_BUS_SIDE_VIEW, PALETTE_RECOLOUR_START + this->result, rtl ? right - 16 : left + 16, top + 7);
 
		DrawString(rtl ? left + 2 : left + 32, rtl ? right - 32 : right - 2, top + max(0, 13 - FONT_HEIGHT_NORMAL), this->String(), sel ? TC_WHITE : TC_BLACK);
 
	}
 
};
 

	
 
/** Widgets of the select company livery window. */
 
enum SelectCompanyLiveryWindowWidgets {
 
	SCLW_WIDGET_CLOSE,
 
	SCLW_WIDGET_CAPTION,
 
	SCLW_WIDGET_CLASS_GENERAL,
 
	SCLW_WIDGET_CLASS_RAIL,
 
	SCLW_WIDGET_CLASS_ROAD,
 
	SCLW_WIDGET_CLASS_SHIP,
 
	SCLW_WIDGET_CLASS_AIRCRAFT,
 
	SCLW_WIDGET_SPACER_CLASS,
 
	SCLW_WIDGET_SPACER_DROPDOWN,
 
	SCLW_WIDGET_PRI_COL_DROPDOWN,
 
	SCLW_WIDGET_SEC_COL_DROPDOWN,
 
	SCLW_WIDGET_MATRIX,
 
};
 

	
 
/** Company livery colour scheme window. */
 
struct SelectCompanyLiveryWindow : public Window {
 
private:
 
	static const uint TEXT_INDENT = 15; ///< Number of pixels to indent the text in each column in the #SCLW_WIDGET_MATRIX to make room for the (coloured) rectangles.
 
	uint32 sel;
 
	LiveryClass livery_class;
 

	
 
	void ShowColourDropDownMenu(uint32 widget)
 
	{
 
		uint32 used_colours = 0;
 
		const Livery *livery;
 
		LiveryScheme scheme;
 

	
 
		/* Disallow other company colours for the primary colour */
 
		if (HasBit(this->sel, LS_DEFAULT) && widget == SCLW_WIDGET_PRI_COL_DROPDOWN) {
 
			const Company *c;
 
			FOR_ALL_COMPANIES(c) {
 
				if (c->index != _local_company) SetBit(used_colours, c->colour);
 
			}
 
		}
 

	
 
		/* Get the first selected livery to use as the default dropdown item */
 
		for (scheme = LS_BEGIN; scheme < LS_END; scheme++) {
 
			if (HasBit(this->sel, scheme)) break;
 
		}
 
		if (scheme == LS_END) scheme = LS_DEFAULT;
 
		livery = &Company::Get((CompanyID)this->window_number)->livery[scheme];
 

	
 
		DropDownList *list = new DropDownList();
 
		for (uint i = 0; i < lengthof(_colour_dropdown); i++) {
 
			list->push_back(new DropDownListColourItem(i, HasBit(used_colours, i)));
 
		}
 

	
 
		ShowDropDownList(this, list, widget == SCLW_WIDGET_PRI_COL_DROPDOWN ? livery->colour1 : livery->colour2, widget);
 
	}
 

	
 
public:
 
	SelectCompanyLiveryWindow(const WindowDesc *desc, CompanyID company) : Window()
 
	{
 
		this->livery_class = LC_OTHER;
 
		this->sel = 1;
 
		this->InitNested(desc, company);
 
		this->owner = company;
 
		this->LowerWidget(SCLW_WIDGET_CLASS_GENERAL);
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		/* Number of liveries in each class, used to determine the height of the livery matrix widget. */
 
		static const byte livery_height[] = {
 
			1,
 
			13,
 
			4,
 
			2,
 
			3,
 
		};
 

	
 
		switch (widget) {
 
			case SCLW_WIDGET_SPACER_DROPDOWN: {
 
				/* The matrix widget below needs enough room to print all the schemes. */
 
				Dimension d = {0, 0};
 
				for (LiveryScheme scheme = LS_DEFAULT; scheme < LS_END; scheme++) {
 
					d = maxdim(d, GetStringBoundingBox(STR_LIVERY_DEFAULT + scheme));
 
				}
 
				size->width = max(size->width, TEXT_INDENT + d.width + WD_FRAMERECT_RIGHT);
 
				break;
 
			}
 

	
 
			case SCLW_WIDGET_MATRIX:
 
				size->height = livery_height[this->livery_class] * (4 + FONT_HEIGHT_NORMAL);
 
				this->GetWidget<NWidgetCore>(SCLW_WIDGET_MATRIX)->widget_data = (livery_height[this->livery_class] << MAT_ROW_START) | (1 << MAT_COL_START);
 
				break;
 

	
 
			case SCLW_WIDGET_SEC_COL_DROPDOWN:
 
				if (!_loaded_newgrf_features.has_2CC) {
 
					size->width = 0;
 
					break;
 
				}
 
				/* Fall through */
 
			case SCLW_WIDGET_PRI_COL_DROPDOWN: {
 
				for (const StringID *id = _colour_dropdown; id != endof(_colour_dropdown); id++) {
 
					size->width = max(size->width, GetStringBoundingBox(*id).width + 34);
 
				}
 
			} break;
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		/* Disable dropdown controls if no scheme is selected */
 
		this->SetWidgetDisabledState(SCLW_WIDGET_PRI_COL_DROPDOWN, this->sel == 0);
 
		this->SetWidgetDisabledState(SCLW_WIDGET_SEC_COL_DROPDOWN, this->sel == 0);
 

	
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		switch (widget) {
 
			case SCLW_WIDGET_PRI_COL_DROPDOWN:
 
			case SCLW_WIDGET_SEC_COL_DROPDOWN: {
 
				const Company *c = Company::Get((CompanyID)this->window_number);
 
				LiveryScheme scheme = LS_DEFAULT;
 

	
 
				if (this->sel != 0) {
 
					for (scheme = LS_BEGIN; scheme < LS_END; scheme++) {
 
						if (HasBit(this->sel, scheme)) break;
 
					}
 
					if (scheme == LS_END) scheme = LS_DEFAULT;
 
				}
 
				SetDParam(0, STR_COLOUR_DARK_BLUE + ((widget == SCLW_WIDGET_PRI_COL_DROPDOWN) ? c->livery[scheme].colour1 : c->livery[scheme].colour2));
 
				break;
 
			}
 
		}
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		if (widget != SCLW_WIDGET_MATRIX) return;
 

	
 
		bool rtl = _dynlang.text_dir == TD_RTL;
 

	
 
		/* Horizontal coordinates of scheme name column. */
 
		const NWidgetBase *nwi = this->GetWidget<NWidgetBase>(SCLW_WIDGET_SPACER_DROPDOWN);
 
		int sch_left = nwi->pos_x;
 
		int sch_right = sch_left + nwi->current_x - 1;
 
		/* Horizontal coordinates of first dropdown. */
 
		nwi = this->GetWidget<NWidgetBase>(SCLW_WIDGET_PRI_COL_DROPDOWN);
 
		int pri_left = nwi->pos_x;
 
		int pri_right = pri_left + nwi->current_x - 1;
 
		/* Horizontal coordinates of second dropdown. */
 
		nwi = this->GetWidget<NWidgetBase>(SCLW_WIDGET_SEC_COL_DROPDOWN);
 
		int sec_left = nwi->pos_x;
 
		int sec_right = sec_left + nwi->current_x - 1;
 

	
 
		int text_left  = (rtl ? (uint)WD_FRAMERECT_LEFT : TEXT_INDENT);
 
		int text_right = (rtl ? TEXT_INDENT : (uint)WD_FRAMERECT_RIGHT);
 

	
 
		int y = r.top + 3;
 
		const Company *c = Company::Get((CompanyID)this->window_number);
 
		for (LiveryScheme scheme = LS_DEFAULT; scheme < LS_END; scheme++) {
 
			if (_livery_class[scheme] == this->livery_class) {
 
				bool sel = HasBit(this->sel, scheme) != 0;
 

	
 
				/* Optional check box + scheme name. */
 
				if (scheme != LS_DEFAULT) {
 
					DrawSprite(c->livery[scheme].in_use ? SPR_BOX_CHECKED : SPR_BOX_EMPTY, PAL_NONE, (rtl ? sch_right - TEXT_INDENT + WD_FRAMERECT_RIGHT : sch_left) + WD_FRAMERECT_LEFT, y);
 
				}
 
				DrawString(sch_left + text_left, sch_right - text_right, y, STR_LIVERY_DEFAULT + scheme, sel ? TC_WHITE : TC_BLACK);
 

	
 
				/* Text below the first dropdown. */
 
				DrawSprite(SPR_SQUARE, GENERAL_SPRITE_COLOUR(c->livery[scheme].colour1), (rtl ? pri_right - TEXT_INDENT + WD_FRAMERECT_RIGHT : pri_left) + WD_FRAMERECT_LEFT, y);
 
				DrawString(pri_left + text_left, pri_right - text_right, y, STR_COLOUR_DARK_BLUE + c->livery[scheme].colour1, sel ? TC_WHITE : TC_GOLD);
 

	
 
				/* Text below the second dropdown. */
 
				if (sec_right > sec_left) { // Second dropdown has non-zero size.
 
					DrawSprite(SPR_SQUARE, GENERAL_SPRITE_COLOUR(c->livery[scheme].colour2), (rtl ? sec_right - TEXT_INDENT + WD_FRAMERECT_RIGHT : sec_left) + WD_FRAMERECT_LEFT, y);
 
					DrawString(sec_left + text_left, sec_right - text_right, y, STR_COLOUR_DARK_BLUE + c->livery[scheme].colour2, sel ? TC_WHITE : TC_GOLD);
 
				}
 

	
 
				y += 4 + FONT_HEIGHT_NORMAL;
 
			}
 
		}
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			/* Livery Class buttons */
 
			case SCLW_WIDGET_CLASS_GENERAL:
 
			case SCLW_WIDGET_CLASS_RAIL:
 
			case SCLW_WIDGET_CLASS_ROAD:
 
			case SCLW_WIDGET_CLASS_SHIP:
 
			case SCLW_WIDGET_CLASS_AIRCRAFT:
 
				this->RaiseWidget(this->livery_class + SCLW_WIDGET_CLASS_GENERAL);
 
				this->livery_class = (LiveryClass)(widget - SCLW_WIDGET_CLASS_GENERAL);
 
				this->LowerWidget(this->livery_class + SCLW_WIDGET_CLASS_GENERAL);
 

	
 
				/* Select the first item in the list */
 
				this->sel = 0;
 
				for (LiveryScheme scheme = LS_DEFAULT; scheme < LS_END; scheme++) {
 
					if (_livery_class[scheme] == this->livery_class) {
 
						this->sel = 1 << scheme;
 
						break;
 
					}
 
				}
 

	
 
				this->ReInit();
 
				break;
 

	
 
			case SCLW_WIDGET_PRI_COL_DROPDOWN: // First colour dropdown
 
				ShowColourDropDownMenu(SCLW_WIDGET_PRI_COL_DROPDOWN);
 
				break;
 

	
 
			case SCLW_WIDGET_SEC_COL_DROPDOWN: // Second colour dropdown
 
				ShowColourDropDownMenu(SCLW_WIDGET_SEC_COL_DROPDOWN);
 
				break;
 

	
 
			case SCLW_WIDGET_MATRIX: {
 
				const NWidgetBase *wid = this->GetWidget<NWidgetBase>(SCLW_WIDGET_MATRIX);
 
				LiveryScheme j = (LiveryScheme)((pt.y - wid->pos_y) / (4 + FONT_HEIGHT_NORMAL));
 

	
 
				for (LiveryScheme scheme = LS_BEGIN; scheme <= j; scheme++) {
 
					if (_livery_class[scheme] != this->livery_class) j++;
 
					if (scheme >= LS_END) return;
 
				}
 
				if (j >= LS_END) return;
 

	
 
				/* If clicking on the left edge, toggle using the livery */
 
				if (_dynlang.text_dir == TD_RTL ? pt.x - wid->pos_x > wid->current_x - TEXT_INDENT : pt.x - wid->pos_x < TEXT_INDENT) {
 
					DoCommandP(0, j | (2 << 8), !Company::Get((CompanyID)this->window_number)->livery[j].in_use, CMD_SET_COMPANY_COLOUR);
 
				}
 

	
 
				if (_ctrl_pressed) {
 
					ToggleBit(this->sel, j);
 
				} else {
 
					this->sel = 1 << j;
 
				}
 
				this->SetDirty();
 
				break;
 
			}
 
		}
 
	}
 

	
 
	virtual void OnDropdownSelect(int widget, int index)
 
	{
 
		for (LiveryScheme scheme = LS_DEFAULT; scheme < LS_END; scheme++) {
 
			if (HasBit(this->sel, scheme)) {
 
				DoCommandP(0, scheme | (widget == SCLW_WIDGET_PRI_COL_DROPDOWN ? 0 : 256), index, CMD_SET_COMPANY_COLOUR);
 
			}
 
		}
 
	}
 

	
 
	virtual void OnInvalidateData(int data = 0)
 
	{
 
		this->ReInit();
 
	}
 
};
 

	
 
static const NWidgetPart _nested_select_company_livery_widgets [] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, SCLW_WIDGET_CLOSE),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, SCLW_WIDGET_CAPTION), SetDataTip(STR_LIVERY_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_IMGBTN, COLOUR_GREY, SCLW_WIDGET_CLASS_GENERAL), SetMinimalSize(22, 22), SetFill(false, true), SetDataTip(SPR_IMG_COMPANY_GENERAL, STR_LIVERY_GENERAL_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_GREY, SCLW_WIDGET_CLASS_RAIL), SetMinimalSize(22, 22), SetFill(false, true), SetDataTip(SPR_IMG_TRAINLIST, STR_LIVERY_TRAIN_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_GREY, SCLW_WIDGET_CLASS_ROAD), SetMinimalSize(22, 22), SetFill(false, true), SetDataTip(SPR_IMG_TRUCKLIST, STR_LIVERY_ROAD_VEHICLE_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_GREY, SCLW_WIDGET_CLASS_SHIP), SetMinimalSize(22, 22), SetFill(false, true), SetDataTip(SPR_IMG_SHIPLIST, STR_LIVERY_SHIP_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_GREY, SCLW_WIDGET_CLASS_AIRCRAFT), SetMinimalSize(22, 22), SetFill(false, true), SetDataTip(SPR_IMG_AIRPLANESLIST, STR_LIVERY_AIRCRAFT_TOOLTIP),
 
		NWidget(WWT_PANEL, COLOUR_GREY, SCLW_WIDGET_SPACER_CLASS), SetMinimalSize(90, 22), SetFill(true, true), EndContainer(),
 
		NWidget(WWT_IMGBTN, COLOUR_GREY, SCLW_WIDGET_CLASS_GENERAL), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_COMPANY_GENERAL, STR_LIVERY_GENERAL_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_GREY, SCLW_WIDGET_CLASS_RAIL), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_TRAINLIST, STR_LIVERY_TRAIN_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_GREY, SCLW_WIDGET_CLASS_ROAD), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_TRUCKLIST, STR_LIVERY_ROAD_VEHICLE_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_GREY, SCLW_WIDGET_CLASS_SHIP), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_SHIPLIST, STR_LIVERY_SHIP_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_GREY, SCLW_WIDGET_CLASS_AIRCRAFT), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_AIRPLANESLIST, STR_LIVERY_AIRCRAFT_TOOLTIP),
 
		NWidget(WWT_PANEL, COLOUR_GREY, SCLW_WIDGET_SPACER_CLASS), SetMinimalSize(90, 22), SetFill(1, 1), EndContainer(),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_PANEL, COLOUR_GREY, SCLW_WIDGET_SPACER_DROPDOWN), SetMinimalSize(150, 12), SetFill(true, true), EndContainer(),
 
		NWidget(WWT_DROPDOWN, COLOUR_GREY, SCLW_WIDGET_PRI_COL_DROPDOWN), SetMinimalSize(125, 12), SetFill(false, true), SetDataTip(STR_BLACK_STRING, STR_LIVERY_PRIMARY_TOOLTIP),
 
		NWidget(WWT_DROPDOWN, COLOUR_GREY, SCLW_WIDGET_SEC_COL_DROPDOWN), SetMinimalSize(125, 12), SetFill(false, true),
 
		NWidget(WWT_PANEL, COLOUR_GREY, SCLW_WIDGET_SPACER_DROPDOWN), SetMinimalSize(150, 12), SetFill(1, 1), EndContainer(),
 
		NWidget(WWT_DROPDOWN, COLOUR_GREY, SCLW_WIDGET_PRI_COL_DROPDOWN), SetMinimalSize(125, 12), SetFill(0, 1), SetDataTip(STR_BLACK_STRING, STR_LIVERY_PRIMARY_TOOLTIP),
 
		NWidget(WWT_DROPDOWN, COLOUR_GREY, SCLW_WIDGET_SEC_COL_DROPDOWN), SetMinimalSize(125, 12), SetFill(0, 1),
 
				SetDataTip(STR_BLACK_STRING, STR_LIVERY_SECONDARY_TOOLTIP),
 
	EndContainer(),
 
	NWidget(WWT_MATRIX, COLOUR_GREY, SCLW_WIDGET_MATRIX), SetMinimalSize(275, 15), SetFill(true, false), SetDataTip((1 << MAT_ROW_START) | (1 << MAT_COL_START), STR_LIVERY_PANEL_TOOLTIP),
 
	NWidget(WWT_MATRIX, COLOUR_GREY, SCLW_WIDGET_MATRIX), SetMinimalSize(275, 15), SetFill(1, 0), SetDataTip((1 << MAT_ROW_START) | (1 << MAT_COL_START), STR_LIVERY_PANEL_TOOLTIP),
 
};
 

	
 
static const WindowDesc _select_company_livery_desc(
 
	WDP_AUTO, WDP_AUTO, 0, 0,
 
	WC_COMPANY_COLOUR, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
 
	_nested_select_company_livery_widgets, lengthof(_nested_select_company_livery_widgets)
 
);
 

	
 
/**
 
 * Draws the face of a company manager's face.
 
 * @param cmf   the company manager's face
 
 * @param colour the (background) colour of the gradient
 
 * @param x     x-position to draw the face
 
 * @param y     y-position to draw the face
 
 */
 
void DrawCompanyManagerFace(CompanyManagerFace cmf, int colour, int x, int y)
 
{
 
	GenderEthnicity ge = (GenderEthnicity)GetCompanyManagerFaceBits(cmf, CMFV_GEN_ETHN, GE_WM);
 

	
 
	bool has_moustache   = !HasBit(ge, GENDER_FEMALE) && GetCompanyManagerFaceBits(cmf, CMFV_HAS_MOUSTACHE,   ge) != 0;
 
	bool has_tie_earring = !HasBit(ge, GENDER_FEMALE) || GetCompanyManagerFaceBits(cmf, CMFV_HAS_TIE_EARRING, ge) != 0;
 
	bool has_glasses     = GetCompanyManagerFaceBits(cmf, CMFV_HAS_GLASSES, ge) != 0;
 
	SpriteID pal;
 

	
 
	/* Modify eye colour palette only if 2 or more valid values exist */
 
	if (_cmf_info[CMFV_EYE_COLOUR].valid_values[ge] < 2) {
 
		pal = PAL_NONE;
 
	} else {
 
		switch (GetCompanyManagerFaceBits(cmf, CMFV_EYE_COLOUR, ge)) {
 
			default: NOT_REACHED();
 
			case 0: pal = PALETTE_TO_BROWN; break;
 
			case 1: pal = PALETTE_TO_BLUE;  break;
 
			case 2: pal = PALETTE_TO_GREEN; break;
 
		}
 
	}
 

	
 
	/* Draw the gradient (background) */
 
	DrawSprite(SPR_GRADIENT, GENERAL_SPRITE_COLOUR(colour), x, y);
 

	
 
	for (CompanyManagerFaceVariable cmfv = CMFV_CHEEKS; cmfv < CMFV_END; cmfv++) {
 
		switch (cmfv) {
 
			case CMFV_MOUSTACHE:   if (!has_moustache)   continue; break;
 
			case CMFV_LIPS:        // FALL THROUGH
 
			case CMFV_NOSE:        if (has_moustache)    continue; break;
 
			case CMFV_TIE_EARRING: if (!has_tie_earring) continue; break;
 
			case CMFV_GLASSES:     if (!has_glasses)     continue; break;
 
			default: break;
 
		}
 
		DrawSprite(GetCompanyManagerFaceSprite(cmf, cmfv, ge), (cmfv == CMFV_EYEBROWS) ? pal : PAL_NONE, x, y);
 
	}
 
}
 

	
 
/**
 
 * Names of the widgets. Keep them in the same order as in the widget array.
 
 * Do not change the order of the widgets from SCMFW_WIDGET_HAS_MOUSTACHE_EARRING to SCMFW_WIDGET_GLASSES_R,
 
 * this order is needed for the WE_CLICK event of DrawFaceStringLabel().
 
 */
 
enum SelectCompanyManagerFaceWidgets {
 
	SCMFW_WIDGET_CLOSEBOX = 0,
 
	SCMFW_WIDGET_CAPTION,
 
	SCMFW_WIDGET_TOGGLE_LARGE_SMALL,
 
	SCMFW_WIDGET_SELECT_FACE,
 
	SCMFW_WIDGET_CANCEL,
 
	SCMFW_WIDGET_ACCEPT,
 
	SCMFW_WIDGET_MALE,
 
	SCMFW_WIDGET_FEMALE,
 
	SCMFW_WIDGET_RANDOM_NEW_FACE,
 
	SCMFW_WIDGET_TOGGLE_LARGE_SMALL_BUTTON,
 
	SCMFM_WIDGET_FACE,
 
	/* from here is the advanced company manager face selection window */
 
	SCMFW_WIDGET_LOAD,
 
	SCMFW_WIDGET_FACECODE,
 
	SCMFW_WIDGET_SAVE,
 
	SCMFW_WIDGET_ETHNICITY_EUR,
 
	SCMFW_WIDGET_ETHNICITY_AFR,
 
	SCMFW_WIDGET_HAS_MOUSTACHE_EARRING,
 
	SCMFW_WIDGET_HAS_GLASSES,
 
	SCMFW_WIDGET_EYECOLOUR_L,
 
	SCMFW_WIDGET_EYECOLOUR,
 
	SCMFW_WIDGET_EYECOLOUR_R,
 
	SCMFW_WIDGET_CHIN_L,
 
	SCMFW_WIDGET_CHIN,
 
	SCMFW_WIDGET_CHIN_R,
 
	SCMFW_WIDGET_EYEBROWS_L,
 
	SCMFW_WIDGET_EYEBROWS,
 
	SCMFW_WIDGET_EYEBROWS_R,
 
	SCMFW_WIDGET_LIPS_MOUSTACHE_L,
 
	SCMFW_WIDGET_LIPS_MOUSTACHE,
 
	SCMFW_WIDGET_LIPS_MOUSTACHE_R,
 
	SCMFW_WIDGET_NOSE_L,
 
	SCMFW_WIDGET_NOSE,
 
	SCMFW_WIDGET_NOSE_R,
 
	SCMFW_WIDGET_HAIR_L,
 
	SCMFW_WIDGET_HAIR,
 
	SCMFW_WIDGET_HAIR_R,
 
	SCMFW_WIDGET_JACKET_L,
 
	SCMFW_WIDGET_JACKET,
 
	SCMFW_WIDGET_JACKET_R,
 
	SCMFW_WIDGET_COLLAR_L,
 
	SCMFW_WIDGET_COLLAR,
 
	SCMFW_WIDGET_COLLAR_R,
 
	SCMFW_WIDGET_TIE_EARRING_L,
 
	SCMFW_WIDGET_TIE_EARRING,
 
	SCMFW_WIDGET_TIE_EARRING_R,
 
	SCMFW_WIDGET_GLASSES_L,
 
	SCMFW_WIDGET_GLASSES,
 
	SCMFW_WIDGET_GLASSES_R,
 
	SCMFW_WIDGET_LABELS,
 
};
 

	
 
/** Nested widget description for the normal/simple company manager face selection dialog */
 
static const NWidgetPart _nested_select_company_manager_face_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, SCMFW_WIDGET_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, SCMFW_WIDGET_CAPTION), SetDataTip(STR_FACE_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_IMGBTN, COLOUR_GREY, SCMFW_WIDGET_TOGGLE_LARGE_SMALL), SetDataTip(SPR_LARGE_SMALL_WINDOW, STR_FACE_ADVANCED_TOOLTIP),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, SCMFW_WIDGET_SELECT_FACE),
 
		NWidget(NWID_VERTICAL), SetPIP(2, 2, 2),
 
			NWidget(NWID_HORIZONTAL), SetPIP(2, 2, 2),
 
				NWidget(WWT_EMPTY, COLOUR_GREY, SCMFM_WIDGET_FACE), SetMinimalSize(92, 119),
 
				NWidget(NWID_VERTICAL), SetPIP(0, 2, 0),
 
					NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SCMFW_WIDGET_TOGGLE_LARGE_SMALL_BUTTON), SetDataTip(STR_FACE_ADVANCED, STR_FACE_ADVANCED_TOOLTIP),
 
					NWidget(NWID_SPACER), SetFill(false, true),
 
					NWidget(NWID_SPACER), SetFill(0, 1),
 
					NWidget(WWT_TEXTBTN, COLOUR_GREY, SCMFW_WIDGET_MALE), SetDataTip(STR_FACE_MALE_BUTTON, STR_FACE_MALE_TOOLTIP),
 
					NWidget(WWT_TEXTBTN, COLOUR_GREY, SCMFW_WIDGET_FEMALE), SetDataTip(STR_FACE_FEMALE_BUTTON, STR_FACE_FEMALE_TOOLTIP),
 
					NWidget(NWID_SPACER), SetFill(false, true),
 
					NWidget(NWID_SPACER), SetFill(0, 1),
 
				EndContainer(),
 
			EndContainer(),
 
			NWidget(NWID_HORIZONTAL),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SCMFW_WIDGET_RANDOM_NEW_FACE), SetMinimalSize(92, 12), SetDataTip(STR_FACE_NEW_FACE_BUTTON, STR_FACE_NEW_FACE_TOOLTIP),
 
				NWidget(NWID_SPACER), SetFill(true, false),
 
				NWidget(NWID_SPACER), SetFill(1, 0),
 
			EndContainer(),
 
		EndContainer(),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SCMFW_WIDGET_CANCEL), SetFill(true, false), SetDataTip(STR_BUTTON_CANCEL, STR_FACE_CANCEL_TOOLTIP),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SCMFW_WIDGET_ACCEPT), SetFill(true, false), SetDataTip(STR_BUTTON_OK, STR_FACE_OK_TOOLTIP),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SCMFW_WIDGET_CANCEL), SetFill(1, 0), SetDataTip(STR_BUTTON_CANCEL, STR_FACE_CANCEL_TOOLTIP),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SCMFW_WIDGET_ACCEPT), SetFill(1, 0), SetDataTip(STR_BUTTON_OK, STR_FACE_OK_TOOLTIP),
 
	EndContainer(),
 
};
 

	
 
/** Nested widget description for the advanced company manager face selection dialog */
 
static const NWidgetPart _nested_select_company_manager_face_adv_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, SCMFW_WIDGET_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, SCMFW_WIDGET_CAPTION), SetDataTip(STR_FACE_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_IMGBTN, COLOUR_GREY, SCMFW_WIDGET_TOGGLE_LARGE_SMALL), SetMinimalSize(15, 14), SetDataTip(SPR_LARGE_SMALL_WINDOW, STR_FACE_SIMPLE_TOOLTIP),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, SCMFW_WIDGET_SELECT_FACE),
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0),
 
			NWidget(NWID_VERTICAL),
 
				NWidget(WWT_EMPTY, COLOUR_GREY, SCMFM_WIDGET_FACE), SetMinimalSize(92, 119), SetPadding(2, 0, 2, 0),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SCMFW_WIDGET_RANDOM_NEW_FACE), SetMinimalSize(92, 12), SetDataTip(STR_MAPGEN_RANDOM, STR_FACE_NEW_FACE_TOOLTIP),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 9),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SCMFW_WIDGET_LOAD), SetMinimalSize(92, 12), SetDataTip(STR_FACE_LOAD, STR_FACE_LOAD_TOOLTIP),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SCMFW_WIDGET_FACECODE), SetMinimalSize(92, 12), SetDataTip(STR_FACE_FACECODE, STR_FACE_FACECODE_TOOLTIP),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SCMFW_WIDGET_SAVE), SetMinimalSize(92, 12), SetDataTip(STR_FACE_SAVE, STR_FACE_SAVE_TOOLTIP),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 14),
 
			EndContainer(),
 
			NWidget(NWID_SPACER), SetMinimalSize(1, 0),
 
			NWidget(NWID_VERTICAL),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 2),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SCMFW_WIDGET_TOGGLE_LARGE_SMALL_BUTTON), SetMinimalSize(123, 12), SetDataTip(STR_FACE_SIMPLE, STR_FACE_SIMPLE_TOOLTIP),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 4),
 
				NWidget(NWID_HORIZONTAL),
 
					NWidget(NWID_SPACER), SetMinimalSize(1, 0),
 
					NWidget(NWID_VERTICAL),
 
						NWidget(NWID_HORIZONTAL),
 
							NWidget(WWT_TEXTBTN, COLOUR_GREY, SCMFW_WIDGET_MALE), SetMinimalSize(61, 12), SetDataTip(STR_FACE_MALE_BUTTON, STR_FACE_MALE_TOOLTIP),
 
							NWidget(WWT_TEXTBTN, COLOUR_GREY, SCMFW_WIDGET_FEMALE), SetMinimalSize(61, 12), SetDataTip(STR_FACE_FEMALE_BUTTON, STR_FACE_FEMALE_TOOLTIP),
 
						EndContainer(),
 
						NWidget(NWID_SPACER), SetMinimalSize(0, 2),
 
						NWidget(NWID_HORIZONTAL),
 
							NWidget(WWT_TEXTBTN, COLOUR_GREY, SCMFW_WIDGET_ETHNICITY_EUR), SetMinimalSize(61, 12), SetDataTip(STR_FACE_EUROPEAN, STR_FACE_SELECT_EUROPEAN),
 
							NWidget(WWT_TEXTBTN, COLOUR_GREY, SCMFW_WIDGET_ETHNICITY_AFR), SetMinimalSize(61, 12), SetDataTip(STR_FACE_AFRICAN, STR_FACE_SELECT_AFRICAN),
 
						EndContainer(),
 
					EndContainer(),
 
				EndContainer(),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 2),
 
				NWidget(NWID_HORIZONTAL),
 
					NWidget(WWT_EMPTY, COLOUR_GREY, SCMFW_WIDGET_LABELS), SetMinimalSize(75, 146), SetPadding(0, 4, 0, 1),
 
					NWidget(NWID_VERTICAL),
 
						NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SCMFW_WIDGET_HAS_MOUSTACHE_EARRING), SetMinimalSize(43, 12), SetDataTip(STR_EMPTY, STR_FACE_MOUSTACHE_EARRING_TOOLTIP),
 
						NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SCMFW_WIDGET_HAS_GLASSES), SetMinimalSize(43, 12), SetDataTip(STR_EMPTY, STR_FACE_GLASSES_TOOLTIP),
 
						NWidget(NWID_SPACER), SetMinimalSize(0, 2),
 
						NWidget(NWID_HORIZONTAL),
 
							NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, SCMFW_WIDGET_HAIR_L), SetMinimalSize(9, 12), SetDataTip(AWV_DECREASE, STR_FACE_HAIR_TOOLTIP),
 
							NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SCMFW_WIDGET_HAIR), SetMinimalSize(25, 12), SetDataTip(STR_EMPTY, STR_FACE_HAIR_TOOLTIP),
 
							NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, SCMFW_WIDGET_HAIR_R), SetMinimalSize(9, 12), SetDataTip(AWV_INCREASE, STR_FACE_HAIR_TOOLTIP),
 
						EndContainer(),
 
						NWidget(NWID_HORIZONTAL),
 
							NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, SCMFW_WIDGET_EYEBROWS_L), SetMinimalSize(9, 12), SetDataTip(AWV_DECREASE, STR_FACE_EYEBROWS_TOOLTIP),
 
							NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SCMFW_WIDGET_EYEBROWS), SetMinimalSize(25, 12), SetDataTip(STR_EMPTY, STR_FACE_EYEBROWS_TOOLTIP),
 
							NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, SCMFW_WIDGET_EYEBROWS_R), SetMinimalSize(9, 12), SetDataTip(AWV_INCREASE, STR_FACE_EYEBROWS_TOOLTIP),
 
						EndContainer(),
 
						NWidget(NWID_HORIZONTAL),
 
							NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, SCMFW_WIDGET_EYECOLOUR_L), SetMinimalSize(9, 12), SetDataTip(AWV_DECREASE, STR_FACE_EYECOLOUR_TOOLTIP),
 
							NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SCMFW_WIDGET_EYECOLOUR), SetMinimalSize(25, 12), SetDataTip(STR_EMPTY, STR_FACE_EYECOLOUR_TOOLTIP),
 
							NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, SCMFW_WIDGET_EYECOLOUR_R), SetMinimalSize(9, 12), SetDataTip(AWV_INCREASE, STR_FACE_EYECOLOUR_TOOLTIP),
 
						EndContainer(),
 
						NWidget(NWID_HORIZONTAL),
 
							NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, SCMFW_WIDGET_GLASSES_L), SetMinimalSize(9, 12), SetDataTip(AWV_DECREASE, STR_FACE_GLASSES_TOOLTIP_2),
 
							NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SCMFW_WIDGET_GLASSES), SetMinimalSize(25, 12), SetDataTip(STR_EMPTY, STR_FACE_GLASSES_TOOLTIP_2),
 
							NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, SCMFW_WIDGET_GLASSES_R), SetMinimalSize(9, 12), SetDataTip(AWV_INCREASE, STR_FACE_GLASSES_TOOLTIP_2),
 
						EndContainer(),
 
						NWidget(NWID_HORIZONTAL),
 
							NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, SCMFW_WIDGET_NOSE_L), SetMinimalSize(9, 12), SetDataTip(AWV_DECREASE, STR_FACE_NOSE_TOOLTIP),
 
							NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SCMFW_WIDGET_NOSE), SetMinimalSize(25, 12), SetDataTip(STR_EMPTY, STR_FACE_NOSE_TOOLTIP),
 
							NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, SCMFW_WIDGET_NOSE_R), SetMinimalSize(9, 12), SetDataTip(AWV_INCREASE, STR_FACE_NOSE_TOOLTIP),
 
						EndContainer(),
 
						NWidget(NWID_HORIZONTAL),
 
							NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, SCMFW_WIDGET_LIPS_MOUSTACHE_L), SetMinimalSize(9, 12), SetDataTip(AWV_DECREASE, STR_FACE_LIPS_MOUSTACHE_TOOLTIP),
 
							NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SCMFW_WIDGET_LIPS_MOUSTACHE), SetMinimalSize(25, 12), SetDataTip(STR_EMPTY, STR_FACE_LIPS_MOUSTACHE_TOOLTIP),
 
							NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, SCMFW_WIDGET_LIPS_MOUSTACHE_R), SetMinimalSize(9, 12), SetDataTip(AWV_INCREASE, STR_FACE_LIPS_MOUSTACHE_TOOLTIP),
 
						EndContainer(),
 
						NWidget(NWID_HORIZONTAL),
 
							NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, SCMFW_WIDGET_CHIN_L), SetMinimalSize(9, 12), SetDataTip(AWV_DECREASE, STR_FACE_CHIN_TOOLTIP),
 
							NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SCMFW_WIDGET_CHIN), SetMinimalSize(25, 12), SetDataTip(STR_EMPTY, STR_FACE_CHIN_TOOLTIP),
 
							NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, SCMFW_WIDGET_CHIN_R), SetMinimalSize(9, 12), SetDataTip(AWV_INCREASE, STR_FACE_CHIN_TOOLTIP),
 
						EndContainer(),
 
						NWidget(NWID_HORIZONTAL),
 
							NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, SCMFW_WIDGET_JACKET_L), SetMinimalSize(9, 12), SetDataTip(AWV_DECREASE, STR_FACE_JACKET_TOOLTIP),
 
							NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SCMFW_WIDGET_JACKET), SetMinimalSize(25, 12), SetDataTip(STR_EMPTY, STR_FACE_JACKET_TOOLTIP),
 
							NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, SCMFW_WIDGET_JACKET_R), SetMinimalSize(9, 12), SetDataTip(AWV_INCREASE, STR_FACE_JACKET_TOOLTIP),
 
						EndContainer(),
 
						NWidget(NWID_HORIZONTAL),
 
							NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, SCMFW_WIDGET_COLLAR_L), SetMinimalSize(9, 12), SetDataTip(AWV_DECREASE, STR_FACE_COLLAR_TOOLTIP),
 
							NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SCMFW_WIDGET_COLLAR), SetMinimalSize(25, 12), SetDataTip(STR_EMPTY, STR_FACE_COLLAR_TOOLTIP),
 
							NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, SCMFW_WIDGET_COLLAR_R), SetMinimalSize(9, 12), SetDataTip(AWV_INCREASE, STR_FACE_COLLAR_TOOLTIP),
 
						EndContainer(),
 
						NWidget(NWID_HORIZONTAL),
 
							NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, SCMFW_WIDGET_TIE_EARRING_L), SetMinimalSize(9, 12), SetDataTip(AWV_DECREASE, STR_FACE_TIE_EARRING_TOOLTIP),
 
							NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SCMFW_WIDGET_TIE_EARRING), SetMinimalSize(25, 12), SetDataTip(STR_EMPTY, STR_FACE_TIE_EARRING_TOOLTIP),
 
							NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, SCMFW_WIDGET_TIE_EARRING_R), SetMinimalSize(9, 12), SetDataTip(AWV_INCREASE, STR_FACE_TIE_EARRING_TOOLTIP),
 
						EndContainer(),
 
					EndContainer(),
 
				EndContainer(),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 2),
 
			EndContainer(),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0),
 
		EndContainer(),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SCMFW_WIDGET_CANCEL), SetMinimalSize(95, 12), SetDataTip(STR_BUTTON_CANCEL, STR_FACE_CANCEL_TOOLTIP),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SCMFW_WIDGET_ACCEPT), SetMinimalSize(125, 12), SetDataTip(STR_BUTTON_OK, STR_FACE_OK_TOOLTIP),
 
	EndContainer(),
 
};
 

	
 
/** Management class for customizing the face of the company manager. */
 
class SelectCompanyManagerFaceWindow : public Window
 
{
 
	CompanyManagerFace face; ///< company manager face bits
 
	bool advanced; ///< advanced company manager face selection window
 

	
 
	GenderEthnicity ge; ///< Gender and ethnicity.
 
	bool is_female;     ///< Female face.
 
	bool is_moust_male; ///< Male face with a moustache.
 

	
 
	/**
 
	 * Draw dynamic a label to the left of the button and a value in the button
 
	 *
 
	 * @param widget_index   index of this widget in the window
 
	 * @param str            the label which will be draw
 
	 * @param val            the value which will be draw
 
	 * @param is_bool_widget is it a bool button
 
	 */
 
	void DrawFaceStringLabel(byte widget_index, StringID str, uint8 val, bool is_bool_widget) const
 
	{
 
		/* Write the label in gold (0x2) to the left of the button. */
 
		const NWidgetBase *nwi_labels = this->GetWidget<NWidgetBase>(SCMFW_WIDGET_LABELS);
 
		const NWidgetCore *nwi_widget = this->GetWidget<NWidgetCore>(widget_index);
 
		DrawString(nwi_labels->pos_x, nwi_labels->pos_x + nwi_labels->current_x, nwi_widget->pos_y + 1, str, TC_GOLD, SA_RIGHT);
 

	
 
		if (!this->IsWidgetDisabled(widget_index)) {
 
			if (is_bool_widget) {
 
				/* if it a bool button write yes or no */
 
				str = (val != 0) ? STR_FACE_YES : STR_FACE_NO;
 
			} else {
 
				/* else write the value + 1 */
 
				SetDParam(0, val + 1);
 
				str = STR_JUST_INT;
 
			}
 

	
 
			/* Draw the value/bool in white (0xC). If the button clicked adds 1px to x and y text coordinates (IsWindowWidgetLowered()). */
 
			DrawString(nwi_widget->pos_x + nwi_widget->IsLowered(), nwi_widget->pos_x + nwi_widget->current_x - 1 - nwi_widget->IsLowered(),
 
					nwi_widget->pos_y + 1 + nwi_widget->IsLowered(), str, TC_WHITE, SA_CENTER);
 
		}
 
	}
 

	
 
	void UpdateData()
 
	{
 
		this->ge = (GenderEthnicity)GB(this->face, _cmf_info[CMFV_GEN_ETHN].offset, _cmf_info[CMFV_GEN_ETHN].length); // get the gender and ethnicity
 
		this->is_female = HasBit(this->ge, GENDER_FEMALE); // get the gender: 0 == male and 1 == female
 
		this->is_moust_male = !is_female && GetCompanyManagerFaceBits(this->face, CMFV_HAS_MOUSTACHE, this->ge) != 0; // is a male face with moustache
 
	}
 

	
 
public:
 
	SelectCompanyManagerFaceWindow(const WindowDesc *desc, Window *parent, bool advanced, int top, int left) : Window()
 
	{
 
		this->InitNested(desc, parent->window_number);
 
		this->parent = parent;
 
		this->owner = (Owner)this->window_number;
 
		this->face = Company::Get((CompanyID)this->window_number)->face;
 
		this->advanced = advanced;
 

	
 
		this->UpdateData();
 

	
 
		/* Check if repositioning from default is required */
 
		if (top != FIRST_GUI_CALL && left != FIRST_GUI_CALL) {
 
			this->top = top;
 
			this->left = left;
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		/* lower the non-selected gender button */
 
		this->SetWidgetLoweredState(SCMFW_WIDGET_MALE,  !this->is_female);
 
		this->SetWidgetLoweredState(SCMFW_WIDGET_FEMALE, this->is_female);
 

	
 
		/* advanced company manager face selection window */
 
		if (this->advanced) {
 
			/* lower the non-selected ethnicity button */
 
			this->SetWidgetLoweredState(SCMFW_WIDGET_ETHNICITY_EUR, !HasBit(this->ge, ETHNICITY_BLACK));
 
			this->SetWidgetLoweredState(SCMFW_WIDGET_ETHNICITY_AFR,  HasBit(this->ge, ETHNICITY_BLACK));
 

	
 

	
 
			/* Disable dynamically the widgets which CompanyManagerFaceVariable has less than 2 options
 
			 * (or in other words you haven't any choice).
 
			 * If the widgets depend on a HAS-variable and this is false the widgets will be disabled, too. */
 

	
 
			/* Eye colour buttons */
 
			this->SetWidgetsDisabledState(_cmf_info[CMFV_EYE_COLOUR].valid_values[this->ge] < 2,
 
				SCMFW_WIDGET_EYECOLOUR, SCMFW_WIDGET_EYECOLOUR_L, SCMFW_WIDGET_EYECOLOUR_R, WIDGET_LIST_END);
 

	
 
			/* Chin buttons */
 
			this->SetWidgetsDisabledState(_cmf_info[CMFV_CHIN].valid_values[this->ge] < 2,
 
				SCMFW_WIDGET_CHIN, SCMFW_WIDGET_CHIN_L, SCMFW_WIDGET_CHIN_R, WIDGET_LIST_END);
 

	
 
			/* Eyebrows buttons */
 
			this->SetWidgetsDisabledState(_cmf_info[CMFV_EYEBROWS].valid_values[this->ge] < 2,
 
				SCMFW_WIDGET_EYEBROWS, SCMFW_WIDGET_EYEBROWS_L, SCMFW_WIDGET_EYEBROWS_R, WIDGET_LIST_END);
 

	
 
			/* Lips or (if it a male face with a moustache) moustache buttons */
 
			this->SetWidgetsDisabledState(_cmf_info[this->is_moust_male ? CMFV_MOUSTACHE : CMFV_LIPS].valid_values[this->ge] < 2,
 
				SCMFW_WIDGET_LIPS_MOUSTACHE, SCMFW_WIDGET_LIPS_MOUSTACHE_L, SCMFW_WIDGET_LIPS_MOUSTACHE_R, WIDGET_LIST_END);
 

	
 
			/* Nose buttons | male faces with moustache haven't any nose options */
 
			this->SetWidgetsDisabledState(_cmf_info[CMFV_NOSE].valid_values[this->ge] < 2 || this->is_moust_male,
 
				SCMFW_WIDGET_NOSE, SCMFW_WIDGET_NOSE_L, SCMFW_WIDGET_NOSE_R, WIDGET_LIST_END);
 

	
 
			/* Hair buttons */
 
			this->SetWidgetsDisabledState(_cmf_info[CMFV_HAIR].valid_values[this->ge] < 2,
 
				SCMFW_WIDGET_HAIR, SCMFW_WIDGET_HAIR_L, SCMFW_WIDGET_HAIR_R, WIDGET_LIST_END);
 

	
 
			/* Jacket buttons */
 
			this->SetWidgetsDisabledState(_cmf_info[CMFV_JACKET].valid_values[this->ge] < 2,
 
				SCMFW_WIDGET_JACKET, SCMFW_WIDGET_JACKET_L, SCMFW_WIDGET_JACKET_R, WIDGET_LIST_END);
 

	
 
			/* Collar buttons */
 
			this->SetWidgetsDisabledState(_cmf_info[CMFV_COLLAR].valid_values[this->ge] < 2,
 
				SCMFW_WIDGET_COLLAR, SCMFW_WIDGET_COLLAR_L, SCMFW_WIDGET_COLLAR_R, WIDGET_LIST_END);
 

	
 
			/* Tie/earring buttons | female faces without earring haven't any earring options */
 
			this->SetWidgetsDisabledState(_cmf_info[CMFV_TIE_EARRING].valid_values[this->ge] < 2 ||
 
					(this->is_female && GetCompanyManagerFaceBits(this->face, CMFV_HAS_TIE_EARRING, this->ge) == 0),
 
				SCMFW_WIDGET_TIE_EARRING, SCMFW_WIDGET_TIE_EARRING_L, SCMFW_WIDGET_TIE_EARRING_R, WIDGET_LIST_END);
 

	
 
			/* Glasses buttons | faces without glasses haven't any glasses options */
 
			this->SetWidgetsDisabledState(_cmf_info[CMFV_GLASSES].valid_values[this->ge] < 2 || GetCompanyManagerFaceBits(this->face, CMFV_HAS_GLASSES, this->ge) == 0,
 
				SCMFW_WIDGET_GLASSES, SCMFW_WIDGET_GLASSES_L, SCMFW_WIDGET_GLASSES_R, WIDGET_LIST_END);
 
		}
 

	
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case SCMFW_WIDGET_HAS_MOUSTACHE_EARRING:
 
				if (this->is_female) { /* Only for female faces */
 
					this->DrawFaceStringLabel(SCMFW_WIDGET_HAS_MOUSTACHE_EARRING, STR_FACE_EARRING,   GetCompanyManagerFaceBits(this->face, CMFV_HAS_TIE_EARRING, this->ge), true);
 
				} else { /* Only for male faces */
 
					this->DrawFaceStringLabel(SCMFW_WIDGET_HAS_MOUSTACHE_EARRING, STR_FACE_MOUSTACHE, GetCompanyManagerFaceBits(this->face, CMFV_HAS_MOUSTACHE,   this->ge), true);
 
				}
 
				break;
 

	
 
			case SCMFW_WIDGET_TIE_EARRING:
 
				if (this->is_female) { /* Only for female faces */
 
					this->DrawFaceStringLabel(SCMFW_WIDGET_TIE_EARRING,           STR_FACE_EARRING,   GetCompanyManagerFaceBits(this->face, CMFV_TIE_EARRING,     this->ge), false);
 
				} else { /* Only for male faces */
 
					this->DrawFaceStringLabel(SCMFW_WIDGET_TIE_EARRING,           STR_FACE_TIE,       GetCompanyManagerFaceBits(this->face, CMFV_TIE_EARRING,     this->ge), false);
 
				}
 
				break;
 

	
 
			case SCMFW_WIDGET_LIPS_MOUSTACHE:
 
				if (this->is_moust_male) { /* Only for male faces with moustache */
 
					this->DrawFaceStringLabel(SCMFW_WIDGET_LIPS_MOUSTACHE,        STR_FACE_MOUSTACHE, GetCompanyManagerFaceBits(this->face, CMFV_MOUSTACHE,       this->ge), false);
 
				} else { /* Only for female faces or male faces without moustache */
 
					this->DrawFaceStringLabel(SCMFW_WIDGET_LIPS_MOUSTACHE,        STR_FACE_LIPS,      GetCompanyManagerFaceBits(this->face, CMFV_LIPS,            this->ge), false);
 
				}
 
				break;
 

	
 
			case SCMFW_WIDGET_HAS_GLASSES:
 
				this->DrawFaceStringLabel(SCMFW_WIDGET_HAS_GLASSES, STR_FACE_GLASSES,   GetCompanyManagerFaceBits(this->face, CMFV_HAS_GLASSES, this->ge), true );
 
				break;
 

	
 
			case SCMFW_WIDGET_HAIR:
 
				this->DrawFaceStringLabel(SCMFW_WIDGET_HAIR,        STR_FACE_HAIR,      GetCompanyManagerFaceBits(this->face, CMFV_HAIR,        this->ge), false);
 
				break;
 

	
 
			case SCMFW_WIDGET_EYEBROWS:
 
				this->DrawFaceStringLabel(SCMFW_WIDGET_EYEBROWS,    STR_FACE_EYEBROWS,  GetCompanyManagerFaceBits(this->face, CMFV_EYEBROWS,    this->ge), false);
 
				break;
 

	
 
			case SCMFW_WIDGET_EYECOLOUR:
 
				this->DrawFaceStringLabel(SCMFW_WIDGET_EYECOLOUR,   STR_FACE_EYECOLOUR, GetCompanyManagerFaceBits(this->face, CMFV_EYE_COLOUR,  this->ge), false);
 
				break;
 

	
 
			case SCMFW_WIDGET_GLASSES:
 
				this->DrawFaceStringLabel(SCMFW_WIDGET_GLASSES,     STR_FACE_GLASSES,   GetCompanyManagerFaceBits(this->face, CMFV_GLASSES,     this->ge), false);
 
				break;
 

	
 
			case SCMFW_WIDGET_NOSE:
 
				this->DrawFaceStringLabel(SCMFW_WIDGET_NOSE,        STR_FACE_NOSE,      GetCompanyManagerFaceBits(this->face, CMFV_NOSE,        this->ge), false);
 
				break;
 

	
 
			case SCMFW_WIDGET_CHIN:
 
				this->DrawFaceStringLabel(SCMFW_WIDGET_CHIN,        STR_FACE_CHIN,      GetCompanyManagerFaceBits(this->face, CMFV_CHIN,        this->ge), false);
 
				break;
 

	
 
			case SCMFW_WIDGET_JACKET:
 
				this->DrawFaceStringLabel(SCMFW_WIDGET_JACKET,      STR_FACE_JACKET,    GetCompanyManagerFaceBits(this->face, CMFV_JACKET,      this->ge), false);
 
				break;
 

	
 
			case SCMFW_WIDGET_COLLAR:
 
				this->DrawFaceStringLabel(SCMFW_WIDGET_COLLAR,      STR_FACE_COLLAR,    GetCompanyManagerFaceBits(this->face, CMFV_COLLAR,      this->ge), false);
 
				break;
 

	
 
			case SCMFM_WIDGET_FACE:
 
				DrawCompanyManagerFace(this->face, Company::Get((CompanyID)this->window_number)->colour, r.left, r.top);
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			/* Toggle size, advanced/simple face selection */
 
			case SCMFW_WIDGET_TOGGLE_LARGE_SMALL:
 
			case SCMFW_WIDGET_TOGGLE_LARGE_SMALL_BUTTON: {
 
				DoCommandP(0, 0, this->face, CMD_SET_COMPANY_MANAGER_FACE);
 

	
 
				/* Backup some data before deletion */
 
				int oldtop = this->top;     ///< current top position of the window before closing it
 
				int oldleft = this->left;   ///< current top position of the window before closing it
 
				bool adv = !this->advanced;
 
				Window *parent = this->parent;
 

	
 
				delete this;
 

	
 
				/* Open up the (toggled size) Face selection window at the same position as the previous */
 
				DoSelectCompanyManagerFace(parent, adv, oldtop, oldleft);
 
			} break;
 

	
 

	
 
			/* OK button */
 
			case SCMFW_WIDGET_ACCEPT:
 
				DoCommandP(0, 0, this->face, CMD_SET_COMPANY_MANAGER_FACE);
 
				/* Fall-Through */
 

	
 
			/* Cancel button */
 
			case SCMFW_WIDGET_CANCEL:
 
				delete this;
 
				break;
 

	
 
			/* Load button */
 
			case SCMFW_WIDGET_LOAD:
 
				this->face = _company_manager_face;
 
				ScaleAllCompanyManagerFaceBits(this->face);
 
				ShowErrorMessage(STR_FACE_LOAD_DONE, INVALID_STRING_ID, 0, 0);
 
				this->UpdateData();
 
				this->SetDirty();
 
				break;
 

	
 
			/* 'Company manager face number' button, view and/or set company manager face number */
 
			case SCMFW_WIDGET_FACECODE:
 
				SetDParam(0, this->face);
 
				ShowQueryString(STR_JUST_INT, STR_FACE_FACECODE_CAPTION, 10 + 1, 0, this, CS_NUMERAL, QSF_NONE);
 
				break;
 

	
 
			/* Save button */
 
			case SCMFW_WIDGET_SAVE:
 
				_company_manager_face = this->face;
 
				ShowErrorMessage(STR_FACE_SAVE_DONE, INVALID_STRING_ID, 0, 0);
 
				break;
 

	
 
			/* Toggle gender (male/female) button */
 
			case SCMFW_WIDGET_MALE:
 
			case SCMFW_WIDGET_FEMALE:
 
				SetCompanyManagerFaceBits(this->face, CMFV_GENDER, this->ge, widget - SCMFW_WIDGET_MALE);
 
				ScaleAllCompanyManagerFaceBits(this->face);
 
				this->UpdateData();
 
				this->SetDirty();
 
				break;
 

	
 
			/* Randomize face button */
 
			case SCMFW_WIDGET_RANDOM_NEW_FACE:
 
				RandomCompanyManagerFaceBits(this->face, this->ge, this->advanced);
 
				this->UpdateData();
 
				this->SetDirty();
 
				break;
 

	
 
			/* Toggle ethnicity (european/african) button */
 
			case SCMFW_WIDGET_ETHNICITY_EUR:
 
			case SCMFW_WIDGET_ETHNICITY_AFR:
 
				SetCompanyManagerFaceBits(this->face, CMFV_ETHNICITY, this->ge, widget - SCMFW_WIDGET_ETHNICITY_EUR);
 
				ScaleAllCompanyManagerFaceBits(this->face);
 
				this->UpdateData();
 
				this->SetDirty();
 
				break;
 

	
 
			default:
 
				/* Here all buttons from SCMFW_WIDGET_HAS_MOUSTACHE_EARRING to SCMFW_WIDGET_GLASSES_R are handled.
 
				 * First it checks which CompanyManagerFaceVariable is being changed, and then either
 
				 * a: invert the value for boolean variables, or
 
				 * b: it checks inside of IncreaseCompanyManagerFaceBits() if a left (_L) butten is pressed and then decrease else increase the variable */
 
				if (this->advanced && widget >= SCMFW_WIDGET_HAS_MOUSTACHE_EARRING && widget <= SCMFW_WIDGET_GLASSES_R) {
 
					CompanyManagerFaceVariable cmfv; // which CompanyManagerFaceVariable shall be edited
 

	
 
					if (widget < SCMFW_WIDGET_EYECOLOUR_L) { // Bool buttons
 
						switch (widget - SCMFW_WIDGET_HAS_MOUSTACHE_EARRING) {
 
							default: NOT_REACHED();
 
							case 0: cmfv = this->is_female ? CMFV_HAS_TIE_EARRING : CMFV_HAS_MOUSTACHE; break; // Has earring/moustache button
 
							case 1: cmfv = CMFV_HAS_GLASSES; break; // Has glasses button
 
						}
 
						SetCompanyManagerFaceBits(this->face, cmfv, this->ge, !GetCompanyManagerFaceBits(this->face, cmfv, this->ge));
 
						ScaleAllCompanyManagerFaceBits(this->face);
 
					} else { // Value buttons
 
						switch ((widget - SCMFW_WIDGET_EYECOLOUR_L) / 3) {
 
							default: NOT_REACHED();
 
							case 0: cmfv = CMFV_EYE_COLOUR; break;  // Eye colour buttons
 
							case 1: cmfv = CMFV_CHIN; break;        // Chin buttons
 
							case 2: cmfv = CMFV_EYEBROWS; break;    // Eyebrows buttons
 
							case 3: cmfv = this->is_moust_male ? CMFV_MOUSTACHE : CMFV_LIPS; break; // Moustache or lips buttons
 
							case 4: cmfv = CMFV_NOSE; break;        // Nose buttons
 
							case 5: cmfv = CMFV_HAIR; break;        // Hair buttons
 
							case 6: cmfv = CMFV_JACKET; break;      // Jacket buttons
 
							case 7: cmfv = CMFV_COLLAR; break;      // Collar buttons
 
							case 8: cmfv = CMFV_TIE_EARRING; break; // Tie/earring buttons
 
							case 9: cmfv = CMFV_GLASSES; break;     // Glasses buttons
 
						}
 
						/* 0 == left (_L), 1 == middle or 2 == right (_R) - button click */
 
						IncreaseCompanyManagerFaceBits(this->face, cmfv, this->ge, (((widget - SCMFW_WIDGET_EYECOLOUR_L) % 3) != 0) ? 1 : -1);
 
					}
 
					this->UpdateData();
 
					this->SetDirty();
 
				}
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnQueryTextFinished(char *str)
 
	{
 
		if (str == NULL) return;
 
		/* Set a new company manager face number */
 
		if (!StrEmpty(str)) {
 
			this->face = strtoul(str, NULL, 10);
 
			ScaleAllCompanyManagerFaceBits(this->face);
 
			ShowErrorMessage(STR_FACE_FACECODE_SET, INVALID_STRING_ID, 0, 0);
 
			this->UpdateData();
 
			this->SetDirty();
 
		} else {
 
			ShowErrorMessage(STR_FACE_FACECODE_ERR, INVALID_STRING_ID, 0, 0);
 
		}
 
	}
 
};
 

	
 
/** normal/simple company manager face selection window description */
 
static const WindowDesc _select_company_manager_face_desc(
 
	WDP_AUTO, WDP_AUTO, 190, 163,
 
	WC_COMPANY_MANAGER_FACE, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_CONSTRUCTION,
 
	_nested_select_company_manager_face_widgets, lengthof(_nested_select_company_manager_face_widgets)
 
);
 

	
 
/** advanced company manager face selection window description */
 
static const WindowDesc _select_company_manager_face_adv_desc(
 
	WDP_AUTO, WDP_AUTO, 220, 220,
 
	WC_COMPANY_MANAGER_FACE, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_CONSTRUCTION,
 
	_nested_select_company_manager_face_adv_widgets, lengthof(_nested_select_company_manager_face_adv_widgets)
 
);
 

	
 
/**
 
 * Open the simple/advanced company manager face selection window
 
 *
 
 * @param parent the parent company window
 
 * @param adv    simple or advanced face selection window
 
 * @param top    previous top position of the window
 
 * @param left   previous left position of the window
 
 */
 
static void DoSelectCompanyManagerFace(Window *parent, bool adv, int top, int left)
 
{
 
	if (!Company::IsValidID((CompanyID)parent->window_number)) return;
 

	
 
	if (BringWindowToFrontById(WC_COMPANY_MANAGER_FACE, parent->window_number)) return;
 
	new SelectCompanyManagerFaceWindow(adv ? &_select_company_manager_face_adv_desc : &_select_company_manager_face_desc, parent, adv, top, left); // simple or advanced window
 
}
 

	
 

	
 
/** Names of the widgets of the #CompanyWindow. Keep them in the same order as in the widget array */
 
enum CompanyWindowWidgets {
 
	CW_WIDGET_CLOSEBOX = 0,
 
	CW_WIDGET_CAPTION,
 
	CW_WIDGET_BACKGROUND,
 

	
 
	CW_WIDGET_FACE,
 
	CW_WIDGET_FACE_TITLE,
 

	
 
	CW_WIDGET_DESC_INAUGURATION,
 
	CW_WIDGET_DESC_COLOUR_SCHEME,
 
	CW_WIDGET_DESC_COLOUR_SCHEME_EXAMPLE,
 
	CW_WIDGET_DESC_VEHICLE,
 
	CW_WIDGET_DESC_VEHICLE_COUNTS,
 
	CW_WIDGET_DESC_COMPANY_VALUE,
 
	CW_WIDGET_DESC_OWNERS,
 

	
 
	CW_WIDGET_SELECT_BUTTONS,     ///< Selection widget for the button bar.
 
	CW_WIDGET_NEW_FACE,
 
	CW_WIDGET_COLOUR_SCHEME,
 
	CW_WIDGET_PRESIDENT_NAME,
 
	CW_WIDGET_COMPANY_NAME,
 
	CW_WIDGET_BUY_SHARE,
 
	CW_WIDGET_SELL_SHARE,
 

	
 
	CW_WIDGET_SELECT_VIEW_BUILD_HQ,
 
	CW_WIDGET_VIEW_HQ,
 
	CW_WIDGET_BUILD_HQ,
 

	
 
	CW_WIDGET_SELECT_RELOCATE,    ///< View/hide the 'Relocate HQ' button.
 
	CW_WIDGET_RELOCATE_HQ,
 

	
 
	CW_WIDGET_SELECT_MULTIPLAYER, ///< Multiplayer selection panel.
 
	CW_WIDGET_COMPANY_PASSWORD,
 
	CW_WIDGET_COMPANY_JOIN,
 
};
 

	
 
static const NWidgetPart _nested_company_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, CW_WIDGET_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, CW_WIDGET_CAPTION), SetDataTip(STR_COMPANY_VIEW_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, CW_WIDGET_BACKGROUND),
 
		NWidget(NWID_HORIZONTAL), SetPIP(4, 6, 4),
 
			NWidget(NWID_VERTICAL), SetPIP(4, 2, 4),
 
				NWidget(WWT_EMPTY, INVALID_COLOUR, CW_WIDGET_FACE), SetMinimalSize(91, 120), SetFill(true, false),
 
				NWidget(WWT_EMPTY, INVALID_COLOUR, CW_WIDGET_FACE_TITLE), SetFill(true, true), SetMinimalTextLines(2, 0),
 
				NWidget(WWT_EMPTY, INVALID_COLOUR, CW_WIDGET_FACE), SetMinimalSize(91, 120), SetFill(1, 0),
 
				NWidget(WWT_EMPTY, INVALID_COLOUR, CW_WIDGET_FACE_TITLE), SetFill(1, 1), SetMinimalTextLines(2, 0),
 
			EndContainer(),
 
			NWidget(NWID_VERTICAL),
 
				NWidget(NWID_HORIZONTAL),
 
					NWidget(NWID_VERTICAL), SetPIP(4, 5, 5),
 
						NWidget(WWT_TEXT, COLOUR_GREY, CW_WIDGET_DESC_INAUGURATION), SetDataTip(STR_COMPANY_VIEW_INAUGURATED_TITLE, STR_NULL), SetFill(true, false),
 
						NWidget(WWT_TEXT, COLOUR_GREY, CW_WIDGET_DESC_INAUGURATION), SetDataTip(STR_COMPANY_VIEW_INAUGURATED_TITLE, STR_NULL), SetFill(1, 0),
 
						NWidget(NWID_HORIZONTAL), SetPIP(0, 5, 0),
 
							NWidget(WWT_LABEL, COLOUR_GREY, CW_WIDGET_DESC_COLOUR_SCHEME), SetDataTip(STR_COMPANY_VIEW_COLOUR_SCHEME_TITLE, STR_NULL),
 
							NWidget(WWT_EMPTY, INVALID_COLOUR, CW_WIDGET_DESC_COLOUR_SCHEME_EXAMPLE), SetMinimalSize(30, 0), SetFill(false, true),
 
							NWidget(NWID_SPACER), SetFill(true, false),
 
							NWidget(WWT_EMPTY, INVALID_COLOUR, CW_WIDGET_DESC_COLOUR_SCHEME_EXAMPLE), SetMinimalSize(30, 0), SetFill(0, 1),
 
							NWidget(NWID_SPACER), SetFill(1, 0),
 
						EndContainer(),
 
						NWidget(NWID_HORIZONTAL), SetPIP(0, 4, 0),
 
							NWidget(NWID_VERTICAL),
 
								NWidget(WWT_TEXT, COLOUR_GREY, CW_WIDGET_DESC_VEHICLE), SetDataTip(STR_COMPANY_VIEW_VEHICLES_TITLE, STR_NULL),
 
								NWidget(NWID_SPACER), SetFill(false, true),
 
								NWidget(NWID_SPACER), SetFill(0, 1),
 
							EndContainer(),
 
							NWidget(WWT_EMPTY, INVALID_COLOUR, CW_WIDGET_DESC_VEHICLE_COUNTS), SetMinimalTextLines(4, 0),
 
							NWidget(NWID_SPACER), SetFill(true, false),
 
							NWidget(NWID_SPACER), SetFill(1, 0),
 
						EndContainer(),
 
					EndContainer(),
 
					NWidget(NWID_VERTICAL), SetPIP(4, 2, 4),
 
						NWidget(NWID_SELECTION, INVALID_COLOUR, CW_WIDGET_SELECT_VIEW_BUILD_HQ),
 
							NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, CW_WIDGET_VIEW_HQ), SetFill(true, false), SetDataTip(STR_COMPANY_VIEW_VIEW_HQ_BUTTON, STR_COMPANY_VIEW_VIEW_HQ_TOOLTIP),
 
							NWidget(WWT_TEXTBTN, COLOUR_GREY, CW_WIDGET_BUILD_HQ), SetFill(true, false), SetDataTip(STR_COMPANY_VIEW_BUILD_HQ_BUTTON, STR_COMPANY_VIEW_BUILD_HQ_TOOLTIP),
 
							NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, CW_WIDGET_VIEW_HQ), SetFill(1, 0), SetDataTip(STR_COMPANY_VIEW_VIEW_HQ_BUTTON, STR_COMPANY_VIEW_VIEW_HQ_TOOLTIP),
 
							NWidget(WWT_TEXTBTN, COLOUR_GREY, CW_WIDGET_BUILD_HQ), SetFill(1, 0), SetDataTip(STR_COMPANY_VIEW_BUILD_HQ_BUTTON, STR_COMPANY_VIEW_BUILD_HQ_TOOLTIP),
 
						EndContainer(),
 
						NWidget(NWID_SELECTION, INVALID_COLOUR, CW_WIDGET_SELECT_RELOCATE),
 
							NWidget(WWT_TEXTBTN, COLOUR_GREY, CW_WIDGET_RELOCATE_HQ), SetFill(true, false), SetDataTip(STR_COMPANY_VIEW_RELOCATE_HQ, STR_COMPANY_VIEW_RELOCATE_COMPANY_HEADQUARTERS),
 
							NWidget(WWT_TEXTBTN, COLOUR_GREY, CW_WIDGET_RELOCATE_HQ), SetFill(1, 0), SetDataTip(STR_COMPANY_VIEW_RELOCATE_HQ, STR_COMPANY_VIEW_RELOCATE_COMPANY_HEADQUARTERS),
 
							NWidget(NWID_SPACER), SetMinimalSize(90, 0),
 
						EndContainer(),
 
						NWidget(NWID_SPACER), SetFill(false, true),
 
						NWidget(NWID_SPACER), SetFill(0, 1),
 
					EndContainer(),
 
				EndContainer(),
 
				NWidget(WWT_TEXT, COLOUR_GREY, CW_WIDGET_DESC_COMPANY_VALUE), SetDataTip(STR_COMPANY_VIEW_COMPANY_VALUE, STR_NULL), SetFill(true, false),
 
				NWidget(WWT_TEXT, COLOUR_GREY, CW_WIDGET_DESC_COMPANY_VALUE), SetDataTip(STR_COMPANY_VIEW_COMPANY_VALUE, STR_NULL), SetFill(1, 0),
 
				NWidget(NWID_HORIZONTAL),
 
					NWidget(NWID_VERTICAL), SetPIP(5, 5, 4),
 
						NWidget(WWT_EMPTY, INVALID_COLOUR, CW_WIDGET_DESC_OWNERS), SetMinimalTextLines(3, 0),
 
						NWidget(NWID_SPACER), SetFill(false, true),
 
						NWidget(NWID_SPACER), SetFill(0, 1),
 
					EndContainer(),
 
					NWidget(NWID_VERTICAL), SetPIP(4, 2, 4),
 
						NWidget(NWID_SPACER), SetMinimalSize(90, 0), SetFill(false, true),
 
						NWidget(NWID_SPACER), SetMinimalSize(90, 0), SetFill(0, 1),
 
						/* Multi player buttons. */
 
						NWidget(NWID_SELECTION, INVALID_COLOUR, CW_WIDGET_SELECT_MULTIPLAYER),
 
							NWidget(NWID_SPACER), SetFill(true, false),
 
							NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, CW_WIDGET_COMPANY_PASSWORD), SetFill(true, false), SetDataTip(STR_COMPANY_VIEW_PASSWORD, STR_COMPANY_VIEW_PASSWORD_TOOLTIP),
 
							NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, CW_WIDGET_COMPANY_JOIN), SetFill(true, false), SetDataTip(STR_COMPANY_VIEW_JOIN, STR_COMPANY_VIEW_JOIN_TOOLTIP),
 
							NWidget(NWID_SPACER), SetFill(1, 0),
 
							NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, CW_WIDGET_COMPANY_PASSWORD), SetFill(1, 0), SetDataTip(STR_COMPANY_VIEW_PASSWORD, STR_COMPANY_VIEW_PASSWORD_TOOLTIP),
 
							NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, CW_WIDGET_COMPANY_JOIN), SetFill(1, 0), SetDataTip(STR_COMPANY_VIEW_JOIN, STR_COMPANY_VIEW_JOIN_TOOLTIP),
 
						EndContainer(),
 
					EndContainer(),
 
				EndContainer(),
 
			EndContainer(),
 
		EndContainer(),
 
	EndContainer(),
 
	/* Button bars at the bottom. */
 
	NWidget(NWID_SELECTION, INVALID_COLOUR, CW_WIDGET_SELECT_BUTTONS),
 
		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, CW_WIDGET_NEW_FACE), SetFill(true, false), SetDataTip(STR_COMPANY_VIEW_NEW_FACE_BUTTON, STR_COMPANY_VIEW_NEW_FACE_TOOLTIP),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, CW_WIDGET_COLOUR_SCHEME), SetFill(true, false), SetDataTip(STR_COMPANY_VIEW_COLOUR_SCHEME_BUTTON, STR_COMPANY_VIEW_COLOUR_SCHEME_TOOLTIP),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, CW_WIDGET_PRESIDENT_NAME), SetFill(true, false), SetDataTip(STR_COMPANY_VIEW_PRESIDENT_NAME_BUTTON, STR_COMPANY_VIEW_PRESIDENT_NAME_TOOLTIP),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, CW_WIDGET_COMPANY_NAME), SetFill(true, false), SetDataTip(STR_COMPANY_VIEW_COMPANY_NAME_BUTTON, STR_COMPANY_VIEW_COMPANY_NAME_TOOLTIP),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, CW_WIDGET_NEW_FACE), SetFill(1, 0), SetDataTip(STR_COMPANY_VIEW_NEW_FACE_BUTTON, STR_COMPANY_VIEW_NEW_FACE_TOOLTIP),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, CW_WIDGET_COLOUR_SCHEME), SetFill(1, 0), SetDataTip(STR_COMPANY_VIEW_COLOUR_SCHEME_BUTTON, STR_COMPANY_VIEW_COLOUR_SCHEME_TOOLTIP),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, CW_WIDGET_PRESIDENT_NAME), SetFill(1, 0), SetDataTip(STR_COMPANY_VIEW_PRESIDENT_NAME_BUTTON, STR_COMPANY_VIEW_PRESIDENT_NAME_TOOLTIP),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, CW_WIDGET_COMPANY_NAME), SetFill(1, 0), SetDataTip(STR_COMPANY_VIEW_COMPANY_NAME_BUTTON, STR_COMPANY_VIEW_COMPANY_NAME_TOOLTIP),
 
		EndContainer(),
 
		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, CW_WIDGET_BUY_SHARE), SetFill(true, false), SetDataTip(STR_COMPANY_VIEW_BUY_SHARE_BUTTON, STR_COMPANY_VIEW_BUY_SHARE_TOOLTIP),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, CW_WIDGET_SELL_SHARE), SetFill(true, false), SetDataTip(STR_COMPANY_VIEW_SELL_SHARE_BUTTON, STR_COMPANY_VIEW_SELL_SHARE_TOOLTIP),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, CW_WIDGET_BUY_SHARE), SetFill(1, 0), SetDataTip(STR_COMPANY_VIEW_BUY_SHARE_BUTTON, STR_COMPANY_VIEW_BUY_SHARE_TOOLTIP),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, CW_WIDGET_SELL_SHARE), SetFill(1, 0), SetDataTip(STR_COMPANY_VIEW_SELL_SHARE_BUTTON, STR_COMPANY_VIEW_SELL_SHARE_TOOLTIP),
 
		EndContainer(),
 
	EndContainer(),
 
};
 

	
 
int GetAmountOwnedBy(const Company *c, Owner owner)
 
{
 
	return (c->share_owners[0] == owner) +
 
				 (c->share_owners[1] == owner) +
 
				 (c->share_owners[2] == owner) +
 
				 (c->share_owners[3] == owner);
 
}
 

	
 
/** Strings for the company vehicle counts */
 
static const StringID _company_view_vehicle_count_strings[] = {
 
	STR_COMPANY_VIEW_TRAINS, STR_COMPANY_VIEW_ROAD_VEHICLES, STR_COMPANY_VIEW_SHIPS, STR_COMPANY_VIEW_AIRCRAFT
 
};
 

	
 
/**
 
 * Window with general information about a company
 
 */
 
struct CompanyWindow : Window
 
{
 
	CompanyWindowWidgets query_widget;
 

	
 
	/** Display planes in the company window. */
 
	enum CompanyWindowPlanes {
 
		/* Display planes of the #CW_WIDGET_SELECT_MULTIPLAYER selection widget. */
 
		CWP_MP_EMPTY = 0, ///< Do not display any multiplayer button.
 
		CWP_MP_C_PWD,     ///< Display the company password button.
 
		CWP_MP_C_JOIN,    ///< Display the join company button.
 

	
 
		/* Display planes of the #CW_WIDGET_SELECT_VIEW_BUILD_HQ selection widget. */
 
		CWP_VB_VIEW = 0,  ///< Display the view button
 
		CWP_VB_BUILD,     ///< Display the build button
 

	
 
		/* Display planes of the #CW_WIDGET_SELECT_RELOCATE selection widget. */
 
		CWP_RELOCATE_SHOW = 0, ///< Show the relocate HQ button.
 
		CWP_RELOCATE_HIDE,     ///< Hide the relocate HQ button.
 

	
 
		/* Display planes of the #CW_WIDGET_SELECT_BUTTONS selection widget. */
 
		CWP_BUTTONS_LOCAL = 0, ///< Buttons of the local company.
 
		CWP_BUTTONS_OTHER,     ///< Buttons of the other companies.
 
	};
 

	
 
	CompanyWindow(const WindowDesc *desc, WindowNumber window_number) : Window()
 
	{
 
		this->InitNested(desc, window_number);
 
		this->owner = (Owner)this->window_number;
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		const Company *c = Company::Get((CompanyID)this->window_number);
 
		bool local = this->window_number == _local_company;
 

	
 
		/* Button bar selection. */
 
		int plane = local ? CWP_BUTTONS_LOCAL : CWP_BUTTONS_OTHER;
 
		NWidgetStacked *wi = this->GetWidget<NWidgetStacked>(CW_WIDGET_SELECT_BUTTONS);
 
		if (plane != wi->shown_plane) {
 
			wi->SetDisplayedPlane(plane);
 
			this->SetDirty();
 
			return;
 
		}
 

	
 
		/* Build HQ button handling. */
 
		plane = (local && c->location_of_HQ == INVALID_TILE) ? CWP_VB_BUILD : CWP_VB_VIEW;
 
		wi = this->GetWidget<NWidgetStacked>(CW_WIDGET_SELECT_VIEW_BUILD_HQ);
 
		if (plane != wi->shown_plane) {
 
			wi->SetDisplayedPlane(plane);
 
			this->SetDirty();
 
			return;
 
		}
 

	
 
		this->SetWidgetDisabledState(CW_WIDGET_VIEW_HQ, c->location_of_HQ == INVALID_TILE);
 

	
 
		/* Enable/disable 'Relocate HQ' button. */
 
		plane = (!local || c->location_of_HQ == INVALID_TILE) ? CWP_RELOCATE_HIDE : CWP_RELOCATE_SHOW;
 
		wi = this->GetWidget<NWidgetStacked>(CW_WIDGET_SELECT_RELOCATE);
 
		if (plane != wi->shown_plane) {
 
			wi->SetDisplayedPlane(plane);
 
			this->SetDirty();
 
			return;
 
		}
 

	
 
		/* Multiplayer buttons. */
 
		plane = ((!_networking) ? CWP_MP_EMPTY : (local ? CWP_MP_C_PWD : CWP_MP_C_JOIN));
 
		wi = this->GetWidget<NWidgetStacked>(CW_WIDGET_SELECT_MULTIPLAYER);
 
		if (plane != wi->shown_plane) {
 
			wi->SetDisplayedPlane(plane);
 
			this->SetDirty();
 
			return;
 
		}
 
		this->SetWidgetDisabledState(CW_WIDGET_COMPANY_JOIN,   c->is_ai);
 

	
 
		if (!local) {
 
			if (_settings_game.economy.allow_shares) { // Shares are allowed
 
				/* If all shares are owned by someone (none by nobody), disable buy button */
 
				this->SetWidgetDisabledState(CW_WIDGET_BUY_SHARE, GetAmountOwnedBy(c, INVALID_OWNER) == 0 ||
 
						/* Only 25% left to buy. If the company is human, disable buying it up.. TODO issues! */
 
						(GetAmountOwnedBy(c, INVALID_OWNER) == 1 && !c->is_ai) ||
 
						/* Spectators cannot do anything of course */
 
						_local_company == COMPANY_SPECTATOR);
 

	
 
				/* If the company doesn't own any shares, disable sell button */
 
				this->SetWidgetDisabledState(CW_WIDGET_SELL_SHARE, (GetAmountOwnedBy(c, _local_company) == 0) ||
 
						/* Spectators cannot do anything of course */
 
						_local_company == COMPANY_SPECTATOR);
 
			} else { // Shares are not allowed, disable buy/sell buttons
 
				this->DisableWidget(CW_WIDGET_BUY_SHARE);
 
				this->DisableWidget(CW_WIDGET_SELL_SHARE);
 
			}
 
		}
 

	
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		switch (widget) {
 
			case CW_WIDGET_DESC_COMPANY_VALUE:
 
				SetDParam(0, INT64_MAX); // Arguably the maximum company value
 
				size->width = GetStringBoundingBox(STR_COMPANY_VIEW_COMPANY_VALUE).width;
 
				break;
 

	
 
			case CW_WIDGET_DESC_VEHICLE_COUNTS:
 
				SetDParam(0, 5000); // Maximum number of vehicles
 
				for (uint i = 0; i < lengthof(_company_view_vehicle_count_strings); i++) {
 
					size->width = max(size->width, GetStringBoundingBox(_company_view_vehicle_count_strings[i]).width);
 
				}
 
				break;
 

	
 
			case CW_WIDGET_DESC_OWNERS: {
 
				const Company *c2;
 

	
 
				FOR_ALL_COMPANIES(c2) {
 
					SetDParam(0, 25);
 
					SetDParam(1, c2->index);
 

	
 
					size->width = max(size->width, GetStringBoundingBox(STR_COMPANY_VIEW_SHARES_OWNED_BY).width);
 
				}
 
			} break;
 
		}
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		const Company *c = Company::Get((CompanyID)this->window_number);
 
		switch (widget) {
 
			case CW_WIDGET_FACE:
 
				DrawCompanyManagerFace(c->face, c->colour, r.left, r.top);
 
				break;
 

	
 
			case CW_WIDGET_FACE_TITLE:
 
				SetDParam(0, c->index);
 
				DrawStringMultiLine(r.left, r.right, r.top, r.bottom, STR_COMPANY_VIEW_PRESIDENT_MANAGER_TITLE, TC_FROMSTRING, SA_CENTER);
 
				break;
 

	
 
			case CW_WIDGET_DESC_COLOUR_SCHEME_EXAMPLE:
 
				DrawSprite(SPR_VEH_BUS_SW_VIEW, COMPANY_SPRITE_COLOUR(c->index), (r.left + r.right) / 2, r.top + FONT_HEIGHT_NORMAL / 10);
 
				break;
 

	
 
			case CW_WIDGET_DESC_VEHICLE_COUNTS: {
 
				uint amounts[] = { 0, 0, 0, 0 };
 
				int y = r.top;
 

	
 
				const Vehicle *v;
 
				FOR_ALL_VEHICLES(v) {
 
					if (v->owner == c->index) {
 
						if (v->IsPrimaryVehicle()) {
 
							assert((size_t)v->type < lengthof(amounts));
 
							amounts[v->type]++;
 
						}
 
					}
 
				}
 

	
 
				if (amounts[0] + amounts[1] + amounts[2] + amounts[3] == 0) {
 
					DrawString(r.left, r.right, y, STR_COMPANY_VIEW_VEHICLES_NONE);
 
				} else {
 
					assert_compile(lengthof(amounts) == lengthof(_company_view_vehicle_count_strings));
 

	
 
					for (uint i = 0; i < lengthof(amounts); i++) {
 
						if (amounts[i] != 0) {
 
							SetDParam(0, amounts[i]);
 
							DrawString(r.left, r.right, y, _company_view_vehicle_count_strings[i]);
 
							y += FONT_HEIGHT_NORMAL;
 
						}
 
					}
 
				}
 
			} break;
 

	
 
			case CW_WIDGET_DESC_OWNERS: {
 
				const Company *c2;
 
				uint y = r.top;
 

	
 
				FOR_ALL_COMPANIES(c2) {
 
					uint amt = GetAmountOwnedBy(c, c2->index);
 
					if (amt != 0) {
 
						SetDParam(0, amt * 25);
 
						SetDParam(1, c2->index);
 

	
 
						DrawString(r.left, r.right, y, STR_COMPANY_VIEW_SHARES_OWNED_BY);
 
						y += FONT_HEIGHT_NORMAL;
 
					}
 
				}
 
			} break;
 

	
 
#ifdef ENABLE_NETWORK
 
			case CW_WIDGET_COMPANY_PASSWORD:
 
				if (_networking && NetworkCompanyIsPassworded(c->index)) {
 
					DrawSprite(SPR_LOCK, PAL_NONE, _dynlang.text_dir == TD_RTL ? r.right + 10 : r.left  - 10, r.top + 2);
 
				}
 
				break;
 
#endif /* ENABLE_NETWORK */
 
		}
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		switch (widget) {
 
			case CW_WIDGET_CAPTION:
 
				SetDParam(0, (CompanyID)this->window_number);
 
				SetDParam(1, (CompanyID)this->window_number);
 
				break;
 

	
 
			case CW_WIDGET_DESC_INAUGURATION:
 
				SetDParam(0, Company::Get((CompanyID)this->window_number)->inaugurated_year);
 
				break;
 

	
 
			case CW_WIDGET_DESC_COMPANY_VALUE:
 
				SetDParam(0, CalculateCompanyValue(Company::Get((CompanyID)this->window_number)));
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case CW_WIDGET_NEW_FACE: DoSelectCompanyManagerFace(this, false); break;
 

	
 
			case CW_WIDGET_COLOUR_SCHEME:
 
				if (BringWindowToFrontById(WC_COMPANY_COLOUR, this->window_number)) break;
 
				new SelectCompanyLiveryWindow(&_select_company_livery_desc, (CompanyID)this->window_number);
 
				break;
 

	
 
			case CW_WIDGET_PRESIDENT_NAME:
 
				this->query_widget = CW_WIDGET_PRESIDENT_NAME;
 
				SetDParam(0, this->window_number);
 
				ShowQueryString(STR_PRESIDENT_NAME, STR_COMPANY_VIEW_PRESIDENT_S_NAME_QUERY_CAPTION, MAX_LENGTH_PRESIDENT_NAME_BYTES, MAX_LENGTH_PRESIDENT_NAME_PIXELS, this, CS_ALPHANUMERAL, QSF_ENABLE_DEFAULT);
 
				break;
 

	
 
			case CW_WIDGET_COMPANY_NAME:
 
				this->query_widget = CW_WIDGET_COMPANY_NAME;
 
				SetDParam(0, this->window_number);
 
				ShowQueryString(STR_COMPANY_NAME, STR_COMPANY_VIEW_COMPANY_NAME_QUERY_CAPTION, MAX_LENGTH_COMPANY_NAME_BYTES, MAX_LENGTH_COMPANY_NAME_PIXELS, this, CS_ALPHANUMERAL, QSF_ENABLE_DEFAULT);
 
				break;
 

	
 
			case CW_WIDGET_VIEW_HQ: {
 
				TileIndex tile = Company::Get((CompanyID)this->window_number)->location_of_HQ;
 
				if (_ctrl_pressed) {
 
					ShowExtraViewPortWindow(tile);
 
				} else {
 
					ScrollMainWindowToTile(tile);
 
				}
 
				break;
 
			}
 

	
 
			case CW_WIDGET_BUILD_HQ:
 
				if ((byte)this->window_number != _local_company) return;
 
				SetObjectToPlaceWnd(SPR_CURSOR_HQ, PAL_NONE, HT_RECT, this);
 
				SetTileSelectSize(2, 2);
 
				this->LowerWidget(CW_WIDGET_BUILD_HQ);
 
				this->SetWidgetDirty(CW_WIDGET_BUILD_HQ);
 
				break;
 

	
 
			case CW_WIDGET_RELOCATE_HQ:
 
				SetObjectToPlaceWnd(SPR_CURSOR_HQ, PAL_NONE, HT_RECT, this);
 
				SetTileSelectSize(2, 2);
 
				this->LowerWidget(CW_WIDGET_RELOCATE_HQ);
 
				this->SetWidgetDirty(CW_WIDGET_RELOCATE_HQ);
 
				break;
 

	
 
			case CW_WIDGET_BUY_SHARE:
 
				DoCommandP(0, this->window_number, 0, CMD_BUY_SHARE_IN_COMPANY | CMD_MSG(STR_ERROR_CAN_T_BUY_25_SHARE_IN_THIS));
 
				break;
 

	
 
			case CW_WIDGET_SELL_SHARE:
 
				DoCommandP(0, this->window_number, 0, CMD_SELL_SHARE_IN_COMPANY | CMD_MSG(STR_ERROR_CAN_T_SELL_25_SHARE_IN));
 
				break;
 

	
 
#ifdef ENABLE_NETWORK
 
			case CW_WIDGET_COMPANY_PASSWORD:
 
				if (this->window_number == _local_company) ShowNetworkCompanyPasswordWindow(this);
 
				break;
 

	
 
			case CW_WIDGET_COMPANY_JOIN: {
 
				this->query_widget = CW_WIDGET_COMPANY_JOIN;
 
				CompanyID company = (CompanyID)this->window_number;
 
				if (_network_server) {
 
					NetworkServerDoMove(CLIENT_ID_SERVER, company);
 
					MarkWholeScreenDirty();
 
				} else if (NetworkCompanyIsPassworded(company)) {
 
					/* ask for the password */
 
					ShowQueryString(STR_EMPTY, STR_NETWORK_NEED_COMPANY_PASSWORD_CAPTION, 20, 180, this, CS_ALPHANUMERAL, QSF_NONE);
 
				} else {
 
					/* just send the join command */
 
					NetworkClientRequestMove(company);
 
				}
 
				break;
 
			}
 
#endif /* ENABLE_NETWORK */
 
		}
 
	}
 

	
 
	virtual void OnHundredthTick()
 
	{
 
		/* redraw the window every now and then */
 
		this->SetDirty();
 
	}
 

	
 
	virtual void OnPlaceObject(Point pt, TileIndex tile)
 
	{
 
		if (DoCommandP(tile, 0, 0, CMD_BUILD_COMPANY_HQ | CMD_MSG(STR_ERROR_CAN_T_BUILD_COMPANY_HEADQUARTERS))) {
 
			ResetObjectToPlace();
 
			this->RaiseButtons();
 
		}
 
	}
 

	
 
	virtual void OnPlaceObjectAbort()
 
	{
 
		this->RaiseButtons();
 
	}
 

	
 
	virtual void OnQueryTextFinished(char *str)
 
	{
 
		if (str == NULL) return;
 

	
 
		switch (this->query_widget) {
 
			default: NOT_REACHED();
 

	
 
			case CW_WIDGET_PRESIDENT_NAME:
 
				DoCommandP(0, 0, 0, CMD_RENAME_PRESIDENT | CMD_MSG(STR_ERROR_CAN_T_CHANGE_PRESIDENT), NULL, str);
 
				break;
 

	
 
			case CW_WIDGET_COMPANY_NAME:
 
				DoCommandP(0, 0, 0, CMD_RENAME_COMPANY | CMD_MSG(STR_ERROR_CAN_T_CHANGE_COMPANY_NAME), NULL, str);
 
				break;
 

	
 
#ifdef ENABLE_NETWORK
 
			case CW_WIDGET_COMPANY_JOIN:
 
				NetworkClientRequestMove((CompanyID)this->window_number, str);
 
				break;
 
#endif /* ENABLE_NETWORK */
 
		}
 
	}
 
};
 

	
 
static const WindowDesc _company_desc(
 
	WDP_AUTO, WDP_AUTO, 360, 170,
 
	WC_COMPANY, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
 
	_nested_company_widgets, lengthof(_nested_company_widgets)
 
);
 

	
 
void ShowCompany(CompanyID company)
 
{
 
	if (!Company::IsValidID(company)) return;
 

	
 
	AllocateWindowDescFront<CompanyWindow>(&_company_desc, company);
 
}
 

	
 
/** widget numbers of the #BuyCompanyWindow. */
 
enum BuyCompanyWidgets {
 
	BCW_CLOSEBOX,
 
	BCW_CAPTION,
 
	BCW_BACKGROUND,
 
	BCW_FACE,
 
	BCW_QUESTION,
 
	BCW_NO,
 
	BCW_YES,
 
};
 

	
 
struct BuyCompanyWindow : Window {
 
	BuyCompanyWindow(const WindowDesc *desc, WindowNumber window_number) : Window()
 
	{
 
		this->InitNested(desc, window_number);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		switch (widget) {
 
			case BCW_FACE:
 
				*size = GetSpriteSize(SPR_GRADIENT);
 
				break;
 

	
 
			case BCW_QUESTION:
 
				const Company *c = Company::Get((CompanyID)this->window_number);
 
				SetDParam(0, c->index);
 
				SetDParam(1, c->bankrupt_value);
 
				size->height = GetStringHeight(STR_BUY_COMPANY_MESSAGE, size->width);
 
				break;
 
		}
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		switch (widget) {
 
			case BCW_CAPTION:
 
				SetDParam(0, STR_COMPANY_NAME);
 
				SetDParam(1, Company::Get((CompanyID)this->window_number)->index);
 
				break;
 
		}
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case BCW_FACE: {
 
				const Company *c = Company::Get((CompanyID)this->window_number);
 
				DrawCompanyManagerFace(c->face, c->colour, r.left, r.top);
 
			} break;
 

	
 
			case BCW_QUESTION: {
 
				const Company *c = Company::Get((CompanyID)this->window_number);
 
				SetDParam(0, c->index);
 
				SetDParam(1, c->bankrupt_value);
 
				DrawStringMultiLine(r.left, r.right, r.top, r.bottom, STR_BUY_COMPANY_MESSAGE, TC_FROMSTRING, SA_CENTER);
 
			} break;
 
		}
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case BCW_NO:
 
				delete this;
 
				break;
 

	
 
			case BCW_YES:
 
				DoCommandP(0, this->window_number, 0, CMD_BUY_COMPANY | CMD_MSG(STR_ERROR_CAN_T_BUY_COMPANY));
 
				break;
 
		}
 
	}
 
};
 

	
 
static const NWidgetPart _nested_buy_company_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_LIGHT_BLUE, BCW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_LIGHT_BLUE, BCW_CAPTION), SetDataTip(STR_ERROR_MESSAGE_CAPTION_OTHER_COMPANY, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, BCW_BACKGROUND),
 
		NWidget(NWID_VERTICAL), SetPIP(8, 8, 8),
 
			NWidget(NWID_HORIZONTAL), SetPIP(8, 10, 8),
 
				NWidget(WWT_EMPTY, INVALID_COLOUR, BCW_FACE), SetFill(false, true),
 
				NWidget(WWT_EMPTY, INVALID_COLOUR, BCW_QUESTION), SetMinimalSize(240, 0), SetFill(true, true),
 
				NWidget(WWT_EMPTY, INVALID_COLOUR, BCW_FACE), SetFill(0, 1),
 
				NWidget(WWT_EMPTY, INVALID_COLOUR, BCW_QUESTION), SetMinimalSize(240, 0), SetFill(1, 1),
 
			EndContainer(),
 
			NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(100, 10, 100),
 
				NWidget(WWT_TEXTBTN, COLOUR_LIGHT_BLUE, BCW_NO), SetMinimalSize(60, 12), SetDataTip(STR_QUIT_NO, STR_NULL), SetFill(true, false),
 
				NWidget(WWT_TEXTBTN, COLOUR_LIGHT_BLUE, BCW_YES), SetMinimalSize(60, 12), SetDataTip(STR_QUIT_YES, STR_NULL), SetFill(true, false),
 
				NWidget(WWT_TEXTBTN, COLOUR_LIGHT_BLUE, BCW_NO), SetMinimalSize(60, 12), SetDataTip(STR_QUIT_NO, STR_NULL), SetFill(1, 0),
 
				NWidget(WWT_TEXTBTN, COLOUR_LIGHT_BLUE, BCW_YES), SetMinimalSize(60, 12), SetDataTip(STR_QUIT_YES, STR_NULL), SetFill(1, 0),
 
			EndContainer(),
 
		EndContainer(),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _buy_company_desc(
 
	153, 171, 334, 137,
 
	WC_BUY_COMPANY, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_CONSTRUCTION,
 
	_nested_buy_company_widgets, lengthof(_nested_buy_company_widgets)
 
);
 

	
 

	
 
void ShowBuyCompanyDialog(CompanyID company)
 
{
 
	AllocateWindowDescFront<BuyCompanyWindow>(&_buy_company_desc, company);
 
}
src/depot_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file depot_gui.cpp The GUI for depots. */
 

	
 
#include "train.h"
 
#include "ship.h"
 
#include "aircraft.h"
 
#include "roadveh.h"
 
#include "gui.h"
 
#include "textbuf_gui.h"
 
#include "viewport_func.h"
 
#include "gfx_func.h"
 
#include "command_func.h"
 
#include "depot_base.h"
 
#include "vehicle_gui.h"
 
#include "newgrf_engine.h"
 
#include "spritecache.h"
 
#include "strings_func.h"
 
#include "window_func.h"
 
#include "vehicle_func.h"
 
#include "company_func.h"
 
#include "tilehighlight_func.h"
 
#include "window_gui.h"
 
#include "vehiclelist.h"
 

	
 
#include "table/strings.h"
 
#include "table/sprites.h"
 

	
 
/*
 
 * Since all depot window sizes aren't the same, we need to modify sizes a little.
 
 * It's done with the following arrays of widget indexes. Each of them tells if a widget side should be moved and in what direction.
 
 * How long they should be moved and for what window types are controlled in ShowDepotWindow()
 
 */
 

	
 
/* Names of the widgets. Keep them in the same order as in the widget array */
 
enum DepotWindowWidgets {
 
	DEPOT_WIDGET_CLOSEBOX = 0,
 
	DEPOT_WIDGET_CAPTION,
 
	DEPOT_WIDGET_STICKY,
 
	DEPOT_WIDGET_SELL,
 
	DEPOT_WIDGET_SELL_CHAIN,
 
	DEPOT_WIDGET_SELL_ALL,
 
	DEPOT_WIDGET_AUTOREPLACE,
 
	DEPOT_WIDGET_MATRIX,
 
	DEPOT_WIDGET_V_SCROLL, ///< Vertical scrollbar
 
	DEPOT_WIDGET_H_SCROLL, ///< Horizontal scrollbar
 
	DEPOT_WIDGET_BUILD,
 
	DEPOT_WIDGET_CLONE,
 
	DEPOT_WIDGET_LOCATION,
 
	DEPOT_WIDGET_VEHICLE_LIST,
 
	DEPOT_WIDGET_STOP_ALL,
 
	DEPOT_WIDGET_START_ALL,
 
	DEPOT_WIDGET_RESIZE,
 
};
 

	
 
/** Nested widget definition for train depots. */
 
static const NWidgetPart _nested_train_depot_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, DEPOT_WIDGET_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, DEPOT_WIDGET_CAPTION),
 
		NWidget(WWT_STICKYBOX, COLOUR_GREY, DEPOT_WIDGET_STICKY),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(NWID_VERTICAL),
 
			NWidget(WWT_MATRIX, COLOUR_GREY, DEPOT_WIDGET_MATRIX), SetDataTip(0x0, STR_NULL), SetResize(1, 1),
 
			NWidget(WWT_HSCROLLBAR, COLOUR_GREY, DEPOT_WIDGET_H_SCROLL),
 
		EndContainer(),
 
		NWidget(NWID_VERTICAL),
 
			NWidget(WWT_IMGBTN, COLOUR_GREY, DEPOT_WIDGET_SELL), SetDataTip(0x0, STR_NULL), SetResize(0, 1), SetFill(false, true),
 
			NWidget(WWT_IMGBTN, COLOUR_GREY, DEPOT_WIDGET_SELL_CHAIN), SetDataTip(SPR_SELL_CHAIN_TRAIN, STR_DEPOT_DRAG_WHOLE_TRAIN_TO_SELL_TOOLTIP), SetResize(0, 1), SetFill(false, true),
 
			NWidget(WWT_IMGBTN, COLOUR_GREY, DEPOT_WIDGET_SELL), SetDataTip(0x0, STR_NULL), SetResize(0, 1), SetFill(0, 1),
 
			NWidget(WWT_IMGBTN, COLOUR_GREY, DEPOT_WIDGET_SELL_CHAIN), SetDataTip(SPR_SELL_CHAIN_TRAIN, STR_DEPOT_DRAG_WHOLE_TRAIN_TO_SELL_TOOLTIP), SetResize(0, 1), SetFill(0, 1),
 
			NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, DEPOT_WIDGET_SELL_ALL), SetDataTip(0x0, STR_NULL),
 
			NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, DEPOT_WIDGET_AUTOREPLACE), SetDataTip(0x0, STR_NULL),
 
		EndContainer(),
 
		NWidget(WWT_SCROLLBAR, COLOUR_GREY, DEPOT_WIDGET_V_SCROLL),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, DEPOT_WIDGET_BUILD), SetDataTip(0x0, STR_NULL), SetFill(true, true), SetResize(1, 0),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, DEPOT_WIDGET_CLONE), SetDataTip(0x0, STR_NULL), SetFill(true, true), SetResize(1, 0),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, DEPOT_WIDGET_LOCATION), SetDataTip(STR_BUTTON_LOCATION, STR_NULL), SetFill(true, true), SetResize(1, 0),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, DEPOT_WIDGET_VEHICLE_LIST), SetDataTip(0x0, STR_NULL), SetFill(false, true),
 
		NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, DEPOT_WIDGET_STOP_ALL), SetDataTip(SPR_FLAG_VEH_STOPPED, STR_NULL), SetFill(false, true),
 
		NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, DEPOT_WIDGET_START_ALL), SetDataTip(SPR_FLAG_VEH_RUNNING, STR_NULL), SetFill(false, true),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, DEPOT_WIDGET_BUILD), SetDataTip(0x0, STR_NULL), SetFill(1, 1), SetResize(1, 0),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, DEPOT_WIDGET_CLONE), SetDataTip(0x0, STR_NULL), SetFill(1, 1), SetResize(1, 0),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, DEPOT_WIDGET_LOCATION), SetDataTip(STR_BUTTON_LOCATION, STR_NULL), SetFill(1, 1), SetResize(1, 0),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, DEPOT_WIDGET_VEHICLE_LIST), SetDataTip(0x0, STR_NULL), SetFill(0, 1),
 
		NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, DEPOT_WIDGET_STOP_ALL), SetDataTip(SPR_FLAG_VEH_STOPPED, STR_NULL), SetFill(0, 1),
 
		NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, DEPOT_WIDGET_START_ALL), SetDataTip(SPR_FLAG_VEH_RUNNING, STR_NULL), SetFill(0, 1),
 
		NWidget(WWT_RESIZEBOX, COLOUR_GREY, DEPOT_WIDGET_RESIZE),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _train_depot_desc(
 
	WDP_AUTO, WDP_AUTO, 362, 123,
 
	WC_VEHICLE_DEPOT, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE,
 
	_nested_train_depot_widgets, lengthof(_nested_train_depot_widgets)
 
);
 

	
 
static const WindowDesc _road_depot_desc(
 
	WDP_AUTO, WDP_AUTO, 316, 97,
 
	WC_VEHICLE_DEPOT, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE,
 
	_nested_train_depot_widgets, lengthof(_nested_train_depot_widgets)
 
);
 

	
 
static const WindowDesc _ship_depot_desc(
 
	WDP_AUTO, WDP_AUTO, 306, 99,
 
	WC_VEHICLE_DEPOT, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE,
 
	_nested_train_depot_widgets, lengthof(_nested_train_depot_widgets)
 
);
 

	
 
static const WindowDesc _aircraft_depot_desc(
 
	WDP_AUTO, WDP_AUTO, 332, 99,
 
	WC_VEHICLE_DEPOT, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE,
 
	_nested_train_depot_widgets, lengthof(_nested_train_depot_widgets)
 
);
 

	
 
extern void DepotSortList(VehicleList *list);
 

	
 
/**
 
 * This is the Callback method after the cloning attempt of a vehicle
 
 * @param success indicates completion (or not) of the operation
 
 * @param tile unused
 
 * @param p1 unused
 
 * @param p2 unused
 
 */
 
void CcCloneVehicle(bool success, TileIndex tile, uint32 p1, uint32 p2)
 
{
 
	if (!success) return;
 

	
 
	const Vehicle *v = Vehicle::Get(_new_vehicle_id);
 

	
 
	ShowVehicleViewWindow(v);
 
}
 

	
 
static void TrainDepotMoveVehicle(const Vehicle *wagon, VehicleID sel, const Vehicle *head)
 
{
 
	const Vehicle *v = Vehicle::Get(sel);
 

	
 
	if (v == wagon) return;
 

	
 
	if (wagon == NULL) {
 
		if (head != NULL) wagon = head->Last();
 
	} else {
 
		wagon = wagon->Previous();
 
		if (wagon == NULL) return;
 
	}
 

	
 
	if (wagon == v) return;
 

	
 
	DoCommandP(v->tile, v->index + ((wagon == NULL ? INVALID_VEHICLE : wagon->index) << 16), _ctrl_pressed ? 1 : 0, CMD_MOVE_RAIL_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_MOVE_VEHICLE));
 
}
 

	
 
/** Array containing the cell size in pixels of the #DEPOT_WIDGET_MATRIX widget for each vehicle type.
 
 * @note The train vehicle type uses the entire row for each train. */
 
static Dimension _base_block_sizes[4];
 

	
 
static void InitBlocksizeForShipAircraft(VehicleType type)
 
{
 
	uint max_width  = 0;
 
	uint max_height = 0;
 

	
 
	const Engine *e;
 
	FOR_ALL_ENGINES_OF_TYPE(e, type) {
 
		EngineID eid = e->index;
 
		uint x, y;
 

	
 
		switch (type) {
 
			default: NOT_REACHED();
 
			case VEH_SHIP:     GetShipSpriteSize(    eid, x, y); break;
 
			case VEH_AIRCRAFT: GetAircraftSpriteSize(eid, x, y); break;
 
		}
 
		if (x > max_width)  max_width  = x;
 
		if (y > max_height) max_height = y;
 
	}
 

	
 
	switch (type) {
 
		default: NOT_REACHED();
 
		case VEH_SHIP:
 
			_base_block_sizes[VEH_SHIP].width = max(76U, max_width);
 
			break;
 
		case VEH_AIRCRAFT:
 
			_base_block_sizes[VEH_AIRCRAFT].width = max(67U, max_width);
 
			break;
 
	}
 
	_base_block_sizes[type].height = max(GetVehicleHeight(type), max_height);
 
}
 

	
 
/** Set the size of the blocks in the window so we can be sure that they are big enough for the vehicle sprites in the current game.
 
 * @note Calling this function once for each game is enough. */
 
void InitDepotWindowBlockSizes()
 
{
 
	_base_block_sizes[VEH_TRAIN].width = 0;
 
	_base_block_sizes[VEH_TRAIN].height = GetVehicleHeight(VEH_TRAIN);
 

	
 
	_base_block_sizes[VEH_ROAD].width = 32;
 
	_base_block_sizes[VEH_ROAD].height = GetVehicleHeight(VEH_ROAD);
 

	
 
	InitBlocksizeForShipAircraft(VEH_SHIP);
 
	InitBlocksizeForShipAircraft(VEH_AIRCRAFT);
 
}
 

	
 
static void DepotSellAllConfirmationCallback(Window *w, bool confirmed);
 
const Sprite *GetAircraftSprite(EngineID engine);
 

	
 
struct DepotWindow : Window {
 
	VehicleID sel;
 
	VehicleType type;
 
	bool generate_list;
 
	VehicleList vehicle_list;
 
	VehicleList wagon_list;
 

	
 
	DepotWindow(const WindowDesc *desc, TileIndex tile, VehicleType type) : Window()
 
	{
 
		assert(IsCompanyBuildableVehicleType(type)); // ensure that we make the call with a valid type
 

	
 
		this->sel = INVALID_VEHICLE;
 
		this->generate_list = true;
 
		this->type = type;
 

	
 
		this->CreateNestedTree(desc);
 
		this->SetupWidgetData(type);
 
		this->FinishInitNested(desc, tile);
 

	
 
		this->owner = GetTileOwner(tile);
 
		_backup_orders_tile = 0;
 

	
 
	}
 

	
 
	~DepotWindow()
 
	{
 
		DeleteWindowById(WC_BUILD_VEHICLE, this->window_number);
 
	}
 

	
 
	/** Draw a vehicle in the depot window in the box with the top left corner at x,y.
 
	 * @param v     Vehicle to draw.
 
	 * @param left  Left side of the box to draw in.
 
	 * @param right Right side of the box to draw in.
 
	 * @param y     Top of the box to draw in.
 
	 */
 
	void DrawVehicleInDepot(const Vehicle *v, int left, int right, int y) const
 
	{
 
		bool free_wagon = false;
 
		int sprite_y = y + (this->resize.step_height - GetVehicleHeight(v->type)) / 2;
 

	
 
		bool rtl = _dynlang.text_dir == TD_RTL;
 
		int image_left  = rtl ? left  + this->count_width  : left  + this->header_width;
 
		int image_right = rtl ? right - this->header_width : right - this->count_width;
 

	
 
		switch (v->type) {
 
			case VEH_TRAIN: {
 
				const Train *u = Train::From(v);
 
				free_wagon = u->IsFreeWagon();
 

	
 
				uint x_space = free_wagon ? TRAININFO_DEFAULT_VEHICLE_WIDTH : 0;
 
				DrawTrainImage(u, image_left + (rtl ? 0 : x_space), image_right - (rtl ? x_space : 0), sprite_y - 1, this->sel, free_wagon ? 0 : this->hscroll.GetPosition());
 

	
 
				/* Number of wagons relative to a standard length wagon (rounded up) */
 
				SetDParam(0, (u->tcache.cached_total_length + 7) / 8);
 
				DrawString(rtl ? left + WD_FRAMERECT_LEFT : right - this->count_width, rtl ? left + this->count_width : right - WD_FRAMERECT_RIGHT, y + (this->resize.step_height - FONT_HEIGHT_SMALL) / 2, STR_TINY_BLACK_COMA, TC_FROMSTRING, SA_RIGHT); // Draw the counter
 
				break;
 
			}
 

	
 
			case VEH_ROAD:     DrawRoadVehImage( v, image_left, image_right, sprite_y, this->sel); break;
 
			case VEH_SHIP:     DrawShipImage(    v, image_left, image_right, sprite_y, this->sel); break;
 
			case VEH_AIRCRAFT: {
 
				const Sprite *spr = GetSprite(v->GetImage(DIR_W), ST_NORMAL);
 
				DrawAircraftImage(v, image_left, image_right,
 
									y + max(spr->height + spr->y_offs - 14, 0), // tall sprites needs an y offset
 
									this->sel);
 
			} break;
 
			default: NOT_REACHED();
 
		}
 

	
 
		uint diff_x, diff_y;
 
		if (v->type == VEH_TRAIN || v->type == VEH_ROAD) {
 
			/* Arrange unitnumber and flag horizontally */
 
			diff_x = this->flag_width + WD_FRAMERECT_LEFT;
 
			diff_y = (this->resize.step_height - this->flag_height) / 2 - 2;
 
		} else {
 
			/* Arrange unitnumber and flag vertically */
 
			diff_x = WD_FRAMERECT_LEFT;
 
			diff_y = FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL;
 
		}
 
		int text_left  = rtl ? right - this->header_width - 1 : left + diff_x;
 
		int text_right = rtl ? right - diff_x : left + this->header_width - 1;
 

	
 
		if (free_wagon) {
 
			DrawString(text_left, text_right, y + 2, STR_DEPOT_NO_ENGINE);
 
		} else {
 
			DrawSprite((v->vehstatus & VS_STOPPED) ? SPR_FLAG_VEH_STOPPED : SPR_FLAG_VEH_RUNNING, PAL_NONE, rtl ? right - this->flag_width : left + WD_FRAMERECT_LEFT, y + diff_y);
 

	
 
			SetDParam(0, v->unitnumber);
 
			DrawString(text_left, text_right, y + 2, (uint16)(v->max_age - DAYS_IN_LEAP_YEAR) >= v->age ? STR_BLACK_COMMA : STR_RED_COMMA);
 
		}
 
	}
 

	
 
	void DrawWidget(const Rect &r, int widget) const
 
	{
 
		if (widget != DEPOT_WIDGET_MATRIX) return;
 

	
 
		bool rtl = _dynlang.text_dir == TD_RTL;
 

	
 
		/* Set the row and number of boxes in each row based on the number of boxes drawn in the matrix */
 
		uint16 mat_data = this->GetWidget<NWidgetCore>(DEPOT_WIDGET_MATRIX)->widget_data;
 
		uint16 rows_in_display   = GB(mat_data, MAT_ROW_START, MAT_ROW_BITS);
 
		uint16 boxes_in_each_row = GB(mat_data, MAT_COL_START, MAT_COL_BITS);
 

	
 
		uint16 num = this->vscroll.GetPosition() * boxes_in_each_row;
 
		int maxval = min(this->vehicle_list.Length(), num + (rows_in_display * boxes_in_each_row));
 
		int y;
 
		for (y = r.top + 1; num < maxval; y += this->resize.step_height) { // Draw the rows
 
			for (byte i = 0; i < boxes_in_each_row && num < maxval; i++, num++) {
 
				/* Draw all vehicles in the current row */
 
				const Vehicle *v = this->vehicle_list[num];
 
				if (boxes_in_each_row == 1) {
 
					this->DrawVehicleInDepot(v, r.left, r.right, y);
 
				} else {
 
					int x = r.left + (rtl ? (boxes_in_each_row - i - 1) : i) * this->resize.step_width;
 
					this->DrawVehicleInDepot(v, x, x + this->resize.step_width - 1, y);
 
				}
 
			}
 
		}
 

	
 
		maxval = min(this->vehicle_list.Length() + this->wagon_list.Length(), (this->vscroll.GetPosition() * boxes_in_each_row) + (rows_in_display * boxes_in_each_row));
 

	
 
		/* draw the train wagons, that do not have an engine in front */
 
		for (; num < maxval; num++, y += 14) {
 
			const Vehicle *v = this->wagon_list[num - this->vehicle_list.Length()];
 
			this->DrawVehicleInDepot(v, r.left, r.right, y);
 
		}
 
	}
 

	
 
	void SetStringParameters(int widget) const
 
	{
 
		if (widget != DEPOT_WIDGET_CAPTION) return;
 

	
 
		/* locate the depot struct */
 
		TileIndex tile = this->window_number;
 
		if (this->type == VEH_AIRCRAFT) {
 
			SetDParam(0, GetStationIndex(tile)); // Airport name
 
		} else {
 
			Depot *depot = Depot::GetByTile(tile);
 
			assert(depot != NULL);
 

	
 
			SetDParam(0, depot->town_index);
 
		}
 
	}
 

	
 
	struct GetDepotVehiclePtData {
 
		const Vehicle *head;
 
		const Vehicle *wagon;
 
	};
 

	
 
	enum DepotGUIAction {
 
		MODE_ERROR,
 
		MODE_DRAG_VEHICLE,
 
		MODE_SHOW_VEHICLE,
 
		MODE_START_STOP,
 
	};
 

	
 
	DepotGUIAction GetVehicleFromDepotWndPt(int x, int y, const Vehicle **veh, GetDepotVehiclePtData *d) const
 
	{
 
		const NWidgetCore *matrix_widget = this->GetWidget<NWidgetCore>(DEPOT_WIDGET_MATRIX);
 
		/* In case of RTL the widgets are swapped as a whole */
 
		if (_dynlang.text_dir == TD_RTL) x = matrix_widget->current_x - x;
 

	
 
		uint xt = 0, xm = 0, ym = 0;
 
		if (this->type == VEH_TRAIN) {
 
			xm = x;
 
		} else {
 
			xt = x / this->resize.step_width;
 
			xm = x % this->resize.step_width;
 
			if (xt >= this->hscroll.GetCapacity()) return MODE_ERROR;
 
		}
 
		ym = y % this->resize.step_height;
 

	
 
		uint row = y / this->resize.step_height;
 
		if (row >= this->vscroll.GetCapacity()) return MODE_ERROR;
 

	
 
		uint boxes_in_each_row = GB(matrix_widget->widget_data, MAT_COL_START, MAT_COL_BITS);
 
		uint pos = ((row + this->vscroll.GetPosition()) * boxes_in_each_row) + xt;
 

	
 
		if (this->vehicle_list.Length() + this->wagon_list.Length() <= pos) {
 
			/* Clicking on 'line' / 'block' without a vehicle */
 
			if (this->type == VEH_TRAIN) {
 
				/* End the dragging */
 
				d->head  = NULL;
 
				d->wagon = NULL;
 
				return MODE_DRAG_VEHICLE;
 
			} else {
 
				return MODE_ERROR; // empty block, so no vehicle is selected
 
			}
 
		}
 

	
 
		bool wagon = false;
 
		if (this->vehicle_list.Length() > pos) {
 
			*veh = this->vehicle_list[pos];
 
			/* Skip vehicles that are scrolled off the list */
 
			x += this->hscroll.GetPosition();
 
		} else {
 
			pos -= this->vehicle_list.Length();
 
			*veh = this->wagon_list[pos];
 
			/* free wagons don't have an initial loco. */
 
			x -= VEHICLEINFO_FULL_VEHICLE_WIDTH;
 
			wagon = true;
 
		}
 

	
 
		const Train *v = NULL;
 
		if (this->type == VEH_TRAIN) {
 
			v = Train::From(*veh);
 
			d->head = d->wagon = v;
 
		}
 

	
 
		if (xm <= this->header_width) {
 
			switch (this->type) {
 
				case VEH_TRAIN:
 
					if (wagon) return MODE_ERROR;
 
				case VEH_ROAD:
 
					if (xm <= this->flag_width) return MODE_START_STOP;
 
					break;
 

	
 
				case VEH_SHIP:
 
				case VEH_AIRCRAFT:
 
					if (xm <= this->flag_width && ym >= (uint)(FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL)) return MODE_START_STOP;
 
					break;
 

	
 
				default: NOT_REACHED();
 
			}
 
			return MODE_SHOW_VEHICLE;
 
		}
 

	
 
		if (this->type != VEH_TRAIN) return MODE_DRAG_VEHICLE;
 

	
 
		/* Clicking on the counter */
 
		if (xm >= matrix_widget->current_x - this->count_width) return wagon ? MODE_ERROR : MODE_SHOW_VEHICLE;
 

	
 
		/* Account for the header */
 
		x -= this->header_width;
 

	
 
		/* find the vehicle in this row that was clicked */
 
		for (; v != NULL; v = v->Next()) {
 
			x -= v->GetDisplayImageWidth();
 
			if (x < 0) break;
 
		}
 

	
 
		d->wagon = (v != NULL ? v->GetFirstEnginePart() : NULL);
 

	
 
		return MODE_DRAG_VEHICLE;
 
	}
 

	
 
	/** Handle click in the depot matrix.
 
	 * @param x Horizontal position in the matrix widget in pixels.
 
	 * @param y Vertical position in the matrix widget in pixels.
 
	 */
 
	void DepotClick(int x, int y)
 
	{
 
		GetDepotVehiclePtData gdvp = { NULL, NULL };
 
		const Vehicle *v = NULL;
 
		DepotGUIAction mode = this->GetVehicleFromDepotWndPt(x, y, &v, &gdvp);
 

	
 
		/* share / copy orders */
 
		if (_thd.place_mode != HT_NONE && mode != MODE_ERROR) {
 
			_place_clicked_vehicle = (this->type == VEH_TRAIN ? gdvp.head : v);
 
			return;
 
		}
 

	
 
		if (this->type == VEH_TRAIN) v = gdvp.wagon;
 

	
 
		switch (mode) {
 
			case MODE_ERROR: // invalid
 
				return;
 

	
 
			case MODE_DRAG_VEHICLE: { // start dragging of vehicle
 
				VehicleID sel = this->sel;
 

	
 
				if (this->type == VEH_TRAIN && sel != INVALID_VEHICLE) {
 
					this->sel = INVALID_VEHICLE;
 
					TrainDepotMoveVehicle(v, sel, gdvp.head);
 
				} else if (v != NULL) {
 
					int image = v->GetImage(DIR_W);
 

	
 
					this->sel = v->index;
 
					this->SetDirty();
 
					SetObjectToPlaceWnd(image, GetVehiclePalette(v), HT_DRAG, this);
 

	
 
					switch (v->type) {
 
						case VEH_TRAIN:
 
							_cursor.short_vehicle_offset = 16 - Train::From(v)->tcache.cached_veh_length * 2;
 
							break;
 

	
 
						case VEH_ROAD:
 
							_cursor.short_vehicle_offset = 16 - RoadVehicle::From(v)->rcache.cached_veh_length * 2;
 
							break;
 

	
 
						default:
 
							_cursor.short_vehicle_offset = 0;
 
							break;
 
					}
 
					_cursor.vehchain = _ctrl_pressed;
 
				}
 
			} break;
 

	
 
			case MODE_SHOW_VEHICLE: // show info window
 
				ShowVehicleViewWindow(v);
 
				break;
 

	
 
			case MODE_START_STOP: { // click start/stop flag
 
				uint command;
 

	
 
				switch (this->type) {
 
					case VEH_TRAIN:    command = CMD_START_STOP_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_STOP_START_TRAIN);        break;
 
					case VEH_ROAD:     command = CMD_START_STOP_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_STOP_START_ROAD_VEHICLE); break;
 
					case VEH_SHIP:     command = CMD_START_STOP_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_STOP_START_SHIP);         break;
 
					case VEH_AIRCRAFT: command = CMD_START_STOP_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_STOP_START_AIRCRAFT);     break;
 
					default: NOT_REACHED();
 
				}
 
				DoCommandP(v->tile, v->index, 0, command);
 
			} break;
 

	
 
			default: NOT_REACHED();
 
		}
 
	}
 

	
 
	/**
 
	 * Clones a vehicle
 
	 * @param *v is the original vehicle to clone
 
	 */
 
	void HandleCloneVehClick(const Vehicle *v)
 
	{
 
		if (v == NULL || !IsCompanyBuildableVehicleType(v)) return;
 

	
 
		if (!v->IsPrimaryVehicle()) {
 
			v = v->First();
 
			/* Do nothing when clicking on a train in depot with no loc attached */
 
			if (v->type == VEH_TRAIN && !Train::From(v)->IsFrontEngine()) return;
 
		}
 

	
 
		DoCommandP(this->window_number, v->index, _ctrl_pressed ? 1 : 0, CMD_CLONE_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_BUY_TRAIN + v->type), CcCloneVehicle);
 

	
 
		ResetObjectToPlace();
 
	}
 

	
 
	/* Function to set up vehicle specific widgets (mainly sprites and strings).
 
	 * Only use this if it's the same widget, that's used for more than one vehicle type and it needs different text/sprites
 
	 * Vehicle specific text/sprites, that's in a widget, that's only shown for one vehicle type (like sell whole train) is set in the nested widget array
 
	 */
 
	void SetupWidgetData(VehicleType type)
 
	{
 
		if (type != VEH_TRAIN) this->GetWidget<NWidgetCore>(DEPOT_WIDGET_SELL_CHAIN)->fill_y = false; // Disable vertical filling of chain-sell widget for non-train windows.
 
		if (type != VEH_TRAIN) this->GetWidget<NWidgetCore>(DEPOT_WIDGET_SELL_CHAIN)->fill_y = 0; // Disable vertical filling of chain-sell widget for non-train windows.
 

	
 
		this->GetWidget<NWidgetCore>(DEPOT_WIDGET_CAPTION)->widget_data   = STR_DEPOT_TRAIN_CAPTION + type;
 
		this->GetWidget<NWidgetCore>(DEPOT_WIDGET_STOP_ALL)->tool_tip     = STR_DEPOT_MASS_STOP_DEPOT_TRAIN_TOOLTIP + type;
 
		this->GetWidget<NWidgetCore>(DEPOT_WIDGET_START_ALL)->tool_tip    = STR_DEPOT_MASS_START_DEPOT_TRAIN_TOOLTIP + type;
 
		this->GetWidget<NWidgetCore>(DEPOT_WIDGET_SELL)->tool_tip         = STR_DEPOT_TRAIN_SELL_TOOLTIP + type;
 
		this->GetWidget<NWidgetCore>(DEPOT_WIDGET_SELL_ALL)->tool_tip     = STR_DEPOT_SELL_ALL_BUTTON_TRAIN_TOOLTIP + type;
 

	
 
		this->GetWidget<NWidgetCore>(DEPOT_WIDGET_BUILD)->SetDataTip(STR_DEPOT_TRAIN_NEW_VEHICLES_BUTTON + type, STR_DEPOT_TRAIN_NEW_VEHICLES_TOOLTIP + type);
 
		this->GetWidget<NWidgetCore>(DEPOT_WIDGET_CLONE)->SetDataTip(STR_DEPOT_CLONE_TRAIN + type, STR_DEPOT_CLONE_TRAIN_DEPOT_INFO + type);
 

	
 
		this->GetWidget<NWidgetCore>(DEPOT_WIDGET_LOCATION)->tool_tip     = STR_DEPOT_TRAIN_LOCATION_TOOLTIP + type;
 
		this->GetWidget<NWidgetCore>(DEPOT_WIDGET_VEHICLE_LIST)->tool_tip = STR_DEPOT_VEHICLE_ORDER_LIST_TRAIN_TOOLTIP + type;
 
		this->GetWidget<NWidgetCore>(DEPOT_WIDGET_AUTOREPLACE)->tool_tip  = STR_DEPOT_AUTOREPLACE_TRAIN_TOOLTIP + type;
 

	
 
		switch (type) {
 
			default: NOT_REACHED();
 

	
 
			case VEH_TRAIN:
 
				this->GetWidget<NWidgetCore>(DEPOT_WIDGET_VEHICLE_LIST)->widget_data = STR_TRAIN;
 

	
 
				/* Sprites */
 
				this->GetWidget<NWidgetCore>(DEPOT_WIDGET_SELL)->widget_data        = SPR_SELL_TRAIN;
 
				this->GetWidget<NWidgetCore>(DEPOT_WIDGET_SELL_ALL)->widget_data    = SPR_SELL_ALL_TRAIN;
 
				this->GetWidget<NWidgetCore>(DEPOT_WIDGET_AUTOREPLACE)->widget_data = SPR_REPLACE_TRAIN;
 
				break;
 

	
 
			case VEH_ROAD:
 
				this->GetWidget<NWidgetCore>(DEPOT_WIDGET_VEHICLE_LIST)->widget_data = STR_LORRY;
 

	
 
				/* Sprites */
 
				this->GetWidget<NWidgetCore>(DEPOT_WIDGET_SELL)->widget_data        = SPR_SELL_ROADVEH;
 
				this->GetWidget<NWidgetCore>(DEPOT_WIDGET_SELL_ALL)->widget_data    = SPR_SELL_ALL_ROADVEH;
 
				this->GetWidget<NWidgetCore>(DEPOT_WIDGET_AUTOREPLACE)->widget_data = SPR_REPLACE_ROADVEH;
 
				break;
 

	
 
			case VEH_SHIP:
 
				this->GetWidget<NWidgetCore>(DEPOT_WIDGET_VEHICLE_LIST)->widget_data = STR_SHIP;
 

	
 
				/* Sprites */
 
				this->GetWidget<NWidgetCore>(DEPOT_WIDGET_SELL)->widget_data        = SPR_SELL_SHIP;
 
				this->GetWidget<NWidgetCore>(DEPOT_WIDGET_SELL_ALL)->widget_data    = SPR_SELL_ALL_SHIP;
 
				this->GetWidget<NWidgetCore>(DEPOT_WIDGET_AUTOREPLACE)->widget_data = SPR_REPLACE_SHIP;
 
				break;
 

	
 
			case VEH_AIRCRAFT:
 
				this->GetWidget<NWidgetCore>(DEPOT_WIDGET_VEHICLE_LIST)->widget_data = STR_PLANE;
 

	
 
				/* Sprites */
 
				this->GetWidget<NWidgetCore>(DEPOT_WIDGET_SELL)->widget_data        = SPR_SELL_AIRCRAFT;
 
				this->GetWidget<NWidgetCore>(DEPOT_WIDGET_SELL_ALL)->widget_data    = SPR_SELL_ALL_AIRCRAFT;
 
				this->GetWidget<NWidgetCore>(DEPOT_WIDGET_AUTOREPLACE)->widget_data = SPR_REPLACE_AIRCRAFT;
 
				break;
 
		}
 
	}
 

	
 
	uint count_width;
 
	uint header_width;
 
	uint flag_width;
 
	uint flag_height;
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		switch (widget) {
 
			case DEPOT_WIDGET_SELL_CHAIN:
 
			case DEPOT_WIDGET_H_SCROLL:
 
				/* Hide the 'sell chain' and the horizontal scrollbar when not a train depot. */
 
				if (this->type != VEH_TRAIN) {
 
					size->height = 0;
 
					resize->height = 0;
 
				}
 
				break;
 

	
 
			case DEPOT_WIDGET_MATRIX: {
 
				uint min_height = 0;
 

	
 
				if (this->type == VEH_TRAIN) {
 
					SetDParam(0, 100);
 
					this->count_width = GetStringBoundingBox(STR_TINY_BLACK_COMA).width + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
 
				} else {
 
					this->count_width = 0;
 
				}
 

	
 
				SetDParam(0, 999);
 
				Dimension unumber = GetStringBoundingBox(STR_BLACK_COMMA);
 
				const Sprite *spr = GetSprite(SPR_FLAG_VEH_STOPPED, ST_NORMAL);
 
				this->flag_width  = spr->width + WD_FRAMERECT_RIGHT;
 
				this->flag_height = spr->height;
 

	
 
				if (this->type == VEH_TRAIN || this->type == VEH_ROAD) {
 
					min_height = max<uint>(unumber.height + WD_MATRIX_TOP, spr->height);
 
					this->header_width = unumber.width + this->flag_width;
 
				} else {
 
					min_height = unumber.height + spr->height + WD_MATRIX_TOP + WD_PAR_VSEP_NORMAL + WD_MATRIX_BOTTOM;
 
					this->header_width = max<uint>(unumber.width, this->flag_width) + WD_FRAMERECT_RIGHT;
 
				}
 
				int base_width = this->count_width + this->header_width;
 

	
 
				resize->height = max(_base_block_sizes[this->type].height, min_height);
 
				if (this->type == VEH_TRAIN) {
 
					resize->width = 1;
 
					size->width = base_width + 2 * 29; // about 2 parts
 
					size->height = resize->height * 6;
 
				} else {
 
					resize->width = base_width + _base_block_sizes[this->type].width;
 
					size->width = resize->width * (this->type == VEH_ROAD ? 5 : 3);
 
					size->height = resize->height * (this->type == VEH_ROAD ? 5 : 3);
 
				}
 
			} break;
 
		}
 
	}
 

	
 
	virtual void OnInvalidateData(int data)
 
	{
 
		this->generate_list = true;
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		if (this->generate_list) {
 
			/* Generate the vehicle list
 
			 * It's ok to use the wagon pointers for non-trains as they will be ignored */
 
			BuildDepotVehicleList(this->type, this->window_number, &this->vehicle_list, &this->wagon_list);
 
			this->generate_list = false;
 
			DepotSortList(&this->vehicle_list);
 
		}
 

	
 
		/* determine amount of items for scroller */
 
		if (this->type == VEH_TRAIN) {
 
			uint max_width = VEHICLEINFO_FULL_VEHICLE_WIDTH;
 
			for (uint num = 0; num < this->vehicle_list.Length(); num++) {
 
				uint width = 0;
 
				for (const Train *v = Train::From(this->vehicle_list[num]); v != NULL; v = v->Next()) {
 
					width += v->GetDisplayImageWidth();
 
				}
 
				max_width = max(max_width, width);
 
			}
 
			/* Always have 1 empty row, so people can change the setting of the train */
 
			this->vscroll.SetCount(this->vehicle_list.Length() + this->wagon_list.Length() + 1);
 
			this->hscroll.SetCount(max_width);
 
		} else {
 
			this->vscroll.SetCount((this->vehicle_list.Length() + this->hscroll.GetCapacity() - 1) / this->hscroll.GetCapacity());
 
		}
 

	
 
		/* Setup disabled buttons. */
 
		TileIndex tile = this->window_number;
 
		this->SetWidgetsDisabledState(!IsTileOwner(tile, _local_company),
 
			DEPOT_WIDGET_STOP_ALL,
 
			DEPOT_WIDGET_START_ALL,
 
			DEPOT_WIDGET_SELL,
 
			DEPOT_WIDGET_SELL_CHAIN,
 
			DEPOT_WIDGET_SELL_ALL,
 
			DEPOT_WIDGET_BUILD,
 
			DEPOT_WIDGET_CLONE,
 
			DEPOT_WIDGET_AUTOREPLACE,
 
			WIDGET_LIST_END);
 

	
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case DEPOT_WIDGET_MATRIX: { // List
 
				NWidgetBase *nwi = this->GetWidget<NWidgetBase>(DEPOT_WIDGET_MATRIX);
 
				this->DepotClick(pt.x - nwi->pos_x, pt.y - nwi->pos_y);
 
				break;
 
			}
 

	
 
			case DEPOT_WIDGET_BUILD: // Build vehicle
 
				ResetObjectToPlace();
 
				ShowBuildVehicleWindow(this->window_number, this->type);
 
				break;
 

	
 
			case DEPOT_WIDGET_CLONE: // Clone button
 
				this->SetWidgetDirty(DEPOT_WIDGET_CLONE);
 
				this->ToggleWidgetLoweredState(DEPOT_WIDGET_CLONE);
 

	
 
				if (this->IsWidgetLowered(DEPOT_WIDGET_CLONE)) {
 
					static const CursorID clone_icons[] = {
 
						SPR_CURSOR_CLONE_TRAIN, SPR_CURSOR_CLONE_ROADVEH,
 
						SPR_CURSOR_CLONE_SHIP, SPR_CURSOR_CLONE_AIRPLANE
 
					};
 

	
 
					_place_clicked_vehicle = NULL;
 
					SetObjectToPlaceWnd(clone_icons[this->type], PAL_NONE, HT_RECT, this);
 
				} else {
 
					ResetObjectToPlace();
 
				}
 
					break;
 

	
 
			case DEPOT_WIDGET_LOCATION:
 
				if (_ctrl_pressed) {
 
					ShowExtraViewPortWindow(this->window_number);
 
				} else {
 
					ScrollMainWindowToTile(this->window_number);
 
				}
 
				break;
 

	
 
			case DEPOT_WIDGET_STOP_ALL:
 
			case DEPOT_WIDGET_START_ALL:
 
				DoCommandP(this->window_number, 0, this->type | (widget == DEPOT_WIDGET_START_ALL ? (1 << 5) : 0), CMD_MASS_START_STOP);
 
				break;
 

	
 
			case DEPOT_WIDGET_SELL_ALL:
 
				/* Only open the confimation window if there are anything to sell */
 
				if (this->vehicle_list.Length() != 0 || this->wagon_list.Length() != 0) {
 
					TileIndex tile = this->window_number;
 
					byte vehtype = this->type;
 

	
 
					SetDParam(0, (vehtype == VEH_AIRCRAFT) ? GetStationIndex(tile) : Depot::GetByTile(tile)->town_index);
 
					ShowQuery(
 
						STR_DEPOT_TRAIN_CAPTION + vehtype,
 
						STR_DEPOT_SELL_CONFIRMATION_TEXT,
 
						this,
 
						DepotSellAllConfirmationCallback
 
					);
 
				}
 
				break;
 

	
 
			case DEPOT_WIDGET_VEHICLE_LIST:
 
				ShowVehicleListWindow(GetTileOwner(this->window_number), this->type, (TileIndex)this->window_number);
 
				break;
 

	
 
			case DEPOT_WIDGET_AUTOREPLACE:
 
				DoCommandP(this->window_number, this->type, 0, CMD_DEPOT_MASS_AUTOREPLACE);
 
				break;
 

	
 
		}
 
	}
 

	
 
	virtual void OnRightClick(Point pt, int widget)
 
	{
 
		if (widget != DEPOT_WIDGET_MATRIX) return;
 

	
 
		GetDepotVehiclePtData gdvp = { NULL, NULL };
 
		const Vehicle *v = NULL;
 
		NWidgetBase *nwi = this->GetWidget<NWidgetBase>(DEPOT_WIDGET_MATRIX);
 
		DepotGUIAction mode = this->GetVehicleFromDepotWndPt(pt.x - nwi->pos_x, pt.y - nwi->pos_y, &v, &gdvp);
 

	
 
		if (this->type == VEH_TRAIN) v = gdvp.wagon;
 

	
 
		if (v != NULL && mode == MODE_DRAG_VEHICLE) {
 
			CargoArray capacity, loaded;
 

	
 
			/* Display info for single (articulated) vehicle, or for whole chain starting with selected vehicle */
 
			bool whole_chain = (this->type == VEH_TRAIN && _ctrl_pressed);
 

	
 
			/* loop through vehicle chain and collect cargos */
 
			uint num = 0;
 
			for (const Vehicle *w = v; w != NULL; w = w->Next()) {
 
				if (w->cargo_cap > 0 && w->cargo_type < NUM_CARGO) {
 
					capacity[w->cargo_type] += w->cargo_cap;
 
					loaded  [w->cargo_type] += w->cargo.Count();
 
				}
 

	
 
				if (w->type == VEH_TRAIN && !Train::From(w)->HasArticulatedPart()) {
 
					num++;
 
					if (!whole_chain) break;
 
				}
 
			}
 

	
 
			/* Build tooltipstring */
 
			static char details[1024];
 
			details[0] = '\0';
 
			char *pos = details;
 

	
 
			for (CargoID cargo_type = 0; cargo_type < NUM_CARGO; cargo_type++) {
 
				if (capacity[cargo_type] == 0) continue;
 

	
 
				SetDParam(0, cargo_type);           // {CARGO} #1
 
				SetDParam(1, loaded[cargo_type]);   // {CARGO} #2
 
				SetDParam(2, cargo_type);           // {SHORTCARGO} #1
 
				SetDParam(3, capacity[cargo_type]); // {SHORTCARGO} #2
 
				pos = GetString(pos, STR_DEPOT_VEHICLE_TOOLTIP_CARGO, lastof(details));
 
			}
 

	
 
			/* Show tooltip window */
 
			uint64 args[2];
 
			args[0] = (whole_chain ? num : v->engine_type);
 
			args[1] = (uint64)(size_t)details;
 
			GuiShowTooltips(whole_chain ? STR_DEPOT_VEHICLE_TOOLTIP_CHAIN : STR_DEPOT_VEHICLE_TOOLTIP, 2, args);
 
		} else {
 
			/* Show tooltip help */
 
			GuiShowTooltips(STR_DEPOT_TRAIN_LIST_TOOLTIP + this->type);
 
		}
 
	}
 

	
 

	
 
	virtual void OnPlaceObject(Point pt, TileIndex tile)
 
	{
 
		const Vehicle *v = CheckMouseOverVehicle();
 

	
 
		if (v != NULL) this->HandleCloneVehClick(v);
 
	}
 

	
 
	virtual void OnPlaceObjectAbort()
 
	{
 
		/* abort clone */
 
		this->RaiseWidget(DEPOT_WIDGET_CLONE);
 
		this->SetWidgetDirty(DEPOT_WIDGET_CLONE);
 

	
 
		/* abort drag & drop */
 
		this->sel = INVALID_VEHICLE;
 
		this->SetWidgetDirty(DEPOT_WIDGET_MATRIX);
 
	};
 

	
 
	/* check if a vehicle in a depot was clicked.. */
 
	virtual void OnMouseLoop()
 
	{
 
		const Vehicle *v = _place_clicked_vehicle;
 

	
 
		/* since OTTD checks all open depot windows, we will make sure that it triggers the one with a clicked clone button */
 
		if (v != NULL && this->IsWidgetLowered(DEPOT_WIDGET_CLONE)) {
 
			_place_clicked_vehicle = NULL;
 
			this->HandleCloneVehClick(v);
 
		}
 
	}
 

	
 
	virtual void OnDragDrop(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case DEPOT_WIDGET_MATRIX: {
 
				const Vehicle *v = NULL;
 
				VehicleID sel = this->sel;
 

	
 
				this->sel = INVALID_VEHICLE;
 
				this->SetDirty();
 

	
 
				NWidgetBase *nwi = this->GetWidget<NWidgetBase>(DEPOT_WIDGET_MATRIX);
 
				if (this->type == VEH_TRAIN) {
 
					GetDepotVehiclePtData gdvp = { NULL, NULL };
 

	
 
					if (this->GetVehicleFromDepotWndPt(pt.x - nwi->pos_x, pt.y - nwi->pos_y, &v, &gdvp) == MODE_DRAG_VEHICLE && sel != INVALID_VEHICLE) {
 
						if (gdvp.wagon != NULL && gdvp.wagon->index == sel && _ctrl_pressed) {
 
							DoCommandP(Vehicle::Get(sel)->tile, Vehicle::Get(sel)->index, true,
 
									CMD_REVERSE_TRAIN_DIRECTION | CMD_MSG(STR_ERROR_CAN_T_REVERSE_DIRECTION_RAIL_VEHICLE));
 
						} else if (gdvp.wagon == NULL || gdvp.wagon->index != sel) {
 
							TrainDepotMoveVehicle(gdvp.wagon, sel, gdvp.head);
 
						} else if (gdvp.head != NULL && Train::From(gdvp.head)->IsFrontEngine()) {
 
							ShowVehicleViewWindow(gdvp.head);
 
						}
 
					}
 
				} else if (this->GetVehicleFromDepotWndPt(pt.x - nwi->pos_x, pt.y - nwi->pos_y, &v, NULL) == MODE_DRAG_VEHICLE && v != NULL && sel == v->index) {
 
					ShowVehicleViewWindow(v);
 
				}
 
			} break;
 

	
 
			case DEPOT_WIDGET_SELL: case DEPOT_WIDGET_SELL_CHAIN: {
 
				if (this->IsWidgetDisabled(widget)) return;
 
				if (this->sel == INVALID_VEHICLE) return;
 

	
 
				this->HandleButtonClick(widget);
 

	
 
				const Vehicle *v = Vehicle::Get(this->sel);
 
				this->sel = INVALID_VEHICLE;
 
				this->SetDirty();
 

	
 
				int sell_cmd = (v->type == VEH_TRAIN && (widget == DEPOT_WIDGET_SELL_CHAIN || _ctrl_pressed)) ? 1 : 0;
 

	
 
				bool is_engine = (v->type != VEH_TRAIN || Train::From(v)->IsFrontEngine());
 

	
 
				if (is_engine) {
 
					_backup_orders_tile = v->tile;
 
					BackupVehicleOrders(v);
 
				}
 

	
 
				if (!DoCommandP(v->tile, v->index, sell_cmd, GetCmdSellVeh(v->type)) && is_engine) _backup_orders_tile = 0;
 
			} break;
 

	
 
			default:
 
				this->sel = INVALID_VEHICLE;
 
				this->SetDirty();
 
		}
 
		_cursor.vehchain = false;
 
	}
 

	
 
	virtual void OnTimeout()
 
	{
 
		if (!this->IsWidgetDisabled(DEPOT_WIDGET_SELL)) {
 
			this->RaiseWidget(DEPOT_WIDGET_SELL);
 
			this->SetWidgetDirty(DEPOT_WIDGET_SELL);
 
		}
 
		if (this->nested_array[DEPOT_WIDGET_SELL] != NULL && !this->IsWidgetDisabled(DEPOT_WIDGET_SELL_CHAIN)) {
 
			this->RaiseWidget(DEPOT_WIDGET_SELL_CHAIN);
 
			this->SetWidgetDirty(DEPOT_WIDGET_SELL_CHAIN);
 
		}
 
	}
 

	
 
	virtual void OnResize()
 
	{
 
		NWidgetCore *nwi = this->GetWidget<NWidgetCore>(DEPOT_WIDGET_MATRIX);
 
		this->vscroll.SetCapacity(nwi->current_y / (int)this->resize.step_height);
 
		if (this->type == VEH_TRAIN) {
 
			this->hscroll.SetCapacity(nwi->current_x - this->header_width - this->count_width);
 
			nwi->widget_data = (this->vscroll.GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START);
 
		} else {
 
			this->hscroll.SetCapacity(nwi->current_x / (int)this->resize.step_width);
 
			nwi->widget_data = (this->vscroll.GetCapacity() << MAT_ROW_START) + (this->hscroll.GetCapacity() << MAT_COL_START);
 
		}
 
	}
 

	
 
	virtual EventState OnCTRLStateChange()
 
	{
 
		if (this->sel != INVALID_VEHICLE) {
 
			_cursor.vehchain = _ctrl_pressed;
 
			this->SetWidgetDirty(DEPOT_WIDGET_MATRIX);
 
			return ES_HANDLED;
 
		}
 

	
 
		return ES_NOT_HANDLED;
 
	}
 
};
 

	
 
static void DepotSellAllConfirmationCallback(Window *win, bool confirmed)
 
{
 
	if (confirmed) {
 
		DepotWindow *w = (DepotWindow*)win;
 
		TileIndex tile = w->window_number;
 
		byte vehtype = w->type;
 
		DoCommandP(tile, vehtype, 0, CMD_DEPOT_SELL_ALL_VEHICLES);
 
	}
 
}
 

	
 
/** Opens a depot window
 
 * @param tile The tile where the depot/hangar is located
 
 * @param type The type of vehicles in the depot
 
 */
 
void ShowDepotWindow(TileIndex tile, VehicleType type)
 
{
 
	if (BringWindowToFrontById(WC_VEHICLE_DEPOT, tile) != NULL) return;
 

	
 
	const WindowDesc *desc;
 
	switch (type) {
 
		default: NOT_REACHED();
 
		case VEH_TRAIN:    desc = &_train_depot_desc;    break;
 
		case VEH_ROAD:     desc = &_road_depot_desc;     break;
 
		case VEH_SHIP:     desc = &_ship_depot_desc;     break;
 
		case VEH_AIRCRAFT: desc = &_aircraft_depot_desc; break;
 
	}
 

	
 
	new DepotWindow(desc, tile, type);
 
}
 

	
 
/** Removes the highlight of a vehicle in a depot window
 
 * @param *v Vehicle to remove all highlights from
 
 */
 
void DeleteDepotHighlightOfVehicle(const Vehicle *v)
 
{
 
	DepotWindow *w;
 

	
 
	/* If we haven't got any vehicles on the mouse pointer, we haven't got any highlighted in any depots either
 
	 * If that is the case, we can skip looping though the windows and save time
 
	 */
 
	if (_special_mouse_mode != WSM_DRAGDROP) return;
 

	
 
	w = dynamic_cast<DepotWindow*>(FindWindowById(WC_VEHICLE_DEPOT, v->tile));
 
	if (w != NULL) {
 
		if (w->sel == v->index) ResetObjectToPlace();
 
	}
 
}
src/dock_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file dock_gui.cpp GUI to create amazing water objects. */
 

	
 
#include "stdafx.h"
 
#include "openttd.h"
 
#include "tile_map.h"
 
#include "terraform_gui.h"
 
#include "window_gui.h"
 
#include "station_gui.h"
 
#include "command_func.h"
 
#include "water.h"
 
#include "window_func.h"
 
#include "vehicle_func.h"
 
#include "sound_func.h"
 
#include "viewport_func.h"
 
#include "gfx_func.h"
 
#include "company_func.h"
 
#include "slope_func.h"
 
#include "tilehighlight_func.h"
 
#include "company_base.h"
 

	
 
#include "table/sprites.h"
 
#include "table/strings.h"
 

	
 
static void ShowBuildDockStationPicker(Window *parent);
 
static void ShowBuildDocksDepotPicker(Window *parent);
 

	
 
static Axis _ship_depot_direction;
 

	
 
void CcBuildDocks(bool success, TileIndex tile, uint32 p1, uint32 p2)
 
{
 
	if (success) {
 
		SndPlayTileFx(SND_02_SPLAT, tile);
 
		if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace();
 
	}
 
}
 

	
 
void CcBuildCanal(bool success, TileIndex tile, uint32 p1, uint32 p2)
 
{
 
	if (success) SndPlayTileFx(SND_02_SPLAT, tile);
 
}
 

	
 

	
 
static void PlaceDocks_Dock(TileIndex tile)
 
{
 
	uint32 p2 = (uint32)INVALID_STATION << 16; // no station to join
 

	
 
	/* tile is always the land tile, so need to evaluate _thd.pos */
 
	CommandContainer cmdcont = { tile, _ctrl_pressed, p2, CMD_BUILD_DOCK | CMD_MSG(STR_ERROR_CAN_T_BUILD_DOCK_HERE), CcBuildDocks, "" };
 
	ShowSelectStationIfNeeded(cmdcont, TileArea(tile, _thd.size.x / TILE_SIZE, _thd.size.y / TILE_SIZE));
 
}
 

	
 
static void PlaceDocks_Depot(TileIndex tile)
 
{
 
	DoCommandP(tile, _ship_depot_direction, 0, CMD_BUILD_SHIP_DEPOT | CMD_MSG(STR_ERROR_CAN_T_BUILD_SHIP_DEPOT), CcBuildDocks);
 
}
 

	
 
static void PlaceDocks_Buoy(TileIndex tile)
 
{
 
	DoCommandP(tile, 0, 0, CMD_BUILD_BUOY | CMD_MSG(STR_ERROR_CAN_T_POSITION_BUOY_HERE), CcBuildDocks);
 
}
 

	
 
static void PlaceDocks_BuildCanal(TileIndex tile)
 
{
 
	VpStartPlaceSizing(tile, (_game_mode == GM_EDITOR) ? VPM_X_AND_Y : VPM_X_OR_Y, DDSP_CREATE_WATER);
 
}
 

	
 
static void PlaceDocks_BuildLock(TileIndex tile)
 
{
 
	DoCommandP(tile, 0, 0, CMD_BUILD_LOCK | CMD_MSG(STR_ERROR_CAN_T_BUILD_LOCKS), CcBuildDocks);
 
}
 

	
 
static void PlaceDocks_BuildRiver(TileIndex tile)
 
{
 
	VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_CREATE_RIVER);
 
}
 

	
 
static void PlaceDocks_Aqueduct(TileIndex tile)
 
{
 
	VpStartPlaceSizing(tile, VPM_X_OR_Y, DDSP_BUILD_BRIDGE);
 
}
 

	
 

	
 
/** Enum referring to the widgets of the build dock toolbar */
 
enum DockToolbarWidgets {
 
	DTW_BEGIN = 0,                 ///< Start of toolbar widgets
 
	DTW_CLOSEBOX = DTW_BEGIN,      ///< Close window button
 
	DTW_CAPTION,                   ///< Window caption
 
	DTW_STICKY,                    ///< Sticky window button
 
	DTW_BUTTONS_BEGIN,             ///< Begin of clickable buttons (except seperating panel)
 
	DTW_CANAL = DTW_BUTTONS_BEGIN, ///< Build canal button
 
	DTW_LOCK,                      ///< Build lock button
 
	DTW_SEPERATOR,                 ///< Seperating panel between lock and demolish
 
	DTW_DEMOLISH,                  ///< Demolish aka dynamite button
 
	DTW_DEPOT,                     ///< Build depot button
 
	DTW_STATION,                   ///< Build station button
 
	DTW_BUOY,                      ///< Build buoy button
 
	DTW_RIVER,                     ///< Build river button (in scenario editor)
 
	DTW_BUILD_AQUEDUCT,            ///< Build aqueduct button
 
	DTW_END,                       ///< End of toolbar widgets
 
};
 

	
 

	
 
static void BuildDocksClick_Canal(Window *w)
 
{
 

	
 
	HandlePlacePushButton(w, DTW_CANAL, SPR_CURSOR_CANAL, HT_RECT, PlaceDocks_BuildCanal);
 
}
 

	
 
static void BuildDocksClick_Lock(Window *w)
 
{
 
	HandlePlacePushButton(w, DTW_LOCK, SPR_CURSOR_LOCK, HT_RECT, PlaceDocks_BuildLock);
 
}
 

	
 
static void BuildDocksClick_Demolish(Window *w)
 
{
 
	HandlePlacePushButton(w, DTW_DEMOLISH, ANIMCURSOR_DEMOLISH, HT_RECT, PlaceProc_DemolishArea);
 
}
 

	
 
static void BuildDocksClick_Depot(Window *w)
 
{
 
	if (!CanBuildVehicleInfrastructure(VEH_SHIP)) return;
 
	if (HandlePlacePushButton(w, DTW_DEPOT, SPR_CURSOR_SHIP_DEPOT, HT_RECT, PlaceDocks_Depot)) ShowBuildDocksDepotPicker(w);
 
}
 

	
 
static void BuildDocksClick_Dock(Window *w)
 
{
 
	if (!CanBuildVehicleInfrastructure(VEH_SHIP)) return;
 
	if (HandlePlacePushButton(w, DTW_STATION, SPR_CURSOR_DOCK, HT_SPECIAL, PlaceDocks_Dock)) ShowBuildDockStationPicker(w);
 
}
 

	
 
static void BuildDocksClick_Buoy(Window *w)
 
{
 
	if (!CanBuildVehicleInfrastructure(VEH_SHIP)) return;
 
	HandlePlacePushButton(w, DTW_BUOY, SPR_CURSOR_BOUY, HT_RECT, PlaceDocks_Buoy);
 
}
 

	
 
static void BuildDocksClick_River(Window *w)
 
{
 
	if (_game_mode != GM_EDITOR) return;
 
	HandlePlacePushButton(w, DTW_RIVER, SPR_CURSOR_RIVER, HT_RECT, PlaceDocks_BuildRiver);
 
}
 

	
 
static void BuildDocksClick_Aqueduct(Window *w)
 
{
 
	HandlePlacePushButton(w, DTW_BUILD_AQUEDUCT, SPR_CURSOR_AQUEDUCT, HT_RECT, PlaceDocks_Aqueduct);
 
}
 

	
 

	
 
typedef void OnButtonClick(Window *w);
 
static OnButtonClick * const _build_docks_button_proc[] = {
 
	BuildDocksClick_Canal,
 
	BuildDocksClick_Lock,
 
	NULL,
 
	BuildDocksClick_Demolish,
 
	BuildDocksClick_Depot,
 
	BuildDocksClick_Dock,
 
	BuildDocksClick_Buoy,
 
	BuildDocksClick_River,
 
	BuildDocksClick_Aqueduct
 
};
 

	
 
struct BuildDocksToolbarWindow : Window {
 
	BuildDocksToolbarWindow(const WindowDesc *desc, WindowNumber window_number) : Window()
 
	{
 
		this->InitNested(desc, window_number);
 
		if (_settings_client.gui.link_terraform_toolbar) ShowTerraformToolbar(this);
 
	}
 

	
 
	~BuildDocksToolbarWindow()
 
	{
 
		if (_settings_client.gui.link_terraform_toolbar) DeleteWindowById(WC_SCEN_LAND_GEN, 0, false);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->SetWidgetsDisabledState(!CanBuildVehicleInfrastructure(VEH_SHIP), DTW_DEPOT, DTW_STATION, DTW_BUOY, WIDGET_LIST_END);
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		if (widget >= DTW_BUTTONS_BEGIN && widget != DTW_SEPERATOR) _build_docks_button_proc[widget - DTW_BUTTONS_BEGIN](this);
 
	}
 

	
 
	virtual EventState OnKeyPress(uint16 key, uint16 keycode)
 
	{
 
		switch (keycode) {
 
			case '1': BuildDocksClick_Canal(this); break;
 
			case '2': BuildDocksClick_Lock(this); break;
 
			case '3': BuildDocksClick_Demolish(this); break;
 
			case '4': BuildDocksClick_Depot(this); break;
 
			case '5': BuildDocksClick_Dock(this); break;
 
			case '6': BuildDocksClick_Buoy(this); break;
 
			case '7': BuildDocksClick_River(this); break;
 
			case 'B':
 
			case '8': BuildDocksClick_Aqueduct(this); break;
 
			default:  return ES_NOT_HANDLED;
 
		}
 
		return ES_HANDLED;
 
	}
 

	
 
	virtual void OnPlaceObject(Point pt, TileIndex tile)
 
	{
 
		_place_proc(tile);
 
	}
 

	
 
	virtual void OnPlaceDrag(ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, Point pt)
 
	{
 
		VpSelectTilesWithMethod(pt.x, pt.y, select_method);
 
	}
 

	
 
	virtual void OnPlaceMouseUp(ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, Point pt, TileIndex start_tile, TileIndex end_tile)
 
	{
 
		if (pt.x != -1) {
 
			switch (select_proc) {
 
				case DDSP_BUILD_BRIDGE:
 
					if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace();
 
					extern void CcBuildBridge(bool success, TileIndex tile, uint32 p1, uint32 p2);
 
					DoCommandP(end_tile, start_tile, TRANSPORT_WATER << 15, CMD_BUILD_BRIDGE | CMD_MSG(STR_ERROR_CAN_T_BUILD_AQUEDUCT_HERE), CcBuildBridge);
 

	
 
				case DDSP_DEMOLISH_AREA:
 
					GUIPlaceProcDragXY(select_proc, start_tile, end_tile);
 
					break;
 
				case DDSP_CREATE_WATER:
 
					DoCommandP(end_tile, start_tile, (_game_mode == GM_EDITOR ? _ctrl_pressed : 0), CMD_BUILD_CANAL | CMD_MSG(STR_ERROR_CAN_T_BUILD_CANALS), CcBuildCanal);
 
					break;
 
				case DDSP_CREATE_RIVER:
 
					DoCommandP(end_tile, start_tile, 2, CMD_BUILD_CANAL | CMD_MSG(STR_ERROR_CAN_T_PLACE_RIVERS), CcBuildCanal);
 
					break;
 

	
 
				default: break;
 
			}
 
		}
 
	}
 

	
 
	virtual void OnPlaceObjectAbort()
 
	{
 
		this->RaiseButtons();
 

	
 
		DeleteWindowById(WC_BUILD_STATION, TRANSPORT_WATER);
 
		DeleteWindowById(WC_BUILD_DEPOT, TRANSPORT_WATER);
 
		DeleteWindowById(WC_SELECT_STATION, 0);
 
		DeleteWindowByClass(WC_BUILD_BRIDGE);
 
	}
 

	
 
	virtual void OnPlacePresize(Point pt, TileIndex tile_from)
 
	{
 
		DiagDirection dir = GetInclinedSlopeDirection(GetTileSlope(tile_from, NULL));
 
		TileIndex tile_to = (dir != INVALID_DIAGDIR ? TileAddByDiagDir(tile_from, ReverseDiagDir(dir)) : tile_from);
 

	
 
		VpSetPresizeRange(tile_from, tile_to);
 
	}
 
};
 

	
 

	
 
/**
 
 * Nested widget parts of docks toolbar, game version.
 
 * Position of #DTW_RIVER widget has changed.
 
 */
 
static const NWidgetPart _nested_build_docks_toolbar_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN, DTW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, DTW_CAPTION), SetDataTip(STR_WATERWAYS_TOOLBAR_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_STICKYBOX, COLOUR_DARK_GREEN, DTW_STICKY),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL_LTR),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, DTW_CANAL), SetMinimalSize(22, 22), SetFill(false, true), SetDataTip(SPR_IMG_BUILD_CANAL, STR_WATERWAYS_TOOLBAR_BUILD_CANALS_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, DTW_LOCK), SetMinimalSize(22, 22), SetFill(false, true), SetDataTip(SPR_IMG_BUILD_LOCK, STR_WATERWAYS_TOOLBAR_BUILD_LOCKS_TOOLTIP),
 
		NWidget(WWT_PANEL, COLOUR_DARK_GREEN, DTW_SEPERATOR), SetMinimalSize(5, 22), SetFill(true, true), EndContainer(),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, DTW_DEMOLISH), SetMinimalSize(22, 22), SetFill(false, true), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, DTW_DEPOT), SetMinimalSize(22, 22), SetFill(false, true), SetDataTip(SPR_IMG_SHIP_DEPOT, STR_WATERWAYS_TOOLBAR_BUILD_DEPOT_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, DTW_STATION), SetMinimalSize(22, 22), SetFill(false, true), SetDataTip(SPR_IMG_SHIP_DOCK, STR_WATERWAYS_TOOLBAR_BUILD_DOCK_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, DTW_BUOY), SetMinimalSize(22, 22), SetFill(false, true), SetDataTip(SPR_IMG_BOUY, STR_WATERWAYS_TOOLBAR_BUOY_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, DTW_BUILD_AQUEDUCT), SetMinimalSize(23, 22), SetFill(false, true), SetDataTip(SPR_IMG_AQUEDUCT, STR_WATERWAYS_TOOLBAR_BUILD_AQUEDUCT_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, DTW_CANAL), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_BUILD_CANAL, STR_WATERWAYS_TOOLBAR_BUILD_CANALS_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, DTW_LOCK), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_BUILD_LOCK, STR_WATERWAYS_TOOLBAR_BUILD_LOCKS_TOOLTIP),
 
		NWidget(WWT_PANEL, COLOUR_DARK_GREEN, DTW_SEPERATOR), SetMinimalSize(5, 22), SetFill(1, 1), EndContainer(),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, DTW_DEMOLISH), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, DTW_DEPOT), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_SHIP_DEPOT, STR_WATERWAYS_TOOLBAR_BUILD_DEPOT_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, DTW_STATION), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_SHIP_DOCK, STR_WATERWAYS_TOOLBAR_BUILD_DOCK_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, DTW_BUOY), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_BOUY, STR_WATERWAYS_TOOLBAR_BUOY_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, DTW_BUILD_AQUEDUCT), SetMinimalSize(23, 22), SetFill(0, 1), SetDataTip(SPR_IMG_AQUEDUCT, STR_WATERWAYS_TOOLBAR_BUILD_AQUEDUCT_TOOLTIP),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _build_docks_toolbar_desc(
 
	WDP_ALIGN_TBR, 22, 160, 36,
 
	WC_BUILD_TOOLBAR, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_STICKY_BUTTON | WDF_CONSTRUCTION,
 
	_nested_build_docks_toolbar_widgets, lengthof(_nested_build_docks_toolbar_widgets)
 
);
 

	
 
void ShowBuildDocksToolbar()
 
{
 
	if (!Company::IsValidID(_local_company)) return;
 

	
 
	DeleteWindowByClass(WC_BUILD_TOOLBAR);
 
	AllocateWindowDescFront<BuildDocksToolbarWindow>(&_build_docks_toolbar_desc, TRANSPORT_WATER);
 
}
 

	
 
/**
 
 * Nested widget parts of docks toolbar, scenario editor version.
 
 * Positions of #DTW_DEPOT, #DTW_STATION, and #DTW_BUOY widgets have changed.
 
 */
 
static const NWidgetPart _nested_build_docks_scen_toolbar_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN, DTW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, DTW_CAPTION), SetDataTip(STR_WATERWAYS_TOOLBAR_CAPTION_SE, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_STICKYBOX, COLOUR_DARK_GREEN, DTW_STICKY),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, DTW_CANAL), SetMinimalSize(22, 22), SetFill(false, true), SetDataTip(SPR_IMG_BUILD_CANAL, STR_WATERWAYS_TOOLBAR_CREATE_LAKE_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, DTW_LOCK), SetMinimalSize(22, 22), SetFill(false, true), SetDataTip(SPR_IMG_BUILD_LOCK, STR_WATERWAYS_TOOLBAR_BUILD_LOCKS_TOOLTIP),
 
		NWidget(WWT_PANEL, COLOUR_DARK_GREEN, DTW_SEPERATOR), SetMinimalSize(5, 22), SetFill(true, true), EndContainer(),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, DTW_DEMOLISH), SetMinimalSize(22, 22), SetFill(false, true), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, DTW_RIVER), SetMinimalSize(22, 22), SetFill(false, true), SetDataTip(SPR_IMG_BUILD_RIVER, STR_WATERWAYS_TOOLBAR_CREATE_RIVER_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, DTW_BUILD_AQUEDUCT), SetMinimalSize(22, 22), SetFill(false, true), SetDataTip(SPR_IMG_AQUEDUCT, STR_WATERWAYS_TOOLBAR_BUILD_AQUEDUCT_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, DTW_CANAL), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_BUILD_CANAL, STR_WATERWAYS_TOOLBAR_CREATE_LAKE_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, DTW_LOCK), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_BUILD_LOCK, STR_WATERWAYS_TOOLBAR_BUILD_LOCKS_TOOLTIP),
 
		NWidget(WWT_PANEL, COLOUR_DARK_GREEN, DTW_SEPERATOR), SetMinimalSize(5, 22), SetFill(1, 1), EndContainer(),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, DTW_DEMOLISH), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, DTW_RIVER), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_BUILD_RIVER, STR_WATERWAYS_TOOLBAR_CREATE_RIVER_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, DTW_BUILD_AQUEDUCT), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_AQUEDUCT, STR_WATERWAYS_TOOLBAR_BUILD_AQUEDUCT_TOOLTIP),
 
	EndContainer(),
 
};
 

	
 
/** Window definition for the build docks in scenario editor window. */
 
static const WindowDesc _build_docks_scen_toolbar_desc(
 
	WDP_AUTO, WDP_AUTO, 115, 36,
 
	WC_SCEN_BUILD_TOOLBAR, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_STICKY_BUTTON | WDF_CONSTRUCTION,
 
	_nested_build_docks_scen_toolbar_widgets, lengthof(_nested_build_docks_scen_toolbar_widgets)
 
);
 

	
 
void ShowBuildDocksScenToolbar()
 
{
 
	AllocateWindowDescFront<BuildDocksToolbarWindow>(&_build_docks_scen_toolbar_desc, TRANSPORT_WATER);
 
}
 

	
 
/** Widget numbers of the build-dock GUI. */
 
enum BuildDockStationWidgets {
 
	BDSW_CLOSE,      ///< Closebox.
 
	BDSW_CAPTION,    ///< Titlebar.
 
	BDSW_BACKGROUND, ///< Background panel.
 
	BDSW_LT_OFF,     ///< 'Off' button of coverage high light.
 
	BDSW_LT_ON,      ///< 'On' button of coverage high light.
 
	BDSW_INFO,       ///< 'Coverage highlight' label.
 
};
 

	
 
struct BuildDocksStationWindow : public PickerWindowBase {
 
public:
 
	BuildDocksStationWindow(const WindowDesc *desc, Window *parent) : PickerWindowBase(parent)
 
	{
 
		this->InitNested(desc, TRANSPORT_WATER);
 
		this->LowerWidget(_settings_client.gui.station_show_coverage + BDSW_LT_OFF);
 
	}
 

	
 
	virtual ~BuildDocksStationWindow()
 
	{
 
		DeleteWindowById(WC_SELECT_STATION, 0);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		int rad = (_settings_game.station.modified_catchment) ? CA_DOCK : CA_UNMODIFIED;
 

	
 
		this->DrawWidgets();
 

	
 
		if (_settings_client.gui.station_show_coverage) {
 
			SetTileSelectBigSize(-rad, -rad, 2 * rad, 2 * rad);
 
		} else {
 
			SetTileSelectSize(1, 1);
 
		}
 

	
 
		/* strings such as 'Size' and 'Coverage Area' */
 
		int top = this->GetWidget<NWidgetBase>(BDSW_LT_OFF)->pos_y + this->GetWidget<NWidgetBase>(BDSW_LT_OFF)->current_y + WD_PAR_VSEP_NORMAL;
 
		NWidgetBase *back_nwi = this->GetWidget<NWidgetBase>(BDSW_BACKGROUND);
 
		int right  = back_nwi->pos_x + back_nwi->current_x;
 
		int bottom = back_nwi->pos_y + back_nwi->current_y;
 
		top = DrawStationCoverageAreaText(back_nwi->pos_x + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, SCT_ALL, rad, false) + WD_PAR_VSEP_NORMAL;
 
		top = DrawStationCoverageAreaText(back_nwi->pos_x + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, SCT_ALL, rad, true) + WD_PAR_VSEP_NORMAL;
 
		/* Resize background if the text is not equally long as the window. */
 
		if (top > bottom || (top < bottom && back_nwi->current_y > back_nwi->smallest_y)) {
 
			ResizeWindow(this, 0, top - bottom);
 
		}
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case BDSW_LT_OFF:
 
			case BDSW_LT_ON:
 
				this->RaiseWidget(_settings_client.gui.station_show_coverage + BDSW_LT_OFF);
 
				_settings_client.gui.station_show_coverage = (widget != BDSW_LT_OFF);
 
				this->LowerWidget(_settings_client.gui.station_show_coverage + BDSW_LT_OFF);
 
				SndPlayFx(SND_15_BEEP);
 
				this->SetDirty();
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnTick()
 
	{
 
		CheckRedrawStationCoverage(this);
 
	}
 
};
 

	
 
/** Nested widget parts of a build dock station window. */
 
static const NWidgetPart _nested_build_dock_station_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN, BDSW_CLOSE),
 
		NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, BDSW_CAPTION), SetDataTip(STR_STATION_BUILD_DOCK_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BDSW_BACKGROUND),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 3),
 
		NWidget(WWT_LABEL, COLOUR_DARK_GREEN, BDSW_INFO), SetMinimalSize(148, 14), SetDataTip(STR_STATION_BUILD_COVERAGE_AREA_TITLE, STR_NULL),
 
		NWidget(NWID_HORIZONTAL), SetPIP(14, 0, 14),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BDSW_LT_OFF), SetMinimalSize(40, 12), SetFill(true, false), SetDataTip(STR_STATION_BUILD_COVERAGE_OFF, STR_STATION_BUILD_COVERAGE_AREA_OFF_TOOLTIP),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BDSW_LT_ON), SetMinimalSize(40, 12), SetFill(true, false), SetDataTip(STR_STATION_BUILD_COVERAGE_ON, STR_STATION_BUILD_COVERAGE_AREA_ON_TOOLTIP),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BDSW_LT_OFF), SetMinimalSize(40, 12), SetFill(1, 0), SetDataTip(STR_STATION_BUILD_COVERAGE_OFF, STR_STATION_BUILD_COVERAGE_AREA_OFF_TOOLTIP),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BDSW_LT_ON), SetMinimalSize(40, 12), SetFill(1, 0), SetDataTip(STR_STATION_BUILD_COVERAGE_ON, STR_STATION_BUILD_COVERAGE_AREA_ON_TOOLTIP),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 20), SetResize(0, 1),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _build_dock_station_desc(
 
	WDP_AUTO, WDP_AUTO, 148, 75,
 
	WC_BUILD_STATION, WC_BUILD_TOOLBAR,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_CONSTRUCTION,
 
	_nested_build_dock_station_widgets, lengthof(_nested_build_dock_station_widgets)
 
);
 

	
 
static void ShowBuildDockStationPicker(Window *parent)
 
{
 
	new BuildDocksStationWindow(&_build_dock_station_desc, parent);
 
}
 

	
 
/** Widgets for the build ship depot window. */
 
enum BuildDockDepotWidgets {
 
	BDDW_CLOSE,
 
	BDDW_CAPTION,
 
	BDDW_BACKGROUND,
 
	BDDW_X,
 
	BDDW_Y,
 
};
 

	
 
struct BuildDocksDepotWindow : public PickerWindowBase {
 
private:
 
	static void UpdateDocksDirection()
 
	{
 
		if (_ship_depot_direction != AXIS_X) {
 
			SetTileSelectSize(1, 2);
 
		} else {
 
			SetTileSelectSize(2, 1);
 
		}
 
	}
 

	
 
public:
 
	BuildDocksDepotWindow(const WindowDesc *desc, Window *parent) : PickerWindowBase(parent)
 
	{
 
		this->InitNested(desc, TRANSPORT_WATER);
 
		this->LowerWidget(_ship_depot_direction + BDDW_X);
 
		UpdateDocksDirection();
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 

	
 
		DrawShipDepotSprite(this->GetWidget<NWidgetBase>(BDDW_X)->pos_x + 64, this->GetWidget<NWidgetBase>(BDDW_X)->pos_y + 18, 0);
 
		DrawShipDepotSprite(this->GetWidget<NWidgetBase>(BDDW_X)->pos_x + 32, this->GetWidget<NWidgetBase>(BDDW_X)->pos_y + 34, 1);
 
		DrawShipDepotSprite(this->GetWidget<NWidgetBase>(BDDW_Y)->pos_x + 32, this->GetWidget<NWidgetBase>(BDDW_Y)->pos_y + 18, 2);
 
		DrawShipDepotSprite(this->GetWidget<NWidgetBase>(BDDW_Y)->pos_x + 64, this->GetWidget<NWidgetBase>(BDDW_Y)->pos_y + 34, 3);
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case BDDW_X:
 
			case BDDW_Y:
 
				this->RaiseWidget(_ship_depot_direction + BDDW_X);
 
				_ship_depot_direction = (widget == BDDW_X ? AXIS_X : AXIS_Y);
 
				this->LowerWidget(_ship_depot_direction + BDDW_X);
 
				SndPlayFx(SND_15_BEEP);
 
				UpdateDocksDirection();
 
				this->SetDirty();
 
				break;
 
		}
 
	}
 
};
 

	
 
static const NWidgetPart _nested_build_docks_depot_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN, BDDW_CLOSE),
 
		NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, BDDW_CAPTION), SetDataTip(STR_DEPOT_BUILD_SHIP_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BDDW_BACKGROUND),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 3),
 
		NWidget(NWID_HORIZONTAL_LTR),
 
			NWidget(NWID_SPACER), SetMinimalSize(3, 0),
 
			NWidget(WWT_PANEL, COLOUR_GREY, BDDW_X), SetMinimalSize(98, 66), SetDataTip(0x0, STR_DEPOT_BUILD_SHIP_ORIENTATION_TOOLTIP),
 
			EndContainer(),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0),
 
			NWidget(WWT_PANEL, COLOUR_GREY, BDDW_Y), SetMinimalSize(98, 66), SetDataTip(0x0, STR_DEPOT_BUILD_SHIP_ORIENTATION_TOOLTIP),
 
			EndContainer(),
 
			NWidget(NWID_SPACER), SetMinimalSize(3, 0),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 3),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _build_docks_depot_desc(
 
	WDP_AUTO, WDP_AUTO, 204, 86,
 
	WC_BUILD_DEPOT, WC_BUILD_TOOLBAR,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_CONSTRUCTION,
 
	_nested_build_docks_depot_widgets, lengthof(_nested_build_docks_depot_widgets)
 
);
 

	
 

	
 
static void ShowBuildDocksDepotPicker(Window *parent)
 
{
 
	new BuildDocksDepotWindow(&_build_docks_depot_desc, parent);
 
}
 

	
 

	
 
void InitializeDockGui()
 
{
 
	_ship_depot_direction = AXIS_X;
 
}
src/engine_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file engine_gui.cpp GUI to show engine related information. */
 

	
 
#include "stdafx.h"
 
#include "window_gui.h"
 
#include "gfx_func.h"
 
#include "engine_func.h"
 
#include "engine_base.h"
 
#include "command_func.h"
 
#include "strings_func.h"
 
#include "engine_gui.h"
 
#include "articulated_vehicles.h"
 
#include "vehicle_func.h"
 
#include "company_func.h"
 
#include "rail.h"
 
#include "settings_type.h"
 

	
 
#include "table/strings.h"
 

	
 
/** Return the category of an engine.
 
 * @param engine Engine to examine.
 
 * @return String describing the category ("road veh", "train". "airplane", or "ship") of the engine.
 
 */
 
StringID GetEngineCategoryName(EngineID engine)
 
{
 
	const Engine *e = Engine::Get(engine);
 
	switch (e->type) {
 
		default: NOT_REACHED();
 
		case VEH_ROAD:              return STR_ENGINE_PREVIEW_ROAD_VEHICLE;
 
		case VEH_AIRCRAFT:          return STR_ENGINE_PREVIEW_AIRCRAFT;
 
		case VEH_SHIP:              return STR_ENGINE_PREVIEW_SHIP;
 
		case VEH_TRAIN:
 
			return GetRailTypeInfo(e->u.rail.railtype)->strings.new_loco;
 
	}
 
}
 

	
 
/** Widgets used for the engine preview window */
 
enum EnginePreviewWidgets {
 
	EPW_CLOSE,      ///< Close button
 
	EPW_CAPTION,    ///< Title bar/caption
 
	EPW_BACKGROUND, ///< Background
 
	EPW_QUESTION,   ///< The container for the question
 
	EPW_NO,         ///< No button
 
	EPW_YES,        ///< Yes button
 
};
 

	
 
static const NWidgetPart _nested_engine_preview_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_LIGHT_BLUE, EPW_CLOSE),
 
		NWidget(WWT_CAPTION, COLOUR_LIGHT_BLUE, EPW_CAPTION), SetDataTip(STR_ENGINE_PREVIEW_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, EPW_BACKGROUND),
 
		NWidget(WWT_EMPTY, INVALID_COLOUR, EPW_QUESTION), SetMinimalSize(300, 0), SetPadding(8, 8, 8, 8), SetFill(true, false),
 
		NWidget(WWT_EMPTY, INVALID_COLOUR, EPW_QUESTION), SetMinimalSize(300, 0), SetPadding(8, 8, 8, 8), SetFill(1, 0),
 
		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(85, 10, 85),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_LIGHT_BLUE, EPW_NO), SetDataTip(STR_QUIT_NO, STR_NULL), SetFill(true, false),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_LIGHT_BLUE, EPW_YES), SetDataTip(STR_QUIT_YES, STR_NULL), SetFill(true, false),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_LIGHT_BLUE, EPW_NO), SetDataTip(STR_QUIT_NO, STR_NULL), SetFill(1, 0),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_LIGHT_BLUE, EPW_YES), SetDataTip(STR_QUIT_YES, STR_NULL), SetFill(1, 0),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 8),
 
	EndContainer(),
 
};
 

	
 
struct EnginePreviewWindow : Window {
 
	static const int VEHICLE_SPACE = 40; // The space to show the vehicle image
 

	
 
	EnginePreviewWindow(const WindowDesc *desc, WindowNumber window_number) : Window()
 
	{
 
		this->InitNested(desc, window_number);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		if (widget != EPW_QUESTION) return;
 

	
 
		EngineID engine = this->window_number;
 
		SetDParam(0, GetEngineCategoryName(engine));
 
		size->height = GetStringHeight(STR_ENGINE_PREVIEW_MESSAGE, size->width) + WD_PAR_VSEP_WIDE + FONT_HEIGHT_NORMAL + VEHICLE_SPACE;
 
		SetDParam(0, engine);
 
		size->height += GetStringHeight(GetEngineInfoString(engine), size->width);
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		if (widget != EPW_QUESTION) return;
 

	
 
		EngineID engine = this->window_number;
 
		SetDParam(0, GetEngineCategoryName(engine));
 
		int y = r.top + GetStringHeight(STR_ENGINE_PREVIEW_MESSAGE, r.right - r.top + 1);
 
		y = DrawStringMultiLine(r.left, r.right, r.top, y, STR_ENGINE_PREVIEW_MESSAGE, TC_FROMSTRING, SA_CENTER) + WD_PAR_VSEP_WIDE;
 

	
 
		SetDParam(0, engine);
 
		DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_ENGINE_NAME, TC_BLACK, SA_CENTER);
 
		y += FONT_HEIGHT_NORMAL;
 

	
 
		DrawVehicleEngine(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, this->width >> 1, y + VEHICLE_SPACE / 2, engine, GetEnginePalette(engine, _local_company));
 

	
 
		y += VEHICLE_SPACE;
 
		DrawStringMultiLine(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, r.bottom, GetEngineInfoString(engine), TC_FROMSTRING, SA_CENTER);
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case EPW_YES:
 
				DoCommandP(0, this->window_number, 0, CMD_WANT_ENGINE_PREVIEW);
 
				/* Fallthrough */
 
			case EPW_NO:
 
				delete this;
 
				break;
 
		}
 
	}
 
};
 

	
 
static const WindowDesc _engine_preview_desc(
 
	WDP_CENTER, WDP_CENTER, 300, 192,
 
	WC_ENGINE_PREVIEW, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_CONSTRUCTION,
 
	_nested_engine_preview_widgets, lengthof(_nested_engine_preview_widgets)
 
);
 

	
 

	
 
void ShowEnginePreviewWindow(EngineID engine)
 
{
 
	AllocateWindowDescFront<EnginePreviewWindow>(&_engine_preview_desc, engine);
 
}
 

	
 
uint GetTotalCapacityOfArticulatedParts(EngineID engine)
 
{
 
	uint total = 0;
 

	
 
	CargoArray cap = GetCapacityOfArticulatedParts(engine);
 
	for (CargoID c = 0; c < NUM_CARGO; c++) {
 
		total += cap[c];
 
	}
 

	
 
	return total;
 
}
 

	
 
static StringID GetTrainEngineInfoString(const Engine *e)
 
{
 
	SetDParam(0, e->GetCost());
 
	SetDParam(2, e->GetDisplayMaxSpeed());
 
	SetDParam(3, e->GetPower());
 
	SetDParam(1, e->GetDisplayWeight());
 
	SetDParam(7, e->GetDisplayMaxTractiveEffort());
 

	
 
	SetDParam(4, e->GetRunningCost());
 

	
 
	uint capacity = GetTotalCapacityOfArticulatedParts(e->index);
 
	if (capacity != 0) {
 
		SetDParam(5, e->GetDefaultCargoType());
 
		SetDParam(6, capacity);
 
	} else {
 
		SetDParam(5, CT_INVALID);
 
	}
 
	return (_settings_game.vehicle.train_acceleration_model != TAM_ORIGINAL && e->u.rail.railtype != RAILTYPE_MAGLEV) ? STR_ENGINE_PREVIEW_COST_WEIGHT_SPEED_POWER_MAX_TE : STR_ENGINE_PREVIEW_COST_WEIGHT_SPEED_POWER;
 
}
 

	
 
static StringID GetAircraftEngineInfoString(const Engine *e)
 
{
 
	CargoID cargo = e->GetDefaultCargoType();
 
	uint16 mail_capacity;
 
	uint capacity = e->GetDisplayDefaultCapacity(&mail_capacity);
 

	
 
	if (mail_capacity > 0) {
 
		SetDParam(0, e->GetCost());
 
		SetDParam(1, e->GetDisplayMaxSpeed());
 
		SetDParam(2, cargo);
 
		SetDParam(3, capacity);
 
		SetDParam(4, CT_MAIL);
 
		SetDParam(5, mail_capacity);
 
		SetDParam(6, e->GetRunningCost());
 
		return STR_ENGINE_PREVIEW_COST_MAX_SPEED_CAPACITY_CAPACITY_RUNCOST;
 
	} else {
 
		SetDParam(0, e->GetCost());
 
		SetDParam(1, e->GetDisplayMaxSpeed());
 
		SetDParam(2, cargo);
 
		SetDParam(3, capacity);
 
		SetDParam(4, e->GetRunningCost());
 
		return STR_ENGINE_PREVIEW_COST_MAX_SPEED_CAPACITY_RUNCOST;
 
	}
 
}
 

	
 
static StringID GetRoadVehEngineInfoString(const Engine *e)
 
{
 
	SetDParam(0, e->GetCost());
 
	SetDParam(1, e->GetDisplayMaxSpeed());
 
	uint capacity = GetTotalCapacityOfArticulatedParts(e->index);
 
	if (capacity != 0) {
 
		SetDParam(2, e->GetDefaultCargoType());
 
		SetDParam(3, capacity);
 
	} else {
 
		SetDParam(2, CT_INVALID);
 
	}
 
	SetDParam(4, e->GetRunningCost());
 
	return STR_ENGINE_PREVIEW_COST_MAX_SPEED_CAPACITY_RUNCOST;
 
}
 

	
 
static StringID GetShipEngineInfoString(const Engine *e)
 
{
 
	SetDParam(0, e->GetCost());
 
	SetDParam(1, e->GetDisplayMaxSpeed());
 
	SetDParam(2, e->GetDefaultCargoType());
 
	SetDParam(3, e->GetDisplayDefaultCapacity());
 
	SetDParam(4, e->GetRunningCost());
 
	return STR_ENGINE_PREVIEW_COST_MAX_SPEED_CAPACITY_RUNCOST;
 
}
 

	
 

	
 
/**
 
 * Get a multi-line string with some technical data, describing the engine.
 
 * @param engine Engine to describe.
 
 * @return String describing the engine.
 
 * @post \c DParam array is set up for printing the string.
 
 */
 
StringID GetEngineInfoString(EngineID engine)
 
{
 
	const Engine *e = Engine::Get(engine);
 

	
 
	switch (e->type) {
 
		case VEH_TRAIN:
 
			return GetTrainEngineInfoString(e);
 

	
 
		case VEH_ROAD:
 
			return GetRoadVehEngineInfoString(e);
 

	
 
		case VEH_SHIP:
 
			return GetShipEngineInfoString(e);
 

	
 
		case VEH_AIRCRAFT:
 
			return GetAircraftEngineInfoString(e);
 

	
 
		default: NOT_REACHED();
 
	}
 
}
 

	
 
/**
 
 * Draw an engine.
 
 * @param left   Minimum horizontal position to use for drawing the engine
 
 * @param right  Maximum horizontal position to use for drawing the engine
 
 * @param preferred_x Horizontal position to use for drawing the engine.
 
 * @param y      Vertical position to use for drawing the engine.
 
 * @param engine Engine to draw.
 
 * @param pal    Palette to use for drawing.
 
 */
 
void DrawVehicleEngine(int left, int right, int preferred_x, int y, EngineID engine, SpriteID pal)
 
{
 
	const Engine *e = Engine::Get(engine);
 

	
 
	switch (e->type) {
 
		case VEH_TRAIN:
 
			DrawTrainEngine(left, right, preferred_x, y, engine, pal);
 
			break;
 

	
 
		case VEH_ROAD:
 
			DrawRoadVehEngine(left, right, preferred_x, y, engine, pal);
 
			break;
 

	
 
		case VEH_SHIP:
 
			DrawShipEngine(left, right, preferred_x, y, engine, pal);
 
			break;
 

	
 
		case VEH_AIRCRAFT:
 
			DrawAircraftEngine(left, right, preferred_x, y, engine, pal);
 
			break;
 

	
 
		default: NOT_REACHED();
 
	}
 
}
 

	
 
/** Sort all items using quick sort and given 'CompareItems' function
 
 * @param el list to be sorted
 
 * @param compare function for evaluation of the quicksort
 
 */
 
void EngList_Sort(GUIEngineList *el, EngList_SortTypeFunction compare)
 
{
 
	uint size = el->Length();
 
	/* out-of-bounds access at the next line for size == 0 (even with operator[] at some systems)
 
	 * generally, do not sort if there are less than 2 items */
 
	if (size < 2) return;
 
	QSortT(el->Begin(), size, compare);
 
}
 

	
 
/** Sort selected range of items (on indices @ <begin, begin+num_items-1>)
 
 * @param el list to be sorted
 
 * @param compare function for evaluation of the quicksort
 
 * @param begin start of sorting
 
 * @param num_items count of items to be sorted
 
 */
 
void EngList_SortPartial(GUIEngineList *el, EngList_SortTypeFunction compare, uint begin, uint num_items)
 
{
 
	if (num_items < 2) return;
 
	assert(begin < el->Length());
 
	assert(begin + num_items <= el->Length());
 
	QSortT(el->Get(begin), num_items, compare);
 
}
 

	
src/genworld_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file genworld_gui.cpp GUI to configure and show progress during map generation. */
 

	
 
#include "stdafx.h"
 
#include "openttd.h"
 
#include "heightmap.h"
 
#include "variables.h"
 
#include "debug.h"
 
#include "genworld.h"
 
#include "network/network.h"
 
#include "newgrf_config.h"
 
#include "strings_func.h"
 
#include "window_func.h"
 
#include "date_func.h"
 
#include "sound_func.h"
 
#include "fios.h"
 
#include "string_func.h"
 
#include "gfx_func.h"
 
#include "widgets/dropdown_type.h"
 
#include "widgets/dropdown_func.h"
 
#include "landscape_type.h"
 
#include "querystring_gui.h"
 
#include "town.h"
 
#include "thread/thread.h"
 
#include "settings_func.h"
 

	
 
#include "table/strings.h"
 
#include "table/sprites.h"
 

	
 
/**
 
 * In what 'mode' the GenerateLandscapeWindowProc is.
 
 */
 
enum glwp_modes {
 
	GLWP_GENERATE,
 
	GLWP_HEIGHTMAP,
 
	GLWP_SCENARIO,
 
	GLWP_END
 
};
 

	
 
extern void SwitchToMode(SwitchMode new_mode);
 
extern void MakeNewgameSettingsLive();
 

	
 
static inline void SetNewLandscapeType(byte landscape)
 
{
 
	_settings_newgame.game_creation.landscape = landscape;
 
	SetWindowClassesDirty(WC_SELECT_GAME);
 
	SetWindowClassesDirty(WC_GENERATE_LANDSCAPE);
 
}
 

	
 
enum GenerateLandscapeWindowWidgets {
 
	GLAND_CLOSEBOX,
 
	GLAND_TITLEBAR,
 
	GLAND_BACKGROUND,
 

	
 
	GLAND_TEMPERATE,
 
	GLAND_ARCTIC,
 
	GLAND_TROPICAL,
 
	GLAND_TOYLAND,
 

	
 
	GLAND_MAPSIZE_X_TEXT,
 
	GLAND_MAPSIZE_X_PULLDOWN,
 
	GLAND_MAPSIZE_Y_TEXT,
 
	GLAND_MAPSIZE_Y_PULLDOWN,
 

	
 
	GLAND_TOWN_TEXT,
 
	GLAND_TOWN_PULLDOWN,
 
	GLAND_INDUSTRY_TEXT,
 
	GLAND_INDUSTRY_PULLDOWN,
 

	
 
	GLAND_RANDOM_TEXT,
 
	GLAND_RANDOM_EDITBOX,
 
	GLAND_RANDOM_BUTTON,
 

	
 
	GLAND_GENERATE_BUTTON,
 

	
 
	GLAND_START_DATE_TEXT1,
 
	GLAND_START_DATE_DOWN,
 
	GLAND_START_DATE_TEXT,
 
	GLAND_START_DATE_UP,
 

	
 
	GLAND_SNOW_LEVEL_TEXT1,
 
	GLAND_SNOW_LEVEL_DOWN,
 
	GLAND_SNOW_LEVEL_TEXT,
 
	GLAND_SNOW_LEVEL_UP,
 

	
 
	GLAND_TREE_TEXT,
 
	GLAND_TREE_PULLDOWN,
 
	GLAND_LANDSCAPE_TEXT,
 
	GLAND_LANDSCAPE_PULLDOWN,
 
	GLAND_HEIGHTMAP_NAME_LABEL,
 
	GLAND_HEIGHTMAP_NAME_TEXT,
 
	GLAND_HEIGHTMAP_NAME_SPACER,
 
	GLAND_HEIGHTMAP_SIZE_LABEL,
 
	GLAND_HEIGHTMAP_SIZE_TEXT,
 
	GLAND_HEIGHTMAP_ROTATION_TEXT,
 
	GLAND_HEIGHTMAP_ROTATION_PULLDOWN,
 

	
 
	GLAND_TERRAIN_TEXT,
 
	GLAND_TERRAIN_PULLDOWN,
 
	GLAND_WATER_TEXT,
 
	GLAND_WATER_PULLDOWN,
 
	GLAND_SMOOTHNESS_TEXT,
 
	GLAND_SMOOTHNESS_PULLDOWN,
 

	
 
	GLAND_BORDER_TYPES,
 
	GLAND_BORDERS_RANDOM,
 
	GLAND_WATER_NW_TEXT,
 
	GLAND_WATER_NE_TEXT,
 
	GLAND_WATER_SE_TEXT,
 
	GLAND_WATER_SW_TEXT,
 
	GLAND_WATER_NW,
 
	GLAND_WATER_NE,
 
	GLAND_WATER_SE,
 
	GLAND_WATER_SW,
 
};
 

	
 
static const NWidgetPart _nested_generate_landscape_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_BROWN, GLAND_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_BROWN, GLAND_TITLEBAR), SetDataTip(STR_MAPGEN_WORLD_GENERATION_CAPTION, STR_NULL),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_BROWN, GLAND_BACKGROUND),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 10),
 
		/* Landscape selection. */
 
		NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 10),
 
			NWidget(NWID_SPACER), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
			NWidget(WWT_IMGBTN_2, COLOUR_ORANGE, GLAND_TEMPERATE), SetDataTip(SPR_SELECT_TEMPERATE, STR_INTRO_TOOLTIP_TEMPERATE),
 
			NWidget(NWID_SPACER), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
			NWidget(WWT_IMGBTN_2, COLOUR_ORANGE, GLAND_ARCTIC), SetDataTip(SPR_SELECT_SUB_ARCTIC, STR_INTRO_TOOLTIP_SUB_ARCTIC_LANDSCAPE),
 
			NWidget(NWID_SPACER), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
			NWidget(WWT_IMGBTN_2, COLOUR_ORANGE, GLAND_TROPICAL), SetDataTip(SPR_SELECT_SUB_TROPICAL, STR_INTRO_TOOLTIP_SUB_TROPICAL_LANDSCAPE),
 
			NWidget(NWID_SPACER), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
			NWidget(WWT_IMGBTN_2, COLOUR_ORANGE, GLAND_TOYLAND), SetDataTip(SPR_SELECT_TOYLAND, STR_INTRO_TOOLTIP_TOYLAND_LANDSCAPE),
 
			NWidget(NWID_SPACER), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 11),
 
		NWidget(NWID_HORIZONTAL), SetPIP(10, 5, 10),
 
			NWidget(NWID_HORIZONTAL), SetPIP(0, 3, 0),
 
				/* Left column with labels. */
 
				NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(0, 4, 0),
 
					NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_MAPSIZE_X_TEXT), SetDataTip(STR_MAPGEN_MAPSIZE, STR_NULL), SetFill(true, true),
 
					NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_LANDSCAPE_TEXT), SetDataTip(STR_MAPGEN_LAND_GENERATOR, STR_NULL), SetFill(true, true),
 
					NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_TOWN_TEXT), SetDataTip(STR_MAPGEN_NUMBER_OF_TOWNS, STR_NULL), SetFill(true, true),
 
					NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_TERRAIN_TEXT), SetDataTip(STR_MAPGEN_TERRAIN_TYPE, STR_NULL), SetFill(true, true),
 
					NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_RANDOM_TEXT), SetDataTip(STR_MAPGEN_RANDOM_SEED, STR_NULL), SetFill(true, true),
 
					NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_WATER_TEXT), SetDataTip(STR_MAPGEN_QUANTITY_OF_SEA_LAKES, STR_NULL), SetFill(true, true),
 
					NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_TREE_TEXT), SetDataTip(STR_MAPGEN_TREE_PLACER, STR_NULL), SetFill(true, true),
 
					NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_BORDER_TYPES), SetDataTip(STR_MAPGEN_BORDER_TYPE, STR_NULL), SetFill(true, true),
 
					NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_MAPSIZE_X_TEXT), SetDataTip(STR_MAPGEN_MAPSIZE, STR_NULL), SetFill(1, 1),
 
					NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_LANDSCAPE_TEXT), SetDataTip(STR_MAPGEN_LAND_GENERATOR, STR_NULL), SetFill(1, 1),
 
					NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_TOWN_TEXT), SetDataTip(STR_MAPGEN_NUMBER_OF_TOWNS, STR_NULL), SetFill(1, 1),
 
					NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_TERRAIN_TEXT), SetDataTip(STR_MAPGEN_TERRAIN_TYPE, STR_NULL), SetFill(1, 1),
 
					NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_RANDOM_TEXT), SetDataTip(STR_MAPGEN_RANDOM_SEED, STR_NULL), SetFill(1, 1),
 
					NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_WATER_TEXT), SetDataTip(STR_MAPGEN_QUANTITY_OF_SEA_LAKES, STR_NULL), SetFill(1, 1),
 
					NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_TREE_TEXT), SetDataTip(STR_MAPGEN_TREE_PLACER, STR_NULL), SetFill(1, 1),
 
					NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_BORDER_TYPES), SetDataTip(STR_MAPGEN_BORDER_TYPE, STR_NULL), SetFill(1, 1),
 
				EndContainer(),
 
				/* Widgets at the right of the labels. */
 
				NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(0, 4, 0),
 
					/* Mapsize X * Y. */
 
					NWidget(NWID_HORIZONTAL), SetPIP(0, 4, 0),
 
						NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_MAPSIZE_X_PULLDOWN), SetDataTip(STR_JUST_INT, STR_NULL), SetFill(true, false),
 
						NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_MAPSIZE_Y_TEXT), SetDataTip(STR_MAPGEN_BY, STR_NULL), SetPadding(1, 0, 0, 0), SetFill(true, true),
 
						NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_MAPSIZE_Y_PULLDOWN), SetDataTip(STR_JUST_INT, STR_NULL), SetFill(true, false),
 
						NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_MAPSIZE_X_PULLDOWN), SetDataTip(STR_JUST_INT, STR_NULL), SetFill(1, 0),
 
						NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_MAPSIZE_Y_TEXT), SetDataTip(STR_MAPGEN_BY, STR_NULL), SetPadding(1, 0, 0, 0), SetFill(1, 1),
 
						NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_MAPSIZE_Y_PULLDOWN), SetDataTip(STR_JUST_INT, STR_NULL), SetFill(1, 0),
 
					EndContainer(),
 
					NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_LANDSCAPE_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(true, false),
 
					NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_TOWN_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(true, false),
 
					NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_TERRAIN_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(true, false),
 
					NWidget(WWT_EDITBOX, COLOUR_WHITE, GLAND_RANDOM_EDITBOX), SetDataTip(STR_MAPGEN_RANDOM_SEED_OSKTITLE, STR_MAPGEN_RANDOM_SEED_HELP), SetFill(true, true),
 
					NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_WATER_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(true, false),
 
					NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_TREE_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(true, false),
 
					NWidget(WWT_PUSHTXTBTN, COLOUR_ORANGE, GLAND_BORDERS_RANDOM), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(true, false),
 
					NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_LANDSCAPE_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(1, 0),
 
					NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_TOWN_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(1, 0),
 
					NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_TERRAIN_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(1, 0),
 
					NWidget(WWT_EDITBOX, COLOUR_WHITE, GLAND_RANDOM_EDITBOX), SetDataTip(STR_MAPGEN_RANDOM_SEED_OSKTITLE, STR_MAPGEN_RANDOM_SEED_HELP), SetFill(1, 1),
 
					NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_WATER_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(1, 0),
 
					NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_TREE_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(1, 0),
 
					NWidget(WWT_PUSHTXTBTN, COLOUR_ORANGE, GLAND_BORDERS_RANDOM), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(1, 0),
 
				EndContainer(),
 
			EndContainer(),
 
			NWidget(NWID_VERTICAL), SetPIP(0, 4, 0),
 
				NWidget(NWID_HORIZONTAL), SetPIP(0, 3, 0),
 
					NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(0, 4, 0),
 
						NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_START_DATE_TEXT1), SetDataTip(STR_MAPGEN_DATE, STR_NULL), SetFill(true, true),
 
						NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_SNOW_LEVEL_TEXT1), SetDataTip(STR_MAPGEN_SNOW_LINE_HEIGHT, STR_NULL), SetFill(true, true),
 
						NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_INDUSTRY_TEXT), SetDataTip(STR_MAPGEN_NUMBER_OF_INDUSTRIES, STR_NULL), SetFill(true, true),
 
						NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_SMOOTHNESS_TEXT), SetDataTip(STR_MAPGEN_SMOOTHNESS, STR_NULL), SetFill(true, true),
 
						NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_START_DATE_TEXT1), SetDataTip(STR_MAPGEN_DATE, STR_NULL), SetFill(1, 1),
 
						NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_SNOW_LEVEL_TEXT1), SetDataTip(STR_MAPGEN_SNOW_LINE_HEIGHT, STR_NULL), SetFill(1, 1),
 
						NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_INDUSTRY_TEXT), SetDataTip(STR_MAPGEN_NUMBER_OF_INDUSTRIES, STR_NULL), SetFill(1, 1),
 
						NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_SMOOTHNESS_TEXT), SetDataTip(STR_MAPGEN_SMOOTHNESS, STR_NULL), SetFill(1, 1),
 
					EndContainer(),
 
					NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(0, 4, 0),
 
						/* Starting date. */
 
						NWidget(NWID_HORIZONTAL),
 
							NWidget(WWT_IMGBTN, COLOUR_ORANGE, GLAND_START_DATE_DOWN), SetDataTip(SPR_ARROW_DOWN, STR_SCENEDIT_TOOLBAR_TOOLTIP_MOVE_THE_STARTING_DATE_BACKWARD), SetFill(false, true),
 
							NWidget(WWT_TEXTBTN, COLOUR_ORANGE, GLAND_START_DATE_TEXT), SetDataTip(STR_BLACK_DATE_LONG, STR_NULL), SetFill(true, false),
 
							NWidget(WWT_IMGBTN, COLOUR_ORANGE, GLAND_START_DATE_UP), SetDataTip(SPR_ARROW_UP, STR_SCENEDIT_TOOLBAR_TOOLTIP_MOVE_THE_STARTING_DATE_FORWARD), SetFill(false, true),
 
							NWidget(WWT_IMGBTN, COLOUR_ORANGE, GLAND_START_DATE_DOWN), SetDataTip(SPR_ARROW_DOWN, STR_SCENEDIT_TOOLBAR_TOOLTIP_MOVE_THE_STARTING_DATE_BACKWARD), SetFill(0, 1),
 
							NWidget(WWT_TEXTBTN, COLOUR_ORANGE, GLAND_START_DATE_TEXT), SetDataTip(STR_BLACK_DATE_LONG, STR_NULL), SetFill(1, 0),
 
							NWidget(WWT_IMGBTN, COLOUR_ORANGE, GLAND_START_DATE_UP), SetDataTip(SPR_ARROW_UP, STR_SCENEDIT_TOOLBAR_TOOLTIP_MOVE_THE_STARTING_DATE_FORWARD), SetFill(0, 1),
 
						EndContainer(),
 
						/* Snow line. */
 
						NWidget(NWID_HORIZONTAL),
 
							NWidget(WWT_IMGBTN, COLOUR_ORANGE, GLAND_SNOW_LEVEL_DOWN), SetDataTip(SPR_ARROW_DOWN, STR_MAPGEN_SNOW_LINE_DOWN), SetFill(false, true),
 
							NWidget(WWT_TEXTBTN, COLOUR_ORANGE, GLAND_SNOW_LEVEL_TEXT), SetDataTip(STR_BLACK_INT, STR_NULL), SetFill(true, false),
 
							NWidget(WWT_IMGBTN, COLOUR_ORANGE, GLAND_SNOW_LEVEL_UP), SetDataTip(SPR_ARROW_UP, STR_MAPGEN_SNOW_LINE_UP), SetFill(false, true),
 
							NWidget(WWT_IMGBTN, COLOUR_ORANGE, GLAND_SNOW_LEVEL_DOWN), SetDataTip(SPR_ARROW_DOWN, STR_MAPGEN_SNOW_LINE_DOWN), SetFill(0, 1),
 
							NWidget(WWT_TEXTBTN, COLOUR_ORANGE, GLAND_SNOW_LEVEL_TEXT), SetDataTip(STR_BLACK_INT, STR_NULL), SetFill(1, 0),
 
							NWidget(WWT_IMGBTN, COLOUR_ORANGE, GLAND_SNOW_LEVEL_UP), SetDataTip(SPR_ARROW_UP, STR_MAPGEN_SNOW_LINE_UP), SetFill(0, 1),
 
						EndContainer(),
 
						NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_INDUSTRY_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_JUST_STRING), SetFill(true, false),
 
						NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_SMOOTHNESS_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(true, false),
 
						NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_INDUSTRY_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_JUST_STRING), SetFill(1, 0),
 
						NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_SMOOTHNESS_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(1, 0),
 
					EndContainer(),
 
				EndContainer(),
 
				NWidget(WWT_TEXTBTN, COLOUR_ORANGE, GLAND_RANDOM_BUTTON), SetDataTip(STR_MAPGEN_RANDOM, STR_MAPGEN_RANDOM_HELP), SetFill(true, false),
 
				NWidget(NWID_SPACER), SetFill(true, true),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREEN, GLAND_GENERATE_BUTTON), SetMinimalSize(84, 30), SetDataTip(STR_MAPGEN_GENERATE, STR_NULL), SetFill(true, false),
 
				NWidget(WWT_TEXTBTN, COLOUR_ORANGE, GLAND_RANDOM_BUTTON), SetDataTip(STR_MAPGEN_RANDOM, STR_MAPGEN_RANDOM_HELP), SetFill(1, 0),
 
				NWidget(NWID_SPACER), SetFill(1, 1),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREEN, GLAND_GENERATE_BUTTON), SetMinimalSize(84, 30), SetDataTip(STR_MAPGEN_GENERATE, STR_NULL), SetFill(1, 0),
 
			EndContainer(),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 4),
 
		/* Map borders buttons for each edge. */
 
		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 0, 10),
 
			NWidget(NWID_HORIZONTAL), SetPIP(0, 0, 3),
 
				NWidget(NWID_SPACER), SetFill(true, true),
 
				NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_WATER_NW_TEXT), SetDataTip(STR_MAPGEN_NORTHWEST, STR_NULL), SetPadding(1, 0, 0, 0), SetFill(false, true),
 
				NWidget(NWID_SPACER), SetFill(1, 1),
 
				NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_WATER_NW_TEXT), SetDataTip(STR_MAPGEN_NORTHWEST, STR_NULL), SetPadding(1, 0, 0, 0), SetFill(0, 1),
 
			EndContainer(),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_ORANGE, GLAND_WATER_NW), SetDataTip(STR_JUST_STRING, STR_MAPGEN_NORTHWEST), SetFill(true, true),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_ORANGE, GLAND_WATER_SW), SetDataTip(STR_JUST_STRING, STR_MAPGEN_SOUTHWEST), SetFill(true, true),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_ORANGE, GLAND_WATER_NW), SetDataTip(STR_JUST_STRING, STR_MAPGEN_NORTHWEST), SetFill(1, 1),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_ORANGE, GLAND_WATER_SW), SetDataTip(STR_JUST_STRING, STR_MAPGEN_SOUTHWEST), SetFill(1, 1),
 
			NWidget(NWID_HORIZONTAL), SetPIP(3, 0, 0),
 
				NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_WATER_NE_TEXT), SetDataTip(STR_MAPGEN_NORTHEAST, STR_NULL), SetPadding(1, 0, 0, 0), SetFill(false, true),
 
				NWidget(NWID_SPACER), SetFill(true, true),
 
				NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_WATER_NE_TEXT), SetDataTip(STR_MAPGEN_NORTHEAST, STR_NULL), SetPadding(1, 0, 0, 0), SetFill(0, 1),
 
				NWidget(NWID_SPACER), SetFill(1, 1),
 
			EndContainer(),
 
		EndContainer(),
 
		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 0, 10),
 
			NWidget(NWID_HORIZONTAL), SetPIP(0, 0, 3),
 
				NWidget(NWID_SPACER), SetFill(true, true),
 
				NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_WATER_SW_TEXT), SetDataTip(STR_MAPGEN_SOUTHWEST, STR_NULL), SetPadding(1, 0, 0, 0), SetFill(false, true),
 
				NWidget(NWID_SPACER), SetFill(1, 1),
 
				NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_WATER_SW_TEXT), SetDataTip(STR_MAPGEN_SOUTHWEST, STR_NULL), SetPadding(1, 0, 0, 0), SetFill(0, 1),
 
			EndContainer(),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_ORANGE, GLAND_WATER_NE), SetDataTip(STR_JUST_STRING, STR_MAPGEN_NORTHEAST), SetFill(true, true),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_ORANGE, GLAND_WATER_SE), SetDataTip(STR_JUST_STRING, STR_MAPGEN_SOUTHEAST), SetFill(true, true),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_ORANGE, GLAND_WATER_NE), SetDataTip(STR_JUST_STRING, STR_MAPGEN_NORTHEAST), SetFill(1, 1),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_ORANGE, GLAND_WATER_SE), SetDataTip(STR_JUST_STRING, STR_MAPGEN_SOUTHEAST), SetFill(1, 1),
 
			NWidget(NWID_HORIZONTAL), SetPIP(3, 0, 0),
 
				NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_WATER_SE_TEXT), SetDataTip(STR_MAPGEN_SOUTHEAST, STR_NULL), SetPadding(1, 0, 0, 0), SetFill(false, true),
 
				NWidget(NWID_SPACER), SetFill(true, true),
 
				NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_WATER_SE_TEXT), SetDataTip(STR_MAPGEN_SOUTHEAST, STR_NULL), SetPadding(1, 0, 0, 0), SetFill(0, 1),
 
				NWidget(NWID_SPACER), SetFill(1, 1),
 
			EndContainer(),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 9), SetFill(true, true),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 9), SetFill(1, 1),
 
	EndContainer(),
 
};
 

	
 
static const NWidgetPart _nested_heightmap_load_widgets[] = {
 
	/* Window header. */
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_BROWN, GLAND_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_BROWN, GLAND_TITLEBAR), SetDataTip(STR_MAPGEN_WORLD_GENERATION_CAPTION, STR_NULL),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_BROWN, GLAND_BACKGROUND),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 10),
 
		/* Landscape selection. */
 
		NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 10),
 
			NWidget(NWID_SPACER), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
			NWidget(WWT_IMGBTN_2, COLOUR_ORANGE, GLAND_TEMPERATE), SetDataTip(SPR_SELECT_TEMPERATE, STR_INTRO_TOOLTIP_TEMPERATE),
 
			NWidget(NWID_SPACER), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
			NWidget(WWT_IMGBTN_2, COLOUR_ORANGE, GLAND_ARCTIC), SetDataTip(SPR_SELECT_SUB_ARCTIC, STR_INTRO_TOOLTIP_SUB_ARCTIC_LANDSCAPE),
 
			NWidget(NWID_SPACER), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
			NWidget(WWT_IMGBTN_2, COLOUR_ORANGE, GLAND_TROPICAL), SetDataTip(SPR_SELECT_SUB_TROPICAL, STR_INTRO_TOOLTIP_SUB_TROPICAL_LANDSCAPE),
 
			NWidget(NWID_SPACER), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
			NWidget(WWT_IMGBTN_2, COLOUR_ORANGE, GLAND_TOYLAND), SetDataTip(SPR_SELECT_TOYLAND, STR_INTRO_TOOLTIP_TOYLAND_LANDSCAPE),
 
			NWidget(NWID_SPACER), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 11), SetFill(false, true),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 11), SetFill(0, 1),
 
		NWidget(NWID_HORIZONTAL), SetPIP(10, 3, 10),
 
			/* Labels at the left side. */
 
			NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(0, 4, 0),
 
				NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_HEIGHTMAP_NAME_LABEL), SetDataTip(STR_MAPGEN_HEIGHTMAP_NAME, STR_NULL), SetFill(true, true),
 
				NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_MAPSIZE_X_TEXT), SetDataTip(STR_MAPGEN_MAPSIZE, STR_NULL), SetFill(true, true),
 
				NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_TOWN_TEXT), SetDataTip(STR_MAPGEN_NUMBER_OF_TOWNS, STR_NULL), SetFill(true, true),
 
				NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_INDUSTRY_TEXT), SetDataTip(STR_MAPGEN_NUMBER_OF_INDUSTRIES, STR_NULL), SetFill(true, true),
 
				NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_RANDOM_TEXT), SetDataTip(STR_MAPGEN_RANDOM_SEED, STR_NULL), SetFill(true, true),
 
				NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_TREE_TEXT), SetDataTip(STR_MAPGEN_TREE_PLACER, STR_NULL), SetFill(true, true),
 
				NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_HEIGHTMAP_ROTATION_TEXT), SetDataTip(STR_MAPGEN_HEIGHTMAP_ROTATION, STR_NULL), SetFill(true, true),
 
				NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_HEIGHTMAP_NAME_LABEL), SetDataTip(STR_MAPGEN_HEIGHTMAP_NAME, STR_NULL), SetFill(1, 1),
 
				NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_MAPSIZE_X_TEXT), SetDataTip(STR_MAPGEN_MAPSIZE, STR_NULL), SetFill(1, 1),
 
				NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_TOWN_TEXT), SetDataTip(STR_MAPGEN_NUMBER_OF_TOWNS, STR_NULL), SetFill(1, 1),
 
				NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_INDUSTRY_TEXT), SetDataTip(STR_MAPGEN_NUMBER_OF_INDUSTRIES, STR_NULL), SetFill(1, 1),
 
				NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_RANDOM_TEXT), SetDataTip(STR_MAPGEN_RANDOM_SEED, STR_NULL), SetFill(1, 1),
 
				NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_TREE_TEXT), SetDataTip(STR_MAPGEN_TREE_PLACER, STR_NULL), SetFill(1, 1),
 
				NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_HEIGHTMAP_ROTATION_TEXT), SetDataTip(STR_MAPGEN_HEIGHTMAP_ROTATION, STR_NULL), SetFill(1, 1),
 
			EndContainer(),
 
			/* Widgets at the right of the labels. */
 
			NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(0, 4, 0),
 
				NWidget(WWT_EMPTY, COLOUR_ORANGE, GLAND_HEIGHTMAP_NAME_TEXT), SetFill(true, false),
 
				NWidget(WWT_EMPTY, COLOUR_ORANGE, GLAND_HEIGHTMAP_NAME_TEXT), SetFill(1, 0),
 
				/* Mapsize X * Y. */
 
				NWidget(NWID_HORIZONTAL), SetPIP(0, 4, 0),
 
					NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_MAPSIZE_X_PULLDOWN), SetDataTip(STR_JUST_INT, STR_NULL), SetFill(true, false),
 
					NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_MAPSIZE_Y_TEXT), SetDataTip(STR_MAPGEN_BY, STR_NULL), SetPadding(1, 0, 0, 0), SetFill(true, true),
 
					NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_MAPSIZE_Y_PULLDOWN), SetDataTip(STR_JUST_INT, STR_NULL), SetFill(true, false),
 
					NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_MAPSIZE_X_PULLDOWN), SetDataTip(STR_JUST_INT, STR_NULL), SetFill(1, 0),
 
					NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_MAPSIZE_Y_TEXT), SetDataTip(STR_MAPGEN_BY, STR_NULL), SetPadding(1, 0, 0, 0), SetFill(1, 1),
 
					NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_MAPSIZE_Y_PULLDOWN), SetDataTip(STR_JUST_INT, STR_NULL), SetFill(1, 0),
 
				EndContainer(),
 
				NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_TOWN_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(true, false),
 
				NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_INDUSTRY_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(true, false),
 
				NWidget(WWT_EDITBOX, COLOUR_WHITE, GLAND_RANDOM_EDITBOX), SetDataTip(STR_MAPGEN_RANDOM_SEED_OSKTITLE, STR_MAPGEN_RANDOM_SEED_HELP), SetFill(true, true),
 
				NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_TREE_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(true, false),
 
				NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_HEIGHTMAP_ROTATION_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(true, false),
 
				NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_TOWN_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(1, 0),
 
				NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_INDUSTRY_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(1, 0),
 
				NWidget(WWT_EDITBOX, COLOUR_WHITE, GLAND_RANDOM_EDITBOX), SetDataTip(STR_MAPGEN_RANDOM_SEED_OSKTITLE, STR_MAPGEN_RANDOM_SEED_HELP), SetFill(1, 1),
 
				NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_TREE_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(1, 0),
 
				NWidget(WWT_DROPDOWN, COLOUR_ORANGE, GLAND_HEIGHTMAP_ROTATION_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(1, 0),
 
			EndContainer(),
 
			NWidget(NWID_VERTICAL), SetPIP(0, 4, 0),
 
				NWidget(NWID_HORIZONTAL), SetPIP(0, 3, 0),
 
					NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(0, 4, 0),
 
						NWidget(WWT_EMPTY, INVALID_COLOUR, GLAND_HEIGHTMAP_NAME_SPACER), SetFill(true, false),
 
						NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_HEIGHTMAP_SIZE_LABEL), SetDataTip(STR_MAPGEN_HEIGHTMAP_SIZE_LABEL, STR_NULL), SetFill(true, true),
 
						NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_START_DATE_TEXT1), SetDataTip(STR_MAPGEN_DATE, STR_NULL), SetFill(true, true),
 
						NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_SNOW_LEVEL_TEXT1), SetDataTip(STR_MAPGEN_SNOW_LINE_HEIGHT, STR_NULL), SetFill(true, true),
 
						NWidget(WWT_EMPTY, INVALID_COLOUR, GLAND_HEIGHTMAP_NAME_SPACER), SetFill(1, 0),
 
						NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_HEIGHTMAP_SIZE_LABEL), SetDataTip(STR_MAPGEN_HEIGHTMAP_SIZE_LABEL, STR_NULL), SetFill(1, 1),
 
						NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_START_DATE_TEXT1), SetDataTip(STR_MAPGEN_DATE, STR_NULL), SetFill(1, 1),
 
						NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_SNOW_LEVEL_TEXT1), SetDataTip(STR_MAPGEN_SNOW_LINE_HEIGHT, STR_NULL), SetFill(1, 1),
 
					EndContainer(),
 
					NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(0, 4, 0),
 
						NWidget(WWT_EMPTY, INVALID_COLOUR, GLAND_HEIGHTMAP_NAME_SPACER), SetFill(true, false),
 
						NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_HEIGHTMAP_SIZE_TEXT), SetDataTip(STR_MAPGEN_HEIGHTMAP_SIZE, STR_NULL), SetFill(true, false),
 
						NWidget(WWT_EMPTY, INVALID_COLOUR, GLAND_HEIGHTMAP_NAME_SPACER), SetFill(1, 0),
 
						NWidget(WWT_TEXT, COLOUR_ORANGE, GLAND_HEIGHTMAP_SIZE_TEXT), SetDataTip(STR_MAPGEN_HEIGHTMAP_SIZE, STR_NULL), SetFill(1, 0),
 
						NWidget(NWID_HORIZONTAL),
 
							NWidget(WWT_IMGBTN, COLOUR_ORANGE, GLAND_START_DATE_DOWN), SetDataTip(SPR_ARROW_DOWN, STR_SCENEDIT_TOOLBAR_TOOLTIP_MOVE_THE_STARTING_DATE_BACKWARD), SetFill(false, true),
 
							NWidget(WWT_TEXTBTN, COLOUR_ORANGE, GLAND_START_DATE_TEXT), SetDataTip(STR_BLACK_DATE_LONG, STR_NULL), SetFill(true, false),
 
							NWidget(WWT_IMGBTN, COLOUR_ORANGE, GLAND_START_DATE_UP), SetDataTip(SPR_ARROW_UP, STR_SCENEDIT_TOOLBAR_TOOLTIP_MOVE_THE_STARTING_DATE_FORWARD), SetFill(false, true),
 
							NWidget(WWT_IMGBTN, COLOUR_ORANGE, GLAND_START_DATE_DOWN), SetDataTip(SPR_ARROW_DOWN, STR_SCENEDIT_TOOLBAR_TOOLTIP_MOVE_THE_STARTING_DATE_BACKWARD), SetFill(0, 1),
 
							NWidget(WWT_TEXTBTN, COLOUR_ORANGE, GLAND_START_DATE_TEXT), SetDataTip(STR_BLACK_DATE_LONG, STR_NULL), SetFill(1, 0),
 
							NWidget(WWT_IMGBTN, COLOUR_ORANGE, GLAND_START_DATE_UP), SetDataTip(SPR_ARROW_UP, STR_SCENEDIT_TOOLBAR_TOOLTIP_MOVE_THE_STARTING_DATE_FORWARD), SetFill(0, 1),
 
						EndContainer(),
 
						NWidget(NWID_HORIZONTAL),
 
							NWidget(WWT_IMGBTN, COLOUR_ORANGE, GLAND_SNOW_LEVEL_DOWN), SetDataTip(SPR_ARROW_DOWN, STR_MAPGEN_SNOW_LINE_DOWN), SetFill(false, true),
 
							NWidget(WWT_TEXTBTN, COLOUR_ORANGE, GLAND_SNOW_LEVEL_TEXT), SetDataTip(STR_BLACK_INT, STR_NULL), SetFill(true, false),
 
							NWidget(WWT_IMGBTN, COLOUR_ORANGE, GLAND_SNOW_LEVEL_UP), SetDataTip(SPR_ARROW_UP, STR_MAPGEN_SNOW_LINE_UP), SetFill(false, true),
 
							NWidget(WWT_IMGBTN, COLOUR_ORANGE, GLAND_SNOW_LEVEL_DOWN), SetDataTip(SPR_ARROW_DOWN, STR_MAPGEN_SNOW_LINE_DOWN), SetFill(0, 1),
 
							NWidget(WWT_TEXTBTN, COLOUR_ORANGE, GLAND_SNOW_LEVEL_TEXT), SetDataTip(STR_BLACK_INT, STR_NULL), SetFill(1, 0),
 
							NWidget(WWT_IMGBTN, COLOUR_ORANGE, GLAND_SNOW_LEVEL_UP), SetDataTip(SPR_ARROW_UP, STR_MAPGEN_SNOW_LINE_UP), SetFill(0, 1),
 
						EndContainer(),
 
					EndContainer(),
 
				EndContainer(),
 
				NWidget(WWT_TEXTBTN, COLOUR_ORANGE, GLAND_RANDOM_BUTTON), SetDataTip(STR_MAPGEN_RANDOM, STR_MAPGEN_RANDOM_HELP), SetFill(true, false),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREEN, GLAND_GENERATE_BUTTON), SetDataTip(STR_MAPGEN_GENERATE, STR_NULL), SetFill(true, true),
 
				NWidget(WWT_TEXTBTN, COLOUR_ORANGE, GLAND_RANDOM_BUTTON), SetDataTip(STR_MAPGEN_RANDOM, STR_MAPGEN_RANDOM_HELP), SetFill(1, 0),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREEN, GLAND_GENERATE_BUTTON), SetDataTip(STR_MAPGEN_GENERATE, STR_NULL), SetFill(1, 1),
 
			EndContainer(),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 9), SetFill(true, true),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 9), SetFill(1, 1),
 
	EndContainer(),
 
};
 

	
 
static void StartGeneratingLandscape(glwp_modes mode)
 
{
 
	DeleteAllNonVitalWindows();
 

	
 
	/* Copy all XXX_newgame to XXX when coming from outside the editor */
 
	MakeNewgameSettingsLive();
 
	ResetGRFConfig(true);
 

	
 
	SndPlayFx(SND_15_BEEP);
 
	switch (mode) {
 
		case GLWP_GENERATE:  _switch_mode = (_game_mode == GM_EDITOR) ? SM_GENRANDLAND    : SM_NEWGAME;         break;
 
		case GLWP_HEIGHTMAP: _switch_mode = (_game_mode == GM_EDITOR) ? SM_LOAD_HEIGHTMAP : SM_START_HEIGHTMAP; break;
 
		case GLWP_SCENARIO:  _switch_mode = SM_EDITOR; break;
 
		default: NOT_REACHED();
 
	}
 
}
 

	
 
static void LandscapeGenerationCallback(Window *w, bool confirmed)
 
{
 
	if (confirmed) StartGeneratingLandscape((glwp_modes)w->window_number);
 
}
 

	
 
static DropDownList *BuildMapsizeDropDown()
 
{
 
	DropDownList *list = new DropDownList();
 

	
 
	for (uint i = MIN_MAP_SIZE_BITS; i <= MAX_MAP_SIZE_BITS; i++) {
 
		DropDownListParamStringItem *item = new DropDownListParamStringItem(STR_JUST_INT, i, false);
 
		item->SetParam(0, 1 << i);
 
		list->push_back(item);
 
	}
 

	
 
	return list;
 
}
 

	
 
static const StringID _elevations[]  = {STR_TERRAIN_TYPE_VERY_FLAT, STR_TERRAIN_TYPE_FLAT, STR_TERRAIN_TYPE_HILLY, STR_TERRAIN_TYPE_MOUNTAINOUS, INVALID_STRING_ID};
 
static const StringID _sea_lakes[]   = {STR_SEA_LEVEL_VERY_LOW, STR_SEA_LEVEL_LOW, STR_SEA_LEVEL_MEDIUM, STR_SEA_LEVEL_HIGH, INVALID_STRING_ID};
 
static const StringID _smoothness[]  = {STR_CONFIG_SETTING_ROUGHNESS_OF_TERRAIN_VERY_SMOOTH, STR_CONFIG_SETTING_ROUGHNESS_OF_TERRAIN_SMOOTH, STR_CONFIG_SETTING_ROUGHNESS_OF_TERRAIN_ROUGH, STR_CONFIG_SETTING_ROUGHNESS_OF_TERRAIN_VERY_ROUGH, INVALID_STRING_ID};
 
static const StringID _tree_placer[] = {STR_CONFIG_SETTING_TREE_PLACER_NONE, STR_CONFIG_SETTING_TREE_PLACER_ORIGINAL, STR_CONFIG_SETTING_TREE_PLACER_IMPROVED, INVALID_STRING_ID};
 
static const StringID _rotation[]    = {STR_CONFIG_SETTING_HEIGHTMAP_ROTATION_COUNTER_CLOCKWISE, STR_CONFIG_SETTING_HEIGHTMAP_ROTATION_CLOCKWISE, INVALID_STRING_ID};
 
static const StringID _landscape[]   = {STR_CONFIG_SETTING_LAND_GENERATOR_ORIGINAL, STR_CONFIG_SETTING_LAND_GENERATOR_TERRA_GENESIS, INVALID_STRING_ID};
 
static const StringID _num_towns[]   = {STR_NUM_VERY_LOW, STR_NUM_LOW, STR_NUM_NORMAL, STR_NUM_HIGH, STR_NUM_CUSTOM, INVALID_STRING_ID};
 
static const StringID _num_inds[]    = {STR_NONE, STR_NUM_VERY_LOW, STR_NUM_LOW, STR_NUM_NORMAL, STR_NUM_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, WindowNumber number = 0) : QueryStringBaseWindow(11)
 
	{
 
		this->InitNested(desc, number);
 

	
 
		this->LowerWidget(_settings_newgame.game_creation.landscape + GLAND_TEMPERATE);
 

	
 
		/* snprintf() always outputs trailing '\0', so whole buffer can be used */
 
		snprintf(this->edit_str_buf, this->edit_str_size, "%u", _settings_newgame.game_creation.generation_seed);
 
		InitializeTextBuffer(&this->text, this->edit_str_buf, this->edit_str_size, 120);
 
		this->SetFocusedWidget(GLAND_RANDOM_EDITBOX);
 
		this->caption = STR_NULL;
 
		this->afilter = CS_NUMERAL;
 

	
 
		this->mode = (glwp_modes)this->window_number;
 
	}
 

	
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		switch (widget) {
 
			case GLAND_START_DATE_TEXT:     SetDParam(0, ConvertYMDToDate(_settings_newgame.game_creation.starting_year, 0, 1)); break;
 
			case GLAND_MAPSIZE_X_PULLDOWN:  SetDParam(0, 1 << _settings_newgame.game_creation.map_x); break;
 
			case GLAND_MAPSIZE_Y_PULLDOWN:  SetDParam(0, 1 << _settings_newgame.game_creation.map_y); break;
 
			case GLAND_SNOW_LEVEL_TEXT:     SetDParam(0, _settings_newgame.game_creation.snow_line_height); break;
 
			case GLAND_TOWN_PULLDOWN:       SetDParam(0, _game_mode == GM_EDITOR ? STR_DISASTERS_OFF : _num_towns[_settings_newgame.difficulty.number_towns]); break;
 
			case GLAND_INDUSTRY_PULLDOWN:   SetDParam(0, _game_mode == GM_EDITOR ? STR_DISASTERS_OFF : _num_inds[_settings_newgame.difficulty.number_industries]); break;
 
			case GLAND_LANDSCAPE_PULLDOWN:  SetDParam(0, _landscape[_settings_newgame.game_creation.land_generator]); break;
 
			case GLAND_TREE_PULLDOWN:       SetDParam(0, _tree_placer[_settings_newgame.game_creation.tree_placer]); break;
 
			case GLAND_TERRAIN_PULLDOWN:    SetDParam(0, _elevations[_settings_newgame.difficulty.terrain_type]); break;
 
			case GLAND_WATER_PULLDOWN:      SetDParam(0, _sea_lakes[_settings_newgame.difficulty.quantity_sea_lakes]); break;
 
			case GLAND_SMOOTHNESS_PULLDOWN: SetDParam(0, _smoothness[_settings_newgame.game_creation.tgen_smoothness]); break;
 
			case GLAND_BORDERS_RANDOM:      SetDParam(0, (_settings_newgame.game_creation.water_borders == BORDERS_RANDOM) ? STR_MAPGEN_BORDER_RANDOMIZE : STR_MAPGEN_BORDER_MANUAL); break;
 
			case GLAND_WATER_NE: SetDParam(0, (_settings_newgame.game_creation.water_borders == BORDERS_RANDOM) ? STR_MAPGEN_BORDER_RANDOM : HasBit(_settings_newgame.game_creation.water_borders, BORDER_NE) ? STR_MAPGEN_BORDER_WATER : STR_MAPGEN_BORDER_FREEFORM); break;
 
			case GLAND_WATER_NW: SetDParam(0, (_settings_newgame.game_creation.water_borders == BORDERS_RANDOM) ? STR_MAPGEN_BORDER_RANDOM : HasBit(_settings_newgame.game_creation.water_borders, BORDER_NW) ? STR_MAPGEN_BORDER_WATER : STR_MAPGEN_BORDER_FREEFORM); break;
 
			case GLAND_WATER_SE: SetDParam(0, (_settings_newgame.game_creation.water_borders == BORDERS_RANDOM) ? STR_MAPGEN_BORDER_RANDOM : HasBit(_settings_newgame.game_creation.water_borders, BORDER_SE) ? STR_MAPGEN_BORDER_WATER : STR_MAPGEN_BORDER_FREEFORM); break;
 
			case GLAND_WATER_SW: SetDParam(0, (_settings_newgame.game_creation.water_borders == BORDERS_RANDOM) ? STR_MAPGEN_BORDER_RANDOM : HasBit(_settings_newgame.game_creation.water_borders, BORDER_SW) ? STR_MAPGEN_BORDER_WATER : STR_MAPGEN_BORDER_FREEFORM); break;
 
			case GLAND_HEIGHTMAP_ROTATION_PULLDOWN: SetDParam(0, _rotation[_settings_newgame.game_creation.heightmap_rotation]); break;
 

	
 
			case GLAND_HEIGHTMAP_SIZE_TEXT:
 
				if (_settings_newgame.game_creation.heightmap_rotation == HM_CLOCKWISE) {
 
					SetDParam(0, this->y);
 
					SetDParam(1, this->x);
 
				} else {
 
					SetDParam(0, this->x);
 
					SetDParam(1, this->y);
 
				}
 
				break;
 
		}
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		const StringID *strs = NULL;
 
		switch (widget) {
 
			case GLAND_START_DATE_TEXT:
 
				SetDParam(0, ConvertYMDToDate(MAX_YEAR, 0, 1));
 
				*size = GetStringBoundingBox(STR_BLACK_DATE_LONG);
 
				break;
 

	
 
			case GLAND_MAPSIZE_X_PULLDOWN:
 
			case GLAND_MAPSIZE_Y_PULLDOWN:
 
				SetDParam(0, MAX_MAP_SIZE);
 
				*size = GetStringBoundingBox(STR_JUST_INT);
 
				break;
 

	
 
			case GLAND_SNOW_LEVEL_TEXT:
 
				SetDParam(0, MAX_TILE_HEIGHT);
 
				*size = GetStringBoundingBox(STR_JUST_INT);
 
				break;
 

	
 
			case GLAND_HEIGHTMAP_SIZE_TEXT:
 
				SetDParam(0, this->x);
 
				SetDParam(1, this->y);
 
				*size = GetStringBoundingBox(STR_MAPGEN_HEIGHTMAP_SIZE);
 
				break;
 

	
 
			case GLAND_TOWN_PULLDOWN:       strs = _num_towns; break;
 
			case GLAND_INDUSTRY_PULLDOWN:   strs = _num_inds; break;
 
			case GLAND_LANDSCAPE_PULLDOWN:  strs = _landscape; break;
 
			case GLAND_TREE_PULLDOWN:       strs = _tree_placer; break;
 
			case GLAND_TERRAIN_PULLDOWN:    strs = _elevations; break;
 
			case GLAND_WATER_PULLDOWN:      strs = _sea_lakes; break;
 
			case GLAND_SMOOTHNESS_PULLDOWN: strs = _smoothness; break;
 
			case GLAND_HEIGHTMAP_ROTATION_PULLDOWN: strs = _rotation; break;
 
			case GLAND_BORDERS_RANDOM:
 
				*size = maxdim(GetStringBoundingBox(STR_MAPGEN_BORDER_RANDOMIZE), GetStringBoundingBox(STR_MAPGEN_BORDER_MANUAL));
 
				break;
 

	
 
			case GLAND_WATER_NE:
 
			case GLAND_WATER_NW:
 
			case GLAND_WATER_SE:
 
			case GLAND_WATER_SW:
 
				*size = maxdim(GetStringBoundingBox(STR_MAPGEN_BORDER_RANDOM), maxdim(GetStringBoundingBox(STR_MAPGEN_BORDER_WATER), GetStringBoundingBox(STR_MAPGEN_BORDER_FREEFORM)));
 
				break;
 

	
 
			case GLAND_HEIGHTMAP_NAME_SPACER:
 
			case GLAND_HEIGHTMAP_NAME_TEXT:
 
				size->width = 0;
 
				break;
 

	
 
			default:
 
				return;
 
		}
 
		if (strs != NULL) {
 
			while (*strs != INVALID_STRING_ID) {
 
				*size = maxdim(*size, GetStringBoundingBox(*strs++));
 
			}
 
		}
 
		size->width += padding.width;
 
		size->height = FONT_HEIGHT_NORMAL + WD_DROPDOWNTEXT_TOP + WD_DROPDOWNTEXT_BOTTOM;
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case GLAND_HEIGHTMAP_NAME_TEXT: {
 
				/* Little bit of a hack going on here; just to get the widgets
 
				 * spaced without doing much magic. The space we can draw on is
 
				 * covered by both the spacer and text widgets, so take their
 
				 * outer most boundaries (left and right) as draw locations. */
 
				const NWidgetBase *nwi_spacer = this->GetWidget<NWidgetBase>(GLAND_HEIGHTMAP_NAME_SPACER);
 
				DrawString(min(r.left, nwi_spacer->pos_x), max<int>(r.right, nwi_spacer->pos_x + nwi_spacer->current_x), r.top, this->name, TC_ORANGE);
 
			} break;
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		/* You can't select smoothness / non-water borders if not terragenesis */
 
		if (mode == GLWP_GENERATE) {
 
			this->SetWidgetDisabledState(GLAND_SMOOTHNESS_PULLDOWN, _settings_newgame.game_creation.land_generator == 0);
 
			this->SetWidgetDisabledState(GLAND_BORDERS_RANDOM, _settings_newgame.game_creation.land_generator == 0 || !_settings_newgame.construction.freeform_edges);
 
			this->SetWidgetsDisabledState(_settings_newgame.game_creation.land_generator == 0 || !_settings_newgame.construction.freeform_edges || _settings_newgame.game_creation.water_borders == BORDERS_RANDOM,
 
					GLAND_WATER_NW, GLAND_WATER_NE, GLAND_WATER_SE, GLAND_WATER_SW, WIDGET_LIST_END);
 

	
 
			this->SetWidgetLoweredState(GLAND_BORDERS_RANDOM, _settings_newgame.game_creation.water_borders == BORDERS_RANDOM);
 

	
 
			this->SetWidgetLoweredState(GLAND_WATER_NW, HasBit(_settings_newgame.game_creation.water_borders, BORDER_NW));
 
			this->SetWidgetLoweredState(GLAND_WATER_NE, HasBit(_settings_newgame.game_creation.water_borders, BORDER_NE));
 
			this->SetWidgetLoweredState(GLAND_WATER_SE, HasBit(_settings_newgame.game_creation.water_borders, BORDER_SE));
 
			this->SetWidgetLoweredState(GLAND_WATER_SW, HasBit(_settings_newgame.game_creation.water_borders, BORDER_SW));
 
		}
 
		/* Disable snowline if not hilly */
 
		this->SetWidgetDisabledState(GLAND_SNOW_LEVEL_TEXT, _settings_newgame.game_creation.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);
 

	
 
		this->SetWidgetDisabledState(GLAND_START_DATE_DOWN, _settings_newgame.game_creation.starting_year <= MIN_YEAR);
 
		this->SetWidgetDisabledState(GLAND_START_DATE_UP,   _settings_newgame.game_creation.starting_year >= MAX_YEAR);
 
		this->SetWidgetDisabledState(GLAND_SNOW_LEVEL_DOWN, _settings_newgame.game_creation.snow_line_height <= 2 || _settings_newgame.game_creation.landscape != LT_ARCTIC);
 
		this->SetWidgetDisabledState(GLAND_SNOW_LEVEL_UP,   _settings_newgame.game_creation.snow_line_height >= MAX_SNOWLINE_HEIGHT || _settings_newgame.game_creation.landscape != LT_ARCTIC);
 

	
 
		this->SetWidgetLoweredState(GLAND_TEMPERATE, _settings_newgame.game_creation.landscape == LT_TEMPERATE);
 
		this->SetWidgetLoweredState(GLAND_ARCTIC,    _settings_newgame.game_creation.landscape == LT_ARCTIC);
 
		this->SetWidgetLoweredState(GLAND_TROPICAL,  _settings_newgame.game_creation.landscape == LT_TROPIC);
 
		this->SetWidgetLoweredState(GLAND_TOYLAND,   _settings_newgame.game_creation.landscape == LT_TOYLAND);
 

	
 
		this->DrawWidgets();
 

	
 
		this->DrawEditBox(GLAND_RANDOM_EDITBOX);
 
	}
 

	
 
	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(_settings_newgame.game_creation.landscape + GLAND_TEMPERATE);
 
				SetNewLandscapeType(widget - GLAND_TEMPERATE);
 
				break;
 

	
 
			case GLAND_MAPSIZE_X_PULLDOWN: // Mapsize X
 
				ShowDropDownList(this, BuildMapsizeDropDown(), _settings_newgame.game_creation.map_x, GLAND_MAPSIZE_X_PULLDOWN);
 
				break;
 

	
 
			case GLAND_MAPSIZE_Y_PULLDOWN: // Mapsize Y
 
				ShowDropDownList(this, BuildMapsizeDropDown(), _settings_newgame.game_creation.map_y, GLAND_MAPSIZE_Y_PULLDOWN);
 
				break;
 

	
 
			case GLAND_TOWN_PULLDOWN: // Number of towns
 
				ShowDropDownMenu(this, _num_towns, _settings_newgame.difficulty.number_towns, GLAND_TOWN_PULLDOWN, 0, 0);
 
				break;
 

	
 
			case GLAND_INDUSTRY_PULLDOWN: // Number of industries
 
				ShowDropDownMenu(this, _num_inds, _settings_newgame.difficulty.number_industries, GLAND_INDUSTRY_PULLDOWN, 0, 0);
 
				break;
 

	
 
			case GLAND_RANDOM_BUTTON: // Random seed
 
				_settings_newgame.game_creation.generation_seed = InteractiveRandom();
 
				snprintf(this->edit_str_buf, this->edit_str_size, "%u", _settings_newgame.game_creation.generation_seed);
 
				UpdateTextBufferSize(&this->text);
 
				this->SetDirty();
 
				break;
 

	
 
			case GLAND_GENERATE_BUTTON: // Generate
 
				MakeNewgameSettingsLive();
 

	
 
				if (mode == GLWP_HEIGHTMAP &&
 
						(this->x * 2 < (1U << _settings_newgame.game_creation.map_x) ||
 
						this->x / 2 > (1U << _settings_newgame.game_creation.map_x) ||
 
						this->y * 2 < (1U << _settings_newgame.game_creation.map_y) ||
 
						this->y / 2 > (1U << _settings_newgame.game_creation.map_y))) {
 
					ShowQuery(
 
						STR_WARNING_HEIGHTMAP_SCALE_CAPTION,
 
						STR_WARNING_HEIGHTMAP_SCALE_MESSAGE,
 
						this,
 
						LandscapeGenerationCallback);
 
				} else {
 
					StartGeneratingLandscape(mode);
 
				}
 
				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) <= WF_TIMEOUT_TRIGGER) {
 
					this->HandleButtonClick(widget);
 
					this->SetDirty();
 

	
 
					_settings_newgame.game_creation.starting_year = Clamp(_settings_newgame.game_creation.starting_year + widget - GLAND_START_DATE_TEXT, MIN_YEAR, MAX_YEAR);
 
				}
 
				_left_button_clicked = false;
 
				break;
 

	
 
			case GLAND_START_DATE_TEXT: // Year text
 
				this->widget_id = GLAND_START_DATE_TEXT;
 
				SetDParam(0, _settings_newgame.game_creation.starting_year);
 
				ShowQueryString(STR_JUST_INT, STR_MAPGEN_START_DATE_QUERY_CAPT, 8, 100, this, CS_NUMERAL, QSF_NONE);
 
				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) <= WF_TIMEOUT_TRIGGER) {
 
					this->HandleButtonClick(widget);
 
					this->SetDirty();
 

	
 
					_settings_newgame.game_creation.snow_line_height = Clamp(_settings_newgame.game_creation.snow_line_height + widget - GLAND_SNOW_LEVEL_TEXT, 2, MAX_SNOWLINE_HEIGHT);
 
				}
 
				_left_button_clicked = false;
 
				break;
 

	
 
			case GLAND_SNOW_LEVEL_TEXT: // Snow line text
 
				this->widget_id = GLAND_SNOW_LEVEL_TEXT;
 
				SetDParam(0, _settings_newgame.game_creation.snow_line_height);
 
				ShowQueryString(STR_JUST_INT, STR_MAPGEN_SNOW_LINE_QUERY_CAPT, 3, 100, this, CS_NUMERAL, QSF_NONE);
 
				break;
 

	
 
			case GLAND_TREE_PULLDOWN: // Tree placer
 
				ShowDropDownMenu(this, _tree_placer, _settings_newgame.game_creation.tree_placer, GLAND_TREE_PULLDOWN, 0, 0);
 
				break;
 

	
 
			case GLAND_LANDSCAPE_PULLDOWN: // Landscape generator
 
				ShowDropDownMenu(this, _landscape, _settings_newgame.game_creation.land_generator, GLAND_LANDSCAPE_PULLDOWN, 0, 0);
 
				break;
 

	
 
			case GLAND_HEIGHTMAP_ROTATION_PULLDOWN: // Heightmap rotation
 
				ShowDropDownMenu(this, _rotation, _settings_newgame.game_creation.heightmap_rotation, GLAND_HEIGHTMAP_ROTATION_PULLDOWN, 0, 0);
 
				break;
 

	
 
			case GLAND_TERRAIN_PULLDOWN: // Terrain type
 
				ShowDropDownMenu(this, _elevations, _settings_newgame.difficulty.terrain_type, GLAND_TERRAIN_PULLDOWN, 0, 0);
 
				break;
 

	
 
			case GLAND_WATER_PULLDOWN: // Water quantity
 
				ShowDropDownMenu(this, _sea_lakes, _settings_newgame.difficulty.quantity_sea_lakes, GLAND_WATER_PULLDOWN, 0, 0);
 
				break;
 

	
 
			case GLAND_SMOOTHNESS_PULLDOWN: // Map smoothness
 
				ShowDropDownMenu(this, _smoothness, _settings_newgame.game_creation.tgen_smoothness, GLAND_SMOOTHNESS_PULLDOWN, 0, 0);
 
				break;
 

	
 
			/* Freetype map borders */
 
			case GLAND_WATER_NW:
 
				_settings_newgame.game_creation.water_borders = ToggleBit(_settings_newgame.game_creation.water_borders, BORDER_NW);
 
				break;
 

	
 
			case GLAND_WATER_NE:
 
				_settings_newgame.game_creation.water_borders = ToggleBit(_settings_newgame.game_creation.water_borders, BORDER_NE);
 
				break;
 

	
 
			case GLAND_WATER_SE:
 
				_settings_newgame.game_creation.water_borders = ToggleBit(_settings_newgame.game_creation.water_borders, BORDER_SE);
 
				break;
 

	
 
			case GLAND_WATER_SW:
 
				_settings_newgame.game_creation.water_borders = ToggleBit(_settings_newgame.game_creation.water_borders, BORDER_SW);
 
				break;
 

	
 
			case GLAND_BORDERS_RANDOM:
 
				_settings_newgame.game_creation.water_borders = (_settings_newgame.game_creation.water_borders == BORDERS_RANDOM) ? 0 : BORDERS_RANDOM;
 
				this->SetDirty();
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnTimeout()
 
	{
 
		static const int raise_widgets[] = {GLAND_START_DATE_DOWN, GLAND_START_DATE_UP, GLAND_SNOW_LEVEL_UP, GLAND_SNOW_LEVEL_DOWN, WIDGET_LIST_END};
 
		for (const int *widget = raise_widgets; *widget != WIDGET_LIST_END; widget++) {
 
			if (this->IsWidgetLowered(*widget)) {
 
				this->RaiseWidget(*widget);
 
				this->SetWidgetDirty(*widget);
 
			}
 
		}
 
	}
 

	
 
	virtual void OnMouseLoop()
 
	{
 
		this->HandleEditBox(GLAND_RANDOM_EDITBOX);
 
	}
 

	
 
	virtual EventState OnKeyPress(uint16 key, uint16 keycode)
 
	{
 
		EventState state;
 
		this->HandleEditBoxKey(GLAND_RANDOM_EDITBOX, key, keycode, state);
 
		/* the seed is unsigned, therefore atoi cannot be used.
 
		 * As UINT32_MAX 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. */
 
		_settings_newgame.game_creation.generation_seed = minu(strtoul(this->edit_str_buf, NULL, 10), UINT32_MAX - 1);
 
		return state;
 
	}
 

	
 
	virtual void OnDropdownSelect(int widget, int index)
 
	{
 
		switch (widget) {
 
			case GLAND_MAPSIZE_X_PULLDOWN:     _settings_newgame.game_creation.map_x = index; break;
 
			case GLAND_MAPSIZE_Y_PULLDOWN:     _settings_newgame.game_creation.map_y = index; break;
 
			case GLAND_TREE_PULLDOWN:          _settings_newgame.game_creation.tree_placer = index; break;
 
			case GLAND_SMOOTHNESS_PULLDOWN:    _settings_newgame.game_creation.tgen_smoothness = index;  break;
 

	
 
			case GLAND_TOWN_PULLDOWN:
 
				if ((uint)index == CUSTOM_TOWN_NUMBER_DIFFICULTY) {
 
					this->widget_id = widget;
 
					SetDParam(0, _settings_newgame.game_creation.custom_town_number);
 
					ShowQueryString(STR_JUST_INT, STR_MAPGEN_NUMBER_OF_TOWNS, 5, 50, this, CS_NUMERAL, QSF_NONE);
 
				};
 
				IConsoleSetSetting("difficulty.number_towns", index);
 
				break;
 

	
 
			case GLAND_INDUSTRY_PULLDOWN:
 
				IConsoleSetSetting("difficulty.number_industries", index);
 
				break;
 

	
 
			case GLAND_LANDSCAPE_PULLDOWN:
 
			/* case GLAND_HEIGHTMAP_PULLDOWN: */
 
				if (mode == GLWP_HEIGHTMAP) {
 
					_settings_newgame.game_creation.heightmap_rotation = index;
 
				} else {
 
					_settings_newgame.game_creation.land_generator = index;
 
				}
 
				break;
 

	
 
			case GLAND_TERRAIN_PULLDOWN: {
 
				GameMode old_gm = _game_mode;
 
				_game_mode = GM_MENU;
 
				IConsoleSetSetting("difficulty.terrain_type", index);
 
				_game_mode = old_gm;
 
				break;
 
			}
 

	
 
			case GLAND_WATER_PULLDOWN: {
 
				GameMode old_gm = _game_mode;
 
				_game_mode = GM_MENU;
 
				IConsoleSetSetting("difficulty.quantity_sea_lakes", index);
 
				_game_mode = old_gm;
 
				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->SetWidgetDirty(GLAND_START_DATE_TEXT);
 
					_settings_newgame.game_creation.starting_year = Clamp(value, MIN_YEAR, MAX_YEAR);
 
					break;
 

	
 
				case GLAND_SNOW_LEVEL_TEXT:
 
					this->SetWidgetDirty(GLAND_SNOW_LEVEL_TEXT);
 
					_settings_newgame.game_creation.snow_line_height = Clamp(value, 2, MAX_SNOWLINE_HEIGHT);
 
					break;
 

	
 
				case GLAND_TOWN_PULLDOWN:
 
					_settings_newgame.game_creation.custom_town_number = Clamp(value, 1, CUSTOM_TOWN_MAX_NUMBER);
 
					break;
 
			}
 

	
 
			this->SetDirty();
 
		}
 
	}
 
};
 

	
 
static const WindowDesc _generate_landscape_desc(
 
	WDP_CENTER, WDP_CENTER, 338, 313,
 
	WC_GENERATE_LANDSCAPE, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
 
	_nested_generate_landscape_widgets, lengthof(_nested_generate_landscape_widgets)
 
);
 

	
 
static const WindowDesc _heightmap_load_desc(
 
	WDP_CENTER, WDP_CENTER, 338, 236,
 
	WC_GENERATE_LANDSCAPE, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_STD_BTN | WDF_UNCLICK_BUTTONS,
 
	_nested_heightmap_load_widgets, lengthof(_nested_heightmap_load_widgets)
 
);
 

	
 
static void _ShowGenerateLandscape(glwp_modes mode)
 
{
 
	uint x = 0;
 
	uint y = 0;
 

	
 
	DeleteWindowByClass(WC_GENERATE_LANDSCAPE);
 

	
 
	/* Always give a new seed if not editor */
 
	if (_game_mode != GM_EDITOR) _settings_newgame.game_creation.generation_seed = InteractiveRandom();
 

	
 
	if (mode == GLWP_HEIGHTMAP) {
 
		/* If the function returns negative, it means there was a problem loading the heightmap */
 
		if (!GetHeightmapDimensions(_file_to_saveload.name, &x, &y)) return;
 
	}
 

	
 
	GenerateLandscapeWindow *w = AllocateWindowDescFront<GenerateLandscapeWindow>((mode == GLWP_HEIGHTMAP) ? &_heightmap_load_desc : &_generate_landscape_desc, mode);
 

	
 
	if (mode == GLWP_HEIGHTMAP) {
 
		w->x = x;
 
		w->y = y;
 
		strecpy(w->name, _file_to_saveload.title, lastof(w->name));
 
	}
 

	
 
	SetWindowDirty(WC_GENERATE_LANDSCAPE, mode);
 
}
 

	
 
void ShowGenerateLandscape()
 
{
 
	_ShowGenerateLandscape(GLWP_GENERATE);
 
}
 

	
 
void ShowHeightmapLoad()
 
{
 
	_ShowGenerateLandscape(GLWP_HEIGHTMAP);
 
}
 

	
 
void StartScenarioEditor()
 
{
 
	StartGeneratingLandscape(GLWP_SCENARIO);
 
}
 

	
 
void StartNewGameWithoutGUI(uint seed)
 
{
 
	/* GenerateWorld takes care of the possible GENERATE_NEW_SEED value in 'seed' */
 
	_settings_newgame.game_creation.generation_seed = seed;
 

	
 
	StartGeneratingLandscape(GLWP_GENERATE);
 
}
 

	
 
/** Widget numbers of the create scenario window. */
 
enum CreateScenarioWindowWidgets {
 
	CSCEN_CLOSEBOX,               ///< Close button.
 
	CSCEN_CAPTION,                ///< Title bar.
 
	CSCEN_BACKGROUND,             ///< Background panel.
 
	CSCEN_TEMPERATE,              ///< Select temperate landscape style.
 
	CSCEN_ARCTIC,                 ///< Select arctic landscape style.
 
	CSCEN_TROPICAL,               ///< Select tropical landscape style.
 
	CSCEN_TOYLAND,                ///< Select toy-land landscape style.
 
	CSCEN_EMPTY_WORLD,            ///< Generate an empty flat world.
 
	CSCEN_RANDOM_WORLD,           ///< Generate random land button
 
	CSCEN_MAPSIZE_X_TEXT,         ///< Clickable currently selected x map size.
 
	CSCEN_MAPSIZE_X_PULLDOWN,     ///< Pull-down arrow for x map size.
 
	CSCEN_MAPSIZE_Y_TEXT,         ///< Clickable currently selected y map size.
 
	CSCEN_MAPSIZE_Y_PULLDOWN,     ///< Pull-down arrow for y map size.
 
	CSCEN_START_DATE_LABEL,       ///< 'Date' label
 
	CSCEN_START_DATE_DOWN,        ///< Decrease start year (start earlier).
 
	CSCEN_START_DATE_TEXT,        ///< Clickable start date value.
 
	CSCEN_START_DATE_UP,          ///< Increase start year (start later).
 
	CSCEN_FLAT_LAND_HEIGHT_LABEL, ///< 'Height of flat land' label.
 
	CSCEN_FLAT_LAND_HEIGHT_DOWN,  ///< Decrease flat land height.
 
	CSCEN_FLAT_LAND_HEIGHT_TEXT,  ///< Clickable flat land height value.
 
	CSCEN_FLAT_LAND_HEIGHT_UP     ///< Increase flat land height.
 
};
 

	
 

	
 
struct CreateScenarioWindow : public Window
 
{
 
	uint widget_id;
 

	
 
	CreateScenarioWindow(const WindowDesc *desc, WindowNumber window_number) : Window()
 
	{
 
		this->InitNested(desc, window_number);
 
		this->LowerWidget(_settings_newgame.game_creation.landscape + CSCEN_TEMPERATE);
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		switch (widget) {
 
			case CSCEN_START_DATE_TEXT:
 
				SetDParam(0, ConvertYMDToDate(_settings_newgame.game_creation.starting_year, 0, 1));
 
				break;
 

	
 
			case CSCEN_MAPSIZE_X_PULLDOWN:
 
				SetDParam(0, 1 << _settings_newgame.game_creation.map_x);
 
				break;
 

	
 
			case CSCEN_MAPSIZE_Y_PULLDOWN:
 
				SetDParam(0, 1 << _settings_newgame.game_creation.map_y);
 
				break;
 

	
 
			case CSCEN_FLAT_LAND_HEIGHT_TEXT:
 
				SetDParam(0, _settings_newgame.game_creation.se_flat_world_height);
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->SetWidgetDisabledState(CSCEN_START_DATE_DOWN,       _settings_newgame.game_creation.starting_year <= MIN_YEAR);
 
		this->SetWidgetDisabledState(CSCEN_START_DATE_UP,         _settings_newgame.game_creation.starting_year >= MAX_YEAR);
 
		this->SetWidgetDisabledState(CSCEN_FLAT_LAND_HEIGHT_DOWN, _settings_newgame.game_creation.se_flat_world_height <= 0);
 
		this->SetWidgetDisabledState(CSCEN_FLAT_LAND_HEIGHT_UP,   _settings_newgame.game_creation.se_flat_world_height >= MAX_TILE_HEIGHT);
 

	
 
		this->SetWidgetLoweredState(CSCEN_TEMPERATE, _settings_newgame.game_creation.landscape == LT_TEMPERATE);
 
		this->SetWidgetLoweredState(CSCEN_ARCTIC,    _settings_newgame.game_creation.landscape == LT_ARCTIC);
 
		this->SetWidgetLoweredState(CSCEN_TROPICAL,  _settings_newgame.game_creation.landscape == LT_TROPIC);
 
		this->SetWidgetLoweredState(CSCEN_TOYLAND,   _settings_newgame.game_creation.landscape == LT_TOYLAND);
 

	
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		StringID str = STR_JUST_INT;
 
		switch (widget) {
 
			case CSCEN_START_DATE_TEXT:
 
				SetDParam(0, ConvertYMDToDate(MAX_YEAR, 0, 1));
 
				str = STR_BLACK_DATE_LONG;
 
				break;
 

	
 
			case CSCEN_MAPSIZE_X_PULLDOWN:
 
			case CSCEN_MAPSIZE_Y_PULLDOWN:
 
				SetDParam(0, MAX_MAP_SIZE);
 
				break;
 

	
 
			case CSCEN_FLAT_LAND_HEIGHT_TEXT:
 
				SetDParam(0, MAX_TILE_HEIGHT);
 
				break;
 

	
 
			default:
 
				return;
 
		}
 
		*size = GetStringBoundingBox(str);
 
		size->width += padding.width;
 
		size->height += padding.height;
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case CSCEN_TEMPERATE:
 
			case CSCEN_ARCTIC:
 
			case CSCEN_TROPICAL:
 
			case CSCEN_TOYLAND:
 
				this->RaiseWidget(_settings_newgame.game_creation.landscape + CSCEN_TEMPERATE);
 
				SetNewLandscapeType(widget - CSCEN_TEMPERATE);
 
				break;
 

	
 
			case CSCEN_MAPSIZE_X_PULLDOWN: // Mapsize X
 
				ShowDropDownList(this, BuildMapsizeDropDown(), _settings_newgame.game_creation.map_x, CSCEN_MAPSIZE_X_PULLDOWN);
 
				break;
 

	
 
			case CSCEN_MAPSIZE_Y_PULLDOWN: // Mapsize Y
 
				ShowDropDownList(this, BuildMapsizeDropDown(), _settings_newgame.game_creation.map_y, CSCEN_MAPSIZE_Y_PULLDOWN);
 
				break;
 

	
 
			case CSCEN_EMPTY_WORLD: // Empty world / flat world
 
				StartGeneratingLandscape(GLWP_SCENARIO);
 
				break;
 

	
 
			case CSCEN_RANDOM_WORLD: // Generate
 
				ShowGenerateLandscape();
 
				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) <= WF_TIMEOUT_TRIGGER) {
 
					this->HandleButtonClick(widget);
 
					this->SetDirty();
 

	
 
					_settings_newgame.game_creation.starting_year = Clamp(_settings_newgame.game_creation.starting_year + widget - CSCEN_START_DATE_TEXT, MIN_YEAR, MAX_YEAR);
 
				}
 
				_left_button_clicked = false;
 
				break;
 

	
 
			case CSCEN_START_DATE_TEXT: // Year text
 
				this->widget_id = CSCEN_START_DATE_TEXT;
 
				SetDParam(0, _settings_newgame.game_creation.starting_year);
 
				ShowQueryString(STR_JUST_INT, STR_MAPGEN_START_DATE_QUERY_CAPT, 8, 100, this, CS_NUMERAL, QSF_NONE);
 
				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) <= WF_TIMEOUT_TRIGGER) {
 
					this->HandleButtonClick(widget);
 
					this->SetDirty();
 

	
 
					_settings_newgame.game_creation.se_flat_world_height = Clamp(_settings_newgame.game_creation.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, _settings_newgame.game_creation.se_flat_world_height);
 
				ShowQueryString(STR_JUST_INT, STR_SE_MAPGEN_FLAT_WORLD_HEIGHT_QUERY_CAPT, 3, 100, this, CS_NUMERAL, QSF_NONE);
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnTimeout()
 
	{
 
		static const int raise_widgets[] = {CSCEN_START_DATE_DOWN, CSCEN_START_DATE_UP, CSCEN_FLAT_LAND_HEIGHT_DOWN, CSCEN_FLAT_LAND_HEIGHT_UP, WIDGET_LIST_END};
 
		for (const int *widget = raise_widgets; *widget != WIDGET_LIST_END; widget++) {
 
			if (this->IsWidgetLowered(*widget)) {
 
				this->RaiseWidget(*widget);
 
				this->SetWidgetDirty(*widget);
 
			}
 
		}
 
	}
 

	
 
	virtual void OnDropdownSelect(int widget, int index)
 
	{
 
		switch (widget) {
 
			case CSCEN_MAPSIZE_X_PULLDOWN: _settings_newgame.game_creation.map_x = index; break;
 
			case CSCEN_MAPSIZE_Y_PULLDOWN: _settings_newgame.game_creation.map_y = index; break;
 
		}
 
		this->SetDirty();
 
	}
 

	
 
	virtual void OnQueryTextFinished(char *str)
 
	{
 
		if (!StrEmpty(str)) {
 
			int32 value = atoi(str);
 

	
 
			switch (this->widget_id) {
 
				case CSCEN_START_DATE_TEXT:
 
					this->SetWidgetDirty(CSCEN_START_DATE_TEXT);
 
					_settings_newgame.game_creation.starting_year = Clamp(value, MIN_YEAR, MAX_YEAR);
 
					break;
 

	
 
				case CSCEN_FLAT_LAND_HEIGHT_TEXT:
 
					this->SetWidgetDirty(CSCEN_FLAT_LAND_HEIGHT_TEXT);
 
					_settings_newgame.game_creation.se_flat_world_height = Clamp(value, 0, MAX_TILE_HEIGHT);
 
					break;
 
			}
 

	
 
			this->SetDirty();
 
		}
 
	}
 
};
 

	
 
static const NWidgetPart _nested_create_scenario_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_BROWN, CSCEN_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_BROWN, CSCEN_CAPTION), SetDataTip(STR_SE_MAPGEN_CAPTION, STR_NULL),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_BROWN, CSCEN_BACKGROUND),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 10),
 
		/* Landscape style selection. */
 
		NWidget(NWID_HORIZONTAL), SetPIP(10, 3, 10),
 
			NWidget(WWT_IMGBTN_2, COLOUR_ORANGE, CSCEN_TEMPERATE), SetDataTip(SPR_SELECT_TEMPERATE, STR_INTRO_TOOLTIP_TEMPERATE),
 
			NWidget(WWT_IMGBTN_2, COLOUR_ORANGE, CSCEN_ARCTIC), SetDataTip(SPR_SELECT_SUB_ARCTIC, STR_INTRO_TOOLTIP_SUB_ARCTIC_LANDSCAPE),
 
			NWidget(WWT_IMGBTN_2, COLOUR_ORANGE, CSCEN_TROPICAL), SetDataTip(SPR_SELECT_SUB_TROPICAL, STR_INTRO_TOOLTIP_SUB_TROPICAL_LANDSCAPE),
 
			NWidget(WWT_IMGBTN_2, COLOUR_ORANGE, CSCEN_TOYLAND), SetDataTip(SPR_SELECT_TOYLAND, STR_INTRO_TOOLTIP_TOYLAND_LANDSCAPE),
 
		EndContainer(),
 
		NWidget(NWID_HORIZONTAL), SetPIP(10, 8, 10),
 
			/* Green generation type buttons: 'Flat land' and 'Random land'. */
 
			NWidget(NWID_VERTICAL), SetPIP(10, 6, 10),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREEN, CSCEN_EMPTY_WORLD), SetDataTip(STR_SE_MAPGEN_FLAT_WORLD, STR_SE_MAPGEN_FLAT_WORLD_TOOLTIP), SetFill(true, true),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREEN, CSCEN_RANDOM_WORLD), SetDataTip(STR_SE_MAPGEN_RANDOM_LAND, STR_TERRAFORM_TOOLTIP_GENERATE_RANDOM_LAND), SetFill(true, true),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREEN, CSCEN_EMPTY_WORLD), SetDataTip(STR_SE_MAPGEN_FLAT_WORLD, STR_SE_MAPGEN_FLAT_WORLD_TOOLTIP), SetFill(1, 1),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREEN, CSCEN_RANDOM_WORLD), SetDataTip(STR_SE_MAPGEN_RANDOM_LAND, STR_TERRAFORM_TOOLTIP_GENERATE_RANDOM_LAND), SetFill(1, 1),
 
			EndContainer(),
 
			/* Labels + setting drop-downs */
 
			NWidget(NWID_VERTICAL), SetPIP(10, 6, 10),
 
				/* Map size. */
 
				NWidget(NWID_HORIZONTAL),
 
					NWidget(WWT_TEXT, COLOUR_ORANGE, CSCEN_MAPSIZE_X_TEXT), SetDataTip(STR_MAPGEN_MAPSIZE, STR_NULL), SetPadding(1, 0, 0, 0),
 
					NWidget(NWID_SPACER), SetMinimalSize(6, 0), SetFill(true, false),
 
					NWidget(NWID_SPACER), SetMinimalSize(6, 0), SetFill(1, 0),
 
					NWidget(WWT_DROPDOWN, COLOUR_ORANGE, CSCEN_MAPSIZE_X_PULLDOWN), SetDataTip(STR_JUST_INT, STR_NULL), SetPadding(0, 4, 0, 0),
 
					NWidget(WWT_TEXT, COLOUR_ORANGE, CSCEN_MAPSIZE_Y_TEXT), SetDataTip(STR_MAPGEN_BY, STR_NULL), SetPadding(1, 2, 0, 0),
 
					NWidget(WWT_DROPDOWN, COLOUR_ORANGE, CSCEN_MAPSIZE_Y_PULLDOWN), SetDataTip(STR_JUST_INT, STR_NULL),
 
				EndContainer(),
 
				/* Date. */
 
				NWidget(NWID_HORIZONTAL),
 
					NWidget(WWT_TEXT, COLOUR_ORANGE, CSCEN_START_DATE_LABEL), SetDataTip(STR_MAPGEN_DATE, STR_NULL), SetPadding(1, 0, 0, 0),
 
					NWidget(NWID_SPACER), SetMinimalSize(6, 0), SetFill(true, false),
 
					NWidget(WWT_IMGBTN, COLOUR_ORANGE, CSCEN_START_DATE_DOWN), SetFill(false, true), SetDataTip(SPR_ARROW_DOWN, STR_SCENEDIT_TOOLBAR_TOOLTIP_MOVE_THE_STARTING_DATE_BACKWARD),
 
					NWidget(NWID_SPACER), SetMinimalSize(6, 0), SetFill(1, 0),
 
					NWidget(WWT_IMGBTN, COLOUR_ORANGE, CSCEN_START_DATE_DOWN), SetFill(0, 1), SetDataTip(SPR_ARROW_DOWN, STR_SCENEDIT_TOOLBAR_TOOLTIP_MOVE_THE_STARTING_DATE_BACKWARD),
 
					NWidget(WWT_TEXTBTN, COLOUR_ORANGE, CSCEN_START_DATE_TEXT), SetDataTip(STR_BLACK_DATE_LONG, STR_NULL),
 
					NWidget(WWT_IMGBTN, COLOUR_ORANGE, CSCEN_START_DATE_UP), SetFill(false, true), SetDataTip(SPR_ARROW_UP, STR_SCENEDIT_TOOLBAR_TOOLTIP_MOVE_THE_STARTING_DATE_FORWARD),
 
					NWidget(WWT_IMGBTN, COLOUR_ORANGE, CSCEN_START_DATE_UP), SetFill(0, 1), SetDataTip(SPR_ARROW_UP, STR_SCENEDIT_TOOLBAR_TOOLTIP_MOVE_THE_STARTING_DATE_FORWARD),
 
				EndContainer(),
 
				/* Flat map height. */
 
				NWidget(NWID_HORIZONTAL),
 
					NWidget(WWT_TEXT, COLOUR_ORANGE, CSCEN_FLAT_LAND_HEIGHT_LABEL),
 
												SetDataTip(STR_SE_MAPGEN_FLAT_WORLD_HEIGHT, STR_NULL), SetPadding(1, 0, 0, 0),
 
					NWidget(NWID_SPACER), SetMinimalSize(6, 0), SetFill(true, false),
 
					NWidget(WWT_IMGBTN, COLOUR_ORANGE, CSCEN_FLAT_LAND_HEIGHT_DOWN), SetFill(false, true), SetDataTip(SPR_ARROW_DOWN, STR_SE_MAPGEN_FLAT_WORLD_HEIGHT_DOWN),
 
					NWidget(NWID_SPACER), SetMinimalSize(6, 0), SetFill(1, 0),
 
					NWidget(WWT_IMGBTN, COLOUR_ORANGE, CSCEN_FLAT_LAND_HEIGHT_DOWN), SetFill(0, 1), SetDataTip(SPR_ARROW_DOWN, STR_SE_MAPGEN_FLAT_WORLD_HEIGHT_DOWN),
 
					NWidget(WWT_TEXTBTN, COLOUR_ORANGE, CSCEN_FLAT_LAND_HEIGHT_TEXT), SetDataTip(STR_BLACK_INT, STR_NULL),
 
					NWidget(WWT_IMGBTN, COLOUR_ORANGE, CSCEN_FLAT_LAND_HEIGHT_UP), SetFill(false, true), SetDataTip(SPR_ARROW_UP, STR_SE_MAPGEN_FLAT_WORLD_HEIGHT_UP),
 
					NWidget(WWT_IMGBTN, COLOUR_ORANGE, CSCEN_FLAT_LAND_HEIGHT_UP), SetFill(0, 1), SetDataTip(SPR_ARROW_UP, STR_SE_MAPGEN_FLAT_WORLD_HEIGHT_UP),
 
				EndContainer(),
 
			EndContainer(),
 
		EndContainer(),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _create_scenario_desc(
 
	WDP_CENTER, WDP_CENTER, 338, 170,
 
	WC_GENERATE_LANDSCAPE, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_STD_BTN | WDF_UNCLICK_BUTTONS,
 
	_nested_create_scenario_widgets, lengthof(_nested_create_scenario_widgets)
 
);
 

	
 
void ShowCreateScenario()
 
{
 
	DeleteWindowByClass(WC_GENERATE_LANDSCAPE);
 
	new CreateScenarioWindow(&_create_scenario_desc, GLWP_SCENARIO);
 
}
 

	
 
enum GenerationProgressWindowWidgets {
 
	GPWW_CAPTION,
 
	GPWW_BACKGROUND,
 
	GPWW_PROGRESS_BAR,
 
	GPWW_PROGRESS_TEXT,
 
	GPWW_ABORT,
 
};
 

	
 
static const NWidgetPart _nested_generate_progress_widgets[] = {
 
	NWidget(WWT_CAPTION, COLOUR_GREY, GPWW_CAPTION), SetDataTip(STR_GENERATION_WORLD, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	NWidget(WWT_PANEL, COLOUR_GREY, GPWW_BACKGROUND),
 
		NWidget(NWID_HORIZONTAL), SetPIP(20, 0, 20),
 
			NWidget(NWID_VERTICAL), SetPIP(11, 8, 11),
 
				NWidget(WWT_EMPTY, INVALID_COLOUR, GPWW_PROGRESS_BAR), SetFill(true, false),
 
				NWidget(WWT_EMPTY, INVALID_COLOUR, GPWW_PROGRESS_TEXT), SetFill(true, false),
 
				NWidget(WWT_TEXTBTN, COLOUR_WHITE, GPWW_ABORT), SetDataTip(STR_GENERATION_ABORT, STR_NULL), SetFill(true, false),
 
				NWidget(WWT_EMPTY, INVALID_COLOUR, GPWW_PROGRESS_BAR), SetFill(1, 0),
 
				NWidget(WWT_EMPTY, INVALID_COLOUR, GPWW_PROGRESS_TEXT), SetFill(1, 0),
 
				NWidget(WWT_TEXTBTN, COLOUR_WHITE, GPWW_ABORT), SetDataTip(STR_GENERATION_ABORT, STR_NULL), SetFill(1, 0),
 
			EndContainer(),
 
		EndContainer(),
 
	EndContainer(),
 
};
 

	
 

	
 
static const WindowDesc _generate_progress_desc(
 
	WDP_CENTER, WDP_CENTER, 181, 97,
 
	WC_GENERATE_PROGRESS_WINDOW, WC_NONE,
 
	WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
 
	_nested_generate_progress_widgets, lengthof(_nested_generate_progress_widgets)
 
);
 

	
 
struct tp_info {
 
	uint percent;
 
	StringID cls;
 
	uint current;
 
	uint total;
 
	int timer;
 
};
 

	
 
static tp_info _tp;
 

	
 
static const StringID _generation_class_table[GWP_CLASS_COUNT]  = {
 
	STR_GENERATION_WORLD_GENERATION,
 
	STR_SCENEDIT_TOOLBAR_LANDSCAPE_GENERATION,
 
	STR_GENERATION_CLEARING_TILES,
 
	STR_SCENEDIT_TOOLBAR_TOWN_GENERATION,
 
	STR_SCENEDIT_TOOLBAR_INDUSTRY_GENERATION,
 
	STR_GENERATION_UNMOVABLE_GENERATION,
 
	STR_GENERATION_TREE_GENERATION,
 
	STR_GENERATION_SETTINGUP_GAME,
 
	STR_GENERATION_PREPARING_TILELOOP,
 
	STR_GENERATION_PREPARING_GAME
 
};
 

	
 

	
 
static void AbortGeneratingWorldCallback(Window *w, bool confirmed)
 
{
 
	if (confirmed) {
 
		AbortGeneratingWorld();
 
	} else if (IsGeneratingWorld() && !IsGeneratingWorldAborted()) {
 
		SetMouseCursor(SPR_CURSOR_ZZZ, PAL_NONE);
 
	}
 
}
 

	
 
struct GenerateProgressWindow : public Window {
 

	
 
	GenerateProgressWindow() : Window()
 
	{
 
		this->InitNested(&_generate_progress_desc);
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case GPWW_ABORT:
 
				if (_cursor.sprite == SPR_CURSOR_ZZZ) SetMouseCursor(SPR_CURSOR_MOUSE, PAL_NONE);
 
				ShowQuery(
 
					STR_GENERATION_ABORT_CAPTION,
 
					STR_GENERATION_ABORT_MESSAGE,
 
					this,
 
					AbortGeneratingWorldCallback
 
				);
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		switch (widget) {
 
			case GPWW_PROGRESS_BAR: {
 
				SetDParam(0, 100);
 
				*size = GetStringBoundingBox(STR_GENERATION_PROGRESS);
 
				/* We need some spacing for the 'border' */
 
				size->height += 8;
 
				size->width += 8;
 
			} break;
 

	
 
			case GPWW_PROGRESS_TEXT:
 
				for (uint i = 0; i < GWP_CLASS_COUNT; i++) {
 
					size->width = max(size->width, GetStringBoundingBox(_generation_class_table[i]).width);
 
				}
 
				size->height = FONT_HEIGHT_NORMAL * 2 + WD_PAR_VSEP_NORMAL;
 
				break;
 
		}
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case GPWW_PROGRESS_BAR:
 
				/* Draw the % complete with a bar and a text */
 
				DrawFrameRect(r.left, r.top, r.right, r.bottom, COLOUR_GREY, FR_BORDERONLY);
 
				DrawFrameRect(r.left + 1, r.top + 1, (int)((r.right - r.left - 2) * _tp.percent / 100) + r.left + 1, r.bottom - 1, COLOUR_MAUVE, FR_NONE);
 
				SetDParam(0, _tp.percent);
 
				DrawString(r.left, r.right, r.top + 5, STR_GENERATION_PROGRESS, TC_FROMSTRING, SA_CENTER);
 
				break;
 

	
 
			case GPWW_PROGRESS_TEXT:
 
				/* Tell which class we are generating */
 
				DrawString(r.left, r.right, r.top, _tp.cls, TC_FROMSTRING, SA_CENTER);
 

	
 
				/* And say where we are in that class */
 
				SetDParam(0, _tp.current);
 
				SetDParam(1, _tp.total);
 
				DrawString(r.left, r.right, r.top + FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL, STR_GENERATION_PROGRESS_NUM, TC_FROMSTRING, SA_CENTER);
 
		}
 
	}
 
};
 

	
 
/**
 
 * Initializes the progress counters to the starting point.
 
 */
 
void PrepareGenerateWorldProgress()
 
{
 
	_tp.cls   = STR_GENERATION_WORLD_GENERATION;
 
	_tp.current = 0;
 
	_tp.total   = 0;
 
	_tp.percent = 0;
 
	_tp.timer   = 0; // Forces to paint the progress window immediatelly
 
}
 

	
 
/**
 
 * Show the window where a user can follow the process of the map generation.
 
 */
 
void ShowGenerateWorldProgress()
 
{
 
	if (BringWindowToFrontById(WC_GENERATE_PROGRESS_WINDOW, 0)) return;
 
	new GenerateProgressWindow();
 
}
 

	
 
static void _SetGeneratingWorldProgress(gwp_class cls, uint progress, uint total)
 
{
 
	static const int percent_table[GWP_CLASS_COUNT + 1] = {0, 5, 15, 20, 40, 60, 65, 80, 85, 99, 100 };
 

	
 
	assert(cls < GWP_CLASS_COUNT);
 

	
 
	/* Do not run this function if we aren't in a thread */
 
	if (!IsGenerateWorldThreaded() && !_network_dedicated) return;
 

	
 
	if (IsGeneratingWorldAborted()) HandleGeneratingWorldAbortion();
 

	
 
	if (total == 0) {
 
		assert(_tp.cls == _generation_class_table[cls]);
 
		_tp.current += progress;
 
	} else {
 
		_tp.cls   = _generation_class_table[cls];
 
		_tp.current = progress;
 
		_tp.total   = total;
 
		_tp.percent = percent_table[cls];
 
	}
 

	
 
	/* Don't update the screen too often. So update it once in every once in a while... */
 
	if (!_network_dedicated && _tp.timer != 0 && _realtime_tick - _tp.timer < GENWORLD_REDRAW_TIMEOUT) return;
 

	
 
	/* Percentage is about the number of completed tasks, so 'current - 1' */
 
	_tp.percent = percent_table[cls] + (percent_table[cls + 1] - percent_table[cls]) * (_tp.current == 0 ? 0 : _tp.current - 1) / _tp.total;
 

	
 
	if (_network_dedicated) {
 
		static uint last_percent = 0;
 

	
 
		/* Never display 0% */
 
		if (_tp.percent == 0) return;
 
		/* Reset if percent is lower than the last recorded */
 
		if (_tp.percent < last_percent) last_percent = 0;
 
		/* Display every 5%, but 6% is also very valid.. just not smaller steps than 5% */
 
		if (_tp.percent % 5 != 0 && _tp.percent <= last_percent + 5) return;
 
		/* Never show steps smaller than 2%, even if it is a mod 5% */
 
		if (_tp.percent <= last_percent + 2) return;
 

	
 
		DEBUG(net, 1, "Map generation percentage complete: %d", _tp.percent);
 
		last_percent = _tp.percent;
 

	
 
		/* Don't continue as dedicated never has a thread running */
 
		return;
 
	}
 

	
 
	SetWindowDirty(WC_GENERATE_PROGRESS_WINDOW, 0);
 
	MarkWholeScreenDirty();
 

	
 
	/* Release the rights to the map generator, and acquire the rights to the
 
	 * paint thread. The 'other' thread already has the paint thread rights so
 
	 * this ensures us that we are waiting until the paint thread is done
 
	 * before we reacquire the mapgen rights */
 
	_genworld_mapgen_mutex->EndCritical();
 
	_genworld_paint_mutex->BeginCritical();
 
	_genworld_mapgen_mutex->BeginCritical();
 
	_genworld_paint_mutex->EndCritical();
 

	
 
	_tp.timer = _realtime_tick;
 
}
 

	
 
/**
 
 * Set the total of a stage of the world generation.
 
 * @param cls the current class we are in.
 
 * @param total Set the total expected items for this class.
 
 *
 
 * Warning: this function isn't clever. Don't go from class 4 to 3. Go upwards, always.
 
 *  Also, progress works if total is zero, total works if progress is zero.
 
 */
 
void SetGeneratingWorldProgress(gwp_class cls, uint total)
 
{
 
	if (total == 0) return;
 

	
 
	_SetGeneratingWorldProgress(cls, 0, total);
 
}
 

	
 
/**
 
 * Increases the current stage of the world generation with one.
 
 * @param cls the current class we are in.
 
 *
 
 * Warning: this function isn't clever. Don't go from class 4 to 3. Go upwards, always.
 
 *  Also, progress works if total is zero, total works if progress is zero.
 
 */
 
void IncreaseGeneratingWorldProgress(gwp_class cls)
 
{
 
	/* In fact the param 'class' isn't needed.. but for some security reasons, we want it around */
 
	_SetGeneratingWorldProgress(cls, 1, 0);
 
}
src/graph_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file graph_gui.cpp GUI that shows performance graphs. */
 

	
 
#include "stdafx.h"
 
#include "openttd.h"
 
#include "gui.h"
 
#include "window_gui.h"
 
#include "company_base.h"
 
#include "company_gui.h"
 
#include "economy_func.h"
 
#include "cargotype.h"
 
#include "strings_func.h"
 
#include "window_func.h"
 
#include "date_func.h"
 
#include "gfx_func.h"
 
#include "sortlist_type.h"
 

	
 
#include "table/strings.h"
 
#include "table/sprites.h"
 

	
 
/* Bitmasks of company and cargo indices that shouldn't be drawn. */
 
static uint _legend_excluded_companies;
 
static uint _legend_excluded_cargo;
 

	
 
/* Apparently these don't play well with enums. */
 
static const OverflowSafeInt64 INVALID_DATAPOINT(INT64_MAX); // Value used for a datapoint that shouldn't be drawn.
 
static const uint INVALID_DATAPOINT_POS = UINT_MAX;  // Used to determine if the previous point was drawn.
 

	
 
/****************/
 
/* GRAPH LEGEND */
 
/****************/
 

	
 
/** Widget numbers of the graph legend window. */
 
enum GraphLegendWidgetNumbers {
 
	GLW_CLOSEBOX,
 
	GLW_CAPTION,
 
	GLW_BACKGROUND,
 

	
 
	GLW_FIRST_COMPANY,
 
	GLW_LAST_COMPANY = GLW_FIRST_COMPANY + MAX_COMPANIES - 1,
 
};
 

	
 
struct GraphLegendWindow : Window {
 
	GraphLegendWindow(const WindowDesc *desc, WindowNumber window_number) : Window()
 
	{
 
		this->InitNested(desc, window_number);
 

	
 
		for (CompanyID c = COMPANY_FIRST; c < MAX_COMPANIES; c++) {
 
			if (!HasBit(_legend_excluded_companies, c)) this->LowerWidget(c + GLW_FIRST_COMPANY);
 

	
 
			this->OnInvalidateData(c);
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		if (!IsInsideMM(widget, GLW_FIRST_COMPANY, MAX_COMPANIES + GLW_FIRST_COMPANY)) return;
 

	
 
		CompanyID cid = (CompanyID)(widget - GLW_FIRST_COMPANY);
 

	
 
		if (!Company::IsValidID(cid)) return;
 

	
 
		bool rtl = _dynlang.text_dir == TD_RTL;
 

	
 
		DrawCompanyIcon(cid, rtl ? r.right - 16 : r.left + 2, r.top + 2);
 

	
 
		SetDParam(0, cid);
 
		SetDParam(1, cid);
 
		DrawString(r.left + (rtl ? WD_FRAMERECT_LEFT : 19), r.right - (rtl ? 19 : WD_FRAMERECT_RIGHT), r.top + 1, STR_COMPANY_NAME_COMPANY_NUM, HasBit(_legend_excluded_companies, cid) ? TC_BLACK : TC_WHITE);
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		if (!IsInsideMM(widget, GLW_FIRST_COMPANY, MAX_COMPANIES + GLW_FIRST_COMPANY)) return;
 

	
 
		ToggleBit(_legend_excluded_companies, widget - GLW_FIRST_COMPANY);
 
		this->ToggleWidgetLoweredState(widget);
 
		this->SetDirty();
 
		SetWindowDirty(WC_INCOME_GRAPH, 0);
 
		SetWindowDirty(WC_OPERATING_PROFIT, 0);
 
		SetWindowDirty(WC_DELIVERED_CARGO, 0);
 
		SetWindowDirty(WC_PERFORMANCE_HISTORY, 0);
 
		SetWindowDirty(WC_COMPANY_VALUE, 0);
 
	}
 

	
 
	virtual void OnInvalidateData(int data)
 
	{
 
		if (Company::IsValidID(data)) return;
 

	
 
		SetBit(_legend_excluded_companies, data);
 
		this->RaiseWidget(data + GLW_FIRST_COMPANY);
 
	}
 
};
 

	
 
/**
 
 * Construct a vertical list of buttons, one for each company.
 
 * @param biggest_index Storage for collecting the biggest index used in the returned tree.
 
 * @return Panel with company buttons.
 
 * @postcond \c *biggest_index contains the largest used index in the tree.
 
 */
 
static NWidgetBase *MakeNWidgetCompanyLines(int *biggest_index)
 
{
 
	NWidgetVertical *vert = new NWidgetVertical();
 

	
 
	for (int widnum = GLW_FIRST_COMPANY; widnum <= GLW_LAST_COMPANY; widnum++) {
 
		NWidgetBackground *panel = new NWidgetBackground(WWT_PANEL, COLOUR_GREY, widnum);
 
		panel->SetMinimalSize(246, 12);
 
		panel->SetFill(false, false);
 
		panel->SetFill(0, 0);
 
		panel->SetDataTip(0x0, STR_GRAPH_KEY_COMPANY_SELECTION_TOOLTIP);
 
		vert->Add(panel);
 
	}
 
	*biggest_index = GLW_LAST_COMPANY;
 
	return vert;
 
}
 

	
 
static const NWidgetPart _nested_graph_legend_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, GLW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, GLW_CAPTION), SetDataTip(STR_GRAPH_KEY_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, GLW_BACKGROUND),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 2),
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0),
 
			NWidgetFunction(MakeNWidgetCompanyLines),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0),
 
		EndContainer(),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _graph_legend_desc(
 
	WDP_AUTO, WDP_AUTO, 250, 196,
 
	WC_GRAPH_LEGEND, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
 
	_nested_graph_legend_widgets, lengthof(_nested_graph_legend_widgets)
 
);
 

	
 
static void ShowGraphLegend()
 
{
 
	AllocateWindowDescFront<GraphLegendWindow>(&_graph_legend_desc, 0);
 
}
 

	
 
/******************/
 
/* BASE OF GRAPHS */
 
/*****************/
 

	
 
/** Widget numbers of a base graph window. */
 
enum CompanyValueWidgets {
 
	BGW_CLOSEBOX,
 
	BGW_CAPTION,
 
	BGW_KEY_BUTTON,
 
	BGW_BACKGROUND,
 
};
 

	
 
struct BaseGraphWindow : Window {
 
protected:
 
	enum {
 
		GRAPH_MAX_DATASETS = 32,
 
		GRAPH_AXIS_LINE_COLOUR  = 215,
 
		GRAPH_NUM_MONTHS = 24, ///< Number of months displayed in the graph.
 

	
 
		GRAPH_NUM_LINES_Y = 9, ///< How many horizontal lines to draw.
 
		/* 9 is convenient as that means the distance between them is the gd_height of the graph / 8,
 
		 * which is the same
 
		 * as height >> 3. */
 
	};
 

	
 
	uint excluded_data; ///< bitmask of the datasets that shouldn't be displayed.
 
	byte num_dataset;
 
	byte num_on_x_axis;
 
	bool has_negative_values;
 
	byte num_vert_lines;
 
	static const TextColour graph_axis_label_colour = TC_BLACK; ///< colour of the graph axis label.
 

	
 
	/* The starting month and year that values are plotted against. If month is
 
	 * 0xFF, use x_values_start and x_values_increment below instead. */
 
	byte month;
 
	Year year;
 

	
 
	/* These values are used if the graph is being plotted against values
 
	 * rather than the dates specified by month and year. */
 
	uint16 x_values_start;
 
	uint16 x_values_increment;
 

	
 
	int graph_widget;
 
	StringID format_str_y_axis;
 
	byte colours[GRAPH_MAX_DATASETS];
 
	OverflowSafeInt64 cost[GRAPH_MAX_DATASETS][GRAPH_NUM_MONTHS]; ///< Stored costs for the last #GRAPH_NUM_MONTHS months
 

	
 
	int64 GetHighestValue(int initial_highest_value) const
 
	{
 
		OverflowSafeInt64 highest_value = initial_highest_value;
 

	
 
		for (int i = 0; i < this->num_dataset; i++) {
 
			if (!HasBit(this->excluded_data, i)) {
 
				for (int j = 0; j < this->num_on_x_axis; j++) {
 
					OverflowSafeInt64 datapoint = this->cost[i][j];
 

	
 
					if (datapoint != INVALID_DATAPOINT) {
 
						/* For now, if the graph has negative values the scaling is
 
						 * symmetrical about the x axis, so take the absolute value
 
						 * of each data point. */
 
						highest_value = max(highest_value, abs(datapoint));
 
					}
 
				}
 
			}
 
		}
 

	
 
		/* Round up highest_value so that it will divide cleanly into the number of
 
		 * axis labels used. */
 
		int round_val = highest_value % (GRAPH_NUM_LINES_Y - 1);
 
		if (round_val != 0) highest_value += (GRAPH_NUM_LINES_Y - 1 - round_val);
 

	
 
		return highest_value;
 
	}
 

	
 
	uint GetYLabelWidth(int64 highest_value) const
 
	{
 
		/* draw text strings on the y axis */
 
		int64 y_label = highest_value;
 
		int64 y_label_separation = highest_value / (GRAPH_NUM_LINES_Y - 1);
 

	
 
		/* If there are negative values, the graph goes from highest_value to
 
		 * -highest_value, not highest_value to 0. */
 
		if (this->has_negative_values) y_label_separation *= 2;
 

	
 
		uint max_width = 0;
 

	
 
		for (int i = 0; i < GRAPH_NUM_LINES_Y; i++) {
 
			SetDParam(0, this->format_str_y_axis);
 
			SetDParam(1, y_label);
 
			Dimension d = GetStringBoundingBox(STR_GRAPH_Y_LABEL);
 
			if (d.width > max_width) max_width = d.width;
 

	
 
			y_label -= y_label_separation;
 
		}
 

	
 
		return max_width;
 
	}
 

	
 
	/**
 
	 * Actually draw the graph.
 
	 * @param r the rectangle of the data field of the graph
 
	 */
 
	void DrawGraph(Rect &r) const
 
	{
 
		uint x, y;                       ///< Reused whenever x and y coordinates are needed.
 
		OverflowSafeInt64 highest_value; ///< Highest value to be drawn.
 
		int x_axis_offset;               ///< Distance from the top of the graph to the x axis.
 

	
 
		/* the colours and cost array of GraphDrawer must accomodate
 
		 * both values for cargo and companies. So if any are higher, quit */
 
		assert_compile(GRAPH_MAX_DATASETS >= (int)NUM_CARGO && GRAPH_MAX_DATASETS >= (int)MAX_COMPANIES);
 
		assert(this->num_vert_lines > 0);
 

	
 
		byte grid_colour = _colour_gradient[COLOUR_GREY][4];
 

	
 
		/* Rect r will be adjusted to contain just the graph, with labels being
 
		 * placed outside the area. */
 
		r.top    += 5 + GetCharacterHeight(FS_SMALL) / 2;
 
		r.bottom -= (this->month == 0xFF ? 1 : 3) * GetCharacterHeight(FS_SMALL) + 4;
 
		r.left   += 9;
 
		r.right  -= 5;
 

	
 
		/* Start of with a highest_value of twice the height of the graph in pixels.
 
		 * It's a bit arbitrary, but it makes the cargo payment graph look a little
 
		 * nicer, and prevents division by zero when calculating where the datapoint
 
		 * should be drawn. */
 
		highest_value = r.bottom - r.top + 1;
 
		if (!this->has_negative_values) highest_value *= 2;
 
		highest_value = GetHighestValue(highest_value);
 

	
 
		/* Get width for Y labels */
 
		int label_width = GetYLabelWidth(highest_value);
 

	
 
		r.left += label_width;
 

	
 
		int x_sep = (r.right - r.left) / this->num_vert_lines;
 
		int y_sep = (r.bottom - r.top) / (GRAPH_NUM_LINES_Y - 1);
 

	
 
		/* Redetermine right and bottom edge of graph to fit with the integer
 
		 * separation values. */
 
		r.right = r.left + x_sep * this->num_vert_lines;
 
		r.bottom = r.top + y_sep * (GRAPH_NUM_LINES_Y - 1);
 

	
 
		/* Where to draw the X axis */
 
		x_axis_offset = r.bottom - r.top;
 
		if (this->has_negative_values) x_axis_offset /= 2;
 

	
 
		/* Draw the vertical grid lines. */
 

	
 
		/* Don't draw the first line, as that's where the axis will be. */
 
		x = r.left + x_sep;
 

	
 
		for (int i = 0; i < this->num_vert_lines; i++) {
 
			GfxFillRect(x, r.top, x, r.bottom, grid_colour);
 
			x += x_sep;
 
		}
 

	
 
		/* Draw the horizontal grid lines. */
 
		y = r.bottom;
 

	
 
		for (int i = 0; i < GRAPH_NUM_LINES_Y; i++) {
 
			GfxFillRect(r.left - 3, y, r.left - 1, y, GRAPH_AXIS_LINE_COLOUR);
 
			GfxFillRect(r.left, y, r.right, y, grid_colour);
 
			y -= y_sep;
 
		}
 

	
 
		/* Draw the y axis. */
 
		GfxFillRect(r.left, r.top, r.left, r.bottom, GRAPH_AXIS_LINE_COLOUR);
 

	
 
		/* Draw the x axis. */
 
		y = x_axis_offset + r.top;
 
		GfxFillRect(r.left, y, r.right, y, GRAPH_AXIS_LINE_COLOUR);
 

	
 
		/* Find the largest value that will be drawn. */
 
		if (this->num_on_x_axis == 0)
 
			return;
 

	
 
		assert(this->num_on_x_axis > 0);
 
		assert(this->num_dataset > 0);
 

	
 
		/* draw text strings on the y axis */
 
		int64 y_label = highest_value;
 
		int64 y_label_separation = highest_value / (GRAPH_NUM_LINES_Y - 1);
 

	
 
		/* If there are negative values, the graph goes from highest_value to
 
		 * -highest_value, not highest_value to 0. */
 
		if (this->has_negative_values) y_label_separation *= 2;
 

	
 
		y = r.top - GetCharacterHeight(FS_SMALL) / 2;
 

	
 
		for (int i = 0; i < GRAPH_NUM_LINES_Y; i++) {
 
			SetDParam(0, this->format_str_y_axis);
 
			SetDParam(1, y_label);
 
			DrawString(r.left - label_width - 4, r.left - 4, y, STR_GRAPH_Y_LABEL, graph_axis_label_colour, SA_RIGHT);
 

	
 
			y_label -= y_label_separation;
 
			y += y_sep;
 
		}
 

	
 
		/* draw strings on the x axis */
 
		if (this->month != 0xFF) {
 
			x = r.left;
 
			y = r.bottom + 2;
 
			byte month = this->month;
 
			Year year  = this->year;
 
			for (int i = 0; i < this->num_on_x_axis; i++) {
 
				SetDParam(0, month + STR_MONTH_ABBREV_JAN);
 
				SetDParam(1, month + STR_MONTH_ABBREV_JAN + 2);
 
				SetDParam(2, year);
 
				DrawStringMultiLine(x, x + x_sep, y, this->height, month == 0 ? STR_GRAPH_X_LABEL_MONTH_YEAR : STR_GRAPH_X_LABEL_MONTH, graph_axis_label_colour);
 

	
 
				month += 3;
 
				if (month >= 12) {
 
					month = 0;
 
					year++;
 
				}
 
				x += x_sep;
 
			}
 
		} else {
 
			/* Draw the label under the data point rather than on the grid line. */
 
			x = r.left;
 
			y = r.bottom + 2;
 
			uint16 label = this->x_values_start;
 

	
 
			for (int i = 0; i < this->num_on_x_axis; i++) {
 
				SetDParam(0, label);
 
				DrawString(x + 1, x + x_sep - 1, y, STR_GRAPH_Y_LABEL_NUMBER, graph_axis_label_colour, SA_CENTER);
 

	
 
				label += this->x_values_increment;
 
				x += x_sep;
 
			}
 
		}
 

	
 
		/* draw lines and dots */
 
		for (int i = 0; i < this->num_dataset; i++) {
 
			if (!HasBit(this->excluded_data, i)) {
 
				/* Centre the dot between the grid lines. */
 
				x = r.left + (x_sep / 2);
 

	
 
				byte colour  = this->colours[i];
 
				uint prev_x = INVALID_DATAPOINT_POS;
 
				uint prev_y = INVALID_DATAPOINT_POS;
 

	
 
				for (int j = 0; j < this->num_on_x_axis; j++) {
 
					OverflowSafeInt64 datapoint = this->cost[i][j];
 

	
 
					if (datapoint != INVALID_DATAPOINT) {
 
						/*
 
						 * Check whether we need to reduce the 'accuracy' of the
 
						 * datapoint value and the highest value to splut overflows.
 
						 * And when 'drawing' 'one million' or 'one million and one'
 
						 * there is no significant difference, so the least
 
						 * significant bits can just be removed.
 
						 *
 
						 * If there are more bits needed than would fit in a 32 bits
 
						 * integer, so at about 31 bits because of the sign bit, the
 
						 * least significant bits are removed.
 
						 */
 
						int mult_range = FindLastBit(x_axis_offset) + FindLastBit(abs(datapoint));
 
						int reduce_range = max(mult_range - 31, 0);
 

	
 
						/* Handle negative values differently (don't shift sign) */
 
						if (datapoint < 0) {
 
							datapoint = -(abs(datapoint) >> reduce_range);
 
						} else {
 
							datapoint >>= reduce_range;
 
						}
 

	
 
						y = r.top + x_axis_offset - (x_axis_offset * datapoint) / (highest_value >> reduce_range);
 

	
 
						/* Draw the point. */
 
						GfxFillRect(x - 1, y - 1, x + 1, y + 1, colour);
 

	
 
						/* Draw the line connected to the previous point. */
 
						if (prev_x != INVALID_DATAPOINT_POS) GfxDrawLine(prev_x, prev_y, x, y, colour);
 

	
 
						prev_x = x;
 
						prev_y = y;
 
					} else {
 
						prev_x = INVALID_DATAPOINT_POS;
 
						prev_y = INVALID_DATAPOINT_POS;
 
					}
 

	
 
					x += x_sep;
 
				}
 
			}
 
		}
 
	}
 

	
 

	
 
	BaseGraphWindow(int widget, bool has_negative_values, StringID format_str_y_axis) :
 
			Window(), has_negative_values(has_negative_values),
 
			format_str_y_axis(format_str_y_axis)
 
	{
 
		SetWindowDirty(WC_GRAPH_LEGEND, 0);
 
		this->num_vert_lines = 24;
 
		this->graph_widget = widget;
 
	}
 

	
 
	void InitializeWindow(const WindowDesc *desc, WindowNumber number)
 
	{
 
		this->InitNested(desc, number);
 

	
 
		/* Initialise the dataset */
 
		this->UpdateStatistics(true);
 
	}
 

	
 
public:
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 

	
 
		NWidgetBase *nwid = this->GetWidget<NWidgetBase>(this->graph_widget);
 
		Rect r;
 
		r.left = nwid->pos_x;
 
		r.right = nwid->pos_x + nwid->current_x - 1;
 
		r.top = nwid->pos_y;
 
		r.bottom = nwid->pos_y + nwid->current_y - 1;
 

	
 
		this->DrawGraph(r);
 
	}
 

	
 
	virtual OverflowSafeInt64 GetGraphData(const Company *c, int j)
 
	{
 
		return INVALID_DATAPOINT;
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		/* Clicked on legend? */
 
		if (widget == BGW_KEY_BUTTON) ShowGraphLegend();
 
	}
 

	
 
	virtual void OnTick()
 
	{
 
		this->UpdateStatistics(false);
 
	}
 

	
 
	/**
 
	 * Update the statistics.
 
	 * @param initialize Initialize the data structure.
 
	 */
 
	void UpdateStatistics(bool initialize)
 
	{
 
		uint excluded_companies = _legend_excluded_companies;
 

	
 
		/* Exclude the companies which aren't valid */
 
		for (CompanyID c = COMPANY_FIRST; c < MAX_COMPANIES; c++) {
 
			if (!Company::IsValidID(c)) SetBit(excluded_companies, c);
 
		}
 

	
 
		byte nums = 0;
 
		const Company *c;
 
		FOR_ALL_COMPANIES(c) {
 
			nums = min(this->num_vert_lines, max(nums, c->num_valid_stat_ent));
 
		}
 

	
 
		int mo = (_cur_month / 3 - nums) * 3;
 
		int yr = _cur_year;
 
		while (mo < 0) {
 
			yr--;
 
			mo += 12;
 
		}
 

	
 
		if (!initialize && this->excluded_data == excluded_companies && this->num_on_x_axis == nums &&
 
				this->year == yr && this->month == mo) {
 
			/* There's no reason to get new stats */
 
			return;
 
		}
 

	
 
		this->excluded_data = excluded_companies;
 
		this->num_on_x_axis = nums;
 
		this->year = yr;
 
		this->month = mo;
 

	
 
		int numd = 0;
 
		for (CompanyID k = COMPANY_FIRST; k < MAX_COMPANIES; k++) {
 
			c = Company::GetIfValid(k);
 
			if (c != NULL) {
 
				this->colours[numd] = _colour_gradient[c->colour][6];
 
				for (int j = this->num_on_x_axis, i = 0; --j >= 0;) {
 
					this->cost[numd][i] = (j >= c->num_valid_stat_ent) ? INVALID_DATAPOINT : GetGraphData(c, j);
 
					i++;
 
				}
 
			}
 
			numd++;
 
		}
 

	
 
		this->num_dataset = numd;
 
	}
 
};
 

	
 

	
 
/********************/
 
/* OPERATING PROFIT */
 
/********************/
 

	
 
struct OperatingProfitGraphWindow : BaseGraphWindow {
 
	OperatingProfitGraphWindow(const WindowDesc *desc, WindowNumber window_number) :
 
			BaseGraphWindow(BGW_BACKGROUND, true, STR_JUST_CURRCOMPACT)
 
	{
 
		this->InitializeWindow(desc, window_number);
 
	}
 

	
 
	virtual OverflowSafeInt64 GetGraphData(const Company *c, int j)
 
	{
 
		return c->old_economy[j].income + c->old_economy[j].expenses;
 
	}
 
};
 

	
 
static const NWidgetPart _nested_operating_profit_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, BGW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, BGW_CAPTION), SetDataTip(STR_GRAPH_OPERATING_PROFIT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, BGW_KEY_BUTTON), SetMinimalSize(50, 0), SetMinimalTextLines(1, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM + 2), SetDataTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, BGW_BACKGROUND), SetMinimalSize(576, 160), EndContainer(),
 
};
 

	
 
static const WindowDesc _operating_profit_desc(
 
	WDP_AUTO, WDP_AUTO, 576, 174,
 
	WC_OPERATING_PROFIT, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
 
	_nested_operating_profit_widgets, lengthof(_nested_operating_profit_widgets)
 
);
 

	
 

	
 
void ShowOperatingProfitGraph()
 
{
 
	AllocateWindowDescFront<OperatingProfitGraphWindow>(&_operating_profit_desc, 0);
 
}
 

	
 

	
 
/****************/
 
/* INCOME GRAPH */
 
/****************/
 

	
 
struct IncomeGraphWindow : BaseGraphWindow {
 
	IncomeGraphWindow(const WindowDesc *desc, WindowNumber window_number) :
 
			BaseGraphWindow(BGW_BACKGROUND, false, STR_JUST_CURRCOMPACT)
 
	{
 
		this->InitializeWindow(desc, window_number);
 
	}
 

	
 
	virtual OverflowSafeInt64 GetGraphData(const Company *c, int j)
 
	{
 
		return c->old_economy[j].income;
 
	}
 
};
 

	
 
static const NWidgetPart _nested_income_graph_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, BGW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, BGW_CAPTION), SetDataTip(STR_GRAPH_INCOME_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, BGW_KEY_BUTTON), SetMinimalSize(50, 0), SetMinimalTextLines(1, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM + 2), SetDataTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, BGW_BACKGROUND), SetMinimalSize(576, 128), EndContainer(),
 
};
 

	
 

	
 
static const WindowDesc _income_graph_desc(
 
	WDP_AUTO, WDP_AUTO, 576, 142,
 
	WC_INCOME_GRAPH, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
 
	_nested_income_graph_widgets, lengthof(_nested_income_graph_widgets)
 
);
 

	
 
void ShowIncomeGraph()
 
{
 
	AllocateWindowDescFront<IncomeGraphWindow>(&_income_graph_desc, 0);
 
}
 

	
 
/*******************/
 
/* DELIVERED CARGO */
 
/*******************/
 

	
 
struct DeliveredCargoGraphWindow : BaseGraphWindow {
 
	DeliveredCargoGraphWindow(const WindowDesc *desc, WindowNumber window_number) :
 
			BaseGraphWindow(BGW_BACKGROUND, false, STR_JUST_COMMA)
 
	{
 
		this->InitializeWindow(desc, window_number);
 
	}
 

	
 
	virtual OverflowSafeInt64 GetGraphData(const Company *c, int j)
 
	{
 
		return c->old_economy[j].delivered_cargo;
 
	}
 
};
 

	
 
static const NWidgetPart _nested_delivered_cargo_graph_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, BGW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, BGW_CAPTION), SetDataTip(STR_GRAPH_CARGO_DELIVERED_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, BGW_KEY_BUTTON), SetMinimalSize(50, 0), SetMinimalTextLines(1, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM + 2), SetDataTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, BGW_BACKGROUND), SetMinimalSize(576, 128), EndContainer(),
 
};
 

	
 
static const WindowDesc _delivered_cargo_graph_desc(
 
	WDP_AUTO, WDP_AUTO, 576, 142,
 
	WC_DELIVERED_CARGO, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
 
	_nested_delivered_cargo_graph_widgets, lengthof(_nested_delivered_cargo_graph_widgets)
 
);
 

	
 
void ShowDeliveredCargoGraph()
 
{
 
	AllocateWindowDescFront<DeliveredCargoGraphWindow>(&_delivered_cargo_graph_desc, 0);
 
}
 

	
 
/***********************/
 
/* PERFORMANCE HISTORY */
 
/***********************/
 

	
 
/** Widget numbers of the performance history window. */
 
enum PerformanceHistoryGraphWidgets {
 
	PHW_CLOSEBOX,
 
	PHW_CAPTION,
 
	PHW_KEY,
 
	PHW_DETAILED_PERFORMANCE,
 
	PHW_BACKGROUND,
 
};
 

	
 
struct PerformanceHistoryGraphWindow : BaseGraphWindow {
 
	PerformanceHistoryGraphWindow(const WindowDesc *desc, WindowNumber window_number) :
 
			BaseGraphWindow(PHW_BACKGROUND, false, STR_JUST_COMMA)
 
	{
 
		this->InitializeWindow(desc, window_number);
 
	}
 

	
 
	virtual OverflowSafeInt64 GetGraphData(const Company *c, int j)
 
	{
 
		return c->old_economy[j].performance_history;
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		if (widget == PHW_DETAILED_PERFORMANCE) ShowPerformanceRatingDetail();
 
		this->BaseGraphWindow::OnClick(pt, widget);
 
	}
 
};
 

	
 
static const NWidgetPart _nested_performance_history_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, PHW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, PHW_CAPTION), SetDataTip(STR_GRAPH_COMPANY_PERFORMANCE_RATINGS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, PHW_DETAILED_PERFORMANCE), SetMinimalSize(50, 0), SetMinimalTextLines(1, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM + 2), SetDataTip(STR_PERFORMANCE_DETAIL_KEY, STR_GRAPH_PERFORMANCE_DETAIL_TOOLTIP),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, PHW_KEY), SetMinimalSize(50, 0), SetMinimalTextLines(1, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM + 2), SetDataTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, PHW_BACKGROUND), SetMinimalSize(576, 224), EndContainer(),
 
};
 

	
 
static const WindowDesc _performance_history_desc(
 
	WDP_AUTO, WDP_AUTO, 576, 238,
 
	WC_PERFORMANCE_HISTORY, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
 
	_nested_performance_history_widgets, lengthof(_nested_performance_history_widgets)
 
);
 

	
 
void ShowPerformanceHistoryGraph()
 
{
 
	AllocateWindowDescFront<PerformanceHistoryGraphWindow>(&_performance_history_desc, 0);
 
}
 

	
 
/*****************/
 
/* COMPANY VALUE */
 
/*****************/
 

	
 
struct CompanyValueGraphWindow : BaseGraphWindow {
 
	CompanyValueGraphWindow(const WindowDesc *desc, WindowNumber window_number) :
 
			BaseGraphWindow(BGW_BACKGROUND, false, STR_JUST_CURRCOMPACT)
 
	{
 
		this->InitializeWindow(desc, window_number);
 
	}
 

	
 
	virtual OverflowSafeInt64 GetGraphData(const Company *c, int j)
 
	{
 
		return c->old_economy[j].company_value;
 
	}
 
};
 

	
 
static const NWidgetPart _nested_company_value_graph_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, BGW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, BGW_CAPTION), SetDataTip(STR_GRAPH_COMPANY_VALUES_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, BGW_KEY_BUTTON), SetMinimalSize(50, 0), SetMinimalTextLines(1, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM + 2), SetDataTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, BGW_BACKGROUND), SetMinimalSize(576, 224), EndContainer(),
 
};
 

	
 
static const WindowDesc _company_value_graph_desc(
 
	WDP_AUTO, WDP_AUTO, 576, 238,
 
	WC_COMPANY_VALUE, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
 
	_nested_company_value_graph_widgets, lengthof(_nested_company_value_graph_widgets)
 
);
 

	
 
void ShowCompanyValueGraph()
 
{
 
	AllocateWindowDescFront<CompanyValueGraphWindow>(&_company_value_graph_desc, 0);
 
}
 

	
 
/*****************/
 
/* PAYMENT RATES */
 
/*****************/
 

	
 
/** Widget numbers of the cargo payment rates. */
 
enum CargoPaymentRatesWidgets {
 
	CPW_CLOSEBOX,
 
	CPW_CAPTION,
 
	CPW_BACKGROUND,
 
	CPW_HEADER,
 
	CPW_GRAPH,
 
	CPW_FOOTER,
 
	CPW_CARGO_FIRST,
 
};
 

	
 
struct PaymentRatesGraphWindow : BaseGraphWindow {
 
	PaymentRatesGraphWindow(const WindowDesc *desc, WindowNumber window_number) :
 
			BaseGraphWindow(CPW_GRAPH, false, STR_JUST_CURRCOMPACT)
 
	{
 
		this->num_on_x_axis = 20;
 
		this->num_vert_lines = 20;
 
		this->month = 0xFF;
 
		this->x_values_start     = 10;
 
		this->x_values_increment = 10;
 

	
 
		/* Initialise the dataset */
 
		this->OnHundredthTick();
 

	
 
		this->InitNested(desc, window_number);
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		if (widget < CPW_CARGO_FIRST) return;
 

	
 
		const CargoSpec *cs = CargoSpec::Get(widget - CPW_CARGO_FIRST);
 
		SetDParam(0, cs->name);
 
		Dimension d = GetStringBoundingBox(STR_GRAPH_CARGO_PAYMENT_CARGO);
 
		d.width += 14; /* colour field */
 
		d.width += WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
 
		d.height += WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
 
		*size = maxdim(d, *size);
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		if (widget < CPW_CARGO_FIRST) return;
 

	
 
		const CargoSpec *cs = CargoSpec::Get(widget - CPW_CARGO_FIRST);
 
		bool rtl = _dynlang.text_dir == TD_RTL;
 

	
 
		/* Since the buttons have no text, no images,
 
		 * both the text and the coloured box have to be manually painted.
 
		 * clk_dif will move one pixel down and one pixel to the right
 
		 * when the button is clicked */
 
		byte clk_dif = this->IsWidgetLowered(widget) ? 1 : 0;
 
		int x = r.left + WD_FRAMERECT_LEFT;
 
		int y = r.top;
 

	
 
		int rect_x = clk_dif + (rtl ? r.right - 12 : r.left + WD_FRAMERECT_LEFT);
 

	
 
		GfxFillRect(rect_x, y + clk_dif, rect_x + 8, y + 5 + clk_dif, 0);
 
		GfxFillRect(rect_x + 1, y + 1 + clk_dif, rect_x + 7, y + 4 + clk_dif, cs->legend_colour);
 
		SetDParam(0, cs->name);
 
		DrawString(rtl ? r.left : x + 14 + clk_dif, (rtl ? r.right - 14 + clk_dif : r.right), y + clk_dif, STR_GRAPH_CARGO_PAYMENT_CARGO);
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		if (widget >= CPW_CARGO_FIRST) {
 
			int i = 0;
 
			const CargoSpec *cs;
 
			FOR_ALL_CARGOSPECS(cs) {
 
				if (cs->Index() + CPW_CARGO_FIRST == widget) break;
 
				i++;
 
			}
 

	
 
			ToggleBit(_legend_excluded_cargo, i);
 
			this->ToggleWidgetLoweredState(widget);
 
			this->excluded_data = _legend_excluded_cargo;
 
			this->SetDirty();
 
		}
 
	}
 

	
 
	virtual void OnTick()
 
	{
 
		/* Override default OnTick */
 
	}
 

	
 
	virtual void OnHundredthTick()
 
	{
 
		this->excluded_data = _legend_excluded_cargo;
 

	
 
		int i = 0;
 
		const CargoSpec *cs;
 
		FOR_ALL_CARGOSPECS(cs) {
 
			this->colours[i] = cs->legend_colour;
 
			for (uint j = 0; j != 20; j++) {
 
				this->cost[i][j] = GetTransportedGoodsIncome(10, 20, j * 4 + 4, cs->Index());
 
			}
 

	
 
			i++;
 
		}
 
		this->num_dataset = i;
 
	}
 
};
 

	
 
/** Construct the row containing the digit keys. */
 
static NWidgetBase *MakeCargoButtons(int *biggest_index)
 
{
 
	NWidgetVertical *ver = new NWidgetVertical;
 

	
 
	const CargoSpec *cs;
 
	FOR_ALL_CARGOSPECS(cs) {
 
		*biggest_index = CPW_CARGO_FIRST + cs->Index();
 
		NWidgetBackground *leaf = new NWidgetBackground(WWT_PANEL, COLOUR_ORANGE, *biggest_index, NULL);
 
		leaf->tool_tip = STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO;
 
		leaf->SetFill(true, false);
 
		leaf->SetFill(1, 0);
 
		leaf->SetLowered(true);
 
		ver->Add(leaf);
 
	}
 
	return ver;
 
}
 

	
 

	
 
static const NWidgetPart _nested_cargo_payment_rates_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, CPW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, CPW_CAPTION), SetDataTip(STR_GRAPH_CARGO_PAYMENT_RATES_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, CPW_BACKGROUND), SetMinimalSize(568, 128), SetResize(0, 1),
 
		NWidget(NWID_VERTICAL),
 
			NWidget(NWID_HORIZONTAL),
 
				NWidget(NWID_SPACER), SetFill(true, false),
 
				NWidget(NWID_SPACER), SetFill(1, 0),
 
				NWidget(WWT_TEXT, COLOUR_GREY, CPW_HEADER), SetMinimalSize(0, 6), SetPadding(2, 0, 2, 0), SetDataTip(STR_GRAPH_CARGO_PAYMENT_RATES_TITLE, STR_NULL),
 
				NWidget(NWID_SPACER), SetFill(true, false),
 
				NWidget(NWID_SPACER), SetFill(1, 0),
 
			EndContainer(),
 
			NWidget(NWID_HORIZONTAL),
 
				NWidget(WWT_EMPTY, COLOUR_GREY, CPW_GRAPH), SetMinimalSize(495, 0), SetFill(true, true),
 
				NWidget(WWT_EMPTY, COLOUR_GREY, CPW_GRAPH), SetMinimalSize(495, 0), SetFill(1, 1),
 
				NWidget(NWID_VERTICAL),
 
					NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(false, false),
 
					NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 0),
 
						NWidgetFunction(MakeCargoButtons),
 
					NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(false, true),
 
					NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
 
				EndContainer(),
 
				NWidget(NWID_SPACER), SetMinimalSize(5, 0), SetFill(false, true),
 
				NWidget(NWID_SPACER), SetMinimalSize(5, 0), SetFill(0, 1),
 
			EndContainer(),
 
			NWidget(NWID_HORIZONTAL),
 
				NWidget(NWID_SPACER), SetFill(true, false),
 
				NWidget(NWID_SPACER), SetFill(1, 0),
 
				NWidget(WWT_TEXT, COLOUR_GREY, CPW_FOOTER), SetMinimalSize(0, 6), SetPadding(2, 0, 2, 0), SetDataTip(STR_GRAPH_CARGO_PAYMENT_RATES_X_LABEL, STR_NULL),
 
				NWidget(NWID_SPACER), SetFill(true, false),
 
				NWidget(NWID_SPACER), SetFill(1, 0),
 
			EndContainer(),
 
		EndContainer(),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _cargo_payment_rates_desc(
 
	WDP_AUTO, WDP_AUTO, 568, 46,
 
	WC_PAYMENT_RATES, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
 
	_nested_cargo_payment_rates_widgets, lengthof(_nested_cargo_payment_rates_widgets)
 
);
 

	
 

	
 
void ShowCargoPaymentRates()
 
{
 
	AllocateWindowDescFront<PaymentRatesGraphWindow>(&_cargo_payment_rates_desc, 0);
 
}
 

	
 
/************************/
 
/* COMPANY LEAGUE TABLE */
 
/************************/
 

	
 
/** Widget numbers for the company league window. */
 
enum CompanyLeagueWidgets {
 
	CLW_CLOSEBOX,
 
	CLW_CAPTION,
 
	CLW_STICKYBOX,
 
	CLW_BACKGROUND,
 
};
 

	
 
static const StringID _performance_titles[] = {
 
	STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_ENGINEER,
 
	STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_ENGINEER,
 
	STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_TRAFFIC_MANAGER,
 
	STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_TRAFFIC_MANAGER,
 
	STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_TRANSPORT_COORDINATOR,
 
	STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_TRANSPORT_COORDINATOR,
 
	STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_ROUTE_SUPERVISOR,
 
	STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_ROUTE_SUPERVISOR,
 
	STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_DIRECTOR,
 
	STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_DIRECTOR,
 
	STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_CHIEF_EXECUTIVE,
 
	STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_CHIEF_EXECUTIVE,
 
	STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_CHAIRMAN,
 
	STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_CHAIRMAN,
 
	STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_PRESIDENT,
 
	STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_TYCOON,
 
};
 

	
 
static inline StringID GetPerformanceTitleFromValue(uint value)
 
{
 
	return _performance_titles[minu(value, 1000) >> 6];
 
}
 

	
 
class CompanyLeagueWindow : public Window {
 
private:
 
	GUIList<const Company*> companies;
 

	
 
	/**
 
	 * (Re)Build the company league list
 
	 */
 
	void BuildCompanyList()
 
	{
 
		if (!this->companies.NeedRebuild()) return;
 

	
 
		this->companies.Clear();
 

	
 
		const Company *c;
 
		FOR_ALL_COMPANIES(c) {
 
			*this->companies.Append() = c;
 
		}
 

	
 
		this->companies.Compact();
 
		this->companies.RebuildDone();
 
	}
 

	
 
	/** Sort the company league by performance history */
 
	static int CDECL PerformanceSorter(const Company * const *c1, const Company * const *c2)
 
	{
 
		return (*c2)->old_economy[1].performance_history - (*c1)->old_economy[1].performance_history;
 
	}
 

	
 
public:
 
	CompanyLeagueWindow(const WindowDesc *desc, WindowNumber window_number) : Window()
 
	{
 
		this->InitNested(desc, window_number);
 
		this->companies.ForceRebuild();
 
		this->companies.NeedResort();
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->BuildCompanyList();
 
		this->companies.Sort(&PerformanceSorter);
 

	
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		if (widget != CLW_BACKGROUND) return;
 

	
 
		uint y = r.top + WD_FRAMERECT_TOP;
 
		for (uint i = 0; i != this->companies.Length(); i++) {
 
			const Company *c = this->companies[i];
 
			SetDParam(0, i + STR_ORDINAL_NUMBER_1ST);
 
			SetDParam(1, c->index);
 
			SetDParam(2, c->index);
 
			SetDParam(3, GetPerformanceTitleFromValue(c->old_economy[1].performance_history));
 

	
 
			DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, i == 0 ? STR_COMPANY_LEAGUE_FIRST : STR_COMPANY_LEAGUE_OTHER);
 
			DrawCompanyIcon(c->index, _dynlang.text_dir == TD_RTL ? r.right - 43 : r.left + 27, y + 1);
 
			y += FONT_HEIGHT_NORMAL;
 
		}
 
	}
 

	
 
	virtual void OnTick()
 
	{
 
		if (this->companies.NeedResort()) {
 
			this->SetDirty();
 
		}
 
	}
 

	
 
	virtual void OnInvalidateData(int data)
 
	{
 
		if (data == 0) {
 
			this->companies.ForceRebuild();
 
		} else {
 
			this->companies.ForceResort();
 
		}
 
	}
 
};
 

	
 
static const NWidgetPart _nested_company_league_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, CLW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, CLW_CAPTION), SetDataTip(STR_COMPANY_LEAGUE_TABLE_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_STICKYBOX, COLOUR_GREY, CLW_STICKYBOX),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, CLW_BACKGROUND), SetMinimalSize(400, 0), SetMinimalTextLines(15, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM),
 
};
 

	
 
static const WindowDesc _company_league_desc(
 
	WDP_AUTO, WDP_AUTO, 400, 167,
 
	WC_COMPANY_LEAGUE, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_STICKY_BUTTON,
 
	_nested_company_league_widgets, lengthof(_nested_company_league_widgets)
 
);
 

	
 
void ShowCompanyLeagueTable()
 
{
 
	AllocateWindowDescFront<CompanyLeagueWindow>(&_company_league_desc, 0);
 
}
 

	
 
/*****************************/
 
/* PERFORMANCE RATING DETAIL */
 
/*****************************/
 

	
 
/** Widget numbers of the performance rating details window. */
 
enum PerformanceRatingDetailsWidgets {
 
	PRW_CLOSEBOX,
 
	PRW_CAPTION,
 
	PRW_BACKGROUND,
 

	
 
	PRW_SCORE_FIRST,
 
	PRW_SCORE_LAST = PRW_SCORE_FIRST + (SCORE_END - SCORE_BEGIN) - 1,
 

	
 
	PRW_COMPANY_FIRST,
 
	PRW_COMPANY_LAST  = PRW_COMPANY_FIRST + MAX_COMPANIES - 1,
 
};
 

	
 
struct PerformanceRatingDetailWindow : Window {
 
	static CompanyID company;
 
	int timeout;
 

	
 
	PerformanceRatingDetailWindow(const WindowDesc *desc, WindowNumber window_number) : Window()
 
	{
 
		this->UpdateCompanyStats();
 

	
 
		this->InitNested(desc, window_number);
 
		this->OnInvalidateData(INVALID_COMPANY);
 
	}
 

	
 
	void UpdateCompanyStats()
 
	{
 
		/* Update all company stats with the current data
 
		 * (this is because _score_info is not saved to a savegame) */
 
		Company *c;
 
		FOR_ALL_COMPANIES(c) {
 
			UpdateCompanyRatingAndValue(c, false);
 
		}
 

	
 
		this->timeout = DAY_TICKS * 5;
 
	}
 

	
 
	uint score_info_left;
 
	uint score_info_right;
 
	uint bar_left;
 
	uint bar_right;
 
	uint bar_width;
 
	uint bar_height;
 
	uint score_detail_left;
 
	uint score_detail_right;
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		switch (widget) {
 
			case PRW_SCORE_FIRST:
 
				this->bar_height = FONT_HEIGHT_NORMAL + 4;
 
				size->height = this->bar_height + 2 * WD_MATRIX_TOP;
 

	
 
				uint score_info_width = 0;
 
				for (uint i = SCORE_BEGIN; i < SCORE_END; i++) {
 
					score_info_width = max(score_info_width, GetStringBoundingBox(STR_PERFORMANCE_DETAIL_VEHICLES + i).width);
 
				}
 
				SetDParam(0, 1000);
 
				score_info_width += GetStringBoundingBox(STR_BLACK_COMMA).width + WD_FRAMERECT_LEFT;
 

	
 
				SetDParam(0, 100);
 
				this->bar_width = GetStringBoundingBox(STR_PERFORMANCE_DETAIL_PERCENT).width + 20; // Wide bars!
 

	
 
				/* At this number we are roughly at the max; it can become wider,
 
				 * but then you need at 1000 times more money. At that time you're
 
				 * not that interested anymore in the last few digits anyway. */
 
				uint max = 999999999; // nine 9s
 
				SetDParam(0, max);
 
				SetDParam(1, max);
 
				uint score_detail_width = GetStringBoundingBox(STR_PERFORMANCE_DETAIL_AMOUNT_CURRENCY).width;
 

	
 
				size->width = 7 + score_info_width + 5 + this->bar_width + 5 + score_detail_width + 7;
 
				uint left  = 7;
 
				uint right = size->width - 7;
 

	
 
				bool rtl = _dynlang.text_dir == TD_RTL;
 
				this->score_info_left  = rtl ? right - score_info_width : left;
 
				this->score_info_right = rtl ? right : left + score_info_width;
 

	
 
				this->score_detail_left  = rtl ? left : right - score_detail_width;
 
				this->score_detail_right = rtl ? left + score_detail_width : right;
 

	
 
				this->bar_left  = left + (rtl ? score_detail_width : score_info_width) + 5;
 
				this->bar_right = this->bar_left + this->bar_width;
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		/* Draw standard stuff */
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		/* No need to draw when there's nothing to draw */
 
		if (this->company == INVALID_COMPANY) return;
 

	
 
		if (IsInsideMM(widget, PRW_COMPANY_FIRST, PRW_COMPANY_LAST + 1)) {
 
			if (this->IsWidgetDisabled(widget)) return;
 
			CompanyID cid = (CompanyID)(widget - PRW_COMPANY_FIRST);
 
			int offset = (cid == this->company) ? 1 : 0;
 
			Dimension sprite_size = GetSpriteSize(SPR_COMPANY_ICON);
 
			DrawCompanyIcon(cid, (r.left + r.right - sprite_size.width) / 2 + offset, (r.top + r.bottom - sprite_size.height) / 2 + offset);
 
			return;
 
		}
 

	
 
		if (!IsInsideMM(widget, PRW_SCORE_FIRST, PRW_SCORE_LAST + 1)) return;
 

	
 
		ScoreID score_type = (ScoreID)(widget - PRW_SCORE_FIRST);
 

	
 
			/* The colours used to show how the progress is going */
 
		int colour_done = _colour_gradient[COLOUR_GREEN][4];
 
		int colour_notdone = _colour_gradient[COLOUR_RED][4];
 

	
 
		/* Draw all the score parts */
 
		int val    = _score_part[company][score_type];
 
		int needed = _score_info[score_type].needed;
 
		int score  = _score_info[score_type].score;
 

	
 
		/* SCORE_TOTAL has his own rules ;) */
 
		if (score_type == SCORE_TOTAL) {
 
			for (ScoreID i = SCORE_BEGIN; i < SCORE_END; i++) score += _score_info[i].score;
 
			needed = SCORE_MAX;
 
		}
 

	
 
		uint bar_top  = r.top + WD_MATRIX_TOP;
 
		uint text_top = bar_top + 2;
 

	
 
		DrawString(this->score_info_left, this->score_info_right, text_top, STR_PERFORMANCE_DETAIL_VEHICLES + score_type);
 

	
 
		/* Draw the score */
 
		SetDParam(0, score);
 
		DrawString(this->score_info_left, this->score_info_right, text_top, STR_BLACK_COMMA, TC_FROMSTRING, SA_RIGHT);
 

	
 
		/* Calculate the %-bar */
 
		uint x = Clamp(val, 0, needed) * this->bar_width / needed;
 
		bool rtl = _dynlang.text_dir == TD_RTL;
 
		if (rtl) {
 
			x = this->bar_right - x;
 
		} else {
 
			x = this->bar_left + x;
 
		}
 

	
 
		/* Draw the bar */
 
		if (x != this->bar_left)  GfxFillRect(this->bar_left, bar_top, x, bar_top + this->bar_height, rtl ? colour_notdone : colour_done);
 
		if (x != this->bar_right) GfxFillRect(x, bar_top, this->bar_right, bar_top + this->bar_height, rtl ? colour_done : colour_notdone);
 

	
 
		/* Draw it */
 
		SetDParam(0, Clamp(val, 0, needed) * 100 / needed);
 
		DrawString(this->bar_left, this->bar_right, text_top, STR_PERFORMANCE_DETAIL_PERCENT, TC_FROMSTRING, SA_CENTER);
 

	
 
		/* SCORE_LOAN is inversed */
 
		if (score_type == SCORE_LOAN) val = needed - val;
 

	
 
		/* Draw the amount we have against what is needed
 
		 * For some of them it is in currency format */
 
		SetDParam(0, val);
 
		SetDParam(1, needed);
 
		switch (score_type) {
 
			case SCORE_MIN_PROFIT:
 
			case SCORE_MIN_INCOME:
 
			case SCORE_MAX_INCOME:
 
			case SCORE_MONEY:
 
			case SCORE_LOAN:
 
				DrawString(this->score_detail_left, this->score_detail_right, text_top, STR_PERFORMANCE_DETAIL_AMOUNT_CURRENCY);
 
				break;
 
			default:
 
				DrawString(this->score_detail_left, this->score_detail_right, text_top, STR_PERFORMANCE_DETAIL_AMOUNT_INT);
 
		}
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		/* Check which button is clicked */
 
		if (IsInsideMM(widget, PRW_COMPANY_FIRST, PRW_COMPANY_LAST + 1)) {
 
			/* Is it no on disable? */
 
			if (!this->IsWidgetDisabled(widget)) {
 
				this->RaiseWidget(this->company + PRW_COMPANY_FIRST);
 
				this->company = (CompanyID)(widget - PRW_COMPANY_FIRST);
 
				this->LowerWidget(this->company + PRW_COMPANY_FIRST);
 
				this->SetDirty();
 
			}
 
		}
 
	}
 

	
 
	virtual void OnTick()
 
	{
 
		if (_pause_mode != PM_UNPAUSED) return;
 

	
 
		/* Update the company score every 5 days */
 
		if (--this->timeout == 0) {
 
			this->UpdateCompanyStats();
 
			this->SetDirty();
 
		}
 
	}
 

	
 
	/**
 
	 * Invalidate the data of this window.
 
	 * @param data the company ID of the company that is going to be removed
 
	 */
 
	virtual void OnInvalidateData(int data)
 
	{
 
		/* Disable the companies who are not active */
 
		for (CompanyID i = COMPANY_FIRST; i < MAX_COMPANIES; i++) {
 
			this->SetWidgetDisabledState(i + PRW_COMPANY_FIRST, !Company::IsValidID(i));
 
		}
 

	
 
		/* Check if the currently selected company is still active. */
 
		if (this->company != INVALID_COMPANY && !Company::IsValidID(this->company)) {
 
			/* Raise the widget for the previous selection. */
 
			this->RaiseWidget(this->company + PRW_COMPANY_FIRST);
 
			this->company = INVALID_COMPANY;
 
		}
 

	
 
		if (this->company == INVALID_COMPANY) {
 
			const Company *c;
 
			FOR_ALL_COMPANIES(c) {
 
				this->company = c->index;
 
				break;
 
			}
 
		}
 

	
 
		/* Make sure the widget is lowered */
 
		this->LowerWidget(this->company + PRW_COMPANY_FIRST);
 
	}
 
};
 

	
 
CompanyID PerformanceRatingDetailWindow::company = INVALID_COMPANY;
 

	
 
/** Make a vertical list of panels for outputting score details.
 
 * @param biggest_index Storage for collecting the biggest index used in the returned tree.
 
 * @return Panel with performance details.
 
 * @postcond \c *biggest_index contains the largest used index in the tree.
 
 */
 
static NWidgetBase *MakePerformanceDetailPanels(int *biggest_index)
 
{
 
	const StringID performance_tips[] = {
 
		STR_PERFORMANCE_DETAIL_VEHICLES_TOOLTIP,
 
		STR_PERFORMANCE_DETAIL_STATIONS_TOOLTIP,
 
		STR_PERFORMANCE_DETAIL_MIN_PROFIT_TOOLTIP,
 
		STR_PERFORMANCE_DETAIL_MIN_INCOME_TOOLTIP,
 
		STR_PERFORMANCE_DETAIL_MAX_INCOME_TOOLTIP,
 
		STR_PERFORMANCE_DETAIL_DELIVERED_TOOLTIP,
 
		STR_PERFORMANCE_DETAIL_CARGO_TOOLTIP,
 
		STR_PERFORMANCE_DETAIL_MONEY_TOOLTIP,
 
		STR_PERFORMANCE_DETAIL_LOAN_TOOLTIP,
 
		STR_PERFORMANCE_DETAIL_TOTAL_TOOLTIP,
 
	};
 

	
 
	assert_compile(lengthof(performance_tips) == SCORE_END - SCORE_BEGIN);
 

	
 
	NWidgetVertical *vert = new NWidgetVertical(NC_EQUALSIZE);
 
	for (int widnum = PRW_SCORE_FIRST; widnum <= PRW_SCORE_LAST; widnum++) {
 
		NWidgetBackground *panel = new NWidgetBackground(WWT_PANEL, COLOUR_GREY, widnum);
 
		panel->SetFill(true, true);
 
		panel->SetFill(1, 1);
 
		panel->SetDataTip(0x0, performance_tips[widnum - PRW_SCORE_FIRST]);
 
		vert->Add(panel);
 
	}
 
	*biggest_index = PRW_SCORE_LAST;
 
	return vert;
 
}
 

	
 
/**
 
 * Make a number of rows with button-like graphics, for enabling/disabling each company.
 
 * @param biggest_index Storage for collecting the biggest index used in the returned tree.
 
 * @return Panel with rows of company buttons.
 
 * @postcond \c *biggest_index contains the largest used index in the tree.
 
 */
 
static NWidgetBase *MakeCompanyButtonRows(int *biggest_index)
 
{
 
	static const int MAX_LENGTH = 8; // Maximal number of company buttons in one row.
 
	NWidgetVertical *vert = NULL; // Storage for all rows.
 
	NWidgetHorizontal *hor = NULL; // Storage for buttons in one row.
 
	int hor_length = 0;
 

	
 
	Dimension sprite_size = GetSpriteSize(SPR_COMPANY_ICON);
 
	sprite_size.width  += WD_MATRIX_LEFT + WD_MATRIX_RIGHT;
 
	sprite_size.height += WD_MATRIX_TOP + WD_MATRIX_BOTTOM + 1; // 1 for the 'offset' of being pressed
 

	
 
	for (int widnum = PRW_COMPANY_FIRST; widnum <= PRW_COMPANY_LAST; widnum++) {
 
		/* Ensure there is room in 'hor' for another button. */
 
		if (hor_length == MAX_LENGTH) {
 
			if (vert == NULL) vert = new NWidgetVertical();
 
			vert->Add(hor);
 
			hor = NULL;
 
			hor_length = 0;
 
		}
 
		if (hor == NULL) {
 
			hor = new NWidgetHorizontal();
 
			hor_length = 0;
 
		}
 

	
 
		NWidgetBackground *panel = new NWidgetBackground(WWT_PANEL, COLOUR_GREY, widnum);
 
		panel->SetMinimalSize(sprite_size.width, sprite_size.height);
 
		panel->SetFill(true, false);
 
		panel->SetFill(1, 0);
 
		panel->SetDataTip(0x0, STR_GRAPH_KEY_COMPANY_SELECTION_TOOLTIP);
 
		hor->Add(panel);
 
		hor_length++;
 
	}
 
	*biggest_index = PRW_COMPANY_LAST;
 
	if (vert == NULL) return hor; // All buttons fit in a single row.
 

	
 
	if (hor_length > 0 && hor_length < MAX_LENGTH) {
 
		/* Last row is partial, add a spacer at the end to force all buttons to the left. */
 
		NWidgetSpacer *spc = new NWidgetSpacer(0, 0);
 
		spc->SetMinimalSize(sprite_size.width, sprite_size.height);
 
		spc->SetFill(true, false);
 
		spc->SetFill(1, 0);
 
		hor->Add(spc);
 
	}
 
	if (hor != NULL) vert->Add(hor);
 
	return vert;
 
}
 

	
 
static const NWidgetPart _nested_performance_rating_detail_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, PRW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, PRW_CAPTION), SetDataTip(STR_PERFORMANCE_DETAIL, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, PRW_BACKGROUND),
 
		NWidgetFunction(MakeCompanyButtonRows), SetPadding(0, 1, 1, 2),
 
	EndContainer(),
 
	NWidgetFunction(MakePerformanceDetailPanels),
 
};
 

	
 
static const WindowDesc _performance_rating_detail_desc(
 
	WDP_AUTO, WDP_AUTO, 299, 241,
 
	WC_PERFORMANCE_DETAIL, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
 
	_nested_performance_rating_detail_widgets, lengthof(_nested_performance_rating_detail_widgets)
 
);
 

	
 
void ShowPerformanceRatingDetail()
 
{
 
	AllocateWindowDescFront<PerformanceRatingDetailWindow>(&_performance_rating_detail_desc, 0);
 
}
src/group_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file group_gui.cpp GUI for the group window. */
 

	
 
#include "stdafx.h"
 
#include "openttd.h"
 
#include "window_gui.h"
 
#include "textbuf_gui.h"
 
#include "command_func.h"
 
#include "vehicle_gui.h"
 
#include "vehicle_base.h"
 
#include "group.h"
 
#include "strings_func.h"
 
#include "window_func.h"
 
#include "vehicle_func.h"
 
#include "autoreplace_gui.h"
 
#include "gfx_func.h"
 
#include "company_func.h"
 
#include "widgets/dropdown_type.h"
 
#include "widgets/dropdown_func.h"
 
#include "tilehighlight_func.h"
 
#include "vehicle_gui_base.h"
 

	
 
#include "table/strings.h"
 
#include "table/sprites.h"
 

	
 
typedef GUIList<const Group*> GUIGroupList;
 

	
 
enum GroupListWidgets {
 
	GRP_WIDGET_CLOSEBOX = 0,
 
	GRP_WIDGET_CAPTION,
 
	GRP_WIDGET_STICKY,
 
	GRP_WIDGET_SORT_BY_ORDER,
 
	GRP_WIDGET_SORT_BY_DROPDOWN,
 
	GRP_WIDGET_EMPTY_TOP_RIGHT,
 
	GRP_WIDGET_LIST_VEHICLE,
 
	GRP_WIDGET_LIST_VEHICLE_SCROLLBAR,
 
	GRP_WIDGET_EMPTY2,
 
	GRP_WIDGET_AVAILABLE_VEHICLES,
 
	GRP_WIDGET_MANAGE_VEHICLES_DROPDOWN,
 
	GRP_WIDGET_STOP_ALL,
 
	GRP_WIDGET_START_ALL,
 
	GRP_WIDGET_EMPTY_BOTTOM_RIGHT,
 
	GRP_WIDGET_RESIZE,
 

	
 
	GRP_WIDGET_EMPTY_TOP_LEFT,
 
	GRP_WIDGET_ALL_VEHICLES,
 
	GRP_WIDGET_DEFAULT_VEHICLES,
 
	GRP_WIDGET_LIST_GROUP,
 
	GRP_WIDGET_LIST_GROUP_SCROLLBAR,
 
	GRP_WIDGET_CREATE_GROUP,
 
	GRP_WIDGET_DELETE_GROUP,
 
	GRP_WIDGET_RENAME_GROUP,
 
	GRP_WIDGET_EMPTY1,
 
	GRP_WIDGET_REPLACE_PROTECTION,
 
};
 

	
 
enum GroupActionListFunction {
 
	GALF_REPLACE,
 
	GALF_SERVICE,
 
	GALF_DEPOT,
 
	GALF_ADD_SHARED,
 
	GALF_REMOVE_ALL,
 
};
 

	
 
/**
 
 * Update/redraw the group action dropdown
 
 * @param w   the window the dropdown belongs to
 
 * @param gid the currently selected group in the window
 
 */
 
static void ShowGroupActionDropdown(Window *w, GroupID gid)
 
{
 
	DropDownList *list = new DropDownList();
 

	
 
	list->push_back(new DropDownListStringItem(STR_VEHICLE_LIST_REPLACE_VEHICLES,    GALF_REPLACE, false));
 
	list->push_back(new DropDownListStringItem(STR_VEHICLE_LIST_SEND_FOR_SERVICING,  GALF_SERVICE, false));
 
	list->push_back(new DropDownListStringItem(STR_VEHICLE_LIST_SEND_TRAIN_TO_DEPOT, GALF_DEPOT,   false));
 

	
 
	if (Group::IsValidID(gid)) {
 
		list->push_back(new DropDownListStringItem(STR_GROUP_ADD_SHARED_VEHICLE,  GALF_ADD_SHARED, false));
 
		list->push_back(new DropDownListStringItem(STR_GROUP_REMOVE_ALL_VEHICLES, GALF_REMOVE_ALL, false));
 
	}
 

	
 
	ShowDropDownList(w, list, 0, GRP_WIDGET_MANAGE_VEHICLES_DROPDOWN);
 
}
 

	
 
static const NWidgetPart _nested_group_widgets[] = {
 
	NWidget(NWID_HORIZONTAL), // Window header
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, GRP_WIDGET_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, GRP_WIDGET_CAPTION),
 
		NWidget(WWT_STICKYBOX, COLOUR_GREY, GRP_WIDGET_STICKY),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		/* left part */
 
		NWidget(NWID_VERTICAL),
 
			NWidget(WWT_PANEL, COLOUR_GREY, GRP_WIDGET_EMPTY_TOP_LEFT), SetMinimalSize(200, 12), EndContainer(),
 
			NWidget(WWT_PANEL, COLOUR_GREY, GRP_WIDGET_ALL_VEHICLES), SetMinimalSize(200, 13), EndContainer(),
 
			NWidget(WWT_PANEL, COLOUR_GREY, GRP_WIDGET_DEFAULT_VEHICLES), SetMinimalSize(200, 13), EndContainer(),
 
			NWidget(NWID_HORIZONTAL),
 
				NWidget(WWT_MATRIX, COLOUR_GREY, GRP_WIDGET_LIST_GROUP), SetMinimalSize(188, 0), SetDataTip(0x701, STR_GROUPS_CLICK_ON_GROUP_FOR_TOOLTIP),
 
						SetFill(true, false), SetResize(0, 1),
 
						SetFill(1, 0), SetResize(0, 1),
 
				NWidget(WWT_SCROLL2BAR, COLOUR_GREY, GRP_WIDGET_LIST_GROUP_SCROLLBAR),
 
			EndContainer(),
 
			NWidget(NWID_HORIZONTAL),
 
				NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, GRP_WIDGET_CREATE_GROUP), SetMinimalSize(24, 25), SetFill(false, true),
 
				NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, GRP_WIDGET_CREATE_GROUP), SetMinimalSize(24, 25), SetFill(0, 1),
 
						SetDataTip(SPR_GROUP_CREATE_TRAIN, STR_GROUP_CREATE_TOOLTIP),
 
				NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, GRP_WIDGET_DELETE_GROUP), SetMinimalSize(24, 25), SetFill(false, true),
 
				NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, GRP_WIDGET_DELETE_GROUP), SetMinimalSize(24, 25), SetFill(0, 1),
 
						SetDataTip(SPR_GROUP_DELETE_TRAIN, STR_GROUP_DELETE_TOOLTIP),
 
				NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, GRP_WIDGET_RENAME_GROUP), SetMinimalSize(24, 25), SetFill(false, true),
 
				NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, GRP_WIDGET_RENAME_GROUP), SetMinimalSize(24, 25), SetFill(0, 1),
 
						SetDataTip(SPR_GROUP_RENAME_TRAIN, STR_GROUP_RENAME_TOOLTIP),
 
				NWidget(WWT_PANEL, COLOUR_GREY, GRP_WIDGET_EMPTY1), SetMinimalSize(92, 25), SetFill(true, true), EndContainer(),
 
				NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, GRP_WIDGET_REPLACE_PROTECTION), SetMinimalSize(24, 25), SetFill(false, true),
 
				NWidget(WWT_PANEL, COLOUR_GREY, GRP_WIDGET_EMPTY1), SetMinimalSize(92, 25), SetFill(1, 1), EndContainer(),
 
				NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, GRP_WIDGET_REPLACE_PROTECTION), SetMinimalSize(24, 25), SetFill(0, 1),
 
						SetDataTip(SPR_GROUP_REPLACE_OFF_TRAIN, STR_GROUP_REPLACE_PROTECTION_TOOLTIP),
 
				NWidget(WWT_PANEL, COLOUR_GREY, GRP_WIDGET_EMPTY2), SetMinimalSize(12, 25), SetFill(false, true), EndContainer(),
 
				NWidget(WWT_PANEL, COLOUR_GREY, GRP_WIDGET_EMPTY2), SetMinimalSize(12, 25), SetFill(0, 1), EndContainer(),
 
			EndContainer(),
 
		EndContainer(),
 
		/* right part */
 
		NWidget(NWID_VERTICAL),
 
			NWidget(NWID_HORIZONTAL),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, GRP_WIDGET_SORT_BY_ORDER), SetMinimalSize(81, 12), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER),
 
				NWidget(WWT_DROPDOWN, COLOUR_GREY, GRP_WIDGET_SORT_BY_DROPDOWN), SetMinimalSize(167, 12), SetDataTip(0x0, STR_TOOLTIP_SORT_CRITERIAP),
 
				NWidget(WWT_PANEL, COLOUR_GREY, GRP_WIDGET_EMPTY_TOP_RIGHT), SetMinimalSize(12, 12), SetResize(1, 0), EndContainer(),
 
			EndContainer(),
 
			NWidget(NWID_HORIZONTAL),
 
				NWidget(WWT_MATRIX, COLOUR_GREY, GRP_WIDGET_LIST_VEHICLE), SetMinimalSize(248, 0), SetDataTip(0x701, STR_NULL), SetResize(1, 1), SetFill(true, false),
 
				NWidget(WWT_MATRIX, COLOUR_GREY, GRP_WIDGET_LIST_VEHICLE), SetMinimalSize(248, 0), SetDataTip(0x701, STR_NULL), SetResize(1, 1), SetFill(1, 0),
 
				NWidget(WWT_SCROLLBAR, COLOUR_GREY, GRP_WIDGET_LIST_VEHICLE_SCROLLBAR),
 
			EndContainer(),
 
			NWidget(NWID_HORIZONTAL),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, GRP_WIDGET_AVAILABLE_VEHICLES), SetMinimalSize(106, 12), SetFill(false, true),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, GRP_WIDGET_AVAILABLE_VEHICLES), SetMinimalSize(106, 12), SetFill(0, 1),
 
						SetDataTip(0x0, STR_VEHICLE_LIST_AVAILABLE_ENGINES_TOOLTIP),
 
				NWidget(WWT_DROPDOWN, COLOUR_GREY, GRP_WIDGET_MANAGE_VEHICLES_DROPDOWN), SetMinimalSize(118, 12), SetFill(false, true),
 
				NWidget(WWT_DROPDOWN, COLOUR_GREY, GRP_WIDGET_MANAGE_VEHICLES_DROPDOWN), SetMinimalSize(118, 12), SetFill(0, 1),
 
						SetDataTip(STR_VEHICLE_LIST_MANAGE_LIST, STR_VEHICLE_LIST_MANAGE_LIST_TOOLTIP),
 
				NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, GRP_WIDGET_STOP_ALL), SetMinimalSize(12, 12), SetFill(false, true),
 
				NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, GRP_WIDGET_STOP_ALL), SetMinimalSize(12, 12), SetFill(0, 1),
 
						SetDataTip(SPR_FLAG_VEH_STOPPED, STR_VEHICLE_LIST_MASS_STOP_LIST_TOOLTIP),
 
				NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, GRP_WIDGET_START_ALL), SetMinimalSize(12, 12), SetFill(false, true),
 
				NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, GRP_WIDGET_START_ALL), SetMinimalSize(12, 12), SetFill(0, 1),
 
						SetDataTip(SPR_FLAG_VEH_RUNNING, STR_VEHICLE_LIST_MASS_START_LIST_TOOLTIP),
 
				NWidget(WWT_PANEL, COLOUR_GREY, GRP_WIDGET_EMPTY_BOTTOM_RIGHT), SetMinimalSize(0, 12), SetFill(true, true), SetResize(1, 0), EndContainer(),
 
				NWidget(WWT_PANEL, COLOUR_GREY, GRP_WIDGET_EMPTY_BOTTOM_RIGHT), SetMinimalSize(0, 12), SetFill(1, 1), SetResize(1, 0), EndContainer(),
 
				NWidget(WWT_RESIZEBOX, COLOUR_GREY, GRP_WIDGET_RESIZE),
 
			EndContainer(),
 
		EndContainer(),
 
	EndContainer(),
 
};
 

	
 
class VehicleGroupWindow : public BaseVehicleListWindow {
 
private:
 
	GroupID group_sel;     ///< Selected group
 
	VehicleID vehicle_sel; ///< Selected vehicle
 
	GroupID group_rename;  ///< Group being renamed, INVALID_GROUP if none
 
	GUIGroupList groups;   ///< List of groups
 
	uint tiny_step_height; ///< Step height for the group list
 

	
 
	/**
 
	 * (Re)Build the group list.
 
	 *
 
	 * @param owner The owner of the window
 
	 */
 
	void BuildGroupList(Owner owner)
 
	{
 
		if (!this->groups.NeedRebuild()) return;
 

	
 
		this->groups.Clear();
 

	
 
		const Group *g;
 
		FOR_ALL_GROUPS(g) {
 
			if (g->owner == owner && g->vehicle_type == this->vehicle_type) {
 
				*this->groups.Append() = g;
 
			}
 
		}
 

	
 
		this->groups.Compact();
 
		this->groups.RebuildDone();
 
	}
 

	
 
	/** Sort the groups by their name */
 
	static int CDECL GroupNameSorter(const Group * const *a, const Group * const *b)
 
	{
 
		static const Group *last_group[2] = { NULL, NULL };
 
		static char         last_name[2][64] = { "", "" };
 

	
 
		if (*a != last_group[0]) {
 
			last_group[0] = *a;
 
			SetDParam(0, (*a)->index);
 
			GetString(last_name[0], STR_GROUP_NAME, lastof(last_name[0]));
 
		}
 

	
 
		if (*b != last_group[1]) {
 
			last_group[1] = *b;
 
			SetDParam(0, (*b)->index);
 
			GetString(last_name[1], STR_GROUP_NAME, lastof(last_name[1]));
 
		}
 

	
 
		int r = strcmp(last_name[0], last_name[1]); // sort by name
 
		if (r == 0) return (*a)->index - (*b)->index;
 
		return r;
 
	}
 

	
 
public:
 
	VehicleGroupWindow(const WindowDesc *desc, WindowNumber window_number) : BaseVehicleListWindow()
 
	{
 
		this->CreateNestedTree(desc);
 

	
 
		this->vehicle_type = (VehicleType)GB(window_number, 11, 5);
 
		switch (this->vehicle_type) {
 
			default: NOT_REACHED();
 
			case VEH_TRAIN:    this->sorting = &_sorting.train;    break;
 
			case VEH_ROAD:     this->sorting = &_sorting.roadveh;  break;
 
			case VEH_SHIP:     this->sorting = &_sorting.ship;     break;
 
			case VEH_AIRCRAFT: this->sorting = &_sorting.aircraft; break;
 
		}
 

	
 
		this->group_sel = ALL_GROUP;
 
		this->vehicle_sel = INVALID_VEHICLE;
 
		this->group_rename = INVALID_GROUP;
 

	
 
		const Owner owner = (Owner)GB(window_number, 0, 8);
 
		this->vehicles.SetListing(*this->sorting);
 
		this->vehicles.ForceRebuild();
 
		this->vehicles.NeedResort();
 
		this->BuildVehicleList(owner, this->group_sel, IsAllGroupID(this->group_sel) ? VLW_STANDARD : VLW_GROUP_LIST);
 
		this->SortVehicleList();
 

	
 
		this->groups.ForceRebuild();
 
		this->groups.NeedResort();
 
		this->BuildGroupList(owner);
 
		this->groups.Sort(&GroupNameSorter);
 

	
 
		this->GetWidget<NWidgetCore>(GRP_WIDGET_CAPTION)->widget_data = STR_VEHICLE_LIST_TRAIN_CAPTION + this->vehicle_type;
 
		this->GetWidget<NWidgetCore>(GRP_WIDGET_LIST_VEHICLE)->tool_tip = STR_VEHICLE_LIST_TRAIN_LIST_TOOLTIP + this->vehicle_type;
 
		this->GetWidget<NWidgetCore>(GRP_WIDGET_AVAILABLE_VEHICLES)->widget_data = STR_VEHICLE_LIST_AVAILABLE_TRAINS + this->vehicle_type;
 

	
 
		this->GetWidget<NWidgetCore>(GRP_WIDGET_CREATE_GROUP)->widget_data += this->vehicle_type;
 
		this->GetWidget<NWidgetCore>(GRP_WIDGET_RENAME_GROUP)->widget_data += this->vehicle_type;
 
		this->GetWidget<NWidgetCore>(GRP_WIDGET_DELETE_GROUP)->widget_data += this->vehicle_type;
 
		this->GetWidget<NWidgetCore>(GRP_WIDGET_REPLACE_PROTECTION)->widget_data += this->vehicle_type;
 

	
 
		this->FinishInitNested(desc, window_number);
 
		this->owner = owner;
 
	}
 

	
 
	~VehicleGroupWindow()
 
	{
 
		*this->sorting = this->vehicles.GetListing();
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		switch (widget) {
 
			case GRP_WIDGET_LIST_GROUP:
 
				this->tiny_step_height = FONT_HEIGHT_NORMAL + WD_MATRIX_TOP;
 
				resize->height = this->tiny_step_height;
 
				/* Minimum height is the height of the list widget minus all and default vehicles and a bit for the bottom bar */
 
				size->height =  4 * GetVehicleListHeight(this->vehicle_type, this->tiny_step_height) - 3 * this->tiny_step_height;
 
				break;
 

	
 
			case GRP_WIDGET_ALL_VEHICLES:
 
			case GRP_WIDGET_DEFAULT_VEHICLES:
 
				size->height = FONT_HEIGHT_NORMAL + WD_MATRIX_TOP;
 
				break;
 

	
 
			case GRP_WIDGET_LIST_VEHICLE:
 
				resize->height = GetVehicleListHeight(this->vehicle_type, FONT_HEIGHT_NORMAL + WD_MATRIX_TOP);
 
				size->height = 4 * resize->height;
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnInvalidateData(int data)
 
	{
 
		if (data == 0) {
 
			this->vehicles.ForceRebuild();
 
			this->groups.ForceRebuild();
 
		} else {
 
			this->vehicles.ForceResort();
 
			this->groups.ForceResort();
 
		}
 

	
 
		if (this->group_rename != INVALID_GROUP && !Group::IsValidID(this->group_rename)) {
 
			DeleteWindowByClass(WC_QUERY_STRING);
 
			this->group_rename = INVALID_GROUP;
 
		}
 

	
 
		if (!(IsAllGroupID(this->group_sel) || IsDefaultGroupID(this->group_sel) || Group::IsValidID(this->group_sel))) {
 
			this->group_sel = ALL_GROUP;
 
			HideDropDownMenu(this);
 
		}
 
		this->SetDirty();
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		if (widget != GRP_WIDGET_CAPTION) return;
 

	
 
		/* If selected_group == DEFAULT_GROUP || ALL_GROUP, draw the standard caption
 
		 * We list all vehicles or ungrouped vehicles */
 
		if (IsDefaultGroupID(this->group_sel) || IsAllGroupID(this->group_sel)) {
 
			SetDParam(0, STR_COMPANY_NAME);
 
			SetDParam(1, GB(this->window_number, 0, 8));
 
			SetDParam(2, this->vehicles.Length());
 
			SetDParam(3, this->vehicles.Length());
 
		} else {
 
			const Group *g = Group::Get(this->group_sel);
 

	
 
			SetDParam(0, STR_GROUP_NAME);
 
			SetDParam(1, g->index);
 
			SetDParam(2, g->num_vehicle);
 
			SetDParam(3, g->num_vehicle);
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		const Owner owner = (Owner)GB(this->window_number, 0, 8);
 

	
 
		/* If we select the all vehicles, this->list will contain all vehicles of the owner
 
		 * else this->list will contain all vehicles which belong to the selected group */
 
		this->BuildVehicleList(owner, this->group_sel, IsAllGroupID(this->group_sel) ? VLW_STANDARD : VLW_GROUP_LIST);
 
		this->SortVehicleList();
 

	
 
		this->BuildGroupList(owner);
 
		this->groups.Sort(&GroupNameSorter);
 

	
 
		this->vscroll2.SetCount(this->groups.Length());
 
		this->vscroll.SetCount(this->vehicles.Length());
 

	
 
		/* The drop down menu is out, *but* it may not be used, retract it. */
 
		if (this->vehicles.Length() == 0 && this->IsWidgetLowered(GRP_WIDGET_MANAGE_VEHICLES_DROPDOWN)) {
 
			this->RaiseWidget(GRP_WIDGET_MANAGE_VEHICLES_DROPDOWN);
 
			HideDropDownMenu(this);
 
		}
 

	
 
		/* Disable all lists management button when the list is empty */
 
		this->SetWidgetsDisabledState(this->vehicles.Length() == 0 || _local_company != owner,
 
				GRP_WIDGET_STOP_ALL,
 
				GRP_WIDGET_START_ALL,
 
				GRP_WIDGET_MANAGE_VEHICLES_DROPDOWN,
 
				WIDGET_LIST_END);
 

	
 
		/* Disable the group specific function when we select the default group or all vehicles */
 
		this->SetWidgetsDisabledState(IsDefaultGroupID(this->group_sel) || IsAllGroupID(this->group_sel) || _local_company != owner,
 
				GRP_WIDGET_DELETE_GROUP,
 
				GRP_WIDGET_RENAME_GROUP,
 
				GRP_WIDGET_REPLACE_PROTECTION,
 
				WIDGET_LIST_END);
 

	
 
		/* Disable remaining buttons for non-local companies
 
		 * Needed while changing _local_company, eg. by cheats
 
		 * All procedures (eg. move vehicle to another group)
 
		 *  verify, whether you are the owner of the vehicle,
 
		 *  so it doesn't have to be disabled
 
		 */
 
		this->SetWidgetsDisabledState(_local_company != owner,
 
				GRP_WIDGET_CREATE_GROUP,
 
				GRP_WIDGET_AVAILABLE_VEHICLES,
 
				WIDGET_LIST_END);
 

	
 
		/* If not a default group and the group has replace protection, show an enabled replace sprite. */
 
		uint16 protect_sprite = SPR_GROUP_REPLACE_OFF_TRAIN;
 
		if (!IsDefaultGroupID(this->group_sel) && !IsAllGroupID(this->group_sel) && Group::Get(this->group_sel)->replace_protection) protect_sprite = SPR_GROUP_REPLACE_ON_TRAIN;
 
		this->GetWidget<NWidgetCore>(GRP_WIDGET_REPLACE_PROTECTION)->widget_data = protect_sprite + this->vehicle_type;
 

	
 
		/* Set text of sort by dropdown */
 
		this->GetWidget<NWidgetCore>(GRP_WIDGET_SORT_BY_DROPDOWN)->widget_data = this->vehicle_sorter_names[this->vehicles.SortType()];
 

	
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case GRP_WIDGET_ALL_VEHICLES:
 
				DrawString(r.left + WD_FRAMERECT_LEFT + 8, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP + 1,
 
						STR_GROUP_ALL_TRAINS + this->vehicle_type, IsAllGroupID(this->group_sel) ? TC_WHITE : TC_BLACK);
 
				break;
 

	
 
			case GRP_WIDGET_DEFAULT_VEHICLES:
 
				DrawString(r.left + WD_FRAMERECT_LEFT + 8, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP + 1,
 
						STR_GROUP_DEFAULT_TRAINS + this->vehicle_type, IsDefaultGroupID(this->group_sel) ? TC_WHITE : TC_BLACK);
 
				break;
 

	
 
			case GRP_WIDGET_LIST_GROUP: {
 
				int y1 = r.top + WD_FRAMERECT_TOP + 1;
 
				int max = min(this->vscroll2.GetPosition() + this->vscroll2.GetCapacity(), this->groups.Length());
 
				for (int i = this->vscroll2.GetPosition() ; i < max ; ++i) {
 
					const Group *g = this->groups[i];
 

	
 
					assert(g->owner == this->owner);
 

	
 
					/* draw the selected group in white, else we draw it in black */
 
					SetDParam(0, g->index);
 
					DrawString(r.left + WD_FRAMERECT_LEFT + 8, r.right - WD_FRAMERECT_RIGHT, y1, STR_GROUP_NAME, (this->group_sel == g->index) ? TC_WHITE : TC_BLACK);
 

	
 
					/* draw the number of vehicles of the group */
 
					SetDParam(0, g->num_vehicle);
 
					DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y1 + 1, STR_TINY_COMMA, (this->group_sel == g->index) ? TC_WHITE : TC_BLACK, SA_RIGHT);
 

	
 
					y1 += this->tiny_step_height;
 
				}
 
				break;
 
			}
 

	
 
			case GRP_WIDGET_SORT_BY_ORDER:
 
				this->DrawSortButtonState(GRP_WIDGET_SORT_BY_ORDER, this->vehicles.IsDescSortOrder() ? SBS_DOWN : SBS_UP);
 
				break;
 

	
 
			case GRP_WIDGET_LIST_VEHICLE:
 
				this->DrawVehicleListItems(this->vehicle_sel, this->resize.step_height, r);
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case GRP_WIDGET_SORT_BY_ORDER: // Flip sorting method ascending/descending
 
				this->vehicles.ToggleSortOrder();
 
				this->SetDirty();
 
				break;
 

	
 
			case GRP_WIDGET_SORT_BY_DROPDOWN: // Select sorting criteria dropdown menu
 
				ShowDropDownMenu(this, this->vehicle_sorter_names, this->vehicles.SortType(),  GRP_WIDGET_SORT_BY_DROPDOWN, 0, (this->vehicle_type == VEH_TRAIN || this->vehicle_type == VEH_ROAD) ? 0 : (1 << 10));
 
				return;
 

	
 
			case GRP_WIDGET_ALL_VEHICLES: // All vehicles button
 
				if (!IsAllGroupID(this->group_sel)) {
 
					this->group_sel = ALL_GROUP;
 
					this->vehicles.ForceRebuild();
 
					this->SetDirty();
 
				}
 
				break;
 

	
 
			case GRP_WIDGET_DEFAULT_VEHICLES: // Ungrouped vehicles button
 
				if (!IsDefaultGroupID(this->group_sel)) {
 
					this->group_sel = DEFAULT_GROUP;
 
					this->vehicles.ForceRebuild();
 
					this->SetDirty();
 
				}
 
				break;
 

	
 
			case GRP_WIDGET_LIST_GROUP: { // Matrix Group
 
				uint16 id_g = (pt.y - this->GetWidget<NWidgetBase>(GRP_WIDGET_LIST_GROUP)->pos_y) / (int)this->tiny_step_height;
 

	
 
				if (id_g >= this->vscroll2.GetCapacity()) return;
 

	
 
				id_g += this->vscroll2.GetPosition();
 

	
 
				if (id_g >= this->groups.Length()) return;
 

	
 
				this->group_sel = this->groups[id_g]->index;;
 

	
 
				this->vehicles.ForceRebuild();
 
				this->SetDirty();
 
				break;
 
			}
 

	
 
			case GRP_WIDGET_LIST_VEHICLE: { // Matrix Vehicle
 
				uint32 id_v = (pt.y - this->GetWidget<NWidgetBase>(GRP_WIDGET_LIST_VEHICLE)->pos_y) / (int)this->resize.step_height;
 
				if (id_v >= this->vscroll.GetCapacity()) return; // click out of bounds
 

	
 
				id_v += this->vscroll.GetPosition();
 

	
 
				if (id_v >= this->vehicles.Length()) return; // click out of list bound
 

	
 
				const Vehicle *v = this->vehicles[id_v];
 

	
 
				this->vehicle_sel = v->index;
 

	
 
				SetObjectToPlaceWnd(v->GetImage(DIR_W), GetVehiclePalette(v), HT_DRAG, this);
 
				_cursor.vehchain = true;
 

	
 
				this->SetDirty();
 
				break;
 
			}
 

	
 
			case GRP_WIDGET_CREATE_GROUP: { // Create a new group
 
				extern void CcCreateGroup(bool success, TileIndex tile, uint32 p1, uint32 p2);
 
				DoCommandP(0, this->vehicle_type, 0, CMD_CREATE_GROUP | CMD_MSG(STR_ERROR_GROUP_CAN_T_CREATE), CcCreateGroup);
 
				break;
 
			}
 

	
 
			case GRP_WIDGET_DELETE_GROUP: { // Delete the selected group
 
				GroupID group = this->group_sel;
 
				this->group_sel = ALL_GROUP;
 

	
 
				DoCommandP(0, group, 0, CMD_DELETE_GROUP | CMD_MSG(STR_ERROR_GROUP_CAN_T_DELETE));
 
				break;
 
			}
 

	
 
			case GRP_WIDGET_RENAME_GROUP: // Rename the selected roup
 
				this->ShowRenameGroupWindow(this->group_sel);
 
				break;
 

	
 
			case GRP_WIDGET_AVAILABLE_VEHICLES:
 
				ShowBuildVehicleWindow(INVALID_TILE, this->vehicle_type);
 
				break;
 

	
 
			case GRP_WIDGET_MANAGE_VEHICLES_DROPDOWN:
 
				ShowGroupActionDropdown(this, this->group_sel);
 
				break;
 

	
 
			case GRP_WIDGET_START_ALL:
 
			case GRP_WIDGET_STOP_ALL: { // Start/stop all vehicles of the list
 
				DoCommandP(0, this->group_sel, ((IsAllGroupID(this->group_sel) ? VLW_STANDARD : VLW_GROUP_LIST) & VLW_MASK)
 
													| (1 << 6)
 
													| (widget == GRP_WIDGET_START_ALL ? (1 << 5) : 0)
 
													| this->vehicle_type, CMD_MASS_START_STOP);
 

	
 
				break;
 
			}
 

	
 
			case GRP_WIDGET_REPLACE_PROTECTION: {
 
				const Group *g = Group::GetIfValid(this->group_sel);
 
				if (g != NULL) {
 
					DoCommandP(0, this->group_sel, !g->replace_protection, CMD_SET_GROUP_REPLACE_PROTECTION);
 
				}
 
				break;
 
			}
 
		}
 
	}
 

	
 
	virtual void OnDragDrop(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case GRP_WIDGET_ALL_VEHICLES: // All vehicles
 
			case GRP_WIDGET_DEFAULT_VEHICLES: // Ungrouped vehicles
 
				DoCommandP(0, DEFAULT_GROUP, this->vehicle_sel, CMD_ADD_VEHICLE_GROUP | CMD_MSG(STR_ERROR_GROUP_CAN_T_ADD_VEHICLE));
 

	
 
				this->vehicle_sel = INVALID_VEHICLE;
 

	
 
				this->SetDirty();
 
				break;
 

	
 
			case GRP_WIDGET_LIST_GROUP: { // Maxtrix group
 
				uint16 id_g = (pt.y - this->GetWidget<NWidgetBase>(GRP_WIDGET_LIST_GROUP)->pos_y) / (int)this->tiny_step_height;
 
				const VehicleID vindex = this->vehicle_sel;
 

	
 
				this->vehicle_sel = INVALID_VEHICLE;
 

	
 
				this->SetDirty();
 

	
 
				if (id_g >= this->vscroll2.GetCapacity()) return;
 

	
 
				id_g += this->vscroll2.GetPosition();
 

	
 
				if (id_g >= this->groups.Length()) return;
 

	
 
				DoCommandP(0, this->groups[id_g]->index, vindex, CMD_ADD_VEHICLE_GROUP | CMD_MSG(STR_ERROR_GROUP_CAN_T_ADD_VEHICLE));
 
				break;
 
			}
 

	
 
			case GRP_WIDGET_LIST_VEHICLE: { // Maxtrix vehicle
 
				uint32 id_v = (pt.y - this->GetWidget<NWidgetBase>(GRP_WIDGET_LIST_VEHICLE)->pos_y) / (int)this->resize.step_height;
 
				const VehicleID vindex = this->vehicle_sel;
 

	
 
				this->vehicle_sel = INVALID_VEHICLE;
 

	
 
				this->SetDirty();
 

	
 
				if (id_v >= this->vscroll.GetCapacity()) return; // click out of bounds
 

	
 
				id_v += this->vscroll.GetPosition();
 

	
 
				if (id_v >= this->vehicles.Length()) return; // click out of list bound
 

	
 
				const Vehicle *v = this->vehicles[id_v];
 
				if (vindex == v->index) {
 
					ShowVehicleViewWindow(v);
 
				}
 
				break;
 
			}
 
		}
 
		_cursor.vehchain = false;
 
	}
 

	
 
	virtual void OnQueryTextFinished(char *str)
 
	{
 
		if (str != NULL) DoCommandP(0, this->group_rename, 0, CMD_RENAME_GROUP | CMD_MSG(STR_ERROR_GROUP_CAN_T_RENAME), NULL, str);
 
		this->group_rename = INVALID_GROUP;
 
	}
 

	
 
	virtual void OnResize()
 
	{
 
		NWidgetCore *nwi = this->GetWidget<NWidgetCore>(GRP_WIDGET_LIST_GROUP);
 
		this->vscroll2.SetCapacity(nwi->current_y / this->tiny_step_height);
 
		nwi->widget_data = (this->vscroll2.GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START);
 

	
 
		nwi = this->GetWidget<NWidgetCore>(GRP_WIDGET_LIST_VEHICLE);
 
		this->vscroll.SetCapacity(this->GetWidget<NWidgetBase>(GRP_WIDGET_LIST_VEHICLE)->current_y / this->resize.step_height);
 
		nwi->widget_data = (this->vscroll.GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START);
 
	}
 

	
 
	virtual void OnDropdownSelect(int widget, int index)
 
	{
 
		switch (widget) {
 
			case GRP_WIDGET_SORT_BY_DROPDOWN:
 
				this->vehicles.SetSortType(index);
 
				break;
 

	
 
			case GRP_WIDGET_MANAGE_VEHICLES_DROPDOWN:
 
				assert(this->vehicles.Length() != 0);
 

	
 
				switch (index) {
 
					case GALF_REPLACE: // Replace window
 
						ShowReplaceGroupVehicleWindow(this->group_sel, this->vehicle_type);
 
						break;
 
					case GALF_SERVICE: // Send for servicing
 
						DoCommandP(0, this->group_sel, ((IsAllGroupID(this->group_sel) ? VLW_STANDARD : VLW_GROUP_LIST) & VLW_MASK)
 
									| DEPOT_MASS_SEND
 
									| DEPOT_SERVICE, GetCmdSendToDepot(this->vehicle_type));
 
						break;
 
					case GALF_DEPOT: // Send to Depots
 
						DoCommandP(0, this->group_sel, ((IsAllGroupID(this->group_sel) ? VLW_STANDARD : VLW_GROUP_LIST) & VLW_MASK)
 
									| DEPOT_MASS_SEND, GetCmdSendToDepot(this->vehicle_type));
 
						break;
 
					case GALF_ADD_SHARED: // Add shared Vehicles
 
						assert(Group::IsValidID(this->group_sel));
 

	
 
						DoCommandP(0, this->group_sel, this->vehicle_type, CMD_ADD_SHARED_VEHICLE_GROUP | CMD_MSG(STR_ERROR_GROUP_CAN_T_ADD_SHARED_VEHICLE));
 
						break;
 
					case GALF_REMOVE_ALL: // Remove all Vehicles from the selected group
 
						assert(Group::IsValidID(this->group_sel));
 

	
 
						DoCommandP(0, this->group_sel, this->vehicle_type, CMD_REMOVE_ALL_VEHICLES_GROUP | CMD_MSG(STR_ERROR_GROUP_CAN_T_REMOVE_ALL_VEHICLES));
 
						break;
 
					default: NOT_REACHED();
 
				}
 
				break;
 

	
 
			default: NOT_REACHED();
 
		}
 

	
 
		this->SetDirty();
 
	}
 

	
 
	virtual void OnTick()
 
	{
 
		if (_pause_mode != PM_UNPAUSED) return;
 
		if (this->groups.NeedResort() || this->vehicles.NeedResort()) {
 
			this->SetDirty();
 
		}
 
	}
 

	
 
	virtual void OnPlaceObjectAbort()
 
	{
 
		/* abort drag & drop */
 
		this->vehicle_sel = INVALID_VEHICLE;
 
		this->SetWidgetDirty(GRP_WIDGET_LIST_VEHICLE);
 
	}
 

	
 
	void ShowRenameGroupWindow(GroupID group)
 
	{
 
		assert(Group::IsValidID(group));
 
		this->group_rename = group;
 
		SetDParam(0, group);
 
		ShowQueryString(STR_GROUP_NAME, STR_GROUP_RENAME_CAPTION, MAX_LENGTH_GROUP_NAME_BYTES, MAX_LENGTH_GROUP_NAME_PIXELS, this, CS_ALPHANUMERAL, QSF_ENABLE_DEFAULT);
 
	}
 

	
 
	/**
 
	 * Tests whether a given vehicle is selected in the window, and unselects it if necessary.
 
	 * Called when the vehicle is deleted.
 
	 * @param vehicle Vehicle that is going to be deleted
 
	 */
 
	void UnselectVehicle(VehicleID vehicle)
 
	{
 
		if (this->vehicle_sel == vehicle) ResetObjectToPlace();
 
	}
 
};
 

	
 

	
 
static WindowDesc _other_group_desc(
 
	WDP_AUTO, WDP_AUTO, 460, 246,
 
	WC_INVALID, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE,
 
	_nested_group_widgets, lengthof(_nested_group_widgets)
 
);
 

	
 
const static WindowDesc _train_group_desc(
 
	WDP_AUTO, WDP_AUTO, 525, 246,
 
	WC_TRAINS_LIST, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE,
 
	_nested_group_widgets, lengthof(_nested_group_widgets)
 
);
 

	
 
void ShowCompanyGroup(CompanyID company, VehicleType vehicle_type)
 
{
 
	if (!Company::IsValidID(company)) return;
 

	
 
	WindowNumber num = (vehicle_type << 11) | VLW_GROUP_LIST | company;
 
	if (vehicle_type == VEH_TRAIN) {
 
		AllocateWindowDescFront<VehicleGroupWindow>(&_train_group_desc, num);
 
	} else {
 
		_other_group_desc.cls = GetWindowClassForVehicleType(vehicle_type);
 
		AllocateWindowDescFront<VehicleGroupWindow>(&_other_group_desc, num);
 
	}
 
}
 

	
 
/**
 
 * Finds a group list window determined by vehicle type and owner
 
 * @param vt vehicle type
 
 * @param owner owner of groups
 
 * @return pointer to VehicleGroupWindow, NULL if not found
 
 */
 
static inline VehicleGroupWindow *FindVehicleGroupWindow(VehicleType vt, Owner owner)
 
{
 
	return (VehicleGroupWindow *)FindWindowById(GetWindowClassForVehicleType(vt), (vt << 11) | VLW_GROUP_LIST | owner);
 
}
 

	
 
/**
 
 * Opens a 'Rename group' window for newly created group
 
 * @param success did command succeed?
 
 * @param tile unused
 
 * @param p1 vehicle type
 
 * @param p2 unused
 
 * @see CmdCreateGroup
 
 */
 
void CcCreateGroup(bool success, TileIndex tile, uint32 p1, uint32 p2)
 
{
 
	if (!success) return;
 
	assert(p1 <= VEH_AIRCRAFT);
 

	
 
	VehicleGroupWindow *w = FindVehicleGroupWindow((VehicleType)p1, _current_company);
 
	if (w != NULL) w->ShowRenameGroupWindow(_new_group_id);
 
}
 

	
 
/**
 
 * Removes the highlight of a vehicle in a group window
 
 * @param *v Vehicle to remove all highlights from
 
 */
 
void DeleteGroupHighlightOfVehicle(const Vehicle *v)
 
{
 
	/* If we haven't got any vehicles on the mouse pointer, we haven't got any highlighted in any group windows either
 
	 * If that is the case, we can skip looping though the windows and save time
 
	 */
 
	if (_special_mouse_mode != WSM_DRAGDROP) return;
 

	
 
	VehicleGroupWindow *w = FindVehicleGroupWindow(v->type, v->owner);
 
	if (w != NULL) w->UnselectVehicle(v->index);
 
}
src/industry_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file industry_gui.cpp GUIs related to industries. */
 

	
 
#include "stdafx.h"
 
#include "openttd.h"
 
#include "gui.h"
 
#include "window_gui.h"
 
#include "textbuf_gui.h"
 
#include "command_func.h"
 
#include "viewport_func.h"
 
#include "gfx_func.h"
 
#include "industry.h"
 
#include "town.h"
 
#include "variables.h"
 
#include "cheat_type.h"
 
#include "newgrf.h"
 
#include "newgrf_industries.h"
 
#include "newgrf_text.h"
 
#include "strings_func.h"
 
#include "company_func.h"
 
#include "tilehighlight_func.h"
 
#include "string_func.h"
 
#include "sortlist_type.h"
 
#include "widgets/dropdown_func.h"
 
#include "company_base.h"
 

	
 
#include "table/strings.h"
 
#include "table/sprites.h"
 

	
 
bool _ignore_restrictions;
 

	
 
/** Cargo suffix type (for which window is it requested) */
 
enum CargoSuffixType {
 
	CST_FUND,  ///< Fund-industry window
 
	CST_VIEW,  ///< View-industry window
 
	CST_DIR,   ///< Industry-directory window
 
};
 

	
 
/**
 
 * Gets the string to display after the cargo name (using callback 37)
 
 * @param cargo the cargo for which the suffix is requested
 
 * - 00 - first accepted cargo type
 
 * - 01 - second accepted cargo type
 
 * - 02 - third accepted cargo type
 
 * - 03 - first produced cargo type
 
 * - 04 - second produced cargo type
 
 * @param cst the cargo suffix type (for which window is it requested). @see CargoSuffixType
 
 * @param ind the industry (NULL if in fund window)
 
 * @param ind_type the industry type
 
 * @param indspec the industry spec
 
 * @param suffix is filled with the string to display
 
 * @param suffix_last lastof(suffix)
 
 */
 
static void GetCargoSuffix(uint cargo, CargoSuffixType cst, const Industry *ind, IndustryType ind_type, const IndustrySpec *indspec, char *suffix, const char *suffix_last)
 
{
 
	suffix[0] = '\0';
 
	if (HasBit(indspec->callback_mask, CBM_IND_CARGO_SUFFIX)) {
 
		uint16 callback = GetIndustryCallback(CBID_INDUSTRY_CARGO_SUFFIX, 0, (cst << 8) | cargo, const_cast<Industry *>(ind), ind_type, (cst != CST_FUND) ? ind->xy : INVALID_TILE);
 
		if (GB(callback, 0, 8) != 0xFF) {
 
			PrepareTextRefStackUsage(6);
 
			GetString(suffix, GetGRFStringID(indspec->grf_prop.grffile->grfid, 0xD000 + callback), suffix_last);
 
			StopTextRefStackUsage();
 
		}
 
	}
 
}
 

	
 
/**
 
 * Gets all strings to display after the cargos of industries (using callback 37)
 
 * @param cb_offset The offset for the cargo used in cb37, 0 for accepted cargos, 3 for produced cargos
 
 * @param cst the cargo suffix type (for which window is it requested). @see CargoSuffixType
 
 * @param ind the industry (NULL if in fund window)
 
 * @param ind_type the industry type
 
 * @param indspec the industry spec
 
 * @param cargos array with cargotypes. for CT_INVALID no suffix will be determined
 
 * @param suffixes is filled with the suffixes
 
 */
 
template <typename TC, typename TS>
 
static inline void GetAllCargoSuffixes(uint cb_offset, CargoSuffixType cst, const Industry *ind, IndustryType ind_type, const IndustrySpec *indspec, const TC &cargos, TS &suffixes)
 
{
 
	assert_compile(lengthof(cargos) <= lengthof(suffixes));
 
	for (uint j = 0; j < lengthof(cargos); j++) {
 
		if (cargos[j] != CT_INVALID) {
 
			GetCargoSuffix(cb_offset + j, cst, ind, ind_type, indspec, suffixes[j], lastof(suffixes[j]));
 
		} else {
 
			suffixes[j][0] = '\0';
 
		}
 
	}
 
}
 

	
 
/** Names of the widgets of the dynamic place industries gui */
 
enum DynamicPlaceIndustriesWidgets {
 
	DPIW_CLOSEBOX = 0,
 
	DPIW_CAPTION,
 
	DPIW_MATRIX_WIDGET,
 
	DPIW_SCROLLBAR,
 
	DPIW_INFOPANEL,
 
	DPIW_FUND_WIDGET,
 
	DPIW_RESIZE_WIDGET,
 
};
 

	
 
static const NWidgetPart _nested_build_industry_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN, DPIW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, DPIW_CAPTION), SetDataTip(STR_FUND_INDUSTRY_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_MATRIX, COLOUR_DARK_GREEN, DPIW_MATRIX_WIDGET), SetDataTip(0x801, STR_FUND_INDUSTRY_SELECTION_TOOLTIP), SetFill(true, false), SetResize(1, 1),
 
		NWidget(WWT_MATRIX, COLOUR_DARK_GREEN, DPIW_MATRIX_WIDGET), SetDataTip(0x801, STR_FUND_INDUSTRY_SELECTION_TOOLTIP), SetFill(1, 0), SetResize(1, 1),
 
		NWidget(WWT_SCROLLBAR, COLOUR_DARK_GREEN, DPIW_SCROLLBAR),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_DARK_GREEN, DPIW_INFOPANEL), SetResize(1, 0),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_TEXTBTN, COLOUR_DARK_GREEN, DPIW_FUND_WIDGET), SetFill(true, false), SetResize(1, 0), SetDataTip(STR_JUST_STRING, STR_NULL),
 
		NWidget(WWT_TEXTBTN, COLOUR_DARK_GREEN, DPIW_FUND_WIDGET), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_JUST_STRING, STR_NULL),
 
		NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN, DPIW_RESIZE_WIDGET),
 
	EndContainer(),
 
};
 

	
 
/** Window definition of the dynamic place industries gui */
 
static const WindowDesc _build_industry_desc(
 
	WDP_AUTO, WDP_AUTO, 170, 212,
 
	WC_BUILD_INDUSTRY, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_RESIZABLE | WDF_CONSTRUCTION,
 
	_nested_build_industry_widgets, lengthof(_nested_build_industry_widgets)
 
);
 

	
 
/** Build (fund or prospect) a new industry, */
 
class BuildIndustryWindow : public Window {
 
	int selected_index;                         ///< index of the element in the matrix
 
	IndustryType selected_type;                 ///< industry corresponding to the above index
 
	uint16 callback_timer;                      ///< timer counter for callback eventual verification
 
	bool timer_enabled;                         ///< timer can be used
 
	uint16 count;                               ///< How many industries are loaded
 
	IndustryType index[NUM_INDUSTRYTYPES + 1];  ///< Type of industry, in the order it was loaded
 
	bool enabled[NUM_INDUSTRYTYPES + 1];        ///< availability state, coming from CBID_INDUSTRY_AVAILABLE (if ever)
 

	
 
	/** The offset for the text in the matrix. */
 
	static const int MATRIX_TEXT_OFFSET = 17;
 

	
 
	void SetupArrays()
 
	{
 
		this->count = 0;
 

	
 
		for (uint i = 0; i < lengthof(this->index); i++) {
 
			this->index[i]   = INVALID_INDUSTRYTYPE;
 
			this->enabled[i] = false;
 
		}
 

	
 
		if (_game_mode == GM_EDITOR) { // give room for the Many Random "button"
 
			this->index[this->count] = INVALID_INDUSTRYTYPE;
 
			this->count++;
 
			this->timer_enabled = false;
 
		}
 
		/* Fill the arrays with industries.
 
		 * The tests performed after the enabled allow to load the industries
 
		 * In the same way they are inserted by grf (if any)
 
		 */
 
		for (IndustryType ind = 0; ind < NUM_INDUSTRYTYPES; ind++) {
 
			const IndustrySpec *indsp = GetIndustrySpec(ind);
 
			if (indsp->enabled) {
 
				/* Rule is that editor mode loads all industries.
 
				 * In game mode, all non raw industries are loaded too
 
				 * and raw ones are loaded only when setting allows it */
 
				if (_game_mode != GM_EDITOR && indsp->IsRawIndustry() && _settings_game.construction.raw_industry_construction == 0) {
 
					/* Unselect if the industry is no longer in the list */
 
					if (this->selected_type == ind) this->selected_index = -1;
 
					continue;
 
				}
 
				this->index[this->count] = ind;
 
				this->enabled[this->count] = (_game_mode == GM_EDITOR) || CheckIfCallBackAllowsAvailability(ind, IACT_USERCREATION);
 
				/* Keep the selection to the correct line */
 
				if (this->selected_type == ind) this->selected_index = this->count;
 
				this->count++;
 
			}
 
		}
 

	
 
		/* first indutry type is selected if the current selection is invalid.
 
		 * I'll be damned if there are none available ;) */
 
		if (this->selected_index == -1) {
 
			this->selected_index = 0;
 
			this->selected_type = this->index[0];
 
		}
 

	
 
		this->vscroll.SetCount(this->count);
 
	}
 

	
 
public:
 
	BuildIndustryWindow() : Window()
 
	{
 
		this->timer_enabled = _loaded_newgrf_features.has_newindustries;
 

	
 
		this->selected_index = -1;
 
		this->selected_type = INVALID_INDUSTRYTYPE;
 

	
 
		/* Initialize arrays */
 
		this->SetupArrays();
 

	
 
		this->callback_timer = DAY_TICKS;
 

	
 
		this->InitNested(&_build_industry_desc, 0);
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		switch (widget) {
 
			case DPIW_MATRIX_WIDGET: {
 
				Dimension d = GetStringBoundingBox(STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES);
 
				for (byte i = 0; i < this->count; i++) {
 
					if (this->index[i] == INVALID_INDUSTRYTYPE) continue;
 
					d = maxdim(d, GetStringBoundingBox(GetIndustrySpec(this->index[i])->name));
 
				}
 
				resize->height = FONT_HEIGHT_NORMAL + WD_MATRIX_TOP + WD_MATRIX_BOTTOM;
 
				d.width += MATRIX_TEXT_OFFSET + padding.width;
 
				d.height = 5 * resize->height;
 
				*size = maxdim(*size, d);
 
				break;
 
			}
 

	
 
			case DPIW_INFOPANEL: {
 
				/* Extra line for cost outside of editor + extra lines for 'extra' information for NewGRFs. */
 
				int height = 2 + (_game_mode == GM_EDITOR ? 0 : 1) + (_loaded_newgrf_features.has_newindustries ? 4 : 0);
 
				Dimension d = {0, 0};
 
				for (byte i = 0; i < this->count; i++) {
 
					if (this->index[i] == INVALID_INDUSTRYTYPE) continue;
 

	
 
					const IndustrySpec *indsp = GetIndustrySpec(this->index[i]);
 

	
 
					char cargo_suffix[3][512];
 
					GetAllCargoSuffixes(0, CST_FUND, NULL, this->selected_type, indsp, indsp->accepts_cargo, cargo_suffix);
 
					StringID str = STR_INDUSTRY_VIEW_REQUIRES_CARGO;
 
					byte p = 0;
 
					SetDParam(0, STR_JUST_NOTHING);
 
					SetDParamStr(1, "");
 
					for (byte j = 0; j < lengthof(indsp->accepts_cargo); j++) {
 
						if (indsp->accepts_cargo[j] == CT_INVALID) continue;
 
						if (p > 0) str++;
 
						SetDParam(p++, CargoSpec::Get(indsp->accepts_cargo[j])->name);
 
						SetDParamStr(p++, cargo_suffix[j]);
 
					}
 
					d = maxdim(d, GetStringBoundingBox(str));
 

	
 
					/* Draw the produced cargos, if any. Otherwhise, will print "Nothing" */
 
					GetAllCargoSuffixes(3, CST_FUND, NULL, this->selected_type, indsp, indsp->produced_cargo, cargo_suffix);
 
					str = STR_INDUSTRY_VIEW_PRODUCES_CARGO;
 
					p = 0;
 
					SetDParam(0, STR_JUST_NOTHING);
 
					SetDParamStr(1, "");
 
					for (byte j = 0; j < lengthof(indsp->produced_cargo); j++) {
 
						if (indsp->produced_cargo[j] == CT_INVALID) continue;
 
						if (p > 0) str++;
 
						SetDParam(p++, CargoSpec::Get(indsp->produced_cargo[j])->name);
 
						SetDParamStr(p++, cargo_suffix[j]);
 
					}
 
					d = maxdim(d, GetStringBoundingBox(str));
 
				}
 

	
 
				/* Set it to something more sane :) */
 
				size->height = height * FONT_HEIGHT_NORMAL + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
 
				size->width  = d.width + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
 
			} break;
 

	
 
			case DPIW_FUND_WIDGET: {
 
				Dimension d = GetStringBoundingBox(STR_FUND_INDUSTRY_BUILD_NEW_INDUSTRY);
 
				d = maxdim(d, GetStringBoundingBox(STR_FUND_INDUSTRY_PROSPECT_NEW_INDUSTRY));
 
				d = maxdim(d, GetStringBoundingBox(STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY));
 
				d.width += padding.width;
 
				d.height += padding.height;
 
				*size = maxdim(*size, d);
 
				break;
 
			}
 
		}
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		switch (widget) {
 
			case DPIW_FUND_WIDGET:
 
				/* Raw industries might be prospected. Show this fact by changing the string
 
				 * In Editor, you just build, while ingame, or you fund or you prospect */
 
				if (_game_mode == GM_EDITOR) {
 
					/* We've chosen many random industries but no industries have been specified */
 
					SetDParam(0, STR_FUND_INDUSTRY_BUILD_NEW_INDUSTRY);
 
				} else {
 
					const IndustrySpec *indsp = GetIndustrySpec(this->index[this->selected_index]);
 
					SetDParam(0, (_settings_game.construction.raw_industry_construction == 2 && indsp->IsRawIndustry()) ? STR_FUND_INDUSTRY_PROSPECT_NEW_INDUSTRY : STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY);
 
				}
 
				break;
 
		}
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case DPIW_MATRIX_WIDGET:
 
				for (byte i = 0; i < this->vscroll.GetCapacity() && i + this->vscroll.GetPosition() < this->count; i++) {
 
					int x = r.left + WD_MATRIX_LEFT;
 
					int y = r.top + WD_MATRIX_TOP + i * this->resize.step_height;
 
					bool selected = this->selected_index == i + this->vscroll.GetPosition();
 

	
 
					if (this->index[i + this->vscroll.GetPosition()] == INVALID_INDUSTRYTYPE) {
 
						DrawString(x + MATRIX_TEXT_OFFSET, r.right - WD_MATRIX_RIGHT, y, STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES, selected ? TC_WHITE : TC_ORANGE);
 
						continue;
 
					}
 
					const IndustrySpec *indsp = GetIndustrySpec(this->index[i + this->vscroll.GetPosition()]);
 

	
 
					/* Draw the name of the industry in white is selected, otherwise, in orange */
 
					DrawString(x + MATRIX_TEXT_OFFSET, r.right - WD_MATRIX_RIGHT, y, indsp->name, selected ? TC_WHITE : TC_ORANGE);
 
					GfxFillRect(x,     y + 1,  x + 10, y + 7, selected ? 15 : 0);
 
					GfxFillRect(x + 1, y + 2,  x +  9, y + 6, indsp->map_colour);
 
				}
 
				break;
 

	
 
			case DPIW_INFOPANEL: {
 
				int y      = r.top    + WD_FRAMERECT_TOP;
 
				int bottom = r.bottom - WD_FRAMERECT_BOTTOM;
 
				int left   = r.left   + WD_FRAMERECT_LEFT;
 
				int right  = r.right  - WD_FRAMERECT_RIGHT;
 

	
 
				if (this->selected_type == INVALID_INDUSTRYTYPE) {
 
					DrawStringMultiLine(left, right, y,  bottom, STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_TOOLTIP);
 
					break;
 
				}
 

	
 
				const IndustrySpec *indsp = GetIndustrySpec(this->selected_type);
 

	
 
				if (_game_mode != GM_EDITOR) {
 
					SetDParam(0, indsp->GetConstructionCost());
 
					DrawString(left, right, y, STR_FUND_INDUSTRY_INDUSTRY_BUILD_COST);
 
					y += FONT_HEIGHT_NORMAL;
 
				}
 

	
 
				/* Draw the accepted cargos, if any. Otherwhise, will print "Nothing" */
 
				char cargo_suffix[3][512];
 
				GetAllCargoSuffixes(0, CST_FUND, NULL, this->selected_type, indsp, indsp->accepts_cargo, cargo_suffix);
 
				StringID str = STR_INDUSTRY_VIEW_REQUIRES_CARGO;
 
				byte p = 0;
 
				SetDParam(0, STR_JUST_NOTHING);
 
				SetDParamStr(1, "");
 
				for (byte j = 0; j < lengthof(indsp->accepts_cargo); j++) {
 
					if (indsp->accepts_cargo[j] == CT_INVALID) continue;
 
					if (p > 0) str++;
 
					SetDParam(p++, CargoSpec::Get(indsp->accepts_cargo[j])->name);
 
					SetDParamStr(p++, cargo_suffix[j]);
 
				}
 
				DrawString(left, right, y, str);
 
				y += FONT_HEIGHT_NORMAL;
 

	
 
				/* Draw the produced cargos, if any. Otherwhise, will print "Nothing" */
 
				GetAllCargoSuffixes(3, CST_FUND, NULL, this->selected_type, indsp, indsp->produced_cargo, cargo_suffix);
 
				str = STR_INDUSTRY_VIEW_PRODUCES_CARGO;
 
				p = 0;
 
				SetDParam(0, STR_JUST_NOTHING);
 
				SetDParamStr(1, "");
 
				for (byte j = 0; j < lengthof(indsp->produced_cargo); j++) {
 
					if (indsp->produced_cargo[j] == CT_INVALID) continue;
 
					if (p > 0) str++;
 
					SetDParam(p++, CargoSpec::Get(indsp->produced_cargo[j])->name);
 
					SetDParamStr(p++, cargo_suffix[j]);
 
				}
 
				DrawString(left, right, y, str);
 
				y += FONT_HEIGHT_NORMAL;
 

	
 
				/* Get the additional purchase info text, if it has not already been queried. */
 
				str = STR_NULL;
 
				if (HasBit(indsp->callback_mask, CBM_IND_FUND_MORE_TEXT)) {
 
					uint16 callback_res = GetIndustryCallback(CBID_INDUSTRY_FUND_MORE_TEXT, 0, 0, NULL, this->selected_type, INVALID_TILE);
 
					if (callback_res != CALLBACK_FAILED) {  // Did it fail?
 
						str = GetGRFStringID(indsp->grf_prop.grffile->grfid, 0xD000 + callback_res);  // No. here's the new string
 
					}
 
				}
 

	
 
				/* Draw the Additional purchase text, provided by newgrf callback, if any.
 
				 * Otherwhise, will print Nothing */
 
				if (str != STR_NULL && str != STR_UNDEFINED) {
 
					SetDParam(0, str);
 
					DrawStringMultiLine(left, right, y, bottom, STR_JUST_STRING);
 
				}
 
			} break;
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void OnDoubleClick(Point pt, int widget)
 
	{
 
		if (widget != DPIW_MATRIX_WIDGET) return;
 
		this->OnClick(pt, DPIW_FUND_WIDGET);
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case DPIW_MATRIX_WIDGET: {
 
				const IndustrySpec *indsp;
 
				int y = (pt.y - this->GetWidget<NWidgetBase>(DPIW_MATRIX_WIDGET)->pos_y) / this->resize.step_height + this->vscroll.GetPosition() ;
 

	
 
				if (y >= 0 && y < count) { // Is it within the boundaries of available data?
 
					this->selected_index = y;
 
					this->selected_type = this->index[y];
 
					indsp = (this->selected_type == INVALID_INDUSTRYTYPE) ? NULL : GetIndustrySpec(this->selected_type);
 

	
 
					this->SetDirty();
 

	
 
					if ((_game_mode != GM_EDITOR && _settings_game.construction.raw_industry_construction == 2 && indsp != NULL && indsp->IsRawIndustry()) ||
 
							this->selected_type == INVALID_INDUSTRYTYPE) {
 
						/* Reset the button state if going to prospecting or "build many industries" */
 
						this->RaiseButtons();
 
						ResetObjectToPlace();
 
					}
 

	
 
					this->SetWidgetDisabledState(DPIW_FUND_WIDGET, !this->enabled[this->selected_index]);
 
				}
 
			} break;
 

	
 
			case DPIW_FUND_WIDGET: {
 
				if (this->selected_type == INVALID_INDUSTRYTYPE) {
 
					this->HandleButtonClick(DPIW_FUND_WIDGET);
 

	
 
					if (Town::GetNumItems() == 0) {
 
						ShowErrorMessage(STR_ERROR_CAN_T_GENERATE_INDUSTRIES, STR_ERROR_MUST_FOUND_TOWN_FIRST, 0, 0);
 
					} else {
 
						extern void GenerateIndustries();
 
						_generating_world = true;
 
						GenerateIndustries();
 
						_generating_world = false;
 
					}
 
				} else if (_game_mode != GM_EDITOR && _settings_game.construction.raw_industry_construction == 2 && GetIndustrySpec(this->selected_type)->IsRawIndustry()) {
 
					DoCommandP(0, this->selected_type, InteractiveRandom(), CMD_BUILD_INDUSTRY | CMD_MSG(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY));
 
					this->HandleButtonClick(DPIW_FUND_WIDGET);
 
				} else {
 
					HandlePlacePushButton(this, DPIW_FUND_WIDGET, SPR_CURSOR_INDUSTRY, HT_RECT, NULL);
 
				}
 
			} break;
 
		}
 
	}
 

	
 
	virtual void OnResize()
 
	{
 
		/* Adjust the number of items in the matrix depending of the resize */
 
		this->vscroll.SetCapacity(this->GetWidget<NWidgetBase>(DPIW_MATRIX_WIDGET)->current_y / this->resize.step_height);
 
		this->GetWidget<NWidgetCore>(DPIW_MATRIX_WIDGET)->widget_data = (this->vscroll.GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START);
 
	}
 

	
 
	virtual void OnPlaceObject(Point pt, TileIndex tile)
 
	{
 
		bool success = true;
 
		/* We do not need to protect ourselves against "Random Many Industries" in this mode */
 
		const IndustrySpec *indsp = GetIndustrySpec(this->selected_type);
 
		uint32 seed = InteractiveRandom();
 

	
 
		if (_game_mode == GM_EDITOR) {
 
			/* Show error if no town exists at all */
 
			if (Town::GetNumItems() == 0) {
 
				SetDParam(0, indsp->name);
 
				ShowErrorMessage(STR_ERROR_CAN_T_BUILD_HERE, STR_ERROR_MUST_FOUND_TOWN_FIRST, pt.x, pt.y);
 
				return;
 
			}
 

	
 
			_current_company = OWNER_NONE;
 
			_generating_world = true;
 
			_ignore_restrictions = true;
 
			success = DoCommandP(tile, (InteractiveRandomRange(indsp->num_table) << 8) | this->selected_type, seed, CMD_BUILD_INDUSTRY | CMD_MSG(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY));
 
			if (!success) {
 
				SetDParam(0, indsp->name);
 
				ShowErrorMessage(STR_ERROR_CAN_T_BUILD_HERE, _error_message, pt.x, pt.y);
 
			}
 

	
 
			_ignore_restrictions = false;
 
			_generating_world = false;
 
		} else {
 
			success = DoCommandP(tile, (InteractiveRandomRange(indsp->num_table) << 8) | this->selected_type, seed, CMD_BUILD_INDUSTRY | CMD_MSG(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY));
 
		}
 

	
 
		/* If an industry has been built, just reset the cursor and the system */
 
		if (success && !_settings_client.gui.persistent_buildingtools) ResetObjectToPlace();
 
	}
 

	
 
	virtual void OnTick()
 
	{
 
		if (_pause_mode != PM_UNPAUSED) return;
 
		if (!this->timer_enabled) return;
 
		if (--this->callback_timer == 0) {
 
			/* We have just passed another day.
 
			 * See if we need to update availability of currently selected industry */
 
			this->callback_timer = DAY_TICKS; // restart counter
 

	
 
			const IndustrySpec *indsp = GetIndustrySpec(this->selected_type);
 

	
 
			if (indsp->enabled) {
 
				bool call_back_result = CheckIfCallBackAllowsAvailability(this->selected_type, IACT_USERCREATION);
 

	
 
				/* Only if result does match the previous state would it require a redraw. */
 
				if (call_back_result != this->enabled[this->selected_index]) {
 
					this->enabled[this->selected_index] = call_back_result;
 
					this->SetWidgetDisabledState(DPIW_FUND_WIDGET, !this->enabled[this->selected_index]);
 
					this->SetDirty();
 
				}
 
			}
 
		}
 
	}
 

	
 
	virtual void OnTimeout()
 
	{
 
		this->RaiseButtons();
 
	}
 

	
 
	virtual void OnPlaceObjectAbort()
 
	{
 
		this->RaiseButtons();
 
	}
 

	
 
	virtual void OnInvalidateData(int data = 0)
 
	{
 
		this->SetupArrays();
 

	
 
		const IndustrySpec *indsp = (this->selected_type == INVALID_INDUSTRYTYPE) ? NULL : GetIndustrySpec(this->selected_type);
 
		if (indsp == NULL) this->enabled[this->selected_index] = _settings_game.difficulty.number_industries != 0;
 
		this->SetWidgetDisabledState(DPIW_FUND_WIDGET, !this->enabled[this->selected_index]);
 
	}
 
};
 

	
 
void ShowBuildIndustryWindow()
 
{
 
	if (_game_mode != GM_EDITOR && !Company::IsValidID(_local_company)) return;
 
	if (BringWindowToFrontById(WC_BUILD_INDUSTRY, 0)) return;
 
	new BuildIndustryWindow();
 
}
 

	
 
static void UpdateIndustryProduction(Industry *i);
 

	
 
static inline bool IsProductionMinimum(const Industry *i, int pt)
 
{
 
	return i->production_rate[pt] == 0;
 
}
 

	
 
static inline bool IsProductionMaximum(const Industry *i, int pt)
 
{
 
	return i->production_rate[pt] >= 255;
 
}
 

	
 
static inline bool IsProductionAlterable(const Industry *i)
 
{
 
	return ((_game_mode == GM_EDITOR || _cheats.setup_prod.value) &&
 
			(i->accepts_cargo[0] == CT_INVALID || i->accepts_cargo[0] == CT_VALUABLES));
 
}
 

	
 
/** Names of the widgets of the view industry gui */
 
enum IndustryViewWidgets {
 
	IVW_CLOSEBOX = 0,
 
	IVW_CAPTION,
 
	IVW_STICKY,
 
	IVW_BACKGROUND,
 
	IVW_INSET,
 
	IVW_VIEWPORT,
 
	IVW_INFO,
 
	IVW_GOTO,
 
	IVW_SPACER,
 
	IVW_RESIZE,
 
};
 

	
 
class IndustryViewWindow : public Window
 
{
 
	byte editbox_line;        ///< The line clicked to open the edit box
 
	byte clicked_line;        ///< The line of the button that has been clicked
 
	byte clicked_button;      ///< The button that has been clicked (to raise)
 
	byte production_offset_y; ///< The offset of the production texts/buttons
 
	int info_height;          ///< Height needed for the #IVW_INFO panel
 

	
 
public:
 
	IndustryViewWindow(const WindowDesc *desc, WindowNumber window_number) : Window()
 
	{
 
		this->flags4 |= WF_DISABLE_VP_SCROLL;
 
		this->editbox_line = 0;
 
		this->clicked_line = 0;
 
		this->clicked_button = 0;
 
		this->info_height = WD_FRAMERECT_TOP + 2 * FONT_HEIGHT_NORMAL + WD_FRAMERECT_BOTTOM + 1; // Info panel has at least two lines text.
 

	
 
		this->InitNested(desc, window_number);
 
		NWidgetViewport *nvp = this->GetWidget<NWidgetViewport>(IVW_VIEWPORT);
 
		nvp->InitializeViewport(this, Industry::Get(window_number)->xy + TileDiffXY(1, 1), ZOOM_LVL_INDUSTRY);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 

	
 
		NWidgetBase *nwi = this->GetWidget<NWidgetBase>(IVW_INFO);
 
		uint expected = this->DrawInfo(nwi->pos_x, nwi->pos_x + nwi->current_x - 1, nwi->pos_y) - nwi->pos_y;
 
		if (expected > nwi->current_y - 1) {
 
			this->info_height = expected + 1;
 
			this->ReInit();
 
			return;
 
		}
 
	}
 

	
 
	/** Draw the text in the #IVW_INFO panel.
 
	 * @param left  Left edge of the panel.
 
	 * @param right Right edge of the panel.
 
	 * @param top   Top edge of the panel.
 
	 * @return Expected position of the bottom edge of the panel.
 
	 */
 
	int DrawInfo(uint left, uint right, uint top)
 
	{
 
		Industry *i = Industry::Get(this->window_number);
 
		const IndustrySpec *ind = GetIndustrySpec(i->type);
 
		int y = top + WD_FRAMERECT_TOP;
 
		bool first = true;
 
		bool has_accept = false;
 
		char cargo_suffix[3][512];
 

	
 
		if (HasBit(ind->callback_mask, CBM_IND_PRODUCTION_CARGO_ARRIVAL) || HasBit(ind->callback_mask, CBM_IND_PRODUCTION_256_TICKS)) {
 
			GetAllCargoSuffixes(0, CST_VIEW, i, i->type, ind, i->accepts_cargo, cargo_suffix);
 
			for (byte j = 0; j < lengthof(i->accepts_cargo); j++) {
 
				if (i->accepts_cargo[j] == CT_INVALID) continue;
 
				has_accept = true;
 
				if (first) {
 
					DrawString(left + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, y, STR_INDUSTRY_VIEW_WAITING_FOR_PROCESSING);
 
					y += FONT_HEIGHT_NORMAL;
 
					first = false;
 
				}
 
				SetDParam(0, i->accepts_cargo[j]);
 
				SetDParam(1, i->incoming_cargo_waiting[j]);
 
				SetDParamStr(2, cargo_suffix[j]);
 
				DrawString(left + WD_FRAMETEXT_LEFT, right - WD_FRAMERECT_RIGHT, y, STR_INDUSTRY_VIEW_WAITING_STOCKPILE_CARGO);
 
				y += FONT_HEIGHT_NORMAL;
 
			}
 
		} else {
 
			GetAllCargoSuffixes(0, CST_VIEW, i, i->type, ind, i->accepts_cargo, cargo_suffix);
 
			StringID str = STR_INDUSTRY_VIEW_REQUIRES_CARGO;
 
			byte p = 0;
 
			for (byte j = 0; j < lengthof(i->accepts_cargo); j++) {
 
				if (i->accepts_cargo[j] == CT_INVALID) continue;
 
				has_accept = true;
 
				if (p > 0) str++;
 
				SetDParam(p++, CargoSpec::Get(i->accepts_cargo[j])->name);
 
				SetDParamStr(p++, cargo_suffix[j]);
 
			}
 
			if (has_accept) {
 
				DrawString(left + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, y, str);
 
				y += FONT_HEIGHT_NORMAL;
 
			}
 
		}
 

	
 
		GetAllCargoSuffixes(3, CST_VIEW, i, i->type, ind, i->produced_cargo, cargo_suffix);
 
		first = true;
 
		for (byte j = 0; j < lengthof(i->produced_cargo); j++) {
 
			if (i->produced_cargo[j] == CT_INVALID) continue;
 
			if (first) {
 
				if (has_accept) y += WD_PAR_VSEP_WIDE;
 
				DrawString(left + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, y, STR_INDUSTRY_VIEW_PRODUCTION_LAST_MONTH_TITLE);
 
				y += FONT_HEIGHT_NORMAL;
 
				this->production_offset_y = y;
 
				first = false;
 
			}
 

	
 
			SetDParam(0, i->produced_cargo[j]);
 
			SetDParam(1, i->last_month_production[j]);
 
			SetDParamStr(2, cargo_suffix[j]);
 
			SetDParam(3, ToPercent8(i->last_month_pct_transported[j]));
 
			uint x = left + WD_FRAMETEXT_LEFT + (IsProductionAlterable(i) ? 30 : 0);
 
			DrawString(x, right - WD_FRAMERECT_RIGHT, y, STR_INDUSTRY_VIEW_TRANSPORTED);
 
			/* Let's put out those buttons.. */
 
			if (IsProductionAlterable(i)) {
 
				DrawArrowButtons(left + WD_FRAMETEXT_LEFT, y, COLOUR_YELLOW, (this->clicked_line == j + 1) ? this->clicked_button : 0,
 
						!IsProductionMinimum(i, j), !IsProductionMaximum(i, j));
 
			}
 
			y += FONT_HEIGHT_NORMAL;
 
		}
 

	
 
		/* Get the extra message for the GUI */
 
		if (HasBit(ind->callback_mask, CBM_IND_WINDOW_MORE_TEXT)) {
 
			uint16 callback_res = GetIndustryCallback(CBID_INDUSTRY_WINDOW_MORE_TEXT, 0, 0, i, i->type, i->xy);
 
			if (callback_res != CALLBACK_FAILED) {
 
				StringID message = GetGRFStringID(ind->grf_prop.grffile->grfid, 0xD000 + callback_res);
 
				if (message != STR_NULL && message != STR_UNDEFINED) {
 
					y += WD_PAR_VSEP_WIDE;
 

	
 
					PrepareTextRefStackUsage(6);
 
					/* Use all the available space left from where we stand up to the
 
					 * end of the window. We ALSO enlarge the window if needed, so we
 
					 * can 'go' wild with the bottom of the window. */
 
					y = DrawStringMultiLine(left + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, y, UINT16_MAX, message);
 
					StopTextRefStackUsage();
 
				}
 
			}
 
		}
 
		return y + WD_FRAMERECT_BOTTOM;
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		if (widget== IVW_CAPTION) SetDParam(0, this->window_number);
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		if (widget == IVW_INFO) size->height = this->info_height;
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		Industry *i;
 

	
 
		switch (widget) {
 
			case IVW_INFO: {
 
				i = Industry::Get(this->window_number);
 

	
 
				/* We should work if needed.. */
 
				if (!IsProductionAlterable(i)) return;
 
				uint x = pt.x;
 
				int line = (pt.y - this->production_offset_y) / FONT_HEIGHT_NORMAL;
 
				if (pt.y >= this->production_offset_y && IsInsideMM(line, 0, 2) && i->produced_cargo[line] != CT_INVALID) {
 
					NWidgetBase *nwi = this->GetWidget<NWidgetBase>(widget);
 
					uint left = nwi->pos_x + WD_FRAMETEXT_LEFT;
 
					uint right = nwi->pos_x + nwi->current_x - 1 - WD_FRAMERECT_RIGHT;
 
					if (IsInsideMM(x, left, left + 20) ) {
 
						/* Clicked buttons, decrease or increase production */
 
						if (x < left + 10) {
 
							if (IsProductionMinimum(i, line)) return;
 
							i->production_rate[line] = max(i->production_rate[line] / 2, 0);
 
						} else {
 
							/* a zero production industry is unlikely to give anything but zero, so push it a little bit */
 
							int new_prod = i->production_rate[line] == 0 ? 1 : i->production_rate[line] * 2;
 
							if (IsProductionMaximum(i, line)) return;
 
							i->production_rate[line] = minu(new_prod, 255);
 
						}
 

	
 
						UpdateIndustryProduction(i);
 
						this->SetDirty();
 
						this->flags4 |= WF_TIMEOUT_BEGIN;
 
						this->clicked_line = line + 1;
 
						this->clicked_button = (x < left + 10 ? 1 : 2);
 
					} else if (IsInsideMM(x, left + 30, right)) {
 
						/* clicked the text */
 
						this->editbox_line = line;
 
						SetDParam(0, i->production_rate[line] * 8);
 
						ShowQueryString(STR_JUST_INT, STR_CONFIG_GAME_PRODUCTION, 10, 100, this, CS_ALPHANUMERAL, QSF_NONE);
 
					}
 
				}
 
			} break;
 

	
 
			case IVW_GOTO:
 
				i = Industry::Get(this->window_number);
 
				if (_ctrl_pressed) {
 
					ShowExtraViewPortWindow(i->xy + TileDiffXY(1, 1));
 
				} else {
 
					ScrollMainWindowToTile(i->xy + TileDiffXY(1, 1));
 
				}
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnTimeout()
 
	{
 
		this->clicked_line = 0;
 
		this->clicked_button = 0;
 
		this->SetDirty();
 
	}
 

	
 
	virtual void OnResize()
 
	{
 
		if (this->viewport != NULL) {
 
			NWidgetViewport *nvp = this->GetWidget<NWidgetViewport>(IVW_VIEWPORT);
 
			nvp->UpdateViewportCoordinates(this);
 
		}
 
	}
 

	
 
	virtual void OnQueryTextFinished(char *str)
 
	{
 
		if (StrEmpty(str)) return;
 

	
 
		Industry *i = Industry::Get(this->window_number);
 
		int line = this->editbox_line;
 

	
 
		i->production_rate[line] = ClampU(atoi(str) / 8, 0, 255);
 
		UpdateIndustryProduction(i);
 
		this->SetDirty();
 
	}
 
};
 

	
 
static void UpdateIndustryProduction(Industry *i)
 
{
 
	for (byte j = 0; j < lengthof(i->produced_cargo); j++) {
 
		if (i->produced_cargo[j] != CT_INVALID) {
 
			i->last_month_production[j] = 8 * i->production_rate[j];
 
		}
 
	}
 
}
 

	
 
/** Widget definition of the view industy gui */
 
static const NWidgetPart _nested_industry_view_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_CREAM, IVW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_CREAM, IVW_CAPTION), SetDataTip(STR_INDUSTRY_VIEW_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_STICKYBOX, COLOUR_CREAM, IVW_STICKY),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_CREAM, IVW_BACKGROUND),
 
		NWidget(WWT_INSET, COLOUR_CREAM, IVW_INSET), SetPadding(2, 2, 2, 2),
 
			NWidget(NWID_VIEWPORT, INVALID_COLOUR, IVW_VIEWPORT), SetMinimalSize(254, 86), SetFill(true, false), SetPadding(1, 1, 1, 1), SetResize(1, 1),
 
			NWidget(NWID_VIEWPORT, INVALID_COLOUR, IVW_VIEWPORT), SetMinimalSize(254, 86), SetFill(1, 0), SetPadding(1, 1, 1, 1), SetResize(1, 1),
 
		EndContainer(),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_CREAM, IVW_INFO), SetMinimalSize(260, 2), SetResize(1, 0),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_CREAM, IVW_GOTO), SetMinimalSize(130, 12), SetDataTip(STR_BUTTON_LOCATION, STR_INDUSTRY_VIEW_LOCATION_TOOLTIP),
 
		NWidget(WWT_PANEL, COLOUR_CREAM, IVW_SPACER), /*SetMinimalSize(118, 12),*/ SetResize(1, 0), EndContainer(),
 
		NWidget(WWT_RESIZEBOX, COLOUR_CREAM, IVW_RESIZE),
 
	EndContainer(),
 
};
 

	
 
/** Window definition of the view industy gui */
 
static const WindowDesc _industry_view_desc(
 
	WDP_AUTO, WDP_AUTO, 260, 120,
 
	WC_INDUSTRY_VIEW, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE,
 
	_nested_industry_view_widgets, lengthof(_nested_industry_view_widgets)
 
);
 

	
 
void ShowIndustryViewWindow(int industry)
 
{
 
	AllocateWindowDescFront<IndustryViewWindow>(&_industry_view_desc, industry);
 
}
 

	
 
/** Names of the widgets of the industry directory gui */
 
enum IndustryDirectoryWidgets {
 
	IDW_CLOSEBOX = 0,
 
	IDW_CAPTION,
 
	IDW_STICKY,
 
	IDW_DROPDOWN_ORDER,
 
	IDW_DROPDOWN_CRITERIA,
 
	IDW_SPACER,
 
	IDW_INDUSTRY_LIST,
 
	IDW_SCROLLBAR,
 
	IDW_RESIZE,
 
};
 

	
 
/** Widget definition of the industy directory gui */
 
static const NWidgetPart _nested_industry_directory_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_BROWN, IDW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_BROWN, IDW_CAPTION), SetDataTip(STR_INDUSTRY_DIRECTORY_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_STICKYBOX, COLOUR_BROWN, IDW_STICKY),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(NWID_VERTICAL),
 
			NWidget(NWID_HORIZONTAL),
 
				NWidget(WWT_TEXTBTN, COLOUR_BROWN, IDW_DROPDOWN_ORDER), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER),
 
				NWidget(WWT_DROPDOWN, COLOUR_BROWN, IDW_DROPDOWN_CRITERIA), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_SORT_CRITERIAP),
 
				NWidget(WWT_PANEL, COLOUR_BROWN, IDW_SPACER), SetResize(1, 0),
 
				EndContainer(),
 
			EndContainer(),
 
			NWidget(WWT_PANEL, COLOUR_BROWN, IDW_INDUSTRY_LIST), SetDataTip(0x0, STR_INDUSTRY_DIRECTORY_LIST_CAPTION), SetResize(1, 1),
 
			EndContainer(),
 
		EndContainer(),
 
		NWidget(NWID_VERTICAL),
 
			NWidget(WWT_SCROLLBAR, COLOUR_BROWN, IDW_SCROLLBAR),
 
			NWidget(WWT_RESIZEBOX, COLOUR_BROWN, IDW_RESIZE),
 
		EndContainer(),
 
	EndContainer(),
 
};
 

	
 
typedef GUIList<const Industry*> GUIIndustryList;
 

	
 

	
 
/**
 
 * The list of industries.
 
 */
 
class IndustryDirectoryWindow : public Window {
 
protected:
 
	/* Runtime saved values */
 
	static Listing last_sorting;
 
	static const Industry *last_industry;
 

	
 
	/* Constants for sorting stations */
 
	static const StringID sorter_names[];
 
	static GUIIndustryList::SortFunction * const sorter_funcs[];
 

	
 
	GUIIndustryList industries;
 

	
 
	/** (Re)Build industries list */
 
	void BuildSortIndustriesList()
 
	{
 
		if (this->industries.NeedRebuild()) {
 
			this->industries.Clear();
 

	
 
			const Industry *i;
 
			FOR_ALL_INDUSTRIES(i) {
 
				*this->industries.Append() = i;
 
			}
 

	
 
			this->industries.Compact();
 
			this->industries.RebuildDone();
 
			this->vscroll.SetCount(this->industries.Length()); // Update scrollbar as well.
 
		}
 

	
 
		if (!this->industries.Sort()) return;
 
		IndustryDirectoryWindow::last_industry = NULL; // Reset name sorter sort cache
 
		this->SetWidgetDirty(IDW_INDUSTRY_LIST); // Set the modified widget dirty
 
	}
 

	
 
	/**
 
	 * Returns percents of cargo transported if industry produces this cargo, else -1
 
	 *
 
	 * @param i industry to check
 
	 * @param id cargo slot
 
	 * @return percents of cargo transported, or -1 if industry doesn't use this cargo slot
 
	 */
 
	static inline int GetCargoTransportedPercentsIfValid(const Industry *i, uint id)
 
	{
 
		assert(id < lengthof(i->produced_cargo));
 

	
 
		if (i->produced_cargo[id] == CT_INVALID) return 101;
 
		return ToPercent8(i->last_month_pct_transported[id]);
 
	}
 

	
 
	/**
 
	 * Returns value representing industry's transported cargo
 
	 *  percentage for industry sorting
 
	 *
 
	 * @param i industry to check
 
	 * @return value used for sorting
 
	 */
 
	static int GetCargoTransportedSortValue(const Industry *i)
 
	{
 
		int p1 = GetCargoTransportedPercentsIfValid(i, 0);
 
		int p2 = GetCargoTransportedPercentsIfValid(i, 1);
 

	
 
		if (p1 > p2) Swap(p1, p2); // lower value has higher priority
 

	
 
		return (p1 << 8) + p2;
 
	}
 

	
 
	/** Sort industries by name */
 
	static int CDECL IndustryNameSorter(const Industry * const *a, const Industry * const *b)
 
	{
 
		static char buf_cache[96];
 
		static char buf[96];
 

	
 
		SetDParam(0, (*a)->town->index);
 
		GetString(buf, STR_TOWN_NAME, lastof(buf));
 

	
 
		if (*b != last_industry) {
 
			last_industry = *b;
 
			SetDParam(0, (*b)->town->index);
 
			GetString(buf_cache, STR_TOWN_NAME, lastof(buf_cache));
 
		}
 

	
 
		return strcmp(buf, buf_cache);
 
	}
 

	
 
	/** Sort industries by type and name */
 
	static int CDECL IndustryTypeSorter(const Industry * const *a, const Industry * const *b)
 
	{
 
		int r = (*a)->type - (*b)->type;
 
		return (r == 0) ? IndustryNameSorter(a, b) : r;
 
	}
 

	
 
	/** Sort industries by production and name */
 
	static int CDECL IndustryProductionSorter(const Industry * const *a, const Industry * const *b)
 
	{
 
		int r = 0;
 

	
 
		if ((*a)->produced_cargo[0] == CT_INVALID) {
 
			if ((*b)->produced_cargo[0] != CT_INVALID) return -1;
 
		} else {
 
			if ((*b)->produced_cargo[0] == CT_INVALID) return 1;
 

	
 
			r = ((*a)->last_month_production[0] + (*a)->last_month_production[1]) -
 
			    ((*b)->last_month_production[0] + (*b)->last_month_production[1]);
 
		}
 

	
 
		return (r == 0) ? IndustryNameSorter(a, b) : r;
 
	}
 

	
 
	/** Sort industries by transported cargo and name */
 
	static int CDECL IndustryTransportedCargoSorter(const Industry * const *a, const Industry * const *b)
 
	{
 
		int r = GetCargoTransportedSortValue(*a) - GetCargoTransportedSortValue(*b);
 
		return (r == 0) ? IndustryNameSorter(a, b) : r;
 
	}
 

	
 
	/**
 
	 * Get the StringID to draw and set the appropriate DParams.
 
	 * @param i the industry to get the StringID of.
 
	 * @return the StringID.
 
	 */
 
	StringID GetIndustryString(const Industry *i) const
 
	{
 
		const IndustrySpec *indsp = GetIndustrySpec(i->type);
 
		byte p = 0;
 

	
 
		/* Industry name */
 
		SetDParam(p++, i->index);
 

	
 
		char cargo_suffix[lengthof(i->produced_cargo)][512];
 
		GetAllCargoSuffixes(3, CST_DIR, i, i->type, indsp, i->produced_cargo, cargo_suffix);
 

	
 
		/* Industry productions */
 
		for (byte j = 0; j < lengthof(i->produced_cargo); j++) {
 
			if (i->produced_cargo[j] == CT_INVALID) continue;
 
			SetDParam(p++, i->produced_cargo[j]);
 
			SetDParam(p++, i->last_month_production[j]);
 
			SetDParamStr(p++, cargo_suffix[j]);
 
		}
 

	
 
		/* Transported productions */
 
		for (byte j = 0; j < lengthof(i->produced_cargo); j++) {
 
			if (i->produced_cargo[j] == CT_INVALID) continue;
 
			SetDParam(p++, ToPercent8(i->last_month_pct_transported[j]));
 
		}
 

	
 
		/* Drawing the right string */
 
		switch (p) {
 
			case 1:  return STR_INDUSTRY_DIRECTORY_ITEM_NOPROD;
 
			case 5:  return STR_INDUSTRY_DIRECTORY_ITEM;
 
			default: return STR_INDUSTRY_DIRECTORY_ITEM_TWO;
 
		}
 
	}
 

	
 
public:
 
	IndustryDirectoryWindow(const WindowDesc *desc, WindowNumber number) : Window()
 
	{
 
		this->industries.SetListing(this->last_sorting);
 
		this->industries.SetSortFuncs(IndustryDirectoryWindow::sorter_funcs);
 
		this->industries.ForceRebuild();
 
		this->BuildSortIndustriesList();
 

	
 
		this->InitNested(desc, 0);
 
	}
 

	
 
	~IndustryDirectoryWindow()
 
	{
 
		this->last_sorting = this->industries.GetListing();
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		if (widget == IDW_DROPDOWN_CRITERIA) SetDParam(0, IndustryDirectoryWindow::sorter_names[this->industries.SortType()]);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case IDW_DROPDOWN_ORDER:
 
				this->DrawSortButtonState(widget, this->industries.IsDescSortOrder() ? SBS_DOWN : SBS_UP);
 
				break;
 

	
 
			case IDW_INDUSTRY_LIST: {
 
				int n = 0;
 
				int y = r.top + WD_FRAMERECT_TOP;
 
				if (this->industries.Length() == 0) {
 
					DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_INDUSTRY_DIRECTORY_NONE);
 
					break;
 
				}
 
				for (uint i = this->vscroll.GetPosition(); i < this->industries.Length(); i++) {
 
					DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, this->GetIndustryString(this->industries[i]));
 

	
 
					y += this->resize.step_height;
 
					if (++n == this->vscroll.GetCapacity()) break; // max number of industries in 1 window
 
				}
 
			} break;
 
		}
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		switch (widget) {
 
			case IDW_DROPDOWN_ORDER: {
 
				Dimension d = GetStringBoundingBox(this->GetWidget<NWidgetCore>(widget)->widget_data);
 
				d.width += padding.width + WD_SORTBUTTON_ARROW_WIDTH * 2; // Doubled since the word is centered, also looks nice.
 
				d.height += padding.height;
 
				*size = maxdim(*size, d);
 
				break;
 
			}
 

	
 
			case IDW_DROPDOWN_CRITERIA: {
 
				Dimension d = {0, 0};
 
				for (uint i = 0; IndustryDirectoryWindow::sorter_names[i] != INVALID_STRING_ID; i++) {
 
					d = maxdim(d, GetStringBoundingBox(IndustryDirectoryWindow::sorter_names[i]));
 
				}
 
				d.width += padding.width;
 
				d.height += padding.height;
 
				*size = maxdim(*size, d);
 
				break;
 
			}
 

	
 
			case IDW_INDUSTRY_LIST: {
 
				Dimension d = GetStringBoundingBox(STR_INDUSTRY_DIRECTORY_NONE);
 
				for (uint i = 0; i < this->industries.Length(); i++) {
 
					d = maxdim(d, GetStringBoundingBox(this->GetIndustryString(this->industries[i])));
 
				}
 
				resize->height = d.height;
 
				d.width += padding.width + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
 
				d.height += padding.height + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
 
				*size = maxdim(*size, d);
 
				break;
 
			}
 
		}
 
	}
 

	
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case IDW_DROPDOWN_ORDER:
 
				this->industries.ToggleSortOrder();
 
				this->SetDirty();
 
				break;
 

	
 
			case IDW_DROPDOWN_CRITERIA:
 
				ShowDropDownMenu(this, IndustryDirectoryWindow::sorter_names, this->industries.SortType(), IDW_DROPDOWN_CRITERIA, 0, 0);
 
				break;
 

	
 
			case IDW_INDUSTRY_LIST: {
 
				int y = (pt.y - this->GetWidget<NWidgetBase>(widget)->pos_y - WD_FRAMERECT_TOP) / this->resize.step_height;
 
				uint16 p;
 

	
 
				if (!IsInsideMM(y, 0, this->vscroll.GetCapacity())) return;
 
				p = y + this->vscroll.GetPosition();
 
				if (p < this->industries.Length()) {
 
					if (_ctrl_pressed) {
 
						ShowExtraViewPortWindow(this->industries[p]->xy);
 
					} else {
 
						ScrollMainWindowToTile(this->industries[p]->xy);
 
					}
 
				}
 
			} break;
 
		}
 
	}
 

	
 
	virtual void OnDropdownSelect(int widget, int index)
 
	{
 
		if (this->industries.SortType() != index) {
 
			this->industries.SetSortType(index);
 
			this->BuildSortIndustriesList();
 
		}
 
	}
 

	
 
	virtual void OnResize()
 
	{
 
		this->vscroll.SetCapacity(this->GetWidget<NWidgetBase>(IDW_INDUSTRY_LIST)->current_y / this->resize.step_height);
 
	}
 

	
 
	virtual void OnHundredthTick()
 
	{
 
		this->industries.ForceResort();
 
		this->BuildSortIndustriesList();
 
	}
 

	
 
	virtual void OnInvalidateData(int data)
 
	{
 
		if (data == 0) {
 
			this->industries.ForceRebuild();
 
		} else {
 
			this->industries.ForceResort();
 
		}
 
		this->BuildSortIndustriesList();
 
	}
 
};
 

	
 
Listing IndustryDirectoryWindow::last_sorting = {false, 0};
 
const Industry *IndustryDirectoryWindow::last_industry = NULL;
 

	
 
/* Availible station sorting functions */
 
GUIIndustryList::SortFunction * const IndustryDirectoryWindow::sorter_funcs[] = {
 
	&IndustryNameSorter,
 
	&IndustryTypeSorter,
 
	&IndustryProductionSorter,
 
	&IndustryTransportedCargoSorter
 
};
 

	
 
/* Names of the sorting functions */
 
const StringID IndustryDirectoryWindow::sorter_names[] = {
 
	STR_SORT_BY_NAME,
 
	STR_SORT_BY_TYPE,
 
	STR_SORT_BY_PRODUCTION,
 
	STR_SORT_BY_TRANSPORTED,
 
	INVALID_STRING_ID
 
};
 

	
 

	
 
/** Window definition of the industy directory gui */
 
static const WindowDesc _industry_directory_desc(
 
	WDP_AUTO, WDP_AUTO, 428, 190,
 
	WC_INDUSTRY_DIRECTORY, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE,
 
	_nested_industry_directory_widgets, lengthof(_nested_industry_directory_widgets)
 
);
 

	
 
void ShowIndustryDirectory()
 
{
 
	AllocateWindowDescFront<IndustryDirectoryWindow>(&_industry_directory_desc, 0);
 
}
src/intro_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file intro_gui.cpp The main menu GUI. */
 

	
 
#include "stdafx.h"
 
#include "openttd.h"
 
#include "gui.h"
 
#include "window_gui.h"
 
#include "textbuf_gui.h"
 
#include "network/network.h"
 
#include "genworld.h"
 
#include "network/network_gui.h"
 
#include "network/network_content.h"
 
#include "landscape_type.h"
 
#include "strings_func.h"
 
#include "window_func.h"
 
#include "fios.h"
 
#include "functions.h"
 
#include "ai/ai_gui.hpp"
 
#include "gfx_func.h"
 

	
 
#include "table/strings.h"
 
#include "table/sprites.h"
 

	
 
static inline void SetNewLandscapeType(byte landscape)
 
{
 
	_settings_newgame.game_creation.landscape = landscape;
 
	SetWindowClassesDirty(WC_SELECT_GAME);
 
}
 

	
 
enum SelectGameIntroWidgets {
 
	SGI_CLOSE,
 
	SGI_CAPTION,
 
	SGI_GENERATE_GAME,
 
	SGI_LOAD_GAME,
 
	SGI_PLAY_SCENARIO,
 
	SGI_PLAY_HEIGHTMAP,
 
	SGI_EDIT_SCENARIO,
 
	SGI_PLAY_NETWORK,
 
	SGI_TEMPERATE_LANDSCAPE,
 
	SGI_ARCTIC_LANDSCAPE,
 
	SGI_TROPIC_LANDSCAPE,
 
	SGI_TOYLAND_LANDSCAPE,
 
	SGI_OPTIONS,
 
	SGI_DIFFICULTIES,
 
	SGI_SETTINGS_OPTIONS,
 
	SGI_GRF_SETTINGS,
 
	SGI_CONTENT_DOWNLOAD,
 
	SGI_AI_SETTINGS,
 
	SGI_EXIT,
 
};
 

	
 
struct SelectGameWindow : public Window {
 

	
 
	SelectGameWindow(const WindowDesc *desc) : Window()
 
	{
 
		this->InitNested(desc);
 
		this->LowerWidget(_settings_newgame.game_creation.landscape + SGI_TEMPERATE_LANDSCAPE);
 
		this->SetLandscapeButtons();
 
	}
 

	
 
	void SetLandscapeButtons()
 
	{
 
		this->SetWidgetLoweredState(SGI_TEMPERATE_LANDSCAPE, _settings_newgame.game_creation.landscape == LT_TEMPERATE);
 
		this->SetWidgetLoweredState(SGI_ARCTIC_LANDSCAPE,    _settings_newgame.game_creation.landscape == LT_ARCTIC);
 
		this->SetWidgetLoweredState(SGI_TROPIC_LANDSCAPE,    _settings_newgame.game_creation.landscape == LT_TROPIC);
 
		this->SetWidgetLoweredState(SGI_TOYLAND_LANDSCAPE,   _settings_newgame.game_creation.landscape == LT_TOYLAND);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		if (widget == SGI_DIFFICULTIES) SetDParam(0, STR_DIFFICULTY_LEVEL_EASY + _settings_newgame.difficulty.diff_level);
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		if (widget != SGI_DIFFICULTIES) return;
 

	
 
		Dimension textdim = {0, 0};
 
		for (uint i = STR_DIFFICULTY_LEVEL_EASY; i <= STR_DIFFICULTY_LEVEL_CUSTOM; i++) {
 
			SetDParam(0, i);
 
			textdim = maxdim(textdim, GetStringBoundingBox(STR_INTRO_DIFFICULTY));
 
		}
 
		textdim.width += padding.width;
 
		textdim.height += padding.height;
 
		*size = maxdim(*size, textdim);
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
#ifdef ENABLE_NETWORK
 
		/* Do not create a network server when you (just) have closed one of the game
 
		 * creation/load windows for the network server. */
 
		if (IsInsideMM(widget, SGI_GENERATE_GAME, SGI_EDIT_SCENARIO + 1)) _is_network_server = false;
 
#endif /* ENABLE_NETWORK */
 

	
 
		switch (widget) {
 
			case SGI_GENERATE_GAME:
 
				if (_ctrl_pressed) {
 
					StartNewGameWithoutGUI(GENERATE_NEW_SEED);
 
				} else {
 
					ShowGenerateLandscape();
 
				}
 
				break;
 

	
 
			case SGI_LOAD_GAME:      ShowSaveLoadDialog(SLD_LOAD_GAME); break;
 
			case SGI_PLAY_SCENARIO:  ShowSaveLoadDialog(SLD_LOAD_SCENARIO); break;
 
			case SGI_PLAY_HEIGHTMAP: ShowSaveLoadDialog(SLD_LOAD_HEIGHTMAP); break;
 
			case SGI_EDIT_SCENARIO:  StartScenarioEditor(); break;
 

	
 
			case SGI_PLAY_NETWORK:
 
				if (!_network_available) {
 
					ShowErrorMessage(STR_NETWORK_ERROR_NOTAVAILABLE, INVALID_STRING_ID, 0, 0);
 
				} else {
 
					ShowNetworkGameWindow();
 
				}
 
				break;
 

	
 
			case SGI_TEMPERATE_LANDSCAPE: case SGI_ARCTIC_LANDSCAPE:
 
			case SGI_TROPIC_LANDSCAPE: case SGI_TOYLAND_LANDSCAPE:
 
				this->RaiseWidget(_settings_newgame.game_creation.landscape + SGI_TEMPERATE_LANDSCAPE);
 
				SetNewLandscapeType(widget - SGI_TEMPERATE_LANDSCAPE);
 
				this->SetLandscapeButtons();
 
				break;
 

	
 
			case SGI_OPTIONS:         ShowGameOptions(); break;
 
			case SGI_DIFFICULTIES:    ShowGameDifficulty(); break;
 
			case SGI_SETTINGS_OPTIONS:ShowGameSettings(); break;
 
			case SGI_GRF_SETTINGS:    ShowNewGRFSettings(true, true, false, &_grfconfig_newgame); break;
 
			case SGI_CONTENT_DOWNLOAD:
 
				if (!_network_available) {
 
					ShowErrorMessage(STR_NETWORK_ERROR_NOTAVAILABLE, INVALID_STRING_ID, 0, 0);
 
				} else {
 
					ShowNetworkContentListWindow();
 
				}
 
				break;
 
			case SGI_AI_SETTINGS:     ShowAIConfigWindow(); break;
 
			case SGI_EXIT:            HandleExitGameRequest(); break;
 
		}
 
	}
 
};
 

	
 
static const NWidgetPart _nested_select_game_widgets[] = {
 
	NWidget(WWT_CAPTION, COLOUR_BROWN, SGI_CLOSE), SetDataTip(STR_INTRO_CAPTION, STR_NULL),
 
	NWidget(WWT_PANEL, COLOUR_BROWN, SGI_CAPTION),
 

	
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 8),
 

	
 
		/* 'generate game' and 'load game' buttons */
 
		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_ORANGE, SGI_GENERATE_GAME), SetMinimalSize(158, 12),
 
								SetDataTip(STR_INTRO_NEW_GAME, STR_INTRO_TOOLTIP_NEW_GAME), SetPadding(0, 0, 0, 10), SetFill(true, false),
 
								SetDataTip(STR_INTRO_NEW_GAME, STR_INTRO_TOOLTIP_NEW_GAME), SetPadding(0, 0, 0, 10), SetFill(1, 0),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_ORANGE, SGI_LOAD_GAME), SetMinimalSize(158, 12),
 
								SetDataTip(STR_INTRO_LOAD_GAME, STR_INTRO_TOOLTIP_LOAD_GAME), SetPadding(0, 10, 0, 0), SetFill(true, false),
 
								SetDataTip(STR_INTRO_LOAD_GAME, STR_INTRO_TOOLTIP_LOAD_GAME), SetPadding(0, 10, 0, 0), SetFill(1, 0),
 
		EndContainer(),
 

	
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 6),
 

	
 
		/* 'play scenario' and 'play heightmap' buttons */
 
		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_ORANGE, SGI_PLAY_SCENARIO), SetMinimalSize(158, 12),
 
								SetDataTip(STR_INTRO_PLAY_SCENARIO, STR_INTRO_TOOLTIP_PLAY_SCENARIO), SetPadding(0, 0, 0, 10), SetFill(true, false),
 
								SetDataTip(STR_INTRO_PLAY_SCENARIO, STR_INTRO_TOOLTIP_PLAY_SCENARIO), SetPadding(0, 0, 0, 10), SetFill(1, 0),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_ORANGE, SGI_PLAY_HEIGHTMAP), SetMinimalSize(158, 12),
 
								SetDataTip(STR_INTRO_PLAY_HEIGHTMAP, STR_INTRO_TOOLTIP_PLAY_HEIGHTMAP), SetPadding(0, 10, 0, 0), SetFill(true, false),
 
								SetDataTip(STR_INTRO_PLAY_HEIGHTMAP, STR_INTRO_TOOLTIP_PLAY_HEIGHTMAP), SetPadding(0, 10, 0, 0), SetFill(1, 0),
 
		EndContainer(),
 

	
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 6),
 

	
 
		/* 'edit scenario' and 'play multiplayer' buttons */
 
		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_ORANGE, SGI_EDIT_SCENARIO), SetMinimalSize(158, 12),
 
								SetDataTip(STR_INTRO_SCENARIO_EDITOR, STR_INTRO_TOOLTIP_SCENARIO_EDITOR), SetPadding(0, 0, 0, 10), SetFill(true, false),
 
								SetDataTip(STR_INTRO_SCENARIO_EDITOR, STR_INTRO_TOOLTIP_SCENARIO_EDITOR), SetPadding(0, 0, 0, 10), SetFill(1, 0),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_ORANGE, SGI_PLAY_NETWORK), SetMinimalSize(158, 12),
 
								SetDataTip(STR_INTRO_MULTIPLAYER, STR_INTRO_TOOLTIP_MULTIPLAYER), SetPadding(0, 10, 0, 0), SetFill(true, false),
 
								SetDataTip(STR_INTRO_MULTIPLAYER, STR_INTRO_TOOLTIP_MULTIPLAYER), SetPadding(0, 10, 0, 0), SetFill(1, 0),
 
		EndContainer(),
 

	
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 7),
 

	
 
		/* climate selection buttons */
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(NWID_SPACER), SetMinimalSize(10, 0), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetMinimalSize(10, 0), SetFill(1, 0),
 
			NWidget(WWT_IMGBTN_2, COLOUR_ORANGE, SGI_TEMPERATE_LANDSCAPE), SetMinimalSize(77, 55),
 
								SetDataTip(SPR_SELECT_TEMPERATE, STR_INTRO_TOOLTIP_TEMPERATE),
 
			NWidget(NWID_SPACER), SetMinimalSize(3, 0), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetMinimalSize(3, 0), SetFill(1, 0),
 
			NWidget(WWT_IMGBTN_2, COLOUR_ORANGE, SGI_ARCTIC_LANDSCAPE), SetMinimalSize(77, 55),
 
								SetDataTip(SPR_SELECT_SUB_ARCTIC, STR_INTRO_TOOLTIP_SUB_ARCTIC_LANDSCAPE),
 
			NWidget(NWID_SPACER), SetMinimalSize(3, 0), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetMinimalSize(3, 0), SetFill(1, 0),
 
			NWidget(WWT_IMGBTN_2, COLOUR_ORANGE, SGI_TROPIC_LANDSCAPE), SetMinimalSize(77, 55),
 
								SetDataTip(SPR_SELECT_SUB_TROPICAL, STR_INTRO_TOOLTIP_SUB_TROPICAL_LANDSCAPE),
 
			NWidget(NWID_SPACER), SetMinimalSize(3, 0), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetMinimalSize(3, 0), SetFill(1, 0),
 
			NWidget(WWT_IMGBTN_2, COLOUR_ORANGE, SGI_TOYLAND_LANDSCAPE), SetMinimalSize(77, 55),
 
								SetDataTip(SPR_SELECT_TOYLAND, STR_INTRO_TOOLTIP_TOYLAND_LANDSCAPE),
 
			NWidget(NWID_SPACER), SetMinimalSize(10, 0), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetMinimalSize(10, 0), SetFill(1, 0),
 
		EndContainer(),
 

	
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 7),
 

	
 
		/* 'game options' and 'difficulty options' buttons */
 
		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_ORANGE, SGI_OPTIONS), SetMinimalSize(158, 12),
 
								SetDataTip(STR_INTRO_GAME_OPTIONS, STR_INTRO_TOOLTIP_GAME_OPTIONS), SetPadding(0, 0, 0, 10), SetFill(true, false),
 
								SetDataTip(STR_INTRO_GAME_OPTIONS, STR_INTRO_TOOLTIP_GAME_OPTIONS), SetPadding(0, 0, 0, 10), SetFill(1, 0),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_ORANGE, SGI_DIFFICULTIES), SetMinimalSize(158, 12),
 
								SetDataTip(STR_INTRO_DIFFICULTY, STR_INTRO_TOOLTIP_DIFFICULTY_OPTIONS), SetPadding(0, 10, 0, 0), SetFill(true, false),
 
								SetDataTip(STR_INTRO_DIFFICULTY, STR_INTRO_TOOLTIP_DIFFICULTY_OPTIONS), SetPadding(0, 10, 0, 0), SetFill(1, 0),
 
		EndContainer(),
 

	
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 6),
 

	
 
		/* 'advanced settings' and 'newgrf settings' buttons */
 
		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_ORANGE, SGI_SETTINGS_OPTIONS), SetMinimalSize(158, 12),
 
								SetDataTip(STR_INTRO_ADVANCED_SETTINGS, STR_INTRO_TOOLTIP_ADVANCED_SETTINGS), SetPadding(0, 0, 0, 10), SetFill(true, false),
 
								SetDataTip(STR_INTRO_ADVANCED_SETTINGS, STR_INTRO_TOOLTIP_ADVANCED_SETTINGS), SetPadding(0, 0, 0, 10), SetFill(1, 0),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_ORANGE, SGI_GRF_SETTINGS), SetMinimalSize(158, 12),
 
								SetDataTip(STR_INTRO_NEWGRF_SETTINGS, STR_INTRO_TOOLTIP_NEWGRF_SETTINGS), SetPadding(0, 10, 0, 0), SetFill(true, false),
 
								SetDataTip(STR_INTRO_NEWGRF_SETTINGS, STR_INTRO_TOOLTIP_NEWGRF_SETTINGS), SetPadding(0, 10, 0, 0), SetFill(1, 0),
 
		EndContainer(),
 

	
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 6),
 

	
 
		/* 'online content' and 'ai settings' buttons */
 
		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_ORANGE, SGI_CONTENT_DOWNLOAD), SetMinimalSize(158, 12),
 
								SetDataTip(STR_INTRO_ONLINE_CONTENT, STR_INTRO_TOOLTIP_ONLINE_CONTENT), SetPadding(0, 0, 0, 10), SetFill(true, false),
 
								SetDataTip(STR_INTRO_ONLINE_CONTENT, STR_INTRO_TOOLTIP_ONLINE_CONTENT), SetPadding(0, 0, 0, 10), SetFill(1, 0),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_ORANGE, SGI_AI_SETTINGS), SetMinimalSize(158, 12),
 
								SetDataTip(STR_INTRO_AI_SETTINGS, STR_INTRO_TOOLTIP_AI_SETTINGS), SetPadding(0, 10, 0, 0), SetFill(true, false),
 
								SetDataTip(STR_INTRO_AI_SETTINGS, STR_INTRO_TOOLTIP_AI_SETTINGS), SetPadding(0, 10, 0, 0), SetFill(1, 0),
 
		EndContainer(),
 

	
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 6),
 

	
 
		/* 'exit program' button */
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(NWID_SPACER), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_ORANGE, SGI_EXIT), SetMinimalSize(128, 12),
 
								SetDataTip(STR_INTRO_QUIT, STR_INTRO_TOOLTIP_QUIT),
 
			NWidget(NWID_SPACER), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
		EndContainer(),
 

	
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 8),
 

	
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _select_game_desc(
 
	WDP_CENTER, WDP_CENTER, 336, 213,
 
	WC_SELECT_GAME, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
 
	_nested_select_game_widgets, lengthof(_nested_select_game_widgets)
 
);
 

	
 
void ShowSelectGameWindow()
 
{
 
	new SelectGameWindow(&_select_game_desc);
 
}
 

	
 
static void AskExitGameCallback(Window *w, bool confirmed)
 
{
 
	if (confirmed) _exit_game = true;
 
}
 

	
 
void AskExitGame()
 
{
 
#if defined(_WIN32)
 
		SetDParam(0, STR_OSNAME_WINDOWS);
 
#elif defined(__APPLE__)
 
		SetDParam(0, STR_OSNAME_OSX);
 
#elif defined(__BEOS__)
 
		SetDParam(0, STR_OSNAME_BEOS);
 
#elif defined(__HAIKU__)
 
		SetDParam(0, STR_OSNAME_HAIKU);
 
#elif defined(__MORPHOS__)
 
		SetDParam(0, STR_OSNAME_MORPHOS);
 
#elif defined(__AMIGA__)
 
		SetDParam(0, STR_OSNAME_AMIGAOS);
 
#elif defined(__OS2__)
 
		SetDParam(0, STR_OSNAME_OS2);
 
#elif defined(SUNOS)
 
		SetDParam(0, STR_OSNAME_SUNOS);
 
#elif defined(DOS)
 
		SetDParam(0, STR_OSNAME_DOS);
 
#else
 
		SetDParam(0, STR_OSNAME_UNIX);
 
#endif
 
	ShowQuery(
 
		STR_QUIT_CAPTION,
 
		STR_QUIT_ARE_YOU_SURE_YOU_WANT_TO_EXIT_OPENTTD,
 
		NULL,
 
		AskExitGameCallback
 
	);
 
}
 

	
 

	
 
static void AskExitToGameMenuCallback(Window *w, bool confirmed)
 
{
 
	if (confirmed) _switch_mode = SM_MENU;
 
}
 

	
 
void AskExitToGameMenu()
 
{
 
	ShowQuery(
 
		STR_ABANDON_GAME_CAPTION,
 
		(_game_mode != GM_EDITOR) ? STR_ABANDON_GAME_QUERY : STR_ABANDOM_SCENARIO_QUERY,
 
		NULL,
 
		AskExitToGameMenuCallback
 
	);
 
}
src/misc_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file misc_gui.cpp GUIs for a number of misc windows. */
 

	
 
#include "stdafx.h"
 
#include "openttd.h"
 
#include "landscape.h"
 
#include "newgrf_text.h"
 
#include "saveload/saveload.h"
 
#include "gui.h"
 
#include "station_gui.h"
 
#include "viewport_func.h"
 
#include "gfx_func.h"
 
#include "station_func.h"
 
#include "command_func.h"
 
#include "company_func.h"
 
#include "town.h"
 
#include "network/network.h"
 
#include "network/network_content.h"
 
#include "company_base.h"
 
#include "texteff.hpp"
 
#include "cargotype.h"
 
#include "company_manager_face.h"
 
#include "strings_func.h"
 
#include "fileio_func.h"
 
#include "fios.h"
 
#include "zoom_func.h"
 
#include "window_func.h"
 
#include "tilehighlight_func.h"
 
#include "querystring_gui.h"
 

	
 
#include "table/strings.h"
 

	
 

	
 
/**
 
 * Try to retrive the current clipboard contents.
 
 *
 
 * @note OS-specific funtion.
 
 * @return True if some text could be retrived.
 
 */
 
bool GetClipboardContents(char *buffer, size_t buff_len);
 

	
 

	
 
/* Variables to display file lists */
 
SaveLoadDialogMode _saveload_mode;
 

	
 

	
 
static bool _fios_path_changed;
 
static bool _savegame_sort_dirty;
 
int _caret_timer;
 

	
 
/** Widgets for the land info window. */
 
enum LandInfoWidgets {
 
	LIW_CLOSE,      ///< Close the window
 
	LIW_CAPTION,    ///< Title bar of the window
 
	LIW_BACKGROUND, ///< Background to draw on
 
};
 

	
 
static const NWidgetPart _nested_land_info_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, LIW_CLOSE),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, LIW_CAPTION), SetDataTip(STR_LAND_AREA_INFORMATION_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, LIW_BACKGROUND), EndContainer(),
 
};
 

	
 
static const WindowDesc _land_info_desc(
 
	WDP_AUTO, WDP_AUTO, 0, 0,
 
	WC_LAND_INFO, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
 
	_nested_land_info_widgets, lengthof(_nested_land_info_widgets)
 
);
 

	
 
class LandInfoWindow : public Window {
 
	enum {
 
		LAND_INFO_CENTERED_LINES   = 12,                       ///< Up to 12 centered lines
 
		LAND_INFO_MULTICENTER_LINE = LAND_INFO_CENTERED_LINES, ///< One multicenter line
 
		LAND_INFO_LINE_END,
 

	
 
		LAND_INFO_LINE_BUFF_SIZE = 512,
 
	};
 

	
 
public:
 
	char landinfo_data[LAND_INFO_LINE_END][LAND_INFO_LINE_BUFF_SIZE];
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		if (widget != LIW_BACKGROUND) return;
 

	
 
		uint y = r.top + WD_TEXTPANEL_TOP;
 
		for (uint i = 0; i < LAND_INFO_CENTERED_LINES; i++) {
 
			if (StrEmpty(this->landinfo_data[i])) break;
 

	
 
			DrawString(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, y, this->landinfo_data[i], i == 0 ? TC_LIGHT_BLUE : TC_FROMSTRING, SA_CENTER);
 
			y += FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL;
 
			if (i == 0) y += 4;
 
		}
 

	
 
		if (!StrEmpty(this->landinfo_data[LAND_INFO_MULTICENTER_LINE])) {
 
			SetDParamStr(0, this->landinfo_data[LAND_INFO_MULTICENTER_LINE]);
 
			DrawStringMultiLine(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, y, r.bottom - WD_TEXTPANEL_BOTTOM, STR_JUST_RAW_STRING, TC_FROMSTRING, SA_CENTER);
 
		}
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		if (widget != LIW_BACKGROUND) return;
 

	
 
		size->height = WD_TEXTPANEL_TOP + WD_TEXTPANEL_BOTTOM;
 
		for (uint i = 0; i < LAND_INFO_CENTERED_LINES; i++) {
 
			if (StrEmpty(this->landinfo_data[i])) break;
 

	
 
			uint width = GetStringBoundingBox(this->landinfo_data[i]).width + WD_FRAMETEXT_LEFT + WD_FRAMETEXT_RIGHT;
 
			size->width = max(size->width, width);
 

	
 
			size->height += FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL;
 
			if (i == 0) size->height += 4;
 
		}
 

	
 
		if (!StrEmpty(this->landinfo_data[LAND_INFO_MULTICENTER_LINE])) {
 
			uint width = GetStringBoundingBox(this->landinfo_data[LAND_INFO_MULTICENTER_LINE]).width + WD_FRAMETEXT_LEFT + WD_FRAMETEXT_RIGHT;
 
			size->width = max(size->width, min(300u, width));
 
			SetDParamStr(0, this->landinfo_data[LAND_INFO_MULTICENTER_LINE]);
 
			size->height += GetStringHeight(STR_JUST_RAW_STRING, size->width - WD_FRAMETEXT_LEFT - WD_FRAMETEXT_RIGHT);
 
		}
 
	}
 

	
 
	LandInfoWindow(TileIndex tile) : Window() {
 
		Town *t = ClosestTownFromTile(tile, _settings_game.economy.dist_local_authority);
 

	
 
		/* Because build_date is not set yet in every TileDesc, we make sure it is empty */
 
		TileDesc td;
 

	
 
		td.build_date = INVALID_DATE;
 

	
 
		/* Most tiles have only one owner, but
 
		 *  - drivethrough roadstops can be build on town owned roads (up to 2 owners) and
 
		 *  - roads can have up to four owners (railroad, road, tram, 3rd-roadtype "highway").
 
		 */
 
		td.owner_type[0] = STR_LAND_AREA_INFORMATION_OWNER; // At least one owner is displayed, though it might be "N/A".
 
		td.owner_type[1] = STR_NULL;       // STR_NULL results in skipping the owner
 
		td.owner_type[2] = STR_NULL;
 
		td.owner_type[3] = STR_NULL;
 
		td.owner[0] = OWNER_NONE;
 
		td.owner[1] = OWNER_NONE;
 
		td.owner[2] = OWNER_NONE;
 
		td.owner[3] = OWNER_NONE;
 

	
 
		td.station_class = STR_NULL;
 
		td.station_name = STR_NULL;
 

	
 
		td.grf = NULL;
 

	
 
		CargoArray acceptance;
 
		AddAcceptedCargo(tile, acceptance, NULL);
 
		GetTileDesc(tile, &td);
 

	
 
		uint line_nr = 0;
 

	
 
		/* Tiletype */
 
		SetDParam(0, td.dparam[0]);
 
		GetString(this->landinfo_data[line_nr], td.str, lastof(this->landinfo_data[line_nr]));
 
		line_nr++;
 

	
 
		/* Up to four owners */
 
		for (uint i = 0; i < 4; i++) {
 
			if (td.owner_type[i] == STR_NULL) continue;
 

	
 
			SetDParam(0, STR_LAND_AREA_INFORMATION_OWNER_N_A);
 
			if (td.owner[i] != OWNER_NONE && td.owner[i] != OWNER_WATER) GetNameOfOwner(td.owner[i], tile);
 
			GetString(this->landinfo_data[line_nr], td.owner_type[i], lastof(this->landinfo_data[line_nr]));
 
			line_nr++;
 
		}
 

	
 
		/* Cost to clear/revenue when cleared */
 
		StringID str = STR_LAND_AREA_INFORMATION_COST_TO_CLEAR_N_A;
 
		Company *c = Company::GetIfValid(_local_company);
 
		if (c != NULL) {
 
			Money old_money = c->money;
 
			c->money = INT64_MAX;
 
			CommandCost costclear = DoCommand(tile, 0, 0, DC_NONE, CMD_LANDSCAPE_CLEAR);
 
			c->money = old_money;
 
			if (CmdSucceeded(costclear)) {
 
				Money cost = costclear.GetCost();
 
				if (cost < 0) {
 
					cost = -cost; // Negate negative cost to a positive revenue
 
					str = STR_LAND_AREA_INFORMATION_REVENUE_WHEN_CLEARED;
 
				} else {
 
					str = STR_LAND_AREA_INFORMATION_COST_TO_CLEAR;
 
				}
 
				SetDParam(0, cost);
 
			}
 
		}
 
		GetString(this->landinfo_data[line_nr], str, lastof(this->landinfo_data[line_nr]));
 
		line_nr++;
 

	
 
		/* Location */
 
		char tmp[16];
 
		snprintf(tmp, lengthof(tmp), "0x%.4X", tile);
 
		SetDParam(0, TileX(tile));
 
		SetDParam(1, TileY(tile));
 
		SetDParam(2, TileHeight(tile));
 
		SetDParamStr(3, tmp);
 
		GetString(this->landinfo_data[line_nr], STR_LAND_AREA_INFORMATION_LANDINFO_COORDS, lastof(this->landinfo_data[line_nr]));
 
		line_nr++;
 

	
 
		/* Local authority */
 
		SetDParam(0, STR_LAND_AREA_INFORMATION_LOCAL_AUTHORITY_NONE);
 
		if (t != NULL) {
 
			SetDParam(0, STR_TOWN_NAME);
 
			SetDParam(1, t->index);
 
		}
 
		GetString(this->landinfo_data[line_nr], STR_LAND_AREA_INFORMATION_LOCAL_AUTHORITY, lastof(this->landinfo_data[line_nr]));
 
		line_nr++;
 

	
 
		/* Build date */
 
		if (td.build_date != INVALID_DATE) {
 
			SetDParam(0, td.build_date);
 
			GetString(this->landinfo_data[line_nr], STR_LAND_AREA_INFORMATION_BUILD_DATE, lastof(this->landinfo_data[line_nr]));
 
			line_nr++;
 
		}
 

	
 
		/* Station class */
 
		if (td.station_class != STR_NULL) {
 
			SetDParam(0, td.station_class);
 
			GetString(this->landinfo_data[line_nr], STR_LAND_AREA_INFORMATION_STATION_CLASS, lastof(this->landinfo_data[line_nr]));
 
			line_nr++;
 
		}
 

	
 
		/* Station type name */
 
		if (td.station_name != STR_NULL) {
 
			SetDParam(0, td.station_name);
 
			GetString(this->landinfo_data[line_nr], STR_LAND_AREA_INFORMATION_STATION_TYPE, lastof(this->landinfo_data[line_nr]));
 
			line_nr++;
 
		}
 

	
 
		/* NewGRF name */
 
		if (td.grf != NULL) {
 
			SetDParamStr(0, td.grf);
 
			GetString(this->landinfo_data[line_nr], STR_LAND_AREA_INFORMATION_NEWGRF_NAME, lastof(this->landinfo_data[line_nr]));
 
			line_nr++;
 
		}
 

	
 
		assert(line_nr < LAND_INFO_CENTERED_LINES);
 

	
 
		/* Mark last line empty */
 
		this->landinfo_data[line_nr][0] = '\0';
 

	
 
		/* Cargo acceptance is displayed in a extra multiline */
 
		char *strp = GetString(this->landinfo_data[LAND_INFO_MULTICENTER_LINE], STR_LAND_AREA_INFORMATION_CARGO_ACCEPTED, lastof(this->landinfo_data[LAND_INFO_MULTICENTER_LINE]));
 
		bool found = false;
 

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

	
 
				/* If the accepted value is less than 8, show it in 1/8:ths */
 
				if (acceptance[i] < 8) {
 
					SetDParam(0, acceptance[i]);
 
					SetDParam(1, CargoSpec::Get(i)->name);
 
					strp = GetString(strp, STR_LAND_AREA_INFORMATION_CARGO_EIGHTS, lastof(this->landinfo_data[LAND_INFO_MULTICENTER_LINE]));
 
				} else {
 
					strp = GetString(strp, CargoSpec::Get(i)->name, lastof(this->landinfo_data[LAND_INFO_MULTICENTER_LINE]));
 
				}
 
			}
 
		}
 
		if (!found) this->landinfo_data[LAND_INFO_MULTICENTER_LINE][0] = '\0';
 

	
 
		this->InitNested(&_land_info_desc);
 

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

	
 
static void Place_LandInfo(TileIndex tile)
 
{
 
	DeleteWindowById(WC_LAND_INFO, 0);
 
	new LandInfoWindow(tile);
 
}
 

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

	
 
/** Widgets for the land info window. */
 
enum AboutWidgets {
 
	AW_CLOSE,                ///< Close the window
 
	AW_CAPTION,              ///< Title bar of the window
 
	AW_BACKGROUND,           ///< Background to draw on
 
	AW_ABOUT_ORIG_COPYRIGHT, ///< Text with original copyright info
 
	AW_ABOUT_VERSION,        ///< OpenTTD version string
 
	AW_FRAME,                ///< The frame with the scrolling text
 
	AW_SCROLLING_TEXT,       ///< The actually scrolling text
 
	AW_WEBSITE,              ///< URL of OpenTTD website
 
	AW_ABOUT_COPYRIGHT,      ///< OpenTTD copyright info
 
};
 

	
 
static const NWidgetPart _nested_about_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, AW_CLOSE),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, AW_CAPTION), SetDataTip(STR_ABOUT_OPENTTD, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, AW_BACKGROUND), SetPIP(4, 2, 4),
 
		NWidget(WWT_TEXT, COLOUR_GREY, AW_ABOUT_ORIG_COPYRIGHT), SetDataTip(STR_ABOUT_ORIGINAL_COPYRIGHT, STR_NULL),
 
		NWidget(WWT_TEXT, COLOUR_GREY, AW_ABOUT_VERSION), SetDataTip(STR_ABOUT_VERSION, STR_NULL),
 
		NWidget(WWT_FRAME, COLOUR_GREY, AW_FRAME), SetPadding(0, 5, 1, 5),
 
			NWidget(WWT_EMPTY, INVALID_COLOUR, AW_SCROLLING_TEXT),
 
		EndContainer(),
 
		NWidget(WWT_TEXT, COLOUR_GREY, AW_WEBSITE), SetDataTip(STR_BLACK_RAW_STRING, STR_NULL),
 
		NWidget(WWT_TEXT, COLOUR_GREY, AW_ABOUT_COPYRIGHT), SetDataTip(STR_ABOUT_COPYRIGHT_OPENTTD, STR_NULL),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _about_desc(
 
	WDP_CENTER, WDP_CENTER, 0, 0,
 
	WC_GAME_OPTIONS, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
 
	_nested_about_widgets, lengthof(_nested_about_widgets)
 
);
 

	
 
static const char * const _credits[] = {
 
	"Original design by Chris Sawyer",
 
	"Original graphics by Simon Foster",
 
	"",
 
	"The OpenTTD team (in alphabetical order):",
 
	"  Albert Hofkamp (Alberth) - GUI expert",
 
	"  Jean-Francois Claeys (Belugas) - GUI, newindustries and more",
 
	"  Bjarni Corfitzen (Bjarni) - MacOSX port, coder and vehicles",
 
	"  Matthijs Kooijman (blathijs) - Pathfinder-guru, pool rework",
 
	"  Victor Fischer (Celestar) - Programming everywhere you need him to",
 
	"  Christoph Elsenhans (frosch) - General coding",
 
	"  Lo\xC3\xAF""c Guilloux (glx) - Windows Expert",
 
	"  Michael Lutz (michi_cc) - Path based signals",
 
	"  Owen Rudge (orudge) - Forum host, OS/2 port",
 
	"  Peter Nelson (peter1138) - Spiritual descendant from newGRF gods",
 
	"  Remko Bijker (Rubidium) - Lead coder and way more",
 
	"  Zden\xC4\x9Bk Sojka (SmatZ) - Bug finder and fixer",
 
	"  Thijs Marinussen (Yexo) - AI Framework",
 
	"",
 
	"Inactive Developers:",
 
	"  Tam\xC3\xA1s Farag\xC3\xB3 (Darkvater) - Ex-Lead coder",
 
	"  Jaroslav Mazanec (KUDr) - YAPG (Yet Another Pathfinder God) ;)",
 
	"  Jonathan Coome (Maedhros) - High priest of the NewGRF Temple",
 
	"  Attila B\xC3\xA1n (MiHaMiX) - Developer WebTranslator 1 and 2",
 
	"  Christoph Mallon (Tron) - Programmer, code correctness police",
 
	"",
 
	"Retired Developers:",
 
	"  Ludvig Strigeus (ludde) - OpenTTD author, main coder (0.1 - 0.3.3)",
 
	"  Serge Paquet (vurlix) - Assistant project manager, coder (0.1 - 0.3.3)",
 
	"  Dominik Scherer (dominik81) - Lead programmer, GUI expert (0.3.0 - 0.3.6)",
 
	"  Benedikt Br\xC3\xBCggemeier (skidd13) - Bug fixer and code reworker",
 
	"  Patric Stout (TrueLight) - Programmer (0.3 - pre0.7), sys op (active)",
 
	"",
 
	"Special thanks go out to:",
 
	"  Josef Drexler - For his great work on TTDPatch",
 
	"  Marcin Grzegorczyk - For his documentation of TTD internals",
 
	"  Petr Baudi\xC5\xA1 (pasky) - Many patches, newGRF support",
 
	"  Stefan Mei\xC3\x9Fner (sign_de) - For his work on the console",
 
	"  Simon Sasburg (HackyKid) - Many bugfixes he has blessed us with",
 
	"  Cian Duffy (MYOB) - BeOS port / manual writing",
 
	"  Christian Rosentreter (tokai) - MorphOS / AmigaOS port",
 
	"  Richard Kempton (richK) - additional airports, initial TGP implementation",
 
	"",
 
	"  Alberto Demichelis - Squirrel scripting language \xC2\xA9 2003-2008",
 
	"  Markus F.X.J. Oberhumer - (Mini)LZO for loading old savegames \xC2\xA9 1996-2008",
 
	"  L. Peter Deutsch - MD5 implementation \xC2\xA9 1999, 2000, 2002",
 
	"  Michael Blunck - Pre-Signals and Semaphores \xC2\xA9 2003",
 
	"  George - Canal/Lock graphics \xC2\xA9 2003-2004",
 
	"  David Dallaston - Tram tracks",
 
	"  Marcin Grzegorczyk - Foundations for Tracks on Slopes",
 
	"  All Translators - Who made OpenTTD a truly international game",
 
	"  Bug Reporters - Without whom OpenTTD would still be full of bugs!",
 
	"",
 
	"",
 
	"And last but not least:",
 
	"  Chris Sawyer - For an amazing game!"
 
};
 

	
 
struct AboutWindow : public Window {
 
	int text_position;                       ///< The top of the scrolling text
 
	byte counter;                            ///< Used to scroll the text every 5 ticks
 
	int line_height;                         ///< The height of a single line
 
	static const int num_visible_lines = 19; ///< The number of lines visible simultaneously
 

	
 
	AboutWindow() : Window()
 
	{
 
		this->InitNested(&_about_desc);
 

	
 
		this->counter = 5;
 
		this->text_position = this->GetWidget<NWidgetBase>(AW_SCROLLING_TEXT)->pos_y + this->GetWidget<NWidgetBase>(AW_SCROLLING_TEXT)->current_y;
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		if (widget == AW_WEBSITE) SetDParamStr(0, "Website: http://www.openttd.org");
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		if (widget != AW_SCROLLING_TEXT) return;
 

	
 
		this->line_height = FONT_HEIGHT_NORMAL;
 

	
 
		Dimension d;
 
		d.height = this->line_height * num_visible_lines;
 

	
 
		d.width = 0;
 
		for (uint i = 0; i < lengthof(_credits); i++) {
 
			d.width = max(d.width, GetStringBoundingBox(_credits[i]).width);
 
		}
 
		*size = maxdim(*size, d);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		if (widget != AW_SCROLLING_TEXT) return;
 

	
 
		int y = this->text_position;
 

	
 
		/* Show all scrolling _credits */
 
		for (uint i = 0; i < lengthof(_credits); i++) {
 
			if (y >= r.top + 7 && y < r.bottom - this->line_height) {
 
				DrawString(r.left, r.right, y, _credits[i], TC_BLACK, SA_LEFT | SA_FORCE);
 
			}
 
			y += this->line_height;
 
		}
 
	}
 

	
 
	virtual void OnTick()
 
	{
 
		if (--this->counter == 0) {
 
			this->counter = 5;
 
			this->text_position--;
 
			/* If the last text has scrolled start a new from the start */
 
			if (this->text_position < (int)(this->GetWidget<NWidgetBase>(AW_SCROLLING_TEXT)->pos_y - lengthof(_credits) * this->line_height)) {
 
				this->text_position = this->GetWidget<NWidgetBase>(AW_SCROLLING_TEXT)->pos_y + this->GetWidget<NWidgetBase>(AW_SCROLLING_TEXT)->current_y;
 
			}
 
			this->SetDirty();
 
		}
 
	}
 
};
 

	
 
void ShowAboutWindow()
 
{
 
	DeleteWindowById(WC_GAME_OPTIONS, 0);
 
	new AboutWindow();
 
}
 

	
 
/** Widgets of the error message windows */
 
enum ErrorMessageWidgets {
 
	EMW_CLOSE = 0,
 
	EMW_CAPTION,
 
	EMW_PANEL,
 
	EMW_FACE,
 
	EMW_MESSAGE,
 
};
 

	
 
static const NWidgetPart _nested_errmsg_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_RED, EMW_CLOSE),
 
		NWidget(WWT_CAPTION, COLOUR_RED, EMW_CAPTION), SetDataTip(STR_ERROR_MESSAGE_CAPTION, STR_NULL),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_RED, EMW_PANEL),
 
		NWidget(WWT_EMPTY, COLOUR_RED, EMW_MESSAGE), SetPadding(0, 2, 0, 2), SetMinimalSize(236, 32),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _errmsg_desc(
 
	0, 0, 240, 46, // x/y position is not used.
 
	WC_ERRMSG, WC_NONE,
 
	WDF_STD_BTN | WDF_DEF_WIDGET,
 
	_nested_errmsg_widgets, lengthof(_nested_errmsg_widgets)
 
);
 

	
 
static const NWidgetPart _nested_errmsg_face_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_RED, EMW_CLOSE),
 
		NWidget(WWT_CAPTION, COLOUR_RED, EMW_CAPTION), SetDataTip(STR_ERROR_MESSAGE_CAPTION_OTHER_COMPANY, STR_NULL),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_RED, EMW_PANEL),
 
		NWidget(NWID_HORIZONTAL), SetPIP(2, 1, 2),
 
			NWidget(WWT_EMPTY, COLOUR_RED, EMW_FACE), SetMinimalSize(91, 120), SetFill(false, true), SetPadding(2, 0, 1, 0),
 
			NWidget(WWT_EMPTY, COLOUR_RED, EMW_MESSAGE), SetFill(false, true), SetMinimalSize(238, 123),
 
			NWidget(WWT_EMPTY, COLOUR_RED, EMW_FACE), SetMinimalSize(91, 120), SetFill(0, 1), SetPadding(2, 0, 1, 0),
 
			NWidget(WWT_EMPTY, COLOUR_RED, EMW_MESSAGE), SetFill(0, 1), SetMinimalSize(238, 123),
 
		EndContainer(),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _errmsg_face_desc(
 
	0, 0, 334, 137, // x/y position is not used.
 
	WC_ERRMSG, WC_NONE,
 
	WDF_STD_BTN | WDF_DEF_WIDGET,
 
	_nested_errmsg_face_widgets, lengthof(_nested_errmsg_face_widgets)
 
);
 

	
 
/** Window class for displaying an error message window. */
 
struct ErrmsgWindow : public Window {
 
private:
 
	uint duration;                  ///< Length of display of the message. 0 means forever,
 
	uint64 decode_params[20];       ///< Parameters of the message strings.
 
	StringID summary_msg;           ///< General error message showed in first line. Must be valid.
 
	StringID detailed_msg;          ///< Detailed error message showed in second line. Can be #INVALID_STRING_ID.
 
	uint height_summary;            ///< Height of the #summary_msg string in pixels in the #EMW_MESSAGE widget.
 
	uint height_detailed;           ///< Height of the #detailed_msg string in pixels in the #EMW_MESSAGE widget.
 
	Point position;                 ///< Position of the error message window.
 

	
 
public:
 
	ErrmsgWindow(Point pt, const WindowDesc *desc, StringID summary_msg, StringID detailed_msg, bool no_timeout) : Window()
 
	{
 
		this->position = pt;
 
		this->duration = no_timeout ? 0 : _settings_client.gui.errmsg_duration;
 
		CopyOutDParam(this->decode_params, 0, lengthof(this->decode_params));
 
		this->summary_msg  = summary_msg;
 
		this->detailed_msg = detailed_msg;
 

	
 
		assert(summary_msg != INVALID_STRING_ID);
 

	
 
		this->InitNested(desc);
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		if (widget != EMW_MESSAGE) return;
 

	
 
		CopyInDParam(0, this->decode_params, lengthof(this->decode_params));
 
		/* If the error message comes from a NewGRF, we must use the text ref. stack reserved for error messages.
 
		 * If the message doesn't come from a NewGRF, it won't use the TTDP-style text ref. stack, so we won't hurt anything
 
		 */
 
		SwitchToErrorRefStack();
 
		RewindTextRefStack();
 

	
 
		int text_width = max(0, (int)size->width - WD_FRAMETEXT_LEFT - WD_FRAMETEXT_RIGHT);
 
		this->height_summary  = GetStringHeight(this->summary_msg, text_width);
 
		this->height_detailed = (this->detailed_msg == INVALID_STRING_ID) ? 0 : GetStringHeight(this->detailed_msg, text_width);
 

	
 
		SwitchToNormalRefStack(); // Switch back to the normal text ref. stack for NewGRF texts.
 

	
 
		uint panel_height = WD_FRAMERECT_TOP + this->height_summary + WD_FRAMERECT_BOTTOM;
 
		if (this->detailed_msg != INVALID_STRING_ID) panel_height += this->height_detailed + WD_PAR_VSEP_WIDE;
 

	
 
		size->height = max(size->height, panel_height);
 
	}
 

	
 
	virtual Point OnInitialPosition(const WindowDesc *desc, int16 sm_width, int16 sm_height, int window_number)
 
	{
 
		/* Position (0, 0) given, center the window. */
 
		if (this->position.x == 0 && this->position.y == 0) {
 
			Point pt = {(_screen.width - sm_width) >> 1, (_screen.height - sm_height) >> 1};
 
			return pt;
 
		}
 

	
 
		/* Find the free screen space between the main toolbar at the top, and the statusbar at the bottom.
 
		 * Add a fixed distance 20 to make it less cluttered.
 
		 */
 
		int scr_top = GetMainViewTop() + 20;
 
		int scr_bot = GetMainViewBottom() - 20;
 

	
 
		Point pt = RemapCoords2(this->position.x, this->position.y);
 
		const ViewPort *vp = FindWindowById(WC_MAIN_WINDOW, 0)->viewport;
 
		if (this->detailed_msg != STR_ERROR_OWNED_BY || GetDParamX(this->decode_params, 2) >= MAX_COMPANIES) {
 
			/* move x pos to opposite corner */
 
			pt.x = UnScaleByZoom(pt.x - vp->virtual_left, vp->zoom) + vp->left;
 
			pt.x = (pt.x < (_screen.width >> 1)) ? _screen.width - sm_width - 20 : 20; // Stay 20 pixels away from the edge of the screen.
 

	
 
			/* move y pos to opposite corner */
 
			pt.y = UnScaleByZoom(pt.y - vp->virtual_top, vp->zoom) + vp->top;
 
			pt.y = (pt.y < (_screen.height >> 1)) ? scr_bot - sm_height : scr_top;
 
		} else {
 
			pt.x = Clamp(UnScaleByZoom(pt.x - vp->virtual_left, vp->zoom) + vp->left - (sm_width / 2),  0, _screen.width  - sm_width);
 
			pt.y = Clamp(UnScaleByZoom(pt.y - vp->virtual_top,  vp->zoom) + vp->top  - (sm_height / 2), scr_top, scr_bot - sm_height);
 
		}
 
		return pt;
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		if (widget == EMW_CAPTION) CopyInDParam(0, this->decode_params, lengthof(this->decode_params));
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case EMW_FACE: {
 
				const Company *c = Company::Get((CompanyID)GetDParamX(this->decode_params, 2));
 
				DrawCompanyManagerFace(c->face, c->colour, r.left, r.top);
 
				break;
 
			}
 

	
 
			case EMW_MESSAGE:
 
				CopyInDParam(0, this->decode_params, lengthof(this->decode_params));
 
				SwitchToErrorRefStack();
 
				RewindTextRefStack();
 

	
 
				if (this->detailed_msg == INVALID_STRING_ID) {
 
					DrawStringMultiLine(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, r.top + WD_FRAMERECT_TOP, r.bottom - WD_FRAMERECT_BOTTOM,
 
							this->summary_msg, TC_FROMSTRING, SA_CENTER);
 
				} else {
 
					int extra = (r.bottom - r.top + 1 - this->height_summary - this->height_detailed - WD_PAR_VSEP_WIDE) / 2;
 

	
 
					int top = r.top + WD_FRAMERECT_TOP;
 
					int bottom = top + this->height_summary + extra;
 
					DrawStringMultiLine(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, top, bottom, this->summary_msg, TC_FROMSTRING, SA_CENTER);
 

	
 
					bottom = r.bottom - WD_FRAMERECT_BOTTOM;
 
					top = bottom - this->height_detailed - extra;
 
					DrawStringMultiLine(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, top, bottom, this->detailed_msg, TC_FROMSTRING, SA_CENTER);
 
				}
 

	
 
				SwitchToNormalRefStack(); // Switch back to the normal text ref. stack for NewGRF texts.
 
				break;
 

	
 
			default:
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnMouseLoop()
 
	{
 
		/* Disallow closing the window too easily, if timeout is disabled */
 
		if (_right_button_down && this->duration != 0) delete this;
 
	}
 

	
 
	virtual void OnHundredthTick()
 
	{
 
		/* Timeout enabled? */
 
		if (this->duration != 0) {
 
			this->duration--;
 
			if (this->duration == 0) delete this;
 
		}
 
	}
 

	
 
	~ErrmsgWindow()
 
	{
 
		SetRedErrorSquare(INVALID_TILE);
 
		extern StringID _switch_mode_errorstr;
 
		_switch_mode_errorstr = INVALID_STRING_ID;
 
	}
 

	
 
	virtual EventState OnKeyPress(uint16 key, uint16 keycode)
 
	{
 
		if (keycode != WKC_SPACE) return ES_NOT_HANDLED;
 
		delete this;
 
		return ES_HANDLED;
 
	}
 
};
 

	
 
/**
 
 * Display an error message in a window.
 
 * @param summary_msg  General error message showed in first line. Must be valid.
 
 * @param detailed_msg Detailed error message showed in second line. Can be INVALID_STRING_ID.
 
 * @param x            World X position (TileVirtX) of the error location. Set both x and y to 0 to just center the message when there is no related error tile.
 
 * @param y            World Y position (TileVirtY) of the error location. Set both x and y to 0 to just center the message when there is no related error tile.
 
 * @param no_timeout   Set to true, if the message is that important that it should not close automatically after some time.
 
 */
 
void ShowErrorMessage(StringID summary_msg, StringID detailed_msg, int x, int y, bool no_timeout)
 
{
 
	DeleteWindowById(WC_ERRMSG, 0);
 

	
 
	if (_settings_client.gui.errmsg_duration == 0 && !no_timeout) return;
 

	
 
	if (summary_msg == STR_NULL) summary_msg = STR_EMPTY;
 

	
 
	Point pt = {x, y};
 
	const WindowDesc *desc = (detailed_msg != STR_ERROR_OWNED_BY || GetDParam(2) >= MAX_COMPANIES) ? &_errmsg_desc : &_errmsg_face_desc;
 
	new ErrmsgWindow(pt, desc, summary_msg, detailed_msg, no_timeout);
 
}
 

	
 
void ShowEstimatedCostOrIncome(Money cost, int x, int y)
 
{
 
	StringID msg = STR_MESSAGE_ESTIMATED_COST;
 

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

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

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

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

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

	
 
TextEffectID ShowFillingPercent(int x, int y, int z, uint8 percent, StringID string)
 
{
 
	Point pt = RemapCoords(x, y, z);
 

	
 
	assert(string != STR_NULL);
 

	
 
	SetDParam(0, percent);
 
	return AddTextEffect(string, pt.x, pt.y, 0xFFFF, TE_STATIC);
 
}
 

	
 
void UpdateFillingPercent(TextEffectID te_id, uint8 percent, StringID string)
 
{
 
	assert(string != STR_NULL);
 

	
 
	SetDParam(0, percent);
 
	UpdateTextEffect(te_id, string);
 
}
 

	
 
void HideFillingPercent(TextEffectID *te_id)
 
{
 
	if (*te_id == INVALID_TE_ID) return;
 

	
 
	RemoveTextEffect(*te_id);
 
	*te_id = INVALID_TE_ID;
 
}
 

	
 
static const NWidgetPart _nested_tooltips_widgets[] = {
 
	NWidget(WWT_PANEL, COLOUR_GREY, 0), SetMinimalSize(200, 32), EndContainer(),
 
};
 

	
 
static const WindowDesc _tool_tips_desc(
 
	100, 100, 0, 0, // Coordinates and sizes are not used,
 
	WC_TOOLTIPS, WC_NONE,
 
	0,
 
	_nested_tooltips_widgets, lengthof(_nested_tooltips_widgets)
 
);
 

	
 
/** Window for displaying a tooltip. */
 
struct TooltipsWindow : public Window
 
{
 
	StringID string_id;         ///< String to display as tooltip.
 
	byte paramcount;            ///< Number of string parameters in #string_id.
 
	uint64 params[5];           ///< The string parameters.
 
	bool use_left_mouse_button; ///< Wait for left mouse button to close window (else, wait for right button).
 

	
 
	TooltipsWindow(StringID str, uint paramcount, const uint64 params[], bool use_left_mouse_button) : Window()
 
	{
 
		this->string_id = str;
 
		assert_compile(sizeof(this->params[0]) == sizeof(params[0]));
 
		assert(paramcount <= lengthof(this->params));
 
		memcpy(this->params, params, sizeof(this->params[0]) * paramcount);
 
		this->paramcount = paramcount;
 
		this->use_left_mouse_button = use_left_mouse_button;
 

	
 
		this->InitNested(&_tool_tips_desc);
 

	
 
		this->flags4 &= ~WF_WHITE_BORDER_MASK; // remove white-border from tooltip
 
	}
 

	
 
	virtual Point OnInitialPosition(const WindowDesc *desc, int16 sm_width, int16 sm_height, int window_number)
 
	{
 
		/* Find the free screen space between the main toolbar at the top, and the statusbar at the bottom.
 
		 * Add a fixed distance 2 so the tooltip floats free from both bars.
 
		 */
 
		int scr_top = GetMainViewTop() + 2;
 
		int scr_bot = GetMainViewBottom() - 2;
 

	
 
		Point pt;
 

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

	
 
		return pt;
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		/* There is only one widget. */
 
		for (uint i = 0; i != this->paramcount; i++) SetDParam(i, this->params[i]);
 

	
 
		size->width  = min(GetStringBoundingBox(this->string_id).width, 194);
 
		size->height = GetStringHeight(this->string_id, size->width);
 

	
 
		/* Increase slightly to have some space around the box. */
 
		size->width  += 2 + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
 
		size->height += 2 + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		/* There is only one widget. */
 
		GfxFillRect(r.left, r.top, r.right, r.bottom, 0);
 
		GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, 0x44);
 

	
 
		for (uint arg = 0; arg < this->paramcount; arg++) {
 
			SetDParam(arg, this->params[arg]);
 
		}
 
		DrawStringMultiLine(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, r.bottom - WD_FRAMERECT_BOTTOM, this->string_id, TC_FROMSTRING, SA_CENTER);
 
	}
 

	
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void OnMouseLoop()
 
	{
 
		/* We can show tooltips while dragging tools. These are shown as long as
 
		 * we are dragging the tool. Normal tooltips work with rmb */
 
		if (this->use_left_mouse_button ? !_left_button_down : !_right_button_down) delete this;
 
	}
 
};
 

	
 
/** Shows a tooltip
 
 * @param str String to be displayed
 
 * @param paramcount number of params to deal with
 
 * @param params (optional) up to 5 pieces of additional information that may be added to a tooltip
 
 * @param use_left_mouse_button close the tooltip when the left (true) or right (false) mousebutton is released
 
 */
 
void GuiShowTooltips(StringID str, uint paramcount, const uint64 params[], bool use_left_mouse_button)
 
{
 
	DeleteWindowById(WC_TOOLTIPS, 0);
 

	
 
	if (str == STR_NULL) return;
 

	
 
	new TooltipsWindow(str, paramcount, params, use_left_mouse_button);
 
}
 

	
 

	
 
static int DrawStationCoverageText(const CargoArray &cargos,
 
	int left, int right, int top, StationCoverageType sct, bool supplies)
 
{
 
	bool first = true;
 

	
 
	char string[512];
 
	char *b = InlineString(string, supplies ? STR_STATION_BUILD_SUPPLIES_CARGO : STR_STATION_BUILD_ACCEPTS_CARGO);
 

	
 
	for (CargoID i = 0; i < NUM_CARGO; i++) {
 
		if (b >= lastof(string) - (1 + 2 * 4)) break; // ',' or ' ' and two calls to Utf8Encode()
 
		switch (sct) {
 
			case SCT_PASSENGERS_ONLY: if (!IsCargoInClass(i, CC_PASSENGERS)) continue; break;
 
			case SCT_NON_PASSENGERS_ONLY: if (IsCargoInClass(i, CC_PASSENGERS)) continue; break;
 
			case SCT_ALL: break;
 
			default: NOT_REACHED();
 
		}
 
		if (cargos[i] >= (supplies ? 1U : 8U)) {
 
			if (first) {
 
				first = false;
 
			} else {
 
				/* Add a comma if this is not the first item */
 
				*b++ = ',';
 
				*b++ = ' ';
 
			}
 
			b = InlineString(b, CargoSpec::Get(i)->name);
 
		}
 
	}
 

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

	
 
	*b = '\0';
 

	
 
	/* Make sure we detect any buffer overflow */
 
	assert(b < endof(string));
 

	
 
	SetDParamStr(0, string);
 
	return DrawStringMultiLine(left, right, top, INT32_MAX, STR_JUST_RAW_STRING);
 
}
 

	
 
/**
 
 * Calculates and draws the accepted or supplied cargo around the selected tile(s)
 
 * @param left x position where the string is to be drawn
 
 * @param right the right most position to draw on
 
 * @param top y position where the string is to be drawn
 
 * @param sct which type of cargo is to be displayed (passengers/non-passengers)
 
 * @param rad radius around selected tile(s) to be searched
 
 * @param supplies if supplied cargos should be drawn, else accepted cargos
 
 * @return Returns the y value below the string that was drawn
 
 */
 
int DrawStationCoverageAreaText(int left, int right, int top, StationCoverageType sct, int rad, bool supplies)
 
{
 
	TileIndex tile = TileVirtXY(_thd.pos.x, _thd.pos.y);
 
	if (tile < MapSize()) {
 
		CargoArray cargos;
 
		if (supplies) {
 
			cargos = GetProductionAroundTiles(tile, _thd.size.x / TILE_SIZE, _thd.size.y / TILE_SIZE, rad);
 
		} else {
 
			cargos = GetAcceptanceAroundTiles(tile, _thd.size.x / TILE_SIZE, _thd.size.y / TILE_SIZE, rad);
 
		}
 
		return DrawStationCoverageText(cargos, left, right, top, sct, supplies);
 
	}
 

	
 
	return top;
 
}
 

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

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

	
 
	if (backspace) s = Utf8PrevChar(s);
 

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

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

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

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

	
 
	return false;
 
}
 

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

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

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

	
 
/**
 
 * Insert a chunk of text from the clipboard onto the textbuffer. Get TEXT clipboard
 
 * and append this up to the maximum length (either absolute or screenlength). If maxlength
 
 * is zero, we don't care about the screenlength but only about the physical length of the string
 
 * @param tb Textbuf type to be changed
 
 * @return true on successful change of Textbuf, or false otherwise
 
 */
 
bool InsertTextBufferClipboard(Textbuf *tb)
 
{
 
	char utf8_buf[512];
 

	
 
	if (!GetClipboardContents(utf8_buf, lengthof(utf8_buf))) return false;
 

	
 
	uint16 width = 0, length = 0;
 
	WChar c;
 
	for (const char *ptr = utf8_buf; (c = Utf8Consume(&ptr)) != '\0';) {
 
		if (!IsPrintable(c)) break;
 

	
 
		byte len = Utf8CharLen(c);
 
		if (tb->size + length + len > tb->maxsize) break;
 

	
 
		byte charwidth = GetCharacterWidth(FS_NORMAL, c);
 
		if (tb->maxwidth != 0 && width + tb->width + charwidth > tb->maxwidth) break;
 

	
 
		width += charwidth;
 
		length += len;
 
	}
 

	
 
	if (length == 0) return false;
 

	
 
	memmove(tb->buf + tb->caretpos + length, tb->buf + tb->caretpos, tb->size - tb->caretpos);
 
	memcpy(tb->buf + tb->caretpos, utf8_buf, length);
 
	tb->width += width;
 
	tb->caretxoffs += width;
 

	
 
	tb->size += length;
 
	tb->caretpos += length;
 
	assert(tb->size <= tb->maxsize);
 
	tb->buf[tb->size - 1] = '\0'; // terminating zero
 

	
 
	return true;
 
}
 

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

	
 
				return true;
 
			}
 
			break;
 

	
 
		case WKC_RIGHT:
 
			if (tb->caretpos < tb->size - 1) {
 
				WChar c;
 

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

	
 
				return true;
 
			}
 
			break;
 

	
 
		case WKC_HOME:
 
			tb->caretpos = 0;
 
			tb->caretxoffs = 0;
 
			return true;
 

	
 
		case WKC_END:
 
			tb->caretpos = tb->size - 1;
 
			tb->caretxoffs = tb->width;
 
			return true;
 

	
 
		default:
 
			break;
 
	}
 

	
 
	return false;
 
}
 

	
 
/**
 
 * Initialize the textbuffer by supplying it the buffer to write into
 
 * and the maximum length of this buffer
 
 * @param tb Textbuf type which is getting initialized
 
 * @param buf the buffer that will be holding the data for input
 
 * @param maxsize maximum size in bytes, including terminating '\0'
 
 * @param maxwidth maximum length in pixels of this buffer. If reached, buffer
 
 * cannot grow, even if maxsize would allow because there is space. Width
 
 * of zero '0' means the buffer is only restricted by maxsize */
 
void InitializeTextBuffer(Textbuf *tb, char *buf, uint16 maxsize, uint16 maxwidth)
 
{
 
	assert(maxsize != 0);
 

	
 
	tb->buf      = buf;
 
	tb->maxsize  = maxsize;
 
	tb->maxwidth = maxwidth;
 
	tb->caret    = true;
 
	UpdateTextBufferSize(tb);
 
}
 

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

	
 
	tb->width = 0;
 
	tb->size = 1; // terminating zero
 

	
 
	WChar c;
 
	while ((c = Utf8Consume(&buf)) != '\0') {
 
		tb->width += GetCharacterWidth(FS_NORMAL, c);
 
		tb->size += Utf8CharLen(c);
 
	}
 

	
 
	assert(tb->size <= tb->maxsize);
 

	
 
	tb->caretpos = tb->size - 1;
 
	tb->caretxoffs = tb->width;
 
}
 

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

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

	
 
bool QueryString::HasEditBoxFocus(const Window *w, int wid) const
 
{
 
	if (w->IsWidgetGloballyFocused(wid)) return true;
 
	if (w->window_class != WC_OSK || _focused_window != w->parent) return false;
 
	return w->parent->nested_focus != NULL && w->parent->nested_focus->type == WWT_EDITBOX;
 
}
 

	
 
HandleEditBoxResult QueryString::HandleEditBoxKey(Window *w, int wid, uint16 key, uint16 keycode, Window::EventState &state)
 
{
 
	if (!QueryString::HasEditBoxFocus(w, wid)) return HEBR_NOT_FOCUSED;
 

	
 
	state = Window::ES_HANDLED;
 

	
 
	switch (keycode) {
 
		case WKC_ESC: return HEBR_CANCEL;
 

	
 
		case WKC_RETURN: case WKC_NUM_ENTER: return HEBR_CONFIRM;
 

	
 
#ifdef WITH_COCOA
 
		case (WKC_META | 'V'):
 
#endif
 
		case (WKC_CTRL | 'V'):
 
			if (InsertTextBufferClipboard(&this->text)) w->SetWidgetDirty(wid);
 
			break;
 

	
 
#ifdef WITH_COCOA
 
		case (WKC_META | 'U'):
 
#endif
 
		case (WKC_CTRL | 'U'):
 
			DeleteTextBufferAll(&this->text);
 
			w->SetWidgetDirty(wid);
 
			break;
 

	
 
		case WKC_BACKSPACE: case WKC_DELETE:
 
			if (DeleteTextBufferChar(&this->text, keycode)) w->SetWidgetDirty(wid);
 
			break;
 

	
 
		case WKC_LEFT: case WKC_RIGHT: case WKC_END: case WKC_HOME:
 
			if (MoveTextBufferPos(&this->text, keycode)) w->SetWidgetDirty(wid);
 
			break;
 

	
 
		default:
 
			if (IsValidChar(key, this->afilter)) {
 
				if (InsertTextBufferChar(&this->text, key)) w->SetWidgetDirty(wid);
 
			} else {
 
				state = Window::ES_NOT_HANDLED;
 
			}
 
	}
 

	
 
	return HEBR_EDITING;
 
}
 

	
 
void QueryString::HandleEditBox(Window *w, int wid)
 
{
 
	if (HasEditBoxFocus(w, wid) && HandleCaret(&this->text)) {
 
		w->SetWidgetDirty(wid);
 
		/* When we're not the OSK, notify 'our' OSK to redraw the widget,
 
		 * so the caret changes appropriately. */
 
		if (w->window_class != WC_OSK) {
 
			Window *w_osk = FindWindowById(WC_OSK, 0);
 
			if (w_osk != NULL && w_osk->parent == w) w_osk->InvalidateData();
 
		}
 
	}
 
}
 

	
 
void QueryString::DrawEditBox(Window *w, int wid)
 
{
 
	const NWidgetBase *wi = w->GetWidget<NWidgetBase>(wid);
 

	
 
	assert((wi->type & WWT_MASK) == WWT_EDITBOX);
 
	int left   = wi->pos_x;
 
	int right  = wi->pos_x + wi->current_x - 1;
 
	int top    = wi->pos_y;
 
	int bottom = wi->pos_y + wi->current_y - 1;
 

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

	
 
	/* Limit the drawing of the string inside the widget boundaries */
 
	DrawPixelInfo dpi;
 
	if (!FillDrawPixelInfo(&dpi, left + WD_FRAMERECT_LEFT, top + WD_FRAMERECT_TOP, right - left - WD_FRAMERECT_RIGHT, bottom - top - WD_FRAMERECT_BOTTOM)) return;
 

	
 
	DrawPixelInfo *old_dpi = _cur_dpi;
 
	_cur_dpi = &dpi;
 

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

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

	
 
	DrawString(delta, tb->width, 0, tb->buf, TC_YELLOW);
 
	if (HasEditBoxFocus(w, wid) && tb->caret) {
 
		int caret_width = GetStringBoundingBox("_").width;
 
		DrawString(tb->caretxoffs + delta, tb->caretxoffs + delta + caret_width, 0, "_", TC_WHITE);
 
	}
 

	
 
	_cur_dpi = old_dpi;
 
}
 

	
 
HandleEditBoxResult QueryStringBaseWindow::HandleEditBoxKey(int wid, uint16 key, uint16 keycode, EventState &state)
 
{
 
	return this->QueryString::HandleEditBoxKey(this, wid, key, keycode, state);
 
}
 

	
 
void QueryStringBaseWindow::HandleEditBox(int wid)
 
{
 
	this->QueryString::HandleEditBox(this, wid);
 
}
 

	
 
void QueryStringBaseWindow::DrawEditBox(int wid)
 
{
 
	this->QueryString::DrawEditBox(this, wid);
 
}
 

	
 
void QueryStringBaseWindow::OnOpenOSKWindow(int wid)
 
{
 
	ShowOnScreenKeyboard(this, wid, 0, 0);
 
}
 

	
 
/** Widget of the string query window. */
 
enum QueryStringWidgets {
 
	QUERY_STR_WIDGET_CLOSEBOX,
 
	QUERY_STR_WIDGET_CAPTION,
 
	QUERY_STR_WIDGET_BACKGROUND,
 
	QUERY_STR_WIDGET_TEXT,
 
	QUERY_STR_WIDGET_DEFAULT,
 
	QUERY_STR_WIDGET_CANCEL,
 
	QUERY_STR_WIDGET_OK
 
};
 

	
 
/** Class for the string query window. */
 
struct QueryStringWindow : public QueryStringBaseWindow
 
{
 
	QueryStringFlags flags; ///< Flags controlling behaviour of the window.
 

	
 
	QueryStringWindow(StringID str, StringID caption, uint maxsize, uint maxwidth, const WindowDesc *desc, Window *parent, CharSetFilter afilter, QueryStringFlags flags) :
 
			QueryStringBaseWindow(maxsize)
 
	{
 
		GetString(this->edit_str_buf, str, &this->edit_str_buf[maxsize - 1]);
 
		this->edit_str_buf[maxsize - 1] = '\0';
 

	
 
		if ((flags & QSF_ACCEPT_UNCHANGED) == 0) this->orig = strdup(this->edit_str_buf);
 

	
 
		this->caption = caption;
 
		this->afilter = afilter;
 
		this->flags = flags;
 
		InitializeTextBuffer(&this->text, this->edit_str_buf, maxsize, maxwidth);
 

	
 
		this->InitNested(desc);
 

	
 
		this->parent = parent;
 

	
 
		this->SetFocusedWidget(QUERY_STR_WIDGET_TEXT);
 
		this->LowerWidget(QUERY_STR_WIDGET_TEXT);
 
	}
 

	
 
	void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		if (widget == QUERY_STR_WIDGET_DEFAULT && (this->flags & QSF_ENABLE_DEFAULT) == 0) {
 
			this->GetWidget<NWidgetCore>(widget)->SetFill(false, true);
 
			this->GetWidget<NWidgetCore>(widget)->SetFill(0, 1);
 
			size->width = 0;
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 

	
 
		this->DrawEditBox(QUERY_STR_WIDGET_TEXT);
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		if (widget == QUERY_STR_WIDGET_CAPTION) SetDParam(0, this->caption);
 
	}
 

	
 
	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);
 
			}
 
			this->handled = true;
 
		}
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case QUERY_STR_WIDGET_DEFAULT:
 
				this->text.buf[0] = '\0';
 
				/* Fallthrough */
 
			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);
 
	}
 

	
 
	virtual EventState OnKeyPress(uint16 key, uint16 keycode)
 
	{
 
		EventState state = ES_NOT_HANDLED;
 
		switch (this->HandleEditBoxKey(QUERY_STR_WIDGET_TEXT, key, keycode, state)) {
 
			default: NOT_REACHED();
 
			case HEBR_EDITING: {
 
				Window *osk = FindWindowById(WC_OSK, 0);
 
				if (osk != NULL && osk->parent == this) osk->InvalidateData();
 
			} break;
 
			case HEBR_CONFIRM: this->OnOk();
 
			/* FALL THROUGH */
 
			case HEBR_CANCEL: delete this; break; // close window, abandon changes
 
			case HEBR_NOT_FOCUSED: break;
 
		}
 
		return state;
 
	}
 

	
 
	virtual void OnOpenOSKWindow(int wid)
 
	{
 
		ShowOnScreenKeyboard(this, wid, QUERY_STR_WIDGET_CANCEL, QUERY_STR_WIDGET_OK);
 
	}
 

	
 
	~QueryStringWindow()
 
	{
 
		if (!this->handled && this->parent != NULL) {
 
			Window *parent = this->parent;
 
			this->parent = NULL; // so parent doesn't try to delete us again
 
			parent->OnQueryTextFinished(NULL);
 
		}
 
	}
 
};
 

	
 
static const NWidgetPart _nested_query_string_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, QUERY_STR_WIDGET_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, QUERY_STR_WIDGET_CAPTION), SetDataTip(STR_WHITE_STRING, STR_NULL),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, QUERY_STR_WIDGET_BACKGROUND),
 
		NWidget(WWT_EDITBOX, COLOUR_GREY, QUERY_STR_WIDGET_TEXT), SetMinimalSize(256, 12), SetFill(true, true), SetPadding(2, 2, 2, 2),
 
		NWidget(WWT_EDITBOX, COLOUR_GREY, QUERY_STR_WIDGET_TEXT), SetMinimalSize(256, 12), SetFill(1, 1), SetPadding(2, 2, 2, 2),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, QUERY_STR_WIDGET_DEFAULT), SetMinimalSize(87, 12), SetFill(true, true), SetDataTip(STR_BUTTON_DEFAULT, STR_NULL),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, QUERY_STR_WIDGET_CANCEL), SetMinimalSize(86, 12), SetFill(true, true), SetDataTip(STR_BUTTON_CANCEL, STR_NULL),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, QUERY_STR_WIDGET_OK), SetMinimalSize(87, 12), SetFill(true, true), SetDataTip(STR_BUTTON_OK, STR_NULL),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, QUERY_STR_WIDGET_DEFAULT), SetMinimalSize(87, 12), SetFill(1, 1), SetDataTip(STR_BUTTON_DEFAULT, STR_NULL),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, QUERY_STR_WIDGET_CANCEL), SetMinimalSize(86, 12), SetFill(1, 1), SetDataTip(STR_BUTTON_CANCEL, STR_NULL),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, QUERY_STR_WIDGET_OK), SetMinimalSize(87, 12), SetFill(1, 1), SetDataTip(STR_BUTTON_OK, STR_NULL),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _query_string_desc(
 
	190, 219, 260, 42,
 
	WC_QUERY_STRING, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
 
	_nested_query_string_widgets, lengthof(_nested_query_string_widgets)
 
);
 

	
 
/** Show a query popup window with a textbox in it.
 
 * @param str StringID for the text shown in the textbox
 
 * @param caption StringID of text shown in caption of querywindow
 
 * @param maxsize maximum size in bytes (including terminating '\0')
 
 * @param maxwidth maximum width in pixels allowed
 
 * @param parent pointer to a Window that will handle the events (ok/cancel) of this
 
 *        window. If NULL, results are handled by global function HandleOnEditText
 
 * @param afilter filters out unwanted character input
 
 * @param flags various flags, @see QueryStringFlags
 
 */
 
void ShowQueryString(StringID str, StringID caption, uint maxsize, uint maxwidth, Window *parent, CharSetFilter afilter, QueryStringFlags flags)
 
{
 
	DeleteWindowById(WC_QUERY_STRING, 0);
 
	new QueryStringWindow(str, caption, maxsize, maxwidth, &_query_string_desc, parent, afilter, flags);
 
}
 

	
 

	
 
enum QueryWidgets {
 
	QUERY_WIDGET_CLOSEBOX,
 
	QUERY_WIDGET_CAPTION,
 
	QUERY_WIDGET_BACKGROUND,
 
	QUERY_WIDGET_TEXT,
 
	QUERY_WIDGET_NO,
 
	QUERY_WIDGET_YES
 
};
 

	
 
/**
 
 * Window used for asking the user a YES/NO question.
 
 */
 
struct QueryWindow : public Window {
 
	QueryCallbackProc *proc; ///< callback function executed on closing of popup. Window* points to parent, bool is true if 'yes' clicked, false otherwise
 
	uint64 params[10];       ///< local copy of _decode_parameters
 
	StringID message;        ///< message shown for query window
 
	StringID caption;        ///< title of window
 

	
 
	QueryWindow(const WindowDesc *desc, StringID caption, StringID message, Window *parent, QueryCallbackProc *callback) : Window()
 
	{
 
		/* Create a backup of the variadic arguments to strings because it will be
 
		 * overridden pretty often. We will copy these back for drawing */
 
		CopyOutDParam(this->params, 0, lengthof(this->params));
 
		this->caption = caption;
 
		this->message = message;
 
		this->proc    = callback;
 

	
 
		this->InitNested(desc);
 

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

	
 
	~QueryWindow()
 
	{
 
		if (this->proc != NULL) this->proc(this->parent, false);
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		switch (widget) {
 
			case QUERY_WIDGET_CAPTION:
 
				CopyInDParam(1, this->params, lengthof(this->params));
 
				SetDParam(0, this->caption);
 
				break;
 

	
 
			case QUERY_WIDGET_TEXT:
 
				CopyInDParam(0, this->params, lengthof(this->params));
 
				break;
 
		}
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		if (widget != QUERY_WIDGET_TEXT) return;
 

	
 
		Dimension d = GetStringMultiLineBoundingBox(this->message, *size);
 
		d.width += padding.width;
 
		d.height += padding.height;
 
		*size = d;
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		if (widget != QUERY_WIDGET_TEXT) return;
 

	
 
		DrawStringMultiLine(r.left, r.right, r.top, r.bottom, this->message, TC_FROMSTRING, SA_CENTER);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case QUERY_WIDGET_YES: {
 
				/* in the Generate New World window, clicking 'Yes' causes
 
				 * DeleteNonVitalWindows() to be called - we shouldn't be in a window then */
 
				QueryCallbackProc *proc = this->proc;
 
				Window *parent = this->parent;
 
				/* Prevent the destructor calling the callback function */
 
				this->proc = NULL;
 
				delete this;
 
				if (proc != NULL) {
 
					proc(parent, true);
 
					proc = NULL;
 
				}
 
			} break;
 
			case QUERY_WIDGET_NO:
 
				delete this;
 
				break;
 
		}
 
	}
 

	
 
	virtual EventState OnKeyPress(uint16 key, uint16 keycode)
 
	{
 
		/* ESC closes the window, Enter confirms the action */
 
		switch (keycode) {
 
			case WKC_RETURN:
 
			case WKC_NUM_ENTER:
 
				if (this->proc != NULL) {
 
					this->proc(this->parent, true);
 
					this->proc = NULL;
 
				}
 
				/* Fallthrough */
 
			case WKC_ESC:
 
				delete this;
 
				return ES_HANDLED;
 
		}
 
		return ES_NOT_HANDLED;
 
	}
 
};
 

	
 
static const NWidgetPart _nested_query_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_RED, QUERY_WIDGET_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_RED, QUERY_WIDGET_CAPTION), SetDataTip(STR_JUST_STRING, STR_NULL),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_RED, QUERY_WIDGET_BACKGROUND), SetPIP(8, 15, 8),
 
		NWidget(WWT_TEXT, COLOUR_RED, QUERY_WIDGET_TEXT), SetMinimalSize(200, 12),
 
		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(20, 29, 20),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, QUERY_WIDGET_NO), SetMinimalSize(71, 12), SetDataTip(STR_QUIT_NO, STR_NULL),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, QUERY_WIDGET_YES), SetMinimalSize(71, 12), SetDataTip(STR_QUIT_YES, STR_NULL),
 
		EndContainer(),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _query_desc(
 
	WDP_CENTER, WDP_CENTER, 210, 82,
 
	WC_CONFIRM_POPUP_QUERY, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_UNCLICK_BUTTONS | WDF_DEF_WIDGET | WDF_MODAL,
 
	_nested_query_widgets, lengthof(_nested_query_widgets)
 
);
 

	
 
/** Show a modal confirmation window with standard 'yes' and 'no' buttons
 
 * The window is aligned to the centre of its parent.
 
 * @param caption string shown as window caption
 
 * @param message string that will be shown for the window
 
 * @param parent pointer to parent window, if this pointer is NULL the parent becomes
 
 * the main window WC_MAIN_WINDOW
 
 * @param callback callback function pointer to set in the window descriptor
 
 */
 
void ShowQuery(StringID caption, StringID message, Window *parent, QueryCallbackProc *callback)
 
{
 
	new QueryWindow(&_query_desc, caption, message, parent, callback);
 
}
 

	
 

	
 
enum SaveLoadWindowWidgets {
 
	SLWW_CLOSE = 0,
 
	SLWW_WINDOWTITLE,
 
	SLWW_SORT_BYNAME,
 
	SLWW_SORT_BYDATE,
 
	SLWW_BACKGROUND,
 
	SLWW_FILE_BACKGROUND,
 
	SLWW_HOME_BUTTON,
 
	SLWW_DRIVES_DIRECTORIES_LIST,
 
	SLWW_SCROLLBAR,
 
	SLWW_CONTENT_DOWNLOAD,     ///< only available for play scenario/heightmap (content download)
 
	SLWW_SAVE_OSK_TITLE,       ///< only available for save operations
 
	SLWW_DELETE_SELECTION,     ///< same in here
 
	SLWW_SAVE_GAME,            ///< not to mention in here too
 
	SLWW_RESIZE,
 
	SLWW_CONTENT_DOWNLOAD_SEL, ///< Selection 'stack' to 'hide' the content download
 
};
 

	
 
static const NWidgetPart _nested_load_dialog_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, SLWW_CLOSE),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, SLWW_WINDOWTITLE),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SLWW_SORT_BYNAME), SetDataTip(STR_SORT_BY_CAPTION_NAME, STR_TOOLTIP_SORT_ORDER), SetFill(true, false), SetResize(1, 0),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SLWW_SORT_BYDATE), SetDataTip(STR_SORT_BY_CAPTION_DATE, STR_TOOLTIP_SORT_ORDER), SetFill(true, false), SetResize(1, 0),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SLWW_SORT_BYNAME), SetDataTip(STR_SORT_BY_CAPTION_NAME, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0), SetResize(1, 0),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SLWW_SORT_BYDATE), SetDataTip(STR_SORT_BY_CAPTION_DATE, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0), SetResize(1, 0),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, SLWW_BACKGROUND), SetFill(true, false), SetResize(1, 0), EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, SLWW_BACKGROUND), SetFill(1, 0), SetResize(1, 0), EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, SLWW_FILE_BACKGROUND),
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(NWID_VERTICAL),
 
				NWidget(WWT_INSET, COLOUR_GREY, SLWW_DRIVES_DIRECTORIES_LIST), SetFill(true, true), SetPadding(2, 1, 2, 2),
 
				NWidget(WWT_INSET, COLOUR_GREY, SLWW_DRIVES_DIRECTORIES_LIST), SetFill(1, 1), SetPadding(2, 1, 2, 2),
 
										SetDataTip(0x0, STR_SAVELOAD_LIST_TOOLTIP), SetResize(1, 10), EndContainer(),
 
				NWidget(NWID_SELECTION, INVALID_COLOUR, SLWW_CONTENT_DOWNLOAD_SEL),
 
					NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SLWW_CONTENT_DOWNLOAD), SetResize(1, 0),
 
										SetDataTip(STR_INTRO_ONLINE_CONTENT, STR_INTRO_TOOLTIP_ONLINE_CONTENT),
 
				EndContainer(),
 
			EndContainer(),
 
			NWidget(NWID_VERTICAL),
 
				NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, SLWW_HOME_BUTTON), SetMinimalSize(12, 12), SetDataTip(SPR_HOUSE_ICON, STR_SAVELOAD_HOME_BUTTON),
 
				NWidget(WWT_SCROLLBAR, COLOUR_GREY, SLWW_SCROLLBAR),
 
				NWidget(WWT_RESIZEBOX, COLOUR_GREY, SLWW_RESIZE),
 
			EndContainer(),
 
		EndContainer(),
 
	EndContainer(),
 
};
 

	
 
static const NWidgetPart _nested_save_dialog_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, SLWW_CLOSE),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, SLWW_WINDOWTITLE),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SLWW_SORT_BYNAME), SetDataTip(STR_SORT_BY_CAPTION_NAME, STR_TOOLTIP_SORT_ORDER), SetFill(true, false), SetResize(1, 0),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SLWW_SORT_BYDATE), SetDataTip(STR_SORT_BY_CAPTION_DATE, STR_TOOLTIP_SORT_ORDER), SetFill(true, false), SetResize(1, 0),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SLWW_SORT_BYNAME), SetDataTip(STR_SORT_BY_CAPTION_NAME, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0), SetResize(1, 0),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SLWW_SORT_BYDATE), SetDataTip(STR_SORT_BY_CAPTION_DATE, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0), SetResize(1, 0),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, SLWW_BACKGROUND), SetFill(true, false), SetResize(1, 0), EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, SLWW_BACKGROUND), SetFill(1, 0), SetResize(1, 0), EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, SLWW_FILE_BACKGROUND),
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(WWT_INSET, COLOUR_GREY, SLWW_DRIVES_DIRECTORIES_LIST), SetPadding(2, 1, 0, 2),
 
										SetDataTip(0x0, STR_SAVELOAD_LIST_TOOLTIP), SetResize(1, 10), EndContainer(),
 
			NWidget(NWID_VERTICAL),
 
				NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, SLWW_HOME_BUTTON), SetMinimalSize(12, 12), SetDataTip(SPR_HOUSE_ICON, STR_SAVELOAD_HOME_BUTTON),
 
				NWidget(WWT_SCROLLBAR, COLOUR_GREY, SLWW_SCROLLBAR),
 
			EndContainer(),
 
		EndContainer(),
 
		NWidget(WWT_EDITBOX, COLOUR_GREY, SLWW_SAVE_OSK_TITLE), SetPadding(3, 2, 2, 2), SetFill(true, false), SetResize(1, 0),
 
		NWidget(WWT_EDITBOX, COLOUR_GREY, SLWW_SAVE_OSK_TITLE), SetPadding(3, 2, 2, 2), SetFill(1, 0), SetResize(1, 0),
 
										SetDataTip(STR_SAVELOAD_OSKTITLE, STR_SAVELOAD_EDITBOX_TOOLTIP),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SLWW_DELETE_SELECTION), SetDataTip(STR_SAVELOAD_DELETE_BUTTON, STR_SAVELOAD_DELETE_TOOLTIP), SetFill(true, false), SetResize(1, 0),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SLWW_SAVE_GAME),        SetDataTip(STR_SAVELOAD_SAVE_BUTTON, STR_SAVELOAD_SAVE_TOOLTIP),     SetFill(true, false), SetResize(1, 0),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SLWW_DELETE_SELECTION), SetDataTip(STR_SAVELOAD_DELETE_BUTTON, STR_SAVELOAD_DELETE_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SLWW_SAVE_GAME),        SetDataTip(STR_SAVELOAD_SAVE_BUTTON, STR_SAVELOAD_SAVE_TOOLTIP),     SetFill(1, 0), SetResize(1, 0),
 
		NWidget(WWT_RESIZEBOX, COLOUR_GREY, SLWW_RESIZE),
 
	EndContainer(),
 
};
 

	
 
/* Colours for fios types */
 
const TextColour _fios_colours[] = {
 
	TC_LIGHT_BLUE, TC_DARK_GREEN,  TC_DARK_GREEN, TC_ORANGE, TC_LIGHT_BROWN,
 
	TC_ORANGE,     TC_LIGHT_BROWN, TC_ORANGE,     TC_ORANGE, TC_YELLOW
 
};
 

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

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

	
 
		default: FiosGetSavegameList(_saveload_mode); break;
 
	}
 
}
 

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

	
 
	/* Directories are always above the files (FIOS_TYPE_DIR)
 
	 * Drives (A:\ (windows only) are always under the files (FIOS_TYPE_DRIVE)
 
	 * Only sort savegames/scenarios, not directories
 
	 */
 
	for (const FiosItem *item = _fios_items.Begin(); item != _fios_items.End(); item++) {
 
		switch (item->type) {
 
			case FIOS_TYPE_DIR:    sort_start++; break;
 
			case FIOS_TYPE_PARENT: sort_start++; break;
 
			case FIOS_TYPE_DRIVE:  sort_end++;   break;
 
			default: break;
 
		}
 
	}
 

	
 
	uint s_amount = _fios_items.Length() - sort_start - sort_end;
 
	QSortT(_fios_items.Get(sort_start), s_amount, CompareFiosItems);
 
}
 

	
 
extern void StartupEngines();
 

	
 
struct SaveLoadWindow : public QueryStringBaseWindow {
 
private:
 
	FiosItem o_dir;
 
public:
 

	
 
	void GenerateFileName()
 
	{
 
		GenerateDefaultSaveName(this->edit_str_buf, &this->edit_str_buf[this->edit_str_size - 1]);
 
	}
 

	
 
	SaveLoadWindow(const WindowDesc *desc, SaveLoadDialogMode mode) : QueryStringBaseWindow(64)
 
	{
 
		static const StringID saveload_captions[] = {
 
			STR_SAVELOAD_LOAD_CAPTION,
 
			STR_SAVELOAD_LOAD_SCENARIO,
 
			STR_SAVELOAD_SAVE_CAPTION,
 
			STR_SAVELOAD_SAVE_SCENARIO,
 
			STR_SAVELOAD_LOAD_HEIGHTMAP,
 
		};
 
		assert((uint)mode < lengthof(saveload_captions));
 

	
 
		/* 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: strecpy(this->edit_str_buf, "UNNAMED", &this->edit_str_buf[edit_str_size - 1]); break;
 
			default:                break;
 
		}
 

	
 
		this->afilter = CS_ALPHANUMERAL;
 
		InitializeTextBuffer(&this->text, this->edit_str_buf, this->edit_str_size, 240);
 

	
 
		this->CreateNestedTree(desc);
 
		if (mode == SLD_LOAD_GAME) this->GetWidget<NWidgetStacked>(SLWW_CONTENT_DOWNLOAD_SEL)->SetDisplayedPlane(STACKED_SELECTION_ZERO_SIZE);
 
		this->GetWidget<NWidgetCore>(SLWW_WINDOWTITLE)->widget_data = saveload_captions[mode];
 

	
 
		this->FinishInitNested(desc, 0);
 

	
 
		this->LowerWidget(SLWW_DRIVES_DIRECTORIES_LIST);
 

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

	
 
		BuildFileList();
 

	
 
		ResetObjectToPlace();
 

	
 
		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 SLD_LOAD_HEIGHTMAP:
 
				FioGetDirectory(o_dir.name, lengthof(o_dir.name), HEIGHTMAP_DIR);
 
				break;
 

	
 
			default:
 
				strecpy(o_dir.name, _personal_dir, lastof(o_dir.name));
 
		}
 

	
 
		/* Focus the edit box by default in the save windows */
 
		if (_saveload_mode == SLD_SAVE_GAME || _saveload_mode == SLD_SAVE_SCENARIO) {
 
			this->SetFocusedWidget(SLWW_SAVE_OSK_TITLE);
 
		}
 
	}
 

	
 
	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) {
 
			DoCommandP(0, PM_PAUSED_SAVELOAD, 0, CMD_PAUSE);
 
		}
 
		FiosFreeSavegameList();
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case SLWW_SORT_BYNAME:
 
			case SLWW_SORT_BYDATE:
 
				if (((_savegame_sort_order & SORT_BY_NAME) != 0) == (widget == SLWW_SORT_BYNAME)) {
 
					this->DrawSortButtonState(widget, _savegame_sort_order & SORT_DESCENDING ? SBS_DOWN : SBS_UP);
 
				}
 
				break;
 

	
 
			case SLWW_BACKGROUND: {
 
				static const char *path = NULL;
 
				static StringID str = STR_ERROR_UNABLE_TO_READ_DRIVE;
 
				static uint64 tot = 0;
 

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

	
 
				if (str != STR_ERROR_UNABLE_TO_READ_DRIVE) SetDParam(0, tot);
 
				DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + FONT_HEIGHT_NORMAL + WD_FRAMERECT_TOP, str);
 
				DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, path, TC_BLACK);
 
			} break;
 

	
 
			case SLWW_DRIVES_DIRECTORIES_LIST: {
 
				GfxFillRect(r.left + 1, r.top + 1, r.right, r.bottom, 0xD7);
 

	
 
				uint y = r.top + WD_FRAMERECT_TOP;
 
				for (uint pos = this->vscroll.GetPosition(); pos < _fios_items.Length(); pos++) {
 
					const FiosItem *item = _fios_items.Get(pos);
 

	
 
					DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, item->title, _fios_colours[item->type]);
 
					y += this->resize.step_height;
 
					if (y >= this->vscroll.GetCapacity() * this->resize.step_height + r.top + WD_FRAMERECT_TOP) break;
 
				}
 
			} break;
 
		}
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		switch (widget) {
 
			case SLWW_CONTENT_DOWNLOAD_SEL:
 
				resize->width = 1;
 
				break;
 

	
 
			case SLWW_BACKGROUND:
 
				size->height = 2 * FONT_HEIGHT_NORMAL + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
 
				break;
 

	
 
			case SLWW_DRIVES_DIRECTORIES_LIST:
 
				resize->height = FONT_HEIGHT_NORMAL;
 
				size->height = resize->height * 10 + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		if (_savegame_sort_dirty) {
 
			_savegame_sort_dirty = false;
 
			MakeSortedSaveGameList();
 
		}
 

	
 
		this->vscroll.SetCount(_fios_items.Length());
 
		this->DrawWidgets();
 

	
 
		if (_saveload_mode == SLD_SAVE_GAME || _saveload_mode == SLD_SAVE_SCENARIO) {
 
			this->DrawEditBox(SLWW_SAVE_OSK_TITLE);
 
		}
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case SLWW_SORT_BYNAME: // 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 SLWW_SORT_BYDATE: // 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;
 

	
 
			case SLWW_HOME_BUTTON: // OpenTTD 'button', jumps to OpenTTD directory
 
				FiosBrowseTo(&o_dir);
 
				this->SetDirty();
 
				BuildFileList();
 
				break;
 

	
 
			case SLWW_DRIVES_DIRECTORIES_LIST: { // Click the listbox
 
				int y = (pt.y - this->GetWidget<NWidgetBase>(SLWW_DRIVES_DIRECTORIES_LIST)->pos_y - WD_FRAMERECT_TOP) / this->resize.step_height;
 

	
 
				if (y < 0 || (y += this->vscroll.GetPosition()) >= this->vscroll.GetCount()) return;
 

	
 
				const FiosItem *file = _fios_items.Get(y);
 

	
 
				const char *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);
 
						strecpy(_file_to_saveload.name, name, lastof(_file_to_saveload.name));
 
						strecpy(_file_to_saveload.title, file->title, lastof(_file_to_saveload.title));
 

	
 
						delete this;
 
					} else if (_saveload_mode == SLD_LOAD_HEIGHTMAP) {
 
						SetFiosType(file->type);
 
						strecpy(_file_to_saveload.name, name, lastof(_file_to_saveload.name));
 
						strecpy(_file_to_saveload.title, file->title, lastof(_file_to_saveload.title));
 

	
 
						delete this;
 
						ShowHeightmapLoad();
 
					} else {
 
						/* SLD_SAVE_GAME, SLD_SAVE_SCENARIO copy clicked name to editbox */
 
						ttd_strlcpy(this->text.buf, file->title, this->text.maxsize);
 
						UpdateTextBufferSize(&this->text);
 
						this->SetWidgetDirty(SLWW_SAVE_OSK_TITLE);
 
					}
 
				} else {
 
					/* Changed directory, need repaint. */
 
					this->SetDirty();
 
					BuildFileList();
 
				}
 
				break;
 
			}
 

	
 
			case SLWW_CONTENT_DOWNLOAD:
 
				if (!_network_available) {
 
					ShowErrorMessage(STR_NETWORK_ERROR_NOTAVAILABLE, INVALID_STRING_ID, 0, 0);
 
				} else {
 
#if defined(ENABLE_NETWORK)
 
					switch (_saveload_mode) {
 
						default: NOT_REACHED();
 
						case SLD_LOAD_SCENARIO:  ShowNetworkContentListWindow(NULL, CONTENT_TYPE_SCENARIO);  break;
 
						case SLD_LOAD_HEIGHTMAP: ShowNetworkContentListWindow(NULL, CONTENT_TYPE_HEIGHTMAP); break;
 
					}
 
#endif
 
				}
 
				break;
 

	
 
			case SLWW_DELETE_SELECTION: case SLWW_SAVE_GAME: // Delete, Save game
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnMouseLoop()
 
	{
 
		if (_saveload_mode == SLD_SAVE_GAME || _saveload_mode == SLD_SAVE_SCENARIO) {
 
			this->HandleEditBox(SLWW_SAVE_OSK_TITLE);
 
		}
 
	}
 

	
 
	virtual EventState OnKeyPress(uint16 key, uint16 keycode)
 
	{
 
		if (keycode == WKC_ESC) {
 
			delete this;
 
			return ES_HANDLED;
 
		}
 

	
 
		EventState state = ES_NOT_HANDLED;
 
		if ((_saveload_mode == SLD_SAVE_GAME || _saveload_mode == SLD_SAVE_SCENARIO) &&
 
				this->HandleEditBoxKey(SLWW_SAVE_OSK_TITLE, key, keycode, state) == HEBR_CONFIRM) {
 
			this->HandleButtonClick(SLWW_SAVE_GAME);
 
		}
 

	
 
		return state;
 
	}
 

	
 
	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;
 

	
 
		if (this->IsWidgetLowered(SLWW_DELETE_SELECTION)) { // Delete button clicked
 
			if (!FiosDelete(this->text.buf)) {
 
				ShowErrorMessage(STR_ERROR_UNABLE_TO_DELETE_FILE, INVALID_STRING_ID, 0, 0);
 
			} else {
 
				BuildFileList();
 
				/* Reset file name to current date on successful delete */
 
				if (_saveload_mode == SLD_SAVE_GAME) GenerateFileName();
 
			}
 

	
 
			UpdateTextBufferSize(&this->text);
 
			this->SetDirty();
 
		} else if (this->IsWidgetLowered(SLWW_SAVE_GAME)) { // 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()
 
	{
 
		this->vscroll.SetCapacity(this->GetWidget<NWidgetBase>(SLWW_DRIVES_DIRECTORIES_LIST)->current_y / this->resize.step_height);
 
	}
 

	
 
	virtual void OnInvalidateData(int data)
 
	{
 
		BuildFileList();
 
	}
 
};
 

	
 
static const WindowDesc _load_dialog_desc(
 
	WDP_CENTER, WDP_CENTER, 257, 294,
 
	WC_SAVELOAD, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_STD_BTN | WDF_UNCLICK_BUTTONS | WDF_RESIZABLE,
 
	_nested_load_dialog_widgets, lengthof(_nested_load_dialog_widgets)
 
);
 

	
 
static const WindowDesc _save_dialog_desc(
 
	WDP_CENTER, WDP_CENTER, 257, 320,
 
	WC_SAVELOAD, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_STD_BTN | WDF_UNCLICK_BUTTONS | WDF_RESIZABLE,
 
	_nested_save_dialog_widgets, lengthof(_nested_save_dialog_widgets)
 
);
 

	
 
/** These values are used to convert the file/operations mode into a corresponding file type.
 
 * So each entry, as expressed by the related comment, is based on the enum   */
 
static const FileType _file_modetotype[] = {
 
	FT_SAVEGAME,  ///< used for SLD_LOAD_GAME
 
	FT_SCENARIO,  ///< used for SLD_LOAD_SCENARIO
 
	FT_SAVEGAME,  ///< used for SLD_SAVE_GAME
 
	FT_SCENARIO,  ///< used for SLD_SAVE_SCENARIO
 
	FT_HEIGHTMAP, ///< used for SLD_LOAD_HEIGHTMAP
 
	FT_SAVEGAME,  ///< SLD_NEW_GAME
 
};
 

	
 
void ShowSaveLoadDialog(SaveLoadDialogMode mode)
 
{
 
	DeleteWindowById(WC_SAVELOAD, 0);
 

	
 
	const WindowDesc *sld;
 
	switch (mode) {
 
		case SLD_SAVE_GAME:
 
		case SLD_SAVE_SCENARIO:
 
			sld = &_save_dialog_desc; break;
 
		default:
 
			sld = &_load_dialog_desc; break;
 
	}
 

	
 
	_saveload_mode = mode;
 
	_file_to_saveload.filetype = _file_modetotype[mode];
 

	
 
	new SaveLoadWindow(sld, mode);
 
}
 

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

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

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

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

	
 
		case FIOS_TYPE_BMP:
 
			_file_to_saveload.mode = SL_BMP;
 
			break;
 

	
 
		default:
 
			_file_to_saveload.mode = SL_INVALID;
 
			break;
 
	}
 
}
src/music_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file music_gui.cpp GUI for the music playback. */
 

	
 
#include "stdafx.h"
 
#include "openttd.h"
 
#include "fileio_func.h"
 
#include "music.h"
 
#include "music/music_driver.hpp"
 
#include "window_gui.h"
 
#include "strings_func.h"
 
#include "window_func.h"
 
#include "sound_func.h"
 
#include "gfx_func.h"
 
#include "core/random_func.hpp"
 

	
 
#include "table/strings.h"
 
#include "table/sprites.h"
 

	
 
static byte _music_wnd_cursong;
 
static bool _song_is_active;
 
static byte _cur_playlist[NUM_SONGS_PLAYLIST];
 

	
 

	
 

	
 
static byte _playlist_all[] = {
 
	1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 0
 
};
 

	
 
static byte _playlist_old_style[] = {
 
	1, 8, 2, 9, 14, 15, 19, 13, 0
 
};
 

	
 
static byte _playlist_new_style[] = {
 
	6, 11, 10, 17, 21, 18, 5, 0
 
};
 

	
 
static byte _playlist_ezy_street[] = {
 
	12, 7, 16, 3, 20, 4, 0
 
};
 

	
 
static byte * const _playlists[] = {
 
	_playlist_all,
 
	_playlist_old_style,
 
	_playlist_new_style,
 
	_playlist_ezy_street,
 
	msf.custom_1,
 
	msf.custom_2,
 
};
 

	
 
static void SkipToPrevSong()
 
{
 
	byte *b = _cur_playlist;
 
	byte *p = b;
 
	byte t;
 

	
 
	if (b[0] == 0) return; // empty playlist
 

	
 
	do p++; while (p[0] != 0); // find the end
 

	
 
	t = *--p; // and copy the bytes
 
	while (p != b) {
 
		p--;
 
		p[1] = p[0];
 
	}
 
	*b = t;
 

	
 
	_song_is_active = false;
 
}
 

	
 
static void SkipToNextSong()
 
{
 
	byte *b = _cur_playlist;
 
	byte t;
 

	
 
	t = b[0];
 
	if (t != 0) {
 
		while (b[1] != 0) {
 
			b[0] = b[1];
 
			b++;
 
		}
 
		b[0] = t;
 
	}
 

	
 
	_song_is_active = false;
 
}
 

	
 
static void MusicVolumeChanged(byte new_vol)
 
{
 
	_music_driver->SetVolume(new_vol);
 
}
 

	
 
static void DoPlaySong()
 
{
 
	char filename[MAX_PATH];
 
	FioFindFullPath(filename, lengthof(filename), GM_DIR,
 
			_origin_songs_specs[_music_wnd_cursong - 1].filename);
 
	_music_driver->PlaySong(filename);
 
}
 

	
 
static void DoStopMusic()
 
{
 
	_music_driver->StopSong();
 
}
 

	
 
static void SelectSongToPlay()
 
{
 
	uint i = 0;
 
	uint j = 0;
 

	
 
	memset(_cur_playlist, 0, sizeof(_cur_playlist));
 
	do {
 
		/* We are now checking for the existence of that file prior
 
		 * to add it to the list of available songs */
 
		if (FioCheckFileExists(_origin_songs_specs[_playlists[msf.playlist][i] - 1].filename, GM_DIR)) {
 
			_cur_playlist[j] = _playlists[msf.playlist][i];
 
			j++;
 
		}
 
	} while (_playlists[msf.playlist][++i] != 0 && j < lengthof(_cur_playlist) - 1);
 

	
 
	/* Do not shuffle when on the intro-start window, as the song to play has to be the original TTD Theme*/
 
	if (msf.shuffle && _game_mode != GM_MENU) {
 
		i = 500;
 
		do {
 
			uint32 r = InteractiveRandom();
 
			byte *a = &_cur_playlist[GB(r, 0, 5)];
 
			byte *b = &_cur_playlist[GB(r, 8, 5)];
 

	
 
			if (*a != 0 && *b != 0) {
 
				byte t = *a;
 
				*a = *b;
 
				*b = t;
 
			}
 
		} while (--i);
 
	}
 
}
 

	
 
static void StopMusic()
 
{
 
	_music_wnd_cursong = 0;
 
	DoStopMusic();
 
	_song_is_active = false;
 
	SetWindowWidgetDirty(WC_MUSIC_WINDOW, 0, 9);
 
}
 

	
 
static void PlayPlaylistSong()
 
{
 
	if (_cur_playlist[0] == 0) {
 
		SelectSongToPlay();
 
		/* if there is not songs in the playlist, it may indicate
 
		 * no file on the gm folder, or even no gm folder.
 
		 * Stop the playback, then */
 
		if (_cur_playlist[0] == 0) {
 
			_song_is_active = false;
 
			_music_wnd_cursong = 0;
 
			msf.playing = false;
 
			return;
 
		}
 
	}
 
	_music_wnd_cursong = _cur_playlist[0];
 
	DoPlaySong();
 
	_song_is_active = true;
 

	
 
	SetWindowWidgetDirty(WC_MUSIC_WINDOW, 0, 9);
 
}
 

	
 
void ResetMusic()
 
{
 
	_music_wnd_cursong = 1;
 
	DoPlaySong();
 
}
 

	
 
void MusicLoop()
 
{
 
	if (!msf.playing && _song_is_active) {
 
		StopMusic();
 
	} else if (msf.playing && !_song_is_active) {
 
		PlayPlaylistSong();
 
	}
 

	
 
	if (!_song_is_active) return;
 

	
 
	if (!_music_driver->IsSongPlaying()) {
 
		if (_game_mode != GM_MENU) {
 
			StopMusic();
 
			SkipToNextSong();
 
			PlayPlaylistSong();
 
		} else {
 
			ResetMusic();
 
		}
 
	}
 
}
 

	
 
static void SelectPlaylist(byte list)
 
{
 
	msf.playlist = list;
 
	InvalidateWindowData(WC_MUSIC_TRACK_SELECTION, 0);
 
	InvalidateWindowData(WC_MUSIC_WINDOW, 0);
 
}
 

	
 
enum MusicTrackSelectionWidgets {
 
	MTSW_CLOSE,
 
	MTSW_CAPTION,
 
	MTSW_BACKGROUND,
 
	MTSW_TRACKLIST,
 
	MTSW_LIST_LEFT,
 
	MTSW_PLAYLIST,
 
	MTSW_LIST_RIGHT,
 
	MTSW_ALL,
 
	MTSW_OLD,
 
	MTSW_NEW,
 
	MTSW_EZY,
 
	MTSW_CUSTOM1,
 
	MTSW_CUSTOM2,
 
	MTSW_CLEAR,
 
};
 

	
 
struct MusicTrackSelectionWindow : public Window {
 
	MusicTrackSelectionWindow(const WindowDesc *desc, WindowNumber number) : Window()
 
	{
 
		this->InitNested(desc, number);
 
		this->LowerWidget(MTSW_LIST_LEFT);
 
		this->LowerWidget(MTSW_LIST_RIGHT);
 
		this->SetWidgetDisabledState(MTSW_CLEAR, msf.playlist <= 3);
 
		this->LowerWidget(MTSW_ALL + msf.playlist);
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		switch (widget) {
 
			case MTSW_PLAYLIST:
 
				SetDParam(0, STR_MUSIC_PLAYLIST_ALL + msf.playlist);
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnInvalidateData(int data = 0)
 
	{
 
		for (int i = 0; i < 6; i++) {
 
			this->SetWidgetLoweredState(MTSW_ALL + i, i == msf.playlist);
 
		}
 
		this->SetWidgetDisabledState(MTSW_CLEAR, msf.playlist <= 3);
 
		this->SetDirty();
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		switch (widget) {
 
			case MTSW_PLAYLIST: {
 
				Dimension d = {0, 0};
 

	
 
				for (int i = 0; i < 6; i++) {
 
					SetDParam(0, STR_MUSIC_PLAYLIST_ALL + i);
 
					d = maxdim(d, GetStringBoundingBox(STR_PLAYLIST_PROGRAM));
 
				}
 
				d.width += padding.width;
 
				d.height += padding.height;
 
				*size = maxdim(*size, d);
 
			} break;
 

	
 
			case MTSW_LIST_LEFT: case MTSW_LIST_RIGHT: {
 
				Dimension d = {0, 0};
 

	
 
				for (uint i = 1; i <= NUM_SONGS_AVAILABLE; i++) {
 
					SetDParam(0, i);
 
					SetDParam(1, 2);
 
					SetDParam(2, SPECSTR_SONGNAME);
 
					SetDParam(3, i);
 
					Dimension d2 = GetStringBoundingBox(STR_PLAYLIST_TRACK_NAME);
 
					d.width = max(d.width, d2.width);
 
					d.height += d2.height;
 
				}
 
				d.width += padding.width;
 
				d.height += padding.height;
 
				*size = maxdim(*size, d);
 
			} break;
 
		}
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case MTSW_LIST_LEFT: {
 
				GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, 0);
 

	
 
				int y = r.top + WD_FRAMERECT_TOP;
 
				for (uint i = 1; i <= NUM_SONGS_AVAILABLE; i++) {
 
					SetDParam(0, i);
 
					SetDParam(1, 2);
 
					SetDParam(2, SPECSTR_SONGNAME);
 
					SetDParam(3, i);
 
					DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_PLAYLIST_TRACK_NAME);
 
					y += FONT_HEIGHT_SMALL;
 
				}
 
			} break;
 

	
 
			case MTSW_LIST_RIGHT: {
 
				GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, 0);
 

	
 
				int y = r.top + WD_FRAMERECT_TOP;
 
				for (const byte *p = _playlists[msf.playlist]; *p != 0; p++) {
 
					uint i = *p;
 
					SetDParam(0, i);
 
					SetDParam(1, 2);
 
					SetDParam(2, SPECSTR_SONGNAME);
 
					SetDParam(3, i);
 
					DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_PLAYLIST_TRACK_NAME);
 
					y += FONT_HEIGHT_SMALL;
 
				}
 
			} break;
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case MTSW_LIST_LEFT: { // add to playlist
 
				int y = (pt.y - this->GetWidget<NWidgetBase>(widget)->pos_y) / FONT_HEIGHT_SMALL;
 

	
 
				if (msf.playlist < 4) return;
 
				if (!IsInsideMM(y, 0, NUM_SONGS_AVAILABLE)) return;
 

	
 
				byte *p = _playlists[msf.playlist];
 
				for (uint i = 0; i != NUM_SONGS_PLAYLIST - 1; i++) {
 
					if (p[i] == 0) {
 
						p[i] = y + 1;
 
						p[i + 1] = 0;
 
						this->SetDirty();
 
						SelectSongToPlay();
 
						break;
 
					}
 
				}
 
			} break;
 

	
 
			case MTSW_LIST_RIGHT: { // remove from playlist
 
				int y = (pt.y - this->GetWidget<NWidgetBase>(widget)->pos_y) / FONT_HEIGHT_SMALL;
 

	
 
				if (msf.playlist < 4) return;
 
				if (!IsInsideMM(y, 0, NUM_SONGS_AVAILABLE)) return;
 

	
 
				byte *p = _playlists[msf.playlist];
 
				for (uint i = y; i != NUM_SONGS_PLAYLIST - 1; i++) {
 
					p[i] = p[i + 1];
 
				}
 

	
 
				this->SetDirty();
 
				SelectSongToPlay();
 
			} break;
 

	
 
			case MTSW_CLEAR: // clear
 
				_playlists[msf.playlist][0] = 0;
 
				this->SetDirty();
 
				StopMusic();
 
				SelectSongToPlay();
 
				break;
 

	
 
			case MTSW_ALL: case MTSW_OLD: case MTSW_NEW:
 
			case MTSW_EZY: case MTSW_CUSTOM1: case MTSW_CUSTOM2: // set playlist
 
				SelectPlaylist(widget - MTSW_ALL);
 
				StopMusic();
 
				SelectSongToPlay();
 
				break;
 
		}
 
	}
 
};
 

	
 
static const NWidgetPart _nested_music_track_selection_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, MTSW_CLOSE),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, MTSW_CAPTION), SetDataTip(STR_PLAYLIST_MUSIC_PROGRAM_SELECTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, MTSW_BACKGROUND),
 
		NWidget(NWID_HORIZONTAL), SetPIP(2, 4, 2),
 
			/* Left panel. */
 
			NWidget(NWID_VERTICAL),
 
				NWidget(WWT_LABEL, COLOUR_GREY, MTSW_TRACKLIST), SetDataTip(STR_PLAYLIST_TRACK_INDEX, STR_NULL),
 
				NWidget(WWT_PANEL, COLOUR_GREY, MTSW_LIST_LEFT), SetMinimalSize(180, 194), SetDataTip(0x0, STR_PLAYLIST_TOOLTIP_CLICK_TO_ADD_TRACK), EndContainer(),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 2),
 
			EndContainer(),
 
			/* Middle buttons. */
 
			NWidget(NWID_VERTICAL),
 
				NWidget(NWID_SPACER), SetMinimalSize(60, 30), // Space above the first button from the title bar.
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, MTSW_ALL), SetFill(true, false), SetDataTip(STR_MUSIC_PLAYLIST_ALL, STR_MUSIC_TOOLTIP_SELECT_ALL_TRACKS_PROGRAM),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, MTSW_OLD), SetFill(true, false), SetDataTip(STR_MUSIC_PLAYLIST_OLD_STYLE, STR_MUSIC_TOOLTIP_SELECT_OLD_STYLE_MUSIC),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, MTSW_NEW), SetFill(true, false), SetDataTip(STR_MUSIC_PLAYLIST_NEW_STYLE, STR_MUSIC_TOOLTIP_SELECT_NEW_STYLE_MUSIC),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, MTSW_EZY), SetFill(true, false), SetDataTip(STR_MUSIC_PLAYLIST_EZY_STREET, STR_MUSIC_TOOLTIP_SELECT_EZY_STREET_STYLE),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, MTSW_CUSTOM1), SetFill(true, false), SetDataTip(STR_MUSIC_PLAYLIST_CUSTOM_1, STR_MUSIC_TOOLTIP_SELECT_CUSTOM_1_USER_DEFINED),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, MTSW_CUSTOM2), SetFill(true, false), SetDataTip(STR_MUSIC_PLAYLIST_CUSTOM_2, STR_MUSIC_TOOLTIP_SELECT_CUSTOM_2_USER_DEFINED),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, MTSW_ALL), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_ALL, STR_MUSIC_TOOLTIP_SELECT_ALL_TRACKS_PROGRAM),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, MTSW_OLD), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_OLD_STYLE, STR_MUSIC_TOOLTIP_SELECT_OLD_STYLE_MUSIC),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, MTSW_NEW), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_NEW_STYLE, STR_MUSIC_TOOLTIP_SELECT_NEW_STYLE_MUSIC),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, MTSW_EZY), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_EZY_STREET, STR_MUSIC_TOOLTIP_SELECT_EZY_STREET_STYLE),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, MTSW_CUSTOM1), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_CUSTOM_1, STR_MUSIC_TOOLTIP_SELECT_CUSTOM_1_USER_DEFINED),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, MTSW_CUSTOM2), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_CUSTOM_2, STR_MUSIC_TOOLTIP_SELECT_CUSTOM_2_USER_DEFINED),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 16), // Space above 'clear' button
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, MTSW_CLEAR), SetFill(true, false), SetDataTip(STR_PLAYLIST_CLEAR, STR_PLAYLIST_TOOLTIP_CLEAR_CURRENT_PROGRAM_CUSTOM1),
 
				NWidget(NWID_SPACER), SetFill(false, true),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, MTSW_CLEAR), SetFill(1, 0), SetDataTip(STR_PLAYLIST_CLEAR, STR_PLAYLIST_TOOLTIP_CLEAR_CURRENT_PROGRAM_CUSTOM1),
 
				NWidget(NWID_SPACER), SetFill(0, 1),
 
			EndContainer(),
 
			/* Right panel. */
 
			NWidget(NWID_VERTICAL),
 
				NWidget(WWT_LABEL, COLOUR_GREY, MTSW_PLAYLIST), SetDataTip(STR_PLAYLIST_PROGRAM, STR_NULL),
 
				NWidget(WWT_PANEL, COLOUR_GREY, MTSW_LIST_RIGHT), SetMinimalSize(180, 194), SetDataTip(0x0, STR_PLAYLIST_TOOLTIP_CLICK_TO_REMOVE_TRACK), EndContainer(),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 2),
 
			EndContainer(),
 
		EndContainer(),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _music_track_selection_desc(
 
	104, 131, 432, 218,
 
	WC_MUSIC_TRACK_SELECTION, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
 
	_nested_music_track_selection_widgets, lengthof(_nested_music_track_selection_widgets)
 
);
 

	
 
static void ShowMusicTrackSelection()
 
{
 
	AllocateWindowDescFront<MusicTrackSelectionWindow>(&_music_track_selection_desc, 0);
 
}
 

	
 
enum MusicWidgets {
 
	MW_CLOSE,
 
	MW_CAPTION,
 
	MW_PREV,
 
	MW_NEXT,
 
	MW_STOP,
 
	MW_PLAY,
 
	MW_SLIDERS,
 
	MW_MUSIC_VOL,
 
	MW_GAUGE,
 
	MW_EFFECT_VOL,
 
	MW_BACKGROUND,
 
	MW_TRACK,
 
	MW_TRACK_NR,
 
	MW_TRACK_TITLE,
 
	MW_TRACK_NAME,
 
	MW_SHUFFLE,
 
	MW_PROGRAMME,
 
	MW_ALL,
 
	MW_OLD,
 
	MW_NEW,
 
	MW_EZY,
 
	MW_CUSTOM1,
 
	MW_CUSTOM2,
 
};
 

	
 
struct MusicWindow : public Window {
 
	static const int slider_bar_height = 4;
 
	static const int slider_height = 7;
 
	static const int slider_width = 3;
 

	
 
	MusicWindow(const WindowDesc *desc, WindowNumber number) : Window()
 
	{
 
		this->InitNested(desc, number);
 
		this->LowerWidget(msf.playlist + MW_ALL);
 
		this->SetWidgetLoweredState(MW_SHUFFLE, msf.shuffle);
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		switch (widget) {
 
			/* Make sure that MW_SHUFFLE and MW_PROGRAMME have the same size.
 
			 * This can't be done by using NC_EQUALSIZE as the MW_INFO is
 
			 * between those widgets and of different size. */
 
			case MW_SHUFFLE: case MW_PROGRAMME: {
 
				Dimension d = maxdim(GetStringBoundingBox(STR_MUSIC_PROGRAM), GetStringBoundingBox(STR_MUSIC_SHUFFLE));
 
				d.width += padding.width;
 
				d.height += padding.height;
 
				*size = maxdim(*size, d);
 
			} break;
 

	
 
			case MW_TRACK_NR: {
 
				Dimension d = GetStringBoundingBox(STR_MUSIC_TRACK_NONE);
 
				d.width += WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
 
				d.height += WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
 
				*size = maxdim(*size, d);
 
			} break;
 

	
 
			case MW_TRACK_NAME: {
 
				Dimension d = GetStringBoundingBox(STR_MUSIC_TITLE_NONE);
 
				for (int i = 0; i < NUM_SONGS_AVAILABLE; i++) {
 
					SetDParam(0, SPECSTR_SONGNAME);
 
					SetDParam(1, i);
 
					d = maxdim(d, GetStringBoundingBox(STR_MUSIC_TITLE_NAME));
 
				}
 
				d.width += WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
 
				d.height += WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
 
				*size = maxdim(*size, d);
 
			} break;
 

	
 
			/* Hack-ish: set the proper widget data; only needs to be done once
 
			 * per (Re)Init as that's the only time the language changes. */
 
			case MW_PREV: this->GetWidget<NWidgetCore>(MW_PREV)->widget_data = _dynlang.text_dir == TD_RTL ? SPR_IMG_SKIP_TO_NEXT : SPR_IMG_SKIP_TO_PREV; break;
 
			case MW_NEXT: this->GetWidget<NWidgetCore>(MW_NEXT)->widget_data = _dynlang.text_dir == TD_RTL ? SPR_IMG_SKIP_TO_PREV : SPR_IMG_SKIP_TO_NEXT; break;
 
			case MW_PLAY: this->GetWidget<NWidgetCore>(MW_PLAY)->widget_data = _dynlang.text_dir == TD_RTL ? SPR_IMG_PLAY_MUSIC_RTL : SPR_IMG_PLAY_MUSIC; break;
 
		}
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case MW_GAUGE:
 
				GfxFillRect(r.left, r.top, r.right, r.bottom, 0);
 

	
 
				for (uint i = 0; i != 8; i++) {
 
					int colour = 0xD0;
 
					if (i > 4) {
 
						colour = 0xBF;
 
						if (i > 6) {
 
							colour = 0xB8;
 
						}
 
					}
 
					GfxFillRect(r.left, r.bottom - i * 2, r.right, r.bottom - i * 2, colour);
 
				}
 
				break;
 

	
 
			case MW_TRACK_NR: {
 
				GfxFillRect(r.left + 1, r.top + 1, r.right, r.bottom, 0);
 
				StringID str = STR_MUSIC_TRACK_NONE;
 
				if (_song_is_active != 0 && _music_wnd_cursong != 0) {
 
					SetDParam(0, _music_wnd_cursong);
 
					SetDParam(0, 2);
 
					str = STR_MUSIC_TRACK_DIGIT;
 
				}
 
				DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, str);
 
			} break;
 

	
 
			case MW_TRACK_NAME: {
 
				GfxFillRect(r.left, r.top + 1, r.right - 1, r.bottom, 0);
 
				StringID str = STR_MUSIC_TITLE_NONE;
 
				if (_song_is_active != 0 && _music_wnd_cursong != 0) {
 
					str = STR_MUSIC_TITLE_NAME;
 
					SetDParam(0, SPECSTR_SONGNAME);
 
					SetDParam(1, _music_wnd_cursong);
 
				}
 
				DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, str, TC_FROMSTRING, SA_CENTER);
 
			} break;
 

	
 
			case MW_MUSIC_VOL: case MW_EFFECT_VOL: {
 
				DrawString(r.left, r.right, r.top + 1, (widget == MW_MUSIC_VOL) ? STR_MUSIC_MUSIC_VOLUME : STR_MUSIC_EFFECTS_VOLUME, TC_FROMSTRING, SA_CENTER);
 
				int y = (r.top + r.bottom - slider_bar_height) / 2;
 
				DrawFrameRect(r.left, y, r.right, y + slider_bar_height, COLOUR_GREY, FR_LOWERED);
 
				DrawString(r.left, r.right, r.bottom - FONT_HEIGHT_SMALL, STR_MUSIC_MIN_MAX_RULER, TC_FROMSTRING, SA_CENTER);
 
				y = (r.top + r.bottom - slider_height) / 2;
 
				byte volume = (widget == MW_MUSIC_VOL) ? msf.music_vol : msf.effect_vol;
 
				int x = (volume * (r.right - r.left) / 127);
 
				if (_dynlang.text_dir == TD_RTL) {
 
					x = r.right - x;
 
				} else {
 
					x += r.left;
 
				}
 
				DrawFrameRect(x, y, x + slider_width, y + slider_height, COLOUR_GREY, FR_NONE);
 
			} break;
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void OnInvalidateData(int data = 0)
 
	{
 
		for (int i = 0; i < 6; i++) {
 
			this->SetWidgetLoweredState(MW_ALL + i, i == msf.playlist);
 
		}
 
		this->SetDirty();
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case MW_PREV: // skip to prev
 
				if (!_song_is_active) return;
 
				SkipToPrevSong();
 
				this->SetDirty();
 
				break;
 

	
 
			case MW_NEXT: // skip to next
 
				if (!_song_is_active) return;
 
				SkipToNextSong();
 
				this->SetDirty();
 
				break;
 

	
 
			case MW_STOP: // stop playing
 
				msf.playing = false;
 
				break;
 

	
 
			case MW_PLAY: // start playing
 
				msf.playing = true;
 
				break;
 

	
 
			case MW_MUSIC_VOL: case MW_EFFECT_VOL: { // volume sliders
 
				int x = pt.x - this->GetWidget<NWidgetBase>(widget)->pos_x;
 

	
 
				byte *vol = (widget == MW_MUSIC_VOL) ? &msf.music_vol : &msf.effect_vol;
 

	
 
				byte new_vol = x * 127 / this->GetWidget<NWidgetBase>(widget)->current_x;
 
				if (_dynlang.text_dir == TD_RTL) new_vol = 127 - new_vol;
 
				if (new_vol != *vol) {
 
					*vol = new_vol;
 
					if (widget == MW_MUSIC_VOL) MusicVolumeChanged(new_vol);
 
					this->SetDirty();
 
				}
 

	
 
				_left_button_clicked = false;
 
			} break;
 

	
 
			case MW_SHUFFLE: // toggle shuffle
 
				msf.shuffle ^= 1;
 
				this->SetWidgetLoweredState(MW_SHUFFLE, msf.shuffle);
 
				this->SetWidgetDirty(MW_SHUFFLE);
 
				StopMusic();
 
				SelectSongToPlay();
 
				this->SetDirty();
 
				break;
 

	
 
			case MW_PROGRAMME: // show track selection
 
				ShowMusicTrackSelection();
 
				break;
 

	
 
			case MW_ALL: case MW_OLD: case MW_NEW:
 
			case MW_EZY: case MW_CUSTOM1: case MW_CUSTOM2: // playlist
 
				SelectPlaylist(widget - MW_ALL);
 
				StopMusic();
 
				SelectSongToPlay();
 
				this->SetDirty();
 
				break;
 
		}
 
	}
 

	
 
#if 0
 
	virtual void OnTick()
 
	{
 
		this->SetWidgetDirty(MW_GAUGE);
 
	}
 
#endif
 
};
 

	
 
static const NWidgetPart _nested_music_window_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, MW_CLOSE),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, MW_CAPTION), SetDataTip(STR_MUSIC_JAZZ_JUKEBOX_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 

	
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, MW_PREV), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_SKIP_TO_PREV, STR_MUSIC_TOOLTIP_SKIP_TO_PREVIOUS_TRACK),
 
		NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, MW_NEXT), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_SKIP_TO_NEXT, STR_MUSIC_TOOLTIP_SKIP_TO_NEXT_TRACK_IN_SELECTION),
 
		NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, MW_STOP), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_STOP_MUSIC, STR_MUSIC_TOOLTIP_STOP_PLAYING_MUSIC),
 
		NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, MW_PLAY), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_PLAY_MUSIC, STR_MUSIC_TOOLTIP_START_PLAYING_MUSIC),
 
		NWidget(WWT_PANEL, COLOUR_GREY, MW_SLIDERS),
 
			NWidget(NWID_HORIZONTAL), SetPIP(20, 0, 20),
 
				NWidget(WWT_EMPTY, COLOUR_GREY, MW_MUSIC_VOL), SetMinimalSize(67, 22), SetFill(false, false), SetDataTip(0x0, STR_MUSIC_TOOLTIP_DRAG_SLIDERS_TO_SET_MUSIC),
 
				NWidget(WWT_PANEL, COLOUR_GREY, MW_GAUGE), SetMinimalSize(16, 20), SetPadding(1, 11, 1, 11), SetFill(false, false), EndContainer(),
 
				NWidget(WWT_EMPTY, COLOUR_GREY, MW_EFFECT_VOL), SetMinimalSize(67, 22), SetFill(false, false), SetDataTip(0x0, STR_MUSIC_TOOLTIP_DRAG_SLIDERS_TO_SET_MUSIC),
 
				NWidget(NWID_SPACER), SetFill(true, false),
 
				NWidget(WWT_EMPTY, COLOUR_GREY, MW_MUSIC_VOL), SetMinimalSize(67, 22), SetFill(0, 0), SetDataTip(0x0, STR_MUSIC_TOOLTIP_DRAG_SLIDERS_TO_SET_MUSIC),
 
				NWidget(WWT_PANEL, COLOUR_GREY, MW_GAUGE), SetMinimalSize(16, 20), SetPadding(1, 11, 1, 11), SetFill(0, 0), EndContainer(),
 
				NWidget(WWT_EMPTY, COLOUR_GREY, MW_EFFECT_VOL), SetMinimalSize(67, 22), SetFill(0, 0), SetDataTip(0x0, STR_MUSIC_TOOLTIP_DRAG_SLIDERS_TO_SET_MUSIC),
 
				NWidget(NWID_SPACER), SetFill(1, 0),
 
			EndContainer(),
 
		EndContainer(),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, MW_BACKGROUND),
 
		NWidget(NWID_HORIZONTAL), SetPIP(6, 0, 6),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, MW_SHUFFLE), SetMinimalSize(50, 8), SetDataTip(STR_MUSIC_SHUFFLE, STR_MUSIC_TOOLTIP_TOGGLE_PROGRAM_SHUFFLE),
 
			NWidget(NWID_VERTICAL), SetPadding(0, 0, 3, 3),
 
				NWidget(WWT_LABEL, COLOUR_GREY, MW_TRACK), SetDataTip(STR_MUSIC_TRACK, STR_NULL),
 
				NWidget(WWT_PANEL, COLOUR_GREY, MW_TRACK_NR), EndContainer(),
 
			EndContainer(),
 
			NWidget(NWID_VERTICAL), SetPadding(0, 3, 3, 0),
 
				NWidget(WWT_LABEL, COLOUR_GREY, MW_TRACK_TITLE), SetFill(true, false), SetDataTip(STR_MUSIC_XTITLE, STR_NULL),
 
				NWidget(WWT_LABEL, COLOUR_GREY, MW_TRACK_TITLE), SetFill(1, 0), SetDataTip(STR_MUSIC_XTITLE, STR_NULL),
 
				NWidget(WWT_PANEL, COLOUR_GREY, MW_TRACK_NAME), EndContainer(),
 
			EndContainer(),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, MW_PROGRAMME), SetMinimalSize(50, 8), SetDataTip(STR_MUSIC_PROGRAM, STR_MUSIC_TOOLTIP_SHOW_MUSIC_TRACK_SELECTION),
 
		EndContainer(),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, MW_ALL), SetFill(true, false), SetDataTip(STR_MUSIC_PLAYLIST_ALL, STR_MUSIC_TOOLTIP_SELECT_ALL_TRACKS_PROGRAM),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, MW_OLD), SetFill(true, false), SetDataTip(STR_MUSIC_PLAYLIST_OLD_STYLE, STR_MUSIC_TOOLTIP_SELECT_OLD_STYLE_MUSIC),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, MW_NEW), SetFill(true, false), SetDataTip(STR_MUSIC_PLAYLIST_NEW_STYLE, STR_MUSIC_TOOLTIP_SELECT_NEW_STYLE_MUSIC),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, MW_EZY), SetFill(true, false), SetDataTip(STR_MUSIC_PLAYLIST_EZY_STREET, STR_MUSIC_TOOLTIP_SELECT_EZY_STREET_STYLE),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, MW_CUSTOM1), SetFill(true, false), SetDataTip(STR_MUSIC_PLAYLIST_CUSTOM_1, STR_MUSIC_TOOLTIP_SELECT_CUSTOM_1_USER_DEFINED),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, MW_CUSTOM2), SetFill(true, false), SetDataTip(STR_MUSIC_PLAYLIST_CUSTOM_2, STR_MUSIC_TOOLTIP_SELECT_CUSTOM_2_USER_DEFINED),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, MW_ALL), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_ALL, STR_MUSIC_TOOLTIP_SELECT_ALL_TRACKS_PROGRAM),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, MW_OLD), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_OLD_STYLE, STR_MUSIC_TOOLTIP_SELECT_OLD_STYLE_MUSIC),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, MW_NEW), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_NEW_STYLE, STR_MUSIC_TOOLTIP_SELECT_NEW_STYLE_MUSIC),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, MW_EZY), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_EZY_STREET, STR_MUSIC_TOOLTIP_SELECT_EZY_STREET_STYLE),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, MW_CUSTOM1), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_CUSTOM_1, STR_MUSIC_TOOLTIP_SELECT_CUSTOM_1_USER_DEFINED),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, MW_CUSTOM2), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_CUSTOM_2, STR_MUSIC_TOOLTIP_SELECT_CUSTOM_2_USER_DEFINED),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _music_window_desc(
 
	0, 22, 300, 66,
 
	WC_MUSIC_WINDOW, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
 
	_nested_music_window_widgets, lengthof(_nested_music_window_widgets)
 
);
 

	
 
void ShowMusicWindow()
 
{
 
	AllocateWindowDescFront<MusicWindow>(&_music_window_desc, 0);
 
}
src/network/network_content_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file network_content_gui.cpp Implementation of the Network Content related GUIs. */
 

	
 
#if defined(ENABLE_NETWORK)
 
#include "../stdafx.h"
 
#include "../strings_func.h"
 
#include "../gfx_func.h"
 
#include "../window_func.h"
 
#include "../gui.h"
 
#include "../ai/ai.hpp"
 
#include "../base_media_base.h"
 
#include "../sortlist_type.h"
 
#include "../querystring_gui.h"
 
#include  "network_content.h"
 

	
 
#include "table/strings.h"
 
#include "../table/sprites.h"
 

	
 
/** Widgets used by this window */
 
enum DownloadStatusWindowWidgets {
 
	NCDSWW_CAPTION,    ///< Caption of the window
 
	NCDSWW_BACKGROUND, ///< Background
 
	NCDSWW_CANCELOK,   ///< Cancel/OK button
 
};
 

	
 
/** Nested widgets for the download window. */
 
static const NWidgetPart _nested_network_content_download_status_window_widgets[] = {
 
	NWidget(WWT_CAPTION, COLOUR_GREY, NCDSWW_CAPTION), SetDataTip(STR_CONTENT_DOWNLOAD_TITLE, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	NWidget(WWT_PANEL, COLOUR_GREY, NCDSWW_BACKGROUND),
 
		NWidget(NWID_SPACER), SetMinimalSize(350, 0), SetMinimalTextLines(3, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM + 30),
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(NWID_SPACER), SetMinimalSize(125, 0),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NCDSWW_CANCELOK), SetMinimalSize(101, 12), SetDataTip(STR_BUTTON_CANCEL, STR_NULL),
 
			NWidget(NWID_SPACER), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 4),
 
	EndContainer(),
 
};
 

	
 
/** Window description for the download window */
 
static const WindowDesc _network_content_download_status_window_desc(
 
	WDP_CENTER, WDP_CENTER, 350, 85,
 
	WC_NETWORK_STATUS_WINDOW, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_MODAL,
 
	_nested_network_content_download_status_window_widgets, lengthof(_nested_network_content_download_status_window_widgets)
 
);
 

	
 
/** Window for showing the download status of content */
 
struct NetworkContentDownloadStatusWindow : public Window, ContentCallback {
 
private:
 
	ClientNetworkContentSocketHandler *connection; ///< Our connection with the content server
 
	SmallVector<ContentType, 4> receivedTypes;     ///< Types we received so we can update their cache
 

	
 
	uint total_files;      ///< Number of files to download
 
	uint downloaded_files; ///< Number of files downloaded
 
	uint total_bytes;      ///< Number of bytes to download
 
	uint downloaded_bytes; ///< Number of bytes downloaded
 

	
 
	uint32 cur_id; ///< The current ID of the downloaded file
 
	char name[48]; ///< The current name of the downloaded file
 

	
 
public:
 
	/**
 
	 * Create a new download window based on a list of content information
 
	 * with flags whether to download them or not.
 
	 * @param infos the list to search in
 
	 */
 
	NetworkContentDownloadStatusWindow() :
 
		cur_id(UINT32_MAX)
 
	{
 
		this->parent = FindWindowById(WC_NETWORK_WINDOW, 1);
 

	
 
		_network_content_client.AddCallback(this);
 
		_network_content_client.DownloadSelectedContent(this->total_files, this->total_bytes);
 

	
 
		this->InitNested(&_network_content_download_status_window_desc, 0);
 
	}
 

	
 
	/** Free whatever we've allocated */
 
	~NetworkContentDownloadStatusWindow()
 
	{
 
		/* Tell all the backends about what we've downloaded */
 
		for (ContentType *iter = this->receivedTypes.Begin(); iter != this->receivedTypes.End(); iter++) {
 
			switch (*iter) {
 
				case CONTENT_TYPE_AI:
 
				case CONTENT_TYPE_AI_LIBRARY:
 
					AI::Rescan();
 
					SetWindowClassesDirty(WC_AI_DEBUG);
 
					break;
 

	
 
				case CONTENT_TYPE_BASE_GRAPHICS:
 
					BaseGraphics::FindSets();
 
					SetWindowDirty(WC_GAME_OPTIONS, 0);
 
					break;
 

	
 
				case CONTENT_TYPE_BASE_SOUNDS:
 
					BaseSounds::FindSets();
 
					SetWindowDirty(WC_GAME_OPTIONS, 0);
 
					break;
 

	
 
				case CONTENT_TYPE_NEWGRF:
 
					ScanNewGRFFiles();
 
					/* Yes... these are the NewGRF windows */
 
					InvalidateWindowClassesData(WC_SAVELOAD);
 
					InvalidateWindowData(WC_GAME_OPTIONS, 0, 1);
 
					InvalidateWindowData(WC_NETWORK_WINDOW, 1, 2);
 
					break;
 

	
 
				case CONTENT_TYPE_SCENARIO:
 
				case CONTENT_TYPE_HEIGHTMAP:
 
					extern void ScanScenarios();
 
					ScanScenarios();
 
					InvalidateWindowData(WC_SAVELOAD, 0, 0);
 
					break;
 

	
 
				default:
 
					break;
 
			}
 
		}
 

	
 
		_network_content_client.RemoveCallback(this);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		if (widget != NCDSWW_BACKGROUND) return;
 

	
 
		/* Draw nice progress bar :) */
 
		DrawFrameRect(r.left + 20, r.top + 4, r.left + 20 + (int)((this->width - 40LL) * this->downloaded_bytes / this->total_bytes), r.top + 14, COLOUR_MAUVE, FR_NONE);
 

	
 
		int y = r.top + 20;
 
		SetDParam(0, this->downloaded_bytes);
 
		SetDParam(1, this->total_bytes);
 
		SetDParam(2, this->downloaded_bytes * 100LL / this->total_bytes);
 
		DrawString(r.left + 2, r.right - 2, y, STR_CONTENT_DOWNLOAD_PROGRESS_SIZE, TC_FROMSTRING, SA_CENTER);
 

	
 
		StringID str;
 
		if (this->downloaded_bytes == this->total_bytes) {
 
			str = STR_CONTENT_DOWNLOAD_COMPLETE;
 
		} else if (!StrEmpty(this->name)) {
 
			SetDParamStr(0, this->name);
 
			SetDParam(1, this->downloaded_files);
 
			SetDParam(2, this->total_files);
 
			str = STR_CONTENT_DOWNLOAD_FILE;
 
		} else {
 
			str = STR_CONTENT_DOWNLOAD_INITIALISE;
 
		}
 

	
 
		y += FONT_HEIGHT_NORMAL + 5;
 
		DrawStringMultiLine(r.left + 2, r.right - 2, y, y + FONT_HEIGHT_NORMAL * 2, str, TC_FROMSTRING, SA_CENTER);
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		if (widget == NCDSWW_CANCELOK) {
 
			if (this->downloaded_bytes != this->total_bytes) _network_content_client.Close();
 
			delete this;
 
		}
 
	}
 

	
 
	virtual void OnDownloadProgress(const ContentInfo *ci, uint bytes)
 
	{
 
		if (ci->id != this->cur_id) {
 
			strecpy(this->name, ci->filename, lastof(this->name));
 
			this->cur_id = ci->id;
 
			this->downloaded_files++;
 
			this->receivedTypes.Include(ci->type);
 
		}
 
		this->downloaded_bytes += bytes;
 

	
 
		/* When downloading is finished change cancel in ok */
 
		if (this->downloaded_bytes == this->total_bytes) {
 
			this->GetWidget<NWidgetCore>(NCDSWW_CANCELOK)->widget_data = STR_BUTTON_OK;
 
		}
 

	
 
		this->SetDirty();
 
	}
 
};
 

	
 
/** Widgets of the content list window. */
 
enum NetworkContentListWindowWidgets {
 
	NCLWW_CLOSE,         ///< Close 'X' button
 
	NCLWW_CAPTION,       ///< Caption of the window
 
	NCLWW_BACKGROUND,    ///< Resize button
 

	
 
	NCLWW_FILTER_CAPT,   ///< Caption for the filter editbox
 
	NCLWW_FILTER,        ///< Filter editbox
 

	
 
	NCLWW_CHECKBOX,      ///< Button above checkboxes
 
	NCLWW_TYPE,          ///< 'Type' button
 
	NCLWW_NAME,          ///< 'Name' button
 

	
 
	NCLWW_MATRIX,        ///< Panel with list of content
 
	NCLWW_SCROLLBAR,     ///< Scrollbar of matrix
 

	
 
	NCLWW_DETAILS,       ///< Panel with content details
 

	
 
	NCLWW_SELECT_ALL,    ///< 'Select all' button
 
	NCLWW_SELECT_UPDATE, ///< 'Select updates' button
 
	NCLWW_UNSELECT,      ///< 'Unselect all' button
 
	NCLWW_CANCEL,        ///< 'Cancel' button
 
	NCLWW_DOWNLOAD,      ///< 'Download' button
 

	
 
	NCLWW_RESIZE,        ///< Resize button
 

	
 
	NCLWW_SEL_ALL_UPDATE, ///< #NWID_SELECTION widget for select all/update buttons.
 
};
 

	
 
/** Window that lists the content that's at the content server */
 
class NetworkContentListWindow : public QueryStringBaseWindow, ContentCallback {
 
	typedef GUIList<const ContentInfo*> GUIContentList;
 

	
 
	enum {
 
		EDITBOX_MAX_SIZE = 50,
 
		EDITBOX_MAX_LENGTH = 300,
 
	};
 

	
 
	/** Runtime saved values */
 
	static Listing last_sorting;
 
	static Filtering last_filtering;
 
	/** The sorter functions */
 
	static GUIContentList::SortFunction * const sorter_funcs[];
 
	static GUIContentList::FilterFunction * const filter_funcs[];
 
	GUIContentList content;      ///< List with content
 

	
 
	const ContentInfo *selected; ///< The selected content info
 
	int list_pos;                ///< Our position in the list
 
	uint filesize_sum;           ///< The sum of all selected file sizes
 

	
 
	/**
 
	 * (Re)build the network game list as its amount has changed because
 
	 * an item has been added or deleted for example
 
	 */
 
	void BuildContentList()
 
	{
 
		if (!this->content.NeedRebuild()) return;
 

	
 
		/* Create temporary array of games to use for listing */
 
		this->content.Clear();
 

	
 
		for (ConstContentIterator iter = _network_content_client.Begin(); iter != _network_content_client.End(); iter++) {
 
			*this->content.Append() = *iter;
 
		}
 

	
 
		this->FilterContentList();
 
		this->content.Compact();
 
		this->content.RebuildDone();
 
		this->SortContentList();
 

	
 
		this->vscroll.SetCount(this->content.Length()); // Update the scrollbar
 
		this->ScrollToSelected();
 
	}
 

	
 
	/** Sort content by name. */
 
	static int CDECL NameSorter(const ContentInfo * const *a, const ContentInfo * const *b)
 
	{
 
		return strcasecmp((*a)->name, (*b)->name);
 
	}
 

	
 
	/** Sort content by type. */
 
	static int CDECL TypeSorter(const ContentInfo * const *a, const ContentInfo * const *b)
 
	{
 
		int r = 0;
 
		if ((*a)->type != (*b)->type) {
 
			char a_str[64];
 
			char b_str[64];
 
			GetString(a_str, STR_CONTENT_TYPE_BASE_GRAPHICS + (*a)->type - CONTENT_TYPE_BASE_GRAPHICS, lastof(a_str));
 
			GetString(b_str, STR_CONTENT_TYPE_BASE_GRAPHICS + (*b)->type - CONTENT_TYPE_BASE_GRAPHICS, lastof(b_str));
 
			r = strcasecmp(a_str, b_str);
 
		}
 
		if (r == 0) r = NameSorter(a, b);
 
		return r;
 
	}
 

	
 
	/** Sort content by state. */
 
	static int CDECL StateSorter(const ContentInfo * const *a, const ContentInfo * const *b)
 
	{
 
		int r = (*a)->state - (*b)->state;
 
		if (r == 0) r = TypeSorter(a, b);
 
		return r;
 
	}
 

	
 
	/** Sort the content list */
 
	void SortContentList()
 
	{
 
		if (!this->content.Sort()) return;
 

	
 
		for (ConstContentIterator iter = this->content.Begin(); iter != this->content.End(); iter++) {
 
			if (*iter == this->selected) {
 
				this->list_pos = iter - this->content.Begin();
 
				break;
 
			}
 
		}
 
	}
 

	
 
	/** Filter content by tags/name */
 
	static bool CDECL TagNameFilter(const ContentInfo * const *a, const char *filter_string)
 
	{
 
		for (int i = 0; i < (*a)->tag_count; i++) {
 
			if (strcasestr((*a)->tags[i], filter_string) != NULL) return true;
 
		}
 
		return strcasestr((*a)->name, filter_string) != NULL;
 
	}
 

	
 
	/** Filter the content list */
 
	void FilterContentList()
 
	{
 
		if (!this->content.Filter(this->edit_str_buf)) return;
 

	
 
		/* update list position */
 
		for (ConstContentIterator iter = this->content.Begin(); iter != this->content.End(); iter++) {
 
			if (*iter == this->selected) {
 
				this->list_pos = iter - this->content.Begin();
 
				return;
 
			}
 
		}
 

	
 
		/* previously selected item not in list anymore */
 
		this->selected = NULL;
 
		this->list_pos = 0;
 
	}
 

	
 
	/** Make sure that the currently selected content info is within the visible part of the matrix */
 
	void ScrollToSelected()
 
	{
 
		if (this->selected == NULL) return;
 

	
 
		this->vscroll.ScrollTowards(this->list_pos);
 
	}
 

	
 
public:
 
	/**
 
	 * Create the content list window.
 
	 * @param desc the window description to pass to Window's constructor.
 
	 */
 
	NetworkContentListWindow(const WindowDesc *desc, bool select_all) :
 
			QueryStringBaseWindow(EDITBOX_MAX_SIZE),
 
			selected(NULL),
 
			list_pos(0)
 
	{
 
		this->InitNested(desc, 1);
 

	
 
		this->GetWidget<NWidgetStacked>(NCLWW_SEL_ALL_UPDATE)->SetDisplayedPlane(select_all);
 

	
 
		this->afilter = CS_ALPHANUMERAL;
 
		InitializeTextBuffer(&this->text, this->edit_str_buf, this->edit_str_size, EDITBOX_MAX_LENGTH);
 
		this->SetFocusedWidget(NCLWW_FILTER);
 

	
 
		_network_content_client.AddCallback(this);
 
		this->content.SetListing(this->last_sorting);
 
		this->content.SetFiltering(this->last_filtering);
 
		this->content.SetSortFuncs(this->sorter_funcs);
 
		this->content.SetFilterFuncs(this->filter_funcs);
 
		this->content.ForceRebuild();
 
		this->FilterContentList();
 
		this->SortContentList();
 
		this->InvalidateData();
 
	}
 

	
 
	/** Free everything we allocated */
 
	~NetworkContentListWindow()
 
	{
 
		_network_content_client.RemoveCallback(this);
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		switch (widget) {
 
			case NCLWW_FILTER_CAPT:
 
				*size = maxdim(*size, GetStringBoundingBox(STR_CONTENT_FILTER_TITLE));
 
				break;
 

	
 
			case NCLWW_TYPE: {
 
				Dimension d = *size;
 
				for (int i = CONTENT_TYPE_BEGIN; i < CONTENT_TYPE_END; i++) {
 
					d = maxdim(d, GetStringBoundingBox(STR_CONTENT_TYPE_BASE_GRAPHICS + i - CONTENT_TYPE_BASE_GRAPHICS));
 
				}
 
				size->width = d.width + WD_MATRIX_RIGHT + WD_MATRIX_LEFT;
 
				break;
 
			}
 

	
 
			case NCLWW_MATRIX:
 
				resize->height = FONT_HEIGHT_NORMAL + WD_MATRIX_TOP + WD_MATRIX_BOTTOM;
 
				size->height = 10 * resize->height;
 
				break;
 
		}
 
	}
 

	
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case NCLWW_FILTER_CAPT:
 
				DrawString(r.left, r.right, r.top, STR_CONTENT_FILTER_TITLE, TC_FROMSTRING, SA_RIGHT);
 
				break;
 

	
 
			case NCLWW_DETAILS:
 
				this->DrawDetails(r);
 
				break;
 

	
 
			case NCLWW_MATRIX:
 
				this->DrawMatrix(r);
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		const SortButtonState arrow = this->content.IsDescSortOrder() ? SBS_DOWN : SBS_UP;
 

	
 
		if (this->content.NeedRebuild()) {
 
			this->BuildContentList();
 
		}
 

	
 
		this->DrawWidgets();
 

	
 
		/* Edit box to filter for keywords */
 
		this->DrawEditBox(NCLWW_FILTER);
 

	
 
		switch (this->content.SortType()) {
 
			case NCLWW_CHECKBOX - NCLWW_CHECKBOX: this->DrawSortButtonState(NCLWW_CHECKBOX, arrow); break;
 
			case NCLWW_TYPE     - NCLWW_CHECKBOX: this->DrawSortButtonState(NCLWW_TYPE,     arrow); break;
 
			case NCLWW_NAME     - NCLWW_CHECKBOX: this->DrawSortButtonState(NCLWW_NAME,     arrow); break;
 
		}
 
	}
 

	
 
	void DrawMatrix(const Rect &r) const
 
	{
 
		const NWidgetBase *nwi_checkbox = this->GetWidget<NWidgetBase>(NCLWW_CHECKBOX);
 
		const NWidgetBase *nwi_name = this->GetWidget<NWidgetBase>(NCLWW_NAME);
 
		const NWidgetBase *nwi_type = this->GetWidget<NWidgetBase>(NCLWW_TYPE);
 

	
 

	
 
		/* Fill the matrix with the information */
 
		uint y = r.top;
 
		int cnt = 0;
 
		for (ConstContentIterator iter = this->content.Get(this->vscroll.GetPosition()); iter != this->content.End() && cnt < this->vscroll.GetCapacity(); iter++, cnt++) {
 
			const ContentInfo *ci = *iter;
 

	
 
			if (ci == this->selected) GfxFillRect(r.left + 1, y + 1, r.right - 1, y + this->resize.step_height - 1, 10);
 

	
 
			SpriteID sprite;
 
			SpriteID pal = PAL_NONE;
 
			switch (ci->state) {
 
				case ContentInfo::UNSELECTED:     sprite = SPR_BOX_EMPTY;   break;
 
				case ContentInfo::SELECTED:       sprite = SPR_BOX_CHECKED; break;
 
				case ContentInfo::AUTOSELECTED:   sprite = SPR_BOX_CHECKED; break;
 
				case ContentInfo::ALREADY_HERE:   sprite = SPR_BLOT; pal = PALETTE_TO_GREEN; break;
 
				case ContentInfo::DOES_NOT_EXIST: sprite = SPR_BLOT; pal = PALETTE_TO_RED;   break;
 
				default: NOT_REACHED();
 
			}
 
			DrawSprite(sprite, pal, nwi_checkbox->pos_x + (pal == PAL_NONE ? 2 : 3), y + WD_MATRIX_TOP + (pal == PAL_NONE ? 1 : 0));
 

	
 
			StringID str = STR_CONTENT_TYPE_BASE_GRAPHICS + ci->type - CONTENT_TYPE_BASE_GRAPHICS;
 
			DrawString(nwi_type->pos_x, nwi_type->pos_x + nwi_type->current_x - 1, y + WD_MATRIX_TOP, str, TC_BLACK, SA_CENTER);
 

	
 
			DrawString(nwi_name->pos_x + WD_FRAMERECT_LEFT, nwi_name->pos_x + nwi_name->current_x - WD_FRAMERECT_RIGHT, y + WD_MATRIX_TOP, ci->name, TC_BLACK);
 
			y += this->resize.step_height;
 
		}
 
	}
 

	
 
	/**
 
	 * Helper function to draw the details part of this window.
 
	 * @param r the rectangle to stay within while drawing
 
	 */
 
	void DrawDetails(const Rect &r) const
 
	{
 
		static const int DETAIL_LEFT         =  5; ///< Number of pixels at the left
 
		static const int DETAIL_RIGHT        =  5; ///< Number of pixels at the right
 
		static const int DETAIL_TOP          =  5; ///< Number of pixels at the top
 

	
 
		/* Height for the title banner */
 
		int DETAIL_TITLE_HEIGHT = 5 * FONT_HEIGHT_NORMAL;
 

	
 
		/* Create the nice grayish rectangle at the details top */
 
		GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.top + DETAIL_TITLE_HEIGHT, 157);
 
		DrawString(r.left + WD_INSET_LEFT, r.right - WD_INSET_RIGHT, r.top + FONT_HEIGHT_NORMAL + WD_INSET_TOP, STR_CONTENT_DETAIL_TITLE, TC_FROMSTRING, SA_CENTER);
 

	
 
		/* Draw the total download size */
 
		SetDParam(0, this->filesize_sum);
 
		DrawString(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, r.bottom - FONT_HEIGHT_NORMAL - WD_PAR_VSEP_NORMAL, STR_CONTENT_TOTAL_DOWNLOAD_SIZE);
 

	
 
		if (this->selected == NULL) return;
 

	
 
		/* And fill the rest of the details when there's information to place there */
 
		DrawStringMultiLine(r.left + WD_INSET_LEFT, r.right - WD_INSET_RIGHT, r.top + DETAIL_TITLE_HEIGHT / 2, r.top + DETAIL_TITLE_HEIGHT, STR_CONTENT_DETAIL_SUBTITLE_UNSELECTED + this->selected->state, TC_FROMSTRING, SA_CENTER);
 

	
 
		/* Also show the total download size, so keep some space from the bottom */
 
		const uint max_y = r.bottom - FONT_HEIGHT_NORMAL - WD_PAR_VSEP_WIDE;
 
		int y = r.top + DETAIL_TITLE_HEIGHT + DETAIL_TOP;
 

	
 
		if (this->selected->upgrade) {
 
			SetDParam(0, STR_CONTENT_TYPE_BASE_GRAPHICS + this->selected->type - CONTENT_TYPE_BASE_GRAPHICS);
 
			y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_UPDATE);
 
			y += WD_PAR_VSEP_WIDE;
 
		}
 

	
 
		SetDParamStr(0, this->selected->name);
 
		y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_NAME);
 

	
 
		if (!StrEmpty(this->selected->version)) {
 
			SetDParamStr(0, this->selected->version);
 
			y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_VERSION);
 
		}
 

	
 
		if (!StrEmpty(this->selected->description)) {
 
			SetDParamStr(0, this->selected->description);
 
			y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_DESCRIPTION);
 
		}
 

	
 
		if (!StrEmpty(this->selected->url)) {
 
			SetDParamStr(0, this->selected->url);
 
			y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_URL);
 
		}
 

	
 
		SetDParam(0, STR_CONTENT_TYPE_BASE_GRAPHICS + this->selected->type - CONTENT_TYPE_BASE_GRAPHICS);
 
		y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_TYPE);
 

	
 
		y += WD_PAR_VSEP_WIDE;
 
		SetDParam(0, this->selected->filesize);
 
		y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_FILESIZE);
 

	
 
		if (this->selected->dependency_count != 0) {
 
			/* List dependencies */
 
			char buf[DRAW_STRING_BUFFER] = "";
 
			char *p = buf;
 
			for (uint i = 0; i < this->selected->dependency_count; i++) {
 
				ContentID cid = this->selected->dependencies[i];
 

	
 
				/* Try to find the dependency */
 
				ConstContentIterator iter = _network_content_client.Begin();
 
				for (; iter != _network_content_client.End(); iter++) {
 
					const ContentInfo *ci = *iter;
 
					if (ci->id != cid) continue;
 

	
 
					p += seprintf(p, lastof(buf), p == buf ? "%s" : ", %s", (*iter)->name);
 
					break;
 
				}
 
			}
 
			SetDParamStr(0, buf);
 
			y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_DEPENDENCIES);
 
		}
 

	
 
		if (this->selected->tag_count != 0) {
 
			/* List all tags */
 
			char buf[DRAW_STRING_BUFFER] = "";
 
			char *p = buf;
 
			for (uint i = 0; i < this->selected->tag_count; i++) {
 
				p += seprintf(p, lastof(buf), i == 0 ? "%s" : ", %s", this->selected->tags[i]);
 
			}
 
			SetDParamStr(0, buf);
 
			y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_TAGS);
 
		}
 

	
 
		if (this->selected->IsSelected()) {
 
			/* When selected show all manually selected content that depends on this */
 
			ConstContentVector tree;
 
			_network_content_client.ReverseLookupTreeDependency(tree, this->selected);
 

	
 
			char buf[DRAW_STRING_BUFFER] = "";
 
			char *p = buf;
 
			for (ConstContentIterator iter = tree.Begin(); iter != tree.End(); iter++) {
 
				const ContentInfo *ci = *iter;
 
				if (ci == this->selected || ci->state != ContentInfo::SELECTED) continue;
 

	
 
				p += seprintf(p, lastof(buf), buf == p ? "%s" : ", %s", ci->name);
 
			}
 
			if (p != buf) {
 
				SetDParamStr(0, buf);
 
				y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_SELECTED_BECAUSE_OF);
 
			}
 
		}
 
	}
 

	
 
	virtual void OnDoubleClick(Point pt, int widget)
 
	{
 
		/* Double clicking on a line in the matrix toggles the state of the checkbox */
 
		if (widget != NCLWW_MATRIX) return;
 

	
 
		pt.x = this->GetWidget<NWidgetBase>(NCLWW_CHECKBOX)->pos_x;
 
		this->OnClick(pt, widget);
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case NCLWW_MATRIX: {
 
				uint32 id_v = (pt.y - this->GetWidget<NWidgetBase>(NCLWW_MATRIX)->pos_y) / this->resize.step_height;
 

	
 
				if (id_v >= this->vscroll.GetCapacity()) return; // click out of bounds
 
				id_v += this->vscroll.GetPosition();
 

	
 
				if (id_v >= this->content.Length()) return; // click out of bounds
 

	
 
				this->selected = *this->content.Get(id_v);
 
				this->list_pos = id_v;
 

	
 
				if (pt.x <= (int)(this->GetWidget<NWidgetBase>(NCLWW_CHECKBOX)->pos_y + this->GetWidget<NWidgetBase>(NCLWW_CHECKBOX)->current_y)) {
 
					_network_content_client.ToggleSelectedState(this->selected);
 
					this->content.ForceResort();
 
				}
 

	
 
				this->InvalidateData();
 
			} break;
 

	
 
			case NCLWW_CHECKBOX:
 
			case NCLWW_TYPE:
 
			case NCLWW_NAME:
 
				if (this->content.SortType() == widget - NCLWW_CHECKBOX) {
 
					this->content.ToggleSortOrder();
 
					this->list_pos = this->content.Length() - this->list_pos - 1;
 
				} else {
 
					this->content.SetSortType(widget - NCLWW_CHECKBOX);
 
					this->content.ForceResort();
 
					this->SortContentList();
 
				}
 
				this->ScrollToSelected();
 
				this->InvalidateData();
 
				break;
 

	
 
			case NCLWW_SELECT_ALL:
 
				_network_content_client.SelectAll();
 
				this->InvalidateData();
 
				break;
 

	
 
			case NCLWW_SELECT_UPDATE:
 
				_network_content_client.SelectUpgrade();
 
				this->InvalidateData();
 
				break;
 

	
 
			case NCLWW_UNSELECT:
 
				_network_content_client.UnselectAll();
 
				this->InvalidateData();
 
				break;
 

	
 
			case NCLWW_CANCEL:
 
				delete this;
 
				break;
 

	
 
			case NCLWW_DOWNLOAD:
 
				if (BringWindowToFrontById(WC_NETWORK_STATUS_WINDOW, 0) == NULL) new NetworkContentDownloadStatusWindow();
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnMouseLoop()
 
	{
 
		this->HandleEditBox(NCLWW_FILTER);
 
	}
 

	
 
	virtual EventState OnKeyPress(uint16 key, uint16 keycode)
 
	{
 
		switch (keycode) {
 
			case WKC_UP:
 
				/* scroll up by one */
 
				if (this->list_pos > 0) this->list_pos--;
 
				break;
 
			case WKC_DOWN:
 
				/* scroll down by one */
 
				if (this->list_pos < (int)this->content.Length() - 1) this->list_pos++;
 
				break;
 
			case WKC_PAGEUP:
 
				/* scroll up a page */
 
				this->list_pos = (this->list_pos < this->vscroll.GetCapacity()) ? 0 : this->list_pos - this->vscroll.GetCapacity();
 
				break;
 
			case WKC_PAGEDOWN:
 
				/* scroll down a page */
 
				this->list_pos = min(this->list_pos + this->vscroll.GetCapacity(), (int)this->content.Length() - 1);
 
				break;
 
			case WKC_HOME:
 
				/* jump to beginning */
 
				this->list_pos = 0;
 
				break;
 
			case WKC_END:
 
				/* jump to end */
 
				this->list_pos = this->content.Length() - 1;
 
				break;
 

	
 
			case WKC_SPACE:
 
			case WKC_RETURN:
 
				if (keycode == WKC_RETURN || !IsWidgetFocused(NCLWW_FILTER)) {
 
					if (this->selected != NULL) {
 
						_network_content_client.ToggleSelectedState(this->selected);
 
						this->content.ForceResort();
 
						this->InvalidateData();
 
					}
 
					return ES_HANDLED;
 
				}
 
				/* Fall through when pressing space is pressed and filter isn't focused */
 

	
 
			default: {
 
				/* Handle editbox input */
 
				EventState state = ES_NOT_HANDLED;
 
				if (this->HandleEditBoxKey(NCLWW_FILTER, key, keycode, state) == HEBR_EDITING) {
 
					this->OnOSKInput(NCLWW_FILTER);
 
				}
 

	
 
				return state;
 
			}
 
		}
 

	
 
		if (_network_content_client.Length() == 0) return ES_HANDLED;
 

	
 
		this->selected = *this->content.Get(this->list_pos);
 

	
 
		/* scroll to the new server if it is outside the current range */
 
		this->ScrollToSelected();
 

	
 
		/* redraw window */
 
		this->InvalidateData();
 
		return ES_HANDLED;
 
	}
 

	
 
	virtual void OnOSKInput(int wid)
 
	{
 
		this->content.SetFilterState(!StrEmpty(this->edit_str_buf));
 
		this->content.ForceRebuild();
 
		this->InvalidateData();
 
	}
 

	
 
	virtual void OnResize()
 
	{
 
		this->vscroll.SetCapacity(this->GetWidget<NWidgetBase>(NCLWW_MATRIX)->current_y / this->resize.step_height);
 
		this->GetWidget<NWidgetCore>(NCLWW_MATRIX)->widget_data = (this->vscroll.GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START);
 
	}
 

	
 
	virtual void OnReceiveContentInfo(const ContentInfo *rci)
 
	{
 
		this->content.ForceRebuild();
 
		this->InvalidateData();
 
	}
 

	
 
	virtual void OnDownloadComplete(ContentID cid)
 
	{
 
		this->content.ForceResort();
 
		this->InvalidateData();
 
	}
 

	
 
	virtual void OnConnect(bool success)
 
	{
 
		if (!success) {
 
			ShowErrorMessage(STR_CONTENT_ERROR_COULD_NOT_CONNECT, INVALID_STRING_ID, 0, 0);
 
			delete this;
 
		}
 

	
 
		this->InvalidateData();
 
	}
 

	
 
	virtual void OnInvalidateData(int data)
 
	{
 
		/* To sum all the bytes we intend to download */
 
		this->filesize_sum = 0;
 
		bool show_select_all = false;
 
		bool show_select_upgrade = false;
 
		for (ConstContentIterator iter = this->content.Begin(); iter != this->content.End(); iter++) {
 
			const ContentInfo *ci = *iter;
 
			switch (ci->state) {
 
				case ContentInfo::SELECTED:
 
				case ContentInfo::AUTOSELECTED:
 
					this->filesize_sum += ci->filesize;
 
					break;
 

	
 
				case ContentInfo::UNSELECTED:
 
					show_select_all = true;
 
					show_select_upgrade |= ci->upgrade;
 
					break;
 

	
 
				default:
 
					break;
 
			}
 
		}
 

	
 
		this->SetWidgetDisabledState(NCLWW_DOWNLOAD, this->filesize_sum == 0 || FindWindowById(WC_NETWORK_STATUS_WINDOW, 0) != NULL);
 
		this->SetWidgetDisabledState(NCLWW_UNSELECT, this->filesize_sum == 0);
 
		this->SetWidgetDisabledState(NCLWW_SELECT_ALL, !show_select_all);
 
		this->SetWidgetDisabledState(NCLWW_SELECT_UPDATE, !show_select_upgrade);
 

	
 
		this->GetWidget<NWidgetCore>(NCLWW_CANCEL)->widget_data = this->filesize_sum == 0 ? STR_AI_SETTINGS_CLOSE : STR_AI_LIST_CANCEL;
 
	}
 
};
 

	
 
Listing NetworkContentListWindow::last_sorting = {false, 1};
 
Filtering NetworkContentListWindow::last_filtering = {false, 0};
 

	
 
NetworkContentListWindow::GUIContentList::SortFunction * const NetworkContentListWindow::sorter_funcs[] = {
 
	&StateSorter,
 
	&TypeSorter,
 
	&NameSorter,
 
};
 

	
 
NetworkContentListWindow::GUIContentList::FilterFunction * const NetworkContentListWindow::filter_funcs[] = {
 
	&TagNameFilter,
 
};
 

	
 
static const NWidgetPart _nested_network_content_list_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_LIGHT_BLUE, NCLWW_CLOSE),
 
		NWidget(WWT_CAPTION, COLOUR_LIGHT_BLUE, NCLWW_CAPTION), SetDataTip(STR_CONTENT_TITLE, STR_NULL),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, NCLWW_BACKGROUND),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 7), SetResize(1, 0),
 
		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(8, 8, 8),
 
			/* Top */
 
			NWidget(WWT_EMPTY, COLOUR_LIGHT_BLUE, NCLWW_FILTER_CAPT), SetFill(true, false), SetResize(1, 0),
 
			NWidget(WWT_EDITBOX, COLOUR_LIGHT_BLUE, NCLWW_FILTER), SetFill(true, false), SetResize(1, 0),
 
			NWidget(WWT_EMPTY, COLOUR_LIGHT_BLUE, NCLWW_FILTER_CAPT), SetFill(1, 0), SetResize(1, 0),
 
			NWidget(WWT_EDITBOX, COLOUR_LIGHT_BLUE, NCLWW_FILTER), SetFill(1, 0), SetResize(1, 0),
 
						SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 7), SetResize(1, 0),
 
		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(8, 8, 8),
 
			/* Left side. */
 
			NWidget(NWID_VERTICAL),
 
				NWidget(NWID_HORIZONTAL),
 
					NWidget(NWID_VERTICAL),
 
						NWidget(NWID_HORIZONTAL),
 
							NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NCLWW_CHECKBOX), SetMinimalSize(13, 1), SetDataTip(STR_EMPTY, STR_NULL),
 
							NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NCLWW_TYPE),
 
											SetDataTip(STR_CONTENT_TYPE_CAPTION, STR_CONTENT_TYPE_CAPTION_TOOLTIP),
 
							NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NCLWW_NAME), SetResize(1, 0), SetFill(true, false),
 
							NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NCLWW_NAME), SetResize(1, 0), SetFill(1, 0),
 
											SetDataTip(STR_CONTENT_NAME_CAPTION, STR_CONTENT_NAME_CAPTION_TOOLTIP),
 
						EndContainer(),
 
						NWidget(WWT_MATRIX, COLOUR_LIGHT_BLUE, NCLWW_MATRIX), SetResize(1, 14), SetFill(true, true),
 
						NWidget(WWT_MATRIX, COLOUR_LIGHT_BLUE, NCLWW_MATRIX), SetResize(1, 14), SetFill(1, 1),
 
					EndContainer(),
 
					NWidget(WWT_SCROLLBAR, COLOUR_LIGHT_BLUE, NCLWW_SCROLLBAR),
 
				EndContainer(),
 
			EndContainer(),
 
			/* Right side. */
 
			NWidget(NWID_VERTICAL),
 
				NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, NCLWW_DETAILS), SetResize(1, 1), SetFill(true, true), EndContainer(),
 
				NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, NCLWW_DETAILS), SetResize(1, 1), SetFill(1, 1), EndContainer(),
 
			EndContainer(),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 7), SetResize(1, 0),
 
		/* Bottom. */
 
		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(8, 8, 8),
 
			NWidget(NWID_SELECTION, INVALID_COLOUR, NCLWW_SEL_ALL_UPDATE), SetResize(1, 0), SetFill(true, false),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NCLWW_SELECT_UPDATE), SetResize(1, 0), SetFill(true, false),
 
			NWidget(NWID_SELECTION, INVALID_COLOUR, NCLWW_SEL_ALL_UPDATE), SetResize(1, 0), SetFill(1, 0),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NCLWW_SELECT_UPDATE), SetResize(1, 0), SetFill(1, 0),
 
										SetDataTip(STR_CONTENT_SELECT_UPDATES_CAPTION, STR_CONTENT_SELECT_UPDATES_CAPTION_TOOLTIP),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NCLWW_SELECT_ALL), SetResize(1, 0), SetFill(true, false),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NCLWW_SELECT_ALL), SetResize(1, 0), SetFill(1, 0),
 
										SetDataTip(STR_CONTENT_SELECT_ALL_CAPTION, STR_CONTENT_SELECT_ALL_CAPTION_TOOLTIP),
 
			EndContainer(),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NCLWW_UNSELECT), SetResize(1, 0), SetFill(true, false),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NCLWW_UNSELECT), SetResize(1, 0), SetFill(1, 0),
 
										SetDataTip(STR_CONTENT_UNSELECT_ALL_CAPTION, STR_CONTENT_UNSELECT_ALL_CAPTION_TOOLTIP),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NCLWW_CANCEL), SetResize(1, 0), SetFill(true, false),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NCLWW_CANCEL), SetResize(1, 0), SetFill(1, 0),
 
										SetDataTip(STR_BUTTON_CANCEL, STR_NULL),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NCLWW_DOWNLOAD), SetResize(1, 0), SetFill(true, false),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NCLWW_DOWNLOAD), SetResize(1, 0), SetFill(1, 0),
 
										SetDataTip(STR_CONTENT_DOWNLOAD_CAPTION, STR_CONTENT_DOWNLOAD_CAPTION_TOOLTIP),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 2), SetResize(1, 0),
 
		/* Resize button. */
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(NWID_SPACER), SetFill(true, false), SetResize(1, 0),
 
			NWidget(NWID_SPACER), SetFill(1, 0), SetResize(1, 0),
 
			NWidget(WWT_RESIZEBOX, COLOUR_LIGHT_BLUE, NCLWW_RESIZE),
 
		EndContainer(),
 
	EndContainer(),
 
};
 

	
 
/** Window description of the content list */
 
static const WindowDesc _network_content_list_desc(
 
	WDP_CENTER, WDP_CENTER, 630, 460,
 
	WC_NETWORK_WINDOW, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_STD_BTN | WDF_UNCLICK_BUTTONS | WDF_RESIZABLE,
 
	_nested_network_content_list_widgets, lengthof(_nested_network_content_list_widgets)
 
);
 

	
 
/**
 
 * Show the content list window with a given set of content
 
 * @param cv the content to show, or NULL when it has to search for itself
 
 * @param type the type to (only) show
 
 */
 
void ShowNetworkContentListWindow(ContentVector *cv, ContentType type)
 
{
 
#if defined(WITH_ZLIB)
 
	_network_content_client.Clear();
 
	if (cv == NULL) {
 
		_network_content_client.RequestContentList(type);
 
	} else {
 
		_network_content_client.RequestContentList(cv, true);
 
	}
 

	
 
	DeleteWindowById(WC_NETWORK_WINDOW, 1);
 
	new NetworkContentListWindow(&_network_content_list_desc, cv != NULL);
 
#else
 
	ShowErrorMessage(STR_CONTENT_NO_ZLIB, STR_CONTENT_NO_ZLIB_SUB, 0, 0);
 
	/* Connection failed... clean up the mess */
 
	if (cv != NULL) {
 
		for (ContentIterator iter = cv->Begin(); iter != cv->End(); iter++) delete *iter;
 
	}
 
#endif /* WITH_ZLIB */
 
}
 

	
 
#endif /* ENABLE_NETWORK */
src/network/network_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file network_gui.cpp Implementation of the Network related GUIs. */
 

	
 
#ifdef ENABLE_NETWORK
 
#include "../stdafx.h"
 
#include "../openttd.h"
 
#include "../strings_func.h"
 
#include "../date_func.h"
 
#include "../fios.h"
 
#include "network_internal.h"
 
#include "network_client.h"
 
#include "network_gui.h"
 
#include "network_gamelist.h"
 
#include "../gui.h"
 
#include "network_udp.h"
 
#include "../window_func.h"
 
#include "../gfx_func.h"
 
#include "../widgets/dropdown_func.h"
 
#include "../querystring_gui.h"
 
#include "../sortlist_type.h"
 
#include "../company_base.h"
 
#include "../company_func.h"
 

	
 
#include "table/strings.h"
 
#include "../table/sprites.h"
 

	
 

	
 
static void ShowNetworkStartServerWindow();
 
static void ShowNetworkLobbyWindow(NetworkGameList *ngl);
 
extern void SwitchToMode(SwitchMode new_mode);
 

	
 
static const StringID _connection_types_dropdown[] = {
 
	STR_NETWORK_START_SERVER_LAN_INTERNET,
 
	STR_NETWORK_START_SERVER_INTERNET_ADVERTISE,
 
	INVALID_STRING_ID
 
};
 

	
 
static const StringID _lan_internet_types_dropdown[] = {
 
	STR_NETWORK_SERVER_LIST_LAN,
 
	STR_NETWORK_SERVER_LIST_INTERNET,
 
	INVALID_STRING_ID
 
};
 

	
 
static StringID _language_dropdown[NETLANG_COUNT + 1] = {STR_NULL};
 

	
 
void SortNetworkLanguages()
 
{
 
	/* Init the strings */
 
	if (_language_dropdown[0] == STR_NULL) {
 
		for (int i = 0; i < NETLANG_COUNT; i++) _language_dropdown[i] = STR_NETWORK_LANG_ANY + i;
 
		_language_dropdown[NETLANG_COUNT] = INVALID_STRING_ID;
 
	}
 

	
 
	/* Sort the strings (we don't move 'any' and the 'invalid' one) */
 
	QSortT(_language_dropdown, NETLANG_COUNT - 1, &StringIDSorter);
 
}
 

	
 
/** Update the network new window because a new server is
 
 * found on the network.
 
 * @param unselect unselect the currently selected item */
 
void UpdateNetworkGameWindow(bool unselect)
 
{
 
	InvalidateWindowData(WC_NETWORK_WINDOW, 0, unselect ? 1 : 0);
 
}
 

	
 
/** Enum for NetworkGameWindow, referring to _network_game_window_widgets */
 
enum NetworkGameWindowWidgets {
 
	NGWW_CLOSE,         ///< Close 'X' button
 
	NGWW_CAPTION,       ///< Caption of the window
 
	NGWW_MAIN,          ///< Main panel
 

	
 
	NGWW_CONNECTION,    ///< Label in front of connection droplist
 
	NGWW_CONN_BTN,      ///< 'Connection' droplist button
 
	NGWW_CLIENT_LABEL,  ///< Label in front of client name edit box
 
	NGWW_CLIENT,        ///< Panel with editbox to set client name
 

	
 
	NGWW_HEADER,        ///< Header container of the matrix
 
	NGWW_NAME,          ///< 'Name' button
 
	NGWW_CLIENTS,       ///< 'Clients' button
 
	NGWW_MAPSIZE,       ///< 'Map size' button
 
	NGWW_DATE,          ///< 'Date' button
 
	NGWW_YEARS,         ///< 'Years' button
 
	NGWW_INFO,          ///< Third button in the game list panel
 

	
 
	NGWW_MATRIX,        ///< Panel with list of games
 
	NGWW_SCROLLBAR,     ///< Scrollbar of matrix
 

	
 
	NGWW_LASTJOINED_LABEL, ///< Label "Last joined server:"
 
	NGWW_LASTJOINED,    ///< Info about the last joined server
 

	
 
	NGWW_DETAILS,       ///< Panel with game details
 
	NGWW_DETAILS_SPACER, ///< Spacer for game actual details
 
	NGWW_JOIN,          ///< 'Join game' button
 
	NGWW_REFRESH,       ///< 'Refresh server' button
 
	NGWW_NEWGRF,        ///< 'NewGRF Settings' button
 
	NGWW_NEWGRF_SEL,    ///< Selection 'widget' to hide the NewGRF settings
 

	
 
	NGWW_FIND,          ///< 'Find server' button
 
	NGWW_ADD,           ///< 'Add server' button
 
	NGWW_START,         ///< 'Start server' button
 
	NGWW_CANCEL,        ///< 'Cancel' button
 

	
 
	NGWW_RESIZE,        ///< Resize button
 
};
 

	
 
typedef GUIList<NetworkGameList*> GUIGameServerList;
 
typedef uint16 ServerListPosition;
 
static const ServerListPosition SLP_INVALID = 0xFFFF;
 

	
 
/** Full blown container to make it behave exactly as we want :) */
 
class NWidgetServerListHeader : public NWidgetContainer {
 
	static const uint MINIMUM_NAME_WIDTH_BEFORE_NEW_HEADER = 150; ///< Minimum width before adding a new header
 
	bool visible[6]; ///< The visible headers
 
public:
 
	NWidgetServerListHeader() : NWidgetContainer(NWID_HORIZONTAL)
 
	{
 
		NWidgetLeaf *leaf = new NWidgetLeaf(WWT_PUSHTXTBTN, COLOUR_WHITE, NGWW_NAME, STR_NETWORK_SERVER_LIST_GAME_NAME, STR_NETWORK_SERVER_LIST_GAME_NAME_TOOLTIP);
 
		leaf->SetResize(1, 0);
 
		leaf->SetFill(true, false);
 
		leaf->SetFill(1, 0);
 
		this->Add(leaf);
 

	
 
		this->Add(new NWidgetLeaf(WWT_PUSHTXTBTN, COLOUR_WHITE, NGWW_CLIENTS, STR_NETWORK_SERVER_LIST_CLIENTS_CAPTION, STR_NETWORK_SERVER_LIST_CLIENTS_CAPTION_TOOLTIP));
 
		this->Add(new NWidgetLeaf(WWT_PUSHTXTBTN, COLOUR_WHITE, NGWW_MAPSIZE, STR_NETWORK_SERVER_LIST_MAP_SIZE_CAPTION, STR_NETWORK_SERVER_LIST_MAP_SIZE_CAPTION_TOOLTIP));
 
		this->Add(new NWidgetLeaf(WWT_PUSHTXTBTN, COLOUR_WHITE, NGWW_DATE, STR_NETWORK_SERVER_LIST_DATE_CAPTION, STR_NETWORK_SERVER_LIST_DATE_CAPTION_TOOLTIP));
 
		this->Add(new NWidgetLeaf(WWT_PUSHTXTBTN, COLOUR_WHITE, NGWW_YEARS, STR_NETWORK_SERVER_LIST_YEARS_CAPTION, STR_NETWORK_SERVER_LIST_YEARS_CAPTION_TOOLTIP));
 

	
 
		leaf = new NWidgetLeaf(WWT_PUSHTXTBTN, COLOUR_WHITE, NGWW_INFO, STR_EMPTY, STR_NETWORK_SERVER_LIST_INFO_ICONS_TOOLTIP);
 
		leaf->SetMinimalSize(40, 12);
 
		leaf->SetFill(false, true);
 
		leaf->SetFill(0, 1);
 
		this->Add(leaf);
 

	
 
		/* First and last are always visible, the rest is implicitly zeroed */
 
		this->visible[0] = true;
 
		*lastof(this->visible) = true;
 
	}
 

	
 
	void SetupSmallestSize(Window *w, bool init_array)
 
	{
 
		/* Oh yeah, we ought to be findable! */
 
		w->nested_array[NGWW_HEADER] = this;
 

	
 
		this->smallest_x = this->head->smallest_x + this->tail->smallest_x; // First and last are always shown, rest not
 
		this->smallest_y = 0; // Biggest child.
 
		this->fill_x = true;
 
		this->fill_y = false;
 
		this->fill_x = 1;
 
		this->fill_y = 0;
 
		this->resize_x = 1; // We only resize in this direction
 
		this->resize_y = 0; // We never resize in this direction
 

	
 
		/* First initialise some variables... */
 
		for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
 
			child_wid->SetupSmallestSize(w, init_array);
 
			this->smallest_y = max(this->smallest_y, child_wid->smallest_y + child_wid->padding_top + child_wid->padding_bottom);
 
		}
 

	
 
		/* ... then in a second pass make sure the 'current' sizes are set. Won't change for most widgets. */
 
		for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
 
			child_wid->current_x = child_wid->smallest_x;
 
			child_wid->current_y = this->smallest_y;
 
		}
 
	}
 

	
 
	void AssignSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height, bool rtl)
 
	{
 
		assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
 

	
 
		this->pos_x = x;
 
		this->pos_y = y;
 
		this->current_x = given_width;
 
		this->current_y = given_height;
 

	
 
		given_width -= this->tail->smallest_x;
 
		NWidgetBase *child_wid = this->head->next;
 
		/* The first and last widget are always visible, determine which other should be visible */
 
		for (uint i = 1; i < lengthof(this->visible) - 1; i++) {
 
			if (given_width - child_wid->smallest_x > MINIMUM_NAME_WIDTH_BEFORE_NEW_HEADER && this->visible[i - 1]) {
 
				this->visible[i] = true;
 
				given_width -= child_wid->smallest_x;
 
			} else {
 
				this->visible[i] = false;
 
			}
 
			child_wid = child_wid->next;
 
		}
 

	
 
		/* All remaining space goes to the first (name) widget */
 
		this->head->current_x = given_width;
 

	
 
		/* Now assign the widgets to their rightful place */
 
		uint position = 0; // Place to put next child relative to origin of the container.
 
		uint i = rtl ? lengthof(this->visible) - 1 : 0;
 
		child_wid = rtl ? this->tail : this->head;
 
		while (child_wid != NULL) {
 
			if (this->visible[i]) {
 
				child_wid->AssignSizePosition(sizing, x + position, y, child_wid->current_x, this->current_y, rtl);
 
				position += child_wid->current_x;
 
			}
 

	
 
			child_wid = rtl ? child_wid->prev : child_wid->next;
 
			i += rtl ? -1 : 1;
 
		}
 
	}
 

	
 
	/* virtual */ void Draw(const Window *w)
 
	{
 
		int i = 0;
 
		for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
 
			if (!this->visible[i++]) continue;
 

	
 
			child_wid->Draw(w);
 
		}
 
	}
 

	
 
	/* virtual */ NWidgetCore *GetWidgetFromPos(int x, int y)
 
	{
 
		if (!IsInsideBS(x, this->pos_x, this->current_x) || !IsInsideBS(y, this->pos_y, this->current_y)) return NULL;
 

	
 
		int i = 0;
 
		for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
 
			if (!this->visible[i++]) continue;
 
			NWidgetCore *nwid = child_wid->GetWidgetFromPos(x, y);
 
			if (nwid != NULL) return nwid;
 
		}
 
		return NULL;
 
	}
 

	
 
	/**
 
	 * Checks whether the given widget is actually visible.
 
	 * @param widget the widget to check for visibility
 
	 * @return true iff the widget is visible.
 
	 */
 
	bool IsWidgetVisible(NetworkGameWindowWidgets widget) const
 
	{
 
		assert((uint)(widget - NGWW_NAME) < lengthof(this->visible));
 
		return this->visible[widget - NGWW_NAME];
 
	}
 
};
 

	
 
class NetworkGameWindow : public QueryStringBaseWindow {
 
protected:
 
	/* Runtime saved values */
 
	static Listing last_sorting;
 

	
 
	/* Constants for sorting servers */
 
	static GUIGameServerList::SortFunction * const sorter_funcs[];
 

	
 
	byte field;                   ///< selected text-field
 
	NetworkGameList *server;      ///< selected server
 
	NetworkGameList *last_joined; ///< the last joined server
 
	GUIGameServerList servers;    ///< list with game servers.
 
	ServerListPosition list_pos;  ///< position of the selected server
 

	
 
	/**
 
	 * (Re)build the network game list as its amount has changed because
 
	 * an item has been added or deleted for example
 
	 */
 
	void BuildNetworkGameList()
 
	{
 
		if (!this->servers.NeedRebuild()) return;
 

	
 
		/* Create temporary array of games to use for listing */
 
		this->servers.Clear();
 

	
 
		for (NetworkGameList *ngl = _network_game_list; ngl != NULL; ngl = ngl->next) {
 
			*this->servers.Append() = ngl;
 
		}
 

	
 
		this->servers.Compact();
 
		this->servers.RebuildDone();
 
		this->vscroll.SetCount(this->servers.Length());
 
	}
 

	
 
	/** Sort servers by name. */
 
	static int CDECL NGameNameSorter(NetworkGameList * const *a, NetworkGameList * const *b)
 
	{
 
		return strcasecmp((*a)->info.server_name, (*b)->info.server_name);
 
	}
 

	
 
	/** Sort servers by the amount of clients online on a
 
	 * server. If the two servers have the same amount, the one with the
 
	 * higher maximum is preferred. */
 
	static int CDECL NGameClientSorter(NetworkGameList * const *a, NetworkGameList * const *b)
 
	{
 
		/* Reverse as per default we are interested in most-clients first */
 
		int r = (*a)->info.clients_on - (*b)->info.clients_on;
 

	
 
		if (r == 0) r = (*a)->info.clients_max - (*b)->info.clients_max;
 
		if (r == 0) r = NGameNameSorter(a, b);
 

	
 
		return r;
 
	}
 

	
 
	/** Sort servers by map size */
 
	static int CDECL NGameMapSizeSorter(NetworkGameList * const *a, NetworkGameList * const *b)
 
	{
 
		/* Sort by the area of the map. */
 
		int r = ((*a)->info.map_height) * ((*a)->info.map_width) - ((*b)->info.map_height) * ((*b)->info.map_width);
 

	
 
		if (r == 0) r = (*a)->info.map_width - (*b)->info.map_width;
 
		return (r != 0) ? r : NGameClientSorter(a, b);
 
	}
 

	
 
	/** Sort servers by current date */
 
	static int CDECL NGameDateSorter(NetworkGameList * const *a, NetworkGameList * const *b)
 
	{
 
		int r = (*a)->info.game_date - (*b)->info.game_date;
 
		return (r != 0) ? r : NGameClientSorter(a, b);
 
	}
 

	
 
	/** Sort servers by the number of days the game is running */
 
	static int CDECL NGameYearsSorter(NetworkGameList * const *a, NetworkGameList * const *b)
 
	{
 
		int r = (*a)->info.game_date - (*a)->info.start_date - (*b)->info.game_date + (*b)->info.start_date;
 
		return (r != 0) ? r : NGameDateSorter(a, b);
 
	}
 

	
 
	/** Sort servers by joinability. If both servers are the
 
	 * same, prefer the non-passworded server first. */
 
	static int CDECL NGameAllowedSorter(NetworkGameList * const *a, NetworkGameList * const *b)
 
	{
 
		/* The servers we do not know anything about (the ones that did not reply) should be at the bottom) */
 
		int r = StrEmpty((*a)->info.server_revision) - StrEmpty((*b)->info.server_revision);
 

	
 
		/* Reverse default as we are interested in version-compatible clients first */
 
		if (r == 0) r = (*b)->info.version_compatible - (*a)->info.version_compatible;
 
		/* The version-compatible ones are then sorted with NewGRF compatible first, incompatible last */
 
		if (r == 0) r = (*b)->info.compatible - (*a)->info.compatible;
 
		/* Passworded servers should be below unpassworded servers */
 
		if (r == 0) r = (*a)->info.use_password - (*b)->info.use_password;
 
		/* Finally sort on the name of the server */
 
		if (r == 0) r = NGameNameSorter(a, b);
 

	
 
		return r;
 
	}
 

	
 
	/** Sort the server list */
 
	void SortNetworkGameList()
 
	{
 
		if (!this->servers.Sort()) return;
 

	
 
		/* 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. While iterating, look for the
 
		 * currently selected server and set list_pos to its position */
 
		this->list_pos = SLP_INVALID;
 
		_network_game_list = this->servers[0];
 
		NetworkGameList *item = _network_game_list;
 
		if (item == this->server) this->list_pos = 0;
 
		for (uint i = 1; i != this->servers.Length(); i++) {
 
			item->next = this->servers[i];
 
			item = item->next;
 
			if (item == this->server) this->list_pos = i;
 
		}
 
		item->next = NULL;
 
	}
 

	
 
	/**
 
	 * 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) const
 
	{
 
		const NWidgetBase *nwi_name = this->GetWidget<NWidgetBase>(NGWW_NAME);
 
		const NWidgetBase *nwi_info = this->GetWidget<NWidgetBase>(NGWW_INFO);
 

	
 
		/* show highlighted item with a different colour */
 
		if (highlight) GfxFillRect(nwi_name->pos_x + 1, y - 2, nwi_info->pos_x + nwi_info->current_x - 2, y + FONT_HEIGHT_NORMAL - 1, 10);
 

	
 
		DrawString(nwi_name->pos_x + WD_FRAMERECT_LEFT, nwi_name->pos_x + nwi_name->current_x - WD_FRAMERECT_RIGHT, y, cur_item->info.server_name, TC_BLACK);
 

	
 
		/* only draw details if the server is online */
 
		if (cur_item->online) {
 
			const NWidgetServerListHeader *nwi_header = this->GetWidget<NWidgetServerListHeader>(NGWW_HEADER);
 

	
 
			if (nwi_header->IsWidgetVisible(NGWW_CLIENTS)) {
 
				const NWidgetBase *nwi_clients = this->GetWidget<NWidgetBase>(NGWW_CLIENTS);
 
				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);
 
				DrawString(nwi_clients->pos_x, nwi_clients->pos_x + nwi_clients->current_x - 1, y, STR_NETWORK_SERVER_LIST_GENERAL_ONLINE, TC_FROMSTRING, SA_CENTER);
 
			}
 

	
 
			if (nwi_header->IsWidgetVisible(NGWW_MAPSIZE)) {
 
				/* map size */
 
				const NWidgetBase *nwi_mapsize = this->GetWidget<NWidgetBase>(NGWW_MAPSIZE);
 
				SetDParam(0, cur_item->info.map_width);
 
				SetDParam(1, cur_item->info.map_height);
 
				DrawString(nwi_mapsize->pos_x, nwi_mapsize->pos_x + nwi_mapsize->current_x - 1, y, STR_NETWORK_SERVER_LIST_MAP_SIZE_SHORT, TC_FROMSTRING, SA_CENTER);
 
			}
 

	
 
			if (nwi_header->IsWidgetVisible(NGWW_DATE)) {
 
				/* current date */
 
				const NWidgetBase *nwi_date = this->GetWidget<NWidgetBase>(NGWW_DATE);
 
				YearMonthDay ymd;
 
				ConvertDateToYMD(cur_item->info.game_date, &ymd);
 
				SetDParam(0, ymd.year);
 
				DrawString(nwi_date->pos_x, nwi_date->pos_x + nwi_date->current_x - 1, y, STR_JUST_INT, TC_BLACK, SA_CENTER);
 
			}
 

	
 
			if (nwi_header->IsWidgetVisible(NGWW_YEARS)) {
 
				/* number of years the game is running */
 
				const NWidgetBase *nwi_years = this->GetWidget<NWidgetBase>(NGWW_YEARS);
 
				YearMonthDay ymd_cur, ymd_start;
 
				ConvertDateToYMD(cur_item->info.game_date, &ymd_cur);
 
				ConvertDateToYMD(cur_item->info.start_date, &ymd_start);
 
				SetDParam(0, ymd_cur.year - ymd_start.year);
 
				DrawString(nwi_years->pos_x, nwi_years->pos_x + nwi_years->current_x - 1, y, STR_JUST_INT, TC_BLACK, SA_CENTER);
 
			}
 

	
 
			/* Align the sprites */
 
			y += (FONT_HEIGHT_NORMAL - 10) / 2;
 

	
 
			/* draw a lock if the server is password protected */
 
			if (cur_item->info.use_password) DrawSprite(SPR_LOCK, PAL_NONE, nwi_info->pos_x + 5, y - 1);
 

	
 
			/* 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)), nwi_info->pos_x + 15, y);
 

	
 
			/* draw flag according to server language */
 
			DrawSprite(SPR_FLAGS_BASE + cur_item->info.server_lang, PAL_NONE, nwi_info->pos_x + 25, y);
 
		}
 
	}
 

	
 
	/**
 
	 * Scroll the list up or down to the currently selected server.
 
	 * If the server is below the currently displayed servers, it will
 
	 * scroll down an amount so that the server appears at the bottom.
 
	 * If the server is above the currently displayed servers, it will
 
	 * scroll up so that the server appears at the top.
 
	 */
 
	void ScrollToSelectedServer()
 
	{
 
		if (this->list_pos == SLP_INVALID) return; // no server selected
 
		this->vscroll.ScrollTowards(this->list_pos);
 
	}
 

	
 
public:
 
	NetworkGameWindow(const WindowDesc *desc) : QueryStringBaseWindow(NETWORK_CLIENT_NAME_LENGTH)
 
	{
 
		this->InitNested(desc, 0);
 

	
 
		ttd_strlcpy(this->edit_str_buf, _settings_client.network.client_name, this->edit_str_size);
 
		this->afilter = CS_ALPHANUMERAL;
 
		InitializeTextBuffer(&this->text, this->edit_str_buf, this->edit_str_size, 120);
 
		this->SetFocusedWidget(NGWW_CLIENT);
 

	
 
		UpdateNetworkGameWindow(true);
 

	
 
		this->field = NGWW_CLIENT;
 
		this->server = NULL;
 
		this->list_pos = SLP_INVALID;
 

	
 
		this->last_joined = NetworkGameListAddItem(NetworkAddress(_settings_client.network.last_host, _settings_client.network.last_port));
 

	
 
		this->servers.SetListing(this->last_sorting);
 
		this->servers.SetSortFuncs(this->sorter_funcs);
 
		this->servers.ForceRebuild();
 
		this->SortNetworkGameList();
 
	}
 

	
 
	~NetworkGameWindow()
 
	{
 
		this->last_sorting = this->servers.GetListing();
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		switch (widget) {
 
			case NGWW_CONN_BTN:
 
				SetDParam(0, _lan_internet_types_dropdown[_settings_client.network.lan_internet]);
 
				break;
 
		}
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		switch (widget) {
 
			case NGWW_CONN_BTN:
 
				*size = maxdim(GetStringBoundingBox(_lan_internet_types_dropdown[0]), GetStringBoundingBox(_lan_internet_types_dropdown[1]));
 
				size->width += padding.width;
 
				size->height += padding.height;
 
				break;
 

	
 
			case NGWW_MATRIX:
 
				resize->height = WD_MATRIX_TOP + FONT_HEIGHT_NORMAL + WD_MATRIX_BOTTOM;
 
				size->height = 10 * resize->height;
 
				break;
 

	
 
			case NGWW_LASTJOINED:
 
				size->height = WD_MATRIX_TOP + FONT_HEIGHT_NORMAL + WD_MATRIX_BOTTOM;
 
				break;
 

	
 
			case NGWW_NAME:
 
				size->width += 2 * WD_SORTBUTTON_ARROW_WIDTH; // Make space for the arrow
 
				break;
 

	
 
			case NGWW_CLIENTS:
 
				size->width += 2 * WD_SORTBUTTON_ARROW_WIDTH; // Make space for the arrow
 
				SetDParam(0, 255);
 
				SetDParam(1, 255);
 
				SetDParam(2, 15);
 
				SetDParam(3, 15);
 
				*size = maxdim(*size, GetStringBoundingBox(STR_NETWORK_SERVER_LIST_GENERAL_ONLINE));
 
				break;
 

	
 
			case NGWW_MAPSIZE:
 
				size->width += 2 * WD_SORTBUTTON_ARROW_WIDTH; // Make space for the arrow
 
				SetDParam(0, 2048);
 
				SetDParam(1, 2048);
 
				*size = maxdim(*size, GetStringBoundingBox(STR_NETWORK_SERVER_LIST_MAP_SIZE_SHORT));
 
				break;
 

	
 
			case NGWW_DATE:
 
			case NGWW_YEARS:
 
				size->width += 2 * WD_SORTBUTTON_ARROW_WIDTH; // Make space for the arrow
 
				SetDParam(0, 99999);
 
				*size = maxdim(*size, GetStringBoundingBox(STR_JUST_INT));
 
				break;
 

	
 
			case NGWW_DETAILS_SPACER:
 
				size->height = 20 + 12 * FONT_HEIGHT_NORMAL;
 
				break;
 
		}
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case NGWW_MATRIX: {
 
				uint16 y = r.top + WD_MATRIX_TOP;
 

	
 
				const int max = min(this->vscroll.GetPosition() + this->vscroll.GetCapacity(), (int)this->servers.Length());
 

	
 
				for (int i = this->vscroll.GetPosition(); i < max; ++i) {
 
					const NetworkGameList *ngl = this->servers[i];
 
					this->DrawServerLine(ngl, y, ngl == this->server);
 
					y += this->resize.step_height;
 
				}
 
			} break;
 

	
 
			case NGWW_LASTJOINED:
 
				/* Draw the last joined server, if any */
 
				if (this->last_joined != NULL) this->DrawServerLine(this->last_joined, r.top + WD_MATRIX_TOP, this->last_joined == this->server);
 
				break;
 

	
 
			case NGWW_DETAILS:
 
				this->DrawDetails(r);
 
				break;
 

	
 
			case NGWW_NAME:
 
			case NGWW_CLIENTS:
 
			case NGWW_MAPSIZE:
 
			case NGWW_DATE:
 
			case NGWW_YEARS:
 
			case NGWW_INFO:
 
				if (widget - NGWW_NAME == this->servers.SortType()) this->DrawSortButtonState(widget, this->servers.IsDescSortOrder() ? SBS_DOWN : SBS_UP);
 
				break;
 
		}
 
	}
 

	
 

	
 
	virtual void OnPaint()
 
	{
 
		if (this->servers.NeedRebuild()) {
 
			this->BuildNetworkGameList();
 
		}
 
		this->SortNetworkGameList();
 

	
 
		NetworkGameList *sel = this->server;
 
		/* '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
 

	
 
		/* 'NewGRF Settings' button invisible if no NewGRF is used */
 
		this->GetWidget<NWidgetStacked>(NGWW_NEWGRF_SEL)->SetDisplayedPlane(sel == NULL || !sel->online || sel->info.grfconfig == NULL);
 

	
 
		this->DrawWidgets();
 
		/* Edit box to set client name */
 
		this->DrawEditBox(NGWW_CLIENT);
 
	}
 

	
 
	void DrawDetails(const Rect &r) const
 
	{
 
		NetworkGameList *sel = this->server;
 

	
 
		const int detail_height = 6 + 8 + 6 + 3 * FONT_HEIGHT_NORMAL;
 

	
 
		/* Draw the right menu */
 
		GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.top + detail_height - 1, 157);
 
		if (sel == NULL) {
 
			DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + 6 + 4 + FONT_HEIGHT_NORMAL, STR_NETWORK_SERVER_LIST_GAME_INFO, TC_FROMSTRING, SA_CENTER);
 
		} else if (!sel->online) {
 
			DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + 6 + 4 + FONT_HEIGHT_NORMAL, sel->info.server_name, TC_ORANGE, SA_CENTER); // game name
 

	
 
			DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + detail_height + 4, STR_NETWORK_SERVER_LIST_SERVER_OFFLINE, TC_FROMSTRING, SA_CENTER); // server offline
 
		} else { // show game info
 

	
 
			DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + 6, STR_NETWORK_SERVER_LIST_GAME_INFO, TC_FROMSTRING, SA_CENTER);
 
			DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + 6 + 4 + FONT_HEIGHT_NORMAL, sel->info.server_name, TC_ORANGE, SA_CENTER); // game name
 
			DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + 6 + 8 + 2 * FONT_HEIGHT_NORMAL, sel->info.map_name, TC_BLACK, SA_CENTER); // map name
 

	
 
			uint16 y = r.top + detail_height + 4;
 

	
 
			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(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_CLIENTS);
 
			y += FONT_HEIGHT_NORMAL;
 

	
 
			SetDParam(0, STR_NETWORK_LANG_ANY + sel->info.server_lang);
 
			DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_LANGUAGE); // server language
 
			y += FONT_HEIGHT_NORMAL;
 

	
 
			SetDParam(0, STR_CHEAT_SWITCH_CLIMATE_TEMPERATE_LANDSCAPE + sel->info.map_set);
 
			DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_TILESET); // tileset
 
			y += FONT_HEIGHT_NORMAL;
 

	
 
			SetDParam(0, sel->info.map_width);
 
			SetDParam(1, sel->info.map_height);
 
			DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_MAP_SIZE); // map size
 
			y += FONT_HEIGHT_NORMAL;
 

	
 
			SetDParamStr(0, sel->info.server_revision);
 
			DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_SERVER_VERSION); // server version
 
			y += FONT_HEIGHT_NORMAL;
 

	
 
			SetDParamStr(0, sel->address.GetAddressAsString());
 
			DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_SERVER_ADDRESS); // server address
 
			y += FONT_HEIGHT_NORMAL;
 

	
 
			SetDParam(0, sel->info.start_date);
 
			DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_START_DATE); // start date
 
			y += FONT_HEIGHT_NORMAL;
 

	
 
			SetDParam(0, sel->info.game_date);
 
			DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_CURRENT_DATE); // current date
 
			y += FONT_HEIGHT_NORMAL;
 

	
 
			y += WD_PAR_VSEP_NORMAL;
 

	
 
			if (!sel->info.compatible) {
 
				DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, sel->info.version_compatible ? STR_NETWORK_SERVER_LIST_GRF_MISMATCH : STR_NETWORK_SERVER_LIST_VERSION_MISMATCH, TC_FROMSTRING, SA_CENTER); // server mismatch
 
			} else if (sel->info.clients_on == sel->info.clients_max) {
 
				/* Show: server full, when clients_on == max_clients */
 
				DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_SERVER_FULL, TC_FROMSTRING, SA_CENTER); // server full
 
			} else if (sel->info.use_password) {
 
				DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_PASSWORD, TC_FROMSTRING, SA_CENTER); // password warning
 
			}
 
		}
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		this->field = widget;
 
		switch (widget) {
 
			case NGWW_CANCEL: // Cancel button
 
				DeleteWindowById(WC_NETWORK_WINDOW, 0);
 
				break;
 

	
 
			case NGWW_CONN_BTN: // 'Connection' droplist
 
				ShowDropDownMenu(this, _lan_internet_types_dropdown, _settings_client.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_MAPSIZE: // Sort by map size
 
			case NGWW_DATE:    // Sort by date
 
			case NGWW_YEARS:   // Sort by years
 
			case NGWW_INFO:    // Connectivity (green dot)
 
				if (this->servers.SortType() == widget - NGWW_NAME) {
 
					this->servers.ToggleSortOrder();
 
					if (this->list_pos != SLP_INVALID) this->list_pos = this->servers.Length() - this->list_pos - 1;
 
				} else {
 
					this->servers.SetSortType(widget - NGWW_NAME);
 
					this->servers.ForceResort();
 
					this->SortNetworkGameList();
 
				}
 
				this->ScrollToSelectedServer();
 
				this->SetDirty();
 
				break;
 

	
 
			case NGWW_MATRIX: { // Matrix to show networkgames
 
				uint32 id_v = (pt.y - this->GetWidget<NWidgetBase>(NGWW_MATRIX)->pos_y) / this->resize.step_height;
 

	
 
				if (id_v >= this->vscroll.GetCapacity()) return; // click out of bounds
 
				id_v += this->vscroll.GetPosition();
 

	
 
				this->server = (id_v < this->servers.Length()) ? this->servers[id_v] : NULL;
 
				this->list_pos = (server == NULL) ? SLP_INVALID : id_v;
 
				this->SetDirty();
 
			} break;
 

	
 
			case NGWW_LASTJOINED: {
 
				NetworkGameList *last_joined = NetworkGameListAddItem(NetworkAddress(_settings_client.network.last_host, _settings_client.network.last_port));
 
				if (last_joined != NULL) {
 
					this->server = last_joined;
 

	
 
					/* search the position of the newly selected server */
 
					for (uint i = 0; i < this->servers.Length(); i++) {
 
						if (this->servers[i] == this->server) {
 
							this->list_pos = i;
 
							break;
 
						}
 
					}
 
					this->ScrollToSelectedServer();
 
					this->SetDirty();
 
				}
 
			} break;
 

	
 
			case NGWW_FIND: // Find server automatically
 
				switch (_settings_client.network.lan_internet) {
 
					case 0: NetworkUDPSearchGame(); break;
 
					case 1: NetworkUDPQueryMasterServer(); break;
 
				}
 
				break;
 

	
 
			case NGWW_ADD: // Add a server
 
				SetDParamStr(0, _settings_client.network.connect_to_ip);
 
				ShowQueryString(
 
					STR_JUST_RAW_STRING,
 
					STR_NETWORK_SERVER_LIST_ENTER_IP,
 
					NETWORK_HOSTNAME_LENGTH,  // maximum number of characters including '\0'
 
					0,                        // no limit in pixels
 
					this, CS_ALPHANUMERAL, QSF_ACCEPT_UNCHANGED);
 
				break;
 

	
 
			case NGWW_START: // Start server
 
				ShowNetworkStartServerWindow();
 
				break;
 

	
 
			case NGWW_JOIN: // Join Game
 
				if (this->server != NULL) {
 
					snprintf(_settings_client.network.last_host, sizeof(_settings_client.network.last_host), "%s", this->server->address.GetHostname());
 
					_settings_client.network.last_port = this->server->address.GetPort();
 
					ShowNetworkLobbyWindow(this->server);
 
				}
 
				break;
 

	
 
			case NGWW_REFRESH: // Refresh
 
				if (this->server != NULL) NetworkUDPQueryServer(this->server->address);
 
				break;
 

	
 
			case NGWW_NEWGRF: // NewGRF Settings
 
				if (this->server != NULL) ShowNewGRFSettings(false, false, false, &this->server->info.grfconfig);
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnDoubleClick(Point pt, int widget)
 
	{
 
		if (widget == NGWW_MATRIX || widget == NGWW_LASTJOINED) {
 
			/* is the Join button enabled? */
 
			if (!this->IsWidgetDisabled(NGWW_JOIN)) this->OnClick(pt, NGWW_JOIN);
 
		}
 
	}
 

	
 
	virtual void OnDropdownSelect(int widget, int index)
 
	{
 
		switch (widget) {
 
			case NGWW_CONN_BTN:
 
				_settings_client.network.lan_internet = index;
 
				break;
 

	
 
			default:
 
				NOT_REACHED();
 
		}
 

	
 
		this->SetDirty();
 
	}
 

	
 
	virtual void OnMouseLoop()
 
	{
 
		if (this->field == NGWW_CLIENT) this->HandleEditBox(NGWW_CLIENT);
 
	}
 

	
 
	virtual void OnInvalidateData(int data)
 
	{
 
		switch (data) {
 
			/* Remove the selection */
 
			case 1:
 
				this->server = NULL;
 
				this->list_pos = SLP_INVALID;
 
				break;
 

	
 
			/* Reiterate the whole server list as we downloaded some files */
 
			case 2:
 
				for (NetworkGameList **iter = this->servers.Begin(); iter != this->servers.End(); iter++) {
 
					NetworkGameList *item = *iter;
 
					bool missing_grfs = false;
 
					for (GRFConfig *c = item->info.grfconfig; c != NULL; c = c->next) {
 
						if (c->status != GCS_NOT_FOUND) continue;
 

	
 
						const GRFConfig *f = FindGRFConfig(c->grfid, c->md5sum);
 
						if (f == NULL) {
 
							missing_grfs = true;
 
							continue;
 
						}
 

	
 
						c->filename  = f->filename;
 
						c->name      = f->name;
 
						c->info      = f->info;
 
						c->status    = GCS_UNKNOWN;
 
					}
 

	
 
					if (!missing_grfs) item->info.compatible = item->info.version_compatible;
 
				}
 
				break;
 
		}
 
		this->servers.ForceRebuild();
 
		this->SetDirty();
 
	}
 

	
 
	virtual EventState OnKeyPress(uint16 key, uint16 keycode)
 
	{
 
		EventState state = ES_NOT_HANDLED;
 

	
 
		/* handle up, down, pageup, pagedown, home and end */
 
		if (keycode == WKC_UP || keycode == WKC_DOWN || keycode == WKC_PAGEUP || keycode == WKC_PAGEDOWN || keycode == WKC_HOME || keycode == WKC_END) {
 
			if (this->servers.Length() == 0) return ES_HANDLED;
 
			switch (keycode) {
 
				case WKC_UP:
 
					/* scroll up by one */
 
					if (this->server == NULL) return ES_HANDLED;
 
					if (this->list_pos > 0) this->list_pos--;
 
					break;
 
				case WKC_DOWN:
 
					/* scroll down by one */
 
					if (this->server == NULL) return ES_HANDLED;
 
					if (this->list_pos < this->servers.Length() - 1) this->list_pos++;
 
					break;
 
				case WKC_PAGEUP:
 
					/* scroll up a page */
 
					if (this->server == NULL) return ES_HANDLED;
 
					this->list_pos = (this->list_pos < this->vscroll.GetCapacity()) ? 0 : this->list_pos - this->vscroll.GetCapacity();
 
					break;
 
				case WKC_PAGEDOWN:
 
					/* scroll down a page */
 
					if (this->server == NULL) return ES_HANDLED;
 
					this->list_pos = min(this->list_pos + this->vscroll.GetCapacity(), (int)this->servers.Length() - 1);
 
					break;
 
				case WKC_HOME:
 
					/* jump to beginning */
 
					this->list_pos = 0;
 
					break;
 
				case WKC_END:
 
					/* jump to end */
 
					this->list_pos = this->servers.Length() - 1;
 
					break;
 
				default: break;
 
			}
 

	
 
			this->server = this->servers[this->list_pos];
 

	
 
			/* scroll to the new server if it is outside the current range */
 
			this->ScrollToSelectedServer();
 

	
 
			/* redraw window */
 
			this->SetDirty();
 
			return ES_HANDLED;
 
		}
 

	
 
		if (this->field != NGWW_CLIENT) {
 
			if (this->server != NULL) {
 
				if (keycode == WKC_DELETE) { // Press 'delete' to remove servers
 
					NetworkGameListRemoveItem(this->server);
 
					this->server = NULL;
 
					this->list_pos = SLP_INVALID;
 
				}
 
			}
 
			return state;
 
		}
 

	
 
		if (this->HandleEditBoxKey(NGWW_CLIENT, key, keycode, state) == HEBR_CONFIRM) return state;
 

	
 
		/* The name is only allowed when it starts with a letter! */
 
		if (!StrEmpty(this->edit_str_buf) && this->edit_str_buf[0] != ' ') {
 
			strecpy(_settings_client.network.client_name, this->edit_str_buf, lastof(_settings_client.network.client_name));
 
		} else {
 
			strecpy(_settings_client.network.client_name, "Player", lastof(_settings_client.network.client_name));
 
		}
 
		return state;
 
	}
 

	
 
	virtual void OnQueryTextFinished(char *str)
 
	{
 
		if (!StrEmpty(str)) NetworkAddServer(str);
 
	}
 

	
 
	virtual void OnResize()
 
	{
 
		NWidgetCore *nwi = this->GetWidget<NWidgetCore>(NGWW_MATRIX);
 
		this->vscroll.SetCapacity(nwi->current_y / this->resize.step_height);
 
		nwi->widget_data = (this->vscroll.GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START);
 
	}
 
};
 

	
 
Listing NetworkGameWindow::last_sorting = {false, 5};
 
GUIGameServerList::SortFunction * const NetworkGameWindow::sorter_funcs[] = {
 
	&NGameNameSorter,
 
	&NGameClientSorter,
 
	&NGameMapSizeSorter,
 
	&NGameDateSorter,
 
	&NGameYearsSorter,
 
	&NGameAllowedSorter
 
};
 

	
 
static NWidgetBase *MakeResizableHeader(int *biggest_index)
 
{
 
	*biggest_index = max<int>(*biggest_index, NGWW_INFO);
 
	return new NWidgetServerListHeader();
 
}
 

	
 
/* Generates incorrect display_flags for widgets NGWW_NAME, and incorrect
 
 * display_flags and/or left/right side for the overlapping widgets
 
 * NGWW_CLIENTS through NGWW_YEARS.
 
 */
 
static const NWidgetPart _nested_network_game_widgets[] = {
 
	/* TOP */
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_LIGHT_BLUE, NGWW_CLOSE),
 
		NWidget(WWT_CAPTION, COLOUR_LIGHT_BLUE, NGWW_CAPTION), SetDataTip(STR_NETWORK_SERVER_LIST_CAPTION, STR_NULL), // XXX Add default caption tooltip!
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, NGWW_MAIN),
 
		NWidget(NWID_VERTICAL), SetPIP(10, 7, 0),
 
			NWidget(NWID_HORIZONTAL), SetPIP(10, 7, 10),
 
				NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, NGWW_CONNECTION), SetDataTip(STR_NETWORK_SERVER_LIST_CONNECTION, STR_NULL),
 
				NWidget(WWT_DROPDOWN, COLOUR_LIGHT_BLUE, NGWW_CONN_BTN),
 
										SetDataTip(STR_BLACK_STRING, STR_NETWORK_SERVER_LIST_CONNECTION_TOOLTIP),
 
				NWidget(NWID_SPACER), SetFill(true, false), SetResize(1, 0),
 
				NWidget(NWID_SPACER), SetFill(1, 0), SetResize(1, 0),
 
				NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, NGWW_CLIENT_LABEL), SetDataTip(STR_NETWORK_SERVER_LIST_PLAYER_NAME, STR_NULL),
 
				NWidget(WWT_EDITBOX, COLOUR_LIGHT_BLUE, NGWW_CLIENT), SetMinimalSize(151, 12),
 
										SetDataTip(STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE, STR_NETWORK_SERVER_LIST_ENTER_NAME_TOOLTIP),
 
			EndContainer(),
 
			NWidget(NWID_HORIZONTAL), SetPIP(10, 7, 10),
 
				/* LEFT SIDE */
 
				NWidget(NWID_VERTICAL),
 
					NWidget(NWID_HORIZONTAL),
 
						NWidget(NWID_VERTICAL),
 
							NWidgetFunction(MakeResizableHeader),
 
							NWidget(WWT_MATRIX, COLOUR_LIGHT_BLUE, NGWW_MATRIX), SetResize(1, 1), SetFill(true, false),
 
							NWidget(WWT_MATRIX, COLOUR_LIGHT_BLUE, NGWW_MATRIX), SetResize(1, 1), SetFill(1, 0),
 
												SetDataTip(0, STR_NETWORK_SERVER_LIST_CLICK_GAME_TO_SELECT),
 
						EndContainer(),
 
						NWidget(WWT_SCROLLBAR, COLOUR_LIGHT_BLUE, NGWW_SCROLLBAR),
 
					EndContainer(),
 
					NWidget(NWID_SPACER), SetMinimalSize(0, 7), SetResize(1, 0), SetFill(true, true),
 
					NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, NGWW_LASTJOINED_LABEL), SetFill(true, false),
 
					NWidget(NWID_SPACER), SetMinimalSize(0, 7), SetResize(1, 0), SetFill(1, 1),
 
					NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, NGWW_LASTJOINED_LABEL), SetFill(1, 0),
 
										SetDataTip(STR_NETWORK_SERVER_LIST_LAST_JOINED_SERVER, STR_NULL), SetResize(1, 0),
 
					NWidget(NWID_HORIZONTAL), SetPIP(0, 0, WD_VSCROLLBAR_WIDTH),
 
						NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, NGWW_LASTJOINED), SetFill(true, false), SetResize(1, 0),
 
						NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, NGWW_LASTJOINED), SetFill(1, 0), SetResize(1, 0),
 
											SetDataTip(0x0, STR_NETWORK_SERVER_LIST_CLICK_TO_SELECT_LAST),
 
						EndContainer(),
 
					EndContainer(),
 
				EndContainer(),
 
				/* RIGHT SIDE */
 
				NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, NGWW_DETAILS),
 
					NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(5, 5, 5),
 
						NWidget(WWT_EMPTY, INVALID_COLOUR, NGWW_DETAILS_SPACER), SetMinimalSize(140, 155), SetResize(0, 1), SetFill(true, true), // Make sure it's at least this wide
 
						NWidget(WWT_EMPTY, INVALID_COLOUR, NGWW_DETAILS_SPACER), SetMinimalSize(140, 155), SetResize(0, 1), SetFill(1, 1), // Make sure it's at least this wide
 
						NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(5, 5, 5),
 
							NWidget(NWID_SPACER), SetFill(true, false),
 
							NWidget(NWID_SPACER), SetFill(1, 0),
 
							NWidget(NWID_SELECTION, INVALID_COLOUR, NGWW_NEWGRF_SEL),
 
								NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NGWW_NEWGRF), SetFill(true, false), SetDataTip(STR_INTRO_NEWGRF_SETTINGS, STR_NULL),
 
								NWidget(NWID_SPACER), SetFill(true, false),
 
								NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NGWW_NEWGRF), SetFill(1, 0), SetDataTip(STR_INTRO_NEWGRF_SETTINGS, STR_NULL),
 
								NWidget(NWID_SPACER), SetFill(1, 0),
 
							EndContainer(),
 
						EndContainer(),
 
						NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(5, 5, 5),
 
							NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NGWW_JOIN), SetFill(true, false), SetDataTip(STR_NETWORK_SERVER_LIST_JOIN_GAME, STR_NULL),
 
							NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NGWW_REFRESH), SetFill(true, false), SetDataTip(STR_NETWORK_SERVER_LIST_REFRESH, STR_NETWORK_SERVER_LIST_REFRESH_TOOLTIP),
 
							NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NGWW_JOIN), SetFill(1, 0), SetDataTip(STR_NETWORK_SERVER_LIST_JOIN_GAME, STR_NULL),
 
							NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NGWW_REFRESH), SetFill(1, 0), SetDataTip(STR_NETWORK_SERVER_LIST_REFRESH, STR_NETWORK_SERVER_LIST_REFRESH_TOOLTIP),
 
						EndContainer(),
 
					EndContainer(),
 
				EndContainer(),
 
			EndContainer(),
 
			/* BOTTOM */
 
			NWidget(NWID_HORIZONTAL),
 
				NWidget(NWID_VERTICAL),
 
					NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 7, 4),
 
						NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NGWW_FIND), SetResize(1, 0), SetFill(true, false), SetDataTip(STR_NETWORK_SERVER_LIST_FIND_SERVER, STR_NETWORK_SERVER_LIST_FIND_SERVER_TOOLTIP),
 
						NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NGWW_ADD), SetResize(1, 0), SetFill(true, false), SetDataTip(STR_NETWORK_SERVER_LIST_ADD_SERVER, STR_NETWORK_SERVER_LIST_ADD_SERVER_TOOLTIP),
 
						NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NGWW_START), SetResize(1, 0), SetFill(true, false), SetDataTip(STR_NETWORK_SERVER_LIST_START_SERVER, STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP),
 
						NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NGWW_CANCEL), SetResize(1, 0), SetFill(true, false), SetDataTip(STR_BUTTON_CANCEL, STR_NULL),
 
						NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NGWW_FIND), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_NETWORK_SERVER_LIST_FIND_SERVER, STR_NETWORK_SERVER_LIST_FIND_SERVER_TOOLTIP),
 
						NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NGWW_ADD), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_NETWORK_SERVER_LIST_ADD_SERVER, STR_NETWORK_SERVER_LIST_ADD_SERVER_TOOLTIP),
 
						NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NGWW_START), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_NETWORK_SERVER_LIST_START_SERVER, STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP),
 
						NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NGWW_CANCEL), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_BUTTON_CANCEL, STR_NULL),
 
					EndContainer(),
 
					NWidget(NWID_SPACER), SetMinimalSize(0, 6), SetResize(1, 0), SetFill(true, false),
 
					NWidget(NWID_SPACER), SetMinimalSize(0, 6), SetResize(1, 0), SetFill(1, 0),
 
				EndContainer(),
 
				NWidget(NWID_VERTICAL),
 
					NWidget(NWID_SPACER), SetFill(false, true),
 
					NWidget(NWID_SPACER), SetFill(0, 1),
 
					NWidget(WWT_RESIZEBOX, COLOUR_LIGHT_BLUE, NGWW_RESIZE),
 
				EndContainer(),
 
			EndContainer(),
 
		EndContainer(),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _network_game_window_desc(
 
	WDP_CENTER, WDP_CENTER, 1000, 730,
 
	WC_NETWORK_WINDOW, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_STD_BTN | WDF_UNCLICK_BUTTONS | WDF_RESIZABLE,
 
	_nested_network_game_widgets, lengthof(_nested_network_game_widgets)
 
);
 

	
 
void ShowNetworkGameWindow()
 
{
 
	static bool first = true;
 
	DeleteWindowById(WC_NETWORK_WINDOW, 0);
 

	
 
	/* Only show once */
 
	if (first) {
 
		first = false;
 
		/* add all servers from the config file to our list */
 
		for (char **iter = _network_host_list.Begin(); iter != _network_host_list.End(); iter++) {
 
			NetworkAddServer(*iter);
 
		}
 
	}
 

	
 
	new NetworkGameWindow(&_network_game_window_desc);
 
}
 

	
 
/** Enum for NetworkStartServerWindow, referring to _network_start_server_window_widgets */
 
enum NetworkStartServerWidgets {
 
	NSSW_CLOSE,             ///< Close 'X' button
 
	NSSW_CAPTION,
 
	NSSW_BACKGROUND,
 
	NSSW_GAMENAME_LABEL,
 
	NSSW_GAMENAME,          ///< Background for editbox to set game name
 
	NSSW_SETPWD,            ///< 'Set password' button
 
	NSSW_SELECT_MAP_LABEL,
 
	NSSW_SELMAP,            ///< 'Select map' list
 
	NSSW_SCROLLBAR,
 
	NSSW_CONNTYPE_LABEL,
 
	NSSW_CONNTYPE_BTN,      ///< 'Connection type' droplist button
 
	NSSW_CLIENTS_LABEL,
 
	NSSW_CLIENTS_BTND,      ///< 'Max clients' downarrow
 
	NSSW_CLIENTS_TXT,       ///< 'Max clients' text
 
	NSSW_CLIENTS_BTNU,      ///< 'Max clients' uparrow
 
	NSSW_COMPANIES_LABEL,
 
	NSSW_COMPANIES_BTND,    ///< 'Max companies' downarrow
 
	NSSW_COMPANIES_TXT,     ///< 'Max companies' text
 
	NSSW_COMPANIES_BTNU,    ///< 'Max companies' uparrow
 
	NSSW_SPECTATORS_LABEL,
 
	NSSW_SPECTATORS_BTND,   ///< 'Max spectators' downarrow
 
	NSSW_SPECTATORS_TXT,    ///< 'Max spectators' text
 
	NSSW_SPECTATORS_BTNU,   ///< 'Max spectators' uparrow
 

	
 
	NSSW_LANGUAGE_LABEL,
 
	NSSW_LANGUAGE_BTN,      ///< 'Language spoken' droplist button
 
	NSSW_START,             ///< 'Start' button
 
	NSSW_LOAD,              ///< 'Load' button
 
	NSSW_CANCEL,            ///< 'Cancel' button
 
};
 

	
 
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(NETWORK_NAME_LENGTH)
 
	{
 
		this->InitNested(desc, 0);
 

	
 
		ttd_strlcpy(this->edit_str_buf, _settings_client.network.server_name, this->edit_str_size);
 

	
 
		_saveload_mode = SLD_NEW_GAME;
 
		BuildFileList();
 
		this->vscroll.SetCapacity(14);
 
		this->vscroll.SetCount(_fios_items.Length() + 1);
 

	
 
		this->afilter = CS_ALPHANUMERAL;
 
		InitializeTextBuffer(&this->text, this->edit_str_buf, this->edit_str_size, 160);
 
		this->SetFocusedWidget(NSSW_GAMENAME);
 

	
 
		this->field = NSSW_GAMENAME;
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		switch (widget) {
 
			case NSSW_CONNTYPE_BTN:
 
				SetDParam(0, _connection_types_dropdown[_settings_client.network.server_advertise]);
 
				break;
 

	
 
			case NSSW_CLIENTS_TXT:
 
				SetDParam(0, _settings_client.network.max_clients);
 
				break;
 

	
 
			case NSSW_COMPANIES_TXT:
 
				SetDParam(0, _settings_client.network.max_companies);
 
				break;
 

	
 
			case NSSW_SPECTATORS_TXT:
 
				SetDParam(0, _settings_client.network.max_spectators);
 
				break;
 

	
 
			case NSSW_LANGUAGE_BTN:
 
				SetDParam(0, STR_NETWORK_LANG_ANY + _settings_client.network.server_lang);
 
				break;
 
		}
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		switch (widget) {
 
			case NSSW_CONNTYPE_BTN:
 
				*size = maxdim(GetStringBoundingBox(_connection_types_dropdown[0]), GetStringBoundingBox(_connection_types_dropdown[1]));
 
				size->width += padding.width;
 
				size->height += padding.height;
 
				break;
 

	
 
			case NSSW_SELMAP:
 
				resize->height = FONT_HEIGHT_NORMAL;
 
				size->height = 14 * resize->height + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
 
				break;
 
		}
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case NSSW_SELMAP:
 
				this->DrawMapSelection(r);
 
				break;
 

	
 
			case NSSW_SETPWD:
 
				/* if password is set, draw red '*' next to 'Set password' button */
 
				if (!StrEmpty(_settings_client.network.server_password)) DrawString(r.right + WD_FRAMERECT_LEFT, this->width - WD_FRAMERECT_RIGHT, r.top, "*", TC_RED);
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		/* draw basic widgets */
 
		this->DrawWidgets();
 

	
 
		/* editbox to set game name */
 
		this->DrawEditBox(NSSW_GAMENAME);
 
	}
 

	
 
	void DrawMapSelection(const Rect &r) const
 
	{
 
		int y = r.top + WD_FRAMERECT_TOP;
 
		/* draw list of maps */
 
		GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, 0xD7);  // black background of maps list
 

	
 
		for (uint pos = this->vscroll.GetPosition(); pos < _fios_items.Length() + 1; pos++) {
 
			const FiosItem *item = _fios_items.Get(pos - 1);
 
			if (item == this->map || (pos == 0 && this->map == NULL)) {
 
				GfxFillRect(r.left + 1, y, r.right - 1, y + FONT_HEIGHT_NORMAL - 1, 155); // show highlighted item with a different colour
 
			}
 

	
 
			if (pos == 0) {
 
				DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_START_SERVER_SERVER_RANDOM_GAME, TC_DARK_GREEN);
 
			} else {
 
				DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, item->title, _fios_colours[item->type] );
 
			}
 
			y += FONT_HEIGHT_NORMAL;
 

	
 
			if (y >= this->vscroll.GetCapacity() * FONT_HEIGHT_NORMAL + r.top) break;
 
		}
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		this->field = widget;
 
		switch (widget) {
 
			case NSSW_CLOSE:  // Close 'X'
 
			case NSSW_CANCEL: // Cancel button
 
				ShowNetworkGameWindow();
 
				break;
 

	
 
			case NSSW_SETPWD: // Set password button
 
				this->widget_id = NSSW_SETPWD;
 
				SetDParamStr(0, _settings_client.network.server_password);
 
				ShowQueryString(STR_JUST_RAW_STRING, STR_NETWORK_START_SERVER_SET_PASSWORD, 20, 250, this, CS_ALPHANUMERAL, QSF_NONE);
 
				break;
 

	
 
			case NSSW_SELMAP: { // Select map
 
				int y = (pt.y - this->GetWidget<NWidgetBase>(NSSW_SELMAP)->pos_y - WD_FRAMERECT_TOP) / FONT_HEIGHT_NORMAL;
 

	
 
				y += this->vscroll.GetPosition();
 
				if (y >= this->vscroll.GetCount()) return;
 

	
 
				this->map = (y == 0) ? NULL : _fios_items.Get(y - 1);
 
				this->SetDirty();
 
			} break;
 

	
 
			case NSSW_CONNTYPE_BTN: // Connection type
 
				ShowDropDownMenu(this, _connection_types_dropdown, _settings_client.network.server_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 ((this->flags4 & WF_TIMEOUT_MASK) <= WF_TIMEOUT_TRIGGER) {
 
					this->HandleButtonClick(widget);
 
					this->SetDirty();
 
					switch (widget) {
 
						default: NOT_REACHED();
 
						case NSSW_CLIENTS_BTND: case NSSW_CLIENTS_BTNU:
 
							_settings_client.network.max_clients    = Clamp(_settings_client.network.max_clients    + widget - NSSW_CLIENTS_TXT,    2, MAX_CLIENTS);
 
							break;
 
						case NSSW_COMPANIES_BTND: case NSSW_COMPANIES_BTNU:
 
							_settings_client.network.max_companies  = Clamp(_settings_client.network.max_companies  + widget - NSSW_COMPANIES_TXT,  1, MAX_COMPANIES);
 
							break;
 
						case NSSW_SPECTATORS_BTND: case NSSW_SPECTATORS_BTNU:
 
							_settings_client.network.max_spectators = Clamp(_settings_client.network.max_spectators + widget - NSSW_SPECTATORS_TXT, 0, MAX_CLIENTS);
 
							break;
 
					}
 
				}
 
				_left_button_clicked = false;
 
				break;
 

	
 
			case NSSW_CLIENTS_TXT:    // Click on number of clients
 
				this->widget_id = NSSW_CLIENTS_TXT;
 
				SetDParam(0, _settings_client.network.max_clients);
 
				ShowQueryString(STR_JUST_INT, STR_NETWORK_START_SERVER_NUMBER_OF_CLIENTS,    4, 50, this, CS_NUMERAL, QSF_NONE);
 
				break;
 

	
 
			case NSSW_COMPANIES_TXT:  // Click on number of companies
 
				this->widget_id = NSSW_COMPANIES_TXT;
 
				SetDParam(0, _settings_client.network.max_companies);
 
				ShowQueryString(STR_JUST_INT, STR_NETWORK_START_SERVER_NUMBER_OF_COMPANIES,  3, 50, this, CS_NUMERAL, QSF_NONE);
 
				break;
 

	
 
			case NSSW_SPECTATORS_TXT: // Click on number of spectators
 
				this->widget_id = NSSW_SPECTATORS_TXT;
 
				SetDParam(0, _settings_client.network.max_spectators);
 
				ShowQueryString(STR_JUST_INT, STR_NETWORK_START_SERVER_NUMBER_OF_SPECTATORS, 4, 50, this, CS_NUMERAL, QSF_NONE);
 
				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 + _settings_client.network.server_lang) {
 
						sel = i;
 
						break;
 
					}
 
				}
 
				ShowDropDownMenu(this, _language_dropdown, sel, NSSW_LANGUAGE_BTN, 0, 0);
 
			} break;
 

	
 
			case NSSW_START: // Start game
 
				_is_network_server = true;
 

	
 
				if (this->map == NULL) { // start random new game
 
					ShowGenerateLandscape();
 
				} else { // load a scenario
 
					const char *name = FiosBrowseTo(this->map);
 
					if (name != NULL) {
 
						SetFiosType(this->map->type);
 
						_file_to_saveload.filetype = FT_SCENARIO;
 
						strecpy(_file_to_saveload.name, name, lastof(_file_to_saveload.name));
 
						strecpy(_file_to_saveload.title, this->map->title, lastof(_file_to_saveload.title));
 

	
 
						delete this;
 
						SwitchToMode(SM_START_SCENARIO);
 
					}
 
				}
 
				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;
 
		}
 
	}
 

	
 
	virtual void OnDropdownSelect(int widget, int index)
 
	{
 
		switch (widget) {
 
			case NSSW_CONNTYPE_BTN:
 
				_settings_client.network.server_advertise = (index != 0);
 
				break;
 
			case NSSW_LANGUAGE_BTN:
 
				_settings_client.network.server_lang = _language_dropdown[index] - STR_NETWORK_LANG_ANY;
 
				break;
 
			default:
 
				NOT_REACHED();
 
		}
 

	
 
		this->SetDirty();
 
	}
 

	
 
	virtual void OnMouseLoop()
 
	{
 
		if (this->field == NSSW_GAMENAME) this->HandleEditBox(NSSW_GAMENAME);
 
	}
 

	
 
	virtual EventState OnKeyPress(uint16 key, uint16 keycode)
 
	{
 
		EventState state = ES_NOT_HANDLED;
 
		if (this->field == NSSW_GAMENAME) {
 
			if (this->HandleEditBoxKey(NSSW_GAMENAME, key, keycode, state) == HEBR_CONFIRM) return state;
 

	
 
			strecpy(_settings_client.network.server_name, this->text.buf, lastof(_settings_client.network.server_name));
 
		}
 

	
 
		return state;
 
	}
 

	
 
	virtual void OnTimeout()
 
	{
 
		static const int raise_widgets[] = {NSSW_CLIENTS_BTND, NSSW_CLIENTS_BTNU, NSSW_COMPANIES_BTND, NSSW_COMPANIES_BTNU, NSSW_SPECTATORS_BTND, NSSW_SPECTATORS_BTNU, WIDGET_LIST_END};
 
		for (const int *widget = raise_widgets; *widget != WIDGET_LIST_END; widget++) {
 
			if (this->IsWidgetLowered(*widget)) {
 
				this->RaiseWidget(*widget);
 
				this->SetWidgetDirty(*widget);
 
			}
 
		}
 
	}
 

	
 
	virtual void OnQueryTextFinished(char *str)
 
	{
 
		if (str == NULL) return;
 

	
 
		if (this->widget_id == NSSW_SETPWD) {
 
			strecpy(_settings_client.network.server_password, str, lastof(_settings_client.network.server_password));
 
		} else {
 
			int32 value = atoi(str);
 
			this->SetWidgetDirty(this->widget_id);
 
			switch (this->widget_id) {
 
				default: NOT_REACHED();
 
				case NSSW_CLIENTS_TXT:    _settings_client.network.max_clients    = Clamp(value, 2, MAX_CLIENTS); break;
 
				case NSSW_COMPANIES_TXT:  _settings_client.network.max_companies  = Clamp(value, 1, MAX_COMPANIES); break;
 
				case NSSW_SPECTATORS_TXT: _settings_client.network.max_spectators = Clamp(value, 0, MAX_CLIENTS); break;
 
			}
 
		}
 

	
 
		this->SetDirty();
 
	}
 
};
 

	
 
static const NWidgetPart _nested_network_start_server_window_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_LIGHT_BLUE, NSSW_CLOSE),
 
		NWidget(WWT_CAPTION, COLOUR_LIGHT_BLUE, NSSW_CAPTION), SetDataTip(STR_NETWORK_START_SERVER_CAPTION, STR_NULL),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, NSSW_BACKGROUND),
 
		NWidget(NWID_HORIZONTAL), SetPIP(10, 8, 10),
 
			NWidget(NWID_VERTICAL), SetPIP(10, 0, 10),
 
				/* Game name widgets */
 
				NWidget(NWID_HORIZONTAL), SetPIP(0, 2, 0),
 
					NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, NSSW_GAMENAME_LABEL), SetDataTip(STR_NETWORK_START_SERVER_NEW_GAME_NAME, STR_NULL),
 
					NWidget(WWT_EDITBOX, COLOUR_LIGHT_BLUE, NSSW_GAMENAME), SetMinimalSize(10, 12), SetFill(true, false),
 
					NWidget(WWT_EDITBOX, COLOUR_LIGHT_BLUE, NSSW_GAMENAME), SetMinimalSize(10, 12), SetFill(1, 0),
 
														SetDataTip(STR_NETWORK_START_SERVER_NEW_GAME_NAME_OSKTITLE, STR_NETWORK_START_SERVER_NEW_GAME_NAME_TOOLTIP),
 
				EndContainer(),
 
				/* List of playable scenarios. */
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 8), SetFill(true, false),
 
				NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, NSSW_SELECT_MAP_LABEL), SetFill(true, false), SetDataTip(STR_NETWORK_START_SERVER_SELECT_MAP, STR_NULL),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 6), SetFill(true, false),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 8), SetFill(1, 0),
 
				NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, NSSW_SELECT_MAP_LABEL), SetFill(1, 0), SetDataTip(STR_NETWORK_START_SERVER_SELECT_MAP, STR_NULL),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 6), SetFill(1, 0),
 
				NWidget(NWID_HORIZONTAL),
 
					NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, NSSW_SELMAP), SetMinimalSize(250, 0), SetFill(true, true), SetDataTip(STR_NULL, STR_NETWORK_START_SERVER_SELECT_MAP_TOOLTIP), EndContainer(),
 
					NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, NSSW_SELMAP), SetMinimalSize(250, 0), SetFill(1, 1), SetDataTip(STR_NULL, STR_NETWORK_START_SERVER_SELECT_MAP_TOOLTIP), EndContainer(),
 
					NWidget(WWT_SCROLLBAR, COLOUR_LIGHT_BLUE, NSSW_SCROLLBAR),
 
				EndContainer(),
 
			EndContainer(),
 
			NWidget(NWID_VERTICAL), SetPIP(10, 0, 10),
 
				/* Password widgets. */
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NSSW_SETPWD), SetFill(true, false),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NSSW_SETPWD), SetFill(1, 0),
 
														SetDataTip(STR_NETWORK_START_SERVER_SET_PASSWORD, STR_NETWORK_START_SERVER_PASSWORD_TOOLTIP),
 
				/* Combo/selection boxes to control Connection Type / Max Clients / Max Companies / Max Observers / Language */
 
				NWidget(NWID_SPACER), SetFill(true, true),
 
				NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, NSSW_CONNTYPE_LABEL), SetFill(true, false), SetDataTip(STR_NETWORK_SERVER_LIST_CONNECTION, STR_NULL),
 
				NWidget(NWID_SPACER), SetFill(1, 1),
 
				NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, NSSW_CONNTYPE_LABEL), SetFill(1, 0), SetDataTip(STR_NETWORK_SERVER_LIST_CONNECTION, STR_NULL),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 1),
 
				NWidget(WWT_DROPDOWN, COLOUR_LIGHT_BLUE, NSSW_CONNTYPE_BTN), SetFill(true, false),
 
				NWidget(WWT_DROPDOWN, COLOUR_LIGHT_BLUE, NSSW_CONNTYPE_BTN), SetFill(1, 0),
 
												SetDataTip(STR_BLACK_STRING, STR_NETWORK_SERVER_LIST_CONNECTION_TOOLTIP),
 

	
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 6),
 
				NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, NSSW_CLIENTS_LABEL), SetFill(true, false), SetDataTip(STR_NETWORK_START_SERVER_NUMBER_OF_CLIENTS, STR_NULL),
 
				NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, NSSW_CLIENTS_LABEL), SetFill(1, 0), SetDataTip(STR_NETWORK_START_SERVER_NUMBER_OF_CLIENTS, STR_NULL),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 1),
 
				NWidget(NWID_HORIZONTAL),
 
					NWidget(WWT_IMGBTN, COLOUR_LIGHT_BLUE, NSSW_CLIENTS_BTND), SetMinimalSize(12, 12), SetFill(false, true),
 
					NWidget(WWT_IMGBTN, COLOUR_LIGHT_BLUE, NSSW_CLIENTS_BTND), SetMinimalSize(12, 12), SetFill(0, 1),
 
												SetDataTip(SPR_ARROW_DOWN, STR_NETWORK_START_SERVER_NUMBER_OF_CLIENTS_TOOLTIP),
 
					NWidget(WWT_PUSHTXTBTN, COLOUR_LIGHT_BLUE, NSSW_CLIENTS_TXT), SetFill(true, false),
 
					NWidget(WWT_PUSHTXTBTN, COLOUR_LIGHT_BLUE, NSSW_CLIENTS_TXT), SetFill(1, 0),
 
												SetDataTip(STR_NETWORK_START_SERVER_CLIENTS_SELECT, STR_NETWORK_START_SERVER_NUMBER_OF_CLIENTS_TOOLTIP),
 
					NWidget(WWT_IMGBTN, COLOUR_LIGHT_BLUE, NSSW_CLIENTS_BTNU), SetMinimalSize(12, 12), SetFill(false, true),
 
					NWidget(WWT_IMGBTN, COLOUR_LIGHT_BLUE, NSSW_CLIENTS_BTNU), SetMinimalSize(12, 12), SetFill(0, 1),
 
												SetDataTip(SPR_ARROW_UP, STR_NETWORK_START_SERVER_NUMBER_OF_CLIENTS_TOOLTIP),
 
				EndContainer(),
 

	
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 6),
 
				NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, NSSW_COMPANIES_LABEL), SetFill(true, false), SetDataTip(STR_NETWORK_START_SERVER_NUMBER_OF_COMPANIES, STR_NULL),
 
				NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, NSSW_COMPANIES_LABEL), SetFill(1, 0), SetDataTip(STR_NETWORK_START_SERVER_NUMBER_OF_COMPANIES, STR_NULL),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 1),
 
				NWidget(NWID_HORIZONTAL),
 
					NWidget(WWT_IMGBTN, COLOUR_LIGHT_BLUE, NSSW_COMPANIES_BTND), SetMinimalSize(12, 12), SetFill(false, true),
 
					NWidget(WWT_IMGBTN, COLOUR_LIGHT_BLUE, NSSW_COMPANIES_BTND), SetMinimalSize(12, 12), SetFill(0, 1),
 
												SetDataTip(SPR_ARROW_DOWN, STR_NETWORK_START_SERVER_NUMBER_OF_COMPANIES_TOOLTIP),
 
					NWidget(WWT_PUSHTXTBTN, COLOUR_LIGHT_BLUE, NSSW_COMPANIES_TXT), SetFill(true, false),
 
					NWidget(WWT_PUSHTXTBTN, COLOUR_LIGHT_BLUE, NSSW_COMPANIES_TXT), SetFill(1, 0),
 
												SetDataTip(STR_NETWORK_START_SERVER_COMPANIES_SELECT, STR_NETWORK_START_SERVER_NUMBER_OF_COMPANIES_TOOLTIP),
 
					NWidget(WWT_IMGBTN, COLOUR_LIGHT_BLUE, NSSW_COMPANIES_BTNU), SetMinimalSize(12, 12), SetFill(false, true),
 
					NWidget(WWT_IMGBTN, COLOUR_LIGHT_BLUE, NSSW_COMPANIES_BTNU), SetMinimalSize(12, 12), SetFill(0, 1),
 
												SetDataTip(SPR_ARROW_UP, STR_NETWORK_START_SERVER_NUMBER_OF_COMPANIES_TOOLTIP),
 
				EndContainer(),
 

	
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 6),
 
				NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, NSSW_SPECTATORS_LABEL), SetFill(true, false), SetDataTip(STR_NETWORK_START_SERVER_NUMBER_OF_SPECTATORS, STR_NULL),
 
				NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, NSSW_SPECTATORS_LABEL), SetFill(1, 0), SetDataTip(STR_NETWORK_START_SERVER_NUMBER_OF_SPECTATORS, STR_NULL),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 1),
 
				NWidget(NWID_HORIZONTAL),
 
					NWidget(WWT_IMGBTN, COLOUR_LIGHT_BLUE, NSSW_SPECTATORS_BTND), SetMinimalSize(12, 12), SetFill(false, true),
 
					NWidget(WWT_IMGBTN, COLOUR_LIGHT_BLUE, NSSW_SPECTATORS_BTND), SetMinimalSize(12, 12), SetFill(0, 1),
 
												SetDataTip(SPR_ARROW_DOWN, STR_NETWORK_START_SERVER_NUMBER_OF_SPECTATORS_TOOLTIP),
 
					NWidget(WWT_PUSHTXTBTN, COLOUR_LIGHT_BLUE, NSSW_SPECTATORS_TXT), SetFill(true, false),
 
					NWidget(WWT_PUSHTXTBTN, COLOUR_LIGHT_BLUE, NSSW_SPECTATORS_TXT), SetFill(1, 0),
 
												SetDataTip(STR_NETWORK_START_SERVER_SPECTATORS_SELECT, STR_NETWORK_START_SERVER_NUMBER_OF_SPECTATORS_TOOLTIP),
 
					NWidget(WWT_IMGBTN, COLOUR_LIGHT_BLUE, NSSW_SPECTATORS_BTNU), SetMinimalSize(12, 12), SetFill(false, true),
 
					NWidget(WWT_IMGBTN, COLOUR_LIGHT_BLUE, NSSW_SPECTATORS_BTNU), SetMinimalSize(12, 12), SetFill(0, 1),
 
												SetDataTip(SPR_ARROW_UP, STR_NETWORK_START_SERVER_NUMBER_OF_SPECTATORS_TOOLTIP),
 
				EndContainer(),
 

	
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 6),
 
				NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, NSSW_LANGUAGE_LABEL), SetFill(true, false), SetDataTip(STR_NETWORK_START_SERVER_LANGUAGE_SPOKEN, STR_NULL),
 
				NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, NSSW_LANGUAGE_LABEL), SetFill(1, 0), SetDataTip(STR_NETWORK_START_SERVER_LANGUAGE_SPOKEN, STR_NULL),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 1),
 
				NWidget(WWT_DROPDOWN, COLOUR_LIGHT_BLUE, NSSW_LANGUAGE_BTN), SetFill(true, false),
 
				NWidget(WWT_DROPDOWN, COLOUR_LIGHT_BLUE, NSSW_LANGUAGE_BTN), SetFill(1, 0),
 
												SetDataTip(STR_BLACK_STRING, STR_NETWORK_START_SERVER_LANGUAGE_TOOLTIP),
 
			EndContainer(),
 
		EndContainer(),
 
		/* Buttons Start / Load / Cancel. */
 
		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 5, 10),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NSSW_START), SetFill(true, false), SetDataTip(STR_NETWORK_START_SERVER_START_GAME, STR_NETWORK_START_SERVER_START_GAME_TOOLTIP),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NSSW_LOAD), SetFill(true, false), SetDataTip(STR_NETWORK_START_SERVER_LOAD_GAME, STR_NETWORK_START_SERVER_LOAD_GAME_TOOLTIP),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NSSW_CANCEL), SetFill(true, false), SetDataTip(STR_BUTTON_CANCEL, STR_NULL),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NSSW_START), SetFill(1, 0), SetDataTip(STR_NETWORK_START_SERVER_START_GAME, STR_NETWORK_START_SERVER_START_GAME_TOOLTIP),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NSSW_LOAD), SetFill(1, 0), SetDataTip(STR_NETWORK_START_SERVER_LOAD_GAME, STR_NETWORK_START_SERVER_LOAD_GAME_TOOLTIP),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NSSW_CANCEL), SetFill(1, 0), SetDataTip(STR_BUTTON_CANCEL, STR_NULL),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 10),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _network_start_server_window_desc(
 
	WDP_CENTER, WDP_CENTER, 420, 244,
 
	WC_NETWORK_WINDOW, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
 
	_nested_network_start_server_window_widgets, lengthof(_nested_network_start_server_window_widgets)
 
);
 

	
 
static void ShowNetworkStartServerWindow()
 
{
 
	DeleteWindowById(WC_NETWORK_WINDOW, 0);
 

	
 
	new NetworkStartServerWindow(&_network_start_server_window_desc);
 
}
 

	
 
/** Enum for NetworkLobbyWindow, referring to _network_lobby_window_widgets */
 
enum NetworkLobbyWindowWidgets {
 
	NLWW_CLOSE,      ///< Close 'X' button
 
	NLWW_CAPTION,    ///< Titlebar
 
	NLWW_BACKGROUND, ///< Background panel
 
	NLWW_TEXT,       ///< Heading text
 
	NLWW_HEADER,     ///< Header above list of companies
 
	NLWW_MATRIX,     ///< List of companies
 
	NLWW_SCROLLBAR,  ///< Scroll bar
 
	NLWW_DETAILS,    ///< Company details
 
	NLWW_JOIN,       ///< 'Join company' button
 
	NLWW_NEW,        ///< 'New company' button
 
	NLWW_SPECTATE,   ///< 'Spectate game' button
 
	NLWW_REFRESH,    ///< 'Refresh server' button
 
	NLWW_CANCEL,     ///< 'Cancel' button
 
};
 

	
 
struct NetworkLobbyWindow : public Window {
 
	CompanyID company;       ///< Select company
 
	NetworkGameList *server; ///< Selected server
 
	NetworkCompanyInfo company_info[MAX_COMPANIES];
 

	
 
	NetworkLobbyWindow(const WindowDesc *desc, NetworkGameList *ngl) :
 
			Window(), company(INVALID_COMPANY), server(ngl)
 
	{
 
		this->InitNested(desc, 0);
 
		this->OnResize();
 
	}
 

	
 
	CompanyID NetworkLobbyFindCompanyIndex(byte pos) const
 
	{
 
		/* Scroll through all this->company_info and get the 'pos' item that is not empty */
 
		for (CompanyID i = COMPANY_FIRST; i < MAX_COMPANIES; i++) {
 
			if (!StrEmpty(this->company_info[i].company_name)) {
 
				if (pos-- == 0) return i;
 
			}
 
		}
 

	
 
		return COMPANY_FIRST;
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		switch (widget) {
 
			case NLWW_HEADER:
 
				size->height = WD_MATRIX_TOP + FONT_HEIGHT_NORMAL + WD_MATRIX_BOTTOM;;
 
				break;
 

	
 
			case NLWW_MATRIX:
 
				resize->height = WD_MATRIX_TOP + FONT_HEIGHT_NORMAL + WD_MATRIX_BOTTOM;
 
				size->height = 10 * resize->height;
 
				break;
 

	
 
			case NLWW_DETAILS:
 
				size->height = 30 + 11 * FONT_HEIGHT_NORMAL;
 
				break;
 
		}
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		switch (widget) {
 
			case NLWW_TEXT:
 
				SetDParamStr(0, this->server->info.server_name);
 
				break;
 
		}
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case NLWW_DETAILS:
 
				this->DrawDetails(r);
 
				break;
 

	
 
			case NLWW_MATRIX:
 
				this->DrawMatrix(r);
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		const NetworkGameInfo *gi = &this->server->info;
 

	
 
		/* Join button is disabled when no company is selected and for AI companies*/
 
		this->SetWidgetDisabledState(NLWW_JOIN, this->company == INVALID_COMPANY || GetLobbyCompanyInfo(this->company)->ai);
 
		/* Cannot start new company if there are too many */
 
		this->SetWidgetDisabledState(NLWW_NEW, gi->companies_on >= gi->companies_max);
 
		/* Cannot spectate if there are too many spectators */
 
		this->SetWidgetDisabledState(NLWW_SPECTATE, gi->spectators_on >= gi->spectators_max);
 

	
 
		this->vscroll.SetCount(gi->companies_on);
 

	
 
		/* Draw window widgets */
 
		this->DrawWidgets();
 
	}
 

	
 
	void DrawMatrix(const Rect &r) const
 
	{
 
		bool rtl = _dynlang.text_dir == TD_RTL;
 
		uint left = r.left + WD_FRAMERECT_LEFT;
 
		uint right = r.right - WD_FRAMERECT_RIGHT;
 

	
 
		uint text_left  = left + (rtl ? 20 : 0);
 
		uint text_right = right - (rtl ? 0 : 20);
 
		uint blob_left  = rtl ? left : right - 10;
 
		uint lock_left  = rtl ? left + 10 : right - 20;
 

	
 
		int y = r.top + WD_MATRIX_TOP;
 
		/* Draw company list */
 
		int pos = this->vscroll.GetPosition();
 
		while (pos < this->server->info.companies_on) {
 
			byte company = NetworkLobbyFindCompanyIndex(pos);
 
			bool income = false;
 
			if (this->company == company) {
 
				GfxFillRect(r.left + 1, y - 2, r.right - 1, y + FONT_HEIGHT_NORMAL, 10); // show highlighted item with a different colour
 
			}
 

	
 
			DrawString(text_left, text_right, y, this->company_info[company].company_name, TC_BLACK);
 
			if (this->company_info[company].use_password != 0) DrawSprite(SPR_LOCK, PAL_NONE, lock_left, y);
 

	
 
			/* If the company's income was positive puts a green dot else a red dot */
 
			if (this->company_info[company].income >= 0) income = true;
 
			DrawSprite(SPR_BLOT, income ? PALETTE_TO_GREEN : PALETTE_TO_RED, blob_left, y + (FONT_HEIGHT_NORMAL - 10) / 2);
 

	
 
			pos++;
 
			y += this->resize.step_height;
 
			if (pos >= this->vscroll.GetPosition() + this->vscroll.GetCapacity()) break;
 
		}
 
	}
 

	
 
	void DrawDetails(const Rect &r) const
 
	{
 
		const int detail_height = 12 + FONT_HEIGHT_NORMAL + 12;
 
		/* Draw info about selected company when it is selected in the left window */
 
		GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.top + detail_height - 1, 157);
 
		DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + 12, STR_NETWORK_GAME_LOBBY_COMPANY_INFO, TC_FROMSTRING, SA_CENTER);
 

	
 
		if (this->company == INVALID_COMPANY || StrEmpty(this->company_info[this->company].company_name)) return;
 

	
 
		int y = r.top + detail_height + 4;
 
		const NetworkGameInfo *gi = &this->server->info;
 

	
 
		SetDParam(0, gi->clients_on);
 
		SetDParam(1, gi->clients_max);
 
		SetDParam(2, gi->companies_on);
 
		SetDParam(3, gi->companies_max);
 
		DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_CLIENTS);
 
		y += FONT_HEIGHT_NORMAL;
 

	
 
		SetDParamStr(0, this->company_info[this->company].company_name);
 
		DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_COMPANY_NAME);
 
		y += FONT_HEIGHT_NORMAL;
 

	
 
		SetDParam(0, this->company_info[this->company].inaugurated_year);
 
		DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_INAUGURATION_YEAR); // inauguration year
 
		y += FONT_HEIGHT_NORMAL;
 

	
 
		SetDParam(0, this->company_info[this->company].company_value);
 
		DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_VALUE); // company value
 
		y += FONT_HEIGHT_NORMAL;
 

	
 
		SetDParam(0, this->company_info[this->company].money);
 
		DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_CURRENT_BALANCE); // current balance
 
		y += FONT_HEIGHT_NORMAL;
 

	
 
		SetDParam(0, this->company_info[this->company].income);
 
		DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_LAST_YEARS_INCOME); // last year's income
 
		y += FONT_HEIGHT_NORMAL;
 

	
 
		SetDParam(0, this->company_info[this->company].performance);
 
		DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_PERFORMANCE); // performance
 
		y += FONT_HEIGHT_NORMAL;
 

	
 
		for (uint i = 0; i < lengthof(this->company_info[this->company].num_vehicle); i++) {
 
			SetDParam(i, this->company_info[this->company].num_vehicle[i]);
 
		}
 
		DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_VEHICLES); // vehicles
 
		y += FONT_HEIGHT_NORMAL;
 

	
 
		for (uint i = 0; i < lengthof(this->company_info[this->company].num_station); i++) {
 
			SetDParam(i, this->company_info[this->company].num_station[i]);
 
		}
 
		DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_STATIONS); // stations
 
		y += FONT_HEIGHT_NORMAL;
 

	
 
		SetDParamStr(0, this->company_info[this->company].clients);
 
		DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_PLAYERS); // players
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case NLWW_CLOSE:    // Close 'X'
 
			case NLWW_CANCEL:   // Cancel button
 
				ShowNetworkGameWindow();
 
				break;
 

	
 
			case NLWW_MATRIX: { // Company list
 
				uint32 id_v = (pt.y - this->GetWidget<NWidgetBase>(NLWW_MATRIX)->pos_y) / this->resize.step_height;
 

	
 
				if (id_v >= this->vscroll.GetCapacity()) break;
 

	
 
				id_v += this->vscroll.GetPosition();
 
				this->company = (id_v >= this->server->info.companies_on) ? INVALID_COMPANY : NetworkLobbyFindCompanyIndex(id_v);
 
				this->SetDirty();
 
			} break;
 

	
 
			case NLWW_JOIN:     // Join company
 
				/* Button can be clicked only when it is enabled */
 
				NetworkClientConnectGame(NetworkAddress(_settings_client.network.last_host, _settings_client.network.last_port), this->company);
 
				break;
 

	
 
			case NLWW_NEW:      // New company
 
				NetworkClientConnectGame(NetworkAddress(_settings_client.network.last_host, _settings_client.network.last_port), COMPANY_NEW_COMPANY);
 
				break;
 

	
 
			case NLWW_SPECTATE: // Spectate game
 
				NetworkClientConnectGame(NetworkAddress(_settings_client.network.last_host, _settings_client.network.last_port), COMPANY_SPECTATOR);
 
				break;
 

	
 
			case NLWW_REFRESH:  // Refresh
 
				NetworkTCPQueryServer(NetworkAddress(_settings_client.network.last_host, _settings_client.network.last_port)); // company info
 
				NetworkUDPQueryServer(NetworkAddress(_settings_client.network.last_host, _settings_client.network.last_port)); // general data
 
				/* Clear the information so removed companies don't remain */
 
				memset(this->company_info, 0, sizeof(this->company_info));
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnDoubleClick(Point pt, int widget)
 
	{
 
		if (widget == NLWW_MATRIX) {
 
			/* is the Join button enabled? */
 
			if (!this->IsWidgetDisabled(NLWW_JOIN)) this->OnClick(pt, NLWW_JOIN);
 
		}
 
	}
 

	
 
	virtual void OnResize()
 
	{
 
		NWidgetCore *nwi = this->GetWidget<NWidgetCore>(NLWW_MATRIX);
 
		this->vscroll.SetCapacity(nwi->current_y / this->resize.step_height);
 
		nwi->widget_data = (this->vscroll.GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START);
 
	}
 
};
 

	
 
static const NWidgetPart _nested_network_lobby_window_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_LIGHT_BLUE, NLWW_CLOSE),
 
		NWidget(WWT_CAPTION, COLOUR_LIGHT_BLUE, NLWW_CAPTION), SetDataTip(STR_NETWORK_GAME_LOBBY_CAPTION, STR_NULL),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, NLWW_BACKGROUND),
 
		NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, NLWW_TEXT), SetDataTip(STR_NETWORK_GAME_LOBBY_PREPARE_TO_JOIN, STR_NULL), SetResize(1, 0), SetPadding(10, 10, 0, 10),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 3),
 
		NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 10),
 
			/* Company list. */
 
			NWidget(NWID_VERTICAL),
 
				NWidget(WWT_PANEL, COLOUR_WHITE, NLWW_HEADER), SetMinimalSize(146, 0), SetResize(1, 0), SetFill(true, false), EndContainer(),
 
				NWidget(WWT_MATRIX, COLOUR_LIGHT_BLUE, NLWW_MATRIX), SetMinimalSize(146, 0), SetResize(1, 1), SetFill(true, true), SetDataTip(0, STR_NETWORK_GAME_LOBBY_COMPANY_LIST_TOOLTIP),
 
				NWidget(WWT_PANEL, COLOUR_WHITE, NLWW_HEADER), SetMinimalSize(146, 0), SetResize(1, 0), SetFill(1, 0), EndContainer(),
 
				NWidget(WWT_MATRIX, COLOUR_LIGHT_BLUE, NLWW_MATRIX), SetMinimalSize(146, 0), SetResize(1, 1), SetFill(1, 1), SetDataTip(0, STR_NETWORK_GAME_LOBBY_COMPANY_LIST_TOOLTIP),
 
			EndContainer(),
 
			NWidget(WWT_SCROLLBAR, COLOUR_LIGHT_BLUE, NLWW_SCROLLBAR),
 
			NWidget(NWID_SPACER), SetMinimalSize(5, 0), SetResize(0, 1),
 
			/* Company info. */
 
			NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, NLWW_DETAILS), SetMinimalSize(232, 0), SetResize(1, 1), SetFill(true, true), EndContainer(),
 
			NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, NLWW_DETAILS), SetMinimalSize(232, 0), SetResize(1, 1), SetFill(1, 1), EndContainer(),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 9),
 
		/* Buttons. */
 
		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 3, 10),
 
			NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(0, 3, 0),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NLWW_JOIN), SetResize(1, 0), SetFill(true, false), SetDataTip(STR_NETWORK_GAME_LOBBY_JOIN_COMPANY, STR_NETWORK_GAME_LOBBY_JOIN_COMPANY_TOOLTIP),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NLWW_NEW), SetResize(1, 0), SetFill(true, false), SetDataTip(STR_NETWORK_GAME_LOBBY_NEW_COMPANY, STR_NETWORK_GAME_LOBBY_NEW_COMPANY_TOOLTIP),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NLWW_JOIN), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_NETWORK_GAME_LOBBY_JOIN_COMPANY, STR_NETWORK_GAME_LOBBY_JOIN_COMPANY_TOOLTIP),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NLWW_NEW), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_NETWORK_GAME_LOBBY_NEW_COMPANY, STR_NETWORK_GAME_LOBBY_NEW_COMPANY_TOOLTIP),
 
			EndContainer(),
 
			NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(0, 3, 0),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NLWW_SPECTATE), SetResize(1, 0), SetFill(true, false), SetDataTip(STR_NETWORK_GAME_LOBBY_SPECTATE_GAME, STR_NETWORK_GAME_LOBBY_SPECTATE_GAME_TOOLTIP),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NLWW_REFRESH), SetResize(1, 0), SetFill(true, false), SetDataTip(STR_NETWORK_SERVER_LIST_REFRESH, STR_NETWORK_SERVER_LIST_REFRESH_TOOLTIP),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NLWW_SPECTATE), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_NETWORK_GAME_LOBBY_SPECTATE_GAME, STR_NETWORK_GAME_LOBBY_SPECTATE_GAME_TOOLTIP),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NLWW_REFRESH), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_NETWORK_SERVER_LIST_REFRESH, STR_NETWORK_SERVER_LIST_REFRESH_TOOLTIP),
 
			EndContainer(),
 
			NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(0, 3, 0),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NLWW_CANCEL), SetResize(1, 0), SetFill(true, false), SetDataTip(STR_BUTTON_CANCEL, STR_NULL),
 
				NWidget(NWID_SPACER), SetFill(true, true),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NLWW_CANCEL), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_BUTTON_CANCEL, STR_NULL),
 
				NWidget(NWID_SPACER), SetFill(1, 1),
 
			EndContainer(),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 8),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _network_lobby_window_desc(
 
	WDP_CENTER, WDP_CENTER, 0, 0,
 
	WC_NETWORK_WINDOW, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_RESIZABLE,
 
	_nested_network_lobby_window_widgets, lengthof(_nested_network_lobby_window_widgets)
 
);
 

	
 
/* Show the networklobbywindow with the selected server
 
 * @param ngl Selected game pointer which is passed to the new window */
 
static void ShowNetworkLobbyWindow(NetworkGameList *ngl)
 
{
 
	DeleteWindowById(WC_NETWORK_WINDOW, 0);
 

	
 
	NetworkTCPQueryServer(NetworkAddress(_settings_client.network.last_host, _settings_client.network.last_port)); // company info
 
	NetworkUDPQueryServer(NetworkAddress(_settings_client.network.last_host, _settings_client.network.last_port)); // general data
 

	
 
	new NetworkLobbyWindow(&_network_lobby_window_desc, ngl);
 
}
 

	
 
/**
 
 * Get the company information of a given company to fill for the lobby.
 
 * @param company the company to get the company info struct from.
 
 * @return the company info struct to write the (downloaded) data to.
 
 */
 
NetworkCompanyInfo *GetLobbyCompanyInfo(CompanyID company)
 
{
 
	NetworkLobbyWindow *lobby = dynamic_cast<NetworkLobbyWindow*>(FindWindowById(WC_NETWORK_WINDOW, 0));
 
	return (lobby != NULL && company < MAX_COMPANIES) ? &lobby->company_info[company] : NULL;
 
}
 

	
 
/* The window below gives information about the connected clients
 
 *  and also makes able to give money to them, kick them (if server)
 
 *  and stuff like that. */
 

	
 
extern void DrawCompanyIcon(CompanyID cid, int x, int y);
 

	
 
/* Every action must be of this form */
 
typedef void ClientList_Action_Proc(byte client_no);
 

	
 
static const NWidgetPart _nested_client_list_popup_widgets[] = {
 
	NWidget(WWT_PANEL, COLOUR_GREY, 0), EndContainer(),
 
};
 

	
 
static const WindowDesc _client_list_popup_desc(
 
	WDP_AUTO, WDP_AUTO, 150, 1,
 
	WC_TOOLBAR_MENU, WC_CLIENT_LIST,
 
	WDF_STD_TOOLTIPS | WDF_DEF_WIDGET,
 
	_nested_client_list_popup_widgets, lengthof(_nested_client_list_popup_widgets)
 
);
 

	
 
/* Finds the Xth client-info that is active */
 
static NetworkClientInfo *NetworkFindClientInfo(byte client_no)
 
{
 
	NetworkClientInfo *ci;
 

	
 
	FOR_ALL_CLIENT_INFOS(ci) {
 
		if (client_no == 0) return ci;
 
		client_no--;
 
	}
 

	
 
	return NULL;
 
}
 

	
 
/* Here we start to define the options out of the menu */
 
static void ClientList_Kick(byte client_no)
 
{
 
	const NetworkClientInfo *ci = NetworkFindClientInfo(client_no);
 

	
 
	if (ci == NULL) return;
 

	
 
	NetworkServerKickClient(ci->client_id);
 
}
 

	
 
static void ClientList_Ban(byte client_no)
 
{
 
	NetworkClientInfo *ci = NetworkFindClientInfo(client_no);
 

	
 
	if (ci == NULL) return;
 

	
 
	NetworkServerBanIP(GetClientIP(ci));
 
}
 

	
 
static void ClientList_GiveMoney(byte client_no)
 
{
 
	if (NetworkFindClientInfo(client_no) != NULL) {
 
		ShowNetworkGiveMoneyWindow(NetworkFindClientInfo(client_no)->client_playas);
 
	}
 
}
 

	
 
static void ClientList_SpeakToClient(byte client_no)
 
{
 
	if (NetworkFindClientInfo(client_no) != NULL) {
 
		ShowNetworkChatQueryWindow(DESTTYPE_CLIENT, NetworkFindClientInfo(client_no)->client_id);
 
	}
 
}
 

	
 
static void ClientList_SpeakToCompany(byte client_no)
 
{
 
	if (NetworkFindClientInfo(client_no) != NULL) {
 
		ShowNetworkChatQueryWindow(DESTTYPE_TEAM, NetworkFindClientInfo(client_no)->client_playas);
 
	}
 
}
 

	
 
static void ClientList_SpeakToAll(byte client_no)
 
{
 
	ShowNetworkChatQueryWindow(DESTTYPE_BROADCAST, 0);
 
}
 

	
 
/** Popup selection window to chose an action to perform */
 
struct NetworkClientListPopupWindow : Window {
 
	/** Container for actions that can be executed. */
 
	struct ClientListAction {
 
		StringID name;                ///< Name of the action to execute
 
		ClientList_Action_Proc *proc; ///< Action to execute
 
	};
 

	
 
	uint sel_index;
 
	int client_no;
 
	Point desired_location;
 
	SmallVector<ClientListAction, 2> actions; ///< Actions to execute
 

	
 
	/**
 
	 * Add an action to the list of actions to execute.
 
	 * @param name the name of the action
 
	 * @param proc the procedure to execute for the action
 
	 */
 
	inline void AddAction(StringID name, ClientList_Action_Proc *proc)
 
	{
 
		ClientListAction *action = this->actions.Append();
 
		action->name = name;
 
		action->proc = proc;
 
	}
 

	
 
	NetworkClientListPopupWindow(const WindowDesc *desc, int x, int y, int client_no) :
 
			Window(),
 
			sel_index(0), client_no(client_no)
 
	{
 
		this->desired_location.x = x;
 
		this->desired_location.y = y;
 

	
 
		const NetworkClientInfo *ci = NetworkFindClientInfo(client_no);
 

	
 
		if (_network_own_client_id != ci->client_id) {
 
			this->AddAction(STR_NETWORK_CLIENTLIST_SPEAK_TO_CLIENT, &ClientList_SpeakToClient);
 
		}
 

	
 
		if (Company::IsValidID(ci->client_playas) || ci->client_playas == COMPANY_SPECTATOR) {
 
			this->AddAction(STR_NETWORK_CLIENTLIST_SPEAK_TO_COMPANY, &ClientList_SpeakToCompany);
 
		}
 
		this->AddAction(STR_NETWORK_CLIENTLIST_SPEAK_TO_ALL, &ClientList_SpeakToAll);
 

	
 
		if (_network_own_client_id != ci->client_id) {
 
			/* We are no spectator and the company we want to give money to is no spectator and money gifts are allowed */
 
			if (Company::IsValidID(_local_company) && Company::IsValidID(ci->client_playas) && _settings_game.economy.give_money) {
 
				this->AddAction(STR_NETWORK_CLIENTLIST_GIVE_MONEY, &ClientList_GiveMoney);
 
			}
 
		}
 

	
 
		/* A server can kick clients (but not himself) */
 
		if (_network_server && _network_own_client_id != ci->client_id) {
 
			this->AddAction(STR_NETWORK_CLIENTLIST_KICK, &ClientList_Kick);
 
			this->AddAction(STR_NETWORK_CLIENTLIST_BAN, &ClientList_Ban);
 
		}
 

	
 
		this->flags4 &= ~WF_WHITE_BORDER_MASK;
 
		this->InitNested(desc, 0);
 
	}
 

	
 
	virtual Point OnInitialPosition(const WindowDesc *desc, int16 sm_width, int16 sm_height, int window_number)
 
	{
 
		return this->desired_location;
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		Dimension d = *size;
 
		for (const ClientListAction *action = this->actions.Begin(); action != this->actions.End(); action++) {
 
			d = maxdim(GetStringBoundingBox(action->name), d);
 
		}
 

	
 
		d.height *= this->actions.Length();
 
		d.width += WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
 
		d.height += WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
 
		*size = d;
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		/* Draw the actions */
 
		int sel = this->sel_index;
 
		int y = r.top + WD_FRAMERECT_TOP;
 
		for (const ClientListAction *action = this->actions.Begin(); action != this->actions.End(); action++, y += FONT_HEIGHT_NORMAL) {
 
			TextColour colour;
 
			if (sel-- == 0) { // Selected item, highlight it
 
				GfxFillRect(r.left + 1, y, r.right - 1, y + FONT_HEIGHT_NORMAL - 1, 0);
 
				colour = TC_WHITE;
 
			} else {
 
				colour = TC_BLACK;
 
			}
 

	
 
			DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, action->name, colour);
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void OnMouseLoop()
 
	{
 
		/* We selected an action */
 
		uint index = (_cursor.pos.y - this->top - WD_FRAMERECT_TOP) / FONT_HEIGHT_NORMAL;
 

	
 
		if (_left_button_down) {
 
			if (index == this->sel_index || index >= this->actions.Length()) return;
 

	
 
			this->sel_index = index;
 
			this->SetDirty();
 
		} else {
 
			if (index < this->actions.Length() && _cursor.pos.y >= this->top) {
 
				this->actions[index].proc(this->client_no);
 
			}
 

	
 
			DeleteWindowById(WC_TOOLBAR_MENU, 0);
 
		}
 
	}
 
};
 

	
 
/**
 
 * Show the popup (action list)
 
 */
 
static void PopupClientList(int client_no, int x, int y)
 
{
 
	DeleteWindowById(WC_TOOLBAR_MENU, 0);
 

	
 
	if (NetworkFindClientInfo(client_no) == NULL) return;
 

	
 
	new NetworkClientListPopupWindow(&_client_list_popup_desc, x, y, client_no);
 
}
 

	
 

	
 
/** Widget numbers of the client list window. */
 
enum ClientListWidgets {
 
	CLW_CLOSE,
 
	CLW_CAPTION,
 
	CLW_STICKY,
 
	CLW_PANEL,
 
};
 

	
 
static const NWidgetPart _nested_client_list_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, CLW_CLOSE),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, CLW_CAPTION), SetDataTip(STR_NETWORK_COMPANY_LIST_CLIENT_LIST, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_STICKYBOX, COLOUR_GREY, CLW_STICKY),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, CLW_PANEL), SetMinimalSize(250, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM), SetResize(1, 1), EndContainer(),
 
};
 

	
 
static const WindowDesc _client_list_desc(
 
	WDP_AUTO, WDP_AUTO, 250, 16,
 
	WC_CLIENT_LIST, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_STICKY_BUTTON | WDF_RESIZABLE,
 
	_nested_client_list_widgets, lengthof(_nested_client_list_widgets)
 
);
 

	
 
/**
 
 * Main handle for clientlist
 
 */
 
struct NetworkClientListWindow : Window {
 
	int selected_item;
 

	
 
	uint server_client_width;
 
	uint company_icon_width;
 

	
 
	NetworkClientListWindow(const WindowDesc *desc, WindowNumber window_number) :
 
			Window(),
 
			selected_item(-1)
 
	{
 
		this->InitNested(desc, window_number);
 
	}
 

	
 
	/**
 
	 * Finds the amount of clients and set the height correct
 
	 */
 
	bool CheckClientListHeight()
 
	{
 
		int num = 0;
 
		const NetworkClientInfo *ci;
 

	
 
		/* Should be replaced with a loop through all clients */
 
		FOR_ALL_CLIENT_INFOS(ci) {
 
			if (ci->client_playas != COMPANY_INACTIVE_CLIENT) num++;
 
		}
 

	
 
		num *= FONT_HEIGHT_NORMAL;
 

	
 
		int diff = (num + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM) - (this->GetWidget<NWidgetBase>(CLW_PANEL)->current_y);
 
		/* If height is changed */
 
		if (diff != 0) {
 
			ResizeWindow(this, 0, diff);
 
			return false;
 
		}
 
		return true;
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		if (widget != CLW_PANEL) return;
 

	
 
		this->server_client_width = max(GetStringBoundingBox(STR_NETWORK_SERVER).width, GetStringBoundingBox(STR_NETWORK_CLIENT).width) + WD_FRAMERECT_RIGHT;
 
		this->company_icon_width = GetSpriteSize(SPR_COMPANY_ICON).width + WD_FRAMERECT_LEFT;
 

	
 
		uint width = 200; // Default width
 
		const NetworkClientInfo *ci;
 
		FOR_ALL_CLIENT_INFOS(ci) {
 
			width = max(width, GetStringBoundingBox(ci->client_name).width);
 
		}
 

	
 
		size->width = WD_FRAMERECT_LEFT + this->server_client_width + this->company_icon_width + WD_FRAMERECT_RIGHT;
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		/* Check if we need to reset the height */
 
		if (!this->CheckClientListHeight()) return;
 

	
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		if (widget != CLW_PANEL) return;
 

	
 
		bool rtl = _dynlang.text_dir == TD_RTL;
 
		uint y = r.top + WD_FRAMERECT_TOP;
 
		uint left = r.left + WD_FRAMERECT_LEFT;
 
		uint right = r.right - WD_FRAMERECT_RIGHT;
 
		uint type_icon_width = this->server_client_width + this->company_icon_width;
 

	
 

	
 
		uint type_left  = rtl ? right - this->server_client_width : left;
 
		uint type_right = rtl ? right : left + this->server_client_width - 1;
 
		uint icon_left  = rtl ? right - type_icon_width + WD_FRAMERECT_LEFT : left + this->server_client_width;
 
		uint name_left  = rtl ? left : left + type_icon_width;
 
		uint name_right = rtl ? right - type_icon_width : right;
 

	
 
		int i = 0;
 
		const NetworkClientInfo *ci;
 
		FOR_ALL_CLIENT_INFOS(ci) {
 
			TextColour colour;
 
			if (this->selected_item == i++) { // Selected item, highlight it
 
				GfxFillRect(r.left + 1, y, r.right - 1, y + FONT_HEIGHT_NORMAL - 1, 0);
 
				colour = TC_WHITE;
 
			} else {
 
				colour = TC_BLACK;
 
			}
 

	
 
			if (ci->client_id == CLIENT_ID_SERVER) {
 
				DrawString(type_left, type_right, y, STR_NETWORK_SERVER, colour);
 
			} else {
 
				DrawString(type_left, type_right, y, STR_NETWORK_CLIENT, colour);
 
			}
 

	
 
			/* Filter out spectators */
 
			if (Company::IsValidID(ci->client_playas)) DrawCompanyIcon(ci->client_playas, icon_left, y + 1);
 

	
 
			DrawString(name_left, name_right, y, ci->client_name, colour);
 

	
 
			y += FONT_HEIGHT_NORMAL;
 
		}
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		/* Show the popup with option */
 
		if (this->selected_item != -1) {
 
			PopupClientList(this->selected_item, pt.x + this->left, pt.y + this->top);
 
		}
 
	}
 

	
 
	virtual void OnMouseOver(Point pt, int widget)
 
	{
 
		/* -1 means we left the current window */
 
		if (pt.y == -1) {
 
			this->selected_item = -1;
 
			this->SetDirty();
 
			return;
 
		}
 

	
 
		/* Find the new selected item (if any) */
 
		pt.y -= this->GetWidget<NWidgetBase>(CLW_PANEL)->pos_y;
 
		int item = -1;
 
		if (IsInsideMM(pt.y, WD_FRAMERECT_TOP, this->GetWidget<NWidgetBase>(CLW_PANEL)->current_y - WD_FRAMERECT_BOTTOM)) {
 
			item = (pt.y - WD_FRAMERECT_TOP) / FONT_HEIGHT_NORMAL;
 
		}
 

	
 
		/* It did not change.. no update! */
 
		if (item == this->selected_item) return;
 
		this->selected_item = item;
 

	
 
		/* Repaint */
 
		this->SetDirty();
 
	}
 
};
 

	
 
void ShowClientList()
 
{
 
	AllocateWindowDescFront<NetworkClientListWindow>(&_client_list_desc, 0);
 
}
 

	
 

	
 
static NetworkPasswordType pw_type;
 

	
 

	
 
void ShowNetworkNeedPassword(NetworkPasswordType npt)
 
{
 
	StringID caption;
 

	
 
	pw_type = npt;
 
	switch (npt) {
 
		default: NOT_REACHED();
 
		case NETWORK_GAME_PASSWORD:    caption = STR_NETWORK_NEED_GAME_PASSWORD_CAPTION; break;
 
		case NETWORK_COMPANY_PASSWORD: caption = STR_NETWORK_NEED_COMPANY_PASSWORD_CAPTION; break;
 
	}
 
	ShowQueryString(STR_EMPTY, caption, 20, 180, FindWindowById(WC_NETWORK_STATUS_WINDOW, 0), CS_ALPHANUMERAL, QSF_NONE);
 
}
 

	
 
/* Vars needed for the join-GUI */
 
NetworkJoinStatus _network_join_status;
 
uint8 _network_join_waiting;
 
uint32 _network_join_bytes;
 
uint32 _network_join_bytes_total;
 

	
 
/** Widgets used for the join status window. */
 
enum NetworkJoinStatusWidgets {
 
	NJSW_CAPTION,    ///< Caption of the window
 
	NJSW_BACKGROUND, ///< Background
 
	NJSW_CANCELOK,   ///< Cancel/OK button
 
};
 

	
 
struct NetworkJoinStatusWindow : Window {
 
	NetworkJoinStatusWindow(const WindowDesc *desc) : Window()
 
	{
 
		this->parent = FindWindowById(WC_NETWORK_WINDOW, 0);
 
		this->InitNested(desc, 0);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		if (widget != NJSW_BACKGROUND) return;
 

	
 
		uint8 progress; // used for progress bar
 
		DrawString(r.left + 2, r.right - 2, r.top + 20, STR_NETWORK_CONNECTING_1 + _network_join_status, TC_FROMSTRING, SA_CENTER);
 
		switch (_network_join_status) {
 
			case NETWORK_JOIN_STATUS_CONNECTING: case NETWORK_JOIN_STATUS_AUTHORIZING:
 
			case NETWORK_JOIN_STATUS_GETTING_COMPANY_INFO:
 
				progress = 10; // first two stages 10%
 
				break;
 
			case NETWORK_JOIN_STATUS_WAITING:
 
				SetDParam(0, _network_join_waiting);
 
				DrawString(r.left + 2, r.right - 2, r.top + 20 + FONT_HEIGHT_NORMAL, STR_NETWORK_CONNECTING_WAITING, TC_FROMSTRING, SA_CENTER);
 
				progress = 15; // third stage is 15%
 
				break;
 
			case NETWORK_JOIN_STATUS_DOWNLOADING:
 
				SetDParam(0, _network_join_bytes);
 
				SetDParam(1, _network_join_bytes_total);
 
				DrawString(r.left + 2, r.right - 2, r.top + 20 + FONT_HEIGHT_NORMAL, STR_NETWORK_CONNECTING_DOWNLOADING, TC_FROMSTRING, SA_CENTER);
 
				/* Fallthrough */
 
			default: // Waiting is 15%, so the resting receivement of map is maximum 70%
 
				progress = 15 + _network_join_bytes * (100 - 15) / _network_join_bytes_total;
 
		}
 

	
 
		/* Draw nice progress bar :) */
 
		DrawFrameRect(r.left + 20, r.top + 5, (int)((this->width - 20) * progress / 100), r.top + 15, COLOUR_MAUVE, FR_NONE);
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		if (widget == NJSW_CANCELOK) { // Disconnect button
 
			NetworkDisconnect();
 
			SwitchToMode(SM_MENU);
 
			ShowNetworkGameWindow();
 
		}
 
	}
 

	
 
	virtual void OnQueryTextFinished(char *str)
 
	{
 
		if (StrEmpty(str)) {
 
			NetworkDisconnect();
 
			ShowNetworkGameWindow();
 
		} else {
 
			SEND_COMMAND(PACKET_CLIENT_PASSWORD)(pw_type, str);
 
		}
 
	}
 
};
 

	
 
static const NWidgetPart _nested_network_join_status_window_widgets[] = {
 
	NWidget(WWT_CAPTION, COLOUR_GREY, NJSW_CAPTION), SetDataTip(STR_NETWORK_CONNECTING_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	NWidget(WWT_PANEL, COLOUR_GREY, NJSW_BACKGROUND),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, NJSW_CANCELOK), SetMinimalSize(101, 12), SetPadding(55, 74, 4, 75), SetDataTip(STR_NETWORK_CONNECTION_DISCONNECT, STR_NULL),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _network_join_status_window_desc(
 
	WDP_CENTER, WDP_CENTER, 250, 85,
 
	WC_NETWORK_STATUS_WINDOW, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_MODAL,
 
	_nested_network_join_status_window_widgets, lengthof(_nested_network_join_status_window_widgets)
 
);
 

	
 
void ShowJoinStatusWindow()
 
{
 
	DeleteWindowById(WC_NETWORK_STATUS_WINDOW, 0);
 
	new NetworkJoinStatusWindow(&_network_join_status_window_desc);
 
}
 

	
 

	
 
/** Enum for NetworkGameWindow, referring to _network_game_window_widgets */
 
enum NetworkCompanyPasswordWindowWidgets {
 
	NCPWW_CLOSE,                    ///< Close 'X' button
 
	NCPWW_CAPTION,                  ///< Caption of the whole window
 
	NCPWW_BACKGROUND,               ///< The background of the interface
 
	NCPWW_LABEL,                    ///< Label in front of the password field
 
	NCPWW_PASSWORD,                 ///< Input field for the password
 
	NCPWW_SAVE_AS_DEFAULT_PASSWORD, ///< Toggle 'button' for saving the current password as default password
 
	NCPWW_CANCEL,                   ///< Close the window without changing anything
 
	NCPWW_OK,                       ///< Safe the password etc.
 
};
 

	
 
struct NetworkCompanyPasswordWindow : public QueryStringBaseWindow {
 
	NetworkCompanyPasswordWindow(const WindowDesc *desc, Window *parent) : QueryStringBaseWindow(lengthof(_settings_client.network.default_company_pass))
 
	{
 
		this->InitNested(desc, 0);
 

	
 
		this->parent = parent;
 
		this->afilter = CS_ALPHANUMERAL;
 
		InitializeTextBuffer(&this->text, this->edit_str_buf, this->edit_str_size, 0);
 
		this->SetFocusedWidget(NCPWW_PASSWORD);
 
	}
 

	
 
	void OnOk()
 
	{
 
		if (this->IsWidgetLowered(NCPWW_SAVE_AS_DEFAULT_PASSWORD)) {
 
			snprintf(_settings_client.network.default_company_pass, lengthof(_settings_client.network.default_company_pass), "%s", this->edit_str_buf);
 
		}
 

	
 
		/* empty password is a '*' because of console argument */
 
		if (StrEmpty(this->edit_str_buf)) snprintf(this->edit_str_buf, this->edit_str_size, "*");
 
		char *password = this->edit_str_buf;
 
		NetworkChangeCompanyPassword(1, &password);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
		this->DrawEditBox(NCPWW_PASSWORD);
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case NCPWW_OK:
 
				this->OnOk();
 

	
 
			/* FALL THROUGH */
 
			case NCPWW_CANCEL:
 
				delete this;
 
				break;
 

	
 
			case NCPWW_SAVE_AS_DEFAULT_PASSWORD:
 
				this->ToggleWidgetLoweredState(NCPWW_SAVE_AS_DEFAULT_PASSWORD);
 
				this->SetDirty();
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnMouseLoop()
 
	{
 
		this->HandleEditBox(4);
 
	}
 

	
 
	virtual EventState OnKeyPress(uint16 key, uint16 keycode)
 
	{
 
		EventState state = ES_NOT_HANDLED;
 
		switch (this->HandleEditBoxKey(4, key, keycode, state)) {
 
			default: break;
 

	
 
			case HEBR_CONFIRM:
 
				this->OnOk();
 
				/* FALL THROUGH */
 

	
 
			case HEBR_CANCEL:
 
				delete this;
 
				break;
 
		}
 
		return state;
 
	}
 

	
 
	virtual void OnOpenOSKWindow(int wid)
 
	{
 
		ShowOnScreenKeyboard(this, wid, NCPWW_CANCEL, NCPWW_OK);
 
	}
 
};
 

	
 
static const NWidgetPart _nested_network_company_password_window_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, NCPWW_CLOSE),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, NCPWW_CAPTION), SetDataTip(STR_COMPANY_PASSWORD_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, NCPWW_BACKGROUND),
 
		NWidget(NWID_VERTICAL), SetPIP(5, 5, 5),
 
			NWidget(NWID_HORIZONTAL), SetPIP(5, 5, 5),
 
				NWidget(WWT_TEXT, COLOUR_GREY, NCPWW_LABEL), SetDataTip(STR_COMPANY_VIEW_PASSWORD, STR_NULL),
 
				NWidget(WWT_EDITBOX, COLOUR_GREY, NCPWW_PASSWORD), SetMinimalSize(194, 12), SetDataTip(STR_COMPANY_VIEW_SET_PASSWORD, STR_NULL),
 
			EndContainer(),
 
			NWidget(NWID_HORIZONTAL), SetPIP(5, 0, 5),
 
				NWidget(NWID_SPACER), SetFill(true, false),
 
				NWidget(NWID_SPACER), SetFill(1, 0),
 
				NWidget(WWT_TEXTBTN, COLOUR_GREY, NCPWW_SAVE_AS_DEFAULT_PASSWORD), SetMinimalSize(194, 12),
 
											SetDataTip(STR_COMPANY_PASSWORD_MAKE_DEFAULT, STR_COMPANY_PASSWORD_MAKE_DEFAULT_TOOLTIP),
 
			EndContainer(),
 
		EndContainer(),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, NCPWW_CANCEL), SetFill(true, false), SetDataTip(STR_BUTTON_CANCEL, STR_COMPANY_PASSWORD_CANCEL),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, NCPWW_OK), SetFill(true, false), SetDataTip(STR_BUTTON_OK, STR_COMPANY_PASSWORD_OK),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, NCPWW_CANCEL), SetFill(1, 0), SetDataTip(STR_BUTTON_CANCEL, STR_COMPANY_PASSWORD_CANCEL),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, NCPWW_OK), SetFill(1, 0), SetDataTip(STR_BUTTON_OK, STR_COMPANY_PASSWORD_OK),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _network_company_password_window_desc(
 
	WDP_AUTO, WDP_AUTO, 300, 63,
 
	WC_COMPANY_PASSWORD_WINDOW, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON,
 
	_nested_network_company_password_window_widgets, lengthof(_nested_network_company_password_window_widgets)
 
);
 

	
 
void ShowNetworkCompanyPasswordWindow(Window *parent)
 
{
 
	DeleteWindowById(WC_COMPANY_PASSWORD_WINDOW, 0);
 

	
 
	new NetworkCompanyPasswordWindow(&_network_company_password_window_desc, parent);
 
}
 

	
 
#endif /* ENABLE_NETWORK */
src/newgrf_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file newgrf_gui.cpp GUI to change NewGRF settings. */
 

	
 
#include "stdafx.h"
 
#include "gui.h"
 
#include "newgrf.h"
 
#include "strings_func.h"
 
#include "window_func.h"
 
#include "gfx_func.h"
 
#include "gamelog.h"
 
#include "settings_func.h"
 
#include "widgets/dropdown_type.h"
 
#include "network/network.h"
 
#include "network/network_content.h"
 
#include "sortlist_type.h"
 
#include "querystring_gui.h"
 

	
 
#include "table/strings.h"
 
#include "table/sprites.h"
 

	
 
/** Parse an integerlist string and set each found value
 
 * @param p the string to be parsed. Each element in the list is seperated by a
 
 * comma or a space character
 
 * @param items pointer to the integerlist-array that will be filled with values
 
 * @param maxitems the maximum number of elements the integerlist-array has
 
 * @return returns the number of items found, or -1 on an error */
 
static int parse_intlist(const char *p, int *items, int maxitems)
 
{
 
	int n = 0, v;
 
	char *end;
 

	
 
	for (;;) {
 
		while (*p == ' ' || *p == ',') p++;
 
		if (*p == '\0') break;
 
		v = strtol(p, &end, 0);
 
		if (p == end || n == maxitems) return -1;
 
		p = end;
 
		items[n++] = v;
 
	}
 

	
 
	return n;
 
}
 

	
 

	
 
static void ShowNewGRFInfo(const GRFConfig *c, uint x, uint y, uint right, uint bottom, bool show_params)
 
{
 
	char buff[256];
 

	
 
	if (c->error != NULL) {
 
		char message[512];
 
		SetDParamStr(0, c->error->custom_message); // is skipped by built-in messages
 
		SetDParam   (1, STR_JUST_RAW_STRING);
 
		SetDParamStr(2, c->filename);
 
		SetDParam   (3, STR_JUST_RAW_STRING);
 
		SetDParamStr(4, c->error->data);
 
		for (uint i = 0; i < c->error->num_params; i++) {
 
			SetDParam(5 + i, c->error->param_value[i]);
 
		}
 
		GetString(message, c->error->custom_message == NULL ? c->error->message : STR_JUST_RAW_STRING, lastof(message));
 

	
 
		SetDParamStr(0, message);
 
		y = DrawStringMultiLine(x, right, y, bottom, c->error->severity);
 
	}
 

	
 
	/* Draw filename or not if it is not known (GRF sent over internet) */
 
	if (c->filename != NULL) {
 
		SetDParamStr(0, c->filename);
 
		y = DrawStringMultiLine(x, right, y, bottom, STR_NEWGRF_SETTINGS_FILENAME);
 
	}
 

	
 
	/* Prepare and draw GRF ID */
 
	snprintf(buff, lengthof(buff), "%08X", BSWAP32(c->grfid));
 
	SetDParamStr(0, buff);
 
	y = DrawStringMultiLine(x, right, y, bottom, STR_NEWGRF_SETTINGS_GRF_ID);
 

	
 
	/* Prepare and draw MD5 sum */
 
	md5sumToString(buff, lastof(buff), c->md5sum);
 
	SetDParamStr(0, buff);
 
	y = DrawStringMultiLine(x, right, y, bottom, STR_NEWGRF_SETTINGS_MD5SUM);
 

	
 
	/* Show GRF parameter list */
 
	if (show_params) {
 
		if (c->num_params > 0) {
 
			GRFBuildParamList(buff, c, lastof(buff));
 
			SetDParam(0, STR_JUST_RAW_STRING);
 
			SetDParamStr(1, buff);
 
		} else {
 
			SetDParam(0, STR_LAND_AREA_INFORMATION_LOCAL_AUTHORITY_NONE);
 
		}
 
		y = DrawStringMultiLine(x, right, y, bottom, STR_NEWGRF_SETTINGS_PARAMETER);
 

	
 
		/* Draw the palette of the NewGRF */
 
		SetDParamStr(0, c->windows_paletted ? "Windows" : "DOS");
 
		y = DrawStringMultiLine(x, right, y, bottom, STR_NEWGRF_SETTINGS_PALETTE);
 
	}
 

	
 
	/* Show flags */
 
	if (c->status == GCS_NOT_FOUND)       y = DrawStringMultiLine(x, right, y, bottom, STR_NEWGRF_SETTINGS_NOT_FOUND);
 
	if (c->status == GCS_DISABLED)        y = DrawStringMultiLine(x, right, y, bottom, STR_NEWGRF_SETTINGS_DISABLED);
 
	if (HasBit(c->flags, GCF_COMPATIBLE)) y = DrawStringMultiLine(x, right, y, bottom, STR_NEWGRF_COMPATIBLE_LOADED);
 

	
 
	/* Draw GRF info if it exists */
 
	if (c->info != NULL && !StrEmpty(c->info)) {
 
		SetDParam(0, STR_JUST_RAW_STRING);
 
		SetDParamStr(1, c->info);
 
		y = DrawStringMultiLine(x, right, y, bottom, STR_BLACK_STRING);
 
	} else {
 
		y = DrawStringMultiLine(x, right, y, bottom, STR_NEWGRF_SETTINGS_NO_INFO);
 
	}
 
}
 

	
 

	
 
/** Names of the add a newgrf window widgets. */
 
enum AddNewGRFWindowWidgets {
 
	ANGRFW_CLOSEBOX = 0,
 
	ANGRFW_CAPTION,
 
	ANGRFW_FILTER_PANEL,
 
	ANGRFW_FILTER_TITLE,
 
	ANGRFW_FILTER,
 
	ANGRFW_BACKGROUND,
 
	ANGRFW_GRF_LIST,
 
	ANGRFW_SCROLLBAR,
 
	ANGRFW_GRF_INFO,
 
	ANGRFW_ADD,
 
	ANGRFW_RESCAN,
 
	ANGRFW_RESIZE,
 
};
 

	
 
/**
 
 * Window for adding NewGRF files
 
 */
 
struct NewGRFAddWindow : public QueryStringBaseWindow {
 
private:
 
	typedef GUIList<const GRFConfig *> GUIGRFConfigList;
 

	
 
	GRFConfig **list;
 

	
 
	/** Runtime saved values */
 
	static Listing last_sorting;
 
	static Filtering last_filtering;
 

	
 
	static GUIGRFConfigList::SortFunction * const sorter_funcs[];
 
	static GUIGRFConfigList::FilterFunction * const filter_funcs[];
 
	GUIGRFConfigList grfs;
 

	
 
	const GRFConfig *sel;
 
	int sel_pos;
 

	
 
	enum {
 
		EDITBOX_MAX_SIZE = 50,
 
		EDITBOX_MAX_LENGTH = 300,
 
	};
 

	
 
	/**
 
	 * (Re)build the grf as its amount has changed because
 
	 * an item has been added or deleted for example
 
	 */
 
	void BuildGrfList()
 
	{
 
		if (!this->grfs.NeedRebuild()) return;
 

	
 
		/* Create temporary array of grfs to use for listing */
 
		this->grfs.Clear();
 

	
 
		for (const GRFConfig *c = _all_grfs; c != NULL; c = c->next) {
 
			*this->grfs.Append() = c;
 
		}
 

	
 
		this->FilterGrfList();
 
		this->grfs.Compact();
 
		this->grfs.RebuildDone();
 
		this->SortGrfList();
 

	
 
		this->vscroll.SetCount(this->grfs.Length()); // Update the scrollbar
 
		this->ScrollToSelected();
 
	}
 

	
 
	/** Sort grfs by name. */
 
	static int CDECL NameSorter(const GRFConfig * const *a, const GRFConfig * const *b)
 
	{
 
		const char *name_a = ((*a)->name != NULL) ? (*a)->name : "";
 
		const char *name_b = ((*b)->name != NULL) ? (*b)->name : "";
 
		int result = strcasecmp(name_a, name_b);
 
		if (result == 0) {
 
			result = strcasecmp((*a)->filename, (*b)->filename);
 
		}
 
		return result;
 
	}
 

	
 
	/** Sort the grf list */
 
	void SortGrfList()
 
	{
 
		if (!this->grfs.Sort()) return;
 

	
 
		/* update list position */
 
		if (this->sel != NULL) {
 
			this->sel_pos = this->grfs.FindIndex(this->sel);
 
			if (this->sel_pos < 0) {
 
				this->sel = NULL;
 
			}
 
		}
 
	}
 

	
 
	/** Filter grfs by tags/name */
 
	static bool CDECL TagNameFilter(const GRFConfig * const *a, const char *filter_string)
 
	{
 
		if ((*a)->name     != NULL && strcasestr((*a)->name,     filter_string) != NULL) return true;
 
		if ((*a)->filename != NULL && strcasestr((*a)->filename, filter_string) != NULL) return true;
 
		if ((*a)->info     != NULL && strcasestr((*a)->info,     filter_string) != NULL) return true;
 
		return false;
 
	}
 

	
 
	/** Filter the grf list */
 
	void FilterGrfList()
 
	{
 
		if (!this->grfs.Filter(this->edit_str_buf)) return;
 
	}
 

	
 
	/** Make sure that the currently selected grf is within the visible part of the list */
 
	void ScrollToSelected()
 
	{
 
		if (this->sel_pos >= 0) {
 
			this->vscroll.ScrollTowards(this->sel_pos);
 
		}
 
	}
 

	
 
public:
 
	NewGRFAddWindow(const WindowDesc *desc, Window *parent, GRFConfig **list) : QueryStringBaseWindow(EDITBOX_MAX_SIZE)
 
	{
 
		this->parent = parent;
 
		this->InitNested(desc, 0);
 

	
 
		InitializeTextBuffer(&this->text, this->edit_str_buf, this->edit_str_size, EDITBOX_MAX_LENGTH);
 
		this->SetFocusedWidget(ANGRFW_FILTER);
 

	
 
		this->list = list;
 
		this->grfs.SetListing(this->last_sorting);
 
		this->grfs.SetFiltering(this->last_filtering);
 
		this->grfs.SetSortFuncs(this->sorter_funcs);
 
		this->grfs.SetFilterFuncs(this->filter_funcs);
 

	
 
		this->OnInvalidateData(0);
 
	}
 

	
 
	virtual void OnInvalidateData(int data)
 
	{
 
		/* data == 0: NewGRFS were rescanned. All pointers to GrfConfigs are invalid.
 
		 * data == 1: User interaction with this window: Filter or selection change. */
 
		if (data == 0) {
 
			this->sel = NULL;
 
			this->sel_pos = -1;
 
			this->grfs.ForceRebuild();
 
		}
 

	
 
		this->BuildGrfList();
 
		this->SetWidgetDisabledState(ANGRFW_ADD, this->sel == NULL || this->sel->IsOpenTTDBaseGRF());
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		switch (widget) {
 
			case ANGRFW_GRF_LIST:
 
				resize->height = FONT_HEIGHT_NORMAL;
 
				size->height = max(size->height, WD_FRAMERECT_TOP + 10 * resize->height + WD_FRAMERECT_BOTTOM + padding.height + 2);
 
				break;
 

	
 
			case ANGRFW_GRF_INFO:
 
				size->height = max(size->height, WD_FRAMERECT_TOP + 10 * FONT_HEIGHT_NORMAL + WD_FRAMERECT_BOTTOM + padding.height + 2);
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnResize()
 
	{
 
		this->vscroll.SetCapacity(this->GetWidget<NWidgetBase>(ANGRFW_GRF_LIST)->current_y / this->resize.step_height);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
		this->DrawEditBox(ANGRFW_FILTER);
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case ANGRFW_GRF_LIST: {
 
				GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, 0xD7);
 

	
 
				uint y = r.top + WD_FRAMERECT_TOP;
 
				uint min_index = this->vscroll.GetPosition();
 
				uint max_index = min(min_index + this->vscroll.GetCapacity(), this->grfs.Length());
 

	
 
				for (uint i = min_index; i < max_index; i++)
 
				{
 
					const GRFConfig *c = this->grfs[i];
 
					bool h = c == this->sel;
 
					const char *text = (c->name != NULL && !StrEmpty(c->name)) ? c->name : c->filename;
 

	
 
					/* Draw selection background */
 
					if (h) GfxFillRect(r.left + 1, y, r.right - 1, y + this->resize.step_height - 1, 156);
 
					DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, text, h ? TC_WHITE : TC_ORANGE);
 
					y += this->resize.step_height;
 
				}
 
				break;
 
			}
 

	
 
			case ANGRFW_GRF_INFO:
 
				if (this->sel != NULL) {
 
					ShowNewGRFInfo(this->sel, r.left + WD_FRAMERECT_LEFT, r.top + WD_FRAMERECT_TOP, r.right - WD_FRAMERECT_RIGHT, r.bottom - WD_FRAMERECT_BOTTOM, false);
 
				}
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnDoubleClick(Point pt, int widget)
 
	{
 
		if (widget == ANGRFW_GRF_LIST) this->OnClick(pt, ANGRFW_ADD);
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case ANGRFW_GRF_LIST: {
 
				/* Get row... */
 
				uint i = (pt.y - this->GetWidget<NWidgetBase>(ANGRFW_GRF_LIST)->pos_y - WD_FRAMERECT_TOP) / this->resize.step_height + this->vscroll.GetPosition();
 

	
 
				if (i < this->grfs.Length()) {
 
					this->sel = this->grfs[i];
 
					this->sel_pos = i;
 
				} else {
 
					this->sel = NULL;
 
					this->sel_pos = -1;
 
				}
 
				this->InvalidateData(1);
 
				break;
 
			}
 

	
 
			case ANGRFW_ADD: // Add selection to list
 
				if (this->sel != NULL) {
 
					const GRFConfig *src = this->sel;
 
					GRFConfig **list;
 

	
 
					/* Find last entry in the list, checking for duplicate grfid on the way */
 
					for (list = this->list; *list != NULL; list = &(*list)->next) {
 
						if ((*list)->grfid == src->grfid) {
 
							ShowErrorMessage(STR_NEWGRF_DUPLICATE_GRFID, INVALID_STRING_ID, 0, 0);
 
							return;
 
						}
 
					}
 

	
 
					/* Copy GRF details from scanned list */
 
					GRFConfig *c = CallocT<GRFConfig>(1);
 
					*c = *src;
 
					c->filename = strdup(src->filename);
 
					if (src->name      != NULL) c->name      = strdup(src->name);
 
					if (src->info      != NULL) c->info      = strdup(src->info);
 
					c->next = NULL;
 

	
 
					/* Append GRF config to configuration list */
 
					*list = c;
 

	
 
					DeleteWindowByClass(WC_SAVELOAD);
 
					InvalidateWindowData(WC_GAME_OPTIONS, 0, 2);
 
				}
 
				break;
 

	
 
			case ANGRFW_RESCAN: // Rescan list
 
				ScanNewGRFFiles();
 
				this->InvalidateData();
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnMouseLoop()
 
	{
 
		this->HandleEditBox(ANGRFW_FILTER);
 
	}
 

	
 
	virtual EventState OnKeyPress(uint16 key, uint16 keycode)
 
	{
 
		switch (keycode) {
 
			case WKC_UP:
 
				/* scroll up by one */
 
				if (this->sel_pos > 0) this->sel_pos--;
 
				break;
 

	
 
			case WKC_DOWN:
 
				/* scroll down by one */
 
				if (this->sel_pos < (int)this->grfs.Length() - 1) this->sel_pos++;
 
				break;
 

	
 
			case WKC_PAGEUP:
 
				/* scroll up a page */
 
				this->sel_pos = (this->sel_pos < this->vscroll.GetCapacity()) ? 0 : this->sel_pos - this->vscroll.GetCapacity();
 
				break;
 

	
 
			case WKC_PAGEDOWN:
 
				/* scroll down a page */
 
				this->sel_pos = min(this->sel_pos + this->vscroll.GetCapacity(), (int)this->grfs.Length() - 1);
 
				break;
 

	
 
			case WKC_HOME:
 
				/* jump to beginning */
 
				this->sel_pos = 0;
 
				break;
 

	
 
			case WKC_END:
 
				/* jump to end */
 
				this->sel_pos = this->grfs.Length() - 1;
 
				break;
 

	
 
			default: {
 
				/* Handle editbox input */
 
				EventState state = ES_NOT_HANDLED;
 
				if (this->HandleEditBoxKey(ANGRFW_FILTER, key, keycode, state) == HEBR_EDITING) {
 
					this->OnOSKInput(ANGRFW_FILTER);
 
				}
 
				return state;
 
			}
 
		}
 

	
 
		if (this->grfs.Length() == 0) this->sel_pos = -1;
 
		if (this->sel_pos >= 0) {
 
			this->sel = this->grfs[this->sel_pos];
 

	
 
			this->ScrollToSelected();
 
			this->InvalidateData(1);
 
		}
 

	
 
		return ES_HANDLED;
 
	}
 

	
 
	virtual void OnOSKInput(int wid)
 
	{
 
		this->grfs.SetFilterState(!StrEmpty(this->edit_str_buf));
 
		this->grfs.ForceRebuild();
 
		this->InvalidateData(1);
 
	}
 
};
 

	
 
Listing NewGRFAddWindow::last_sorting = {false, 0};
 
Filtering NewGRFAddWindow::last_filtering = {false, 0};
 

	
 
NewGRFAddWindow::GUIGRFConfigList::SortFunction * const NewGRFAddWindow::sorter_funcs[] = {
 
	&NameSorter,
 
};
 

	
 
NewGRFAddWindow::GUIGRFConfigList::FilterFunction * const NewGRFAddWindow::filter_funcs[] = {
 
	&TagNameFilter,
 
};
 

	
 
static const NWidgetPart _nested_newgrf_add_dlg_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, ANGRFW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, ANGRFW_CAPTION), SetDataTip(STR_NEWGRF_ADD_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, ANGRFW_FILTER_PANEL), SetResize(1, 0),
 
		NWidget(NWID_HORIZONTAL), SetPadding(WD_TEXTPANEL_TOP, 0, WD_TEXTPANEL_BOTTOM, 0), SetPIP(WD_FRAMETEXT_LEFT, WD_FRAMETEXT_RIGHT, WD_FRAMETEXT_RIGHT),
 
			NWidget(WWT_TEXT, COLOUR_GREY, ANGRFW_FILTER_TITLE), SetFill(false, true), SetDataTip(STR_LIST_FILTER_TITLE, STR_NULL),
 
			NWidget(WWT_EDITBOX, COLOUR_GREY, ANGRFW_FILTER), SetFill(true, false), SetMinimalSize(100, 12), SetResize(1, 0), SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
 
			NWidget(WWT_TEXT, COLOUR_GREY, ANGRFW_FILTER_TITLE), SetFill(0, 1), SetDataTip(STR_LIST_FILTER_TITLE, STR_NULL),
 
			NWidget(WWT_EDITBOX, COLOUR_GREY, ANGRFW_FILTER), SetFill(1, 0), SetMinimalSize(100, 12), SetResize(1, 0), SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
 
		EndContainer(),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_PANEL, COLOUR_GREY, ANGRFW_BACKGROUND),
 
			NWidget(WWT_INSET, COLOUR_GREY, ANGRFW_GRF_LIST), SetMinimalSize(290, 1), SetResize(1, 1), SetPadding(2, 2, 2, 2), EndContainer(),
 
		EndContainer(),
 
		NWidget(WWT_SCROLLBAR, COLOUR_GREY, ANGRFW_SCROLLBAR),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, ANGRFW_GRF_INFO), SetMinimalSize(306, 1), SetResize(1, 0), EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, ANGRFW_ADD), SetMinimalSize(147, 12), SetResize(1, 0), SetDataTip(STR_NEWGRF_ADD_FILE, STR_NEWGRF_ADD_FILE_TOOLTIP),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, ANGRFW_RESCAN), SetMinimalSize(147, 12), SetResize(1, 0), SetDataTip(STR_NEWGRF_ADD_RESCAN_FILES, STR_NEWGRF_ADD_RESCAN_FILES_TOOLTIP),
 
		EndContainer(),
 
		NWidget(WWT_RESIZEBOX, COLOUR_GREY, ANGRFW_RESIZE),
 
	EndContainer(),
 
};
 

	
 
/* Window definition for the add a newgrf window */
 
static const WindowDesc _newgrf_add_dlg_desc(
 
	WDP_CENTER, WDP_CENTER, 306, 347,
 
	WC_SAVELOAD, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_STD_BTN | WDF_UNCLICK_BUTTONS | WDF_RESIZABLE,
 
	_nested_newgrf_add_dlg_widgets, lengthof(_nested_newgrf_add_dlg_widgets)
 
);
 

	
 
static GRFPresetList _grf_preset_list;
 

	
 
class DropDownListPresetItem : public DropDownListItem {
 
public:
 
	DropDownListPresetItem(int result) : DropDownListItem(result, false) {}
 

	
 
	virtual ~DropDownListPresetItem() {}
 

	
 
	bool Selectable() const
 
	{
 
		return true;
 
	}
 

	
 
	void Draw(int left, int right, int top, int bottom, bool sel, int bg_colour) const
 
	{
 
		DrawString(left + 2, right + 2, top, _grf_preset_list[this->result], sel ? TC_WHITE : TC_BLACK);
 
	}
 
};
 

	
 
static void NewGRFConfirmationCallback(Window *w, bool confirmed);
 

	
 
/** Names of the manage newgrfs window widgets. */
 
enum ShowNewGRFStateWidgets {
 
	SNGRFS_CLOSEBOX = 0,
 
	SNGRFS_CAPTION,
 
	SNGRFS_BACKGROUND1,
 
	SNGRFS_PRESET_LIST,
 
	SNGRFS_PRESET_SAVE,
 
	SNGRFS_PRESET_DELETE,
 
	SNGRFS_BACKGROUND2,
 
	SNGRFS_ADD,
 
	SNGRFS_REMOVE,
 
	SNGRFS_MOVE_UP,
 
	SNGRFS_MOVE_DOWN,
 
	SNGRFS_FILE_LIST,
 
	SNGRFS_SCROLLBAR,
 
	SNGRFS_NEWGRF_INFO,
 
	SNGRFS_SET_PARAMETERS,
 
	SNGRFS_TOGGLE_PALETTE,
 
	SNGRFS_APPLY_CHANGES,
 
	SNGRFS_CONTENT_DOWNLOAD,
 
	SNGRFS_RESIZE,
 
};
 

	
 
/**
 
 * Window for showing NewGRF files
 
 */
 
struct NewGRFWindow : public Window {
 
	GRFConfig **orig_list; ///< grf list the window is shown with
 
	GRFConfig *list;       ///< temporary grf list to which changes are made
 
	GRFConfig *sel;        ///< selected grf item
 
	bool editable;         ///< is the window editable
 
	bool show_params;      ///< are the grf-parameters shown in the info-panel
 
	bool execute;          ///< on pressing 'apply changes' are grf changes applied immediately, or only list is updated
 
	int query_widget;      ///< widget that opened a query
 
	int preset;            ///< selected preset
 

	
 
	NewGRFWindow(const WindowDesc *desc, bool editable, bool show_params, bool exec_changes, GRFConfig **config) : Window()
 
	{
 
		this->sel         = NULL;
 
		this->list        = NULL;
 
		this->orig_list   = config;
 
		this->editable    = editable;
 
		this->execute     = exec_changes;
 
		this->show_params = show_params;
 
		this->preset      = -1;
 

	
 
		CopyGRFConfigList(&this->list, *config, false);
 
		GetGRFPresetList(&_grf_preset_list);
 

	
 
		this->InitNested(desc);
 
		this->OnInvalidateData(2);
 
	}
 

	
 
	~NewGRFWindow()
 
	{
 
		if (this->editable && !this->execute) {
 
			CopyGRFConfigList(this->orig_list, this->list, true);
 
			ResetGRFConfig(false);
 
			ReloadNewGRFData();
 
		}
 

	
 
		/* Remove the temporary copy of grf-list used in window */
 
		ClearGRFConfigList(&this->list);
 
		_grf_preset_list.Clear();
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		switch (widget) {
 
			case SNGRFS_FILE_LIST:
 
				resize->height = FONT_HEIGHT_NORMAL + WD_MATRIX_TOP + WD_MATRIX_BOTTOM;
 
				size->height = max(size->height, 7 * resize->height);
 
				break;
 

	
 
			case SNGRFS_NEWGRF_INFO:
 
				size->height = max(size->height, WD_FRAMERECT_TOP + 10 * FONT_HEIGHT_NORMAL + WD_FRAMERECT_BOTTOM + padding.height + 2);
 
				break;
 

	
 
			case SNGRFS_PRESET_LIST: {
 
				Dimension d = GetStringBoundingBox(STR_NUM_CUSTOM);
 
				for (uint i = 0; i < _grf_preset_list.Length(); i++) {
 
					if (_grf_preset_list[i] != NULL) {
 
						SetDParamStr(0, _grf_preset_list[i]);
 
						d = maxdim(d, GetStringBoundingBox(STR_JUST_RAW_STRING));
 
					}
 
				}
 
				d.width += padding.width;
 
				*size = maxdim(d, *size);
 
				break;
 
			}
 
		}
 
	}
 

	
 
	virtual void OnResize()
 
	{
 
		NWidgetCore *nwi = this->GetWidget<NWidgetCore>(SNGRFS_FILE_LIST);
 
		this->vscroll.SetCapacity(nwi->current_y / this->resize.step_height);
 
		nwi->widget_data = (this->vscroll.GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START);
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		switch (widget) {
 
			case SNGRFS_PRESET_LIST:
 
				if (this->preset == -1) {
 
					SetDParam(0, STR_NUM_CUSTOM);
 
				} else {
 
					SetDParam(0, STR_JUST_RAW_STRING);
 
					SetDParamStr(1, _grf_preset_list[this->preset]);
 
				}
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case SNGRFS_FILE_LIST: {
 
				uint y = r.top + WD_MATRIX_TOP;
 
				int sprite_offset_y = (FONT_HEIGHT_NORMAL - 10) / 2 - 1;
 

	
 
				bool rtl = _dynlang.text_dir == TD_RTL;
 
				uint text_left    = rtl ? r.left + WD_FRAMERECT_LEFT : r.left + 25;
 
				uint text_right   = rtl ? r.right - 25 : r.right - WD_FRAMERECT_RIGHT;
 
				uint square_left  = rtl ? r.right - 15 : r.left + 5;
 
				uint warning_left = rtl ? r.right - 30 : r.left + 20;
 

	
 
				int i = 0;
 
				for (const GRFConfig *c = this->list; c != NULL; c = c->next, i++) {
 
					if (this->vscroll.IsVisible(i)) {
 
						const char *text = (c->name != NULL && !StrEmpty(c->name)) ? c->name : c->filename;
 
						SpriteID pal;
 

	
 
						/* Pick a colour */
 
						switch (c->status) {
 
							case GCS_NOT_FOUND:
 
							case GCS_DISABLED:
 
								pal = PALETTE_TO_RED;
 
								break;
 
							case GCS_ACTIVATED:
 
								pal = PALETTE_TO_GREEN;
 
								break;
 
							default:
 
								pal = PALETTE_TO_BLUE;
 
								break;
 
						}
 

	
 
						/* Do not show a "not-failure" colour when it actually failed to load */
 
						if (pal != PALETTE_TO_RED) {
 
							if (HasBit(c->flags, GCF_STATIC)) {
 
								pal = PALETTE_TO_GREY;
 
							} else if (HasBit(c->flags, GCF_COMPATIBLE)) {
 
								pal = PALETTE_TO_ORANGE;
 
							}
 
						}
 

	
 
						DrawSprite(SPR_SQUARE, pal, square_left, y + sprite_offset_y);
 
						if (c->error != NULL) DrawSprite(SPR_WARNING_SIGN, 0, warning_left, y + sprite_offset_y);
 
						uint txtoffset = c->error == NULL ? 0 : 10;
 
						DrawString(text_left + (rtl ? 0 : txtoffset), text_right - (rtl ? txtoffset : 0), y, text, this->sel == c ? TC_WHITE : TC_BLACK);
 
						y += this->resize.step_height;
 
					}
 
				}
 
			} break;
 

	
 
			case SNGRFS_NEWGRF_INFO:
 
				if (this->sel != NULL) {
 
					ShowNewGRFInfo(this->sel, r.left + WD_FRAMERECT_LEFT, r.top + WD_FRAMERECT_TOP, r.right - WD_FRAMERECT_RIGHT, r.bottom - WD_FRAMERECT_BOTTOM, this->show_params);
 
				}
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnDoubleClick(Point pt, int widget)
 
	{
 
		if (widget == SNGRFS_FILE_LIST) this->OnClick(pt, SNGRFS_SET_PARAMETERS);
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case SNGRFS_PRESET_LIST: {
 
				DropDownList *list = new DropDownList();
 

	
 
				/* Add 'None' option for clearing list */
 
				list->push_back(new DropDownListStringItem(STR_NONE, -1, false));
 

	
 
				for (uint i = 0; i < _grf_preset_list.Length(); i++) {
 
					if (_grf_preset_list[i] != NULL) {
 
						list->push_back(new DropDownListPresetItem(i));
 
					}
 
				}
 

	
 
				ShowDropDownList(this, list, this->preset, SNGRFS_PRESET_LIST);
 
				break;
 
			}
 

	
 
			case SNGRFS_PRESET_SAVE:
 
				this->query_widget = widget;
 
				ShowQueryString(STR_EMPTY, STR_NEWGRF_SETTINGS_PRESET_SAVE_QUERY, 32, 100, this, CS_ALPHANUMERAL, QSF_NONE);
 
				break;
 

	
 
			case SNGRFS_PRESET_DELETE:
 
				if (this->preset == -1) return;
 

	
 
				DeleteGRFPresetFromConfig(_grf_preset_list[this->preset]);
 
				GetGRFPresetList(&_grf_preset_list);
 
				this->preset = -1;
 
				this->InvalidateData();
 
				break;
 

	
 
			case SNGRFS_ADD: // Add GRF
 
				DeleteWindowByClass(WC_SAVELOAD);
 
				new NewGRFAddWindow(&_newgrf_add_dlg_desc, this, &this->list);
 
				break;
 

	
 
			case SNGRFS_REMOVE: { // Remove GRF
 
				GRFConfig **pc, *c, *newsel;
 

	
 
				/* Choose the next GRF file to be the selected file */
 
				newsel = this->sel->next;
 

	
 
				for (pc = &this->list; (c = *pc) != NULL; pc = &c->next) {
 
					/* If the new selection is empty (i.e. we're deleting the last item
 
					 * in the list, pick the file just before the selected file */
 
					if (newsel == NULL && c->next == this->sel) newsel = c;
 

	
 
					if (c == this->sel) {
 
						*pc = c->next;
 
						free(c);
 
						break;
 
					}
 
				}
 

	
 
				this->sel = newsel;
 
				this->preset = -1;
 
				this->InvalidateData();
 
				this->DeleteChildWindows(WC_QUERY_STRING); // Remove the parameter query window
 
				break;
 
			}
 

	
 
			case SNGRFS_MOVE_UP: { // Move GRF up
 
				GRFConfig **pc, *c;
 
				if (this->sel == NULL) break;
 

	
 
				for (pc = &this->list; (c = *pc) != NULL; pc = &c->next) {
 
					if (c->next == this->sel) {
 
						c->next = this->sel->next;
 
						this->sel->next = c;
 
						*pc = this->sel;
 
						break;
 
					}
 
				}
 
				this->preset = -1;
 
				this->InvalidateData();
 
				break;
 
			}
 

	
 
			case SNGRFS_MOVE_DOWN: { // Move GRF down
 
				GRFConfig **pc, *c;
 
				if (this->sel == NULL) break;
 

	
 
				for (pc = &this->list; (c = *pc) != NULL; pc = &c->next) {
 
					if (c == this->sel) {
 
						*pc = c->next;
 
						c->next = c->next->next;
 
						(*pc)->next = c;
 
						break;
 
					}
 
				}
 
				this->preset = -1;
 
				this->InvalidateData();
 
				break;
 
			}
 

	
 
			case SNGRFS_FILE_LIST: { // Select a GRF
 
				GRFConfig *c;
 
				uint i = (pt.y - this->GetWidget<NWidgetBase>(SNGRFS_FILE_LIST)->pos_y) / this->resize.step_height + this->vscroll.GetPosition();
 

	
 
				for (c = this->list; c != NULL && i > 0; c = c->next, i--) {}
 

	
 
				if (this->sel != c) this->DeleteChildWindows(WC_QUERY_STRING); // Remove the parameter query window
 
				this->sel = c;
 

	
 
				this->InvalidateData();
 
				break;
 
			}
 

	
 
			case SNGRFS_APPLY_CHANGES: // Apply changes made to GRF list
 
				if (this->execute) {
 
					ShowQuery(
 
						STR_NEWGRF_POPUP_CAUTION_CAPTION,
 
						STR_NEWGRF_CONFIRMATION_TEXT,
 
						this,
 
						NewGRFConfirmationCallback
 
					);
 
				} else {
 
					CopyGRFConfigList(this->orig_list, this->list, true);
 
					ResetGRFConfig(false);
 
					ReloadNewGRFData();
 
				}
 
				break;
 

	
 
			case SNGRFS_SET_PARAMETERS: { // Edit parameters
 
				if (this->sel == NULL) break;
 

	
 
				this->query_widget = widget;
 
				static char buff[512];
 
				GRFBuildParamList(buff, this->sel, lastof(buff));
 
				SetDParamStr(0, buff);
 
				ShowQueryString(STR_JUST_RAW_STRING, STR_NEWGRF_SETTINGS_PARAMETER_QUERY, 63, 250, this, CS_NUMERAL_SPACE, QSF_NONE);
 
				break;
 
			}
 

	
 
			case SNGRFS_TOGGLE_PALETTE:
 
				if (this->sel != NULL) {
 
					this->sel->windows_paletted ^= true;
 
					this->SetDirty();
 
				}
 
				break;
 

	
 
			case SNGRFS_CONTENT_DOWNLOAD:
 
				if (!_network_available) {
 
					ShowErrorMessage(STR_NETWORK_ERROR_NOTAVAILABLE, INVALID_STRING_ID, 0, 0);
 
				} else {
 
#if defined(ENABLE_NETWORK)
 
				/* Only show the things in the current list, or everything when nothing's selected */
 
					ContentVector cv;
 
					for (const GRFConfig *c = this->list; c != NULL; c = c->next) {
 
						if (c->status != GCS_NOT_FOUND && !HasBit(c->flags, GCF_COMPATIBLE)) continue;
 

	
 
						ContentInfo *ci = new ContentInfo();
 
						ci->type = CONTENT_TYPE_NEWGRF;
 
						ci->state = ContentInfo::DOES_NOT_EXIST;
 
						ttd_strlcpy(ci->name, c->name != NULL ? c->name : c->filename, lengthof(ci->name));
 
						ci->unique_id = BSWAP32(c->grfid);
 
						memcpy(ci->md5sum, c->md5sum, sizeof(ci->md5sum));
 
						if (HasBit(c->flags, GCF_COMPATIBLE)) GamelogGetOriginalGRFMD5Checksum(c->grfid, ci->md5sum);
 
						*cv.Append() = ci;
 
					}
 
					ShowNetworkContentListWindow(cv.Length() == 0 ? NULL : &cv, CONTENT_TYPE_NEWGRF);
 
#endif
 
				}
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnDropdownSelect(int widget, int index)
 
	{
 
		if (index == -1) {
 
			ClearGRFConfigList(&this->list);
 
			this->preset = -1;
 
		} else {
 
			GRFConfig *c = LoadGRFPresetFromConfig(_grf_preset_list[index]);
 

	
 
			if (c != NULL) {
 
				this->sel = NULL;
 
				ClearGRFConfigList(&this->list);
 
				this->list = c;
 
				this->preset = index;
 
			}
 
		}
 

	
 
		this->sel = NULL;
 
		this->InvalidateData(3);
 
	}
 

	
 
	virtual void OnQueryTextFinished(char *str)
 
	{
 
		if (str == NULL) return;
 

	
 
		switch (this->query_widget) {
 
			default: NOT_REACHED();
 

	
 
			case SNGRFS_PRESET_SAVE:
 
				SaveGRFPresetToConfig(str, this->list);
 
				GetGRFPresetList(&_grf_preset_list);
 

	
 
				/* Switch to this preset */
 
				for (uint i = 0; i < _grf_preset_list.Length(); i++) {
 
					if (_grf_preset_list[i] != NULL && strcmp(_grf_preset_list[i], str) == 0) {
 
						this->preset = i;
 
						break;
 
					}
 
				}
 
				break;
 

	
 
			case SNGRFS_SET_PARAMETERS: {
 
				/* Parse our new "int list" */
 
				GRFConfig *c = this->sel;
 
				c->num_params = parse_intlist(str, (int*)c->param, lengthof(c->param));
 

	
 
				/* parse_intlist returns -1 on error */
 
				if (c->num_params == (byte)-1) c->num_params = 0;
 

	
 
				this->preset = -1;
 
				break;
 
			}
 
		}
 

	
 
		this->InvalidateData();
 
	}
 

	
 
	virtual void OnInvalidateData(int data = 0)
 
	{
 
		switch (data) {
 
			default: NOT_REACHED();
 
			case 0:
 
				/* Nothing important to do */
 
				break;
 

	
 
			case 1:
 
				/* Search the list for items that are now found and mark them as such. */
 
				for (GRFConfig *c = this->list; c != NULL; c = c->next) {
 
					if (c->status != GCS_NOT_FOUND) continue;
 

	
 
					const GRFConfig *f = FindGRFConfig(c->grfid, c->md5sum);
 
					if (f == NULL) continue;
 

	
 
					free(c->filename);
 
					free(c->name);
 
					free(c->info);
 

	
 
					c->filename  = f->filename == NULL ? NULL : strdup(f->filename);
 
					c->name      = f->name == NULL ? NULL : strdup(f->name);;
 
					c->info      = f->info == NULL ? NULL : strdup(f->info);;
 
					c->status    = GCS_UNKNOWN;
 
				}
 
				break;
 

	
 
			case 2:
 
				this->preset = -1;
 
				/* Fall through */
 
			case 3:
 
				const GRFConfig *c;
 
				int i;
 

	
 
				for (c = this->list, i = 0; c != NULL; c = c->next, i++) {}
 

	
 
				this->vscroll.SetCapacity((this->GetWidget<NWidgetBase>(SNGRFS_FILE_LIST)->current_y) / this->resize.step_height);
 
				this->GetWidget<NWidgetCore>(SNGRFS_FILE_LIST)->widget_data = (this->vscroll.GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START);
 
				this->vscroll.SetCount(i);
 
				break;
 
		}
 

	
 
		this->SetWidgetsDisabledState(!this->editable,
 
			SNGRFS_PRESET_LIST,
 
			SNGRFS_ADD,
 
			SNGRFS_APPLY_CHANGES,
 
			SNGRFS_TOGGLE_PALETTE,
 
			WIDGET_LIST_END
 
		);
 

	
 
		bool disable_all = this->sel == NULL || !this->editable;
 
		this->SetWidgetsDisabledState(disable_all,
 
			SNGRFS_REMOVE,
 
			SNGRFS_MOVE_UP,
 
			SNGRFS_MOVE_DOWN,
 
			WIDGET_LIST_END
 
		);
 
		this->SetWidgetDisabledState(SNGRFS_SET_PARAMETERS, !this->show_params || disable_all);
 
		this->SetWidgetDisabledState(SNGRFS_TOGGLE_PALETTE, disable_all);
 

	
 
		if (!disable_all) {
 
			/* All widgets are now enabled, so disable widgets we can't use */
 
			if (this->sel == this->list)       this->DisableWidget(SNGRFS_MOVE_UP);
 
			if (this->sel->next == NULL)       this->DisableWidget(SNGRFS_MOVE_DOWN);
 
			if (this->sel->IsOpenTTDBaseGRF()) this->DisableWidget(SNGRFS_REMOVE);
 
		}
 

	
 
		this->SetWidgetDisabledState(SNGRFS_PRESET_DELETE, this->preset == -1);
 

	
 
		bool has_missing = false;
 
		bool has_compatible = false;
 
		for (const GRFConfig *c = this->list; !has_missing && c != NULL; c = c->next) {
 
			has_missing    |= c->status == GCS_NOT_FOUND;
 
			has_compatible |= HasBit(c->flags, GCF_COMPATIBLE);
 
		}
 
		if (has_missing || has_compatible) {
 
			this->GetWidget<NWidgetCore>(SNGRFS_CONTENT_DOWNLOAD)->widget_data = STR_NEWGRF_SETTINGS_FIND_MISSING_CONTENT_BUTTON;
 
			this->GetWidget<NWidgetCore>(SNGRFS_CONTENT_DOWNLOAD)->tool_tip    = STR_NEWGRF_SETTINGS_FIND_MISSING_CONTENT_TOOLTIP;
 
		} else {
 
			this->GetWidget<NWidgetCore>(SNGRFS_CONTENT_DOWNLOAD)->widget_data = STR_INTRO_ONLINE_CONTENT;
 
			this->GetWidget<NWidgetCore>(SNGRFS_CONTENT_DOWNLOAD)->tool_tip    = STR_INTRO_TOOLTIP_ONLINE_CONTENT;
 
		}
 
		this->SetWidgetDisabledState(SNGRFS_PRESET_SAVE, has_missing);
 
	}
 
};
 

	
 
/* Widget definition of the manage newgrfs window */
 
static const NWidgetPart _nested_newgrf_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_MAUVE, SNGRFS_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_MAUVE, SNGRFS_CAPTION), SetDataTip(STR_NEWGRF_SETTINGS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_MAUVE, SNGRFS_BACKGROUND1),
 
		NWidget(NWID_HORIZONTAL), SetPadding(2, 10, 2, 10),
 
			NWidget(WWT_DROPDOWN, COLOUR_YELLOW, SNGRFS_PRESET_LIST), SetFill(true, false), SetResize(1, 0), SetDataTip(STR_JUST_STRING, STR_NEWGRF_SETTINGS_PRESET_LIST_TOOLTIP),
 
			NWidget(WWT_DROPDOWN, COLOUR_YELLOW, SNGRFS_PRESET_LIST), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_JUST_STRING, STR_NEWGRF_SETTINGS_PRESET_LIST_TOOLTIP),
 
			NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, SNGRFS_PRESET_SAVE), SetFill(true, false), SetDataTip(STR_NEWGRF_SETTINGS_PRESET_SAVE, STR_NEWGRF_SETTINGS_PRESET_SAVE_TOOLTIP),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, SNGRFS_PRESET_DELETE), SetFill(true, false), SetDataTip(STR_NEWGRF_SETTINGS_PRESET_DELETE, STR_NEWGRF_SETTINGS_PRESET_DELETE_TOOLTIP),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, SNGRFS_PRESET_SAVE), SetFill(1, 0), SetDataTip(STR_NEWGRF_SETTINGS_PRESET_SAVE, STR_NEWGRF_SETTINGS_PRESET_SAVE_TOOLTIP),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, SNGRFS_PRESET_DELETE), SetFill(1, 0), SetDataTip(STR_NEWGRF_SETTINGS_PRESET_DELETE, STR_NEWGRF_SETTINGS_PRESET_DELETE_TOOLTIP),
 
			EndContainer(),
 
		EndContainer(),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_MAUVE, SNGRFS_BACKGROUND2),
 
		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPadding(2, 10, 2, 10),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, SNGRFS_ADD), SetFill(true, false), SetResize(1, 0), SetDataTip(STR_NEWGRF_SETTINGS_ADD, STR_NEWGRF_SETTINGS_ADD_TOOLTIP),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, SNGRFS_REMOVE), SetFill(true, false), SetResize(1, 0), SetDataTip(STR_NEWGRF_SETTINGS_REMOVE, STR_NEWGRF_SETTINGS_REMOVE_TOOLTIP),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, SNGRFS_MOVE_UP), SetFill(true, false), SetResize(1, 0), SetDataTip(STR_NEWGRF_SETTINGS_MOVEUP, STR_NEWGRF_SETTINGS_MOVEUP_TOOLTIP),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, SNGRFS_MOVE_DOWN), SetFill(true, false), SetResize(1, 0), SetDataTip(STR_NEWGRF_SETTINGS_MOVEDOWN, STR_NEWGRF_SETTINGS_MOVEDOWN_TOOLTIP),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, SNGRFS_ADD), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_NEWGRF_SETTINGS_ADD, STR_NEWGRF_SETTINGS_ADD_TOOLTIP),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, SNGRFS_REMOVE), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_NEWGRF_SETTINGS_REMOVE, STR_NEWGRF_SETTINGS_REMOVE_TOOLTIP),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, SNGRFS_MOVE_UP), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_NEWGRF_SETTINGS_MOVEUP, STR_NEWGRF_SETTINGS_MOVEUP_TOOLTIP),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, SNGRFS_MOVE_DOWN), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_NEWGRF_SETTINGS_MOVEDOWN, STR_NEWGRF_SETTINGS_MOVEDOWN_TOOLTIP),
 
		EndContainer(),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_MATRIX, COLOUR_MAUVE, SNGRFS_FILE_LIST), SetFill(true, false), SetDataTip(0x501, STR_NEWGRF_SETTINGS_FILE_TOOLTIP), SetResize(1, 0),
 
		NWidget(WWT_MATRIX, COLOUR_MAUVE, SNGRFS_FILE_LIST), SetFill(1, 0), SetDataTip(0x501, STR_NEWGRF_SETTINGS_FILE_TOOLTIP), SetResize(1, 0),
 
		NWidget(WWT_SCROLLBAR, COLOUR_MAUVE, SNGRFS_SCROLLBAR),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_MAUVE, SNGRFS_NEWGRF_INFO), SetFill(true, false), SetResize(1, 0), EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_MAUVE, SNGRFS_NEWGRF_INFO), SetFill(1, 0), SetResize(1, 0), EndContainer(),
 
	NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, SNGRFS_SET_PARAMETERS), SetFill(true, false), SetResize(1, 0), SetDataTip(STR_NEWGRF_SETTINGS_SET_PARAMETERS, STR_NULL),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, SNGRFS_TOGGLE_PALETTE), SetFill(true, false), SetResize(1, 0), SetDataTip(STR_NEWGRF_SETTINGS_TOGGLE_PALETTE, STR_NEWGRF_SETTINGS_TOGGLE_PALETTE_TOOLTIP),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, SNGRFS_APPLY_CHANGES), SetFill(true, false), SetResize(1, 0), SetDataTip(STR_NEWGRF_SETTINGS_APPLY_CHANGES, STR_NULL),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, SNGRFS_SET_PARAMETERS), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_NEWGRF_SETTINGS_SET_PARAMETERS, STR_NULL),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, SNGRFS_TOGGLE_PALETTE), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_NEWGRF_SETTINGS_TOGGLE_PALETTE, STR_NEWGRF_SETTINGS_TOGGLE_PALETTE_TOOLTIP),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, SNGRFS_APPLY_CHANGES), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_NEWGRF_SETTINGS_APPLY_CHANGES, STR_NULL),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, SNGRFS_CONTENT_DOWNLOAD), SetFill(true, false), SetResize(1, 0), SetDataTip(STR_INTRO_ONLINE_CONTENT, STR_INTRO_TOOLTIP_ONLINE_CONTENT),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, SNGRFS_CONTENT_DOWNLOAD), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_INTRO_ONLINE_CONTENT, STR_INTRO_TOOLTIP_ONLINE_CONTENT),
 
		NWidget(WWT_RESIZEBOX, COLOUR_MAUVE, SNGRFS_RESIZE),
 
	EndContainer(),
 
};
 

	
 
/* Window definition of the manage newgrfs window */
 
static const WindowDesc _newgrf_desc(
 
	WDP_CENTER, WDP_CENTER, 300, 263,
 
	WC_GAME_OPTIONS, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_RESIZABLE,
 
	_nested_newgrf_widgets, lengthof(_nested_newgrf_widgets)
 
);
 

	
 
/** Callback function for the newgrf 'apply changes' confirmation window
 
 * @param w Window which is calling this callback
 
 * @param confirmed boolean value, true when yes was clicked, false otherwise
 
 */
 
static void NewGRFConfirmationCallback(Window *w, bool confirmed)
 
{
 
	if (confirmed) {
 
		NewGRFWindow *nw = dynamic_cast<NewGRFWindow*>(w);
 
		GRFConfig *c;
 
		int i = 0;
 

	
 
		GamelogStartAction(GLAT_GRF);
 
		GamelogGRFUpdate(_grfconfig, nw->list); // log GRF changes
 
		CopyGRFConfigList(nw->orig_list, nw->list, false);
 
		ReloadNewGRFData();
 
		GamelogStopAction();
 

	
 
		/* Show new, updated list */
 
		for (c = nw->list; c != NULL && c != nw->sel; c = c->next, i++) {}
 
		CopyGRFConfigList(&nw->list, *nw->orig_list, false);
 
		for (c = nw->list; c != NULL && i > 0; c = c->next, i--) {}
 
		nw->sel = c;
 

	
 
		w->SetDirty();
 
	}
 
}
 

	
 

	
 

	
 
/** Setup the NewGRF gui
 
 * @param editable allow the user to make changes to the grfconfig in the window
 
 * @param show_params show information about what parameters are set for the grf files
 
 * @param exec_changes if changes are made to the list (editable is true), apply these
 
 *        changes immediately or only update the list
 
 * @param config pointer to a linked-list of grfconfig's that will be shown */
 
void ShowNewGRFSettings(bool editable, bool show_params, bool exec_changes, GRFConfig **config)
 
{
 
	DeleteWindowByClass(WC_GAME_OPTIONS);
 
	new NewGRFWindow(&_newgrf_desc, editable, show_params, exec_changes, config);
 
}
src/news_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file news_gui.cpp GUI functions related to news messages. */
 

	
 
#include "stdafx.h"
 
#include "openttd.h"
 
#include "gui.h"
 
#include "window_gui.h"
 
#include "viewport_func.h"
 
#include "news_type.h"
 
#include "gfx_func.h"
 
#include "strings_func.h"
 
#include "window_func.h"
 
#include "date_func.h"
 
#include "vehicle_base.h"
 
#include "vehicle_func.h"
 
#include "station_base.h"
 
#include "industry.h"
 
#include "town.h"
 
#include "sound_func.h"
 
#include "string_func.h"
 
#include "widgets/dropdown_func.h"
 
#include "statusbar_gui.h"
 
#include "company_manager_face.h"
 
#include "company_func.h"
 
#include "engine_gui.h"
 

	
 
#include "table/strings.h"
 

	
 
NewsItem _statusbar_news_item;
 
bool _news_ticker_sound; ///< Make a ticker sound when a news item is published.
 

	
 
static uint MIN_NEWS_AMOUNT = 30;           ///< prefered minimum amount of news messages
 
static uint _total_news = 0;                ///< current number of news items
 
static NewsItem *_oldest_news = NULL;       ///< head of news items queue
 
static NewsItem *_latest_news = NULL;       ///< tail of news items queue
 

	
 
/** Forced news item.
 
 * Users can force an item by accessing the history or "last message".
 
 * If the message being shown was forced by the user, a pointer is stored
 
 * in _forced_news. Otherwise, \a _forced_news variable is NULL. */
 
static NewsItem *_forced_news = NULL;       ///< item the user has asked for
 

	
 
/** Current news item (last item shown regularly). */
 
static NewsItem *_current_news = NULL;
 

	
 

	
 
/**
 
 * Get the position a news-reference is referencing.
 
 * @param reftype The type of reference.
 
 * @param ref     The reference.
 
 * @return A tile for the referenced object, or INVALID_TILE if none.
 
 */
 
static TileIndex GetReferenceTile(NewsReferenceType reftype, uint32 ref)
 
{
 
	switch (reftype) {
 
		case NR_TILE:     return (TileIndex)ref;
 
		case NR_STATION:  return Station::Get((StationID)ref)->xy;
 
		case NR_INDUSTRY: return Industry::Get((IndustryID)ref)->xy + TileDiffXY(1, 1);
 
		case NR_TOWN:     return Town::Get((TownID)ref)->xy;
 
		default:          return INVALID_TILE;
 
	}
 
}
 

	
 
/** Widget numbers of the news display windows. */
 
enum NewsTypeWidgets {
 
	NTW_PANEL,       ///< The news item background panel.
 
	NTW_TITLE,       ///< Title of the company news.
 
	NTW_HEADLINE,    ///< The news headline.
 
	NTW_CLOSEBOX,    ///< Close the window.
 
	NTW_DATE,        ///< Date of the news item.
 
	NTW_CAPTION,     ///< Title bar of the window. Only used in small news items.
 
	NTW_INSET,       ///< Inset around the viewport in the window. Only used in small news items.
 
	NTW_VIEWPORT,    ///< Viewport in the window.
 
	NTW_COMPANY_MSG, ///< Message in company news items.
 
	NTW_MESSAGE,     ///< Space for displaying the message. Only used in small news items.
 
	NTW_MGR_FACE,    ///< Face of the manager.
 
	NTW_MGR_NAME,    ///< Name of the manager.
 
	NTW_VEH_TITLE,   ///< Vehicle new title.
 
	NTW_VEH_BKGND,   ///< Dark background of new vehicle news.
 
	NTW_VEH_NAME,    ///< Name of the new vehicle.
 
	NTW_VEH_SPR,     ///< Graphical display of the new vehicle.
 
	NTW_VEH_INFO,    ///< Some technical data of the new vehicle.
 
};
 

	
 
/* Normal news items. */
 
static const NWidgetPart _nested_normal_news_widgets[] = {
 
	NWidget(WWT_PANEL, COLOUR_WHITE, NTW_PANEL),
 
		NWidget(NWID_HORIZONTAL), SetPadding(1, 1, 0, 1),
 
			NWidget(WWT_TEXT, COLOUR_WHITE, NTW_CLOSEBOX), SetDataTip(STR_SILVER_CROSS, STR_NULL), SetPadding(0, 0, 0, 1),
 
			NWidget(NWID_SPACER), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
			NWidget(NWID_VERTICAL),
 
				NWidget(WWT_LABEL, COLOUR_WHITE, NTW_DATE), SetDataTip(STR_DATE_LONG_SMALL, STR_NULL),
 
				NWidget(NWID_SPACER), SetFill(false, true),
 
				NWidget(NWID_SPACER), SetFill(0, 1),
 
			EndContainer(),
 
		EndContainer(),
 
		NWidget(WWT_EMPTY, COLOUR_WHITE, NTW_MESSAGE), SetMinimalSize(428, 154), SetPadding(0, 1, 1, 1),
 
	EndContainer(),
 
};
 

	
 
static WindowDesc _normal_news_desc(
 
	WDP_CENTER, 476, 430, 170,
 
	WC_NEWS_WINDOW, WC_NONE,
 
	WDF_DEF_WIDGET,
 
	_nested_normal_news_widgets, lengthof(_nested_normal_news_widgets)
 
);
 

	
 
/* New vehicles news items. */
 
static const NWidgetPart _nested_vehicle_news_widgets[] = {
 
	NWidget(WWT_PANEL, COLOUR_WHITE, NTW_PANEL),
 
		NWidget(NWID_HORIZONTAL), SetPadding(1, 1, 0, 1),
 
			NWidget(NWID_VERTICAL),
 
				NWidget(WWT_TEXT, COLOUR_WHITE, NTW_CLOSEBOX), SetDataTip(STR_SILVER_CROSS, STR_NULL), SetPadding(0, 0, 0, 1),
 
				NWidget(NWID_SPACER), SetFill(false, true),
 
				NWidget(NWID_SPACER), SetFill(0, 1),
 
			EndContainer(),
 
			NWidget(WWT_LABEL, COLOUR_WHITE, NTW_VEH_TITLE), SetFill(true, true), SetMinimalSize(419, 55), SetDataTip(STR_EMPTY, STR_NULL),
 
			NWidget(WWT_LABEL, COLOUR_WHITE, NTW_VEH_TITLE), SetFill(1, 1), SetMinimalSize(419, 55), SetDataTip(STR_EMPTY, STR_NULL),
 
		EndContainer(),
 
		NWidget(WWT_PANEL, COLOUR_WHITE, NTW_VEH_BKGND), SetPadding(0, 25, 1, 25),
 
			NWidget(NWID_VERTICAL),
 
				NWidget(WWT_EMPTY, INVALID_COLOUR, NTW_VEH_NAME), SetMinimalSize(369, 33), SetFill(true, false),
 
				NWidget(WWT_EMPTY, INVALID_COLOUR, NTW_VEH_SPR),  SetMinimalSize(369, 32), SetFill(true, false),
 
				NWidget(WWT_EMPTY, INVALID_COLOUR, NTW_VEH_INFO), SetMinimalSize(369, 46), SetFill(true, false),
 
				NWidget(WWT_EMPTY, INVALID_COLOUR, NTW_VEH_NAME), SetMinimalSize(369, 33), SetFill(1, 0),
 
				NWidget(WWT_EMPTY, INVALID_COLOUR, NTW_VEH_SPR),  SetMinimalSize(369, 32), SetFill(1, 0),
 
				NWidget(WWT_EMPTY, INVALID_COLOUR, NTW_VEH_INFO), SetMinimalSize(369, 46), SetFill(1, 0),
 
			EndContainer(),
 
		EndContainer(),
 
	EndContainer(),
 
};
 

	
 
static WindowDesc _vehicle_news_desc(
 
	WDP_CENTER, 476, 430, 170,
 
	WC_NEWS_WINDOW, WC_NONE,
 
	WDF_DEF_WIDGET,
 
	_nested_vehicle_news_widgets, lengthof(_nested_vehicle_news_widgets)
 
);
 

	
 
/* Company news items. */
 
static const NWidgetPart _nested_company_news_widgets[] = {
 
	NWidget(WWT_PANEL, COLOUR_WHITE, NTW_PANEL),
 
		NWidget(NWID_HORIZONTAL), SetPadding(1, 1, 0, 1),
 
			NWidget(NWID_VERTICAL),
 
				NWidget(WWT_TEXT, COLOUR_WHITE, NTW_CLOSEBOX), SetDataTip(STR_SILVER_CROSS, STR_NULL), SetPadding(0, 0, 0, 1),
 
				NWidget(NWID_SPACER), SetFill(false, true),
 
				NWidget(NWID_SPACER), SetFill(0, 1),
 
			EndContainer(),
 
			NWidget(WWT_LABEL, COLOUR_WHITE, NTW_TITLE), SetFill(true, true), SetMinimalSize(410, 20), SetDataTip(STR_EMPTY, STR_NULL),
 
			NWidget(WWT_LABEL, COLOUR_WHITE, NTW_TITLE), SetFill(1, 1), SetMinimalSize(410, 20), SetDataTip(STR_EMPTY, STR_NULL),
 
		EndContainer(),
 
		NWidget(NWID_HORIZONTAL), SetPadding(0, 1, 1, 1),
 
			NWidget(NWID_VERTICAL),
 
				NWidget(WWT_EMPTY, COLOUR_WHITE, NTW_MGR_FACE), SetMinimalSize(93, 119), SetPadding(2, 6, 2, 1),
 
				NWidget(NWID_HORIZONTAL),
 
					NWidget(WWT_EMPTY, COLOUR_WHITE, NTW_MGR_NAME), SetMinimalSize(93, 24), SetPadding(0, 0, 0, 1),
 
					NWidget(NWID_SPACER), SetFill(true, false),
 
					NWidget(NWID_SPACER), SetFill(1, 0),
 
				EndContainer(),
 
				NWidget(NWID_SPACER), SetFill(false, true),
 
				NWidget(NWID_SPACER), SetFill(0, 1),
 
			EndContainer(),
 
			NWidget(WWT_EMPTY, COLOUR_WHITE, NTW_COMPANY_MSG), SetFill(true, true), SetMinimalSize(328, 150),
 
			NWidget(WWT_EMPTY, COLOUR_WHITE, NTW_COMPANY_MSG), SetFill(1, 1), SetMinimalSize(328, 150),
 
		EndContainer(),
 
	EndContainer(),
 
};
 

	
 
static WindowDesc _company_news_desc(
 
	WDP_CENTER, 476, 430, 170,
 
	WC_NEWS_WINDOW, WC_NONE,
 
	WDF_DEF_WIDGET,
 
	_nested_company_news_widgets, lengthof(_nested_company_news_widgets)
 
);
 

	
 
/* Thin news items. */
 
static const NWidgetPart _nested_thin_news_widgets[] = {
 
	NWidget(WWT_PANEL, COLOUR_WHITE, NTW_PANEL),
 
		NWidget(NWID_HORIZONTAL), SetPadding(1, 1, 0, 1),
 
			NWidget(WWT_TEXT, COLOUR_WHITE, NTW_CLOSEBOX), SetDataTip(STR_SILVER_CROSS, STR_NULL), SetPadding(0, 0, 0, 1),
 
			NWidget(NWID_SPACER), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
			NWidget(NWID_VERTICAL),
 
				NWidget(WWT_LABEL, COLOUR_WHITE, NTW_DATE), SetDataTip(STR_DATE_LONG_SMALL, STR_NULL),
 
				NWidget(NWID_SPACER), SetFill(false, true),
 
				NWidget(NWID_SPACER), SetFill(0, 1),
 
			EndContainer(),
 
		EndContainer(),
 
		NWidget(WWT_EMPTY, COLOUR_WHITE, NTW_MESSAGE), SetMinimalSize(428, 48), SetFill(true, false), SetPadding(0, 1, 0, 1),
 
		NWidget(WWT_EMPTY, COLOUR_WHITE, NTW_MESSAGE), SetMinimalSize(428, 48), SetFill(1, 0), SetPadding(0, 1, 0, 1),
 
		NWidget(NWID_VIEWPORT, INVALID_COLOUR, NTW_VIEWPORT), SetMinimalSize(426, 70), SetPadding(1, 2, 2, 2),
 
	EndContainer(),
 
};
 

	
 
static WindowDesc _thin_news_desc(
 
	WDP_CENTER, 476, 430, 130,
 
	WC_NEWS_WINDOW, WC_NONE,
 
	WDF_DEF_WIDGET,
 
	_nested_thin_news_widgets, lengthof(_nested_thin_news_widgets)
 
);
 

	
 
/* Small news items. */
 
static NWidgetPart _nested_small_news_widgets[] = {
 
	/* Caption + close box */
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_LIGHT_BLUE, NTW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_LIGHT_BLUE, NTW_CAPTION), SetDataTip(STR_NEWS_MESSAGE_CAPTION, STR_NULL),
 
	EndContainer(),
 

	
 
	/* Main part */
 
	NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, NTW_HEADLINE),
 
		NWidget(WWT_INSET, COLOUR_LIGHT_BLUE, NTW_INSET), SetPadding(2, 2, 2, 2),
 
			NWidget(NWID_VIEWPORT, INVALID_COLOUR, NTW_VIEWPORT), SetPadding(1, 1, 1, 1), SetMinimalSize(274, 47), SetFill(true, false),
 
			NWidget(NWID_VIEWPORT, INVALID_COLOUR, NTW_VIEWPORT), SetPadding(1, 1, 1, 1), SetMinimalSize(274, 47), SetFill(1, 0),
 
		EndContainer(),
 
		NWidget(WWT_EMPTY, COLOUR_WHITE, NTW_MESSAGE), SetMinimalSize(275, 20), SetFill(true, false),
 
		NWidget(WWT_EMPTY, COLOUR_WHITE, NTW_MESSAGE), SetMinimalSize(275, 20), SetFill(1, 0),
 
	EndContainer(),
 
};
 

	
 
static WindowDesc _small_news_desc(
 
	WDP_CENTER, 476, 280, 87,
 
	WC_NEWS_WINDOW, WC_NONE,
 
	WDF_DEF_WIDGET,
 
	_nested_small_news_widgets, lengthof(_nested_small_news_widgets)
 
);
 

	
 
/**
 
 * Data common to all news items of a given subtype (structure)
 
 */
 
struct NewsSubtypeData {
 
	NewsType type;         ///< News category @see NewsType
 
	NewsFlag flags;        ///< Initial NewsFlags bits @see NewsFlag
 
	WindowDesc *desc;      ///< Window description for displaying this news.
 
};
 

	
 
/**
 
 * Data common to all news items of a given subtype (actual data)
 
 */
 
static const NewsSubtypeData _news_subtype_data[] = {
 
	/* type,               display_mode, flags,                         window description,            callback */
 
	{ NT_ARRIVAL_COMPANY,  (NF_NO_TRANSPARENT | NF_SHADE), &_thin_news_desc    }, ///< NS_ARRIVAL_COMPANY
 
	{ NT_ARRIVAL_OTHER,    (NF_NO_TRANSPARENT | NF_SHADE), &_thin_news_desc    }, ///< NS_ARRIVAL_OTHER
 
	{ NT_ACCIDENT,         (NF_NO_TRANSPARENT | NF_SHADE), &_thin_news_desc    }, ///< NS_ACCIDENT
 
	{ NT_COMPANY_INFO,     NF_NONE,                        &_company_news_desc }, ///< NS_COMPANY_TROUBLE
 
	{ NT_COMPANY_INFO,     NF_NONE,                        &_company_news_desc }, ///< NS_COMPANY_MERGER
 
	{ NT_COMPANY_INFO,     NF_NONE,                        &_company_news_desc }, ///< NS_COMPANY_BANKRUPT
 
	{ NT_COMPANY_INFO,     NF_NONE,                        &_company_news_desc }, ///< NS_COMPANY_NEW
 
	{ NT_INDUSTRY_OPEN,    (NF_NO_TRANSPARENT | NF_SHADE), &_thin_news_desc    }, ///< NS_INDUSTRY_OPEN
 
	{ NT_INDUSTRY_CLOSE,   (NF_NO_TRANSPARENT | NF_SHADE), &_thin_news_desc    }, ///< NS_INDUSTRY_CLOSE
 
	{ NT_ECONOMY,          NF_NONE,                        &_normal_news_desc  }, ///< NS_ECONOMY
 
	{ NT_INDUSTRY_COMPANY, (NF_NO_TRANSPARENT | NF_SHADE), &_thin_news_desc    }, ///< NS_INDUSTRY_COMPANY
 
	{ NT_INDUSTRY_OTHER,   (NF_NO_TRANSPARENT | NF_SHADE), &_thin_news_desc    }, ///< NS_INDUSTRY_OTHER
 
	{ NT_INDUSTRY_NOBODY,  (NF_NO_TRANSPARENT | NF_SHADE), &_thin_news_desc    }, ///< NS_INDUSTRY_NOBODY
 
	{ NT_ADVICE,           NF_INCOLOUR,                    &_small_news_desc   }, ///< NS_ADVICE
 
	{ NT_NEW_VEHICLES,     NF_NONE,                        &_vehicle_news_desc }, ///< NS_NEW_VEHICLES
 
	{ NT_ACCEPTANCE,       NF_INCOLOUR,                    &_small_news_desc   }, ///< NS_ACCEPTANCE
 
	{ NT_SUBSIDIES,        NF_NONE,                        &_normal_news_desc  }, ///< NS_SUBSIDIES
 
	{ NT_GENERAL,          NF_NONE,                        &_normal_news_desc  }, ///< NS_GENERAL
 
};
 

	
 
assert_compile(lengthof(_news_subtype_data) == NS_END);
 

	
 
/**
 
 * Per-NewsType data
 
 */
 
NewsTypeData _news_type_data[] = {
 
	/*            name,              age, sound,           description */
 
	NewsTypeData("arrival_player",    60, SND_1D_APPLAUSE, STR_NEWS_MESSAGE_TYPE_ARRIVAL_OF_FIRST_VEHICLE_OWN       ),  ///< NT_ARRIVAL_COMPANY
 
	NewsTypeData("arrival_other",     60, SND_1D_APPLAUSE, STR_NEWS_MESSAGE_TYPE_ARRIVAL_OF_FIRST_VEHICLE_OTHER     ),  ///< NT_ARRIVAL_OTHER
 
	NewsTypeData("accident",          90, SND_BEGIN,       STR_NEWS_MESSAGE_TYPE_ACCIDENTS_DISASTERS                ),  ///< NT_ACCIDENT
 
	NewsTypeData("company_info",      60, SND_BEGIN,       STR_NEWS_MESSAGE_TYPE_COMPANY_INFORMATION                ),  ///< NT_COMPANY_INFO
 
	NewsTypeData("open",              90, SND_BEGIN,       STR_NEWS_MESSAGE_TYPE_INDUSTRY_OPEN                      ),  ///< NT_INDUSTRY_OPEN
 
	NewsTypeData("close",             90, SND_BEGIN,       STR_NEWS_MESSAGE_TYPE_INDUSTRY_CLOSE                     ),  ///< NT_INDUSTRY_CLOSE
 
	NewsTypeData("economy",           30, SND_BEGIN,       STR_NEWS_MESSAGE_TYPE_ECONOMY_CHANGES                    ),  ///< NT_ECONOMY
 
	NewsTypeData("production_player", 30, SND_BEGIN,       STR_NEWS_MESSAGE_TYPE_INDUSTRY_CHANGES_SERVED_BY_COMPANY ),  ///< NT_INDUSTRY_COMPANY
 
	NewsTypeData("production_other",  30, SND_BEGIN,       STR_NEWS_MESSAGE_TYPE_INDUSTRY_CHANGES_SERVED_BY_OTHER   ),  ///< NT_INDUSTRY_OTHER
 
	NewsTypeData("production_nobody", 30, SND_BEGIN,       STR_NEWS_MESSAGE_TYPE_INDUSTRY_CHANGES_UNSERVED          ),  ///< NT_INDUSTRY_NOBODY
 
	NewsTypeData("advice",           150, SND_BEGIN,       STR_NEWS_MESSAGE_TYPE_ADVICE_INFORMATION_ON_COMPANY      ),  ///< NT_ADVICE
 
	NewsTypeData("new_vehicles",      30, SND_1E_OOOOH,    STR_NEWS_MESSAGE_TYPE_NEW_VEHICLES                       ),  ///< NT_NEW_VEHICLES
 
	NewsTypeData("acceptance",        90, SND_BEGIN,       STR_NEWS_MESSAGE_TYPE_CHANGES_OF_CARGO_ACCEPTANCE        ),  ///< NT_ACCEPTANCE
 
	NewsTypeData("subsidies",        180, SND_BEGIN,       STR_NEWS_MESSAGE_TYPE_SUBSIDIES                          ),  ///< NT_SUBSIDIES
 
	NewsTypeData("general",           60, SND_BEGIN,       STR_NEWS_MESSAGE_TYPE_GENERAL_INFORMATION                ),  ///< NT_GENERAL
 
};
 

	
 
assert_compile(lengthof(_news_type_data) == NT_END);
 

	
 
/** Window class displaying a news item. */
 
struct NewsWindow : Window {
 
	uint16 chat_height;   ///< Height of the chat window.
 
	uint16 status_height; ///< Height of the status bar window
 
	NewsItem *ni;         ///< News item to display.
 
	static uint duration; ///< Remaining time for showing current news message (may only be accessed while a news item is displayed).
 

	
 
	NewsWindow(const WindowDesc *desc, NewsItem *ni) : Window(), ni(ni)
 
	{
 
		NewsWindow::duration = 555;
 
		const Window *w = FindWindowById(WC_SEND_NETWORK_MSG, 0);
 
		this->chat_height = (w != NULL) ? w->height : 0;
 
		this->status_height = FindWindowById(WC_STATUS_BAR, 0)->height;
 

	
 
		this->flags4 |= WF_DISABLE_VP_SCROLL;
 

	
 
		this->CreateNestedTree(desc);
 
		switch (this->ni->subtype) {
 
			case NS_COMPANY_TROUBLE:
 
				this->GetWidget<NWidgetCore>(NTW_TITLE)->widget_data = STR_NEWS_COMPANY_IN_TROUBLE_TITLE;
 
				break;
 

	
 
			case NS_COMPANY_MERGER:
 
				this->GetWidget<NWidgetCore>(NTW_TITLE)->widget_data = STR_NEWS_COMPANY_MERGER_TITLE;
 
				break;
 

	
 
			case NS_COMPANY_BANKRUPT:
 
				this->GetWidget<NWidgetCore>(NTW_TITLE)->widget_data = STR_NEWS_COMPANY_BANKRUPT_TITLE;
 
				break;
 

	
 
			case NS_COMPANY_NEW:
 
				this->GetWidget<NWidgetCore>(NTW_TITLE)->widget_data = STR_NEWS_COMPANY_LAUNCH_TITLE;
 
				break;
 

	
 
			default:
 
				break;
 
		}
 
		this->FinishInitNested(desc, 0);
 

	
 
		/* Initialize viewport if it exists. */
 
		NWidgetViewport *nvp = this->GetWidget<NWidgetViewport>(NTW_VIEWPORT);
 
		if (nvp != NULL) {
 
			nvp->InitializeViewport(this, ni->reftype1 == NR_VEHICLE ? 0x80000000 | ni->ref1 : GetReferenceTile(ni->reftype1, ni->ref1), ZOOM_LVL_NEWS);
 
			if (this->ni->flags & NF_NO_TRANSPARENT) nvp->disp_flags |= ND_NO_TRANSPARENCY;
 
			if ((this->ni->flags & NF_INCOLOUR) == 0) {
 
				nvp->disp_flags |= ND_SHADE_GREY;
 
			} else if (this->ni->flags & NF_SHADE) {
 
				nvp->disp_flags |= ND_SHADE_DIMMED;
 
			}
 
		}
 
	}
 

	
 
	void DrawNewsBorder(const Rect &r) const
 
	{
 
		GfxFillRect(r.left,  r.top,    r.right, r.bottom, 0xF);
 

	
 
		GfxFillRect(r.left,  r.top,    r.left,  r.bottom, 0xD7);
 
		GfxFillRect(r.right, r.top,    r.right, r.bottom, 0xD7);
 
		GfxFillRect(r.left,  r.top,    r.right, r.top,    0xD7);
 
		GfxFillRect(r.left,  r.bottom, r.right, r.bottom, 0xD7);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		StringID str = STR_NULL;
 
		switch (widget) {
 
			case NTW_MESSAGE:
 
				CopyInDParam(0, this->ni->params, lengthof(this->ni->params));
 
				str = this->ni->string_id;
 
				break;
 

	
 
			case NTW_COMPANY_MSG:
 
				str = this->GetCompanyMessageString();
 
				break;
 

	
 
			case NTW_VEH_NAME:
 
			case NTW_VEH_TITLE:
 
				str = this->GetNewVehicleMessageString(widget);
 
				break;
 

	
 
			case NTW_VEH_INFO: {
 
				assert(this->ni->reftype1 == NR_ENGINE);
 
				EngineID engine = this->ni->ref1;
 
				str = GetEngineInfoString(engine);
 
				break;
 
			}
 
			default:
 
				return; // Do nothing
 
		}
 

	
 
		/* Update minimal size with length of the multi-line string. */
 
		Dimension d = *size;
 
		d.width = (d.width >= padding.width) ? d.width - padding.width : 0;
 
		d.height = (d.height >= padding.height) ? d.height - padding.height : 0;
 
		d = GetStringMultiLineBoundingBox(str, d);
 
		d.width += padding.width;
 
		d.height += padding.height;
 
		*size = maxdim(*size, d);
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		if (widget == NTW_DATE) SetDParam(0, this->ni->date);
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case NTW_PANEL:
 
				this->DrawNewsBorder(r);
 
				return;
 

	
 
			case NTW_MESSAGE:
 
				CopyInDParam(0, this->ni->params, lengthof(this->ni->params));
 
				DrawStringMultiLine(r.left + 2, r.right - 2, r.top, r.bottom, this->ni->string_id, TC_FROMSTRING, SA_CENTER);
 
				break;
 

	
 
			case NTW_MGR_FACE: {
 
				const CompanyNewsInformation *cni = (const CompanyNewsInformation*)this->ni->free_data;
 
				DrawCompanyManagerFace(cni->face, cni->colour, r.left, r.top);
 
				GfxFillRect(r.left + 1, r.top, r.left + 1 + 91, r.top + 118, PALETTE_TO_STRUCT_GREY, FILLRECT_RECOLOUR);
 
				break;
 
			}
 
			case NTW_MGR_NAME: {
 
				const CompanyNewsInformation *cni = (const CompanyNewsInformation*)this->ni->free_data;
 
				SetDParamStr(0, cni->president_name);
 
				DrawStringMultiLine(r.left, r.right, r.top, r.bottom, STR_JUST_RAW_STRING, TC_FROMSTRING, SA_CENTER);
 
				break;
 
			}
 
			case NTW_COMPANY_MSG:
 
				DrawStringMultiLine(r.left, r.right, r.top, r.bottom, this->GetCompanyMessageString(), TC_FROMSTRING, SA_CENTER);
 
				break;
 

	
 
			case NTW_VEH_BKGND:
 
				GfxFillRect(r.left, r.top, r.right, r.bottom, 10);
 
				break;
 

	
 
			case NTW_VEH_NAME:
 
			case NTW_VEH_TITLE:
 
				DrawStringMultiLine(r.left, r.right, r.top, r.bottom, this->GetNewVehicleMessageString(widget), TC_FROMSTRING, SA_CENTER);
 
				break;
 

	
 
			case NTW_VEH_SPR: {
 
				assert(this->ni->reftype1 == NR_ENGINE);
 
				EngineID engine = this->ni->ref1;
 
				DrawVehicleEngine(r.left, r.right, (r.left + r.right) / 2, (r.top + r.bottom) / 2, engine, GetEnginePalette(engine, _local_company));
 
				GfxFillRect(r.left, r.top, r.right, r.bottom, PALETTE_TO_STRUCT_GREY, FILLRECT_RECOLOUR);
 
				break;
 
			}
 
			case NTW_VEH_INFO: {
 
				assert(this->ni->reftype1 == NR_ENGINE);
 
				EngineID engine = this->ni->ref1;
 
				DrawStringMultiLine(r.left, r.right, r.top, r.bottom, GetEngineInfoString(engine), TC_FROMSTRING, SA_CENTER);
 
				break;
 
			}
 
		}
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case NTW_CLOSEBOX:
 
				NewsWindow::duration = 0;
 
				delete this;
 
				_forced_news = NULL;
 
				break;
 

	
 
			case NTW_CAPTION:
 
			case NTW_VIEWPORT:
 
				break; // Ignore clicks
 

	
 
			default:
 
				if (this->ni->reftype1 == NR_VEHICLE) {
 
					const Vehicle *v = Vehicle::Get(this->ni->ref1);
 
					ScrollMainWindowTo(v->x_pos, v->y_pos, v->z_pos);
 
				} else {
 
					TileIndex tile1 = GetReferenceTile(this->ni->reftype1, this->ni->ref1);
 
					TileIndex tile2 = GetReferenceTile(this->ni->reftype2, this->ni->ref2);
 
					if (_ctrl_pressed) {
 
						if (tile1 != INVALID_TILE) ShowExtraViewPortWindow(tile1);
 
						if (tile2 != INVALID_TILE) ShowExtraViewPortWindow(tile2);
 
					} else {
 
						if ((tile1 == INVALID_TILE || !ScrollMainWindowToTile(tile1)) && tile2 != INVALID_TILE) {
 
							ScrollMainWindowToTile(tile2);
 
						}
 
					}
 
				}
 
				break;
 
		}
 
	}
 

	
 
	virtual EventState OnKeyPress(uint16 key, uint16 keycode)
 
	{
 
		if (keycode == WKC_SPACE) {
 
			/* Don't continue. */
 
			delete this;
 
			return ES_HANDLED;
 
		}
 
		return ES_NOT_HANDLED;
 
	}
 

	
 
	virtual void OnInvalidateData(int data)
 
	{
 
		/* The chatbar has notified us that is was either created or closed */
 
		this->chat_height = data;
 
	}
 

	
 
	virtual void OnTick()
 
	{
 
		/* Scroll up newsmessages from the bottom in steps of 4 pixels */
 
		int y = max(this->top - 4, _screen.height - this->height - this->status_height - this->chat_height);
 
		if (y == this->top) return;
 

	
 
		if (this->viewport != NULL) this->viewport->top += y - this->top;
 

	
 
		int diff = Delta(this->top, y);
 
		this->top = y;
 

	
 
		SetDirtyBlocks(this->left, this->top - diff, this->left + this->width, this->top + this->height);
 
	}
 

	
 
private:
 
	StringID GetCompanyMessageString() const
 
	{
 
		switch (this->ni->subtype) {
 
			case NS_COMPANY_TROUBLE:
 
				SetDParam(0, this->ni->params[2]);
 
				return STR_NEWS_COMPANY_IN_TROUBLE_DESCRIPTION;
 

	
 
			case NS_COMPANY_MERGER:
 
				SetDParam(0, this->ni->params[2]);
 
				SetDParam(1, this->ni->params[3]);
 
				SetDParam(2, this->ni->params[4]);
 
				return this->ni->params[4] == 0 ? STR_NEWS_MERGER_TAKEOVER_TITLE : STR_NEWS_COMPANY_MERGER_DESCRIPTION;
 

	
 
			case NS_COMPANY_BANKRUPT:
 
				SetDParam(0, this->ni->params[2]);
 
				return STR_NEWS_COMPANY_BANKRUPT_DESCRIPTION;
 

	
 
			case NS_COMPANY_NEW:
 
				SetDParam(0, this->ni->params[2]);
 
				SetDParam(1, this->ni->params[3]);
 
				return STR_NEWS_COMPANY_LAUNCH_DESCRIPTION;
 

	
 
			default:
 
				NOT_REACHED();
 
		}
 
	}
 

	
 
	StringID GetNewVehicleMessageString(int widget) const
 
	{
 
		assert(this->ni->reftype1 == NR_ENGINE);
 
		EngineID engine = this->ni->ref1;
 

	
 
		switch (widget) {
 
			case NTW_VEH_TITLE:
 
				SetDParam(0, GetEngineCategoryName(engine));
 
				return STR_NEWS_NEW_VEHICLE_NOW_AVAILABLE;
 

	
 
			case NTW_VEH_NAME:
 
				SetDParam(0, engine);
 
				return STR_NEWS_NEW_VEHICLE_TYPE;
 

	
 
			default:
 
				NOT_REACHED();
 
		}
 
	}
 
};
 

	
 
/* static */ uint NewsWindow::duration = 0; // Instance creation.
 

	
 

	
 
/** Open up an own newspaper window for the news item */
 
static void ShowNewspaper(NewsItem *ni)
 
{
 
	SoundFx sound = _news_type_data[_news_subtype_data[ni->subtype].type].sound;
 
	if (sound != 0) SndPlayFx(sound);
 

	
 
	WindowDesc *desc = _news_subtype_data[ni->subtype].desc;
 
	desc->top = _screen.height;
 
	new NewsWindow(desc, ni);
 
}
 

	
 
/** Show news item in the ticker */
 
static void ShowTicker(const NewsItem *ni)
 
{
 
	if (_news_ticker_sound) SndPlayFx(SND_16_MORSE);
 

	
 
	_statusbar_news_item = *ni;
 
	InvalidateWindowData(WC_STATUS_BAR, 0, SBI_SHOW_TICKER);
 
}
 

	
 
/** Initialize the news-items data structures */
 
void InitNewsItemStructs()
 
{
 
	for (NewsItem *ni = _oldest_news; ni != NULL; ) {
 
		NewsItem *next = ni->next;
 
		delete ni;
 
		ni = next;
 
	}
 

	
 
	_total_news = 0;
 
	_oldest_news = NULL;
 
	_latest_news = NULL;
 
	_forced_news = NULL;
 
	_current_news = NULL;
 
	NewsWindow::duration = 0;
 
}
 

	
 
/**
 
 * Are we ready to show another news item?
 
 * Only if nothing is in the newsticker and no newspaper is displayed
 
 */
 
static bool ReadyForNextItem()
 
{
 
	NewsItem *ni = _forced_news == NULL ? _current_news : _forced_news;
 
	if (ni == NULL) return true;
 

	
 
	/* Ticker message
 
	 * Check if the status bar message is still being displayed? */
 
	if (IsNewsTickerShown()) return false;
 

	
 
	/* Newspaper message, decrement duration counter */
 
	if (NewsWindow::duration != 0) NewsWindow::duration--;
 

	
 
	/* neither newsticker nor newspaper are running */
 
	return (NewsWindow::duration == 0 || FindWindowById(WC_NEWS_WINDOW, 0) == NULL);
 
}
 

	
 
/** Move to the next news item */
 
static void MoveToNextItem()
 
{
 
	InvalidateWindowData(WC_STATUS_BAR, 0, SBI_NEWS_DELETED); // invalidate the statusbar
 
	DeleteWindowById(WC_NEWS_WINDOW, 0); // close the newspapers window if shown
 
	_forced_news = NULL;
 

	
 
	/* if we're not at the last item, then move on */
 
	if (_current_news != _latest_news) {
 
		_current_news = (_current_news == NULL) ? _oldest_news : _current_news->next;
 
		NewsItem *ni = _current_news;
 
		const NewsType type = _news_subtype_data[ni->subtype].type;
 

	
 
		/* check the date, don't show too old items */
 
		if (_date - _news_type_data[type].age > ni->date) return;
 

	
 
		switch (_news_type_data[type].display) {
 
			default: NOT_REACHED();
 
			case ND_OFF: // Off - show nothing only a small reminder in the status bar
 
				InvalidateWindowData(WC_STATUS_BAR, 0, SBI_SHOW_REMINDER);
 
				break;
 

	
 
			case ND_SUMMARY: // Summary - show ticker
 
				ShowTicker(ni);
 
				break;
 

	
 
			case ND_FULL: // Full - show newspaper
 
				ShowNewspaper(ni);
 
				break;
 
		}
 
	}
 
}
 

	
 
/**
 
 * Add a new newsitem to be shown.
 
 * @param string String to display
 
 * @param subtype news category, any of the NewsSubtype enums (NS_)
 
 * @param reftype1 Type of ref1
 
 * @param ref1     Reference 1 to some object: Used for a possible viewport, scrolling after clicking on the news, and for deleteing the news when the object is deleted.
 
 * @param reftype2 Type of ref2
 
 * @param ref2     Reference 2 to some object: Used for scrolling after clicking on the news, and for deleteing the news when the object is deleted.
 
 * @param free_data Pointer to data that must be freed once the news message is cleared
 
 *
 
 * @see NewsSubype
 
 */
 
void AddNewsItem(StringID string, NewsSubtype subtype, NewsReferenceType reftype1, uint32 ref1, NewsReferenceType reftype2, uint32 ref2, void *free_data)
 
{
 
	if (_game_mode == GM_MENU) return;
 

	
 
	/* Create new news item node */
 
	NewsItem *ni = new NewsItem;
 

	
 
	ni->string_id = string;
 
	ni->subtype = subtype;
 
	ni->flags = _news_subtype_data[subtype].flags;
 

	
 
	/* show this news message in colour? */
 
	if (_cur_year >= _settings_client.gui.coloured_news_year) ni->flags |= NF_INCOLOUR;
 

	
 
	ni->reftype1 = reftype1;
 
	ni->reftype2 = reftype2;
 
	ni->ref1 = ref1;
 
	ni->ref2 = ref2;
 
	ni->free_data = free_data;
 
	ni->date = _date;
 
	CopyOutDParam(ni->params, 0, lengthof(ni->params));
 

	
 
	if (_total_news++ == 0) {
 
		assert(_oldest_news == NULL);
 
		_oldest_news = ni;
 
		ni->prev = NULL;
 
	} else {
 
		assert(_latest_news->next == NULL);
 
		_latest_news->next = ni;
 
		ni->prev = _latest_news;
 
	}
 

	
 
	ni->next = NULL;
 
	_latest_news = ni;
 

	
 
	SetWindowDirty(WC_MESSAGE_HISTORY, 0);
 
}
 

	
 
/** Delete a news item from the queue */
 
static void DeleteNewsItem(NewsItem *ni)
 
{
 
	if (_forced_news == ni || _current_news == ni) {
 
		/* about to remove the currently forced item (shown as newspapers) ||
 
		 * about to remove the currently displayed item (newspapers, ticker, or just a reminder) */
 
		MoveToNextItem();
 
	}
 

	
 
	/* delete item */
 

	
 
	if (ni->prev != NULL) {
 
		ni->prev->next = ni->next;
 
	} else {
 
		assert(_oldest_news == ni);
 
		_oldest_news = ni->next;
 
	}
 

	
 
	if (ni->next != NULL) {
 
		ni->next->prev = ni->prev;
 
	} else {
 
		assert(_latest_news == ni);
 
		_latest_news = ni->prev;
 
	}
 

	
 
	free(ni->free_data);
 

	
 
	if (_current_news == ni) _current_news = ni->prev;
 
	_total_news--;
 
	delete ni;
 

	
 
	SetWindowDirty(WC_MESSAGE_HISTORY, 0);
 
}
 

	
 
void DeleteVehicleNews(VehicleID vid, StringID news)
 
{
 
	NewsItem *ni = _oldest_news;
 

	
 
	while (ni != NULL) {
 
		NewsItem *next = ni->next;
 
		if (((ni->reftype1 == NR_VEHICLE && ni->ref1 == vid) || (ni->reftype2 == NR_VEHICLE && ni->ref2 == vid)) &&
 
				(news == INVALID_STRING_ID || ni->string_id == news)) {
 
			DeleteNewsItem(ni);
 
		}
 
		ni = next;
 
	}
 
}
 

	
 
/** Remove news regarding given station so there are no 'unknown station now accepts Mail'
 
 * or 'First train arrived at unknown station' news items.
 
 * @param sid station to remove news about
 
 */
 
void DeleteStationNews(StationID sid)
 
{
 
	NewsItem *ni = _oldest_news;
 

	
 
	while (ni != NULL) {
 
		NewsItem *next = ni->next;
 
		if ((ni->reftype1 == NR_STATION && ni->ref1 == sid) || (ni->reftype2 == NR_STATION && ni->ref2 == sid)) {
 
			DeleteNewsItem(ni);
 
		}
 
		ni = next;
 
	}
 
}
 

	
 
/** Remove news regarding given industry
 
 * @param iid industry to remove news about
 
 */
 
void DeleteIndustryNews(IndustryID iid)
 
{
 
	NewsItem *ni = _oldest_news;
 

	
 
	while (ni != NULL) {
 
		NewsItem *next = ni->next;
 
		if ((ni->reftype1 == NR_INDUSTRY && ni->ref1 == iid) || (ni->reftype2 == NR_INDUSTRY && ni->ref2 == iid)) {
 
			DeleteNewsItem(ni);
 
		}
 
		ni = next;
 
	}
 
}
 

	
 
static void RemoveOldNewsItems()
 
{
 
	NewsItem *next;
 
	for (NewsItem *cur = _oldest_news; _total_news > MIN_NEWS_AMOUNT && cur != NULL; cur = next) {
 
		next = cur->next;
 
		if (_date - _news_type_data[_news_subtype_data[cur->subtype].type].age * _settings_client.gui.news_message_timeout > cur->date) DeleteNewsItem(cur);
 
	}
 
}
 

	
 
/**
 
 * Report a change in vehicle IDs (due to autoreplace) to affected vehicle news.
 
 * @note Viewports of currently displayed news is changed via #ChangeVehicleViewports
 
 * @param from_index the old vehicle ID
 
 * @param to_index the new vehicle ID
 
 */
 
void ChangeVehicleNews(VehicleID from_index, VehicleID to_index)
 
{
 
	for (NewsItem *ni = _oldest_news; ni != NULL; ni = ni->next) {
 
		if (ni->reftype1 == NR_VEHICLE && ni->ref1 == from_index) ni->ref1 = to_index;
 
		if (ni->reftype2 == NR_VEHICLE && ni->ref2 == from_index) ni->ref2 = to_index;
 

	
 
		/* Oh noes :(
 
		 * Autoreplace is breaking the whole news-reference concept here, as we want to keep the news,
 
		 * but do not know which DParams to change.
 
		 *
 
		 * Currently only NS_ADVICE news have vehicle IDs in their DParams.
 
		 * And all NS_ADVICE news have the ID in param 0.
 
		 */
 
		if (ni->subtype == NS_ADVICE && ni->params[0] == from_index) ni->params[0] = to_index;
 
	}
 
}
 

	
 
void NewsLoop()
 
{
 
	/* no news item yet */
 
	if (_total_news == 0) return;
 

	
 
	static byte _last_clean_month = 0;
 

	
 
	if (_last_clean_month != _cur_month) {
 
		RemoveOldNewsItems();
 
		_last_clean_month = _cur_month;
 
	}
 

	
 
	if (ReadyForNextItem()) MoveToNextItem();
 
}
 

	
 
/** Do a forced show of a specific message */
 
static void ShowNewsMessage(NewsItem *ni)
 
{
 
	assert(_total_news != 0);
 

	
 
	/* Delete the news window */
 
	DeleteWindowById(WC_NEWS_WINDOW, 0);
 

	
 
	/* setup forced news item */
 
	_forced_news = ni;
 

	
 
	if (_forced_news != NULL) {
 
		DeleteWindowById(WC_NEWS_WINDOW, 0);
 
		ShowNewspaper(ni);
 
	}
 
}
 

	
 
/** Show previous news item */
 
void ShowLastNewsMessage()
 
{
 
	if (_total_news == 0) {
 
		return;
 
	} else if (_forced_news == NULL) {
 
		/* Not forced any news yet, show the current one, unless a news window is
 
		 * open (which can only be the current one), then show the previous item */
 
		const Window *w = FindWindowById(WC_NEWS_WINDOW, 0);
 
		ShowNewsMessage((w == NULL || (_current_news == _oldest_news)) ? _current_news : _current_news->prev);
 
	} else if (_forced_news == _oldest_news) {
 
		/* We have reached the oldest news, start anew with the latest */
 
		ShowNewsMessage(_latest_news);
 
	} else {
 
		/* 'Scrolling' through news history show each one in turn */
 
		ShowNewsMessage(_forced_news->prev);
 
	}
 
}
 

	
 

	
 
/**
 
 * Draw an unformatted news message truncated to a maximum length. If
 
 * length exceeds maximum length it will be postfixed by '...'
 
 * @param left  the left most location for the string
 
 * @param right the right most location for the string
 
 * @param y position of the string
 
 * @param colour the colour the string will be shown in
 
 * @param *ni NewsItem being printed
 
 * @param maxw maximum width of string in pixels
 
 */
 
static void DrawNewsString(uint left, uint right, int y, TextColour colour, const NewsItem *ni)
 
{
 
	char buffer[512], buffer2[512];
 
	StringID str;
 

	
 
	CopyInDParam(0, ni->params, lengthof(ni->params));
 
	str = ni->string_id;
 

	
 
	GetString(buffer, str, lastof(buffer));
 
	/* Copy the just gotten string to another buffer to remove any formatting
 
	 * from it such as big fonts, etc. */
 
	const char *ptr = buffer;
 
	char *dest = buffer2;
 
	WChar c_last = '\0';
 
	for (;;) {
 
		WChar c = Utf8Consume(&ptr);
 
		if (c == 0) break;
 
		/* Make a space from a newline, but ignore multiple newlines */
 
		if (c == '\n' && c_last != '\n') {
 
			dest[0] = ' ';
 
			dest++;
 
		} else if (c == '\r') {
 
			dest[0] = dest[1] = dest[2] = dest[3] = ' ';
 
			dest += 4;
 
		} else if (IsPrintable(c)) {
 
			dest += Utf8Encode(dest, c);
 
		}
 
		c_last = c;
 
	}
 

	
 
	*dest = '\0';
 
	/* Truncate and show string; postfixed by '...' if neccessary */
 
	DrawString(left, right, y, buffer2, colour);
 
}
 

	
 
/** Widget numbers of the message history window. */
 
enum MessageHistoryWidgets {
 
	MHW_CLOSEBOX,
 
	MHW_CAPTION,
 
	MHW_STICKYBOX,
 
	MHW_BACKGROUND,
 
	MHW_SCROLLBAR,
 
	MHW_RESIZEBOX,
 
};
 

	
 
struct MessageHistoryWindow : Window {
 
	static const int top_spacing;    ///< Additional spacing at the top of the #MHW_BACKGROUND widget.
 
	static const int bottom_spacing; ///< Additional spacing at the bottom of the #MHW_BACKGROUND widget.
 

	
 
	int line_height; /// < Height of a single line in the news histoy window including spacing.
 
	int date_width;  /// < Width needed for the date part.
 

	
 
	MessageHistoryWindow(const WindowDesc *desc) : Window()
 
	{
 
		this->InitNested(desc); // Initializes 'this->line_height' and 'this->date_width'.
 
		this->OnInvalidateData(0);
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		if (widget == MHW_BACKGROUND) {
 
			this->line_height = FONT_HEIGHT_NORMAL + 2;
 
			resize->height = this->line_height;
 

	
 
			SetDParam(0, ConvertYMDToDate(ORIGINAL_MAX_YEAR, 12, 30));
 
			this->date_width = GetStringBoundingBox(STR_SHORT_DATE).width;
 

	
 
			size->height = 4 * resize->height + this->top_spacing + this->bottom_spacing; // At least 4 lines are visible.
 
			size->width = max(200u, size->width); // At least 200 pixels wide.
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->OnInvalidateData(0);
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		if (widget != MHW_BACKGROUND || _total_news == 0) return;
 

	
 
		/* Find the first news item to display. */
 
		NewsItem *ni = _latest_news;
 
		for (int n = this->vscroll.GetPosition(); n > 0; n--) {
 
			ni = ni->prev;
 
			if (ni == NULL) return;
 
		}
 

	
 
		/* Fill the widget with news items. */
 
		int y = r.top + this->top_spacing;
 
		bool rtl = _dynlang.text_dir == TD_RTL;
 
		uint date_left  = rtl ? r.right - WD_FRAMERECT_RIGHT - this->date_width : r.left + WD_FRAMERECT_LEFT;
 
		uint date_right = rtl ? r.right - WD_FRAMERECT_RIGHT : r.left + WD_FRAMERECT_LEFT + this->date_width;
 
		uint news_left  = rtl ? r.left + WD_FRAMERECT_LEFT : r.left + WD_FRAMERECT_LEFT + this->date_width + WD_FRAMERECT_RIGHT;
 
		uint news_right = rtl ? r.right - WD_FRAMERECT_RIGHT - this->date_width - WD_FRAMERECT_RIGHT : r.right - WD_FRAMERECT_RIGHT;
 
		for (int n = this->vscroll.GetCapacity(); n > 0; n--) {
 
			SetDParam(0, ni->date);
 
			DrawString(date_left, date_right, y, STR_SHORT_DATE);
 

	
 
			DrawNewsString(news_left, news_right, y, TC_WHITE, ni);
 
			y += this->line_height;
 

	
 
			ni = ni->prev;
 
			if (ni == NULL) return;
 
		}
 
	}
 

	
 
	virtual void OnInvalidateData(int data)
 
	{
 
		this->vscroll.SetCount(_total_news);
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		if (widget == MHW_BACKGROUND) {
 
			NewsItem *ni = _latest_news;
 
			if (ni == NULL) return;
 

	
 
			for (int n = (pt.y - this->GetWidget<NWidgetBase>(MHW_BACKGROUND)->pos_y - WD_FRAMERECT_TOP) / this->line_height + this->vscroll.GetPosition(); n > 0; n--) {
 
				ni = ni->prev;
 
				if (ni == NULL) return;
 
			}
 

	
 
			ShowNewsMessage(ni);
 
		}
 
	}
 

	
 
	virtual void OnResize()
 
	{
 
		this->vscroll.SetCapacity(this->GetWidget<NWidgetBase>(MHW_BACKGROUND)->current_y / this->line_height);
 
	}
 
};
 

	
 
const int MessageHistoryWindow::top_spacing = WD_FRAMERECT_TOP + 4;
 
const int MessageHistoryWindow::bottom_spacing = WD_FRAMERECT_BOTTOM;
 

	
 
static const NWidgetPart _nested_message_history[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_BROWN, MHW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_BROWN, MHW_CAPTION), SetDataTip(STR_MESSAGE_HISTORY, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_STICKYBOX, COLOUR_BROWN, MHW_STICKYBOX),
 
	EndContainer(),
 

	
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_PANEL, COLOUR_BROWN, MHW_BACKGROUND), SetMinimalSize(200, 125), SetDataTip(0x0, STR_MESSAGE_HISTORY_TOOLTIP), SetResize(1, 12),
 
		EndContainer(),
 
		NWidget(NWID_VERTICAL),
 
			NWidget(WWT_SCROLLBAR, COLOUR_BROWN, MHW_SCROLLBAR),
 
			NWidget(WWT_RESIZEBOX, COLOUR_BROWN, MHW_RESIZEBOX),
 
		EndContainer(),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _message_history_desc(
 
	240, 22, 400, 140,
 
	WC_MESSAGE_HISTORY, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE,
 
	_nested_message_history, lengthof(_nested_message_history)
 
);
 

	
 
/** Display window with news messages history */
 
void ShowMessageHistory()
 
{
 
	DeleteWindowById(WC_MESSAGE_HISTORY, 0);
 
	new MessageHistoryWindow(&_message_history_desc);
 
}
 

	
 
/** Constants in the message options window. */
 
enum MessageOptionsSpace {
 
	MOS_WIDG_PER_SETTING      = 4,  ///< Number of widgets needed for each news category, starting at widget #WIDGET_NEWSOPT_START_OPTION.
 

	
 
	MOS_LEFT_EDGE             = 6,  ///< Number of pixels between left edge of the window and the options buttons column.
 
	MOS_COLUMN_SPACING        = 4,  ///< Number of pixels between the buttons and the description columns.
 
	MOS_RIGHT_EDGE            = 6,  ///< Number of pixels between right edge of the window and the options descriptions column.
 
	MOS_BUTTON_SPACE          = 10, ///< Additional space in the button with the option value (for better looks).
 

	
 
	MOS_ABOVE_GLOBAL_SETTINGS = 6,  ///< Number of vertical pixels between the categories and the global options.
 
	MOS_BOTTOM_EDGE           = 6,  ///< Number of pixels between bottom edge of the window and bottom of the global options.
 
};
 

	
 
/** Message options widget numbers. */
 
enum MessageOptionWidgets {
 
	WIDGET_NEWSOPT_CLOSEBOX,          ///< Close box.
 
	WIDGET_NEWSOPT_CAPTION,           ///< Caption.
 
	WIDGET_NEWSOPT_BACKGROUND,        ///< Background widget.
 
	WIDGET_NEWSOPT_LABEL,             ///< Top label.
 
	WIDGET_NEWSOPT_DROP_SUMMARY,      ///< Dropdown that adjusts at once the level for all settings.
 
	WIDGET_NEWSOPT_LABEL_SUMMARY,     ///< Label of the summary drop down.
 
	WIDGET_NEWSOPT_SOUNDTICKER,       ///< Button for (de)activating sound on events.
 
	WIDGET_NEWSOPT_SOUNDTICKER_LABEL, ///< Label of the soundticker button,
 

	
 
	WIDGET_NEWSOPT_START_OPTION,      ///< First widget that is part of a group [<][label][>] [description]
 
	WIDGET_NEWSOPT_END_OPTION = WIDGET_NEWSOPT_START_OPTION + NT_END * MOS_WIDG_PER_SETTING, ///< First widget after the groups.
 
};
 

	
 
struct MessageOptionsWindow : Window {
 
	static const StringID message_opt[]; ///< Message report options, 'off', 'summary', or 'full'.
 
	int state; ///< Option value for setting all categories at once.
 

	
 
	MessageOptionsWindow(const WindowDesc *desc) : Window()
 
	{
 
		this->InitNested(desc);
 
		/* Set up the initial disabled buttons in the case of 'off' or 'full' */
 
		NewsDisplay all_val = _news_type_data[0].display;
 
		for (int i = 0; i < NT_END; i++) {
 
			this->SetMessageButtonStates(_news_type_data[i].display, i);
 
			/* If the value doesn't match the ALL-button value, set the ALL-button value to 'off' */
 
			if (_news_type_data[i].display != all_val) all_val = ND_OFF;
 
		}
 
		/* If all values are the same value, the ALL-button will take over this value */
 
		this->state = all_val;
 
		this->OnInvalidateData(0);
 
	}
 

	
 
	/**
 
	 * Setup the disabled/enabled buttons in the message window
 
	 * If the value is 'off' disable the [<] widget, and enable the [>] one
 
	 * Same-wise for all the others. Starting value of 4 is the first widget
 
	 * group. These are grouped as [<][>] .. [<][>], etc.
 
	 * @param value to set in the widget
 
	 * @param element index of the group of widget to set
 
	 */
 
	void SetMessageButtonStates(byte value, int element)
 
	{
 
		element *= MOS_WIDG_PER_SETTING;
 

	
 
		this->SetWidgetDisabledState(element + WIDGET_NEWSOPT_START_OPTION, value == 0);
 
		this->SetWidgetDisabledState(element + WIDGET_NEWSOPT_START_OPTION + 2, value == 2);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		if (widget >= WIDGET_NEWSOPT_START_OPTION && widget < WIDGET_NEWSOPT_END_OPTION && (widget -  WIDGET_NEWSOPT_START_OPTION) % MOS_WIDG_PER_SETTING == 1) {
 
			/* Draw the string of each setting on each button. */
 
			int i = (widget -  WIDGET_NEWSOPT_START_OPTION) / MOS_WIDG_PER_SETTING;
 
			DrawString(r.left, r.right, r.top + 2, this->message_opt[_news_type_data[i].display], TC_BLACK, SA_CENTER);
 
		}
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		if (widget >= WIDGET_NEWSOPT_START_OPTION && widget < WIDGET_NEWSOPT_END_OPTION) {
 
			/* Height is the biggest widget height in a row. */
 
			size->height = FONT_HEIGHT_NORMAL + max(WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM, WD_IMGBTN_TOP + WD_IMGBTN_BOTTOM);
 

	
 
			/* Compute width for the label widget only. */
 
			if ((widget - WIDGET_NEWSOPT_START_OPTION) % MOS_WIDG_PER_SETTING == 1) {
 
				Dimension d = {0, 0};
 
				for (const StringID *str = message_opt; *str != INVALID_STRING_ID; str++) d = maxdim(d, GetStringBoundingBox(*str));
 
				size->width = d.width + padding.width + MOS_BUTTON_SPACE; // A bit extra for better looks.
 
			}
 
			return;
 
		}
 

	
 
		/* Size computations for global message options. */
 
		if (widget == WIDGET_NEWSOPT_DROP_SUMMARY || widget == WIDGET_NEWSOPT_LABEL_SUMMARY || widget == WIDGET_NEWSOPT_SOUNDTICKER || widget == WIDGET_NEWSOPT_SOUNDTICKER_LABEL) {
 
			/* Height is the biggest widget height in a row. */
 
			size->height = FONT_HEIGHT_NORMAL + max(WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM, WD_DROPDOWNTEXT_TOP + WD_DROPDOWNTEXT_BOTTOM);
 

	
 
			if (widget == WIDGET_NEWSOPT_DROP_SUMMARY) {
 
				Dimension d = {0, 0};
 
				for (const StringID *str = message_opt; *str != INVALID_STRING_ID; str++) d = maxdim(d, GetStringBoundingBox(*str));
 
				size->width = d.width + padding.width + MOS_BUTTON_SPACE; // A bit extra for better looks.
 
			} else if (widget == WIDGET_NEWSOPT_SOUNDTICKER) {
 
				size->width += MOS_BUTTON_SPACE; // A bit extra for better looks.
 
			}
 
			return;
 
		}
 
	}
 

	
 
	virtual void OnInvalidateData(int data)
 
	{
 
		/* Update the dropdown value for 'set all categories'. */
 
		this->GetWidget<NWidgetCore>(WIDGET_NEWSOPT_DROP_SUMMARY)->widget_data = this->message_opt[this->state];
 

	
 
		/* Update widget to reflect the value of the #_news_ticker_sound variable. */
 
		this->SetWidgetLoweredState(WIDGET_NEWSOPT_SOUNDTICKER, _news_ticker_sound);
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case WIDGET_NEWSOPT_DROP_SUMMARY: // Dropdown menu for all settings
 
				ShowDropDownMenu(this, this->message_opt, this->state, WIDGET_NEWSOPT_DROP_SUMMARY, 0, 0);
 
				break;
 

	
 
			case WIDGET_NEWSOPT_SOUNDTICKER: // Change ticker sound on/off
 
				_news_ticker_sound ^= 1;
 
				this->InvalidateData();
 
				break;
 

	
 
			default: { // Clicked on the [<] .. [>] widgets
 
				if (widget >= WIDGET_NEWSOPT_START_OPTION && widget < WIDGET_NEWSOPT_END_OPTION) {
 
					int wid = widget - WIDGET_NEWSOPT_START_OPTION;
 
					int element = wid / MOS_WIDG_PER_SETTING;
 
					byte val = (_news_type_data[element].display + ((wid % MOS_WIDG_PER_SETTING) ? 1 : -1)) % 3;
 

	
 
					this->SetMessageButtonStates(val, element);
 
					_news_type_data[element].display = (NewsDisplay)val;
 
					this->SetDirty();
 
				}
 
				break;
 
			}
 
		}
 
	}
 

	
 
	virtual void OnDropdownSelect(int widget, int index)
 
	{
 
		this->state = index;
 

	
 
		for (int i = 0; i < NT_END; i++) {
 
			this->SetMessageButtonStates(index, i);
 
			_news_type_data[i].display = (NewsDisplay)index;
 
		}
 
		this->InvalidateData();
 
	}
 
};
 

	
 
const StringID MessageOptionsWindow::message_opt[] = {STR_NEWS_MESSAGES_OFF, STR_NEWS_MESSAGES_SUMMARY, STR_NEWS_MESSAGES_FULL, INVALID_STRING_ID};
 

	
 
/** Make a column with the buttons for changing each news category setting, and the global settings. */
 
static NWidgetBase *MakeButtonsColumn(int *biggest_index)
 
{
 
	NWidgetVertical *vert_buttons = new NWidgetVertical;
 

	
 
	/* Top-part of the column, one row for each new category. */
 
	int widnum = WIDGET_NEWSOPT_START_OPTION;
 
	for (int i = 0; i < NT_END; i++) {
 
		NWidgetHorizontal *hor = new NWidgetHorizontal;
 
		/* [<] button. */
 
		NWidgetLeaf *leaf = new NWidgetLeaf(NWID_BUTTON_ARROW, COLOUR_YELLOW, widnum, AWV_DECREASE, STR_TOOLTIP_HSCROLL_BAR_SCROLLS_LIST);
 
		leaf->SetFill(true, true);
 
		leaf->SetFill(1, 1);
 
		hor->Add(leaf);
 
		/* Label. */
 
		leaf = new NWidgetLeaf(WWT_PUSHTXTBTN, COLOUR_YELLOW, widnum + 1, STR_EMPTY, STR_NULL);
 
		leaf->SetFill(true, true);
 
		leaf->SetFill(1, 1);
 
		hor->Add(leaf);
 
		/* [>] button. */
 
		leaf = new NWidgetLeaf(NWID_BUTTON_ARROW, COLOUR_YELLOW, widnum + 2, AWV_INCREASE, STR_TOOLTIP_HSCROLL_BAR_SCROLLS_LIST);
 
		leaf->SetFill(true, true);
 
		leaf->SetFill(1, 1);
 
		hor->Add(leaf);
 
		vert_buttons->Add(hor);
 

	
 
		widnum += MOS_WIDG_PER_SETTING;
 
	}
 
	*biggest_index = widnum - MOS_WIDG_PER_SETTING + 2;
 

	
 
	/* Space between the category buttons and the global settings buttons. */
 
	NWidgetSpacer *spacer = new NWidgetSpacer(0, MOS_ABOVE_GLOBAL_SETTINGS);
 
	vert_buttons->Add(spacer);
 

	
 
	/* Bottom part of the column with buttons for global changes. */
 
	NWidgetLeaf *leaf = new NWidgetLeaf(WWT_DROPDOWN, COLOUR_YELLOW, WIDGET_NEWSOPT_DROP_SUMMARY, STR_EMPTY, STR_NULL);
 
	leaf->SetFill(true, true);
 
	leaf->SetFill(1, 1);
 
	vert_buttons->Add(leaf);
 

	
 
	leaf = new NWidgetLeaf(WWT_TEXTBTN_2, COLOUR_YELLOW, WIDGET_NEWSOPT_SOUNDTICKER, STR_STATION_BUILD_COVERAGE_OFF, STR_NULL);
 
	leaf->SetFill(true, true);
 
	leaf->SetFill(1, 1);
 
	vert_buttons->Add(leaf);
 

	
 
	*biggest_index = max(*biggest_index, max<int>(WIDGET_NEWSOPT_DROP_SUMMARY, WIDGET_NEWSOPT_SOUNDTICKER));
 
	return vert_buttons;
 
}
 

	
 
/** Make a column with descriptions for each news category and the global settings. */
 
static NWidgetBase *MakeDescriptionColumn(int *biggest_index)
 
{
 
	NWidgetVertical *vert_desc = new NWidgetVertical;
 

	
 
	/* Top-part of the column, one row for each new category. */
 
	int widnum = WIDGET_NEWSOPT_START_OPTION;
 
	for (int i = 0; i < NT_END; i++) {
 
		NWidgetHorizontal *hor = new NWidgetHorizontal;
 

	
 
		/* Descriptive text. */
 
		NWidgetLeaf *leaf = new NWidgetLeaf(WWT_TEXT, COLOUR_YELLOW, widnum + 3, _news_type_data[i].description, STR_NULL);
 
		hor->Add(leaf);
 
		/* Filling empty space to push text to the left. */
 
		NWidgetSpacer *spacer = new NWidgetSpacer(0, 0);
 
		spacer->SetFill(true, false);
 
		spacer->SetFill(1, 0);
 
		hor->Add(spacer);
 
		vert_desc->Add(hor);
 

	
 
		widnum += MOS_WIDG_PER_SETTING;
 
	}
 
	*biggest_index = widnum - MOS_WIDG_PER_SETTING + 3;
 

	
 
	/* Space between the category descriptions and the global settings descriptions. */
 
	NWidgetSpacer *spacer = new NWidgetSpacer(0, MOS_ABOVE_GLOBAL_SETTINGS);
 
	vert_desc->Add(spacer);
 

	
 
	/* Bottom part of the column with descriptions of global changes. */
 
	NWidgetHorizontal *hor = new NWidgetHorizontal;
 
	NWidgetLeaf *leaf = new NWidgetLeaf(WWT_TEXT, COLOUR_YELLOW, WIDGET_NEWSOPT_LABEL_SUMMARY, STR_NEWS_MESSAGES_ALL, STR_NULL);
 
	hor->Add(leaf);
 
	/* Filling empty space to push text to the left. */
 
	spacer = new NWidgetSpacer(0, 0);
 
	spacer->SetFill(true, false);
 
	spacer->SetFill(1, 0);
 
	hor->Add(spacer);
 
	vert_desc->Add(hor);
 

	
 
	hor = new NWidgetHorizontal;
 
	leaf = new NWidgetLeaf(WWT_TEXT, COLOUR_YELLOW, WIDGET_NEWSOPT_SOUNDTICKER_LABEL, STR_NEWS_MESSAGES_SOUND, STR_NULL);
 
	hor->Add(leaf);
 
	/* Filling empty space to push text to the left. */
 
	spacer = new NWidgetSpacer(0, 0);
 
	leaf->SetFill(true, false);
 
	leaf->SetFill(1, 0);
 
	hor->Add(spacer);
 
	vert_desc->Add(hor);
 

	
 
	*biggest_index = max(*biggest_index, max<int>(WIDGET_NEWSOPT_LABEL_SUMMARY, WIDGET_NEWSOPT_SOUNDTICKER_LABEL));
 
	return vert_desc;
 
}
 

	
 
static const NWidgetPart _nested_message_options_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_BROWN, WIDGET_NEWSOPT_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_BROWN, WIDGET_NEWSOPT_CAPTION), SetDataTip(STR_NEWS_MESSAGE_OPTIONS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_BROWN, WIDGET_NEWSOPT_BACKGROUND),
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(NWID_SPACER), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
			NWidget(WWT_LABEL, COLOUR_BROWN, WIDGET_NEWSOPT_LABEL), SetMinimalSize(0, 14), SetDataTip(STR_NEWS_MESSAGE_TYPES, STR_NULL),
 
			NWidget(NWID_SPACER), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
		EndContainer(),
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(NWID_SPACER), SetMinimalSize(MOS_LEFT_EDGE, 0),
 
			NWidgetFunction(MakeButtonsColumn),
 
			NWidget(NWID_SPACER), SetMinimalSize(MOS_COLUMN_SPACING, 0),
 
			NWidgetFunction(MakeDescriptionColumn),
 
			NWidget(NWID_SPACER), SetMinimalSize(MOS_RIGHT_EDGE, 0),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, MOS_BOTTOM_EDGE),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _message_options_desc(
 
	270, 22, 0, 0,
 
	WC_GAME_OPTIONS, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
 
	_nested_message_options_widgets, lengthof(_nested_message_options_widgets)
 
);
 

	
 
void ShowMessageOptions()
 
{
 
	DeleteWindowById(WC_GAME_OPTIONS, 0);
 
	new MessageOptionsWindow(&_message_options_desc);
 
}
src/order_gui.cpp
Show inline comments
 
@@ -534,916 +534,916 @@ private:
 
		if (load_type < 0) {
 
			load_type = order->GetLoadType() == OLF_LOAD_IF_POSSIBLE ? OLF_FULL_LOAD_ANY : OLF_LOAD_IF_POSSIBLE;
 
		}
 
		DoCommandP(this->vehicle->tile, this->vehicle->index + (sel_ord << 16), MOF_LOAD | (load_type << 4), CMD_MODIFY_ORDER | CMD_MSG(STR_ERROR_CAN_T_MODIFY_THIS_ORDER));
 
	}
 

	
 
	/**
 
	 * Handle the click on the service.
 
	 */
 
	void OrderClick_Service(int i)
 
	{
 
		VehicleOrderID sel_ord = this->OrderGetSel();
 

	
 
		if (i < 0) {
 
			const Order *order = this->vehicle->GetOrder(sel_ord);
 
			if (order == NULL) return;
 
			i = (order->GetDepotOrderType() & ODTFB_SERVICE) ? DA_ALWAYS_GO : DA_SERVICE;
 
		}
 
		DoCommandP(this->vehicle->tile, this->vehicle->index + (sel_ord << 16), MOF_DEPOT_ACTION | (i << 4), CMD_MODIFY_ORDER | CMD_MSG(STR_ERROR_CAN_T_MODIFY_THIS_ORDER));
 
	}
 

	
 
	/**
 
	 * Handle the click on the service in nearest depot button.
 
	 * @param i Dummy parameter.
 
	 */
 
	void OrderClick_NearestDepot(int i)
 
	{
 
		Order order;
 
		order.next = NULL;
 
		order.index = 0;
 
		order.MakeGoToDepot(0, ODTFB_PART_OF_ORDERS,
 
				_settings_client.gui.new_nonstop && (this->vehicle->type == VEH_TRAIN || this->vehicle->type == VEH_ROAD) ? ONSF_NO_STOP_AT_INTERMEDIATE_STATIONS : ONSF_STOP_EVERYWHERE);
 
		order.SetDepotActionType(ODATFB_NEAREST_DEPOT);
 

	
 
		DoCommandP(this->vehicle->tile, this->vehicle->index + (this->OrderGetSel() << 16), order.Pack(), CMD_INSERT_ORDER | CMD_MSG(STR_ERROR_CAN_T_INSERT_NEW_ORDER));
 
	}
 

	
 
	/**
 
	 * Handle the click on the conditional order button.
 
	 * @param i Dummy parameter.
 
	 */
 
	void OrderClick_Conditional(int i)
 
	{
 
		this->SetWidgetDirty(ORDER_WIDGET_GOTO);
 
		this->LowerWidget(ORDER_WIDGET_GOTO);
 
		SetObjectToPlaceWnd(ANIMCURSOR_PICKSTATION, PAL_NONE, HT_RECT, this);
 
		this->goto_type = OPOS_CONDITIONAL;
 
	}
 

	
 
	/**
 
	 * Handle the click on the unload button.
 
	 */
 
	void OrderClick_Unload(int unload_type)
 
	{
 
		VehicleOrderID sel_ord = this->OrderGetSel();
 
		const Order *order = this->vehicle->GetOrder(sel_ord);
 

	
 
		if (order == NULL || order->GetUnloadType() == unload_type) return;
 

	
 
		if (unload_type < 0) {
 
			unload_type = order->GetUnloadType() == OUF_UNLOAD_IF_POSSIBLE ? OUFB_UNLOAD : OUF_UNLOAD_IF_POSSIBLE;
 
		}
 

	
 
		DoCommandP(this->vehicle->tile, this->vehicle->index + (sel_ord << 16), MOF_UNLOAD | (unload_type << 4), CMD_MODIFY_ORDER | CMD_MSG(STR_ERROR_CAN_T_MODIFY_THIS_ORDER));
 
	}
 

	
 
	/**
 
	 * Handle the click on the nonstop button.
 
	 * @param non_stop what non-stop type to use; -1 to use the 'next' one.
 
	 */
 
	void OrderClick_Nonstop(int non_stop)
 
	{
 
		VehicleOrderID sel_ord = this->OrderGetSel();
 
		const Order *order = this->vehicle->GetOrder(sel_ord);
 

	
 
		if (order == NULL || order->GetNonStopType() == non_stop) return;
 

	
 
		/* Keypress if negative, so 'toggle' to the next */
 
		if (non_stop < 0) {
 
			non_stop = order->GetNonStopType() ^ ONSF_NO_STOP_AT_INTERMEDIATE_STATIONS;
 
		}
 

	
 
		this->SetWidgetDirty(ORDER_WIDGET_NON_STOP);
 
		DoCommandP(this->vehicle->tile, this->vehicle->index + (sel_ord << 16), MOF_NON_STOP | non_stop << 4,  CMD_MODIFY_ORDER | CMD_MSG(STR_ERROR_CAN_T_MODIFY_THIS_ORDER));
 
	}
 

	
 
	/**
 
	 * Handle the click on the skip button.
 
	 * If ctrl is pressed, skip to selected order, else skip to current order + 1
 
	 * @param i Dummy parameter.
 
	 */
 
	void OrderClick_Skip(int i)
 
	{
 
		/* Don't skip when there's nothing to skip */
 
		if (_ctrl_pressed && this->vehicle->cur_order_index == this->OrderGetSel()) return;
 
		if (this->vehicle->GetNumOrders() <= 1) return;
 

	
 
		DoCommandP(this->vehicle->tile, this->vehicle->index, _ctrl_pressed ? this->OrderGetSel() : ((this->vehicle->cur_order_index + 1) % this->vehicle->GetNumOrders()),
 
				CMD_SKIP_TO_ORDER | CMD_MSG(_ctrl_pressed ? STR_ERROR_CAN_T_SKIP_TO_ORDER : STR_ERROR_CAN_T_SKIP_ORDER));
 
	}
 

	
 
	/**
 
	 * Handle the click on the delete button.
 
	 * @param i Dummy parameter.
 
	 */
 
	void OrderClick_Delete(int i)
 
	{
 
		/* When networking, move one order lower */
 
		int selected = this->selected_order + (int)_networking;
 

	
 
		if (DoCommandP(this->vehicle->tile, this->vehicle->index, this->OrderGetSel(), CMD_DELETE_ORDER | CMD_MSG(STR_ERROR_CAN_T_DELETE_THIS_ORDER))) {
 
			this->selected_order = selected >= this->vehicle->GetNumOrders() ? -1 : selected;
 
			this->UpdateButtonState();
 
		}
 
	}
 

	
 
	/**
 
	 * Handle the click on the refit button.
 
	 * If ctrl is pressed, cancel refitting, else show the refit window.
 
	 * @param i Dummy parameter.
 
	 */
 
	void OrderClick_Refit(int i)
 
	{
 
		if (_ctrl_pressed) {
 
			/* Cancel refitting */
 
			DoCommandP(this->vehicle->tile, this->vehicle->index, (this->OrderGetSel() << 16) | (CT_NO_REFIT << 8) | CT_NO_REFIT, CMD_ORDER_REFIT);
 
		} else {
 
			ShowVehicleRefitWindow(this->vehicle, this->OrderGetSel(), this);
 
		}
 
	}
 
	typedef void (OrdersWindow::*Handler)(int);
 
	struct KeyToEvent {
 
		uint16 keycode;
 
		Handler proc;
 
	};
 

	
 
public:
 
	OrdersWindow(const WindowDesc *desc, const Vehicle *v) : Window()
 
	{
 
		this->vehicle = v;
 

	
 
		this->InitNested(desc, v->index);
 

	
 
		this->selected_order = -1;
 
		this->owner = v->owner;
 

	
 
		if (_settings_client.gui.quick_goto && v->owner == _local_company) {
 
			/* If there are less than 2 station, make Go To active. */
 
			int station_orders = 0;
 
			const Order *order;
 
			FOR_VEHICLE_ORDERS(v, order) {
 
				if (order->IsType(OT_GOTO_STATION)) station_orders++;
 
			}
 

	
 
			if (station_orders < 2) this->OrderClick_Goto(0);
 
		}
 
		this->OnInvalidateData(-2);
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		switch (widget) {
 
			case ORDER_WIDGET_TIMETABLE_VIEW:
 
				if (!_settings_game.order.timetabling) size->width = 0;
 
				break;
 

	
 
			case ORDER_WIDGET_ORDER_LIST:
 
				resize->height = FONT_HEIGHT_NORMAL;
 
				size->height = 6 * resize->height + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
 
				break;
 

	
 
			case ORDER_WIDGET_COND_VARIABLE: {
 
				Dimension d = {0, 0};
 
				for (int i = 0; _order_conditional_variable[i] != INVALID_STRING_ID; i++) {
 
					d = maxdim(d, GetStringBoundingBox(_order_conditional_variable[i]));
 
				}
 
				d.width += padding.width;
 
				d.height += padding.height;
 
				*size = maxdim(*size, d);
 
				break;
 
			}
 

	
 
			case ORDER_WIDGET_COND_COMPARATOR: {
 
				Dimension d = {0, 0};
 
				for (int i = 0; _order_conditional_condition[i] != INVALID_STRING_ID; i++) {
 
					d = maxdim(d, GetStringBoundingBox(_order_conditional_condition[i]));
 
				}
 
				d.width += padding.width;
 
				d.height += padding.height;
 
				*size = maxdim(*size, d);
 
				break;
 
			}
 
		}
 
	}
 

	
 
	virtual void OnInvalidateData(int data)
 
	{
 
		switch (data) {
 
			case 0:
 
				/* Autoreplace replaced the vehicle */
 
				this->vehicle = Vehicle::Get(this->window_number);
 
				break;
 

	
 
			case -1:
 
				/* Removed / replaced all orders (after deleting / sharing) */
 
				if (this->selected_order == -1) break;
 

	
 
				this->DeleteChildWindows();
 
				HideDropDownMenu(this);
 
				this->selected_order = -1;
 
				break;
 

	
 
			case -2:
 
				/* Some other order changes */
 
				break;
 

	
 
			default: {
 
				/* Moving an order. If one of these is INVALID_VEH_ORDER_ID, then
 
				 * the order is being created / removed */
 
				if (this->selected_order == -1) break;
 

	
 
				VehicleOrderID from = GB(data, 0, 8);
 
				VehicleOrderID to   = GB(data, 8, 8);
 

	
 
				if (from == to) break; // no need to change anything
 

	
 
				if (from != this->selected_order) {
 
					/* Moving from preceeding order? */
 
					this->selected_order -= (int)(from <= this->selected_order);
 
					/* Moving to   preceeding order? */
 
					this->selected_order += (int)(to   <= this->selected_order);
 
					break;
 
				}
 

	
 
				/* Now we are modifying the selected order */
 
				if (to == INVALID_VEH_ORDER_ID) {
 
					/* Deleting selected order */
 
					this->DeleteChildWindows();
 
					HideDropDownMenu(this);
 
					this->selected_order = -1;
 
					break;
 
				}
 

	
 
				/* Moving selected order */
 
				this->selected_order = to;
 
			} break;
 
		}
 

	
 
		this->vscroll.SetCount(this->vehicle->GetNumOrders() + 1);
 
		this->UpdateButtonState();
 
	}
 

	
 
	void UpdateButtonState()
 
	{
 
		if (this->vehicle->owner != _local_company) return; // No buttons are displayed with competitor order windows.
 

	
 
		bool shared_orders = this->vehicle->IsOrderListShared();
 
		int sel = this->OrderGetSel();
 
		const Order *order = this->vehicle->GetOrder(sel);
 

	
 
		/* Second row. */
 
		/* skip */
 
		this->SetWidgetDisabledState(ORDER_WIDGET_SKIP, this->vehicle->GetNumOrders() <= 1);
 

	
 
		/* delete */
 
		this->SetWidgetDisabledState(ORDER_WIDGET_DELETE,
 
				(uint)this->vehicle->GetNumOrders() + ((shared_orders || this->vehicle->GetNumOrders() != 0) ? 1 : 0) <= (uint)this->selected_order);
 

	
 
		/* First row. */
 
		this->RaiseWidget(ORDER_WIDGET_FULL_LOAD);
 
		this->RaiseWidget(ORDER_WIDGET_UNLOAD);
 
		this->RaiseWidget(ORDER_WIDGET_SERVICE);
 

	
 
		/* Selection widgets. */
 
		/* Train or road vehicle. */
 
		NWidgetStacked *left_sel   = this->GetWidget<NWidgetStacked>(ORDER_WIDGET_SEL_TOP_LEFT);
 
		NWidgetStacked *middle_sel = this->GetWidget<NWidgetStacked>(ORDER_WIDGET_SEL_TOP_MIDDLE);
 
		NWidgetStacked *right_sel  = this->GetWidget<NWidgetStacked>(ORDER_WIDGET_SEL_TOP_RIGHT);
 
		/* Ship or airplane. */
 
		NWidgetStacked *row_sel = this->GetWidget<NWidgetStacked>(ORDER_WIDGET_SEL_TOP_ROW);
 
		assert(row_sel != NULL || (left_sel != NULL && middle_sel != NULL && right_sel != NULL));
 

	
 

	
 
		if (order == NULL) {
 
			if (row_sel != NULL) {
 
				row_sel->SetDisplayedPlane(DP_ROW_LOAD);
 
			} else {
 
				left_sel->SetDisplayedPlane(DP_LEFT_NONSTOP);
 
				middle_sel->SetDisplayedPlane(DP_MIDDLE_LOAD);
 
				right_sel->SetDisplayedPlane(DP_RIGHT_UNLOAD);
 
				this->DisableWidget(ORDER_WIDGET_NON_STOP);
 
				this->RaiseWidget(ORDER_WIDGET_NON_STOP);
 
			}
 
			this->DisableWidget(ORDER_WIDGET_FULL_LOAD);
 
			this->DisableWidget(ORDER_WIDGET_UNLOAD);
 
		} else {
 
			this->SetWidgetDisabledState(ORDER_WIDGET_FULL_LOAD, (order->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION) != 0); // full load
 
			this->SetWidgetDisabledState(ORDER_WIDGET_UNLOAD,    (order->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION) != 0); // unload
 

	
 
			switch (order->GetType()) {
 
				case OT_GOTO_STATION:
 
					if (row_sel != NULL) {
 
						row_sel->SetDisplayedPlane(DP_ROW_LOAD);
 
					} else {
 
						left_sel->SetDisplayedPlane(DP_LEFT_NONSTOP);
 
						middle_sel->SetDisplayedPlane(DP_MIDDLE_LOAD);
 
						right_sel->SetDisplayedPlane(DP_RIGHT_UNLOAD);
 
						this->EnableWidget(ORDER_WIDGET_NON_STOP);
 
						this->SetWidgetLoweredState(ORDER_WIDGET_NON_STOP, order->GetNonStopType() & ONSF_NO_STOP_AT_INTERMEDIATE_STATIONS);
 
					}
 
					this->SetWidgetLoweredState(ORDER_WIDGET_FULL_LOAD, order->GetLoadType() == OLF_FULL_LOAD_ANY);
 
					this->SetWidgetLoweredState(ORDER_WIDGET_UNLOAD, order->GetUnloadType() == OUFB_UNLOAD);
 
					break;
 

	
 
				case OT_GOTO_WAYPOINT:
 
					if (row_sel != NULL) {
 
						row_sel->SetDisplayedPlane(DP_ROW_LOAD);
 
					} else {
 
						left_sel->SetDisplayedPlane(DP_LEFT_NONSTOP);
 
						middle_sel->SetDisplayedPlane(DP_MIDDLE_LOAD);
 
						right_sel->SetDisplayedPlane(DP_RIGHT_UNLOAD);
 
						this->SetWidgetLoweredState(ORDER_WIDGET_NON_STOP, order->GetNonStopType() & ONSF_NO_STOP_AT_INTERMEDIATE_STATIONS);
 
					}
 
					this->DisableWidget(ORDER_WIDGET_FULL_LOAD);
 
					this->DisableWidget(ORDER_WIDGET_UNLOAD);
 
					break;
 

	
 
				case OT_GOTO_DEPOT:
 
					if (row_sel != NULL) {
 
						row_sel->SetDisplayedPlane(DP_ROW_DEPOT);
 
					} else {
 
						left_sel->SetDisplayedPlane(DP_LEFT_NONSTOP);
 
						middle_sel->SetDisplayedPlane(DP_MIDDLE_REFIT);
 
						right_sel->SetDisplayedPlane(DP_RIGHT_SERVICE);
 
						this->SetWidgetLoweredState(ORDER_WIDGET_NON_STOP, order->GetNonStopType() & ONSF_NO_STOP_AT_INTERMEDIATE_STATIONS);
 
					}
 
					this->SetWidgetLoweredState(ORDER_WIDGET_SERVICE, order->GetDepotOrderType() & ODTFB_SERVICE);
 
					break;
 

	
 
				case OT_CONDITIONAL: {
 
					if (row_sel != NULL) {
 
						row_sel->SetDisplayedPlane(DP_ROW_CONDITIONAL);
 
					} else {
 
						left_sel->SetDisplayedPlane(DP_LEFT_CONDVAR);
 
						middle_sel->SetDisplayedPlane(DP_MIDDLE_COMPARE);
 
						right_sel->SetDisplayedPlane(DP_RIGHT_CONDVAL);
 
					}
 
					OrderConditionVariable ocv = order->GetConditionVariable();
 
					/* Set the strings for the dropdown boxes. */
 
					this->GetWidget<NWidgetCore>(ORDER_WIDGET_COND_VARIABLE)->widget_data   = _order_conditional_variable[order == NULL ? 0 : ocv];
 
					this->GetWidget<NWidgetCore>(ORDER_WIDGET_COND_COMPARATOR)->widget_data = _order_conditional_condition[order == NULL ? 0 : order->GetConditionComparator()];
 
					this->SetWidgetDisabledState(ORDER_WIDGET_COND_COMPARATOR, ocv == OCV_UNCONDITIONALLY);
 
					this->SetWidgetDisabledState(ORDER_WIDGET_COND_VALUE, ocv == OCV_REQUIRES_SERVICE || ocv == OCV_UNCONDITIONALLY);
 
					break;
 
				}
 

	
 
				default: // every other order
 
					if (row_sel != NULL) {
 
						row_sel->SetDisplayedPlane(DP_ROW_LOAD);
 
					} else {
 
						left_sel->SetDisplayedPlane(DP_LEFT_NONSTOP);
 
						middle_sel->SetDisplayedPlane(DP_MIDDLE_LOAD);
 
						right_sel->SetDisplayedPlane(DP_RIGHT_UNLOAD);
 
						this->DisableWidget(ORDER_WIDGET_NON_STOP);
 
					}
 
					this->DisableWidget(ORDER_WIDGET_FULL_LOAD);
 
					this->DisableWidget(ORDER_WIDGET_UNLOAD);
 
					break;
 
			}
 
		}
 

	
 
		/* Disable list of vehicles with the same shared orders if there is no list */
 
		this->SetWidgetDisabledState(ORDER_WIDGET_SHARED_ORDER_LIST, !shared_orders);
 

	
 
		this->SetDirty();
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		if (widget != ORDER_WIDGET_ORDER_LIST) return;
 

	
 
		int y = r.top + WD_FRAMERECT_TOP;
 

	
 
		int i = this->vscroll.GetPosition();
 
		const Order *order = this->vehicle->GetOrder(i);
 
		StringID str;
 
		while (order != NULL) {
 
			/* Don't draw anything if it extends past the end of the window. */
 
			if (!this->vscroll.IsVisible(i)) break;
 

	
 
			DrawOrderString(this->vehicle, order, i, y, i == this->selected_order, false, r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT);
 
			y += this->resize.step_height;
 

	
 
			i++;
 
			order = order->next;
 
		}
 

	
 
		if (this->vscroll.IsVisible(i)) {
 
			str = this->vehicle->IsOrderListShared() ? STR_ORDERS_END_OF_SHARED_ORDERS : STR_ORDERS_END_OF_ORDERS;
 
			DrawString(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, y, str, (i == this->selected_order) ? TC_WHITE : TC_BLACK);
 
		}
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		switch (widget) {
 
			case ORDER_WIDGET_COND_VALUE: {
 
				int sel = this->OrderGetSel();
 
				const Order *order = this->vehicle->GetOrder(sel);
 

	
 
				if (order != NULL && order->IsType(OT_CONDITIONAL)) {
 
					uint value = order->GetConditionValue();
 
					if (order->GetConditionVariable() == OCV_MAX_SPEED) value = ConvertSpeedToDisplaySpeed(value);
 
					SetDParam(0, value);
 
				}
 
				break;
 
			}
 

	
 
			case ORDER_WIDGET_CAPTION:
 
				SetDParam(0, this->vehicle->index);
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case ORDER_WIDGET_ORDER_LIST: {
 
				ResetObjectToPlace();
 

	
 
				int sel = this->GetOrderFromPt(pt.y);
 

	
 
				if (_ctrl_pressed && sel < this->vehicle->GetNumOrders()) {
 
					const Order *ord = this->vehicle->GetOrder(sel);
 
					TileIndex xy = INVALID_TILE;
 

	
 
					switch (ord->GetType()) {
 
						case OT_GOTO_WAYPOINT:
 
							if (this->vehicle->type == VEH_TRAIN) {
 
								xy = Waypoint::Get(ord->GetDestination())->xy;
 
								break;
 
							}
 
							/* FALL THROUGH */
 
						case OT_GOTO_STATION:
 
							xy = Station::Get(ord->GetDestination())->xy;
 
							break;
 

	
 
						case OT_GOTO_DEPOT:
 
							if ((ord->GetDepotActionType() & ODATFB_NEAREST_DEPOT) != 0) break;
 
							xy = (this->vehicle->type == VEH_AIRCRAFT) ?  Station::Get(ord->GetDestination())->xy : Depot::Get(ord->GetDestination())->xy;
 
							break;
 
						default:
 
							break;
 
					}
 

	
 
					if (xy != INVALID_TILE) ScrollMainWindowToTile(xy);
 
					return;
 
				}
 

	
 
				/* This order won't be selected any more, close all child windows and dropdowns */
 
				this->DeleteChildWindows();
 
				HideDropDownMenu(this);
 

	
 
				if (sel == INVALID_ORDER) {
 
					/* Deselect clicked order */
 
					this->selected_order = -1;
 
				} else if (sel == this->selected_order) {
 
					if (this->vehicle->type == VEH_TRAIN && sel < this->vehicle->GetNumOrders()) {
 
						DoCommandP(this->vehicle->tile, this->vehicle->index + (sel << 16),
 
								MOF_STOP_LOCATION | ((this->vehicle->GetOrder(sel)->GetStopLocation() + 1) % OSL_END) << 4,
 
								CMD_MODIFY_ORDER | CMD_MSG(STR_ERROR_CAN_T_MODIFY_THIS_ORDER));
 
					}
 
				} else {
 
					/* Select clicked order */
 
					this->selected_order = sel;
 

	
 
					if (this->vehicle->owner == _local_company) {
 
						/* Activate drag and drop */
 
						SetObjectToPlaceWnd(SPR_CURSOR_MOUSE, PAL_NONE, HT_DRAG, this);
 
					}
 
				}
 

	
 
				this->UpdateButtonState();
 
			} break;
 

	
 
			case ORDER_WIDGET_SKIP:
 
				this->OrderClick_Skip(0);
 
				break;
 

	
 
			case ORDER_WIDGET_DELETE:
 
				this->OrderClick_Delete(0);
 
				break;
 

	
 
			case ORDER_WIDGET_NON_STOP:
 
				if (GetWidget<NWidgetLeaf>(widget)->ButtonHit(pt)) {
 
					this->OrderClick_Nonstop(-1);
 
				} else {
 
					const Order *o = this->vehicle->GetOrder(this->OrderGetSel());
 
					ShowDropDownMenu(this, _order_non_stop_drowdown, o->GetNonStopType(), ORDER_WIDGET_NON_STOP, 0,
 
													o->IsType(OT_GOTO_STATION) ? 0 : (o->IsType(OT_GOTO_WAYPOINT) ? 3 : 12));
 
				}
 
				break;
 

	
 
			case ORDER_WIDGET_GOTO:
 
				if (GetWidget<NWidgetLeaf>(widget)->ButtonHit(pt)) {
 
					this->OrderClick_Goto(0);
 
				} else {
 
					ShowDropDownMenu(this, this->vehicle->type == VEH_AIRCRAFT ? _order_goto_dropdown_aircraft : _order_goto_dropdown, 0, ORDER_WIDGET_GOTO, 0, 0);
 
				}
 
				break;
 

	
 
			case ORDER_WIDGET_FULL_LOAD:
 
				if (GetWidget<NWidgetLeaf>(widget)->ButtonHit(pt)) {
 
					this->OrderClick_FullLoad(-1);
 
				} else {
 
					ShowDropDownMenu(this, _order_full_load_drowdown, this->vehicle->GetOrder(this->OrderGetSel())->GetLoadType(), ORDER_WIDGET_FULL_LOAD, 0, 2);
 
				}
 
				break;
 

	
 
			case ORDER_WIDGET_UNLOAD:
 
				if (GetWidget<NWidgetLeaf>(widget)->ButtonHit(pt)) {
 
					this->OrderClick_Unload(-1);
 
				} else {
 
					ShowDropDownMenu(this, _order_unload_drowdown, this->vehicle->GetOrder(this->OrderGetSel())->GetUnloadType(), ORDER_WIDGET_UNLOAD, 0, 8);
 
				}
 
				break;
 

	
 
			case ORDER_WIDGET_REFIT:
 
				this->OrderClick_Refit(0);
 
				break;
 

	
 
			case ORDER_WIDGET_SERVICE:
 
				if (GetWidget<NWidgetLeaf>(widget)->ButtonHit(pt)) {
 
					this->OrderClick_Service(-1);
 
				} else {
 
					ShowDropDownMenu(this, _order_depot_action_dropdown, DepotActionStringIndex(this->vehicle->GetOrder(this->OrderGetSel())), ORDER_WIDGET_SERVICE, 0, 0);
 
				}
 
				break;
 

	
 
			case ORDER_WIDGET_TIMETABLE_VIEW:
 
				ShowTimetableWindow(this->vehicle);
 
				break;
 

	
 
			case ORDER_WIDGET_COND_VARIABLE:
 
				ShowDropDownMenu(this, _order_conditional_variable, this->vehicle->GetOrder(this->OrderGetSel())->GetConditionVariable(), ORDER_WIDGET_COND_VARIABLE, 0, 0);
 
				break;
 

	
 
			case ORDER_WIDGET_COND_COMPARATOR: {
 
				const Order *o = this->vehicle->GetOrder(this->OrderGetSel());
 
				ShowDropDownMenu(this, _order_conditional_condition, o->GetConditionComparator(), ORDER_WIDGET_COND_COMPARATOR, 0, (o->GetConditionVariable() == OCV_REQUIRES_SERVICE) ? 0x3F : 0xC0);
 
			} break;
 

	
 
			case ORDER_WIDGET_COND_VALUE: {
 
				const Order *order = this->vehicle->GetOrder(this->OrderGetSel());
 
				uint value = order->GetConditionValue();
 
				if (order->GetConditionVariable() == OCV_MAX_SPEED) value = ConvertSpeedToDisplaySpeed(value);
 
				SetDParam(0, value);
 
				ShowQueryString(STR_JUST_INT, STR_ORDER_CONDITIONAL_VALUE_CAPT, 5, 100, this, CS_NUMERAL, QSF_NONE);
 
			} break;
 

	
 
			case ORDER_WIDGET_SHARED_ORDER_LIST:
 
				ShowVehicleListWindow(this->vehicle);
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnQueryTextFinished(char *str)
 
	{
 
		if (!StrEmpty(str)) {
 
			VehicleOrderID sel = this->OrderGetSel();
 
			uint value = atoi(str);
 

	
 
			switch (this->vehicle->GetOrder(sel)->GetConditionVariable()) {
 
				case OCV_MAX_SPEED:
 
					value = ConvertDisplaySpeedToSpeed(value);
 
					break;
 

	
 
				case OCV_RELIABILITY:
 
				case OCV_LOAD_PERCENTAGE:
 
					value = Clamp(value, 0, 100);
 

	
 
				default:
 
					break;
 
			}
 
			DoCommandP(this->vehicle->tile, this->vehicle->index + (sel << 16), MOF_COND_VALUE | Clamp(value, 0, 2047) << 4, CMD_MODIFY_ORDER | CMD_MSG(STR_ERROR_CAN_T_MODIFY_THIS_ORDER));
 
		}
 
	}
 

	
 
	virtual void OnDropdownSelect(int widget, int index)
 
	{
 
		switch (widget) {
 
			case ORDER_WIDGET_NON_STOP:
 
				this->OrderClick_Nonstop(index);
 
				break;
 

	
 
			case ORDER_WIDGET_FULL_LOAD:
 
				this->OrderClick_FullLoad(index);
 
				break;
 

	
 
			case ORDER_WIDGET_UNLOAD:
 
				this->OrderClick_Unload(index);
 
				break;
 

	
 
			case ORDER_WIDGET_GOTO:
 
				switch (index) {
 
					case 0: this->OrderClick_Goto(0); break;
 
					case 1: this->OrderClick_NearestDepot(0); break;
 
					case 2: this->OrderClick_Conditional(0); break;
 
					default: NOT_REACHED();
 
				}
 
				break;
 

	
 
			case ORDER_WIDGET_SERVICE:
 
				this->OrderClick_Service(index);
 
				break;
 

	
 
			case ORDER_WIDGET_COND_VARIABLE:
 
				DoCommandP(this->vehicle->tile, this->vehicle->index + (this->OrderGetSel() << 16), MOF_COND_VARIABLE | index << 4,  CMD_MODIFY_ORDER | CMD_MSG(STR_ERROR_CAN_T_MODIFY_THIS_ORDER));
 
				break;
 

	
 
			case ORDER_WIDGET_COND_COMPARATOR:
 
				DoCommandP(this->vehicle->tile, this->vehicle->index + (this->OrderGetSel() << 16), MOF_COND_COMPARATOR | index << 4,  CMD_MODIFY_ORDER | CMD_MSG(STR_ERROR_CAN_T_MODIFY_THIS_ORDER));
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnDragDrop(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case ORDER_WIDGET_ORDER_LIST: {
 
				int from_order = this->OrderGetSel();
 
				int to_order = this->GetOrderFromPt(pt.y);
 

	
 
				if (!(from_order == to_order || from_order == INVALID_ORDER || from_order > this->vehicle->GetNumOrders() || to_order == INVALID_ORDER || to_order > this->vehicle->GetNumOrders()) &&
 
						DoCommandP(this->vehicle->tile, this->vehicle->index, from_order | (to_order << 16), CMD_MOVE_ORDER | CMD_MSG(STR_ERROR_CAN_T_MOVE_THIS_ORDER))) {
 
					this->selected_order = -1;
 
					this->UpdateButtonState();
 
				}
 
			} break;
 

	
 
			case ORDER_WIDGET_DELETE:
 
				this->OrderClick_Delete(0);
 
				break;
 
		}
 

	
 
		ResetObjectToPlace();
 
	}
 

	
 
	virtual EventState OnKeyPress(uint16 key, uint16 keycode)
 
	{
 
		static const KeyToEvent keytoevent[] = {
 
			{'D', &OrdersWindow::OrderClick_Skip},
 
			{'F', &OrdersWindow::OrderClick_Delete},
 
			{'G', &OrdersWindow::OrderClick_Goto},
 
			{'H', &OrdersWindow::OrderClick_Nonstop},
 
			{'J', &OrdersWindow::OrderClick_FullLoad},
 
			{'K', &OrdersWindow::OrderClick_Unload},
 
			//('?', &OrdersWindow::OrderClick_Service},
 
		};
 

	
 
		if (this->vehicle->owner != _local_company) return ES_NOT_HANDLED;
 

	
 
		for (uint i = 0; i < lengthof(keytoevent); i++) {
 
			if (keycode == keytoevent[i].keycode) {
 
				(this->*(keytoevent[i].proc))(-1);
 
				return ES_HANDLED;
 
			}
 
		}
 
		return ES_NOT_HANDLED;
 
	}
 

	
 
	virtual void OnPlaceObject(Point pt, TileIndex tile)
 
	{
 
		if (this->goto_type == OPOS_GOTO) {
 
			/* check if we're clicking on a vehicle first.. clone orders in that case. */
 
			const Vehicle *v = CheckMouseOverVehicle();
 
			if (v != NULL && this->HandleOrderVehClick(v)) return;
 

	
 
			const Order cmd = GetOrderCmdFromTile(this->vehicle, tile);
 
			if (cmd.IsType(OT_NOTHING)) return;
 

	
 
			if (DoCommandP(this->vehicle->tile, this->vehicle->index + (this->OrderGetSel() << 16), cmd.Pack(), CMD_INSERT_ORDER | CMD_MSG(STR_ERROR_CAN_T_INSERT_NEW_ORDER))) {
 
				/* With quick goto the Go To button stays active */
 
				if (!_settings_client.gui.quick_goto) ResetObjectToPlace();
 
			}
 
		}
 
	}
 

	
 
	virtual void OnPlaceObjectAbort()
 
	{
 
		if (this->goto_type == OPOS_CONDITIONAL) {
 
			this->goto_type = OPOS_GOTO;
 
			NWidgetBase *nwid = this->GetWidget<NWidgetBase>(ORDER_WIDGET_ORDER_LIST);
 
			if (IsInsideBS(_cursor.pos.x, this->left + nwid->pos_x, nwid->current_x) && IsInsideBS(_cursor.pos.y, this->top + nwid->pos_y, nwid->current_y)) {
 
				int order_id = this->GetOrderFromPt(_cursor.pos.y - this->top);
 
				if (order_id != INVALID_ORDER) {
 
					Order order;
 
					order.next = NULL;
 
					order.index = 0;
 
					order.MakeConditional(order_id);
 

	
 
					DoCommandP(this->vehicle->tile, this->vehicle->index + (this->OrderGetSel() << 16), order.Pack(), CMD_INSERT_ORDER | CMD_MSG(STR_ERROR_CAN_T_INSERT_NEW_ORDER));
 
				}
 
			}
 
		}
 
		this->RaiseWidget(ORDER_WIDGET_GOTO);
 
		this->SetWidgetDirty(ORDER_WIDGET_GOTO);
 
	}
 

	
 
	virtual void OnMouseLoop()
 
	{
 
		const Vehicle *v = _place_clicked_vehicle;
 
		/*
 
		 * Check if we clicked on a vehicle
 
		 * and if the GOTO button of this window is pressed
 
		 * This is because of all open order windows WE_MOUSELOOP is called
 
		 * and if you have 3 windows open, and this check is not done
 
		 * the order is copied to the last open window instead of the
 
		 * one where GOTO is enabled
 
		 */
 
		if (v != NULL && this->IsWidgetLowered(ORDER_WIDGET_GOTO)) {
 
			_place_clicked_vehicle = NULL;
 
			this->HandleOrderVehClick(v);
 
		}
 
	}
 

	
 
	virtual void OnResize()
 
	{
 
		/* Update the scroll bar */
 
		this->vscroll.SetCapacity(this->GetWidget<NWidgetBase>(ORDER_WIDGET_ORDER_LIST)->current_y / this->resize.step_height);
 
	}
 

	
 
	virtual void OnTimeout()
 
	{
 
		/* unclick all buttons except for the 'goto' button (ORDER_WIDGET_GOTO), which is 'persistent' */
 
		for (uint i = 0; i < this->nested_array_size; i++) {
 
			if (this->nested_array[i] != NULL && i != ORDER_WIDGET_GOTO &&
 
					i != ORDER_WIDGET_SEL_TOP_LEFT && i != ORDER_WIDGET_SEL_TOP_MIDDLE && i != ORDER_WIDGET_SEL_TOP_RIGHT &&
 
					i != ORDER_WIDGET_SEL_TOP_ROW && this->IsWidgetLowered(i)) {
 
				this->RaiseWidget(i);
 
				this->SetWidgetDirty(i);
 
			}
 
		}
 
	}
 
};
 

	
 
/** Nested widget definition for "your" train orders. */
 
static const NWidgetPart _nested_orders_train_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, ORDER_WIDGET_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, ORDER_WIDGET_CAPTION), SetDataTip(STR_ORDERS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, ORDER_WIDGET_TIMETABLE_VIEW), SetMinimalSize(61, 14), SetDataTip(STR_ORDERS_TIMETABLE_VIEW, STR_ORDERS_TIMETABLE_VIEW_TOOLTIP),
 
		NWidget(WWT_STICKYBOX, COLOUR_GREY, ORDER_WIDGET_STICKY),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_PANEL, COLOUR_GREY, ORDER_WIDGET_ORDER_LIST), SetMinimalSize(372, 62), SetDataTip(0x0, STR_ORDERS_LIST_TOOLTIP), SetResize(1, 1), EndContainer(),
 
		NWidget(WWT_SCROLLBAR, COLOUR_GREY, ORDER_WIDGET_SCROLLBAR),
 
	EndContainer(),
 

	
 
	/* First button row. */
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
			NWidget(NWID_SELECTION, INVALID_COLOUR, ORDER_WIDGET_SEL_TOP_LEFT),
 
				NWidget(NWID_BUTTON_DROPDOWN, COLOUR_GREY, ORDER_WIDGET_NON_STOP), SetMinimalSize(124, 12), SetFill(true, false),
 
				NWidget(NWID_BUTTON_DROPDOWN, COLOUR_GREY, ORDER_WIDGET_NON_STOP), SetMinimalSize(124, 12), SetFill(1, 0),
 
														SetDataTip(STR_ORDER_NON_STOP, STR_ORDER_TOOLTIP_NON_STOP), SetResize(1, 0),
 
				NWidget(WWT_DROPDOWN, COLOUR_GREY, ORDER_WIDGET_COND_VARIABLE), SetMinimalSize(124, 12), SetFill(true, false),
 
				NWidget(WWT_DROPDOWN, COLOUR_GREY, ORDER_WIDGET_COND_VARIABLE), SetMinimalSize(124, 12), SetFill(1, 0),
 
														SetDataTip(STR_NULL, STR_ORDER_CONDITIONAL_VARIABLE_TOOLTIP), SetResize(1, 0),
 
			EndContainer(),
 
			NWidget(NWID_SELECTION, INVALID_COLOUR, ORDER_WIDGET_SEL_TOP_MIDDLE),
 
				NWidget(NWID_BUTTON_DROPDOWN, COLOUR_GREY, ORDER_WIDGET_FULL_LOAD), SetMinimalSize(124, 12), SetFill(true, false),
 
				NWidget(NWID_BUTTON_DROPDOWN, COLOUR_GREY, ORDER_WIDGET_FULL_LOAD), SetMinimalSize(124, 12), SetFill(1, 0),
 
														SetDataTip(STR_ORDER_TOGGLE_FULL_LOAD, STR_ORDER_TOOLTIP_FULL_LOAD), SetResize(1, 0),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, ORDER_WIDGET_REFIT), SetMinimalSize(124, 12), SetFill(true, false),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, ORDER_WIDGET_REFIT), SetMinimalSize(124, 12), SetFill(1, 0),
 
														SetDataTip(STR_ORDER_REFIT, STR_ORDER_REFIT_TOOLTIP), SetResize(1, 0),
 
				NWidget(WWT_DROPDOWN, COLOUR_GREY, ORDER_WIDGET_COND_COMPARATOR), SetMinimalSize(124, 12), SetFill(true, false),
 
				NWidget(WWT_DROPDOWN, COLOUR_GREY, ORDER_WIDGET_COND_COMPARATOR), SetMinimalSize(124, 12), SetFill(1, 0),
 
														SetDataTip(STR_NULL, STR_ORDER_CONDITIONAL_COMPARATOR_TOOLTIP), SetResize(1, 0),
 
			EndContainer(),
 
			NWidget(NWID_SELECTION, INVALID_COLOUR, ORDER_WIDGET_SEL_TOP_RIGHT),
 
				NWidget(NWID_BUTTON_DROPDOWN, COLOUR_GREY, ORDER_WIDGET_UNLOAD), SetMinimalSize(124, 12), SetFill(true, false),
 
				NWidget(NWID_BUTTON_DROPDOWN, COLOUR_GREY, ORDER_WIDGET_UNLOAD), SetMinimalSize(124, 12), SetFill(1, 0),
 
														SetDataTip(STR_ORDER_TOGGLE_UNLOAD, STR_ORDER_TOOLTIP_UNLOAD), SetResize(1, 0),
 
				NWidget(NWID_BUTTON_DROPDOWN, COLOUR_GREY, ORDER_WIDGET_SERVICE), SetMinimalSize(124, 12), SetFill(true, false),
 
				NWidget(NWID_BUTTON_DROPDOWN, COLOUR_GREY, ORDER_WIDGET_SERVICE), SetMinimalSize(124, 12), SetFill(1, 0),
 
														SetDataTip(STR_ORDER_SERVICE, STR_ORDER_SERVICE_TOOLTIP), SetResize(1, 0),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, ORDER_WIDGET_COND_VALUE), SetMinimalSize(124, 12), SetFill(true, false),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, ORDER_WIDGET_COND_VALUE), SetMinimalSize(124, 12), SetFill(1, 0),
 
														SetDataTip(STR_BLACK_COMMA, STR_ORDER_CONDITIONAL_VALUE_TOOLTIP), SetResize(1, 0),
 
			EndContainer(),
 
		EndContainer(),
 
		NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, ORDER_WIDGET_SHARED_ORDER_LIST), SetMinimalSize(12, 12), SetDataTip(SPR_SHARED_ORDERS_ICON, STR_ORDERS_VEH_WITH_SHARED_ORDERS_LIST_TOOLTIP),
 
	EndContainer(),
 

	
 
	/* Second button row. */
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, ORDER_WIDGET_SKIP), SetMinimalSize(124, 12), SetFill(true, false),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, ORDER_WIDGET_SKIP), SetMinimalSize(124, 12), SetFill(1, 0),
 
													SetDataTip(STR_ORDERS_SKIP_BUTTON, STR_ORDERS_SKIP_TOOLTIP), SetResize(1, 0),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, ORDER_WIDGET_DELETE), SetMinimalSize(124, 12), SetFill(true, false),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, ORDER_WIDGET_DELETE), SetMinimalSize(124, 12), SetFill(1, 0),
 
													SetDataTip(STR_ORDERS_DELETE_BUTTON, STR_ORDERS_DELETE_TOOLTIP), SetResize(1, 0),
 
			NWidget(NWID_BUTTON_DROPDOWN, COLOUR_GREY, ORDER_WIDGET_GOTO), SetMinimalSize(124, 12), SetFill(true, false),
 
			NWidget(NWID_BUTTON_DROPDOWN, COLOUR_GREY, ORDER_WIDGET_GOTO), SetMinimalSize(124, 12), SetFill(1, 0),
 
													SetDataTip(STR_ORDERS_GO_TO_BUTTON, STR_ORDERS_GO_TO_TOOLTIP), SetResize(1, 0),
 
		EndContainer(),
 
		NWidget(WWT_RESIZEBOX, COLOUR_GREY, ORDER_WIDGET_RESIZE),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _orders_train_desc(
 
	WDP_AUTO, WDP_AUTO, 384, 100,
 
	WC_VEHICLE_ORDERS, WC_VEHICLE_VIEW,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_STICKY_BUTTON | WDF_RESIZABLE,
 
	_nested_orders_train_widgets, lengthof(_nested_orders_train_widgets)
 
);
 

	
 
/** Nested widget definition for "your" orders (non-train). */
 
static const NWidgetPart _nested_orders_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, ORDER_WIDGET_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, ORDER_WIDGET_CAPTION), SetDataTip(STR_ORDERS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, ORDER_WIDGET_TIMETABLE_VIEW), SetMinimalSize(61, 14), SetDataTip(STR_ORDERS_TIMETABLE_VIEW, STR_ORDERS_TIMETABLE_VIEW_TOOLTIP),
 
		NWidget(WWT_STICKYBOX, COLOUR_GREY, ORDER_WIDGET_STICKY),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_PANEL, COLOUR_GREY, ORDER_WIDGET_ORDER_LIST), SetMinimalSize(372, 62), SetDataTip(0x0, STR_ORDERS_LIST_TOOLTIP), SetResize(1, 1), EndContainer(),
 
		NWidget(WWT_SCROLLBAR, COLOUR_GREY, ORDER_WIDGET_SCROLLBAR),
 
	EndContainer(),
 

	
 
	/* First button row. */
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(NWID_SELECTION, INVALID_COLOUR, ORDER_WIDGET_SEL_TOP_ROW),
 
			/* load + unload buttons. */
 
			NWidget(NWID_HORIZONTAL),
 
				NWidget(NWID_BUTTON_DROPDOWN, COLOUR_GREY, ORDER_WIDGET_FULL_LOAD), SetMinimalSize(186, 12), SetFill(true, false),
 
				NWidget(NWID_BUTTON_DROPDOWN, COLOUR_GREY, ORDER_WIDGET_FULL_LOAD), SetMinimalSize(186, 12), SetFill(1, 0),
 
													SetDataTip(STR_ORDER_TOGGLE_FULL_LOAD, STR_ORDER_TOOLTIP_FULL_LOAD), SetResize(1, 0),
 
				NWidget(NWID_BUTTON_DROPDOWN, COLOUR_GREY, ORDER_WIDGET_UNLOAD), SetMinimalSize(186, 12), SetFill(true, false),
 
				NWidget(NWID_BUTTON_DROPDOWN, COLOUR_GREY, ORDER_WIDGET_UNLOAD), SetMinimalSize(186, 12), SetFill(1, 0),
 
													SetDataTip(STR_ORDER_TOGGLE_UNLOAD, STR_ORDER_TOOLTIP_UNLOAD), SetResize(1, 0),
 
			EndContainer(),
 
			/* Refit + service buttons. */
 
			NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, ORDER_WIDGET_REFIT), SetMinimalSize(186, 12), SetFill(true, false),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, ORDER_WIDGET_REFIT), SetMinimalSize(186, 12), SetFill(1, 0),
 
													SetDataTip(STR_ORDER_REFIT, STR_ORDER_REFIT_TOOLTIP), SetResize(1, 0),
 
				NWidget(NWID_BUTTON_DROPDOWN, COLOUR_GREY, ORDER_WIDGET_SERVICE), SetMinimalSize(124, 12), SetFill(true, false),
 
				NWidget(NWID_BUTTON_DROPDOWN, COLOUR_GREY, ORDER_WIDGET_SERVICE), SetMinimalSize(124, 12), SetFill(1, 0),
 
													SetDataTip(STR_ORDER_SERVICE, STR_ORDER_SERVICE_TOOLTIP), SetResize(1, 0),
 
			EndContainer(),
 

	
 
			/* Buttons for setting a condition. */
 
			NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
				NWidget(WWT_DROPDOWN, COLOUR_GREY, ORDER_WIDGET_COND_VARIABLE), SetMinimalSize(124, 12), SetFill(true, false),
 
				NWidget(WWT_DROPDOWN, COLOUR_GREY, ORDER_WIDGET_COND_VARIABLE), SetMinimalSize(124, 12), SetFill(1, 0),
 
													SetDataTip(STR_NULL, STR_ORDER_CONDITIONAL_VARIABLE_TOOLTIP), SetResize(1, 0),
 
				NWidget(WWT_DROPDOWN, COLOUR_GREY, ORDER_WIDGET_COND_COMPARATOR), SetMinimalSize(124, 12), SetFill(true, false),
 
				NWidget(WWT_DROPDOWN, COLOUR_GREY, ORDER_WIDGET_COND_COMPARATOR), SetMinimalSize(124, 12), SetFill(1, 0),
 
													SetDataTip(STR_NULL, STR_ORDER_CONDITIONAL_COMPARATOR_TOOLTIP), SetResize(1, 0),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, ORDER_WIDGET_COND_VALUE), SetMinimalSize(124, 12), SetFill(true, false),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, ORDER_WIDGET_COND_VALUE), SetMinimalSize(124, 12), SetFill(1, 0),
 
													SetDataTip(STR_BLACK_COMMA, STR_ORDER_CONDITIONAL_VALUE_TOOLTIP), SetResize(1, 0),
 
			EndContainer(),
 
		EndContainer(),
 

	
 
		NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, ORDER_WIDGET_SHARED_ORDER_LIST), SetMinimalSize(12, 12), SetDataTip(SPR_SHARED_ORDERS_ICON, STR_ORDERS_VEH_WITH_SHARED_ORDERS_LIST_TOOLTIP),
 
	EndContainer(),
 

	
 
	/* Second button row. */
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, ORDER_WIDGET_SKIP), SetMinimalSize(124, 12), SetFill(true, false),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, ORDER_WIDGET_SKIP), SetMinimalSize(124, 12), SetFill(1, 0),
 
											SetDataTip(STR_ORDERS_SKIP_BUTTON, STR_ORDERS_SKIP_TOOLTIP), SetResize(1, 0),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, ORDER_WIDGET_DELETE), SetMinimalSize(124, 12), SetFill(true, false),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, ORDER_WIDGET_DELETE), SetMinimalSize(124, 12), SetFill(1, 0),
 
											SetDataTip(STR_ORDERS_DELETE_BUTTON, STR_ORDERS_DELETE_TOOLTIP), SetResize(1, 0),
 
		NWidget(NWID_BUTTON_DROPDOWN, COLOUR_GREY, ORDER_WIDGET_GOTO), SetMinimalSize(124, 12), SetFill(true, false),
 
		NWidget(NWID_BUTTON_DROPDOWN, COLOUR_GREY, ORDER_WIDGET_GOTO), SetMinimalSize(124, 12), SetFill(1, 0),
 
											SetDataTip(STR_ORDERS_GO_TO_BUTTON, STR_ORDERS_GO_TO_TOOLTIP), SetResize(1, 0),
 
		NWidget(WWT_RESIZEBOX, COLOUR_GREY, ORDER_WIDGET_RESIZE),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _orders_desc(
 
	WDP_AUTO, WDP_AUTO, 384, 100,
 
	WC_VEHICLE_ORDERS, WC_VEHICLE_VIEW,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_STICKY_BUTTON | WDF_RESIZABLE,
 
	_nested_orders_widgets, lengthof(_nested_orders_widgets)
 
);
 

	
 
/** Nested widget definition for competitor orders. */
 
static const NWidgetPart _nested_other_orders_widgets[] = {
 
	NWidget(NWID_VERTICAL),
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(WWT_CLOSEBOX, COLOUR_GREY, ORDER_WIDGET_CLOSEBOX),
 
			NWidget(WWT_CAPTION, COLOUR_GREY, ORDER_WIDGET_CAPTION), SetDataTip(STR_ORDERS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, ORDER_WIDGET_TIMETABLE_VIEW), SetMinimalSize(61, 14), SetDataTip(STR_ORDERS_TIMETABLE_VIEW, STR_ORDERS_TIMETABLE_VIEW_TOOLTIP),
 
			NWidget(WWT_STICKYBOX, COLOUR_GREY, ORDER_WIDGET_STICKY),
 
		EndContainer(),
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(WWT_PANEL, COLOUR_GREY, ORDER_WIDGET_ORDER_LIST), SetMinimalSize(372, 72), SetDataTip(0x0, STR_ORDERS_LIST_TOOLTIP), SetResize(1, 1), EndContainer(),
 
			NWidget(NWID_VERTICAL),
 
				NWidget(WWT_SCROLLBAR, COLOUR_GREY, ORDER_WIDGET_SCROLLBAR),
 
				NWidget(WWT_RESIZEBOX, COLOUR_GREY, ORDER_WIDGET_RESIZE),
 
			EndContainer(),
 
		EndContainer(),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _other_orders_desc(
 
	WDP_AUTO, WDP_AUTO, 384, 86,
 
	WC_VEHICLE_ORDERS, WC_VEHICLE_VIEW,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE | WDF_CONSTRUCTION,
 
	_nested_other_orders_widgets, lengthof(_nested_other_orders_widgets)
 
);
 

	
 
void ShowOrdersWindow(const Vehicle *v)
 
{
 
	DeleteWindowById(WC_VEHICLE_DETAILS, v->index, false);
 
	DeleteWindowById(WC_VEHICLE_TIMETABLE, v->index, false);
 
	if (BringWindowToFrontById(WC_VEHICLE_ORDERS, v->index) != NULL) return;
 

	
 
	if (v->owner != _local_company) {
 
		new OrdersWindow(&_other_orders_desc, v);
 
	} else {
 
		new OrdersWindow((v->type == VEH_TRAIN || v->type == VEH_ROAD) ? &_orders_train_desc : &_orders_desc, v);
 
	}
 
}
src/rail_gui.cpp
Show inline comments
 
@@ -60,1937 +60,1937 @@ struct RailStationGUISettings {
 
static RailStationGUISettings _railstation; ///< Settings of the station builder GUI
 

	
 

	
 
static void HandleStationPlacement(TileIndex start, TileIndex end);
 
static void ShowBuildTrainDepotPicker(Window *parent);
 
static void ShowBuildWaypointPicker(Window *parent);
 
static void ShowStationBuilder(Window *parent);
 
static void ShowSignalBuilder(Window *parent);
 

	
 
void CcPlaySound1E(bool success, TileIndex tile, uint32 p1, uint32 p2)
 
{
 
	if (success) SndPlayTileFx(SND_20_SPLAT_2, tile);
 
}
 

	
 
static void GenericPlaceRail(TileIndex tile, int cmd)
 
{
 
	DoCommandP(tile, _cur_railtype, cmd,
 
		_remove_button_clicked ?
 
		CMD_REMOVE_SINGLE_RAIL | CMD_MSG(STR_ERROR_CAN_T_REMOVE_RAILROAD_TRACK) :
 
		CMD_BUILD_SINGLE_RAIL | CMD_MSG(STR_ERROR_CAN_T_BUILD_RAILROAD_TRACK),
 
		CcPlaySound1E
 
	);
 
}
 

	
 
static void PlaceRail_N(TileIndex tile)
 
{
 
	VpStartPlaceSizing(tile, VPM_FIX_VERTICAL | VPM_RAILDIRS, DDSP_PLACE_RAIL);
 
}
 

	
 
static void PlaceRail_NE(TileIndex tile)
 
{
 
	VpStartPlaceSizing(tile, VPM_FIX_Y | VPM_RAILDIRS, DDSP_PLACE_RAIL);
 
}
 

	
 
static void PlaceRail_E(TileIndex tile)
 
{
 
	VpStartPlaceSizing(tile, VPM_FIX_HORIZONTAL | VPM_RAILDIRS, DDSP_PLACE_RAIL);
 
}
 

	
 
static void PlaceRail_NW(TileIndex tile)
 
{
 
	VpStartPlaceSizing(tile, VPM_FIX_X | VPM_RAILDIRS, DDSP_PLACE_RAIL);
 
}
 

	
 
static void PlaceRail_AutoRail(TileIndex tile)
 
{
 
	VpStartPlaceSizing(tile, VPM_RAILDIRS, DDSP_PLACE_RAIL);
 
}
 

	
 
/**
 
 * Try to add an additional rail-track at the entrance of a depot
 
 * @param tile  Tile to use for adding the rail-track
 
 * @param extra Track to add
 
 * @see CcRailDepot()
 
 */
 
static void PlaceExtraDepotRail(TileIndex tile, uint16 extra)
 
{
 
	if (GetRailTileType(tile) != RAIL_TILE_NORMAL) return;
 
	if ((GetTrackBits(tile) & GB(extra, 8, 8)) == 0) return;
 

	
 
	DoCommandP(tile, _cur_railtype, extra & 0xFF, CMD_BUILD_SINGLE_RAIL);
 
}
 

	
 
/** Additional pieces of track to add at the entrance of a depot. */
 
static const uint16 _place_depot_extra[12] = {
 
	0x0604, 0x2102, 0x1202, 0x0505,  // First additional track for directions 0..3
 
	0x2400, 0x2801, 0x1800, 0x1401,  // Second additional track
 
	0x2203, 0x0904, 0x0A05, 0x1103,  // Third additional track
 
};
 

	
 

	
 
void CcRailDepot(bool success, TileIndex tile, uint32 p1, uint32 p2)
 
{
 
	if (success) {
 
		DiagDirection dir = (DiagDirection)p2;
 

	
 
		SndPlayTileFx(SND_20_SPLAT_2, tile);
 
		if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace();
 

	
 
		tile += TileOffsByDiagDir(dir);
 

	
 
		if (IsTileType(tile, MP_RAILWAY)) {
 
			PlaceExtraDepotRail(tile, _place_depot_extra[dir]);
 
			PlaceExtraDepotRail(tile, _place_depot_extra[dir + 4]);
 
			PlaceExtraDepotRail(tile, _place_depot_extra[dir + 8]);
 
		}
 
	}
 
}
 

	
 
static void PlaceRail_Depot(TileIndex tile)
 
{
 
	DoCommandP(tile, _cur_railtype, _build_depot_direction,
 
		CMD_BUILD_TRAIN_DEPOT | CMD_MSG(STR_ERROR_CAN_T_BUILD_TRAIN_DEPOT),
 
		CcRailDepot);
 
}
 

	
 
static void PlaceRail_Waypoint(TileIndex tile)
 
{
 
	if (_remove_button_clicked) {
 
		VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_REMOVE_STATION);
 
		return;
 
	}
 

	
 
	Axis axis = GetAxisForNewWaypoint(tile);
 
	if (IsValidAxis(axis)) {
 
		/* Valid tile for waypoints */
 
		VpStartPlaceSizing(tile, axis == AXIS_X ? VPM_FIX_X : VPM_FIX_Y, DDSP_BUILD_STATION);
 
	} else {
 
		/* Tile where we can't build rail waypoints. This is always going to fail,
 
		 * but provides the user with a proper error message. */
 
		DoCommandP(tile, 1 << 8 | 1 << 16, STAT_CLASS_WAYP | INVALID_STATION << 16, CMD_BUILD_RAIL_WAYPOINT | CMD_MSG(STR_ERROR_CAN_T_BUILD_TRAIN_WAYPOINT));
 
	}
 
}
 

	
 
void CcStation(bool success, TileIndex tile, uint32 p1, uint32 p2)
 
{
 
	if (success) {
 
		SndPlayTileFx(SND_20_SPLAT_2, tile);
 
		/* Only close the station builder window if the default station and non persistent building is chosen. */
 
		if (_railstation.station_class == STAT_CLASS_DFLT && _railstation.station_type == 0 && !_settings_client.gui.persistent_buildingtools) ResetObjectToPlace();
 
	}
 
}
 

	
 
static void PlaceRail_Station(TileIndex tile)
 
{
 
	if (_remove_button_clicked) {
 
		VpStartPlaceSizing(tile, VPM_X_AND_Y_LIMITED, DDSP_REMOVE_STATION);
 
		VpSetPlaceSizingLimit(-1);
 
	} else if (_settings_client.gui.station_dragdrop) {
 
		VpStartPlaceSizing(tile, VPM_X_AND_Y_LIMITED, DDSP_BUILD_STATION);
 
		VpSetPlaceSizingLimit(_settings_game.station.station_spread);
 
	} else {
 
		uint32 p1 = _cur_railtype | _railstation.orientation << 4 | _settings_client.gui.station_numtracks << 8 | _settings_client.gui.station_platlength << 16 | _ctrl_pressed << 24;
 
		uint32 p2 = _railstation.station_class | _railstation.station_type << 8 | INVALID_STATION << 16;
 

	
 
		int w = _settings_client.gui.station_numtracks;
 
		int h = _settings_client.gui.station_platlength;
 
		if (!_railstation.orientation) Swap(w, h);
 

	
 
		CommandContainer cmdcont = { tile, p1, p2, CMD_BUILD_RAIL_STATION | CMD_MSG(STR_ERROR_CAN_T_BUILD_RAILROAD_STATION), CcStation, "" };
 
		ShowSelectStationIfNeeded(cmdcont, TileArea(tile, w, h));
 
	}
 
}
 

	
 
/**
 
 * Build a new signal or edit/remove a present signal, use CmdBuildSingleSignal() or CmdRemoveSingleSignal() in rail_cmd.cpp
 
 *
 
 * @param tile The tile where the signal will build or edit
 
 */
 
static void GenericPlaceSignals(TileIndex tile)
 
{
 
	TrackBits trackbits = TrackStatusToTrackBits(GetTileTrackStatus(tile, TRANSPORT_RAIL, 0));
 

	
 
	if (trackbits & TRACK_BIT_VERT) { // N-S direction
 
		trackbits = (_tile_fract_coords.x <= _tile_fract_coords.y) ? TRACK_BIT_RIGHT : TRACK_BIT_LEFT;
 
	}
 

	
 
	if (trackbits & TRACK_BIT_HORZ) { // E-W direction
 
		trackbits = (_tile_fract_coords.x + _tile_fract_coords.y <= 15) ? TRACK_BIT_UPPER : TRACK_BIT_LOWER;
 
	}
 

	
 
	Track track = FindFirstTrack(trackbits);
 

	
 
	if (_remove_button_clicked) {
 
		DoCommandP(tile, track, 0, CMD_REMOVE_SIGNALS | CMD_MSG(STR_ERROR_CAN_T_REMOVE_SIGNALS_FROM), CcPlaySound1E);
 
	} else {
 
		const Window *w = FindWindowById(WC_BUILD_SIGNAL, 0);
 

	
 
		/* Map the setting cycle_signal_types to the lower and upper allowed signal type. */
 
		static const uint cycle_bounds[] = {SIGTYPE_NORMAL | (SIGTYPE_LAST_NOPBS << 3), SIGTYPE_PBS | (SIGTYPE_LAST << 3), SIGTYPE_NORMAL | (SIGTYPE_LAST << 3)};
 

	
 
		/* various bitstuffed elements for CmdBuildSingleSignal() */
 
		uint32 p1 = track;
 

	
 
		if (w != NULL) {
 
			/* signal GUI is used */
 
			SB(p1, 3, 1, _ctrl_pressed);
 
			SB(p1, 4, 1, _cur_signal_variant);
 
			SB(p1, 5, 3, _cur_signal_type);
 
			SB(p1, 8, 1, _convert_signal_button);
 
			SB(p1, 9, 6, cycle_bounds[_settings_client.gui.cycle_signal_types]);
 
		} else {
 
			SB(p1, 3, 1, _ctrl_pressed);
 
			SB(p1, 4, 1, (_cur_year < _settings_client.gui.semaphore_build_before ? SIG_SEMAPHORE : SIG_ELECTRIC));
 
			SB(p1, 5, 3, _default_signal_type[_settings_client.gui.default_signal_type]);
 
			SB(p1, 8, 1, 0);
 
			SB(p1, 9, 6, cycle_bounds[_settings_client.gui.cycle_signal_types]);
 
		}
 

	
 
		DoCommandP(tile, p1, 0, CMD_BUILD_SIGNALS |
 
			CMD_MSG((w != NULL && _convert_signal_button) ? STR_ERROR_SIGNAL_CAN_T_CONVERT_SIGNALS_HERE : STR_ERROR_CAN_T_BUILD_SIGNALS_HERE),
 
			CcPlaySound1E);
 
	}
 
}
 

	
 
static void PlaceRail_Bridge(TileIndex tile)
 
{
 
	VpStartPlaceSizing(tile, VPM_X_OR_Y, DDSP_BUILD_BRIDGE);
 
}
 

	
 
/** Command callback for building a tunnel */
 
void CcBuildRailTunnel(bool success, TileIndex tile, uint32 p1, uint32 p2)
 
{
 
	if (success) {
 
		SndPlayTileFx(SND_20_SPLAT_2, tile);
 
		if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace();
 
	} else {
 
		SetRedErrorSquare(_build_tunnel_endtile);
 
	}
 
}
 

	
 
static void PlaceRail_Tunnel(TileIndex tile)
 
{
 
	DoCommandP(tile, _cur_railtype, 0, CMD_BUILD_TUNNEL | CMD_MSG(STR_ERROR_CAN_T_BUILD_TUNNEL_HERE), CcBuildRailTunnel);
 
}
 

	
 
static void PlaceRail_ConvertRail(TileIndex tile)
 
{
 
	VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_CONVERT_RAIL);
 
}
 

	
 
static void PlaceRail_AutoSignals(TileIndex tile)
 
{
 
	VpStartPlaceSizing(tile, VPM_SIGNALDIRS, DDSP_BUILD_SIGNALS);
 
}
 

	
 

	
 
/** Enum referring to the widgets of the build rail toolbar */
 
enum RailToolbarWidgets {
 
	RTW_CLOSEBOX = 0,
 
	RTW_CAPTION,
 
	RTW_STICKY,
 
	RTW_SPACER,
 
	RTW_BUILD_NS,
 
	RTW_BUILD_X,
 
	RTW_BUILD_EW,
 
	RTW_BUILD_Y,
 
	RTW_AUTORAIL,
 
	RTW_DEMOLISH,
 
	RTW_BUILD_DEPOT,
 
	RTW_BUILD_WAYPOINT,
 
	RTW_BUILD_STATION,
 
	RTW_BUILD_SIGNALS,
 
	RTW_BUILD_BRIDGE,
 
	RTW_BUILD_TUNNEL,
 
	RTW_REMOVE,
 
	RTW_CONVERT_RAIL,
 
};
 

	
 

	
 
/** Toggles state of the Remove button of Build rail toolbar
 
 * @param w window the button belongs to
 
 */
 
static void ToggleRailButton_Remove(Window *w)
 
{
 
	DeleteWindowById(WC_SELECT_STATION, 0);
 
	w->ToggleWidgetLoweredState(RTW_REMOVE);
 
	w->SetWidgetDirty(RTW_REMOVE);
 
	_remove_button_clicked = w->IsWidgetLowered(RTW_REMOVE);
 
	SetSelectionRed(_remove_button_clicked);
 
}
 

	
 
/** Updates the Remove button because of Ctrl state change
 
 * @param w window the button belongs to
 
 * @return true iff the remove buton was changed
 
 */
 
static bool RailToolbar_CtrlChanged(Window *w)
 
{
 
	if (w->IsWidgetDisabled(RTW_REMOVE)) return false;
 

	
 
	/* allow ctrl to switch remove mode only for these widgets */
 
	for (uint i = RTW_BUILD_NS; i <= RTW_BUILD_STATION; i++) {
 
		if ((i <= RTW_AUTORAIL || i >= RTW_BUILD_WAYPOINT) && w->IsWidgetLowered(i)) {
 
			ToggleRailButton_Remove(w);
 
			return true;
 
		}
 
	}
 

	
 
	return false;
 
}
 

	
 

	
 
/**
 
 * The "rail N"-button click proc of the build-rail toolbar.
 
 * @param w Build-rail toolbar window
 
 * @see BuildRailToolbWndProc()
 
 */
 
static void BuildRailClick_N(Window *w)
 
{
 
	HandlePlacePushButton(w, RTW_BUILD_NS, GetRailTypeInfo(_cur_railtype)->cursor.rail_ns, HT_LINE | HT_DIR_VL, PlaceRail_N);
 
}
 

	
 
/**
 
 * The "rail NE"-button click proc of the build-rail toolbar.
 
 * @param w Build-rail toolbar window
 
 * @see BuildRailToolbWndProc()
 
 */
 
static void BuildRailClick_NE(Window *w)
 
{
 
	HandlePlacePushButton(w, RTW_BUILD_X, GetRailTypeInfo(_cur_railtype)->cursor.rail_swne, HT_LINE | HT_DIR_X, PlaceRail_NE);
 
}
 

	
 
/**
 
 * The "rail E"-button click proc of the build-rail toolbar.
 
 * @param w Build-rail toolbar window
 
 * @see BuildRailToolbWndProc()
 
 */
 
static void BuildRailClick_E(Window *w)
 
{
 
	HandlePlacePushButton(w, RTW_BUILD_EW, GetRailTypeInfo(_cur_railtype)->cursor.rail_ew, HT_LINE | HT_DIR_HL, PlaceRail_E);
 
}
 

	
 
/**
 
 * The "rail NW"-button click proc of the build-rail toolbar.
 
 * @param w Build-rail toolbar window
 
 * @see BuildRailToolbWndProc()
 
 */
 
static void BuildRailClick_NW(Window *w)
 
{
 
	HandlePlacePushButton(w, RTW_BUILD_Y, GetRailTypeInfo(_cur_railtype)->cursor.rail_nwse, HT_LINE | HT_DIR_Y, PlaceRail_NW);
 
}
 

	
 
/**
 
 * The "auto-rail"-button click proc of the build-rail toolbar.
 
 * @param w Build-rail toolbar window
 
 * @see BuildRailToolbWndProc()
 
 */
 
static void BuildRailClick_AutoRail(Window *w)
 
{
 
	HandlePlacePushButton(w, RTW_AUTORAIL, GetRailTypeInfo(_cur_railtype)->cursor.autorail, HT_RAIL, PlaceRail_AutoRail);
 
}
 

	
 
/**
 
 * The "demolish"-button click proc of the build-rail toolbar.
 
 * @param w Build-rail toolbar window
 
 * @see BuildRailToolbWndProc()
 
 */
 
static void BuildRailClick_Demolish(Window *w)
 
{
 
	HandlePlacePushButton(w, RTW_DEMOLISH, ANIMCURSOR_DEMOLISH, HT_RECT, PlaceProc_DemolishArea);
 
}
 

	
 
/**
 
 * The "build depot"-button click proc of the build-rail toolbar.
 
 * @param w Build-rail toolbar window
 
 * @see BuildRailToolbWndProc()
 
 */
 
static void BuildRailClick_Depot(Window *w)
 
{
 
	if (HandlePlacePushButton(w, RTW_BUILD_DEPOT, GetRailTypeInfo(_cur_railtype)->cursor.depot, HT_RECT, PlaceRail_Depot)) {
 
		ShowBuildTrainDepotPicker(w);
 
	}
 
}
 

	
 
/**
 
 * The "build waypoint"-button click proc of the build-rail toolbar.
 
 * If there are newGRF waypoints, also open a window to pick the waypoint type.
 
 * @param w Build-rail toolbar window
 
 * @see BuildRailToolbWndProc()
 
 */
 
static void BuildRailClick_Waypoint(Window *w)
 
{
 
	_waypoint_count = GetNumCustomStations(STAT_CLASS_WAYP);
 
	if (HandlePlacePushButton(w, RTW_BUILD_WAYPOINT, SPR_CURSOR_WAYPOINT, HT_RECT, PlaceRail_Waypoint) &&
 
			_waypoint_count > 1) {
 
		ShowBuildWaypointPicker(w);
 
	}
 
}
 

	
 
/**
 
 * The "build station"-button click proc of the build-rail toolbar.
 
 * @param w Build-rail toolbar window
 
 * @see BuildRailToolbWndProc()
 
 */
 
static void BuildRailClick_Station(Window *w)
 
{
 
	if (HandlePlacePushButton(w, RTW_BUILD_STATION, SPR_CURSOR_RAIL_STATION, HT_RECT, PlaceRail_Station)) ShowStationBuilder(w);
 
}
 

	
 
/**
 
 * The "build signal"-button click proc of the build-rail toolbar.
 
 * Start ShowSignalBuilder() and/or HandleAutoSignalPlacement().
 
 * @param w Build-rail toolbar window
 
 * @see BuildRailToolbWndProc()
 
 */
 
static void BuildRailClick_AutoSignals(Window *w)
 
{
 
	if (_settings_client.gui.enable_signal_gui != _ctrl_pressed) {
 
		if (HandlePlacePushButton(w, RTW_BUILD_SIGNALS, ANIMCURSOR_BUILDSIGNALS, HT_RECT, PlaceRail_AutoSignals)) ShowSignalBuilder(w);
 
	} else {
 
		HandlePlacePushButton(w, RTW_BUILD_SIGNALS, ANIMCURSOR_BUILDSIGNALS, HT_RECT, PlaceRail_AutoSignals);
 
	}
 
}
 

	
 
/**
 
 * The "build bridge"-button click proc of the build-rail toolbar.
 
 * @param w Build-rail toolbar window
 
 * @see BuildRailToolbWndProc()
 
 */
 
static void BuildRailClick_Bridge(Window *w)
 
{
 
	HandlePlacePushButton(w, RTW_BUILD_BRIDGE, SPR_CURSOR_BRIDGE, HT_RECT, PlaceRail_Bridge);
 
}
 

	
 
/**
 
 * The "build tunnel"-button click proc of the build-rail toolbar.
 
 * @param w Build-rail toolbar window
 
 * @see BuildRailToolbWndProc()
 
 */
 
static void BuildRailClick_Tunnel(Window *w)
 
{
 
	HandlePlacePushButton(w, RTW_BUILD_TUNNEL, GetRailTypeInfo(_cur_railtype)->cursor.tunnel, HT_SPECIAL, PlaceRail_Tunnel);
 
}
 

	
 
/**
 
 * The "remove"-button click proc of the build-rail toolbar.
 
 * @param w Build-rail toolbar window
 
 * @see BuildRailToolbWndProc()
 
 */
 
static void BuildRailClick_Remove(Window *w)
 
{
 
	if (w->IsWidgetDisabled(RTW_REMOVE)) return;
 
	ToggleRailButton_Remove(w);
 
	SndPlayFx(SND_15_BEEP);
 

	
 
	/* handle station builder */
 
	if (w->IsWidgetLowered(RTW_BUILD_STATION)) {
 
		if (_remove_button_clicked) {
 
			/* starting drag & drop remove */
 
			if (!_settings_client.gui.station_dragdrop) {
 
				SetTileSelectSize(1, 1);
 
			} else {
 
				VpSetPlaceSizingLimit(-1);
 
			}
 
		} else {
 
			/* starting station build mode */
 
			if (!_settings_client.gui.station_dragdrop) {
 
				int x = _settings_client.gui.station_numtracks;
 
				int y = _settings_client.gui.station_platlength;
 
				if (_railstation.orientation == 0) Swap(x, y);
 
				SetTileSelectSize(x, y);
 
			} else {
 
				VpSetPlaceSizingLimit(_settings_game.station.station_spread);
 
			}
 
		}
 
	}
 
}
 

	
 
/**
 
 * The "convert-rail"-button click proc of the build-rail toolbar.
 
 * Switches to 'convert-rail' mode
 
 * @param w Build-rail toolbar window
 
 * @see BuildRailToolbWndProc()
 
 */
 
static void BuildRailClick_Convert(Window *w)
 
{
 
	HandlePlacePushButton(w, RTW_CONVERT_RAIL, GetRailTypeInfo(_cur_railtype)->cursor.convert, HT_RECT, PlaceRail_ConvertRail);
 
}
 

	
 

	
 
static void DoRailroadTrack(int mode)
 
{
 
	DoCommandP(TileVirtXY(_thd.selstart.x, _thd.selstart.y), TileVirtXY(_thd.selend.x, _thd.selend.y), _cur_railtype | (mode << 4),
 
		_remove_button_clicked ?
 
		CMD_REMOVE_RAILROAD_TRACK | CMD_MSG(STR_ERROR_CAN_T_REMOVE_RAILROAD_TRACK) :
 
		CMD_BUILD_RAILROAD_TRACK  | CMD_MSG(STR_ERROR_CAN_T_BUILD_RAILROAD_TRACK)
 
	);
 
}
 

	
 
static void HandleAutodirPlacement()
 
{
 
	TileHighlightData *thd = &_thd;
 
	int trackstat = thd->drawstyle & 0xF; // 0..5
 

	
 
	if (thd->drawstyle & HT_RAIL) { // one tile case
 
		GenericPlaceRail(TileVirtXY(thd->selend.x, thd->selend.y), trackstat);
 
		return;
 
	}
 

	
 
	DoRailroadTrack(trackstat);
 
}
 

	
 
/**
 
 * Build new signals or remove signals or (if only one tile marked) edit a signal.
 
 *
 
 * If one tile marked abort and use GenericPlaceSignals()
 
 * else use CmdBuildSingleSignal() or CmdRemoveSingleSignal() in rail_cmd.cpp to build many signals
 
 */
 
static void HandleAutoSignalPlacement()
 
{
 
	TileHighlightData *thd = &_thd;
 
	uint32 p2 = GB(thd->drawstyle, 0, 3); // 0..5
 

	
 
	if (thd->drawstyle == HT_RECT) { // one tile case
 
		GenericPlaceSignals(TileVirtXY(thd->selend.x, thd->selend.y));
 
		return;
 
	}
 

	
 
	const Window *w = FindWindowById(WC_BUILD_SIGNAL, 0);
 

	
 
	if (w != NULL) {
 
		/* signal GUI is used */
 
		SB(p2,  3, 1, 0);
 
		SB(p2,  4, 1, _cur_signal_variant);
 
		SB(p2,  6, 1, _ctrl_pressed);
 
		SB(p2,  7, 3, _cur_signal_type);
 
		SB(p2, 24, 8, _settings_client.gui.drag_signals_density);
 
	} else {
 
		SB(p2,  3, 1, 0);
 
		SB(p2,  4, 1, (_cur_year < _settings_client.gui.semaphore_build_before ? SIG_SEMAPHORE : SIG_ELECTRIC));
 
		SB(p2,  6, 1, _ctrl_pressed);
 
		SB(p2,  7, 3, _default_signal_type[_settings_client.gui.default_signal_type]);
 
		SB(p2, 24, 8, _settings_client.gui.drag_signals_density);
 
	}
 

	
 
	/* _settings_client.gui.drag_signals_density is given as a parameter such that each user
 
	 * in a network game can specify his/her own signal density */
 
	DoCommandP(
 
		TileVirtXY(thd->selstart.x, thd->selstart.y),
 
		TileVirtXY(thd->selend.x, thd->selend.y),
 
		p2,
 
		_remove_button_clicked ?
 
			CMD_REMOVE_SIGNAL_TRACK | CMD_MSG(STR_ERROR_CAN_T_REMOVE_SIGNALS_FROM) :
 
			CMD_BUILD_SIGNAL_TRACK  | CMD_MSG(STR_ERROR_CAN_T_BUILD_SIGNALS_HERE),
 
		CcPlaySound1E);
 
}
 

	
 

	
 
typedef void OnButtonClick(Window *w);
 

	
 
/** Data associated with a push button in the build rail toolbar window */
 
struct RailBuildingGUIButtonData {
 
	uint16 keycode;            ///< Keycode associated with the button
 
	OnButtonClick *click_proc; ///< Procedure to call when button is clicked
 
};
 

	
 
/**
 
 * GUI rail-building button data constants.
 
 * Offsets match widget order, starting at RTW_BUILD_NS
 
 */
 
static const RailBuildingGUIButtonData _rail_build_button_data[] = {
 
	{'1', BuildRailClick_N          },
 
	{'2', BuildRailClick_NE         },
 
	{'3', BuildRailClick_E          },
 
	{'4', BuildRailClick_NW         },
 
	{'5', BuildRailClick_AutoRail   },
 
	{'6', BuildRailClick_Demolish   },
 
	{'7', BuildRailClick_Depot      },
 
	{'8', BuildRailClick_Waypoint   },
 
	{'9', BuildRailClick_Station    },
 
	{'S', BuildRailClick_AutoSignals},
 
	{'B', BuildRailClick_Bridge     },
 
	{'T', BuildRailClick_Tunnel     },
 
	{'R', BuildRailClick_Remove     },
 
	{'C', BuildRailClick_Convert    }
 
};
 

	
 
/**
 
 * Based on the widget clicked, update the status of the 'remove' button.
 
 * @param w              Rail toolbar window
 
 * @param clicked_widget Widget clicked in the toolbar
 
 */
 
struct BuildRailToolbarWindow : Window {
 
	BuildRailToolbarWindow(const WindowDesc *desc, WindowNumber window_number, RailType railtype) : Window()
 
	{
 
		this->InitNested(desc);
 
		this->SetupRailToolbar(railtype);
 
		this->DisableWidget(RTW_REMOVE);
 

	
 
		if (_settings_client.gui.link_terraform_toolbar) ShowTerraformToolbar(this);
 
	}
 

	
 
	~BuildRailToolbarWindow()
 
	{
 
		if (_settings_client.gui.link_terraform_toolbar) DeleteWindowById(WC_SCEN_LAND_GEN, 0, false);
 
	}
 

	
 
	/** Configures the rail toolbar for railtype given
 
	 * @param railtype the railtype to display
 
	 */
 
	void SetupRailToolbar(RailType railtype)
 
	{
 
		const RailtypeInfo *rti = GetRailTypeInfo(railtype);
 

	
 
		assert(railtype < RAILTYPE_END);
 
		this->GetWidget<NWidgetCore>(RTW_CAPTION)->widget_data      = rti->strings.toolbar_caption;
 
		this->GetWidget<NWidgetCore>(RTW_BUILD_NS)->widget_data     = rti->gui_sprites.build_ns_rail;
 
		this->GetWidget<NWidgetCore>(RTW_BUILD_X)->widget_data      = rti->gui_sprites.build_x_rail;
 
		this->GetWidget<NWidgetCore>(RTW_BUILD_EW)->widget_data     = rti->gui_sprites.build_ew_rail;
 
		this->GetWidget<NWidgetCore>(RTW_BUILD_Y)->widget_data      = rti->gui_sprites.build_y_rail;
 
		this->GetWidget<NWidgetCore>(RTW_AUTORAIL)->widget_data     = rti->gui_sprites.auto_rail;
 
		this->GetWidget<NWidgetCore>(RTW_BUILD_DEPOT)->widget_data  = rti->gui_sprites.build_depot;
 
		this->GetWidget<NWidgetCore>(RTW_CONVERT_RAIL)->widget_data = rti->gui_sprites.convert_rail;
 
		this->GetWidget<NWidgetCore>(RTW_BUILD_TUNNEL)->widget_data = rti->gui_sprites.build_tunnel;
 
	}
 

	
 
	/** Switch to another rail type.
 
	 * @param railtype New rail type.
 
	 */
 
	void ModifyRailType(RailType railtype)
 
	{
 
		this->SetupRailToolbar(railtype);
 
		this->ReInit();
 
	}
 

	
 
	void UpdateRemoveWidgetStatus(int clicked_widget)
 
	{
 
		switch (clicked_widget) {
 
			case RTW_REMOVE:
 
				/* If it is the removal button that has been clicked, do nothing,
 
				 * as it is up to the other buttons to drive removal status */
 
				return;
 
				break;
 
			case RTW_BUILD_NS:
 
			case RTW_BUILD_X:
 
			case RTW_BUILD_EW:
 
			case RTW_BUILD_Y:
 
			case RTW_AUTORAIL:
 
			case RTW_BUILD_WAYPOINT:
 
			case RTW_BUILD_STATION:
 
			case RTW_BUILD_SIGNALS:
 
				/* Removal button is enabled only if the rail/signal/waypoint/station
 
				 * button is still lowered.  Once raised, it has to be disabled */
 
				this->SetWidgetDisabledState(RTW_REMOVE, !this->IsWidgetLowered(clicked_widget));
 
				break;
 

	
 
			default:
 
				/* When any other buttons than rail/signal/waypoint/station, raise and
 
				 * disable the removal button */
 
				this->DisableWidget(RTW_REMOVE);
 
				this->RaiseWidget(RTW_REMOVE);
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		if (widget >= RTW_BUILD_NS) {
 
			_remove_button_clicked = false;
 
			_rail_build_button_data[widget - RTW_BUILD_NS].click_proc(this);
 
		}
 
		this->UpdateRemoveWidgetStatus(widget);
 
		if (_ctrl_pressed) RailToolbar_CtrlChanged(this);
 
	}
 

	
 
	virtual EventState OnKeyPress(uint16 key, uint16 keycode)
 
	{
 
		EventState state = ES_NOT_HANDLED;
 
		for (uint8 i = 0; i != lengthof(_rail_build_button_data); i++) {
 
			if (keycode == _rail_build_button_data[i].keycode) {
 
				_remove_button_clicked = false;
 
				_rail_build_button_data[i].click_proc(this);
 
				this->UpdateRemoveWidgetStatus(i + RTW_BUILD_NS);
 
				if (_ctrl_pressed) RailToolbar_CtrlChanged(this);
 
				state = ES_HANDLED;
 
				break;
 
			}
 
		}
 
		MarkTileDirtyByTile(TileVirtXY(_thd.pos.x, _thd.pos.y)); // redraw tile selection
 
		return state;
 
	}
 

	
 
	virtual void OnPlaceObject(Point pt, TileIndex tile)
 
	{
 
		_place_proc(tile);
 
	}
 

	
 
	virtual void OnPlaceDrag(ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, Point pt)
 
	{
 
		/* no dragging if you have pressed the convert button */
 
		if (FindWindowById(WC_BUILD_SIGNAL, 0) != NULL && _convert_signal_button && this->IsWidgetLowered(RTW_BUILD_SIGNALS)) return;
 

	
 
		VpSelectTilesWithMethod(pt.x, pt.y, select_method);
 
	}
 

	
 
	virtual void OnPlaceMouseUp(ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, Point pt, TileIndex start_tile, TileIndex end_tile)
 
	{
 
		if (pt.x != -1) {
 
			switch (select_proc) {
 
				default: NOT_REACHED();
 
				case DDSP_BUILD_BRIDGE:
 
					if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace();
 
					ShowBuildBridgeWindow(start_tile, end_tile, TRANSPORT_RAIL, _cur_railtype);
 
					break;
 

	
 
				case DDSP_PLACE_RAIL:
 
					HandleAutodirPlacement();
 
					break;
 

	
 
				case DDSP_BUILD_SIGNALS:
 
					HandleAutoSignalPlacement();
 
					break;
 

	
 
				case DDSP_DEMOLISH_AREA:
 
					GUIPlaceProcDragXY(select_proc, start_tile, end_tile);
 
					break;
 

	
 
				case DDSP_CONVERT_RAIL:
 
					DoCommandP(end_tile, start_tile, _cur_railtype, CMD_CONVERT_RAIL | CMD_MSG(STR_ERROR_CAN_T_CONVERT_RAIL), CcPlaySound10);
 
					break;
 

	
 
				case DDSP_REMOVE_STATION:
 
				case DDSP_BUILD_STATION:
 
					if (this->IsWidgetLowered(RTW_BUILD_STATION)) {
 
						/* Station */
 
						if (_remove_button_clicked) {
 
							DoCommandP(end_tile, start_tile, _ctrl_pressed ? 0 : 1, CMD_REMOVE_FROM_RAIL_STATION | CMD_MSG(STR_ERROR_CAN_T_REMOVE_PART_OF_STATION), CcPlaySound1E);
 
						} else {
 
							HandleStationPlacement(start_tile, end_tile);
 
						}
 
					} else {
 
						/* Waypoint */
 
						if (_remove_button_clicked) {
 
							DoCommandP(end_tile, start_tile, _ctrl_pressed ? 0 : 1, CMD_REMOVE_FROM_RAIL_WAYPOINT | CMD_MSG(STR_ERROR_CAN_T_REMOVE_TRAIN_WAYPOINT), CcPlaySound1E);
 
						} else {
 
							TileArea ta(start_tile, end_tile);
 
							uint32 p1 = _cur_railtype | (select_method == VPM_FIX_X ? AXIS_X : AXIS_Y) << 4 | ta.w << 8 | ta.h << 16 | _ctrl_pressed << 24;
 
							uint32 p2 = STAT_CLASS_WAYP | _cur_waypoint_type << 8 | INVALID_STATION << 16;
 

	
 
							CommandContainer cmdcont = { ta.tile, p1, p2, CMD_BUILD_RAIL_WAYPOINT | CMD_MSG(STR_ERROR_CAN_T_BUILD_TRAIN_WAYPOINT), CcPlaySound1E, "" };
 
							ShowSelectWaypointIfNeeded(cmdcont, ta);
 
						}
 
					}
 
					break;
 
			}
 
		}
 
	}
 

	
 
	virtual void OnPlaceObjectAbort()
 
	{
 
		this->RaiseButtons();
 
		this->DisableWidget(RTW_REMOVE);
 
		this->SetWidgetDirty(RTW_REMOVE);
 

	
 
		DeleteWindowById(WC_BUILD_SIGNAL, TRANSPORT_RAIL);
 
		DeleteWindowById(WC_BUILD_STATION, TRANSPORT_RAIL);
 
		DeleteWindowById(WC_BUILD_DEPOT, TRANSPORT_RAIL);
 
		DeleteWindowById(WC_SELECT_STATION, 0);
 
		DeleteWindowByClass(WC_BUILD_BRIDGE);
 
	}
 

	
 
	virtual void OnPlacePresize(Point pt, TileIndex tile)
 
	{
 
		DoCommand(tile, _cur_railtype, 0, DC_AUTO, CMD_BUILD_TUNNEL);
 
		VpSetPresizeRange(tile, _build_tunnel_endtile == 0 ? tile : _build_tunnel_endtile);
 
	}
 

	
 
	virtual EventState OnCTRLStateChange()
 
	{
 
		/* do not toggle Remove button by Ctrl when placing station */
 
		if (!this->IsWidgetLowered(RTW_BUILD_STATION) && !this->IsWidgetLowered(RTW_BUILD_WAYPOINT) && RailToolbar_CtrlChanged(this)) return ES_HANDLED;
 
		return ES_NOT_HANDLED;
 
	}
 
};
 

	
 
static const NWidgetPart _nested_build_rail_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN, RTW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, RTW_CAPTION), SetDataTip(STR_RAIL_TOOLBAR_RAILROAD_CONSTRUCTION_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_STICKYBOX, COLOUR_DARK_GREEN, RTW_STICKY),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_BUILD_NS),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_RAIL_NS, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_TRACK),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_RAIL_NS, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_TRACK),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_BUILD_X),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_RAIL_NE, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_TRACK),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_RAIL_NE, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_TRACK),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_BUILD_EW),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_RAIL_EW, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_TRACK),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_RAIL_EW, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_TRACK),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_BUILD_Y),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_RAIL_NW, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_TRACK),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_RAIL_NW, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_TRACK),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_AUTORAIL),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_AUTORAIL, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_AUTORAIL),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_AUTORAIL, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_AUTORAIL),
 

	
 
		NWidget(WWT_PANEL, COLOUR_DARK_GREEN, RTW_SPACER), SetMinimalSize(4, 22), SetDataTip(0x0, STR_NULL), EndContainer(),
 

	
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_DEMOLISH),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_BUILD_DEPOT),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_DEPOT_RAIL, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_TRAIN_DEPOT_FOR_BUILDING),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_DEPOT_RAIL, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_TRAIN_DEPOT_FOR_BUILDING),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_BUILD_WAYPOINT),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_WAYPOINT, STR_RAIL_TOOLBAR_TOOLTIP_CONVERT_RAIL_TO_WAYPOINT),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_WAYPOINT, STR_RAIL_TOOLBAR_TOOLTIP_CONVERT_RAIL_TO_WAYPOINT),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_BUILD_STATION),
 
						SetFill(false, true), SetMinimalSize(42, 22), SetDataTip(SPR_IMG_RAIL_STATION, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_STATION),
 
						SetFill(0, 1), SetMinimalSize(42, 22), SetDataTip(SPR_IMG_RAIL_STATION, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_STATION),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_BUILD_SIGNALS),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_RAIL_SIGNALS, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_SIGNALS),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_RAIL_SIGNALS, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_SIGNALS),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_BUILD_BRIDGE),
 
						SetFill(false, true), SetMinimalSize(42, 22), SetDataTip(SPR_IMG_BRIDGE, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_BRIDGE),
 
						SetFill(0, 1), SetMinimalSize(42, 22), SetDataTip(SPR_IMG_BRIDGE, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_BRIDGE),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_BUILD_TUNNEL),
 
						SetFill(false, true), SetMinimalSize(20, 22), SetDataTip(SPR_IMG_TUNNEL_RAIL, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_TUNNEL),
 
						SetFill(0, 1), SetMinimalSize(20, 22), SetDataTip(SPR_IMG_TUNNEL_RAIL, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_TUNNEL),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_REMOVE),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_REMOVE, STR_RAIL_TOOLBAR_TOOLTIP_TOGGLE_BUILD_REMOVE_FOR),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_REMOVE, STR_RAIL_TOOLBAR_TOOLTIP_TOGGLE_BUILD_REMOVE_FOR),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_CONVERT_RAIL),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_CONVERT_RAIL, STR_RAIL_TOOLBAR_TOOLTIP_CONVERT_RAIL),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_CONVERT_RAIL, STR_RAIL_TOOLBAR_TOOLTIP_CONVERT_RAIL),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _build_rail_desc(
 
	WDP_ALIGN_TBR, 22, 350, 36,
 
	WC_BUILD_TOOLBAR, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_STICKY_BUTTON | WDF_CONSTRUCTION,
 
	_nested_build_rail_widgets, lengthof(_nested_build_rail_widgets)
 
);
 

	
 

	
 
/**
 
 * Open the build rail toolbar window for a specific rail type.
 
 * The window may be opened in the 'normal' way by clicking at the rail icon in
 
 * the main toolbar, or by means of selecting one of the functions of the
 
 * toolbar. In the latter case, the corresponding widget is also selected.
 
 *
 
 * If the terraform toolbar is linked to the toolbar, that window is also opened.
 
 *
 
 * @param railtype Rail type to open the window for
 
 * @param button   Widget clicked (\c -1 means no button clicked)
 
 */
 
void ShowBuildRailToolbar(RailType railtype, int button)
 
{
 
	if (!Company::IsValidID(_local_company)) return;
 
	if (!ValParamRailtype(railtype)) return;
 

	
 
	BuildRailToolbarWindow *w = (BuildRailToolbarWindow *)FindWindowById(WC_BUILD_TOOLBAR, TRANSPORT_RAIL);
 

	
 
	/* don't recreate the window if we're clicking on a button and the window exists. */
 
	if (button < 0 || w == NULL) {
 
		DeleteWindowByClass(WC_BUILD_TOOLBAR);
 
		_cur_railtype = railtype;
 
		w = new BuildRailToolbarWindow(&_build_rail_desc, TRANSPORT_RAIL, railtype);
 
	}
 

	
 
	_remove_button_clicked = false;
 
	if (w != NULL && button >= RTW_CLOSEBOX) {
 
		_rail_build_button_data[button].click_proc(w);
 
		w->UpdateRemoveWidgetStatus(button + RTW_BUILD_NS);
 
	}
 
}
 

	
 
/* TODO: For custom stations, respect their allowed platforms/lengths bitmasks!
 
 * --pasky */
 

	
 
static void HandleStationPlacement(TileIndex start, TileIndex end)
 
{
 
	TileArea ta(start, end);
 
	uint numtracks = ta.w;
 
	uint platlength = ta.h;
 

	
 
	if (_railstation.orientation == AXIS_X) Swap(numtracks, platlength);
 

	
 
	uint32 p1 = _cur_railtype | _railstation.orientation << 4 | numtracks << 8 | platlength << 16 | _ctrl_pressed << 24;
 
	uint32 p2 = _railstation.station_class | _railstation.station_type << 8 | INVALID_STATION << 16;
 

	
 
	CommandContainer cmdcont = { ta.tile, p1, p2, CMD_BUILD_RAIL_STATION | CMD_MSG(STR_ERROR_CAN_T_BUILD_RAILROAD_STATION), CcStation, "" };
 
	ShowSelectStationIfNeeded(cmdcont, ta);
 
}
 

	
 
/** Enum referring to the widgets of the rail stations window */
 
enum BuildRailStationWidgets {
 
	BRSW_CLOSEBOX = 0,
 
	BRSW_CAPTION,
 
	BRSW_BACKGROUND,
 

	
 
	BRSW_ORIENT_LABEL, ///< Text label 'Orientation'.
 
	BRSW_PLATFORM_DIR_X,
 
	BRSW_PLATFORM_DIR_Y,
 

	
 
	BRSW_PLATFORM_NUM_LABEL, ///< Text label 'Number of platforms'
 
	BRSW_PLATFORM_NUM_BEGIN = BRSW_PLATFORM_NUM_LABEL,
 
	BRSW_PLATFORM_NUM_1,
 
	BRSW_PLATFORM_NUM_2,
 
	BRSW_PLATFORM_NUM_3,
 
	BRSW_PLATFORM_NUM_4,
 
	BRSW_PLATFORM_NUM_5,
 
	BRSW_PLATFORM_NUM_6,
 
	BRSW_PLATFORM_NUM_7,
 

	
 
	BRSW_PLATFORM_LEN_LABEL, ///< Text label 'Platform length'
 
	BRSW_PLATFORM_LEN_BEGIN = BRSW_PLATFORM_LEN_LABEL,
 
	BRSW_PLATFORM_LEN_1,
 
	BRSW_PLATFORM_LEN_2,
 
	BRSW_PLATFORM_LEN_3,
 
	BRSW_PLATFORM_LEN_4,
 
	BRSW_PLATFORM_LEN_5,
 
	BRSW_PLATFORM_LEN_6,
 
	BRSW_PLATFORM_LEN_7,
 

	
 
	BRSW_PLATFORM_DRAG_N_DROP,
 

	
 
	BRSW_HIGHLIGHT_LABEL, ///< Text label 'Coverage area highlight'
 
	BRSW_HIGHLIGHT_OFF,
 
	BRSW_HIGHLIGHT_ON,
 

	
 
	BRSW_NEWST_DROPDOWN,
 
	BRSW_NEWST_LIST,
 
	BRSW_NEWST_SCROLL
 
};
 

	
 
struct BuildRailStationWindow : public PickerWindowBase {
 
private:
 
	uint line_height; ///< Height of a single line in the newstation selection matrix (#BRSW_NEWST_LIST widget).
 

	
 
	/**
 
	 * Verify whether the currently selected station size is allowed after selecting a new station class/type.
 
	 * If not, change the station size variables ( _settings_client.gui.station_numtracks and _settings_client.gui.station_platlength ).
 
	 * @param statspec Specification of the new station class/type
 
	 */
 
	void CheckSelectedSize(const StationSpec *statspec)
 
	{
 
		if (statspec == NULL || _settings_client.gui.station_dragdrop) return;
 

	
 
		/* If current number of tracks is not allowed, make it as big as possible (which is always less than currently selected) */
 
		if (HasBit(statspec->disallowed_platforms, _settings_client.gui.station_numtracks - 1)) {
 
			this->RaiseWidget(_settings_client.gui.station_numtracks + BRSW_PLATFORM_NUM_BEGIN);
 
			_settings_client.gui.station_numtracks = 1;
 
			while (HasBit(statspec->disallowed_platforms, _settings_client.gui.station_numtracks - 1)) {
 
				_settings_client.gui.station_numtracks++;
 
			}
 
			this->LowerWidget(_settings_client.gui.station_numtracks + BRSW_PLATFORM_NUM_BEGIN);
 
		}
 

	
 
		if (HasBit(statspec->disallowed_lengths, _settings_client.gui.station_platlength - 1)) {
 
			this->RaiseWidget(_settings_client.gui.station_platlength + BRSW_PLATFORM_LEN_BEGIN);
 
			_settings_client.gui.station_platlength = 1;
 
			while (HasBit(statspec->disallowed_lengths, _settings_client.gui.station_platlength - 1)) {
 
				_settings_client.gui.station_platlength++;
 
			}
 
			this->LowerWidget(_settings_client.gui.station_platlength + BRSW_PLATFORM_LEN_BEGIN);
 
		}
 
	}
 

	
 
	/** Build a dropdown list of available station classes */
 
	static DropDownList *BuildStationClassDropDown()
 
	{
 
		DropDownList *list = new DropDownList();
 

	
 
		for (uint i = 0; i < GetNumStationClasses(); i++) {
 
			if (i == STAT_CLASS_WAYP) continue;
 
			list->push_back(new DropDownListStringItem(GetStationClassName((StationClassID)i), i, false));
 
		}
 

	
 
		return list;
 
	}
 

	
 
public:
 
	BuildRailStationWindow(const WindowDesc *desc, Window *parent, bool newstation) : PickerWindowBase(parent)
 
	{
 
		this->InitNested(desc, TRANSPORT_RAIL);
 

	
 
		this->LowerWidget(_railstation.orientation + BRSW_PLATFORM_DIR_X);
 
		if (_settings_client.gui.station_dragdrop) {
 
			this->LowerWidget(BRSW_PLATFORM_DRAG_N_DROP);
 
		} else {
 
			this->LowerWidget(_settings_client.gui.station_numtracks + BRSW_PLATFORM_NUM_BEGIN);
 
			this->LowerWidget(_settings_client.gui.station_platlength + BRSW_PLATFORM_LEN_BEGIN);
 
		}
 
		this->SetWidgetLoweredState(BRSW_HIGHLIGHT_OFF, !_settings_client.gui.station_show_coverage);
 
		this->SetWidgetLoweredState(BRSW_HIGHLIGHT_ON, _settings_client.gui.station_show_coverage);
 

	
 
		_railstation.newstations = newstation;
 

	
 
		if (newstation) {
 
			_railstation.station_count = GetNumCustomStations(_railstation.station_class);
 

	
 
			this->vscroll.SetCount(_railstation.station_count);
 
			this->vscroll.SetCapacity(GB(this->GetWidget<NWidgetCore>(BRSW_NEWST_LIST)->widget_data, MAT_ROW_START, MAT_ROW_BITS));
 
			this->vscroll.SetPosition(Clamp(_railstation.station_type - 2, 0, this->vscroll.GetCount() - this->vscroll.GetCapacity()));
 
		} else {
 
			/* New stations are not available, so ensure the default station
 
			 * type is 'selected'. */
 
			_railstation.station_class = STAT_CLASS_DFLT;
 
			_railstation.station_type = 0;
 
		}
 
	}
 

	
 
	virtual ~BuildRailStationWindow()
 
	{
 
		DeleteWindowById(WC_SELECT_STATION, 0);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		bool newstations = _railstation.newstations;
 
		const StationSpec *statspec = newstations ? GetCustomStationSpec(_railstation.station_class, _railstation.station_type) : NULL;
 

	
 
		if (_settings_client.gui.station_dragdrop) {
 
			SetTileSelectSize(1, 1);
 
		} else {
 
			int x = _settings_client.gui.station_numtracks;
 
			int y = _settings_client.gui.station_platlength;
 
			if (_railstation.orientation == AXIS_X) Swap(x, y);
 
			if (!_remove_button_clicked)
 
				SetTileSelectSize(x, y);
 
		}
 

	
 
		int rad = (_settings_game.station.modified_catchment) ? CA_TRAIN : CA_UNMODIFIED;
 

	
 
		if (_settings_client.gui.station_show_coverage)
 
			SetTileSelectBigSize(-rad, -rad, 2 * rad, 2 * rad);
 

	
 
		for (uint bits = 0; bits < 7; bits++) {
 
			bool disable = bits >= _settings_game.station.station_spread;
 
			if (statspec == NULL) {
 
				this->SetWidgetDisabledState(bits + BRSW_PLATFORM_NUM_1, disable);
 
				this->SetWidgetDisabledState(bits + BRSW_PLATFORM_LEN_1, disable);
 
			} else {
 
				this->SetWidgetDisabledState(bits + BRSW_PLATFORM_NUM_1, HasBit(statspec->disallowed_platforms, bits) || disable);
 
				this->SetWidgetDisabledState(bits + BRSW_PLATFORM_LEN_1, HasBit(statspec->disallowed_lengths,   bits) || disable);
 
			}
 
		}
 

	
 
		this->DrawWidgets();
 

	
 
		/* 'Accepts' and 'Supplies' texts. */
 
		int top = this->GetWidget<NWidgetBase>(BRSW_HIGHLIGHT_ON)->pos_y + this->GetWidget<NWidgetBase>(BRSW_HIGHLIGHT_ON)->current_y + WD_PAR_VSEP_NORMAL;
 
		NWidgetBase *back_nwi = this->GetWidget<NWidgetBase>(BRSW_BACKGROUND);
 
		int right = back_nwi->pos_x +  back_nwi->current_x;
 
		int bottom = back_nwi->pos_y +  back_nwi->current_y;
 
		top = DrawStationCoverageAreaText(back_nwi->pos_x + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, SCT_ALL, rad, false) + WD_PAR_VSEP_NORMAL;
 
		top = DrawStationCoverageAreaText(back_nwi->pos_x + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, SCT_ALL, rad, true) + WD_PAR_VSEP_NORMAL;
 
		/* Resize background if the text is not equally long as the window. */
 
		if (top > bottom || (top < bottom && back_nwi->current_y > back_nwi->smallest_y)) {
 
			ResizeWindow(this, 0, top - bottom);
 
		}
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		switch (widget) {
 
			case BRSW_NEWST_DROPDOWN: {
 
				Dimension d = {0, 0};
 
				for (uint i = 0; i < GetNumStationClasses(); i++) {
 
					if (i == STAT_CLASS_WAYP) continue;
 
					SetDParam(0, GetStationClassName((StationClassID)i));
 
					d = maxdim(d, GetStringBoundingBox(STR_BLACK_STRING));
 
				}
 
				d.width += padding.width;
 
				d.height += padding.height;
 
				*size = maxdim(*size, d);
 
				break;
 
			}
 
			case BRSW_NEWST_LIST: {
 
				Dimension d = GetStringBoundingBox(STR_STATION_CLASS_DFLT);
 
				for (StationClassID statclass = STAT_CLASS_BEGIN; statclass < (StationClassID)GetNumStationClasses(); statclass++) {
 
					if (statclass == STAT_CLASS_WAYP) continue;
 
					for (uint16 j = 0; j < GetNumCustomStations(statclass); j++) {
 
						const StationSpec *statspec = GetCustomStationSpec(statclass, j);
 
						if (statspec != NULL && statspec->name != 0) d = maxdim(d, GetStringBoundingBox(statspec->name));
 
					}
 
				}
 
				size->width = max(size->width, d.width + padding.width);
 

	
 
				this->line_height = FONT_HEIGHT_NORMAL + WD_MATRIX_TOP + WD_MATRIX_BOTTOM;
 
				size->height = GB(this->GetWidget<NWidgetCore>(widget)->widget_data, MAT_ROW_START, MAT_ROW_BITS) * this->line_height;
 
				break;
 
			}
 
		}
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		DrawPixelInfo tmp_dpi;
 

	
 
		switch (widget) {
 
			case BRSW_PLATFORM_DIR_X:
 
				/* Set up a clipping area for the '/' station preview */
 
				if (FillDrawPixelInfo(&tmp_dpi, r.left, r.top, r.right - r.left + 1, r.bottom - r.top + 1)) {
 
					DrawPixelInfo *old_dpi = _cur_dpi;
 
					_cur_dpi = &tmp_dpi;
 
					if (!DrawStationTile(32, 16, _cur_railtype, AXIS_X, _railstation.station_class, _railstation.station_type)) {
 
						StationPickerDrawSprite(32, 16, STATION_RAIL, _cur_railtype, INVALID_ROADTYPE, 2);
 
					}
 
					_cur_dpi = old_dpi;
 
				}
 
				break;
 

	
 
			case BRSW_PLATFORM_DIR_Y:
 
				/* Set up a clipping area for the '\' station preview */
 
				if (FillDrawPixelInfo(&tmp_dpi, r.left, r.top, r.right - r.left + 1, r.bottom - r.top + 1)) {
 
					DrawPixelInfo *old_dpi = _cur_dpi;
 
					_cur_dpi = &tmp_dpi;
 
					if (!DrawStationTile(32, 16, _cur_railtype, AXIS_Y, _railstation.station_class, _railstation.station_type)) {
 
						StationPickerDrawSprite(32, 16, STATION_RAIL, _cur_railtype, INVALID_ROADTYPE, 3);
 
					}
 
					_cur_dpi = old_dpi;
 
				}
 
				break;
 

	
 
			case BRSW_NEWST_LIST: {
 
				uint y = r.top;
 
				for (uint16 i = this->vscroll.GetPosition(); i < _railstation.station_count && this->vscroll.IsVisible(i); i++) {
 
					const StationSpec *statspec = GetCustomStationSpec(_railstation.station_class, i);
 

	
 
					StringID str = STR_STATION_CLASS_DFLT;
 
					if (statspec != NULL && statspec->name != 0) {
 
						if (HasBit(statspec->callback_mask, CBM_STATION_AVAIL) && GB(GetStationCallback(CBID_STATION_AVAILABILITY, 0, 0, statspec, NULL, INVALID_TILE), 0, 8) == 0) {
 
							GfxFillRect(r.left + 1, y + 1, r.right - 1, y + this->line_height - 2, 0, FILLRECT_CHECKER);
 
						}
 
						str = statspec->name;
 
					}
 
					DrawString(r.left + WD_MATRIX_LEFT, r.right - WD_MATRIX_RIGHT, y + WD_MATRIX_TOP, str, i == _railstation.station_type ? TC_WHITE : TC_BLACK);
 

	
 
					y += this->line_height;
 
				}
 
				break;
 
			}
 
		}
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		if (widget == BRSW_NEWST_DROPDOWN) SetDParam(0, GetStationClassName(_railstation.station_class));
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case BRSW_PLATFORM_DIR_X:
 
			case BRSW_PLATFORM_DIR_Y:
 
				this->RaiseWidget(_railstation.orientation + BRSW_PLATFORM_DIR_X);
 
				_railstation.orientation = (Axis)(widget - BRSW_PLATFORM_DIR_X);
 
				this->LowerWidget(_railstation.orientation + BRSW_PLATFORM_DIR_X);
 
				SndPlayFx(SND_15_BEEP);
 
				this->SetDirty();
 
				DeleteWindowById(WC_SELECT_STATION, 0);
 
				break;
 

	
 
			case BRSW_PLATFORM_NUM_1:
 
			case BRSW_PLATFORM_NUM_2:
 
			case BRSW_PLATFORM_NUM_3:
 
			case BRSW_PLATFORM_NUM_4:
 
			case BRSW_PLATFORM_NUM_5:
 
			case BRSW_PLATFORM_NUM_6:
 
			case BRSW_PLATFORM_NUM_7: {
 
				this->RaiseWidget(_settings_client.gui.station_numtracks + BRSW_PLATFORM_NUM_BEGIN);
 
				this->RaiseWidget(BRSW_PLATFORM_DRAG_N_DROP);
 

	
 
				_settings_client.gui.station_numtracks = widget - BRSW_PLATFORM_NUM_BEGIN;
 
				_settings_client.gui.station_dragdrop = false;
 

	
 
				_settings_client.gui.station_dragdrop = false;
 

	
 
				const StationSpec *statspec = _railstation.newstations ? GetCustomStationSpec(_railstation.station_class, _railstation.station_type) : NULL;
 
				if (statspec != NULL && HasBit(statspec->disallowed_lengths, _settings_client.gui.station_platlength - 1)) {
 
					/* The previously selected number of platforms in invalid */
 
					for (uint i = 0; i < 7; i++) {
 
						if (!HasBit(statspec->disallowed_lengths, i)) {
 
							this->RaiseWidget(_settings_client.gui.station_platlength + BRSW_PLATFORM_LEN_BEGIN);
 
							_settings_client.gui.station_platlength = i + 1;
 
							break;
 
						}
 
					}
 
				}
 

	
 
				this->LowerWidget(_settings_client.gui.station_numtracks + BRSW_PLATFORM_NUM_BEGIN);
 
				this->LowerWidget(_settings_client.gui.station_platlength + BRSW_PLATFORM_LEN_BEGIN);
 
				SndPlayFx(SND_15_BEEP);
 
				this->SetDirty();
 
				DeleteWindowById(WC_SELECT_STATION, 0);
 
				break;
 
			}
 

	
 
			case BRSW_PLATFORM_LEN_1:
 
			case BRSW_PLATFORM_LEN_2:
 
			case BRSW_PLATFORM_LEN_3:
 
			case BRSW_PLATFORM_LEN_4:
 
			case BRSW_PLATFORM_LEN_5:
 
			case BRSW_PLATFORM_LEN_6:
 
			case BRSW_PLATFORM_LEN_7: {
 
				this->RaiseWidget(_settings_client.gui.station_platlength + BRSW_PLATFORM_LEN_BEGIN);
 
				this->RaiseWidget(BRSW_PLATFORM_DRAG_N_DROP);
 

	
 
				_settings_client.gui.station_platlength = widget - BRSW_PLATFORM_LEN_BEGIN;
 
				_settings_client.gui.station_dragdrop = false;
 

	
 
				_settings_client.gui.station_dragdrop = false;
 

	
 
				const StationSpec *statspec = _railstation.newstations ? GetCustomStationSpec(_railstation.station_class, _railstation.station_type) : NULL;
 
				if (statspec != NULL && HasBit(statspec->disallowed_platforms, _settings_client.gui.station_numtracks - 1)) {
 
					/* The previously selected number of tracks in invalid */
 
					for (uint i = 0; i < 7; i++) {
 
						if (!HasBit(statspec->disallowed_platforms, i)) {
 
							this->RaiseWidget(_settings_client.gui.station_numtracks + BRSW_PLATFORM_NUM_BEGIN);
 
							_settings_client.gui.station_numtracks = i + 1;
 
							break;
 
						}
 
					}
 
				}
 

	
 
				this->LowerWidget(_settings_client.gui.station_numtracks + BRSW_PLATFORM_NUM_BEGIN);
 
				this->LowerWidget(_settings_client.gui.station_platlength + BRSW_PLATFORM_LEN_BEGIN);
 
				SndPlayFx(SND_15_BEEP);
 
				this->SetDirty();
 
				DeleteWindowById(WC_SELECT_STATION, 0);
 
				break;
 
			}
 

	
 
			case BRSW_PLATFORM_DRAG_N_DROP: {
 
				_settings_client.gui.station_dragdrop ^= true;
 

	
 
				this->ToggleWidgetLoweredState(BRSW_PLATFORM_DRAG_N_DROP);
 

	
 
				/* get the first allowed length/number of platforms */
 
				const StationSpec *statspec = _railstation.newstations ? GetCustomStationSpec(_railstation.station_class, _railstation.station_type) : NULL;
 
				if (statspec != NULL && HasBit(statspec->disallowed_lengths, _settings_client.gui.station_platlength - 1)) {
 
					for (uint i = 0; i < 7; i++) {
 
						if (!HasBit(statspec->disallowed_lengths, i)) {
 
							this->RaiseWidget(_settings_client.gui.station_platlength + BRSW_PLATFORM_LEN_BEGIN);
 
							_settings_client.gui.station_platlength = i + 1;
 
							break;
 
						}
 
					}
 
				}
 
				if (statspec != NULL && HasBit(statspec->disallowed_platforms, _settings_client.gui.station_numtracks - 1)) {
 
					for (uint i = 0; i < 7; i++) {
 
						if (!HasBit(statspec->disallowed_platforms, i)) {
 
							this->RaiseWidget(_settings_client.gui.station_numtracks + BRSW_PLATFORM_NUM_BEGIN);
 
							_settings_client.gui.station_numtracks = i + 1;
 
							break;
 
						}
 
					}
 
				}
 

	
 
				this->SetWidgetLoweredState(_settings_client.gui.station_numtracks + BRSW_PLATFORM_NUM_BEGIN, !_settings_client.gui.station_dragdrop);
 
				this->SetWidgetLoweredState(_settings_client.gui.station_platlength + BRSW_PLATFORM_LEN_BEGIN, !_settings_client.gui.station_dragdrop);
 
				SndPlayFx(SND_15_BEEP);
 
				this->SetDirty();
 
				DeleteWindowById(WC_SELECT_STATION, 0);
 
			} break;
 

	
 
			case BRSW_HIGHLIGHT_OFF:
 
			case BRSW_HIGHLIGHT_ON:
 
				_settings_client.gui.station_show_coverage = (widget != BRSW_HIGHLIGHT_OFF);
 

	
 
				this->SetWidgetLoweredState(BRSW_HIGHLIGHT_OFF, !_settings_client.gui.station_show_coverage);
 
				this->SetWidgetLoweredState(BRSW_HIGHLIGHT_ON, _settings_client.gui.station_show_coverage);
 
				SndPlayFx(SND_15_BEEP);
 
				this->SetDirty();
 
				break;
 

	
 
			case BRSW_NEWST_DROPDOWN:
 
				ShowDropDownList(this, BuildStationClassDropDown(), _railstation.station_class, BRSW_NEWST_DROPDOWN);
 
				break;
 

	
 
			case BRSW_NEWST_LIST: {
 
				const StationSpec *statspec;
 
				int y = (pt.y - this->GetWidget<NWidgetBase>(BRSW_NEWST_LIST)->pos_y) / this->line_height;
 

	
 
				if (y >= this->vscroll.GetCapacity()) return;
 
				y += this->vscroll.GetPosition();
 
				if (y >= _railstation.station_count) return;
 

	
 
				/* Check station availability callback */
 
				statspec = GetCustomStationSpec(_railstation.station_class, y);
 
				if (statspec != NULL &&
 
					HasBit(statspec->callback_mask, CBM_STATION_AVAIL) &&
 
					GB(GetStationCallback(CBID_STATION_AVAILABILITY, 0, 0, statspec, NULL, INVALID_TILE), 0, 8) == 0) return;
 

	
 
				_railstation.station_type = y;
 

	
 
				this->CheckSelectedSize(statspec);
 

	
 
				SndPlayFx(SND_15_BEEP);
 
				this->SetDirty();
 
				DeleteWindowById(WC_SELECT_STATION, 0);
 
				break;
 
			}
 
		}
 
	}
 

	
 
	virtual void OnDropdownSelect(int widget, int index)
 
	{
 
		if (_railstation.station_class != index) {
 
			_railstation.station_class = (StationClassID)index;
 
			_railstation.station_type  = 0;
 
			_railstation.station_count = GetNumCustomStations(_railstation.station_class);
 

	
 
			this->CheckSelectedSize(GetCustomStationSpec(_railstation.station_class, _railstation.station_type));
 

	
 
			this->vscroll.SetCount(_railstation.station_count);
 
			this->vscroll.SetPosition(_railstation.station_type);
 
		}
 

	
 
		SndPlayFx(SND_15_BEEP);
 
		this->SetDirty();
 
		DeleteWindowById(WC_SELECT_STATION, 0);
 
	}
 

	
 
	virtual void OnTick()
 
	{
 
		CheckRedrawStationCoverage(this);
 
	}
 
};
 

	
 
static const NWidgetPart _nested_station_builder_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN, BRSW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, BRSW_CAPTION), SetDataTip(STR_STATION_BUILD_RAIL_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BRSW_BACKGROUND),
 
		NWidget(WWT_LABEL, COLOUR_DARK_GREEN, BRSW_ORIENT_LABEL), SetMinimalSize(144, 11), SetDataTip(STR_STATION_BUILD_ORIENTATION, STR_NULL), SetPadding(1, 2, 0, 2),
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(NWID_SPACER), SetMinimalSize(7, 0), SetFill(true, false),
 
			NWidget(WWT_PANEL, COLOUR_GREY, BRSW_PLATFORM_DIR_X), SetMinimalSize(66, 48), SetFill(false, false), SetDataTip(0x0, STR_STATION_BUILD_RAILROAD_ORIENTATION_TOOLTIP), EndContainer(),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0), SetFill(true, false),
 
			NWidget(WWT_PANEL, COLOUR_GREY, BRSW_PLATFORM_DIR_Y), SetMinimalSize(66, 48), SetFill(false, false), SetDataTip(0x0, STR_STATION_BUILD_RAILROAD_ORIENTATION_TOOLTIP), EndContainer(),
 
			NWidget(NWID_SPACER), SetMinimalSize(7, 0), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetMinimalSize(7, 0), SetFill(1, 0),
 
			NWidget(WWT_PANEL, COLOUR_GREY, BRSW_PLATFORM_DIR_X), SetMinimalSize(66, 48), SetFill(0, 0), SetDataTip(0x0, STR_STATION_BUILD_RAILROAD_ORIENTATION_TOOLTIP), EndContainer(),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0), SetFill(1, 0),
 
			NWidget(WWT_PANEL, COLOUR_GREY, BRSW_PLATFORM_DIR_Y), SetMinimalSize(66, 48), SetFill(0, 0), SetDataTip(0x0, STR_STATION_BUILD_RAILROAD_ORIENTATION_TOOLTIP), EndContainer(),
 
			NWidget(NWID_SPACER), SetMinimalSize(7, 0), SetFill(1, 0),
 
		EndContainer(),
 
		NWidget(WWT_LABEL, COLOUR_DARK_GREEN, BRSW_PLATFORM_NUM_LABEL), SetMinimalSize(144, 11), SetDataTip(STR_STATION_BUILD_NUMBER_OF_TRACKS, STR_NULL), SetPadding(2, 2, 0, 2),
 
		NWidget(NWID_HORIZONTAL), SetPIP(22, 0, 21),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_PLATFORM_NUM_1), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_1, STR_STATION_BUILD_NUMBER_OF_TRACKS_TOOLTIP),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_PLATFORM_NUM_2), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_2, STR_STATION_BUILD_NUMBER_OF_TRACKS_TOOLTIP),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_PLATFORM_NUM_3), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_3, STR_STATION_BUILD_NUMBER_OF_TRACKS_TOOLTIP),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_PLATFORM_NUM_4), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_4, STR_STATION_BUILD_NUMBER_OF_TRACKS_TOOLTIP),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_PLATFORM_NUM_5), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_5, STR_STATION_BUILD_NUMBER_OF_TRACKS_TOOLTIP),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_PLATFORM_NUM_6), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_6, STR_STATION_BUILD_NUMBER_OF_TRACKS_TOOLTIP),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_PLATFORM_NUM_7), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_7, STR_STATION_BUILD_NUMBER_OF_TRACKS_TOOLTIP),
 
		EndContainer(),
 
		NWidget(WWT_LABEL, COLOUR_DARK_GREEN, BRSW_PLATFORM_LEN_LABEL), SetMinimalSize(144, 11), SetDataTip(STR_STATION_BUILD_PLATFORM_LENGTH, STR_NULL), SetPadding(2, 2, 0, 2),
 
		NWidget(NWID_HORIZONTAL), SetPIP(22, 0, 21),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_PLATFORM_LEN_1), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_1, STR_STATION_BUILD_PLATFORM_LENGTH_TOOLTIP),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_PLATFORM_LEN_2), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_2, STR_STATION_BUILD_PLATFORM_LENGTH_TOOLTIP),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_PLATFORM_LEN_3), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_3, STR_STATION_BUILD_PLATFORM_LENGTH_TOOLTIP),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_PLATFORM_LEN_4), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_4, STR_STATION_BUILD_PLATFORM_LENGTH_TOOLTIP),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_PLATFORM_LEN_5), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_5, STR_STATION_BUILD_PLATFORM_LENGTH_TOOLTIP),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_PLATFORM_LEN_6), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_6, STR_STATION_BUILD_PLATFORM_LENGTH_TOOLTIP),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_PLATFORM_LEN_7), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_7, STR_STATION_BUILD_PLATFORM_LENGTH_TOOLTIP),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 2),
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0), SetFill(1, 0),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_PLATFORM_DRAG_N_DROP), SetMinimalSize(75, 12), SetDataTip(STR_STATION_BUILD_DRAG_DROP, STR_STATION_BUILD_DRAG_DROP_TOOLTIP),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0), SetFill(1, 0),
 
		EndContainer(),
 
		NWidget(WWT_LABEL, COLOUR_DARK_GREEN, BRSW_HIGHLIGHT_LABEL), SetMinimalSize(144, 11), SetDataTip(STR_STATION_BUILD_COVERAGE_AREA_TITLE, STR_NULL), SetPadding(3, 2, 0, 2),
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0), SetFill(1, 0),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_HIGHLIGHT_OFF), SetMinimalSize(60, 12),
 
										SetDataTip(STR_STATION_BUILD_COVERAGE_OFF, STR_STATION_BUILD_COVERAGE_AREA_OFF_TOOLTIP),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_HIGHLIGHT_ON), SetMinimalSize(60, 12),
 
										SetDataTip(STR_STATION_BUILD_COVERAGE_ON, STR_STATION_BUILD_COVERAGE_AREA_ON_TOOLTIP),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0), SetFill(1, 0),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 20), SetResize(0, 1),
 
	EndContainer(),
 
};
 

	
 
static const NWidgetPart _nested_newstation_builder_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN, BRSW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, BRSW_CAPTION), SetDataTip(STR_STATION_BUILD_RAIL_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BRSW_BACKGROUND),
 
		/* begin newstations gui additions. */
 
		NWidget(WWT_DROPDOWN, COLOUR_GREY, BRSW_NEWST_DROPDOWN), SetMinimalSize(134, 12), SetFill(true, false), SetPadding(3, 7, 3, 7), SetDataTip(STR_BLACK_STRING, STR_STATION_BUILD_STATION_CLASS_TOOLTIP),
 
		NWidget(WWT_DROPDOWN, COLOUR_GREY, BRSW_NEWST_DROPDOWN), SetMinimalSize(134, 12), SetFill(1, 0), SetPadding(3, 7, 3, 7), SetDataTip(STR_BLACK_STRING, STR_STATION_BUILD_STATION_CLASS_TOOLTIP),
 
		NWidget(NWID_HORIZONTAL), SetPIP(7, 0, 7),
 
			NWidget(WWT_MATRIX, COLOUR_GREY, BRSW_NEWST_LIST), SetMinimalSize(122, 71), SetFill(true, false), SetDataTip(0x501, STR_STATION_BUILD_STATION_TYPE_TOOLTIP),
 
			NWidget(WWT_MATRIX, COLOUR_GREY, BRSW_NEWST_LIST), SetMinimalSize(122, 71), SetFill(1, 0), SetDataTip(0x501, STR_STATION_BUILD_STATION_TYPE_TOOLTIP),
 
			NWidget(WWT_SCROLLBAR, COLOUR_GREY, BRSW_NEWST_SCROLL),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 1),
 
		/* end newstations gui additions. */
 
		NWidget(WWT_LABEL, COLOUR_DARK_GREEN, BRSW_ORIENT_LABEL), SetMinimalSize(144, 11), SetDataTip(STR_STATION_BUILD_ORIENTATION, STR_NULL), SetPadding(1, 2, 0, 2),
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(NWID_SPACER), SetMinimalSize(7, 0), SetFill(true, false),
 
			NWidget(WWT_PANEL, COLOUR_GREY, BRSW_PLATFORM_DIR_X), SetMinimalSize(66, 48), SetFill(false, false), SetDataTip(0x0, STR_STATION_BUILD_RAILROAD_ORIENTATION_TOOLTIP), EndContainer(),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0), SetFill(true, false),
 
			NWidget(WWT_PANEL, COLOUR_GREY, BRSW_PLATFORM_DIR_Y), SetMinimalSize(66, 48), SetFill(false, false), SetDataTip(0x0, STR_STATION_BUILD_RAILROAD_ORIENTATION_TOOLTIP), EndContainer(),
 
			NWidget(NWID_SPACER), SetMinimalSize(7, 0), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetMinimalSize(7, 0), SetFill(1, 0),
 
			NWidget(WWT_PANEL, COLOUR_GREY, BRSW_PLATFORM_DIR_X), SetMinimalSize(66, 48), SetFill(0, 0), SetDataTip(0x0, STR_STATION_BUILD_RAILROAD_ORIENTATION_TOOLTIP), EndContainer(),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0), SetFill(1, 0),
 
			NWidget(WWT_PANEL, COLOUR_GREY, BRSW_PLATFORM_DIR_Y), SetMinimalSize(66, 48), SetFill(0, 0), SetDataTip(0x0, STR_STATION_BUILD_RAILROAD_ORIENTATION_TOOLTIP), EndContainer(),
 
			NWidget(NWID_SPACER), SetMinimalSize(7, 0), SetFill(1, 0),
 
		EndContainer(),
 
		NWidget(WWT_LABEL, COLOUR_DARK_GREEN, BRSW_PLATFORM_NUM_LABEL), SetMinimalSize(144, 11), SetDataTip(STR_STATION_BUILD_NUMBER_OF_TRACKS, STR_NULL), SetPadding(2, 2, 0, 2),
 
		NWidget(NWID_HORIZONTAL), SetPIP(22, 0, 21),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_PLATFORM_NUM_1), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_1, STR_STATION_BUILD_NUMBER_OF_TRACKS_TOOLTIP),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_PLATFORM_NUM_2), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_2, STR_STATION_BUILD_NUMBER_OF_TRACKS_TOOLTIP),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_PLATFORM_NUM_3), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_3, STR_STATION_BUILD_NUMBER_OF_TRACKS_TOOLTIP),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_PLATFORM_NUM_4), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_4, STR_STATION_BUILD_NUMBER_OF_TRACKS_TOOLTIP),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_PLATFORM_NUM_5), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_5, STR_STATION_BUILD_NUMBER_OF_TRACKS_TOOLTIP),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_PLATFORM_NUM_6), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_6, STR_STATION_BUILD_NUMBER_OF_TRACKS_TOOLTIP),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_PLATFORM_NUM_7), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_7, STR_STATION_BUILD_NUMBER_OF_TRACKS_TOOLTIP),
 
		EndContainer(),
 
		NWidget(WWT_LABEL, COLOUR_DARK_GREEN, BRSW_PLATFORM_LEN_LABEL), SetMinimalSize(144, 11), SetDataTip(STR_STATION_BUILD_PLATFORM_LENGTH, STR_NULL), SetPadding(2, 2, 0, 2),
 
		NWidget(NWID_HORIZONTAL), SetPIP(22, 0, 21),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_PLATFORM_LEN_1), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_1, STR_STATION_BUILD_PLATFORM_LENGTH_TOOLTIP),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_PLATFORM_LEN_2), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_2, STR_STATION_BUILD_PLATFORM_LENGTH_TOOLTIP),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_PLATFORM_LEN_3), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_3, STR_STATION_BUILD_PLATFORM_LENGTH_TOOLTIP),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_PLATFORM_LEN_4), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_4, STR_STATION_BUILD_PLATFORM_LENGTH_TOOLTIP),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_PLATFORM_LEN_5), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_5, STR_STATION_BUILD_PLATFORM_LENGTH_TOOLTIP),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_PLATFORM_LEN_6), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_6, STR_STATION_BUILD_PLATFORM_LENGTH_TOOLTIP),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_PLATFORM_LEN_7), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_7, STR_STATION_BUILD_PLATFORM_LENGTH_TOOLTIP),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 2),
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0), SetFill(1, 0),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_PLATFORM_DRAG_N_DROP), SetMinimalSize(75, 12), SetDataTip(STR_STATION_BUILD_DRAG_DROP, STR_STATION_BUILD_DRAG_DROP_TOOLTIP),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0), SetFill(1, 0),
 
		EndContainer(),
 
		NWidget(WWT_LABEL, COLOUR_DARK_GREEN, BRSW_HIGHLIGHT_LABEL), SetMinimalSize(144, 11), SetDataTip(STR_STATION_BUILD_COVERAGE_AREA_TITLE, STR_NULL), SetPadding(3, 2, 0, 2),
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0), SetFill(1, 0),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_HIGHLIGHT_OFF), SetMinimalSize(60, 12),
 
										SetDataTip(STR_STATION_BUILD_COVERAGE_OFF, STR_STATION_BUILD_COVERAGE_AREA_OFF_TOOLTIP),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_HIGHLIGHT_ON), SetMinimalSize(60, 12),
 
										SetDataTip(STR_STATION_BUILD_COVERAGE_ON, STR_STATION_BUILD_COVERAGE_AREA_ON_TOOLTIP),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0), SetFill(1, 0),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 20), SetResize(0, 1),
 
	EndContainer(),
 
};
 

	
 
/** High level window description of the default station-build window */
 
static const WindowDesc _station_builder_desc(
 
	WDP_AUTO, WDP_AUTO, 148, 200,
 
	WC_BUILD_STATION, WC_BUILD_TOOLBAR,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_CONSTRUCTION,
 
	_nested_station_builder_widgets, lengthof(_nested_station_builder_widgets)
 
);
 

	
 
/** High level window description of the newGRF station-build window */
 
static const WindowDesc _newstation_builder_desc(
 
	WDP_AUTO, WDP_AUTO, 148, 290,
 
	WC_BUILD_STATION, WC_BUILD_TOOLBAR,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_CONSTRUCTION,
 
	_nested_newstation_builder_widgets, lengthof(_nested_newstation_builder_widgets)
 
);
 

	
 
/** Open station build window */
 
static void ShowStationBuilder(Window *parent)
 
{
 
	if (GetNumStationClasses() <= 2 && GetNumCustomStations(STAT_CLASS_DFLT) == 1) {
 
		new BuildRailStationWindow(&_station_builder_desc, parent, false);
 
	} else {
 
		new BuildRailStationWindow(&_newstation_builder_desc, parent, true);
 
	}
 
}
 

	
 
/** Enum referring to the widgets of the signal window */
 
enum BuildSignalWidgets {
 
	BSW_CLOSEBOX = 0,
 
	BSW_CAPTION,
 
	BSW_SEMAPHORE_NORM,
 
	BSW_SEMAPHORE_ENTRY,
 
	BSW_SEMAPHORE_EXIT,
 
	BSW_SEMAPHORE_COMBO,
 
	BSW_SEMAPHORE_PBS,
 
	BSW_SEMAPHORE_PBS_OWAY,
 
	BSW_ELECTRIC_NORM,
 
	BSW_ELECTRIC_ENTRY,
 
	BSW_ELECTRIC_EXIT,
 
	BSW_ELECTRIC_COMBO,
 
	BSW_ELECTRIC_PBS,
 
	BSW_ELECTRIC_PBS_OWAY,
 
	BSW_CONVERT,
 
	BSW_DRAG_SIGNALS_DENSITY,
 
	BSW_DRAG_SIGNALS_DENSITY_LABEL,
 
	BSW_DRAG_SIGNALS_DENSITY_DECREASE,
 
	BSW_DRAG_SIGNALS_DENSITY_INCREASE,
 
};
 

	
 
struct BuildSignalWindow : public PickerWindowBase {
 
private:
 
	/**
 
	 * Draw dynamic a signal-sprite in a button in the signal GUI
 
	 * Draw the sprite +1px to the right and down if the button is lowered and change the sprite to sprite + 1 (red to green light)
 
	 *
 
	 * @param widget_index index of this widget in the window
 
	 * @param image        the sprite to draw
 
	 */
 
	void DrawSignalSprite(byte widget_index, SpriteID image) const
 
	{
 
		/* First get the right image, which is one later for 'green' signals. */
 
		image += this->IsWidgetLowered(widget_index);
 

	
 
		/* Next get the actual sprite so we can calculate the right offsets. */
 
		const Sprite *sprite = GetSprite(image, ST_NORMAL);
 

	
 
		/* For the x offset we want the sprite to be centered, so undo the offset
 
		 * for sprite drawing and add half of the sprite's width. For the y offset
 
		 * we want the sprite to be aligned on the bottom, so again we undo the
 
		 * offset for sprite drawing and assume it is the bottom of the sprite. */
 
		int sprite_center_x_offset = sprite->x_offs + sprite->width / 2;
 
		int sprite_bottom_y_offset = sprite->height + sprite->y_offs;
 

	
 
		/* Next we want to know where on the window to draw. Calculate the center
 
		 * and the bottom of the area to draw. */
 
		const NWidgetBase *widget = this->GetWidget<NWidgetBase>(widget_index);
 
		int widget_center_x = widget->pos_x + widget->current_x / 2;
 
		int widget_bottom_y = widget->pos_y + widget->current_y - 2;
 

	
 
		/* Finally we draw the signal. */
 
		DrawSprite(image, PAL_NONE,
 
				widget_center_x - sprite_center_x_offset + this->IsWidgetLowered(widget_index),
 
				widget_bottom_y - sprite_bottom_y_offset + this->IsWidgetLowered(widget_index));
 
	}
 

	
 
public:
 
	BuildSignalWindow(const WindowDesc *desc, Window *parent) : PickerWindowBase(parent)
 
	{
 
		this->InitNested(desc, TRANSPORT_RAIL);
 
		this->OnInvalidateData();
 
	};
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		switch (widget) {
 
			case BSW_DRAG_SIGNALS_DENSITY_LABEL:
 
				SetDParam(0, _settings_client.gui.drag_signals_density);
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		if (IsInsideMM(widget, BSW_SEMAPHORE_NORM, BSW_ELECTRIC_PBS_OWAY + 1)) {
 
			/* We need to do some custom sprite widget drawing for the signals. */
 
			const SpriteID _signal_lookup[] = {
 
				SPR_IMG_SIGNAL_SEMAPHORE_NORM,  SPR_IMG_SIGNAL_SEMAPHORE_ENTRY, SPR_IMG_SIGNAL_SEMAPHORE_EXIT,
 
				SPR_IMG_SIGNAL_SEMAPHORE_COMBO, SPR_IMG_SIGNAL_SEMAPHORE_PBS,   SPR_IMG_SIGNAL_SEMAPHORE_PBS_OWAY,
 

	
 
				SPR_IMG_SIGNAL_ELECTRIC_NORM,  SPR_IMG_SIGNAL_ELECTRIC_ENTRY, SPR_IMG_SIGNAL_ELECTRIC_EXIT,
 
				SPR_IMG_SIGNAL_ELECTRIC_COMBO, SPR_IMG_SIGNAL_ELECTRIC_PBS,   SPR_IMG_SIGNAL_ELECTRIC_PBS_OWAY
 
			};
 

	
 
			this->DrawSignalSprite(widget, _signal_lookup[widget - BSW_SEMAPHORE_NORM]);
 
		}
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case BSW_SEMAPHORE_NORM:
 
			case BSW_SEMAPHORE_ENTRY:
 
			case BSW_SEMAPHORE_EXIT:
 
			case BSW_SEMAPHORE_COMBO:
 
			case BSW_SEMAPHORE_PBS:
 
			case BSW_SEMAPHORE_PBS_OWAY:
 
			case BSW_ELECTRIC_NORM:
 
			case BSW_ELECTRIC_ENTRY:
 
			case BSW_ELECTRIC_EXIT:
 
			case BSW_ELECTRIC_COMBO:
 
			case BSW_ELECTRIC_PBS:
 
			case BSW_ELECTRIC_PBS_OWAY:
 
				this->RaiseWidget((_cur_signal_variant == SIG_ELECTRIC ? BSW_ELECTRIC_NORM : BSW_SEMAPHORE_NORM) + _cur_signal_type);
 

	
 
				_cur_signal_type = (SignalType)((uint)((widget - BSW_SEMAPHORE_NORM) % (SIGTYPE_LAST + 1)));
 
				_cur_signal_variant = widget >= BSW_ELECTRIC_NORM ? SIG_ELECTRIC : SIG_SEMAPHORE;
 
				break;
 

	
 
			case BSW_CONVERT:
 
				_convert_signal_button = !_convert_signal_button;
 
				break;
 

	
 
			case BSW_DRAG_SIGNALS_DENSITY_DECREASE:
 
				if (_settings_client.gui.drag_signals_density > 1) {
 
					_settings_client.gui.drag_signals_density--;
 
					SetWindowDirty(WC_GAME_OPTIONS, 0);
 
				}
 
				break;
 

	
 
			case BSW_DRAG_SIGNALS_DENSITY_INCREASE:
 
				if (_settings_client.gui.drag_signals_density < 20) {
 
					_settings_client.gui.drag_signals_density++;
 
					SetWindowDirty(WC_GAME_OPTIONS, 0);
 
				}
 
				break;
 

	
 
			default: break;
 
		}
 

	
 
		this->InvalidateData();
 
	}
 

	
 
	virtual void OnInvalidateData(int data = 0)
 
	{
 
		this->LowerWidget((_cur_signal_variant == SIG_ELECTRIC ? BSW_ELECTRIC_NORM : BSW_SEMAPHORE_NORM) + _cur_signal_type);
 

	
 
		this->SetWidgetLoweredState(BSW_CONVERT, _convert_signal_button);
 

	
 
		this->SetWidgetDisabledState(BSW_DRAG_SIGNALS_DENSITY_DECREASE, _settings_client.gui.drag_signals_density == 1);
 
		this->SetWidgetDisabledState(BSW_DRAG_SIGNALS_DENSITY_INCREASE, _settings_client.gui.drag_signals_density == 20);
 
	}
 
};
 

	
 
/** Nested widget definition of the build signal window */
 
static const NWidgetPart _nested_signal_builder_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN, BSW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, BSW_CAPTION), SetDataTip(STR_BUILD_SIGNAL_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(NWID_VERTICAL, NC_EQUALSIZE),
 
		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BSW_SEMAPHORE_NORM), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_NORM_TOOLTIP), EndContainer(), SetFill(true, true),
 
			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BSW_SEMAPHORE_ENTRY), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_ENTRY_TOOLTIP), EndContainer(), SetFill(true, true),
 
			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BSW_SEMAPHORE_EXIT), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_EXIT_TOOLTIP), EndContainer(), SetFill(true, true),
 
			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BSW_SEMAPHORE_COMBO), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_COMBO_TOOLTIP), EndContainer(), SetFill(true, true),
 
			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BSW_SEMAPHORE_PBS), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_PBS_TOOLTIP), EndContainer(), SetFill(true, true),
 
			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BSW_SEMAPHORE_PBS_OWAY), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_PBS_OWAY_TOOLTIP), EndContainer(), SetFill(true, true),
 
			NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, BSW_CONVERT), SetDataTip(SPR_IMG_SIGNAL_CONVERT, STR_BUILD_SIGNAL_CONVERT_TOOLTIP), SetFill(true, true),
 
			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BSW_SEMAPHORE_NORM), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_NORM_TOOLTIP), EndContainer(), SetFill(1, 1),
 
			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BSW_SEMAPHORE_ENTRY), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_ENTRY_TOOLTIP), EndContainer(), SetFill(1, 1),
 
			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BSW_SEMAPHORE_EXIT), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_EXIT_TOOLTIP), EndContainer(), SetFill(1, 1),
 
			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BSW_SEMAPHORE_COMBO), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_COMBO_TOOLTIP), EndContainer(), SetFill(1, 1),
 
			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BSW_SEMAPHORE_PBS), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_PBS_TOOLTIP), EndContainer(), SetFill(1, 1),
 
			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BSW_SEMAPHORE_PBS_OWAY), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_PBS_OWAY_TOOLTIP), EndContainer(), SetFill(1, 1),
 
			NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, BSW_CONVERT), SetDataTip(SPR_IMG_SIGNAL_CONVERT, STR_BUILD_SIGNAL_CONVERT_TOOLTIP), SetFill(1, 1),
 
		EndContainer(),
 
		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BSW_ELECTRIC_NORM), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_NORM_TOOLTIP), EndContainer(), SetFill(true, true),
 
			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BSW_ELECTRIC_ENTRY), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_ENTRY_TOOLTIP), EndContainer(), SetFill(true, true),
 
			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BSW_ELECTRIC_EXIT), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_EXIT_TOOLTIP), EndContainer(), SetFill(true, true),
 
			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BSW_ELECTRIC_COMBO), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_COMBO_TOOLTIP), EndContainer(), SetFill(true, true),
 
			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BSW_ELECTRIC_PBS), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_PBS_TOOLTIP), EndContainer(), SetFill(true, true),
 
			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BSW_ELECTRIC_PBS_OWAY), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_PBS_OWAY_TOOLTIP), EndContainer(), SetFill(true, true),
 
			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BSW_DRAG_SIGNALS_DENSITY), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_DRAG_SIGNALS_DENSITY_TOOLTIP), SetFill(true, true),
 
				NWidget(WWT_LABEL, COLOUR_DARK_GREEN, BSW_DRAG_SIGNALS_DENSITY_LABEL), SetDataTip(STR_ORANGE_INT, STR_BUILD_SIGNAL_DRAG_SIGNALS_DENSITY_TOOLTIP), SetFill(true, true),
 
			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BSW_ELECTRIC_NORM), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_NORM_TOOLTIP), EndContainer(), SetFill(1, 1),
 
			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BSW_ELECTRIC_ENTRY), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_ENTRY_TOOLTIP), EndContainer(), SetFill(1, 1),
 
			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BSW_ELECTRIC_EXIT), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_EXIT_TOOLTIP), EndContainer(), SetFill(1, 1),
 
			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BSW_ELECTRIC_COMBO), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_COMBO_TOOLTIP), EndContainer(), SetFill(1, 1),
 
			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BSW_ELECTRIC_PBS), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_PBS_TOOLTIP), EndContainer(), SetFill(1, 1),
 
			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BSW_ELECTRIC_PBS_OWAY), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_PBS_OWAY_TOOLTIP), EndContainer(), SetFill(1, 1),
 
			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BSW_DRAG_SIGNALS_DENSITY), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_DRAG_SIGNALS_DENSITY_TOOLTIP), SetFill(1, 1),
 
				NWidget(WWT_LABEL, COLOUR_DARK_GREEN, BSW_DRAG_SIGNALS_DENSITY_LABEL), SetDataTip(STR_ORANGE_INT, STR_BUILD_SIGNAL_DRAG_SIGNALS_DENSITY_TOOLTIP), SetFill(1, 1),
 
				NWidget(NWID_HORIZONTAL), SetPIP(2, 0, 2),
 
					NWidget(NWID_SPACER), SetFill(true, false),
 
					NWidget(NWID_SPACER), SetFill(1, 0),
 
					NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, BSW_DRAG_SIGNALS_DENSITY_DECREASE), SetMinimalSize(9, 12), SetDataTip(AWV_DECREASE, STR_BUILD_SIGNAL_DRAG_SIGNALS_DENSITY_DECREASE_TOOLTIP),
 
					NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, BSW_DRAG_SIGNALS_DENSITY_INCREASE), SetMinimalSize(9, 12), SetDataTip(AWV_INCREASE, STR_BUILD_SIGNAL_DRAG_SIGNALS_DENSITY_INCREASE_TOOLTIP),
 
					NWidget(NWID_SPACER), SetFill(true, false),
 
					NWidget(NWID_SPACER), SetFill(1, 0),
 
				EndContainer(),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 2), SetFill(true, false),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 2), SetFill(1, 0),
 
			EndContainer(),
 
		EndContainer(),
 
	EndContainer(),
 
};
 

	
 
/** Signal selection window description */
 
static const WindowDesc _signal_builder_desc(
 
	WDP_AUTO, WDP_AUTO, 154, 68,
 
	WC_BUILD_SIGNAL, WC_BUILD_TOOLBAR,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_CONSTRUCTION,
 
	_nested_signal_builder_widgets, lengthof(_nested_signal_builder_widgets)
 
);
 

	
 
/**
 
 * Open the signal selection window
 
 */
 
static void ShowSignalBuilder(Window *parent)
 
{
 
	new BuildSignalWindow(&_signal_builder_desc, parent);
 
}
 

	
 
/** Enum referring to the widgets of the build rail depot window */
 
enum BuildRailDepotWidgets {
 
	BRDW_CLOSEBOX = 0,
 
	BRDW_CAPTION,
 
	BRDW_BACKGROUND,
 
	BRDW_DEPOT_NE,
 
	BRDW_DEPOT_SE,
 
	BRDW_DEPOT_SW,
 
	BRDW_DEPOT_NW,
 
};
 

	
 
struct BuildRailDepotWindow : public PickerWindowBase {
 
	BuildRailDepotWindow(const WindowDesc *desc, Window *parent) : PickerWindowBase(parent)
 
	{
 
		this->InitNested(desc, TRANSPORT_RAIL);
 
		this->LowerWidget(_build_depot_direction + BRDW_DEPOT_NE);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		if (!IsInsideMM(widget, BRDW_DEPOT_NE, BRDW_DEPOT_NW + 1)) return;
 

	
 
		DrawTrainDepotSprite(r.left - 1, r.top, widget - BRDW_DEPOT_NE + DIAGDIR_NE, _cur_railtype);
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case BRDW_DEPOT_NE:
 
			case BRDW_DEPOT_SE:
 
			case BRDW_DEPOT_SW:
 
			case BRDW_DEPOT_NW:
 
				this->RaiseWidget(_build_depot_direction + BRDW_DEPOT_NE);
 
				_build_depot_direction = (DiagDirection)(widget - BRDW_DEPOT_NE);
 
				this->LowerWidget(_build_depot_direction + BRDW_DEPOT_NE);
 
				SndPlayFx(SND_15_BEEP);
 
				this->SetDirty();
 
				break;
 
		}
 
	}
 
};
 

	
 
/** Nested widget definition of the build rail depot window */
 
static const NWidgetPart _nested_build_depot_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN, BRDW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, BRDW_CAPTION), SetDataTip(STR_BUILD_DEPOT_TRAIN_ORIENTATION_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BRDW_BACKGROUND),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 3),
 
		NWidget(NWID_HORIZONTAL_LTR),
 
			NWidget(NWID_SPACER), SetMinimalSize(3, 0),
 
			NWidget(NWID_VERTICAL),
 
				NWidget(WWT_PANEL, COLOUR_GREY, BRDW_DEPOT_NW), SetMinimalSize(66, 50), SetDataTip(0x0, STR_BUILD_DEPOT_TRAIN_ORIENTATION_TOOLTIP),
 
				EndContainer(),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 2),
 
				NWidget(WWT_PANEL, COLOUR_GREY, BRDW_DEPOT_SW), SetMinimalSize(66, 50), SetDataTip(0x0, STR_BUILD_DEPOT_TRAIN_ORIENTATION_TOOLTIP),
 
				EndContainer(),
 
			EndContainer(),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0),
 
			NWidget(NWID_VERTICAL),
 
				NWidget(WWT_PANEL, COLOUR_GREY, BRDW_DEPOT_NE), SetMinimalSize(66, 50), SetDataTip(0x0, STR_BUILD_DEPOT_TRAIN_ORIENTATION_TOOLTIP),
 
				EndContainer(),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 2),
 
				NWidget(WWT_PANEL, COLOUR_GREY, BRDW_DEPOT_SE), SetMinimalSize(66, 50), SetDataTip(0x0, STR_BUILD_DEPOT_TRAIN_ORIENTATION_TOOLTIP),
 
				EndContainer(),
 
			EndContainer(),
 
			NWidget(NWID_SPACER), SetMinimalSize(3, 0),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 3),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _build_depot_desc(
 
	WDP_AUTO, WDP_AUTO, 140, 122,
 
	WC_BUILD_DEPOT, WC_BUILD_TOOLBAR,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_CONSTRUCTION,
 
	_nested_build_depot_widgets, lengthof(_nested_build_depot_widgets)
 
);
 

	
 
static void ShowBuildTrainDepotPicker(Window *parent)
 
{
 
	new BuildRailDepotWindow(&_build_depot_desc, parent);
 
}
 

	
 
/** Enum referring to the widgets of the build NewGRF rail waypoint window */
 
enum BuildRailWaypointWidgets {
 
	BRWW_CLOSEBOX = 0,
 
	BRWW_CAPTION,
 
	BRWW_BACKGROUND,
 
	BRWW_WAYPOINT_1,
 
	BRWW_WAYPOINT_2,
 
	BRWW_WAYPOINT_3,
 
	BRWW_WAYPOINT_4,
 
	BRWW_WAYPOINT_5,
 
	BRWW_SCROLL,
 
};
 

	
 
struct BuildRailWaypointWindow : PickerWindowBase {
 
	BuildRailWaypointWindow(const WindowDesc *desc, Window *parent) : PickerWindowBase(parent)
 
	{
 
		this->InitNested(desc, TRANSPORT_RAIL);
 
		this->hscroll.SetCapacity(5);
 
		this->hscroll.SetCount(_waypoint_count);
 
	};
 

	
 
	virtual void OnPaint()
 
	{
 
		for (uint i = 0; i < this->hscroll.GetCapacity(); i++) {
 
			this->SetWidgetLoweredState(i + BRWW_WAYPOINT_1, (this->hscroll.GetPosition() + i) == _cur_waypoint_type);
 
		}
 

	
 
		this->DrawWidgets();
 

	
 
		for (uint i = 0; i < this->hscroll.GetCapacity(); i++) {
 
			if (this->hscroll.GetPosition() + i < this->hscroll.GetCount()) {
 
				const StationSpec *statspec = GetCustomStationSpec(STAT_CLASS_WAYP, this->hscroll.GetPosition() + i);
 
				NWidgetBase *nw = this->GetWidget<NWidgetBase>(BRWW_WAYPOINT_1 + i);
 

	
 
				int bottom = nw->pos_y + nw->current_y;
 
				DrawWaypointSprite(nw->pos_x + TILE_PIXELS, bottom - TILE_PIXELS, this->hscroll.GetPosition() + i, _cur_railtype);
 

	
 
				if (statspec != NULL &&
 
						HasBit(statspec->callback_mask, CBM_STATION_AVAIL) &&
 
						GB(GetStationCallback(CBID_STATION_AVAILABILITY, 0, 0, statspec, NULL, INVALID_TILE), 0, 8) == 0) {
 
					GfxFillRect(nw->pos_x + 1, nw->pos_y + 1, nw->pos_x + nw->current_x - 2, bottom - 2, 0, FILLRECT_CHECKER);
 
				}
 
			}
 
		}
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case BRWW_WAYPOINT_1:
 
			case BRWW_WAYPOINT_2:
 
			case BRWW_WAYPOINT_3:
 
			case BRWW_WAYPOINT_4:
 
			case BRWW_WAYPOINT_5: {
 
				byte type = widget - BRWW_WAYPOINT_1 + this->hscroll.GetPosition();
 

	
 
				/* Check station availability callback */
 
				const StationSpec *statspec = GetCustomStationSpec(STAT_CLASS_WAYP, type);
 
				if (statspec != NULL &&
 
						HasBit(statspec->callback_mask, CBM_STATION_AVAIL) &&
 
						GB(GetStationCallback(CBID_STATION_AVAILABILITY, 0, 0, statspec, NULL, INVALID_TILE), 0, 8) == 0) return;
 

	
 
				_cur_waypoint_type = type;
 
				SndPlayFx(SND_15_BEEP);
 
				this->SetDirty();
 
				break;
 
			}
 
		}
 
	}
 
};
 

	
 
/** Nested widget definition for the build NewGRF rail waypoint window */
 
static const NWidgetPart _nested_build_waypoint_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN, BRWW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, BRWW_CAPTION), SetDataTip(STR_WAYPOINT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BRWW_BACKGROUND),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 3),
 
		NWidget(NWID_HORIZONTAL), SetPIP(3, 2, 3),
 
			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BRWW_WAYPOINT_1), SetMinimalSize(66, 60), SetDataTip(0x0, STR_WAYPOINT_GRAPHICS_TOOLTIP), EndContainer(),
 
			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BRWW_WAYPOINT_2), SetMinimalSize(66, 60), SetDataTip(0x0, STR_WAYPOINT_GRAPHICS_TOOLTIP), EndContainer(),
 
			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BRWW_WAYPOINT_3), SetMinimalSize(66, 60), SetDataTip(0x0, STR_WAYPOINT_GRAPHICS_TOOLTIP), EndContainer(),
 
			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BRWW_WAYPOINT_4), SetMinimalSize(66, 60), SetDataTip(0x0, STR_WAYPOINT_GRAPHICS_TOOLTIP), EndContainer(),
 
			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BRWW_WAYPOINT_5), SetMinimalSize(66, 60), SetDataTip(0x0, STR_WAYPOINT_GRAPHICS_TOOLTIP), EndContainer(),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 3),
 
		NWidget(WWT_HSCROLLBAR, COLOUR_DARK_GREEN, BRWW_SCROLL),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _build_waypoint_desc(
 
	WDP_AUTO, WDP_AUTO, 344, 92,
 
	WC_BUILD_DEPOT, WC_BUILD_TOOLBAR,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_CONSTRUCTION,
 
	_nested_build_waypoint_widgets, lengthof(_nested_build_waypoint_widgets)
 
);
 

	
 
static void ShowBuildWaypointPicker(Window *parent)
 
{
 
	new BuildRailWaypointWindow(&_build_waypoint_desc, parent);
 
}
 

	
 
/**
 
 * Initialize rail building GUI settings
 
 */
 
void InitializeRailGui()
 
{
 
	_build_depot_direction = DIAGDIR_NW;
 
}
 

	
 
/**
 
 * Re-initialize rail-build toolbar after toggling support for electric trains
 
 * @param disable Boolean whether electric trains are disabled (removed from the game)
 
 */
 
void ReinitGuiAfterToggleElrail(bool disable)
 
{
 
	extern RailType _last_built_railtype;
 
	if (disable && _last_built_railtype == RAILTYPE_ELECTRIC) {
 
		_last_built_railtype = _cur_railtype = RAILTYPE_RAIL;
 
		BuildRailToolbarWindow *w = dynamic_cast<BuildRailToolbarWindow *>(FindWindowById(WC_BUILD_TOOLBAR, TRANSPORT_RAIL));
 
		if (w != NULL) w->ModifyRailType(_cur_railtype);
 
	}
 
	MarkWholeScreenDirty();
 
}
 

	
 
/** Set the initial (default) railtype to use */
 
static void SetDefaultRailGui()
 
{
 
	if (_local_company == COMPANY_SPECTATOR || !Company::IsValidID(_local_company)) return;
 

	
 
	extern RailType _last_built_railtype;
 
	RailType rt = (RailType)_settings_client.gui.default_rail_type;
 
	if (rt >= RAILTYPE_END) {
 
		if (rt == DEF_RAILTYPE_MOST_USED) {
 
			/* Find the most used rail type */
 
			RailType count[RAILTYPE_END];
 
			memset(count, 0, sizeof(count));
 
			for (TileIndex t = 0; t < MapSize(); t++) {
 
				if (IsTileType(t, MP_RAILWAY) || IsLevelCrossingTile(t) || HasStationTileRail(t) ||
 
						(IsTileType(t, MP_TUNNELBRIDGE) && GetTunnelBridgeTransportType(t) == TRANSPORT_RAIL)) {
 
					count[GetRailType(t)]++;
 
				}
 
			}
 

	
 
			rt = RAILTYPE_RAIL;
 
			for (RailType r = RAILTYPE_ELECTRIC; r < RAILTYPE_END; r++) {
 
				if (count[r] >= count[rt]) rt = r;
 
			}
 

	
 
			/* No rail, just get the first available one */
 
			if (count[rt] == 0) rt = DEF_RAILTYPE_FIRST;
 
		}
 
		switch (rt) {
 
			case DEF_RAILTYPE_FIRST:
 
				rt = RAILTYPE_RAIL;
 
				while (rt < RAILTYPE_END && !HasRailtypeAvail(_local_company, rt)) rt++;
 
				break;
 

	
 
			case DEF_RAILTYPE_LAST:
 
				rt = GetBestRailtype(_local_company);
 
				break;
 

	
 
			default:
 
				break;
 
		}
 
	}
 

	
 
	_last_built_railtype = _cur_railtype = rt;
 
	BuildRailToolbarWindow *w = dynamic_cast<BuildRailToolbarWindow *>(FindWindowById(WC_BUILD_TOOLBAR, TRANSPORT_RAIL));
 
	if (w != NULL) w->ModifyRailType(_cur_railtype);
 
}
 

	
 
/**
 
 * Updates the current signal variant used in the signal GUI
 
 * to the one adequate to current year.
 
 * @param p needed to be called when a setting changes
 
 * @return success, needed for settings
 
 */
 
bool ResetSignalVariant(int32 p = 0)
 
{
 
	SignalVariant new_variant = (_cur_year < _settings_client.gui.semaphore_build_before ? SIG_SEMAPHORE : SIG_ELECTRIC);
 

	
 
	if (new_variant != _cur_signal_variant) {
 
		Window *w = FindWindowById(WC_BUILD_SIGNAL, 0);
 
		if (w != NULL) {
 
			w->SetDirty();
 
			w->RaiseWidget((_cur_signal_variant == SIG_ELECTRIC ? BSW_ELECTRIC_NORM : BSW_SEMAPHORE_NORM) + _cur_signal_type);
 
		}
 
		_cur_signal_variant = new_variant;
 
	}
 

	
 
	return true;
 
}
 

	
 
/** Resets the rail GUI - sets default railtype to build
 
 * and resets the signal GUI
 
 */
 
void InitializeRailGUI()
 
{
 
	SetDefaultRailGui();
 

	
 
	_convert_signal_button = false;
 
	_cur_signal_type = _default_signal_type[_settings_client.gui.default_signal_type];
 
	ResetSignalVariant();
 
}
src/road_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file road_gui.cpp GUI for building roads. */
 

	
 
#include "stdafx.h"
 
#include "openttd.h"
 
#include "gui.h"
 
#include "window_gui.h"
 
#include "station_gui.h"
 
#include "terraform_gui.h"
 
#include "viewport_func.h"
 
#include "gfx_func.h"
 
#include "command_func.h"
 
#include "road_type.h"
 
#include "road_cmd.h"
 
#include "road_map.h"
 
#include "station_func.h"
 
#include "functions.h"
 
#include "window_func.h"
 
#include "vehicle_func.h"
 
#include "sound_func.h"
 
#include "company_func.h"
 
#include "tunnelbridge.h"
 
#include "tilehighlight_func.h"
 
#include "company_base.h"
 

	
 
#include "table/sprites.h"
 
#include "table/strings.h"
 

	
 
static void ShowRVStationPicker(Window *parent, RoadStopType rs);
 
static void ShowRoadDepotPicker(Window *parent);
 

	
 
static bool _remove_button_clicked;
 
static bool _one_way_button_clicked;
 

	
 
/**
 
 * Define the values of the RoadFlags
 
 * @see CmdBuildLongRoad
 
 */
 
enum RoadFlags {
 
	RF_NONE             = 0x00,
 
	RF_START_HALFROAD_Y = 0x01,    // The start tile in Y-dir should have only a half road
 
	RF_END_HALFROAD_Y   = 0x02,    // The end tile in Y-dir should have only a half road
 
	RF_DIR_Y            = 0x04,    // The direction is Y-dir
 
	RF_DIR_X            = RF_NONE, // Dummy; Dir X is set when RF_DIR_Y is not set
 
	RF_START_HALFROAD_X = 0x08,    // The start tile in X-dir should have only a half road
 
	RF_END_HALFROAD_X   = 0x10,    // The end tile in X-dir should have only a half road
 
};
 
DECLARE_ENUM_AS_BIT_SET(RoadFlags);
 

	
 
static RoadFlags _place_road_flag;
 

	
 
static RoadType _cur_roadtype;
 

	
 
static DiagDirection _road_depot_orientation;
 
static DiagDirection _road_station_picker_orientation;
 

	
 
void CcPlaySound1D(bool success, TileIndex tile, uint32 p1, uint32 p2)
 
{
 
	if (success) SndPlayTileFx(SND_1F_SPLAT, tile);
 
}
 

	
 
/**
 
 * Set the initial flags for the road constuction.
 
 * The flags are:
 
 * @li The direction is the X-dir
 
 * @li The first tile has a partitial RoadBit (true or false)
 
 *
 
 * @param tile The start tile
 
 */
 
static void PlaceRoad_X_Dir(TileIndex tile)
 
{
 
	_place_road_flag = RF_DIR_X;
 
	if (_tile_fract_coords.x >= 8) _place_road_flag |= RF_START_HALFROAD_X;
 
	VpStartPlaceSizing(tile, VPM_FIX_Y, DDSP_PLACE_ROAD_X_DIR);
 
}
 

	
 
/**
 
 * Set the initial flags for the road constuction.
 
 * The flags are:
 
 * @li The direction is the Y-dir
 
 * @li The first tile has a partitial RoadBit (true or false)
 
 *
 
 * @param tile The start tile
 
 */
 
static void PlaceRoad_Y_Dir(TileIndex tile)
 
{
 
	_place_road_flag = RF_DIR_Y;
 
	if (_tile_fract_coords.y >= 8) _place_road_flag |= RF_START_HALFROAD_Y;
 
	VpStartPlaceSizing(tile, VPM_FIX_X, DDSP_PLACE_ROAD_Y_DIR);
 
}
 

	
 
/**
 
 * Set the initial flags for the road constuction.
 
 * The flags are:
 
 * @li The direction is not set.
 
 * @li The first tile has a partitial RoadBit (true or false)
 
 *
 
 * @param tile The start tile
 
 */
 
static void PlaceRoad_AutoRoad(TileIndex tile)
 
{
 
	_place_road_flag = RF_NONE;
 
	if (_tile_fract_coords.x >= 8) _place_road_flag |= RF_START_HALFROAD_X;
 
	if (_tile_fract_coords.y >= 8) _place_road_flag |= RF_START_HALFROAD_Y;
 
	VpStartPlaceSizing(tile, VPM_X_OR_Y, DDSP_PLACE_AUTOROAD);
 
}
 

	
 
static void PlaceRoad_Bridge(TileIndex tile)
 
{
 
	VpStartPlaceSizing(tile, VPM_X_OR_Y, DDSP_BUILD_BRIDGE);
 
}
 

	
 

	
 
void CcBuildRoadTunnel(bool success, TileIndex tile, uint32 p1, uint32 p2)
 
{
 
	if (success) {
 
		SndPlayTileFx(SND_20_SPLAT_2, tile);
 
		if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace();
 
	} else {
 
		SetRedErrorSquare(_build_tunnel_endtile);
 
	}
 
}
 

	
 
/** Structure holding information per roadtype for several functions */
 
struct RoadTypeInfo {
 
	StringID err_build_road;        ///< Building a normal piece of road
 
	StringID err_remove_road;       ///< Removing a normal piece of road
 
	StringID err_depot;             ///< Building a depot
 
	StringID err_build_station[2];  ///< Building a bus or truck station
 
	StringID err_remove_station[2]; ///< Removing of a bus or truck station
 

	
 
	StringID picker_title[2];       ///< Title for the station picker for bus or truck stations
 
	StringID picker_tooltip[2];     ///< Tooltip for the station picker for bus or truck stations
 

	
 
	SpriteID cursor_nesw;           ///< Cursor for building NE and SW bits
 
	SpriteID cursor_nwse;           ///< Cursor for building NW and SE bits
 
	SpriteID cursor_autoroad;       ///< Cursor for building autoroad
 
};
 

	
 
/** What errors/cursors must be shown for several types of roads */
 
static const RoadTypeInfo _road_type_infos[] = {
 
	{
 
		STR_ERROR_CAN_T_BUILD_ROAD_HERE,
 
		STR_ERROR_CAN_T_REMOVE_ROAD_FROM,
 
		STR_ERROR_CAN_T_BUILD_ROAD_DEPOT,
 
		{ STR_ERROR_CAN_T_BUILD_BUS_STATION,         STR_ERROR_CAN_T_BUILD_TRUCK_STATION          },
 
		{ STR_ERROR_CAN_T_REMOVE_BUS_STATION,        STR_ERROR_CAN_T_REMOVE_TRUCK_STATION         },
 
		{ STR_STATION_BUILD_BUS_ORIENTATION,         STR_STATION_BUILD_TRUCK_ORIENTATION          },
 
		{ STR_STATION_BUILD_BUS_ORIENTATION_TOOLTIP, STR_STATION_BUILD_TRUCK_ORIENTATION_TOOLTIP  },
 

	
 
		SPR_CURSOR_ROAD_NESW,
 
		SPR_CURSOR_ROAD_NWSE,
 
		SPR_CURSOR_AUTOROAD,
 
	},
 
	{
 
		STR_ERROR_CAN_T_BUILD_TRAMWAY_HERE,
 
		STR_ERROR_CAN_T_REMOVE_TRAMWAY_FROM,
 
		STR_ERROR_CAN_T_BUILD_TRAM_DEPOT,
 
		{ STR_ERROR_CAN_T_BUILD_PASSENGER_TRAM_STATION,         STR_ERROR_CAN_T_BUILD_CARGO_TRAM_STATION         },
 
		{ STR_ERROR_CAN_T_REMOVE_PASSENGER_TRAM_STATION,        STR_ERROR_CAN_T_REMOVE_CARGO_TRAM_STATION        },
 
		{ STR_STATION_BUILD_PASSENGER_TRAM_ORIENTATION,         STR_STATION_BUILD_CARGO_TRAM_ORIENTATION         },
 
		{ STR_STATION_BUILD_PASSENGER_TRAM_ORIENTATION_TOOLTIP, STR_STATION_BUILD_CARGO_TRAM_ORIENTATION_TOOLTIP },
 

	
 
		SPR_CURSOR_TRAMWAY_NESW,
 
		SPR_CURSOR_TRAMWAY_NWSE,
 
		SPR_CURSOR_AUTOTRAM,
 
	},
 
};
 

	
 
static void PlaceRoad_Tunnel(TileIndex tile)
 
{
 
	DoCommandP(tile, 0x200 | RoadTypeToRoadTypes(_cur_roadtype), 0, CMD_BUILD_TUNNEL | CMD_MSG(STR_ERROR_CAN_T_BUILD_TUNNEL_HERE), CcBuildRoadTunnel);
 
}
 

	
 
static void BuildRoadOutsideStation(TileIndex tile, DiagDirection direction)
 
{
 
	tile += TileOffsByDiagDir(direction);
 
	/* if there is a roadpiece just outside of the station entrance, build a connecting route */
 
	if (IsNormalRoadTile(tile)) {
 
		if (GetRoadBits(tile, _cur_roadtype) != ROAD_NONE) {
 
			DoCommandP(tile, _cur_roadtype << 4 | DiagDirToRoadBits(ReverseDiagDir(direction)), 0, CMD_BUILD_ROAD);
 
		}
 
	}
 
}
 

	
 
void CcRoadDepot(bool success, TileIndex tile, uint32 p1, uint32 p2)
 
{
 
	if (success) {
 
		DiagDirection dir = (DiagDirection)GB(p1, 0, 2);
 
		SndPlayTileFx(SND_1F_SPLAT, tile);
 
		if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace();
 
		BuildRoadOutsideStation(tile, dir);
 
		/* For a drive-through road stop build connecting road for other entrance */
 
		if (HasBit(p2, 1)) BuildRoadOutsideStation(tile, ReverseDiagDir(dir));
 
	}
 
}
 

	
 
static void PlaceRoad_Depot(TileIndex tile)
 
{
 
	DoCommandP(tile, _cur_roadtype << 2 | _road_depot_orientation, 0, CMD_BUILD_ROAD_DEPOT | CMD_MSG(_road_type_infos[_cur_roadtype].err_depot), CcRoadDepot);
 
}
 

	
 
static void PlaceRoadStop(TileIndex tile, uint32 p2, uint32 cmd)
 
{
 
	uint32 p1 = _road_station_picker_orientation;
 
	SB(p2, 16, 16, INVALID_STATION); // no station to join
 

	
 
	if (p1 >= DIAGDIR_END) {
 
		SetBit(p2, 1); // It's a drive-through stop
 
		p1 -= DIAGDIR_END; // Adjust picker result to actual direction
 
	}
 
	CommandContainer cmdcont = { tile, p1, p2, cmd, CcRoadDepot, "" };
 
	ShowSelectStationIfNeeded(cmdcont, TileArea(tile, 1, 1));
 
}
 

	
 
static void PlaceRoad_BusStation(TileIndex tile)
 
{
 
	if (_remove_button_clicked) {
 
		DoCommandP(tile, 0, ROADSTOP_BUS, CMD_REMOVE_ROAD_STOP | CMD_MSG(_road_type_infos[_cur_roadtype].err_remove_station[ROADSTOP_BUS]), CcPlaySound1D);
 
	} else {
 
		PlaceRoadStop(tile, (_ctrl_pressed << 5) | RoadTypeToRoadTypes(_cur_roadtype) << 2 | ROADSTOP_BUS, CMD_BUILD_ROAD_STOP | CMD_MSG(_road_type_infos[_cur_roadtype].err_build_station[ROADSTOP_BUS]));
 
	}
 
}
 

	
 
static void PlaceRoad_TruckStation(TileIndex tile)
 
{
 
	if (_remove_button_clicked) {
 
		DoCommandP(tile, 0, ROADSTOP_TRUCK, CMD_REMOVE_ROAD_STOP | CMD_MSG(_road_type_infos[_cur_roadtype].err_remove_station[ROADSTOP_TRUCK]), CcPlaySound1D);
 
	} else {
 
		PlaceRoadStop(tile, (_ctrl_pressed << 5) | RoadTypeToRoadTypes(_cur_roadtype) << 2 | ROADSTOP_TRUCK, CMD_BUILD_ROAD_STOP | CMD_MSG(_road_type_infos[_cur_roadtype].err_build_station[ROADSTOP_TRUCK]));
 
	}
 
}
 

	
 
/** Enum referring to the widgets of the build road toolbar */
 
enum RoadToolbarWidgets {
 
	RTW_CLOSEBOX = 0,
 
	RTW_CAPTION,
 
	RTW_STICKY,
 
	RTW_ROAD_X,
 
	RTW_ROAD_Y,
 
	RTW_AUTOROAD,
 
	RTW_DEMOLISH,
 
	RTW_DEPOT,
 
	RTW_BUS_STATION,
 
	RTW_TRUCK_STATION,
 
	RTW_ONE_WAY,
 
	RTW_BUILD_BRIDGE,
 
	RTW_BUILD_TUNNEL,
 
	RTW_REMOVE,
 
};
 

	
 
typedef void OnButtonClick(Window *w);
 

	
 

	
 
/** Toogles state of the Remove button of Build road toolbar
 
 * @param w window the button belongs to
 
 */
 
static void ToggleRoadButton_Remove(Window *w)
 
{
 
	w->ToggleWidgetLoweredState(RTW_REMOVE);
 
	w->SetWidgetDirty(RTW_REMOVE);
 
	_remove_button_clicked = w->IsWidgetLowered(RTW_REMOVE);
 
	SetSelectionRed(_remove_button_clicked);
 
}
 

	
 
/** Updates the Remove button because of Ctrl state change
 
 * @param w window the button belongs to
 
 * @return true iff the remove buton was changed
 
 */
 
static bool RoadToolbar_CtrlChanged(Window *w)
 
{
 
	if (w->IsWidgetDisabled(RTW_REMOVE)) return false;
 

	
 
	/* allow ctrl to switch remove mode only for these widgets */
 
	for (uint i = RTW_ROAD_X; i <= RTW_AUTOROAD; i++) {
 
		if (w->IsWidgetLowered(i)) {
 
			ToggleRoadButton_Remove(w);
 
			return true;
 
		}
 
	}
 

	
 
	return false;
 
}
 

	
 

	
 
/**
 
 * Function that handles the click on the
 
 *  X road placement button.
 
 *
 
 * @param w The current window
 
 */
 
static void BuildRoadClick_X_Dir(Window *w)
 
{
 
	HandlePlacePushButton(w, RTW_ROAD_X, _road_type_infos[_cur_roadtype].cursor_nwse, HT_RECT, PlaceRoad_X_Dir);
 
}
 

	
 
/**
 
 * Function that handles the click on the
 
 *  Y road placement button.
 
 *
 
 * @param w The current window
 
 */
 
static void BuildRoadClick_Y_Dir(Window *w)
 
{
 
	HandlePlacePushButton(w, RTW_ROAD_Y, _road_type_infos[_cur_roadtype].cursor_nesw, HT_RECT, PlaceRoad_Y_Dir);
 
}
 

	
 
/**
 
 * Function that handles the click on the
 
 *  autoroad placement button.
 
 *
 
 * @param w The current window
 
 */
 
static void BuildRoadClick_AutoRoad(Window *w)
 
{
 
	HandlePlacePushButton(w, RTW_AUTOROAD, _road_type_infos[_cur_roadtype].cursor_autoroad, HT_RECT, PlaceRoad_AutoRoad);
 
}
 

	
 
static void BuildRoadClick_Demolish(Window *w)
 
{
 
	HandlePlacePushButton(w, RTW_DEMOLISH, ANIMCURSOR_DEMOLISH, HT_RECT, PlaceProc_DemolishArea);
 
}
 

	
 
static void BuildRoadClick_Depot(Window *w)
 
{
 
	if (_game_mode == GM_EDITOR || !CanBuildVehicleInfrastructure(VEH_ROAD)) return;
 
	if (HandlePlacePushButton(w, RTW_DEPOT, SPR_CURSOR_ROAD_DEPOT, HT_RECT, PlaceRoad_Depot)) ShowRoadDepotPicker(w);
 
}
 

	
 
static void BuildRoadClick_BusStation(Window *w)
 
{
 
	if (_game_mode == GM_EDITOR || !CanBuildVehicleInfrastructure(VEH_ROAD)) return;
 
	if (HandlePlacePushButton(w, RTW_BUS_STATION, SPR_CURSOR_BUS_STATION, HT_RECT, PlaceRoad_BusStation)) ShowRVStationPicker(w, ROADSTOP_BUS);
 
}
 

	
 
static void BuildRoadClick_TruckStation(Window *w)
 
{
 
	if (_game_mode == GM_EDITOR || !CanBuildVehicleInfrastructure(VEH_ROAD)) return;
 
	if (HandlePlacePushButton(w, RTW_TRUCK_STATION, SPR_CURSOR_TRUCK_STATION, HT_RECT, PlaceRoad_TruckStation)) ShowRVStationPicker(w, ROADSTOP_TRUCK);
 
}
 

	
 
/**
 
 * Function that handles the click on the
 
 *  one way road button.
 
 *
 
 * @param w The current window
 
 */
 
static void BuildRoadClick_OneWay(Window *w)
 
{
 
	if (w->IsWidgetDisabled(RTW_ONE_WAY)) return;
 
	w->SetDirty();
 
	w->ToggleWidgetLoweredState(RTW_ONE_WAY);
 
	SetSelectionRed(false);
 
}
 

	
 
static void BuildRoadClick_Bridge(Window *w)
 
{
 
	HandlePlacePushButton(w, RTW_BUILD_BRIDGE, SPR_CURSOR_BRIDGE, HT_RECT, PlaceRoad_Bridge);
 
}
 

	
 
static void BuildRoadClick_Tunnel(Window *w)
 
{
 
	HandlePlacePushButton(w, RTW_BUILD_TUNNEL, SPR_CURSOR_ROAD_TUNNEL, HT_SPECIAL, PlaceRoad_Tunnel);
 
}
 

	
 
static void BuildRoadClick_Remove(Window *w)
 
{
 
	if (w->IsWidgetDisabled(RTW_REMOVE)) return;
 

	
 
	DeleteWindowById(WC_SELECT_STATION, 0);
 
	ToggleRoadButton_Remove(w);
 
	SndPlayFx(SND_15_BEEP);
 
}
 

	
 
/** Array with the handlers of the button-clicks for the road-toolbar */
 
static OnButtonClick * const _build_road_button_proc[] = {
 
	BuildRoadClick_X_Dir,
 
	BuildRoadClick_Y_Dir,
 
	BuildRoadClick_AutoRoad,
 
	BuildRoadClick_Demolish,
 
	BuildRoadClick_Depot,
 
	BuildRoadClick_BusStation,
 
	BuildRoadClick_TruckStation,
 
	BuildRoadClick_OneWay,
 
	BuildRoadClick_Bridge,
 
	BuildRoadClick_Tunnel,
 
	BuildRoadClick_Remove
 
};
 

	
 
/** Array with the keycode of the button-clicks for the road-toolbar */
 
static const uint16 _road_keycodes[] = {
 
	'1',
 
	'2',
 
	'3',
 
	'4',
 
	'5',
 
	'6',
 
	'7',
 
	'8',
 
	'B',
 
	'T',
 
	'R',
 
};
 

	
 
struct BuildRoadToolbarWindow : Window {
 
	BuildRoadToolbarWindow(const WindowDesc *desc, WindowNumber window_number) : Window()
 
	{
 
		this->InitNested(desc, window_number);
 
		this->SetWidgetsDisabledState(true,
 
			RTW_REMOVE,
 
			RTW_ONE_WAY,
 
			WIDGET_LIST_END);
 

	
 
		this->SetWidgetsDisabledState(!CanBuildVehicleInfrastructure(VEH_ROAD),
 
			RTW_DEPOT,
 
			RTW_BUS_STATION,
 
			RTW_TRUCK_STATION,
 
			WIDGET_LIST_END);
 

	
 
		if (_settings_client.gui.link_terraform_toolbar) ShowTerraformToolbar(this);
 
	}
 

	
 
	~BuildRoadToolbarWindow()
 
	{
 
		if (_settings_client.gui.link_terraform_toolbar) DeleteWindowById(WC_SCEN_LAND_GEN, 0, false);
 
	}
 

	
 
	/**
 
	 * Update the remove button lowered state of the road toolbar
 
	 *
 
	 * @param clicked_widget The widget which the client clicked just now
 
	 */
 
	void UpdateOptionWidgetStatus(RoadToolbarWidgets clicked_widget)
 
	{
 
		/* The remove and the one way button state is driven
 
		 * by the other buttons so they don't act on themselfs.
 
		 * Both are only valid if they are able to apply as options. */
 
		switch (clicked_widget) {
 
			case RTW_REMOVE:
 
				this->RaiseWidget(RTW_ONE_WAY);
 
				this->SetWidgetDirty(RTW_ONE_WAY);
 
				break;
 

	
 
			case RTW_ONE_WAY:
 
				this->RaiseWidget(RTW_REMOVE);
 
				this->SetWidgetDirty(RTW_REMOVE);
 
				break;
 

	
 
			case RTW_BUS_STATION:
 
			case RTW_TRUCK_STATION:
 
				this->DisableWidget(RTW_ONE_WAY);
 
				this->SetWidgetDisabledState(RTW_REMOVE, !this->IsWidgetLowered(clicked_widget));
 
				break;
 

	
 
			case RTW_ROAD_X:
 
			case RTW_ROAD_Y:
 
			case RTW_AUTOROAD:
 
				this->SetWidgetsDisabledState(!this->IsWidgetLowered(clicked_widget),
 
					RTW_REMOVE,
 
					RTW_ONE_WAY,
 
					WIDGET_LIST_END);
 
				break;
 

	
 
			default:
 
				/* When any other buttons than road/station, raise and
 
				 * disable the removal button */
 
				this->SetWidgetsDisabledState(true,
 
					RTW_REMOVE,
 
					RTW_ONE_WAY,
 
					WIDGET_LIST_END);
 
				this->SetWidgetsLoweredState(false,
 
					RTW_REMOVE,
 
					RTW_ONE_WAY,
 
					WIDGET_LIST_END);
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		if (widget >= RTW_ROAD_X) {
 
			_remove_button_clicked = false;
 
			_one_way_button_clicked = false;
 
			_build_road_button_proc[widget - RTW_ROAD_X](this);
 
		}
 
		this->UpdateOptionWidgetStatus((RoadToolbarWidgets)widget);
 
		if (_ctrl_pressed) RoadToolbar_CtrlChanged(this);
 
	}
 

	
 
	virtual EventState OnKeyPress(uint16 key, uint16 keycode)
 
	{
 
		EventState state = ES_NOT_HANDLED;
 
		for (uint i = 0; i != lengthof(_road_keycodes); i++) {
 
			if (keycode == _road_keycodes[i]) {
 
				_remove_button_clicked = false;
 
				_one_way_button_clicked = false;
 
				_build_road_button_proc[i](this);
 
				this->UpdateOptionWidgetStatus((RoadToolbarWidgets)(i + RTW_ROAD_X));
 
				if (_ctrl_pressed) RoadToolbar_CtrlChanged(this);
 
				state = ES_HANDLED;
 
				break;
 
			}
 
		}
 
		MarkTileDirtyByTile(TileVirtXY(_thd.pos.x, _thd.pos.y)); // redraw tile selection
 
		return state;
 
	}
 

	
 
	virtual void OnPlaceObject(Point pt, TileIndex tile)
 
	{
 
		_remove_button_clicked = this->IsWidgetLowered(RTW_REMOVE);
 
		_one_way_button_clicked = this->IsWidgetLowered(RTW_ONE_WAY);
 
		_place_proc(tile);
 
	}
 

	
 
	virtual void OnPlaceObjectAbort()
 
	{
 
		this->RaiseButtons();
 
		this->SetWidgetsDisabledState(true,
 
			RTW_REMOVE,
 
			RTW_ONE_WAY,
 
			WIDGET_LIST_END);
 
		this->SetWidgetDirty(RTW_REMOVE);
 
		this->SetWidgetDirty(RTW_ONE_WAY);
 

	
 
		DeleteWindowById(WC_BUS_STATION, TRANSPORT_ROAD);
 
		DeleteWindowById(WC_TRUCK_STATION, TRANSPORT_ROAD);
 
		DeleteWindowById(WC_BUILD_DEPOT, TRANSPORT_ROAD);
 
		DeleteWindowById(WC_SELECT_STATION, 0);
 
		DeleteWindowByClass(WC_BUILD_BRIDGE);
 
	}
 

	
 
	virtual void OnPlaceDrag(ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, Point pt)
 
	{
 
		/* Here we update the end tile flags
 
		 * of the road placement actions.
 
		 * At first we reset the end halfroad
 
		 * bits and if needed we set them again. */
 
		switch (select_proc) {
 
			case DDSP_PLACE_ROAD_X_DIR:
 
				_place_road_flag &= ~RF_END_HALFROAD_X;
 
				if (pt.x & 8) _place_road_flag |= RF_END_HALFROAD_X;
 
				break;
 

	
 
			case DDSP_PLACE_ROAD_Y_DIR:
 
				_place_road_flag &= ~RF_END_HALFROAD_Y;
 
				if (pt.y & 8) _place_road_flag |= RF_END_HALFROAD_Y;
 
				break;
 

	
 
			case DDSP_PLACE_AUTOROAD:
 
				_place_road_flag &= ~(RF_END_HALFROAD_Y | RF_END_HALFROAD_X);
 
				if (pt.y & 8) _place_road_flag |= RF_END_HALFROAD_Y;
 
				if (pt.x & 8) _place_road_flag |= RF_END_HALFROAD_X;
 

	
 
				/* For autoroad we need to update the
 
				 * direction of the road */
 
				if (_thd.size.x > _thd.size.y || (_thd.size.x == _thd.size.y &&
 
						( (_tile_fract_coords.x < _tile_fract_coords.y && (_tile_fract_coords.x + _tile_fract_coords.y) < 16) ||
 
						(_tile_fract_coords.x > _tile_fract_coords.y && (_tile_fract_coords.x + _tile_fract_coords.y) > 16) ))) {
 
					/* Set dir = X */
 
					_place_road_flag &= ~RF_DIR_Y;
 
				} else {
 
					/* Set dir = Y */
 
					_place_road_flag |= RF_DIR_Y;
 
				}
 

	
 
				break;
 

	
 
			default:
 
				break;
 
		}
 

	
 
		VpSelectTilesWithMethod(pt.x, pt.y, select_method);
 
	}
 

	
 
	virtual void OnPlaceMouseUp(ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, Point pt, TileIndex start_tile, TileIndex end_tile)
 
	{
 
		if (pt.x != -1) {
 
			switch (select_proc) {
 
				default: NOT_REACHED();
 
				case DDSP_BUILD_BRIDGE:
 
					if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace();
 
					ShowBuildBridgeWindow(start_tile, end_tile, TRANSPORT_ROAD, RoadTypeToRoadTypes(_cur_roadtype));
 
					break;
 

	
 
				case DDSP_DEMOLISH_AREA:
 
					GUIPlaceProcDragXY(select_proc, start_tile, end_tile);
 
					break;
 

	
 
				case DDSP_PLACE_ROAD_X_DIR:
 
				case DDSP_PLACE_ROAD_Y_DIR:
 
				case DDSP_PLACE_AUTOROAD:
 
					/* Flag description:
 
					 * Use the first three bits (0x07) if dir == Y
 
					 * else use the last 2 bits (X dir has
 
					 * not the 3rd bit set) */
 
					_place_road_flag = (RoadFlags)((_place_road_flag & RF_DIR_Y) ? (_place_road_flag & 0x07) : (_place_road_flag >> 3));
 

	
 
					DoCommandP(end_tile, start_tile, _place_road_flag | (_cur_roadtype << 3) | (_one_way_button_clicked << 5),
 
						_remove_button_clicked ?
 
						CMD_REMOVE_LONG_ROAD | CMD_MSG(_road_type_infos[_cur_roadtype].err_remove_road) :
 
						CMD_BUILD_LONG_ROAD | CMD_MSG(_road_type_infos[_cur_roadtype].err_build_road), CcPlaySound1D);
 
					break;
 
			}
 
		}
 
	}
 

	
 
	virtual void OnPlacePresize(Point pt, TileIndex tile)
 
	{
 
		DoCommand(tile, 0x200 | RoadTypeToRoadTypes(_cur_roadtype), 0, DC_AUTO, CMD_BUILD_TUNNEL);
 
		VpSetPresizeRange(tile, _build_tunnel_endtile == 0 ? tile : _build_tunnel_endtile);
 
	}
 

	
 
	virtual EventState OnCTRLStateChange()
 
	{
 
		if (RoadToolbar_CtrlChanged(this)) return ES_HANDLED;
 
		return ES_NOT_HANDLED;
 
	}
 
};
 

	
 
static const NWidgetPart _nested_build_road_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN, RTW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, RTW_CAPTION), SetDataTip(STR_ROAD_TOOLBAR_ROAD_CONSTRUCTION_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_STICKYBOX, COLOUR_DARK_GREEN, RTW_STICKY),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_ROAD_X),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_X_DIR, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_SECTION),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_X_DIR, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_SECTION),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_ROAD_Y),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_Y_DIR, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_SECTION),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_Y_DIR, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_SECTION),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_AUTOROAD),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_AUTOROAD, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_AUTOROAD),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_AUTOROAD, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_AUTOROAD),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_DEMOLISH),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_DEPOT),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_DEPOT, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_VEHICLE_DEPOT),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_DEPOT, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_VEHICLE_DEPOT),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_BUS_STATION),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_BUS_STATION, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_BUS_STATION),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_BUS_STATION, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_BUS_STATION),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_TRUCK_STATION),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_TRUCK_BAY, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRUCK_LOADING_BAY),
 
		NWidget(WWT_PANEL, COLOUR_DARK_GREEN, -1), SetMinimalSize(0, 22), SetFill(true, true), EndContainer(),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_TRUCK_BAY, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRUCK_LOADING_BAY),
 
		NWidget(WWT_PANEL, COLOUR_DARK_GREEN, -1), SetMinimalSize(0, 22), SetFill(1, 1), EndContainer(),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_ONE_WAY),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_ONE_WAY, STR_ROAD_TOOLBAR_TOOLTIP_TOGGLE_ONE_WAY_ROAD),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_ONE_WAY, STR_ROAD_TOOLBAR_TOOLTIP_TOGGLE_ONE_WAY_ROAD),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_BUILD_BRIDGE),
 
						SetFill(false, true), SetMinimalSize(43, 22), SetDataTip(SPR_IMG_BRIDGE, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_BRIDGE),
 
						SetFill(0, 1), SetMinimalSize(43, 22), SetDataTip(SPR_IMG_BRIDGE, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_BRIDGE),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_BUILD_TUNNEL),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_TUNNEL, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_TUNNEL),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_TUNNEL, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_TUNNEL),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_REMOVE),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_REMOVE, STR_ROAD_TOOLBAR_TOOLTIP_TOGGLE_BUILD_REMOVE_FOR_ROAD),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_REMOVE, STR_ROAD_TOOLBAR_TOOLTIP_TOGGLE_BUILD_REMOVE_FOR_ROAD),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _build_road_desc(
 
	WDP_ALIGN_TBR, 22, 263, 36,
 
	WC_BUILD_TOOLBAR, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_STICKY_BUTTON | WDF_CONSTRUCTION,
 
	_nested_build_road_widgets, lengthof(_nested_build_road_widgets)
 
);
 

	
 
static const NWidgetPart _nested_build_tramway_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN, RTW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, RTW_CAPTION), SetDataTip(STR_ROAD_TOOLBAR_TRAM_CONSTRUCTION_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_STICKYBOX, COLOUR_DARK_GREEN, RTW_STICKY),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_ROAD_X),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_TRAMWAY_X_DIR, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAMWAY_SECTION),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_TRAMWAY_X_DIR, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAMWAY_SECTION),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_ROAD_Y),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_TRAMWAY_Y_DIR, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAMWAY_SECTION),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_TRAMWAY_Y_DIR, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAMWAY_SECTION),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_AUTOROAD),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_AUTOTRAM, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_AUTOTRAM),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_AUTOTRAM, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_AUTOTRAM),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_DEMOLISH),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_DEPOT),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_DEPOT, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAM_VEHICLE_DEPOT),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_DEPOT, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAM_VEHICLE_DEPOT),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_BUS_STATION),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_BUS_STATION, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_PASSENGER_TRAM_STATION),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_BUS_STATION, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_PASSENGER_TRAM_STATION),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_TRUCK_STATION),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_TRUCK_BAY, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_CARGO_TRAM_STATION),
 
		NWidget(WWT_PANEL, COLOUR_DARK_GREEN, -1), SetMinimalSize(0, 22), SetFill(true, true), EndContainer(),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_TRUCK_BAY, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_CARGO_TRAM_STATION),
 
		NWidget(WWT_PANEL, COLOUR_DARK_GREEN, -1), SetMinimalSize(0, 22), SetFill(1, 1), EndContainer(),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_BUILD_BRIDGE),
 
						SetFill(false, true), SetMinimalSize(43, 22), SetDataTip(SPR_IMG_BRIDGE, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAMWAY_BRIDGE),
 
						SetFill(0, 1), SetMinimalSize(43, 22), SetDataTip(SPR_IMG_BRIDGE, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAMWAY_BRIDGE),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_BUILD_TUNNEL),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_TUNNEL, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAMWAY_TUNNEL),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_TUNNEL, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAMWAY_TUNNEL),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_REMOVE),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_REMOVE, STR_ROAD_TOOLBAR_TOOLTIP_TOGGLE_BUILD_REMOVE_FOR_TRAMWAYS),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_REMOVE, STR_ROAD_TOOLBAR_TOOLTIP_TOGGLE_BUILD_REMOVE_FOR_TRAMWAYS),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _build_tramway_desc(
 
	WDP_ALIGN_TBR, 22, 241, 36,
 
	WC_BUILD_TOOLBAR, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_STICKY_BUTTON | WDF_CONSTRUCTION,
 
	_nested_build_tramway_widgets, lengthof(_nested_build_tramway_widgets)
 
);
 

	
 
void ShowBuildRoadToolbar(RoadType roadtype)
 
{
 
	if (!Company::IsValidID(_local_company)) return;
 
	_cur_roadtype = roadtype;
 

	
 
	DeleteWindowByClass(WC_BUILD_TOOLBAR);
 
	AllocateWindowDescFront<BuildRoadToolbarWindow>(roadtype == ROADTYPE_ROAD ? &_build_road_desc : &_build_tramway_desc, TRANSPORT_ROAD);
 
}
 

	
 
static const NWidgetPart _nested_build_road_scen_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN, RTW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, RTW_CAPTION), SetDataTip(STR_ROAD_TOOLBAR_ROAD_CONSTRUCTION_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_STICKYBOX, COLOUR_DARK_GREEN, RTW_STICKY),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_ROAD_X),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_X_DIR, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_SECTION),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_X_DIR, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_SECTION),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_ROAD_Y),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_Y_DIR, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_SECTION),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_Y_DIR, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_SECTION),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_AUTOROAD),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_AUTOROAD, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_AUTOROAD),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_AUTOROAD, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_AUTOROAD),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_DEMOLISH),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC),
 
		NWidget(WWT_PANEL, COLOUR_DARK_GREEN, -1), SetMinimalSize(0, 22), SetFill(true, true), EndContainer(),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC),
 
		NWidget(WWT_PANEL, COLOUR_DARK_GREEN, -1), SetMinimalSize(0, 22), SetFill(1, 1), EndContainer(),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_ONE_WAY),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_ONE_WAY, STR_ROAD_TOOLBAR_TOOLTIP_TOGGLE_ONE_WAY_ROAD),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_ONE_WAY, STR_ROAD_TOOLBAR_TOOLTIP_TOGGLE_ONE_WAY_ROAD),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_BUILD_BRIDGE),
 
						SetFill(false, true), SetMinimalSize(43, 22), SetDataTip(SPR_IMG_BRIDGE, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_BRIDGE),
 
						SetFill(0, 1), SetMinimalSize(43, 22), SetDataTip(SPR_IMG_BRIDGE, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_BRIDGE),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_BUILD_TUNNEL),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_TUNNEL, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_TUNNEL),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_TUNNEL, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_TUNNEL),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, RTW_REMOVE),
 
						SetFill(false, true), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_REMOVE, STR_ROAD_TOOLBAR_TOOLTIP_TOGGLE_BUILD_REMOVE_FOR_ROAD),
 
						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_REMOVE, STR_ROAD_TOOLBAR_TOOLTIP_TOGGLE_BUILD_REMOVE_FOR_ROAD),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _build_road_scen_desc(
 
	WDP_AUTO, WDP_AUTO, 197, 36,
 
	WC_SCEN_BUILD_TOOLBAR, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_STICKY_BUTTON | WDF_CONSTRUCTION,
 
	_nested_build_road_scen_widgets, lengthof(_nested_build_road_scen_widgets)
 
);
 

	
 
void ShowBuildRoadScenToolbar()
 
{
 
	_cur_roadtype = ROADTYPE_ROAD;
 
	AllocateWindowDescFront<BuildRoadToolbarWindow>(&_build_road_scen_desc, 0);
 
}
 

	
 
/** Enum referring to the widgets of the build road depot window */
 
enum BuildRoadDepotWidgets {
 
	BRDW_CLOSEBOX = 0,
 
	BRDW_CAPTION,
 
	BRDW_BACKGROUND,
 
	BRDW_DEPOT_NE,
 
	BRDW_DEPOT_SE,
 
	BRDW_DEPOT_SW,
 
	BRDW_DEPOT_NW,
 
};
 

	
 
struct BuildRoadDepotWindow : public PickerWindowBase {
 
	BuildRoadDepotWindow(const WindowDesc *desc, Window *parent) : PickerWindowBase(parent)
 
	{
 
		this->CreateNestedTree(desc);
 

	
 
		this->LowerWidget(_road_depot_orientation + BRDW_DEPOT_NE);
 
		if ( _cur_roadtype == ROADTYPE_TRAM) {
 
			this->GetWidget<NWidgetCore>(BRDW_CAPTION)->widget_data = STR_BUILD_DEPOT_TRAM_ORIENTATION_CAPTION;
 
			for (int i = BRDW_DEPOT_NE; i <= BRDW_DEPOT_NW; i++) this->GetWidget<NWidgetCore>(i)->tool_tip = STR_BUILD_DEPOT_TRAM_ORIENTATION_SELECT_TOOLTIP;
 
		}
 

	
 
		this->FinishInitNested(desc, TRANSPORT_ROAD);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		if (!IsInsideMM(widget, BRDW_DEPOT_NE, BRDW_DEPOT_NW + 1)) return;
 

	
 
		DrawRoadDepotSprite(r.left - 1, r.top, (DiagDirection)(widget - BRDW_DEPOT_NE + DIAGDIR_NE), _cur_roadtype);
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case BRDW_DEPOT_NW:
 
			case BRDW_DEPOT_NE:
 
			case BRDW_DEPOT_SW:
 
			case BRDW_DEPOT_SE:
 
				this->RaiseWidget(_road_depot_orientation + BRDW_DEPOT_NE);
 
				_road_depot_orientation = (DiagDirection)(widget - BRDW_DEPOT_NE);
 
				this->LowerWidget(_road_depot_orientation + BRDW_DEPOT_NE);
 
				SndPlayFx(SND_15_BEEP);
 
				this->SetDirty();
 
				break;
 

	
 
			default:
 
				break;
 
		}
 
	}
 
};
 

	
 
static const NWidgetPart _nested_build_road_depot_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN, BRDW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, BRDW_CAPTION), SetDataTip(STR_BUILD_DEPOT_ROAD_ORIENTATION_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BRDW_BACKGROUND),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 3),
 
		NWidget(NWID_HORIZONTAL_LTR),
 
			NWidget(NWID_SPACER), SetMinimalSize(3, 0), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetMinimalSize(3, 0), SetFill(1, 0),
 
			NWidget(NWID_VERTICAL),
 
				NWidget(WWT_PANEL, COLOUR_GREY, BRDW_DEPOT_NW), SetMinimalSize(66, 50), SetDataTip(0x0, STR_BUILD_DEPOT_ROAD_ORIENTATION_SELECT_TOOLTIP),
 
				EndContainer(),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 2),
 
				NWidget(WWT_PANEL, COLOUR_GREY, BRDW_DEPOT_SW), SetMinimalSize(66, 50), SetDataTip(0x0, STR_BUILD_DEPOT_ROAD_ORIENTATION_SELECT_TOOLTIP),
 
				EndContainer(),
 
			EndContainer(),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0),
 
			NWidget(NWID_VERTICAL),
 
				NWidget(WWT_PANEL, COLOUR_GREY, BRDW_DEPOT_NE), SetMinimalSize(66, 50), SetDataTip(0x0, STR_BUILD_DEPOT_ROAD_ORIENTATION_SELECT_TOOLTIP),
 
				EndContainer(),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 2),
 
				NWidget(WWT_PANEL, COLOUR_GREY, BRDW_DEPOT_SE), SetMinimalSize(66, 50), SetDataTip(0x0, STR_BUILD_DEPOT_ROAD_ORIENTATION_SELECT_TOOLTIP),
 
				EndContainer(),
 
			EndContainer(),
 
			NWidget(NWID_SPACER), SetMinimalSize(3, 0), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetMinimalSize(3, 0), SetFill(1, 0),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 3),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _build_road_depot_desc(
 
	WDP_AUTO, WDP_AUTO, 140, 122,
 
	WC_BUILD_DEPOT, WC_BUILD_TOOLBAR,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_CONSTRUCTION,
 
	_nested_build_road_depot_widgets, lengthof(_nested_build_road_depot_widgets)
 
);
 

	
 
static void ShowRoadDepotPicker(Window *parent)
 
{
 
	new BuildRoadDepotWindow(&_build_road_depot_desc, parent);
 
}
 

	
 
/** Enum referring to the widgets of the build road station window */
 
enum BuildRoadStationWidgets {
 
	BRSW_CLOSEBOX = 0,
 
	BRSW_CAPTION,
 
	BRSW_BACKGROUND,
 
	BRSW_STATION_NE,
 
	BRSW_STATION_SE,
 
	BRSW_STATION_SW,
 
	BRSW_STATION_NW,
 
	BRSW_STATION_X,
 
	BRSW_STATION_Y,
 
	BRSW_LT_OFF,
 
	BRSW_LT_ON,
 
	BRSW_INFO,
 
};
 

	
 
struct BuildRoadStationWindow : public PickerWindowBase {
 
	BuildRoadStationWindow(const WindowDesc *desc, Window *parent, RoadStopType rs) : PickerWindowBase(parent)
 
	{
 
		this->CreateNestedTree(desc);
 

	
 
		/* Trams don't have non-drivethrough stations */
 
		if (_cur_roadtype == ROADTYPE_TRAM && _road_station_picker_orientation < DIAGDIR_END) {
 
			_road_station_picker_orientation = DIAGDIR_END;
 
		}
 
		this->SetWidgetsDisabledState(_cur_roadtype == ROADTYPE_TRAM,
 
			BRSW_STATION_NE,
 
			BRSW_STATION_SE,
 
			BRSW_STATION_SW,
 
			BRSW_STATION_NW,
 
			WIDGET_LIST_END);
 

	
 
		this->GetWidget<NWidgetCore>(BRSW_CAPTION)->widget_data = _road_type_infos[_cur_roadtype].picker_title[rs];
 
		for (uint i = BRSW_STATION_NE; i < BRSW_LT_OFF; i++) this->GetWidget<NWidgetCore>(i)->tool_tip = _road_type_infos[_cur_roadtype].picker_tooltip[rs];
 

	
 
		this->LowerWidget(_road_station_picker_orientation + BRSW_STATION_NE);
 
		this->LowerWidget(_settings_client.gui.station_show_coverage + BRSW_LT_OFF);
 

	
 
		this->FinishInitNested(desc, TRANSPORT_ROAD);
 

	
 
		this->window_class = (rs == ROADSTOP_BUS) ? WC_BUS_STATION : WC_TRUCK_STATION;
 
	}
 

	
 
	virtual ~BuildRoadStationWindow()
 
	{
 
		DeleteWindowById(WC_SELECT_STATION, 0);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 

	
 
		int rad = _settings_game.station.modified_catchment ? CA_TRUCK /* = CA_BUS */ : CA_UNMODIFIED;
 
		if (_settings_client.gui.station_show_coverage) {
 
			SetTileSelectBigSize(-rad, -rad, 2 * rad, 2 * rad);
 
		} else {
 
			SetTileSelectSize(1, 1);
 
		}
 

	
 
		/* 'Accepts' and 'Supplies' texts. */
 
		StationCoverageType sct = (this->window_class == WC_BUS_STATION) ? SCT_PASSENGERS_ONLY : SCT_NON_PASSENGERS_ONLY;
 
		int top = this->GetWidget<NWidgetBase>(BRSW_LT_ON)->pos_y + this->GetWidget<NWidgetBase>(BRSW_LT_ON)->current_y + WD_PAR_VSEP_NORMAL;
 
		NWidgetBase *back_nwi = this->GetWidget<NWidgetBase>(BRSW_BACKGROUND);
 
		int right = back_nwi->pos_x +  back_nwi->current_x;
 
		int bottom = back_nwi->pos_y +  back_nwi->current_y;
 
		top = DrawStationCoverageAreaText(back_nwi->pos_x + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, sct, rad, false) + WD_PAR_VSEP_NORMAL;
 
		top = DrawStationCoverageAreaText(back_nwi->pos_x + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, sct, rad, true) + WD_PAR_VSEP_NORMAL;
 
		/* Resize background if the text is not equally long as the window. */
 
		if (top > bottom || (top < bottom && back_nwi->current_y > back_nwi->smallest_y)) {
 
			ResizeWindow(this, 0, top - bottom);
 
		}
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		if (!IsInsideMM(widget, BRSW_STATION_NE, BRSW_STATION_Y + 1)) return;
 

	
 
		StationType st = (this->window_class == WC_BUS_STATION) ? STATION_BUS : STATION_TRUCK;
 
		StationPickerDrawSprite(r.left + TILE_PIXELS, r.bottom - TILE_PIXELS, st, INVALID_RAILTYPE, widget < BRSW_STATION_X ? ROADTYPE_ROAD : _cur_roadtype, widget - BRSW_STATION_NE);
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case BRSW_STATION_NE:
 
			case BRSW_STATION_SE:
 
			case BRSW_STATION_SW:
 
			case BRSW_STATION_NW:
 
			case BRSW_STATION_X:
 
			case BRSW_STATION_Y:
 
				this->RaiseWidget(_road_station_picker_orientation + BRSW_STATION_NE);
 
				_road_station_picker_orientation = (DiagDirection)(widget - BRSW_STATION_NE);
 
				this->LowerWidget(_road_station_picker_orientation + BRSW_STATION_NE);
 
				SndPlayFx(SND_15_BEEP);
 
				this->SetDirty();
 
				DeleteWindowById(WC_SELECT_STATION, 0);
 
				break;
 

	
 
			case BRSW_LT_OFF:
 
			case BRSW_LT_ON:
 
				this->RaiseWidget(_settings_client.gui.station_show_coverage + BRSW_LT_OFF);
 
				_settings_client.gui.station_show_coverage = (widget != BRSW_LT_OFF);
 
				this->LowerWidget(_settings_client.gui.station_show_coverage + BRSW_LT_OFF);
 
				SndPlayFx(SND_15_BEEP);
 
				this->SetDirty();
 
				break;
 

	
 
			default:
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnTick()
 
	{
 
		CheckRedrawStationCoverage(this);
 
	}
 
};
 

	
 
/** Widget definition of the build road station window */
 
static const NWidgetPart _nested_rv_station_picker_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN, BRSW_CLOSEBOX),
 
		NWidget(WWT_CAPTION,  COLOUR_DARK_GREEN, BRSW_CAPTION),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_DARK_GREEN, BRSW_BACKGROUND),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 3),
 
		NWidget(NWID_HORIZONTAL), SetPIP(0, 2, 0),
 
			NWidget(NWID_SPACER), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
			NWidget(WWT_PANEL, COLOUR_GREY, BRSW_STATION_NW), SetMinimalSize(66, 50), EndContainer(),
 
			NWidget(WWT_PANEL, COLOUR_GREY, BRSW_STATION_NE), SetMinimalSize(66, 50), EndContainer(),
 
			NWidget(WWT_PANEL, COLOUR_GREY, BRSW_STATION_X),  SetMinimalSize(66, 50), EndContainer(),
 
			NWidget(NWID_SPACER), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 2),
 
		NWidget(NWID_HORIZONTAL), SetPIP(0, 2, 0),
 
			NWidget(NWID_SPACER), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
			NWidget(WWT_PANEL, COLOUR_GREY, BRSW_STATION_SW), SetMinimalSize(66, 50), EndContainer(),
 
			NWidget(WWT_PANEL, COLOUR_GREY, BRSW_STATION_SE), SetMinimalSize(66, 50), EndContainer(),
 
			NWidget(WWT_PANEL, COLOUR_GREY, BRSW_STATION_Y),  SetMinimalSize(66, 50), EndContainer(),
 
			NWidget(NWID_SPACER), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 1),
 
		NWidget(NWID_HORIZONTAL), SetPIP(2, 0, 2),
 
			NWidget(WWT_LABEL, COLOUR_DARK_GREEN, BRSW_INFO), SetMinimalSize(140, 14), SetDataTip(STR_STATION_BUILD_COVERAGE_AREA_TITLE, STR_NULL),
 
			NWidget(NWID_SPACER), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
		EndContainer(),
 
		NWidget(NWID_HORIZONTAL), SetPIP(2, 0, 2),
 
			NWidget(NWID_SPACER), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_LT_OFF), SetMinimalSize(60, 12),
 
											SetDataTip(STR_STATION_BUILD_COVERAGE_OFF, STR_STATION_BUILD_COVERAGE_AREA_OFF_TOOLTIP),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, BRSW_LT_ON), SetMinimalSize(60, 12),
 
											SetDataTip(STR_STATION_BUILD_COVERAGE_ON, STR_STATION_BUILD_COVERAGE_AREA_ON_TOOLTIP),
 
			NWidget(NWID_SPACER), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 10), SetResize(0, 1),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _rv_station_picker_desc(
 
	WDP_AUTO, WDP_AUTO, 207, 178,
 
	WC_BUS_STATION, WC_BUILD_TOOLBAR,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_CONSTRUCTION,
 
	_nested_rv_station_picker_widgets, lengthof(_nested_rv_station_picker_widgets)
 
);
 

	
 
static void ShowRVStationPicker(Window *parent, RoadStopType rs)
 
{
 
	new BuildRoadStationWindow(&_rv_station_picker_desc, parent, rs);
 
}
 

	
 
void InitializeRoadGui()
 
{
 
	_road_depot_orientation = DIAGDIR_NW;
 
	_road_station_picker_orientation = DIAGDIR_NW;
 
}
src/settings_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file settings_gui.cpp GUI for settings. */
 

	
 
#include "stdafx.h"
 
#include "openttd.h"
 
#include "currency.h"
 
#include "gui.h"
 
#include "window_gui.h"
 
#include "textbuf_gui.h"
 
#include "command_func.h"
 
#include "screenshot.h"
 
#include "network/network.h"
 
#include "town.h"
 
#include "settings_internal.h"
 
#include "newgrf_townname.h"
 
#include "strings_func.h"
 
#include "window_func.h"
 
#include "string_func.h"
 
#include "gfx_func.h"
 
#include "widgets/dropdown_type.h"
 
#include "widgets/dropdown_func.h"
 
#include "station_func.h"
 
#include "highscore.h"
 
#include "base_media_base.h"
 
#include "company_base.h"
 
#include "company_func.h"
 
#include <map>
 

	
 
#include "table/sprites.h"
 
#include "table/strings.h"
 

	
 
static const StringID _units_dropdown[] = {
 
	STR_GAME_OPTIONS_MEASURING_UNITS_IMPERIAL,
 
	STR_GAME_OPTIONS_MEASURING_UNITS_METRIC,
 
	STR_GAME_OPTIONS_MEASURING_UNITS_SI,
 
	INVALID_STRING_ID
 
};
 

	
 
static const StringID _driveside_dropdown[] = {
 
	STR_GAME_OPTIONS_ROAD_VEHICLES_DROPDOWN_LEFT,
 
	STR_GAME_OPTIONS_ROAD_VEHICLES_DROPDOWN_RIGHT,
 
	INVALID_STRING_ID
 
};
 

	
 
static const StringID _autosave_dropdown[] = {
 
	STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_OFF,
 
	STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_1_MONTH,
 
	STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_3_MONTHS,
 
	STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_6_MONTHS,
 
	STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_12_MONTHS,
 
	INVALID_STRING_ID,
 
};
 

	
 
static StringID *BuildDynamicDropdown(StringID base, int num)
 
{
 
	static StringID buf[32 + 1];
 
	StringID *p = buf;
 
	while (--num >= 0) *p++ = base++;
 
	*p = INVALID_STRING_ID;
 
	return buf;
 
}
 

	
 
int _nb_orig_names = SPECSTR_TOWNNAME_LAST - SPECSTR_TOWNNAME_START + 1;
 
static StringID *_grf_names = NULL;
 
static int _nb_grf_names = 0;
 

	
 
void InitGRFTownGeneratorNames()
 
{
 
	free(_grf_names);
 
	_grf_names = GetGRFTownNameList();
 
	_nb_grf_names = 0;
 
	for (StringID *s = _grf_names; *s != INVALID_STRING_ID; s++) _nb_grf_names++;
 
}
 

	
 
static inline StringID TownName(int town_name)
 
{
 
	if (town_name < _nb_orig_names) return STR_GAME_OPTIONS_TOWN_NAME_ORIGINAL_ENGLISH + town_name;
 
	town_name -= _nb_orig_names;
 
	if (town_name < _nb_grf_names) return _grf_names[town_name];
 
	return STR_UNDEFINED;
 
}
 

	
 
static int GetCurRes()
 
{
 
	int i;
 

	
 
	for (i = 0; i != _num_resolutions; i++) {
 
		if ((int)_resolutions[i].width == _screen.width &&
 
				(int)_resolutions[i].height == _screen.height) {
 
			break;
 
		}
 
	}
 
	return i;
 
}
 

	
 
/** Widgets of the game options menu */
 
enum GameOptionsWidgets {
 
	GOW_CLOSEBOX = 0,        ///< Close the window
 
	GOW_CAPTION,             ///< Caption of the window
 
	GOW_BACKGROUND,          ///< Background of the window
 
	GOW_CURRENCY_FRAME,      ///< Frame of the currency dropdown
 
	GOW_CURRENCY_DROPDOWN,   ///< Currency dropdown
 
	GOW_DISTANCE_FRAME,      ///< Measuring unit frame
 
	GOW_DISTANCE_DROPDOWN,   ///< Measuring unit dropdown
 
	GOW_ROADSIDE_FRAME,      ///< Road side frame
 
	GOW_ROADSIDE_DROPDOWN,   ///< Dropdown to select the road side (to set the right side ;))
 
	GOW_TOWNNAME_FRAME,      ///< Frame for the town name dropdown
 
	GOW_TOWNNAME_DROPDOWN,   ///< Town name dropdown
 
	GOW_AUTOSAVE_FRAME,      ///< Frame for autosave
 
	GOW_AUTOSAVE_DROPDOWN,   ///< Dropdown to say how often to autosave
 
	GOW_LANG_FRAME,          ///< Language dropdown frame
 
	GOW_LANG_DROPDOWN,       ///< Language dropdown
 
	GOW_RESOLUTION_FRAME,    ///< Frame for the dropdown for the resolution
 
	GOW_RESOLUTION_DROPDOWN, ///< Dropdown for the resolution
 
	GOW_FULLSCREEN_LABEL,    ///< Text (right) to the fullscreen button
 
	GOW_FULLSCREEN_BUTTON,   ///< Toggle fullscreen
 
	GOW_SCREENSHOT_FRAME,    ///< Frame for the screenshot type
 
	GOW_SCREENSHOT_DROPDOWN, ///< Select the screenshot type... please use PNG!
 
	GOW_BASE_GRF_FRAME,      ///< Base GRF selection frame
 
	GOW_BASE_GRF_DROPDOWN,   ///< Use to select a base GRF
 
	GOW_BASE_GRF_STATUS,     ///< Info about missing files etc.
 
	GOW_BASE_GRF_DESCRIPTION,///< Description of selected base GRF
 
	GOW_BASE_SFX_FRAME,      ///< Base SFX selection frame
 
	GOW_BASE_SFX_DROPDOWN,   ///< Use to select a base SFX
 
	GOW_BASE_SFX_DESCRIPTION,///< Description of selected base SFX
 
};
 

	
 
/**
 
 * Update/redraw the townnames dropdown
 
 * @param w   the window the dropdown belongs to
 
 * @param sel the currently selected townname generator
 
 */
 
static void ShowTownnameDropdown(Window *w, int sel)
 
{
 
	typedef std::map<StringID, int, StringIDCompare> TownList;
 
	TownList townnames;
 

	
 
	/* Add and sort original townnames generators */
 
	for (int i = 0; i < _nb_orig_names; i++) townnames[STR_GAME_OPTIONS_TOWN_NAME_ORIGINAL_ENGLISH + i] = i;
 

	
 
	/* Add and sort newgrf townnames generators */
 
	for (int i = 0; i < _nb_grf_names; i++) townnames[_grf_names[i]] = _nb_orig_names + i;
 

	
 
	DropDownList *list = new DropDownList();
 
	for (TownList::iterator it = townnames.begin(); it != townnames.end(); it++) {
 
		list->push_back(new DropDownListStringItem((*it).first, (*it).second, !(_game_mode == GM_MENU || Town::GetNumItems() == 0 || (*it).second == sel)));
 
	}
 

	
 
	ShowDropDownList(w, list, sel, GOW_TOWNNAME_DROPDOWN);
 
}
 

	
 
static void ShowCustCurrency();
 

	
 
template <class T>
 
static void ShowSetMenu(Window *w, int widget)
 
{
 
	int n = T::GetNumSets();
 
	int current = T::GetIndexOfUsedSet();
 

	
 
	DropDownList *list = new DropDownList();
 
	for (int i = 0; i < n; i++) {
 
		list->push_back(new DropDownListCharStringItem(T::GetSet(i)->name, i, (_game_mode == GM_MENU) ? false : (current != i)));
 
	}
 

	
 
	ShowDropDownList(w, list, current, widget);
 
}
 

	
 
struct GameOptionsWindow : Window {
 
	GameSettings *opt;
 
	bool reload;
 

	
 
	GameOptionsWindow(const WindowDesc *desc) : Window()
 
	{
 
		this->opt = (_game_mode == GM_MENU) ? &_settings_newgame : &_settings_game;
 
		this->reload = false;
 

	
 
		this->InitNested(desc);
 
		this->OnInvalidateData(0);
 
	}
 

	
 
	~GameOptionsWindow()
 
	{
 
		DeleteWindowById(WC_CUSTOM_CURRENCY, 0);
 
		if (this->reload) _switch_mode = SM_MENU;
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		switch (widget) {
 
			case GOW_CURRENCY_DROPDOWN:   SetDParam(0, _currency_specs[this->opt->locale.currency].name); break;
 
			case GOW_DISTANCE_DROPDOWN:   SetDParam(0, STR_GAME_OPTIONS_MEASURING_UNITS_IMPERIAL + this->opt->locale.units); break;
 
			case GOW_ROADSIDE_DROPDOWN:   SetDParam(0, STR_GAME_OPTIONS_ROAD_VEHICLES_DROPDOWN_LEFT + this->opt->vehicle.road_side); break;
 
			case GOW_TOWNNAME_DROPDOWN:   SetDParam(0, TownName(this->opt->game_creation.town_name)); break;
 
			case GOW_AUTOSAVE_DROPDOWN:   SetDParam(0, _autosave_dropdown[_settings_client.gui.autosave]); break;
 
			case GOW_LANG_DROPDOWN:       SetDParam(0, SPECSTR_LANGUAGE_START + _dynlang.curr); break;
 
			case GOW_RESOLUTION_DROPDOWN: SetDParam(0, GetCurRes() == _num_resolutions ? STR_RES_OTHER : SPECSTR_RESOLUTION_START + GetCurRes()); break;
 
			case GOW_SCREENSHOT_DROPDOWN: SetDParam(0, SPECSTR_SCREENSHOT_START + _cur_screenshot_format); break;
 
			case GOW_BASE_GRF_DROPDOWN:   SetDParamStr(0, BaseGraphics::GetUsedSet()->name); break;
 
			case GOW_BASE_GRF_STATUS:     SetDParam(0, BaseGraphics::GetUsedSet()->GetNumInvalid()); break;
 
			case GOW_BASE_SFX_DROPDOWN:   SetDParamStr(0, BaseSounds::GetUsedSet()->name); break;
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case GOW_BASE_GRF_DESCRIPTION:
 
				SetDParamStr(0, BaseGraphics::GetUsedSet()->GetDescription(GetCurrentLanguageIsoCode()));
 
				DrawStringMultiLine(r.left, r.right, r.top, UINT16_MAX, STR_BLACK_RAW_STRING);
 
				break;
 

	
 
			case GOW_BASE_SFX_DESCRIPTION:
 
				SetDParamStr(0, BaseSounds::GetUsedSet()->GetDescription(GetCurrentLanguageIsoCode()));
 
				DrawStringMultiLine(r.left, r.right, r.top, UINT16_MAX, STR_BLACK_RAW_STRING);
 
				break;
 
		}
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		switch (widget) {
 
			case GOW_BASE_GRF_DESCRIPTION:
 
				/* Find the biggest description for the default size. */
 
				for (int i = 0; i < BaseGraphics::GetNumSets(); i++) {
 
					SetDParamStr(0, BaseGraphics::GetSet(i)->GetDescription(GetCurrentLanguageIsoCode()));
 
					size->height = max(size->height, (uint)GetStringHeight(STR_BLACK_RAW_STRING, size->width));
 
				}
 
				break;
 

	
 
			case GOW_BASE_GRF_STATUS:
 
				/* Find the biggest description for the default size. */
 
				for (int i = 0; i < BaseGraphics::GetNumSets(); i++) {
 
					uint invalid_files = BaseGraphics::GetSet(i)->GetNumInvalid();
 
					if (invalid_files == 0) continue;
 

	
 
					SetDParam(0, invalid_files);
 
					*size = maxdim(*size, GetStringBoundingBox(STR_GAME_OPTIONS_BASE_GRF_STATUS));
 
				}
 
				break;
 

	
 
			case GOW_BASE_SFX_DESCRIPTION:
 
				/* Find the biggest description for the default size. */
 
				for (int i = 0; i < BaseSounds::GetNumSets(); i++) {
 
					SetDParamStr(0, BaseSounds::GetSet(i)->GetDescription(GetCurrentLanguageIsoCode()));
 
					size->height = max(size->height, (uint)GetStringHeight(STR_BLACK_RAW_STRING, size->width));
 
				}
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case GOW_CURRENCY_DROPDOWN: // Setup currencies dropdown
 
				ShowDropDownMenu(this, BuildCurrencyDropdown(), this->opt->locale.currency, GOW_CURRENCY_DROPDOWN, _game_mode == GM_MENU ? 0 : ~GetMaskOfAllowedCurrencies(), 0);
 
				break;
 

	
 
			case GOW_DISTANCE_DROPDOWN: // Setup distance unit dropdown
 
				ShowDropDownMenu(this, _units_dropdown, this->opt->locale.units, GOW_DISTANCE_DROPDOWN, 0, 0);
 
				break;
 

	
 
			case GOW_ROADSIDE_DROPDOWN: { // Setup road-side dropdown
 
				int i = 0;
 
				extern bool RoadVehiclesAreBuilt();
 

	
 
				/* You can only change the drive side if you are in the menu or ingame with
 
				 * no vehicles present. In a networking game only the server can change it */
 
				if ((_game_mode != GM_MENU && RoadVehiclesAreBuilt()) || (_networking && !_network_server)) {
 
					i = (-1) ^ (1 << this->opt->vehicle.road_side); // disable the other value
 
				}
 

	
 
				ShowDropDownMenu(this, _driveside_dropdown, this->opt->vehicle.road_side, GOW_ROADSIDE_DROPDOWN, i, 0);
 
			} break;
 

	
 
			case GOW_TOWNNAME_DROPDOWN: // Setup townname dropdown
 
				ShowTownnameDropdown(this, this->opt->game_creation.town_name);
 
				break;
 

	
 
			case GOW_AUTOSAVE_DROPDOWN: // Setup autosave dropdown
 
				ShowDropDownMenu(this, _autosave_dropdown, _settings_client.gui.autosave, GOW_AUTOSAVE_DROPDOWN, 0, 0);
 
				break;
 

	
 
			case GOW_LANG_DROPDOWN: { // Setup interface language dropdown
 
				typedef std::map<StringID, int, StringIDCompare> LangList;
 

	
 
				/* Sort language names */
 
				LangList langs;
 
				for (int i = 0; i < _dynlang.num; i++) langs[SPECSTR_LANGUAGE_START + i] = i;
 

	
 
				DropDownList *list = new DropDownList();
 
				for (LangList::iterator it = langs.begin(); it != langs.end(); it++) {
 
					list->push_back(new DropDownListStringItem((*it).first, (*it).second, false));
 
				}
 

	
 
				ShowDropDownList(this, list, _dynlang.curr, GOW_LANG_DROPDOWN);
 
			} break;
 

	
 
			case GOW_RESOLUTION_DROPDOWN: // Setup resolution dropdown
 
				ShowDropDownMenu(this, BuildDynamicDropdown(SPECSTR_RESOLUTION_START, _num_resolutions), GetCurRes(), GOW_RESOLUTION_DROPDOWN, 0, 0);
 
				break;
 

	
 
			case GOW_FULLSCREEN_BUTTON: // Click fullscreen on/off
 
				/* try to toggle full-screen on/off */
 
				if (!ToggleFullScreen(!_fullscreen)) {
 
					ShowErrorMessage(STR_ERROR_FULLSCREEN_FAILED, INVALID_STRING_ID, 0, 0);
 
				}
 
				this->SetWidgetLoweredState(GOW_FULLSCREEN_BUTTON, _fullscreen);
 
				this->SetDirty();
 
				break;
 

	
 
			case GOW_SCREENSHOT_DROPDOWN: // Setup screenshot format dropdown
 
				ShowDropDownMenu(this, BuildDynamicDropdown(SPECSTR_SCREENSHOT_START, _num_screenshot_formats), _cur_screenshot_format, GOW_SCREENSHOT_DROPDOWN, 0, 0);
 
				break;
 

	
 
			case GOW_BASE_GRF_DROPDOWN:
 
				ShowSetMenu<BaseGraphics>(this, GOW_BASE_GRF_DROPDOWN);
 
				break;
 

	
 
			case GOW_BASE_SFX_DROPDOWN:
 
				ShowSetMenu<BaseSounds>(this, GOW_BASE_SFX_DROPDOWN);
 
				break;
 
		}
 
	}
 

	
 
	/**
 
	 * Set the base media set.
 
	 * @param index the index of the media set
 
	 * @tparam T class of media set
 
	 */
 
	template <class T>
 
	void SetMediaSet(int index)
 
	{
 
		if (_game_mode == GM_MENU) {
 
			const char *name = T::GetSet(index)->name;
 

	
 
			free(const_cast<char *>(T::ini_set));
 
			T::ini_set = strdup(name);
 

	
 
			T::SetSet(name);
 
			this->reload = true;
 
			this->InvalidateData();
 
		}
 
	}
 

	
 
	virtual void OnDropdownSelect(int widget, int index)
 
	{
 
		switch (widget) {
 
			case GOW_CURRENCY_DROPDOWN: // Currency
 
				if (index == CUSTOM_CURRENCY_ID) ShowCustCurrency();
 
				this->opt->locale.currency = index;
 
				MarkWholeScreenDirty();
 
				break;
 

	
 
			case GOW_DISTANCE_DROPDOWN: // Measuring units
 
				this->opt->locale.units = index;
 
				MarkWholeScreenDirty();
 
				break;
 

	
 
			case GOW_ROADSIDE_DROPDOWN: // Road side
 
				if (this->opt->vehicle.road_side != index) { // only change if setting changed
 
					uint i;
 
					if (GetSettingFromName("vehicle.road_side", &i) == NULL) NOT_REACHED();
 
					SetSettingValue(i, index);
 
					MarkWholeScreenDirty();
 
				}
 
				break;
 

	
 
			case GOW_TOWNNAME_DROPDOWN: // Town names
 
				if (_game_mode == GM_MENU || Town::GetNumItems() == 0) {
 
					this->opt->game_creation.town_name = index;
 
					SetWindowDirty(WC_GAME_OPTIONS, 0);
 
				}
 
				break;
 

	
 
			case GOW_AUTOSAVE_DROPDOWN: // Autosave options
 
				_settings_client.gui.autosave = index;
 
				this->SetDirty();
 
				break;
 

	
 
			case GOW_LANG_DROPDOWN: // Change interface language
 
				ReadLanguagePack(index);
 
				CheckForMissingGlyphsInLoadedLanguagePack();
 
				UpdateAllStationVirtCoords();
 
				ReInitAllWindows();
 
				break;
 

	
 
			case GOW_RESOLUTION_DROPDOWN: // Change resolution
 
				if (index < _num_resolutions && ChangeResInGame(_resolutions[index].width, _resolutions[index].height)) {
 
					this->SetDirty();
 
				}
 
				break;
 

	
 
			case GOW_SCREENSHOT_DROPDOWN: // Change screenshot format
 
				SetScreenshotFormat(index);
 
				this->SetDirty();
 
				break;
 

	
 
			case GOW_BASE_GRF_DROPDOWN:
 
				this->SetMediaSet<BaseGraphics>(index);
 
				break;
 

	
 
			case GOW_BASE_SFX_DROPDOWN:
 
				this->SetMediaSet<BaseSounds>(index);
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnInvalidateData(int data)
 
	{
 
		this->SetWidgetLoweredState(GOW_FULLSCREEN_BUTTON, _fullscreen);
 

	
 
		bool missing_files = BaseGraphics::GetUsedSet()->GetNumMissing() == 0;
 
		this->GetWidget<NWidgetCore>(GOW_BASE_GRF_STATUS)->SetDataTip(missing_files ? STR_EMPTY : STR_GAME_OPTIONS_BASE_GRF_STATUS, STR_NULL);
 
	}
 
};
 

	
 
static const NWidgetPart _nested_game_options_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, GOW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, GOW_CAPTION), SetDataTip(STR_GAME_OPTIONS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, GOW_BACKGROUND), SetPIP(6, 6, 10),
 
		NWidget(NWID_HORIZONTAL), SetPIP(10, 10, 10),
 
			NWidget(NWID_VERTICAL), SetPIP(0, 6, 0),
 
				NWidget(WWT_FRAME, COLOUR_GREY, GOW_CURRENCY_FRAME), SetDataTip(STR_GAME_OPTIONS_CURRENCY_UNITS_FRAME, STR_NULL),
 
					NWidget(WWT_DROPDOWN, COLOUR_GREY, GOW_CURRENCY_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_STRING, STR_GAME_OPTIONS_CURRENCY_UNITS_DROPDOWN_TOOLTIP), SetFill(true, false),
 
					NWidget(WWT_DROPDOWN, COLOUR_GREY, GOW_CURRENCY_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_STRING, STR_GAME_OPTIONS_CURRENCY_UNITS_DROPDOWN_TOOLTIP), SetFill(1, 0),
 
				EndContainer(),
 
				NWidget(WWT_FRAME, COLOUR_GREY, GOW_ROADSIDE_FRAME), SetDataTip(STR_GAME_OPTIONS_ROAD_VEHICLES_FRAME, STR_NULL),
 
					NWidget(WWT_DROPDOWN, COLOUR_GREY, GOW_ROADSIDE_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_STRING, STR_GAME_OPTIONS_ROAD_VEHICLES_DROPDOWN_TOOLTIP), SetFill(true, false),
 
					NWidget(WWT_DROPDOWN, COLOUR_GREY, GOW_ROADSIDE_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_STRING, STR_GAME_OPTIONS_ROAD_VEHICLES_DROPDOWN_TOOLTIP), SetFill(1, 0),
 
				EndContainer(),
 
				NWidget(WWT_FRAME, COLOUR_GREY, GOW_AUTOSAVE_FRAME), SetDataTip(STR_GAME_OPTIONS_AUTOSAVE_FRAME, STR_NULL),
 
					NWidget(WWT_DROPDOWN, COLOUR_GREY, GOW_AUTOSAVE_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_STRING, STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_TOOLTIP), SetFill(true, false),
 
					NWidget(WWT_DROPDOWN, COLOUR_GREY, GOW_AUTOSAVE_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_STRING, STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_TOOLTIP), SetFill(1, 0),
 
				EndContainer(),
 
				NWidget(WWT_FRAME, COLOUR_GREY, GOW_RESOLUTION_FRAME), SetDataTip(STR_GAME_OPTIONS_RESOLUTION, STR_NULL),
 
					NWidget(WWT_DROPDOWN, COLOUR_GREY, GOW_RESOLUTION_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_STRING, STR_GAME_OPTIONS_RESOLUTION_TOOLTIP), SetFill(true, false), SetPadding(0, 0, 3, 0),
 
					NWidget(WWT_DROPDOWN, COLOUR_GREY, GOW_RESOLUTION_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_STRING, STR_GAME_OPTIONS_RESOLUTION_TOOLTIP), SetFill(1, 0), SetPadding(0, 0, 3, 0),
 
					NWidget(NWID_HORIZONTAL),
 
						NWidget(WWT_TEXT, COLOUR_GREY, GOW_FULLSCREEN_LABEL), SetMinimalSize(0, 12), SetFill(true, false), SetDataTip(STR_GAME_OPTIONS_FULLSCREEN, STR_NULL),
 
						NWidget(WWT_TEXT, COLOUR_GREY, GOW_FULLSCREEN_LABEL), SetMinimalSize(0, 12), SetFill(1, 0), SetDataTip(STR_GAME_OPTIONS_FULLSCREEN, STR_NULL),
 
						NWidget(WWT_TEXTBTN, COLOUR_GREY, GOW_FULLSCREEN_BUTTON), SetMinimalSize(21, 9), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_FULLSCREEN_TOOLTIP),
 
					EndContainer(),
 
				EndContainer(),
 
			EndContainer(),
 

	
 
			NWidget(NWID_VERTICAL), SetPIP(0, 6, 0),
 
				NWidget(WWT_FRAME, COLOUR_GREY, GOW_DISTANCE_FRAME), SetDataTip(STR_GAME_OPTIONS_MEASURING_UNITS_FRAME, STR_NULL),
 
					NWidget(WWT_DROPDOWN, COLOUR_GREY, GOW_DISTANCE_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_STRING, STR_GAME_OPTIONS_MEASURING_UNITS_DROPDOWN_TOOLTIP), SetFill(true, false),
 
					NWidget(WWT_DROPDOWN, COLOUR_GREY, GOW_DISTANCE_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_STRING, STR_GAME_OPTIONS_MEASURING_UNITS_DROPDOWN_TOOLTIP), SetFill(1, 0),
 
				EndContainer(),
 
				NWidget(WWT_FRAME, COLOUR_GREY, GOW_TOWNNAME_FRAME), SetDataTip(STR_GAME_OPTIONS_TOWN_NAMES_FRAME, STR_NULL),
 
					NWidget(WWT_DROPDOWN, COLOUR_GREY, GOW_TOWNNAME_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_STRING, STR_GAME_OPTIONS_TOWN_NAMES_DROPDOWN_TOOLTIP), SetFill(true, false),
 
					NWidget(WWT_DROPDOWN, COLOUR_GREY, GOW_TOWNNAME_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_STRING, STR_GAME_OPTIONS_TOWN_NAMES_DROPDOWN_TOOLTIP), SetFill(1, 0),
 
				EndContainer(),
 
				NWidget(WWT_FRAME, COLOUR_GREY, GOW_LANG_FRAME), SetDataTip(STR_GAME_OPTIONS_LANGUAGE, STR_NULL),
 
					NWidget(WWT_DROPDOWN, COLOUR_GREY, GOW_LANG_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_STRING, STR_GAME_OPTIONS_LANGUAGE_TOOLTIP), SetFill(true, false),
 
					NWidget(WWT_DROPDOWN, COLOUR_GREY, GOW_LANG_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_STRING, STR_GAME_OPTIONS_LANGUAGE_TOOLTIP), SetFill(1, 0),
 
				EndContainer(),
 
				NWidget(WWT_FRAME, COLOUR_GREY, GOW_SCREENSHOT_FRAME), SetDataTip(STR_GAME_OPTIONS_SCREENSHOT_FORMAT, STR_NULL),
 
					NWidget(WWT_DROPDOWN, COLOUR_GREY, GOW_SCREENSHOT_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_STRING, STR_GAME_OPTIONS_SCREENSHOT_FORMAT_TOOLTIP), SetFill(true, false),
 
					NWidget(WWT_DROPDOWN, COLOUR_GREY, GOW_SCREENSHOT_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_STRING, STR_GAME_OPTIONS_SCREENSHOT_FORMAT_TOOLTIP), SetFill(1, 0),
 
				EndContainer(),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 0), SetFill(false, true),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 0), SetFill(0, 1),
 
			EndContainer(),
 
		EndContainer(),
 

	
 
		NWidget(WWT_FRAME, COLOUR_GREY, GOW_BASE_GRF_FRAME), SetDataTip(STR_GAME_OPTIONS_BASE_GRF, STR_NULL), SetPadding(0, 10, 0, 10),
 
			NWidget(NWID_HORIZONTAL), SetPIP(00, 30, 0),
 
				NWidget(WWT_DROPDOWN, COLOUR_GREY, GOW_BASE_GRF_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_RAW_STRING, STR_GAME_OPTIONS_BASE_GRF_TOOLTIP),
 
				NWidget(WWT_TEXT, COLOUR_GREY, GOW_BASE_GRF_STATUS), SetMinimalSize(150, 12), SetDataTip(STR_EMPTY, STR_NULL), SetFill(true, false),
 
				NWidget(WWT_TEXT, COLOUR_GREY, GOW_BASE_GRF_STATUS), SetMinimalSize(150, 12), SetDataTip(STR_EMPTY, STR_NULL), SetFill(1, 0),
 
			EndContainer(),
 
			NWidget(WWT_TEXT, COLOUR_GREY, GOW_BASE_GRF_DESCRIPTION), SetMinimalSize(330, 0), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_BASE_GRF_DESCRIPTION_TOOLTIP), SetFill(true, false), SetPadding(6, 0, 0, 0),
 
			NWidget(WWT_TEXT, COLOUR_GREY, GOW_BASE_GRF_DESCRIPTION), SetMinimalSize(330, 0), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_BASE_GRF_DESCRIPTION_TOOLTIP), SetFill(1, 0), SetPadding(6, 0, 0, 0),
 
		EndContainer(),
 

	
 
		NWidget(WWT_FRAME, COLOUR_GREY, GOW_BASE_SFX_FRAME), SetDataTip(STR_GAME_OPTIONS_BASE_SFX, STR_NULL), SetPadding(0, 10, 0, 10),
 
			NWidget(NWID_HORIZONTAL), SetPIP(0, 30, 0),
 
				NWidget(WWT_DROPDOWN, COLOUR_GREY, GOW_BASE_SFX_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_RAW_STRING, STR_GAME_OPTIONS_BASE_SFX_TOOLTIP),
 
				NWidget(NWID_SPACER), SetFill(true, false),
 
				NWidget(NWID_SPACER), SetFill(1, 0),
 
			EndContainer(),
 
			NWidget(WWT_TEXT, COLOUR_GREY, GOW_BASE_SFX_DESCRIPTION), SetMinimalSize(330, 0), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_BASE_SFX_DESCRIPTION_TOOLTIP), SetFill(true, false), SetPadding(6, 0, 0, 0),
 
			NWidget(WWT_TEXT, COLOUR_GREY, GOW_BASE_SFX_DESCRIPTION), SetMinimalSize(330, 0), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_BASE_SFX_DESCRIPTION_TOOLTIP), SetFill(1, 0), SetPadding(6, 0, 0, 0),
 
		EndContainer(),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _game_options_desc(
 
	WDP_CENTER, WDP_CENTER, 370, 249,
 
	WC_GAME_OPTIONS, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
 
	_nested_game_options_widgets, lengthof(_nested_game_options_widgets)
 
);
 

	
 

	
 
void ShowGameOptions()
 
{
 
	DeleteWindowById(WC_GAME_OPTIONS, 0);
 
	new GameOptionsWindow(&_game_options_desc);
 
}
 

	
 
extern void StartupEconomy();
 

	
 

	
 
/* Names of the game difficulty settings window */
 
enum GameDifficultyWidgets {
 
	GDW_CAPTION,
 
	GDW_UPPER_BG,
 
	GDW_LVL_EASY,
 
	GDW_LVL_MEDIUM,
 
	GDW_LVL_HARD,
 
	GDW_LVL_CUSTOM,
 
	GDW_HIGHSCORE,
 
	GDW_SETTING_BG,
 
	GDW_LOWER_BG,
 
	GDW_ACCEPT,
 
	GDW_CANCEL,
 

	
 
	GDW_OPTIONS_START,
 
};
 

	
 
void SetDifficultyLevel(int mode, DifficultySettings *gm_opt);
 

	
 
class GameDifficultyWindow : public Window {
 
private:
 
	/* Temporary holding place of values in the difficulty window until 'Save' is clicked */
 
	GameSettings opt_mod_temp;
 

	
 
public:
 
	/** The number of difficulty settings */
 
	static const uint GAME_DIFFICULTY_NUM = 18;
 
	/** The number of widgets per difficulty setting */
 
	static const uint WIDGETS_PER_DIFFICULTY = 3;
 

	
 
	GameDifficultyWindow(const WindowDesc *desc) : Window()
 
	{
 
		this->InitNested(desc);
 

	
 
		/* Copy current settings (ingame or in intro) to temporary holding place
 
		 * change that when setting stuff, copy back on clicking 'OK' */
 
		this->opt_mod_temp = (_game_mode == GM_MENU) ? _settings_newgame : _settings_game;
 
		/* Setup disabled buttons when creating window
 
		 * disable all other difficulty buttons during gameplay except for 'custom' */
 
		this->SetWidgetsDisabledState(_game_mode != GM_MENU,
 
			GDW_LVL_EASY,
 
			GDW_LVL_MEDIUM,
 
			GDW_LVL_HARD,
 
			GDW_LVL_CUSTOM,
 
			WIDGET_LIST_END);
 
		this->SetWidgetDisabledState(GDW_HIGHSCORE, _game_mode == GM_EDITOR || _networking); // highscore chart in multiplayer
 
		this->SetWidgetDisabledState(GDW_ACCEPT, _networking && !_network_server); // Save-button in multiplayer (and if client)
 
		this->LowerWidget(GDW_LVL_EASY + this->opt_mod_temp.difficulty.diff_level);
 
		this->OnInvalidateData();
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		widget -= GDW_OPTIONS_START;
 
		if (widget < 0 || (widget % 3) != 2) return;
 

	
 
		widget /= 3;
 

	
 
		uint i;
 
		const SettingDesc *sd = GetSettingFromName("difficulty.max_no_competitors", &i) + widget;
 
		int32 value = (int32)ReadValue(GetVariableAddress(&this->opt_mod_temp, &sd->save), sd->save.conv);
 
		SetDParam(0, sd->desc.str + value);
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		/* Only for the 'descriptions' */
 
		int index = widget - GDW_OPTIONS_START;
 
		if (index < 0 || (index % 3) != 2) return;
 

	
 
		index /= 3;
 

	
 
		uint i;
 
		const SettingDesc *sd = GetSettingFromName("difficulty.max_no_competitors", &i) + index;
 
		const SettingDescBase *sdb = &sd->desc;
 

	
 
		/* Get the string and try all strings from the smallest to the highest value */
 
		StringID str = this->GetWidget<NWidgetCore>(widget)->widget_data;
 
		for (int32 value = sdb->min; (uint32)value <= sdb->max; value += sdb->interval) {
 
			SetDParam(0, sdb->str + value);
 
			*size = maxdim(*size, GetStringBoundingBox(str));
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		if (widget >= GDW_OPTIONS_START) {
 
			widget -= GDW_OPTIONS_START;
 
			if ((widget % 3) == 2) return;
 

	
 
			/* Don't allow clients to make any changes */
 
			if (_networking && !_network_server) return;
 

	
 
			uint i;
 
			const SettingDesc *sd = GetSettingFromName("difficulty.max_no_competitors", &i) + (widget / 3);
 
			const SettingDescBase *sdb = &sd->desc;
 

	
 
			int32 val = (int32)ReadValue(GetVariableAddress(&this->opt_mod_temp, &sd->save), sd->save.conv);
 
			if (widget % 3 == 1) {
 
				/* Increase button clicked */
 
				val = min(val + sdb->interval, (int32)sdb->max);
 
			} else {
 
				/* Decrease button clicked */
 
				val -= sdb->interval;
 
				val = max(val, sdb->min);
 
			}
 

	
 
			/* save value in temporary variable */
 
			WriteValue(GetVariableAddress(&this->opt_mod_temp, &sd->save), sd->save.conv, val);
 
			this->RaiseWidget(GDW_LVL_EASY + this->opt_mod_temp.difficulty.diff_level);
 
			SetDifficultyLevel(3, &this->opt_mod_temp.difficulty); // set difficulty level to custom
 
			this->LowerWidget(GDW_LVL_CUSTOM);
 
			this->InvalidateData();
 
			return;
 
		}
 

	
 
		switch (widget) {
 
			case GDW_LVL_EASY:
 
			case GDW_LVL_MEDIUM:
 
			case GDW_LVL_HARD:
 
			case GDW_LVL_CUSTOM:
 
				/* temporarily change difficulty level */
 
				this->RaiseWidget(GDW_LVL_EASY + this->opt_mod_temp.difficulty.diff_level);
 
				SetDifficultyLevel(widget - GDW_LVL_EASY, &this->opt_mod_temp.difficulty);
 
				this->LowerWidget(GDW_LVL_EASY + this->opt_mod_temp.difficulty.diff_level);
 
				this->InvalidateData();
 
				break;
 

	
 
			case GDW_HIGHSCORE: // Highscore Table
 
				ShowHighscoreTable(this->opt_mod_temp.difficulty.diff_level, -1);
 
				break;
 

	
 
			case GDW_ACCEPT: { // Save button - save changes
 
				GameSettings *opt_ptr = (_game_mode == GM_MENU) ? &_settings_newgame : &_settings_game;
 

	
 
				uint i;
 
				GetSettingFromName("difficulty.diff_level", &i);
 
				DoCommandP(0, i, this->opt_mod_temp.difficulty.diff_level, CMD_CHANGE_SETTING);
 

	
 
				const SettingDesc *sd = GetSettingFromName("difficulty.max_no_competitors", &i);
 
				for (uint btn = 0; btn != GAME_DIFFICULTY_NUM; btn++, sd++) {
 
					int32 new_val = (int32)ReadValue(GetVariableAddress(&this->opt_mod_temp, &sd->save), sd->save.conv);
 
					int32 cur_val = (int32)ReadValue(GetVariableAddress(opt_ptr, &sd->save), sd->save.conv);
 
					/* if setting has changed, change it */
 
					if (new_val != cur_val) {
 
						DoCommandP(0, i + btn, new_val, CMD_CHANGE_SETTING);
 
					}
 
				}
 
				delete this;
 
				/* If we are in the editor, we should reload the economy.
 
				 * This way when you load a game, the max loan and interest rate
 
				 * are loaded correctly. */
 
				if (_game_mode == GM_EDITOR) StartupEconomy();
 
				break;
 
			}
 

	
 
			case GDW_CANCEL: // Cancel button - close window, abandon changes
 
				delete this;
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnInvalidateData(int data = 0)
 
	{
 
		uint i;
 
		const SettingDesc *sd = GetSettingFromName("difficulty.max_no_competitors", &i);
 
		for (i = 0; i < GAME_DIFFICULTY_NUM; i++, sd++) {
 
			const SettingDescBase *sdb = &sd->desc;
 
			/* skip deprecated difficulty options */
 
			if (!SlIsObjectCurrentlyValid(sd->save.version_from, sd->save.version_to)) continue;
 
			int32 value = (int32)ReadValue(GetVariableAddress(&this->opt_mod_temp, &sd->save), sd->save.conv);
 
			bool disable = (sd->desc.flags & SGF_NEWGAME_ONLY) &&
 
					(_game_mode == GM_NORMAL ||
 
					(_game_mode == GM_EDITOR && (sd->desc.flags & SGF_SCENEDIT_TOO) == 0));
 

	
 
			this->SetWidgetDisabledState(GDW_OPTIONS_START + i * 3 + 0, disable || sdb->min == value);
 
			this->SetWidgetDisabledState(GDW_OPTIONS_START + i * 3 + 1, disable || sdb->max == (uint32)value);
 
		}
 
	}
 
};
 

	
 
static NWidgetBase *MakeDifficultyOptionsWidgets(int *biggest_index)
 
{
 
	NWidgetVertical *vert_desc = new NWidgetVertical;
 

	
 
	int widnum = GDW_OPTIONS_START;
 
	uint i, j;
 
	const SettingDesc *sd = GetSettingFromName("difficulty.max_no_competitors", &i);
 

	
 
	for (i = 0, j = 0; i < GameDifficultyWindow::GAME_DIFFICULTY_NUM; i++, sd++, widnum += GameDifficultyWindow::WIDGETS_PER_DIFFICULTY) {
 
		if (!SlIsObjectCurrentlyValid(sd->save.version_from, sd->save.version_to)) continue;
 

	
 
		NWidgetHorizontal *hor = new NWidgetHorizontal;
 

	
 
		/* [<] button. */
 
		NWidgetLeaf *leaf = new NWidgetLeaf(NWID_BUTTON_ARROW, COLOUR_YELLOW, widnum, AWV_DECREASE, STR_TOOLTIP_HSCROLL_BAR_SCROLLS_LIST);
 
		hor->Add(leaf);
 

	
 
		/* [>] button. */
 
		leaf = new NWidgetLeaf(NWID_BUTTON_ARROW, COLOUR_YELLOW, widnum + 1, AWV_INCREASE, STR_TOOLTIP_HSCROLL_BAR_SCROLLS_LIST);
 
		hor->Add(leaf);
 

	
 
		/* Some spacing between the text and the description */
 
		NWidgetSpacer *spacer = new NWidgetSpacer(5, 0);
 
		hor->Add(spacer);
 

	
 
		/* Descriptive text. */
 
		leaf = new NWidgetLeaf(WWT_TEXT, COLOUR_YELLOW, widnum + 2, STR_DIFFICULTY_LEVEL_SETTING_MAXIMUM_NO_COMPETITORS + (j++), STR_NULL);
 
		leaf->SetFill(true, false);
 
		leaf->SetFill(1, 0);
 
		hor->Add(leaf);
 
		vert_desc->Add(hor);
 

	
 
		/* Space vertically */
 
		vert_desc->Add(new NWidgetSpacer(0, 2));
 
	}
 
	*biggest_index = widnum - 1;
 
	return vert_desc;
 
}
 

	
 

	
 
/** Widget definition for the game difficulty settings window */
 
static const NWidgetPart _nested_game_difficulty_widgets[] = {
 
	NWidget(WWT_CAPTION, COLOUR_MAUVE, GDW_CAPTION), SetDataTip(STR_DIFFICULTY_LEVEL_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	NWidget(WWT_PANEL, COLOUR_MAUVE, GDW_UPPER_BG),
 
		NWidget(NWID_VERTICAL), SetPIP(2, 0, 2),
 
			NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 0, 10),
 
				NWidget(WWT_TEXTBTN, COLOUR_YELLOW, GDW_LVL_EASY), SetDataTip(STR_DIFFICULTY_LEVEL_EASY, STR_NULL), SetFill(true, false),
 
				NWidget(WWT_TEXTBTN, COLOUR_YELLOW, GDW_LVL_MEDIUM), SetDataTip(STR_DIFFICULTY_LEVEL_MEDIUM, STR_NULL), SetFill(true, false),
 
				NWidget(WWT_TEXTBTN, COLOUR_YELLOW, GDW_LVL_HARD), SetDataTip(STR_DIFFICULTY_LEVEL_HARD, STR_NULL), SetFill(true, false),
 
				NWidget(WWT_TEXTBTN, COLOUR_YELLOW, GDW_LVL_CUSTOM), SetDataTip(STR_DIFFICULTY_LEVEL_CUSTOM, STR_NULL), SetFill(true, false),
 
				NWidget(WWT_TEXTBTN, COLOUR_YELLOW, GDW_LVL_EASY), SetDataTip(STR_DIFFICULTY_LEVEL_EASY, STR_NULL), SetFill(1, 0),
 
				NWidget(WWT_TEXTBTN, COLOUR_YELLOW, GDW_LVL_MEDIUM), SetDataTip(STR_DIFFICULTY_LEVEL_MEDIUM, STR_NULL), SetFill(1, 0),
 
				NWidget(WWT_TEXTBTN, COLOUR_YELLOW, GDW_LVL_HARD), SetDataTip(STR_DIFFICULTY_LEVEL_HARD, STR_NULL), SetFill(1, 0),
 
				NWidget(WWT_TEXTBTN, COLOUR_YELLOW, GDW_LVL_CUSTOM), SetDataTip(STR_DIFFICULTY_LEVEL_CUSTOM, STR_NULL), SetFill(1, 0),
 
			EndContainer(),
 
			NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 10),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_GREEN, GDW_HIGHSCORE), SetDataTip(STR_DIFFICULTY_LEVEL_HIGH_SCORE_BUTTON, STR_NULL), SetFill(true, false),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_GREEN, GDW_HIGHSCORE), SetDataTip(STR_DIFFICULTY_LEVEL_HIGH_SCORE_BUTTON, STR_NULL), SetFill(1, 0),
 
			EndContainer(),
 
		EndContainer(),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_MAUVE, GDW_SETTING_BG),
 
		NWidget(NWID_VERTICAL), SetPIP(3, 0, 1),
 
			NWidget(NWID_HORIZONTAL), SetPIP(5, 0, 5),
 
				NWidgetFunction(MakeDifficultyOptionsWidgets),
 
			EndContainer(),
 
		EndContainer(),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_MAUVE, GDW_LOWER_BG),
 
		NWidget(NWID_VERTICAL), SetPIP(2, 0, 2),
 
			NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 0, 10),
 
				NWidget(NWID_SPACER), SetFill(true, false),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, GDW_ACCEPT), SetDataTip(STR_DIFFICULTY_LEVEL_SAVE, STR_NULL), SetFill(true, false),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, GDW_CANCEL), SetDataTip(STR_BUTTON_CANCEL, STR_NULL), SetFill(true, false),
 
				NWidget(NWID_SPACER), SetFill(true, false),
 
				NWidget(NWID_SPACER), SetFill(1, 0),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, GDW_ACCEPT), SetDataTip(STR_DIFFICULTY_LEVEL_SAVE, STR_NULL), SetFill(1, 0),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, GDW_CANCEL), SetDataTip(STR_BUTTON_CANCEL, STR_NULL), SetFill(1, 0),
 
				NWidget(NWID_SPACER), SetFill(1, 0),
 
			EndContainer(),
 
		EndContainer(),
 
	EndContainer(),
 
};
 

	
 
/** Window definition for the game difficulty settings window */
 
static const WindowDesc _game_difficulty_desc(
 
	WDP_CENTER, WDP_CENTER, 370, 279,
 
	WC_GAME_OPTIONS, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
 
	_nested_game_difficulty_widgets, lengthof(_nested_game_difficulty_widgets)
 
);
 

	
 
void ShowGameDifficulty()
 
{
 
	DeleteWindowById(WC_GAME_OPTIONS, 0);
 
	new GameDifficultyWindow(&_game_difficulty_desc);
 
}
 

	
 
static int SETTING_HEIGHT = 11;    ///< Height of a single setting in the tree view in pixels
 
static const int LEVEL_WIDTH = 15; ///< Indenting width of a sub-page in pixels
 

	
 
/**
 
 * Flags for #SettingEntry
 
 * @note The #SEF_BUTTONS_MASK matches expectations of the formal parameter 'state' of #DrawArrowButtons
 
 */
 
enum SettingEntryFlags {
 
	SEF_LEFT_DEPRESSED  = 0x01, ///< Of a numeric setting entry, the left button is depressed
 
	SEF_RIGHT_DEPRESSED = 0x02, ///< Of a numeric setting entry, the right button is depressed
 
	SEF_BUTTONS_MASK = (SEF_LEFT_DEPRESSED | SEF_RIGHT_DEPRESSED), ///< Bit-mask for button flags
 

	
 
	SEF_LAST_FIELD = 0x04, ///< This entry is the last one in a (sub-)page
 

	
 
	/* Entry kind */
 
	SEF_SETTING_KIND = 0x10, ///< Entry kind: Entry is a setting
 
	SEF_SUBTREE_KIND = 0x20, ///< Entry kind: Entry is a sub-tree
 
	SEF_KIND_MASK    = (SEF_SETTING_KIND | SEF_SUBTREE_KIND), ///< Bit-mask for fetching entry kind
 
};
 

	
 
struct SettingsPage; // Forward declaration
 

	
 
/** Data fields for a sub-page (#SEF_SUBTREE_KIND kind)*/
 
struct SettingEntrySubtree {
 
	SettingsPage *page; ///< Pointer to the sub-page
 
	bool folded;        ///< Sub-page is folded (not visible except for its title)
 
	StringID title;     ///< Title of the sub-page
 
};
 

	
 
/** Data fields for a single setting (#SEF_SETTING_KIND kind) */
 
struct SettingEntrySetting {
 
	const char *name;           ///< Name of the setting
 
	const SettingDesc *setting; ///< Setting description of the setting
 
	uint index;                 ///< Index of the setting in the settings table
 
};
 

	
 
/** Data structure describing a single setting in a tab */
 
struct SettingEntry {
 
	byte flags; ///< Flags of the setting entry. @see SettingEntryFlags
 
	byte level; ///< Nesting level of this setting entry
 
	union {
 
		SettingEntrySetting entry; ///< Data fields if entry is a setting
 
		SettingEntrySubtree sub;   ///< Data fields if entry is a sub-page
 
	} d; ///< Data fields for each kind
 

	
 
	SettingEntry(const char *nm);
 
	SettingEntry(SettingsPage *sub, StringID title);
 

	
 
	void Init(byte level, bool last_field);
 
	void FoldAll();
 
	void SetButtons(byte new_val);
 

	
 
	uint Length() const;
 
	SettingEntry *FindEntry(uint row, uint *cur_row);
 

	
 
	uint Draw(GameSettings *settings_ptr, int base_x, int base_y, int max_x, uint first_row, uint max_row, uint cur_row, uint parent_last);
 

	
 
private:
 
	void DrawSetting(GameSettings *settings_ptr, const SettingDesc *sd, int x, int y, int max_x, int state);
 
};
 

	
 
/** Data structure describing one page of settings in the settings window. */
 
struct SettingsPage {
 
	SettingEntry *entries; ///< Array of setting entries of the page.
 
	byte num;              ///< Number of entries on the page (statically filled).
 

	
 
	void Init(byte level = 0);
 
	void FoldAll();
 

	
 
	uint Length() const;
 
	SettingEntry *FindEntry(uint row, uint *cur_row) const;
 

	
 
	uint Draw(GameSettings *settings_ptr, int base_x, int base_y, int max_x, uint first_row, uint max_row, uint cur_row = 0, uint parent_last = 0) const;
 
};
 

	
 

	
 
/* == SettingEntry methods == */
 

	
 
/**
 
 * Constructor for a single setting in the 'advanced settings' window
 
 * @param nm Name of the setting in the setting table
 
 */
 
SettingEntry::SettingEntry(const char *nm)
 
{
 
	this->flags = SEF_SETTING_KIND;
 
	this->level = 0;
 
	this->d.entry.name = nm;
 
	this->d.entry.setting = NULL;
 
	this->d.entry.index = 0;
 
}
 

	
 
/**
 
 * Constructor for a sub-page in the 'advanced settings' window
 
 * @param sub   Sub-page
 
 * @param title Title of the sub-page
 
 */
 
SettingEntry::SettingEntry(SettingsPage *sub, StringID title)
 
{
 
	this->flags = SEF_SUBTREE_KIND;
 
	this->level = 0;
 
	this->d.sub.page = sub;
 
	this->d.sub.folded = true;
 
	this->d.sub.title = title;
 
}
 

	
 
/**
 
 * Initialization of a setting entry
 
 * @param level      Page nesting level of this entry
 
 * @param last_field Boolean indicating this entry is the last at the (sub-)page
 
 */
 
void SettingEntry::Init(byte level, bool last_field)
 
{
 
	this->level = level;
 
	if (last_field) this->flags |= SEF_LAST_FIELD;
 

	
 
	switch (this->flags & SEF_KIND_MASK) {
 
		case SEF_SETTING_KIND:
 
			this->d.entry.setting = GetSettingFromName(this->d.entry.name, &this->d.entry.index);
 
			assert(this->d.entry.setting != NULL);
 
			break;
 
		case SEF_SUBTREE_KIND:
 
			this->d.sub.page->Init(level + 1);
 
			break;
 
		default: NOT_REACHED();
 
	}
 
}
 

	
 
/** Recursively close all folds of sub-pages */
 
void SettingEntry::FoldAll()
 
{
 
	switch (this->flags & SEF_KIND_MASK) {
 
		case SEF_SETTING_KIND:
 
			break;
 

	
 
		case SEF_SUBTREE_KIND:
 
			this->d.sub.folded = true;
 
			this->d.sub.page->FoldAll();
 
			break;
 

	
 
		default: NOT_REACHED();
 
	}
 
}
 

	
 

	
 
/**
 
 * Set the button-depressed flags (#SEF_LEFT_DEPRESSED and #SEF_RIGHT_DEPRESSED) to a specified value
 
 * @param new_val New value for the button flags
 
 * @see SettingEntryFlags
 
 */
 
void SettingEntry::SetButtons(byte new_val)
 
{
 
	assert((new_val & ~SEF_BUTTONS_MASK) == 0); // Should not touch any flags outside the buttons
 
	this->flags = (this->flags & ~SEF_BUTTONS_MASK) | new_val;
 
}
 

	
 
/** Return numbers of rows needed to display the entry */
 
uint SettingEntry::Length() const
 
{
 
	switch (this->flags & SEF_KIND_MASK) {
 
		case SEF_SETTING_KIND:
 
			return 1;
 
		case SEF_SUBTREE_KIND:
 
			if (this->d.sub.folded) return 1; // Only displaying the title
 

	
 
			return 1 + this->d.sub.page->Length(); // 1 extra row for the title
 
		default: NOT_REACHED();
 
	}
 
}
 

	
 
/**
 
 * Find setting entry at row \a row_num
 
 * @param row_num Index of entry to return
 
 * @param cur_row Current row number
 
 * @return The requested setting entry or \c NULL if it not found
 
 */
 
SettingEntry *SettingEntry::FindEntry(uint row_num, uint *cur_row)
 
{
 
	if (row_num == *cur_row) return this;
 

	
 
	switch (this->flags & SEF_KIND_MASK) {
 
		case SEF_SETTING_KIND:
 
			(*cur_row)++;
 
			break;
 
		case SEF_SUBTREE_KIND:
 
			(*cur_row)++; // add one for row containing the title
 
			if (this->d.sub.folded) {
 
				break;
 
			}
 

	
 
			/* sub-page is visible => search it too */
 
			return this->d.sub.page->FindEntry(row_num, cur_row);
 
		default: NOT_REACHED();
 
	}
 
	return NULL;
 
}
 

	
 
/**
 
 * Draw a row in the settings panel.
 
 *
 
 * See SettingsPage::Draw() for an explanation about how drawing is performed.
 
 *
 
 * The \a parent_last parameter ensures that the vertical lines at the left are
 
 * only drawn when another entry follows, that it prevents output like
 
 * \verbatim
 
 *  |-- setting
 
 *  |-- (-) - Title
 
 *  |    |-- setting
 
 *  |    |-- setting
 
 * \endverbatim
 
 * The left-most vertical line is not wanted. It is prevented by setting the
 
 * appropiate bit in the \a parent_last parameter.
 
 *
 
 * @param settings_ptr Pointer to current values of all settings
 
 * @param left         Left-most position in window/panel to start drawing \a first_row
 
 * @param right        Right-most x position to draw strings at.
 
 * @param base_y       Upper-most position in window/panel to start drawing \a first_row
 
 * @param first_row    First row number to draw
 
 * @param max_row      Row-number to stop drawing (the row-number of the row below the last row to draw)
 
 * @param cur_row      Current row number (internal variable)
 
 * @param parent_last  Last-field booleans of parent page level (page level \e i sets bit \e i to 1 if it is its last field)
 
 * @return Row number of the next row to draw
 
 */
 
uint SettingEntry::Draw(GameSettings *settings_ptr, int left, int right, int base_y, uint first_row, uint max_row, uint cur_row, uint parent_last)
 
{
 
	if (cur_row >= max_row) return cur_row;
 

	
 
	bool rtl = _dynlang.text_dir == TD_RTL;
 
	int offset = rtl ? -4 : 4;
 
	int level_width = rtl ? -LEVEL_WIDTH : LEVEL_WIDTH;
 

	
 
	int x = rtl ? right : left;
 
	int y = base_y;
 
	if (cur_row >= first_row) {
 
		int colour = _colour_gradient[COLOUR_ORANGE][4];
 
		y = base_y + (cur_row - first_row) * SETTING_HEIGHT; // Compute correct y start position
 

	
 
		/* Draw vertical for parent nesting levels */
 
		for (uint lvl = 0; lvl < this->level; lvl++) {
 
			if (!HasBit(parent_last, lvl)) GfxDrawLine(x + offset, y, x + offset, y + SETTING_HEIGHT - 1, colour);
 
			x += level_width;
 
		}
 
		/* draw own |- prefix */
 
		int halfway_y = y + SETTING_HEIGHT / 2;
 
		int bottom_y = (flags & SEF_LAST_FIELD) ? halfway_y : y + SETTING_HEIGHT - 1;
 
		GfxDrawLine(x + offset, y, x + offset, bottom_y, colour);
 
		/* Small horizontal line from the last vertical line */
 
		GfxDrawLine(x + offset, halfway_y, x + level_width - offset, halfway_y, colour);
 
		x += level_width;
 
	}
 

	
 
	switch (this->flags & SEF_KIND_MASK) {
 
		case SEF_SETTING_KIND:
 
			if (cur_row >= first_row) {
 
				DrawSetting(settings_ptr, this->d.entry.setting, rtl ? left : x, rtl ? x : right, y, this->flags & SEF_BUTTONS_MASK);
 
			}
 
			cur_row++;
 
			break;
 
		case SEF_SUBTREE_KIND:
 
			if (cur_row >= first_row) {
 
				DrawSprite((this->d.sub.folded ? SPR_CIRCLE_FOLDED : SPR_CIRCLE_UNFOLDED), PAL_NONE, rtl ? x - 8 : x, y + (SETTING_HEIGHT - 11) / 2);
 
				DrawString(rtl ? left : x + 12, rtl ? x - 12 : right, y, this->d.sub.title);
 
			}
 
			cur_row++;
 
			if (!this->d.sub.folded) {
 
				if (this->flags & SEF_LAST_FIELD) {
 
					assert(this->level < sizeof(parent_last));
 
					SetBit(parent_last, this->level); // Add own last-field state
 
				}
 

	
 
				cur_row = this->d.sub.page->Draw(settings_ptr, left, right, base_y, first_row, max_row, cur_row, parent_last);
 
			}
 
			break;
 
		default: NOT_REACHED();
 
	}
 
	return cur_row;
 
}
 

	
 
static const void *ResolveVariableAddress(const GameSettings *settings_ptr, const SettingDesc *sd)
 
{
 
	if ((sd->desc.flags & SGF_PER_COMPANY) != 0) {
 
		if (Company::IsValidID(_local_company) && _game_mode != GM_MENU) {
 
			return GetVariableAddress(&Company::Get(_local_company)->settings, &sd->save);
 
		} else {
 
			return GetVariableAddress(&_settings_client.company, &sd->save);
 
		}
 
	} else {
 
		return GetVariableAddress(settings_ptr, &sd->save);
 
	}
 
}
 

	
 
/**
 
 * Private function to draw setting value (button + text + current value)
 
 * @param settings_ptr Pointer to current values of all settings
 
 * @param sd           Pointer to value description of setting to draw
 
 * @param left         Left-most position in window/panel to start drawing
 
 * @param right        Right-most position in window/panel to draw
 
 * @param y            Upper-most position in window/panel to start drawing
 
 * @param state        State of the left + right arrow buttons to draw for the setting
 
 */
 
void SettingEntry::DrawSetting(GameSettings *settings_ptr, const SettingDesc *sd, int left, int right, int y, int state)
 
{
 
	const SettingDescBase *sdb = &sd->desc;
 
	const void *var = ResolveVariableAddress(settings_ptr, sd);
 
	bool editable = true;
 
	bool disabled = false;
 

	
 
	bool rtl = _dynlang.text_dir == TD_RTL;
 
	uint buttons_left = rtl ? right - 19 : left;
 
	uint text_left  = left + (rtl ? 0 : 25);
 
	uint text_right = right - (rtl ? 25 : 0);
 
	uint button_y = y + (SETTING_HEIGHT - 11) / 2;
 

	
 
	/* We do not allow changes of some items when we are a client in a networkgame */
 
	if (!(sd->save.conv & SLF_NETWORK_NO) && _networking && !_network_server && !(sdb->flags & SGF_PER_COMPANY)) editable = false;
 
	if ((sdb->flags & SGF_NETWORK_ONLY) && !_networking) editable = false;
 
	if ((sdb->flags & SGF_NO_NETWORK) && _networking) editable = false;
 

	
 
	if (sdb->cmd == SDT_BOOLX) {
 
		static const Colours _bool_ctabs[2][2] = {{COLOUR_CREAM, COLOUR_RED}, {COLOUR_DARK_GREEN, COLOUR_GREEN}};
 
		/* Draw checkbox for boolean-value either on/off */
 
		bool on = (*(bool*)var);
 

	
 
		DrawFrameRect(buttons_left, button_y, buttons_left + 19, button_y + 8, _bool_ctabs[!!on][!!editable], on ? FR_LOWERED : FR_NONE);
 
		SetDParam(0, on ? STR_CONFIG_SETTING_ON : STR_CONFIG_SETTING_OFF);
 
	} else {
 
		int32 value;
 

	
 
		value = (int32)ReadValue(var, sd->save.conv);
 

	
 
		/* Draw [<][>] boxes for settings of an integer-type */
 
		DrawArrowButtons(buttons_left, button_y, COLOUR_YELLOW, state, editable && value != (sdb->flags & SGF_0ISDISABLED ? 0 : sdb->min), editable && (uint32)value != sdb->max);
 

	
 
		disabled = (value == 0) && (sdb->flags & SGF_0ISDISABLED);
 
		if (disabled) {
 
			SetDParam(0, STR_CONFIG_SETTING_DISABLED);
 
		} else {
 
			if (sdb->flags & SGF_CURRENCY) {
 
				SetDParam(0, STR_JUST_CURRENCY);
 
			} else if (sdb->flags & SGF_MULTISTRING) {
 
				SetDParam(0, sdb->str + value + 1);
 
			} else {
 
				SetDParam(0, (sdb->flags & SGF_NOCOMMA) ? STR_JUST_INT : STR_JUST_COMMA);
 
			}
 
			SetDParam(1, value);
 
		}
 
	}
 
	DrawString(text_left, text_right, y, (sdb->str) + disabled);
 
}
 

	
 

	
 
/* == SettingsPage methods == */
 

	
 
/**
 
 * Initialization of an entire setting page
 
 * @param level Nesting level of this page (internal variable, do not provide a value for it when calling)
 
 */
 
void SettingsPage::Init(byte level)
 
{
 
	for (uint field = 0; field < this->num; field++) {
 
		this->entries[field].Init(level, field + 1 == num);
 
	}
 
}
 

	
 
/** Recursively close all folds of sub-pages */
 
void SettingsPage::FoldAll()
 
{
 
	for (uint field = 0; field < this->num; field++) {
 
		this->entries[field].FoldAll();
 
	}
 
}
 

	
 
/** Return number of rows needed to display the whole page */
 
uint SettingsPage::Length() const
 
{
 
	uint length = 0;
 
	for (uint field = 0; field < this->num; field++) {
 
		length += this->entries[field].Length();
 
	}
 
	return length;
 
}
 

	
 
/**
 
 * Find the setting entry at row number \a row_num
 
 * @param row_num Index of entry to return
 
 * @param cur_row Variable used for keeping track of the current row number. Should point to memory initialized to \c 0 when first called.
 
 * @return The requested setting entry or \c NULL if it does not exist
 
 */
 
SettingEntry *SettingsPage::FindEntry(uint row_num, uint *cur_row) const
 
{
 
	SettingEntry *pe = NULL;
 

	
 
	for (uint field = 0; field < this->num; field++) {
 
		pe = this->entries[field].FindEntry(row_num, cur_row);
 
		if (pe != NULL) {
 
			break;
 
		}
 
	}
 
	return pe;
 
}
 

	
 
/**
 
 * Draw a selected part of the settings page.
 
 *
 
 * The scrollbar uses rows of the page, while the page data strucure is a tree of #SettingsPage and #SettingEntry objects.
 
 * As a result, the drawing routing traverses the tree from top to bottom, counting rows in \a cur_row until it reaches \a first_row.
 
 * Then it enables drawing rows while traversing until \a max_row is reached, at which point drawing is terminated.
 
 *
 
 * @param settings_ptr Pointer to current values of all settings
 
 * @param left         Left-most position in window/panel to start drawing of each setting row
 
 * @param right        Right-most position in window/panel to draw at
 
 * @param base_y       Upper-most position in window/panel to start drawing of row number \a first_row
 
 * @param first_row    Number of first row to draw
 
 * @param max_row      Row-number to stop drawing (the row-number of the row below the last row to draw)
 
 * @param cur_row      Current row number (internal variable)
 
 * @param parent_last  Last-field booleans of parent page level (page level \e i sets bit \e i to 1 if it is its last field)
 
 * @return Row number of the next row to draw
 
 */
 
uint SettingsPage::Draw(GameSettings *settings_ptr, int left, int right, int base_y, uint first_row, uint max_row, uint cur_row, uint parent_last) const
 
{
 
	if (cur_row >= max_row) return cur_row;
 

	
 
	for (uint i = 0; i < this->num; i++) {
 
		cur_row = this->entries[i].Draw(settings_ptr, left, right, base_y, first_row, max_row, cur_row, parent_last);
 
		if (cur_row >= max_row) {
 
			break;
 
		}
 
	}
 
	return cur_row;
 
}
 

	
 

	
 
static SettingEntry _settings_ui_display[] = {
 
	SettingEntry("gui.vehicle_speed"),
 
	SettingEntry("gui.status_long_date"),
 
	SettingEntry("gui.date_format_in_default_names"),
 
	SettingEntry("gui.population_in_label"),
 
	SettingEntry("gui.measure_tooltip"),
 
	SettingEntry("gui.loading_indicators"),
 
	SettingEntry("gui.liveries"),
 
	SettingEntry("gui.show_track_reservation"),
 
	SettingEntry("gui.expenses_layout"),
 
};
 
/** Display options sub-page */
 
static SettingsPage _settings_ui_display_page = {_settings_ui_display, lengthof(_settings_ui_display)};
 

	
 
static SettingEntry _settings_ui_interaction[] = {
 
	SettingEntry("gui.window_snap_radius"),
 
	SettingEntry("gui.window_soft_limit"),
 
	SettingEntry("gui.link_terraform_toolbar"),
 
	SettingEntry("gui.prefer_teamchat"),
 
	SettingEntry("gui.autoscroll"),
 
	SettingEntry("gui.reverse_scroll"),
 
	SettingEntry("gui.smooth_scroll"),
 
	SettingEntry("gui.left_mouse_btn_scrolling"),
 
	/* While the horizontal scrollwheel scrolling is written as general code, only
 
	 *  the cocoa (OSX) driver generates input for it.
 
	 *  Since it's also able to completely disable the scrollwheel will we display it on all platforms anyway */
 
	SettingEntry("gui.scrollwheel_scrolling"),
 
	SettingEntry("gui.scrollwheel_multiplier"),
 
#ifdef __APPLE__
 
	/* We might need to emulate a right mouse button on mac */
 
	SettingEntry("gui.right_mouse_btn_emulation"),
 
#endif
 
};
 
/** Interaction sub-page */
 
static SettingsPage _settings_ui_interaction_page = {_settings_ui_interaction, lengthof(_settings_ui_interaction)};
 

	
 
static SettingEntry _settings_ui[] = {
 
	SettingEntry(&_settings_ui_display_page, STR_CONFIG_SETTING_DISPLAY_OPTIONS),
 
	SettingEntry(&_settings_ui_interaction_page, STR_CONFIG_SETTING_INTERACTION),
 
	SettingEntry("gui.show_finances"),
 
	SettingEntry("gui.errmsg_duration"),
 
	SettingEntry("gui.toolbar_pos"),
 
	SettingEntry("gui.pause_on_newgame"),
 
	SettingEntry("gui.advanced_vehicle_list"),
 
	SettingEntry("gui.timetable_in_ticks"),
 
	SettingEntry("gui.quick_goto"),
 
	SettingEntry("gui.default_rail_type"),
 
	SettingEntry("gui.always_build_infrastructure"),
 
	SettingEntry("gui.persistent_buildingtools"),
 
	SettingEntry("gui.coloured_news_year"),
 
};
 
/** Interface subpage */
 
static SettingsPage _settings_ui_page = {_settings_ui, lengthof(_settings_ui)};
 

	
 
static SettingEntry _settings_construction_signals[] = {
 
	SettingEntry("construction.signal_side"),
 
	SettingEntry("gui.enable_signal_gui"),
 
	SettingEntry("gui.drag_signals_density"),
 
	SettingEntry("gui.semaphore_build_before"),
 
	SettingEntry("gui.default_signal_type"),
 
	SettingEntry("gui.cycle_signal_types"),
 
};
 
/** Signals subpage */
 
static SettingsPage _settings_construction_signals_page = {_settings_construction_signals, lengthof(_settings_construction_signals)};
 

	
 
static SettingEntry _settings_construction[] = {
 
	SettingEntry(&_settings_construction_signals_page, STR_CONFIG_SETTING_CONSTRUCTION_SIGNALS),
 
	SettingEntry("construction.build_on_slopes"),
 
	SettingEntry("construction.autoslope"),
 
	SettingEntry("construction.extra_dynamite"),
 
	SettingEntry("construction.longbridges"),
 
	SettingEntry("station.never_expire_airports"),
 
	SettingEntry("construction.freeform_edges"),
 
};
 
/** Construction sub-page */
 
static SettingsPage _settings_construction_page = {_settings_construction, lengthof(_settings_construction)};
 

	
 
static SettingEntry _settings_stations_cargo[] = {
 
	SettingEntry("order.improved_load"),
 
	SettingEntry("order.gradual_loading"),
 
	SettingEntry("order.selectgoods"),
 
};
 
/** Cargo handling sub-page */
 
static SettingsPage _settings_stations_cargo_page = {_settings_stations_cargo, lengthof(_settings_stations_cargo)};
 

	
 
static SettingEntry _settings_stations[] = {
 
	SettingEntry(&_settings_stations_cargo_page, STR_CONFIG_SETTING_STATIONS_CARGOHANDLING),
 
	SettingEntry("station.join_stations"),
 
	SettingEntry("station.nonuniform_stations"),
 
	SettingEntry("station.adjacent_stations"),
 
	SettingEntry("station.distant_join_stations"),
 
	SettingEntry("station.station_spread"),
 
	SettingEntry("economy.station_noise_level"),
 
	SettingEntry("station.modified_catchment"),
 
	SettingEntry("construction.road_stop_on_town_road"),
 
	SettingEntry("construction.road_stop_on_competitor_road"),
 
};
 
/** Stations sub-page */
 
static SettingsPage _settings_stations_page = {_settings_stations, lengthof(_settings_stations)};
 

	
 
static SettingEntry _settings_economy_towns[] = {
 
	SettingEntry("economy.bribe"),
 
	SettingEntry("economy.exclusive_rights"),
 
	SettingEntry("economy.town_layout"),
 
	SettingEntry("economy.allow_town_roads"),
 
	SettingEntry("economy.mod_road_rebuild"),
 
	SettingEntry("economy.town_growth_rate"),
 
	SettingEntry("economy.larger_towns"),
 
	SettingEntry("economy.initial_city_size"),
 
};
 
/** Towns sub-page */
 
static SettingsPage _settings_economy_towns_page = {_settings_economy_towns, lengthof(_settings_economy_towns)};
 

	
 
static SettingEntry _settings_economy_industries[] = {
 
	SettingEntry("construction.raw_industry_construction"),
 
	SettingEntry("economy.multiple_industry_per_town"),
 
	SettingEntry("economy.same_industry_close"),
 
	SettingEntry("game_creation.oil_refinery_limit"),
 
};
 
/** Industries sub-page */
 
static SettingsPage _settings_economy_industries_page = {_settings_economy_industries, lengthof(_settings_economy_industries)};
 

	
 
static SettingEntry _settings_economy[] = {
 
	SettingEntry(&_settings_economy_towns_page, STR_CONFIG_SETTING_ECONOMY_TOWNS),
 
	SettingEntry(&_settings_economy_industries_page, STR_CONFIG_SETTING_ECONOMY_INDUSTRIES),
 
	SettingEntry("economy.inflation"),
 
	SettingEntry("economy.smooth_economy"),
 
};
 
/** Economy sub-page */
 
static SettingsPage _settings_economy_page = {_settings_economy, lengthof(_settings_economy)};
 

	
 
static SettingEntry _settings_ai_npc[] = {
 
	SettingEntry("ai.ai_in_multiplayer"),
 
	SettingEntry("ai.ai_disable_veh_train"),
 
	SettingEntry("ai.ai_disable_veh_roadveh"),
 
	SettingEntry("ai.ai_disable_veh_aircraft"),
 
	SettingEntry("ai.ai_disable_veh_ship"),
 
	SettingEntry("ai.ai_max_opcode_till_suspend"),
 
};
 
/** Computer players sub-page */
 
static SettingsPage _settings_ai_npc_page = {_settings_ai_npc, lengthof(_settings_ai_npc)};
 

	
 
static SettingEntry _settings_ai[] = {
 
	SettingEntry(&_settings_ai_npc_page, STR_CONFIG_SETTING_AI_NPC),
 
	SettingEntry("economy.give_money"),
 
	SettingEntry("economy.allow_shares"),
 
};
 
/** AI sub-page */
 
static SettingsPage _settings_ai_page = {_settings_ai, lengthof(_settings_ai)};
 

	
 
static SettingEntry _settings_vehicles_routing[] = {
 
	SettingEntry("pf.pathfinder_for_trains"),
 
	SettingEntry("pf.forbid_90_deg"),
 
	SettingEntry("pf.pathfinder_for_roadvehs"),
 
	SettingEntry("pf.roadveh_queue"),
 
	SettingEntry("pf.pathfinder_for_ships"),
 
};
 
/** Autorenew sub-page */
 
static SettingsPage _settings_vehicles_routing_page = {_settings_vehicles_routing, lengthof(_settings_vehicles_routing)};
 

	
 
static SettingEntry _settings_vehicles_autorenew[] = {
 
	SettingEntry("company.engine_renew"),
 
	SettingEntry("company.engine_renew_months"),
 
	SettingEntry("company.engine_renew_money"),
 
};
 
/** Autorenew sub-page */
 
static SettingsPage _settings_vehicles_autorenew_page = {_settings_vehicles_autorenew, lengthof(_settings_vehicles_autorenew)};
 

	
 
static SettingEntry _settings_vehicles_servicing[] = {
 
	SettingEntry("vehicle.servint_ispercent"),
 
	SettingEntry("vehicle.servint_trains"),
 
	SettingEntry("vehicle.servint_roadveh"),
 
	SettingEntry("vehicle.servint_ships"),
 
	SettingEntry("vehicle.servint_aircraft"),
 
	SettingEntry("order.no_servicing_if_no_breakdowns"),
 
	SettingEntry("order.serviceathelipad"),
 
};
 
/** Servicing sub-page */
 
static SettingsPage _settings_vehicles_servicing_page = {_settings_vehicles_servicing, lengthof(_settings_vehicles_servicing)};
 

	
 
static SettingEntry _settings_vehicles_trains[] = {
 
	SettingEntry("vehicle.train_acceleration_model"),
 
	SettingEntry("vehicle.mammoth_trains"),
 
	SettingEntry("gui.lost_train_warn"),
 
	SettingEntry("vehicle.wagon_speed_limits"),
 
	SettingEntry("vehicle.disable_elrails"),
 
	SettingEntry("vehicle.freight_trains"),
 
	SettingEntry("gui.stop_location"),
 
};
 
/** Trains sub-page */
 
static SettingsPage _settings_vehicles_trains_page = {_settings_vehicles_trains, lengthof(_settings_vehicles_trains)};
 

	
 
static SettingEntry _settings_vehicles[] = {
 
	SettingEntry(&_settings_vehicles_routing_page, STR_CONFIG_SETTING_VEHICLES_ROUTING),
 
	SettingEntry(&_settings_vehicles_autorenew_page, STR_CONFIG_SETTING_VEHICLES_AUTORENEW),
 
	SettingEntry(&_settings_vehicles_servicing_page, STR_CONFIG_SETTING_VEHICLES_SERVICING),
 
	SettingEntry(&_settings_vehicles_trains_page, STR_CONFIG_SETTING_VEHICLES_TRAINS),
 
	SettingEntry("order.gotodepot"),
 
	SettingEntry("gui.new_nonstop"),
 
	SettingEntry("gui.order_review_system"),
 
	SettingEntry("gui.vehicle_income_warn"),
 
	SettingEntry("vehicle.never_expire_vehicles"),
 
	SettingEntry("vehicle.max_trains"),
 
	SettingEntry("vehicle.max_roadveh"),
 
	SettingEntry("vehicle.max_aircraft"),
 
	SettingEntry("vehicle.max_ships"),
 
	SettingEntry("vehicle.plane_speed"),
 
	SettingEntry("order.timetabling"),
 
	SettingEntry("vehicle.dynamic_engines"),
 
};
 
/** Vehicles sub-page */
 
static SettingsPage _settings_vehicles_page = {_settings_vehicles, lengthof(_settings_vehicles)};
 

	
 
static SettingEntry _settings_main[] = {
 
	SettingEntry(&_settings_ui_page,           STR_CONFIG_SETTING_GUI),
 
	SettingEntry(&_settings_construction_page, STR_CONFIG_SETTING_CONSTRUCTION),
 
	SettingEntry(&_settings_vehicles_page,     STR_CONFIG_SETTING_VEHICLES),
 
	SettingEntry(&_settings_stations_page,     STR_CONFIG_SETTING_STATIONS),
 
	SettingEntry(&_settings_economy_page,      STR_CONFIG_SETTING_ECONOMY),
 
	SettingEntry(&_settings_ai_page,           STR_CONFIG_SETTING_AI),
 
};
 

	
 
/** Main page, holding all advanced settings */
 
static SettingsPage _settings_main_page = {_settings_main, lengthof(_settings_main)};
 

	
 
/** Widget numbers of settings window */
 
enum GameSettingsWidgets {
 
	SETTINGSEL_CLOSEBOX,     ///< Close box at top-left
 
	SETTINGSEL_CAPTION,      ///< Title bar
 
	SETTINGSEL_OPTIONSPANEL, ///< Panel widget containing the option lists
 
	SETTINGSEL_SCROLLBAR,    ///< Scrollbar
 
	SETTINGSEL_RESIZE,       ///< Resize button
 
};
 

	
 
struct GameSettingsWindow : Window {
 
	static const int SETTINGTREE_LEFT_OFFSET   = 5; ///< Position of left edge of setting values
 
	static const int SETTINGTREE_RIGHT_OFFSET  = 5; ///< Position of right edge of setting values
 
	static const int SETTINGTREE_TOP_OFFSET    = 5; ///< Position of top edge of setting values
 
	static const int SETTINGTREE_BOTTOM_OFFSET = 5; ///< Position of bottom edge of setting values
 

	
 
	static GameSettings *settings_ptr;  ///< Pointer to the game settings being displayed and modified
 

	
 
	SettingEntry *valuewindow_entry; ///< If non-NULL, pointer to setting for which a value-entering window has been opened
 
	SettingEntry *clicked_entry; ///< If non-NULL, pointer to a clicked numeric setting (with a depressed left or right button)
 

	
 
	GameSettingsWindow(const WindowDesc *desc) : Window()
 
	{
 
		static bool first_time = true;
 

	
 
		settings_ptr = (_game_mode == GM_MENU) ? &_settings_newgame : &_settings_game;
 

	
 
		/* Build up the dynamic settings-array only once per OpenTTD session */
 
		if (first_time) {
 
			_settings_main_page.Init();
 
			first_time = false;
 
		} else {
 
			_settings_main_page.FoldAll(); // Close all sub-pages
 
		}
 

	
 
		this->valuewindow_entry = NULL; // No setting entry for which a entry window is opened
 
		this->clicked_entry = NULL; // No numeric setting buttons are depressed
 

	
 
		this->InitNested(desc, 0);
 

	
 
		this->vscroll.SetCount(_settings_main_page.Length());
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		if (widget != SETTINGSEL_OPTIONSPANEL) return;
 

	
 
		resize->height = SETTING_HEIGHT = max(11, FONT_HEIGHT_NORMAL + 1);
 
		resize->width  = 1;
 

	
 
		size->height = 5 * resize->height + SETTINGTREE_TOP_OFFSET + SETTINGTREE_BOTTOM_OFFSET;
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		if (widget != SETTINGSEL_OPTIONSPANEL) return;
 

	
 
		_settings_main_page.Draw(settings_ptr, r.left + SETTINGTREE_LEFT_OFFSET, r.right - SETTINGTREE_RIGHT_OFFSET, r.top + SETTINGTREE_TOP_OFFSET,
 
				this->vscroll.GetPosition(), this->vscroll.GetPosition() + this->vscroll.GetCapacity());
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		if (widget != SETTINGSEL_OPTIONSPANEL) return;
 

	
 
		int y = pt.y - this->GetWidget<NWidgetBase>(widget)->pos_y - SETTINGTREE_TOP_OFFSET;  // Shift y coordinate
 
		if (y < 0) return;  // Clicked above first entry
 

	
 
		byte btn = this->vscroll.GetPosition() + y / this->resize.step_height;  // Compute which setting is selected
 
		if (y % this->resize.step_height > this->resize.step_height - 2) return;  // Clicked too low at the setting
 

	
 
		uint cur_row = 0;
 
		SettingEntry *pe = _settings_main_page.FindEntry(btn, &cur_row);
 

	
 
		if (pe == NULL) return;  // Clicked below the last setting of the page
 

	
 
		int x = (_dynlang.text_dir == TD_RTL ? this->width - pt.x : pt.x) - SETTINGTREE_LEFT_OFFSET - (pe->level + 1) * LEVEL_WIDTH;  // Shift x coordinate
 
		if (x < 0) return;  // Clicked left of the entry
 

	
 
		if ((pe->flags & SEF_KIND_MASK) == SEF_SUBTREE_KIND) {
 
			pe->d.sub.folded = !pe->d.sub.folded; // Flip 'folded'-ness of the sub-page
 

	
 
			this->vscroll.SetCount(_settings_main_page.Length());
 
			this->SetDirty();
 
			return;
 
		}
 

	
 
		assert((pe->flags & SEF_KIND_MASK) == SEF_SETTING_KIND);
 
		const SettingDesc *sd = pe->d.entry.setting;
 

	
 
		/* return if action is only active in network, or only settable by server */
 
		if (!(sd->save.conv & SLF_NETWORK_NO) && _networking && !_network_server && !(sd->desc.flags & SGF_PER_COMPANY)) return;
 
		if ((sd->desc.flags & SGF_NETWORK_ONLY) && !_networking) return;
 
		if ((sd->desc.flags & SGF_NO_NETWORK) && _networking) return;
 

	
 
		const void *var = ResolveVariableAddress(settings_ptr, sd);
 
		int32 value = (int32)ReadValue(var, sd->save.conv);
 

	
 
		/* clicked on the icon on the left side. Either scroller or bool on/off */
 
		if (x < 21) {
 
			const SettingDescBase *sdb = &sd->desc;
 
			int32 oldvalue = value;
 

	
 
			switch (sdb->cmd) {
 
				case SDT_BOOLX: value ^= 1; break;
 
				case SDT_ONEOFMANY:
 
				case SDT_NUMX: {
 
					/* Add a dynamic step-size to the scroller. In a maximum of
 
					 * 50-steps you should be able to get from min to max,
 
					 * unless specified otherwise in the 'interval' variable
 
					 * of the current setting. */
 
					uint32 step = (sdb->interval == 0) ? ((sdb->max - sdb->min) / 50) : sdb->interval;
 
					if (step == 0) step = 1;
 

	
 
					/* don't allow too fast scrolling */
 
					if ((this->flags4 & WF_TIMEOUT_MASK) > WF_TIMEOUT_TRIGGER) {
 
						_left_button_clicked = false;
 
						return;
 
					}
 

	
 
					/* Increase or decrease the value and clamp it to extremes */
 
					if (x >= 10) {
 
						value += step;
 
						if (sdb->min < 0) {
 
							assert((int32)sdb->max >= 0);
 
							if (value > (int32)sdb->max) value = (int32)sdb->max;
 
						} else {
 
							if ((uint32)value > sdb->max) value = (int32)sdb->max;
 
						}
 
						if (value < sdb->min) value = sdb->min; // skip between "disabled" and minimum
 
					} else {
 
						value -= step;
 
						if (value < sdb->min) value = (sdb->flags & SGF_0ISDISABLED) ? 0 : sdb->min;
 
					}
 

	
 
					/* Set up scroller timeout for numeric values */
 
					if (value != oldvalue && !(sd->desc.flags & SGF_MULTISTRING)) {
 
						if (this->clicked_entry != NULL) { // Release previous buttons if any
 
							this->clicked_entry->SetButtons(0);
 
						}
 
						this->clicked_entry = pe;
 
						this->clicked_entry->SetButtons((x >= 10) != (_dynlang.text_dir == TD_RTL) ? SEF_RIGHT_DEPRESSED : SEF_LEFT_DEPRESSED);
 
						this->flags4 |= WF_TIMEOUT_BEGIN;
 
						_left_button_clicked = false;
 
					}
 
				} break;
 

	
 
				default: NOT_REACHED();
 
			}
 

	
 
			if (value != oldvalue) {
 
				if ((sd->desc.flags & SGF_PER_COMPANY) != 0) {
 
					SetCompanySetting(pe->d.entry.index, value);
 
				} else {
 
					SetSettingValue(pe->d.entry.index, value);
 
				}
 
				this->SetDirty();
 
			}
 
		} else {
 
			/* only open editbox for types that its sensible for */
 
			if (sd->desc.cmd != SDT_BOOLX && !(sd->desc.flags & SGF_MULTISTRING)) {
 
				/* Show the correct currency-translated value */
 
				if (sd->desc.flags & SGF_CURRENCY) value *= _currency->rate;
 

	
 
				this->valuewindow_entry = pe;
 
				SetDParam(0, value);
 
				ShowQueryString(STR_JUST_INT, STR_CONFIG_SETTING_QUERY_CAPTION, 10, 100, this, CS_NUMERAL, QSF_NONE);
 
			}
 
		}
 
	}
 

	
 
	virtual void OnTimeout()
 
	{
 
		if (this->clicked_entry != NULL) { // On timeout, release any depressed buttons
 
			this->clicked_entry->SetButtons(0);
 
			this->clicked_entry = NULL;
 
			this->SetDirty();
 
		}
 
	}
 

	
 
	virtual void OnQueryTextFinished(char *str)
 
	{
 
		if (!StrEmpty(str)) {
 
			assert(this->valuewindow_entry != NULL);
 
			assert((this->valuewindow_entry->flags & SEF_KIND_MASK) == SEF_SETTING_KIND);
 
			const SettingDesc *sd = this->valuewindow_entry->d.entry.setting;
 
			int32 value = atoi(str);
 

	
 
			/* Save the correct currency-translated value */
 
			if (sd->desc.flags & SGF_CURRENCY) value /= _currency->rate;
 

	
 
			if ((sd->desc.flags & SGF_PER_COMPANY) != 0) {
 
				SetCompanySetting(this->valuewindow_entry->d.entry.index, value);
 
			} else {
 
				SetSettingValue(this->valuewindow_entry->d.entry.index, value);
 
			}
 
			this->SetDirty();
 
		}
 
	}
 

	
 
	virtual void OnResize()
 
	{
 
		this->vscroll.SetCapacity((this->GetWidget<NWidgetBase>(SETTINGSEL_OPTIONSPANEL)->current_y - SETTINGTREE_TOP_OFFSET - SETTINGTREE_BOTTOM_OFFSET) / this->resize.step_height);
 
	}
 
};
 

	
 
GameSettings *GameSettingsWindow::settings_ptr = NULL;
 

	
 
static const NWidgetPart _nested_settings_selection_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_MAUVE, SETTINGSEL_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_MAUVE, SETTINGSEL_CAPTION), SetDataTip(STR_CONFIG_SETTING_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_PANEL, COLOUR_MAUVE, SETTINGSEL_OPTIONSPANEL), SetMinimalSize(400, 174), EndContainer(),
 
		NWidget(NWID_VERTICAL),
 
			NWidget(WWT_SCROLLBAR, COLOUR_MAUVE, SETTINGSEL_SCROLLBAR),
 
			NWidget(WWT_RESIZEBOX, COLOUR_MAUVE, SETTINGSEL_RESIZE),
 
		EndContainer(),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _settings_selection_desc(
 
	WDP_CENTER, WDP_CENTER, 450, 397,
 
	WC_GAME_OPTIONS, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_RESIZABLE,
 
	_nested_settings_selection_widgets, lengthof(_nested_settings_selection_widgets)
 
);
 

	
 
void ShowGameSettings()
 
{
 
	DeleteWindowById(WC_GAME_OPTIONS, 0);
 
	new GameSettingsWindow(&_settings_selection_desc);
 
}
 

	
 

	
 
/**
 
 * Draw [<][>] boxes.
 
 * @param x the x position to draw
 
 * @param y the y position to draw
 
 * @param button_colour the colour of the button
 
 * @param state 0 = none clicked, 1 = first clicked, 2 = second clicked
 
 * @param clickable_left is the left button clickable?
 
 * @param clickable_right is the right button clickable?
 
 */
 
void DrawArrowButtons(int x, int y, Colours button_colour, byte state, bool clickable_left, bool clickable_right)
 
{
 
	int colour = _colour_gradient[button_colour][2];
 

	
 
	DrawFrameRect(x,      y + 1, x +  9, y + 9, button_colour, (state == 1) ? FR_LOWERED : FR_NONE);
 
	DrawFrameRect(x + 10, y + 1, x + 19, y + 9, button_colour, (state == 2) ? FR_LOWERED : FR_NONE);
 
	DrawSprite(SPR_ARROW_LEFT, PAL_NONE, x + WD_IMGBTN_LEFT, y + WD_IMGBTN_TOP);
 
	DrawSprite(SPR_ARROW_RIGHT, PAL_NONE, x + WD_IMGBTN_LEFT + 10, y + WD_IMGBTN_TOP);
 

	
 
	/* Grey out the buttons that aren't clickable */
 
	bool rtl = _dynlang.text_dir == TD_RTL;
 
	if (rtl ? !clickable_right : !clickable_left) {
 
		GfxFillRect(x +  1, y + 1, x +  1 + 8, y + 8, colour, FILLRECT_CHECKER);
 
	}
 
	if (rtl ? !clickable_left : !clickable_right) {
 
		GfxFillRect(x + 11, y + 1, x + 11 + 8, y + 8, colour, FILLRECT_CHECKER);
 
	}
 
}
 

	
 
/** Widget numbers of the custom currency window. */
 
enum CustomCurrencyWidgets {
 
	CUSTCURR_CLOSEBOX,
 
	CUSTCURR_CAPTION,
 
	CUSTCURR_BACKGROUND,
 

	
 
	CUSTCURR_RATE_DOWN,
 
	CUSTCURR_RATE_UP,
 
	CUSTCURR_RATE,
 
	CUSTCURR_SEPARATOR_EDIT,
 
	CUSTCURR_SEPARATOR,
 
	CUSTCURR_PREFIX_EDIT,
 
	CUSTCURR_PREFIX,
 
	CUSTCURR_SUFFIX_EDIT,
 
	CUSTCURR_SUFFIX,
 
	CUSTCURR_YEAR_DOWN,
 
	CUSTCURR_YEAR_UP,
 
	CUSTCURR_YEAR,
 
	CUSTCURR_PREVIEW,
 
};
 

	
 
struct CustomCurrencyWindow : Window {
 
	int query_widget;
 

	
 
	CustomCurrencyWindow(const WindowDesc *desc) : Window()
 
	{
 
		this->InitNested(desc);
 

	
 
		SetButtonState();
 
	}
 

	
 
	void SetButtonState()
 
	{
 
		this->SetWidgetDisabledState(CUSTCURR_RATE_DOWN, _custom_currency.rate == 1);
 
		this->SetWidgetDisabledState(CUSTCURR_RATE_UP, _custom_currency.rate == UINT16_MAX);
 
		this->SetWidgetDisabledState(CUSTCURR_YEAR_DOWN, _custom_currency.to_euro == CF_NOEURO);
 
		this->SetWidgetDisabledState(CUSTCURR_YEAR_UP, _custom_currency.to_euro == MAX_YEAR);
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		switch (widget) {
 
			case CUSTCURR_RATE:      SetDParam(0, 1); SetDParam(1, 1);            break;
 
			case CUSTCURR_SEPARATOR: SetDParamStr(0, _custom_currency.separator); break;
 
			case CUSTCURR_PREFIX:    SetDParamStr(0, _custom_currency.prefix);    break;
 
			case CUSTCURR_SUFFIX:    SetDParamStr(0, _custom_currency.suffix);    break;
 
			case CUSTCURR_YEAR:
 
				SetDParam(0, (_custom_currency.to_euro != CF_NOEURO) ? STR_CURRENCY_SWITCH_TO_EURO : STR_CURRENCY_SWITCH_TO_EURO_NEVER);
 
				SetDParam(1, _custom_currency.to_euro);
 
				break;
 

	
 
			case CUSTCURR_PREVIEW:
 
				SetDParam(0, 10000);
 
				break;
 
		}
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		switch (widget) {
 
			/* Set the appropriate width for the edit 'buttons' */
 
			case CUSTCURR_SEPARATOR_EDIT:
 
			case CUSTCURR_PREFIX_EDIT:
 
			case CUSTCURR_SUFFIX_EDIT:
 
				size->width  = this->GetWidget<NWidgetBase>(CUSTCURR_RATE_DOWN)->smallest_x + this->GetWidget<NWidgetBase>(CUSTCURR_RATE_UP)->smallest_x;
 
				break;
 

	
 
			/* Make sure the window is wide enough for the widest exchange rate */
 
			case CUSTCURR_RATE:
 
				SetDParam(0, 1);
 
				SetDParam(1, INT32_MAX);
 
				*size = GetStringBoundingBox(STR_CURRENCY_EXCHANGE_RATE);
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		int line = 0;
 
		int len = 0;
 
		StringID str = 0;
 
		CharSetFilter afilter = CS_ALPHANUMERAL;
 

	
 
		switch (widget) {
 
			case CUSTCURR_RATE_DOWN:
 
				if (_custom_currency.rate > 1) _custom_currency.rate--;
 
				if (_custom_currency.rate == 1) this->DisableWidget(CUSTCURR_RATE_DOWN);
 
				this->EnableWidget(CUSTCURR_RATE_UP);
 
				break;
 

	
 
			case CUSTCURR_RATE_UP:
 
				if (_custom_currency.rate < UINT16_MAX) _custom_currency.rate++;
 
				if (_custom_currency.rate == UINT16_MAX) this->DisableWidget(CUSTCURR_RATE_UP);
 
				this->EnableWidget(CUSTCURR_RATE_DOWN);
 
				break;
 

	
 
			case CUSTCURR_RATE:
 
				SetDParam(0, _custom_currency.rate);
 
				str = STR_JUST_INT;
 
				len = 5;
 
				line = CUSTCURR_RATE;
 
				afilter = CS_NUMERAL;
 
				break;
 

	
 
			case CUSTCURR_SEPARATOR_EDIT:
 
			case CUSTCURR_SEPARATOR:
 
				SetDParamStr(0, _custom_currency.separator);
 
				str = STR_JUST_RAW_STRING;
 
				len = 1;
 
				line = CUSTCURR_SEPARATOR;
 
				break;
 

	
 
			case CUSTCURR_PREFIX_EDIT:
 
			case CUSTCURR_PREFIX:
 
				SetDParamStr(0, _custom_currency.prefix);
 
				str = STR_JUST_RAW_STRING;
 
				len = 12;
 
				line = CUSTCURR_PREFIX;
 
				break;
 

	
 
			case CUSTCURR_SUFFIX_EDIT:
 
			case CUSTCURR_SUFFIX:
 
				SetDParamStr(0, _custom_currency.suffix);
 
				str = STR_JUST_RAW_STRING;
 
				len = 12;
 
				line = CUSTCURR_SUFFIX;
 
				break;
 

	
 
			case CUSTCURR_YEAR_DOWN:
 
				_custom_currency.to_euro = (_custom_currency.to_euro <= 2000) ? CF_NOEURO : _custom_currency.to_euro - 1;
 
				if (_custom_currency.to_euro == CF_NOEURO) this->DisableWidget(CUSTCURR_YEAR_DOWN);
 
				this->EnableWidget(CUSTCURR_YEAR_UP);
 
				break;
 

	
 
			case CUSTCURR_YEAR_UP:
 
				_custom_currency.to_euro = Clamp(_custom_currency.to_euro + 1, 2000, MAX_YEAR);
 
				if (_custom_currency.to_euro == MAX_YEAR) this->DisableWidget(CUSTCURR_YEAR_UP);
 
				this->EnableWidget(CUSTCURR_YEAR_DOWN);
 
				break;
 

	
 
			case CUSTCURR_YEAR:
 
				SetDParam(0, _custom_currency.to_euro);
 
				str = STR_JUST_INT;
 
				len = 7;
 
				line = CUSTCURR_YEAR;
 
				afilter = CS_NUMERAL;
 
				break;
 
		}
 

	
 
		if (len != 0) {
 
			this->query_widget = line;
 
			ShowQueryString(str, STR_CURRENCY_CHANGE_PARAMETER, len + 1, 250, this, afilter, QSF_NONE);
 
		}
 

	
 
		this->flags4 |= WF_TIMEOUT_BEGIN;
 
		this->SetDirty();
 
	}
 

	
 
	virtual void OnQueryTextFinished(char *str)
 
	{
 
		if (str == NULL) return;
 

	
 
		switch (this->query_widget) {
 
			case CUSTCURR_RATE:
 
				_custom_currency.rate = Clamp(atoi(str), 1, UINT16_MAX);
 
				break;
 

	
 
			case CUSTCURR_SEPARATOR: // Thousands seperator
 
				strecpy(_custom_currency.separator, str, lastof(_custom_currency.separator));
 
				break;
 

	
 
			case CUSTCURR_PREFIX:
 
				strecpy(_custom_currency.prefix, str, lastof(_custom_currency.prefix));
 
				break;
 

	
 
			case CUSTCURR_SUFFIX:
 
				strecpy(_custom_currency.suffix, str, lastof(_custom_currency.suffix));
 
				break;
 

	
 
			case CUSTCURR_YEAR: { // Year to switch to euro
 
				int val = atoi(str);
 

	
 
				_custom_currency.to_euro = (val < 2000 ? CF_NOEURO : min(val, MAX_YEAR));
 
				break;
 
			}
 
		}
 
		MarkWholeScreenDirty();
 
		SetButtonState();
 
	}
 

	
 
	virtual void OnTimeout()
 
	{
 
		this->SetDirty();
 
	}
 
};
 

	
 
static const NWidgetPart _nested_cust_currency_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, CUSTCURR_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, CUSTCURR_CAPTION), SetDataTip(STR_CURRENCY_WINDOW, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, CUSTCURR_BACKGROUND),
 
		NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(7, 3, 0),
 
			NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
 
				NWidget(NWID_BUTTON_ARROW, COLOUR_YELLOW, CUSTCURR_RATE_DOWN), SetDataTip(AWV_DECREASE, STR_CURRENCY_DECREASE_EXCHANGE_RATE_TOOLTIP),
 
				NWidget(NWID_BUTTON_ARROW, COLOUR_YELLOW, CUSTCURR_RATE_UP), SetDataTip(AWV_INCREASE, STR_CURRENCY_INCREASE_EXCHANGE_RATE_TOOLTIP),
 
				NWidget(NWID_SPACER), SetMinimalSize(5, 0),
 
				NWidget(WWT_TEXT, COLOUR_BLUE, CUSTCURR_RATE), SetDataTip(STR_CURRENCY_EXCHANGE_RATE, STR_CURRENCY_SET_EXCHANGE_RATE_TOOLTIP), SetFill(true, false),
 
				NWidget(WWT_TEXT, COLOUR_BLUE, CUSTCURR_RATE), SetDataTip(STR_CURRENCY_EXCHANGE_RATE, STR_CURRENCY_SET_EXCHANGE_RATE_TOOLTIP), SetFill(1, 0),
 
			EndContainer(),
 
			NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
 
				NWidget(WWT_PUSHBTN, COLOUR_DARK_BLUE, CUSTCURR_SEPARATOR_EDIT), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_SEPARATOR_TOOLTIP), SetFill(false, true),
 
				NWidget(WWT_PUSHBTN, COLOUR_DARK_BLUE, CUSTCURR_SEPARATOR_EDIT), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_SEPARATOR_TOOLTIP), SetFill(0, 1),
 
				NWidget(NWID_SPACER), SetMinimalSize(5, 0),
 
				NWidget(WWT_TEXT, COLOUR_BLUE, CUSTCURR_SEPARATOR), SetDataTip(STR_CURRENCY_SEPARATOR, STR_CURRENCY_SET_CUSTOM_CURRENCY_SEPARATOR_TOOLTIP), SetFill(true, false),
 
				NWidget(WWT_TEXT, COLOUR_BLUE, CUSTCURR_SEPARATOR), SetDataTip(STR_CURRENCY_SEPARATOR, STR_CURRENCY_SET_CUSTOM_CURRENCY_SEPARATOR_TOOLTIP), SetFill(1, 0),
 
			EndContainer(),
 
			NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
 
				NWidget(WWT_PUSHBTN, COLOUR_DARK_BLUE, CUSTCURR_PREFIX_EDIT), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_PREFIX_TOOLTIP), SetFill(false, true),
 
				NWidget(WWT_PUSHBTN, COLOUR_DARK_BLUE, CUSTCURR_PREFIX_EDIT), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_PREFIX_TOOLTIP), SetFill(0, 1),
 
				NWidget(NWID_SPACER), SetMinimalSize(5, 0),
 
				NWidget(WWT_TEXT, COLOUR_BLUE, CUSTCURR_PREFIX), SetDataTip(STR_CURRENCY_PREFIX, STR_CURRENCY_SET_CUSTOM_CURRENCY_PREFIX_TOOLTIP), SetFill(true, false),
 
				NWidget(WWT_TEXT, COLOUR_BLUE, CUSTCURR_PREFIX), SetDataTip(STR_CURRENCY_PREFIX, STR_CURRENCY_SET_CUSTOM_CURRENCY_PREFIX_TOOLTIP), SetFill(1, 0),
 
			EndContainer(),
 
			NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
 
				NWidget(WWT_PUSHBTN, COLOUR_DARK_BLUE, CUSTCURR_SUFFIX_EDIT), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_SUFFIX_TOOLTIP), SetFill(false, true),
 
				NWidget(WWT_PUSHBTN, COLOUR_DARK_BLUE, CUSTCURR_SUFFIX_EDIT), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_SUFFIX_TOOLTIP), SetFill(0, 1),
 
				NWidget(NWID_SPACER), SetMinimalSize(5, 0),
 
				NWidget(WWT_TEXT, COLOUR_BLUE, CUSTCURR_SUFFIX), SetDataTip(STR_CURRENCY_SUFFIX, STR_CURRENCY_SET_CUSTOM_CURRENCY_SUFFIX_TOOLTIP), SetFill(true, false),
 
				NWidget(WWT_TEXT, COLOUR_BLUE, CUSTCURR_SUFFIX), SetDataTip(STR_CURRENCY_SUFFIX, STR_CURRENCY_SET_CUSTOM_CURRENCY_SUFFIX_TOOLTIP), SetFill(1, 0),
 
			EndContainer(),
 
			NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
 
				NWidget(NWID_BUTTON_ARROW, COLOUR_YELLOW, CUSTCURR_YEAR_DOWN), SetDataTip(AWV_DECREASE, STR_CURRENCY_DECREASE_CUSTOM_CURRENCY_TO_EURO_TOOLTIP),
 
				NWidget(NWID_BUTTON_ARROW, COLOUR_YELLOW, CUSTCURR_YEAR_UP), SetDataTip(AWV_INCREASE, STR_CURRENCY_INCREASE_CUSTOM_CURRENCY_TO_EURO_TOOLTIP),
 
				NWidget(NWID_SPACER), SetMinimalSize(5, 0),
 
				NWidget(WWT_TEXT, COLOUR_BLUE, CUSTCURR_YEAR), SetDataTip(STR_JUST_STRING, STR_CURRENCY_SET_CUSTOM_CURRENCY_TO_EURO_TOOLTIP), SetFill(true, false),
 
				NWidget(WWT_TEXT, COLOUR_BLUE, CUSTCURR_YEAR), SetDataTip(STR_JUST_STRING, STR_CURRENCY_SET_CUSTOM_CURRENCY_TO_EURO_TOOLTIP), SetFill(1, 0),
 
			EndContainer(),
 
		EndContainer(),
 
		NWidget(WWT_LABEL, COLOUR_BLUE, CUSTCURR_PREVIEW),
 
								SetDataTip(STR_CURRENCY_PREVIEW, STR_CURRENCY_CUSTOM_CURRENCY_PREVIEW_TOOLTIP), SetPadding(15, 1, 18, 2),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _cust_currency_desc(
 
	WDP_CENTER, WDP_CENTER, 230, 120,
 
	WC_CUSTOM_CURRENCY, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
 
	_nested_cust_currency_widgets, lengthof(_nested_cust_currency_widgets)
 
);
 

	
 
static void ShowCustCurrency()
 
{
 
	DeleteWindowById(WC_CUSTOM_CURRENCY, 0);
 
	new CustomCurrencyWindow(&_cust_currency_desc);
 
}
src/signs_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file signs_gui.cpp The GUI for signs. */
 

	
 
#include "stdafx.h"
 
#include "company_gui.h"
 
#include "company_func.h"
 
#include "signs_base.h"
 
#include "signs_func.h"
 
#include "debug.h"
 
#include "command_func.h"
 
#include "strings_func.h"
 
#include "window_func.h"
 
#include "map_func.h"
 
#include "gfx_func.h"
 
#include "viewport_func.h"
 
#include "querystring_gui.h"
 
#include "sortlist_type.h"
 
#include "string_func.h"
 

	
 
#include "table/strings.h"
 
#include "table/sprites.h"
 

	
 
struct SignList {
 
	typedef GUIList<const Sign *> GUISignList;
 

	
 
	static const Sign *last_sign;
 
	GUISignList signs;
 

	
 
	void BuildSignsList()
 
	{
 
		if (!this->signs.NeedRebuild()) return;
 

	
 
		DEBUG(misc, 3, "Building sign list");
 

	
 
		this->signs.Clear();
 

	
 
		const Sign *si;
 
		FOR_ALL_SIGNS(si) *this->signs.Append() = si;
 

	
 
		this->signs.Compact();
 
		this->signs.RebuildDone();
 
	}
 

	
 
	/** Sort signs by their name */
 
	static int CDECL SignNameSorter(const Sign * const *a, const Sign * const *b)
 
	{
 
		static char buf_cache[64];
 
		char buf[64];
 

	
 
		SetDParam(0, (*a)->index);
 
		GetString(buf, STR_SIGN_NAME, lastof(buf));
 

	
 
		if (*b != last_sign) {
 
			last_sign = *b;
 
			SetDParam(0, (*b)->index);
 
			GetString(buf_cache, STR_SIGN_NAME, lastof(buf_cache));
 
		}
 

	
 
		return strcasecmp(buf, buf_cache);
 
	}
 

	
 
	void SortSignsList()
 
	{
 
		if (!this->signs.Sort(&SignNameSorter)) return;
 

	
 
		/* Reset the name sorter sort cache */
 
		this->last_sign = NULL;
 
	}
 
};
 

	
 
const Sign *SignList::last_sign = NULL;
 

	
 
/** Enum referring to the widgets of the sign list window */
 
enum SignListWidgets {
 
	SLW_CLOSEBOX = 0,
 
	SLW_CAPTION,
 
	SLW_STICKY,
 
	SLW_LIST,
 
	SLW_SCROLLBAR,
 
	SLW_RESIZE,
 
};
 

	
 
struct SignListWindow : Window, SignList {
 
	int text_offset; // Offset of the sign text relative to the left edge of the SLW_LIST widget.
 

	
 
	SignListWindow(const WindowDesc *desc, WindowNumber window_number) : Window()
 
	{
 
		this->InitNested(desc, window_number);
 

	
 
		/* Create initial list. */
 
		this->signs.ForceRebuild();
 
		this->signs.ForceResort();
 
		this->BuildSignsList();
 
		this->SortSignsList();
 
		this->vscroll.SetCount(this->signs.Length());
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case SLW_LIST: {
 
				uint y = r.top + WD_FRAMERECT_TOP; // Offset from top of widget.
 
				/* No signs? */
 
				if (this->vscroll.GetCount() == 0) {
 
					DrawString(r.left + WD_FRAMETEXT_LEFT, r.right, y, STR_STATION_LIST_NONE);
 
					return;
 
				}
 

	
 
				bool rtl = _dynlang.text_dir == TD_RTL;
 
				int sprite_offset_y = (FONT_HEIGHT_NORMAL - 10) / 2 + 1;
 
				uint icon_left  = 4 + (rtl ? r.right - this->text_offset : r.left);
 
				uint text_left  = r.left + (rtl ? WD_FRAMERECT_LEFT : this->text_offset);
 
				uint text_right = r.right - (rtl ? this->text_offset : WD_FRAMERECT_RIGHT);
 

	
 
				/* At least one sign available. */
 
				for (uint16 i = this->vscroll.GetPosition(); this->vscroll.IsVisible(i) && i < this->vscroll.GetCount(); i++) {
 
					const Sign *si = this->signs[i];
 

	
 
					if (si->owner != OWNER_NONE) DrawCompanyIcon(si->owner, icon_left, y + sprite_offset_y);
 

	
 
					SetDParam(0, si->index);
 
					DrawString(text_left, text_right, y, STR_SIGN_NAME, TC_YELLOW);
 
					y += this->resize.step_height;
 
				}
 
				break;
 
			}
 
		}
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		if (widget == SLW_CAPTION) SetDParam(0, this->vscroll.GetCount());
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		if (widget == SLW_LIST) {
 
			uint id_v = (pt.y - this->GetWidget<NWidgetBase>(SLW_LIST)->pos_y - WD_FRAMERECT_TOP) / this->resize.step_height;
 

	
 
			if (id_v >= this->vscroll.GetCapacity()) return;
 
			id_v += this->vscroll.GetPosition();
 
			if (id_v >= this->vscroll.GetCount()) return;
 

	
 
			const Sign *si = this->signs[id_v];
 
			ScrollMainWindowToTile(TileVirtXY(si->x, si->y));
 
		}
 
	}
 

	
 
	virtual void OnResize()
 
	{
 
		this->vscroll.SetCapacity((this->GetWidget<NWidgetBase>(SLW_LIST)->current_y - WD_FRAMERECT_TOP - WD_FRAMERECT_BOTTOM) / this->resize.step_height);
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		switch (widget) {
 
			case SLW_LIST: {
 
				Dimension spr_dim = GetSpriteSize(SPR_COMPANY_ICON);
 
				this->text_offset = WD_FRAMETEXT_LEFT + spr_dim.width + 2; // 2 pixels space between icon and the sign text.
 
				resize->height = max<uint>(FONT_HEIGHT_NORMAL, GetSpriteSize(SPR_COMPANY_ICON).height);
 
				Dimension d = {this->text_offset + MAX_LENGTH_SIGN_NAME_PIXELS + WD_FRAMETEXT_RIGHT, WD_FRAMERECT_TOP + 5 * resize->height + WD_FRAMERECT_BOTTOM};
 
				*size = maxdim(*size, d);
 
			} break;
 

	
 
			case SLW_CAPTION:
 
				SetDParam(0, max<uint>(1000, Sign::GetPoolSize()));
 
				*size = GetStringBoundingBox(STR_SIGN_LIST_CAPTION);
 
				size->height += padding.height;
 
				size->width  += padding.width;
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnInvalidateData(int data)
 
	{
 
		if (data == 0) { // New or deleted sign.
 
			this->signs.ForceRebuild();
 
			this->BuildSignsList();
 
			this->SetWidgetDirty(SLW_CAPTION);
 
			this->vscroll.SetCount(this->signs.Length());
 
		} else { // Change of sign contents.
 
			this->signs.ForceResort();
 
		}
 

	
 
		this->SortSignsList();
 
	}
 
};
 

	
 
static const NWidgetPart _nested_sign_list_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, SLW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, SLW_CAPTION), SetDataTip(STR_SIGN_LIST_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_STICKYBOX, COLOUR_GREY, SLW_STICKY),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_PANEL, COLOUR_GREY, SLW_LIST), SetMinimalSize(WD_FRAMETEXT_LEFT + 16 + MAX_LENGTH_SIGN_NAME_PIXELS + WD_FRAMETEXT_RIGHT, 50),
 
							SetResize(1, 10), SetFill(true, false), EndContainer(),
 
							SetResize(1, 10), SetFill(1, 0), EndContainer(),
 
		NWidget(NWID_VERTICAL),
 
			NWidget(WWT_SCROLLBAR, COLOUR_GREY, SLW_SCROLLBAR),
 
			NWidget(WWT_RESIZEBOX, COLOUR_GREY, SLW_RESIZE),
 
		EndContainer(),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _sign_list_desc(
 
	WDP_AUTO, WDP_AUTO, 358, 138,
 
	WC_SIGN_LIST, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_STICKY_BUTTON | WDF_RESIZABLE,
 
	_nested_sign_list_widgets, lengthof(_nested_sign_list_widgets)
 
);
 

	
 

	
 
void ShowSignList()
 
{
 
	AllocateWindowDescFront<SignListWindow>(&_sign_list_desc, 0);
 
}
 

	
 
/**
 
 * Actually rename the sign.
 
 * @param index the sign to rename.
 
 * @param text  the new name.
 
 * @return true if the window will already be removed after returning.
 
 */
 
static bool RenameSign(SignID index, const char *text)
 
{
 
	bool remove = StrEmpty(text);
 
	DoCommandP(0, index, 0, CMD_RENAME_SIGN | (StrEmpty(text) ? CMD_MSG(STR_ERROR_CAN_T_DELETE_SIGN) : CMD_MSG(STR_ERROR_CAN_T_CHANGE_SIGN_NAME)), NULL, text);
 
	return remove;
 
}
 

	
 
/** Widget numbers of the query sign edit window. */
 
enum QueryEditSignWidgets {
 
	QUERY_EDIT_SIGN_WIDGET_CLOSEBOX,
 
	QUERY_EDIT_SIGN_WIDGET_CAPTION,
 
	QUERY_EDIT_SIGN_WIDGET_PANEL,
 
	QUERY_EDIT_SIGN_WIDGET_TEXT,
 
	QUERY_EDIT_SIGN_WIDGET_OK,
 
	QUERY_EDIT_SIGN_WIDGET_CANCEL,
 
	QUERY_EDIT_SIGN_WIDGET_DELETE,
 
	QUERY_EDIT_SIGN_WIDGET_FILL,
 
	QUERY_EDIT_SIGN_WIDGET_PREVIOUS,
 
	QUERY_EDIT_SIGN_WIDGET_NEXT,
 
};
 

	
 
struct SignWindow : QueryStringBaseWindow, SignList {
 
	SignID cur_sign;
 

	
 
	SignWindow(const WindowDesc *desc, const Sign *si) : QueryStringBaseWindow(MAX_LENGTH_SIGN_NAME_BYTES)
 
	{
 
		this->caption = STR_EDIT_SIGN_CAPTION;
 
		this->afilter = CS_ALPHANUMERAL;
 

	
 
		this->InitNested(desc);
 

	
 
		this->LowerWidget(QUERY_EDIT_SIGN_WIDGET_TEXT);
 
		UpdateSignEditWindow(si);
 
		this->SetFocusedWidget(QUERY_EDIT_SIGN_WIDGET_TEXT);
 
	}
 

	
 
	void UpdateSignEditWindow(const Sign *si)
 
	{
 
		char *last_of = &this->edit_str_buf[this->edit_str_size - 1]; // points to terminating '\0'
 

	
 
		/* 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, last_of);
 
		} else {
 
			GetString(this->edit_str_buf, STR_EMPTY, last_of);
 
		}
 
		*last_of = '\0';
 

	
 
		this->cur_sign = si->index;
 
		InitializeTextBuffer(&this->text, this->edit_str_buf, this->edit_str_size, MAX_LENGTH_SIGN_NAME_PIXELS);
 

	
 
		this->SetWidgetDirty(QUERY_EDIT_SIGN_WIDGET_TEXT);
 
		this->SetFocusedWidget(QUERY_EDIT_SIGN_WIDGET_TEXT);
 
	}
 

	
 
	/**
 
	 * Returns a pointer to the (alphabetically) previous or next sign of the current sign.
 
	 * @param next false if the previous sign is wanted, true if the next sign is wanted
 
	 * @return pointer to the previous/next sign
 
	 */
 
	const Sign *PrevNextSign(bool next)
 
	{
 
		/* Rebuild the sign list */
 
		this->signs.ForceRebuild();
 
		this->signs.NeedResort();
 
		this->BuildSignsList();
 
		this->SortSignsList();
 

	
 
		/* Search through the list for the current sign, excluding
 
		 * - the first sign if we want the previous sign or
 
		 * - the last sign if we want the next sign */
 
		uint end = this->signs.Length() - (next ? 1 : 0);
 
		for (uint i = next ? 0 : 1; i < end; i++) {
 
			if (this->cur_sign == this->signs[i]->index) {
 
				/* We've found the current sign, so return the sign before/after it */
 
				return this->signs[i + (next ? 1 : -1)];
 
			}
 
		}
 
		/* If we haven't found the current sign by now, return the last/first sign */
 
		return this->signs[next ? 0 : this->signs.Length() - 1];
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		switch (widget) {
 
			case QUERY_EDIT_SIGN_WIDGET_CAPTION:
 
				SetDParam(0, this->caption);
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
		this->DrawEditBox(QUERY_EDIT_SIGN_WIDGET_TEXT);
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case QUERY_EDIT_SIGN_WIDGET_PREVIOUS:
 
			case QUERY_EDIT_SIGN_WIDGET_NEXT: {
 
				const Sign *si = this->PrevNextSign(widget == QUERY_EDIT_SIGN_WIDGET_NEXT);
 

	
 
				/* Rebuild the sign list */
 
				this->signs.ForceRebuild();
 
				this->signs.NeedResort();
 
				this->BuildSignsList();
 
				this->SortSignsList();
 

	
 
				/* Scroll to sign and reopen window */
 
				ScrollMainWindowToTile(TileVirtXY(si->x, si->y));
 
				UpdateSignEditWindow(si);
 
				break;
 
			}
 

	
 
			case QUERY_EDIT_SIGN_WIDGET_DELETE:
 
				/* Only need to set the buffer to null, the rest is handled as the OK button */
 
				RenameSign(this->cur_sign, "");
 
				/* don't delete this, we are deleted in Sign::~Sign() -> DeleteRenameSignWindow() */
 
				break;
 

	
 
			case QUERY_EDIT_SIGN_WIDGET_OK:
 
				if (RenameSign(this->cur_sign, this->text.buf)) break;
 
				/* FALL THROUGH */
 

	
 
			case QUERY_EDIT_SIGN_WIDGET_CANCEL:
 
				delete this;
 
				break;
 
		}
 
	}
 

	
 
	virtual EventState OnKeyPress(uint16 key, uint16 keycode)
 
	{
 
		EventState state = ES_NOT_HANDLED;
 
		switch (this->HandleEditBoxKey(QUERY_EDIT_SIGN_WIDGET_TEXT, key, keycode, state)) {
 
			default: break;
 

	
 
			case HEBR_CONFIRM:
 
				if (RenameSign(this->cur_sign, this->text.buf)) break;
 
				/* FALL THROUGH */
 

	
 
			case HEBR_CANCEL: // close window, abandon changes
 
				delete this;
 
				break;
 
		}
 
		return state;
 
	}
 

	
 
	virtual void OnMouseLoop()
 
	{
 
		this->HandleEditBox(QUERY_EDIT_SIGN_WIDGET_TEXT);
 
	}
 

	
 
	virtual void OnOpenOSKWindow(int wid)
 
	{
 
		ShowOnScreenKeyboard(this, wid, QUERY_EDIT_SIGN_WIDGET_CANCEL, QUERY_EDIT_SIGN_WIDGET_OK);
 
	}
 
};
 

	
 
static const NWidgetPart _nested_query_sign_edit_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, QUERY_EDIT_SIGN_WIDGET_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, QUERY_EDIT_SIGN_WIDGET_CAPTION), SetDataTip(STR_WHITE_STRING, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, QUERY_EDIT_SIGN_WIDGET_PANEL),
 
		NWidget(WWT_EDITBOX, COLOUR_GREY, QUERY_EDIT_SIGN_WIDGET_TEXT), SetMinimalSize(256, 12), SetDataTip(STR_EDIT_SIGN_SIGN_OSKTITLE, STR_NULL), SetPadding(2, 2, 2, 2),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, QUERY_EDIT_SIGN_WIDGET_OK), SetMinimalSize(61, 12), SetDataTip(STR_BUTTON_OK, STR_NULL),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, QUERY_EDIT_SIGN_WIDGET_CANCEL), SetMinimalSize(60, 12), SetDataTip(STR_BUTTON_CANCEL, STR_NULL),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, QUERY_EDIT_SIGN_WIDGET_DELETE), SetMinimalSize(60, 12), SetDataTip(STR_TOWN_VIEW_DELETE_BUTTON, STR_NULL),
 
		NWidget(WWT_PANEL, COLOUR_GREY, QUERY_EDIT_SIGN_WIDGET_FILL), SetFill(true, true), EndContainer(),
 
		NWidget(WWT_PANEL, COLOUR_GREY, QUERY_EDIT_SIGN_WIDGET_FILL), SetFill(1, 1), EndContainer(),
 
		NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, QUERY_EDIT_SIGN_WIDGET_PREVIOUS), SetMinimalSize(11, 12), SetDataTip(AWV_DECREASE, STR_EDIT_SIGN_PREVIOUS_SIGN_TOOLTIP),
 
		NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, QUERY_EDIT_SIGN_WIDGET_NEXT), SetMinimalSize(11, 12), SetDataTip(AWV_INCREASE, STR_EDIT_SIGN_NEXT_SIGN_TOOLTIP),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _query_sign_edit_desc(
 
	190, 170, 260, 42,
 
	WC_QUERY_STRING, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_CONSTRUCTION | WDF_UNCLICK_BUTTONS,
 
	_nested_query_sign_edit_widgets, lengthof(_nested_query_sign_edit_widgets)
 
);
 

	
 
void HandleClickOnSign(const Sign *si)
 
{
 
	if (_ctrl_pressed && si->owner == _local_company) {
 
		RenameSign(si->index, NULL);
 
		return;
 
	}
 
	ShowRenameSignWindow(si);
 
}
 

	
 
void ShowRenameSignWindow(const Sign *si)
 
{
 
	/* Delete all other edit windows */
 
	DeleteWindowById(WC_QUERY_STRING, 0);
 

	
 
	new SignWindow(&_query_sign_edit_desc, si);
 
}
 

	
 
void DeleteRenameSignWindow(SignID sign)
 
{
 
	SignWindow *w = dynamic_cast<SignWindow *>(FindWindowById(WC_QUERY_STRING, 0));
 

	
 
	if (w != NULL && w->cur_sign == sign) delete w;
 
}
src/smallmap_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file smallmap_gui.cpp GUI that shows a small map of the world with metadata like owner or height. */
 

	
 
#include "stdafx.h"
 
#include "clear_map.h"
 
#include "industry.h"
 
#include "station_map.h"
 
#include "landscape.h"
 
#include "window_gui.h"
 
#include "tree_map.h"
 
#include "viewport_func.h"
 
#include "gfx_func.h"
 
#include "town.h"
 
#include "blitter/factory.hpp"
 
#include "tunnelbridge_map.h"
 
#include "strings_func.h"
 
#include "core/endian_func.hpp"
 
#include "vehicle_base.h"
 
#include "sound_func.h"
 
#include "window_func.h"
 

	
 
#include "table/strings.h"
 
#include "table/sprites.h"
 

	
 
/** Widget numbers of the small map window. */
 
enum SmallMapWindowWidgets {
 
	SM_WIDGET_CLOSEBOX,
 
	SM_WIDGET_CAPTION,
 
	SM_WIDGET_STICKYBOX,
 
	SM_WIDGET_MAP_BORDER,
 
	SM_WIDGET_MAP,
 
	SM_WIDGET_LEGEND,
 
	SM_WIDGET_BUTTONSPANEL,
 
	SM_WIDGET_CONTOUR,
 
	SM_WIDGET_VEHICLES,
 
	SM_WIDGET_INDUSTRIES,
 
	SM_WIDGET_ROUTES,
 
	SM_WIDGET_VEGETATION,
 
	SM_WIDGET_OWNERS,
 
	SM_WIDGET_CENTERMAP,
 
	SM_WIDGET_TOGGLETOWNNAME,
 
	SM_WIDGET_BOTTOMPANEL,
 
	SM_WIDGET_SELECTINDUSTRIES,
 
	SM_WIDGET_ENABLEINDUSTRIES,
 
	SM_WIDGET_DISABLEINDUSTRIES,
 
	SM_WIDGET_RESIZEBOX,
 
};
 

	
 
static const NWidgetPart _nested_smallmap_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_BROWN, SM_WIDGET_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_BROWN, SM_WIDGET_CAPTION), SetDataTip(STR_SMALLMAP_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_STICKYBOX, COLOUR_BROWN, SM_WIDGET_STICKYBOX),
 
	EndContainer(),
 
	/* Small map display. */
 
	NWidget(WWT_PANEL, COLOUR_BROWN, SM_WIDGET_MAP_BORDER),
 
		NWidget(WWT_INSET, COLOUR_BROWN, SM_WIDGET_MAP), SetMinimalSize(346, 140), SetResize(1, 1), SetPadding(2, 2, 2, 2), EndContainer(),
 
	EndContainer(),
 
	/* Panel. */
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_PANEL, COLOUR_BROWN, SM_WIDGET_LEGEND), SetMinimalSize(262, 44), SetResize(1, 0), EndContainer(),
 
		NWidget(NWID_VERTICAL),
 
			/* Top button row. */
 
			NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
				NWidget(WWT_PUSHIMGBTN, COLOUR_BROWN, SM_WIDGET_CENTERMAP), SetDataTip(SPR_IMG_SMALLMAP, STR_SMALLMAP_CENTER),
 
				NWidget(WWT_IMGBTN, COLOUR_BROWN, SM_WIDGET_CONTOUR), SetDataTip(SPR_IMG_SHOW_COUNTOURS, STR_SMALLMAP_TOOLTIP_SHOW_LAND_CONTOURS_ON_MAP),
 
				NWidget(WWT_IMGBTN, COLOUR_BROWN, SM_WIDGET_VEHICLES), SetDataTip(SPR_IMG_SHOW_VEHICLES, STR_SMALLMAP_TOOLTIP_SHOW_VEHICLES_ON_MAP),
 
				NWidget(WWT_IMGBTN, COLOUR_BROWN, SM_WIDGET_INDUSTRIES), SetDataTip(SPR_IMG_INDUSTRY, STR_SMALLMAP_TOOLTIP_SHOW_INDUSTRIES_ON_MAP),
 
			EndContainer(),
 
			/* Bottom button row. */
 
			NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
				NWidget(WWT_IMGBTN, COLOUR_BROWN, SM_WIDGET_TOGGLETOWNNAME), SetDataTip(SPR_IMG_TOWN, STR_SMALLMAP_TOOLTIP_TOGGLE_TOWN_NAMES_ON_OFF),
 
				NWidget(WWT_IMGBTN, COLOUR_BROWN, SM_WIDGET_ROUTES), SetDataTip(SPR_IMG_SHOW_ROUTES, STR_SMALLMAP_TOOLTIP_SHOW_TRANSPORT_ROUTES_ON),
 
				NWidget(WWT_IMGBTN, COLOUR_BROWN, SM_WIDGET_VEGETATION), SetDataTip(SPR_IMG_PLANTTREES, STR_SMALLMAP_TOOLTIP_SHOW_VEGETATION_ON_MAP),
 
				NWidget(WWT_IMGBTN, COLOUR_BROWN, SM_WIDGET_OWNERS), SetDataTip(SPR_IMG_COMPANY_GENERAL, STR_SMALLMAP_TOOLTIP_SHOW_LAND_OWNERS_ON_MAP),
 
			EndContainer(),
 
			NWidget(WWT_PANEL, COLOUR_BROWN, SM_WIDGET_BUTTONSPANEL), SetFill(true, true), EndContainer(),
 
			NWidget(WWT_PANEL, COLOUR_BROWN, SM_WIDGET_BUTTONSPANEL), SetFill(1, 1), EndContainer(),
 
		EndContainer(),
 
	EndContainer(),
 
	/* Bottom button row and resize box. */
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_PANEL, COLOUR_BROWN, SM_WIDGET_BOTTOMPANEL),
 
			NWidget(NWID_HORIZONTAL),
 
				NWidget(NWID_SELECTION, INVALID_COLOUR, SM_WIDGET_SELECTINDUSTRIES),
 
					NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
						NWidget(WWT_TEXTBTN, COLOUR_BROWN, SM_WIDGET_ENABLEINDUSTRIES), SetMinimalSize(100, 12), SetDataTip(STR_SMALLMAP_ENABLE_ALL, STR_NULL),
 
						NWidget(WWT_TEXTBTN, COLOUR_BROWN, SM_WIDGET_DISABLEINDUSTRIES), SetMinimalSize(100, 12), SetDataTip(STR_SMALLMAP_DISABLE_ALL, STR_NULL),
 
					EndContainer(),
 
					NWidget(NWID_SPACER), SetFill(true, true),
 
					NWidget(NWID_SPACER), SetFill(1, 1),
 
				EndContainer(),
 
				NWidget(NWID_SPACER), SetFill(true, false), SetResize(1, 0),
 
				NWidget(NWID_SPACER), SetFill(1, 0), SetResize(1, 0),
 
			EndContainer(),
 
		EndContainer(),
 
		NWidget(WWT_RESIZEBOX, COLOUR_BROWN, SM_WIDGET_RESIZEBOX),
 
	EndContainer(),
 
};
 

	
 

	
 
static int _smallmap_industry_count; ///< Number of used industries
 

	
 
/** Macro for ordinary entry of LegendAndColour */
 
#define MK(a, b) {a, b, INVALID_INDUSTRYTYPE, true, false, false}
 
/** Macro for end of list marker in arrays of LegendAndColour */
 
#define MKEND() {0, STR_NULL, INVALID_INDUSTRYTYPE, true, true, false}
 
/** Macro for break marker in arrays of LegendAndColour.
 
 * It will have valid data, though */
 
#define MS(a, b) {a, b, INVALID_INDUSTRYTYPE, true, false, true}
 

	
 
/** Structure for holding relevant data for legends in small map */
 
struct LegendAndColour {
 
	uint8 colour;      ///< colour of the item on the map
 
	StringID legend;   ///< string corresponding to the coloured item
 
	IndustryType type; ///< type of industry
 
	bool show_on_map;  ///< for filtering industries, if true is shown on map in colour
 
	bool end;          ///< this is the end of the list
 
	bool col_break;    ///< perform a break and go one column further
 
};
 

	
 
/** Legend text giving the colours to look for on the minimap */
 
static const LegendAndColour _legend_land_contours[] = {
 
	MK(0x5A, STR_SMALLMAP_LEGENDA_100M),
 
	MK(0x5C, STR_SMALLMAP_LEGENDA_200M),
 
	MK(0x5E, STR_SMALLMAP_LEGENDA_300M),
 
	MK(0x1F, STR_SMALLMAP_LEGENDA_400M),
 
	MK(0x27, STR_SMALLMAP_LEGENDA_500M),
 

	
 
	MS(0xD7, STR_SMALLMAP_LEGENDA_ROADS),
 
	MK(0x0A, STR_SMALLMAP_LEGENDA_RAILROADS),
 
	MK(0x98, STR_SMALLMAP_LEGENDA_STATIONS_AIRPORTS_DOCKS),
 
	MK(0xB5, STR_SMALLMAP_LEGENDA_BUILDINGS_INDUSTRIES),
 
	MK(0x0F, STR_SMALLMAP_LEGENDA_VEHICLES),
 
	MKEND()
 
};
 

	
 
static const LegendAndColour _legend_vehicles[] = {
 
	MK(0xB8, STR_SMALLMAP_LEGENDA_TRAINS),
 
	MK(0xBF, STR_SMALLMAP_LEGENDA_ROAD_VEHICLES),
 
	MK(0x98, STR_SMALLMAP_LEGENDA_SHIPS),
 
	MK(0x0F, STR_SMALLMAP_LEGENDA_AIRCRAFT),
 

	
 
	MS(0xD7, STR_SMALLMAP_LEGENDA_TRANSPORT_ROUTES),
 
	MK(0xB5, STR_SMALLMAP_LEGENDA_BUILDINGS_INDUSTRIES),
 
	MKEND()
 
};
 

	
 
static const LegendAndColour _legend_routes[] = {
 
	MK(0xD7, STR_SMALLMAP_LEGENDA_ROADS),
 
	MK(0x0A, STR_SMALLMAP_LEGENDA_RAILROADS),
 
	MK(0xB5, STR_SMALLMAP_LEGENDA_BUILDINGS_INDUSTRIES),
 

	
 
	MS(0x56, STR_SMALLMAP_LEGENDA_RAILROAD_STATION),
 
	MK(0xC2, STR_SMALLMAP_LEGENDA_TRUCK_LOADING_BAY),
 
	MK(0xBF, STR_SMALLMAP_LEGENDA_BUS_STATION),
 
	MK(0xB8, STR_SMALLMAP_LEGENDA_AIRPORT_HELIPORT),
 
	MK(0x98, STR_SMALLMAP_LEGENDA_DOCK),
 
	MKEND()
 
};
 

	
 
static const LegendAndColour _legend_vegetation[] = {
 
	MK(0x52, STR_SMALLMAP_LEGENDA_ROUGH_LAND),
 
	MK(0x54, STR_SMALLMAP_LEGENDA_GRASS_LAND),
 
	MK(0x37, STR_SMALLMAP_LEGENDA_BARE_LAND),
 
	MK(0x25, STR_SMALLMAP_LEGENDA_FIELDS),
 
	MK(0x57, STR_SMALLMAP_LEGENDA_TREES),
 
	MK(0xD0, STR_SMALLMAP_LEGENDA_FOREST),
 

	
 
	MS(0x0A, STR_SMALLMAP_LEGENDA_ROCKS),
 
	MK(0xC2, STR_SMALLMAP_LEGENDA_DESERT),
 
	MK(0x98, STR_SMALLMAP_LEGENDA_SNOW),
 
	MK(0xD7, STR_SMALLMAP_LEGENDA_TRANSPORT_ROUTES),
 
	MK(0xB5, STR_SMALLMAP_LEGENDA_BUILDINGS_INDUSTRIES),
 
	MKEND()
 
};
 

	
 
static const LegendAndColour _legend_land_owners[] = {
 
	MK(0xCA, STR_SMALLMAP_LEGENDA_WATER),
 
	MK(0x54, STR_SMALLMAP_LEGENDA_NO_OWNER),
 
	MK(0xB4, STR_SMALLMAP_LEGENDA_TOWNS),
 
	MK(0x20, STR_SMALLMAP_LEGENDA_INDUSTRIES),
 
	MKEND()
 
};
 
#undef MK
 
#undef MS
 
#undef MKEND
 

	
 
/** Allow room for all industries, plus a terminator entry
 
 * This is required in order to have the indutry slots all filled up */
 
static LegendAndColour _legend_from_industries[NUM_INDUSTRYTYPES + 1];
 
/* For connecting industry type to position in industries list(small map legend) */
 
static uint _industry_to_list_pos[NUM_INDUSTRYTYPES];
 

	
 
/**
 
 * Fills an array for the industries legends.
 
 */
 
void BuildIndustriesLegend()
 
{
 
	uint j = 0;
 

	
 
	/* Add each name */
 
	for (IndustryType i = 0; i < NUM_INDUSTRYTYPES; i++) {
 
		const IndustrySpec *indsp = GetIndustrySpec(i);
 
		if (indsp->enabled) {
 
			_legend_from_industries[j].legend = indsp->name;
 
			_legend_from_industries[j].colour = indsp->map_colour;
 
			_legend_from_industries[j].type = i;
 
			_legend_from_industries[j].show_on_map = true;
 
			_legend_from_industries[j].col_break = false;
 
			_legend_from_industries[j].end = false;
 

	
 
			/* Store widget number for this industry type */
 
			_industry_to_list_pos[i] = j;
 
			j++;
 
		}
 
	}
 
	/* Terminate the list */
 
	_legend_from_industries[j].end = true;
 

	
 
	/* Store number of enabled industries */
 
	_smallmap_industry_count = j;
 
}
 

	
 
static const LegendAndColour * const _legend_table[] = {
 
	_legend_land_contours,
 
	_legend_vehicles,
 
	_legend_from_industries,
 
	_legend_routes,
 
	_legend_vegetation,
 
	_legend_land_owners,
 
};
 

	
 
#define MKCOLOUR(x) TO_LE32X(x)
 

	
 
/**
 
 * Height encodings; MAX_TILE_HEIGHT + 1 levels, from 0 to MAX_TILE_HEIGHT
 
 */
 
static const uint32 _map_height_bits[] = {
 
	MKCOLOUR(0x5A5A5A5A),
 
	MKCOLOUR(0x5A5B5A5B),
 
	MKCOLOUR(0x5B5B5B5B),
 
	MKCOLOUR(0x5B5C5B5C),
 
	MKCOLOUR(0x5C5C5C5C),
 
	MKCOLOUR(0x5C5D5C5D),
 
	MKCOLOUR(0x5D5D5D5D),
 
	MKCOLOUR(0x5D5E5D5E),
 
	MKCOLOUR(0x5E5E5E5E),
 
	MKCOLOUR(0x5E5F5E5F),
 
	MKCOLOUR(0x5F5F5F5F),
 
	MKCOLOUR(0x5F1F5F1F),
 
	MKCOLOUR(0x1F1F1F1F),
 
	MKCOLOUR(0x1F271F27),
 
	MKCOLOUR(0x27272727),
 
	MKCOLOUR(0x27272727),
 
};
 
assert_compile(lengthof(_map_height_bits) == MAX_TILE_HEIGHT + 1);
 

	
 
struct AndOr {
 
	uint32 mor;
 
	uint32 mand;
 
};
 

	
 
static inline uint32 ApplyMask(uint32 colour, const AndOr *mask)
 
{
 
	return (colour & mask->mand) | mask->mor;
 
}
 

	
 

	
 
static const AndOr _smallmap_contours_andor[] = {
 
	{MKCOLOUR(0x00000000), MKCOLOUR(0xFFFFFFFF)},
 
	{MKCOLOUR(0x000A0A00), MKCOLOUR(0xFF0000FF)},
 
	{MKCOLOUR(0x00D7D700), MKCOLOUR(0xFF0000FF)},
 
	{MKCOLOUR(0x00B5B500), MKCOLOUR(0xFF0000FF)},
 
	{MKCOLOUR(0x00000000), MKCOLOUR(0xFFFFFFFF)},
 
	{MKCOLOUR(0x98989898), MKCOLOUR(0x00000000)},
 
	{MKCOLOUR(0xCACACACA), MKCOLOUR(0x00000000)},
 
	{MKCOLOUR(0x00000000), MKCOLOUR(0xFFFFFFFF)},
 
	{MKCOLOUR(0xB5B5B5B5), MKCOLOUR(0x00000000)},
 
	{MKCOLOUR(0x00000000), MKCOLOUR(0xFFFFFFFF)},
 
	{MKCOLOUR(0x00B5B500), MKCOLOUR(0xFF0000FF)},
 
	{MKCOLOUR(0x000A0A00), MKCOLOUR(0xFF0000FF)},
 
};
 

	
 
static const AndOr _smallmap_vehicles_andor[] = {
 
	{MKCOLOUR(0x00000000), MKCOLOUR(0xFFFFFFFF)},
 
	{MKCOLOUR(0x00D7D700), MKCOLOUR(0xFF0000FF)},
 
	{MKCOLOUR(0x00D7D700), MKCOLOUR(0xFF0000FF)},
 
	{MKCOLOUR(0x00B5B500), MKCOLOUR(0xFF0000FF)},
 
	{MKCOLOUR(0x00000000), MKCOLOUR(0xFFFFFFFF)},
 
	{MKCOLOUR(0x00D7D700), MKCOLOUR(0xFF0000FF)},
 
	{MKCOLOUR(0xCACACACA), MKCOLOUR(0x00000000)},
 
	{MKCOLOUR(0x00000000), MKCOLOUR(0xFFFFFFFF)},
 
	{MKCOLOUR(0xB5B5B5B5), MKCOLOUR(0x00000000)},
 
	{MKCOLOUR(0x00000000), MKCOLOUR(0xFFFFFFFF)},
 
	{MKCOLOUR(0x00B5B500), MKCOLOUR(0xFF0000FF)},
 
	{MKCOLOUR(0x00D7D700), MKCOLOUR(0xFF0000FF)},
 
};
 

	
 
static const AndOr _smallmap_vegetation_andor[] = {
 
	{MKCOLOUR(0x00000000), MKCOLOUR(0xFFFFFFFF)},
 
	{MKCOLOUR(0x00D7D700), MKCOLOUR(0xFF0000FF)},
 
	{MKCOLOUR(0x00D7D700), MKCOLOUR(0xFF0000FF)},
 
	{MKCOLOUR(0x00B5B500), MKCOLOUR(0xFF0000FF)},
 
	{MKCOLOUR(0x00575700), MKCOLOUR(0xFF0000FF)},
 
	{MKCOLOUR(0x00D7D700), MKCOLOUR(0xFF0000FF)},
 
	{MKCOLOUR(0xCACACACA), MKCOLOUR(0x00000000)},
 
	{MKCOLOUR(0x00000000), MKCOLOUR(0xFFFFFFFF)},
 
	{MKCOLOUR(0xB5B5B5B5), MKCOLOUR(0x00000000)},
 
	{MKCOLOUR(0x00000000), MKCOLOUR(0xFFFFFFFF)},
 
	{MKCOLOUR(0x00B5B500), MKCOLOUR(0xFF0000FF)},
 
	{MKCOLOUR(0x00D7D700), MKCOLOUR(0xFF0000FF)},
 
};
 

	
 
typedef uint32 GetSmallMapPixels(TileIndex tile); ///< Typedef callthrough function
 

	
 

	
 
static inline TileType GetEffectiveTileType(TileIndex tile)
 
{
 
	TileType t = GetTileType(tile);
 

	
 
	if (t == MP_TUNNELBRIDGE) {
 
		TransportType tt = GetTunnelBridgeTransportType(tile);
 

	
 
		switch (tt) {
 
			case TRANSPORT_RAIL: t = MP_RAILWAY; break;
 
			case TRANSPORT_ROAD: t = MP_ROAD;    break;
 
			default:             t = MP_WATER;   break;
 
		}
 
	}
 
	return t;
 
}
 

	
 
/**
 
 * Return the colour a tile would be displayed with in the small map in mode "Contour".
 
 * @param tile The tile of which we would like to get the colour.
 
 * @return The colour of tile in the small map in mode "Contour"
 
 */
 
static inline uint32 GetSmallMapContoursPixels(TileIndex tile)
 
{
 
	TileType t = GetEffectiveTileType(tile);
 

	
 
	return ApplyMask(_map_height_bits[TileHeight(tile)], &_smallmap_contours_andor[t]);
 
}
 

	
 
/**
 
 * Return the colour a tile would be displayed with in the small map in mode "Vehicles".
 
 *
 
 * @param tile The tile of which we would like to get the colour.
 
 * @return The colour of tile in the small map in mode "Vehicles"
 
 */
 
static inline uint32 GetSmallMapVehiclesPixels(TileIndex tile)
 
{
 
	TileType t = GetEffectiveTileType(tile);
 

	
 
	return ApplyMask(MKCOLOUR(0x54545454), &_smallmap_vehicles_andor[t]);
 
}
 

	
 
/**
 
 * Return the colour a tile would be displayed with in the small map in mode "Industries".
 
 *
 
 * @param tile The tile of which we would like to get the colour.
 
 * @return The colour of tile in the small map in mode "Industries"
 
 */
 
static inline uint32 GetSmallMapIndustriesPixels(TileIndex tile)
 
{
 
	TileType t = GetEffectiveTileType(tile);
 

	
 
	if (t == MP_INDUSTRY) {
 
		/* If industry is allowed to be seen, use its colour on the map */
 
		if (_legend_from_industries[_industry_to_list_pos[Industry::GetByTile(tile)->type]].show_on_map) {
 
			return GetIndustrySpec(Industry::GetByTile(tile)->type)->map_colour * 0x01010101;
 
		} else {
 
			/* Otherwise, return the colour of the clear tiles, which will make it disappear */
 
			return ApplyMask(MKCOLOUR(0x54545454), &_smallmap_vehicles_andor[MP_CLEAR]);
 
		}
 
	}
 

	
 
	return ApplyMask(MKCOLOUR(0x54545454), &_smallmap_vehicles_andor[t]);
 
}
 

	
 
/**
 
 * Return the colour a tile would be displayed with in the small map in mode "Routes".
 
 *
 
 * @param tile The tile of which we would like to get the colour.
 
 * @return The colour of tile  in the small map in mode "Routes"
 
 */
 
static inline uint32 GetSmallMapRoutesPixels(TileIndex tile)
 
{
 
	TileType t = GetEffectiveTileType(tile);
 

	
 
	if (t == MP_STATION) {
 
		switch (GetStationType(tile)) {
 
			case STATION_RAIL:    return MKCOLOUR(0x56565656);
 
			case STATION_AIRPORT: return MKCOLOUR(0xB8B8B8B8);
 
			case STATION_TRUCK:   return MKCOLOUR(0xC2C2C2C2);
 
			case STATION_BUS:     return MKCOLOUR(0xBFBFBFBF);
 
			case STATION_DOCK:    return MKCOLOUR(0x98989898);
 
			default:              return MKCOLOUR(0xFFFFFFFF);
 
		}
 
	}
 

	
 
	/* Ground colour */
 
	return ApplyMask(MKCOLOUR(0x54545454), &_smallmap_contours_andor[t]);
 
}
 

	
 

	
 
static const uint32 _vegetation_clear_bits[] = {
 
	MKCOLOUR(0x54545454), ///< full grass
 
	MKCOLOUR(0x52525252), ///< rough land
 
	MKCOLOUR(0x0A0A0A0A), ///< rocks
 
	MKCOLOUR(0x25252525), ///< fields
 
	MKCOLOUR(0x98989898), ///< snow
 
	MKCOLOUR(0xC2C2C2C2), ///< desert
 
	MKCOLOUR(0x54545454), ///< unused
 
	MKCOLOUR(0x54545454), ///< unused
 
};
 

	
 
static inline uint32 GetSmallMapVegetationPixels(TileIndex tile)
 
{
 
	TileType t = GetEffectiveTileType(tile);
 

	
 
	switch (t) {
 
		case MP_CLEAR:
 
			return (IsClearGround(tile, CLEAR_GRASS) && GetClearDensity(tile) < 3) ? MKCOLOUR(0x37373737) : _vegetation_clear_bits[GetClearGround(tile)];
 

	
 
		case MP_INDUSTRY:
 
			return GetIndustrySpec(Industry::GetByTile(tile)->type)->check_proc == CHECK_FOREST ? MKCOLOUR(0xD0D0D0D0) : MKCOLOUR(0xB5B5B5B5);
 

	
 
		case MP_TREES:
 
			if (GetTreeGround(tile) == TREE_GROUND_SNOW_DESERT) {
 
				return (_settings_game.game_creation.landscape == LT_ARCTIC) ? MKCOLOUR(0x98575798) : MKCOLOUR(0xC25757C2);
 
			}
 
			return MKCOLOUR(0x54575754);
 

	
 
		default:
 
			return ApplyMask(MKCOLOUR(0x54545454), &_smallmap_vehicles_andor[t]);
 
	}
 
}
 

	
 

	
 
static uint32 _owner_colours[OWNER_END + 1];
 

	
 
/**
 
 * Return the colour a tile would be displayed with in the small map in mode "Owner".
 
 *
 
 * @param tile The tile of which we would like to get the colour.
 
 * @return The colour of tile in the small map in mode "Owner"
 
 */
 
static inline uint32 GetSmallMapOwnerPixels(TileIndex tile)
 
{
 
	Owner o;
 

	
 
	switch (GetTileType(tile)) {
 
		case MP_INDUSTRY: o = OWNER_END;          break;
 
		case MP_HOUSE:    o = OWNER_TOWN;         break;
 
		default:          o = GetTileOwner(tile); break;
 
		/* FIXME: For MP_ROAD there are multiple owners.
 
		 * GetTileOwner returns the rail owner (level crossing) resp. the owner of ROADTYPE_ROAD (normal road),
 
		 * even if there are no ROADTYPE_ROAD bits on the tile.
 
		 */
 
	}
 

	
 
	return _owner_colours[o];
 
}
 

	
 

	
 
static const uint32 _smallmap_mask_left[3] = {
 
	MKCOLOUR(0xFF000000),
 
	MKCOLOUR(0xFFFF0000),
 
	MKCOLOUR(0xFFFFFF00),
 
};
 

	
 
static const uint32 _smallmap_mask_right[] = {
 
	MKCOLOUR(0x000000FF),
 
	MKCOLOUR(0x0000FFFF),
 
	MKCOLOUR(0x00FFFFFF),
 
};
 

	
 
/* Each tile has 4 x pixels and 1 y pixel */
 

	
 
static GetSmallMapPixels * const _smallmap_draw_procs[] = {
 
	GetSmallMapContoursPixels,
 
	GetSmallMapVehiclesPixels,
 
	GetSmallMapIndustriesPixels,
 
	GetSmallMapRoutesPixels,
 
	GetSmallMapVegetationPixels,
 
	GetSmallMapOwnerPixels,
 
};
 

	
 
static const byte _vehicle_type_colours[6] = {
 
	184, 191, 152, 15, 215, 184
 
};
 

	
 

	
 
class SmallMapWindow : public Window {
 
	enum SmallMapType {
 
		SMT_CONTOUR,
 
		SMT_VEHICLES,
 
		SMT_INDUSTRY,
 
		SMT_ROUTES,
 
		SMT_VEGETATION,
 
		SMT_OWNER,
 
	};
 

	
 
	static SmallMapType map_type;
 
	static bool show_towns;
 

	
 
	static const uint LEGEND_BLOB_WIDTH = 8;
 
	uint column_width;
 
	uint number_of_rows;
 

	
 
	int32 scroll_x;
 
	int32 scroll_y;
 
	int32 subscroll;
 

	
 
	static const uint8 FORCE_REFRESH_PERIOD = 0x1F; ///< map is redrawn after that many ticks
 
	uint8 refresh; ///< refresh counter, zeroed every FORCE_REFRESH_PERIOD ticks
 

	
 
	/**
 
	 * Remap a map's tile X coordinate (TileX(TileIndex)) to
 
	 * a location on this smallmap.
 
	 * @param tile_x the tile's X coordinate.
 
	 * @return the X coordinate to draw on.
 
	 */
 
	inline int RemapX(int tile_x) const
 
	{
 
		return tile_x - this->scroll_x / TILE_SIZE;
 
	}
 

	
 
	/**
 
	 * Remap a map's tile Y coordinate (TileY(TileIndex)) to
 
	 * a location on this smallmap.
 
	 * @param tile_y the tile's Y coordinate.
 
	 * @return the Y coordinate to draw on.
 
	 */
 
	inline int RemapY(int tile_y) const
 
	{
 
		return tile_y - this->scroll_y / TILE_SIZE;
 
	}
 

	
 
	/**
 
	 * Draws one column of the small map in a certain mode onto the screen buffer. This
 
	 * function looks exactly the same for all types
 
	 *
 
	 * @param dst Pointer to a part of the screen buffer to write to.
 
	 * @param xc The X coordinate of the first tile in the column.
 
	 * @param yc The Y coordinate of the first tile in the column
 
	 * @param pitch Number of pixels to advance in the screen buffer each time a pixel is written.
 
	 * @param reps Number of lines to draw
 
	 * @param mask Some bytes may need to be masked out when at the border of drawn area
 
	 * @param blitter current blitter
 
	 * @param proc Pointer to the colour function
 
	 * @see GetSmallMapPixels(TileIndex)
 
	 */
 
	void DrawSmallMapStuff(void *dst, uint xc, uint yc, int pitch, int reps, uint32 mask, Blitter *blitter, GetSmallMapPixels *proc) const
 
	{
 
		void *dst_ptr_abs_end = blitter->MoveTo(_screen.dst_ptr, 0, _screen.height);
 
		void *dst_ptr_end = blitter->MoveTo(dst_ptr_abs_end, -4, 0);
 

	
 
		do {
 
			/* Check if the tile (xc,yc) is within the map range */
 
			uint min_xy = _settings_game.construction.freeform_edges ? 1 : 0;
 
			if (IsInsideMM(xc, min_xy, MapMaxX()) && IsInsideMM(yc, min_xy, MapMaxY())) {
 
				/* Check if the dst pointer points to a pixel inside the screen buffer */
 
				if (dst < _screen.dst_ptr) continue;
 
				if (dst >= dst_ptr_abs_end) continue;
 

	
 
				uint32 val = proc(TileXY(xc, yc)) & mask;
 
				uint8 *val8 = (uint8 *)&val;
 

	
 
				if (dst <= dst_ptr_end) {
 
					blitter->SetPixelIfEmpty(dst, 0, 0, val8[0]);
 
					blitter->SetPixelIfEmpty(dst, 1, 0, val8[1]);
 
					blitter->SetPixelIfEmpty(dst, 2, 0, val8[2]);
 
					blitter->SetPixelIfEmpty(dst, 3, 0, val8[3]);
 
				} else {
 
					/* It happens that there are only 1, 2 or 3 pixels left to fill, so
 
					 * in that special case, write till the end of the video-buffer */
 
					int i = 0;
 
					do {
 
						blitter->SetPixelIfEmpty(dst, 0, 0, val8[i]);
 
					} while (i++, dst = blitter->MoveTo(dst, 1, 0), dst < dst_ptr_abs_end);
 
				}
 
			}
 
		/* Switch to next tile in the column */
 
		} while (xc++, yc++, dst = blitter->MoveTo(dst, pitch, 0), --reps != 0);
 
	}
 

	
 
	/**
 
	 * Adds vehicles to the smallmap.
 
	 * @param dpi the part of the smallmap to be drawn into
 
	 * @param blitter current blitter
 
	 */
 
	void DrawVehicles(const DrawPixelInfo *dpi, Blitter *blitter) const
 
	{
 
		const Vehicle *v;
 
		FOR_ALL_VEHICLES(v) {
 
			if (v->type == VEH_EFFECT) continue;
 
			if (v->vehstatus & (VS_HIDDEN | VS_UNCLICKABLE)) continue;
 

	
 
			/* Remap into flat coordinates. */
 
			Point pt = RemapCoords(
 
					this->RemapX(v->x_pos / TILE_SIZE),
 
					this->RemapY(v->y_pos / TILE_SIZE),
 
					0);
 
			int x = pt.x;
 
			int y = pt.y;
 

	
 
			/* Check if y is out of bounds? */
 
			y -= dpi->top;
 
			if (!IsInsideMM(y, 0, dpi->height)) continue;
 

	
 
			/* Default is to draw both pixels. */
 
			bool skip = false;
 

	
 
			/* Offset X coordinate */
 
			x -= this->subscroll + 3 + dpi->left;
 

	
 
			if (x < 0) {
 
				/* if x+1 is 0, that means we're on the very left edge,
 
				 * and should thus only draw a single pixel */
 
				if (++x != 0) continue;
 
				skip = true;
 
			} else if (x >= dpi->width - 1) {
 
				/* Check if we're at the very right edge, and if so draw only a single pixel */
 
				if (x != dpi->width - 1) continue;
 
				skip = true;
 
			}
 

	
 
			/* Calculate pointer to pixel and the colour */
 
			byte colour = (this->map_type == SMT_VEHICLES) ? _vehicle_type_colours[v->type] : 0xF;
 

	
 
			/* And draw either one or two pixels depending on clipping */
 
			blitter->SetPixel(dpi->dst_ptr, x, y, colour);
 
			if (!skip) blitter->SetPixel(dpi->dst_ptr, x + 1, y, colour);
 
		}
 
	}
 

	
 
	/**
 
	 * Adds town names to the smallmap.
 
	 * @param dpi the part of the smallmap to be drawn into
 
	 */
 
	void DrawTowns(const DrawPixelInfo *dpi) const
 
	{
 
		const Town *t;
 
		FOR_ALL_TOWNS(t) {
 
			/* Remap the town coordinate */
 
			Point pt = RemapCoords(
 
					this->RemapX(TileX(t->xy)),
 
					this->RemapY(TileY(t->xy)),
 
					0);
 
			int x = pt.x - this->subscroll - (t->sign.width_small >> 1);
 
			int y = pt.y;
 

	
 
			/* Check if the town sign is within bounds */
 
			if (x + t->sign.width_small > dpi->left &&
 
					x < dpi->left + dpi->width &&
 
					y + FONT_HEIGHT_SMALL > dpi->top &&
 
					y < dpi->top + dpi->height) {
 
				/* And draw it. */
 
				SetDParam(0, t->index);
 
				DrawString(x, x + t->sign.width_small, y, STR_SMALLMAP_TOWN);
 
			}
 
		}
 
	}
 

	
 
	/**
 
	 * Draws vertical part of map indicator
 
	 * @param x X coord of left/right border of main viewport
 
	 * @param y Y coord of top border of main viewport
 
	 * @param y2 Y coord of bottom border of main viewport
 
	 */
 
	static inline void DrawVertMapIndicator(int x, int y, int y2)
 
	{
 
		GfxFillRect(x, y,      x, y + 3, 69);
 
		GfxFillRect(x, y2 - 3, x, y2,    69);
 
	}
 

	
 
	/**
 
	 * Draws horizontal part of map indicator
 
	 * @param x X coord of left border of main viewport
 
	 * @param x2 X coord of right border of main viewport
 
	 * @param y Y coord of top/bottom border of main viewport
 
	 */
 
	static inline void DrawHorizMapIndicator(int x, int x2, int y)
 
	{
 
		GfxFillRect(x,      y, x + 3, y, 69);
 
		GfxFillRect(x2 - 3, y, x2,    y, 69);
 
	}
 

	
 
	/**
 
	 * Adds map indicators to the smallmap.
 
	 */
 
	void DrawMapIndicators() const
 
	{
 
		/* Find main viewport. */
 
		const ViewPort *vp = FindWindowById(WC_MAIN_WINDOW, 0)->viewport;
 

	
 
		Point pt = RemapCoords(this->scroll_x, this->scroll_y, 0);
 

	
 
		int x = vp->virtual_left - pt.x;
 
		int y = vp->virtual_top - pt.y;
 
		int x2 = (x + vp->virtual_width) / TILE_SIZE;
 
		int y2 = (y + vp->virtual_height) / TILE_SIZE;
 
		x /= TILE_SIZE;
 
		y /= TILE_SIZE;
 

	
 
		x -= this->subscroll;
 
		x2 -= this->subscroll;
 

	
 
		SmallMapWindow::DrawVertMapIndicator(x, y, y2);
 
		SmallMapWindow::DrawVertMapIndicator(x2, y, y2);
 

	
 
		SmallMapWindow::DrawHorizMapIndicator(x, x2, y);
 
		SmallMapWindow::DrawHorizMapIndicator(x, x2, y2);
 
	}
 

	
 
	/**
 
	 * Draws the small map.
 
	 *
 
	 * Basically, the small map is draw column of pixels by column of pixels. The pixels
 
	 * are drawn directly into the screen buffer. The final map is drawn in multiple passes.
 
	 * The passes are:
 
	 * <ol><li>The colours of tiles in the different modes.</li>
 
	 * <li>Town names (optional)</li></ol>
 
	 *
 
	 * @param dpi pointer to pixel to write onto
 
	 */
 
	void DrawSmallMap(DrawPixelInfo *dpi) const
 
	{
 
		Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
 
		DrawPixelInfo *old_dpi;
 

	
 
		old_dpi = _cur_dpi;
 
		_cur_dpi = dpi;
 

	
 
		/* Clear it */
 
		GfxFillRect(dpi->left, dpi->top, dpi->left + dpi->width - 1, dpi->top + dpi->height - 1, 0);
 

	
 
		/* Setup owner table */
 
		if (this->map_type == SMT_OWNER) {
 
			const Company *c;
 

	
 
			/* Fill with some special colours */
 
			_owner_colours[OWNER_TOWN]  = MKCOLOUR(0xB4B4B4B4);
 
			_owner_colours[OWNER_NONE]  = MKCOLOUR(0x54545454);
 
			_owner_colours[OWNER_WATER] = MKCOLOUR(0xCACACACA);
 
			_owner_colours[OWNER_END]   = MKCOLOUR(0x20202020); // Industry
 

	
 
			/* Now fill with the company colours */
 
			FOR_ALL_COMPANIES(c) {
 
				_owner_colours[c->index] = _colour_gradient[c->colour][5] * 0x01010101;
 
			}
 
		}
 

	
 
		int tile_x = this->scroll_x / TILE_SIZE;
 
		int tile_y = this->scroll_y / TILE_SIZE;
 

	
 
		int dx = dpi->left + this->subscroll;
 
		tile_x -= dx / 4;
 
		tile_y += dx / 4;
 
		dx &= 3;
 

	
 
		int dy = dpi->top;
 
		tile_x += dy / 2;
 
		tile_y += dy / 2;
 

	
 
		if (dy & 1) {
 
			tile_x++;
 
			dx += 2;
 
			if (dx > 3) {
 
				dx -= 4;
 
				tile_x--;
 
				tile_y++;
 
			}
 
		}
 

	
 
		void *ptr = blitter->MoveTo(dpi->dst_ptr, -dx - 4, 0);
 
		int x = - dx - 4;
 
		int y = 0;
 

	
 
		for (;;) {
 
			uint32 mask = 0xFFFFFFFF;
 

	
 
			/* Distance from left edge */
 
			if (x >= -3) {
 
				if (x < 0) {
 
					/* Mask to use at the left edge */
 
					mask = _smallmap_mask_left[x + 3];
 
				}
 

	
 
				/* Distance from right edge */
 
				int t = dpi->width - x;
 
				if (t < 4) {
 
					if (t <= 0) break; // Exit loop
 
					/* Mask to use at the right edge */
 
					mask &= _smallmap_mask_right[t - 1];
 
				}
 

	
 
				/* Number of lines */
 
				int reps = (dpi->height - y + 1) / 2;
 
				if (reps > 0) {
 
					this->DrawSmallMapStuff(ptr, tile_x, tile_y, dpi->pitch * 2, reps, mask, blitter, _smallmap_draw_procs[this->map_type]);
 
				}
 
			}
 

	
 
			if (y == 0) {
 
				tile_y++;
 
				y++;
 
				ptr = blitter->MoveTo(ptr, 0, 1);
 
			} else {
 
				tile_x--;
 
				y--;
 
				ptr = blitter->MoveTo(ptr, 0, -1);
 
			}
 
			ptr = blitter->MoveTo(ptr, 2, 0);
 
			x += 2;
 
		}
 

	
 
		/* Draw vehicles */
 
		if (this->map_type == SMT_CONTOUR || this->map_type == SMT_VEHICLES) this->DrawVehicles(dpi, blitter);
 

	
 
		/* Draw town names */
 
		if (this->show_towns) this->DrawTowns(dpi);
 

	
 
		/* Draw map indicators */
 
		this->DrawMapIndicators();
 

	
 
		_cur_dpi = old_dpi;
 
	}
 

	
 
public:
 
	SmallMapWindow(const WindowDesc *desc, int window_number) : Window(), refresh(FORCE_REFRESH_PERIOD)
 
	{
 
		this->InitNested(desc, window_number);
 
		this->LowerWidget(this->map_type + SM_WIDGET_CONTOUR);
 

	
 
		this->SetWidgetLoweredState(SM_WIDGET_TOGGLETOWNNAME, this->show_towns);
 
		this->GetWidget<NWidgetStacked>(SM_WIDGET_SELECTINDUSTRIES)->SetDisplayedPlane(this->map_type != SMT_INDUSTRY);
 

	
 
		this->SmallMapCenterOnCurrentPos();
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		switch (widget) {
 
			case SM_WIDGET_CAPTION:
 
				SetDParam(0, STR_SMALLMAP_TYPE_CONTOURS + this->map_type);
 
				break;
 
		}
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		if (widget != SM_WIDGET_LEGEND) return;
 

	
 
		uint min_height = 0;
 
		uint min_width = 0;
 
		for (uint i = 0; i < lengthof(_legend_table); i++) {
 
			/* Only check the width, which are a bit more special! */
 
			if (i == SMT_INDUSTRY) {
src/station_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file station_gui.cpp The GUI for stations. */
 

	
 
#include "stdafx.h"
 
#include "openttd.h"
 
#include "debug.h"
 
#include "gui.h"
 
#include "window_gui.h"
 
#include "textbuf_gui.h"
 
#include "company_func.h"
 
#include "command_func.h"
 
#include "vehicle_gui.h"
 
#include "cargotype.h"
 
#include "station_gui.h"
 
#include "strings_func.h"
 
#include "window_func.h"
 
#include "viewport_func.h"
 
#include "gfx_func.h"
 
#include "widgets/dropdown_func.h"
 
#include "newgrf_cargo.h"
 
#include "station_base.h"
 
#include "waypoint_base.h"
 
#include "tilehighlight_func.h"
 
#include "company_base.h"
 
#include "sortlist_type.h"
 

	
 
#include "table/strings.h"
 
#include "table/sprites.h"
 

	
 
/**
 
 * Draw small boxes of cargo amount and ratings data at the given
 
 * coordinates. If amount exceeds 576 units, it is shown 'full', same
 
 * goes for the rating: at above 90% orso (224) it is also 'full'
 
 *
 
 * @param left   left most coordinate to draw the box at
 
 * @param right  right most coordinate to draw the box at
 
 * @param y      coordinate to draw the box at
 
 * @param type   Cargo type
 
 * @param amount Cargo amount
 
 * @param rating ratings data for that particular cargo
 
 *
 
 * @note Each cargo-bar is 16 pixels wide and 6 pixels high
 
 * @note Each rating 14 pixels wide and 1 pixel high and is 1 pixel below the cargo-bar
 
 */
 
static void StationsWndShowStationRating(int left, int right, int y, CargoID type, uint amount, byte rating)
 
{
 
	static const uint units_full  = 576; ///< number of units to show station as 'full'
 
	static const uint rating_full = 224; ///< rating needed so it is shown as 'full'
 

	
 
	const CargoSpec *cs = CargoSpec::Get(type);
 
	if (!cs->IsValid()) return;
 

	
 
	int colour = cs->rating_colour;
 
	uint w = (minu(amount, units_full) + 5) / 36;
 

	
 
	int height = GetCharacterHeight(FS_SMALL);
 

	
 
	/* Draw total cargo (limited) on station (fits into 16 pixels) */
 
	if (w != 0) GfxFillRect(left, y, left + w - 1, y + height, colour);
 

	
 
	/* Draw a one pixel-wide bar of additional cargo meter, useful
 
	 * for stations with only a small amount (<=30) */
 
	if (w == 0) {
 
		uint rest = amount / 5;
 
		if (rest != 0) {
 
			w += left;
 
			GfxFillRect(w, y + height - rest, w, y + height, colour);
 
		}
 
	}
 

	
 
	DrawString(left + 1, right, y, cs->abbrev, TC_BLACK);
 

	
 
	/* Draw green/red ratings bar (fits into 14 pixels) */
 
	y += height + 2;
 
	GfxFillRect(left + 1, y, left + 14, y, 0xB8);
 
	rating = minu(rating, rating_full) / 16;
 
	if (rating != 0) GfxFillRect(left + 1, y, left + rating, y, 0xD0);
 
}
 

	
 
typedef GUIList<const Station*> GUIStationList;
 

	
 
/** Enum for CompanyStations, referring to _company_stations_widgets */
 
enum StationListWidgets {
 
	SLW_CLOSEBOX =  0,  ///< Close window button
 
	SLW_CAPTION,        ///< Window caption
 
	SLW_STICKY,         ///< Sticky button
 
	SLW_LIST,           ///< The main panel, list of stations
 
	SLW_SCROLLBAR,      ///< Scrollbar next to the main panel
 
	SLW_RESIZE,         ///< Resize button
 

	
 
	SLW_TRAIN,          ///< 'TRAIN' button - list only facilities where is a railroad station
 
	SLW_TRUCK,          ///< 'TRUCK' button - list only facilities where is a truck stop
 
	SLW_BUS,            ///< 'BUS' button - list only facilities where is a bus stop
 
	SLW_AIRPLANE,       ///< 'AIRPLANE' button - list only facilities where is an airport
 
	SLW_SHIP,           ///< 'SHIP' button - list only facilities where is a dock
 
	SLW_FACILALL,       ///< 'ALL' button - list all facilities
 

	
 
	SLW_PAN_BETWEEN,    ///< Small panel between list of types of ficilities and list of cargo types
 
	SLW_NOCARGOWAITING, ///< 'NO' button - list stations where no cargo is waiting
 
	SLW_CARGOALL,       ///< 'ALL' button - list all stations
 
	SLW_PAN_RIGHT,      ///< Panel right of list of cargo types
 

	
 
	SLW_SORTBY,         ///< 'Sort by' button - reverse sort direction
 
	SLW_SORTDROPBTN,    ///< Dropdown button
 
	SLW_PAN_SORT_RIGHT, ///< Panel right of sorting options
 

	
 
	SLW_CARGOSTART,     ///< Widget numbers used for list of cargo types (not present in _company_stations_widgets)
 
};
 

	
 
/**
 
 * The list of stations per company.
 
 */
 
class CompanyStationsWindow : public Window
 
{
 
protected:
 
	/* Runtime saved values */
 
	static Listing last_sorting;
 
	static byte facilities;               // types of stations of interest
 
	static bool include_empty;            // whether we should include stations without waiting cargo
 
	static const uint32 cargo_filter_max;
 
	static uint32 cargo_filter;           // bitmap of cargo types to include
 
	static const Station *last_station;
 

	
 
	/* Constants for sorting stations */
 
	static const StringID sorter_names[];
 
	static GUIStationList::SortFunction * const sorter_funcs[];
 

	
 
	GUIStationList stations;
 

	
 

	
 
	/**
 
	 * (Re)Build station list
 
	 *
 
	 * @param owner company whose stations are to be in list
 
	 */
 
	void BuildStationsList(const Owner owner)
 
	{
 
		if (!this->stations.NeedRebuild()) return;
 

	
 
		DEBUG(misc, 3, "Building station list for company %d", owner);
 

	
 
		this->stations.Clear();
 

	
 
		const Station *st;
 
		FOR_ALL_STATIONS(st) {
 
			if (st->owner == owner || (st->owner == OWNER_NONE && HasStationInUse(st->index, owner))) {
 
				if (this->facilities & st->facilities) { // only stations with selected facilities
 
					int num_waiting_cargo = 0;
 
					for (CargoID j = 0; j < NUM_CARGO; j++) {
 
						if (!st->goods[j].cargo.Empty()) {
 
							num_waiting_cargo++; // count number of waiting cargo
 
							if (HasBit(this->cargo_filter, j)) {
 
								*this->stations.Append() = st;
 
								break;
 
							}
 
						}
 
					}
 
					/* stations without waiting cargo */
 
					if (num_waiting_cargo == 0 && this->include_empty) {
 
						*this->stations.Append() = st;
 
					}
 
				}
 
			}
 
		}
 

	
 
		this->stations.Compact();
 
		this->stations.RebuildDone();
 

	
 
		this->vscroll.SetCount(this->stations.Length()); // Update the scrollbar
 
	}
 

	
 
	/** Sort stations by their name */
 
	static int CDECL StationNameSorter(const Station * const *a, const Station * const *b)
 
	{
 
		static char buf_cache[64];
 
		char buf[64];
 

	
 
		SetDParam(0, (*a)->index);
 
		GetString(buf, STR_STATION_NAME, lastof(buf));
 

	
 
		if (*b != last_station) {
 
			last_station = *b;
 
			SetDParam(0, (*b)->index);
 
			GetString(buf_cache, STR_STATION_NAME, lastof(buf_cache));
 
		}
 

	
 
		return strcmp(buf, buf_cache);
 
	}
 

	
 
	/** Sort stations by their type */
 
	static int CDECL StationTypeSorter(const Station * const *a, const Station * const *b)
 
	{
 
		return (*a)->facilities - (*b)->facilities;
 
	}
 

	
 
	/** Sort stations by their waiting cargo */
 
	static int CDECL StationWaitingSorter(const Station * const *a, const Station * const *b)
 
	{
 
		Money diff = 0;
 

	
 
		for (CargoID j = 0; j < NUM_CARGO; j++) {
 
			if (!HasBit(cargo_filter, j)) continue;
 
			if (!(*a)->goods[j].cargo.Empty()) diff += GetTransportedGoodsIncome((*a)->goods[j].cargo.Count(), 20, 50, j);
 
			if (!(*b)->goods[j].cargo.Empty()) diff -= GetTransportedGoodsIncome((*b)->goods[j].cargo.Count(), 20, 50, j);
 
		}
 

	
 
		return ClampToI32(diff);
 
	}
 

	
 
	/** Sort stations by their rating */
 
	static int CDECL StationRatingMaxSorter(const Station * const *a, const Station * const *b)
 
	{
 
		byte maxr1 = 0;
 
		byte maxr2 = 0;
 

	
 
		for (CargoID j = 0; j < NUM_CARGO; j++) {
 
			if (!HasBit(cargo_filter, j)) continue;
 
			if (HasBit((*a)->goods[j].acceptance_pickup, GoodsEntry::PICKUP)) maxr1 = max(maxr1, (*a)->goods[j].rating);
 
			if (HasBit((*b)->goods[j].acceptance_pickup, GoodsEntry::PICKUP)) maxr2 = max(maxr2, (*b)->goods[j].rating);
 
		}
 

	
 
		return maxr1 - maxr2;
 
	}
 

	
 
	/** Sort stations by their rating */
 
	static int CDECL StationRatingMinSorter(const Station * const *a, const Station * const *b)
 
	{
 
		byte minr1 = 255;
 
		byte minr2 = 255;
 

	
 
		for (CargoID j = 0; j < NUM_CARGO; j++) {
 
			if (!HasBit(cargo_filter, j)) continue;
 
			if (HasBit((*a)->goods[j].acceptance_pickup, GoodsEntry::PICKUP)) minr1 = min(minr1, (*a)->goods[j].rating);
 
			if (HasBit((*b)->goods[j].acceptance_pickup, GoodsEntry::PICKUP)) minr2 = min(minr2, (*b)->goods[j].rating);
 
		}
 

	
 
		return -(minr1 - minr2);
 
	}
 

	
 
	/** Sort the stations list */
 
	void SortStationsList()
 
	{
 
		if (!this->stations.Sort()) return;
 

	
 
		/* Reset name sorter sort cache */
 
		this->last_station = NULL;
 

	
 
		/* Set the modified widget dirty */
 
		this->SetWidgetDirty(SLW_LIST);
 
	}
 

	
 
public:
 
	CompanyStationsWindow(const WindowDesc *desc, WindowNumber window_number) : Window()
 
	{
 
		this->stations.SetListing(this->last_sorting);
 
		this->stations.SetSortFuncs(this->sorter_funcs);
 
		this->stations.ForceRebuild();
 
		this->stations.NeedResort();
 
		this->SortStationsList();
 

	
 
		this->InitNested(desc, window_number);
 
		this->owner = (Owner)this->window_number;
 

	
 
		for (uint i = 0; i < NUM_CARGO; i++) {
 
			const CargoSpec *cs = CargoSpec::Get(i);
 
			if (cs->IsValid() && HasBit(this->cargo_filter, i)) this->LowerWidget(SLW_CARGOSTART + i);
 
		}
 

	
 
		if (this->cargo_filter == this->cargo_filter_max) this->cargo_filter = _cargo_mask;
 

	
 
		for (uint i = 0; i < 5; i++) {
 
			if (HasBit(this->facilities, i)) this->LowerWidget(i + SLW_TRAIN);
 
		}
 
		this->SetWidgetLoweredState(SLW_FACILALL, this->facilities == (FACIL_TRAIN | FACIL_TRUCK_STOP | FACIL_BUS_STOP | FACIL_AIRPORT | FACIL_DOCK));
 
		this->SetWidgetLoweredState(SLW_CARGOALL, this->cargo_filter == _cargo_mask && this->include_empty);
 
		this->SetWidgetLoweredState(SLW_NOCARGOWAITING, this->include_empty);
 

	
 
		this->GetWidget<NWidgetCore>(SLW_SORTDROPBTN)->widget_data = this->sorter_names[this->stations.SortType()];
 
	}
 

	
 
	~CompanyStationsWindow()
 
	{
 
		this->last_sorting = this->stations.GetListing();
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		switch (widget) {
 
			case SLW_SORTDROPBTN: {
 
				Dimension d = {0, 0};
 
				for (int i = 0; this->sorter_names[i] != INVALID_STRING_ID; i++) {
 
					d = maxdim(d, GetStringBoundingBox(this->sorter_names[i]));
 
				}
 
				d.width += padding.width;
 
				d.height += padding.height;
 
				*size = maxdim(*size, d);
 
				break;
 
			}
 

	
 
			case SLW_LIST:
 
				resize->height = FONT_HEIGHT_NORMAL;
 
				size->height = WD_FRAMERECT_TOP + 5 * resize->height + WD_FRAMERECT_BOTTOM;
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->BuildStationsList((Owner)this->window_number);
 
		this->SortStationsList();
 

	
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case SLW_SORTBY:
 
				/* draw arrow pointing up/down for ascending/descending sorting */
 
				this->DrawSortButtonState(SLW_SORTBY, this->stations.IsDescSortOrder() ? SBS_DOWN : SBS_UP);
 
				break;
 

	
 
			case SLW_LIST: {
 
				bool rtl = _dynlang.text_dir == TD_RTL;
 
				int max = min(this->vscroll.GetPosition() + this->vscroll.GetCapacity(), this->stations.Length());
 
				int y = r.top + WD_FRAMERECT_TOP;
 
				for (int i = this->vscroll.GetPosition(); i < max; ++i) { // do until max number of stations of owner
 
					const Station *st = this->stations[i];
 
					assert(st->xy != INVALID_TILE);
 

	
 
					/* Do not do the complex check HasStationInUse here, it may be even false
 
					 * when the order had been removed and the station list hasn't been removed yet */
 
					assert(st->owner == owner || st->owner == OWNER_NONE);
 

	
 
					SetDParam(0, st->index);
 
					SetDParam(1, st->facilities);
 
					int x = DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_STATION_LIST_STATION);
 
					x += rtl ? -5 : 5;
 

	
 
					/* show cargo waiting and station ratings */
 
					for (CargoID j = 0; j < NUM_CARGO; j++) {
 
						if (!st->goods[j].cargo.Empty()) {
 
							/* For RTL we work in exactly the opposite direction. So
 
							 * decrement the space needed first, then draw to the left
 
							 * instead of drawing to the left and then incrementing
 
							 * the space. */
 
							if (rtl) {
 
								x -= 20;
 
								if (x < r.left + WD_FRAMERECT_LEFT) break;
 
							}
 
							StationsWndShowStationRating(x, x + 16, y, j, st->goods[j].cargo.Count(), st->goods[j].rating);
 
							if (!rtl) {
 
								x += 20;
 
								if (x > r.right - WD_FRAMERECT_RIGHT) break;
 
							}
 
						}
 
					}
 
					y += FONT_HEIGHT_NORMAL;
 
				}
 

	
 
				if (this->vscroll.GetCount() == 0) { // company has no stations
 
					DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_STATION_LIST_NONE);
 
					return;
 
				}
 
				break;
 
			}
 

	
 
			case SLW_NOCARGOWAITING: {
 
				int cg_ofst = this->IsWidgetLowered(widget) ? 2 : 1;
 
				DrawString(r.left + cg_ofst, r.right + cg_ofst, r.top + cg_ofst, STR_ABBREV_NONE, TC_BLACK, SA_CENTER);
 
				break;
 
			}
 

	
 
			case SLW_CARGOALL: {
 
				int cg_ofst = this->IsWidgetLowered(widget) ? 2 : 1;
 
				DrawString(r.left + cg_ofst, r.right + cg_ofst, r.top + cg_ofst, STR_ABBREV_ALL, TC_BLACK, SA_CENTER);
 
				break;
 
			}
 

	
 
			case SLW_FACILALL: {
 
				int cg_ofst = this->IsWidgetLowered(widget) ? 2 : 1;
 
				DrawString(r.left + cg_ofst, r.right + cg_ofst, r.top + cg_ofst, STR_ABBREV_ALL, TC_BLACK);
 
				break;
 
			}
 

	
 
			default:
 
				if (widget >= SLW_CARGOSTART) {
 
					const CargoSpec *cs = CargoSpec::Get(widget - SLW_CARGOSTART);
 
					if (cs->IsValid()) {
 
						int cg_ofst = HasBit(this->cargo_filter, cs->Index()) ? 2 : 1;
 
						GfxFillRect(r.left + cg_ofst, r.top + cg_ofst, r.left + cg_ofst + 10, r.top + cg_ofst + 7, cs->rating_colour);
 
						DrawString(r.left + cg_ofst, r.left + 12 + cg_ofst, r.top + cg_ofst, cs->abbrev, TC_BLACK, SA_CENTER);
 
					}
 
				}
 
				break;
 
		}
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		if (widget == SLW_CAPTION) {
 
			SetDParam(0, this->window_number);
 
			SetDParam(1, this->vscroll.GetCount());
 
		}
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case SLW_LIST: {
 
				uint32 id_v = (pt.y - this->GetWidget<NWidgetBase>(SLW_LIST)->pos_y - WD_FRAMERECT_TOP) / FONT_HEIGHT_NORMAL;
 

	
 
				if (id_v >= this->vscroll.GetCapacity()) return; // click out of bounds
 

	
 
				id_v += this->vscroll.GetPosition();
 

	
 
				if (id_v >= this->stations.Length()) return; // click out of list bound
 

	
 
				const Station *st = this->stations[id_v];
 
				/* do not check HasStationInUse - it is slow and may be invalid */
 
				assert(st->owner == (Owner)this->window_number || st->owner == OWNER_NONE);
 

	
 
				if (_ctrl_pressed) {
 
					ShowExtraViewPortWindow(st->xy);
 
				} else {
 
					ScrollMainWindowToTile(st->xy);
 
				}
 
				break;
 
			}
 

	
 
			case SLW_TRAIN:
 
			case SLW_TRUCK:
 
			case SLW_BUS:
 
			case SLW_AIRPLANE:
 
			case SLW_SHIP:
 
				if (_ctrl_pressed) {
 
					ToggleBit(this->facilities, widget - SLW_TRAIN);
 
					this->ToggleWidgetLoweredState(widget);
 
				} else {
 
					uint i;
 
					FOR_EACH_SET_BIT(i, this->facilities) {
 
						this->RaiseWidget(i + SLW_TRAIN);
 
					}
 
					SetBit(this->facilities, widget - SLW_TRAIN);
 
					this->LowerWidget(widget);
 
				}
 
				this->SetWidgetLoweredState(SLW_FACILALL, this->facilities == (FACIL_TRAIN | FACIL_TRUCK_STOP | FACIL_BUS_STOP | FACIL_AIRPORT | FACIL_DOCK));
 
				this->stations.ForceRebuild();
 
				this->SetDirty();
 
				break;
 

	
 
			case SLW_FACILALL:
 
				for (uint i = 0; i < 5; i++) {
 
					this->LowerWidget(i + SLW_TRAIN);
 
				}
 
				this->LowerWidget(SLW_FACILALL);
 

	
 
				this->facilities = FACIL_TRAIN | FACIL_TRUCK_STOP | FACIL_BUS_STOP | FACIL_AIRPORT | FACIL_DOCK;
 
				this->stations.ForceRebuild();
 
				this->SetDirty();
 
				break;
 

	
 
			case SLW_CARGOALL: {
 
				for (uint i = 0; i < NUM_CARGO; i++) {
 
					const CargoSpec *cs = CargoSpec::Get(i);
 
					if (cs->IsValid()) this->LowerWidget(SLW_CARGOSTART + i);
 
				}
 
				this->LowerWidget(SLW_NOCARGOWAITING);
 
				this->LowerWidget(SLW_CARGOALL);
 

	
 
				this->cargo_filter = _cargo_mask;
 
				this->include_empty = true;
 
				this->stations.ForceRebuild();
 
				this->SetDirty();
 
				break;
 
			}
 

	
 
			case SLW_SORTBY: // flip sorting method asc/desc
 
				this->stations.ToggleSortOrder();
 
				this->flags4 |= WF_TIMEOUT_BEGIN;
 
				this->LowerWidget(SLW_SORTBY);
 
				this->SetDirty();
 
				break;
 

	
 
			case SLW_SORTDROPBTN: // select sorting criteria dropdown menu
 
				ShowDropDownMenu(this, this->sorter_names, this->stations.SortType(), SLW_SORTDROPBTN, 0, 0);
 
				break;
 

	
 
			case SLW_NOCARGOWAITING:
 
				if (_ctrl_pressed) {
 
					this->include_empty = !this->include_empty;
 
					this->ToggleWidgetLoweredState(SLW_NOCARGOWAITING);
 
				} else {
 
					for (uint i = 0; i < NUM_CARGO; i++) {
 
						const CargoSpec *cs = CargoSpec::Get(i);
 
						if (cs->IsValid()) this->RaiseWidget(SLW_CARGOSTART + i);
 
					}
 

	
 
					this->cargo_filter = 0;
 
					this->include_empty = true;
 

	
 
					this->LowerWidget(SLW_NOCARGOWAITING);
 
				}
 
				this->SetWidgetLoweredState(SLW_CARGOALL, this->cargo_filter == _cargo_mask && this->include_empty);
 
				this->stations.ForceRebuild();
 
				this->SetDirty();
 
				break;
 

	
 
			default:
 
				if (widget >= SLW_CARGOSTART) { // change cargo_filter
 
					/* Determine the selected cargo type */
 
					const CargoSpec *cs = CargoSpec::Get(widget - SLW_CARGOSTART);
 
					if (!cs->IsValid()) break;
 

	
 
					if (_ctrl_pressed) {
 
						ToggleBit(this->cargo_filter, cs->Index());
 
						this->ToggleWidgetLoweredState(widget);
 
					} else {
 
						for (uint i = 0; i < NUM_CARGO; i++) {
 
							const CargoSpec *cs = CargoSpec::Get(i);
 
							if (cs->IsValid()) this->RaiseWidget(SLW_CARGOSTART + i);
 
						}
 
						this->RaiseWidget(SLW_NOCARGOWAITING);
 

	
 
						this->cargo_filter = 0;
 
						this->include_empty = false;
 

	
 
						SetBit(this->cargo_filter, cs->Index());
 
						this->LowerWidget(widget);
 
					}
 
					this->SetWidgetLoweredState(SLW_CARGOALL, this->cargo_filter == _cargo_mask && this->include_empty);
 
					this->stations.ForceRebuild();
 
					this->SetDirty();
 
				}
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnDropdownSelect(int widget, int index)
 
	{
 
		if (this->stations.SortType() != index) {
 
			this->stations.SetSortType(index);
 

	
 
			/* Display the current sort variant */
 
			this->GetWidget<NWidgetCore>(SLW_SORTDROPBTN)->widget_data = this->sorter_names[this->stations.SortType()];
 

	
 
			this->SetDirty();
 
		}
 
	}
 

	
 
	virtual void OnTick()
 
	{
 
		if (_pause_mode != PM_UNPAUSED) return;
 
		if (this->stations.NeedResort()) {
 
			DEBUG(misc, 3, "Periodic rebuild station list company %d", this->window_number);
 
			this->SetDirty();
 
		}
 
	}
 

	
 
	virtual void OnTimeout()
 
	{
 
		this->RaiseWidget(SLW_SORTBY);
 
		this->SetDirty();
 
	}
 

	
 
	virtual void OnResize()
 
	{
 
		this->vscroll.SetCapacity((this->GetWidget<NWidgetBase>(SLW_LIST)->current_y - WD_FRAMERECT_TOP - WD_FRAMERECT_BOTTOM) / FONT_HEIGHT_NORMAL);
 
	}
 

	
 
	virtual void OnInvalidateData(int data)
 
	{
 
		if (data == 0) {
 
			this->stations.ForceRebuild();
 
		} else {
 
			this->stations.ForceResort();
 
		}
 
	}
 
};
 

	
 
Listing CompanyStationsWindow::last_sorting = {false, 0};
 
byte CompanyStationsWindow::facilities = FACIL_TRAIN | FACIL_TRUCK_STOP | FACIL_BUS_STOP | FACIL_AIRPORT | FACIL_DOCK;
 
bool CompanyStationsWindow::include_empty = true;
 
const uint32 CompanyStationsWindow::cargo_filter_max = UINT32_MAX;
 
uint32 CompanyStationsWindow::cargo_filter = UINT32_MAX;
 
const Station *CompanyStationsWindow::last_station = NULL;
 

	
 
/* Availible station sorting functions */
 
GUIStationList::SortFunction * const CompanyStationsWindow::sorter_funcs[] = {
 
	&StationNameSorter,
 
	&StationTypeSorter,
 
	&StationWaitingSorter,
 
	&StationRatingMaxSorter,
 
	&StationRatingMinSorter
 
};
 

	
 
/* Names of the sorting functions */
 
const StringID CompanyStationsWindow::sorter_names[] = {
 
	STR_SORT_BY_NAME,
 
	STR_SORT_BY_FACILITY,
 
	STR_SORT_BY_WAITING,
 
	STR_SORT_BY_RATING_MAX,
 
	STR_SORT_BY_RATING_MIN,
 
	INVALID_STRING_ID
 
};
 

	
 
/** Make a horizontal row of cargo buttons, starting at widget #SLW_CARGOSTART.
 
 * @param biggest_index Pointer to store biggest used widget number of the buttons.
 
 * @return Horizontal row.
 
 */
 
static NWidgetBase *CargoWidgets(int *biggest_index)
 
{
 
	NWidgetHorizontal *container = new NWidgetHorizontal();
 

	
 
	for (uint i = 0; i < NUM_CARGO; i++) {
 
		const CargoSpec *cs = CargoSpec::Get(i);
 
		if (cs->IsValid()) {
 
			NWidgetBackground *panel = new NWidgetBackground(WWT_PANEL, COLOUR_GREY, SLW_CARGOSTART + i);
 
			panel->SetMinimalSize(14, 11);
 
			panel->SetResize(0, 0);
 
			panel->SetFill(false, true);
 
			panel->SetFill(0, 1);
 
			panel->SetDataTip(0, STR_STATION_LIST_USE_CTRL_TO_SELECT_MORE);
 
			container->Add(panel);
 
		} else {
 
			NWidgetLeaf *nwi = new NWidgetLeaf(WWT_EMPTY, COLOUR_GREY, SLW_CARGOSTART + i, 0x0, STR_NULL);
 
			nwi->SetMinimalSize(0, 11);
 
			nwi->SetResize(0, 0);
 
			nwi->SetFill(false, true);
 
			nwi->SetFill(0, 1);
 
			container->Add(nwi);
 
		}
 
	}
 
	*biggest_index = SLW_CARGOSTART + NUM_CARGO;
 
	return container;
 
}
 

	
 
static const NWidgetPart _nested_company_stations_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, SLW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, SLW_CAPTION), SetDataTip(STR_STATION_LIST_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_STICKYBOX, COLOUR_GREY, SLW_STICKY),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, SLW_TRAIN), SetMinimalSize(14, 11), SetDataTip(STR_TRAIN, STR_STATION_LIST_USE_CTRL_TO_SELECT_MORE), SetFill(false, true),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, SLW_TRUCK), SetMinimalSize(14, 11), SetDataTip(STR_LORRY, STR_STATION_LIST_USE_CTRL_TO_SELECT_MORE), SetFill(false, true),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, SLW_BUS), SetMinimalSize(14, 11), SetDataTip(STR_BUS, STR_STATION_LIST_USE_CTRL_TO_SELECT_MORE), SetFill(false, true),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, SLW_AIRPLANE), SetMinimalSize(14, 11), SetDataTip(STR_PLANE, STR_STATION_LIST_USE_CTRL_TO_SELECT_MORE), SetFill(false, true),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, SLW_SHIP), SetMinimalSize(14, 11), SetDataTip(STR_SHIP, STR_STATION_LIST_USE_CTRL_TO_SELECT_MORE), SetFill(false, true),
 
		NWidget(WWT_PANEL, COLOUR_GREY, SLW_FACILALL), SetMinimalSize(14, 11), SetDataTip(0x0, STR_STATION_LIST_SELECT_ALL_FACILITIES), SetFill(false, true), EndContainer(),
 
		NWidget(WWT_PANEL, COLOUR_GREY, SLW_PAN_BETWEEN), SetMinimalSize(5, 11), SetDataTip(0x0, STR_NULL), SetFill(false, true), EndContainer(),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, SLW_TRAIN), SetMinimalSize(14, 11), SetDataTip(STR_TRAIN, STR_STATION_LIST_USE_CTRL_TO_SELECT_MORE), SetFill(0, 1),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, SLW_TRUCK), SetMinimalSize(14, 11), SetDataTip(STR_LORRY, STR_STATION_LIST_USE_CTRL_TO_SELECT_MORE), SetFill(0, 1),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, SLW_BUS), SetMinimalSize(14, 11), SetDataTip(STR_BUS, STR_STATION_LIST_USE_CTRL_TO_SELECT_MORE), SetFill(0, 1),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, SLW_AIRPLANE), SetMinimalSize(14, 11), SetDataTip(STR_PLANE, STR_STATION_LIST_USE_CTRL_TO_SELECT_MORE), SetFill(0, 1),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, SLW_SHIP), SetMinimalSize(14, 11), SetDataTip(STR_SHIP, STR_STATION_LIST_USE_CTRL_TO_SELECT_MORE), SetFill(0, 1),
 
		NWidget(WWT_PANEL, COLOUR_GREY, SLW_FACILALL), SetMinimalSize(14, 11), SetDataTip(0x0, STR_STATION_LIST_SELECT_ALL_FACILITIES), SetFill(0, 1), EndContainer(),
 
		NWidget(WWT_PANEL, COLOUR_GREY, SLW_PAN_BETWEEN), SetMinimalSize(5, 11), SetDataTip(0x0, STR_NULL), SetFill(0, 1), EndContainer(),
 
		NWidgetFunction(CargoWidgets),
 
		NWidget(WWT_PANEL, COLOUR_GREY, SLW_NOCARGOWAITING), SetMinimalSize(14, 11), SetDataTip(0x0, STR_STATION_LIST_NO_WAITING_CARGO), SetFill(false, true), EndContainer(),
 
		NWidget(WWT_PANEL, COLOUR_GREY, SLW_CARGOALL), SetMinimalSize(14, 11), SetDataTip(0x0, STR_STATION_LIST_SELECT_ALL_TYPES), SetFill(false, true), EndContainer(),
 
		NWidget(WWT_PANEL, COLOUR_GREY, SLW_PAN_RIGHT), SetDataTip(0x0, STR_NULL), SetResize(1, 0), SetFill(true, true), EndContainer(),
 
		NWidget(WWT_PANEL, COLOUR_GREY, SLW_NOCARGOWAITING), SetMinimalSize(14, 11), SetDataTip(0x0, STR_STATION_LIST_NO_WAITING_CARGO), SetFill(0, 1), EndContainer(),
 
		NWidget(WWT_PANEL, COLOUR_GREY, SLW_CARGOALL), SetMinimalSize(14, 11), SetDataTip(0x0, STR_STATION_LIST_SELECT_ALL_TYPES), SetFill(0, 1), EndContainer(),
 
		NWidget(WWT_PANEL, COLOUR_GREY, SLW_PAN_RIGHT), SetDataTip(0x0, STR_NULL), SetResize(1, 0), SetFill(1, 1), EndContainer(),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, SLW_SORTBY), SetMinimalSize(81, 12), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER),
 
		NWidget(WWT_DROPDOWN, COLOUR_GREY, SLW_SORTDROPBTN), SetMinimalSize(163, 12), SetDataTip(STR_SORT_BY_NAME, STR_TOOLTIP_SORT_CRITERIAP), // widget_data gets overwritten.
 
		NWidget(WWT_PANEL, COLOUR_GREY, SLW_PAN_SORT_RIGHT), SetDataTip(0x0, STR_NULL), SetResize(1, 0), SetFill(true, true), EndContainer(),
 
		NWidget(WWT_PANEL, COLOUR_GREY, SLW_PAN_SORT_RIGHT), SetDataTip(0x0, STR_NULL), SetResize(1, 0), SetFill(1, 1), EndContainer(),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_PANEL, COLOUR_GREY, SLW_LIST), SetMinimalSize(346, 125), SetResize(1, 10), SetDataTip(0x0, STR_STATION_LIST_TOOLTIP), EndContainer(),
 
		NWidget(NWID_VERTICAL),
 
			NWidget(WWT_SCROLLBAR, COLOUR_GREY, SLW_SCROLLBAR),
 
			NWidget(WWT_RESIZEBOX, COLOUR_GREY, SLW_RESIZE),
 
		EndContainer(),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _company_stations_desc(
 
	WDP_AUTO, WDP_AUTO, 358, 162,
 
	WC_STATION_LIST, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_STICKY_BUTTON | WDF_RESIZABLE,
 
	_nested_company_stations_widgets, lengthof(_nested_company_stations_widgets)
 
);
 

	
 
/**
 
 * Opens window with list of company's stations
 
 *
 
 * @param company whose stations' list show
 
 */
 
void ShowCompanyStations(CompanyID company)
 
{
 
	if (!Company::IsValidID(company)) return;
 

	
 
	AllocateWindowDescFront<CompanyStationsWindow>(&_company_stations_desc, company);
 
}
 

	
 
static const NWidgetPart _nested_station_view_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, SVW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, SVW_CAPTION), SetDataTip(STR_STATION_VIEW_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_STICKYBOX, COLOUR_GREY, SVW_STICKYBOX),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_PANEL, COLOUR_GREY, SVW_WAITING), SetMinimalSize(237, 52), SetResize(1, 10), EndContainer(),
 
		NWidget(WWT_SCROLLBAR, COLOUR_GREY, SVW_SCROLLBAR),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, SVW_ACCEPTLIST), SetMinimalSize(249, 32), SetResize(1, 0), EndContainer(),
 
	NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SVW_LOCATION), SetMinimalSize(60, 12), SetResize(1, 0), SetFill(true, true),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SVW_LOCATION), SetMinimalSize(60, 12), SetResize(1, 0), SetFill(1, 1),
 
				SetDataTip(STR_BUTTON_LOCATION, STR_STATION_VIEW_CENTER_TOOLTIP),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SVW_ACCEPTS), SetMinimalSize(61, 12), SetResize(1, 0), SetFill(true, true),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SVW_ACCEPTS), SetMinimalSize(61, 12), SetResize(1, 0), SetFill(1, 1),
 
				SetDataTip(STR_STATION_VIEW_RATINGS_BUTTON, STR_STATION_VIEW_RATINGS_TOOLTIP),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SVW_RENAME), SetMinimalSize(60, 12), SetResize(1, 0), SetFill(true, true),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SVW_RENAME), SetMinimalSize(60, 12), SetResize(1, 0), SetFill(1, 1),
 
				SetDataTip(STR_BUTTON_RENAME, STR_STATION_VIEW_RENAME_TOOLTIP),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SVW_TRAINS), SetMinimalSize(14, 12), SetFill(false, true), SetDataTip(STR_TRAIN, STR_STATION_VIEW_SCHEDULED_TRAINS_TOOLTIP),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SVW_ROADVEHS), SetMinimalSize(14, 12), SetFill(false, true), SetDataTip(STR_LORRY, STR_STATION_VIEW_SCHEDULED_ROAD_VEHICLES_TOOLTIP),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SVW_PLANES),  SetMinimalSize(14, 12), SetFill(false, true), SetDataTip(STR_PLANE, STR_STATION_VIEW_SCHEDULED_AIRCRAFT_TOOLTIP),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SVW_SHIPS), SetMinimalSize(14, 12), SetFill(false, true), SetDataTip(STR_SHIP, STR_STATION_VIEW_SCHEDULED_SHIPS_TOOLTIP),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SVW_TRAINS), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_TRAIN, STR_STATION_VIEW_SCHEDULED_TRAINS_TOOLTIP),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SVW_ROADVEHS), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_LORRY, STR_STATION_VIEW_SCHEDULED_ROAD_VEHICLES_TOOLTIP),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SVW_PLANES),  SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_PLANE, STR_STATION_VIEW_SCHEDULED_AIRCRAFT_TOOLTIP),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SVW_SHIPS), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_SHIP, STR_STATION_VIEW_SCHEDULED_SHIPS_TOOLTIP),
 
		NWidget(WWT_RESIZEBOX, COLOUR_GREY, SVW_RESIZE),
 
	EndContainer(),
 
};
 

	
 
SpriteID GetCargoSprite(CargoID i)
 
{
 
	const CargoSpec *cs = CargoSpec::Get(i);
 
	SpriteID sprite;
 

	
 
	if (cs->sprite == 0xFFFF) {
 
		/* A value of 0xFFFF indicates we should draw a custom icon */
 
		sprite = GetCustomCargoSprite(cs);
 
	} else {
 
		sprite = cs->sprite;
 
	}
 

	
 
	if (sprite == 0) sprite = SPR_CARGO_GOODS;
 

	
 
	return sprite;
 
}
 

	
 
/**
 
 * Draws icons of waiting cargo in the StationView window
 
 *
 
 * @param i type of cargo
 
 * @param waiting number of waiting units
 
 * @param left  left most coordinate to draw on
 
 * @param right right most coordinate to draw on
 
 * @param y y coordinate
 
 * @param width the width of the view
 
 */
 
static void DrawCargoIcons(CargoID i, uint waiting, int left, int right, int y)
 
{
 
	uint num = min((waiting + 5) / 10, (right - left) / 10); // maximum is width / 10 icons so it won't overflow
 
	if (num == 0) return;
 

	
 
	SpriteID sprite = GetCargoSprite(i);
 

	
 
	int x = _dynlang.text_dir == TD_RTL ? right - num * 10 : left;
 
	do {
 
		DrawSprite(sprite, PAL_NONE, x, y);
 
		x += 10;
 
	} while (--num);
 
}
 

	
 
struct CargoData {
 
	CargoID cargo;
 
	StationID source;
 
	uint count;
 

	
 
	CargoData(CargoID cargo, StationID source, uint count) :
 
		cargo(cargo),
 
		source(source),
 
		count(count)
 
	{ }
 
};
 

	
 
typedef std::list<CargoData> CargoDataList;
 

	
 
/**
 
 * The StationView window
 
 */
 
struct StationViewWindow : public Window {
 
	uint32 cargo;                 ///< Bitmask of cargo types to expand
 
	uint16 cargo_rows[NUM_CARGO]; ///< Header row for each cargo type
 
	uint expand_shrink_width;     ///< The width allocated to the expand/shrink 'button'
 

	
 
	/** Height of the #SVW_ACCEPTLIST widget for different views. */
 
	enum AcceptListHeight {
 
		ALH_RATING  = 13, ///< Height of the cargo ratings view.
 
		ALH_ACCEPTS = 3,  ///< Height of the accepted cargo view.
 
	};
 

	
 
	StationViewWindow(const WindowDesc *desc, WindowNumber window_number) : Window()
 
	{
 
		this->CreateNestedTree(desc);
 
		/* Nested widget tree creation is done in two steps to ensure that this->GetWidget<NWidgetCore>(SVW_ACCEPTS) exists in UpdateWidgetSize(). */
 
		this->FinishInitNested(desc, window_number);
 

	
 
		Owner owner = Station::Get(window_number)->owner;
 
		if (owner != OWNER_NONE) this->owner = owner;
 
	}
 

	
 
	~StationViewWindow()
 
	{
 
		WindowNumber wno = (this->window_number << 16) | VLW_STATION_LIST | Station::Get(this->window_number)->owner;
 

	
 
		DeleteWindowById(WC_TRAINS_LIST, wno | (VEH_TRAIN << 11), false);
 
		DeleteWindowById(WC_ROADVEH_LIST, wno | (VEH_ROAD << 11), false);
 
		DeleteWindowById(WC_SHIPS_LIST, wno | (VEH_SHIP << 11), false);
 
		DeleteWindowById(WC_AIRCRAFT_LIST, wno | (VEH_AIRCRAFT << 11), false);
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		switch (widget) {
 
			case SVW_WAITING:
 
				resize->height = FONT_HEIGHT_NORMAL;
 
				size->height = WD_FRAMERECT_TOP + 5 * resize->height + WD_FRAMERECT_BOTTOM;
 
				this->expand_shrink_width = max(GetStringBoundingBox("-").width, GetStringBoundingBox("+").width) + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
 
				break;
 

	
 
			case SVW_ACCEPTLIST:
 
				size->height = WD_FRAMERECT_TOP + ((this->GetWidget<NWidgetCore>(SVW_ACCEPTS)->widget_data == STR_STATION_VIEW_RATINGS_BUTTON) ? ALH_ACCEPTS : ALH_RATING) * FONT_HEIGHT_NORMAL + WD_FRAMERECT_BOTTOM;
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		CargoDataList cargolist;
 
		uint32 transfers = 0;
 
		this->OrderWaitingCargo(&cargolist, &transfers);
 

	
 
		this->vscroll.SetCount((int)cargolist.size() + 1); // update scrollbar
 

	
 
		/* disable some buttons */
 
		const Station *st = Station::Get(this->window_number);
 
		this->SetWidgetDisabledState(SVW_RENAME,   st->owner != _local_company);
 
		this->SetWidgetDisabledState(SVW_TRAINS,   !(st->facilities & FACIL_TRAIN));
 
		this->SetWidgetDisabledState(SVW_ROADVEHS, !(st->facilities & FACIL_TRUCK_STOP) && !(st->facilities & FACIL_BUS_STOP));
 
		this->SetWidgetDisabledState(SVW_PLANES,   !(st->facilities & FACIL_AIRPORT));
 
		this->SetWidgetDisabledState(SVW_SHIPS,    !(st->facilities & FACIL_DOCK));
 

	
 
		this->DrawWidgets();
 

	
 
		NWidgetBase *nwi = this->GetWidget<NWidgetBase>(SVW_WAITING);
 
		Rect waiting_rect = {nwi->pos_x, nwi->pos_y, nwi->pos_x + nwi->current_x - 1, nwi->pos_y + nwi->current_y - 1};
 
		this->DrawWaitingCargo(waiting_rect, cargolist, transfers);
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		if (widget != SVW_ACCEPTLIST) return;
 

	
 
		if (this->GetWidget<NWidgetCore>(SVW_ACCEPTS)->widget_data == STR_STATION_VIEW_RATINGS_BUTTON) {
 
			this->DrawAcceptedCargo(r);
 
		} else {
 
			this->DrawCargoRatings(r);
 
		}
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		if (widget == SVW_CAPTION) {
 
			const Station *st = Station::Get(this->window_number);
 
			SetDParam(0, st->index);
 
			SetDParam(1, st->facilities);
 
		}
 
	}
 

	
 
	/** Order waiting cargo by type and destination.
 
	 * @param cargolist [out] Ordered cargo.
 
	 * @param transfers [out] Bitmask for cargoes being transfered.
 
	 * @pre \c *cargolist must be empty.
 
	 */
 
	void OrderWaitingCargo(CargoDataList *cargolist, uint32 *transfers)
 
	{
 
		assert(cargolist->size() == 0);
 
		*transfers = 0;
 

	
 
		StationID station_id = this->window_number;
 
		const Station *st = Station::Get(station_id);
 

	
 
		/* count types of cargos waiting in station */
 
		for (CargoID i = 0; i < NUM_CARGO; i++) {
 
			if (st->goods[i].cargo.Empty()) {
 
				this->cargo_rows[i] = 0;
 
			} else {
 
				/* Add an entry for total amount of cargo of this type waiting. */
 
				cargolist->push_back(CargoData(i, INVALID_STATION, st->goods[i].cargo.Count()));
 

	
 
				/* Set the row for this cargo entry for the expand/hide button */
 
				this->cargo_rows[i] = (uint16)cargolist->size();
 

	
 
				/* Add an entry for each distinct cargo source. */
 
				const StationCargoList::List *packets = st->goods[i].cargo.Packets();
 
				for (StationCargoList::ConstIterator it(packets->begin()); it != packets->end(); it++) {
 
					const CargoPacket *cp = *it;
 
					if (cp->SourceStation() != station_id) {
 
						bool added = false;
 

	
 
						/* Enable the expand/hide button for this cargo type */
 
						SetBit(*transfers, i);
 

	
 
						/* Don't add cargo lines if not expanded */
 
						if (!HasBit(this->cargo, i)) break;
 

	
 
						/* Check if we already have this source in the list */
 
						for (CargoDataList::iterator jt(cargolist->begin()); jt != cargolist->end(); jt++) {
 
							CargoData *cd = &(*jt);
 
							if (cd->cargo == i && cd->source == cp->SourceStation()) {
 
								cd->count += cp->Count();
 
								added = true;
 
								break;
 
							}
 
						}
 

	
 
						if (!added) cargolist->push_back(CargoData(i, cp->SourceStation(), cp->Count()));
 
					}
 
				}
 
			}
 
		}
 
	}
 

	
 
	/** Draw waiting cargo.
 
	 * @param r Rectangle of the widget.
 
	 * @param cargolist Cargo, ordered by type and destination.
 
	 * @param transfers Bitmask for cargoes that are transfered.
 
	 */
 
	void DrawWaitingCargo(const Rect &r, const CargoDataList &cargolist, uint32 transfers) const
 
	{
 
		int y = r.top + WD_FRAMERECT_TOP;
 
		int pos = this->vscroll.GetPosition();
 

	
 
		const Station *st = Station::Get(this->window_number);
 
		if (--pos < 0) {
 
			StringID str = STR_JUST_NOTHING;
 
			for (CargoID i = 0; i < NUM_CARGO; i++) {
 
				if (!st->goods[i].cargo.Empty()) str = STR_EMPTY;
 
			}
 
			SetDParam(0, str);
 
			DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_STATION_VIEW_WAITING_TITLE);
 
			y += FONT_HEIGHT_NORMAL;
 
		}
 

	
 
		bool rtl = _dynlang.text_dir == TD_RTL;
 
		int text_left    = rtl ? r.left + this->expand_shrink_width : r.left + WD_FRAMERECT_LEFT;
 
		int text_right   = rtl ? r.right - WD_FRAMERECT_LEFT : r.right - this->expand_shrink_width;
 
		int shrink_left  = rtl ? r.left + WD_FRAMERECT_LEFT : r.right - this->expand_shrink_width + WD_FRAMERECT_LEFT;
 
		int shrink_right = rtl ? r.left + this->expand_shrink_width - WD_FRAMERECT_RIGHT : r.right - WD_FRAMERECT_RIGHT;
 

	
 

	
 
		int maxrows = this->vscroll.GetCapacity();
 
		for (CargoDataList::const_iterator it = cargolist.begin(); it != cargolist.end() && pos > -maxrows; ++it) {
 
			if (--pos < 0) {
 
				const CargoData *cd = &(*it);
 
				if (cd->source == INVALID_STATION) {
 
					/* Heading */
 
					DrawCargoIcons(cd->cargo, cd->count, r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y);
 
					SetDParam(0, cd->cargo);
 
					SetDParam(1, cd->count);
 
					if (HasBit(transfers, cd->cargo)) {
 
						/* This cargo has transfers waiting so show the expand or shrink 'button' */
 
						const char *sym = HasBit(this->cargo, cd->cargo) ? "-" : "+";
 
						DrawString(text_left, text_right, y, STR_STATION_VIEW_WAITING_CARGO, TC_FROMSTRING, SA_RIGHT);
 
						DrawString(shrink_left, shrink_right, y, sym, TC_YELLOW, SA_RIGHT);
 
					} else {
 
						DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_STATION_VIEW_WAITING_CARGO, TC_FROMSTRING, SA_RIGHT);
 
					}
 
				} else {
 
					SetDParam(0, cd->cargo);
 
					SetDParam(1, cd->count);
 
					SetDParam(2, cd->source);
 
					DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_STATION_VIEW_EN_ROUTE_FROM, TC_FROMSTRING, SA_RIGHT);
 
				}
 

	
 
				y += FONT_HEIGHT_NORMAL;
 
			}
 
		}
 
	}
 

	
 
	/** Draw accepted cargo in the #SVW_ACCEPTLIST widget.
 
	 * @param r Rectangle of the widget.
 
	 */
 
	void DrawAcceptedCargo(const Rect &r) const
 
	{
 
		char string[512];
 
		char *b = string;
 
		bool first = true;
 

	
 
		b = InlineString(b, STR_STATION_VIEW_ACCEPTS_CARGO);
 

	
 
		const Station *st = Station::Get(this->window_number);
 
		for (CargoID i = 0; i < NUM_CARGO; i++) {
 
			if (b >= lastof(string) - (1 + 2 * 4)) break; // ',' or ' ' and two calls to Utf8Encode()
 
			if (HasBit(st->goods[i].acceptance_pickup, GoodsEntry::ACCEPTANCE)) {
 
				if (first) {
 
					first = false;
 
				} else {
 
					/* Add a comma if this is not the first item */
 
					*b++ = ',';
 
					*b++ = ' ';
 
				}
 
				b = InlineString(b, CargoSpec::Get(i)->name);
 
			}
 
		}
 

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

	
 
		*b = '\0';
 

	
 
		/* Make sure we detect any buffer overflow */
 
		assert(b < endof(string));
 

	
 
		SetDParamStr(0, string);
 
		DrawStringMultiLine(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, r.bottom - WD_FRAMERECT_BOTTOM, STR_JUST_RAW_STRING);
 
	}
 

	
 
	/** Draw cargo ratings in the #SVW_ACCEPTLIST widget.
 
	 * @param r Rectangle of the widget.
 
	 */
 
	void DrawCargoRatings(const Rect &r) const
 
	{
 
		const Station *st = Station::Get(this->window_number);
 
		int y = r.top + WD_FRAMERECT_TOP;
 

	
 
		DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_STATION_VIEW_CARGO_RATINGS_TITLE);
 
		y += FONT_HEIGHT_NORMAL;
 

	
 
		const CargoSpec *cs;
 
		FOR_ALL_CARGOSPECS(cs) {
 
			const GoodsEntry *ge = &st->goods[cs->Index()];
 
			if (!HasBit(ge->acceptance_pickup, GoodsEntry::PICKUP)) continue;
 

	
 
			SetDParam(0, cs->name);
 
			SetDParam(2, ToPercent8(ge->rating));
 
			SetDParam(1, STR_CARGO_RATING_APPALLING + (ge->rating >> 5));
 
			DrawString(r.left + WD_FRAMERECT_LEFT + 6, r.right - WD_FRAMERECT_RIGHT - 6, y, STR_STATION_VIEW_CARGO_RATING);
 
			y += FONT_HEIGHT_NORMAL;
 
		}
 
	}
 

	
 
	void HandleCargoWaitingClick(int row)
 
	{
 
		if (row == 0) return;
 

	
 
		for (CargoID c = 0; c < NUM_CARGO; c++) {
 
			if (this->cargo_rows[c] == row) {
 
				ToggleBit(this->cargo, c);
 
				this->SetWidgetDirty(SVW_WAITING);
 
				break;
 
			}
 
		}
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case SVW_WAITING:
 
				this->HandleCargoWaitingClick((pt.y - this->GetWidget<NWidgetBase>(SVW_WAITING)->pos_y - WD_FRAMERECT_TOP) / FONT_HEIGHT_NORMAL + this->vscroll.GetPosition());
 
				break;
 

	
 
			case SVW_LOCATION:
 
				if (_ctrl_pressed) {
 
					ShowExtraViewPortWindow(Station::Get(this->window_number)->xy);
 
				} else {
 
					ScrollMainWindowToTile(Station::Get(this->window_number)->xy);
 
				}
 
				break;
 

	
 
			case SVW_RATINGS: {
 
				/* Swap between 'accepts' and 'ratings' view. */
 
				int height_change;
 
				NWidgetCore *nwi = this->GetWidget<NWidgetCore>(SVW_RATINGS);
 
				if (this->GetWidget<NWidgetCore>(SVW_RATINGS)->widget_data == STR_STATION_VIEW_RATINGS_BUTTON) {
 
					nwi->SetDataTip(STR_STATION_VIEW_ACCEPTS_BUTTON, STR_STATION_VIEW_ACCEPTS_TOOLTIP); // Switch to accepts view.
 
					height_change = ALH_RATING - ALH_ACCEPTS;
 
				} else {
 
					nwi->SetDataTip(STR_STATION_VIEW_RATINGS_BUTTON, STR_STATION_VIEW_RATINGS_TOOLTIP); // Switch to ratings view.
 
					height_change = ALH_ACCEPTS - ALH_RATING;
 
				}
 
				this->ReInit(0, height_change);
 
				break;
 
			}
 

	
 
			case SVW_RENAME:
 
				SetDParam(0, this->window_number);
 
				ShowQueryString(STR_STATION_NAME, STR_STATION_VIEW_RENAME_STATION_CAPTION, MAX_LENGTH_STATION_NAME_BYTES, MAX_LENGTH_STATION_NAME_PIXELS,
 
						this, CS_ALPHANUMERAL, QSF_ENABLE_DEFAULT);
 
				break;
 

	
 
			case SVW_TRAINS: { // Show a list of scheduled trains to this station
 
				const Station *st = Station::Get(this->window_number);
 
				ShowVehicleListWindow(st->owner, VEH_TRAIN, (StationID)this->window_number);
 
				break;
 
			}
 

	
 
			case SVW_ROADVEHS: { // Show a list of scheduled road-vehicles to this station
 
				const Station *st = Station::Get(this->window_number);
 
				ShowVehicleListWindow(st->owner, VEH_ROAD, (StationID)this->window_number);
 
				break;
 
			}
 

	
 
			case SVW_PLANES: { // Show a list of scheduled aircraft to this station
 
				const Station *st = Station::Get(this->window_number);
 
				/* Since oilrigs have no owners, show the scheduled aircraft of local company */
 
				Owner owner = (st->owner == OWNER_NONE) ? _local_company : st->owner;
 
				ShowVehicleListWindow(owner, VEH_AIRCRAFT, (StationID)this->window_number);
 
				break;
 
			}
 

	
 
			case SVW_SHIPS: { // Show a list of scheduled ships to this station
 
				const Station *st = Station::Get(this->window_number);
 
				/* Since oilrigs/bouys have no owners, show the scheduled ships of local company */
 
				Owner owner = (st->owner == OWNER_NONE) ? _local_company : st->owner;
 
				ShowVehicleListWindow(owner, VEH_SHIP, (StationID)this->window_number);
 
				break;
 
			}
 
		}
 
	}
 

	
 
	virtual void OnQueryTextFinished(char *str)
 
	{
 
		if (str == NULL) return;
 

	
 
		DoCommandP(0, this->window_number, 0, CMD_RENAME_STATION | CMD_MSG(STR_ERROR_CAN_T_RENAME_STATION), NULL, str);
 
	}
 

	
 
	virtual void OnResize()
 
	{
 
		this->vscroll.SetCapacity((this->GetWidget<NWidgetBase>(SVW_WAITING)->current_y - WD_FRAMERECT_TOP - WD_FRAMERECT_BOTTOM) / this->resize.step_height);
 
	}
 
};
 

	
 

	
 
static const WindowDesc _station_view_desc(
 
	WDP_AUTO, WDP_AUTO, 249, 110,
 
	WC_STATION_VIEW, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE,
 
	_nested_station_view_widgets, lengthof(_nested_station_view_widgets)
 
);
 

	
 
/**
 
 * Opens StationViewWindow for given station
 
 *
 
 * @param station station which window should be opened
 
 */
 
void ShowStationViewWindow(StationID station)
 
{
 
	AllocateWindowDescFront<StationViewWindow>(&_station_view_desc, station);
 
}
 

	
 
/** Struct containing TileIndex and StationID */
 
struct TileAndStation {
 
	TileIndex tile;    ///< TileIndex
 
	StationID station; ///< StationID
 
};
 

	
 
static SmallVector<TileAndStation, 8> _deleted_stations_nearby;
 
static SmallVector<StationID, 8> _stations_nearby_list;
 

	
 
/**
 
 * Add station on this tile to _stations_nearby_list if it's fully within the
 
 * station spread.
 
 * @param tile Tile just being checked
 
 * @param user_data Pointer to TileArea context
 
 * @tparam T the type of station to look for
 
 */
 
template <class T>
 
static bool AddNearbyStation(TileIndex tile, void *user_data)
 
{
 
	TileArea *ctx = (TileArea *)user_data;
 

	
 
	/* First check if there were deleted stations here */
 
	for (uint i = 0; i < _deleted_stations_nearby.Length(); i++) {
 
		TileAndStation *ts = _deleted_stations_nearby.Get(i);
 
		if (ts->tile == tile) {
 
			*_stations_nearby_list.Append() = _deleted_stations_nearby[i].station;
 
			_deleted_stations_nearby.Erase(ts);
 
			i--;
 
		}
 
	}
 

	
 
	/* Check if own station and if we stay within station spread */
 
	if (!IsTileType(tile, MP_STATION)) return false;
 

	
 
	StationID sid = GetStationIndex(tile);
 

	
 
	/* This station is (likely) a waypoint */
 
	if (!T::IsValidID(sid)) return false;
 

	
 
	T *st = T::Get(sid);
 
	if (st->owner != _local_company || _stations_nearby_list.Contains(sid)) return false;
 

	
 
	if (st->rect.BeforeAddRect(ctx->tile, ctx->w, ctx->h, StationRect::ADD_TEST)) {
 
		*_stations_nearby_list.Append() = sid;
 
	}
 

	
 
	return false; // We want to include *all* nearby stations
 
}
 

	
 
/**
 
 * Circulate around the to-be-built station to find stations we could join.
 
 * Make sure that only stations are returned where joining wouldn't exceed
 
 * station spread and are our own station.
 
 * @param ta Base tile area of the to-be-built station
 
 * @param distant_join Search for adjacent stations (false) or stations fully
 
 *                     within station spread
 
 * @tparam T the type of station to look for
 
 **/
 
template <class T>
 
static const T *FindStationsNearby(TileArea ta, bool distant_join)
 
{
 
	TileArea ctx = ta;
 

	
 
	_stations_nearby_list.Clear();
 
	_deleted_stations_nearby.Clear();
 

	
 
	/* Check the inside, to return, if we sit on another station */
 
	TILE_LOOP(t, ta.w, ta.h, ta.tile) {
 
		if (t < MapSize() && IsTileType(t, MP_STATION) && T::IsValidID(GetStationIndex(t))) return T::GetByTile(t);
 
	}
 

	
 
	/* Look for deleted stations */
 
	const BaseStation *st;
 
	FOR_ALL_BASE_STATIONS(st) {
 
		if (T::IsExpected(st) && !st->IsInUse() && st->owner == _local_company) {
 
			/* Include only within station spread (yes, it is strictly less than) */
 
			if (max(DistanceMax(ta.tile, st->xy), DistanceMax(TILE_ADDXY(ta.tile, ta.w - 1, ta.h - 1), st->xy)) < _settings_game.station.station_spread) {
 
				TileAndStation *ts = _deleted_stations_nearby.Append();
 
				ts->tile = st->xy;
 
				ts->station = st->index;
 

	
 
				/* Add the station when it's within where we're going to build */
 
				if (IsInsideBS(TileX(st->xy), TileX(ctx.tile), ctx.w) &&
 
						IsInsideBS(TileY(st->xy), TileY(ctx.tile), ctx.h)) {
 
					AddNearbyStation<T>(st->xy, &ctx);
 
				}
 
			}
 
		}
 
	}
 

	
 
	/* Only search tiles where we have a chance to stay within the station spread.
 
	 * The complete check needs to be done in the callback as we don't know the
 
	 * extent of the found station, yet. */
 
	if (distant_join && min(ta.w, ta.h) >= _settings_game.station.station_spread) return NULL;
 
	uint max_dist = distant_join ? _settings_game.station.station_spread - min(ta.w, ta.h) : 1;
 

	
 
	TileIndex tile = TILE_ADD(ctx.tile, TileOffsByDir(DIR_N));
 
	CircularTileSearch(&tile, max_dist, ta.w, ta.h, AddNearbyStation<T>, &ctx);
 

	
 
	return NULL;
 
}
 

	
 
enum JoinStationWidgets {
 
	JSW_WIDGET_CLOSEBOX = 0,
 
	JSW_WIDGET_CAPTION,
 
	JSW_PANEL,
 
	JSW_SCROLLBAR,
 
	JSW_RESIZEBOX,
 
};
 

	
 
static const NWidgetPart _nested_select_station_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN, JSW_WIDGET_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, JSW_WIDGET_CAPTION), SetDataTip(STR_JOIN_STATION_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_PANEL, COLOUR_DARK_GREEN, JSW_PANEL), SetResize(1, 0), EndContainer(),
 
		NWidget(NWID_VERTICAL),
 
			NWidget(WWT_SCROLLBAR, COLOUR_DARK_GREEN, JSW_SCROLLBAR),
 
			NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN, JSW_RESIZEBOX),
 
		EndContainer(),
 
	EndContainer(),
 
};
 

	
 
/**
 
 * Window for selecting stations/waypoints to (distant) join to.
 
 * @tparam T The type of station to join with
 
 */
 
template <class T>
 
struct SelectStationWindow : Window {
 
	CommandContainer select_station_cmd; ///< Command to build new station
 
	TileArea area; ///< Location of new station
 

	
 
	SelectStationWindow(const WindowDesc *desc, CommandContainer cmd, TileArea ta) :
 
		Window(),
 
		select_station_cmd(cmd),
 
		area(ta)
 
	{
 
		this->CreateNestedTree(desc);
 
		this->GetWidget<NWidgetCore>(JSW_WIDGET_CAPTION)->widget_data = T::EXPECTED_FACIL == FACIL_WAYPOINT ? STR_JOIN_WAYPOINT_CAPTION : STR_JOIN_STATION_CAPTION;
 
		this->FinishInitNested(desc, 0);
 
		this->OnInvalidateData(0);
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		if (widget != JSW_PANEL) return;
 

	
 
		/* Determine the widest string */
 
		Dimension d = GetStringBoundingBox(T::EXPECTED_FACIL == FACIL_WAYPOINT ? STR_JOIN_WAYPOINT_CREATE_SPLITTED_WAYPOINT : STR_JOIN_STATION_CREATE_SPLITTED_STATION);
 
		for (uint i = 0; i < _stations_nearby_list.Length(); i++) {
 
			const T *st = T::Get(_stations_nearby_list[i]);
 
			SetDParam(0, st->index);
 
			SetDParam(1, st->facilities);
 
			d = maxdim(d, GetStringBoundingBox(T::EXPECTED_FACIL == FACIL_WAYPOINT ? STR_STATION_LIST_WAYPOINT : STR_STATION_LIST_STATION));
 
		}
 

	
 
		resize->height = d.height;
 
		d.height *= 5;
 
		d.width += WD_FRAMERECT_RIGHT + WD_FRAMERECT_LEFT;
 
		d.height += WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
 
		*size = d;
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		if (widget != JSW_PANEL) return;
 

	
 
		uint y = r.top + WD_FRAMERECT_TOP;
 
		if (this->vscroll.GetPosition() == 0) {
 
			DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, T::EXPECTED_FACIL == FACIL_WAYPOINT ? STR_JOIN_WAYPOINT_CREATE_SPLITTED_WAYPOINT : STR_JOIN_STATION_CREATE_SPLITTED_STATION);
 
			y += this->resize.step_height;
 
		}
 

	
 
		for (uint i = max<uint>(1, this->vscroll.GetPosition()); i <= _stations_nearby_list.Length(); ++i, y += this->resize.step_height) {
 
			/* Don't draw anything if it extends past the end of the window. */
 
			if (i - this->vscroll.GetPosition() >= this->vscroll.GetCapacity()) break;
 

	
 
			const T *st = T::Get(_stations_nearby_list[i - 1]);
 
			SetDParam(0, st->index);
 
			SetDParam(1, st->facilities);
 
			DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, T::EXPECTED_FACIL == FACIL_WAYPOINT ? STR_STATION_LIST_WAYPOINT : STR_STATION_LIST_STATION);
 
		}
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		if (widget != JSW_PANEL) return;
 

	
 
		uint32 st_index = (pt.y - this->GetWidget<NWidgetBase>(JSW_PANEL)->pos_y - WD_FRAMERECT_TOP) / this->resize.step_height;
 
		bool distant_join = (st_index > 0);
 
		if (distant_join) st_index--;
 

	
 
		if (distant_join && st_index >= _stations_nearby_list.Length()) return;
 

	
 
		/* Insert station to be joined into stored command */
 
		SB(this->select_station_cmd.p2, 16, 16,
 
		   (distant_join ? _stations_nearby_list[st_index] : NEW_STATION));
 

	
 
		/* Execute stored Command */
 
		DoCommandP(&this->select_station_cmd);
 

	
 
		/* Close Window; this might cause double frees! */
 
		DeleteWindowById(WC_SELECT_STATION, 0);
 
	}
 

	
 
	virtual void OnTick()
 
	{
 
		if (_thd.dirty & 2) {
 
			_thd.dirty &= ~2;
 
			this->SetDirty();
 
		}
 
	}
 

	
 
	virtual void OnResize()
 
	{
 
		this->vscroll.SetCapacity((this->GetWidget<NWidgetBase>(JSW_PANEL)->current_y - WD_FRAMERECT_TOP - WD_FRAMERECT_BOTTOM) / this->resize.step_height);
 
	}
 

	
 
	virtual void OnInvalidateData(int data)
 
	{
 
		FindStationsNearby<T>(this->area, true);
 
		this->vscroll.SetCount(_stations_nearby_list.Length() + 1);
 
		this->SetDirty();
 
	}
 
};
 

	
 
static const WindowDesc _select_station_desc(
 
	WDP_AUTO, WDP_AUTO, 200, 180,
 
	WC_SELECT_STATION, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_RESIZABLE | WDF_CONSTRUCTION,
 
	_nested_select_station_widgets, lengthof(_nested_select_station_widgets)
 
);
 

	
 

	
 
/**
 
 * Check whether we need to show the station selection window.
 
 * @param cmd Command to build the station.
 
 * @param ta Tile area of the to-be-built station
 
 * @tparam T the type of station
 
 * @return whether we need to show the station selection window.
 
 */
 
template <class T>
 
static bool StationJoinerNeeded(CommandContainer cmd, TileArea ta)
 
{
 
	/* Only show selection if distant join is enabled in the settings */
 
	if (!_settings_game.station.distant_join_stations) return false;
 

	
 
	/* If a window is already opened and we didn't ctrl-click,
 
	 * return true (i.e. just flash the old window) */
 
	Window *selection_window = FindWindowById(WC_SELECT_STATION, 0);
 
	if (selection_window != NULL) {
 
		if (!_ctrl_pressed) return true;
 

	
 
		/* Abort current distant-join and start new one */
 
		delete selection_window;
 
		UpdateTileSelection();
 
	}
 

	
 
	/* only show the popup, if we press ctrl */
 
	if (!_ctrl_pressed) return false;
 

	
 
	/* Now check if we could build there */
 
	if (CmdFailed(DoCommand(&cmd, CommandFlagsToDCFlags(GetCommandFlags(cmd.cmd))))) return false;
 

	
 
	/* Test for adjacent station or station below selection.
 
	 * If adjacent-stations is disabled and we are building next to a station, do not show the selection window.
 
	 * but join the other station immediatelly. */
 
	const T *st = FindStationsNearby<T>(ta, false);
 
	return st == NULL && (_settings_game.station.adjacent_stations || _stations_nearby_list.Length() == 0);
 
}
 

	
 
/**
 
 * Show the station selection window when needed. If not, build the station.
 
 * @param cmd Command to build the station.
 
 * @param ta Area to build the station in
 
 * @tparam the class to find stations for
 
 */
 
template <class T>
 
void ShowSelectBaseStationIfNeeded(CommandContainer cmd, TileArea ta)
 
{
 
	if (StationJoinerNeeded<T>(cmd, ta)) {
 
		if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace();
 
		if (BringWindowToFrontById(WC_SELECT_STATION, 0)) return;
 
		new SelectStationWindow<T>(&_select_station_desc, cmd, ta);
 
	} else {
 
		DoCommandP(&cmd);
 
	}
 
}
 

	
 
/**
 
 * Show the station selection window when needed. If not, build the station.
 
 * @param cmd Command to build the station.
 
 * @param ta Area to build the station in
 
 */
 
void ShowSelectStationIfNeeded(CommandContainer cmd, TileArea ta)
 
{
 
	ShowSelectBaseStationIfNeeded<Station>(cmd, ta);
 
}
 

	
 
/**
 
 * Show the waypoint selection window when needed. If not, build the waypoint.
 
 * @param cmd Command to build the waypoint.
 
 * @param ta Area to build the waypoint in
 
 */
 
void ShowSelectWaypointIfNeeded(CommandContainer cmd, TileArea ta)
 
{
 
	ShowSelectBaseStationIfNeeded<Waypoint>(cmd, ta);
 
}
src/terraform_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file terraform_gui.cpp GUI related to terraforming the map. */
 

	
 
#include "stdafx.h"
 
#include "openttd.h"
 
#include "clear_map.h"
 
#include "company_func.h"
 
#include "company_base.h"
 
#include "gui.h"
 
#include "window_gui.h"
 
#include "window_func.h"
 
#include "viewport_func.h"
 
#include "gfx_func.h"
 
#include "command_func.h"
 
#include "signs_func.h"
 
#include "variables.h"
 
#include "functions.h"
 
#include "sound_func.h"
 
#include "base_station_base.h"
 
#include "unmovable_map.h"
 
#include "textbuf_gui.h"
 
#include "genworld.h"
 
#include "tree_map.h"
 
#include "landscape_type.h"
 
#include "tilehighlight_func.h"
 

	
 
#include "table/sprites.h"
 
#include "table/strings.h"
 

	
 
void CcTerraform(bool success, TileIndex tile, uint32 p1, uint32 p2)
 
{
 
	if (success) {
 
		SndPlayTileFx(SND_1F_SPLAT, tile);
 
	} else {
 
		extern TileIndex _terraform_err_tile;
 
		SetRedErrorSquare(_terraform_err_tile);
 
	}
 
}
 

	
 

	
 
/** Scenario editor command that generates desert areas */
 
static void GenerateDesertArea(TileIndex end, TileIndex start)
 
{
 
	int size_x, size_y;
 
	int sx = TileX(start);
 
	int sy = TileY(start);
 
	int ex = TileX(end);
 
	int ey = TileY(end);
 

	
 
	if (_game_mode != GM_EDITOR) return;
 

	
 
	if (ex < sx) Swap(ex, sx);
 
	if (ey < sy) Swap(ey, sy);
 
	size_x = (ex - sx) + 1;
 
	size_y = (ey - sy) + 1;
 

	
 
	_generating_world = true;
 
	TILE_LOOP(tile, size_x, size_y, TileXY(sx, sy)) {
 
		SetTropicZone(tile, (_ctrl_pressed) ? TROPICZONE_NORMAL : TROPICZONE_DESERT);
 
		DoCommandP(tile, 0, 0, CMD_LANDSCAPE_CLEAR);
 
		MarkTileDirtyByTile(tile);
 
	}
 
	_generating_world = false;
 
}
 

	
 
/** Scenario editor command that generates rocky areas */
 
static void GenerateRockyArea(TileIndex end, TileIndex start)
 
{
 
	int size_x, size_y;
 
	bool success = false;
 
	int sx = TileX(start);
 
	int sy = TileY(start);
 
	int ex = TileX(end);
 
	int ey = TileY(end);
 

	
 
	if (_game_mode != GM_EDITOR) return;
 

	
 
	if (ex < sx) Swap(ex, sx);
 
	if (ey < sy) Swap(ey, sy);
 
	size_x = (ex - sx) + 1;
 
	size_y = (ey - sy) + 1;
 

	
 
	TILE_LOOP(tile, size_x, size_y, TileXY(sx, sy)) {
 
		switch (GetTileType(tile)) {
 
			case MP_TREES:
 
				if (GetTreeGround(tile) == TREE_GROUND_SHORE) continue;
 
			/* FALL THROUGH */
 
			case MP_CLEAR:
 
				MakeClear(tile, CLEAR_ROCKS, 3);
 
				break;
 

	
 
			default: continue;
 
		}
 
		MarkTileDirtyByTile(tile);
 
		success = true;
 
	}
 

	
 
	if (success) SndPlayTileFx(SND_1F_SPLAT, end);
 
}
 

	
 
/**
 
 * A central place to handle all X_AND_Y dragged GUI functions.
 
 * @param proc       Procedure related to the dragging
 
 * @param start_tile Begin of the dragging
 
 * @param end_tile   End of the dragging
 
 * @return Returns true if the action was found and handled, and false otherwise. This
 
 * allows for additional implements that are more local. For example X_Y drag
 
 * of convertrail which belongs in rail_gui.cpp and not terraform_gui.cpp
 
 **/
 
bool GUIPlaceProcDragXY(ViewportDragDropSelectionProcess proc, TileIndex start_tile, TileIndex end_tile)
 
{
 
	if (!_settings_game.construction.freeform_edges) {
 
		/* When end_tile is MP_VOID, the error tile will not be visible to the
 
		 * user. This happens when terraforming at the southern border. */
 
		if (TileX(end_tile) == MapMaxX()) end_tile += TileDiffXY(-1, 0);
 
		if (TileY(end_tile) == MapMaxY()) end_tile += TileDiffXY(0, -1);
 
	}
 

	
 
	switch (proc) {
 
		case DDSP_DEMOLISH_AREA:
 
			DoCommandP(end_tile, start_tile, 0, CMD_CLEAR_AREA | CMD_MSG(STR_ERROR_CAN_T_CLEAR_THIS_AREA), CcPlaySound10);
 
			break;
 
		case DDSP_RAISE_AND_LEVEL_AREA:
 
			DoCommandP(end_tile, start_tile, 1, CMD_LEVEL_LAND | CMD_MSG(STR_ERROR_CAN_T_RAISE_LAND_HERE), CcTerraform);
 
			break;
 
		case DDSP_LOWER_AND_LEVEL_AREA:
 
			DoCommandP(end_tile, start_tile, (uint32)-1, CMD_LEVEL_LAND | CMD_MSG(STR_ERROR_CAN_T_LOWER_LAND_HERE), CcTerraform);
 
			break;
 
		case DDSP_LEVEL_AREA:
 
			DoCommandP(end_tile, start_tile, 0, CMD_LEVEL_LAND | CMD_MSG(STR_ERROR_CAN_T_LEVEL_LAND_HERE), CcTerraform);
 
			break;
 
		case DDSP_CREATE_ROCKS:
 
			GenerateRockyArea(end_tile, start_tile);
 
			break;
 
		case DDSP_CREATE_DESERT:
 
			GenerateDesertArea(end_tile, start_tile);
 
			break;
 
		default:
 
			return false;
 
	}
 

	
 
	return true;
 
}
 

	
 
typedef void OnButtonClick(Window *w);
 

	
 
static const uint16 _terraform_keycodes[] = {
 
	'Q',
 
	'W',
 
	'E',
 
	'D',
 
	'U',
 
	'I',
 
	'O',
 
};
 

	
 
void CcPlaySound1E(bool success, TileIndex tile, uint32 p1, uint32 p2);
 

	
 
static void PlaceProc_BuyLand(TileIndex tile)
 
{
 
	DoCommandP(tile, 0, 0, CMD_PURCHASE_LAND_AREA | CMD_MSG(STR_ERROR_CAN_T_PURCHASE_THIS_LAND), CcPlaySound1E);
 
}
 

	
 
void PlaceProc_DemolishArea(TileIndex tile)
 
{
 
	VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_DEMOLISH_AREA);
 
}
 

	
 
static void PlaceProc_RaiseLand(TileIndex tile)
 
{
 
	VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_RAISE_AND_LEVEL_AREA);
 
}
 

	
 
static void PlaceProc_LowerLand(TileIndex tile)
 
{
 
	VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_LOWER_AND_LEVEL_AREA);
 
}
 

	
 
static void PlaceProc_LevelLand(TileIndex tile)
 
{
 
	VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_LEVEL_AREA);
 
}
 

	
 
/** Enum referring to the widgets of the terraform toolbar */
 
enum TerraformToolbarWidgets {
 
	TTW_CLOSEBOX = 0,                     ///< Close window button
 
	TTW_CAPTION,                          ///< Window caption
 
	TTW_STICKY,                           ///< Sticky window button
 
	TTW_SEPERATOR,                        ///< Thin seperator line between level land button and demolish button
 
	TTW_BUTTONS_START,                    ///< Start of pushable buttons
 
	TTW_LOWER_LAND = TTW_BUTTONS_START,   ///< Lower land button
 
	TTW_RAISE_LAND,                       ///< Raise land button
 
	TTW_LEVEL_LAND,                       ///< Level land button
 
	TTW_DEMOLISH,                         ///< Demolish aka dynamite button
 
	TTW_BUY_LAND,                         ///< Buy land button
 
	TTW_PLANT_TREES,                      ///< Plant trees button (note: opens seperate window, no place-push-button)
 
	TTW_PLACE_SIGN,                       ///< Place sign button
 
};
 

	
 
static void TerraformClick_Lower(Window *w)
 
{
 
	HandlePlacePushButton(w, TTW_LOWER_LAND, ANIMCURSOR_LOWERLAND, HT_POINT, PlaceProc_LowerLand);
 
}
 

	
 
static void TerraformClick_Raise(Window *w)
 
{
 
	HandlePlacePushButton(w, TTW_RAISE_LAND, ANIMCURSOR_RAISELAND, HT_POINT, PlaceProc_RaiseLand);
 
}
 

	
 
static void TerraformClick_Level(Window *w)
 
{
 
	HandlePlacePushButton(w, TTW_LEVEL_LAND, SPR_CURSOR_LEVEL_LAND, HT_POINT, PlaceProc_LevelLand);
 
}
 

	
 
static void TerraformClick_Dynamite(Window *w)
 
{
 
	HandlePlacePushButton(w, TTW_DEMOLISH, ANIMCURSOR_DEMOLISH, HT_RECT, PlaceProc_DemolishArea);
 
}
 

	
 
static void TerraformClick_BuyLand(Window *w)
 
{
 
	HandlePlacePushButton(w, TTW_BUY_LAND, SPR_CURSOR_BUY_LAND, HT_RECT, PlaceProc_BuyLand);
 
}
 

	
 
static void TerraformClick_Trees(Window *w)
 
{
 
	/* This button is NOT a place-push-button, so don't treat it as such */
 
	ShowBuildTreesToolbar();
 
}
 

	
 
static void TerraformClick_PlaceSign(Window *w)
 
{
 
	HandlePlacePushButton(w, TTW_PLACE_SIGN, SPR_CURSOR_SIGN, HT_RECT, PlaceProc_Sign);
 
}
 

	
 
static OnButtonClick * const _terraform_button_proc[] = {
 
	TerraformClick_Lower,
 
	TerraformClick_Raise,
 
	TerraformClick_Level,
 
	TerraformClick_Dynamite,
 
	TerraformClick_BuyLand,
 
	TerraformClick_Trees,
 
	TerraformClick_PlaceSign,
 
};
 

	
 
struct TerraformToolbarWindow : Window {
 
	TerraformToolbarWindow(const WindowDesc *desc, WindowNumber window_number) : Window()
 
	{
 
		this->InitNested(desc, window_number);
 
	}
 

	
 
	~TerraformToolbarWindow()
 
	{
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		if (widget >= TTW_BUTTONS_START) _terraform_button_proc[widget - TTW_BUTTONS_START](this);
 
	}
 

	
 
	virtual EventState OnKeyPress(uint16 key, uint16 keycode)
 
	{
 
		for (uint i = 0; i != lengthof(_terraform_keycodes); i++) {
 
			if (keycode == _terraform_keycodes[i]) {
 
				_terraform_button_proc[i](this);
 
				return ES_HANDLED;
 
			}
 
		}
 
		return ES_NOT_HANDLED;
 
	}
 

	
 
	virtual void OnPlaceObject(Point pt, TileIndex tile)
 
	{
 
		_place_proc(tile);
 
	}
 

	
 
	virtual void OnPlaceDrag(ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, Point pt)
 
	{
 
		VpSelectTilesWithMethod(pt.x, pt.y, select_method);
 
	}
 

	
 
	virtual void OnPlaceMouseUp(ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, Point pt, TileIndex start_tile, TileIndex end_tile)
 
	{
 
		if (pt.x != -1) {
 
			switch (select_proc) {
 
				default: NOT_REACHED();
 
				case DDSP_DEMOLISH_AREA:
 
				case DDSP_RAISE_AND_LEVEL_AREA:
 
				case DDSP_LOWER_AND_LEVEL_AREA:
 
				case DDSP_LEVEL_AREA:
 
					GUIPlaceProcDragXY(select_proc, start_tile, end_tile);
 
					break;
 
			}
 
		}
 
	}
 

	
 
	virtual void OnPlaceObjectAbort()
 
	{
 
		this->RaiseButtons();
 
	}
 
};
 

	
 
static const NWidgetPart _nested_terraform_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN, TTW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, TTW_CAPTION), SetDataTip(STR_LANDSCAPING_TOOLBAR, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_STICKYBOX, COLOUR_DARK_GREEN, TTW_STICKY),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, TTW_LOWER_LAND), SetMinimalSize(22,22),
 
								SetFill(false, true), SetDataTip(SPR_IMG_TERRAFORM_DOWN, STR_LANDSCAPING_TOOLTIP_LOWER_A_CORNER_OF_LAND),
 
								SetFill(0, 1), SetDataTip(SPR_IMG_TERRAFORM_DOWN, STR_LANDSCAPING_TOOLTIP_LOWER_A_CORNER_OF_LAND),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, TTW_RAISE_LAND), SetMinimalSize(22,22),
 
								SetFill(false, true), SetDataTip(SPR_IMG_TERRAFORM_UP, STR_LANDSCAPING_TOOLTIP_RAISE_A_CORNER_OF_LAND),
 
								SetFill(0, 1), SetDataTip(SPR_IMG_TERRAFORM_UP, STR_LANDSCAPING_TOOLTIP_RAISE_A_CORNER_OF_LAND),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, TTW_LEVEL_LAND), SetMinimalSize(22,22),
 
								SetFill(false, true), SetDataTip(SPR_IMG_LEVEL_LAND, STR_LANDSCAPING_LEVEL_LAND_TOOLTIP),
 
								SetFill(0, 1), SetDataTip(SPR_IMG_LEVEL_LAND, STR_LANDSCAPING_LEVEL_LAND_TOOLTIP),
 

	
 
		NWidget(WWT_PANEL, COLOUR_DARK_GREEN, TTW_SEPERATOR), SetMinimalSize(4, 22), EndContainer(),
 

	
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, TTW_DEMOLISH), SetMinimalSize(22,22),
 
								SetFill(false, true), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC),
 
								SetFill(0, 1), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, TTW_BUY_LAND), SetMinimalSize(22,22),
 
								SetFill(false, true), SetDataTip(SPR_IMG_BUY_LAND, STR_LANDSCAPING_TOOLTIP_PURCHASE_LAND),
 
								SetFill(0, 1), SetDataTip(SPR_IMG_BUY_LAND, STR_LANDSCAPING_TOOLTIP_PURCHASE_LAND),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, TTW_PLANT_TREES), SetMinimalSize(22,22),
 
								SetFill(false, true), SetDataTip(SPR_IMG_PLANTTREES, STR_SCENEDIT_TOOLBAR_PLANT_TREES),
 
								SetFill(0, 1), SetDataTip(SPR_IMG_PLANTTREES, STR_SCENEDIT_TOOLBAR_PLANT_TREES),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, TTW_PLACE_SIGN), SetMinimalSize(22,22),
 
								SetFill(false, true), SetDataTip(SPR_IMG_SIGN, STR_SCENEDIT_TOOLBAR_PLACE_SIGN),
 
								SetFill(0, 1), SetDataTip(SPR_IMG_SIGN, STR_SCENEDIT_TOOLBAR_PLACE_SIGN),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _terraform_desc(
 
	WDP_ALIGN_TBR, 22 + 36, 158, 36,
 
	WC_SCEN_LAND_GEN, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_STICKY_BUTTON | WDF_CONSTRUCTION,
 
	_nested_terraform_widgets, lengthof(_nested_terraform_widgets)
 
);
 

	
 
Window *ShowTerraformToolbar(Window *link)
 
{
 
	if (!Company::IsValidID(_local_company)) return NULL;
 

	
 
	Window *w = AllocateWindowDescFront<TerraformToolbarWindow>(&_terraform_desc, 0);
 
	if (link == NULL) return w;
 

	
 
	if (w == NULL) {
 
		w = FindWindowById(WC_SCEN_LAND_GEN, 0);
 
		if (w == NULL) return NULL;
 
	} else {
 
		w->top = 22;
 
		w->SetDirty();
 
	}
 

	
 
	/* Align the terraform toolbar under the main toolbar and put the linked
 
	 * toolbar to left of it
 
	 */
 
	link->top  = w->top;
 
	link->left = w->left - link->width;
 
	link->SetDirty();
 

	
 
	return w;
 
}
 

	
 
void ShowTerraformToolbarWithTool(uint16 key, uint16 keycode)
 
{
 
	Window *w = FindWindowById(WC_SCEN_LAND_GEN, 0);
 

	
 
	if (w == NULL) w = ShowTerraformToolbar(NULL);
 
	if (w == NULL) return;
 

	
 
	w->OnKeyPress(key, keycode);
 
}
 

	
 
static byte _terraform_size = 1;
 

	
 
/**
 
 * Raise/Lower a bigger chunk of land at the same time in the editor. When
 
 * raising get the lowest point, when lowering the highest point, and set all
 
 * tiles in the selection to that height.
 
 * @todo : Incorporate into game itself to allow for ingame raising/lowering of
 
 *         larger chunks at the same time OR remove altogether, as we have 'level land' ?
 
 * @param tile The top-left tile where the terraforming will start
 
 * @param mode 1 for raising, 0 for lowering land
 
 */
 
static void CommonRaiseLowerBigLand(TileIndex tile, int mode)
 
{
 
	int sizex, sizey;
 
	uint h;
 

	
 
	if (_terraform_size == 1) {
 
		StringID msg =
 
			mode ? STR_ERROR_CAN_T_RAISE_LAND_HERE : STR_ERROR_CAN_T_LOWER_LAND_HERE;
 

	
 
		DoCommandP(tile, SLOPE_N, (uint32)mode, CMD_TERRAFORM_LAND | CMD_MSG(msg), CcTerraform);
 
	} else {
 
		assert(_terraform_size != 0);
 
		/* check out for map overflows */
 
		sizex = min(MapSizeX() - TileX(tile), _terraform_size);
 
		sizey = min(MapSizeY() - TileY(tile), _terraform_size);
 

	
 
		if (sizex == 0 || sizey == 0) return;
 

	
 
		SndPlayTileFx(SND_1F_SPLAT, tile);
 

	
 
		if (mode != 0) {
 
			/* Raise land */
 
			h = 15; // XXX - max height
 
			TILE_LOOP(tile2, sizex, sizey, tile) {
 
				h = min(h, TileHeight(tile2));
 
			}
 
		} else {
 
			/* Lower land */
 
			h = 0;
 
			TILE_LOOP(tile2, sizex, sizey, tile) {
 
				h = max(h, TileHeight(tile2));
 
			}
 
		}
 

	
 
		TILE_LOOP(tile2, sizex, sizey, tile) {
 
			if (TileHeight(tile2) == h) {
 
				DoCommandP(tile2, SLOPE_N, (uint32)mode, CMD_TERRAFORM_LAND);
 
			}
 
		}
 
	}
 
}
 

	
 
static void PlaceProc_RaiseBigLand(TileIndex tile)
 
{
 
	CommonRaiseLowerBigLand(tile, 1);
 
}
 

	
 
static void PlaceProc_LowerBigLand(TileIndex tile)
 
{
 
	CommonRaiseLowerBigLand(tile, 0);
 
}
 

	
 
static void PlaceProc_RockyArea(TileIndex tile)
 
{
 
	VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_CREATE_ROCKS);
 
}
 

	
 
static void PlaceProc_LightHouse(TileIndex tile)
 
{
 
	/* not flat || not(trees || clear without bridge above) */
 
	if (GetTileSlope(tile, NULL) != SLOPE_FLAT || !(IsTileType(tile, MP_TREES) || (IsTileType(tile, MP_CLEAR) && !IsBridgeAbove(tile)))) {
 
		return;
 
	}
 

	
 
	MakeLighthouse(tile);
 
	MarkTileDirtyByTile(tile);
 
	SndPlayTileFx(SND_1F_SPLAT, tile);
 
}
 

	
 
static void PlaceProc_Transmitter(TileIndex tile)
 
{
 
	/* not flat || not(trees || clear without bridge above) */
 
	if (GetTileSlope(tile, NULL) != SLOPE_FLAT || !(IsTileType(tile, MP_TREES) || (IsTileType(tile, MP_CLEAR) && !IsBridgeAbove(tile)))) {
 
		return;
 
	}
 

	
 
	MakeTransmitter(tile);
 
	MarkTileDirtyByTile(tile);
 
	SndPlayTileFx(SND_1F_SPLAT, tile);
 
}
 

	
 
static void PlaceProc_DesertArea(TileIndex tile)
 
{
 
	VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_CREATE_DESERT);
 
}
 

	
 
static const int8 _multi_terraform_coords[][2] = {
 
	{  0, -2},
 
	{  4,  0}, { -4,  0}, {  0,  2},
 
	{ -8,  2}, { -4,  4}, {  0,  6}, {  4,  4}, {  8,  2},
 
	{-12,  0}, { -8, -2}, { -4, -4}, {  0, -6}, {  4, -4}, {  8, -2}, { 12,  0},
 
	{-16,  2}, {-12,  4}, { -8,  6}, { -4,  8}, {  0, 10}, {  4,  8}, {  8,  6}, { 12,  4}, { 16,  2},
 
	{-20,  0}, {-16, -2}, {-12, -4}, { -8, -6}, { -4, -8}, {  0,-10}, {  4, -8}, {  8, -6}, { 12, -4}, { 16, -2}, { 20,  0},
 
	{-24,  2}, {-20,  4}, {-16,  6}, {-12,  8}, { -8, 10}, { -4, 12}, {  0, 14}, {  4, 12}, {  8, 10}, { 12,  8}, { 16,  6}, { 20,  4}, { 24,  2},
 
	{-28,  0}, {-24, -2}, {-20, -4}, {-16, -6}, {-12, -8}, { -8,-10}, { -4,-12}, {  0,-14}, {  4,-12}, {  8,-10}, { 12, -8}, { 16, -6}, { 20, -4}, { 24, -2}, { 28,  0},
 
};
 

	
 
/** Enum referring to the widgets of the editor terraform toolbar */
 
enum EditorTerraformToolbarWidgets {
 
	ETTW_START = 0,                        ///< Used for iterations
 
	ETTW_CLOSEBOX = ETTW_START,            ///< Close window button
 
	ETTW_CAPTION,                          ///< Window caption
 
	ETTW_STICKY,                           ///< Sticky window button
 
	ETTW_BACKGROUND,                       ///< Background of the lower part of the window
 
	ETTW_DOTS,                             ///< Invisible widget for rendering the terraform size on.
 
	ETTW_BUTTONS_START,                    ///< Start of pushable buttons
 
	ETTW_DEMOLISH = ETTW_BUTTONS_START,    ///< Demolish aka dynamite button
 
	ETTW_LOWER_LAND,                       ///< Lower land button
 
	ETTW_RAISE_LAND,                       ///< Raise land button
 
	ETTW_LEVEL_LAND,                       ///< Level land button
 
	ETTW_PLACE_ROCKS,                      ///< Place rocks button
 
	ETTW_PLACE_DESERT_LIGHTHOUSE,          ///< Place desert button (in tropical climate) / place lighthouse button (else)
 
	ETTW_PLACE_TRANSMITTER,                ///< Place transmitter button
 
	ETTW_BUTTONS_END,                      ///< End of pushable buttons
 
	ETTW_INCREASE_SIZE = ETTW_BUTTONS_END, ///< Upwards arrow button to increase terraforming size
 
	ETTW_DECREASE_SIZE,                    ///< Downwards arrow button to decrease terraforming size
 
	ETTW_NEW_SCENARIO,                     ///< Button for generating a new scenario
 
	ETTW_RESET_LANDSCAPE,                  ///< Button for removing all company-owned property
 
};
 

	
 
static const NWidgetPart _nested_scen_edit_land_gen_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN, ETTW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, ETTW_CAPTION), SetDataTip(STR_TERRAFORM_TOOLBAR_LAND_GENERATION_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_STICKYBOX, COLOUR_DARK_GREEN, ETTW_STICKY),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_DARK_GREEN, ETTW_BACKGROUND),
 
		NWidget(NWID_HORIZONTAL), SetPadding(2, 2, 7, 2),
 
			NWidget(NWID_SPACER), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
			NWidget(WWT_IMGBTN, COLOUR_GREY, ETTW_DEMOLISH), SetMinimalSize(22, 22),
 
										SetFill(false, true), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC),
 
										SetFill(0, 1), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC),
 
			NWidget(WWT_IMGBTN, COLOUR_GREY, ETTW_LOWER_LAND), SetMinimalSize(22, 22),
 
										SetFill(false, true), SetDataTip(SPR_IMG_TERRAFORM_DOWN, STR_LANDSCAPING_TOOLTIP_LOWER_A_CORNER_OF_LAND),
 
										SetFill(0, 1), SetDataTip(SPR_IMG_TERRAFORM_DOWN, STR_LANDSCAPING_TOOLTIP_LOWER_A_CORNER_OF_LAND),
 
			NWidget(WWT_IMGBTN, COLOUR_GREY, ETTW_RAISE_LAND), SetMinimalSize(22, 22),
 
										SetFill(false, true), SetDataTip(SPR_IMG_TERRAFORM_UP, STR_LANDSCAPING_TOOLTIP_RAISE_A_CORNER_OF_LAND),
 
										SetFill(0, 1), SetDataTip(SPR_IMG_TERRAFORM_UP, STR_LANDSCAPING_TOOLTIP_RAISE_A_CORNER_OF_LAND),
 
			NWidget(WWT_IMGBTN, COLOUR_GREY, ETTW_LEVEL_LAND), SetMinimalSize(22, 22),
 
										SetFill(false, true), SetDataTip(SPR_IMG_LEVEL_LAND, STR_LANDSCAPING_LEVEL_LAND_TOOLTIP),
 
										SetFill(0, 1), SetDataTip(SPR_IMG_LEVEL_LAND, STR_LANDSCAPING_LEVEL_LAND_TOOLTIP),
 
			NWidget(WWT_IMGBTN, COLOUR_GREY, ETTW_PLACE_ROCKS), SetMinimalSize(22, 22),
 
										SetFill(false, true), SetDataTip(SPR_IMG_ROCKS, STR_TERRAFORM_TOOLTIP_PLACE_ROCKY_AREAS_ON_LANDSCAPE),
 
										SetFill(0, 1), SetDataTip(SPR_IMG_ROCKS, STR_TERRAFORM_TOOLTIP_PLACE_ROCKY_AREAS_ON_LANDSCAPE),
 
			NWidget(WWT_IMGBTN, COLOUR_GREY, ETTW_PLACE_DESERT_LIGHTHOUSE), SetMinimalSize(22, 22),
 
										SetFill(false, true), SetDataTip(SPR_IMG_LIGHTHOUSE_DESERT, STR_NULL),
 
										SetFill(0, 1), SetDataTip(SPR_IMG_LIGHTHOUSE_DESERT, STR_NULL),
 
			NWidget(WWT_IMGBTN, COLOUR_GREY, ETTW_PLACE_TRANSMITTER), SetMinimalSize(23, 22),
 
										SetFill(false, true), SetDataTip(SPR_IMG_TRANSMITTER, STR_TERRAFORM_TOOLTIP_PLACE_TRANSMITTER),
 
			NWidget(NWID_SPACER), SetFill(true, false),
 
										SetFill(0, 1), SetDataTip(SPR_IMG_TRANSMITTER, STR_TERRAFORM_TOOLTIP_PLACE_TRANSMITTER),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
		EndContainer(),
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(NWID_SPACER), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
			NWidget(WWT_EMPTY, COLOUR_DARK_GREEN, ETTW_DOTS), SetMinimalSize(59, 31), SetDataTip(STR_EMPTY, STR_NULL),
 
			NWidget(NWID_SPACER), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
			NWidget(NWID_VERTICAL),
 
				NWidget(NWID_SPACER), SetFill(false, true),
 
				NWidget(NWID_SPACER), SetFill(0, 1),
 
				NWidget(WWT_IMGBTN, COLOUR_GREY, ETTW_INCREASE_SIZE), SetMinimalSize(12, 12), SetDataTip(SPR_ARROW_UP, STR_TERRAFORM_TOOLTIP_INCREASE_SIZE_OF_LAND_AREA),
 
				NWidget(NWID_SPACER), SetMinimalSize(0, 1),
 
				NWidget(WWT_IMGBTN, COLOUR_GREY, ETTW_DECREASE_SIZE), SetMinimalSize(12, 12), SetDataTip(SPR_ARROW_DOWN, STR_TERRAFORM_TOOLTIP_DECREASE_SIZE_OF_LAND_AREA),
 
				NWidget(NWID_SPACER), SetFill(false, true),
 
				NWidget(NWID_SPACER), SetFill(0, 1),
 
			EndContainer(),
 
			NWidget(NWID_SPACER), SetMinimalSize(2, 0),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 6),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, ETTW_NEW_SCENARIO), SetMinimalSize(160, 12),
 
								SetFill(true, false), SetDataTip(STR_TERRAFORM_SE_NEW_WORLD, STR_TERRAFORM_TOOLTIP_GENERATE_RANDOM_LAND), SetPadding(0, 2, 0, 2),
 
								SetFill(1, 0), SetDataTip(STR_TERRAFORM_SE_NEW_WORLD, STR_TERRAFORM_TOOLTIP_GENERATE_RANDOM_LAND), SetPadding(0, 2, 0, 2),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, ETTW_RESET_LANDSCAPE), SetMinimalSize(160, 12),
 
								SetFill(true, false), SetDataTip(STR_TERRAFORM_RESET_LANDSCAPE, STR_TERRAFORM_RESET_LANDSCAPE_TOOLTIP), SetPadding(1, 2, 2, 2),
 
								SetFill(1, 0), SetDataTip(STR_TERRAFORM_RESET_LANDSCAPE, STR_TERRAFORM_RESET_LANDSCAPE_TOOLTIP), SetPadding(1, 2, 2, 2),
 
	EndContainer(),
 
};
 

	
 
/**
 
 * @todo Merge with terraform_gui.cpp (move there) after I have cooled down at its braindeadness
 
 * and changed OnButtonClick to include the widget as well in the function declaration. Post 0.4.0 - Darkvater
 
 */
 
static void EditorTerraformClick_Dynamite(Window *w)
 
{
 
	HandlePlacePushButton(w, ETTW_DEMOLISH, ANIMCURSOR_DEMOLISH, HT_RECT, PlaceProc_DemolishArea);
 
}
 

	
 
static void EditorTerraformClick_LowerBigLand(Window *w)
 
{
 
	HandlePlacePushButton(w, ETTW_LOWER_LAND, ANIMCURSOR_LOWERLAND, HT_POINT, PlaceProc_LowerBigLand);
 
}
 

	
 
static void EditorTerraformClick_RaiseBigLand(Window *w)
 
{
 
	HandlePlacePushButton(w, ETTW_RAISE_LAND, ANIMCURSOR_RAISELAND, HT_POINT, PlaceProc_RaiseBigLand);
 
}
 

	
 
static void EditorTerraformClick_LevelLand(Window *w)
 
{
 
	HandlePlacePushButton(w, ETTW_LEVEL_LAND, SPR_CURSOR_LEVEL_LAND, HT_POINT, PlaceProc_LevelLand);
 
}
 

	
 
static void EditorTerraformClick_RockyArea(Window *w)
 
{
 
	HandlePlacePushButton(w, ETTW_PLACE_ROCKS, SPR_CURSOR_ROCKY_AREA, HT_RECT, PlaceProc_RockyArea);
 
}
 

	
 
static void EditorTerraformClick_DesertLightHouse(Window *w)
 
{
 
	HandlePlacePushButton(w, ETTW_PLACE_DESERT_LIGHTHOUSE, SPR_CURSOR_LIGHTHOUSE, HT_RECT, (_settings_game.game_creation.landscape == LT_TROPIC) ? PlaceProc_DesertArea : PlaceProc_LightHouse);
 
}
 

	
 
static void EditorTerraformClick_Transmitter(Window *w)
 
{
 
	HandlePlacePushButton(w, ETTW_PLACE_TRANSMITTER, SPR_CURSOR_TRANSMITTER, HT_RECT, PlaceProc_Transmitter);
 
}
 

	
 
static const uint16 _editor_terraform_keycodes[] = {
 
	'D',
 
	'Q',
 
	'W',
 
	'E',
 
	'R',
 
	'T',
 
	'Y'
 
};
 

	
 
typedef void OnButtonClick(Window *w);
 
static OnButtonClick * const _editor_terraform_button_proc[] = {
 
	EditorTerraformClick_Dynamite,
 
	EditorTerraformClick_LowerBigLand,
 
	EditorTerraformClick_RaiseBigLand,
 
	EditorTerraformClick_LevelLand,
 
	EditorTerraformClick_RockyArea,
 
	EditorTerraformClick_DesertLightHouse,
 
	EditorTerraformClick_Transmitter
 
};
 

	
 

	
 
/** Callback function for the scenario editor 'reset landscape' confirmation window
 
 * @param w Window unused
 
 * @param confirmed boolean value, true when yes was clicked, false otherwise */
 
static void ResetLandscapeConfirmationCallback(Window *w, bool confirmed)
 
{
 
	if (confirmed) {
 
		/* Set generating_world to true to get instant-green grass after removing
 
		 * company property. */
 
		_generating_world = true;
 

	
 
		/* Delete all companies */
 
		Company *c;
 
		FOR_ALL_COMPANIES(c) {
 
			ChangeOwnershipOfCompanyItems(c->index, INVALID_OWNER);
 
			delete c;
 
		}
 

	
 
		_generating_world = false;
 

	
 
		/* Delete all station signs */
 
		BaseStation *st;
 
		FOR_ALL_BASE_STATIONS(st) {
 
			/* There can be buoys, remove them */
 
			if (IsBuoyTile(st->xy)) DoCommand(st->xy, 0, 0, DC_EXEC | DC_BANKRUPT, CMD_LANDSCAPE_CLEAR);
 
			if (!st->IsInUse()) delete st;
 
		}
 

	
 
		MarkWholeScreenDirty();
 
	}
 
}
 

	
 
struct ScenarioEditorLandscapeGenerationWindow : Window {
 
	ScenarioEditorLandscapeGenerationWindow(const WindowDesc *desc, WindowNumber window_number) : Window()
 
	{
 
		this->InitNested(desc, window_number);
 
		this->GetWidget<NWidgetCore>(ETTW_PLACE_DESERT_LIGHTHOUSE)->tool_tip = (_settings_game.game_creation.landscape == LT_TROPIC) ? STR_TERRAFORM_TOOLTIP_DEFINE_DESERT_AREA : STR_TERRAFORM_TOOLTIP_PLACE_LIGHTHOUSE;
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 

	
 
		if (this->IsWidgetLowered(ETTW_LOWER_LAND) || this->IsWidgetLowered(ETTW_RAISE_LAND)) { // change area-size if raise/lower corner is selected
 
			SetTileSelectSize(_terraform_size, _terraform_size);
 
		}
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		if (widget != ETTW_DOTS) return;
 

	
 
		int center_x = (r.left + r.right + 1) / 2;
 
		int center_y = (r.top + r.bottom + 1) / 2;
 

	
 
		int n = _terraform_size * _terraform_size;
 
		const int8 *coords = &_multi_terraform_coords[0][0];
 

	
 
		assert(n != 0);
 
		do {
 
			DrawSprite(SPR_WHITE_POINT, PAL_NONE, center_x + coords[0], center_y + coords[1]);
 
			coords += 2;
 
		} while (--n);
 
	}
 

	
 
	virtual EventState OnKeyPress(uint16 key, uint16 keycode)
 
	{
 
		for (uint i = 0; i != lengthof(_editor_terraform_keycodes); i++) {
 
			if (keycode == _editor_terraform_keycodes[i]) {
 
				_editor_terraform_button_proc[i](this);
 
				return ES_HANDLED;
 
			}
 
		}
 
		return ES_NOT_HANDLED;
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		if (IsInsideMM(widget, ETTW_BUTTONS_START, ETTW_BUTTONS_END)) {
 
			_editor_terraform_button_proc[widget - ETTW_BUTTONS_START](this);
 
		} else {
 
			switch (widget) {
 
				case ETTW_INCREASE_SIZE:
 
				case ETTW_DECREASE_SIZE: { // Increase/Decrease terraform size
 
					int size = (widget == ETTW_INCREASE_SIZE) ? 1 : -1;
 
					this->HandleButtonClick(widget);
 
					size += _terraform_size;
 

	
 
					if (!IsInsideMM(size, 1, 8 + 1)) return;
 
					_terraform_size = size;
 

	
 
					SndPlayFx(SND_15_BEEP);
 
					this->SetDirty();
 
				} break;
 
				case ETTW_NEW_SCENARIO: // gen random land
 
					this->HandleButtonClick(widget);
 
					ShowCreateScenario();
 
					break;
 
				case ETTW_RESET_LANDSCAPE: // Reset landscape
 
					ShowQuery(
 
						STR_QUERY_RESET_LANDSCAPE_CAPTION,
 
						STR_RESET_LANDSCAPE_CONFIRMATION_TEXT,
 
						NULL,
 
						ResetLandscapeConfirmationCallback);
 
					break;
 
			}
 
		}
 
	}
 

	
 
	virtual void OnTimeout()
 
	{
 
		for (uint i = ETTW_START; i < this->nested_array_size; i++) {
 
			if (i == ETTW_BUTTONS_START) i = ETTW_BUTTONS_END; // skip the buttons
 
			if (this->IsWidgetLowered(i)) {
 
				this->RaiseWidget(i);
 
				this->SetWidgetDirty(i);
 
			}
 
		}
 
	}
 

	
 
	virtual void OnPlaceObject(Point pt, TileIndex tile)
 
	{
 
		_place_proc(tile);
 
	}
 

	
 
	virtual void OnPlaceDrag(ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, Point pt)
 
	{
 
		VpSelectTilesWithMethod(pt.x, pt.y, select_method);
 
	}
 

	
 
	virtual void OnPlaceMouseUp(ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, Point pt, TileIndex start_tile, TileIndex end_tile)
 
	{
 
		if (pt.x != -1) {
 
			switch (select_proc) {
 
				default: NOT_REACHED();
 
				case DDSP_CREATE_ROCKS:
 
				case DDSP_CREATE_DESERT:
 
				case DDSP_RAISE_AND_LEVEL_AREA:
 
				case DDSP_LOWER_AND_LEVEL_AREA:
 
				case DDSP_LEVEL_AREA:
 
				case DDSP_DEMOLISH_AREA:
 
					GUIPlaceProcDragXY(select_proc, start_tile, end_tile);
 
					break;
 
			}
 
		}
 
	}
 

	
 
	virtual void OnPlaceObjectAbort()
 
	{
 
		this->RaiseButtons();
 
		this->SetDirty();
 
	}
 
};
 

	
 
static const WindowDesc _scen_edit_land_gen_desc(
 
	WDP_AUTO, WDP_AUTO, 163, 103,
 
	WC_SCEN_LAND_GEN, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_STICKY_BUTTON | WDF_CONSTRUCTION,
 
	_nested_scen_edit_land_gen_widgets, lengthof(_nested_scen_edit_land_gen_widgets)
 
);
 

	
 
Window *ShowEditorTerraformToolbar()
 
{
 
	return AllocateWindowDescFront<ScenarioEditorLandscapeGenerationWindow>(&_scen_edit_land_gen_desc, 0);
 
}
 

	
 
void ShowEditorTerraformToolbarWithTool(uint16 key, uint16 keycode)
 
{
 
	Window *w = FindWindowById(WC_SCEN_LAND_GEN, 0);
 

	
 
	if (w == NULL) w = ShowEditorTerraformToolbar();
 
	if (w == NULL) return;
 

	
 
	w->OnKeyPress(key, keycode);
 
}
src/toolbar_gui.cpp
Show inline comments
 
@@ -149,1454 +149,1454 @@ public:
 
		return true;
 
	}
 

	
 
	uint Width() const
 
	{
 
		char buffer[512];
 
		CompanyID company = (CompanyID)result;
 
		SetDParam(0, company);
 
		SetDParam(1, company);
 
		GetString(buffer, STR_COMPANY_NAME_COMPANY_NUM, lastof(buffer));
 
		return GetStringBoundingBox(buffer).width + 19;
 
	}
 

	
 
	void Draw(int left, int right, int top, int bottom, bool sel, int bg_colour) const
 
	{
 
		CompanyID company = (CompanyID)result;
 
		bool rtl = _dynlang.text_dir == TD_RTL;
 

	
 
		/* It's possible the company is deleted while the dropdown is open */
 
		if (!Company::IsValidID(company)) return;
 

	
 
		DrawCompanyIcon(company, rtl ? right - 16 : left + 2, top + 1);
 

	
 
		SetDParam(0, company);
 
		SetDParam(1, company);
 
		TextColour col;
 
		if (this->greyed) {
 
			col = TC_GREY;
 
		} else {
 
			col = sel ? TC_WHITE : TC_BLACK;
 
		}
 
		DrawString(rtl ? left + 2 : left + 19, rtl ? right - 19 : right - 2, top, STR_COMPANY_NAME_COMPANY_NUM, col);
 
	}
 
};
 

	
 
/**
 
 * Pop up a generic text only menu.
 
 */
 
static void PopupMainToolbMenu(Window *w, int widget, StringID string, int count)
 
{
 
	DropDownList *list = new DropDownList();
 
	for (int i = 0; i < count; i++) {
 
		list->push_back(new DropDownListStringItem(string + i, i, false));
 
	}
 
	ShowDropDownList(w, list, 0, widget, 140, true, true);
 
	SndPlayFx(SND_15_BEEP);
 
}
 

	
 
/** Enum for the Company Toolbar's network related buttons */
 
enum {
 
	CTMN_CLIENT_LIST = -1, ///< Show the client list
 
	CTMN_NEW_COMPANY = -2, ///< Create a new company
 
	CTMN_SPECTATE    = -3, ///< Become spectator
 
};
 

	
 
/**
 
 * Pop up a generic company list menu.
 
 */
 
static void PopupMainCompanyToolbMenu(Window *w, int widget, int grey = 0)
 
{
 
	DropDownList *list = new DropDownList();
 

	
 
#ifdef ENABLE_NETWORK
 
	if (widget == TBN_COMPANIES && _networking) {
 
		/* Add the client list button for the companies menu */
 
		list->push_back(new DropDownListStringItem(STR_NETWORK_COMPANY_LIST_CLIENT_LIST, CTMN_CLIENT_LIST, false));
 

	
 
		if (_local_company == COMPANY_SPECTATOR) {
 
			list->push_back(new DropDownListStringItem(STR_NETWORK_COMPANY_LIST_NEW_COMPANY, CTMN_NEW_COMPANY, NetworkMaxCompaniesReached()));
 
		} else {
 
			list->push_back(new DropDownListStringItem(STR_NETWORK_COMPANY_LIST_SPECTATE, CTMN_SPECTATE, NetworkMaxSpectatorsReached()));
 
		}
 
	}
 
#endif /* ENABLE_NETWORK */
 

	
 
	for (CompanyID c = COMPANY_FIRST; c < MAX_COMPANIES; c++) {
 
		if (!Company::IsValidID(c)) continue;
 
		list->push_back(new DropDownListCompanyItem(c, false, HasBit(grey, c)));
 
	}
 

	
 
	ShowDropDownList(w, list, _local_company == COMPANY_SPECTATOR ? CTMN_CLIENT_LIST : (int)_local_company, widget, 240, true, true);
 
	SndPlayFx(SND_15_BEEP);
 
}
 

	
 

	
 
static ToolbarMode _toolbar_mode;
 

	
 
static void SelectSignTool()
 
{
 
	if (_cursor.sprite == SPR_CURSOR_SIGN) {
 
		ResetObjectToPlace();
 
	} else {
 
		SetObjectToPlace(SPR_CURSOR_SIGN, PAL_NONE, HT_RECT, WC_MAIN_TOOLBAR, 0);
 
		_place_proc = PlaceProc_Sign;
 
	}
 
}
 

	
 
/* --- Pausing --- */
 

	
 
static void ToolbarPauseClick(Window *w)
 
{
 
	if (_networking && !_network_server) return; // only server can pause the game
 

	
 
	if (DoCommandP(0, PM_PAUSED_NORMAL, _pause_mode == PM_UNPAUSED, CMD_PAUSE)) SndPlayFx(SND_15_BEEP);
 
}
 

	
 
/* --- Fast forwarding --- */
 

	
 
static void ToolbarFastForwardClick(Window *w)
 
{
 
	_fast_forward ^= true;
 
	SndPlayFx(SND_15_BEEP);
 
}
 

	
 
/* --- Options button menu --- */
 

	
 
enum OptionMenuEntries {
 
	OME_GAMEOPTIONS,
 
	OME_DIFFICULTIES,
 
	OME_SETTINGS,
 
	OME_NEWGRFSETTINGS,
 
	OME_TRANSPARENCIES,
 
	OME_SHOW_TOWNNAMES,
 
	OME_SHOW_STATIONNAMES,
 
	OME_SHOW_WAYPOINTNAMES,
 
	OME_SHOW_SIGNS,
 
	OME_FULL_ANIMATION,
 
	OME_FULL_DETAILS,
 
	OME_TRANSPARENTBUILDINGS,
 
	OME_SHOW_STATIONSIGNS,
 
};
 

	
 
static void ToolbarOptionsClick(Window *w)
 
{
 
	DropDownList *list = new DropDownList();
 
	list->push_back(new DropDownListStringItem(STR_SETTINGS_MENU_GAME_OPTIONS,             OME_GAMEOPTIONS, false));
 
	list->push_back(new DropDownListStringItem(STR_SETTINGS_MENU_DIFFICULTY_SETTINGS,      OME_DIFFICULTIES, false));
 
	list->push_back(new DropDownListStringItem(STR_SETTINGS_MENU_CONFIG_SETTINGS,          OME_SETTINGS, false));
 
	list->push_back(new DropDownListStringItem(STR_SETTINGS_MENU_NEWGRF_SETTINGS,          OME_NEWGRFSETTINGS, false));
 
	list->push_back(new DropDownListStringItem(STR_SETTINGS_MENU_TRANSPARENCY_OPTIONS,     OME_TRANSPARENCIES, false));
 
	list->push_back(new DropDownListItem(-1, false));
 
	list->push_back(new DropDownListCheckedItem(STR_SETTINGS_MENU_TOWN_NAMES_DISPLAYED,    OME_SHOW_TOWNNAMES, false, HasBit(_display_opt, DO_SHOW_TOWN_NAMES)));
 
	list->push_back(new DropDownListCheckedItem(STR_SETTINGS_MENU_STATION_NAMES_DISPLAYED, OME_SHOW_STATIONNAMES, false, HasBit(_display_opt, DO_SHOW_STATION_NAMES)));
 
	list->push_back(new DropDownListCheckedItem(STR_SETTINGS_MENU_WAYPOINTS_DISPLAYED,     OME_SHOW_WAYPOINTNAMES, false, HasBit(_display_opt, DO_SHOW_WAYPOINT_NAMES)));
 
	list->push_back(new DropDownListCheckedItem(STR_SETTINGS_MENU_SIGNS_DISPLAYED,         OME_SHOW_SIGNS, false, HasBit(_display_opt, DO_SHOW_SIGNS)));
 
	list->push_back(new DropDownListCheckedItem(STR_SETTINGS_MENU_FULL_ANIMATION,          OME_FULL_ANIMATION, false, HasBit(_display_opt, DO_FULL_ANIMATION)));
 
	list->push_back(new DropDownListCheckedItem(STR_SETTINGS_MENU_FULL_DETAIL,             OME_FULL_DETAILS, false, HasBit(_display_opt, DO_FULL_DETAIL)));
 
	list->push_back(new DropDownListCheckedItem(STR_SETTINGS_MENU_TRANSPARENT_BUILDINGS,   OME_TRANSPARENTBUILDINGS, false, IsTransparencySet(TO_HOUSES)));
 
	list->push_back(new DropDownListCheckedItem(STR_SETTINGS_MENU_TRANSPARENT_SIGNS,       OME_SHOW_STATIONSIGNS, false, IsTransparencySet(TO_SIGNS)));
 

	
 
	ShowDropDownList(w, list, 0, TBN_SETTINGS, 140, true, true);
 
	SndPlayFx(SND_15_BEEP);
 
}
 

	
 
static void MenuClickSettings(int index)
 
{
 
	switch (index) {
 
		case OME_GAMEOPTIONS:          ShowGameOptions();                               return;
 
		case OME_DIFFICULTIES:         ShowGameDifficulty();                            return;
 
		case OME_SETTINGS:             ShowGameSettings();                              return;
 
		case OME_NEWGRFSETTINGS:       ShowNewGRFSettings(!_networking, true, true, &_grfconfig);   return;
 
		case OME_TRANSPARENCIES:       ShowTransparencyToolbar();                       break;
 

	
 
		case OME_SHOW_TOWNNAMES:       ToggleBit(_display_opt, DO_SHOW_TOWN_NAMES);     break;
 
		case OME_SHOW_STATIONNAMES:    ToggleBit(_display_opt, DO_SHOW_STATION_NAMES);  break;
 
		case OME_SHOW_WAYPOINTNAMES:   ToggleBit(_display_opt, DO_SHOW_WAYPOINT_NAMES); break;
 
		case OME_SHOW_SIGNS:           ToggleBit(_display_opt, DO_SHOW_SIGNS);          break;
 
		case OME_FULL_ANIMATION:       ToggleBit(_display_opt, DO_FULL_ANIMATION);      break;
 
		case OME_FULL_DETAILS:         ToggleBit(_display_opt, DO_FULL_DETAIL);         break;
 
		case OME_TRANSPARENTBUILDINGS: ToggleTransparency(TO_HOUSES);                   break;
 
		case OME_SHOW_STATIONSIGNS:    ToggleTransparency(TO_SIGNS);                    break;
 
	}
 
	MarkWholeScreenDirty();
 
}
 

	
 
/* --- Saving/loading button menu --- */
 

	
 
enum SaveLoadEditorMenuEntries {
 
	SLEME_SAVE_SCENARIO   = 0,
 
	SLEME_LOAD_SCENARIO,
 
	SLEME_LOAD_HEIGHTMAP,
 
	SLEME_EXIT_TOINTRO,
 
	SLEME_EXIT_GAME       = 5,
 
	SLEME_MENUCOUNT,
 
};
 

	
 
enum SaveLoadNormalMenuEntries {
 
	SLNME_SAVE_GAME   = 0,
 
	SLNME_LOAD_GAME,
 
	SLNME_EXIT_TOINTRO,
 
	SLNME_EXIT_GAME,
 
	SLNME_MENUCOUNT,
 
};
 

	
 
static void ToolbarSaveClick(Window *w)
 
{
 
	PopupMainToolbMenu(w, TBN_SAVEGAME, STR_FILE_MENU_SAVE_GAME, SLNME_MENUCOUNT);
 
}
 

	
 
static void ToolbarScenSaveOrLoad(Window *w)
 
{
 
	PopupMainToolbMenu(w, TBSE_SAVESCENARIO, STR_SCENEDIT_FILE_MENU_SAVE_SCENARIO, SLEME_MENUCOUNT);
 
}
 

	
 
static void MenuClickSaveLoad(int index = 0)
 
{
 
	if (_game_mode == GM_EDITOR) {
 
		switch (index) {
 
			case SLEME_SAVE_SCENARIO:  ShowSaveLoadDialog(SLD_SAVE_SCENARIO);  break;
 
			case SLEME_LOAD_SCENARIO:  ShowSaveLoadDialog(SLD_LOAD_SCENARIO);  break;
 
			case SLEME_LOAD_HEIGHTMAP: ShowSaveLoadDialog(SLD_LOAD_HEIGHTMAP); break;
 
			case SLEME_EXIT_TOINTRO:   AskExitToGameMenu();                    break;
 
			case SLEME_EXIT_GAME:      HandleExitGameRequest();                break;
 
		}
 
	} else {
 
		switch (index) {
 
			case SLNME_SAVE_GAME:      ShowSaveLoadDialog(SLD_SAVE_GAME); break;
 
			case SLNME_LOAD_GAME:      ShowSaveLoadDialog(SLD_LOAD_GAME); break;
 
			case SLNME_EXIT_TOINTRO:   AskExitToGameMenu();               break;
 
			case SLNME_EXIT_GAME:      HandleExitGameRequest();           break;
 
		}
 
	}
 
}
 

	
 
/* --- Map button menu --- */
 

	
 
enum MapMenuEntries {
 
	MME_SHOW_SMALLMAP        = 0,
 
	MME_SHOW_EXTRAVIEWPORTS,
 
	MME_SHOW_SIGNLISTS,
 
	MME_SHOW_TOWNDIRECTORY,    ///< This entry is only used in Editor mode
 
	MME_MENUCOUNT_NORMAL     = 3,
 
	MME_MENUCOUNT_EDITOR     = 4,
 
};
 

	
 
static void ToolbarMapClick(Window *w)
 
{
 
	PopupMainToolbMenu(w, TBN_SMALLMAP, STR_MAP_MENU_MAP_OF_WORLD, MME_MENUCOUNT_NORMAL);
 
}
 

	
 
static void ToolbarScenMapTownDir(Window *w)
 
{
 
	PopupMainToolbMenu(w, TBSE_SMALLMAP, STR_MAP_MENU_MAP_OF_WORLD, MME_MENUCOUNT_EDITOR);
 
}
 

	
 
static void MenuClickMap(int index)
 
{
 
	switch (index) {
 
		case MME_SHOW_SMALLMAP:       ShowSmallMap();            break;
 
		case MME_SHOW_EXTRAVIEWPORTS: ShowExtraViewPortWindow(); break;
 
		case MME_SHOW_SIGNLISTS:      ShowSignList();            break;
 
		case MME_SHOW_TOWNDIRECTORY:  if (_game_mode == GM_EDITOR) ShowTownDirectory(); break;
 
	}
 
}
 

	
 
/* --- Town button menu --- */
 

	
 
static void ToolbarTownClick(Window *w)
 
{
 
	PopupMainToolbMenu(w, TBN_TOWNDIRECTORY, STR_TOWN_MENU_TOWN_DIRECTORY, 1);
 
}
 

	
 
static void MenuClickTown(int index)
 
{
 
	ShowTownDirectory();
 
}
 

	
 
/* --- Subidies button menu --- */
 

	
 
static void ToolbarSubsidiesClick(Window *w)
 
{
 
	PopupMainToolbMenu(w, TBN_SUBSIDIES, STR_SUBSIDIES_MENU_SUBSIDIES, 1);
 
}
 

	
 
static void MenuClickSubsidies(int index)
 
{
 
	ShowSubsidiesList();
 
}
 

	
 
/* --- Stations button menu --- */
 

	
 
static void ToolbarStationsClick(Window *w)
 
{
 
	PopupMainCompanyToolbMenu(w, TBN_STATIONS);
 
}
 

	
 
static void MenuClickStations(int index)
 
{
 
	ShowCompanyStations((CompanyID)index);
 
}
 

	
 
/* --- Finances button menu --- */
 

	
 
static void ToolbarFinancesClick(Window *w)
 
{
 
	PopupMainCompanyToolbMenu(w, TBN_FINANCES);
 
}
 

	
 
static void MenuClickFinances(int index)
 
{
 
	ShowCompanyFinances((CompanyID)index);
 
}
 

	
 
/* --- Company's button menu --- */
 

	
 
static void ToolbarCompaniesClick(Window *w)
 
{
 
	PopupMainCompanyToolbMenu(w, TBN_COMPANIES);
 
}
 

	
 
static void MenuClickCompany(int index)
 
{
 
#ifdef ENABLE_NETWORK
 
	if (_networking) {
 
		switch (index) {
 
			case CTMN_CLIENT_LIST:
 
				ShowClientList();
 
				return;
 

	
 
			case CTMN_NEW_COMPANY:
 
				if (_network_server) {
 
					DoCommandP(0, 0, _network_own_client_id, CMD_COMPANY_CTRL);
 
				} else {
 
					NetworkSend_Command(0, 0, 0, CMD_COMPANY_CTRL, NULL, NULL);
 
				}
 
				return;
 

	
 
			case CTMN_SPECTATE:
 
				if (_network_server) {
 
					NetworkServerDoMove(CLIENT_ID_SERVER, COMPANY_SPECTATOR);
 
					MarkWholeScreenDirty();
 
				} else {
 
					NetworkClientRequestMove(COMPANY_SPECTATOR);
 
				}
 
				return;
 
		}
 
	}
 
#endif /* ENABLE_NETWORK */
 
	ShowCompany((CompanyID)index);
 
}
 

	
 
/* --- Graphs button menu --- */
 

	
 
static void ToolbarGraphsClick(Window *w)
 
{
 
	PopupMainToolbMenu(w, TBN_GRAPHICS, STR_GRAPH_MENU_OPERATING_PROFIT_GRAPH, (_toolbar_mode == TB_NORMAL) ? 6 : 8);
 
}
 

	
 
static void MenuClickGraphs(int index)
 
{
 
	switch (index) {
 
		case 0: ShowOperatingProfitGraph();    break;
 
		case 1: ShowIncomeGraph();             break;
 
		case 2: ShowDeliveredCargoGraph();     break;
 
		case 3: ShowPerformanceHistoryGraph(); break;
 
		case 4: ShowCompanyValueGraph();       break;
 
		case 5: ShowCargoPaymentRates();       break;
 
		/* functions for combined graphs/league button */
 
		case 6: ShowCompanyLeagueTable();      break;
 
		case 7: ShowPerformanceRatingDetail(); break;
 
	}
 
}
 

	
 
/* --- League button menu --- */
 

	
 
static void ToolbarLeagueClick(Window *w)
 
{
 
	PopupMainToolbMenu(w, TBN_LEAGUE, STR_GRAPH_MENU_COMPANY_LEAGUE_TABLE, 2);
 
}
 

	
 
static void MenuClickLeague(int index)
 
{
 
	switch (index) {
 
		case 0: ShowCompanyLeagueTable();      break;
 
		case 1: ShowPerformanceRatingDetail(); break;
 
	}
 
}
 

	
 
/* --- Industries button menu --- */
 

	
 
static void ToolbarIndustryClick(Window *w)
 
{
 
	/* Disable build-industry menu if we are a spectator */
 
	PopupMainToolbMenu(w, TBN_INDUSTRIES, STR_INDUSTRY_MENU_INDUSTRY_DIRECTORY, (_local_company == COMPANY_SPECTATOR) ? 1 : 2);
 
}
 

	
 
static void MenuClickIndustry(int index)
 
{
 
	switch (index) {
 
		case 0: ShowIndustryDirectory();   break;
 
		case 1: ShowBuildIndustryWindow(); break;
 
	}
 
}
 

	
 
/* --- Trains button menu + 1 helper function for all vehicles. --- */
 

	
 
static void ToolbarVehicleClick(Window *w, VehicleType veh)
 
{
 
	const Vehicle *v;
 
	int dis = ~0;
 

	
 
	FOR_ALL_VEHICLES(v) {
 
		if (v->type == veh && v->IsPrimaryVehicle()) ClrBit(dis, v->owner);
 
	}
 
	PopupMainCompanyToolbMenu(w, TBN_VEHICLESTART + veh, dis);
 
}
 

	
 

	
 
static void ToolbarTrainClick(Window *w)
 
{
 
	ToolbarVehicleClick(w, VEH_TRAIN);
 
}
 

	
 
static void MenuClickShowTrains(int index)
 
{
 
	ShowVehicleListWindow((CompanyID)index, VEH_TRAIN);
 
}
 

	
 
/* --- Road vehicle button menu --- */
 

	
 
static void ToolbarRoadClick(Window *w)
 
{
 
	ToolbarVehicleClick(w, VEH_ROAD);
 
}
 

	
 
static void MenuClickShowRoad(int index)
 
{
 
	ShowVehicleListWindow((CompanyID)index, VEH_ROAD);
 
}
 

	
 
/* --- Ship button menu --- */
 

	
 
static void ToolbarShipClick(Window *w)
 
{
 
	ToolbarVehicleClick(w, VEH_SHIP);
 
}
 

	
 
static void MenuClickShowShips(int index)
 
{
 
	ShowVehicleListWindow((CompanyID)index, VEH_SHIP);
 
}
 

	
 
/* --- Aircraft button menu --- */
 

	
 
static void ToolbarAirClick(Window *w)
 
{
 
	ToolbarVehicleClick(w, VEH_AIRCRAFT);
 
}
 

	
 
static void MenuClickShowAir(int index)
 
{
 
	ShowVehicleListWindow((CompanyID)index, VEH_AIRCRAFT);
 
}
 

	
 
/* --- Zoom in button --- */
 

	
 
static void ToolbarZoomInClick(Window *w)
 
{
 
	if (DoZoomInOutWindow(ZOOM_IN, FindWindowById(WC_MAIN_WINDOW, 0))) {
 
		w->HandleButtonClick((_game_mode == GM_EDITOR) ? (byte)TBSE_ZOOMIN : (byte)TBN_ZOOMIN);
 
		SndPlayFx(SND_15_BEEP);
 
	}
 
}
 

	
 
/* --- Zoom out button --- */
 

	
 
static void ToolbarZoomOutClick(Window *w)
 
{
 
	if (DoZoomInOutWindow(ZOOM_OUT, FindWindowById(WC_MAIN_WINDOW, 0))) {
 
		w->HandleButtonClick((_game_mode == GM_EDITOR) ? (byte)TBSE_ZOOMOUT : (byte)TBN_ZOOMOUT);
 
		SndPlayFx(SND_15_BEEP);
 
	}
 
}
 

	
 
/* --- Rail button menu --- */
 

	
 
static void ToolbarBuildRailClick(Window *w)
 
{
 
	const Company *c = Company::Get(_local_company);
 
	DropDownList *list = new DropDownList();
 
	for (RailType rt = RAILTYPE_BEGIN; rt != RAILTYPE_END; rt++) {
 
		const RailtypeInfo *rti = GetRailTypeInfo(rt);
 
		/* Skip rail type if it has no label */
 
		if (rti->label == 0) continue;
 
		list->push_back(new DropDownListStringItem(rti->strings.menu_text, rt, !HasBit(c->avail_railtypes, rt)));
 
	}
 
	ShowDropDownList(w, list, _last_built_railtype, TBN_RAILS, 140, true, true);
 
	SndPlayFx(SND_15_BEEP);
 
}
 

	
 
static void MenuClickBuildRail(int index)
 
{
 
	_last_built_railtype = (RailType)index;
 
	ShowBuildRailToolbar(_last_built_railtype, -1);
 
}
 

	
 
/* --- Road button menu --- */
 

	
 
static void ToolbarBuildRoadClick(Window *w)
 
{
 
	const Company *c = Company::Get(_local_company);
 
	DropDownList *list = new DropDownList();
 
	for (RoadType rt = ROADTYPE_BEGIN; rt != ROADTYPE_END; rt++) {
 
		/* The standard road button is *always* available */
 
		list->push_back(new DropDownListStringItem(STR_ROAD_MENU_ROAD_CONSTRUCTION + rt, rt, !(HasBit(c->avail_roadtypes, rt) || rt == ROADTYPE_ROAD)));
 
	}
 
	ShowDropDownList(w, list, _last_built_roadtype, TBN_ROADS, 140, true, true);
 
	SndPlayFx(SND_15_BEEP);
 
}
 

	
 
static void MenuClickBuildRoad(int index)
 
{
 
	_last_built_roadtype = (RoadType)index;
 
	ShowBuildRoadToolbar(_last_built_roadtype);
 
}
 

	
 
/* --- Water button menu --- */
 

	
 
static void ToolbarBuildWaterClick(Window *w)
 
{
 
	PopupMainToolbMenu(w, TBN_WATER, STR_WATERWAYS_MENU_WATERWAYS_CONSTRUCTION, 1);
 
}
 

	
 
static void MenuClickBuildWater(int index)
 
{
 
	ShowBuildDocksToolbar();
 
}
 

	
 
/* --- Airport button menu --- */
 

	
 
static void ToolbarBuildAirClick(Window *w)
 
{
 
	PopupMainToolbMenu(w, TBN_AIR, STR_AIRCRAFT_MENU_AIRPORT_CONSTRUCTION, 1);
 
}
 

	
 
static void MenuClickBuildAir(int index)
 
{
 
	ShowBuildAirToolbar();
 
}
 

	
 
/* --- Forest button menu --- */
 

	
 
static void ToolbarForestClick(Window *w)
 
{
 
	PopupMainToolbMenu(w, TBN_LANDSCAPE, STR_LANDSCAPING_MENU_LANDSCAPING, 3);
 
}
 

	
 
static void MenuClickForest(int index)
 
{
 
	switch (index) {
 
		case 0: ShowTerraformToolbar();  break;
 
		case 1: ShowBuildTreesToolbar(); break;
 
		case 2: SelectSignTool();        break;
 
	}
 
}
 

	
 
/* --- Music button menu --- */
 

	
 
static void ToolbarMusicClick(Window *w)
 
{
 
	PopupMainToolbMenu(w, TBN_MUSICSOUND, STR_TOOLBAR_SOUND_MUSIC, 1);
 
}
 

	
 
static void MenuClickMusicWindow(int index)
 
{
 
	ShowMusicWindow();
 
}
 

	
 
/* --- Newspaper button menu --- */
 

	
 
static void ToolbarNewspaperClick(Window *w)
 
{
 
	PopupMainToolbMenu(w, TBN_NEWSREPORT, STR_NEWS_MENU_LAST_MESSAGE_NEWS_REPORT, 3);
 
}
 

	
 
static void MenuClickNewspaper(int index)
 
{
 
	switch (index) {
 
		case 0: ShowLastNewsMessage(); break;
 
		case 1: ShowMessageOptions();  break;
 
		case 2: ShowMessageHistory();  break;
 
	}
 
}
 

	
 
/* --- Help button menu --- */
 

	
 
static void ToolbarHelpClick(Window *w)
 
{
 
	PopupMainToolbMenu(w, TBN_HELP, STR_ABOUT_MENU_LAND_BLOCK_INFO, 7);
 
}
 

	
 
static void MenuClickSmallScreenshot()
 
{
 
	RequestScreenshot(SC_VIEWPORT, NULL);
 
}
 

	
 
static void MenuClickWorldScreenshot()
 
{
 
	RequestScreenshot(SC_WORLD, NULL);
 
}
 

	
 
static void MenuClickHelp(int index)
 
{
 
	switch (index) {
 
		case 0: PlaceLandBlockInfo();       break;
 
		case 2: IConsoleSwitch();           break;
 
		case 3: ShowAIDebugWindow();        break;
 
		case 4: MenuClickSmallScreenshot(); break;
 
		case 5: MenuClickWorldScreenshot(); break;
 
		case 6: ShowAboutWindow();          break;
 
	}
 
}
 

	
 
/* --- Switch toolbar button --- */
 

	
 
static void ToolbarSwitchClick(Window *w)
 
{
 
	if (_toolbar_mode != TB_LOWER) {
 
		_toolbar_mode = TB_LOWER;
 
	} else {
 
		_toolbar_mode = TB_UPPER;
 
	}
 

	
 
	w->ReInit();
 
	w->SetWidgetLoweredState(TBN_SWITCHBAR, _toolbar_mode == TB_LOWER);
 
	SndPlayFx(SND_15_BEEP);
 
}
 

	
 
/* --- Scenario editor specific handlers. */
 

	
 
static void ToolbarScenDateBackward(Window *w)
 
{
 
	/* don't allow too fast scrolling */
 
	if ((w->flags4 & WF_TIMEOUT_MASK) <= WF_TIMEOUT_TRIGGER) {
 
		w->HandleButtonClick(TBSE_DATEBACKWARD);
 
		w->SetDirty();
 

	
 
		_settings_game.game_creation.starting_year = Clamp(_settings_game.game_creation.starting_year - 1, MIN_YEAR, MAX_YEAR);
 
		SetDate(ConvertYMDToDate(_settings_game.game_creation.starting_year, 0, 1));
 
	}
 
	_left_button_clicked = false;
 
}
 

	
 
static void ToolbarScenDateForward(Window *w)
 
{
 
	/* don't allow too fast scrolling */
 
	if ((w->flags4 & WF_TIMEOUT_MASK) <= WF_TIMEOUT_TRIGGER) {
 
		w->HandleButtonClick(TBSE_DATEFORWARD);
 
		w->SetDirty();
 

	
 
		_settings_game.game_creation.starting_year = Clamp(_settings_game.game_creation.starting_year + 1, MIN_YEAR, MAX_YEAR);
 
		SetDate(ConvertYMDToDate(_settings_game.game_creation.starting_year, 0, 1));
 
	}
 
	_left_button_clicked = false;
 
}
 

	
 
static void ToolbarScenGenLand(Window *w)
 
{
 
	w->HandleButtonClick(TBSE_LANDGENERATE);
 
	SndPlayFx(SND_15_BEEP);
 

	
 
	ShowEditorTerraformToolbar();
 
}
 

	
 

	
 
static void ToolbarScenGenTown(Window *w)
 
{
 
	w->HandleButtonClick(TBSE_TOWNGENERATE);
 
	SndPlayFx(SND_15_BEEP);
 
	ShowFoundTownWindow();
 
}
 

	
 
static void ToolbarScenGenIndustry(Window *w)
 
{
 
	w->HandleButtonClick(TBSE_INDUSTRYGENERATE);
 
	SndPlayFx(SND_15_BEEP);
 
	ShowBuildIndustryWindow();
 
}
 

	
 
static void ToolbarScenBuildRoad(Window *w)
 
{
 
	w->HandleButtonClick(TBSE_BUILDROAD);
 
	SndPlayFx(SND_15_BEEP);
 
	ShowBuildRoadScenToolbar();
 
}
 

	
 
static void ToolbarScenBuildDocks(Window *w)
 
{
 
	w->HandleButtonClick(TBSE_BUILDDOCKS);
 
	SndPlayFx(SND_15_BEEP);
 
	ShowBuildDocksScenToolbar();
 
}
 

	
 
static void ToolbarScenPlantTrees(Window *w)
 
{
 
	w->HandleButtonClick(TBSE_PLANTTREES);
 
	SndPlayFx(SND_15_BEEP);
 
	ShowBuildTreesToolbar();
 
}
 

	
 
static void ToolbarScenPlaceSign(Window *w)
 
{
 
	w->HandleButtonClick(TBSE_PLACESIGNS);
 
	SndPlayFx(SND_15_BEEP);
 
	SelectSignTool();
 
}
 

	
 
static void ToolbarBtn_NULL(Window *w)
 
{
 
}
 

	
 
typedef void MenuClickedProc(int index);
 

	
 
static MenuClickedProc * const _menu_clicked_procs[] = {
 
	NULL,                 // 0
 
	NULL,                 // 1
 
	MenuClickSettings,    // 2
 
	MenuClickSaveLoad,    // 3
 
	MenuClickMap,         // 4
 
	MenuClickTown,        // 5
 
	MenuClickSubsidies,   // 6
 
	MenuClickStations,    // 7
 
	MenuClickFinances,    // 8
 
	MenuClickCompany,     // 9
 
	MenuClickGraphs,      // 10
 
	MenuClickLeague,      // 11
 
	MenuClickIndustry,    // 12
 
	MenuClickShowTrains,  // 13
 
	MenuClickShowRoad,    // 14
 
	MenuClickShowShips,   // 15
 
	MenuClickShowAir,     // 16
 
	MenuClickMap,         // 17
 
	NULL,                 // 18
 
	MenuClickBuildRail,   // 19
 
	MenuClickBuildRoad,   // 20
 
	MenuClickBuildWater,  // 21
 
	MenuClickBuildAir,    // 22
 
	MenuClickForest,      // 23
 
	MenuClickMusicWindow, // 24
 
	MenuClickNewspaper,   // 25
 
	MenuClickHelp,        // 26
 
};
 

	
 
/** Full blown container to make it behave exactly as we want :) */
 
class NWidgetToolbarContainer : public NWidgetContainer {
 
	bool visible[TBN_END]; ///< The visible headers
 
protected:
 
	uint spacers;          ///< Number of spacer widgets in this toolbar
 

	
 
public:
 
	NWidgetToolbarContainer() : NWidgetContainer(NWID_HORIZONTAL)
 
	{
 
	}
 

	
 
	/**
 
	 * Check whether the given widget type is a button for us.
 
	 * @param type the widget type to check
 
	 * @return true if it is a button for us
 
	 */
 
	bool IsButton(WidgetType type) const
 
	{
 
		return type == WWT_IMGBTN || type == WWT_IMGBTN_2 || type == WWT_PUSHIMGBTN;
 
	}
 

	
 
	void SetupSmallestSize(Window *w, bool init_array)
 
	{
 
		this->smallest_x = 0; // Biggest child
 
		this->smallest_y = 0; // Biggest child
 
		this->fill_x = true;
 
		this->fill_y = false;
 
		this->fill_x = 1;
 
		this->fill_y = 0;
 
		this->resize_x = 1; // We only resize in this direction
 
		this->resize_y = 0; // We never resize in this direction
 
		this->spacers = 0;
 

	
 
		/* First initialise some variables... */
 
		for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
 
			child_wid->SetupSmallestSize(w, init_array);
 
			this->smallest_y = max(this->smallest_y, child_wid->smallest_y + child_wid->padding_top + child_wid->padding_bottom);
 
			if (this->IsButton(child_wid->type)) {
 
				this->smallest_x = max(this->smallest_x, child_wid->smallest_x + child_wid->padding_left + child_wid->padding_right);
 
			} else if (child_wid->type == NWID_SPACER) {
 
				this->spacers++;
 
			}
 
		}
 

	
 
		/* ... then in a second pass make sure the 'current' heights are set. Won't change ever. */
 
		for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
 
			child_wid->current_y = this->smallest_y;
 
			if (!this->IsButton(child_wid->type)) {
 
				child_wid->current_x = child_wid->smallest_x;
 
			}
 
		}
 
	}
 

	
 
	void AssignSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height, bool rtl)
 
	{
 
		assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
 

	
 
		this->pos_x = x;
 
		this->pos_y = y;
 
		this->current_x = given_width;
 
		this->current_y = given_height;
 

	
 
		/* Figure out what are the visible buttons */
 
		memset(this->visible, 0, sizeof(this->visible));
 
		uint arrangable_count, button_count, spacer_count;
 
		const byte *arrangement = GetButtonArrangement(given_width, arrangable_count, button_count, spacer_count);
 
		for (uint i = 0; i < arrangable_count; i++) {
 
			this->visible[arrangement[i]] = true;
 
		}
 

	
 
		/* Create us ourselves a quick lookup table */
 
		NWidgetBase *widgets[TBN_END];
 
		for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
 
			if (child_wid->type == NWID_SPACER) continue;
 
			widgets[((NWidgetCore*)child_wid)->index] = child_wid;
 
		}
 

	
 
		/* Now assign the widgets to their rightful place */
 
		uint position = 0; // Place to put next child relative to origin of the container.
 
		uint spacer_space = max(0, (int)given_width - (int)(button_count * this->smallest_x)); // Remaining spacing for 'spacer' widgets
 
		uint button_space = given_width - spacer_space; // Remaining spacing for the buttons
 
		uint spacer_i = 0;
 
		uint button_i = 0;
 

	
 
		/* Index into the arrangement indices. The macro lastof cannot be used here! */
 
		const byte *cur_wid = rtl ? &arrangement[arrangable_count - 1] : arrangement;
 
		for (uint i = 0; i < arrangable_count; i++) {
 
			NWidgetBase *child_wid = widgets[*cur_wid];
 
			/* If we have to give space to the spacers, do that */
 
			if (spacer_space != 0) {
 
				NWidgetBase *possible_spacer = rtl ? child_wid->next : child_wid->prev;
 
				if (possible_spacer != NULL && possible_spacer->type == NWID_SPACER) {
 
					uint add = spacer_space / (spacer_count - spacer_i);
 
					position += add;
 
					spacer_space -= add;
 
					spacer_i++;
 
				}
 
			}
 

	
 
			/* Buttons can be scaled, the others not. */
 
			if (this->IsButton(child_wid->type)) {
 
				child_wid->current_x = button_space / (button_count - button_i);
 
				button_space -= child_wid->current_x;
 
				button_i++;
 
			}
 
			child_wid->AssignSizePosition(sizing, x + position, y, child_wid->current_x, this->current_y, rtl);
 
			position += child_wid->current_x;
 

	
 
			if (rtl) {
 
				cur_wid--;
 
			} else {
 
				cur_wid++;
 
			}
 
		}
 
	}
 

	
 
	/* virtual */ void Draw(const Window *w)
 
	{
 
		/* Draw brown-red toolbar bg. */
 
		GfxFillRect(this->pos_x, this->pos_y, this->pos_x + this->current_x - 1, this->pos_y + this->current_y - 1, 0xB2);
 
		GfxFillRect(this->pos_x, this->pos_y, this->pos_x + this->current_x - 1, this->pos_y + this->current_y - 1, 0xB4, FILLRECT_CHECKER);
 

	
 
		for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
 
			if (child_wid->type == NWID_SPACER) continue;
 
			if (!this->visible[((NWidgetCore*)child_wid)->index]) continue;
 

	
 
			child_wid->Draw(w);
 
		}
 
	}
 

	
 
	/* virtual */ NWidgetCore *GetWidgetFromPos(int x, int y)
 
	{
 
		if (!IsInsideBS(x, this->pos_x, this->current_x) || !IsInsideBS(y, this->pos_y, this->current_y)) return NULL;
 

	
 
		for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
 
			if (child_wid->type == NWID_SPACER) continue;
 
			if (!this->visible[((NWidgetCore*)child_wid)->index]) continue;
 

	
 
			NWidgetCore *nwid = child_wid->GetWidgetFromPos(x, y);
 
			if (nwid != NULL) return nwid;
 
		}
 
		return NULL;
 
	}
 

	
 
	/**
 
	 * Get the arrangement of the buttons for the toolbar.
 
	 * @param width the new width of the toolbar
 
	 * @param arrangable_count output of the number of visible items
 
	 * @param button_count output of the number of visible buttons
 
	 * @param spacer_count output of the number of spacers
 
	 * @return the button configuration
 
	 */
 
	virtual const byte *GetButtonArrangement(uint &width, uint &arrangable_count, uint &button_count, uint &spacer_count) const = 0;
 
};
 

	
 
/** Container for the 'normal' main toolbar */
 
class NWidgetMainToolbarContainer : public NWidgetToolbarContainer {
 
	/* virtual */ const byte *GetButtonArrangement(uint &width, uint &arrangable_count, uint &button_count, uint &spacer_count) const
 
	{
 
		static const uint SMALLEST_ARRANGEMENT = 14;
 
		static const uint BIGGEST_ARRANGEMENT  = 19;
 
		static const byte arrange14[] = {
 
			0,  1, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 27,
 
			2,  3,  4,  5,  6,  7,  8,  9, 10, 12, 24, 25, 26, 27,
 
		};
 
		static const byte arrange15[] = {
 
			0,  1,  4, 13, 14, 15, 16, 19, 20, 21, 22, 23, 17, 18, 27,
 
			0,  2,  4,  3,  5,  6,  7,  8,  9, 10, 12, 24, 25, 26, 27,
 
		};
 
		static const byte arrange16[] = {
 
			0,  1,  2,  4, 13, 14, 15, 16, 19, 20, 21, 22, 23, 17, 18, 27,
 
			0,  1,  3,  5,  6,  7,  8,  9, 10, 12, 24, 25, 26, 17, 18, 27,
 
		};
 
		static const byte arrange17[] = {
 
			0,  1,  2,  4,  6, 13, 14, 15, 16, 19, 20, 21, 22, 23, 17, 18, 27,
 
			0,  1,  3,  4,  6,  5,  7,  8,  9, 10, 12, 24, 25, 26, 17, 18, 27,
 
		};
 
		static const byte arrange18[] = {
 
			0,  1,  2,  4,  5,  6,  7,  8,  9, 12, 19, 20, 21, 22, 23, 17, 18, 27,
 
			0,  1,  3,  4,  5,  6,  7, 10, 13, 14, 15, 16, 24, 25, 26, 17, 18, 27,
 
		};
 
		static const byte arrange19[] = {
 
			0,  1,  2,  4,  5,  6, 13, 14, 15, 16, 19, 20, 21, 22, 23, 24, 17, 18, 27,
 
			0,  1,  3,  4,  7,  8,  9, 10, 12, 25, 19, 20, 21, 22, 23, 26, 17, 18, 27,
 
		};
 
		static const byte arrange_all[] = {
 
			0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
 
		};
 

	
 
		/* If at least BIGGEST_ARRANGEMENT fit, just spread all the buttons nicely */
 
		uint full_buttons = max((width + this->smallest_x - 1) / this->smallest_x, SMALLEST_ARRANGEMENT);
 
		if (full_buttons > BIGGEST_ARRANGEMENT) {
 
			button_count = arrangable_count = lengthof(arrange_all);
 
			spacer_count = this->spacers;
 
			return arrange_all;
 
		}
 

	
 
		/* Introduce the split toolbar */
 
		static const byte * const arrangements[] = { arrange14, arrange15, arrange16, arrange17, arrange18, arrange19 };
 

	
 
		button_count = arrangable_count = full_buttons;
 
		spacer_count = this->spacers;
 
		return arrangements[full_buttons - SMALLEST_ARRANGEMENT] + ((_toolbar_mode == TB_LOWER) ? full_buttons : 0);
 
	}
 
};
 

	
 
/** Container for the scenario editor's toolbar */
 
class NWidgetScenarioToolbarContainer : public NWidgetToolbarContainer {
 
	uint panel_widths[2]; ///< The width of the two panels (the text panel and date panel)
 

	
 
	void SetupSmallestSize(Window *w, bool init_array)
 
	{
 
		this->NWidgetToolbarContainer::SetupSmallestSize(w, init_array);
 

	
 
		/* Find the size of panel_widths */
 
		uint i = 0;
 
		for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
 
			if (child_wid->type == NWID_SPACER || this->IsButton(child_wid->type)) continue;
 

	
 
			assert(i < lengthof(this->panel_widths));
 
			this->panel_widths[i++] = child_wid->current_x;
 
		}
 
	}
 

	
 
	/* virtual */ const byte *GetButtonArrangement(uint &width, uint &arrangable_count, uint &button_count, uint &spacer_count) const
 
	{
 
		static const byte arrange_all[] = {
 
			0, 1, 2, 3, 4, 18, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 24, 26,
 
		};
 
		static const byte arrange_nopanel[] = {
 
			0, 1, 2, 3, 18, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 24, 26,
 
		};
 
		static const byte arrange_switch[] = {
 
			18,  8, 11, 12, 13, 14, 15, 16, 17, 27,
 
			 0,  1,  2,  3, 18,  9, 10, 24, 26, 27,
 
		};
 

	
 
		/* If we can place all buttons *and* the panels, show them. */
 
		uint min_full_width = (lengthof(arrange_all) - lengthof(this->panel_widths)) * this->smallest_x + this->panel_widths[0] + this->panel_widths[1];
 
		if (width >= min_full_width) {
 
			width -= this->panel_widths[0] + this->panel_widths[1];
 
			arrangable_count = lengthof(arrange_all);
 
			button_count = arrangable_count - 2;
 
			spacer_count = this->spacers;
 
			return arrange_all;
 
		}
 

	
 
		/* Otherwise don't show the date panel and if we can't fit half the buttons and the panels anymore, split the toolbar in two */
 
		uint min_small_width = (lengthof(arrange_switch) - lengthof(this->panel_widths)) * this->smallest_x / 2 + this->panel_widths[1];
 
		if (width > min_small_width) {
 
			width -= this->panel_widths[1];
 
			arrangable_count = lengthof(arrange_nopanel);
 
			button_count = arrangable_count - 1;
 
			spacer_count = this->spacers - 1;
 
			return arrange_nopanel;
 
		}
 

	
 
		/* Split toolbar */
 
		width -= this->panel_widths[1];
 
		arrangable_count = lengthof(arrange_switch) / 2;
 
		button_count = arrangable_count - 1;
 
		spacer_count = 0;
 
		return arrange_switch + ((_toolbar_mode == TB_LOWER) ? arrangable_count : 0);
 
	}
 
};
 

	
 
/* --- Toolbar handling for the 'normal' case */
 

	
 
typedef void ToolbarButtonProc(Window *w);
 

	
 
static ToolbarButtonProc * const _toolbar_button_procs[] = {
 
	ToolbarPauseClick,
 
	ToolbarFastForwardClick,
 
	ToolbarOptionsClick,
 
	ToolbarSaveClick,
 
	ToolbarMapClick,
 
	ToolbarTownClick,
 
	ToolbarSubsidiesClick,
 
	ToolbarStationsClick,
 
	ToolbarFinancesClick,
 
	ToolbarCompaniesClick,
 
	ToolbarGraphsClick,
 
	ToolbarLeagueClick,
 
	ToolbarIndustryClick,
 
	ToolbarTrainClick,
 
	ToolbarRoadClick,
 
	ToolbarShipClick,
 
	ToolbarAirClick,
 
	ToolbarZoomInClick,
 
	ToolbarZoomOutClick,
 
	ToolbarBuildRailClick,
 
	ToolbarBuildRoadClick,
 
	ToolbarBuildWaterClick,
 
	ToolbarBuildAirClick,
 
	ToolbarForestClick,
 
	ToolbarMusicClick,
 
	ToolbarNewspaperClick,
 
	ToolbarHelpClick,
 
	ToolbarSwitchClick,
 
};
 

	
 
struct MainToolbarWindow : Window {
 
	MainToolbarWindow(const WindowDesc *desc) : Window()
 
	{
 
		this->InitNested(desc, 0);
 

	
 
		CLRBITS(this->flags4, WF_WHITE_BORDER_MASK);
 
		this->SetWidgetDisabledState(TBN_PAUSE, _networking && !_network_server); // if not server, disable pause button
 
		this->SetWidgetDisabledState(TBN_FASTFORWARD, _networking); // if networking, disable fast-forward button
 
		PositionMainToolbar(this);
 
		DoZoomInOutWindow(ZOOM_NONE, this);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		/* If spectator, disable all construction buttons
 
		 * ie : Build road, rail, ships, airports and landscaping
 
		 * Since enabled state is the default, just disable when needed */
 
		this->SetWidgetsDisabledState(_local_company == COMPANY_SPECTATOR, TBN_RAILS, TBN_ROADS, TBN_WATER, TBN_AIR, TBN_LANDSCAPE, WIDGET_LIST_END);
 
		/* disable company list drop downs, if there are no companies */
 
		this->SetWidgetsDisabledState(Company::GetNumItems() == 0, TBN_STATIONS, TBN_FINANCES, TBN_TRAINS, TBN_ROADVEHS, TBN_SHIPS, TBN_AIRCRAFTS, WIDGET_LIST_END);
 

	
 
		this->SetWidgetDisabledState(TBN_RAILS, !CanBuildVehicleInfrastructure(VEH_TRAIN));
 
		this->SetWidgetDisabledState(TBN_AIR, !CanBuildVehicleInfrastructure(VEH_AIRCRAFT));
 

	
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		if (_game_mode != GM_MENU && !this->IsWidgetDisabled(widget)) _toolbar_button_procs[widget](this);
 
	}
 

	
 
	virtual void OnDropdownSelect(int widget, int index)
 
	{
 
		_menu_clicked_procs[widget](index);
 
	}
 

	
 
	virtual EventState OnKeyPress(uint16 key, uint16 keycode)
 
	{
 
		switch (keycode) {
 
			case WKC_F1: case WKC_PAUSE: ToolbarPauseClick(this); break;
 
			case WKC_F2: ShowGameOptions(); break;
 
			case WKC_F3: MenuClickSaveLoad(); break;
 
			case WKC_F4: ShowSmallMap(); break;
 
			case WKC_F5: ShowTownDirectory(); break;
 
			case WKC_F6: ShowSubsidiesList(); break;
 
			case WKC_F7: ShowCompanyStations(_local_company); break;
 
			case WKC_F8: ShowCompanyFinances(_local_company); break;
 
			case WKC_F9: ShowCompany(_local_company); break;
 
			case WKC_F10: ShowOperatingProfitGraph(); break;
 
			case WKC_F11: ShowCompanyLeagueTable(); break;
 
			case WKC_F12: ShowBuildIndustryWindow(); break;
 
			case WKC_SHIFT | WKC_F1: ShowVehicleListWindow(_local_company, VEH_TRAIN); break;
 
			case WKC_SHIFT | WKC_F2: ShowVehicleListWindow(_local_company, VEH_ROAD); break;
 
			case WKC_SHIFT | WKC_F3: ShowVehicleListWindow(_local_company, VEH_SHIP); break;
 
			case WKC_SHIFT | WKC_F4: ShowVehicleListWindow(_local_company, VEH_AIRCRAFT); break;
 
			case WKC_NUM_PLUS: // Fall through
 
			case WKC_EQUALS: // Fall through
 
			case WKC_SHIFT | WKC_EQUALS: // Fall through
 
			case WKC_SHIFT | WKC_F5: ToolbarZoomInClick(this); break;
 
			case WKC_NUM_MINUS: // Fall through
 
			case WKC_MINUS: // Fall through
 
			case WKC_SHIFT | WKC_MINUS: // Fall through
 
			case WKC_SHIFT | WKC_F6: ToolbarZoomOutClick(this); break;
 
			case WKC_SHIFT | WKC_F7: if (CanBuildVehicleInfrastructure(VEH_TRAIN)) ShowBuildRailToolbar(_last_built_railtype, -1); break;
 
			case WKC_SHIFT | WKC_F8: ShowBuildRoadToolbar(_last_built_roadtype); break;
 
			case WKC_SHIFT | WKC_F9: ShowBuildDocksToolbar(); break;
 
			case WKC_SHIFT | WKC_F10: if (CanBuildVehicleInfrastructure(VEH_AIRCRAFT)) ShowBuildAirToolbar(); break;
 
			case WKC_SHIFT | WKC_F11: ShowBuildTreesToolbar(); break;
 
			case WKC_SHIFT | WKC_F12: ShowMusicWindow(); break;
 
			case WKC_CTRL  | 'S': MenuClickSmallScreenshot(); break;
 
			case WKC_CTRL  | 'G': MenuClickWorldScreenshot(); break;
 
			case WKC_CTRL | WKC_ALT | 'C': if (!_networking) ShowCheatWindow(); break;
 
			case 'A': if (CanBuildVehicleInfrastructure(VEH_TRAIN)) ShowBuildRailToolbar(_last_built_railtype, 4); break; // Invoke Autorail
 
			case 'L': ShowTerraformToolbar(); break;
 
			case 'Q': case 'W': case 'E': case 'D': ShowTerraformToolbarWithTool(key, keycode); break;
 
			case 'M': ShowSmallMap(); break;
 
			case 'V': ShowExtraViewPortWindow(); break;
 
			default: return ES_NOT_HANDLED;
 
		}
 
		return ES_HANDLED;
 
	}
 

	
 
	virtual void OnPlaceObject(Point pt, TileIndex tile)
 
	{
 
		_place_proc(tile);
 
	}
 

	
 
	virtual void OnTick()
 
	{
 
		if (this->IsWidgetLowered(TBN_PAUSE) != !!_pause_mode) {
 
			this->ToggleWidgetLoweredState(TBN_PAUSE);
 
			this->SetWidgetDirty(TBN_PAUSE);
 
		}
 

	
 
		if (this->IsWidgetLowered(TBN_FASTFORWARD) != !!_fast_forward) {
 
			this->ToggleWidgetLoweredState(TBN_FASTFORWARD);
 
			this->SetWidgetDirty(TBN_FASTFORWARD);
 
		}
 
	}
 

	
 
	virtual void OnTimeout()
 
	{
 
		/* We do not want to automatically raise the pause, fast forward and
 
		 * switchbar buttons; they have to stay down when pressed etc. */
 
		for (uint i = TBN_SETTINGS; i < TBN_SWITCHBAR; i++) {
 
			if (this->IsWidgetLowered(i)) {
 
				this->RaiseWidget(i);
 
				this->SetWidgetDirty(i);
 
			}
 
		}
 
	}
 

	
 
	virtual void OnInvalidateData(int data)
 
	{
 
		if (FindWindowById(WC_MAIN_WINDOW, 0) != NULL) HandleZoomMessage(this, FindWindowById(WC_MAIN_WINDOW, 0)->viewport, TBN_ZOOMIN, TBN_ZOOMOUT);
 
	}
 
};
 

	
 
static NWidgetBase *MakeMainToolbar(int *biggest_index)
 
{
 
	/** Sprites to use for the different toolbar buttons */
 
	static const SpriteID toolbar_button_sprites[] = {
 
		SPR_IMG_PAUSE,           // TBN_PAUSE
 
		SPR_IMG_FASTFORWARD,     // TBN_FASTFORWARD
 
		SPR_IMG_SETTINGS,        // TBN_SETTINGS
 
		SPR_IMG_SAVE,            // TBN_SAVEGAME
 
		SPR_IMG_SMALLMAP,        // TBN_SMALLMAP
 
		SPR_IMG_TOWN,            // TBN_TOWNDIRECTORY
 
		SPR_IMG_SUBSIDIES,       // TBN_SUBSIDIES
 
		SPR_IMG_COMPANY_LIST,    // TBN_STATIONS
 
		SPR_IMG_COMPANY_FINANCE, // TBN_FINANCES
 
		SPR_IMG_COMPANY_GENERAL, // TBN_COMPANIES
 
		SPR_IMG_GRAPHS,          // TBN_GRAPHICS
 
		SPR_IMG_COMPANY_LEAGUE,  // TBN_LEAGUE
 
		SPR_IMG_INDUSTRY,        // TBN_INDUSTRIES
 
		SPR_IMG_TRAINLIST,       // TBN_TRAINS
 
		SPR_IMG_TRUCKLIST,       // TBN_ROADVEHS
 
		SPR_IMG_SHIPLIST,        // TBN_SHIPS
 
		SPR_IMG_AIRPLANESLIST,   // TBN_AIRCRAFTS
 
		SPR_IMG_ZOOMIN,          // TBN_ZOOMIN
 
		SPR_IMG_ZOOMOUT,         // TBN_ZOOMOUT
 
		SPR_IMG_BUILDRAIL,       // TBN_RAILS
 
		SPR_IMG_BUILDROAD,       // TBN_ROADS
 
		SPR_IMG_BUILDWATER,      // TBN_WATER
 
		SPR_IMG_BUILDAIR,        // TBN_AIR
 
		SPR_IMG_LANDSCAPING,     // TBN_LANDSCAPE
 
		SPR_IMG_MUSIC,           // TBN_MUSICSOUND
 
		SPR_IMG_MESSAGES,        // TBN_NEWSREPORT
 
		SPR_IMG_QUERY,           // TBN_HELP
 
		SPR_IMG_SWITCH_TOOLBAR,  // TBN_SWITCHBAR
 
	};
 

	
 
	NWidgetMainToolbarContainer *hor = new NWidgetMainToolbarContainer();
 
	for (uint i = 0; i < TBN_END; i++) {
 
		switch (i) {
 
			case 4: case 8: case 13: case 17: case 19: case 24: hor->Add(new NWidgetSpacer(0, 0)); break;
 
		}
 
		hor->Add(new NWidgetLeaf(i == TBN_SAVEGAME ? WWT_IMGBTN_2 : WWT_IMGBTN, COLOUR_GREY, i, toolbar_button_sprites[i], STR_TOOLBAR_TOOLTIP_PAUSE_GAME + i));
 
	}
 

	
 
	*biggest_index = max<int>(*biggest_index, TBN_SWITCHBAR);
 
	return hor;
 
}
 

	
 
static const NWidgetPart _nested_toolbar_normal_widgets[] = {
 
	NWidgetFunction(MakeMainToolbar),
 
};
 

	
 
static const WindowDesc _toolb_normal_desc(
 
	0, 0, 640, 22,
 
	WC_MAIN_TOOLBAR, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_NO_FOCUS,
 
	_nested_toolbar_normal_widgets, lengthof(_nested_toolbar_normal_widgets)
 
);
 

	
 

	
 
/* --- Toolbar handling for the scenario editor */
 

	
 
static ToolbarButtonProc * const _scen_toolbar_button_procs[] = {
 
	ToolbarPauseClick,
 
	ToolbarFastForwardClick,
 
	ToolbarOptionsClick,
 
	ToolbarScenSaveOrLoad,
 
	ToolbarBtn_NULL,
 
	ToolbarBtn_NULL,
 
	ToolbarScenDateBackward,
 
	ToolbarScenDateForward,
 
	ToolbarScenMapTownDir,
 
	ToolbarZoomInClick,
 
	ToolbarZoomOutClick,
 
	ToolbarScenGenLand,
 
	ToolbarScenGenTown,
 
	ToolbarScenGenIndustry,
 
	ToolbarScenBuildRoad,
 
	ToolbarScenBuildDocks,
 
	ToolbarScenPlantTrees,
 
	ToolbarScenPlaceSign,
 
	ToolbarBtn_NULL,
 
	NULL,
 
	NULL,
 
	NULL,
 
	NULL,
 
	NULL,
 
	ToolbarMusicClick,
 
	NULL,
 
	ToolbarHelpClick,
 
	ToolbarSwitchClick,
 
};
 

	
 
struct ScenarioEditorToolbarWindow : Window {
 
public:
 
	ScenarioEditorToolbarWindow(const WindowDesc *desc) : Window()
 
	{
 
		this->InitNested(desc, 0);
 

	
 
		CLRBITS(this->flags4, WF_WHITE_BORDER_MASK);
 
		PositionMainToolbar(this);
 
		DoZoomInOutWindow(ZOOM_NONE, this);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->SetWidgetDisabledState(TBSE_DATEBACKWARD, _settings_game.game_creation.starting_year <= MIN_YEAR);
 
		this->SetWidgetDisabledState(TBSE_DATEFORWARD, _settings_game.game_creation.starting_year >= MAX_YEAR);
 

	
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case TBSE_DATEPANEL:
 
				SetDParam(0, ConvertYMDToDate(_settings_game.game_creation.starting_year, 0, 1));
 
				DrawString(r.left, r.right, (this->height - FONT_HEIGHT_NORMAL) / 2, STR_WHITE_DATE_LONG, TC_FROMSTRING, SA_CENTER);
 
				break;
 

	
 
			case TBSE_SPACERPANEL: {
 
				int height = r.bottom - r.top;
 
				if (height > 2 * FONT_HEIGHT_NORMAL) {
 
					DrawString(r.left, r.right, (height + 1) / 2 - FONT_HEIGHT_NORMAL, STR_SCENEDIT_TOOLBAR_OPENTTD, TC_FROMSTRING, SA_CENTER);
 
					DrawString(r.left, r.right, (height + 1) / 2, STR_SCENEDIT_TOOLBAR_SCENARIO_EDITOR, TC_FROMSTRING, SA_CENTER);
 
				} else {
 
					DrawString(r.left, r.right, (height - FONT_HEIGHT_NORMAL) / 2, STR_SCENEDIT_TOOLBAR_SCENARIO_EDITOR, TC_FROMSTRING, SA_CENTER);
 
				}
 
			} break;
 
		}
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		switch (widget) {
 
			case TBSE_SPACERPANEL:
 
				size->width = max(GetStringBoundingBox(STR_SCENEDIT_TOOLBAR_OPENTTD).width, GetStringBoundingBox(STR_SCENEDIT_TOOLBAR_SCENARIO_EDITOR).width) + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
 
				break;
 

	
 
			case TBSE_DATEPANEL:
 
				SetDParam(0, ConvertYMDToDate(MAX_YEAR, 0, 1));
 
				*size = GetStringBoundingBox(STR_WHITE_DATE_LONG);
 
				size->height = max(size->height, GetSpriteSize(SPR_IMG_SAVE).height + WD_IMGBTN_TOP + WD_IMGBTN_BOTTOM);
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		if (_game_mode == GM_MENU) return;
 
		_scen_toolbar_button_procs[widget](this);
 
	}
 

	
 
	virtual void OnDropdownSelect(int widget, int index)
 
	{
 
		/* The map button is in a different location on the scenario
 
		 * editor toolbar, so we need to adjust for it. */
 
		if (widget == TBSE_SMALLMAP) widget = TBN_SMALLMAP;
 
		_menu_clicked_procs[widget](index);
 
		SndPlayFx(SND_15_BEEP);
 
	}
 

	
 
	virtual EventState OnKeyPress(uint16 key, uint16 keycode)
 
	{
 
		switch (keycode) {
 
			case WKC_F1: case WKC_PAUSE: ToolbarPauseClick(this); break;
 
			case WKC_F2: ShowGameOptions(); break;
 
			case WKC_F3: MenuClickSaveLoad(); break;
 
			case WKC_F4: ToolbarScenGenLand(this); break;
 
			case WKC_F5: ToolbarScenGenTown(this); break;
 
			case WKC_F6: ToolbarScenGenIndustry(this); break;
 
			case WKC_F7: ToolbarScenBuildRoad(this); break;
 
			case WKC_F8: ToolbarScenBuildDocks(this); break;
 
			case WKC_F9: ToolbarScenPlantTrees(this); break;
 
			case WKC_F10: ToolbarScenPlaceSign(this); break;
 
			case WKC_F11: ShowMusicWindow(); break;
 
			case WKC_F12: PlaceLandBlockInfo(); break;
 
			case WKC_CTRL | 'S': MenuClickSmallScreenshot(); break;
 
			case WKC_CTRL | 'G': MenuClickWorldScreenshot(); break;
 

	
 
			/* those following are all fall through */
 
			case WKC_NUM_PLUS:
 
			case WKC_EQUALS:
 
			case WKC_SHIFT | WKC_EQUALS:
 
			case WKC_SHIFT | WKC_F5: ToolbarZoomInClick(this); break;
 

	
 
			/* those following are all fall through */
 
			case WKC_NUM_MINUS:
 
			case WKC_MINUS:
 
			case WKC_SHIFT | WKC_MINUS:
 
			case WKC_SHIFT | WKC_F6: ToolbarZoomOutClick(this); break;
 

	
 
			case 'L': ShowEditorTerraformToolbar(); break;
 
			case 'Q': case 'W': case 'E': case 'D': ShowEditorTerraformToolbarWithTool(key, keycode); break;
 
			case 'M': ShowSmallMap(); break;
 
			case 'V': ShowExtraViewPortWindow(); break;
 
			default: return ES_NOT_HANDLED;
 
		}
 
		return ES_HANDLED;
 
	}
 

	
 
	virtual void OnPlaceObject(Point pt, TileIndex tile)
 
	{
 
		_place_proc(tile);
 
	}
 

	
 
	virtual void OnTimeout()
 
	{
 
		this->SetWidgetsLoweredState(false, TBSE_DATEBACKWARD, TBSE_DATEFORWARD, WIDGET_LIST_END);
 
		this->SetWidgetDirty(TBSE_DATEBACKWARD);
 
		this->SetWidgetDirty(TBSE_DATEFORWARD);
 
	}
 

	
 
	virtual void OnTick()
 
	{
 
		if (this->IsWidgetLowered(TBSE_PAUSE) != !!_pause_mode) {
 
			this->ToggleWidgetLoweredState(TBSE_PAUSE);
 
			this->SetDirty();
 
		}
 

	
 
		if (this->IsWidgetLowered(TBSE_FASTFORWARD) != !!_fast_forward) {
 
			this->ToggleWidgetLoweredState(TBSE_FASTFORWARD);
 
			this->SetDirty();
 
		}
 
	}
 

	
 
	virtual void OnInvalidateData(int data)
 
	{
 
		if (FindWindowById(WC_MAIN_WINDOW, 0) != NULL) HandleZoomMessage(this, FindWindowById(WC_MAIN_WINDOW, 0)->viewport, TBSE_ZOOMIN, TBSE_ZOOMOUT);
 
	}
 
};
 

	
 
static const NWidgetPart _nested_toolb_scen_inner_widgets[] = {
 
	NWidget(WWT_IMGBTN, COLOUR_GREY, TBSE_PAUSE), SetDataTip(SPR_IMG_PAUSE, STR_TOOLBAR_TOOLTIP_PAUSE_GAME),
 
	NWidget(WWT_IMGBTN, COLOUR_GREY, TBSE_FASTFORWARD), SetDataTip(SPR_IMG_FASTFORWARD, STR_TOOLBAR_TOOLTIP_FORWARD),
 
	NWidget(WWT_IMGBTN, COLOUR_GREY, TBSE_SETTINGS), SetDataTip(SPR_IMG_SETTINGS, STR_TOOLBAR_TOOLTIP_OPTIONS),
 
	NWidget(WWT_IMGBTN_2, COLOUR_GREY, TBSE_SAVESCENARIO), SetDataTip(SPR_IMG_SAVE, STR_SCENEDIT_TOOLBAR_TOOLTIP_SAVE_SCENARIO_LOAD_SCENARIO),
 
	NWidget(NWID_SPACER),
 
	NWidget(WWT_PANEL, COLOUR_GREY, TBSE_SPACERPANEL), EndContainer(),
 
	NWidget(NWID_SPACER),
 
	NWidget(WWT_PANEL, COLOUR_GREY, TBSE_DATEPANEL_CONTAINER),
 
		NWidget(NWID_HORIZONTAL), SetPIP(3, 2, 3),
 
			NWidget(WWT_IMGBTN, COLOUR_GREY, TBSE_DATEBACKWARD), SetDataTip(SPR_ARROW_DOWN, STR_SCENEDIT_TOOLBAR_TOOLTIP_MOVE_THE_STARTING_DATE_BACKWARD),
 
			NWidget(WWT_EMPTY, COLOUR_GREY, TBSE_DATEPANEL),
 
			NWidget(WWT_IMGBTN, COLOUR_GREY, TBSE_DATEFORWARD), SetDataTip(SPR_ARROW_UP, STR_SCENEDIT_TOOLBAR_TOOLTIP_MOVE_THE_STARTING_DATE_FORWARD),
 
		EndContainer(),
 
	EndContainer(),
 
	NWidget(NWID_SPACER),
 
	NWidget(WWT_IMGBTN, COLOUR_GREY, TBSE_SMALLMAP), SetDataTip(SPR_IMG_SMALLMAP, STR_SCENEDIT_TOOLBAR_TOOLTIP_DISPLAY_MAP_TOWN_DIRECTORY),
 
	NWidget(NWID_SPACER),
 
	NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, TBSE_ZOOMIN), SetDataTip(SPR_IMG_ZOOMIN, STR_TOOLBAR_TOOLTIP_ZOOM_THE_VIEW_IN),
 
	NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, TBSE_ZOOMOUT), SetDataTip(SPR_IMG_ZOOMOUT, STR_TOOLBAR_TOOLTIP_ZOOM_THE_VIEW_OUT),
 
	NWidget(NWID_SPACER),
 
	NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, TBSE_LANDGENERATE), SetDataTip(SPR_IMG_LANDSCAPING, STR_SCENEDIT_TOOLBAR_LANDSCAPE_GENERATION),
 
	NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, TBSE_TOWNGENERATE), SetDataTip(SPR_IMG_TOWN, STR_SCENEDIT_TOOLBAR_TOWN_GENERATION),
 
	NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, TBSE_INDUSTRYGENERATE), SetDataTip(SPR_IMG_INDUSTRY, STR_SCENEDIT_TOOLBAR_INDUSTRY_GENERATION),
 
	NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, TBSE_BUILDROAD), SetDataTip(SPR_IMG_BUILDROAD, STR_SCENEDIT_TOOLBAR_ROAD_CONSTRUCTION),
 
	NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, TBSE_BUILDDOCKS), SetDataTip(SPR_IMG_BUILDWATER, STR_TOOLBAR_TOOLTIP_BUILD_SHIP_DOCKS),
 
	NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, TBSE_PLANTTREES), SetDataTip(SPR_IMG_PLANTTREES, STR_SCENEDIT_TOOLBAR_PLANT_TREES),
 
	NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, TBSE_PLACESIGNS), SetDataTip(SPR_IMG_SIGN, STR_SCENEDIT_TOOLBAR_PLACE_SIGN),
 
	NWidget(NWID_SPACER),
 
	NWidget(WWT_IMGBTN, COLOUR_GREY, TBN_MUSICSOUND), SetDataTip(SPR_IMG_MUSIC, STR_TOOLBAR_TOOLTIP_SHOW_SOUND_MUSIC_WINDOW),
 
	NWidget(WWT_IMGBTN, COLOUR_GREY, TBN_HELP), SetDataTip(SPR_IMG_QUERY, STR_TOOLBAR_TOOLTIP_LAND_BLOCK_INFORMATION),
 
	NWidget(WWT_IMGBTN, COLOUR_GREY, TBN_SWITCHBAR), SetDataTip(SPR_IMG_SWITCH_TOOLBAR, STR_TOOLBAR_TOOLTIP_SWITCH_TOOLBAR),
 
};
 

	
 
static NWidgetBase *MakeScenarioToolbar(int *biggest_index)
 
{
 
	return MakeNWidgets(_nested_toolb_scen_inner_widgets, lengthof(_nested_toolb_scen_inner_widgets), biggest_index, new NWidgetScenarioToolbarContainer());
 
}
 

	
 
static const NWidgetPart _nested_toolb_scen_widgets[] = {
 
	NWidgetFunction(MakeScenarioToolbar),
 
};
 

	
 
static const WindowDesc _toolb_scen_desc(
 
	0, 0, 640, 22,
 
	WC_MAIN_TOOLBAR, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_NO_FOCUS,
 
	_nested_toolb_scen_widgets, lengthof(_nested_toolb_scen_widgets)
 
);
 

	
 
/* --- Allocating the toolbar --- */
 

	
 
void AllocateToolbar()
 
{
 
	/* Clean old GUI values; railtype is (re)set by rail_gui.cpp */
 
	_last_built_roadtype = ROADTYPE_ROAD;
 

	
 
	if (_game_mode == GM_EDITOR) {
 
		new ScenarioEditorToolbarWindow(&_toolb_scen_desc);;
 
	} else {
 
		new MainToolbarWindow(&_toolb_normal_desc);
 
	}
 
}
src/town_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file town_gui.cpp GUI for towns. */
 

	
 
#include "stdafx.h"
 
#include "openttd.h"
 
#include "town.h"
 
#include "viewport_func.h"
 
#include "gfx_func.h"
 
#include "gui.h"
 
#include "command_func.h"
 
#include "company_func.h"
 
#include "company_base.h"
 
#include "company_gui.h"
 
#include "network/network.h"
 
#include "variables.h"
 
#include "strings_func.h"
 
#include "sound_func.h"
 
#include "economy_func.h"
 
#include "tilehighlight_func.h"
 
#include "sortlist_type.h"
 
#include "road_cmd.h"
 
#include "landscape.h"
 
#include "cargotype.h"
 
#include "querystring_gui.h"
 
#include "window_func.h"
 
#include "townname_func.h"
 
#include "townname_type.h"
 

	
 
#include "table/sprites.h"
 
#include "table/strings.h"
 

	
 
typedef GUIList<const Town*> GUITownList;
 

	
 
/** Widget numbers of the town authority window. */
 
enum TownAuthorityWidgets {
 
	TWA_CLOSEBOX,
 
	TWA_CAPTION,
 
	TWA_RATING_INFO,  ///< Overview with ratings for each company.
 
	TWA_COMMAND_LIST, ///< List of commands for the player.
 
	TWA_SCROLLBAR,
 
	TWA_ACTION_INFO,  ///< Additional information about the action.
 
	TWA_EXECUTE,      ///< Do-it button.
 
};
 

	
 
static const NWidgetPart _nested_town_authority_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_BROWN, TWA_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_BROWN, TWA_CAPTION), SetDataTip(STR_LOCAL_AUTHORITY_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_BROWN, TWA_RATING_INFO), SetMinimalSize(317, 92), SetResize(0, 1), EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_PANEL, COLOUR_BROWN, TWA_COMMAND_LIST), SetMinimalSize(305, 52), SetDataTip(0x0, STR_LOCAL_AUTHORITY_ACTIONS_TOOLTIP), EndContainer(),
 
		NWidget(WWT_SCROLLBAR, COLOUR_BROWN, TWA_SCROLLBAR),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_BROWN, TWA_ACTION_INFO), SetMinimalSize(317, 52), EndContainer(),
 
	NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, TWA_EXECUTE),  SetMinimalSize(317, 12), SetDataTip(STR_LOCAL_AUTHORITY_DO_IT_BUTTON, STR_LOCAL_AUTHORITY_DO_IT_TOOLTIP),
 
};
 

	
 
/** Town authority window. */
 
struct TownAuthorityWindow : Window {
 
private:
 
	Town *town;    ///< Town being displayed.
 
	int sel_index; ///< Currently selected town action, \c 0 to \c TACT_COUNT-1, \c -1 means no action selected.
 

	
 
	/**
 
	 * Get the position of the Nth set bit.
 
	 *
 
	 * If there is no Nth bit set return -1
 
	 *
 
	 * @param bits The value to search in
 
	 * @param n The Nth set bit from which we want to know the position
 
	 * @return The position of the Nth set bit
 
	 */
 
	static int GetNthSetBit(uint32 bits, int n)
 
	{
 
		if (n >= 0) {
 
			uint i;
 
			FOR_EACH_SET_BIT(i, bits) {
 
				n--;
 
				if (n < 0) return i;
 
			}
 
		}
 
		return -1;
 
	}
 

	
 
public:
 
	TownAuthorityWindow(const WindowDesc *desc, WindowNumber window_number) : Window(), sel_index(-1)
 
	{
 
		this->town = Town::Get(window_number);
 
		this->InitNested(desc, window_number);
 
		this->vscroll.SetCapacity((this->GetWidget<NWidgetBase>(TWA_COMMAND_LIST)->current_y - WD_FRAMERECT_TOP - WD_FRAMERECT_BOTTOM) / FONT_HEIGHT_NORMAL);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		int numact;
 
		uint buttons = GetMaskOfTownActions(&numact, _local_company, this->town);
 

	
 
		this->vscroll.SetCount(numact + 1);
 

	
 
		if (this->sel_index != -1 && !HasBit(buttons, this->sel_index)) {
 
			this->sel_index = -1;
 
		}
 

	
 
		this->SetWidgetDisabledState(TWA_EXECUTE, this->sel_index == -1);
 

	
 
		this->DrawWidgets();
 
		this->DrawRatings();
 
	}
 

	
 
	/** Draw the contents of the ratings panel. May request a resize of the window if the contents does not fit. */
 
	void DrawRatings()
 
	{
 
		NWidgetBase *nwid = this->GetWidget<NWidgetBase>(TWA_RATING_INFO);
 
		uint left = nwid->pos_x + WD_FRAMERECT_LEFT;
 
		uint right = nwid->pos_x + nwid->current_x - 1 - WD_FRAMERECT_RIGHT;
 

	
 
		uint y = nwid->pos_y + WD_FRAMERECT_TOP;
 

	
 
		DrawString(left, right, y, STR_LOCAL_AUTHORITY_COMPANY_RATINGS);
 
		y += FONT_HEIGHT_NORMAL;
 

	
 
		bool rtl = _dynlang.text_dir == TD_RTL;
 
		uint text_left  = left + (rtl ? 0 : 26);
 
		uint text_right = right - (rtl ? 26 : 0);
 
		uint icon_left  = rtl ? right - 14 : left;
 
		uint blob_left  = rtl ? right - 24 : left + 16;
 

	
 
		/* Draw list of companies */
 
		const Company *c;
 
		FOR_ALL_COMPANIES(c) {
 
			if ((HasBit(this->town->have_ratings, c->index) || this->town->exclusivity == c->index)) {
 
				DrawCompanyIcon(c->index, icon_left, y);
 

	
 
				SetDParam(0, c->index);
 
				SetDParam(1, c->index);
 

	
 
				int r = this->town->ratings[c->index];
 
				StringID str;
 
				(str = STR_CARGO_RATING_APPALLING, r <= RATING_APPALLING) || // Apalling
 
				(str++,                    r <= RATING_VERYPOOR)  || // Very Poor
 
				(str++,                    r <= RATING_POOR)      || // Poor
 
				(str++,                    r <= RATING_MEDIOCRE)  || // Mediocore
 
				(str++,                    r <= RATING_GOOD)      || // Good
 
				(str++,                    r <= RATING_VERYGOOD)  || // Very Good
 
				(str++,                    r <= RATING_EXCELLENT) || // Excellent
 
				(str++,                    true);                    // Outstanding
 

	
 
				SetDParam(2, str);
 
				if (this->town->exclusivity == c->index) { // red icon for company with exclusive rights
 
					DrawSprite(SPR_BLOT, PALETTE_TO_RED, blob_left, y);
 
				}
 

	
 
				DrawString(text_left, text_right, y, STR_LOCAL_AUTHORITY_COMPANY_RATING);
 
				y += FONT_HEIGHT_NORMAL;
 
			}
 
		}
 

	
 
		y = y + WD_FRAMERECT_BOTTOM - nwid->pos_y; // Compute needed size of the widget.
 
		if (y > nwid->current_y) {
 
			/* If the company list is too big to fit, mark ourself dirty and draw again. */
 
			ResizeWindow(this, 0, y - nwid->current_y);
 
		}
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		if (widget == TWA_CAPTION) SetDParam(0, this->window_number);
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case TWA_ACTION_INFO:
 
				if (this->sel_index != -1) {
 
					SetDParam(1, (_price[PR_BUILD_INDUSTRY] >> 8) * _town_action_costs[this->sel_index]);
 
					SetDParam(0, STR_LOCAL_AUTHORITY_ACTION_SMALL_ADVERTISING_CAMPAIGN + this->sel_index);
 
					DrawStringMultiLine(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, r.bottom - WD_FRAMERECT_BOTTOM,
 
								STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_SMALL_ADVERTISING + this->sel_index);
 
				}
 
				break;
 
			case TWA_COMMAND_LIST: {
 
				int numact;
 
				uint buttons = GetMaskOfTownActions(&numact, _local_company, this->town);
 
				int y = r.top + WD_FRAMERECT_TOP;
 
				int pos = this->vscroll.GetPosition();
 

	
 
				if (--pos < 0) {
 
					DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_LOCAL_AUTHORITY_ACTIONS_TITLE);
 
					y += FONT_HEIGHT_NORMAL;
 
				}
 

	
 
				for (int i = 0; buttons; i++, buttons >>= 1) {
 
					if (pos <= -5) break; ///< Draw only the 5 fitting lines
 

	
 
					if ((buttons & 1) && --pos < 0) {
 
						DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_LOCAL_AUTHORITY_ACTION_SMALL_ADVERTISING_CAMPAIGN + i, TC_ORANGE);
 
						y += FONT_HEIGHT_NORMAL;
 
					}
 
				}
 
				break;
 
			}
 
		}
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		switch (widget) {
 
			case TWA_ACTION_INFO: {
 
				assert(size->width > padding.width && size->height > padding.height);
 
				size->width -= WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
 
				size->height -= WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
 
				Dimension d = {0, 0};
 
				for (int i = 0; i < TACT_COUNT; i++) {
 
					SetDParam(1, (_price[PR_BUILD_INDUSTRY] >> 8) * _town_action_costs[i]);
 
					SetDParam(0, STR_LOCAL_AUTHORITY_ACTION_SMALL_ADVERTISING_CAMPAIGN + i);
 
					d = maxdim(d, GetStringMultiLineBoundingBox(STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_SMALL_ADVERTISING + i, *size));
 
				}
 
				*size = maxdim(*size, d);
 
				size->width += WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
 
				size->height += WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
 
				break;
 
			}
 

	
 
			case TWA_COMMAND_LIST:
 
				size->height = WD_FRAMERECT_TOP + 5 * FONT_HEIGHT_NORMAL + WD_FRAMERECT_BOTTOM;
 
				break;
 

	
 
			case TWA_RATING_INFO:
 
				size->height = WD_FRAMERECT_TOP + 9 * FONT_HEIGHT_NORMAL + WD_FRAMERECT_BOTTOM;
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnDoubleClick(Point pt, int widget) { HandleClick(pt, widget, true); }
 
	virtual void OnClick(Point pt, int widget) { HandleClick(pt, widget, false); }
 

	
 
	void HandleClick(Point pt, int widget, bool double_click)
 
	{
 
		switch (widget) {
 
			case TWA_COMMAND_LIST: {
 
				int y = (pt.y - this->GetWidget<NWidgetBase>(TWA_COMMAND_LIST)->pos_y - 1) / FONT_HEIGHT_NORMAL;
 

	
 
				if (!IsInsideMM(y, 0, 5)) return;
 

	
 
				y = GetNthSetBit(GetMaskOfTownActions(NULL, _local_company, this->town), y + this->vscroll.GetPosition() - 1);
 
				if (y >= 0) {
 
					this->sel_index = y;
 
					this->SetDirty();
 
				}
 
				/* Fall through to clicking in case we are double-clicked */
 
				if (!double_click || y < 0) break;
 
			}
 

	
 
			case TWA_EXECUTE:
 
				DoCommandP(this->town->xy, this->window_number, this->sel_index, CMD_DO_TOWN_ACTION | CMD_MSG(STR_ERROR_CAN_T_DO_THIS));
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnHundredthTick()
 
	{
 
		this->SetDirty();
 
	}
 
};
 

	
 
static const WindowDesc _town_authority_desc(
 
	WDP_AUTO, WDP_AUTO, 317, 222,
 
	WC_TOWN_AUTHORITY, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
 
	_nested_town_authority_widgets, lengthof(_nested_town_authority_widgets)
 
);
 

	
 
static void ShowTownAuthorityWindow(uint town)
 
{
 
	AllocateWindowDescFront<TownAuthorityWindow>(&_town_authority_desc, town);
 
}
 

	
 
/** Widget numbers of the town view window. */
 
enum TownViewWidgets {
 
	TVW_CLOSEBOX,
 
	TVW_CAPTION,
 
	TVW_STICKY,
 
	TVW_VIEWPORTPANEL,
 
	TVW_VIEWPORTINSET,
 
	TVW_VIEWPORT,
 
	TVW_INFOPANEL,
 
	TVW_CENTERVIEW,
 
	TVW_SHOWAUTHORITY,
 
	TVW_CHANGENAME,
 
	TVW_EXPAND,
 
	TVW_DELETE,
 
};
 

	
 
/* Town view window. */
 
struct TownViewWindow : Window {
 
private:
 
	Town *town; ///< Town displayed by the window.
 

	
 
public:
 
	enum {
 
		TVW_HEIGHT_NORMAL = 150,
 
	};
 

	
 
	TownViewWindow(const WindowDesc *desc, WindowNumber window_number) : Window()
 
	{
 
		this->CreateNestedTree(desc);
 

	
 
		this->town = Town::Get(window_number);
 
		if (this->town->larger_town) this->GetWidget<NWidgetCore>(TVW_CAPTION)->widget_data = STR_TOWN_VIEW_CITY_CAPTION;
 

	
 
		this->FinishInitNested(desc, window_number);
 

	
 
		this->flags4 |= WF_DISABLE_VP_SCROLL;
 
		NWidgetViewport *nvp = this->GetWidget<NWidgetViewport>(TVW_VIEWPORT);
 
		nvp->InitializeViewport(this, this->town->xy, ZOOM_LVL_NEWS);
 

	
 
		this->ResizeWindowAsNeeded();
 

	
 
		/* disable renaming town in network games if you are not the server */
 
		this->SetWidgetDisabledState(TVW_CHANGENAME, _networking && !_network_server);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		if (widget == TVW_CAPTION) SetDParam(0, this->town->index);
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		if (widget != TVW_INFOPANEL) return;
 

	
 
		uint y = r.top + WD_FRAMERECT_TOP;
 

	
 
		SetDParam(0, this->town->population);
 
		SetDParam(1, this->town->num_houses);
 
		DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_LEFT, y, STR_TOWN_VIEW_POPULATION_HOUSES);
 

	
 
		SetDParam(0, this->town->act_pass);
 
		SetDParam(1, this->town->max_pass);
 
		DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_LEFT, y += FONT_HEIGHT_NORMAL, STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX);
 

	
 
		SetDParam(0, this->town->act_mail);
 
		SetDParam(1, this->town->max_mail);
 
		DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_LEFT, y += FONT_HEIGHT_NORMAL, STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX);
 

	
 
		uint cargo_needed_for_growth = 0;
 
		switch (_settings_game.game_creation.landscape) {
 
			case LT_ARCTIC:
 
				if (TilePixelHeight(this->town->xy) >= LowestSnowLine()) cargo_needed_for_growth = 1;
 
				break;
 

	
 
			case LT_TROPIC:
 
				if (GetTropicZone(this->town->xy) == TROPICZONE_DESERT) cargo_needed_for_growth = 2;
 
				break;
 

	
 
			default: break;
 
		}
 

	
 
		if (cargo_needed_for_growth > 0) {
 
			DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_LEFT, y += FONT_HEIGHT_NORMAL, STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH);
 

	
 
			CargoID first_food_cargo = CT_INVALID;
 
			StringID food_name = STR_CARGO_PLURAL_FOOD;
 
			CargoID first_water_cargo = CT_INVALID;
 
			StringID water_name = STR_CARGO_PLURAL_WATER;
 
			for (CargoID cid = 0; cid < NUM_CARGO; cid++) {
 
				const CargoSpec *cs = CargoSpec::Get(cid);
 
				if (first_food_cargo == CT_INVALID && cs->town_effect == TE_FOOD) {
 
					first_food_cargo = cid;
 
					food_name = cs->name;
 
				}
 
				if (first_water_cargo == CT_INVALID && cs->town_effect == TE_WATER) {
 
					first_water_cargo = cid;
 
					water_name = cs->name;
 
				}
 
			}
 

	
 
			if (first_food_cargo != CT_INVALID && this->town->act_food > 0) {
 
				SetDParam(0, first_food_cargo);
 
				SetDParam(1, this->town->act_food);
 
				DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y += FONT_HEIGHT_NORMAL, STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_LAST_MONTH);
 
			} else {
 
				SetDParam(0, food_name);
 
				DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_LEFT, y += FONT_HEIGHT_NORMAL, STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED);
 
			}
 

	
 
			if (cargo_needed_for_growth > 1) {
 
				if (first_water_cargo != CT_INVALID && this->town->act_water > 0) {
 
					SetDParam(0, first_water_cargo);
 
					SetDParam(1, this->town->act_water);
 
					DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_LEFT, y += FONT_HEIGHT_NORMAL, STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_LAST_MONTH);
 
				} else {
 
					SetDParam(0, water_name);
 
					DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_LEFT, y += FONT_HEIGHT_NORMAL, STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED);
 
				}
 
			}
 
		}
 

	
 
		/* only show the town noise, if the noise option is activated. */
 
		if (_settings_game.economy.station_noise_level) {
 
			SetDParam(0, this->town->noise_reached);
 
			SetDParam(1, this->town->MaxTownNoise());
 
			DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_LEFT, y += FONT_HEIGHT_NORMAL, STR_TOWN_VIEW_NOISE_IN_TOWN);
 
		}
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case TVW_CENTERVIEW: // scroll to location
 
				if (_ctrl_pressed) {
 
					ShowExtraViewPortWindow(this->town->xy);
 
				} else {
 
					ScrollMainWindowToTile(this->town->xy);
 
				}
 
				break;
 

	
 
			case TVW_SHOWAUTHORITY: // town authority
 
				ShowTownAuthorityWindow(this->window_number);
 
				break;
 

	
 
			case TVW_CHANGENAME: // rename
 
				SetDParam(0, this->window_number);
 
				ShowQueryString(STR_TOWN_NAME, STR_TOWN_VIEW_RENAME_TOWN_BUTTON, MAX_LENGTH_TOWN_NAME_BYTES, MAX_LENGTH_TOWN_NAME_PIXELS, this, CS_ALPHANUMERAL, QSF_ENABLE_DEFAULT);
 
				break;
 

	
 
			case TVW_EXPAND: // expand town - only available on Scenario editor
 
				ExpandTown(this->town);
 
				break;
 

	
 
			case TVW_DELETE: // delete town - only available on Scenario editor
 
				delete this->town;
 
				break;
 
		}
 
	}
 

	
 
	void ResizeWindowAsNeeded()
 
	{
 
		uint aimed_height = 3 * FONT_HEIGHT_NORMAL + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
 

	
 
		switch (_settings_game.game_creation.landscape) {
 
			case LT_ARCTIC:
 
				if (TilePixelHeight(this->town->xy) >= LowestSnowLine()) aimed_height += 2 * FONT_HEIGHT_NORMAL;
 
				break;
 

	
 
			case LT_TROPIC:
 
				if (GetTropicZone(this->town->xy) == TROPICZONE_DESERT) aimed_height += 3 * FONT_HEIGHT_NORMAL;
 
				break;
 

	
 
			default: break;
 
		}
 

	
 
		if (_settings_game.economy.station_noise_level) aimed_height += FONT_HEIGHT_NORMAL;
 

	
 
		NWidgetBase *nwid_info = this->GetWidget<NWidgetBase>(TVW_INFOPANEL);
 
		if (aimed_height > nwid_info->current_y || (aimed_height < nwid_info->current_y && nwid_info->current_y > nwid_info->smallest_y)) {
 
			ResizeWindow(this, 0, aimed_height - nwid_info->current_y);
 
		}
 
	}
 

	
 
	virtual void OnResize()
 
	{
 
		if (this->viewport != NULL) {
 
			NWidgetViewport *nvp = this->GetWidget<NWidgetViewport>(TVW_VIEWPORT);
 
			nvp->UpdateViewportCoordinates(this);
 
		}
 
	}
 

	
 
	virtual void OnInvalidateData(int data = 0)
 
	{
 
		/* Called when setting station noise have changed, in order to resize the window */
 
		this->SetDirty(); // refresh display for current size. This will allow to avoid glitches when downgrading
 
		this->ResizeWindowAsNeeded();
 
	}
 

	
 
	virtual void OnQueryTextFinished(char *str)
 
	{
 
		if (str == NULL) return;
 

	
 
		DoCommandP(0, this->window_number, 0, CMD_RENAME_TOWN | CMD_MSG(STR_ERROR_CAN_T_RENAME_TOWN), NULL, str);
 
	}
 
};
 

	
 
static const NWidgetPart _nested_town_game_view_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_BROWN, TVW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_BROWN, TVW_CAPTION), SetDataTip(STR_TOWN_VIEW_TOWN_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_STICKYBOX, COLOUR_BROWN, TVW_STICKY),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_BROWN, TVW_VIEWPORTPANEL),
 
		NWidget(WWT_INSET, COLOUR_BROWN, TVW_VIEWPORTINSET), SetPadding(2, 2, 2, 2),
 
			NWidget(NWID_VIEWPORT, INVALID_COLOUR, TVW_VIEWPORT), SetMinimalSize(254, 86), SetFill(true, false), SetPadding(1, 1, 1, 1),
 
			NWidget(NWID_VIEWPORT, INVALID_COLOUR, TVW_VIEWPORT), SetMinimalSize(254, 86), SetFill(1, 0), SetPadding(1, 1, 1, 1),
 
		EndContainer(),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_BROWN, TVW_INFOPANEL), SetMinimalSize(260, 32), SetResize(0, 1), SetFill(true, false), EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_BROWN, TVW_INFOPANEL), SetMinimalSize(260, 32), SetResize(0, 1), SetFill(1, 0), EndContainer(),
 
	NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, TVW_CENTERVIEW), SetMinimalSize(80, 12), SetFill(true, true), SetDataTip(STR_BUTTON_LOCATION, STR_TOWN_VIEW_CENTER_TOOLTIP),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, TVW_SHOWAUTHORITY), SetMinimalSize(80, 12), SetFill(true, true), SetDataTip(STR_TOWN_VIEW_LOCAL_AUTHORITY_BUTTON, STR_TOWN_VIEW_LOCAL_AUTHORITY_TOOLTIP),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, TVW_CHANGENAME), SetMinimalSize(80, 12), SetFill(true, true), SetDataTip(STR_BUTTON_RENAME, STR_TOWN_VIEW_RENAME_TOOLTIP),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, TVW_CENTERVIEW), SetMinimalSize(80, 12), SetFill(1, 1), SetDataTip(STR_BUTTON_LOCATION, STR_TOWN_VIEW_CENTER_TOOLTIP),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, TVW_SHOWAUTHORITY), SetMinimalSize(80, 12), SetFill(1, 1), SetDataTip(STR_TOWN_VIEW_LOCAL_AUTHORITY_BUTTON, STR_TOWN_VIEW_LOCAL_AUTHORITY_TOOLTIP),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, TVW_CHANGENAME), SetMinimalSize(80, 12), SetFill(1, 1), SetDataTip(STR_BUTTON_RENAME, STR_TOWN_VIEW_RENAME_TOOLTIP),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _town_game_view_desc(
 
	WDP_AUTO, WDP_AUTO, 260, TownViewWindow::TVW_HEIGHT_NORMAL,
 
	WC_TOWN_VIEW, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON,
 
	_nested_town_game_view_widgets, lengthof(_nested_town_game_view_widgets)
 
);
 

	
 
static const NWidgetPart _nested_town_editor_view_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_BROWN, TVW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_BROWN, TVW_CAPTION), SetDataTip(STR_TOWN_VIEW_TOWN_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, TVW_CHANGENAME), SetMinimalSize(76, 14), SetDataTip(STR_BUTTON_RENAME, STR_TOWN_VIEW_RENAME_TOOLTIP),
 
		NWidget(WWT_STICKYBOX, COLOUR_BROWN, TVW_STICKY),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_BROWN, TVW_VIEWPORTPANEL),
 
		NWidget(WWT_INSET, COLOUR_BROWN, TVW_VIEWPORTINSET), SetPadding(2, 2, 2, 2),
 
			NWidget(NWID_VIEWPORT, INVALID_COLOUR, TVW_VIEWPORT), SetMinimalSize(254, 86), SetFill(true, false), SetPadding(1, 1, 1, 1),
 
			NWidget(NWID_VIEWPORT, INVALID_COLOUR, TVW_VIEWPORT), SetMinimalSize(254, 86), SetFill(1, 0), SetPadding(1, 1, 1, 1),
 
		EndContainer(),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_BROWN, TVW_INFOPANEL), SetMinimalSize(260, 32), SetResize(0, 1), SetFill(true, false), EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_BROWN, TVW_INFOPANEL), SetMinimalSize(260, 32), SetResize(0, 1), SetFill(1, 0), EndContainer(),
 
	NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, TVW_CENTERVIEW), SetMinimalSize(80, 12), SetFill(true, true), SetDataTip(STR_BUTTON_LOCATION, STR_TOWN_VIEW_CENTER_TOOLTIP),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, TVW_EXPAND), SetMinimalSize(80, 12), SetFill(true, true), SetDataTip(STR_TOWN_VIEW_EXPAND_BUTTON, STR_TOWN_VIEW_EXPAND_TOOLTIP),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, TVW_DELETE), SetMinimalSize(80, 12), SetFill(true, true), SetDataTip(STR_TOWN_VIEW_DELETE_BUTTON, STR_TOWN_VIEW_DELETE_TOOLTIP),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, TVW_CENTERVIEW), SetMinimalSize(80, 12), SetFill(1, 1), SetDataTip(STR_BUTTON_LOCATION, STR_TOWN_VIEW_CENTER_TOOLTIP),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, TVW_EXPAND), SetMinimalSize(80, 12), SetFill(1, 1), SetDataTip(STR_TOWN_VIEW_EXPAND_BUTTON, STR_TOWN_VIEW_EXPAND_TOOLTIP),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, TVW_DELETE), SetMinimalSize(80, 12), SetFill(1, 1), SetDataTip(STR_TOWN_VIEW_DELETE_BUTTON, STR_TOWN_VIEW_DELETE_TOOLTIP),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _town_editor_view_desc(
 
	WDP_AUTO, WDP_AUTO, 260, TownViewWindow::TVW_HEIGHT_NORMAL,
 
	WC_TOWN_VIEW, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON,
 
	_nested_town_editor_view_widgets, lengthof(_nested_town_editor_view_widgets)
 
);
 

	
 
void ShowTownViewWindow(TownID town)
 
{
 
	if (_game_mode == GM_EDITOR) {
 
		AllocateWindowDescFront<TownViewWindow>(&_town_editor_view_desc, town);
 
	} else {
 
		AllocateWindowDescFront<TownViewWindow>(&_town_game_view_desc, town);
 
	}
 
}
 

	
 
/** Widget numbers of town directory window. */
 
enum TownDirectoryWidgets {
 
	TDW_CLOSEBOX,
 
	TDW_CAPTION,
 
	TDW_STICKYBOX,
 
	TDW_SORTNAME,
 
	TDW_SORTPOPULATION,
 
	TDW_CENTERTOWN,
 
	TDW_SCROLLBAR,
 
	TDW_BOTTOM_PANEL,
 
	TDW_BOTTOM_TEXT,
 
	TDW_RESIZEBOX,
 
};
 

	
 
static const NWidgetPart _nested_town_directory_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_BROWN, TDW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_BROWN, TDW_CAPTION), SetDataTip(STR_TOWN_DIRECTORY_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_STICKYBOX, COLOUR_BROWN, TDW_STICKYBOX),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(NWID_VERTICAL),
 
			NWidget(NWID_HORIZONTAL),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, TDW_SORTNAME), SetMinimalSize(99, 12), SetDataTip(STR_SORT_BY_CAPTION_NAME, STR_TOOLTIP_SORT_ORDER), SetFill(true, false),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, TDW_SORTPOPULATION), SetMinimalSize(97, 12), SetDataTip(STR_SORT_BY_CAPTION_POPULATION, STR_TOOLTIP_SORT_ORDER), SetFill(true, false),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, TDW_SORTNAME), SetMinimalSize(99, 12), SetDataTip(STR_SORT_BY_CAPTION_NAME, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, TDW_SORTPOPULATION), SetMinimalSize(97, 12), SetDataTip(STR_SORT_BY_CAPTION_POPULATION, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0),
 
			EndContainer(),
 
			NWidget(WWT_PANEL, COLOUR_BROWN, TDW_CENTERTOWN), SetMinimalSize(196, 164), SetDataTip(0x0, STR_TOWN_DIRECTORY_LIST_TOOLTIP),
 
							SetFill(true, false), SetResize(0, 10), EndContainer(),
 
							SetFill(1, 0), SetResize(0, 10), EndContainer(),
 
			NWidget(WWT_PANEL, COLOUR_BROWN, TDW_BOTTOM_PANEL),
 
				NWidget(WWT_TEXT, COLOUR_BROWN, TDW_BOTTOM_TEXT), SetPadding(2, 0, 0, 2), SetMinimalSize(196, 12), SetFill(true, false), SetDataTip(STR_TOWN_POPULATION, STR_NULL),
 
				NWidget(WWT_TEXT, COLOUR_BROWN, TDW_BOTTOM_TEXT), SetPadding(2, 0, 0, 2), SetMinimalSize(196, 12), SetFill(1, 0), SetDataTip(STR_TOWN_POPULATION, STR_NULL),
 
			EndContainer(),
 
		EndContainer(),
 
		NWidget(NWID_VERTICAL),
 
			NWidget(WWT_SCROLLBAR, COLOUR_BROWN, TDW_SCROLLBAR),
 
			NWidget(WWT_RESIZEBOX, COLOUR_BROWN, TDW_RESIZEBOX),
 
		EndContainer(),
 
	EndContainer(),
 
};
 

	
 
/** Town directory window class. */
 
struct TownDirectoryWindow : public Window {
 
private:
 
	/* Runtime saved values */
 
	static Listing last_sorting;
 
	static const Town *last_town;
 

	
 
	/* Constants for sorting towns */
 
	static GUITownList::SortFunction * const sorter_funcs[];
 

	
 
	GUITownList towns;
 

	
 
	void BuildSortTownList()
 
	{
 
		if (this->towns.NeedRebuild()) {
 
			this->towns.Clear();
 

	
 
			const Town *t;
 
			FOR_ALL_TOWNS(t) {
 
				*this->towns.Append() = t;
 
			}
 

	
 
			this->towns.Compact();
 
			this->towns.RebuildDone();
 
			this->vscroll.SetCount(this->towns.Length()); // Update scrollbar as well.
 
		}
 
		/* Always sort the towns. */
 
		this->last_town = NULL;
 
		this->towns.Sort();
 
	}
 

	
 
	/** Sort by town name */
 
	static int CDECL TownNameSorter(const Town * const *a, const Town * const *b)
 
	{
 
		static char buf_cache[64];
 
		const Town *ta = *a;
 
		const Town *tb = *b;
 
		char buf[64];
 

	
 
		SetDParam(0, ta->index);
 
		GetString(buf, STR_TOWN_NAME, lastof(buf));
 

	
 
		/* If 'b' is the same town as in the last round, use the cached value
 
		 * We do this to speed stuff up ('b' is called with the same value a lot of
 
		 * times after eachother) */
 
		if (tb != last_town) {
 
			last_town = tb;
 
			SetDParam(0, tb->index);
 
			GetString(buf_cache, STR_TOWN_NAME, lastof(buf_cache));
 
		}
 

	
 
		return strcmp(buf, buf_cache);
 
	}
 

	
 
	/** Sort by population */
 
	static int CDECL TownPopulationSorter(const Town * const *a, const Town * const *b)
 
	{
 
		return (*a)->population - (*b)->population;
 
	}
 

	
 
public:
 
	TownDirectoryWindow(const WindowDesc *desc) : Window()
 
	{
 
		this->towns.SetListing(this->last_sorting);
 
		this->towns.SetSortFuncs(TownDirectoryWindow::sorter_funcs);
 
		this->towns.ForceRebuild();
 
		this->BuildSortTownList();
 

	
 
		this->InitNested(desc, 0);
 
	}
 

	
 
	~TownDirectoryWindow()
 
	{
 
		this->last_sorting = this->towns.GetListing();
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		if (widget == TDW_BOTTOM_TEXT) SetDParam(0, GetWorldPopulation());
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case TDW_SORTNAME:
 
				if (this->towns.SortType() == 0) this->DrawSortButtonState(widget, this->towns.IsDescSortOrder() ? SBS_DOWN : SBS_UP);
 
				break;
 

	
 
			case TDW_SORTPOPULATION:
 
				if (this->towns.SortType() != 0) this->DrawSortButtonState(widget, this->towns.IsDescSortOrder() ? SBS_DOWN : SBS_UP);
 
				break;
 

	
 
			case TDW_CENTERTOWN: {
 
				int n = 0;
 
				int y = r.top + WD_FRAMERECT_TOP;
 
				if (this->towns.Length() == 0) { // No towns available.
 
					DrawString(r.left + WD_FRAMERECT_LEFT, r.right, y, STR_TOWN_DIRECTORY_NONE);
 
					break;
 
				}
 
				/* At least one town available. */
 
				for (uint i = this->vscroll.GetPosition(); i < this->towns.Length(); i++) {
 
					const Town *t = this->towns[i];
 

	
 
					assert(t->xy != INVALID_TILE);
 

	
 
					SetDParam(0, t->index);
 
					SetDParam(1, t->population);
 
					DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_TOWN_DIRECTORY_TOWN);
 

	
 
					y += this->resize.step_height;
 
					if (++n == this->vscroll.GetCapacity()) break; // max number of towns in 1 window
 
				}
 
			} break;
 
		}
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		switch (widget) {
 
			case TDW_SORTNAME:
 
			case TDW_SORTPOPULATION: {
 
				Dimension d = GetStringBoundingBox(this->GetWidget<NWidgetCore>(widget)->widget_data);
 
				d.width += padding.width + WD_SORTBUTTON_ARROW_WIDTH * 2; // Doubled since the word is centered, also looks nice.
 
				d.height += padding.height;
 
				*size = maxdim(*size, d);
 
				break;
 
			}
 
			case TDW_CENTERTOWN: {
 
				Dimension d = GetStringBoundingBox(STR_TOWN_DIRECTORY_NONE);
 
				for (uint i = 0; i < this->towns.Length(); i++) {
 
					const Town *t = this->towns[i];
 

	
 
					assert(t != NULL);
 

	
 
					SetDParam(0, t->index);
 
					SetDParam(1, 10000000); // 10^7
 
					d = maxdim(d, GetStringBoundingBox(STR_TOWN_DIRECTORY_TOWN));
 
				}
 
				d.width += padding.width + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
 
				d.height += padding.height;
 
				*size = maxdim(*size, d);
 
				resize->height = d.height;
 
				break;
 
			}
 
			case TDW_BOTTOM_TEXT: {
 
				SetDParam(0, 1000000000); // 10^9
 
				Dimension d = GetStringBoundingBox(STR_TOWN_POPULATION);
 
				d.width += padding.width;
 
				d.height += padding.height;
 
				*size = maxdim(*size, d);
 
				break;
 
			}
 
		}
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case TDW_SORTNAME: // Sort by Name ascending/descending
 
				if (this->towns.SortType() == 0) {
 
					this->towns.ToggleSortOrder();
 
				} else {
 
					this->towns.SetSortType(0);
 
				}
 
				this->BuildSortTownList();
 
				this->SetDirty();
 
				break;
 

	
 
			case TDW_SORTPOPULATION: // Sort by Population ascending/descending
 
				if (this->towns.SortType() == 1) {
 
					this->towns.ToggleSortOrder();
 
				} else {
 
					this->towns.SetSortType(1);
 
				}
 
				this->BuildSortTownList();
 
				this->SetDirty();
 
				break;
 

	
 
			case TDW_CENTERTOWN: { // Click on Town Matrix
 
				uint16 id_v = (pt.y - this->GetWidget<NWidgetBase>(widget)->pos_y - WD_FRAMERECT_TOP) / this->resize.step_height;
 

	
 
				if (id_v >= this->vscroll.GetCapacity()) return; // click out of bounds
 

	
 
				id_v += this->vscroll.GetPosition();
 

	
 
				if (id_v >= this->towns.Length()) return; // click out of town bounds
 

	
 
				const Town *t = this->towns[id_v];
 
				assert(t != NULL);
 
				if (_ctrl_pressed) {
 
					ShowExtraViewPortWindow(t->xy);
 
				} else {
 
					ScrollMainWindowToTile(t->xy);
 
				}
 
				break;
 
			}
 
		}
 
	}
 

	
 
	virtual void OnHundredthTick()
 
	{
 
		this->BuildSortTownList();
 
		this->SetDirty();
 
	}
 

	
 
	virtual void OnResize()
 
	{
 
		this->vscroll.SetCapacity(this->GetWidget<NWidgetBase>(TDW_CENTERTOWN)->current_y / this->resize.step_height);
 
	}
 

	
 
	virtual void OnInvalidateData(int data)
 
	{
 
		if (data == 0) {
 
			this->towns.ForceRebuild();
 
		} else {
 
			this->towns.ForceResort();
 
		}
 
		this->BuildSortTownList();
 
	}
 
};
 

	
 
Listing TownDirectoryWindow::last_sorting = {false, 0};
 
const Town *TownDirectoryWindow::last_town = NULL;
 

	
 
/* Available town directory sorting functions */
 
GUITownList::SortFunction * const TownDirectoryWindow::sorter_funcs[] = {
 
	&TownNameSorter,
 
	&TownPopulationSorter,
 
};
 

	
 
static const WindowDesc _town_directory_desc(
 
	WDP_AUTO, WDP_AUTO, 208, 202,
 
	WC_TOWN_DIRECTORY, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE,
 
	_nested_town_directory_widgets, lengthof(_nested_town_directory_widgets)
 
);
 

	
 
void ShowTownDirectory()
 
{
 
	if (BringWindowToFrontById(WC_TOWN_DIRECTORY, 0)) return;
 
	new TownDirectoryWindow(&_town_directory_desc);
 
}
 

	
 
void CcFoundTown(bool success, TileIndex tile, uint32 p1, uint32 p2)
 
{
 
	if (success) {
 
		SndPlayTileFx(SND_1F_SPLAT, tile);
 
		if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace();
 
	}
 
}
 

	
 
void CcFoundRandomTown(bool success, TileIndex tile, uint32 p1, uint32 p2)
 
{
 
	if (success) ScrollMainWindowToTile(Town::Get(_new_town_id)->xy);
 
}
 

	
 
/** Widget numbers of town scenario editor window. */
 
enum TownScenarioEditorWidgets {
 
	TSEW_CLOSEBOX,
 
	TSEW_CAPTION,
 
	TSEW_STICKYBOX,
 
	TSEW_BACKGROUND,
 
	TSEW_NEWTOWN,
 
	TSEW_RANDOMTOWN,
 
	TSEW_MANYRANDOMTOWNS,
 
	TSEW_TOWNNAME_TEXT,
 
	TSEW_TOWNNAME_EDITBOX,
 
	TSEW_TOWNNAME_RANDOM,
 
	TSEW_TOWNSIZE,
 
	TSEW_SIZE_SMALL,
 
	TSEW_SIZE_MEDIUM,
 
	TSEW_SIZE_LARGE,
 
	TSEW_SIZE_RANDOM,
 
	TSEW_CITY,
 
	TSEW_TOWNLAYOUT,
 
	TSEW_LAYOUT_ORIGINAL,
 
	TSEW_LAYOUT_BETTER,
 
	TSEW_LAYOUT_GRID2,
 
	TSEW_LAYOUT_GRID3,
 
	TSEW_LAYOUT_RANDOM,
 
};
 

	
 
static const NWidgetPart _nested_found_town_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN, TSEW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, TSEW_CAPTION), SetDataTip(STR_FOUND_TOWN_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_STICKYBOX, COLOUR_DARK_GREEN, TSEW_STICKYBOX),
 
	EndContainer(),
 
	/* Construct new town(s) buttons. */
 
	NWidget(WWT_PANEL, COLOUR_DARK_GREEN, TSEW_BACKGROUND),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 2),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, TSEW_NEWTOWN), SetMinimalSize(156, 12), SetFill(true, false),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, TSEW_NEWTOWN), SetMinimalSize(156, 12), SetFill(1, 0),
 
										SetDataTip(STR_FOUND_TOWN_NEW_TOWN_BUTTON, STR_FOUND_TOWN_NEW_TOWN_TOOLTIP), SetPadding(0, 2, 1, 2),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, TSEW_RANDOMTOWN), SetMinimalSize(156, 12), SetFill(true, false),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, TSEW_RANDOMTOWN), SetMinimalSize(156, 12), SetFill(1, 0),
 
										SetDataTip(STR_FOUND_TOWN_RANDOM_TOWN_BUTTON, STR_FOUND_TOWN_RANDOM_TOWN_TOOLTIP), SetPadding(0, 2, 1, 2),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, TSEW_MANYRANDOMTOWNS), SetMinimalSize(156, 12), SetFill(true, false),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, TSEW_MANYRANDOMTOWNS), SetMinimalSize(156, 12), SetFill(1, 0),
 
										SetDataTip(STR_FOUND_TOWN_MANY_RANDOM_TOWNS, STR_FOUND_TOWN_RANDOM_TOWNS_TOOLTIP), SetPadding(0, 2, 0, 2),
 
		/* Town name selection. */
 
		NWidget(NWID_VERTICAL),
 
			NWidget(WWT_LABEL, COLOUR_DARK_GREEN, TSEW_TOWNSIZE), SetMinimalSize(156, 14), SetDataTip(STR_FOUND_TOWN_NAME_TITLE, STR_NULL),
 
			NWidget(WWT_EDITBOX, COLOUR_WHITE, TSEW_TOWNNAME_EDITBOX), SetMinimalSize(156, 12), SetDataTip(STR_FOUND_TOWN_NAME_EDITOR_TITLE, STR_FOUND_TOWN_NAME_EDITOR_HELP),
 
			NWidget(NWID_SPACER), SetMinimalSize(0, 3),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, TSEW_TOWNNAME_RANDOM), SetMinimalSize(78, 12), SetFill(true, false),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, TSEW_TOWNNAME_RANDOM), SetMinimalSize(78, 12), SetFill(1, 0),
 
										SetDataTip(STR_FOUND_TOWN_NAME_RANDOM_BUTTON, STR_FOUND_TOWN_NAME_RANDOM_TOOLTIP),
 
		EndContainer(),
 
		/* Town size selection. */
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(NWID_SPACER), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
			NWidget(WWT_LABEL, COLOUR_DARK_GREEN, TSEW_TOWNSIZE), SetMinimalSize(148, 14), SetDataTip(STR_FOUND_TOWN_INITIAL_SIZE_TITLE, STR_NULL),
 
			NWidget(NWID_SPACER), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
		EndContainer(),
 
		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(2, 0, 2),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, TSEW_SIZE_SMALL), SetMinimalSize(78, 12), SetFill(true, false),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, TSEW_SIZE_SMALL), SetMinimalSize(78, 12), SetFill(1, 0),
 
										SetDataTip(STR_FOUND_TOWN_INITIAL_SIZE_SMALL_BUTTON, STR_FOUND_TOWN_INITIAL_SIZE_TOOLTIP),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, TSEW_SIZE_MEDIUM), SetMinimalSize(78, 12), SetFill(true, false),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, TSEW_SIZE_MEDIUM), SetMinimalSize(78, 12), SetFill(1, 0),
 
										SetDataTip(STR_FOUND_TOWN_INITIAL_SIZE_MEDIUM_BUTTON, STR_FOUND_TOWN_INITIAL_SIZE_TOOLTIP),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 1),
 
		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(2, 0, 2),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, TSEW_SIZE_LARGE), SetMinimalSize(78, 12), SetFill(true, false),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, TSEW_SIZE_LARGE), SetMinimalSize(78, 12), SetFill(1, 0),
 
										SetDataTip(STR_FOUND_TOWN_INITIAL_SIZE_LARGE_BUTTON, STR_FOUND_TOWN_INITIAL_SIZE_TOOLTIP),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, TSEW_SIZE_RANDOM), SetMinimalSize(78, 12), SetFill(true, false),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, TSEW_SIZE_RANDOM), SetMinimalSize(78, 12), SetFill(1, 0),
 
										SetDataTip(STR_FOUND_TOWN_SIZE_RANDOM, STR_FOUND_TOWN_INITIAL_SIZE_TOOLTIP),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 3),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, TSEW_CITY), SetPadding(0, 2, 0, 2), SetMinimalSize(156, 12), SetFill(true, false),
 
										SetDataTip(STR_FOUND_TOWN_CITY, STR_FOUND_TOWN_CITY_TOOLTIP), SetFill(true, false),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, TSEW_CITY), SetPadding(0, 2, 0, 2), SetMinimalSize(156, 12), SetFill(1, 0),
 
										SetDataTip(STR_FOUND_TOWN_CITY, STR_FOUND_TOWN_CITY_TOOLTIP), SetFill(1, 0),
 
		/* Town roads selection. */
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(NWID_SPACER), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
			NWidget(WWT_LABEL, COLOUR_DARK_GREEN, TSEW_TOWNLAYOUT), SetMinimalSize(148, 14), SetDataTip(STR_FOUND_TOWN_ROAD_LAYOUT, STR_NULL),
 
			NWidget(NWID_SPACER), SetFill(true, false),
 
			NWidget(NWID_SPACER), SetFill(1, 0),
 
		EndContainer(),
 
		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(2, 0, 2),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, TSEW_LAYOUT_ORIGINAL), SetMinimalSize(78, 12), SetFill(true, false), SetDataTip(STR_FOUND_TOWN_SELECT_LAYOUT_ORIGINAL, STR_FOUND_TOWN_SELECT_TOWN_ROAD_LAYOUT),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, TSEW_LAYOUT_BETTER), SetMinimalSize(78, 12), SetFill(true, false), SetDataTip(STR_FOUND_TOWN_SELECT_LAYOUT_BETTER_ROADS, STR_FOUND_TOWN_SELECT_TOWN_ROAD_LAYOUT),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, TSEW_LAYOUT_ORIGINAL), SetMinimalSize(78, 12), SetFill(1, 0), SetDataTip(STR_FOUND_TOWN_SELECT_LAYOUT_ORIGINAL, STR_FOUND_TOWN_SELECT_TOWN_ROAD_LAYOUT),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, TSEW_LAYOUT_BETTER), SetMinimalSize(78, 12), SetFill(1, 0), SetDataTip(STR_FOUND_TOWN_SELECT_LAYOUT_BETTER_ROADS, STR_FOUND_TOWN_SELECT_TOWN_ROAD_LAYOUT),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 1),
 
		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(2, 0, 2),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, TSEW_LAYOUT_GRID2), SetMinimalSize(78, 12), SetFill(true, false), SetDataTip(STR_FOUND_TOWN_SELECT_LAYOUT_2X2_GRID, STR_FOUND_TOWN_SELECT_TOWN_ROAD_LAYOUT),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, TSEW_LAYOUT_GRID3), SetMinimalSize(78, 12), SetFill(true, false), SetDataTip(STR_FOUND_TOWN_SELECT_LAYOUT_3X3_GRID, STR_FOUND_TOWN_SELECT_TOWN_ROAD_LAYOUT),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, TSEW_LAYOUT_GRID2), SetMinimalSize(78, 12), SetFill(1, 0), SetDataTip(STR_FOUND_TOWN_SELECT_LAYOUT_2X2_GRID, STR_FOUND_TOWN_SELECT_TOWN_ROAD_LAYOUT),
 
			NWidget(WWT_TEXTBTN, COLOUR_GREY, TSEW_LAYOUT_GRID3), SetMinimalSize(78, 12), SetFill(1, 0), SetDataTip(STR_FOUND_TOWN_SELECT_LAYOUT_3X3_GRID, STR_FOUND_TOWN_SELECT_TOWN_ROAD_LAYOUT),
 
		EndContainer(),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 1),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, TSEW_LAYOUT_RANDOM), SetPadding(0, 2, 0, 2), SetMinimalSize(0, 12), SetFill(true, false),
 
										SetDataTip(STR_FOUND_TOWN_SELECT_LAYOUT_RANDOM, STR_FOUND_TOWN_SELECT_TOWN_ROAD_LAYOUT), SetFill(true, false),
 
		NWidget(WWT_TEXTBTN, COLOUR_GREY, TSEW_LAYOUT_RANDOM), SetPadding(0, 2, 0, 2), SetMinimalSize(0, 12), SetFill(1, 0),
 
										SetDataTip(STR_FOUND_TOWN_SELECT_LAYOUT_RANDOM, STR_FOUND_TOWN_SELECT_TOWN_ROAD_LAYOUT), SetFill(1, 0),
 
		NWidget(NWID_SPACER), SetMinimalSize(0, 2),
 
	EndContainer(),
 
};
 

	
 
/** Found a town window class. */
 
struct FoundTownWindow : QueryStringBaseWindow {
 
private:
 
	TownSize town_size;     ///< Selected town size
 
	TownLayout town_layout; ///< Selected town layout
 
	bool city;              ///< Are we building a city?
 
	bool townnamevalid;     ///< Is generated town name valid?
 
	uint32 townnameparts;   ///< Generated town name
 
	TownNameParams params;  ///< Town name parameters
 

	
 
public:
 
	FoundTownWindow(const WindowDesc *desc, WindowNumber window_number) :
 
			QueryStringBaseWindow(MAX_LENGTH_TOWN_NAME_BYTES),
 
			town_size(TS_MEDIUM),
 
			town_layout(_settings_game.economy.town_layout),
 
			params(_settings_game.game_creation.town_name)
 
	{
 
		this->InitNested(desc, window_number);
 
		InitializeTextBuffer(&this->text, this->edit_str_buf, this->edit_str_size, MAX_LENGTH_TOWN_NAME_PIXELS);
 
		this->RandomTownName();
 
		this->UpdateButtons();
 
	}
 

	
 
	void RandomTownName()
 
	{
 
		this->townnamevalid = GenerateTownName(&this->townnameparts);
 

	
 
		if (!this->townnamevalid) {
 
			this->edit_str_buf[0] = '\0';
 
		} else {
 
			GetTownName(this->edit_str_buf, &this->params, this->townnameparts, &this->edit_str_buf[this->edit_str_size - 1]);
 
		}
 
		UpdateTextBufferSize(&this->text);
 
		UpdateOSKOriginalText(this, TSEW_TOWNNAME_EDITBOX);
 

	
 
		this->SetFocusedWidget(TSEW_TOWNNAME_EDITBOX);
 
		this->SetWidgetDirty(TSEW_TOWNNAME_EDITBOX);
 
	}
 

	
 
	void UpdateButtons()
 
	{
 
		for (int i = TSEW_SIZE_SMALL; i <= TSEW_SIZE_RANDOM; i++) {
 
			this->SetWidgetLoweredState(i, i == TSEW_SIZE_SMALL + this->town_size);
 
		}
 

	
 
		this->SetWidgetLoweredState(TSEW_CITY, this->city);
 

	
 
		for (int i = TSEW_LAYOUT_ORIGINAL; i <= TSEW_LAYOUT_RANDOM; i++) {
 
			this->SetWidgetLoweredState(i, i == TSEW_LAYOUT_ORIGINAL + this->town_layout);
 
		}
 

	
 
		this->SetDirty();
 
	}
 

	
 
	void ExecuteFoundTownCommand(TileIndex tile, bool random, StringID errstr, CommandCallback cc)
 
	{
 
		const char *name = NULL;
 

	
 
		if (!this->townnamevalid) {
 
			name = this->edit_str_buf;
 
		} else {
 
			/* If user changed the name, send it */
 
			char buf[MAX_LENGTH_TOWN_NAME_BYTES];
 
			GetTownName(buf, &this->params, this->townnameparts, lastof(buf));
 
			if (strcmp(buf, this->edit_str_buf) != 0) name = this->edit_str_buf;
 
		}
 

	
 
		bool success = DoCommandP(tile, this->town_size | this->city << 2 | this->town_layout << 3 | random << 6,
 
				townnameparts, CMD_FOUND_TOWN | CMD_MSG(errstr), cc, name);
 

	
 
		if (success) this->RandomTownName();
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
		this->DrawEditBox(TSEW_TOWNNAME_EDITBOX);
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case TSEW_NEWTOWN:
 
				HandlePlacePushButton(this, TSEW_NEWTOWN, SPR_CURSOR_TOWN, HT_RECT, NULL);
 
				break;
 

	
 
			case TSEW_RANDOMTOWN:
 
				this->HandleButtonClick(TSEW_RANDOMTOWN);
 
				this->ExecuteFoundTownCommand(0, true, STR_ERROR_CAN_T_GENERATE_TOWN, CcFoundRandomTown);
 
				break;
 

	
 
			case TSEW_TOWNNAME_RANDOM:
 
				this->RandomTownName();
 
				break;
 

	
 
			case TSEW_MANYRANDOMTOWNS:
 
				this->HandleButtonClick(TSEW_MANYRANDOMTOWNS);
 

	
 
				_generating_world = true;
 
				UpdateNearestTownForRoadTiles(true);
 
				if (!GenerateTowns(this->town_layout)) {
 
					ShowErrorMessage(STR_ERROR_CAN_T_GENERATE_TOWN, STR_ERROR_NO_SPACE_FOR_TOWN, 0, 0);
 
				}
 
				UpdateNearestTownForRoadTiles(false);
 
				_generating_world = false;
 
				break;
 

	
 
			case TSEW_SIZE_SMALL: case TSEW_SIZE_MEDIUM: case TSEW_SIZE_LARGE: case TSEW_SIZE_RANDOM:
 
				this->town_size = (TownSize)(widget - TSEW_SIZE_SMALL);
 
				this->UpdateButtons();
 
				break;
 

	
 
			case TSEW_CITY:
 
				this->city ^= true;
 
				this->SetWidgetLoweredState(TSEW_CITY, this->city);
 
				this->SetDirty();
 
				break;
 

	
 
			case TSEW_LAYOUT_ORIGINAL: case TSEW_LAYOUT_BETTER: case TSEW_LAYOUT_GRID2:
 
			case TSEW_LAYOUT_GRID3: case TSEW_LAYOUT_RANDOM:
 
				this->town_layout = (TownLayout)(widget - TSEW_LAYOUT_ORIGINAL);
 
				this->UpdateButtons();
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnTimeout()
 
	{
 
		this->RaiseWidget(TSEW_RANDOMTOWN);
 
		this->RaiseWidget(TSEW_MANYRANDOMTOWNS);
 
		this->SetDirty();
 
	}
 

	
 
	virtual void OnMouseLoop()
 
	{
 
		this->HandleEditBox(TSEW_TOWNNAME_EDITBOX);
 
	}
 

	
 
	virtual EventState OnKeyPress(uint16 key, uint16 keycode)
 
	{
 
		EventState state;
 
		this->HandleEditBoxKey(TSEW_TOWNNAME_EDITBOX, key, keycode, state);
 
		return state;
 
	}
 

	
 
	virtual void OnPlaceObject(Point pt, TileIndex tile)
 
	{
 
		this->ExecuteFoundTownCommand(tile, false, STR_ERROR_CAN_T_FOUND_TOWN_HERE, CcFoundTown);
 
	}
 

	
 
	virtual void OnPlaceObjectAbort()
 
	{
 
		this->RaiseButtons();
 
		this->UpdateButtons();
 
	}
 
};
 

	
 
static const WindowDesc _found_town_desc(
 
	WDP_AUTO, WDP_AUTO, 160, 162,
 
	WC_FOUND_TOWN, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_STICKY_BUTTON | WDF_CONSTRUCTION,
 
	_nested_found_town_widgets, lengthof(_nested_found_town_widgets)
 
);
 

	
 
void ShowFoundTownWindow()
 
{
 
	if (_game_mode != GM_EDITOR && !Company::IsValidID(_local_company)) return;
 
	AllocateWindowDescFront<FoundTownWindow>(&_found_town_desc, 0);
 
}
src/transparency_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file transparency_gui.cpp The transparency GUI. */
 

	
 
#include "stdafx.h"
 
#include "openttd.h"
 
#include "window_gui.h"
 
#include "transparency.h"
 
#include "sound_func.h"
 

	
 
#include "table/sprites.h"
 
#include "table/strings.h"
 

	
 
TransparencyOptionBits _transparency_opt;
 
TransparencyOptionBits _transparency_lock;
 
TransparencyOptionBits _invisibility_opt;
 

	
 
/** Widget numbers of the transparency window. */
 
enum TransparencyToolbarWidgets {
 
	TTW_WIDGET_CLOSEBOX,                 ///< Closebox.
 
	TTW_WIDGET_CAPTION,                  ///< Titlebar caption.
 
	TTW_WIDGET_STICKYBOX,                ///< Stickybox.
 

	
 
	/* Button row. */
 
	TTW_WIDGET_BEGIN,                    ///< First toggle button.
 
	TTW_WIDGET_SIGNS = TTW_WIDGET_BEGIN, ///< Signs background transparency toggle button.
 
	TTW_WIDGET_TREES,                    ///< Trees transparency toggle button.
 
	TTW_WIDGET_HOUSES,                   ///< Houses transparency toggle button.
 
	TTW_WIDGET_INDUSTRIES,               ///< industries transparency toggle button.
 
	TTW_WIDGET_BUILDINGS,                ///< Company buildings and structures transparency toggle button.
 
	TTW_WIDGET_BRIDGES,                  ///< Bridges transparency toggle button.
 
	TTW_WIDGET_STRUCTURES,               ///< Unmovable structures transparency toggle button.
 
	TTW_WIDGET_CATENARY,                 ///< Catenary transparency toggle button.
 
	TTW_WIDGET_LOADING,                  ///< Loading indicators transparency toggle button.
 
	TTW_WIDGET_END,                      ///< End of toggle buttons.
 

	
 
	/* Panel with buttons for invisibility */
 
	TTW_WIDGET_BUTTONS,                  ///< Panel with 'invisibility' buttons.
 
};
 

	
 
class TransparenciesWindow : public Window
 
{
 
public:
 
	TransparenciesWindow(const WindowDesc *desc, int window_number) : Window()
 
	{
 
		this->InitNested(desc, window_number);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		OnInvalidateData(0); // Must be sure that the widgets show the transparency variable changes, also when we use shortcuts.
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case TTW_WIDGET_SIGNS:
 
			case TTW_WIDGET_TREES:
 
			case TTW_WIDGET_HOUSES:
 
			case TTW_WIDGET_INDUSTRIES:
 
			case TTW_WIDGET_BUILDINGS:
 
			case TTW_WIDGET_BRIDGES:
 
			case TTW_WIDGET_STRUCTURES:
 
			case TTW_WIDGET_CATENARY:
 
			case TTW_WIDGET_LOADING: {
 
				uint i = widget - TTW_WIDGET_BEGIN;
 
				if (HasBit(_transparency_lock, i)) DrawSprite(SPR_LOCK, PAL_NONE, r.left + 1, r.top + 1);
 
				break;
 
			}
 
			case TTW_WIDGET_BUTTONS:
 
				for (uint i = TTW_WIDGET_BEGIN; i < TTW_WIDGET_END; i++) {
 
					if (i == TTW_WIDGET_LOADING) continue; // Do not draw button for invisible loading indicators.
 

	
 
					const NWidgetBase *wi = this->GetWidget<NWidgetBase>(i);
 
					DrawFrameRect(wi->pos_x + 1, r.top + 2, wi->pos_x + wi->current_x - 2, r.bottom - 2, COLOUR_PALE_GREEN,
 
							HasBit(_invisibility_opt, i - TTW_WIDGET_BEGIN) ? FR_LOWERED : FR_NONE);
 
				}
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		if (widget >= TTW_WIDGET_BEGIN && widget < TTW_WIDGET_END) {
 
			if (_ctrl_pressed) {
 
				/* toggle the bit of the transparencies lock variable */
 
				ToggleTransparencyLock((TransparencyOption)(widget - TTW_WIDGET_BEGIN));
 
				this->SetDirty();
 
			} else {
 
				/* toggle the bit of the transparencies variable and play a sound */
 
				ToggleTransparency((TransparencyOption)(widget - TTW_WIDGET_BEGIN));
 
				SndPlayFx(SND_15_BEEP);
 
				MarkWholeScreenDirty();
 
			}
 
		} else if (widget == TTW_WIDGET_BUTTONS) {
 
			uint i;
 
			for (i = TTW_WIDGET_BEGIN; i < TTW_WIDGET_END; i++) {
 
				const NWidgetBase *nwid = this->GetWidget<NWidgetBase>(i);
 
				if (IsInsideBS(pt.x, nwid->pos_x, nwid->current_x))
 
					break;
 
			}
 
			if (i == TTW_WIDGET_LOADING || i == TTW_WIDGET_END) return;
 

	
 
			ToggleInvisibility((TransparencyOption)(i - TTW_WIDGET_BEGIN));
 
			SndPlayFx(SND_15_BEEP);
 

	
 
			/* Redraw whole screen only if transparency is set */
 
			if (IsTransparencySet((TransparencyOption)(i - TTW_WIDGET_BEGIN))) {
 
				MarkWholeScreenDirty();
 
			} else {
 
				this->SetWidgetDirty(TTW_WIDGET_BUTTONS);
 
			}
 
		}
 
	}
 

	
 
	virtual void OnInvalidateData(int data)
 
	{
 
		for (uint i = TTW_WIDGET_BEGIN; i < TTW_WIDGET_END; i++) {
 
			this->SetWidgetLoweredState(i, IsTransparencySet((TransparencyOption)(i - TTW_WIDGET_BEGIN)));
 
		}
 
	}
 
};
 

	
 
static const NWidgetPart _nested_transparency_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN, TTW_WIDGET_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, TTW_WIDGET_CAPTION), SetDataTip(STR_TRANSPARENCY_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_STICKYBOX, COLOUR_DARK_GREEN, TTW_WIDGET_STICKYBOX),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, TTW_WIDGET_SIGNS), SetMinimalSize(22, 22), SetFill(false, true), SetDataTip(SPR_IMG_SIGN, STR_TRANSPARENT_SIGNS_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, TTW_WIDGET_TREES), SetMinimalSize(22, 22), SetFill(false, true), SetDataTip(SPR_IMG_PLANTTREES, STR_TRANSPARENT_TREES_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, TTW_WIDGET_HOUSES), SetMinimalSize(22, 22), SetFill(false, true), SetDataTip(SPR_IMG_TOWN, STR_TRANSPARENT_HOUSES_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, TTW_WIDGET_INDUSTRIES), SetMinimalSize(22, 22), SetFill(false, true), SetDataTip(SPR_IMG_INDUSTRY, STR_TRANSPARENT_INDUSTRIES_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, TTW_WIDGET_BUILDINGS), SetMinimalSize(22, 22), SetFill(false, true), SetDataTip(SPR_IMG_COMPANY_LIST, STR_TRANSPARENT_BUILDINGS_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, TTW_WIDGET_BRIDGES), SetMinimalSize(43, 22), SetFill(false, true), SetDataTip(SPR_IMG_BRIDGE, STR_TRANSPARENT_BRIDGES_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, TTW_WIDGET_STRUCTURES), SetMinimalSize(22, 22), SetFill(false, true), SetDataTip(SPR_IMG_TRANSMITTER, STR_TRANSPARENT_STRUCTURES_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, TTW_WIDGET_CATENARY), SetMinimalSize(22, 22), SetFill(false, true), SetDataTip(SPR_BUILD_X_ELRAIL, STR_TRANSPARENT_CATENARY_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, TTW_WIDGET_LOADING), SetMinimalSize(22, 22), SetFill(false, true), SetDataTip(SPR_IMG_TRAINLIST, STR_TRANSPARENT_LOADING_TOOLTIP),
 
		NWidget(WWT_PANEL, COLOUR_DARK_GREEN, TTW_WIDGET_END), SetFill(true, true), EndContainer(),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, TTW_WIDGET_SIGNS), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_SIGN, STR_TRANSPARENT_SIGNS_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, TTW_WIDGET_TREES), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_PLANTTREES, STR_TRANSPARENT_TREES_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, TTW_WIDGET_HOUSES), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_TOWN, STR_TRANSPARENT_HOUSES_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, TTW_WIDGET_INDUSTRIES), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_INDUSTRY, STR_TRANSPARENT_INDUSTRIES_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, TTW_WIDGET_BUILDINGS), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_COMPANY_LIST, STR_TRANSPARENT_BUILDINGS_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, TTW_WIDGET_BRIDGES), SetMinimalSize(43, 22), SetFill(0, 1), SetDataTip(SPR_IMG_BRIDGE, STR_TRANSPARENT_BRIDGES_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, TTW_WIDGET_STRUCTURES), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_TRANSMITTER, STR_TRANSPARENT_STRUCTURES_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, TTW_WIDGET_CATENARY), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_BUILD_X_ELRAIL, STR_TRANSPARENT_CATENARY_TOOLTIP),
 
		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, TTW_WIDGET_LOADING), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_TRAINLIST, STR_TRANSPARENT_LOADING_TOOLTIP),
 
		NWidget(WWT_PANEL, COLOUR_DARK_GREEN, TTW_WIDGET_END), SetFill(1, 1), EndContainer(),
 
	EndContainer(),
 
	/* Panel with 'inivisibility' buttons. */
 
	NWidget(WWT_PANEL, COLOUR_DARK_GREEN, TTW_WIDGET_BUTTONS), SetMinimalSize(219, 13), SetDataTip(0x0, STR_TRANSPARENT_INVISIBLE_TOOLTIP),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _transparency_desc(
 
	WDP_ALIGN_TBR, 94, 219, 49,
 
	WC_TRANSPARENCY_TOOLBAR, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_STICKY_BUTTON,
 
	_nested_transparency_widgets, lengthof(_nested_transparency_widgets)
 
);
 

	
 
void ShowTransparencyToolbar()
 
{
 
	AllocateWindowDescFront<TransparenciesWindow>(&_transparency_desc, 0);
 
}
src/vehicle_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file vehicle_gui.cpp The base GUI for all vehicles. */
 

	
 
#include "stdafx.h"
 
#include "openttd.h"
 
#include "debug.h"
 
#include "company_func.h"
 
#include "gui.h"
 
#include "window_gui.h"
 
#include "textbuf_gui.h"
 
#include "command_func.h"
 
#include "vehicle_gui.h"
 
#include "vehicle_gui_base.h"
 
#include "viewport_func.h"
 
#include "gfx_func.h"
 
#include "newgrf_engine.h"
 
#include "newgrf_text.h"
 
#include "waypoint_base.h"
 
#include "roadveh.h"
 
#include "train.h"
 
#include "aircraft.h"
 
#include "depot_base.h"
 
#include "group_gui.h"
 
#include "strings_func.h"
 
#include "window_func.h"
 
#include "vehicle_func.h"
 
#include "autoreplace_gui.h"
 
#include "string_func.h"
 
#include "widgets/dropdown_func.h"
 
#include "timetable.h"
 
#include "vehiclelist.h"
 
#include "articulated_vehicles.h"
 
#include "cargotype.h"
 
#include "spritecache.h"
 

	
 
#include "table/sprites.h"
 
#include "table/strings.h"
 

	
 
Sorting _sorting;
 

	
 
static GUIVehicleList::SortFunction VehicleNumberSorter;
 
static GUIVehicleList::SortFunction VehicleNameSorter;
 
static GUIVehicleList::SortFunction VehicleAgeSorter;
 
static GUIVehicleList::SortFunction VehicleProfitThisYearSorter;
 
static GUIVehicleList::SortFunction VehicleProfitLastYearSorter;
 
static GUIVehicleList::SortFunction VehicleCargoSorter;
 
static GUIVehicleList::SortFunction VehicleReliabilitySorter;
 
static GUIVehicleList::SortFunction VehicleMaxSpeedSorter;
 
static GUIVehicleList::SortFunction VehicleModelSorter;
 
static GUIVehicleList::SortFunction VehicleValueSorter;
 
static GUIVehicleList::SortFunction VehicleLengthSorter;
 
static GUIVehicleList::SortFunction VehicleTimeToLiveSorter;
 
static GUIVehicleList::SortFunction VehicleTimetableDelaySorter;
 

	
 
GUIVehicleList::SortFunction * const BaseVehicleListWindow::vehicle_sorter_funcs[] = {
 
	&VehicleNumberSorter,
 
	&VehicleNameSorter,
 
	&VehicleAgeSorter,
 
	&VehicleProfitThisYearSorter,
 
	&VehicleProfitLastYearSorter,
 
	&VehicleCargoSorter,
 
	&VehicleReliabilitySorter,
 
	&VehicleMaxSpeedSorter,
 
	&VehicleModelSorter,
 
	&VehicleValueSorter,
 
	&VehicleLengthSorter,
 
	&VehicleTimeToLiveSorter,
 
	&VehicleTimetableDelaySorter,
 
};
 

	
 
const StringID BaseVehicleListWindow::vehicle_sorter_names[] = {
 
	STR_SORT_BY_NUMBER,
 
	STR_SORT_BY_NAME,
 
	STR_SORT_BY_AGE,
 
	STR_SORT_BY_PROFIT_THIS_YEAR,
 
	STR_SORT_BY_PROFIT_LAST_YEAR,
 
	STR_SORT_BY_TOTAL_CAPACITY_PER_CARGOTYPE,
 
	STR_SORT_BY_RELIABILITY,
 
	STR_SORT_BY_MAX_SPEED,
 
	STR_SORT_BY_MODEL,
 
	STR_SORT_BY_VALUE,
 
	STR_SORT_BY_LENGTH,
 
	STR_SORT_BY_LIFE_TIME,
 
	STR_SORT_BY_TIMETABLE_DELAY,
 
	INVALID_STRING_ID
 
};
 

	
 
void BaseVehicleListWindow::BuildVehicleList(Owner owner, uint16 index, uint16 window_type)
 
{
 
	if (!this->vehicles.NeedRebuild()) return;
 

	
 
	DEBUG(misc, 3, "Building vehicle list for company %d at station %d", owner, index);
 

	
 
	GenerateVehicleSortList(&this->vehicles, this->vehicle_type, owner, index, window_type);
 

	
 
	uint unitnumber = 0;
 
	for (const Vehicle **v = this->vehicles.Begin(); v != this->vehicles.End(); v++) {
 
		unitnumber = max<uint>(unitnumber, (*v)->unitnumber);
 
	}
 

	
 
	/* Because 111 is much less wide than e.g. 999 we use the
 
	 * wider numbers to determine the width instead of just
 
	 * the random number that it seems to be. */
 
	if (unitnumber >= 1000) {
 
		this->max_unitnumber = 9999;
 
	} else if (unitnumber >= 100) {
 
		this->max_unitnumber = 999;
 
	} else {
 
		this->max_unitnumber = 99;
 
	}
 

	
 
	this->vehicles.RebuildDone();
 
	this->vscroll.SetCount(this->vehicles.Length());
 
}
 

	
 
/* cached values for VehicleNameSorter to spare many GetString() calls */
 
static const Vehicle *_last_vehicle[2] = { NULL, NULL };
 

	
 
void BaseVehicleListWindow::SortVehicleList()
 
{
 
	if (this->vehicles.Sort()) return;
 

	
 
	/* invalidate cached values for name sorter - vehicle names could change */
 
	_last_vehicle[0] = _last_vehicle[1] = NULL;
 
}
 

	
 
void DepotSortList(VehicleList *list)
 
{
 
	if (list->Length() < 2) return;
 
	QSortT(list->Begin(), list->Length(), &VehicleNumberSorter);
 
}
 

	
 
/** draw the vehicle profit button in the vehicle list window. */
 
static void DrawVehicleProfitButton(const Vehicle *v, int x, int y)
 
{
 
	SpriteID pal;
 

	
 
	/* draw profit-based coloured icons */
 
	if (v->age <= DAYS_IN_YEAR * 2) {
 
		pal = PALETTE_TO_GREY;
 
	} else if (v->GetDisplayProfitLastYear() < 0) {
 
		pal = PALETTE_TO_RED;
 
	} else if (v->GetDisplayProfitLastYear() < 10000) {
 
		pal = PALETTE_TO_YELLOW;
 
	} else {
 
		pal = PALETTE_TO_GREEN;
 
	}
 
	DrawSprite(SPR_BLOT, pal, x, y);
 
}
 

	
 
struct RefitOption {
 
	CargoID cargo;
 
	byte subtype;
 
	uint16 value;
 
	EngineID engine;
 
};
 

	
 
struct RefitList {
 
	uint num_lines;     ///< Number of #items.
 
	RefitOption *items;
 
};
 

	
 
static RefitList *BuildRefitList(const Vehicle *v)
 
{
 
	uint max_lines = 256;
 
	RefitOption *refit = CallocT<RefitOption>(max_lines);
 
	RefitList *list = CallocT<RefitList>(1);
 
	Vehicle *u = const_cast<Vehicle *>(v);
 
	uint num_lines = 0;
 
	uint i;
 

	
 
	do {
 
		const Engine *e = Engine::Get(u->engine_type);
 
		uint32 cmask = e->info.refit_mask;
 
		byte callback_mask = e->info.callback_mask;
 

	
 
		/* Skip this engine if it has no capacity */
 
		if (u->cargo_cap == 0) continue;
 

	
 
		/* Loop through all cargos in the refit mask */
 
		for (CargoID cid = 0; cid < NUM_CARGO && num_lines < max_lines; cid++) {
 
			/* Skip cargo type if it's not listed */
 
			if (!HasBit(cmask, cid)) continue;
 

	
 
			/* Check the vehicle's callback mask for cargo suffixes */
 
			if (HasBit(callback_mask, CBM_VEHICLE_CARGO_SUFFIX)) {
 
				/* Make a note of the original cargo type. It has to be
 
				 * changed to test the cargo & subtype... */
 
				CargoID temp_cargo = u->cargo_type;
 
				byte temp_subtype  = u->cargo_subtype;
 
				byte refit_cyc;
 

	
 
				u->cargo_type = cid;
 

	
 
				for (refit_cyc = 0; refit_cyc < 16 && num_lines < max_lines; refit_cyc++) {
 
					bool duplicate = false;
 
					uint16 callback;
 

	
 
					u->cargo_subtype = refit_cyc;
 
					callback = GetVehicleCallback(CBID_VEHICLE_CARGO_SUFFIX, 0, 0, u->engine_type, u);
 

	
 
					if (callback == 0xFF) callback = CALLBACK_FAILED;
 
					if (refit_cyc != 0 && callback == CALLBACK_FAILED) break;
 

	
 
					/* Check if this cargo and subtype combination are listed */
 
					for (i = 0; i < num_lines && !duplicate; i++) {
 
						if (refit[i].cargo == cid && refit[i].value == callback) duplicate = true;
 
					}
 

	
 
					if (duplicate) continue;
 

	
 
					refit[num_lines].cargo   = cid;
 
					refit[num_lines].subtype = refit_cyc;
 
					refit[num_lines].value   = callback;
 
					refit[num_lines].engine  = u->engine_type;
 
					num_lines++;
 
				}
 

	
 
				/* Reset the vehicle's cargo type */
 
				u->cargo_type    = temp_cargo;
 
				u->cargo_subtype = temp_subtype;
 
			} else {
 
				/* No cargo suffix callback -- use no subtype */
 
				bool duplicate = false;
 

	
 
				for (i = 0; i < num_lines && !duplicate; i++) {
 
					if (refit[i].cargo == cid && refit[i].value == CALLBACK_FAILED) duplicate = true;
 
				}
 

	
 
				if (!duplicate) {
 
					refit[num_lines].cargo   = cid;
 
					refit[num_lines].subtype = 0;
 
					refit[num_lines].value   = CALLBACK_FAILED;
 
					refit[num_lines].engine  = INVALID_ENGINE;
 
					num_lines++;
 
				}
 
			}
 
		}
 
	} while ((v->type == VEH_TRAIN || v->type == VEH_ROAD) && (u = u->Next()) != NULL && num_lines < max_lines);
 

	
 
	list->num_lines = num_lines;
 
	list->items = refit;
 

	
 
	return list;
 
}
 

	
 
/** Draw the list of available refit options for a consist and highlight the selected refit option (if any).
 
 * @param *list First vehicle in consist to get the refit-options of
 
 * @param sel   Selected refit cargo-type in the window
 
 * @param pos   Position of the selected item in caller widow
 
 * @param rows  Number of rows(capacity) in caller window
 
 * @param delta Step height in caller window
 
 * @param r     Rectangle of the matrix widget.
 
 */
 
static void DrawVehicleRefitWindow(const RefitList *list, int sel, uint pos, uint rows, uint delta, const Rect &r)
 
{
 
	uint y = r.top + WD_MATRIX_TOP;
 
	/* Draw the list, and find the selected cargo (by its position in list) */
 
	for (uint i = pos; i < pos + rows && i < list->num_lines; i++) {
 
		TextColour colour = (sel == (int)i) ? TC_WHITE : TC_BLACK;
 
		RefitOption *refit = &list->items[i];
 

	
 
		/* Get the cargo name */
 
		SetDParam(0, CargoSpec::Get(refit->cargo)->name);
 

	
 
		/* If the callback succeeded, draw the cargo suffix */
 
		if (refit->value != CALLBACK_FAILED) {
 
			SetDParam(1, GetGRFStringID(GetEngineGRFID(refit->engine), 0xD000 + refit->value));
 
			DrawString(r.left + WD_MATRIX_LEFT, r.right - WD_MATRIX_RIGHT, y, STR_JUST_STRING_SPACE_STRING, colour);
 
		} else {
 
			DrawString(r.left + WD_MATRIX_LEFT, r.right - WD_MATRIX_RIGHT, y, STR_JUST_STRING, colour);
 
		}
 

	
 
		y += delta;
 
	}
 
}
 

	
 
/** Widget numbers of the vehicle refit window. */
 
enum VehicleRefitWidgets {
 
	VRW_CLOSEBOX,
 
	VRW_CAPTION,
 
	VRW_SELECTHEADER,
 
	VRW_MATRIX,
 
	VRW_SCROLLBAR,
 
	VRW_INFOPANEL,
 
	VRW_REFITBUTTON,
 
	VRW_RESIZEBOX,
 
};
 

	
 
/** Refit cargo window. */
 
struct RefitWindow : public Window {
 
	int sel;              ///< Index in refit options, \c -1 if nothing is selected.
 
	RefitOption *cargo;   ///< Refit option selected by \v sel.
 
	RefitList *list;      ///< List of cargo types available for refitting.
 
	uint length;          ///< For trains, the number of vehicles.
 
	VehicleOrderID order; ///< If not #INVALID_VEH_ORDER_ID, selection is part of a refit order (rather than execute directly).
 

	
 
	RefitWindow(const WindowDesc *desc, const Vehicle *v, VehicleOrderID order) : Window()
 
	{
 
		this->CreateNestedTree(desc);
 

	
 
		this->GetWidget<NWidgetCore>(VRW_SELECTHEADER)->tool_tip = STR_REFIT_TRAIN_LIST_TOOLTIP + v->type;
 
		this->GetWidget<NWidgetCore>(VRW_MATRIX)->tool_tip       = STR_REFIT_TRAIN_LIST_TOOLTIP + v->type;
 
		NWidgetCore *nwi = this->GetWidget<NWidgetCore>(VRW_REFITBUTTON);
 
		nwi->widget_data = STR_REFIT_TRAIN_REFIT_BUTTON + v->type;
 
		nwi->tool_tip    = STR_REFIT_TRAIN_REFIT_TOOLTIP + v->type;
 

	
 
		this->FinishInitNested(desc, v->index);
 
		this->owner = v->owner;
 

	
 
		this->order = order;
 
		this->sel  = -1;
 
		this->list = BuildRefitList(v);
 
		if (v->type == VEH_TRAIN) this->length = CountVehiclesInChain(v);
 
		this->vscroll.SetCount(this->list->num_lines);
 
	}
 

	
 
	~RefitWindow()
 
	{
 
		free(this->list->items);
 
		free(this->list);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		Vehicle *v = Vehicle::Get(this->window_number);
 

	
 
		if (v->type == VEH_TRAIN) {
 
			uint length = CountVehiclesInChain(v);
 

	
 
			if (length != this->length) {
 
				/* Consist length has changed, so rebuild the refit list */
 
				free(this->list->items);
 
				free(this->list);
 
				this->list = BuildRefitList(v);
 
				this->length = length;
 
			}
 
		}
 

	
 
		this->vscroll.SetCount(this->list->num_lines);
 

	
 
		this->cargo = (this->sel >= 0 && this->sel < (int)this->list->num_lines) ? &this->list->items[this->sel] : NULL;
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		switch (widget) {
 
			case VRW_MATRIX:
 
				resize->height = WD_MATRIX_TOP + FONT_HEIGHT_NORMAL + WD_MATRIX_BOTTOM;
 
				size->height = resize->height * 8;
 
				break;
 
			case VRW_INFOPANEL:
 
				size->height = max(size->height, (uint)(WD_FRAMERECT_TOP + 2 * FONT_HEIGHT_NORMAL + WD_FRAMERECT_BOTTOM));
 
				break;
 
		}
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		if (widget == VRW_CAPTION) SetDParam(0, Vehicle::Get(this->window_number)->index);
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case VRW_MATRIX:
 
				DrawVehicleRefitWindow(this->list, this->sel, this->vscroll.GetPosition(), this->vscroll.GetCapacity(), this->resize.step_height, r);
 
				break;
 

	
 
			case VRW_INFOPANEL:
 
				if (this->cargo != NULL) {
 
					Vehicle *v = Vehicle::Get(this->window_number);
 
					CommandCost cost = DoCommand(v->tile, v->index, this->cargo->cargo | this->cargo->subtype << 8, DC_QUERY_COST, GetCmdRefitVeh(v->type));
 
					if (CmdSucceeded(cost)) {
 
						SetDParam(0, this->cargo->cargo);
 
						SetDParam(1, _returned_refit_capacity);
 
						SetDParam(2, cost.GetCost());
 
						DrawStringMultiLine(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT,
 
								r.top + WD_FRAMERECT_TOP, r.bottom - WD_FRAMERECT_BOTTOM, STR_REFIT_NEW_CAPACITY_COST_OF_REFIT);
 
					}
 
				}
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnDoubleClick(Point pt, int widget)
 
	{
 
		if (widget == VRW_MATRIX) this->OnClick(pt, VRW_REFITBUTTON);
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case VRW_MATRIX: { // listbox
 
				int y = pt.y - this->GetWidget<NWidgetBase>(VRW_MATRIX)->pos_y;
 
				if (y >= 0) {
 
					this->sel = (y / (int)this->resize.step_height) + this->vscroll.GetPosition();
 
					this->SetDirty();
 
				}
 
				break;
 
			}
 

	
 
			case VRW_REFITBUTTON: // refit button
 
				if (this->cargo != NULL) {
 
					const Vehicle *v = Vehicle::Get(this->window_number);
 

	
 
					if (this->order == INVALID_VEH_ORDER_ID) {
 
						if (DoCommandP(v->tile, v->index, this->cargo->cargo | this->cargo->subtype << 8, GetCmdRefitVeh(v))) delete this;
 
					} else {
 
						if (DoCommandP(v->tile, v->index, this->cargo->cargo | this->cargo->subtype << 8 | this->order << 16, CMD_ORDER_REFIT)) delete this;
 
					}
 
				}
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnResize()
 
	{
 
		NWidgetCore *nwi = this->GetWidget<NWidgetCore>(VRW_MATRIX);
 
		this->vscroll.SetCapacity(nwi->current_y / this->resize.step_height);
 
		nwi->widget_data = (this->vscroll.GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START);
 
	}
 
};
 

	
 
static const NWidgetPart _nested_vehicle_refit_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, VRW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, VRW_CAPTION), SetDataTip(STR_REFIT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
	EndContainer(),
 
	NWidget(WWT_TEXTBTN, COLOUR_GREY, VRW_SELECTHEADER), SetMinimalSize(240, 14), SetDataTip(STR_REFIT_TITLE, STR_NULL),
 
	/* Matrix + scrollbar. */
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_MATRIX, COLOUR_GREY, VRW_MATRIX), SetMinimalSize(228, 112), SetResize(0, 14), SetDataTip(0x801, STR_NULL),
 
		NWidget(WWT_SCROLLBAR, COLOUR_GREY, VRW_SCROLLBAR),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, VRW_INFOPANEL), SetMinimalSize(240, 22), EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, VRW_REFITBUTTON), SetMinimalSize(228, 12),
 
		NWidget(WWT_RESIZEBOX, COLOUR_GREY, VRW_RESIZEBOX),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _vehicle_refit_desc(
 
	WDP_AUTO, WDP_AUTO, 240, 174,
 
	WC_VEHICLE_REFIT, WC_VEHICLE_VIEW,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_RESIZABLE | WDF_CONSTRUCTION,
 
	_nested_vehicle_refit_widgets, lengthof(_nested_vehicle_refit_widgets)
 
);
 

	
 
/** Show the refit window for a vehicle
 
 * @param *v The vehicle to show the refit window for
 
 * @param order of the vehicle ( ? )
 
 * @param parent the parent window of the refit window
 
 */
 
void ShowVehicleRefitWindow(const Vehicle *v, VehicleOrderID order, Window *parent)
 
{
 
	DeleteWindowById(WC_VEHICLE_REFIT, v->index);
 
	RefitWindow *w = new RefitWindow(&_vehicle_refit_desc, v, order);
 
	w->parent = parent;
 
}
 

	
 
/** Display list of cargo types of the engine, for the purchase information window */
 
uint ShowRefitOptionsList(int left, int right, int y, EngineID engine)
 
{
 
	/* List of cargo types of this engine */
 
	uint32 cmask = GetUnionOfArticulatedRefitMasks(engine, false);
 
	/* List of cargo types available in this climate */
 
	uint32 lmask = _cargo_mask;
 
	char string[512];
 
	char *b = string;
 

	
 
	/* Draw nothing if the engine is not refittable */
 
	if (CountBits(cmask) <= 1) return y;
 

	
 
	b = InlineString(b, STR_PURCHASE_INFO_REFITTABLE_TO);
 

	
 
	if (cmask == lmask) {
 
		/* Engine can be refitted to all types in this climate */
 
		b = InlineString(b, STR_PURCHASE_INFO_ALL_TYPES);
 
	} else {
 
		/* Check if we are able to refit to more cargo types and unable to. If
 
		 * so, invert the cargo types to list those that we can't refit to. */
 
		if (CountBits(cmask ^ lmask) < CountBits(cmask)) {
 
			cmask ^= lmask;
 
			b = InlineString(b, STR_PURCHASE_INFO_ALL_BUT);
 
		}
 

	
 
		bool first = true;
 

	
 
		/* Add each cargo type to the list */
 
		for (CargoID cid = 0; cid < NUM_CARGO; cid++) {
 
			if (!HasBit(cmask, cid)) continue;
 

	
 
			if (b >= lastof(string) - (2 + 2 * 4)) break; // ", " and two calls to Utf8Encode()
 

	
 
			if (!first) b = strecpy(b, ", ", lastof(string));
 
			first = false;
 

	
 
			b = InlineString(b, CargoSpec::Get(cid)->name);
 
		}
 
	}
 

	
 
	/* Terminate and display the completed string */
 
	*b = '\0';
 

	
 
	/* Make sure we detect any buffer overflow */
 
	assert(b < endof(string));
 

	
 
	SetDParamStr(0, string);
 
	return DrawStringMultiLine(left, right, y, INT32_MAX, STR_JUST_RAW_STRING);
 
}
 

	
 
/** Get the cargo subtype text from NewGRF for the vehicle details window. */
 
StringID GetCargoSubtypeText(const Vehicle *v)
 
{
 
	if (HasBit(EngInfo(v->engine_type)->callback_mask, CBM_VEHICLE_CARGO_SUFFIX)) {
 
		uint16 cb = GetVehicleCallback(CBID_VEHICLE_CARGO_SUFFIX, 0, 0, v->engine_type, v);
 
		if (cb != CALLBACK_FAILED) {
 
			return GetGRFStringID(GetEngineGRFID(v->engine_type), 0xD000 + cb);
 
		}
 
	}
 
	return STR_EMPTY;
 
}
 

	
 
/** Sort vehicles by their number */
 
static int CDECL VehicleNumberSorter(const Vehicle * const *a, const Vehicle * const *b)
 
{
 
	return (*a)->unitnumber - (*b)->unitnumber;
 
}
 

	
 
/** Sort vehicles by their name */
 
static int CDECL VehicleNameSorter(const Vehicle * const *a, const Vehicle * const *b)
 
{
 
	static char last_name[2][64];
 

	
 
	if (*a != _last_vehicle[0]) {
 
		_last_vehicle[0] = *a;
 
		SetDParam(0, (*a)->index);
 
		GetString(last_name[0], STR_VEHICLE_NAME, lastof(last_name[0]));
 
	}
 

	
 
	if (*b != _last_vehicle[1]) {
 
		_last_vehicle[1] = *b;
 
		SetDParam(0, (*b)->index);
 
		GetString(last_name[1], STR_VEHICLE_NAME, lastof(last_name[1]));
 
	}
 

	
 
	int r = strcmp(last_name[0], last_name[1]);
 
	return (r != 0) ? r : VehicleNumberSorter(a, b);
 
}
 

	
 
/** Sort vehicles by their age */
 
static int CDECL VehicleAgeSorter(const Vehicle * const *a, const Vehicle * const *b)
 
{
 
	int r = (*a)->age - (*b)->age;
 
	return (r != 0) ? r : VehicleNumberSorter(a, b);
 
}
 

	
 
/** Sort vehicles by this year profit */
 
static int CDECL VehicleProfitThisYearSorter(const Vehicle * const *a, const Vehicle * const *b)
 
{
 
	int r = ClampToI32((*a)->GetDisplayProfitThisYear() - (*b)->GetDisplayProfitThisYear());
 
	return (r != 0) ? r : VehicleNumberSorter(a, b);
 
}
 

	
 
/** Sort vehicles by last year profit */
 
static int CDECL VehicleProfitLastYearSorter(const Vehicle * const *a, const Vehicle * const *b)
 
{
 
	int r = ClampToI32((*a)->GetDisplayProfitLastYear() - (*b)->GetDisplayProfitLastYear());
 
	return (r != 0) ? r : VehicleNumberSorter(a, b);
 
}
 

	
 
/** Sort vehicles by their cargo */
 
static int CDECL VehicleCargoSorter(const Vehicle * const *a, const Vehicle * const *b)
 
{
 
	const Vehicle *v;
 
	CargoArray diff;
 

	
 
	/* Append the cargo of the connected weagons */
 
	for (v = *a; v != NULL; v = v->Next()) diff[v->cargo_type] += v->cargo_cap;
 
	for (v = *b; v != NULL; v = v->Next()) diff[v->cargo_type] -= v->cargo_cap;
 

	
 
	int r = 0;
 
	for (CargoID i = 0; i < NUM_CARGO; i++) {
 
		r = diff[i];
 
		if (r != 0) break;
 
	}
 

	
 
	return (r != 0) ? r : VehicleNumberSorter(a, b);
 
}
 

	
 
/** Sort vehicles by their reliability */
 
static int CDECL VehicleReliabilitySorter(const Vehicle * const *a, const Vehicle * const *b)
 
{
 
	int r = (*a)->reliability - (*b)->reliability;
 
	return (r != 0) ? r : VehicleNumberSorter(a, b);
 
}
 

	
 
/** Sort vehicles by their max speed */
 
static int CDECL VehicleMaxSpeedSorter(const Vehicle * const *a, const Vehicle * const *b)
 
{
 
	int r = 0;
 
	if ((*a)->type == VEH_TRAIN && (*b)->type == VEH_TRAIN) {
 
		r = Train::From(*a)->tcache.cached_max_speed - Train::From(*b)->tcache.cached_max_speed;
 
	} else {
 
		r = (*a)->max_speed - (*b)->max_speed;
 
	}
 
	return (r != 0) ? r : VehicleNumberSorter(a, b);
 
}
 

	
 
/** Sort vehicles by model */
 
static int CDECL VehicleModelSorter(const Vehicle * const *a, const Vehicle * const *b)
 
{
 
	int r = (*a)->engine_type - (*b)->engine_type;
 
	return (r != 0) ? r : VehicleNumberSorter(a, b);
 
}
 

	
 
/** Sort vehciles by their value */
 
static int CDECL VehicleValueSorter(const Vehicle * const *a, const Vehicle * const *b)
 
{
 
	const Vehicle *u;
 
	Money diff = 0;
 

	
 
	for (u = *a; u != NULL; u = u->Next()) diff += u->value;
 
	for (u = *b; u != NULL; u = u->Next()) diff -= u->value;
 

	
 
	int r = ClampToI32(diff);
 
	return (r != 0) ? r : VehicleNumberSorter(a, b);
 
}
 

	
 
/** Sort vehicles by their length */
 
static int CDECL VehicleLengthSorter(const Vehicle * const *a, const Vehicle * const *b)
 
{
 
	int r = 0;
 
	switch ((*a)->type) {
 
		case VEH_TRAIN:
 
			r = Train::From(*a)->tcache.cached_total_length - Train::From(*b)->tcache.cached_total_length;
 
			break;
 

	
 
		case VEH_ROAD: {
 
			const RoadVehicle *u;
 
			for (u = RoadVehicle::From(*a); u != NULL; u = u->Next()) r += u->rcache.cached_veh_length;
 
			for (u = RoadVehicle::From(*b); u != NULL; u = u->Next()) r -= u->rcache.cached_veh_length;
 
		} break;
 

	
 
		default: NOT_REACHED();
 
	}
 
	return (r != 0) ? r : VehicleNumberSorter(a, b);
 
}
 

	
 
/** Sort vehicles by the time they can still live */
 
static int CDECL VehicleTimeToLiveSorter(const Vehicle * const *a, const Vehicle * const *b)
 
{
 
	int r = ClampToI32(((*a)->max_age - (*a)->age) - ((*b)->max_age - (*b)->age));
 
	return (r != 0) ? r : VehicleNumberSorter(a, b);
 
}
 

	
 
/** Sort vehicles by the timetable delay */
 
static int CDECL VehicleTimetableDelaySorter(const Vehicle * const *a, const Vehicle * const *b)
 
{
 
	int r = (*a)->lateness_counter - (*b)->lateness_counter;
 
	return (r != 0) ? r : VehicleNumberSorter(a, b);
 
}
 

	
 
void InitializeGUI()
 
{
 
	MemSetT(&_sorting, 0);
 
}
 

	
 
/**
 
 * Assign a vehicle window a new vehicle
 
 * @param window_class WindowClass to search for
 
 * @param from_index the old vehicle ID
 
 * @param to_index the new vehicle ID
 
 */
 
static inline void ChangeVehicleWindow(WindowClass window_class, VehicleID from_index, VehicleID to_index)
 
{
 
	Window *w = FindWindowById(window_class, from_index);
 
	if (w != NULL) {
 
		w->window_number = to_index;
 
		if (w->viewport != NULL) w->viewport->follow_vehicle = to_index;
 
		if (to_index != INVALID_VEHICLE) w->InvalidateData();
 
	}
 
}
 

	
 
/**
 
 * Report a change in vehicle IDs (due to autoreplace) to affected vehicle windows.
 
 * @param from_index the old vehicle ID
 
 * @param to_index the new vehicle ID
 
 */
 
void ChangeVehicleViewWindow(VehicleID from_index, VehicleID to_index)
 
{
 
	ChangeVehicleWindow(WC_VEHICLE_VIEW,      from_index, to_index);
 
	ChangeVehicleWindow(WC_VEHICLE_ORDERS,    from_index, to_index);
 
	ChangeVehicleWindow(WC_VEHICLE_REFIT,     from_index, to_index);
 
	ChangeVehicleWindow(WC_VEHICLE_DETAILS,   from_index, to_index);
 
	ChangeVehicleWindow(WC_VEHICLE_TIMETABLE, from_index, to_index);
 
}
 

	
 
enum VehicleListWindowWidgets {
 
	VLW_WIDGET_CLOSEBOX = 0,
 
	VLW_WIDGET_CAPTION,
 
	VLW_WIDGET_STICKY,
 
	VLW_WIDGET_SORT_ORDER,
 
	VLW_WIDGET_SORT_BY_PULLDOWN,
 
	VLW_WIDGET_EMPTY_TOP_RIGHT,
 
	VLW_WIDGET_LIST,
 
	VLW_WIDGET_SCROLLBAR,
 
	VLW_WIDGET_HIDE_BUTTONS,
 
	VLW_WIDGET_OTHER_COMPANY_FILLER,
 
	VLW_WIDGET_AVAILABLE_VEHICLES,
 
	VLW_WIDGET_MANAGE_VEHICLES_DROPDOWN,
 
	VLW_WIDGET_STOP_ALL,
 
	VLW_WIDGET_START_ALL,
 
	VLW_WIDGET_EMPTY_BOTTOM_RIGHT,
 
	VLW_WIDGET_RESIZE,
 
};
 

	
 
static const NWidgetPart _nested_vehicle_list[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, VLW_WIDGET_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, VLW_WIDGET_CAPTION),
 
		NWidget(WWT_STICKYBOX, COLOUR_GREY, VLW_WIDGET_STICKY),
 
	EndContainer(),
 

	
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, VLW_WIDGET_SORT_ORDER), SetMinimalSize(81, 12), SetFill(false, true), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER),
 
		NWidget(WWT_DROPDOWN, COLOUR_GREY, VLW_WIDGET_SORT_BY_PULLDOWN), SetMinimalSize(167, 12), SetFill(false, true), SetDataTip(0x0, STR_TOOLTIP_SORT_CRITERIAP),
 
		NWidget(WWT_PANEL, COLOUR_GREY, VLW_WIDGET_EMPTY_TOP_RIGHT), SetMinimalSize(12, 12), SetFill(true, true), SetResize(1, 0),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, VLW_WIDGET_SORT_ORDER), SetMinimalSize(81, 12), SetFill(0, 1), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER),
 
		NWidget(WWT_DROPDOWN, COLOUR_GREY, VLW_WIDGET_SORT_BY_PULLDOWN), SetMinimalSize(167, 12), SetFill(0, 1), SetDataTip(0x0, STR_TOOLTIP_SORT_CRITERIAP),
 
		NWidget(WWT_PANEL, COLOUR_GREY, VLW_WIDGET_EMPTY_TOP_RIGHT), SetMinimalSize(12, 12), SetFill(1, 1), SetResize(1, 0),
 
		EndContainer(),
 
	EndContainer(),
 

	
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_MATRIX, COLOUR_GREY, VLW_WIDGET_LIST), SetMinimalSize(248, 0), SetFill(true, false),
 
		NWidget(WWT_MATRIX, COLOUR_GREY, VLW_WIDGET_LIST), SetMinimalSize(248, 0), SetFill(1, 0),
 
		NWidget(WWT_SCROLLBAR, COLOUR_GREY, VLW_WIDGET_SCROLLBAR),
 
	EndContainer(),
 

	
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(NWID_SELECTION, INVALID_COLOUR, VLW_WIDGET_HIDE_BUTTONS),
 
			NWidget(NWID_HORIZONTAL),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, VLW_WIDGET_AVAILABLE_VEHICLES), SetMinimalSize(106, 12), SetFill(false, true),
 
				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, VLW_WIDGET_AVAILABLE_VEHICLES), SetMinimalSize(106, 12), SetFill(0, 1),
 
								SetDataTip(0x0, STR_VEHICLE_LIST_AVAILABLE_ENGINES_TOOLTIP),
 
				NWidget(WWT_DROPDOWN, COLOUR_GREY, VLW_WIDGET_MANAGE_VEHICLES_DROPDOWN), SetMinimalSize(118, 12), SetFill(false, true),
 
				NWidget(WWT_DROPDOWN, COLOUR_GREY, VLW_WIDGET_MANAGE_VEHICLES_DROPDOWN), SetMinimalSize(118, 12), SetFill(0, 1),
 
								SetDataTip(STR_VEHICLE_LIST_MANAGE_LIST, STR_VEHICLE_LIST_MANAGE_LIST_TOOLTIP),
 
				NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, VLW_WIDGET_STOP_ALL), SetMinimalSize(12, 12), SetFill(false, true),
 
				NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, VLW_WIDGET_STOP_ALL), SetMinimalSize(12, 12), SetFill(0, 1),
 
								SetDataTip(SPR_FLAG_VEH_STOPPED, STR_VEHICLE_LIST_MASS_STOP_LIST_TOOLTIP),
 
				NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, VLW_WIDGET_START_ALL), SetMinimalSize(12, 12), SetFill(false, true),
 
				NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, VLW_WIDGET_START_ALL), SetMinimalSize(12, 12), SetFill(0, 1),
 
								SetDataTip(SPR_FLAG_VEH_RUNNING, STR_VEHICLE_LIST_MASS_START_LIST_TOOLTIP),
 
				NWidget(WWT_PANEL, COLOUR_GREY, VLW_WIDGET_EMPTY_BOTTOM_RIGHT), SetMinimalSize(0, 12), SetResize(1, 0), SetFill(false, true), EndContainer(),
 
				NWidget(WWT_PANEL, COLOUR_GREY, VLW_WIDGET_EMPTY_BOTTOM_RIGHT), SetMinimalSize(0, 12), SetResize(1, 0), SetFill(0, 1), EndContainer(),
 
			EndContainer(),
 
			/* Widget to be shown for other companies hiding the previous 5 widgets. */
 
			NWidget(WWT_PANEL, COLOUR_GREY, VLW_WIDGET_OTHER_COMPANY_FILLER), SetFill(true, true), SetResize(1, 0), EndContainer(),
 
			NWidget(WWT_PANEL, COLOUR_GREY, VLW_WIDGET_OTHER_COMPANY_FILLER), SetFill(1, 1), SetResize(1, 0), EndContainer(),
 
		EndContainer(),
 
		NWidget(WWT_RESIZEBOX, COLOUR_GREY, VLW_WIDGET_RESIZE),
 
	EndContainer(),
 
};
 

	
 
static void DrawSmallOrderList(const Vehicle *v, int left, int right, int y)
 
{
 
	const Order *order;
 
	int i = 0;
 

	
 
	int sel = v->cur_order_index;
 

	
 
	FOR_VEHICLE_ORDERS(v, order) {
 
		if (sel == 0) DrawString(left, right, y, STR_TINY_RIGHT_ARROW, TC_BLACK);
 
		sel--;
 

	
 
		if (order->IsType(OT_GOTO_STATION)) {
 
			SetDParam(0, order->GetDestination());
 
			DrawString(left + 6, right - 6, y, STR_TINY_BLACK_STATION);
 

	
 
			y += FONT_HEIGHT_SMALL;
 
			if (++i == 4) break;
 
		}
 
	}
 
}
 

	
 
/**
 
 * Draws an image of a vehicle chain
 
 * @param v         Front vehicle
 
 * @param left      The minimum horizontal position
 
 * @param right     The maximum horizontal position
 
 * @param y         Vertical position to draw at
 
 * @param selection Selected vehicle to draw a frame around
 
 * @param skip      Number of pixels to skip at the front (for scrolling)
 
 */
 
static void DrawVehicleImage(const Vehicle *v, int left, int right, int y, VehicleID selection, int skip)
 
{
 
	switch (v->type) {
 
		case VEH_TRAIN:    DrawTrainImage(Train::From(v), left, right, y, selection, skip); break;
 
		case VEH_ROAD:     DrawRoadVehImage(v, left, right, y, selection);  break;
 
		case VEH_SHIP:     DrawShipImage(v, left, right, y, selection);     break;
 
		case VEH_AIRCRAFT: DrawAircraftImage(v, left, right, y, selection); break;
 
		default: NOT_REACHED();
 
	}
 
}
 

	
 
/**
 
 * Get the height of a vehicle in the vehicle list GUIs.
 
 * @param type    the vehicle type to look at
 
 * @param divisor the resulting height must be dividable by this
 
 * @return the height
 
 */
 
uint GetVehicleListHeight(VehicleType type, uint divisor)
 
{
 
	/* Name + vehicle + profit */
 
	uint base = GetVehicleHeight(type) + 2 * FONT_HEIGHT_SMALL;
 
	/* Drawing of the 4 small orders + profit*/
 
	if (type >= VEH_SHIP) base = max(base, 5U * FONT_HEIGHT_SMALL);
 

	
 
	if (divisor == 1) return base;
 

	
 
	/* Make sure the height is dividable by divisor */
 
	uint rem = base % divisor;
 
	return base + (rem == 0 ? 0 : divisor - rem);
 
}
 

	
 
/**
 
 * Draw all the vehicle list items.
 
 * @param selected_vehicle The vehicle that is to be highlighted.
 
 * @param line_height      Height of a single item line.
 
 * @param r                Rectangle with edge positions of the matrix widget.
 
 */
 
void BaseVehicleListWindow::DrawVehicleListItems(VehicleID selected_vehicle, int line_height, const Rect &r) const
 
{
 
	int left = r.left + WD_MATRIX_LEFT;
 
	int right = r.right - WD_MATRIX_RIGHT;
 
	int width = right - left;
 
	bool rtl = _dynlang.text_dir == TD_RTL;
 

	
 
	SetDParam(0, this->max_unitnumber);
 
	int text_offset = GetStringBoundingBox(STR_JUST_INT).width + WD_FRAMERECT_RIGHT;
 
	int text_left  = left  + (rtl ?           0 : text_offset);
 
	int text_right = right - (rtl ? text_offset :           0);
 

	
 
	bool show_orderlist = vehicle_type >= VEH_SHIP;
 
	int orderlist_left  = left  + (rtl ? 0 : max(100 + text_offset, width / 2));
 
	int orderlist_right = right - (rtl ? max(100 + text_offset, width / 2) : 0);
 

	
 
	int image_left  = (rtl && show_orderlist) ? orderlist_right : text_left;
 
	int image_right = (!rtl && show_orderlist) ? orderlist_left : text_right;
 

	
 
	int vehicle_button_x = rtl ? right - 8 : left;
 

	
 
	int y = r.top;
 
	uint max = min(this->vscroll.GetPosition() + this->vscroll.GetCapacity(), this->vehicles.Length());
 
	for (uint i = this->vscroll.GetPosition(); i < max; ++i) {
 
		const Vehicle *v = this->vehicles[i];
 
		StringID str;
 

	
 
		SetDParam(0, v->GetDisplayProfitThisYear());
 
		SetDParam(1, v->GetDisplayProfitLastYear());
 

	
 
		DrawVehicleImage(v, image_left, image_right, y + FONT_HEIGHT_SMALL - 1, selected_vehicle, 0);
 
		DrawString(text_left, text_right, y + line_height - FONT_HEIGHT_SMALL - WD_FRAMERECT_BOTTOM - 1, STR_VEHICLE_LIST_PROFIT_THIS_YEAR_LAST_YEAR);
 

	
 
		if (v->name != NULL) {
 
			/* The vehicle got a name so we will print it */
 
			SetDParam(0, v->index);
 
			DrawString(text_left, text_right, y, STR_TINY_BLACK_VEHICLE);
 
		} else if (v->group_id != DEFAULT_GROUP) {
 
			/* The vehicle has no name, but is member of a group, so print group name */
 
			SetDParam(0, v->group_id);
 
			DrawString(text_left, text_right, y, STR_TINY_GROUP, TC_BLACK);
 
		}
 

	
 
		if (show_orderlist) DrawSmallOrderList(v, orderlist_left, orderlist_right, y);
 

	
 
		if (v->IsInDepot()) {
 
			str = STR_BLUE_COMMA;
 
		} else {
 
			str = (v->age > v->max_age - DAYS_IN_LEAP_YEAR) ? STR_RED_COMMA : STR_BLACK_COMMA;
 
		}
 

	
 
		SetDParam(0, v->unitnumber);
 
		DrawString(left, right, y + 2, str);
 

	
 
		DrawVehicleProfitButton(v, vehicle_button_x, y + FONT_HEIGHT_NORMAL + 3);
 

	
 
		y += line_height;
 
	}
 
}
 

	
 
/**
 
 * Window for the (old) vehicle listing.
 
 *
 
 * bitmask for w->window_number
 
 * 0-7 CompanyID (owner)
 
 * 8-10 window type (use flags in vehicle_gui.h)
 
 * 11-15 vehicle type (using VEH_, but can be compressed to fewer bytes if needed)
 
 * 16-31 StationID or OrderID depending on window type (bit 8-10)
 
 */
 
struct VehicleListWindow : public BaseVehicleListWindow {
 
private:
 
	/** Enumeration of planes of the button row at the bottom. */
 
	enum ButtonPlanes {
 
		BP_SHOW_BUTTONS, ///< Show the buttons.
 
		BP_HIDE_BUTTONS, ///< Show the empty panel.
 
	};
 

	
 
public:
 
	VehicleListWindow(const WindowDesc *desc, WindowNumber window_number) : BaseVehicleListWindow()
 
	{
 
		uint16 window_type = window_number & VLW_MASK;
 
		CompanyID company = (CompanyID)GB(window_number, 0, 8);
 

	
 
		this->vehicle_type = (VehicleType)GB(window_number, 11, 5);
 

	
 
		/* Set up sorting. Make the window-specific _sorting variable
 
		 * point to the correct global _sorting struct so we are freed
 
		 * from having conditionals during window operation */
 
		switch (this->vehicle_type) {
 
			case VEH_TRAIN:    this->sorting = &_sorting.train; break;
 
			case VEH_ROAD:     this->sorting = &_sorting.roadveh; break;
 
			case VEH_SHIP:     this->sorting = &_sorting.ship; break;
 
			case VEH_AIRCRAFT: this->sorting = &_sorting.aircraft; break;
 
			default: NOT_REACHED();
 
		}
 

	
 
		this->vehicles.SetListing(*this->sorting);
 
		this->vehicles.ForceRebuild();
 
		this->vehicles.NeedResort();
 
		this->BuildVehicleList(company, GB(window_number, 16, 16), window_type);
 
		this->SortVehicleList();
 

	
 
		this->CreateNestedTree(desc);
 

	
 
		/* Set up the window widgets */
 
		this->GetWidget<NWidgetCore>(VLW_WIDGET_LIST)->tool_tip                  = STR_VEHICLE_LIST_TRAIN_LIST_TOOLTIP + this->vehicle_type;
 
		this->GetWidget<NWidgetCore>(VLW_WIDGET_AVAILABLE_VEHICLES)->widget_data = STR_VEHICLE_LIST_AVAILABLE_TRAINS + this->vehicle_type;
 

	
 
		if (window_type == VLW_SHARED_ORDERS) {
 
			this->GetWidget<NWidgetCore>(VLW_WIDGET_CAPTION)->widget_data = STR_VEHICLE_LIST_SHARED_ORDERS_LIST_CAPTION;
 
		} else {
 
			this->GetWidget<NWidgetCore>(VLW_WIDGET_CAPTION)->widget_data = STR_VEHICLE_LIST_TRAIN_CAPTION + this->vehicle_type;
 
		}
 

	
 
		this->FinishInitNested(desc, window_number);
 
		this->owner = company;
 

	
 
		if (this->vehicle_type == VEH_TRAIN) ResizeWindow(this, 65, 0);
 
	}
 

	
 
	~VehicleListWindow()
 
	{
 
		*this->sorting = this->vehicles.GetListing();
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		if (widget != VLW_WIDGET_LIST) return;
 

	
 
		resize->width = 0;
 
		resize->height = GetVehicleListHeight(this->vehicle_type, 1);
 

	
 
		switch (this->vehicle_type) {
 
			case VEH_TRAIN:
 
				resize->width = 1;
 
				/* Fallthrough */
 
			case VEH_ROAD:
 
				size->height = 6 * resize->height;
 
				break;
 
			case VEH_SHIP:
 
			case VEH_AIRCRAFT:
 
				size->height = 4 * resize->height;
 
				break;
 
			default: NOT_REACHED();
 
		}
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		if (widget != VLW_WIDGET_CAPTION) return;
 

	
 
		const uint16 index = GB(this->window_number, 16, 16);
 
		switch (this->window_number & VLW_MASK) {
 
			case VLW_SHARED_ORDERS: // Shared Orders
 
				if (this->vehicles.Length() == 0) {
 
					/* We can't open this window without vehicles using this order
 
					 * and we should close the window when deleting the order      */
 
					NOT_REACHED();
 
				}
 
				SetDParam(0, this->vscroll.GetCount());
 
				break;
 

	
 
			case VLW_STANDARD: // Company Name
 
				SetDParam(0, STR_COMPANY_NAME);
 
				SetDParam(1, index);
 
				SetDParam(2, this->vscroll.GetCount());
 
				break;
 

	
 
			case VLW_WAYPOINT_LIST:
 
				SetDParam(0, STR_WAYPOINT_NAME);
 
				SetDParam(1, index);
 
				SetDParam(2, this->vscroll.GetCount());
 
				break;
 

	
 
			case VLW_STATION_LIST: // Station Name
 
				SetDParam(0, STR_STATION_NAME);
 
				SetDParam(1, index);
 
				SetDParam(2, this->vscroll.GetCount());
 
				break;
 

	
 
			case VLW_DEPOT_LIST:
 
				SetDParam(0, STR_DEPOT_TRAIN_CAPTION + this->vehicle_type);
 
				if (this->vehicle_type == VEH_AIRCRAFT) {
 
					SetDParam(1, index); // Airport name
 
				} else {
 
					SetDParam(1, Depot::Get(index)->town_index);
 
				}
 
				SetDParam(2, this->vscroll.GetCount());
 
				break;
 
			default: NOT_REACHED();
 
		}
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		switch (widget) {
 
			case VLW_WIDGET_SORT_ORDER:
 
				/* draw arrow pointing up/down for ascending/descending sorting */
 
				this->DrawSortButtonState(widget, this->vehicles.IsDescSortOrder() ? SBS_DOWN : SBS_UP);
 
				break;
 

	
 
			case VLW_WIDGET_LIST:
 
				this->DrawVehicleListItems(INVALID_VEHICLE, this->resize.step_height, r);
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		const uint16 window_type = this->window_number & VLW_MASK;
 

	
 
		this->BuildVehicleList(this->owner, GB(this->window_number, 16, 16), window_type);
 
		this->SortVehicleList();
 

	
 
		if (this->vehicles.Length() == 0) HideDropDownMenu(this);
 

	
 
		/* Hide the widgets that we will not use in this window
 
		 * Some windows contains actions only fit for the owner */
 
		int plane_to_show = (this->owner == _local_company) ? BP_SHOW_BUTTONS : BP_HIDE_BUTTONS;
 
		NWidgetStacked *nwi = this->GetWidget<NWidgetStacked>(VLW_WIDGET_HIDE_BUTTONS);
 
		if (plane_to_show != nwi->shown_plane) {
 
			nwi->SetDisplayedPlane(plane_to_show);
 
			nwi->SetDirty(this);
 
		}
 
		if (this->owner == _local_company) {
 
			this->SetWidgetDisabledState(VLW_WIDGET_AVAILABLE_VEHICLES, window_type != VLW_STANDARD);
 
			this->SetWidgetsDisabledState(this->vehicles.Length() == 0,
 
				VLW_WIDGET_MANAGE_VEHICLES_DROPDOWN,
 
				VLW_WIDGET_STOP_ALL,
 
				VLW_WIDGET_START_ALL,
 
				WIDGET_LIST_END);
 
		}
 

	
 
		/* Set text of sort by dropdown widget. */
 
		this->GetWidget<NWidgetCore>(VLW_WIDGET_SORT_BY_PULLDOWN)->widget_data = this->vehicle_sorter_names[this->vehicles.SortType()];
 

	
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case VLW_WIDGET_SORT_ORDER: // Flip sorting method ascending/descending
 
				this->vehicles.ToggleSortOrder();
 
				this->SetDirty();
 
				break;
 

	
 
			case VLW_WIDGET_SORT_BY_PULLDOWN:// Select sorting criteria dropdown menu
 
				ShowDropDownMenu(this, this->vehicle_sorter_names, this->vehicles.SortType(), VLW_WIDGET_SORT_BY_PULLDOWN, 0,
 
						(this->vehicle_type == VEH_TRAIN || this->vehicle_type == VEH_ROAD) ? 0 : (1 << 10));
 
				return;
 

	
 
			case VLW_WIDGET_LIST: { // Matrix to show vehicles
 
				uint32 id_v = (pt.y - this->GetWidget<NWidgetBase>(VLW_WIDGET_LIST)->pos_y) / this->resize.step_height;
 
				const Vehicle *v;
 

	
 
				if (id_v >= this->vscroll.GetCapacity()) return; // click out of bounds
 

	
 
				id_v += this->vscroll.GetPosition();
 

	
 
				if (id_v >= this->vehicles.Length()) return; // click out of list bound
 

	
 
				v = this->vehicles[id_v];
 

	
 
				ShowVehicleViewWindow(v);
 
			} break;
 

	
 
			case VLW_WIDGET_AVAILABLE_VEHICLES:
 
				ShowBuildVehicleWindow(INVALID_TILE, this->vehicle_type);
 
				break;
 

	
 
			case VLW_WIDGET_MANAGE_VEHICLES_DROPDOWN: {
 
				static StringID action_str[] = {
 
					STR_VEHICLE_LIST_REPLACE_VEHICLES,
 
					STR_VEHICLE_LIST_SEND_FOR_SERVICING,
 
					STR_NULL,
 
					INVALID_STRING_ID
 
				};
 

	
 
				static const StringID depot_name[] = {
 
					STR_VEHICLE_LIST_SEND_TRAIN_TO_DEPOT,
 
					STR_VEHICLE_LIST_SEND_ROAD_VEHICLE_TO_DEPOT,
 
					STR_VEHICLE_LIST_SEND_SHIP_TO_DEPOT,
 
					STR_VEHICLE_LIST_SEND_AIRCRAFT_TO_HANGAR
 
				};
 

	
 
				/* XXX - Substite string since the dropdown cannot handle dynamic strings */
 
				action_str[2] = depot_name[this->vehicle_type];
 
				ShowDropDownMenu(this, action_str, 0, VLW_WIDGET_MANAGE_VEHICLES_DROPDOWN, 0, (this->window_number & VLW_MASK) == VLW_STANDARD ? 0 : 1);
 
				break;
 
			}
 

	
 
			case VLW_WIDGET_STOP_ALL:
 
			case VLW_WIDGET_START_ALL:
 
				DoCommandP(0, GB(this->window_number, 16, 16),
 
						(this->window_number & VLW_MASK) | (1 << 6) | (widget == VLW_WIDGET_START_ALL ? (1 << 5) : 0) | this->vehicle_type, CMD_MASS_START_STOP);
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnDropdownSelect(int widget, int index)
 
	{
 
		switch (widget) {
 
			case VLW_WIDGET_SORT_BY_PULLDOWN:
 
				this->vehicles.SetSortType(index);
 
				break;
 
			case VLW_WIDGET_MANAGE_VEHICLES_DROPDOWN:
 
				assert(this->vehicles.Length() != 0);
 

	
 
				switch (index) {
 
					case 0: // Replace window
 
						ShowReplaceGroupVehicleWindow(DEFAULT_GROUP, this->vehicle_type);
 
						break;
 
					case 1: // Send for servicing
 
						DoCommandP(0, GB(this->window_number, 16, 16) /* StationID or OrderID (depending on VLW) */,
 
							(this->window_number & VLW_MASK) | DEPOT_MASS_SEND | DEPOT_SERVICE, GetCmdSendToDepot(this->vehicle_type));
 
						break;
 
					case 2: // Send to Depots
 
						DoCommandP(0, GB(this->window_number, 16, 16) /* StationID or OrderID (depending on VLW) */,
 
							(this->window_number & VLW_MASK) | DEPOT_MASS_SEND, GetCmdSendToDepot(this->vehicle_type));
 
						break;
 

	
 
					default: NOT_REACHED();
 
				}
 
				break;
 
			default: NOT_REACHED();
 
		}
 
		this->SetDirty();
 
	}
 

	
 
	virtual void OnTick()
 
	{
 
		if (_pause_mode != PM_UNPAUSED) return;
 
		if (this->vehicles.NeedResort()) {
 
			StationID station = ((this->window_number & VLW_MASK) == VLW_STATION_LIST) ? GB(this->window_number, 16, 16) : INVALID_STATION;
 

	
 
			DEBUG(misc, 3, "Periodic resort %d list company %d at station %d", this->vehicle_type, this->owner, station);
 
			this->SetDirty();
 
		}
 
	}
 

	
 
	virtual void OnResize()
 
	{
 
		this->vscroll.SetCapacity(this->GetWidget<NWidgetBase>(VLW_WIDGET_LIST)->current_y / this->resize.step_height);
 
		this->GetWidget<NWidgetCore>(VLW_WIDGET_LIST)->widget_data = (this->vscroll.GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START);
 
	}
 

	
 
	virtual void OnInvalidateData(int data)
 
	{
 
		if (HasBit(data, 15) && (this->window_number & VLW_MASK) == VLW_SHARED_ORDERS) {
 
			SB(this->window_number, 16, 16, GB(data, 16, 16));
 
			this->vehicles.ForceRebuild();
 
			return;
 
		}
 

	
 
		if (data == 0) {
 
			this->vehicles.ForceRebuild();
 
		} else {
 
			this->vehicles.ForceResort();
 
		}
 
	}
 
};
 

	
 
static WindowDesc _vehicle_list_desc(
 
	WDP_AUTO, WDP_AUTO, 260, 246,
 
	WC_INVALID, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE,
 
	_nested_vehicle_list, lengthof(_nested_vehicle_list)
 
);
 

	
 
static void ShowVehicleListWindowLocal(CompanyID company, uint16 VLW_flag, VehicleType vehicle_type, uint16 unique_number)
 
{
 
	if (!Company::IsValidID(company)) return;
 

	
 
	_vehicle_list_desc.cls = GetWindowClassForVehicleType(vehicle_type);
 
	WindowNumber num = (unique_number << 16) | (vehicle_type << 11) | VLW_flag | company;
 
	AllocateWindowDescFront<VehicleListWindow>(&_vehicle_list_desc, num);
 
}
 

	
 
void ShowVehicleListWindow(CompanyID company, VehicleType vehicle_type)
 
{
 
	/* If _settings_client.gui.advanced_vehicle_list > 1, display the Advanced list
 
	 * if _settings_client.gui.advanced_vehicle_list == 1, display Advanced list only for local company
 
	 * if _ctrl_pressed, do the opposite action (Advanced list x Normal list)
 
	 */
 

	
 
	if ((_settings_client.gui.advanced_vehicle_list > (uint)(company != _local_company)) != _ctrl_pressed) {
 
		ShowCompanyGroup(company, vehicle_type);
 
	} else {
 
		ShowVehicleListWindowLocal(company, VLW_STANDARD, vehicle_type, company);
 
	}
 
}
 

	
 
void ShowVehicleListWindow(CompanyID company, VehicleType vehicle_type, const Waypoint *wp)
 
{
 
	if (wp == NULL) return;
 
	ShowVehicleListWindowLocal(company, VLW_WAYPOINT_LIST, vehicle_type, wp->index);
 
}
 

	
 
void ShowVehicleListWindow(const Vehicle *v)
 
{
 
	ShowVehicleListWindowLocal(v->owner, VLW_SHARED_ORDERS, v->type, v->FirstShared()->index);
 
}
 

	
 
void ShowVehicleListWindow(CompanyID company, VehicleType vehicle_type, StationID station)
 
{
 
	ShowVehicleListWindowLocal(company, VLW_STATION_LIST, vehicle_type, station);
 
}
 

	
 
void ShowVehicleListWindow(CompanyID company, VehicleType vehicle_type, TileIndex depot_tile)
 
{
 
	uint16 depot_airport_index;
 

	
 
	if (vehicle_type == VEH_AIRCRAFT) {
 
		depot_airport_index = GetStationIndex(depot_tile);
 
	} else {
 
		depot_airport_index = GetDepotIndex(depot_tile);
 
	}
 
	ShowVehicleListWindowLocal(company, VLW_DEPOT_LIST, vehicle_type, depot_airport_index);
 
}
 

	
 

	
 
/* Unified vehicle GUI - Vehicle Details Window */
 

	
 
/** Constants of vehicle details widget indices */
 
enum VehicleDetailsWindowWidgets {
 
	VLD_WIDGET_CLOSEBOX = 0,
 
	VLD_WIDGET_CAPTION,
 
	VLD_WIDGET_RENAME_VEHICLE,
 
	VLD_WIDGET_STICKY,
 
	VLD_WIDGET_TOP_DETAILS,
 
	VLD_WIDGET_INCREASE_SERVICING_INTERVAL,
 
	VLD_WIDGET_DECREASE_SERVICING_INTERVAL,
 
	VLD_WIDGET_SERVICING_INTERVAL,
 
	VLD_WIDGET_BOTTOM_RIGHT,
 
	VLD_WIDGET_MIDDLE_DETAILS,
 
	VLD_WIDGET_MATRIX,
 
	VLD_WIDGET_SCROLLBAR,
 
	VLD_WIDGET_DETAILS_CARGO_CARRIED,
 
	VLD_WIDGET_DETAILS_TRAIN_VEHICLES,
 
	VLD_WIDGET_DETAILS_CAPACITY_OF_EACH,
 
	VLD_WIDGET_DETAILS_TOTAL_CARGO,
 
	VLD_WIDGET_RESIZE,
 
};
 

	
 
assert_compile(VLD_WIDGET_DETAILS_CARGO_CARRIED    == VLD_WIDGET_DETAILS_CARGO_CARRIED + TDW_TAB_CARGO   );
 
assert_compile(VLD_WIDGET_DETAILS_TRAIN_VEHICLES   == VLD_WIDGET_DETAILS_CARGO_CARRIED + TDW_TAB_INFO    );
 
assert_compile(VLD_WIDGET_DETAILS_CAPACITY_OF_EACH == VLD_WIDGET_DETAILS_CARGO_CARRIED + TDW_TAB_CAPACITY);
 
assert_compile(VLD_WIDGET_DETAILS_TOTAL_CARGO      == VLD_WIDGET_DETAILS_CARGO_CARRIED + TDW_TAB_TOTALS  );
 

	
 
/** Vehicle details widgets (other than train). */
 
static const NWidgetPart _nested_nontrain_vehicle_details_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, VLD_WIDGET_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, VLD_WIDGET_CAPTION), SetDataTip(STR_VEHICLE_DETAILS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, VLD_WIDGET_RENAME_VEHICLE), SetMinimalSize(40, 0), SetMinimalTextLines(1, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM + 2), SetDataTip(STR_VEHICLE_NAME_BUTTON, STR_NULL /* filled in later */),
 
		NWidget(WWT_STICKYBOX, COLOUR_GREY, VLD_WIDGET_STICKY),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, VLD_WIDGET_TOP_DETAILS), SetMinimalSize(405, 42), EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, VLD_WIDGET_MIDDLE_DETAILS), SetMinimalSize(405, 45), EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, VLD_WIDGET_BOTTOM_RIGHT),
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, VLD_WIDGET_DECREASE_SERVICING_INTERVAL), SetFill(false, true),
 
			NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, VLD_WIDGET_DECREASE_SERVICING_INTERVAL), SetFill(0, 1),
 
					SetDataTip(AWV_DECREASE, STR_VEHICLE_DETAILS_DECREASE_SERVICING_INTERVAL_TOOLTIP),
 
			NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, VLD_WIDGET_INCREASE_SERVICING_INTERVAL), SetFill(false, true),
 
			NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, VLD_WIDGET_INCREASE_SERVICING_INTERVAL), SetFill(0, 1),
 
					SetDataTip(AWV_INCREASE, STR_VEHICLE_DETAILS_INCREASE_SERVICING_INTERVAL_TOOLTIP),
 
			NWidget(WWT_EMPTY, COLOUR_GREY, VLD_WIDGET_SERVICING_INTERVAL), SetFill(true, true),
 
			NWidget(WWT_EMPTY, COLOUR_GREY, VLD_WIDGET_SERVICING_INTERVAL), SetFill(1, 1),
 
		EndContainer(),
 
	EndContainer(),
 
};
 

	
 
/** Train details widgets. */
 
static const NWidgetPart _nested_train_vehicle_details_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, VLD_WIDGET_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, VLD_WIDGET_CAPTION), SetDataTip(STR_VEHICLE_DETAILS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, VLD_WIDGET_RENAME_VEHICLE), SetMinimalSize(40, 0), SetMinimalTextLines(1, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM + 2), SetDataTip(STR_VEHICLE_NAME_BUTTON, STR_NULL /* filled in later */),
 
		NWidget(WWT_STICKYBOX, COLOUR_GREY, VLD_WIDGET_STICKY),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, VLD_WIDGET_TOP_DETAILS), SetResize(1, 0), SetMinimalSize(405, 42), EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_MATRIX, COLOUR_GREY, VLD_WIDGET_MATRIX), SetResize(1, 1), SetMinimalSize(393, 45), SetDataTip(0x701, STR_NULL), SetFill(true, false),
 
		NWidget(WWT_MATRIX, COLOUR_GREY, VLD_WIDGET_MATRIX), SetResize(1, 1), SetMinimalSize(393, 45), SetDataTip(0x701, STR_NULL), SetFill(1, 0),
 
		NWidget(WWT_SCROLLBAR, COLOUR_GREY, VLD_WIDGET_SCROLLBAR),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, VLD_WIDGET_BOTTOM_RIGHT),
 
		NWidget(NWID_HORIZONTAL),
 
			NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, VLD_WIDGET_DECREASE_SERVICING_INTERVAL), SetFill(false, true),
 
			NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, VLD_WIDGET_DECREASE_SERVICING_INTERVAL), SetFill(0, 1),
 
					SetDataTip(AWV_DECREASE, STR_VEHICLE_DETAILS_DECREASE_SERVICING_INTERVAL_TOOLTIP),
 
			NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, VLD_WIDGET_INCREASE_SERVICING_INTERVAL), SetFill(false, true),
 
			NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, VLD_WIDGET_INCREASE_SERVICING_INTERVAL), SetFill(0, 1),
 
					SetDataTip(AWV_INCREASE, STR_VEHICLE_DETAILS_DECREASE_SERVICING_INTERVAL_TOOLTIP),
 
			NWidget(WWT_EMPTY, COLOUR_GREY, VLD_WIDGET_SERVICING_INTERVAL), SetFill(true, true), SetResize(1, 0),
 
			NWidget(WWT_EMPTY, COLOUR_GREY, VLD_WIDGET_SERVICING_INTERVAL), SetFill(1, 1), SetResize(1, 0),
 
		EndContainer(),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, VLD_WIDGET_DETAILS_CARGO_CARRIED), SetMinimalSize(96, 12),
 
				SetDataTip(STR_VEHICLE_DETAIL_TAB_CARGO, STR_VEHICLE_DETAILS_TRAIN_CARGO_TOOLTIP), SetResize(1, 0),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, VLD_WIDGET_DETAILS_TRAIN_VEHICLES), SetMinimalSize(99, 12),
 
				SetDataTip(STR_VEHICLE_DETAIL_TAB_INFORMATION, STR_VEHICLE_DETAILS_TRAIN_INFORMATION_TOOLTIP), SetResize(1, 0),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, VLD_WIDGET_DETAILS_CAPACITY_OF_EACH), SetMinimalSize(99, 12),
 
				SetDataTip(STR_VEHICLE_DETAIL_TAB_CAPACITIES, STR_VEHICLE_DETAILS_TRAIN_CAPACITIES_TOOLTIP), SetResize(1, 0),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, VLD_WIDGET_DETAILS_TOTAL_CARGO), SetMinimalSize(99, 12),
 
				SetDataTip(STR_VEHICLE_DETAIL_TAB_TOTAL_CARGO, STR_VEHICLE_DETAILS_TRAIN_TOTAL_CARGO_TOOLTIP), SetResize(1, 0),
 
		NWidget(WWT_RESIZEBOX, COLOUR_GREY, VLD_WIDGET_RESIZE),
 
	EndContainer(),
 
};
 

	
 

	
 
extern int GetTrainDetailsWndVScroll(VehicleID veh_id, TrainDetailsWindowTabs det_tab);
 
extern void DrawTrainDetails(const Train *v, int left, int right, int y, int vscroll_pos, uint16 vscroll_cap, TrainDetailsWindowTabs det_tab);
 
extern void DrawRoadVehDetails(const Vehicle *v, int left, int right, int y);
 
extern void DrawShipDetails(const Vehicle *v, int left, int right, int y);
 
extern void DrawAircraftDetails(const Aircraft *v, int left, int right, int y);
 

	
 
/** Class for managing the vehicle details window. */
 
struct VehicleDetailsWindow : Window {
 
	TrainDetailsWindowTabs tab; ///< For train vehicles: which tab is displayed.
 

	
 
	/** Initialize a newly created vehicle details window */
 
	VehicleDetailsWindow(const WindowDesc *desc, WindowNumber window_number) : Window()
 
	{
 
		this->InitNested(desc, window_number);
 

	
 
		const Vehicle *v = Vehicle::Get(this->window_number);
 

	
 
		this->GetWidget<NWidgetCore>(VLD_WIDGET_RENAME_VEHICLE)->tool_tip = STR_VEHICLE_DETAILS_TRAIN_RENAME + v->type;
 

	
 
		this->owner = v->owner;
 
		this->tab = TDW_TAB_CARGO;
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		switch (widget) {
 
			case VLD_WIDGET_TOP_DETAILS:
 
				size->height = WD_FRAMERECT_TOP + 4 * FONT_HEIGHT_NORMAL + WD_FRAMERECT_BOTTOM;
 
				break;
 

	
 
			case VLD_WIDGET_MIDDLE_DETAILS: {
 
				const Vehicle *v = Vehicle::Get(this->window_number);
 
				switch (v->type) {
 
					case VEH_ROAD:
 
						if (RoadVehicle::From(v)->HasArticulatedPart()) {
 
							/* An articulated RV has its text drawn under the sprite instead of after it, hence 15 pixels extra. */
 
							size->height = WD_FRAMERECT_TOP + 15 + 3 * FONT_HEIGHT_NORMAL + 2 + WD_FRAMERECT_BOTTOM;
 
							/* Add space for the cargo amount for each part. */
 
							for (const Vehicle *u = v; u != NULL; u = u->Next()) {
 
								if (u->cargo_cap != 0) size->height += FONT_HEIGHT_NORMAL + 1;
 
							}
 
						} else {
 
							size->height = WD_FRAMERECT_TOP + 4 * FONT_HEIGHT_NORMAL + 3 + WD_FRAMERECT_BOTTOM;
 
						}
 
						break;
 

	
 
					case VEH_SHIP:
 
						size->height = WD_FRAMERECT_TOP + 4 * FONT_HEIGHT_NORMAL + 3 + WD_FRAMERECT_BOTTOM;
 
						break;
 

	
 
					case VEH_AIRCRAFT:
 
						size->height = WD_FRAMERECT_TOP + 5 * FONT_HEIGHT_NORMAL + 4 + WD_FRAMERECT_BOTTOM;
 
						break;
 

	
 
					default:
 
						NOT_REACHED(); // Train uses VLD_WIDGET_MATRIX instead.
 
				}
 
				break;
 
			}
 

	
 
			case VLD_WIDGET_MATRIX:
 
				resize->height = WD_MATRIX_TOP + FONT_HEIGHT_NORMAL + WD_MATRIX_BOTTOM;
 
				size->height = 4 * resize->height;
 
				break;
 

	
 
			case VLD_WIDGET_SERVICING_INTERVAL:
 
				size->height = WD_FRAMERECT_TOP + FONT_HEIGHT_NORMAL + WD_FRAMERECT_BOTTOM;
 
				break;
 
		}
 
	}
 

	
 
	/** Checks whether service interval is enabled for the vehicle. */
 
	static bool IsVehicleServiceIntervalEnabled(const VehicleType vehicle_type, CompanyID company_id)
 
	{
 
		const VehicleDefaultSettings *vds = &Company::Get(company_id)->settings.vehicle;
 
		switch (vehicle_type) {
 
			default: NOT_REACHED();
 
			case VEH_TRAIN:    return vds->servint_trains   != 0;
 
			case VEH_ROAD:     return vds->servint_roadveh  != 0;
 
			case VEH_SHIP:     return vds->servint_ships    != 0;
 
			case VEH_AIRCRAFT: return vds->servint_aircraft != 0;
 
		}
 
	}
 

	
 
	/**
 
	 * Draw the details for the given vehicle at the position of the Details windows
 
	 *
 
	 * @param v     current vehicle
 
	 * @param left  The left most coordinate to draw
 
	 * @param right The right most coordinate to draw
 
	 * @param y     The y coordinate
 
	 * @param vscroll_pos Position of scrollbar (train only)
 
	 * @param vscroll_cap Number of lines currently displayed (train only)
 
	 * @param det_tab Selected details tab (train only)
 
	 */
 
	static void DrawVehicleDetails(const Vehicle *v, int left, int right, int y, int vscroll_pos, uint vscroll_cap, TrainDetailsWindowTabs det_tab)
 
	{
 
		switch (v->type) {
 
			case VEH_TRAIN:    DrawTrainDetails(Train::From(v), left, right, y, vscroll_pos, vscroll_cap, det_tab);  break;
 
			case VEH_ROAD:     DrawRoadVehDetails(v, left, right, y);  break;
 
			case VEH_SHIP:     DrawShipDetails(v, left, right, y);     break;
 
			case VEH_AIRCRAFT: DrawAircraftDetails(Aircraft::From(v), left, right, y); break;
 
			default: NOT_REACHED();
 
		}
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		if (widget == VLD_WIDGET_CAPTION) SetDParam(0, Vehicle::Get(this->window_number)->index);
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		const Vehicle *v = Vehicle::Get(this->window_number);
 

	
 
		switch (widget) {
 
			case VLD_WIDGET_TOP_DETAILS: {
 
				int y = r.top + WD_FRAMERECT_TOP;
 

	
 
				/* Draw running cost */
 
				SetDParam(1, v->age / DAYS_IN_LEAP_YEAR);
 
				SetDParam(0, (v->age + DAYS_IN_YEAR < v->max_age) ? STR_VEHICLE_INFO_AGE : STR_VEHICLE_INFO_AGE_RED);
 
				SetDParam(2, v->max_age / DAYS_IN_LEAP_YEAR);
 
				SetDParam(3, v->GetDisplayRunningCost());
 
				DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_VEHICLE_INFO_AGE_RUNNING_COST_YR);
 
				y += FONT_HEIGHT_NORMAL;
 

	
 
				/* Draw max speed */
 
				switch (v->type) {
 
					case VEH_TRAIN:
 
						SetDParam(2, v->GetDisplayMaxSpeed());
 
						SetDParam(1, Train::From(v)->tcache.cached_power);
 
						SetDParam(0, Train::From(v)->tcache.cached_weight);
 
						SetDParam(3, Train::From(v)->tcache.cached_max_te / 1000);
 
						DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, (_settings_game.vehicle.train_acceleration_model != TAM_ORIGINAL && Train::From(v)->railtype != RAILTYPE_MAGLEV) ?
 
								STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED_MAX_TE : STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED);
 
						break;
 

	
 
					case VEH_ROAD:
 
					case VEH_SHIP:
 
					case VEH_AIRCRAFT:
 
						SetDParam(0, v->GetDisplayMaxSpeed());
 
						DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_VEHICLE_INFO_MAX_SPEED);
 
						break;
 

	
 
					default: NOT_REACHED();
 
				}
 
				y += FONT_HEIGHT_NORMAL;
 

	
 
				/* Draw profit */
 
				SetDParam(0, v->GetDisplayProfitThisYear());
 
				SetDParam(1, v->GetDisplayProfitLastYear());
 
				DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_VEHICLE_INFO_PROFIT_THIS_YEAR_LAST_YEAR);
 
				y += FONT_HEIGHT_NORMAL;
 

	
 
				/* Draw breakdown & reliability */
 
				SetDParam(0, ToPercent16(v->reliability));
 
				SetDParam(1, v->breakdowns_since_last_service);
 
				DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_VEHICLE_INFO_RELIABILITY_BREAKDOWNS);
 
				break;
 
			}
 

	
 
			case VLD_WIDGET_MATRIX:
 
				/* For trains only. */
 
				DrawVehicleDetails(v, r.left + WD_MATRIX_LEFT, r.right - WD_MATRIX_RIGHT, r.top + WD_MATRIX_TOP, this->vscroll.GetPosition(), this->vscroll.GetCapacity(), this->tab);
 
				break;
 

	
 
			case VLD_WIDGET_MIDDLE_DETAILS: {
 
				/* For other vehicles, at the place of the matrix. */
 
				bool rtl = _dynlang.text_dir == TD_RTL;
 
				uint sprite_width = max<uint>(GetSprite(v->GetImage(rtl ? DIR_E : DIR_W), ST_NORMAL)->width, 70U) + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
 

	
 
				uint text_left  = r.left  + (rtl ? 0 : sprite_width);
 
				uint text_right = r.right - (rtl ? sprite_width : 0);
 

	
 
				uint sprite_left  = rtl ? text_right : r.left;
 
				uint sprite_right = rtl ? r.right : text_left;
 

	
 
				DrawVehicleImage(v, sprite_left + WD_FRAMERECT_LEFT, sprite_right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, INVALID_VEHICLE, 0);
 
				DrawVehicleDetails(v, text_left + WD_FRAMERECT_LEFT, text_right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, this->vscroll.GetPosition(), this->vscroll.GetCapacity(), this->tab);
 
			} break;
 

	
 
			case VLD_WIDGET_SERVICING_INTERVAL:
 
				/* Draw service interval text */
 
				SetDParam(0, v->service_interval);
 
				SetDParam(1, v->date_of_last_service);
 
				DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + (r.bottom - r.top + 1 - FONT_HEIGHT_NORMAL) / 2,
 
						Company::Get(v->owner)->settings.vehicle.servint_ispercent ? STR_VEHICLE_DETAILS_SERVICING_INTERVAL_PERCENT : STR_VEHICLE_DETAILS_SERVICING_INTERVAL_DAYS);
 
				break;
 
		}
 
	}
 

	
 
	/** Repaint vehicle details window. */
 
	virtual void OnPaint()
 
	{
 
		const Vehicle *v = Vehicle::Get(this->window_number);
 

	
 
		this->SetWidgetDisabledState(VLD_WIDGET_RENAME_VEHICLE, v->owner != _local_company);
 

	
 
		if (v->type == VEH_TRAIN) {
 
			this->DisableWidget(this->tab + VLD_WIDGET_DETAILS_CARGO_CARRIED);
 
			this->vscroll.SetCount(GetTrainDetailsWndVScroll(v->index, this->tab));
 
		}
 

	
 
		/* Disable service-scroller when interval is set to disabled */
 
		this->SetWidgetsDisabledState(!IsVehicleServiceIntervalEnabled(v->type, v->owner),
 
			VLD_WIDGET_INCREASE_SERVICING_INTERVAL,
 
			VLD_WIDGET_DECREASE_SERVICING_INTERVAL,
 
			WIDGET_LIST_END);
 

	
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case VLD_WIDGET_RENAME_VEHICLE: { // rename
 
				const Vehicle *v = Vehicle::Get(this->window_number);
 
				SetDParam(0, v->index);
 
				ShowQueryString(STR_VEHICLE_NAME, STR_QUERY_RENAME_TRAIN_CAPTION + v->type,
 
						MAX_LENGTH_VEHICLE_NAME_BYTES, MAX_LENGTH_VEHICLE_NAME_PIXELS, this, CS_ALPHANUMERAL, QSF_ENABLE_DEFAULT);
 
			} break;
 

	
 
			case VLD_WIDGET_INCREASE_SERVICING_INTERVAL:   // increase int
 
			case VLD_WIDGET_DECREASE_SERVICING_INTERVAL: { // decrease int
 
				int mod = _ctrl_pressed ? 5 : 10;
 
				const Vehicle *v = Vehicle::Get(this->window_number);
 

	
 
				mod = (widget == VLD_WIDGET_DECREASE_SERVICING_INTERVAL) ? -mod : mod;
 
				mod = GetServiceIntervalClamped(mod + v->service_interval, v->owner);
 
				if (mod == v->service_interval) return;
 

	
 
				DoCommandP(v->tile, v->index, mod, CMD_CHANGE_SERVICE_INT | CMD_MSG(STR_ERROR_CAN_T_CHANGE_SERVICING));
 
			} break;
 

	
 
			case VLD_WIDGET_DETAILS_CARGO_CARRIED:
 
			case VLD_WIDGET_DETAILS_TRAIN_VEHICLES:
 
			case VLD_WIDGET_DETAILS_CAPACITY_OF_EACH:
 
			case VLD_WIDGET_DETAILS_TOTAL_CARGO:
 
				this->SetWidgetsDisabledState(false,
 
					VLD_WIDGET_DETAILS_CARGO_CARRIED,
 
					VLD_WIDGET_DETAILS_TRAIN_VEHICLES,
 
					VLD_WIDGET_DETAILS_CAPACITY_OF_EACH,
 
					VLD_WIDGET_DETAILS_TOTAL_CARGO,
 
					widget,
 
					WIDGET_LIST_END);
 

	
 
				this->tab = (TrainDetailsWindowTabs)(widget - VLD_WIDGET_DETAILS_CARGO_CARRIED);
 
				this->SetDirty();
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnQueryTextFinished(char *str)
 
	{
 
		if (str == NULL) return;
 

	
 
		DoCommandP(0, this->window_number, 0, CMD_RENAME_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_RENAME_TRAIN + Vehicle::Get(this->window_number)->type), NULL, str);
 
	}
 

	
 
	virtual void OnResize()
 
	{
 
		NWidgetCore *nwi = this->GetWidget<NWidgetCore>(VLD_WIDGET_MATRIX);
 
		if (nwi != NULL) {
 
			this->vscroll.SetCapacity(nwi->current_y / this->resize.step_height);
 
			nwi->widget_data = (this->vscroll.GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START);
 
		}
 
	}
 
};
 

	
 
/** Vehicle details window descriptor. */
 
static const WindowDesc _train_vehicle_details_desc(
 
	WDP_AUTO, WDP_AUTO, 405, 178,
 
	WC_VEHICLE_DETAILS, WC_VEHICLE_VIEW,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE,
 
	_nested_train_vehicle_details_widgets, lengthof(_nested_train_vehicle_details_widgets)
 
);
 

	
 
/** Vehicle details window descriptor for other vehicles than a train. */
 
static const WindowDesc _nontrain_vehicle_details_desc(
 
	WDP_AUTO, WDP_AUTO, 405, 113,
 
	WC_VEHICLE_DETAILS, WC_VEHICLE_VIEW,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE,
 
	_nested_nontrain_vehicle_details_widgets, lengthof(_nested_nontrain_vehicle_details_widgets)
 
);
 

	
 
/** Shows the vehicle details window of the given vehicle. */
 
static void ShowVehicleDetailsWindow(const Vehicle *v)
 
{
 
	DeleteWindowById(WC_VEHICLE_ORDERS, v->index, false);
 
	DeleteWindowById(WC_VEHICLE_TIMETABLE, v->index, false);
 
	AllocateWindowDescFront<VehicleDetailsWindow>((v->type == VEH_TRAIN) ? &_train_vehicle_details_desc : &_nontrain_vehicle_details_desc, v->index);
 
}
 

	
 

	
 
/* Unified vehicle GUI - Vehicle View Window */
 

	
 
/** Vehicle view widgets. */
 
static const NWidgetPart _nested_vehicle_view_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, VVW_WIDGET_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, VVW_WIDGET_CAPTION), SetDataTip(STR_VEHICLE_VIEW_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_STICKYBOX, COLOUR_GREY, VVW_WIDGET_STICKY),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_PANEL, COLOUR_GREY, VVW_WIDGET_PANEL),
 
			NWidget(WWT_INSET, COLOUR_GREY, VVW_WIDGET_INSET), SetPadding(2, 2, 2, 2),
 
				NWidget(NWID_VIEWPORT, INVALID_COLOUR, VVW_WIDGET_VIEWPORT), SetMinimalSize(226, 84), SetResize(1, 1), SetPadding(1, 1, 1, 1),
 
			EndContainer(),
 
		EndContainer(),
 
		NWidget(NWID_VERTICAL),
 
			NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, VVW_WIDGET_CENTER_MAIN_VIEH), SetMinimalSize(18, 18), SetFill(true, true), SetDataTip(SPR_CENTRE_VIEW_VEHICLE, 0x0 /* filled later */),
 
			NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, VVW_WIDGET_CENTER_MAIN_VIEH), SetMinimalSize(18, 18), SetFill(1, 1), SetDataTip(SPR_CENTRE_VIEW_VEHICLE, 0x0 /* filled later */),
 
			NWidget(NWID_SELECTION, INVALID_COLOUR, VVW_WIDGET_SELECT_DEPOT_CLONE),
 
				NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, VVW_WIDGET_GOTO_DEPOT), SetMinimalSize(18, 18), SetFill(true, true), SetDataTip(0x0 /* filled later */, 0x0 /* filled later */),
 
				NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, VVW_WIDGET_CLONE_VEH), SetMinimalSize(18, 18), SetFill(true, true), SetDataTip(0x0 /* filled later */, 0x0 /* filled later */),
 
				NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, VVW_WIDGET_GOTO_DEPOT), SetMinimalSize(18, 18), SetFill(1, 1), SetDataTip(0x0 /* filled later */, 0x0 /* filled later */),
 
				NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, VVW_WIDGET_CLONE_VEH), SetMinimalSize(18, 18), SetFill(1, 1), SetDataTip(0x0 /* filled later */, 0x0 /* filled later */),
 
			EndContainer(),
 
			/* For trains only, 'ignore signal' button. */
 
			NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, VVW_WIDGET_FORCE_PROCEED), SetMinimalSize(18, 18), SetFill(true, true),
 
			NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, VVW_WIDGET_FORCE_PROCEED), SetMinimalSize(18, 18), SetFill(1, 1),
 
											SetDataTip(SPR_IGNORE_SIGNALS, STR_VEHICLE_VIEW_TRAIN_IGNORE_SIGNAL_TOOLTIP),
 
			NWidget(NWID_SELECTION, INVALID_COLOUR, VVW_WIDGET_SELECT_REFIT_TURN),
 
				NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, VVW_WIDGET_REFIT_VEH), SetMinimalSize(18, 18), SetFill(true, true), SetDataTip(SPR_REFIT_VEHICLE, 0x0 /* filled later */),
 
				NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, VVW_WIDGET_TURN_AROUND), SetMinimalSize(18, 18), SetFill(true, true),
 
				NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, VVW_WIDGET_REFIT_VEH), SetMinimalSize(18, 18), SetFill(1, 1), SetDataTip(SPR_REFIT_VEHICLE, 0x0 /* filled later */),
 
				NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, VVW_WIDGET_TURN_AROUND), SetMinimalSize(18, 18), SetFill(1, 1),
 
												SetDataTip(SPR_FORCE_VEHICLE_TURN, STR_VEHICLE_VIEW_ROAD_VEHICLE_REVERSE_TOOLTIP),
 
			EndContainer(),
 
			NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, VVW_WIDGET_SHOW_ORDERS), SetFill(true, true), SetMinimalSize(18, 18), SetDataTip(SPR_SHOW_ORDERS, 0x0 /* filled later */),
 
			NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, VVW_WIDGET_SHOW_DETAILS), SetFill(true, true), SetMinimalSize(18, 18), SetDataTip(SPR_SHOW_VEHICLE_DETAILS, 0x0 /* filled later */),
 
			NWidget(WWT_PANEL, COLOUR_GREY, VVW_WIDGET_EMPTY_BOTTOM_RIGHT), SetFill(true, true), SetMinimalSize(18, 0), SetResize(0, 1), EndContainer(),
 
			NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, VVW_WIDGET_SHOW_ORDERS), SetFill(1, 1), SetMinimalSize(18, 18), SetDataTip(SPR_SHOW_ORDERS, 0x0 /* filled later */),
 
			NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, VVW_WIDGET_SHOW_DETAILS), SetFill(1, 1), SetMinimalSize(18, 18), SetDataTip(SPR_SHOW_VEHICLE_DETAILS, 0x0 /* filled later */),
 
			NWidget(WWT_PANEL, COLOUR_GREY, VVW_WIDGET_EMPTY_BOTTOM_RIGHT), SetFill(1, 1), SetMinimalSize(18, 0), SetResize(0, 1), EndContainer(),
 
		EndContainer(),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_PUSHBTN, COLOUR_GREY, VVW_WIDGET_START_STOP_VEH), SetMinimalTextLines(1, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM), SetResize(1, 0), SetFill(true, false),
 
		NWidget(WWT_PUSHBTN, COLOUR_GREY, VVW_WIDGET_START_STOP_VEH), SetMinimalTextLines(1, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM), SetResize(1, 0), SetFill(1, 0),
 
		NWidget(WWT_RESIZEBOX, COLOUR_GREY, VVW_WIDGET_RESIZE),
 
	EndContainer(),
 
};
 

	
 
/** Vehicle view window descriptor for all vehicles but trains. */
 
static const WindowDesc _vehicle_view_desc(
 
	WDP_AUTO, WDP_AUTO, 250, 116,
 
	WC_VEHICLE_VIEW, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE,
 
	_nested_vehicle_view_widgets, lengthof(_nested_vehicle_view_widgets)
 
);
 

	
 
/** Vehicle view window descriptor for trains. Only minimum_height and
 
 *  default_height are different for train view.
 
 */
 
static const WindowDesc _train_view_desc(
 
	WDP_AUTO, WDP_AUTO, 250, 134,
 
	WC_VEHICLE_VIEW, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE,
 
	_nested_vehicle_view_widgets, lengthof(_nested_vehicle_view_widgets)
 
);
 

	
 

	
 
/* Just to make sure, nobody has changed the vehicle type constants, as we are
 
	 using them for array indexing in a number of places here. */
 
assert_compile(VEH_TRAIN == 0);
 
assert_compile(VEH_ROAD == 1);
 
assert_compile(VEH_SHIP == 2);
 
assert_compile(VEH_AIRCRAFT == 3);
 

	
 
/** Zoom levels for vehicle views indexed by vehicle type. */
 
static const ZoomLevel _vehicle_view_zoom_levels[] = {
 
	ZOOM_LVL_TRAIN,
 
	ZOOM_LVL_ROADVEH,
 
	ZOOM_LVL_SHIP,
 
	ZOOM_LVL_AIRCRAFT,
 
};
 

	
 
/* Constants for geometry of vehicle view viewport */
 
static const int VV_INITIAL_VIEWPORT_WIDTH = 226;
 
static const int VV_INITIAL_VIEWPORT_HEIGHT = 84;
 
static const int VV_INITIAL_VIEWPORT_HEIGHT_TRAIN = 102;
 

	
 
/** Command indices for the _vehicle_command_translation_table. */
 
enum VehicleCommandTranslation {
 
	VCT_CMD_START_STOP = 0,
 
	VCT_CMD_CLONE_VEH,
 
	VCT_CMD_TURN_AROUND,
 
};
 

	
 
/** Command codes for the shared buttons indexed by VehicleCommandTranslation and vehicle type. */
 
static const uint32 _vehicle_command_translation_table[][4] = {
 
	{ // VCT_CMD_START_STOP
 
		CMD_START_STOP_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_STOP_START_TRAIN),
 
		CMD_START_STOP_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_STOP_START_ROAD_VEHICLE),
 
		CMD_START_STOP_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_STOP_START_SHIP),
 
		CMD_START_STOP_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_STOP_START_AIRCRAFT)
 
	},
 
	{ // VCT_CMD_CLONE_VEH
 
		CMD_CLONE_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_BUY_TRAIN),
 
		CMD_CLONE_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_BUY_ROAD_VEHICLE),
 
		CMD_CLONE_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_BUY_SHIP),
 
		CMD_CLONE_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_BUY_AIRCRAFT)
 
	},
 
	{ // VCT_CMD_TURN_AROUND
 
		CMD_REVERSE_TRAIN_DIRECTION | CMD_MSG(STR_ERROR_CAN_T_REVERSE_DIRECTION_TRAIN),
 
		CMD_TURN_ROADVEH            | CMD_MSG(STR_ERROR_CAN_T_MAKE_ROAD_VEHICLE_TURN),
 
		0xffffffff, // invalid for ships
 
		0xffffffff  // invalid for aircrafts
 
	},
 
};
 

	
 
/** Checks whether the vehicle may be refitted at the moment.*/
 
static bool IsVehicleRefitable(const Vehicle *v)
 
{
 
	if (!v->IsStoppedInDepot()) return false;
 

	
 
	do {
 
		if (IsEngineRefittable(v->engine_type)) return true;
 
	} while ((v->type == VEH_TRAIN || v->type == VEH_ROAD) && (v = v->Next()) != NULL);
 

	
 
	return false;
 
}
 

	
 
/** Window manager class for viewing a vehicle. */
 
struct VehicleViewWindow : Window {
 
private:
 
	/** Display planes available in the vehicle view window. */
 
	enum PlaneSelections {
 
		SEL_DC_GOTO_DEPOT,  ///< Display 'goto depot' button in #VVW_WIDGET_SELECT_DEPOT_CLONE stacked widget.
 
		SEL_DC_CLONE,       ///< Display 'clone vehicle' button in #VVW_WIDGET_SELECT_DEPOT_CLONE stacked widget.
 

	
 
		SEL_RT_REFIT,       ///< Display 'refit' button in #VVW_WIDGET_SELECT_REFIT_TURN stacked widget.
 
		SEL_RT_TURN_AROUND, ///< Display 'turn around' button in #VVW_WIDGET_SELECT_REFIT_TURN stacked widget.
 

	
 
		SEL_DC_BASEPLANE = SEL_DC_GOTO_DEPOT, ///< First plane of the #VVW_WIDGET_SELECT_DEPOT_CLONE stacked widget.
 
		SEL_RT_BASEPLANE = SEL_RT_REFIT,      ///< First plane of the #VVW_WIDGET_SELECT_REFIT_TURN stacked widget.
 
	};
 

	
 
	/** Display a plane in the window.
 
	 * @param plane Plane to show.
 
	 */
 
	void SelectPlane(PlaneSelections plane)
 
	{
 
		switch (plane) {
 
			case SEL_DC_GOTO_DEPOT:
 
			case SEL_DC_CLONE:
 
				this->GetWidget<NWidgetStacked>(VVW_WIDGET_SELECT_DEPOT_CLONE)->SetDisplayedPlane(plane - SEL_DC_BASEPLANE);
 
				break;
 

	
 
			case SEL_RT_REFIT:
 
			case SEL_RT_TURN_AROUND:
 
				this->GetWidget<NWidgetStacked>(VVW_WIDGET_SELECT_REFIT_TURN)->SetDisplayedPlane(plane - SEL_RT_BASEPLANE);
 
				break;
 

	
 
			default:
 
				NOT_REACHED();
 
		}
 
	}
 

	
 
public:
 
	VehicleViewWindow(const WindowDesc *desc, WindowNumber window_number) : Window()
 
	{
 
		this->CreateNestedTree(desc);
 

	
 
		/* Sprites for the 'send to depot' button indexed by vehicle type. */
 
		static const SpriteID vehicle_view_goto_depot_sprites[] = {
 
			SPR_SEND_TRAIN_TODEPOT,
 
			SPR_SEND_ROADVEH_TODEPOT,
 
			SPR_SEND_SHIP_TODEPOT,
 
			SPR_SEND_AIRCRAFT_TODEPOT,
 
		};
 
		const Vehicle *v = Vehicle::Get(window_number);
 
		this->GetWidget<NWidgetCore>(VVW_WIDGET_GOTO_DEPOT)->widget_data = vehicle_view_goto_depot_sprites[v->type];
 

	
 
		/* Sprites for the 'clone vehicle' button indexed by vehicle type. */
 
		static const SpriteID vehicle_view_clone_sprites[] = {
 
			SPR_CLONE_TRAIN,
 
			SPR_CLONE_ROADVEH,
 
			SPR_CLONE_SHIP,
 
			SPR_CLONE_AIRCRAFT,
 
		};
 
		this->GetWidget<NWidgetCore>(VVW_WIDGET_CLONE_VEH)->widget_data = vehicle_view_clone_sprites[v->type];
 

	
 
		switch (v->type) {
 
			case VEH_TRAIN:
 
				this->GetWidget<NWidgetCore>(VVW_WIDGET_TURN_AROUND)->tool_tip = STR_VEHICLE_VIEW_TRAIN_REVERSE_TOOLTIP;
 
				break;
 

	
 
			case VEH_ROAD:
 
				break;
 

	
 
			case VEH_SHIP:
 
			case VEH_AIRCRAFT:
 
				this->SelectPlane(SEL_RT_REFIT);
 
				break;
 

	
 
			default: NOT_REACHED();
 
		}
 
		this->FinishInitNested(desc, window_number);
 
		this->owner = v->owner;
 
		this->GetWidget<NWidgetViewport>(VVW_WIDGET_VIEWPORT)->InitializeViewport(this, this->window_number | (1 << 31), _vehicle_view_zoom_levels[v->type]);
 

	
 
		this->GetWidget<NWidgetCore>(VVW_WIDGET_START_STOP_VEH)->tool_tip   = STR_VEHICLE_VIEW_TRAIN_STATE_START_STOP_TOOLTIP + v->type;
 
		this->GetWidget<NWidgetCore>(VVW_WIDGET_CENTER_MAIN_VIEH)->tool_tip = STR_VEHICLE_VIEW_TRAIN_LOCATION_TOOLTIP + v->type;
 
		this->GetWidget<NWidgetCore>(VVW_WIDGET_REFIT_VEH)->tool_tip        = STR_VEHICLE_VIEW_TRAIN_REFIT_TOOLTIP + v->type;
 
		this->GetWidget<NWidgetCore>(VVW_WIDGET_GOTO_DEPOT)->tool_tip       = STR_VEHICLE_VIEW_TRAIN_SEND_TO_DEPOT_TOOLTIP + v->type;
 
		this->GetWidget<NWidgetCore>(VVW_WIDGET_SHOW_ORDERS)->tool_tip      = STR_VEHICLE_VIEW_TRAIN_ORDERS_TOOLTIP + v->type;
 
		this->GetWidget<NWidgetCore>(VVW_WIDGET_SHOW_DETAILS)->tool_tip     = STR_VEHICLE_VIEW_TRAIN_SHOW_DETAILS_TOOLTIP + v->type;
 
		this->GetWidget<NWidgetCore>(VVW_WIDGET_CLONE_VEH)->tool_tip        = STR_VEHICLE_VIEW_CLONE_TRAIN_INFO + v->type;
 
	}
 

	
 
	~VehicleViewWindow()
 
	{
 
		DeleteWindowById(WC_VEHICLE_ORDERS, this->window_number, false);
 
		DeleteWindowById(WC_VEHICLE_REFIT, this->window_number, false);
 
		DeleteWindowById(WC_VEHICLE_DETAILS, this->window_number, false);
 
		DeleteWindowById(WC_VEHICLE_TIMETABLE, this->window_number, false);
 
	}
 

	
 
	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *resize)
 
	{
 
		const Vehicle *v = Vehicle::Get(this->window_number);
 
		switch (widget) {
 
			case VVW_WIDGET_FORCE_PROCEED:
 
				if (v->type != VEH_TRAIN) {
 
					size->height = 0;
 
					size->width = 0;
 
				} break;
 

	
 
			case VVW_WIDGET_VIEWPORT:
 
				size->width = VV_INITIAL_VIEWPORT_WIDTH;
 
				size->height = (v->type == VEH_TRAIN) ? VV_INITIAL_VIEWPORT_HEIGHT_TRAIN : VV_INITIAL_VIEWPORT_HEIGHT;
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		const Vehicle *v = Vehicle::Get(this->window_number);
 
		bool is_localcompany = v->owner == _local_company;
 
		bool refitable_and_stopped_in_depot = IsVehicleRefitable(v);
 

	
 
		this->SetWidgetDisabledState(VVW_WIDGET_GOTO_DEPOT, !is_localcompany);
 
		this->SetWidgetDisabledState(VVW_WIDGET_REFIT_VEH, !refitable_and_stopped_in_depot || !is_localcompany);
 
		this->SetWidgetDisabledState(VVW_WIDGET_CLONE_VEH, !is_localcompany);
 

	
 
		if (v->type == VEH_TRAIN) {
 
			this->SetWidgetDisabledState(VVW_WIDGET_FORCE_PROCEED, !is_localcompany);
 
			this->SetWidgetDisabledState(VVW_WIDGET_TURN_AROUND, !is_localcompany);
 
		}
 

	
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		if (widget != VVW_WIDGET_CAPTION) return;
 

	
 
		const Vehicle *v = Vehicle::Get(this->window_number);
 
		SetDParam(0, v->index);
 
	}
 

	
 
	virtual void DrawWidget(const Rect &r, int widget) const
 
	{
 
		if (widget != VVW_WIDGET_START_STOP_VEH) return;
 

	
 
		const Vehicle *v = Vehicle::Get(this->window_number);
 
		StringID str;
 
		if (v->vehstatus & VS_CRASHED) {
 
			str = STR_VEHICLE_STATUS_CRASHED;
 
		} else if (v->type != VEH_AIRCRAFT && v->breakdown_ctr == 1) { // check for aircraft necessary?
 
			str = STR_VEHICLE_STATUS_BROKEN_DOWN;
 
		} else if (v->vehstatus & VS_STOPPED) {
 
			if (v->type == VEH_TRAIN) {
 
				if (v->cur_speed == 0) {
 
					if (Train::From(v)->tcache.cached_power == 0) {
 
						str = STR_VEHICLE_STATUS_TRAIN_NO_POWER;
 
					} else {
 
						str = STR_VEHICLE_STATUS_STOPPED;
 
					}
 
				} else {
 
					SetDParam(0, v->GetDisplaySpeed());
 
					str = STR_VEHICLE_STATUS_TRAIN_STOPPING + _settings_client.gui.vehicle_speed;
 
				}
 
			} else { // no train
 
				str = STR_VEHICLE_STATUS_STOPPED;
 
			}
 
		} else if (v->type == VEH_TRAIN && HasBit(Train::From(v)->flags, VRF_TRAIN_STUCK) && !v->current_order.IsType(OT_LOADING)) {
 
			str = STR_VEHICLE_STATUS_TRAIN_STUCK;
 
		} else { // vehicle is in a "normal" state, show current order
 
			switch (v->current_order.GetType()) {
 
				case OT_GOTO_STATION: {
 
					SetDParam(0, v->current_order.GetDestination());
 
					SetDParam(1, v->GetDisplaySpeed());
 
					str = STR_VEHICLE_STATUS_HEADING_FOR_STATION + _settings_client.gui.vehicle_speed;
 
				} break;
 

	
 
				case OT_GOTO_DEPOT: {
 
					if (v->type == VEH_AIRCRAFT) {
 
						/* Aircrafts always go to a station, even if you say depot */
 
						SetDParam(0, v->current_order.GetDestination());
 
						SetDParam(1, v->GetDisplaySpeed());
 
					} else {
 
						Depot *depot = Depot::Get(v->current_order.GetDestination());
 
						SetDParam(0, depot->town_index);
 
						SetDParam(1, v->GetDisplaySpeed());
 
					}
 
					if (v->current_order.GetDepotActionType() & ODATFB_HALT) {
 
						str = STR_VEHICLE_STATUS_HEADING_FOR_TRAIN_DEPOT + 2 * v->type + _settings_client.gui.vehicle_speed;
 
					} else {
 
						str = STR_VEHICLE_STATUS_HEADING_FOR_TRAIN_DEPOT_SERVICE + 2 * v->type + _settings_client.gui.vehicle_speed;
 
					}
 
				} break;
 

	
 
				case OT_LOADING:
 
					str = STR_VEHICLE_STATUS_LOADING_UNLOADING;
 
					break;
 

	
 
				case OT_GOTO_WAYPOINT: {
 
					assert(v->type == VEH_TRAIN || v->type == VEH_SHIP);
 
					SetDParam(0, v->current_order.GetDestination());
 
					str = STR_VEHICLE_STATUS_HEADING_FOR_WAYPOINT + _settings_client.gui.vehicle_speed;
 
					SetDParam(1, v->GetDisplaySpeed());
 
					break;
 
				}
 

	
 
				case OT_LEAVESTATION:
 
					if (v->type != VEH_AIRCRAFT) {
 
						str = STR_VEHICLE_STATUS_LEAVING;
 
						break;
 
					}
 
					/* fall-through if aircraft. Does this even happen? */
 

	
 
				default:
 
					if (v->GetNumOrders() == 0) {
 
						str = STR_VEHICLE_STATUS_NO_ORDERS + _settings_client.gui.vehicle_speed;
 
						SetDParam(0, v->GetDisplaySpeed());
 
					} else {
 
						str = STR_EMPTY;
 
					}
 
					break;
 
			}
 
		}
 

	
 
		/* draw the flag plus orders */
 
		DrawSprite(v->vehstatus & VS_STOPPED ? SPR_FLAG_VEH_STOPPED : SPR_FLAG_VEH_RUNNING, PAL_NONE, WD_FRAMERECT_LEFT, r.top + WD_FRAMERECT_TOP);
 
		DrawString(r.left + WD_FRAMERECT_LEFT + 6, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, str, TC_FROMSTRING, SA_CENTER);
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		const Vehicle *v = Vehicle::Get(this->window_number);
 

	
 
		switch (widget) {
 
			case VVW_WIDGET_START_STOP_VEH: // start stop
 
				DoCommandP(v->tile, v->index, 0,
 
										_vehicle_command_translation_table[VCT_CMD_START_STOP][v->type]);
 
				break;
 
			case VVW_WIDGET_CENTER_MAIN_VIEH: {// center main view
 
				const Window *mainwindow = FindWindowById(WC_MAIN_WINDOW, 0);
 
				/* code to allow the main window to 'follow' the vehicle if the ctrl key is pressed */
 
				if (_ctrl_pressed && mainwindow->viewport->zoom == ZOOM_LVL_NORMAL) {
 
					mainwindow->viewport->follow_vehicle = v->index;
 
				} else {
 
					ScrollMainWindowTo(v->x_pos, v->y_pos, v->z_pos);
 
				}
 
			} break;
 

	
 
			case VVW_WIDGET_GOTO_DEPOT: // goto hangar
 
				DoCommandP(v->tile, v->index, _ctrl_pressed ? DEPOT_SERVICE : 0, GetCmdSendToDepot(v));
 
				break;
 
			case VVW_WIDGET_REFIT_VEH: // refit
 
				ShowVehicleRefitWindow(v, INVALID_VEH_ORDER_ID, this);
 
				break;
 
			case VVW_WIDGET_SHOW_ORDERS: // show orders
 
				if (_ctrl_pressed) {
 
					ShowTimetableWindow(v);
 
				} else {
 
					ShowOrdersWindow(v);
 
				}
 
				break;
 
			case VVW_WIDGET_SHOW_DETAILS: // show details
 
				ShowVehicleDetailsWindow(v);
 
				break;
 
			case VVW_WIDGET_CLONE_VEH: // clone vehicle
 
				DoCommandP(v->tile, v->index, _ctrl_pressed ? 1 : 0,
 
										_vehicle_command_translation_table[VCT_CMD_CLONE_VEH][v->type],
 
										CcCloneVehicle);
 
				break;
 
			case VVW_WIDGET_TURN_AROUND: // turn around
 
				assert(v->type == VEH_TRAIN || v->type == VEH_ROAD);
 
				DoCommandP(v->tile, v->index, 0,
 
										_vehicle_command_translation_table[VCT_CMD_TURN_AROUND][v->type]);
 
				break;
 
			case VVW_WIDGET_FORCE_PROCEED: // force proceed
 
				assert(v->type == VEH_TRAIN);
 
				DoCommandP(v->tile, v->index, 0, CMD_FORCE_TRAIN_PROCEED | CMD_MSG(STR_ERROR_CAN_T_MAKE_TRAIN_PASS_SIGNAL));
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnResize()
 
	{
 
		if (this->viewport != NULL) {
 
			NWidgetViewport *nvp = this->GetWidget<NWidgetViewport>(VVW_WIDGET_VIEWPORT);
 
			nvp->UpdateViewportCoordinates(this);
 
		}
 
	}
 

	
 
	virtual void OnTick()
 
	{
 
		const Vehicle *v = Vehicle::Get(this->window_number);
 
		bool veh_stopped = v->IsStoppedInDepot();
 

	
 
		/* Widget VVW_WIDGET_GOTO_DEPOT must be hidden if the vehicle is already stopped in depot.
 
		 * Widget VVW_WIDGET_CLONE_VEH should then be shown, since cloning is allowed only while in depot and stopped.
 
		 */
 
		PlaneSelections plane = veh_stopped ? SEL_DC_CLONE : SEL_DC_GOTO_DEPOT;
 
		NWidgetStacked *nwi = this->GetWidget<NWidgetStacked>(VVW_WIDGET_SELECT_DEPOT_CLONE); // Selection widget 'send to depot' / 'clone'.
 
		if (nwi->shown_plane + SEL_DC_BASEPLANE != plane) {
 
			this->SelectPlane(plane);
 
			this->SetWidgetDirty(VVW_WIDGET_SELECT_DEPOT_CLONE);
 
		}
 
		/* The same system applies to widget VVW_WIDGET_REFIT_VEH and VVW_WIDGET_TURN_AROUND.*/
 
		if (v->type == VEH_ROAD || v->type == VEH_TRAIN) {
 
			PlaneSelections plane = veh_stopped ? SEL_RT_REFIT : SEL_RT_TURN_AROUND;
 
			NWidgetStacked *nwi = this->GetWidget<NWidgetStacked>(VVW_WIDGET_SELECT_REFIT_TURN);
 
			if (nwi->shown_plane + SEL_RT_BASEPLANE != plane) {
 
				this->SelectPlane(plane);
 
				this->SetWidgetDirty(VVW_WIDGET_SELECT_REFIT_TURN);
 
			}
 
		}
 
	}
 
};
 

	
 

	
 
/** Shows the vehicle view window of the given vehicle. */
 
void ShowVehicleViewWindow(const Vehicle *v)
 
{
 
	AllocateWindowDescFront<VehicleViewWindow>((v->type == VEH_TRAIN) ? &_train_view_desc : &_vehicle_view_desc, v->index);
 
}
 

	
 
void StopGlobalFollowVehicle(const Vehicle *v)
 
{
 
	Window *w = FindWindowById(WC_MAIN_WINDOW, 0);
 
	if (w != NULL && w->viewport->follow_vehicle == v->index) {
 
		ScrollMainWindowTo(v->x_pos, v->y_pos, v->z_pos, true); // lock the main view on the vehicle's last position
 
		w->viewport->follow_vehicle = INVALID_VEHICLE;
 
	}
 
}
src/viewport_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file viewport_gui.cpp Extra viewport window. */
 

	
 
#include "stdafx.h"
 
#include "landscape.h"
 
#include "window_gui.h"
 
#include "viewport_func.h"
 
#include "gfx_func.h"
 
#include "strings_func.h"
 
#include "zoom_func.h"
 
#include "window_func.h"
 

	
 
#include "table/strings.h"
 
#include "table/sprites.h"
 

	
 
/** Widget numbers of the extra viewport window. */
 
enum ExtraViewportWindowWidgets {
 
	EVW_CLOSE,
 
	EVW_CAPTION,
 
	EVW_STICKY,
 
	EVW_BACKGROUND,
 
	EVW_VIEWPORT,
 
	EVW_ZOOMIN,
 
	EVW_ZOOMOUT,
 
	EVW_MAIN_TO_VIEW,
 
	EVW_VIEW_TO_MAIN,
 
	EVW_SPACER,
 
	EVW_RESIZE,
 
};
 

	
 
/* Extra ViewPort Window Stuff */
 
static const NWidgetPart _nested_extra_view_port_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, EVW_CLOSE),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, EVW_CAPTION), SetDataTip(STR_EXTRA_VIEW_PORT_TITLE, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_STICKYBOX, COLOUR_GREY, EVW_STICKY),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, EVW_BACKGROUND),
 
		NWidget(NWID_VIEWPORT, INVALID_COLOUR, EVW_VIEWPORT), SetPadding(2, 2, 2, 2), SetResize(1, 1), SetFill(true, true),
 
		NWidget(NWID_VIEWPORT, INVALID_COLOUR, EVW_VIEWPORT), SetPadding(2, 2, 2, 2), SetResize(1, 1), SetFill(1, 1),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, EVW_ZOOMIN), SetDataTip(SPR_IMG_ZOOMIN, STR_TOOLBAR_TOOLTIP_ZOOM_THE_VIEW_IN),
 
		NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, EVW_ZOOMOUT), SetDataTip(SPR_IMG_ZOOMOUT, STR_TOOLBAR_TOOLTIP_ZOOM_THE_VIEW_OUT),
 
		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, EVW_MAIN_TO_VIEW), SetFill(true, true), SetResize(1, 0),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, EVW_MAIN_TO_VIEW), SetFill(1, 1), SetResize(1, 0),
 
										SetDataTip(STR_EXTRA_VIEW_MOVE_MAIN_TO_VIEW, STR_EXTRA_VIEW_MOVE_MAIN_TO_VIEW_TT),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, EVW_VIEW_TO_MAIN), SetFill(true, true), SetResize(1, 0),
 
			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, EVW_VIEW_TO_MAIN), SetFill(1, 1), SetResize(1, 0),
 
										SetDataTip(STR_EXTRA_VIEW_MOVE_VIEW_TO_MAIN, STR_EXTRA_VIEW_MOVE_VIEW_TO_MAIN_TT),
 
		EndContainer(),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_PANEL, COLOUR_GREY, EVW_SPACER), SetFill(true, true), SetResize(1, 0), EndContainer(),
 
		NWidget(WWT_PANEL, COLOUR_GREY, EVW_SPACER), SetFill(1, 1), SetResize(1, 0), EndContainer(),
 
		NWidget(WWT_RESIZEBOX, COLOUR_GREY, EVW_RESIZE),
 
	EndContainer(),
 
};
 

	
 
class ExtraViewportWindow : public Window {
 
public:
 
	ExtraViewportWindow(const WindowDesc *desc, int window_number, TileIndex tile) : Window()
 
	{
 
		this->InitNested(desc, window_number);
 

	
 
		NWidgetViewport *nvp = this->GetWidget<NWidgetViewport>(EVW_VIEWPORT);
 
		nvp->InitializeViewport(this, 0, ZOOM_LVL_NORMAL);
 
		this->DisableWidget(EVW_ZOOMIN);
 

	
 
		Point pt;
 
		if (tile == INVALID_TILE) {
 
			/* the main window with the main view */
 
			const Window *w = FindWindowById(WC_MAIN_WINDOW, 0);
 

	
 
			/* center on same place as main window (zoom is maximum, no adjustment needed) */
 
			pt.x = w->viewport->scrollpos_x + w->viewport->virtual_height / 2;
 
			pt.y = w->viewport->scrollpos_y + w->viewport->virtual_height / 2;
 
		} else {
 
			pt = RemapCoords(TileX(tile) * TILE_SIZE + TILE_SIZE / 2, TileY(tile) * TILE_SIZE + TILE_SIZE / 2, TileHeight(tile));
 
		}
 

	
 
		this->viewport->scrollpos_x = pt.x - (nvp->pos_x - ((nvp->current_x - 1) / 2));
 
		this->viewport->scrollpos_y = pt.y - (nvp->pos_y - ((nvp->current_y - 1) / 2));
 
		this->viewport->dest_scrollpos_x = this->viewport->scrollpos_x;
 
		this->viewport->dest_scrollpos_y = this->viewport->scrollpos_y;
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		switch (widget) {
 
			case EVW_CAPTION:
 
				/* set the number in the title bar */
 
				SetDParam(0, this->window_number + 1);
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case EVW_ZOOMIN: DoZoomInOutWindow(ZOOM_IN,  this); break;
 
			case EVW_ZOOMOUT: DoZoomInOutWindow(ZOOM_OUT, this); break;
 

	
 
			case EVW_MAIN_TO_VIEW: { // location button (move main view to same spot as this view) 'Paste Location'
 
				Window *w = FindWindowById(WC_MAIN_WINDOW, 0);
 
				int x = this->viewport->scrollpos_x; // Where is the main looking at
 
				int y = this->viewport->scrollpos_y;
 

	
 
				/* set this view to same location. Based on the center, adjusting for zoom */
 
				w->viewport->dest_scrollpos_x =  x - (w->viewport->virtual_width -  this->viewport->virtual_width) / 2;
 
				w->viewport->dest_scrollpos_y =  y - (w->viewport->virtual_height - this->viewport->virtual_height) / 2;
 
				w->viewport->follow_vehicle   = INVALID_VEHICLE;
 
			} break;
 

	
 
			case EVW_VIEW_TO_MAIN: { // inverse location button (move this view to same spot as main view) 'Copy Location'
 
				const Window *w = FindWindowById(WC_MAIN_WINDOW, 0);
 
				int x = w->viewport->scrollpos_x;
 
				int y = w->viewport->scrollpos_y;
 

	
 
				this->viewport->dest_scrollpos_x =  x + (w->viewport->virtual_width -  this->viewport->virtual_width) / 2;
 
				this->viewport->dest_scrollpos_y =  y + (w->viewport->virtual_height - this->viewport->virtual_height) / 2;
 
			} break;
 
		}
 
	}
 

	
 
	virtual void OnResize()
 
	{
 
		if (this->viewport != NULL) {
 
			NWidgetViewport *nvp = this->GetWidget<NWidgetViewport>(EVW_VIEWPORT);
 
			nvp->UpdateViewportCoordinates(this);
 
		}
 
	}
 

	
 
	virtual void OnScroll(Point delta)
 
	{
 
		const ViewPort *vp = IsPtInWindowViewport(this, _cursor.pos.x, _cursor.pos.y);
 
		if (vp == NULL) return;
 

	
 
		this->viewport->scrollpos_x += ScaleByZoom(delta.x, vp->zoom);
 
		this->viewport->scrollpos_y += ScaleByZoom(delta.y, vp->zoom);
 
		this->viewport->dest_scrollpos_x = this->viewport->scrollpos_x;
 
		this->viewport->dest_scrollpos_y = this->viewport->scrollpos_y;
 
	}
 

	
 
	virtual void OnMouseWheel(int wheel)
 
	{
 
		ZoomInOrOutToCursorWindow(wheel < 0, this);
 
	}
 

	
 
	virtual void OnInvalidateData(int data = 0)
 
	{
 
		/* Only handle zoom message if intended for us (msg ZOOM_IN/ZOOM_OUT) */
 
		HandleZoomMessage(this, this->viewport, EVW_ZOOMIN, EVW_ZOOMOUT);
 
	}
 
};
 

	
 
static const WindowDesc _extra_view_port_desc(
 
	WDP_AUTO, WDP_AUTO, 300, 268,
 
	WC_EXTRA_VIEW_PORT, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE,
 
	_nested_extra_view_port_widgets, lengthof(_nested_extra_view_port_widgets)
 
);
 

	
 
void ShowExtraViewPortWindow(TileIndex tile)
 
{
 
	int i = 0;
 

	
 
	/* find next free window number for extra viewport */
 
	while (FindWindowById(WC_EXTRA_VIEW_PORT, i) != NULL) i++;
 

	
 
	new ExtraViewportWindow(&_extra_view_port_desc, i, tile);
 
}
src/waypoint_gui.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file waypoint_gui.cpp Handling of waypoints gui. */
 

	
 
#include "stdafx.h"
 
#include "window_gui.h"
 
#include "gui.h"
 
#include "textbuf_gui.h"
 
#include "vehicle_gui.h"
 
#include "viewport_func.h"
 
#include "strings_func.h"
 
#include "gfx_func.h"
 
#include "command_func.h"
 
#include "company_func.h"
 
#include "window_func.h"
 
#include "waypoint_base.h"
 

	
 
#include "table/strings.h"
 

	
 
/** Widget definitions for the waypoint window. */
 
enum WaypointWindowWidgets {
 
	WAYPVW_CLOSEBOX = 0,
 
	WAYPVW_CAPTION,
 
	WAYPVW_STICKY,
 
	WAYPVW_VIEWPORTPANEL,
 
	WAYPVW_SPACER,
 
	WAYPVW_VIEWPORT,
 
	WAYPVW_CENTERVIEW,
 
	WAYPVW_RENAME,
 
	WAYPVW_SHOW_VEHICLES,
 
};
 

	
 
struct WaypointWindow : Window {
 
private:
 
	VehicleType vt;
 
	Waypoint *wp;
 

	
 
public:
 
	WaypointWindow(const WindowDesc *desc, WindowNumber window_number) : Window()
 
	{
 
		this->wp = Waypoint::Get(window_number);
 
		this->vt = (wp->string_id == STR_SV_STNAME_WAYPOINT) ? VEH_TRAIN : VEH_SHIP;
 

	
 
		if (this->wp->owner != OWNER_NONE) this->owner = this->wp->owner;
 

	
 
		this->CreateNestedTree(desc);
 
		if (this->vt == VEH_TRAIN) {
 
			this->GetWidget<NWidgetCore>(WAYPVW_SHOW_VEHICLES)->SetDataTip(STR_TRAIN, STR_STATION_VIEW_SCHEDULED_TRAINS_TOOLTIP);
 
			this->GetWidget<NWidgetCore>(WAYPVW_CENTERVIEW)->tool_tip = STR_WAYPOINT_VIEW_CENTER_TOOLTIP;
 
			this->GetWidget<NWidgetCore>(WAYPVW_RENAME)->tool_tip = STR_WAYPOINT_VIEW_CHANGE_WAYPOINT_NAME;
 
		}
 
		this->FinishInitNested(desc, window_number);
 

	
 
		this->flags4 |= WF_DISABLE_VP_SCROLL;
 
		NWidgetViewport *nvp = this->GetWidget<NWidgetViewport>(WAYPVW_VIEWPORT);
 
		nvp->InitializeViewport(this, this->wp->xy, ZOOM_LVL_MIN);
 

	
 
		this->OnInvalidateData(0);
 
	}
 

	
 
	~WaypointWindow()
 
	{
 
		DeleteWindowById(GetWindowClassForVehicleType(this->vt), (this->window_number << 16) | (this->vt << 11) | VLW_WAYPOINT_LIST | this->wp->owner);
 
	}
 

	
 
	virtual void SetStringParameters(int widget) const
 
	{
 
		if (widget == WAYPVW_CAPTION) SetDParam(0, this->wp->index);
 
	}
 

	
 
	virtual void OnPaint()
 
	{
 
		this->DrawWidgets();
 
	}
 

	
 
	virtual void OnClick(Point pt, int widget)
 
	{
 
		switch (widget) {
 
			case WAYPVW_CENTERVIEW: // scroll to location
 
				if (_ctrl_pressed) {
 
					ShowExtraViewPortWindow(this->wp->xy);
 
				} else {
 
					ScrollMainWindowToTile(this->wp->xy);
 
				}
 
				break;
 

	
 
			case WAYPVW_RENAME: // rename
 
				SetDParam(0, this->wp->index);
 
				ShowQueryString(STR_WAYPOINT_NAME, STR_EDIT_WAYPOINT_NAME, MAX_LENGTH_STATION_NAME_BYTES, MAX_LENGTH_STATION_NAME_PIXELS, this, CS_ALPHANUMERAL, QSF_ENABLE_DEFAULT);
 
				break;
 

	
 
			case WAYPVW_SHOW_VEHICLES: // show list of vehicles having this waypoint in their orders
 
				ShowVehicleListWindow((this->wp->owner == OWNER_NONE) ? _local_company : this->wp->owner, this->vt, this->wp);
 
				break;
 
		}
 
	}
 

	
 
	virtual void OnInvalidateData(int data)
 
	{
 
		/* You can only change your own waypoints */
 
		this->SetWidgetDisabledState(WAYPVW_RENAME, !this->wp->IsInUse() || (this->wp->owner != _local_company && this->wp->owner != OWNER_NONE));
 
		/* Disable the widget for waypoints with no use */
 
		this->SetWidgetDisabledState(WAYPVW_SHOW_VEHICLES, !this->wp->IsInUse());
 

	
 
		int x = TileX(this->wp->xy) * TILE_SIZE;
 
		int y = TileY(this->wp->xy) * TILE_SIZE;
 
		ScrollWindowTo(x, y, -1, this);
 
	}
 

	
 
	virtual void OnQueryTextFinished(char *str)
 
	{
 
		if (str == NULL) return;
 

	
 
		DoCommandP(0, this->window_number, 0, CMD_RENAME_WAYPOINT | CMD_MSG(STR_ERROR_CAN_T_CHANGE_WAYPOINT_NAME), NULL, str);
 
	}
 

	
 
};
 

	
 
static const NWidgetPart _nested_waypoint_view_widgets[] = {
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_CLOSEBOX, COLOUR_GREY, WAYPVW_CLOSEBOX),
 
		NWidget(WWT_CAPTION, COLOUR_GREY, WAYPVW_CAPTION), SetDataTip(STR_WAYPOINT_VIEW_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 
		NWidget(WWT_STICKYBOX, COLOUR_GREY, WAYPVW_STICKY),
 
	EndContainer(),
 
	NWidget(WWT_PANEL, COLOUR_GREY, WAYPVW_VIEWPORTPANEL),
 
		NWidget(WWT_INSET, COLOUR_GREY, WAYPVW_SPACER), SetPadding(2, 2, 2, 2),
 
			NWidget(NWID_VIEWPORT, COLOUR_GREY, WAYPVW_VIEWPORT), SetMinimalSize(256, 88), SetPadding(1, 1, 1, 1),
 
		EndContainer(),
 
	EndContainer(),
 
	NWidget(NWID_HORIZONTAL),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WAYPVW_CENTERVIEW), SetMinimalSize(100, 12), SetFill(true, false), SetDataTip(STR_BUTTON_LOCATION, STR_BUOY_VIEW_CENTER_TOOLTIP),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WAYPVW_RENAME), SetMinimalSize(100, 12), SetFill(true, false), SetDataTip(STR_BUTTON_RENAME, STR_BUOY_VIEW_CHANGE_BUOY_NAME),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WAYPVW_CENTERVIEW), SetMinimalSize(100, 12), SetFill(1, 0), SetDataTip(STR_BUTTON_LOCATION, STR_BUOY_VIEW_CENTER_TOOLTIP),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WAYPVW_RENAME), SetMinimalSize(100, 12), SetFill(1, 0), SetDataTip(STR_BUTTON_RENAME, STR_BUOY_VIEW_CHANGE_BUOY_NAME),
 
		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WAYPVW_SHOW_VEHICLES), SetMinimalSize(15, 12), SetDataTip(STR_SHIP, STR_STATION_VIEW_SCHEDULED_SHIPS_TOOLTIP),
 
	EndContainer(),
 
};
 

	
 
static const WindowDesc _waypoint_view_desc(
 
	WDP_AUTO, WDP_AUTO, 260, 118,
 
	WC_WAYPOINT_VIEW, WC_NONE,
 
	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON,
 
	_nested_waypoint_view_widgets, lengthof(_nested_waypoint_view_widgets)
 
);
 

	
 
void ShowWaypointWindow(const Waypoint *wp)
 
{
 
	AllocateWindowDescFront<WaypointWindow>(&_waypoint_view_desc, wp->index);
 
}
src/widget.cpp
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file widget.cpp Handling of the default/simple widgets. */
 

	
 
#include "stdafx.h"
 
#include "openttd.h"
 
#include "company_func.h"
 
#include "window_gui.h"
 
#include "viewport_func.h"
 
#include "zoom_func.h"
 
#include "debug.h"
 
#include "strings_func.h"
 
#include "transparency.h"
 

	
 
#include "table/sprites.h"
 
#include "table/strings.h"
 

	
 
static const char *UPARROW   = "\xEE\x8A\xA0"; ///< String containing an upwards pointing arrow.
 
static const char *DOWNARROW = "\xEE\x8A\xAA"; ///< String containing a downwards pointing arrow.
 

	
 
/**
 
 * Compute the vertical position of the draggable part of scrollbar
 
 * @param sb     Scrollbar list data
 
 * @param top    Top position of the scrollbar (top position of the up-button)
 
 * @param bottom Bottom position of the scrollbar (bottom position of the down-button)
 
 * @param horizontal Whether the scrollbar is horizontal or not
 
 * @return A Point, with x containing the top coordinate of the draggable part, and
 
 *                       y containing the bottom coordinate of the draggable part
 
 */
 
static Point HandleScrollbarHittest(const Scrollbar *sb, int top, int bottom, bool horizontal)
 
{
 
	/* Base for reversion */
 
	int rev_base = top + bottom;
 
	top += 10;   // top    points to just below the up-button
 
	bottom -= 9; // bottom points to top of the down-button
 

	
 
	int height = (bottom - top);
 
	int pos = sb->GetPosition();
 
	int count = sb->GetCount();
 
	int cap = sb->GetCapacity();
 

	
 
	if (count != 0) top += height * pos / count;
 

	
 
	if (cap > count) cap = count;
 
	if (count != 0) bottom -= (count - pos - cap) * height / count;
 

	
 
	Point pt;
 
	if (horizontal && _dynlang.text_dir == TD_RTL) {
 
		pt.x = rev_base - (bottom - 1);
 
		pt.y = rev_base - top;
 
	} else {
 
		pt.x = top;
 
		pt.y = bottom - 1;
 
	}
 
	return pt;
 
}
 

	
 
/**
 
 * Compute new position of the scrollbar after a click and updates the window flags.
 
 * @param w   Window on which a scroll was performed.
 
 * @param wtp Scrollbar widget type.
 
 * @param mi  Minimum coordinate of the scroll bar.
 
 * @param ma  Maximum coordinate of the scroll bar.
 
 * @param x   The X coordinate of the mouse click.
 
 * @param y   The Y coordinate of the mouse click.
 
 */
 
static void ScrollbarClickPositioning(Window *w, WidgetType wtp, int x, int y, int mi, int ma)
 
{
 
	int pos;
 
	Scrollbar *sb;
 
	bool rtl = false;
 

	
 
	switch (wtp) {
 
		case WWT_SCROLLBAR:
 
			/* vertical scroller */
 
			w->flags4 &= ~WF_HSCROLL;
 
			w->flags4 &= ~WF_SCROLL2;
 
			pos = y;
 
			sb = &w->vscroll;
 
			break;
 

	
 
		case WWT_SCROLL2BAR:
 
			/* 2nd vertical scroller */
 
			w->flags4 &= ~WF_HSCROLL;
 
			w->flags4 |= WF_SCROLL2;
 
			pos = y;
 
			sb = &w->vscroll2;
 
			break;
 

	
 
		case WWT_HSCROLLBAR:
 
			/* horizontal scroller */
 
			w->flags4 &= ~WF_SCROLL2;
 
			w->flags4 |= WF_HSCROLL;
 
			pos = x;
 
			sb = &w->hscroll;
 
			rtl = _dynlang.text_dir == TD_RTL;
 
			break;
 

	
 
		default: NOT_REACHED();
 
	}
 
	if (pos <= mi + 9) {
 
		/* Pressing the upper button? */
 
		w->flags4 |= WF_SCROLL_UP;
 
		if (_scroller_click_timeout == 0) {
 
			_scroller_click_timeout = 6;
 
			sb->UpdatePosition(rtl ? 1 : -1);
 
		}
 
		_left_button_clicked = false;
 
	} else if (pos >= ma - 10) {
 
		/* Pressing the lower button? */
 
		w->flags4 |= WF_SCROLL_DOWN;
 

	
 
		if (_scroller_click_timeout == 0) {
 
			_scroller_click_timeout = 6;
 
			sb->UpdatePosition(rtl ? -1 : 1);
 
		}
 
		_left_button_clicked = false;
 
	} else {
 
		Point pt = HandleScrollbarHittest(sb, mi, ma, wtp == WWT_HSCROLLBAR);
 

	
 
		if (pos < pt.x) {
 
			sb->UpdatePosition(rtl ? sb->GetCapacity() : -sb->GetCapacity());
 
		} else if (pos > pt.y) {
 
			sb->UpdatePosition(rtl ? -sb->GetCapacity() : sb->GetCapacity());
 
		} else {
 
			_scrollbar_start_pos = pt.x - mi - 9;
 
			_scrollbar_size = ma - mi - 23;
 
			w->flags4 |= WF_SCROLL_MIDDLE;
 
			_scrolling_scrollbar = true;
 
			_cursorpos_drag_start = _cursor.pos;
 
		}
 
	}
 

	
 
	w->SetDirty();
 
}
 

	
 
/** Special handling for the scrollbar widget type.
 
 * Handles the special scrolling buttons and other scrolling.
 
 * @param w Window on which a scroll was performed.
 
 * @param nw Pointer to the scrollbar widget.
 
 * @param x The X coordinate of the mouse click.
 
 * @param y The Y coordinate of the mouse click.
 
 */
 
void ScrollbarClickHandler(Window *w, const NWidgetCore *nw, int x, int y)
 
{
 
	int mi, ma;
 

	
 
	switch (nw->type) {
 
		case WWT_SCROLLBAR:
 
			/* vertical scroller */
 
			mi = nw->pos_y;
 
			ma = nw->pos_y + nw->current_y;
 
			break;
 

	
 
		case WWT_SCROLL2BAR:
 
			/* 2nd vertical scroller */
 
			mi = nw->pos_y;
 
			ma = nw->pos_y + nw->current_y;
 
			break;
 

	
 
		case WWT_HSCROLLBAR:
 
			/* horizontal scroller */
 
			mi = nw->pos_x;
 
			ma = nw->pos_x + nw->current_x;
 
			break;
 

	
 
		default: NOT_REACHED();
 
	}
 
	ScrollbarClickPositioning(w, nw->type, x, y, mi, ma);
 
}
 

	
 
/** Returns the index for the widget located at the given position
 
 * relative to the window. It includes all widget-corner pixels as well.
 
 * @param *w Window to look inside
 
 * @param  x The Window client X coordinate
 
 * @param  y The Window client y coordinate
 
 * @return A widget index, or -1 if no widget was found.
 
 */
 
int GetWidgetFromPos(const Window *w, int x, int y)
 
{
 
	NWidgetCore *nw = w->nested_root->GetWidgetFromPos(x, y);
 
	return (nw != NULL) ? nw->index : -1;
 
}
 

	
 
/**
 
 * Draw frame rectangle.
 
 * @param left   Left edge of the frame
 
 * @param top    Top edge of the frame
 
 * @param right  Right edge of the frame
 
 * @param bottom Bottom edge of the frame
 
 * @param colour Colour table to use. @see _colour_gradient
 
 * @param flags  Flags controlling how to draw the frame. @see FrameFlags
 
 */
 
void DrawFrameRect(int left, int top, int right, int bottom, Colours colour, FrameFlags flags)
 
{
 
	uint dark         = _colour_gradient[colour][3];
 
	uint medium_dark  = _colour_gradient[colour][5];
 
	uint medium_light = _colour_gradient[colour][6];
 
	uint light        = _colour_gradient[colour][7];
 

	
 
	if (flags & FR_TRANSPARENT) {
 
		GfxFillRect(left, top, right, bottom, PALETTE_TO_TRANSPARENT, FILLRECT_RECOLOUR);
 
	} else {
 
		uint interior;
 

	
 
		if (flags & FR_LOWERED) {
 
			GfxFillRect(left,     top,     left,  bottom,     dark);
 
			GfxFillRect(left + 1, top,     right, top,        dark);
 
			GfxFillRect(right,    top + 1, right, bottom - 1, light);
 
			GfxFillRect(left + 1, bottom,  right, bottom,     light);
 
			interior = (flags & FR_DARKENED ? medium_dark : medium_light);
 
		} else {
 
			GfxFillRect(left,     top,    left,      bottom - 1, light);
 
			GfxFillRect(left + 1, top,    right - 1, top,        light);
 
			GfxFillRect(right,    top,    right,     bottom - 1, dark);
 
			GfxFillRect(left,     bottom, right,     bottom,     dark);
 
			interior = medium_dark;
 
		}
 
		if (!(flags & FR_BORDERONLY)) {
 
			GfxFillRect(left + 1, top + 1, right - 1, bottom - 1, interior);
 
		}
 
	}
 
}
 

	
 
/**
 
 * Draw an image button.
 
 * @param r       Rectangle of the button.
 
 * @param type    Widget type (#WWT_IMGBTN or #WWT_IMGBTN_2).
 
 * @param colour  Colour of the button.
 
 * @param clicked Button is lowered.
 
 * @param img     Sprite to draw.
 
 */
 
static inline void DrawImageButtons(const Rect &r, WidgetType type, Colours colour, bool clicked, SpriteID img)
 
{
 
	assert(img != 0);
 
	DrawFrameRect(r.left, r.top, r.right, r.bottom, colour, (clicked) ? FR_LOWERED : FR_NONE);
 

	
 
	if ((type & WWT_MASK) == WWT_IMGBTN_2 && clicked) img++; // Show different image when clicked for #WWT_IMGBTN_2.
 
	DrawSprite(img, PAL_NONE, r.left + WD_IMGBTN_LEFT + clicked, r.top + WD_IMGBTN_TOP + clicked);
 
}
 

	
 
/**
 
 * Draw the label-part of a widget.
 
 * @param r       Rectangle of the label background.
 
 * @param type    Widget type (#WWT_TEXTBTN, #WWT_TEXTBTN_2, or #WWT_LABEL).
 
 * @param clicked Label is rendered lowered.
 
 * @param str     Text to draw.
 
 */
 
static inline void DrawLabel(const Rect &r, WidgetType type, bool clicked, StringID str)
 
{
 
	if (str == STR_NULL) return;
 
	if ((type & WWT_MASK) == WWT_TEXTBTN_2 && clicked) str++;
 
	Dimension d = GetStringBoundingBox(str);
 
	int offset = max(0, ((int)(r.bottom - r.top + 1) - (int)d.height) / 2); // Offset for rendering the text vertically centered
 
	DrawString(r.left + clicked, r.right + clicked, r.top + offset + clicked, str, TC_FROMSTRING, SA_CENTER);
 
}
 

	
 
/**
 
 * Draw text.
 
 * @param r      Rectangle of the background.
 
 * @param colour Colour of the text.
 
 * @param str    Text to draw.
 
 */
 
static inline void DrawText(const Rect &r, TextColour colour, StringID str)
 
{
 
	Dimension d = GetStringBoundingBox(str);
 
	int offset = max(0, ((int)(r.bottom - r.top + 1) - (int)d.height) / 2); // Offset for rendering the text vertically centered
 
	if (str != STR_NULL) DrawString(r.left, r.right, r.top + offset, str, colour);
 
}
 

	
 
/**
 
 * Draw an inset widget.
 
 * @param r      Rectangle of the background.
 
 * @param colour Colour of the inset.
 
 * @param str    Text to draw.
 
 */
 
static inline void DrawInset(const Rect &r, Colours colour, StringID str)
 
{
 
	DrawFrameRect(r.left, r.top, r.right, r.bottom, colour, FR_LOWERED | FR_DARKENED);
 
	if (str != STR_NULL) DrawString(r.left + WD_INSET_LEFT, r.right - WD_INSET_RIGHT, r.top + WD_INSET_TOP, str);
 
}
 

	
 
/**
 
 * Draw a matrix widget.
 
 * @param r       Rectangle of the matrix background.
 
 * @param colour  Colour of the background.
 
 * @param clicked Matrix is rendered lowered.
 
 * @param data    Data of the widget, number of rows and columns of the widget.
 
 */
 
static inline void DrawMatrix(const Rect &r, Colours colour, bool clicked, uint16 data)
 
{
 
	DrawFrameRect(r.left, r.top, r.right, r.bottom, colour, (clicked) ? FR_LOWERED : FR_NONE);
 

	
 
	int num_columns = GB(data, MAT_COL_START, MAT_COL_BITS);  // Lower 8 bits of the widget data: Number of columns in the matrix.
 
	int column_width = (r.right - r.left + 1) / num_columns; // Width of a single column in the matrix.
 

	
 
	int num_rows = GB(data, MAT_ROW_START, MAT_ROW_BITS); // Upper 8 bits of the widget data: Number of rows in the matrix.
 
	int row_height = (r.bottom - r.top + 1) / num_rows; // Height of a single row in the matrix.
 

	
 
	int col = _colour_gradient[colour & 0xF][6];
 

	
 
	int x = r.left;
 
	for (int ctr = num_columns; ctr > 1; ctr--) {
 
		x += column_width;
 
		GfxFillRect(x, r.top + 1, x, r.bottom - 1, col);
 
	}
 

	
 
	x = r.top;
 
	for (int ctr = num_rows; ctr > 1; ctr--) {
 
		x += row_height;
 
		GfxFillRect(r.left + 1, x, r.right - 1, x, col);
 
	}
 

	
 
	col = _colour_gradient[colour & 0xF][4];
 

	
 
	x = r.left - 1;
 
	for (int ctr = num_columns; ctr > 1; ctr--) {
 
		x += column_width;
 
		GfxFillRect(x, r.top + 1, x, r.bottom - 1, col);
 
	}
 

	
 
	x = r.top - 1;
 
	for (int ctr = num_rows; ctr > 1; ctr--) {
 
		x += row_height;
 
		GfxFillRect(r.left + 1, x, r.right - 1, x, col);
 
	}
 
}
 

	
 
/**
 
 * Draw a vertical scrollbar.
 
 * @param r            Rectangle of the scrollbar widget.
 
 * @param colour       Colour of the scrollbar widget.
 
 * @param up_clicked   Up-arrow is clicked.
 
 * @param bar_dragged  Bar is dragged.
 
 * @param down_clicked Down-arrow is clicked.
 
 * @param scrollbar    Scrollbar size, offset, and capacity information.
 
 */
 
static inline void DrawVerticalScrollbar(const Rect &r, Colours colour, bool up_clicked, bool bar_dragged, bool down_clicked, const Scrollbar *scrollbar)
 
{
 
	/* draw up/down buttons */
 
	DrawFrameRect(r.left, r.top, r.right, r.top + 9, colour, (up_clicked) ? FR_LOWERED : FR_NONE);
 
	DrawString(r.left + up_clicked, r.right + up_clicked, r.top + up_clicked, UPARROW, TC_BLACK, SA_CENTER);
 

	
 
	DrawFrameRect(r.left, r.bottom - 9, r.right, r.bottom, colour, (down_clicked) ? FR_LOWERED : FR_NONE);
 
	DrawString(r.left + down_clicked, r.right + down_clicked, r.bottom - 9 + down_clicked, DOWNARROW, TC_BLACK, SA_CENTER);
 

	
 
	int c1 = _colour_gradient[colour & 0xF][3];
 
	int c2 = _colour_gradient[colour & 0xF][7];
 

	
 
	/* draw "shaded" background */
 
	GfxFillRect(r.left, r.top + 10, r.right, r.bottom - 10, c2);
 
	GfxFillRect(r.left, r.top + 10, r.right, r.bottom - 10, c1, FILLRECT_CHECKER);
 

	
 
	/* draw shaded lines */
 
	GfxFillRect(r.left + 2, r.top + 10, r.left + 2, r.bottom - 10, c1);
 
	GfxFillRect(r.left + 3, r.top + 10, r.left + 3, r.bottom - 10, c2);
 
	GfxFillRect(r.left + 7, r.top + 10, r.left + 7, r.bottom - 10, c1);
 
	GfxFillRect(r.left + 8, r.top + 10, r.left + 8, r.bottom - 10, c2);
 

	
 
	Point pt = HandleScrollbarHittest(scrollbar, r.top, r.bottom, false);
 
	DrawFrameRect(r.left, pt.x, r.right, pt.y, colour, bar_dragged ? FR_LOWERED : FR_NONE);
 
}
 

	
 
/**
 
 * Draw a horizontal scrollbar.
 
 * @param r             Rectangle of the scrollbar widget.
 
 * @param colour        Colour of the scrollbar widget.
 
 * @param left_clicked  Left-arrow is clicked.
 
 * @param bar_dragged   Bar is dragged.
 
 * @param right_clicked Right-arrow is clicked.
 
 * @param scrollbar     Scrollbar size, offset, and capacity information.
 
 */
 
static inline void DrawHorizontalScrollbar(const Rect &r, Colours colour, bool left_clicked, bool bar_dragged, bool right_clicked, const Scrollbar *scrollbar)
 
{
 
	DrawFrameRect(r.left, r.top, r.left + 9, r.bottom, colour, left_clicked ? FR_LOWERED : FR_NONE);
 
	DrawSprite(SPR_ARROW_LEFT, PAL_NONE, r.left + 1 + left_clicked, r.top + 1 + left_clicked);
 

	
 
	DrawFrameRect(r.right - 9, r.top, r.right, r.bottom, colour, right_clicked ? FR_LOWERED : FR_NONE);
 
	DrawSprite(SPR_ARROW_RIGHT, PAL_NONE, r.right - 8 + right_clicked, r.top + 1 + right_clicked);
 

	
 
	int c1 = _colour_gradient[colour & 0xF][3];
 
	int c2 = _colour_gradient[colour & 0xF][7];
 

	
 
	/* draw "shaded" background */
 
	GfxFillRect(r.left + 10, r.top, r.right - 10, r.bottom, c2);
 
	GfxFillRect(r.left + 10, r.top, r.right - 10, r.bottom, c1, FILLRECT_CHECKER);
 

	
 
	/* draw shaded lines */
 
	GfxFillRect(r.left + 10, r.top + 2, r.right - 10, r.top + 2, c1);
 
	GfxFillRect(r.left + 10, r.top + 3, r.right - 10, r.top + 3, c2);
 
	GfxFillRect(r.left + 10, r.top + 7, r.right - 10, r.top + 7, c1);
 
	GfxFillRect(r.left + 10, r.top + 8, r.right - 10, r.top + 8, c2);
 

	
 
	/* draw actual scrollbar */
 
	Point pt = HandleScrollbarHittest(scrollbar, r.left, r.right, true);
 
	DrawFrameRect(pt.x, r.top, pt.y, r.bottom, colour, bar_dragged ? FR_LOWERED : FR_NONE);
 
}
 

	
 
/**
 
 * Draw a frame widget.
 
 * @param r      Rectangle of the frame.
 
 * @param colour Colour of the frame.
 
 * @param str    Text of the frame.
 
 */
 
static inline void DrawFrame(const Rect &r, Colours colour, StringID str)
 
{
 
	int x2 = r.left; // by default the left side is the left side of the widget
 

	
 
	if (str != STR_NULL) x2 = DrawString(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, r.top, str);
 

	
 
	int c1 = _colour_gradient[colour][3];
 
	int c2 = _colour_gradient[colour][7];
 

	
 
	/* If the frame has text, adjust the top bar to fit half-way through */
 
	int dy1 = 4;
 
	if (str != STR_NULL) dy1 = FONT_HEIGHT_NORMAL / 2 - 1;
 
	int dy2 = dy1 + 1;
 

	
 
	if (_dynlang.text_dir == TD_LTR) {
 
		/* Line from upper left corner to start of text */
 
		GfxFillRect(r.left, r.top + dy1, r.left + 4, r.top + dy1, c1);
 
		GfxFillRect(r.left + 1, r.top + dy2, r.left + 4, r.top + dy2, c2);
 

	
 
		/* Line from end of text to upper right corner */
 
		GfxFillRect(x2, r.top + dy1, r.right - 1, r.top + dy1, c1);
 
		GfxFillRect(x2, r.top + dy2, r.right - 2, r.top + dy2, c2);
 
	} else {
 
		/* Line from upper left corner to start of text */
 
		GfxFillRect(r.left, r.top + dy1, x2 - 2, r.top + dy1, c1);
 
		GfxFillRect(r.left + 1, r.top + dy2, x2 - 2, r.top + dy2, c2);
 

	
 
		/* Line from end of text to upper right corner */
 
		GfxFillRect(r.right - 5, r.top + dy1, r.right - 1, r.top + dy1, c1);
 
		GfxFillRect(r.right - 5, r.top + dy2, r.right - 2, r.top + dy2, c2);
 
	}
 

	
 
	/* Line from upper left corner to bottom left corner */
 
	GfxFillRect(r.left, r.top + dy2, r.left, r.bottom - 1, c1);
 
	GfxFillRect(r.left + 1, r.top + dy2 + 1, r.left + 1, r.bottom - 2, c2);
 

	
 
	/* Line from upper right corner to bottom right corner */
 
	GfxFillRect(r.right - 1, r.top + dy2, r.right - 1, r.bottom - 2, c1);
 
	GfxFillRect(r.right, r.top + dy1, r.right, r.bottom - 1, c2);
 

	
 
	GfxFillRect(r.left + 1, r.bottom - 1, r.right - 1, r.bottom - 1, c1);
 
	GfxFillRect(r.left, r.bottom, r.right, r.bottom, c2);
 
}
 

	
 
/**
 
 * Draw a sticky box.
 
 * @param r       Rectangle of the box.
 
 * @param colour  Colour of the sticky box.
 
 * @param clicked Box is lowered.
 
 */
 
static inline void DrawStickyBox(const Rect &r, Colours colour, bool clicked)
 
{
 
	DrawFrameRect(r.left, r.top, r.right, r.bottom, colour, (clicked) ? FR_LOWERED : FR_NONE);
 
	DrawSprite((clicked) ? SPR_PIN_UP : SPR_PIN_DOWN, PAL_NONE, r.left + WD_STICKYBOX_LEFT + clicked, r.top + WD_STICKYBOX_TOP + clicked);
 
}
 

	
 
/**
 
 * Draw a resize box.
 
 * @param r       Rectangle of the box.
 
 * @param colour  Colour of the resize box.
 
 * @param at_left Resize box is at left-side of the window,
 
 * @param clicked Box is lowered.
 
 */
 
static inline void DrawResizeBox(const Rect &r, Colours colour, bool at_left, bool clicked)
 
{
 
	DrawFrameRect(r.left, r.top, r.right, r.bottom, colour, (clicked) ? FR_LOWERED : FR_NONE);
 
	if (at_left) {
 
		DrawSprite(SPR_WINDOW_RESIZE_LEFT, PAL_NONE, r.left + WD_RESIZEBOX_RIGHT + clicked, r.top + WD_RESIZEBOX_TOP + clicked);
 
	} else {
 
		DrawSprite(SPR_WINDOW_RESIZE_RIGHT, PAL_NONE, r.left + WD_RESIZEBOX_LEFT + clicked, r.top + WD_RESIZEBOX_TOP + clicked);
 
	}
 
}
 

	
 
/**
 
 * Draw a close box.
 
 * @param r      Rectangle of the box.
 
 * @param colour Colour of the close box.
 
 * @param str    Cross to draw (#STR_BLACK_CROSS or #STR_SILVER_CROSS).
 
 */
 
static inline void DrawCloseBox(const Rect &r, Colours colour, StringID str)
 
{
 
	assert(str == STR_BLACK_CROSS || str == STR_SILVER_CROSS); // black or silver cross
 
	DrawFrameRect(r.left, r.top, r.right, r.bottom, colour, FR_NONE);
 
	DrawString(r.left + WD_CLOSEBOX_LEFT, r.right - WD_CLOSEBOX_RIGHT, r.top + WD_CLOSEBOX_TOP, str, TC_FROMSTRING, SA_CENTER);
 
}
 

	
 
/**
 
 * Draw a caption bar.
 
 * @param r      Rectangle of the bar.
 
 * @param colour Colour of the window.
 
 * @param owner  'Owner' of the window.
 
 * @param str    Text to draw in the bar.
 
 */
 
static inline void DrawCaption(const Rect &r, Colours colour, Owner owner, StringID str)
 
{
 
	DrawFrameRect(r.left, r.top, r.right, r.bottom, colour, FR_BORDERONLY);
 
	DrawFrameRect(r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, colour, (owner == INVALID_OWNER) ? FR_LOWERED | FR_DARKENED : FR_LOWERED | FR_DARKENED | FR_BORDERONLY);
 

	
 
	if (owner != INVALID_OWNER) {
 
		GfxFillRect(r.left + 2, r.top + 2, r.right - 2, r.bottom - 2, _colour_gradient[_company_colours[owner]][4]);
 
	}
 

	
 
	if (str != STR_NULL) DrawString(r.left + WD_CAPTIONTEXT_LEFT, r.right - WD_CAPTIONTEXT_RIGHT, r.top + WD_CAPTIONTEXT_TOP, str, TC_FROMSTRING, SA_CENTER);
 
}
 

	
 
/**
 
 * Draw a button with a dropdown (#WWT_DROPDOWN and #NWID_BUTTON_DROPDOWN).
 
 * @param r                Rectangle containing the widget.
 
 * @param colour           Background colour of the widget.
 
 * @param clicked_button   The button-part is lowered.
 
 * @param clicked_dropdown The drop-down part is lowered.
 
 * @param str              Text of the button.
 
 *
 
 * @note Magic constants are also used in #NWidgetLeaf::ButtonHit.
 
 */
 
static inline void DrawButtonDropdown(const Rect &r, Colours colour, bool clicked_button, bool clicked_dropdown, StringID str)
 
{
 
	if (_dynlang.text_dir == TD_LTR) {
 
		DrawFrameRect(r.left, r.top, r.right - 12, r.bottom, colour, clicked_button ? FR_LOWERED : FR_NONE);
 
		DrawFrameRect(r.right - 11, r.top, r.right, r.bottom, colour, clicked_dropdown ? FR_LOWERED : FR_NONE);
 
		DrawString(r.right - (clicked_dropdown ? 10 : 11), r.right, r.top + (clicked_dropdown ? 2 : 1), DOWNARROW, TC_BLACK, SA_CENTER);
 
		if (str != STR_NULL) DrawString(r.left + WD_DROPDOWNTEXT_LEFT + clicked_button, r.right - WD_DROPDOWNTEXT_RIGHT + clicked_button, r.top + WD_DROPDOWNTEXT_TOP + clicked_button, str, TC_BLACK);
 
	} else {
 
		DrawFrameRect(r.left + 12, r.top, r.right, r.bottom, colour, clicked_button ? FR_LOWERED : FR_NONE);
 
		DrawFrameRect(r.left, r.top, r.left + 11, r.bottom, colour, clicked_dropdown ? FR_LOWERED : FR_NONE);
 
		DrawString(r.left + clicked_dropdown, r.left + 11, r.top + (clicked_dropdown ? 2 : 1), DOWNARROW, TC_BLACK, SA_CENTER);
 
		if (str != STR_NULL) DrawString(r.left + WD_DROPDOWNTEXT_RIGHT + clicked_button, r.right - WD_DROPDOWNTEXT_LEFT + clicked_button, r.top + WD_DROPDOWNTEXT_TOP + clicked_button, str, TC_BLACK);
 
	}
 
}
 

	
 
/**
 
 * Draw a dropdown #WWT_DROPDOWN widget.
 
 * @param r       Rectangle containing the widget.
 
 * @param colour  Background colour of the widget.
 
 * @param clicked The widget is lowered.
 
 * @param str     Text of the button.
 
 */
 
static inline void DrawDropdown(const Rect &r, Colours colour, bool clicked, StringID str)
 
{
 
	DrawButtonDropdown(r, colour, false, clicked, str);
 
}
 

	
 
/**
 
 * Paint all widgets of a window.
 
 */
 
void Window::DrawWidgets() const
 
{
 
	this->nested_root->Draw(this);
 

	
 
	if (this->flags4 & WF_WHITE_BORDER_MASK) {
 
		DrawFrameRect(0, 0, this->width - 1, this->height - 1, COLOUR_WHITE, FR_BORDERONLY);
 
	}
 
}
 

	
 
/**
 
 * Draw a sort button's up or down arrow symbol.
 
 * @param widget Sort button widget
 
 * @param state State of sort button
 
 */
 
void Window::DrawSortButtonState(int widget, SortButtonState state) const
 
{
 
	if (state == SBS_OFF) return;
 

	
 
	assert(this->nested_array != NULL);
 
	const NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget);
 

	
 
	int offset = this->IsWidgetLowered(widget) ? 1 : 0;
 
	int base = offset + nwid->pos_x + (_dynlang.text_dir == TD_LTR ? nwid->current_x - WD_SORTBUTTON_ARROW_WIDTH : 0);
 
	int top = nwid->pos_y;
 

	
 
	DrawString(base, base + WD_SORTBUTTON_ARROW_WIDTH, top + 1 + offset, state == SBS_DOWN ? DOWNARROW : UPARROW, TC_BLACK, SA_CENTER);
 
}
 

	
 

	
 
/**
 
 * @defgroup NestedWidgets Hierarchical widgets
 
 * Hierarchical widgets, also known as nested widgets, are widgets stored in a tree. At the leafs of the tree are (mostly) the 'real' widgets
 
 * visible to the user. At higher levels, widgets get organized in container widgets, until all widgets of the window are merged.
 
 *
 
 * \section nestedwidgetkinds Hierarchical widget kinds
 
 * A leaf widget is one of
 
 * <ul>
 
 * <li> #NWidgetLeaf for widgets visible for the user, or
 
 * <li> #NWidgetSpacer for creating (flexible) empty space between widgets.
 
 * </ul>
 
 * The purpose of a leaf widget is to provide interaction with the user by displaying settings, and/or allowing changing the settings.
 
 *
 
 * A container widget is one of
 
 * <ul>
 
 * <li> #NWidgetHorizontal for organizing child widgets in a (horizontal) row. The row switches order depending on the language setting (thus supporting
 
 *      right-to-left languages),
 
 * <li> #NWidgetHorizontalLTR for organizing child widgets in a (horizontal) row, always in the same order. All childs below this container will also
 
 *      never swap order.
 
 * <li> #NWidgetVertical for organizing child widgets underneath each other.
 
 * <li> #NWidgetBackground for adding a background behind its child widget.
 
 * <li> #NWidgetStacked for stacking child widgets on top of each other.
 
 * </ul>
 
 * The purpose of a container widget is to structure its leafs and sub-containers to allow proper resizing.
 
 *
 
 * \section nestedwidgetscomputations Hierarchical widget computations
 
 * The first 'computation' is the creation of the nested widgets tree by calling the constructors of the widgets listed above and calling \c Add() for every child,
 
 * or by means of specifying the tree as a collection of nested widgets parts and instantiating the tree from the array.
 
 *
 
 * After the creation step,
 
 * - The leafs have their own minimal size (\e min_x, \e min_y), filling (\e fill_x, \e fill_y), and resize steps (\e resize_x, \e resize_y).
 
 * - Containers only know what their children are, \e fill_x, \e fill_y, \e resize_x, and \e resize_y are not initialized.
 
 *
 
 * Computations in the nested widgets take place as follows:
 
 * <ol>
 
 * <li> A bottom-up sweep by recursively calling NWidgetBase::SetupSmallestSize() to initialize the smallest size (\e smallest_x, \e smallest_y) and
 
 *      to propagate filling and resize steps upwards to the root of the tree.
 
 * <li> A top-down sweep by recursively calling NWidgetBase::AssignSizePosition() with #ST_SMALLEST to make the smallest sizes consistent over
 
 *      the entire tree, and to assign the top-left (\e pos_x, \e pos_y) position of each widget in the tree. This step uses \e fill_x and \e fill_y at each
 
 *      node in the tree to decide how to fill each widget towards consistent sizes. Also the current size (\e current_x and \e current_y) is set.
 
 * <li> After initializing the smallest size in the widget tree with #ST_SMALLEST, the tree can be resized (the current size modified) by calling
 
 *      NWidgetBase::AssignSizePosition() at the root with #ST_RESIZE and the new size of the window. For proper functioning, the new size should be the smallest
 
 *      size + a whole number of resize steps in both directions (ie you can only resize in steps of length resize_{x,y} from smallest_{x,y}).
 
 * </ol>
 
 * After the second step, the current size of the widgets are set to the smallest size.
 
 *
 
 * To resize, perform the last step with the new window size. This can be done as often as desired.
 
 * When the smallest size of at least one widget changes, the whole procedure has to be redone from the start.
 
 *
 
 * @see NestedWidgetParts
 
 */
 

	
 
/**
 
 * Base class constructor.
 
 * @param tp Nested widget type.
 
 */
 
NWidgetBase::NWidgetBase(WidgetType tp) : ZeroedMemoryAllocator()
 
{
 
	this->type = tp;
 
}
 

	
 
/* ~NWidgetContainer() takes care of #next and #prev data members. */
 

	
 
/**
 
 * @fn void NWidgetBase::SetupSmallestSize(Window *w, bool init_array)
 
 * Compute smallest size needed by the widget.
 
 *
 
 * The smallest size of a widget is the smallest size that a widget needs to
 
 * display itself properly. In addition, filling and resizing of the widget are computed.
 
 * If \a w is not \c NULL, the function calls #Window::UpdateWidgetSize for each leaf widget and
 
 * background widget without child with a non-negative index.
 
 *
 
 * @param w Optional window owning the widget.
 
 * @param init_array Initialize the \c w->nested_array as well. Should only be set if \a w != NULL.
 
 *
 
 * @note After the computation, the results can be queried by accessing the #smallest_x and #smallest_y data members of the widget.
 
 */
 

	
 
/**
 
 * @fn void NWidgetBase::AssignSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height, bool rtl)
 
 * Assign size and position to the widget.
 
 * @param sizing       Type of resizing to perform.
 
 * @param x            Horizontal offset of the widget relative to the left edge of the window.
 
 * @param y            Vertical offset of the widget relative to the top edge of the window.
 
 * @param given_width  Width allocated to the widget.
 
 * @param given_height Height allocated to the widget.
 
 * @param rtl          Adapt for right-to-left languages (position contents of horizontal containers backwards).
 
 *
 
 * Afterwards, \e pos_x and \e pos_y contain the top-left position of the widget, \e smallest_x and \e smallest_y contain
 
 * the smallest size such that all widgets of the window are consistent, and \e current_x and \e current_y contain the current size.
 
 */
 

	
 
/**
 
 * @fn void FillNestedArray(NWidgetBase **array, uint length)
 
 * Fill the Window::nested_array array with pointers to nested widgets in the tree.
 
 * @param array Base pointer of the array.
 
 * @param length Length of the array.
 
 */
 

	
 
/**
 
 * Store size and position.
 
 * @param sizing       Type of resizing to perform.
 
 * @param x            Horizontal offset of the widget relative to the left edge of the window.
 
 * @param y            Vertical offset of the widget relative to the top edge of the window.
 
 * @param given_width  Width allocated to the widget.
 
 * @param given_height Height allocated to the widget.
 
 */
 
inline void NWidgetBase::StoreSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height)
 
{
 
	this->pos_x = x;
 
	this->pos_y = y;
 
	if (sizing == ST_SMALLEST) {
 
		this->smallest_x = given_width;
 
		this->smallest_y = given_height;
 
	}
 
	this->current_x = given_width;
 
	this->current_y = given_height;
 
}
 

	
 
/**
 
 * @fn void Draw(const Window *w)
 
 * Draw the widgets of the tree.
 
 * The function calls #Window::DrawWidget for each widget with a non-negative index, after the widget itself is painted.
 
 * @param w Window that owns the tree.
 
 */
 

	
 
/**
 
 * Mark the widget as 'dirty' (in need of repaint).
 
 * @param w Window owning the widget.
 
 */
 
void NWidgetBase::SetDirty(const Window *w) const
 
{
 
	int abs_left = w->left + this->pos_x;
 
	int abs_top = w->top + this->pos_y;
 
	SetDirtyBlocks(abs_left, abs_top, abs_left + this->current_x, abs_top + this->current_y);
 
}
 

	
 
/**
 
 * @fn NWidgetCore *NWidgetBase::GetWidgetFromPos(int x, int y)
 
 * Retrieve a widget by its position.
 
 * @param x Horizontal position relative to the left edge of the window.
 
 * @param y Vertical position relative to the top edge of the window.
 
 * @return Returns the deepest nested widget that covers the given position, or \c NULL if no widget can be found.
 
 */
 

	
 
/**
 
 * Retrieve a widget by its type.
 
 * @param tp Widget type to search for.
 
 * @return Returns the first widget of the specified type, or \c NULL if no widget can be found.
 
 */
 
NWidgetBase *NWidgetBase::GetWidgetOfType(WidgetType tp)
 
{
 
	return (this->type == tp) ? this : NULL;
 
}
 

	
 
/**
 
 * Constructor for resizable nested widgets.
 
 * @param tp     Nested widget type.
 
 * @param fill_x Allow horizontal filling from initial size.
 
 * @param fill_y Allow vertical filling from initial size.
 
 * @param fill_x Horizontal fill step size, \c 0 means no filling is allowed.
 
 * @param fill_y Vertical fill step size, \c 0 means no filling is allowed.
 
 */
 
NWidgetResizeBase::NWidgetResizeBase(WidgetType tp, bool fill_x, bool fill_y) : NWidgetBase(tp)
 
NWidgetResizeBase::NWidgetResizeBase(WidgetType tp, uint fill_x, uint fill_y) : NWidgetBase(tp)
 
{
 
	this->fill_x = fill_x;
 
	this->fill_y = fill_y;
 
}
 

	
 
/**
 
 * Set minimal size of the widget.
 
 * @param min_x Horizontal minimal size of the widget.
 
 * @param min_y Vertical minimal size of the widget.
 
 */
 
void NWidgetResizeBase::SetMinimalSize(uint min_x, uint min_y)
 
{
 
	this->min_x = min_x;
 
	this->min_y = min_y;
 
}
 

	
 
/**
 
 * Set minimal text lines for the widget.
 
 * @param min_lines Number of text lines of the widget.
 
 * @param spacing   Extra spacing (eg WD_FRAMERECT_TOP + _BOTTOM) of the widget.
 
 * @param size      Font size of text.
 
 */
 
void NWidgetResizeBase::SetMinimalTextLines(uint8 min_lines, uint8 spacing, FontSize size)
 
{
 
	this->min_y = min_lines * GetCharacterHeight(size) + spacing;
 
}
 

	
 
/**
 
 * Set the filling of the widget from initial size.
 
 * @param fill_x Allow horizontal filling from initial size.
 
 * @param fill_y Allow vertical filling from initial size.
 
 * @param fill_x Horizontal fill step size, \c 0 means no filling is allowed.
 
 * @param fill_y Vertical fill step size, \c 0 means no filling is allowed.
 
 */
 
void NWidgetResizeBase::SetFill(bool fill_x, bool fill_y)
 
void NWidgetResizeBase::SetFill(uint fill_x, uint fill_y)
 
{
 
	this->fill_x = fill_x;
 
	this->fill_y = fill_y;
 
}
 

	
 
/**
 
 * Set resize step of the widget.
 
 * @param resize_x Resize step in horizontal direction, value \c 0 means no resize, otherwise the step size in pixels.
 
 * @param resize_y Resize step in vertical direction, value \c 0 means no resize, otherwise the step size in pixels.
 
 */
 
void NWidgetResizeBase::SetResize(uint resize_x, uint resize_y)
 
{
 
	this->resize_x = resize_x;
 
	this->resize_y = resize_y;
 
}
 

	
 
void NWidgetResizeBase::AssignSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height, bool rtl)
 
{
 
	StoreSizePosition(sizing, x, y, given_width, given_height);
 
}
 

	
 
/**
 
 * Initialization of a 'real' widget.
 
 * @param tp          Type of the widget.
 
 * @param colour      Colour of the widget.
 
 * @param fill_x      Default horizontal filling.
 
 * @param fill_y      Default vertical filling.
 
 * @param widget_data Data component of the widget. @see Widget::data
 
 * @param tool_tip    Tool tip of the widget. @see Widget::tootips
 
 */
 
NWidgetCore::NWidgetCore(WidgetType tp, Colours colour, bool fill_x, bool fill_y, uint16 widget_data, StringID tool_tip) : NWidgetResizeBase(tp, fill_x, fill_y)
 
NWidgetCore::NWidgetCore(WidgetType tp, Colours colour, uint fill_x, uint fill_y, uint16 widget_data, StringID tool_tip) : NWidgetResizeBase(tp, fill_x, fill_y)
 
{
 
	this->colour = colour;
 
	this->index = -1;
 
	this->widget_data = widget_data;
 
	this->tool_tip = tool_tip;
 
}
 

	
 
/**
 
 * Set index of the nested widget in the widget array.
 
 * @param index Index to use.
 
 */
 
void NWidgetCore::SetIndex(int index)
 
{
 
	assert(index >= 0);
 
	this->index = index;
 
}
 

	
 
/**
 
 * Set data and tool tip of the nested widget.
 
 * @param widget_data Data to use.
 
 * @param tool_tip    Tool tip string to use.
 
 */
 
void NWidgetCore::SetDataTip(uint16 widget_data, StringID tool_tip)
 
{
 
	this->widget_data = widget_data;
 
	this->tool_tip = tool_tip;
 
}
 

	
 
void NWidgetCore::FillNestedArray(NWidgetBase **array, uint length)
 
{
 
	if (this->index >= 0 && (uint)(this->index) < length) array[this->index] = this;
 
}
 

	
 
NWidgetCore *NWidgetCore::GetWidgetFromPos(int x, int y)
 
{
 
	return (IsInsideBS(x, this->pos_x, this->current_x) && IsInsideBS(y, this->pos_y, this->current_y)) ? this : NULL;
 
}
 

	
 
/**
 
 * @fn Scrollbar *NWidgetCore::FindScrollbar(Window *w, bool allow_next = true)
 
 * Find the scrollbar of the widget through the Window::nested_array.
 
 * @param w          Window containing the widgets and the scrollbar,
 
 * @param allow_next Search may be extended to the next widget.
 
 *
 
 * @todo This implementation uses the constraint that a scrollbar must be the next item in the #Window::nested_array, and the scrollbar
 
 *       data is stored in the #Window structure (#Window::vscroll, #Window::vscroll2, and #Window::hscroll).
 
 *       Alternative light-weight implementations may be considered, eg by sub-classing a canvas-like widget, and/or by having
 
 *       an explicit link between the scrollbar and the widget being scrolled.
 
 */
 

	
 
/**
 
 * Constructor container baseclass.
 
 * @param tp Type of the container.
 
 */
 
NWidgetContainer::NWidgetContainer(WidgetType tp) : NWidgetBase(tp)
 
{
 
	this->head = NULL;
 
	this->tail = NULL;
 
}
 

	
 
NWidgetContainer::~NWidgetContainer()
 
{
 
	while (this->head != NULL) {
 
		NWidgetBase *wid = this->head->next;
 
		delete this->head;
 
		this->head = wid;
 
	}
 
	this->tail = NULL;
 
}
 

	
 
NWidgetBase *NWidgetContainer::GetWidgetOfType(WidgetType tp)
 
{
 
	if (this->type == tp) return this;
 
	for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
 
		NWidgetBase *nwid = child_wid->GetWidgetOfType(tp);
 
		if (nwid != NULL) return nwid;
 
	}
 
	return NULL;
 
}
 

	
 
/**
 
 * Append widget \a wid to container.
 
 * @param wid Widget to append.
 
 */
 
void NWidgetContainer::Add(NWidgetBase *wid)
 
{
 
	assert(wid->next == NULL && wid->prev == NULL);
 

	
 
	if (this->head == NULL) {
 
		this->head = wid;
 
		this->tail = wid;
 
	} else {
 
		assert(this->tail != NULL);
 
		assert(this->tail->next == NULL);
 

	
 
		this->tail->next = wid;
 
		wid->prev = this->tail;
 
		this->tail = wid;
 
	}
 
}
 

	
 
void NWidgetContainer::FillNestedArray(NWidgetBase **array, uint length)
 
{
 
	for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
 
		child_wid->FillNestedArray(array, length);
 
	}
 
}
 

	
 
/**
 
 * Return the biggest possible size of a nested widget.
 
 * @param base      Base size of the widget.
 
 * @param max_space Available space for the widget.
 
 * @param step      Stepsize of the widget.
 
 * @return Biggest possible size of the widget, assuming that \a base may only be incremented by \a step size steps.
 
 */
 
static inline uint ComputeMaxSize(uint base, uint max_space, uint step)
 
{
 
	if (base >= max_space || step == 0) return base;
 
	if (step == 1) return max_space;
 
	int increment = max_space - base;
 
	increment -= increment % step;
 
	return base + increment;
 
}
 

	
 
/**
 
 * Widgets stacked on top of each other.
 
 */
 
NWidgetStacked::NWidgetStacked() : NWidgetContainer(NWID_SELECTION)
 
{
 
	this->index = -1;
 
}
 

	
 
void NWidgetStacked::SetIndex(int index)
 
{
 
	this->index = index;
 
}
 

	
 
void NWidgetStacked::SetupSmallestSize(Window *w, bool init_array)
 
{
 
	if (this->index >= 0 && init_array) { // Fill w->nested_array[]
 
		assert(w->nested_array_size > (uint)this->index);
 
		w->nested_array[this->index] = this;
 
	}
 

	
 
	/* Zero size plane selected */
 
	if (this->shown_plane == STACKED_SELECTION_ZERO_SIZE) {
 
		this->fill_x = false;
 
		this->fill_y = false;
 
		this->fill_x = 0;
 
		this->fill_y = 0;
 

	
 
		Dimension size = {0, 0};
 
		Dimension resize = {0, 0};
 
		Dimension padding = {0, 0};
 
		/* Here we're primarily interested in the value of resize */
 
		w->UpdateWidgetSize(this->index, &size, padding, &resize);
 

	
 
		this->smallest_x = size.width;
 
		this->smallest_y = size.height;
 
		this->resize_x = resize.width;
 
		this->resize_y = resize.height;
 
		return;
 
	}
 

	
 
	/* First sweep, recurse down and compute minimal size and filling. */
 
	this->smallest_x = 0;
 
	this->smallest_y = 0;
 
	this->fill_x = (this->head != NULL);
 
	this->fill_y = (this->head != NULL);
 
	this->fill_x = (this->head != NULL) ? 1 : 0;
 
	this->fill_y = (this->head != NULL) ? 1 : 0;
 
	this->resize_x = (this->head != NULL) ? 1 : 0;
 
	this->resize_y = (this->head != NULL) ? 1 : 0;
 
	for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
 
		child_wid->SetupSmallestSize(w, init_array);
 

	
 
		this->smallest_x = max(this->smallest_x, child_wid->smallest_x + child_wid->padding_left + child_wid->padding_right);
 
		this->smallest_y = max(this->smallest_y, child_wid->smallest_y + child_wid->padding_top + child_wid->padding_bottom);
 
		this->fill_x &= child_wid->fill_x;
 
		this->fill_y &= child_wid->fill_y;
 
		this->fill_x = LeastCommonMultiple(this->fill_x, child_wid->fill_x);
 
		this->fill_y = LeastCommonMultiple(this->fill_y, child_wid->fill_y);
 
		this->resize_x = LeastCommonMultiple(this->resize_x, child_wid->resize_x);
 
		this->resize_y = LeastCommonMultiple(this->resize_y, child_wid->resize_y);
 
	}
 
}
 

	
 
void NWidgetStacked::AssignSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height, bool rtl)
 
{
 
	assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
 
	StoreSizePosition(sizing, x, y, given_width, given_height);
 

	
 
	if (this->shown_plane == STACKED_SELECTION_ZERO_SIZE) return;
 

	
 
	for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
 
		uint hor_step = (sizing == ST_SMALLEST) ? 1 : child_wid->GetHorizontalStepSize(sizing);
 
		uint child_width = ComputeMaxSize(child_wid->smallest_x, given_width - child_wid->padding_left - child_wid->padding_right, hor_step);
 
		uint child_pos_x = (rtl ? child_wid->padding_right : child_wid->padding_left);
 

	
 
		uint vert_step = (sizing == ST_SMALLEST) ? 1 : child_wid->GetVerticalStepSize(sizing);
 
		uint child_height = ComputeMaxSize(child_wid->smallest_y, given_height - child_wid->padding_top - child_wid->padding_bottom, vert_step);
 
		uint child_pos_y = child_wid->padding_top;
 

	
 
		child_wid->AssignSizePosition(sizing, x + child_pos_x, y + child_pos_y, child_width, child_height, rtl);
 
	}
 
}
 

	
 
void NWidgetStacked::FillNestedArray(NWidgetBase **array, uint length)
 
{
 
	if (this->index >= 0 && (uint)(this->index) < length) array[this->index] = this;
 
	NWidgetContainer::FillNestedArray(array, length);
 
}
 

	
 
void NWidgetStacked::Draw(const Window *w)
 
{
 
	if (this->shown_plane == STACKED_SELECTION_ZERO_SIZE) return;
 

	
 
	int plane = 0;
 
	for (NWidgetBase *child_wid = this->head; child_wid != NULL; plane++, child_wid = child_wid->next) {
 
		if (plane == this->shown_plane) {
 
			child_wid->Draw(w);
 
			return;
 
		}
 
	}
 

	
 
	NOT_REACHED();
 
}
 

	
 
NWidgetCore *NWidgetStacked::GetWidgetFromPos(int x, int y)
 
{
 
	if (this->shown_plane == STACKED_SELECTION_ZERO_SIZE) return NULL;
 

	
 
	if (!IsInsideBS(x, this->pos_x, this->current_x) || !IsInsideBS(y, this->pos_y, this->current_y)) return NULL;
 
	int plane = 0;
 
	for (NWidgetBase *child_wid = this->head; child_wid != NULL; plane++, child_wid = child_wid->next) {
 
		if (plane == this->shown_plane) {
 
			return child_wid->GetWidgetFromPos(x, y);
 
		}
 
	}
 
	return NULL;
 
}
 

	
 
/** Select which plane to show (for #NWID_SELECTION only).
 
 * @param plane Plane number to display.
 
 */
 
void NWidgetStacked::SetDisplayedPlane(int plane)
 
{
 
	this->shown_plane = plane;
 
}
 

	
 
NWidgetPIPContainer::NWidgetPIPContainer(WidgetType tp, NWidContainerFlags flags) : NWidgetContainer(tp)
 
{
 
	this->flags = flags;
 
}
 

	
 
/**
 
 * Set additional pre/inter/post space for the container.
 
 *
 
 * @param pip_pre   Additional space in front of the first child widget (above
 
 *                  for the vertical container, at the left for the horizontal container).
 
 * @param pip_inter Additional space between two child widgets.
 
 * @param pip_post  Additional space after the last child widget (below for the
 
 *                  vertical container, at the right for the horizontal container).
 
 */
 
void NWidgetPIPContainer::SetPIP(uint8 pip_pre, uint8 pip_inter, uint8 pip_post)
 
{
 
	this->pip_pre = pip_pre;
 
	this->pip_inter = pip_inter;
 
	this->pip_post = pip_post;
 
}
 

	
 
void NWidgetPIPContainer::Draw(const Window *w)
 
{
 
	for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
 
		child_wid->Draw(w);
 
	}
 
}
 

	
 
NWidgetCore *NWidgetPIPContainer::GetWidgetFromPos(int x, int y)
 
{
 
	if (!IsInsideBS(x, this->pos_x, this->current_x) || !IsInsideBS(y, this->pos_y, this->current_y)) return NULL;
 

	
 
	for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
 
		NWidgetCore *nwid = child_wid->GetWidgetFromPos(x, y);
 
		if (nwid != NULL) return nwid;
 
	}
 
	return NULL;
 
}
 

	
 
/** Horizontal container widget. */
 
NWidgetHorizontal::NWidgetHorizontal(NWidContainerFlags flags) : NWidgetPIPContainer(NWID_HORIZONTAL, flags)
 
{
 
}
 

	
 
void NWidgetHorizontal::SetupSmallestSize(Window *w, bool init_array)
 
{
 
	this->smallest_x = 0;   // Sum of minimal size of all childs.
 
	this->smallest_y = 0;   // Biggest child.
 
	this->fill_x = false;   // true if at least one child allows fill_x.
 
	this->fill_y = true;    // true if all childs allow fill_y.
 
	this->fill_x = 0;     // smallest non-zero child widget fill step.
 
	this->fill_y = 1;     // smallest common child fill step.
 
	this->resize_x = 0;     // smallest non-zero child widget resize step.
 
	this->resize_y = 1;     // smallest common child resize step
 
	this->resize_y = 1;   // smallest common child resize step.
 

	
 
	/* 1. Forward call, collect biggest nested array index, and longest child length. */
 
	uint longest = 0; // Longest child found.
 
	for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
 
		child_wid->SetupSmallestSize(w, init_array);
 
		longest = max(longest, child_wid->smallest_x);
 
	}
 
	/* 2. For containers that must maintain equal width, extend child minimal size. */
 
	if (this->flags & NC_EQUALSIZE) {
 
		for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
 
			if (child_wid->fill_x) child_wid->smallest_x = longest;
 
			if (child_wid->fill_x == 1) child_wid->smallest_x = longest;
 
		}
 
	}
 
	/* 3. Move PIP space to the childs, compute smallest, fill, and resize values of the container. */
 
	if (this->head != NULL) this->head->padding_left += this->pip_pre;
 
	for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
 
		if (child_wid->next != NULL) {
 
			child_wid->padding_right += this->pip_inter;
 
		} else {
 
			child_wid->padding_right += this->pip_post;
 
		}
 

	
 
		this->smallest_x += child_wid->smallest_x + child_wid->padding_left + child_wid->padding_right;
 
		this->smallest_y = max(this->smallest_y, child_wid->smallest_y + child_wid->padding_top + child_wid->padding_bottom);
 
		this->fill_x |= child_wid->fill_x;
 
		this->fill_y &= child_wid->fill_y;
 
		if (child_wid->fill_x > 0) {
 
			if (this->fill_x == 0 || this->fill_x > child_wid->fill_x) this->fill_x = child_wid->fill_x;
 
		}
 
		this->fill_y = LeastCommonMultiple(this->fill_y, child_wid->fill_y);
 

	
 
		if (child_wid->resize_x > 0) {
 
			if (this->resize_x == 0 || this->resize_x > child_wid->resize_x) this->resize_x = child_wid->resize_x;
 
		}
 
		this->resize_y = LeastCommonMultiple(this->resize_y, child_wid->resize_y);
 
	}
 
	/* We need to zero the PIP settings so we can re-initialize the tree. */
 
	this->pip_pre = this->pip_inter = this->pip_post = 0;
 
}
 

	
 
void NWidgetHorizontal::AssignSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height, bool rtl)
 
{
 
	assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
 

	
 
	uint additional_length = given_width - this->smallest_x; // Additional width given to us.
 
	StoreSizePosition(sizing, x, y, given_width, given_height);
 

	
 
	/* In principle, the additional horizontal space is distributed evenly over the available resizable childs. Due to step sizes, this may not always be feasible.
 
	 * To make resizing work as good as possible, first childs with biggest step sizes are done. These may get less due to rounding down.
 
	 * This additional space is then given to childs with smaller step sizes. This will give a good result when resize steps of each child is a multiple
 
	 * of the child with the smallest non-zero stepsize.
 
	 *
 
	 * Since child sizes are computed out of order, positions cannot be calculated until all sizes are known. That means it is not possible to compute the child
 
	 * size and position, and directly call child->AssignSizePosition() with the computed values.
 
	 * Instead, computed child widths and heights are stored in child->current_x and child->current_y values. That is allowed, since this method overwrites those values
 
	 * then we call the child.
 
	 */
 

	
 
	/* First loop: Find biggest stepsize, find number of childs that want a piece of the pie, handle vertical size for all childs, handle horizontal size for non-resizing childs. */
 
	int num_changing_childs = 0; // Number of childs that can change size.
 
	uint biggest_stepsize = 0;
 
	for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
 
		uint hor_step = child_wid->GetHorizontalStepSize(sizing);
 
		if (hor_step > 0) {
 
			num_changing_childs++;
 
			biggest_stepsize = max(biggest_stepsize, hor_step);
 
		} else {
 
			child_wid->current_x = child_wid->smallest_x;
 
		}
 

	
 
		uint vert_step = (sizing == ST_SMALLEST) ? 1 : child_wid->GetVerticalStepSize(sizing);
 
		child_wid->current_y = ComputeMaxSize(child_wid->smallest_y, given_height - child_wid->padding_top - child_wid->padding_bottom, vert_step);
 
	}
 

	
 
	/* Second loop: Allocate the additional horizontal space over the resizing childs, starting with the biggest resize steps. */
 
	while (biggest_stepsize > 0) {
 
		uint next_biggest_stepsize = 0;
 
		for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
 
			uint hor_step = child_wid->GetHorizontalStepSize(sizing);
 
			if (hor_step > biggest_stepsize) continue; // Already done
 
			if (hor_step == biggest_stepsize) {
 
				uint increment = additional_length / num_changing_childs;
 
				num_changing_childs--;
 
				if (hor_step > 1) increment -= increment % hor_step;
 
				child_wid->current_x = child_wid->smallest_x + increment;
 
				additional_length -= increment;
 
				continue;
 
			}
 
			next_biggest_stepsize = max(next_biggest_stepsize, hor_step);
 
		}
 
		biggest_stepsize = next_biggest_stepsize;
 
	}
 
	assert(num_changing_childs == 0);
 

	
 
	/* Third loop: Compute position and call the child. */
 
	uint position = 0; // Place to put next child relative to origin of the container.
 
	NWidgetBase *child_wid = rtl ? this->tail : this->head;
 
	while (child_wid != NULL) {
 
		uint child_width = child_wid->current_x;
 
		uint child_x = x + position + (rtl ? child_wid->padding_right : child_wid->padding_left);
 
		uint child_y = y + child_wid->padding_top;
 

	
 
		child_wid->AssignSizePosition(sizing, child_x, child_y, child_width, child_wid->current_y, rtl);
 
		position += child_width + child_wid->padding_right + child_wid->padding_left;
 

	
 
		child_wid = rtl ? child_wid->prev : child_wid->next;
 
	}
 
}
 

	
 
/** Horizontal left-to-right container widget. */
 
NWidgetHorizontalLTR::NWidgetHorizontalLTR(NWidContainerFlags flags) : NWidgetHorizontal(flags)
 
{
 
	this->type = NWID_HORIZONTAL_LTR;
 
}
 

	
 
void NWidgetHorizontalLTR::AssignSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height, bool rtl)
 
{
 
	NWidgetHorizontal::AssignSizePosition(sizing, x, y, given_width, given_height, false);
 
}
 

	
 
/** Vertical container widget. */
 
NWidgetVertical::NWidgetVertical(NWidContainerFlags flags) : NWidgetPIPContainer(NWID_VERTICAL, flags)
 
{
 
}
 

	
 
void NWidgetVertical::SetupSmallestSize(Window *w, bool init_array)
 
{
 
	this->smallest_x = 0;   // Biggest child.
 
	this->smallest_y = 0;   // Sum of minimal size of all childs.
 
	this->fill_x = true;    // true if all childs allow fill_x.
 
	this->fill_y = false;   // true if at least one child allows fill_y.
 
	this->resize_x = 1;     // smallest common child resize step
 
	this->fill_x = 1;     // smallest common child fill step.
 
	this->fill_y = 0;     // smallest non-zero child widget fill step.
 
	this->resize_x = 1;   // smallest common child resize step.
 
	this->resize_y = 0;     // smallest non-zero child widget resize step.
 

	
 
	/* 1. Forward call, collect biggest nested array index, and longest child length. */
 
	uint highest = 0; // Highest child found.
 
	for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
 
		child_wid->SetupSmallestSize(w, init_array);
 
		highest = max(highest, child_wid->smallest_y);
 
	}
 
	/* 2. For containers that must maintain equal width, extend child minimal size. */
 
	if (this->flags & NC_EQUALSIZE) {
 
		for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
 
			if (child_wid->fill_y) child_wid->smallest_y = highest;
 
			if (child_wid->fill_y == 1) child_wid->smallest_y = highest;
 
		}
 
	}
 
	/* 3. Move PIP space to the childs, compute smallest, fill, and resize values of the container. */
 
	if (this->head != NULL) this->head->padding_top += this->pip_pre;
 
	for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
 
		if (child_wid->next != NULL) {
 
			child_wid->padding_bottom += this->pip_inter;
 
		} else {
 
			child_wid->padding_bottom += this->pip_post;
 
		}
 

	
 
		this->smallest_y += child_wid->smallest_y + child_wid->padding_top + child_wid->padding_bottom;
 
		this->smallest_x = max(this->smallest_x, child_wid->smallest_x + child_wid->padding_left + child_wid->padding_right);
 
		this->fill_y |= child_wid->fill_y;
 
		this->fill_x &= child_wid->fill_x;
 
		if (child_wid->fill_y > 0) {
 
			if (this->fill_y == 0 || this->fill_y > child_wid->fill_y) this->fill_y = child_wid->fill_y;
 
		}
 
		this->fill_x = LeastCommonMultiple(this->fill_x, child_wid->fill_x);
 

	
 
		if (child_wid->resize_y > 0) {
 
			if (this->resize_y == 0 || this->resize_y > child_wid->resize_y) this->resize_y = child_wid->resize_y;
 
		}
 
		this->resize_x = LeastCommonMultiple(this->resize_x, child_wid->resize_x);
 
	}
 
	/* We need to zero the PIP settings so we can re-initialize the tree. */
 
	this->pip_pre = this->pip_inter = this->pip_post = 0;
 
}
 

	
 
void NWidgetVertical::AssignSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height, bool rtl)
 
{
 
	assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
 

	
 
	int additional_length = given_height - this->smallest_y; // Additional height given to us.
 
	StoreSizePosition(sizing, x, y, given_width, given_height);
 

	
 
	/* Like the horizontal container, the vertical container also distributes additional height evenly, starting with the childs with the biggest resize steps.
 
	 * It also stores computed widths and heights into current_x and current_y values of the child.
 
	 */
 

	
 
	/* First loop: Find biggest stepsize, find number of childs that want a piece of the pie, handle horizontal size for all childs, handle vertical size for non-resizing childs. */
 
	int num_changing_childs = 0; // Number of childs that can change size.
 
	uint biggest_stepsize = 0;
 
	for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
 
		uint vert_step = child_wid->GetVerticalStepSize(sizing);
 
		if (vert_step > 0) {
 
			num_changing_childs++;
 
			biggest_stepsize = max(biggest_stepsize, vert_step);
 
		} else {
 
			child_wid->current_y = child_wid->smallest_y;
 
		}
 

	
 
		uint hor_step = (sizing == ST_SMALLEST) ? 1 : child_wid->GetHorizontalStepSize(sizing);
 
		child_wid->current_x = ComputeMaxSize(child_wid->smallest_x, given_width - child_wid->padding_left - child_wid->padding_right, hor_step);
 
	}
 

	
 
	/* Second loop: Allocate the additional vertical space over the resizing childs, starting with the biggest resize steps. */
 
	while (biggest_stepsize > 0) {
 
		uint next_biggest_stepsize = 0;
 
		for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
 
			uint vert_step = child_wid->GetVerticalStepSize(sizing);
 
			if (vert_step > biggest_stepsize) continue; // Already done
 
			if (vert_step == biggest_stepsize) {
 
				uint increment = additional_length / num_changing_childs;
 
				num_changing_childs--;
 
				if (vert_step > 1) increment -= increment % vert_step;
 
				child_wid->current_y = child_wid->smallest_y + increment;
 
				additional_length -= increment;
 
				continue;
 
			}
 
			next_biggest_stepsize = max(next_biggest_stepsize, vert_step);
 
		}
 
		biggest_stepsize = next_biggest_stepsize;
 
	}
 
	assert(num_changing_childs == 0);
 

	
 
	/* Third loop: Compute position and call the child. */
 
	uint position = 0; // Place to put next child relative to origin of the container.
 
	for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
 
		uint child_x = x + (rtl ? child_wid->padding_right : child_wid->padding_left);
 
		uint child_height = child_wid->current_y;
 

	
 
		child_wid->AssignSizePosition(sizing, child_x, y + position + child_wid->padding_top, child_wid->current_x, child_height, rtl);
 
		position += child_height + child_wid->padding_top + child_wid->padding_bottom;
 
	}
 
}
 

	
 
/**
 
 * Generic spacer widget.
 
 * @param length Horizontal size of the spacer widget.
 
 * @param height Vertical size of the spacer widget.
 
 */
 
NWidgetSpacer::NWidgetSpacer(int length, int height) : NWidgetResizeBase(NWID_SPACER, false, false)
 
NWidgetSpacer::NWidgetSpacer(int length, int height) : NWidgetResizeBase(NWID_SPACER, 0, 0)
 
{
 
	this->SetMinimalSize(length, height);
 
	this->SetResize(0, 0);
 
}
 

	
 
void NWidgetSpacer::SetupSmallestSize(Window *w, bool init_array)
 
{
 
	this->smallest_x = this->min_x;
 
	this->smallest_y = this->min_y;
 
}
 

	
 
void NWidgetSpacer::FillNestedArray(NWidgetBase **array, uint length)
 
{
 
}
 

	
 
void NWidgetSpacer::Draw(const Window *w)
 
{
 
	/* Spacer widget is never visible. */
 
}
 

	
 
void NWidgetSpacer::SetDirty(const Window *w) const
 
{
 
	/* Spacer widget never need repainting. */
 
}
 

	
 
NWidgetCore *NWidgetSpacer::GetWidgetFromPos(int x, int y)
 
{
 
	return NULL;
 
}
 

	
 
/**
 
 * Constructor parent nested widgets.
 
 * @param tp     Type of parent widget.
 
 * @param colour Colour of the parent widget.
 
 * @param index  Index in the widget array used by the window system.
 
 * @param child  Child container widget (if supplied). If not supplied, a
 
 *               vertical container will be inserted while adding the first
 
 *               child widget.
 
 */
 
NWidgetBackground::NWidgetBackground(WidgetType tp, Colours colour, int index, NWidgetPIPContainer *child) : NWidgetCore(tp, colour, true, true, 0x0, STR_NULL)
 
NWidgetBackground::NWidgetBackground(WidgetType tp, Colours colour, int index, NWidgetPIPContainer *child) : NWidgetCore(tp, colour, 1, 1, 0x0, STR_NULL)
 
{
 
	assert(tp == WWT_PANEL || tp == WWT_INSET || tp == WWT_FRAME);
 
	if (index >= 0) this->SetIndex(index);
 
	this->child = child;
 
}
 

	
 
NWidgetBackground::~NWidgetBackground()
 
{
 
	if (this->child != NULL) delete this->child;
 
}
 

	
 
/**
 
 * Add a child to the parent.
 
 * @param nwid Nested widget to add to the background widget.
 
 *
 
 * Unless a child container has been given in the constructor, a parent behaves as a vertical container.
 
 * You can add several childs to it, and they are put underneath each other.
 
 */
 
void NWidgetBackground::Add(NWidgetBase *nwid)
 
{
 
	if (this->child == NULL) {
 
		this->child = new NWidgetVertical();
 
	}
 
	this->child->Add(nwid);
 
}
 

	
 
/**
 
 * Set additional pre/inter/post space for the background widget.
 
 *
 
 * @param pip_pre   Additional space in front of the first child widget (above
 
 *                  for the vertical container, at the left for the horizontal container).
 
 * @param pip_inter Additional space between two child widgets.
 
 * @param pip_post  Additional space after the last child widget (below for the
 
 *                  vertical container, at the right for the horizontal container).
 
 * @note Using this function implies that the widget has (or will have) child widgets.
 
 */
 
void NWidgetBackground::SetPIP(uint8 pip_pre, uint8 pip_inter, uint8 pip_post)
 
{
 
	if (this->child == NULL) {
 
		this->child = new NWidgetVertical();
 
	}
 
	this->child->SetPIP(pip_pre, pip_inter, pip_post);
 
}
 

	
 
void NWidgetBackground::SetupSmallestSize(Window *w, bool init_array)
 
{
 
	if (init_array && this->index >= 0) {
 
		assert(w->nested_array_size > (uint)this->index);
 
		w->nested_array[this->index] = this;
 
	}
 
	if (this->child != NULL) {
 
		this->child->SetupSmallestSize(w, init_array);
 

	
 
		this->smallest_x = this->child->smallest_x;
 
		this->smallest_y = this->child->smallest_y;
 
		this->fill_x = this->child->fill_x;
 
		this->fill_y = this->child->fill_y;
 
		this->resize_x = this->child->resize_x;
 
		this->resize_y = this->child->resize_y;
 

	
 
		/* Account for the size of the frame's text if that exists */
 
		if (w != NULL && this->type == WWT_FRAME) {
 
			this->child->padding_left   = WD_FRAMETEXT_LEFT;
 
			this->child->padding_right  = WD_FRAMETEXT_RIGHT;
 
			this->child->padding_top    = max((int)WD_FRAMETEXT_TOP, this->widget_data != STR_NULL ? FONT_HEIGHT_NORMAL + WD_FRAMETEXT_TOP / 2 : 0);
 
			this->child->padding_bottom = WD_FRAMETEXT_BOTTOM;
 

	
 
			this->smallest_x += this->child->padding_left + this->child->padding_right;
 
			this->smallest_y += this->child->padding_top + this->child->padding_bottom;
 

	
 
			if (this->index >= 0) w->SetStringParameters(this->index);
 
			this->smallest_x = max(this->smallest_x, GetStringBoundingBox(this->widget_data).width + WD_FRAMETEXT_LEFT + WD_FRAMETEXT_RIGHT);
 
		}
 
	} else {
 
		Dimension d = {this->min_x, this->min_y};
 
		Dimension resize  = {this->resize_x, this->resize_y};
 
		if (w != NULL) { // A non-NULL window pointer acts as switch to turn dynamic widget size on.
 
			if (this->type == WWT_FRAME || this->type == WWT_INSET) {
 
				if (this->index >= 0) w->SetStringParameters(this->index);
 
				Dimension background = GetStringBoundingBox(this->widget_data);
 
				background.width += (this->type == WWT_FRAME) ? (WD_FRAMETEXT_LEFT + WD_FRAMERECT_RIGHT) : (WD_INSET_LEFT + WD_INSET_RIGHT);
 
				d = maxdim(d, background);
 
			}
 
			if (this->index >= 0) {
 
				static const Dimension padding = {0, 0};
 
				w->UpdateWidgetSize(this->index, &d, padding, &resize);
 
			}
 
		}
 
		this->smallest_x = d.width;
 
		this->smallest_y = d.height;
 
		this->resize_x = resize.width;
 
		this->resize_y = resize.height;
 
	}
 
}
 

	
 
void NWidgetBackground::AssignSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height, bool rtl)
 
{
 
	StoreSizePosition(sizing, x, y, given_width, given_height);
 

	
 
	if (this->child != NULL) {
 
		uint x_offset = (rtl ? this->child->padding_right : this->child->padding_left);
 
		uint width = given_width - this->child->padding_right - this->child->padding_left;
 
		uint height = given_height - this->child->padding_top - this->child->padding_bottom;
 
		this->child->AssignSizePosition(sizing, x + x_offset, y + this->child->padding_top, width, height, rtl);
 
	}
 
}
 

	
 
void NWidgetBackground::FillNestedArray(NWidgetBase **array, uint length)
 
{
 
	if (this->index >= 0 && (uint)(this->index) < length) array[this->index] = this;
 
	if (this->child != NULL) this->child->FillNestedArray(array, length);
 
}
 

	
 
void NWidgetBackground::Draw(const Window *w)
 
{
 
	if (this->current_x == 0 || this->current_y == 0) return;
 

	
 
	Rect r;
 
	r.left = this->pos_x;
 
	r.right = this->pos_x + this->current_x - 1;
 
	r.top = this->pos_y;
 
	r.bottom = this->pos_y + this->current_y - 1;
 

	
 
	const DrawPixelInfo *dpi = _cur_dpi;
 
	if (dpi->left > r.right || dpi->left + dpi->width <= r.left || dpi->top > r.bottom || dpi->top + dpi->height <= r.top) return;
 

	
 
	switch (this->type) {
 
		case WWT_PANEL:
 
			assert(this->widget_data == 0);
 
			DrawFrameRect(r.left, r.top, r.right, r.bottom, this->colour, this->IsLowered() ? FR_LOWERED : FR_NONE);
 
			break;
 

	
 
		case WWT_FRAME:
 
			if (this->index >= 0) w->SetStringParameters(this->index);
 
			DrawFrame(r, this->colour, this->widget_data);
 
			break;
 

	
 
		case WWT_INSET:
 
			if (this->index >= 0) w->SetStringParameters(this->index);
 
			DrawInset(r, this->colour, this->widget_data);
 
			break;
 

	
 
		default:
 
			NOT_REACHED();
 
	}
 

	
 
	if (this->index >= 0) w->DrawWidget(r, this->index);
 
	if (this->child != NULL) this->child->Draw(w);
 

	
 
	if (this->IsDisabled()) {
 
		GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, _colour_gradient[this->colour & 0xF][2], FILLRECT_CHECKER);
 
	}
 
}
 

	
 
NWidgetCore *NWidgetBackground::GetWidgetFromPos(int x, int y)
 
{
 
	NWidgetCore *nwid = NULL;
 
	if (IsInsideBS(x, this->pos_x, this->current_x) && IsInsideBS(y, this->pos_y, this->current_y)) {
 
		if (this->child != NULL) nwid = this->child->GetWidgetFromPos(x, y);
 
		if (nwid == NULL) nwid = this;
 
	}
 
	return nwid;
 
}
 

	
 
Scrollbar *NWidgetBackground::FindScrollbar(Window *w, bool allow_next)
 
{
 
	if (this->index > 0 && allow_next && this->child == NULL && (uint)(this->index) + 1 < w->nested_array_size) {
 
		NWidgetCore *next_wid = w->GetWidget<NWidgetCore>(this->index + 1);
 
		if (next_wid != NULL) return next_wid->FindScrollbar(w, false);
 
	}
 
	return NULL;
 
}
 

	
 
NWidgetBase *NWidgetBackground::GetWidgetOfType(WidgetType tp)
 
{
 
	NWidgetBase *nwid = NULL;
 
	if (this->child != NULL) nwid = this->child->GetWidgetOfType(tp);
 
	if (nwid == NULL && this->type == tp) nwid = this;
 
	return nwid;
 
}
 

	
 
NWidgetViewport::NWidgetViewport(int index) : NWidgetCore(NWID_VIEWPORT, INVALID_COLOUR, true, true, 0x0, STR_NULL)
 
NWidgetViewport::NWidgetViewport(int index) : NWidgetCore(NWID_VIEWPORT, INVALID_COLOUR, 1, 1, 0x0, STR_NULL)
 
{
 
	this->SetIndex(index);
 
}
 

	
 
void NWidgetViewport::SetupSmallestSize(Window *w, bool init_array)
 
{
 
	if (init_array && this->index >= 0) {
 
		assert(w->nested_array_size > (uint)this->index);
 
		w->nested_array[this->index] = this;
 
	}
 
	this->smallest_x = this->min_x;
 
	this->smallest_y = this->min_y;
 
}
 

	
 
void NWidgetViewport::Draw(const Window *w)
 
{
 
	if (this->disp_flags & ND_NO_TRANSPARENCY) {
 
		TransparencyOptionBits to_backup = _transparency_opt;
 
		_transparency_opt = 0; // Disable all transparency
 
		w->DrawViewport();
 
		_transparency_opt = to_backup;
 
	} else {
 
		w->DrawViewport();
 
	}
 

	
 
	/* Optionally shade the viewport. */
 
	if (this->disp_flags & (ND_SHADE_GREY | ND_SHADE_DIMMED)) {
 
		GfxFillRect(this->pos_x, this->pos_y, this->pos_x + this->current_x - 1, this->pos_y + this->current_y - 1,
 
				(this->disp_flags & ND_SHADE_DIMMED) ? PALETTE_TO_TRANSPARENT : PALETTE_TO_STRUCT_GREY, FILLRECT_RECOLOUR);
 
	}
 
}
 

	
 
Scrollbar *NWidgetViewport::FindScrollbar(Window *w, bool allow_next)
 
{
 
	return NULL;
 
}
 

	
 
/**
 
 * Initialize the viewport of the window.
 
 * @param w            Window owning the viewport.
 
 * @param follow_flags Type of viewport, see #InitializeViewport().
 
 * @param zoom         Zoom level.
 
 */
 
void NWidgetViewport::InitializeViewport(Window *w, uint32 follow_flags, ZoomLevel zoom)
 
{
 
	InitializeWindowViewport(w, this->pos_x, this->pos_y, this->current_x, this->current_y, follow_flags, zoom);
 
}
 

	
 
/**
 
 * Update the position and size of the viewport (after eg a resize).
 
 * @param w Window owning the viewport.
 
 */
 
void NWidgetViewport::UpdateViewportCoordinates(Window *w)
 
{
 
	ViewPort *vp = w->viewport;
 
	if (vp != NULL) {
 
		vp->left = w->left + this->pos_x;
 
		vp->top  = w->top + this->pos_y;
 
		vp->width  = this->current_x;
 
		vp->height = this->current_y;
 

	
 
		vp->virtual_width  = ScaleByZoom(vp->width, vp->zoom);
 
		vp->virtual_height = ScaleByZoom(vp->height, vp->zoom);
 
	}
 
}
 

	
 
/** Reset the cached dimensions. */
 
/* static */ void NWidgetLeaf::InvalidateDimensionCache()
 
{
 
	stickybox_dimension.width = stickybox_dimension.height = 0;
 
	resizebox_dimension.width = resizebox_dimension.height = 0;
 
	closebox_dimension.width  = closebox_dimension.height  = 0;
 
}
 

	
 
Dimension NWidgetLeaf::stickybox_dimension = {0, 0};
 
Dimension NWidgetLeaf::resizebox_dimension = {0, 0};
 
Dimension NWidgetLeaf::closebox_dimension  = {0, 0};
 

	
 
/**
 
 * Nested leaf widget.
 
 * @param tp     Type of leaf widget.
 
 * @param colour Colour of the leaf widget.
 
 * @param index  Index in the widget array used by the window system.
 
 * @param data   Data of the widget.
 
 * @param tip    Tooltip of the widget.
 
 */
 
NWidgetLeaf::NWidgetLeaf(WidgetType tp, Colours colour, int index, uint16 data, StringID tip) : NWidgetCore(tp, colour, true, true, data, tip)
 
NWidgetLeaf::NWidgetLeaf(WidgetType tp, Colours colour, int index, uint16 data, StringID tip) : NWidgetCore(tp, colour, 1, 1, data, tip)
 
{
 
	this->SetIndex(index);
 
	this->SetMinimalSize(0, 0);
 
	this->SetResize(0, 0);
 

	
 
	switch (tp) {
 
		case WWT_EMPTY:
 
			break;
 

	
 
		case WWT_PUSHBTN:
 
			this->SetFill(false, false);
 
			this->SetFill(0, 0);
 
			break;
 

	
 
		case WWT_IMGBTN:
 
		case WWT_PUSHIMGBTN:
 
		case WWT_IMGBTN_2:
 
			this->SetFill(false, false);
 
			this->SetFill(0, 0);
 
			break;
 

	
 
		case WWT_TEXTBTN:
 
		case WWT_PUSHTXTBTN:
 
		case WWT_TEXTBTN_2:
 
		case WWT_LABEL:
 
		case WWT_TEXT:
 
		case WWT_MATRIX:
 
		case WWT_EDITBOX:
 
		case NWID_BUTTON_DROPDOWN:
 
		case NWID_BUTTON_ARROW:
 
			this->SetFill(false, false);
 
			this->SetFill(0, 0);
 
			break;
 

	
 
		case WWT_SCROLLBAR:
 
		case WWT_SCROLL2BAR:
 
			this->SetFill(false, true);
 
			this->SetFill(0, 1);
 
			this->SetResize(0, 1);
 
			this->min_x = WD_VSCROLLBAR_WIDTH;
 
			this->SetDataTip(0x0, STR_TOOLTIP_VSCROLL_BAR_SCROLLS_LIST);
 
			break;
 

	
 
		case WWT_CAPTION:
 
			this->SetFill(true, false);
 
			this->SetFill(1, 0);
 
			this->SetResize(1, 0);
 
			this->min_y = WD_CAPTION_HEIGHT;
 
			this->SetDataTip(data, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS);
 
			break;
 

	
 
		case WWT_HSCROLLBAR:
 
			this->SetFill(true, false);
 
			this->SetFill(1, 0);
 
			this->SetResize(1, 0);
 
			this->min_y = WD_HSCROLLBAR_HEIGHT;
 
			this->SetDataTip(0x0, STR_TOOLTIP_HSCROLL_BAR_SCROLLS_LIST);
 
			break;
 

	
 
		case WWT_STICKYBOX:
 
			this->SetFill(false, false);
 
			this->SetFill(0, 0);
 
			this->SetMinimalSize(WD_STICKYBOX_WIDTH, 14);
 
			this->SetDataTip(STR_NULL, STR_TOOLTIP_STICKY);
 
			break;
 

	
 
		case WWT_RESIZEBOX:
 
			this->SetFill(false, false);
 
			this->SetFill(0, 0);
 
			this->SetMinimalSize(WD_RESIZEBOX_WIDTH, 12);
 
			this->SetDataTip(STR_NULL, STR_TOOLTIP_RESIZE);
 
			break;
 

	
 
		case WWT_CLOSEBOX:
 
			this->SetFill(false, false);
 
			this->SetFill(0, 0);
 
			this->SetMinimalSize(WD_CLOSEBOX_WIDTH, 14);
 
			this->SetDataTip(STR_BLACK_CROSS, STR_TOOLTIP_CLOSE_WINDOW);
 
			break;
 

	
 
		case WWT_DROPDOWN:
 
			this->SetFill(false, false);
 
			this->SetFill(0, 0);
 
			this->min_y = WD_DROPDOWN_HEIGHT;
 
			break;
 

	
 
		default:
 
			NOT_REACHED();
 
	}
 
}
 

	
 
void NWidgetLeaf::SetupSmallestSize(Window *w, bool init_array)
 
{
 
	if (w == NULL) { // Conversion to widget array.
 
		this->smallest_x = this->min_x;
 
		this->smallest_y = this->min_y;
 
		/* All other data is already at the right place. */
 
		return;
 
	}
 

	
 
	if (this->index >= 0 && init_array) { // Fill w->nested_array[]
 
		assert(w->nested_array_size > (uint)this->index);
 
		w->nested_array[this->index] = this;
 
	}
 

	
 
	/* A non-NULL window pointer acts as switch to turn dynamic widget sizing on. */
 
	Dimension size = {this->min_x, this->min_y};
 
	Dimension resize = {this->resize_x, this->resize_y};
 
	/* Get padding, and update size with the real content size if appropriate. */
 
	const Dimension *padding = NULL;
 
	switch (this->type) {
 
		case WWT_EMPTY:
 
		case WWT_SCROLLBAR:
 
		case WWT_SCROLL2BAR:
 
		case WWT_HSCROLLBAR: {
 
			static const Dimension extra = {0, 0};
 
			padding = &extra;
 
			break;
 
		}
 
		case WWT_MATRIX: {
 
			static const Dimension extra = {WD_MATRIX_LEFT + WD_MATRIX_RIGHT, WD_MATRIX_TOP + WD_MATRIX_BOTTOM};
 
			padding = &extra;
 
			break;
 
		}
 
		case WWT_STICKYBOX: {
 
			static const Dimension extra = {WD_STICKYBOX_LEFT + WD_STICKYBOX_RIGHT, WD_STICKYBOX_TOP + WD_STICKYBOX_BOTTOM};
 
			padding = &extra;
 
			if (NWidgetLeaf::stickybox_dimension.width == 0) {
 
				NWidgetLeaf::stickybox_dimension = maxdim(GetSpriteSize(SPR_PIN_UP), GetSpriteSize(SPR_PIN_DOWN));
 
				NWidgetLeaf::stickybox_dimension.width += extra.width;
 
				NWidgetLeaf::stickybox_dimension.height += extra.height;
 
			}
 
			size = maxdim(size, NWidgetLeaf::stickybox_dimension);
 
			break;
 
		}
 
		case WWT_RESIZEBOX: {
 
			static const Dimension extra = {WD_RESIZEBOX_LEFT + WD_RESIZEBOX_RIGHT, WD_RESIZEBOX_TOP + WD_RESIZEBOX_BOTTOM};
 
			padding = &extra;
 
			if (NWidgetLeaf::resizebox_dimension.width == 0) {
 
				NWidgetLeaf::resizebox_dimension = maxdim(GetSpriteSize(SPR_WINDOW_RESIZE_LEFT), GetSpriteSize(SPR_WINDOW_RESIZE_RIGHT));
 
				NWidgetLeaf::resizebox_dimension.width += extra.width;
 
				NWidgetLeaf::resizebox_dimension.height += extra.height;
 
			}
 
			size = maxdim(size, NWidgetLeaf::resizebox_dimension);
 
			break;
 
		}
 
		case WWT_EDITBOX:
 
			size.height = max(size.height, GetStringBoundingBox("_").height + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM);
 
			/* fall through */
 
		case WWT_PUSHBTN: {
 
			static const Dimension extra = {WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM};
 
			padding = &extra;
 
			break;
 
		}
 
		case WWT_IMGBTN:
 
		case WWT_IMGBTN_2:
 
		case WWT_PUSHIMGBTN: {
 
			static const Dimension extra = {WD_IMGBTN_LEFT + WD_IMGBTN_RIGHT,  WD_IMGBTN_TOP + WD_IMGBTN_BOTTOM};
 
			padding = &extra;
 
			Dimension d2 = GetSpriteSize(this->widget_data);
 
			if (this->type == WWT_IMGBTN_2) d2 = maxdim(d2, GetSpriteSize(this->widget_data + 1));
 
			d2.width += extra.width;
 
			d2.height += extra.height;
 
			size = maxdim(size, d2);
 
			break;
 
		}
 
		case NWID_BUTTON_ARROW: {
 
			static const Dimension extra = {WD_IMGBTN_LEFT + WD_IMGBTN_RIGHT,  WD_IMGBTN_TOP + WD_IMGBTN_BOTTOM};
 
			padding = &extra;
 
			Dimension d2 = maxdim(GetSpriteSize(SPR_ARROW_LEFT), GetSpriteSize(SPR_ARROW_RIGHT));
 
			d2.width += extra.width;
 
			d2.height += extra.height;
 
			size = maxdim(size, d2);
 
			break;
 
		}
 

	
 
		case WWT_CLOSEBOX: {
 
			static const Dimension extra = {WD_CLOSEBOX_LEFT + WD_CLOSEBOX_RIGHT, WD_CLOSEBOX_TOP + WD_CLOSEBOX_BOTTOM};
 
			padding = &extra;
 
			if (NWidgetLeaf::closebox_dimension.width == 0) {
 
				NWidgetLeaf::closebox_dimension = maxdim(GetStringBoundingBox(STR_BLACK_CROSS), GetStringBoundingBox(STR_SILVER_CROSS));
 
				NWidgetLeaf::closebox_dimension.width += extra.width;
 
				NWidgetLeaf::closebox_dimension.height += extra.height;
 
			}
 
			size = maxdim(size, NWidgetLeaf::closebox_dimension);
 
			break;
 
		}
 
		case WWT_TEXTBTN:
 
		case WWT_PUSHTXTBTN:
 
		case WWT_TEXTBTN_2: {
 
			static const Dimension extra = {WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT,  WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM};
 
			padding = &extra;
 
			if (this->index >= 0) w->SetStringParameters(this->index);
 
			Dimension d2 = GetStringBoundingBox(this->widget_data);
 
			d2.width += extra.width;
 
			d2.height += extra.height;
 
			size = maxdim(size, d2);
 
			break;
 
		}
 
		case WWT_LABEL:
 
		case WWT_TEXT: {
 
			static const Dimension extra = {0, 0};
 
			padding = &extra;
 
			if (this->index >= 0) w->SetStringParameters(this->index);
 
			size = maxdim(size, GetStringBoundingBox(this->widget_data));
 
			break;
 
		}
 
		case WWT_CAPTION: {
 
			static const Dimension extra = {WD_CAPTIONTEXT_LEFT + WD_CAPTIONTEXT_RIGHT, WD_CAPTIONTEXT_TOP + WD_CAPTIONTEXT_BOTTOM};
 
			padding = &extra;
 
			if (this->index >= 0) w->SetStringParameters(this->index);
 
			Dimension d2 = GetStringBoundingBox(this->widget_data);
 
			d2.width += extra.width;
 
			d2.height += extra.height;
 
			size = maxdim(size, d2);
 
			break;
 
		}
 
		case WWT_DROPDOWN:
 
		case NWID_BUTTON_DROPDOWN: {
 
			static const Dimension extra = {WD_DROPDOWNTEXT_LEFT + WD_DROPDOWNTEXT_RIGHT, WD_DROPDOWNTEXT_TOP + WD_DROPDOWNTEXT_BOTTOM};
 
			padding = &extra;
 
			if (this->index >= 0) w->SetStringParameters(this->index);
 
			Dimension d2 = GetStringBoundingBox(this->widget_data);
 
			d2.width += extra.width;
 
			d2.height += extra.height;
 
			size = maxdim(size, d2);
 
			break;
 
		}
 
		default:
 
			NOT_REACHED();
 
	}
 

	
 
	if (this->index >= 0) w->UpdateWidgetSize(this->index, &size, *padding, &resize);
 

	
 
	this->smallest_x = size.width;
 
	this->smallest_y = size.height;
 
	this->resize_x = resize.width;
 
	this->resize_y = resize.height;
 
	/* this->fill_x and this->fill_y are already correct. */
 
}
 

	
 
void NWidgetLeaf::Draw(const Window *w)
 
{
 
	if (this->current_x == 0 || this->current_y == 0) return;
 

	
 
	Rect r;
 
	r.left = this->pos_x;
 
	r.right = this->pos_x + this->current_x - 1;
 
	r.top = this->pos_y;
 
	r.bottom = this->pos_y + this->current_y - 1;
 

	
 
	const DrawPixelInfo *dpi = _cur_dpi;
 
	if (dpi->left > r.right || dpi->left + dpi->width <= r.left || dpi->top > r.bottom || dpi->top + dpi->height <= r.top) return;
 

	
 
	bool clicked = this->IsLowered();
 
	switch (this->type) {
 
		case WWT_EMPTY:
 
			break;
 

	
 
		case WWT_PUSHBTN:
 
			assert(this->widget_data == 0);
 
			DrawFrameRect(r.left, r.top, r.right, r.bottom, this->colour, (clicked) ? FR_LOWERED : FR_NONE);
 
			break;
 

	
 
		case WWT_IMGBTN:
 
		case WWT_PUSHIMGBTN:
 
		case WWT_IMGBTN_2:
 
			DrawImageButtons(r, this->type, this->colour, clicked, this->widget_data);
 
			break;
 

	
 
		case WWT_TEXTBTN:
 
		case WWT_PUSHTXTBTN:
 
		case WWT_TEXTBTN_2:
 
			if (this->index >= 0) w->SetStringParameters(this->index);
 
			DrawFrameRect(r.left, r.top, r.right, r.bottom, this->colour, (clicked) ? FR_LOWERED : FR_NONE);
 
			DrawLabel(r, this->type, clicked, this->widget_data);
 
			break;
 

	
 
		case NWID_BUTTON_ARROW: {
 
			SpriteID sprite;
 
			switch (this->widget_data) {
 
				case AWV_DECREASE: sprite = _dynlang.text_dir != TD_RTL ? SPR_ARROW_LEFT : SPR_ARROW_RIGHT; break;
 
				case AWV_INCREASE: sprite = _dynlang.text_dir == TD_RTL ? SPR_ARROW_LEFT : SPR_ARROW_RIGHT; break;
 
				case AWV_LEFT:     sprite = SPR_ARROW_LEFT;  break;
 
				case AWV_RIGHT:    sprite = SPR_ARROW_RIGHT; break;
 
				default: NOT_REACHED();
 
			}
 
			DrawImageButtons(r, WWT_PUSHIMGBTN, this->colour, clicked, sprite);
 
		}
 

	
 
		case WWT_LABEL:
 
			if (this->index >= 0) w->SetStringParameters(this->index);
 
			DrawLabel(r, this->type, clicked, this->widget_data);
 
			break;
 

	
 
		case WWT_TEXT:
 
			if (this->index >= 0) w->SetStringParameters(this->index);
 
			DrawText(r, (TextColour)this->colour, this->widget_data);
 
			break;
 

	
 
		case WWT_MATRIX:
 
			DrawMatrix(r, this->colour, clicked, this->widget_data);
 
			break;
 

	
 
		case WWT_EDITBOX:
 
			DrawFrameRect(r.left, r.top, r.right, r.bottom, this->colour, FR_LOWERED | FR_DARKENED);
 
			break;
 

	
 
		case WWT_SCROLLBAR:
 
			assert(this->widget_data == 0);
 
			DrawVerticalScrollbar(r, this->colour, (w->flags4 & (WF_SCROLL_UP | WF_HSCROLL | WF_SCROLL2)) == WF_SCROLL_UP,
 
								(w->flags4 & (WF_SCROLL_MIDDLE | WF_HSCROLL | WF_SCROLL2)) == WF_SCROLL_MIDDLE,
 
								(w->flags4 & (WF_SCROLL_DOWN | WF_HSCROLL | WF_SCROLL2)) == WF_SCROLL_DOWN, &w->vscroll);
 
			break;
 

	
 
		case WWT_SCROLL2BAR:
 
			assert(this->widget_data == 0);
 
			DrawVerticalScrollbar(r, this->colour, (w->flags4 & (WF_SCROLL_UP | WF_HSCROLL | WF_SCROLL2)) == (WF_SCROLL_UP | WF_SCROLL2),
 
								(w->flags4 & (WF_SCROLL_MIDDLE | WF_HSCROLL | WF_SCROLL2)) == (WF_SCROLL_MIDDLE | WF_SCROLL2),
 
								(w->flags4 & (WF_SCROLL_DOWN | WF_HSCROLL | WF_SCROLL2)) == (WF_SCROLL_DOWN | WF_SCROLL2), &w->vscroll2);
 
			break;
 

	
 
		case WWT_CAPTION:
 
			if (this->index >= 0) w->SetStringParameters(this->index);
 
			DrawCaption(r, this->colour, w->owner, this->widget_data);
 
			break;
 

	
 
		case WWT_HSCROLLBAR:
 
			assert(this->widget_data == 0);
 
			DrawHorizontalScrollbar(r, this->colour, (w->flags4 & (WF_SCROLL_UP | WF_HSCROLL)) == (WF_SCROLL_UP | WF_HSCROLL),
 
								(w->flags4 & (WF_SCROLL_MIDDLE | WF_HSCROLL)) == (WF_SCROLL_MIDDLE | WF_HSCROLL),
 
								(w->flags4 & (WF_SCROLL_DOWN | WF_HSCROLL)) == (WF_SCROLL_DOWN | WF_HSCROLL), &w->hscroll);
 
			break;
 

	
 
		case WWT_STICKYBOX:
 
			assert(this->widget_data == 0);
 
			DrawStickyBox(r, this->colour, !!(w->flags4 & WF_STICKY));
 
			break;
 

	
 
		case WWT_RESIZEBOX:
 
			assert(this->widget_data == 0);
 
			DrawResizeBox(r, this->colour, this->pos_x < (uint)(w->width / 2), !!(w->flags4 & WF_SIZING));
 
			break;
 

	
 
		case WWT_CLOSEBOX:
 
			DrawCloseBox(r, this->colour, this->widget_data);
 
			break;
 

	
 
		case WWT_DROPDOWN:
 
			if (this->index >= 0) w->SetStringParameters(this->index);
 
			DrawDropdown(r, this->colour, clicked, this->widget_data);
 
			break;
 

	
 
		case NWID_BUTTON_DROPDOWN:
 
			if (this->index >= 0) w->SetStringParameters(this->index);
 
			DrawButtonDropdown(r, this->colour, clicked, (this->disp_flags & ND_DROPDOWN_ACTIVE) != 0, this->widget_data);
 
			break;
 

	
 
		default:
 
			NOT_REACHED();
 
	}
 
	if (this->index >= 0) w->DrawWidget(r, this->index);
 

	
 
	if (this->IsDisabled()) {
 
		GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, _colour_gradient[this->colour & 0xF][2], FILLRECT_CHECKER);
 
	}
 
}
 

	
 
Scrollbar *NWidgetLeaf::FindScrollbar(Window *w, bool allow_next)
 
{
 
	if (this->type == WWT_SCROLLBAR) return &w->vscroll;
 
	if (this->type == WWT_SCROLL2BAR) return &w->vscroll2;
 

	
 
	if (this->index > 0 && allow_next && (uint)(this->index) + 1 < w->nested_array_size) {
 
		NWidgetCore *next_wid = w->GetWidget<NWidgetCore>(this->index + 1);
 
		if (next_wid != NULL) return next_wid->FindScrollbar(w, false);
 
	}
 
	return NULL;
 
}
 

	
 
/**
 
 * For a #NWID_BUTTON_DROPDOWN, test whether \a pt refers to the button or to the drop-down.
 
 * @param pt Point in the widget.
 
 * @return The point refers to the button.
 
 *
 
 * @note The magic constants are also used at #DrawButtonDropdown.
 
 */
 
bool NWidgetLeaf::ButtonHit(const Point &pt)
 
{
 
	if (_dynlang.text_dir == TD_LTR) {
 
		int button_width = this->pos_x + this->current_x - 12;
 
		return pt.x < button_width;
 
	} else {
 
		int button_left = this->pos_x + 12;
 
		return pt.x >= button_left;
 
	}
 
}
 

	
 
/* == Conversion code from NWidgetPart array to NWidgetBase* tree == */
 

	
 
/**
 
 * Construct a single nested widget in \a *dest from its parts.
 
 *
 
 * Construct a NWidgetBase object from a #NWidget function, and apply all
 
 * settings that follow it, until encountering a #EndContainer, another
 
 * #NWidget, or the end of the parts array.
 
 *
 
 * @param parts Array with parts of the nested widget.
 
 * @param count Length of the \a parts array.
 
 * @param dest  Address of pointer to use for returning the composed widget.
 
 * @param fill_dest Fill the composed widget with child widgets.
 
 * @param biggest_index Pointer to biggest nested widget index in the tree encountered so far.
 
 * @return Number of widget part elements used to compose the widget.
 
 * @precond \c biggest_index != NULL.
 
 */
 
static int MakeNWidget(const NWidgetPart *parts, int count, NWidgetBase **dest, bool *fill_dest, int *biggest_index)
 
{
 
	int num_used = 0;
 

	
 
	*dest = NULL;
 
	*fill_dest = false;
 

	
 
	while (count > num_used) {
 
		switch (parts->type) {
 
			case NWID_SPACER:
 
				if (*dest != NULL) return num_used;
 
				*dest = new NWidgetSpacer(0, 0);
 
				break;
 

	
 
			case NWID_HORIZONTAL:
 
				if (*dest != NULL) return num_used;
 
				*dest = new NWidgetHorizontal(parts->u.cont_flags);
 
				*fill_dest = true;
 
				break;
 

	
 
			case NWID_HORIZONTAL_LTR:
 
				if (*dest != NULL) return num_used;
 
				*dest = new NWidgetHorizontalLTR(parts->u.cont_flags);
 
				*fill_dest = true;
 
				break;
 

	
 
			case WWT_PANEL:
 
			case WWT_INSET:
 
			case WWT_FRAME:
 
				if (*dest != NULL) return num_used;
 
				*dest = new NWidgetBackground(parts->type, parts->u.widget.colour, parts->u.widget.index);
 
				*biggest_index = max(*biggest_index, (int)parts->u.widget.index);
 
				*fill_dest = true;
 
				break;
 

	
 
			case NWID_VERTICAL:
 
				if (*dest != NULL) return num_used;
 
				*dest = new NWidgetVertical(parts->u.cont_flags);
 
				*fill_dest = true;
 
				break;
 

	
 
			case WPT_FUNCTION: {
 
				if (*dest != NULL) return num_used;
 
				/* Ensure proper functioning even when the called code simply writes its largest index. */
 
				int biggest = -1;
 
				*dest = parts->u.func_ptr(&biggest);
 
				*biggest_index = max(*biggest_index, biggest);
 
				*fill_dest = false;
 
				break;
 
			}
 

	
 
			case WPT_RESIZE: {
 
				NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(*dest);
 
				if (nwrb != NULL) {
 
					assert(parts->u.xy.x >= 0 && parts->u.xy.y >= 0);
 
					nwrb->SetResize(parts->u.xy.x, parts->u.xy.y);
 
				}
 
				break;
 
			}
 

	
 
			case WPT_MINSIZE: {
 
				NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(*dest);
 
				if (nwrb != NULL) {
 
					assert(parts->u.xy.x >= 0 && parts->u.xy.y >= 0);
 
					nwrb->SetMinimalSize(parts->u.xy.x, parts->u.xy.y);
 
				}
 
				break;
 
			}
 

	
 
			case WPT_MINTEXTLINES: {
 
				NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(*dest);
 
				if (nwrb != NULL) {
 
					assert(parts->u.text_lines.size >= FS_BEGIN && parts->u.text_lines.size < FS_END);
 
					nwrb->SetMinimalTextLines(parts->u.text_lines.lines, parts->u.text_lines.spacing, parts->u.text_lines.size);
 
				}
 
				break;
 
			}
 

	
 
			case WPT_FILL: {
 
				NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(*dest);
 
				if (nwrb != NULL) nwrb->SetFill(parts->u.xy.x != 0, parts->u.xy.y != 0);
 
				if (nwrb != NULL) nwrb->SetFill(parts->u.xy.x, parts->u.xy.y);
 
				break;
 
			}
 

	
 
			case WPT_DATATIP: {
 
				NWidgetCore *nwc = dynamic_cast<NWidgetCore *>(*dest);
 
				if (nwc != NULL) {
 
					nwc->widget_data = parts->u.data_tip.data;
 
					nwc->tool_tip = parts->u.data_tip.tooltip;
 
				}
 
				break;
 
			}
 

	
 
			case WPT_PADDING:
 
				if (*dest != NULL) (*dest)->SetPadding(parts->u.padding.top, parts->u.padding.right, parts->u.padding.bottom, parts->u.padding.left);
 
				break;
 

	
 
			case WPT_PIPSPACE: {
 
				NWidgetPIPContainer *nwc = dynamic_cast<NWidgetPIPContainer *>(*dest);
 
				if (nwc != NULL) nwc->SetPIP(parts->u.pip.pre,  parts->u.pip.inter, parts->u.pip.post);
 

	
 
				NWidgetBackground *nwb = dynamic_cast<NWidgetBackground *>(*dest);
 
				if (nwb != NULL) nwb->SetPIP(parts->u.pip.pre,  parts->u.pip.inter, parts->u.pip.post);
 
				break;
 
			}
 

	
 
			case WPT_ENDCONTAINER:
 
				return num_used;
 

	
 
			case NWID_VIEWPORT:
 
				if (*dest != NULL) return num_used;
 
				*dest = new NWidgetViewport(parts->u.widget.index);
 
				*biggest_index = max(*biggest_index, (int)parts->u.widget.index);
 
				break;
 

	
 
			case NWID_SELECTION: {
 
				if (*dest != NULL) return num_used;
 
				NWidgetStacked *nws = new NWidgetStacked();
 
				*dest = nws;
 
				*fill_dest = true;
 
				nws->SetIndex(parts->u.widget.index);
 
				*biggest_index = max(*biggest_index, (int)parts->u.widget.index);
 
				break;
 
			}
 

	
 
			default:
 
				if (*dest != NULL) return num_used;
 
				assert((parts->type & WWT_MASK) < WWT_LAST || parts->type == NWID_BUTTON_DROPDOWN || parts->type == NWID_BUTTON_ARROW);
 
				*dest = new NWidgetLeaf(parts->type, parts->u.widget.colour, parts->u.widget.index, 0x0, STR_NULL);
 
				*biggest_index = max(*biggest_index, (int)parts->u.widget.index);
 
				break;
 
		}
 
		num_used++;
 
		parts++;
 
	}
 

	
 
	return num_used;
 
}
 

	
 
/**
 
 * Build a nested widget tree by recursively filling containers with nested widgets read from their parts.
 
 * @param parts  Array with parts of the nested widgets.
 
 * @param count  Length of the \a parts array.
 
 * @param parent Container to use for storing the child widgets.
 
 * @param biggest_index Pointer to biggest nested widget index in the tree.
 
 * @return Number of widget part elements used to fill the container.
 
 * @postcond \c *biggest_index contains the largest widget index of the tree and \c -1 if no index is used.
 
 */
 
static int MakeWidgetTree(const NWidgetPart *parts, int count, NWidgetBase *parent, int *biggest_index)
 
{
 
	/* Given parent must be either a #NWidgetContainer or a #NWidgetBackground object. */
 
	NWidgetContainer *nwid_cont = dynamic_cast<NWidgetContainer *>(parent);
 
	NWidgetBackground *nwid_parent = dynamic_cast<NWidgetBackground *>(parent);
 
	assert((nwid_cont != NULL && nwid_parent == NULL) || (nwid_cont == NULL && nwid_parent != NULL));
 

	
 
	int total_used = 0;
 
	while (true) {
 
		NWidgetBase *sub_widget = NULL;
 
		bool fill_sub = false;
 
		int num_used = MakeNWidget(parts, count - total_used, &sub_widget, &fill_sub, biggest_index);
 
		parts += num_used;
 
		total_used += num_used;
 

	
 
		/* Break out of loop when end reached */
 
		if (sub_widget == NULL) break;
 

	
 
		/* Add sub_widget to parent container. */
 
		if (nwid_cont) nwid_cont->Add(sub_widget);
 
		if (nwid_parent) nwid_parent->Add(sub_widget);
 

	
 
		/* If sub-widget is a container, recursively fill that container. */
 
		WidgetType tp = sub_widget->type;
 
		if (fill_sub && (tp == NWID_HORIZONTAL || tp == NWID_HORIZONTAL_LTR || tp == NWID_VERTICAL
 
							|| tp == WWT_PANEL || tp == WWT_FRAME || tp == WWT_INSET || tp == NWID_SELECTION)) {
 
			int num_used = MakeWidgetTree(parts, count - total_used, sub_widget, biggest_index);
 
			parts += num_used;
 
			total_used += num_used;
 
		}
 
	}
 

	
 
	if (count == total_used) return total_used; // Reached the end of the array of parts?
 

	
 
	assert(total_used < count);
 
	assert(parts->type == WPT_ENDCONTAINER);
 
	return total_used + 1; // *parts is also 'used'
 
}
 

	
 
/**
 
 * Construct a nested widget tree from an array of parts.
 
 * @param parts Array with parts of the widgets.
 
 * @param count Length of the \a parts array.
 
 * @param biggest_index Pointer to biggest nested widget index collected in the tree.
 
 * @param container Container to add the nested widgets to. In case it is NULL a vertical container is used.
 
 * @return Root of the nested widget tree, a vertical container containing the entire GUI.
 
 * @ingroup NestedWidgetParts
 
 * @pre \c biggest_index != NULL
 
 * @post \c *biggest_index contains the largest widget index of the tree and \c -1 if no index is used.
 
 */
 
NWidgetContainer *MakeNWidgets(const NWidgetPart *parts, int count, int *biggest_index, NWidgetContainer *container)
 
{
 
	*biggest_index = -1;
 
	if (container == NULL) container = new NWidgetVertical();
 
	MakeWidgetTree(parts, count, container, biggest_index);
 
	return container;
 
}
src/widget_type.h
Show inline comments
 
/* $Id$ */
 

	
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file widget_type.h Definitions about widgets. */
 

	
 
#ifndef WIDGET_TYPE_H
 
#define WIDGET_TYPE_H
 

	
 
#include "core/bitmath_func.hpp"
 
#include "strings_type.h"
 
#include "gfx_type.h"
 

	
 
enum {
 
	WIDGET_LIST_END = -1, ///< indicate the end of widgets' list for vararg functions
 
};
 

	
 
/** Bits of the #WWT_MATRIX widget data. */
 
enum MatrixWidgetValues {
 
	/* Number of column bits of the WWT_MATRIX widget data. */
 
	MAT_COL_START = 0, ///< Lowest bit of the number of columns.
 
	MAT_COL_BITS  = 8, ///< Number of bits for the number of columns in the matrix.
 

	
 
	/* Number of row bits of the WWT_MATRIX widget data. */
 
	MAT_ROW_START = 8, ///< Lowest bit of the number of rows.
 
	MAT_ROW_BITS  = 8, ///< Number of bits for the number of rows in the matrix.
 
};
 

	
 
/** Values for an arrow widget */
 
enum ArrowWidgetValues {
 
	AWV_DECREASE, ///< Arrow to the left or in case of RTL to the right
 
	AWV_INCREASE, ///< Arrow to the right or in case of RTL to the left
 
	AWV_LEFT,     ///< Force the arrow to the left
 
	AWV_RIGHT,    ///< Force the arrow to the right
 
};
 

	
 
/**
 
 * Window widget types, nested widget types, and nested widget part types.
 
 */
 
enum WidgetType {
 
	/* Window widget types. */
 
	WWT_EMPTY,      ///< Empty widget, place holder to reserve space in widget array
 

	
 
	WWT_PANEL,      ///< Simple depressed panel
 
	WWT_INSET,      ///< Pressed (inset) panel, most commonly used as combo box _text_ area
 
	WWT_IMGBTN,     ///< Button with image
 
	WWT_IMGBTN_2,   ///< Button with diff image when clicked
 

	
 
	WWT_TEXTBTN,    ///< Button with text
 
	WWT_TEXTBTN_2,  ///< Button with diff text when clicked
 
	WWT_LABEL,      ///< Centered label
 
	WWT_TEXT,       ///< Pure simple text
 
	WWT_MATRIX,     ///< Grid of rows and columns. @see MatrixWidgetValues
 
	WWT_SCROLLBAR,  ///< Vertical scrollbar
 
	WWT_FRAME,      ///< Frame
 
	WWT_CAPTION,    ///< Window caption (window title between closebox and stickybox)
 

	
 
	WWT_HSCROLLBAR, ///< Horizontal scrollbar
 
	WWT_STICKYBOX,  ///< Sticky box (normally at top-right of a window)
 
	WWT_SCROLL2BAR, ///< 2nd vertical scrollbar
 
	WWT_RESIZEBOX,  ///< Resize box (normally at bottom-right of a window)
 
	WWT_CLOSEBOX,   ///< Close box (at top-left of a window)
 
	WWT_DROPDOWN,   ///< Drop down list
 
	WWT_EDITBOX,    ///< a textbox for typing
 
	WWT_LAST,       ///< Last Item. use WIDGETS_END to fill up padding!!
 

	
 
	/* Nested widget types. */
 
	NWID_HORIZONTAL,      ///< Horizontal container.
 
	NWID_HORIZONTAL_LTR,  ///< Horizontal container that doesn't change the order of the widgets for RTL languages.
 
	NWID_VERTICAL,        ///< Vertical container.
 
	NWID_SPACER,          ///< Invisible widget that takes some space.
 
	NWID_SELECTION,       ///< Stacked widgets, only one visible at a time (eg in a panel with tabs).
 
	NWID_VIEWPORT,        ///< Nested widget containing a viewport.
 
	NWID_BUTTON_DROPDOWN, ///< Button with a drop-down.
 
	NWID_BUTTON_ARROW,    ///< Button with an arrow
 

	
 
	/* Nested widget part types. */
 
	WPT_RESIZE,       ///< Widget part for specifying resizing.
 
	WPT_MINSIZE,      ///< Widget part for specifying minimal size.
 
	WPT_MINTEXTLINES, ///< Widget part for specifying minimal number of lines of text.
 
	WPT_FILL,         ///< Widget part for specifying fill.
 
	WPT_DATATIP,      ///< Widget part for specifying data and tooltip.
 
	WPT_PADDING,      ///< Widget part for specifying a padding.
 
	WPT_PIPSPACE,     ///< Widget part for specifying pre/inter/post space for containers.
 
	WPT_ENDCONTAINER, ///< Widget part to denote end of a container.
 
	WPT_FUNCTION,     ///< Widget part for calling a user function.
 

	
 
	/* Pushable window widget types. */
 
	WWT_MASK = 0x7F,
 

	
 
	WWB_PUSHBUTTON  = 1 << 7,
 

	
 
	WWT_PUSHBTN     = WWT_PANEL   | WWB_PUSHBUTTON,
 
	WWT_PUSHTXTBTN  = WWT_TEXTBTN | WWB_PUSHBUTTON,
 
	WWT_PUSHIMGBTN  = WWT_IMGBTN  | WWB_PUSHBUTTON,
 
};
 

	
 
/** Different forms of sizing nested widgets, using NWidgetBase::AssignSizePosition() */
 
enum SizingType {
 
	ST_SMALLEST, ///< Initialize nested widget tree to smallest size. Also updates \e current_x and \e current_y.
 
	ST_RESIZE,   ///< Resize the nested widget tree.
 
};
 

	
 
/* Forward declarations. */
 
class NWidgetCore;
 
class Scrollbar;
 

	
 
/**
 
 * Baseclass for nested widgets.
 
 * @invariant After initialization, \f$current\_x = smallest\_x + n * resize\_x, for n \geq 0\f$.
 
 * @invariant After initialization, \f$current\_y = smallest\_y + m * resize\_y, for m \geq 0\f$.
 
 * @ingroup NestedWidgets
 
 */
 
class NWidgetBase : public ZeroedMemoryAllocator {
 
public:
 
	NWidgetBase(WidgetType tp);
 

	
 
	virtual void SetupSmallestSize(Window *w, bool init_array) = 0;
 
	virtual void AssignSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height, bool rtl) = 0;
 

	
 
	virtual void FillNestedArray(NWidgetBase **array, uint length) = 0;
 

	
 
	virtual NWidgetCore *GetWidgetFromPos(int x, int y) = 0;
 
	virtual NWidgetBase *GetWidgetOfType(WidgetType tp);
 

	
 
	/**
 
	 * Set additional space (padding) around the widget.
 
	 * @param top    Amount of additional space above the widget.
 
	 * @param right  Amount of additional space right of the widget.
 
	 * @param bottom Amount of additional space below the widget.
 
	 * @param left   Amount of additional space left of the widget.
 
	 */
 
	inline void SetPadding(uint8 top, uint8 right, uint8 bottom, uint8 left)
 
	{
 
		this->padding_top = top;
 
		this->padding_right = right;
 
		this->padding_bottom = bottom;
 
		this->padding_left = left;
 
	};
 

	
 
	inline uint GetHorizontalStepSize(SizingType sizing) const;
 
	inline uint GetVerticalStepSize(SizingType sizing) const;
 

	
 
	virtual void Draw(const Window *w) = 0;
 
	virtual void SetDirty(const Window *w) const;
 

	
 
	WidgetType type;      ///< Type of the widget / nested widget.
 
	bool fill_x;          ///< Allow horizontal filling from initial size.
 
	bool fill_y;          ///< Allow vertical filling from initial size.
 
	uint fill_x;          ///< Horizontal fill stepsize (from initial size, \c 0 means not resizable).
 
	uint fill_y;          ///< Vertical fill stepsize (from initial size, \c 0 means not resizable).
 
	uint resize_x;        ///< Horizontal resize step (\c 0 means not resizable).
 
	uint resize_y;        ///< Vertical resize step (\c 0 means not resizable).
 
	/* Size of the widget in the smallest window possible.
 
	 * Computed by #SetupSmallestSize() followed by #AssignSizePosition().
 
	 */
 
	uint smallest_x;      ///< Smallest horizontal size of the widget in a filled window.
 
	uint smallest_y;      ///< Smallest vertical size of the widget in a filled window.
 
	/* Current widget size (that is, after resizing). */
 
	uint current_x;       ///< Current horizontal size (after resizing).
 
	uint current_y;       ///< Current vertical size (after resizing).
 

	
 
	uint pos_x;           ///< Horizontal position of top-left corner of the widget in the window.
 
	uint pos_y;           ///< Vertical position of top-left corner of the widget in the window.
 

	
 
	NWidgetBase *next;    ///< Pointer to next widget in container. Managed by parent container widget.
 
	NWidgetBase *prev;    ///< Pointer to previous widget in container. Managed by parent container widget.
 

	
 
	uint8 padding_top;    ///< Paddings added to the top of the widget. Managed by parent container widget.
 
	uint8 padding_right;  ///< Paddings added to the right of the widget. Managed by parent container widget.
 
	uint8 padding_bottom; ///< Paddings added to the bottom of the widget. Managed by parent container widget.
 
	uint8 padding_left;   ///< Paddings added to the left of the widget. Managed by parent container widget.
 

	
 
protected:
 
	inline void StoreSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height);
 
};
 

	
 
/**
 
 * Get the horizontal sizing step.
 
 * @param sizing Type of resize being performed.
 
 */
 
inline uint NWidgetBase::GetHorizontalStepSize(SizingType sizing) const
 
{
 
	if (sizing == ST_RESIZE) return this->resize_x;
 
	return this->fill_x ? 1 : 0;
 
	return (sizing == ST_RESIZE) ? this->resize_x : this->fill_x;
 
}
 

	
 
/**
 
 * Get the vertical sizing step.
 
 * @param sizing Type of resize being performed.
 
 */
 
inline uint NWidgetBase::GetVerticalStepSize(SizingType sizing) const
 
{
 
	if (sizing == ST_RESIZE) return this->resize_y;
 
	return this->fill_y ? 1 : 0;
 
	return (sizing == ST_RESIZE) ? this->resize_y : this->fill_y;
 
}
 

	
 
/** Base class for a resizable nested widget.
 
 * @ingroup NestedWidgets */
 
class NWidgetResizeBase : public NWidgetBase {
 
public:
 
	NWidgetResizeBase(WidgetType tp, bool fill_x, bool fill_y);
 
	NWidgetResizeBase(WidgetType tp, uint fill_x, uint fill_y);
 

	
 
	void SetMinimalSize(uint min_x, uint min_y);
 
	void SetMinimalTextLines(uint8 min_lines, uint8 spacing, FontSize size);
 
	void SetFill(bool fill_x, bool fill_y);
 
	void SetFill(uint fill_x, uint fill_y);
 
	void SetResize(uint resize_x, uint resize_y);
 

	
 
	void AssignSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height, bool rtl);
 

	
 
	uint min_x; ///< Minimal horizontal size of only this widget.
 
	uint min_y; ///< Minimal vertical size of only this widget.
 
};
 

	
 
/** Nested widget flags that affect display and interaction withe 'real' widgets. */
 
enum NWidgetDisplay {
 
	/* Generic. */
 
	NDB_LOWERED         = 0, ///< Widget is lowered (pressed down) bit.
 
	NDB_DISABLED        = 1, ///< Widget is disabled (greyed out) bit.
 
	/* Viewport widget. */
 
	NDB_NO_TRANSPARENCY = 2, ///< Viewport is never transparent.
 
	NDB_SHADE_GREY      = 3, ///< Shade viewport to grey-scale.
 
	NDB_SHADE_DIMMED    = 4, ///< Display dimmed colours in the viewport.
 
	/* Button dropdown widget. */
 
	NDB_DROPDOWN_ACTIVE = 5, ///< Dropdown menu of the button dropdown widget is active. @see #NWID_BUTTON_DRPDOWN
 

	
 
	ND_LOWERED  = 1 << NDB_LOWERED,                ///< Bit value of the lowered flag.
 
	ND_DISABLED = 1 << NDB_DISABLED,               ///< Bit value of the disabled flag.
 
	ND_NO_TRANSPARENCY = 1 << NDB_NO_TRANSPARENCY, ///< Bit value of the 'no transparency' flag.
 
	ND_SHADE_GREY      = 1 << NDB_SHADE_GREY,      ///< Bit value of the 'shade to grey' flag.
 
	ND_SHADE_DIMMED    = 1 << NDB_SHADE_DIMMED,    ///< Bit value of the 'dimmed colours' flag.
 
	ND_DROPDOWN_ACTIVE = 1 << NDB_DROPDOWN_ACTIVE, ///< Bit value of the 'dropdown active' flag.
 
};
 
DECLARE_ENUM_AS_BIT_SET(NWidgetDisplay);
 

	
 
/** Base class for a 'real' widget.
 
 * @ingroup NestedWidgets */
 
class NWidgetCore : public NWidgetResizeBase {
 
public:
 
	NWidgetCore(WidgetType tp, Colours colour, bool def_fill_x, bool def_fill_y, uint16 widget_data, StringID tool_tip);
 
	NWidgetCore(WidgetType tp, Colours colour, uint fill_x, uint fill_y, uint16 widget_data, StringID tool_tip);
 

	
 
	void SetIndex(int index);
 
	void SetDataTip(uint16 widget_data, StringID tool_tip);
 

	
 
	inline void SetLowered(bool lowered);
 
	inline bool IsLowered() const;
 
	inline void SetDisabled(bool disabled);
 
	inline bool IsDisabled() const;
 

	
 
	/* virtual */ void FillNestedArray(NWidgetBase **array, uint length);
 
	/* virtual */ NWidgetCore *GetWidgetFromPos(int x, int y);
 

	
 
	virtual Scrollbar *FindScrollbar(Window *w, bool allow_next = true) = 0;
 

	
 
	NWidgetDisplay disp_flags; ///< Flags that affect display and interaction with the widget.
 
	Colours colour;            ///< Colour of this widget.
 
	int index;                 ///< Index of the nested widget in the widget array of the window (\c -1 means 'not used').
 
	uint16 widget_data;        ///< Data of the widget. @see Widget::data
 
	StringID tool_tip;         ///< Tooltip of the widget. @see Widget::tootips
 
};
 

	
 
/**
 
 * Lower or raise the widget.
 
 * @param lowered Widget must be lowered (drawn pressed down).
 
 */
 
inline void NWidgetCore::SetLowered(bool lowered)
 
{
 
	this->disp_flags = lowered ? SETBITS(this->disp_flags, ND_LOWERED) : CLRBITS(this->disp_flags, ND_LOWERED);
 
}
 

	
 
/** Return whether the widget is lowered. */
 
inline bool NWidgetCore::IsLowered() const
 
{
 
	return HasBit(this->disp_flags, NDB_LOWERED);
 
}
 

	
 
/**
 
 * Disable (grey-out) or enable the widget.
 
 * @param disabled Widget must be disabled.
 
 */
 
inline void NWidgetCore::SetDisabled(bool disabled)
 
{
 
	this->disp_flags = disabled ? SETBITS(this->disp_flags, ND_DISABLED) : CLRBITS(this->disp_flags, ND_DISABLED);
 
}
 

	
 
/** Return whether the widget is disabled. */
 
inline bool NWidgetCore::IsDisabled() const
 
{
 
	return HasBit(this->disp_flags, NDB_DISABLED);
 
}
 

	
 

	
 
/** Baseclass for container widgets.
 
 * @ingroup NestedWidgets */
 
class NWidgetContainer : public NWidgetBase {
 
public:
 
	NWidgetContainer(WidgetType tp);
 
	~NWidgetContainer();
 

	
 
	void Add(NWidgetBase *wid);
 
	/* virtual */ void FillNestedArray(NWidgetBase **array, uint length);
 

	
 
	/** Return whether the container is empty. */
 
	inline bool IsEmpty() { return head == NULL; };
 

	
 
	/* virtual */ NWidgetBase *GetWidgetOfType(WidgetType tp);
 

	
 
protected:
 
	NWidgetBase *head; ///< Pointer to first widget in container.
 
	NWidgetBase *tail; ///< Pointer to last widget in container.
 
};
 

	
 
static const int STACKED_SELECTION_ZERO_SIZE = INT_MAX; ///< Display plane value for getting a zero-size widget.
 

	
 
/** Stacked widgets, widgets all occupying the same space in the window.
 
 * #NWID_SELECTION allows for selecting one of several panels (planes) to tbe displayed. All planes must have the same size.
 
 * Since all planes are also initialized, switching between different planes can be done while the window is displayed.
 
 *
 
 * There is also a special plane #STACKED_SELECTION_ZERO_SIZE which always has zero size, and is not resizable or fillable. It is used to make all child
 
 * planes of the widget disappear. Unlike the regular display planes, switching from or to the #STACKED_SELECTION_ZERO_SIZE plane means that a
 
 * #Windows::ReInit() is needed to re-initialize the window.
 
 */
 
class NWidgetStacked : public NWidgetContainer {
 
public:
 
	NWidgetStacked();
 

	
 
	void SetIndex(int index);
 

	
 
	void SetupSmallestSize(Window *w, bool init_array);
 
	void AssignSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height, bool rtl);
 
	/* virtual */ void FillNestedArray(NWidgetBase **array, uint length);
 

	
 
	/* virtual */ void Draw(const Window *w);
 
	/* virtual */ NWidgetCore *GetWidgetFromPos(int x, int y);
 

	
 
	void SetDisplayedPlane(int plane);
 

	
 
	int shown_plane; ///< Plane being displayed (for #NWID_SELECTION only).
 
	int index;       ///< If non-negative, index in the #Window::nested_array.
 
};
 

	
 
/** Nested widget container flags, */
 
enum NWidContainerFlags {
 
	NCB_EQUALSIZE = 0, ///< Containers should keep all their (resizing) children equally large.
 

	
 
	NC_NONE = 0,                       ///< All flags cleared.
 
	NC_EQUALSIZE = 1 << NCB_EQUALSIZE, ///< Value of the #NCB_EQUALSIZE flag.
 
};
 
DECLARE_ENUM_AS_BIT_SET(NWidContainerFlags);
 

	
 
/** Container with pre/inter/post child space. */
 
class NWidgetPIPContainer : public NWidgetContainer {
 
public:
 
	NWidgetPIPContainer(WidgetType tp, NWidContainerFlags flags = NC_NONE);
 

	
 
	void SetPIP(uint8 pip_pre, uint8 pip_inter, uint8 pip_post);
 

	
 
	/* virtual */ void Draw(const Window *w);
 
	/* virtual */ NWidgetCore *GetWidgetFromPos(int x, int y);
 

	
 
protected:
 
	NWidContainerFlags flags; ///< Flags of the container.
 
	uint8 pip_pre;            ///< Amount of space before first widget.
 
	uint8 pip_inter;          ///< Amount of space between widgets.
 
	uint8 pip_post;           ///< Amount of space after last widget.
 
};
 

	
 
/** Horizontal container.
 
 * @ingroup NestedWidgets */
 
class NWidgetHorizontal : public NWidgetPIPContainer {
 
public:
 
	NWidgetHorizontal(NWidContainerFlags flags = NC_NONE);
 

	
 
	void SetupSmallestSize(Window *w, bool init_array);
 
	void AssignSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height, bool rtl);
 
};
 

	
 
/** Horizontal container that doesn't change the direction of the widgets for RTL languages.
 
 * @ingroup NestedWidgets */
 
class NWidgetHorizontalLTR : public NWidgetHorizontal {
 
public:
 
	NWidgetHorizontalLTR(NWidContainerFlags flags = NC_NONE);
 

	
 
	void AssignSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height, bool rtl);
 
};
 

	
 
/** Vertical container.
 
 * @ingroup NestedWidgets */
 
class NWidgetVertical : public NWidgetPIPContainer {
 
public:
 
	NWidgetVertical(NWidContainerFlags flags = NC_NONE);
 

	
 
	void SetupSmallestSize(Window *w, bool init_array);
 
	void AssignSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height, bool rtl);
 
};
 

	
 

	
 
/** Spacer widget.
 
 * @ingroup NestedWidgets */
 
class NWidgetSpacer : public NWidgetResizeBase {
 
public:
 
	NWidgetSpacer(int length, int height);
 

	
 
	void SetupSmallestSize(Window *w, bool init_array);
 
	/* virtual */ void FillNestedArray(NWidgetBase **array, uint length);
 

	
 
	/* virtual */ void Draw(const Window *w);
 
	/* virtual */ void SetDirty(const Window *w) const;
 
	/* virtual */ NWidgetCore *GetWidgetFromPos(int x, int y);
 
};
 

	
 
/** Nested widget with a child.
 
 * @ingroup NestedWidgets */
 
class NWidgetBackground : public NWidgetCore {
 
public:
 
	NWidgetBackground(WidgetType tp, Colours colour, int index, NWidgetPIPContainer *child = NULL);
 
	~NWidgetBackground();
 

	
 
	void Add(NWidgetBase *nwid);
 
	void SetPIP(uint8 pip_pre, uint8 pip_inter, uint8 pip_post);
 

	
 
	void SetupSmallestSize(Window *w, bool init_array);
 
	void AssignSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height, bool rtl);
 

	
 
	/* virtual */ void FillNestedArray(NWidgetBase **array, uint length);
 

	
 
	/* virtual */ void Draw(const Window *w);
 
	/* virtual */ NWidgetCore *GetWidgetFromPos(int x, int y);
 
	/* virtual */ NWidgetBase *GetWidgetOfType(WidgetType tp);
 
	/* virtual */ Scrollbar *FindScrollbar(Window *w, bool allow_next = true);
 

	
 
private:
 
	NWidgetPIPContainer *child; ///< Child widget.
 
};
 

	
 
/**
 
 * Nested widget to display a viewport in a window.
 
 * After initializing the nested widget tree, call #InitializeViewport(). After changing the window size,
 
 * call #UpdateViewportCoordinates() eg from Window::OnResize().
 
 * If the #display_flags field contains the #ND_NO_TRANSPARENCY bit, the viewport will disable transparency.
 
 * Shading to grey-scale is controlled with the #ND_SHADE_GREY bit (used for B&W news papers), the #ND_SHADE_DIMMED gives dimmed colours (for colour news papers).
 
 * @todo Class derives from #NWidgetCore, but does not use #colour, #widget_data, or #tool_tip.
 
 * @ingroup NestedWidgets */
 
class NWidgetViewport : public NWidgetCore {
 
public:
 
	NWidgetViewport(int index);
 

	
 
	/* virtual */ void SetupSmallestSize(Window *w, bool init_array);
 
	/* virtual */ void Draw(const Window *w);
 
	/* virtual */ Scrollbar *FindScrollbar(Window *w, bool allow_next = true);
 

	
 
	void InitializeViewport(Window *w, uint32 follow_flags, ZoomLevel zoom);
 
	void UpdateViewportCoordinates(Window *w);
 
};
 

	
 
/** Leaf widget.
 
 * @ingroup NestedWidgets */
 
class NWidgetLeaf : public NWidgetCore {
 
public:
 
	NWidgetLeaf(WidgetType tp, Colours colour, int index, uint16 data, StringID tip);
 

	
 
	/* virtual */ void SetupSmallestSize(Window *w, bool init_array);
 
	/* virtual */ void Draw(const Window *w);
 
	/* virtual */ Scrollbar *FindScrollbar(Window *w, bool allow_next = true);
 

	
 
	bool ButtonHit(const Point &pt);
 

	
 
	static void InvalidateDimensionCache();
 
private:
 
	static Dimension stickybox_dimension; ///< Cached size of a stickybox widget.
 
	static Dimension resizebox_dimension; ///< Cached size of a resizebox widget.
 
	static Dimension closebox_dimension;  ///< Cached size of a closebox widget.
 
};
 

	
 
/**
 
 * @defgroup NestedWidgetParts Hierarchical widget parts
 
 * To make nested widgets easier to enter, nested widget parts have been created. They allow the tree to be defined in a flat array of parts.
 
 *
 
 * - Leaf widgets start with a #NWidget(WidgetType tp, Colours col, int16 idx) part.
 
 *   Next, specify its properties with one or more of
 
 *   - #SetMinimalSize Define the minimal size of the widget.
 
 *   - #SetFill Define how the widget may grow to make it nicely.
 
 *   - #SetDataTip Define the data and the tooltip of the widget.
 
 *   - #SetResize Define how the widget may resize.
 
 *   - #SetPadding Create additional space around the widget.
 
 *
 
 * - To insert a nested widget tree from an external source, nested widget part #NWidgetFunction exists.
 
 *   For further customization, the #SetPadding part may be used.
 
 *
 
 * - Space widgets (#NWidgetSpacer) start with a #NWidget(WidgetType tp), followed by one or more of
 
 *   - #SetMinimalSize Define the minimal size of the widget.
 
 *   - #SetFill Define how the widget may grow to make it nicely.
 
 *   - #SetResize Define how the widget may resize.
 
 *   - #SetPadding Create additional space around the widget.
 
 *
 
 * - Container widgets #NWidgetHorizontal, #NWidgetHorizontalLTR, and #NWidgetVertical, start with a #NWidget(WidgetType tp) part.
 
 *   Their properties are derived from the child widgets so they cannot be specified.
 
 *   You can however use
 
 *   - #SetPadding Define additional padding around the container.
 
 *   - #SetPIP Set additional pre/inter/post child widget space.
 
 *   .
 
 *   Underneath these properties, all child widgets of the container must be defined. To denote that they are childs, add an indent before the nested widget parts of
 
 *   the child widgets (it has no meaning for the compiler but it makes the widget parts easier to read).
 
 *   Below the last child widget, use an #EndContainer part. This part should be aligned with the #NWidget part that started the container.
 
 *
 
 * - Stacked widgets #NWidgetStacked map each of their childs onto the same space. It behaves like a container, except there is no pre/inter/post space,
 
 *   so the widget does not support #SetPIP. #SetPadding is allowed though.
 
 *   Like the other container widgets, below the last child widgets, a #EndContainer part should be used to denote the end of the stacked widget.
 
 *
 
 * - Background widgets #NWidgetBackground start with a #NWidget(WidgetType tp, Colours col, int16 idx) part.
 
 *   What follows depends on how the widget is used.
 
 *   - If the widget is used as a leaf widget, that is, to create some space in the window to display a viewport or some text, use the properties of the
 
 *     leaf widgets to define how it behaves.
 
 *   - If the widget is used a background behind other widgets, it is considered to be a container widgets. Use the properties listed there to define its
 
 *     behaviour.
 
 *   .
 
 *   In both cases, the background widget \b MUST end with a #EndContainer widget part.
 
 *
 
 * @see NestedWidgets
 
 */
 

	
 
/** Widget part for storing data and tooltip information.
 
 * @ingroup NestedWidgetParts */
 
struct NWidgetPartDataTip {
 
	uint16 data;      ///< Data value of the widget.
 
	StringID tooltip; ///< Tooltip of the widget.
 
};
 

	
 
/** Widget part for storing basic widget information.
 
 * @ingroup NestedWidgetParts */
 
struct NWidgetPartWidget {
 
	Colours colour; ///< Widget colour.
 
	int16 index;    ///< Widget index in the widget array.
 
};
 

	
 
/** Widget part for storing padding.
 
 * @ingroup NestedWidgetParts */
 
struct NWidgetPartPaddings {
 
	uint8 top, right, bottom, left; ///< Paddings for all directions.
 
};
 

	
 
/** Widget part for storing pre/inter/post spaces.
 
 * @ingroup NestedWidgetParts */
 
struct NWidgetPartPIP {
 
	uint8 pre, inter, post; ///< Amount of space before/between/after child widgets.
 
};
 

	
 
/** Widget part for storing minimal text line data.
 
 * @ingroup NestedWidgetParts */
 
struct NWidgetPartTextLines {
 
	uint8 lines;   ///< Number of text lines.
 
	uint8 spacing; ///< Extra spacing around lines.
 
	FontSize size; ///< Font size of text lines.
 
};
 

	
 
/** Pointer to function returning a nested widget.
 
 * @param biggest_index Pointer to storage for collecting the biggest index used in the nested widget.
 
 * @return Nested widget (tree).
 
 * @postcond \c *biggest_index must contain the value of the biggest index in the returned tree.
 
 */
 
typedef NWidgetBase *NWidgetFunctionType(int *biggest_index);
 

	
 
/** Partial widget specification to allow NWidgets to be written nested.
 
 * @ingroup NestedWidgetParts */
 
struct NWidgetPart {
 
	WidgetType type;                         ///< Type of the part. @see NWidgetPartType.
 
	union {
 
		Point xy;                        ///< Part with an x/y size.
 
		NWidgetPartDataTip data_tip;     ///< Part with a data/tooltip.
 
		NWidgetPartWidget widget;        ///< Part with a start of a widget.
 
		NWidgetPartPaddings padding;     ///< Part with paddings.
 
		NWidgetPartPIP pip;              ///< Part with pre/inter/post spaces.
 
		NWidgetPartTextLines text_lines; ///< Part with text line data.
 
		NWidgetFunctionType *func_ptr;   ///< Part with a function call.
 
		NWidContainerFlags cont_flags;   ///< Part with container flags.
 
	} u;
 
};
 

	
 
/**
 
 * Widget part function for setting the resize step.
 
 * @param dx Horizontal resize step. 0 means no horizontal resizing.
 
 * @param dy Vertical resize step. 0 means no vertical resizing.
 
 * @ingroup NestedWidgetParts
 
 */
 
static inline NWidgetPart SetResize(int16 dx, int16 dy)
 
{
 
	NWidgetPart part;
 

	
 
	part.type = WPT_RESIZE;
 
	part.u.xy.x = dx;
 
	part.u.xy.y = dy;
 

	
 
	return part;
 
}
 

	
 
/**
 
 * Widget part function for setting the minimal size.
 
 * @param x Horizontal minimal size.
 
 * @param y Vertical minimal size.
 
 * @ingroup NestedWidgetParts
 
 */
 
static inline NWidgetPart SetMinimalSize(int16 x, int16 y)
 
{
 
	NWidgetPart part;
 

	
 
	part.type = WPT_MINSIZE;
 
	part.u.xy.x = x;
 
	part.u.xy.y = y;
 

	
 
	return part;
 
}
 

	
 
/**
 
 * Widget part function for setting the minimal text lines.
 
 * @param lines   Number of text lines.
 
 * @param spacing Extra spacing required.
 
 * @param size    Font size of text.
 
 * @ingroup NestedWidgetParts
 
 */
 
static inline NWidgetPart SetMinimalTextLines(uint8 lines, uint8 spacing, FontSize size = FS_NORMAL)
 
{
 
	NWidgetPart part;
 

	
 
	part.type = WPT_MINTEXTLINES;
 
	part.u.text_lines.lines = lines;
 
	part.u.text_lines.spacing = spacing;
 
	part.u.text_lines.size = size;
 

	
 
	return part;
 
}
 

	
 
/**
 
 * Widget part function for setting filling.
 
 * @param x_fill Allow horizontal filling from minimal size.
 
 * @param y_fill Allow vertical filling from minimal size.
 
 * @ingroup NestedWidgetParts
 
 */
 
static inline NWidgetPart SetFill(bool x_fill, bool y_fill)
 
static inline NWidgetPart SetFill(uint fill_x, uint fill_y)
 
{
 
	NWidgetPart part;
 

	
 
	part.type = WPT_FILL;
 
	part.u.xy.x = x_fill;
 
	part.u.xy.y = y_fill;
 
	part.u.xy.x = fill_x;
 
	part.u.xy.y = fill_y;
 

	
 
	return part;
 
}
 

	
 
/**
 
 * Widget part function for denoting the end of a container
 
 * (horizontal, vertical, WWT_FRAME, WWT_INSET, or WWT_PANEL).
 
 * @ingroup NestedWidgetParts
 
 */
 
static inline NWidgetPart EndContainer()
 
{
 
	NWidgetPart part;
 

	
 
	part.type = WPT_ENDCONTAINER;
 

	
 
	return part;
 
}
 

	
 
/** Widget part function for setting the data and tooltip.
 
 * @param data Data of the widget.
 
 * @param tip  Tooltip of the widget.
 
 * @ingroup NestedWidgetParts
 
 */
 
static inline NWidgetPart SetDataTip(uint16 data, StringID tip)
 
{
 
	NWidgetPart part;
 

	
 
	part.type = WPT_DATATIP;
 
	part.u.data_tip.data = data;
 
	part.u.data_tip.tooltip = tip;
 

	
 
	return part;
 
}
 

	
 
/**
 
 * Widget part function for setting additional space around a widget.
 
 * Parameters start above the widget, and are specified in clock-wise direction.
 
 * @param top The padding above the widget.
 
 * @param right The padding right of the widget.
 
 * @param bottom The padding below the widget.
 
 * @param left The padding left of the widget.
 
 * @ingroup NestedWidgetParts
 
 */
 
static inline NWidgetPart SetPadding(uint8 top, uint8 right, uint8 bottom, uint8 left)
 
{
 
	NWidgetPart part;
 

	
 
	part.type = WPT_PADDING;
 
	part.u.padding.top = top;
 
	part.u.padding.right = right;
 
	part.u.padding.bottom = bottom;
 
	part.u.padding.left = left;
 

	
 
	return part;
 
}
 

	
 
/**
 
 * Widget part function for setting a padding.
 
 * @param padding The padding to use for all directions.
 
 * @ingroup NestedWidgetParts
 
 */
 
static inline NWidgetPart SetPadding(uint8 padding)
 
{
 
	return SetPadding(padding, padding, padding, padding);
 
}
 

	
 
/**
 
 * Widget part function for setting a pre/inter/post spaces.
 
 * @param pre The amount of space before the first widget.
 
 * @param inter The amount of space between widgets.
 
 * @param post The amount of space after the last widget.
 
 * @ingroup NestedWidgetParts
 
 */
 
static inline NWidgetPart SetPIP(uint8 pre, uint8 inter, uint8 post)
 
{
 
	NWidgetPart part;
 

	
 
	part.type = WPT_PIPSPACE;
 
	part.u.pip.pre = pre;
 
	part.u.pip.inter = inter;
 
	part.u.pip.post = post;
 

	
 
	return part;
 
}
 

	
 
/**
 
 * Widget part function for starting a new 'real' widget.
 
 * @param tp  Type of the new nested widget.
 
 * @param col Colour of the new widget.
 
 * @param idx Index of the widget in the widget array.
 
 * @note with #WWT_PANEL, #WWT_FRAME, #WWT_INSET, a new container is started.
 
 *       Child widgets must have a index bigger than the parent index.
 
 * @ingroup NestedWidgetParts
 
 */
 
static inline NWidgetPart NWidget(WidgetType tp, Colours col, int16 idx)
 
{
 
	NWidgetPart part;
 

	
 
	part.type = tp;
 
	part.u.widget.colour = col;
 
	part.u.widget.index = idx;
 

	
 
	return part;
 
}
 

	
 
/**
 
 * Widget part function for starting a new horizontal container, vertical container, or spacer widget.
 
 * @param tp         Type of the new nested widget, #NWID_HORIZONTAL(_LTR), #NWID_VERTICAL, #NWID_SPACER, #NWID_SELECTION, or #NWID_LAYERED.
 
 * @param cont_flags Flags for the containers (#NWID_HORIZONTAL(_LTR) and #NWID_VERTICAL).
 
 * @ingroup NestedWidgetParts
 
 */
 
static inline NWidgetPart NWidget(WidgetType tp, NWidContainerFlags cont_flags = NC_NONE)
 
{
 
	NWidgetPart part;
 

	
 
	part.type = tp;
 
	part.u.cont_flags = cont_flags;
 

	
 
	return part;
 
}
 

	
 
/**
 
 * Obtain a nested widget (sub)tree from an external source.
 
 * @param func_ptr Pointer to function that returns the tree.
 
 * @ingroup NestedWidgetParts
 
 */
 
static inline NWidgetPart NWidgetFunction(NWidgetFunctionType *func_ptr)
 
{
 
	NWidgetPart part;
 

	
 
	part.type = WPT_FUNCTION;
 
	part.u.func_ptr = func_ptr;
 

	
 
	return part;
 
}
 

	
 
NWidgetContainer *MakeNWidgets(const NWidgetPart *parts, int count, int *biggest_index, NWidgetContainer *container = NULL);
 

	
 
#endif /* WIDGET_TYPE_H */
0 comments (0 inline, 0 general)