code:
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#ifndef GUIINVENTORYMENU_HEADER
#define GUIINVENTORYMENU_HEADER

#include <utility>
#include <stack>

#include "irrlichttypes_extrabloated.h"
#include "inventorymanager.h"
#include "modalMenu.h"
#include "guiTable.h"
#include "network/networkprotocol.h"
#include "client/joystick_controller.h"
#include "util/string.h"
#include "util/enriched_string.h"

class InventoryManager;
class ISimpleTextureSource;
class Client;

typedef enum {
	f_Button,
	f_Table,
	f_TabHeader,
	f_CheckBox,
	f_DropDown,
	f_ScrollBar,
	f_Unknown
} FormspecFieldType;

typedef enum {
	quit_mode_no,
	quit_mode_accept,
	quit_mode_cancel
} FormspecQuitMode;

struct TextDest
{
	virtual ~TextDest() {}
	// This is deprecated I guess? -celeron55
	virtual void gotText(const std::wstring &text) {}
	virtual void gotText(const StringMap &fields) = 0;

	std::string m_formname;
};

class IFormSource
{
public:
	virtual ~IFormSource(){}
	virtual std::string getForm() = 0;
	// Fill in variables in field text
	virtual std::string resolveText(const std::string &str) { return str; }
};

class GUIFormSpecMenu : public GUIModalMenu
{
	struct ItemSpec
	{
		ItemSpec() :
			i(-1)
		{
		}

		ItemSpec(const InventoryLocation &a_inventoryloc,
				const std::string &a_listname,
				s32 a_i) :
			inventoryloc(a_inventoryloc),
			listname(a_listname),
			i(a_i)
		{
		}

		bool isValid() const { return i != -1; }

		InventoryLocation inventoryloc;
		std::string listname;
		s32 i;
	};

	struct ListDrawSpec
	{
		ListDrawSpec()
		{
		}
		ListDrawSpec(const InventoryLocation &a_inventoryloc,
				const std::string &a_listname,
				v2s32 a_pos, v2s32 a_geom, s32 a_start_item_i):
			inventoryloc(a_inventoryloc),
			listname(a_listname),
			pos(a_pos),
			geom(a_geom),
			start_item_i(a_start_item_i)
		{
		}

		InventoryLocation inventoryloc;
		std::string listname;
		v2s32 pos;
		v2s32 geom;
		s32 start_item_i;
	};

	struct ListRingSpec
	{
		ListRingSpec()
		{
		}
		ListRingSpec(const InventoryLocation &a_inventoryloc,
				const std::string &a_listname):
			inventoryloc(a_inventoryloc),
			listname(a_listname)
		{
		}

		InventoryLocation inventoryloc;
		std::string listname;
	};

	struct ImageDrawSpec
	{
		ImageDrawSpec():
			parent_button(NULL),
			clip(false)
		{
		}

		ImageDrawSpec(const std::string &a_name,
				const std::string &a_item_name,
				gui::IGUIButton *a_parent_button,
				const v2s32 &a_pos, const v2s32 &a_geom):
			name(a_name),
			item_name(a_item_name),
			parent_button(a_parent_button),
			pos(a_pos),
			geom(a_geom),
			scale(true),
			clip(false)
		{
		}

		ImageDrawSpec(const std::string &a_name,
				const std::string &a_item_name,
				const v2s32 &a_pos, const v2s32 &a_geom):
			name(a_name),
			item_name(a_item_name),
			parent_button(NULL),
			pos(a_pos),
			geom(a_geom),
			scale(true),
			clip(false)
		{
		}

		ImageDrawSpec(const std::string &a_name,
				const v2s32 &a_pos, const v2s32 &a_geom, bool clip=false):
			name(a_name),
			parent_button(NULL),
			pos(a_pos),
			geom(a_geom),
			scale(true),
			clip(clip)
		{
		}

		ImageDrawSpec(const std::string &a_name,
				const v2s32 &a_pos):
			name(a_name),
			parent_button(NULL),
			pos(a_pos),
			scale(false),
			clip(false)
		{
		}

		std::string name;
		std::string item_name;
		gui::IGUIButton *parent_button;
		v2s32 pos;
		v2s32 geom;
		bool scale;
		bool clip;
	};

	struct FieldSpec
	{
		FieldSpec()
		{
		}
		FieldSpec(const std::string &name, const std::wstring &label,
				const std::wstring &default_text, int id) :
			fname(name),
			flabel(label),
			fdefault(unescape_enriched(default_text)),
			fid(id),
			send(false),
			ftype(f_Unknown),
			is_exit(false)
		{
		}

		std::string fname;
		std::wstring flabel;
		std::wstring fdefault;
		int fid;
		bool send;
		FormspecFieldType ftype;
		bool is_exit;
		core::rect<s32> rect;
	};

	struct BoxDrawSpec
	{
		BoxDrawSpec(v2s32 a_pos, v2s32 a_geom,irr::video::SColor a_color):
			pos(a_pos),
			geom(a_geom),
			color(a_color)
		{
		}
		v2s32 pos;
		v2s32 geom;
		irr::video::SColor color;
	};

	struct TooltipSpec
	{
		TooltipSpec() {}
		TooltipSpec(const std::string &a_tooltip, irr::video::SColor a_bgcolor,
				irr::video::SColor a_color):
			tooltip(utf8_to_wide(a_tooltip)),
			bgcolor(a_bgcolor),
			color(a_color)
		{
		}

		std::wstring tooltip;
		irr::video::SColor bgcolor;
		irr::video::SColor color;
	};

	struct StaticTextSpec
	{
		StaticTextSpec():
			parent_button(NULL)
		{
		}

		StaticTextSpec(const std::wstring &a_text,
				const core::rect<s32> &a_rect):
			text(a_text),
			rect(a_rect),
			parent_button(NULL)
		{
		}

		StaticTextSpec(const std::wstring &a_text,
				const core::rect<s32> &a_rect,
				gui::IGUIButton *a_parent_button):
			text(a_text),
			rect(a_rect),
			parent_button(a_parent_button)
		{
		}

		std::wstring text;
		core::rect<s32> rect;
		gui::IGUIButton *parent_button;
	};

public:
	GUIFormSpecMenu(irr::IrrlichtDevice* dev,
			JoystickController *joystick,
			gui::IGUIElement* parent, s32 id,
			IMenuManager *menumgr,
			Client *client,
			ISimpleTextureSource *tsrc,
			IFormSource* fs_src,
			TextDest* txt_dst,
			bool remap_dbl_click = true);

	~GUIFormSpecMenu();

	void setFormSpec(const std::string &formspec_string,
			const InventoryLocation &current_inventory_location)
	{
		m_formspec_string = formspec_string;
		m_current_inventory_location = current_inventory_location;
		regenerateGui(m_screensize_old);
	}

	// form_src is deleted by this GUIFormSpecMenu
	void setFormSource(IFormSource *form_src)
	{
		if (m_form_src != NULL) {
			delete m_form_src;
		}
		m_form_src = form_src;
	}

	// text_dst is deleted by this GUIFormSpecMenu
	void setTextDest(TextDest *text_dst)
	{
		if (m_text_dst != NULL) {
			delete m_text_dst;
		}
		m_text_dst = text_dst;
	}

	void allowClose(bool value)
	{
		m_allowclose = value;
	}

	void lockSize(bool lock,v2u32 basescreensize=v2u32(0,0))
	{
		m_lock = lock;
		m_lockscreensize = basescreensize;
	}

	void removeChildren();
	void setInitialFocus();

	void setFocus(const std::string &elementname)
	{
		m_focused_element = elementname;
	}

	/*
		Remove and re-add (or reposition) stuff
	*/
	void regenerateGui(v2u32 screensize);

	ItemSpec getItemAtPos(v2s32 p) const;
	void drawList(const ListDrawSpec &s, int phase,	bool &item_hovered);
	void drawSelectedItem();
	void drawMenu();
	void updateSelectedItem();
	ItemStack verifySelectedItem();

	void acceptInput(FormspecQuitMode quitmode);
	bool preprocessEvent(const SEvent& event);
	bool OnEvent(const SEvent& event);
	bool doPause;
	bool pausesGame() { return doPause; }

	GUITable* getTable(const std::string &tablename);
	std::vector<std::string>* getDropDownValues(const std::string &name);

#ifdef __ANDROID__
	bool getAndroidUIInput();
#endif

protected:
	v2s32 getBasePos() const
	{
			return padding + offset + AbsoluteRect.UpperLeftCorner;
	}

	v2s32 padding;
	v2s32 spacing;
	v2s32 imgsize;
	v2s32 offset;
	v2s32 pos_offset;
	std::stack<v2s32> container_stack;

	irr::IrrlichtDevice* m_device;
	InventoryManager *m_invmgr;
	ISimpleTextureSource *m_tsrc;
	Client *m_client;

	std::string m_formspec_string;
	InventoryLocation m_current_inventory_location;

	std::vector<ListDrawSpec> m_inventorylists;
	std::vector<ListRingSpec> m_inventory_rings;
	std::vector<ImageDrawSpec> m_backgrounds;
	std::vector<ImageDrawSpec> m_images;
	std::vector<ImageDrawSpec> m_itemimages;
	std::vector<BoxDrawSpec> m_boxes;
	UNORDERED_MAP<std::string, bool> field_close_on_enter;
	std::vector<FieldSpec> m_fields;
	std::vector<StaticTextSpec> m_static_texts;
	std::vector<std::pair<FieldSpec,GUITable*> > m_tables;
	std::vector<std::pair<FieldSpec,gui::IGUICheckBox*> > m_checkboxes;
	std::map<std::string, TooltipSpec> m_tooltips;
	std::vector<std::pair<FieldSpec,gui::IGUIScrollBar*> > m_scrollbars;
	std::vector<std::pair<FieldSpec, std::vector<std::string> > > m_dropdowns;

	ItemSpec *m_selected_item;
	u32 m_selected_amount;
	bool m_selected_dragging;

	// WARNING: BLACK MAGIC
	// Used to guess and keep up with some special things the server can do.
	// If name is "", no guess exists.
	ItemStack m_selected_content_guess;
	InventoryLocation m_selected_content_guess_inventory;

	v2s32 m_pointer;
	v2s32 m_old_pointer;  // Mouse position after previous mouse event
	gui::IGUIStaticText *m_tooltip_element;

	u64 m_tooltip_show_delay;
	u64 m_hovered_time;
	s32 m_old_tooltip_id;
	std::wstring m_old_tooltip;

	bool m_rmouse_auto_place;

	bool m_allowclose;
	bool m_lock;
	v2u32 m_lockscreensize;

	bool m_bgfullscreen;
	bool m_slotborder;
	video::SColor m_bgcolor;
	video::SColor m_slotbg_n;
	video::SColor m_slotbg_h;
	video::SColor m_slotbordercolor;
	video::SColor m_default_tooltip_bgcolor;
	video::SColor m_default_tooltip_color;

private:
	IFormSource        *m_form_src;
	TextDest           *m_text_dst;
	unsigned int        m_formspec_version;
	std::string         m_focused_element;
	JoystickController *m_joystick;

	typedef struct {
		bool explicit_size;
		v2f invsize;
		v2s32 size;
		v2f32 offset;
		v2f32 anchor;
		core::rect<s32> rect;
		v2s32 basepos;
		v2u32 screensize;
		std::string focused_fieldname;
		GUITable::TableOptions table_options;
		GUITable::TableColumns table_columns;
		// used to restore table selection/scroll/treeview state
		UNORDERED_MAP<std::string, GUITable::DynamicData> table_dyndata;
	} parserData;

	typedef struct {
		bool key_up;
		bool key_down;
		bool key_enter;
		bool key_escape;
	} fs_key_pendig;

	fs_key_pendig current_keys_pending;
	std::string current_field_enter_pending;

	void parseElement(parserData* data, const std::string &element);

	void parseSize(parserData* data, const std::string &element);
	void parseContainer(parserData* data, const std::string &element);
	void parseContainerEnd(parserData* data);
	void parseList(parserData* data, const std::string &element);
	void parseListRing(parserData* data, const std::string &element);
	void parseCheckbox(parserData* data, const std::string &element);
	void parseImage(parserData* data, const std::string &element);
	void parseItemImage(parserData* data, const std::string &element);
	void parseButton(parserData* data, const std::string &element,
			const std::string &typ);
	void parseBackground(parserData* data, const std::string &element);
	void parseTableOptions(parserData* data, const std::string &element);
	void parseTableColumns(parserData* data, const std::string &element);
	void parseTable(parserData* data, const std::string &element);
	void parseTextList(parserData* data, const std::string &element);
	void parseDropDown(parserData* data, const std::string &element);
	void parseFieldCloseOnEnter(parserData *data, const std::string &element);
	void parsePwdField(parserData* data, const std::string &element);
	void parseField(parserData* data, const std::string &element, const std::string &type);
	void parseSimpleField(parserData* data,std::vector<std::string> &parts);
	void parseTextArea(parserData* data,std::vector<std::string>& parts,
			const std::string &type);
	void parseLabel(parserData* data, const std::string &element);
	void parseVertLabel(parserData* data, const std::string &element);
	void parseImageButton(parserData* data, const std::string &element,
			const std::string &type);
	void parseItemImageButton(parserData* data, const std::string &element);
	void parseTabHeader(parserData* data, const std::string &element);
	void parseBox(parserData* data, const std::string &element);
	void parseBackgroundColor(parserData* data, const std::string &element);
	void parseListColors(parserData* data, const std::string &element);
	void parseTooltip(parserData* data, const std::string &element);
	bool parseVersionDirect(const std::string &data);
	bool parseSizeDirect(parserData* data, const std::string &element);
	void parseScrollBar(parserData* data, const std::string &element);
	bool parsePositionDirect(parserData *data, const std::string &element);
	void parsePosition(parserData *data, const std::string &element);
	bool parseAnchorDirect(parserData *data, const std::string &element);
	void parseAnchor(parserData *data, const std::string &element);

	void tryClose();

	void showTooltip(const std::wstring &text, const irr::video::SColor &color,
		const irr::video::SColor &bgcolor);

	/**
	 * check if event is part of a double click
	 * @param event event to evaluate
	 * @return true/false if a doubleclick was detected
	 */
	bool DoubleClickDetection(const SEvent event);

	struct clickpos
	{
		v2s32 pos;
		s64 time;
	};
	clickpos m_doubleclickdetect[2];

	int m_btn_height;
	gui::IGUIFont *m_font;

	std::wstring getLabelByID(s32 id);
	std::string getNameByID(s32 id);
#ifdef __ANDROID__
	v2s32 m_down_pos;
	std::string m_JavaDialogFieldName;
#endif

	/* If true, remap a double-click (or double-tap) action to ESC. This is so
	 * that, for example, Android users can double-tap to close a formspec.
	*
	 * This value can (currently) only be set by the class constructor
	 * and the default value for the setting is true.
	 */
	bool m_remap_dbl_click;

};

class FormspecFormSource: public IFormSource
{
public:
	FormspecFormSource(const std::string &formspec):
		m_formspec(formspec)
	{
	}

	~FormspecFormSource()
	{
	}

	void setForm(const std::string &formspec)
	{
		m_formspec = FORMSPEC_VERSION_STRING + formspec;
	}

	std::string getForm() { return m_formspec; }

	std::string m_formspec;
};

#endif

repo_name: pgimeno/minetest

license: unknown


code:
/*
 Minetest
 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
 Copyright (C) 2013 Ciaran Gultnieks <ciaran@ciarang.com>
 Copyright (C) 2013 teddydestodes <derkomtur@schattengang.net>

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU Lesser General Public License as published by
 the Free Software Foundation; either version 2.1 of the License, or
 (at your option) any later version.

 This program 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 Lesser General Public License for more details.

 You should have received a copy of the GNU Lesser General Public License along
 with this program; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "guiKeyChangeMenu.h"
#include "debug.h"
#include "serialization.h"
#include <string>
#include <IGUICheckBox.h>
#include <IGUIEditBox.h>
#include <IGUIButton.h>
#include <IGUIStaticText.h>
#include <IGUIFont.h>
#include "settings.h"
#include <algorithm>

#include "mainmenumanager.h"  // for g_gamecallback

#define KMaxButtonPerColumns 12

extern MainGameCallback *g_gamecallback;

enum
{
	GUI_ID_BACK_BUTTON = 101, GUI_ID_ABORT_BUTTON, GUI_ID_SCROLL_BAR,
	// buttons
	GUI_ID_KEY_FORWARD_BUTTON,
	GUI_ID_KEY_BACKWARD_BUTTON,
	GUI_ID_KEY_LEFT_BUTTON,
	GUI_ID_KEY_RIGHT_BUTTON,
	GUI_ID_KEY_USE_BUTTON,
	GUI_ID_KEY_FLY_BUTTON,
	GUI_ID_KEY_FAST_BUTTON,
	GUI_ID_KEY_JUMP_BUTTON,
	GUI_ID_KEY_NOCLIP_BUTTON,
	GUI_ID_KEY_CINEMATIC_BUTTON,
	GUI_ID_KEY_CHAT_BUTTON,
	GUI_ID_KEY_CMD_BUTTON,
	GUI_ID_KEY_CMD_LOCAL_BUTTON,
	GUI_ID_KEY_CONSOLE_BUTTON,
	GUI_ID_KEY_SNEAK_BUTTON,
	GUI_ID_KEY_DROP_BUTTON,
	GUI_ID_KEY_INVENTORY_BUTTON,
	GUI_ID_KEY_HOTBAR_PREV_BUTTON,
	GUI_ID_KEY_HOTBAR_NEXT_BUTTON,
	GUI_ID_KEY_MUTE_BUTTON,
	GUI_ID_KEY_DEC_VOLUME_BUTTON,
	GUI_ID_KEY_INC_VOLUME_BUTTON,
	GUI_ID_KEY_DUMP_BUTTON,
	GUI_ID_KEY_RANGE_BUTTON,
	GUI_ID_KEY_ZOOM_BUTTON,
	// other
	GUI_ID_CB_AUX1_DESCENDS,
	GUI_ID_CB_DOUBLETAP_JUMP,
};

GUIKeyChangeMenu::GUIKeyChangeMenu(gui::IGUIEnvironment* env,
				gui::IGUIElement* parent, s32 id, IMenuManager *menumgr) :
GUIModalMenu(env, parent, id, menumgr)
{
	shift_down = false;
	activeKey = -1;
	this->key_used_text = NULL;
	init_keys();
	for(size_t i=0; i<key_settings.size(); i++)
		this->key_used.push_back(key_settings.at(i)->key);
}

GUIKeyChangeMenu::~GUIKeyChangeMenu()
{
	removeChildren();

	for (std::vector<key_setting*>::iterator iter = key_settings.begin();
			iter != key_settings.end(); ++iter) {
		delete[] (*iter)->button_name;
		delete (*iter);
	}
	key_settings.clear();
}

void GUIKeyChangeMenu::removeChildren()
{
	const core::list<gui::IGUIElement*> &children = getChildren();
	core::list<gui::IGUIElement*> children_copy;
	for (core::list<gui::IGUIElement*>::ConstIterator i = children.begin(); i
		 != children.end(); i++)
	{
		children_copy.push_back(*i);
	}
	for (core::list<gui::IGUIElement*>::Iterator i = children_copy.begin(); i
		 != children_copy.end(); i++)
	{
		(*i)->remove();
	}
}

void GUIKeyChangeMenu::regenerateGui(v2u32 screensize)
{
	removeChildren();
	v2s32 size(745, 430);

	core::rect < s32 > rect(screensize.X / 2 - size.X / 2,
							screensize.Y / 2 - size.Y / 2, screensize.X / 2 + size.X / 2,
							screensize.Y / 2 + size.Y / 2);

	DesiredRect = rect;
	recalculateAbsolutePosition(false);

	v2s32 topleft(0, 0);

	{
		core::rect < s32 > rect(0, 0, 600, 40);
		rect += topleft + v2s32(25, 3);
		//gui::IGUIStaticText *t =
		const wchar_t *text = wgettext("Keybindings. (If this menu screws up, remove stuff from minetest.conf)");
		Environment->addStaticText(text,
								   rect, false, true, this, -1);
		delete[] text;
		//t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
	}

	// Build buttons

	v2s32 offset(25, 60);

	for(size_t i = 0; i < key_settings.size(); i++)
	{
		key_setting *k = key_settings.at(i);
		{
			core::rect < s32 > rect(0, 0, 150, 20);
			rect += topleft + v2s32(offset.X, offset.Y);
			Environment->addStaticText(k->button_name, rect, false, true, this, -1);
		}

		{
			core::rect < s32 > rect(0, 0, 100, 30);
			rect += topleft + v2s32(offset.X + 120, offset.Y - 5);
			const wchar_t *text = wgettext(k->key.name());
			k->button = Environment->addButton(rect, this, k->id, text);
			delete[] text;
		}
		if ((i + 1) % KMaxButtonPerColumns == 0) {
			offset.X += 230;
			offset.Y = 60;
		} else {
			offset += v2s32(0, 25);
		}
	}

	{
		s32 option_x = offset.X;
		s32 option_y = offset.Y + 5;
		u32 option_w = 180;
		{
			core::rect<s32> rect(0, 0, option_w, 30);
			rect += topleft + v2s32(option_x, option_y);
			const wchar_t *text = wgettext("\"Use\" = climb down");
			Environment->addCheckBox(g_settings->getBool("aux1_descends"), rect, this,
					GUI_ID_CB_AUX1_DESCENDS, text);
			delete[] text;
		}
		offset += v2s32(0, 25);
	}

	{
		s32 option_x = offset.X;
		s32 option_y = offset.Y + 5;
		u32 option_w = 280;
		{
			core::rect<s32> rect(0, 0, option_w, 30);
			rect += topleft + v2s32(option_x, option_y);
			const wchar_t *text = wgettext("Double tap \"jump\" to toggle fly");
			Environment->addCheckBox(g_settings->getBool("doubletap_jump"), rect, this,
					GUI_ID_CB_DOUBLETAP_JUMP, text);
			delete[] text;
		}
		offset += v2s32(0, 25);
	}

	{
		core::rect < s32 > rect(0, 0, 100, 30);
		rect += topleft + v2s32(size.X / 2 - 105, size.Y - 40);
		const wchar_t *text =  wgettext("Save");
		Environment->addButton(rect, this, GUI_ID_BACK_BUTTON,
				 text);
		delete[] text;
	}
	{
		core::rect < s32 > rect(0, 0, 100, 30);
		rect += topleft + v2s32(size.X / 2 + 5, size.Y - 40);
		const wchar_t *text = wgettext("Cancel");
		Environment->addButton(rect, this, GUI_ID_ABORT_BUTTON,
				text);
		delete[] text;
	}
}

void GUIKeyChangeMenu::drawMenu()
{
	gui::IGUISkin* skin = Environment->getSkin();
	if (!skin)
		return;
	video::IVideoDriver* driver = Environment->getVideoDriver();

	video::SColor bgcolor(140, 0, 0, 0);

	{
		core::rect < s32 > rect(0, 0, 745, 620);
		rect += AbsoluteRect.UpperLeftCorner;
		driver->draw2DRectangle(bgcolor, rect, &AbsoluteClippingRect);
	}

	gui::IGUIElement::draw();
}

bool GUIKeyChangeMenu::acceptInput()
{
	for(size_t i = 0; i < key_settings.size(); i++)
	{
		key_setting *k = key_settings.at(i);
		g_settings->set(k->setting_name, k->key.sym());
	}
	{
		gui::IGUIElement *e = getElementFromId(GUI_ID_CB_AUX1_DESCENDS);
		if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX)
			g_settings->setBool("aux1_descends", ((gui::IGUICheckBox*)e)->isChecked());
	}
	{
		gui::IGUIElement *e = getElementFromId(GUI_ID_CB_DOUBLETAP_JUMP);
		if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX)
			g_settings->setBool("doubletap_jump", ((gui::IGUICheckBox*)e)->isChecked());
	}

	clearKeyCache();

	g_gamecallback->signalKeyConfigChange();

	return true;
}

bool GUIKeyChangeMenu::resetMenu()
{
	if (activeKey >= 0)
	{
		for(size_t i = 0; i < key_settings.size(); i++)
		{
			key_setting *k = key_settings.at(i);
			if(k->id == activeKey)
			{
				const wchar_t *text = wgettext(k->key.name());
				k->button->setText(text);
				delete[] text;
				break;
			}
		}
		activeKey = -1;
		return false;
	}
	return true;
}
bool GUIKeyChangeMenu::OnEvent(const SEvent& event)
{
	if (event.EventType == EET_KEY_INPUT_EVENT && activeKey >= 0
			&& event.KeyInput.PressedDown) {

		bool prefer_character = shift_down;
		KeyPress kp(event.KeyInput, prefer_character);

		bool shift_went_down = false;
		if(!shift_down &&
				(event.KeyInput.Key == irr::KEY_SHIFT ||
				event.KeyInput.Key == irr::KEY_LSHIFT ||
				event.KeyInput.Key == irr::KEY_RSHIFT))
			shift_went_down = true;

		// Remove Key already in use message
		if(this->key_used_text)
		{
			this->key_used_text->remove();
			this->key_used_text = NULL;
		}
		// Display Key already in use message
		if (std::find(this->key_used.begin(), this->key_used.end(), kp) != this->key_used.end())
		{
			core::rect < s32 > rect(0, 0, 600, 40);
			rect += v2s32(0, 0) + v2s32(25, 30);
			const wchar_t *text = wgettext("Key already in use");
			this->key_used_text = Environment->addStaticText(text,
					rect, false, true, this, -1);
			delete[] text;
			//infostream << "Key already in use" << std::endl;
		}

		// But go on
		{
			key_setting *k = NULL;
			for(size_t i = 0; i < key_settings.size(); i++)
			{
				if(key_settings.at(i)->id == activeKey)
				{
					k = key_settings.at(i);
					break;
				}
			}
			FATAL_ERROR_IF(k == NULL, "Key setting not found");
			k->key = kp;
			const wchar_t *text = wgettext(k->key.name());
			k->button->setText(text);
			delete[] text;

			this->key_used.push_back(kp);

			// Allow characters made with shift
			if(shift_went_down){
				shift_down = true;
				return false;
			}else{
				activeKey = -1;
				return true;
			}
		}
	} else if (event.EventType == EET_KEY_INPUT_EVENT && activeKey < 0
			&& event.KeyInput.PressedDown
			&& event.KeyInput.Key == irr::KEY_ESCAPE) {
		quitMenu();
		return true;
	} else if (event.EventType == EET_GUI_EVENT) {
		if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST
			&& isVisible())
		{
			if (!canTakeFocus(event.GUIEvent.Element))
			{
				dstream << "GUIMainMenu: Not allowing focus change."
				<< std::endl;
				// Returning true disables focus change
				return true;
			}
		}
		if (event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED)
		{
			switch (event.GUIEvent.Caller->getID())
			{
				case GUI_ID_BACK_BUTTON: //back
					acceptInput();
					quitMenu();
					return true;
				case GUI_ID_ABORT_BUTTON: //abort
					quitMenu();
					return true;
				default:
					key_setting *k = NULL;
					for(size_t i = 0; i < key_settings.size(); i++)
					{
						if(key_settings.at(i)->id == event.GUIEvent.Caller->getID())
						{
							k = key_settings.at(i);
							break;
						}
					}
					FATAL_ERROR_IF(k == NULL, "Key setting not found");

					resetMenu();
					shift_down = false;
					activeKey = event.GUIEvent.Caller->getID();
					const wchar_t *text = wgettext("press key");
					k->button->setText(text);
					delete[] text;
					this->key_used.erase(std::remove(this->key_used.begin(),
							this->key_used.end(), k->key), this->key_used.end());
					break;
			}
			Environment->setFocus(this);
		}
	}
	return Parent ? Parent->OnEvent(event) : false;
}

void GUIKeyChangeMenu::add_key(int id, const wchar_t *button_name, const std::string &setting_name)
{
	key_setting *k = new key_setting;
	k->id = id;

	k->button_name = button_name;
	k->setting_name = setting_name;
	k->key = getKeySetting(k->setting_name.c_str());
	key_settings.push_back(k);
}

void GUIKeyChangeMenu::init_keys()
{
	this->add_key(GUI_ID_KEY_FORWARD_BUTTON,   wgettext("Forward"),          "keymap_forward");
	this->add_key(GUI_ID_KEY_BACKWARD_BUTTON,  wgettext("Backward"),         "keymap_backward");
	this->add_key(GUI_ID_KEY_LEFT_BUTTON,      wgettext("Left"),             "keymap_left");
	this->add_key(GUI_ID_KEY_RIGHT_BUTTON,     wgettext("Right"),            "keymap_right");
	this->add_key(GUI_ID_KEY_USE_BUTTON,       wgettext("Use"),              "keymap_special1");
	this->add_key(GUI_ID_KEY_JUMP_BUTTON,      wgettext("Jump"),             "keymap_jump");
	this->add_key(GUI_ID_KEY_SNEAK_BUTTON,     wgettext("Sneak"),            "keymap_sneak");
	this->add_key(GUI_ID_KEY_DROP_BUTTON,      wgettext("Drop"),             "keymap_drop");
	this->add_key(GUI_ID_KEY_INVENTORY_BUTTON, wgettext("Inventory"),        "keymap_inventory");
	this->add_key(GUI_ID_KEY_HOTBAR_PREV_BUTTON,wgettext("Prev. item"),      "keymap_hotbar_previous");
	this->add_key(GUI_ID_KEY_HOTBAR_NEXT_BUTTON,wgettext("Next item"),       "keymap_hotbar_next");
	this->add_key(GUI_ID_KEY_MUTE_BUTTON,      wgettext("Mute"),             "keymap_mute");
	this->add_key(GUI_ID_KEY_DEC_VOLUME_BUTTON,wgettext("Dec. volume"),      "keymap_decrease_volume");
	this->add_key(GUI_ID_KEY_INC_VOLUME_BUTTON,wgettext("Inc. volume"),      "keymap_increase_volume");
	this->add_key(GUI_ID_KEY_CHAT_BUTTON,      wgettext("Chat"),             "keymap_chat");
	this->add_key(GUI_ID_KEY_CMD_BUTTON,       wgettext("Command"),          "keymap_cmd");
	this->add_key(GUI_ID_KEY_CMD_LOCAL_BUTTON, wgettext("Local command"),    "keymap_cmd_local");
	this->add_key(GUI_ID_KEY_CONSOLE_BUTTON,   wgettext("Console"),          "keymap_console");
	this->add_key(GUI_ID_KEY_FLY_BUTTON,       wgettext("Toggle fly"),       "keymap_freemove");
	this->add_key(GUI_ID_KEY_FAST_BUTTON,      wgettext("Toggle fast"),      "keymap_fastmove");
	this->add_key(GUI_ID_KEY_CINEMATIC_BUTTON, wgettext("Toggle Cinematic"), "keymap_cinematic");
	this->add_key(GUI_ID_KEY_NOCLIP_BUTTON,    wgettext("Toggle noclip"),    "keymap_noclip");
	this->add_key(GUI_ID_KEY_RANGE_BUTTON,     wgettext("Range select"),     "keymap_rangeselect");
	this->add_key(GUI_ID_KEY_DUMP_BUTTON,      wgettext("Print stacks"),     "keymap_print_debug_stacks");
	this->add_key(GUI_ID_KEY_ZOOM_BUTTON,      wgettext("Zoom"),             "keymap_zoom");
}


code:
/*
 Minetest
 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
 Copyright (C) 2013 Ciaran Gultnieks <ciaran@ciarang.com>
 Copyright (C) 2013 teddydestodes <derkomtur@schattengang.net>

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU Lesser General Public License as published by
 the Free Software Foundation; either version 2.1 of the License, or
 (at your option) any later version.

 This program 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 Lesser General Public License for more details.

 You should have received a copy of the GNU Lesser General Public License along
 with this program; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#ifndef GUIKEYCHANGEMENU_HEADER
#define GUIKEYCHANGEMENU_HEADER

#include "irrlichttypes_extrabloated.h"
#include "modalMenu.h"
#include "client.h"
#include "gettext.h"
#include "keycode.h"
#include <string>
#include <vector>

struct key_setting
{
	int id;
	const wchar_t *button_name;
	KeyPress key;
	std::string setting_name;
	gui::IGUIButton *button;
};

class GUIKeyChangeMenu : public GUIModalMenu
{
public:
	GUIKeyChangeMenu(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id,
			IMenuManager *menumgr);
	~GUIKeyChangeMenu();

	void removeChildren();
	/*
	 Remove and re-add (or reposition) stuff
	 */
	void regenerateGui(v2u32 screensize);

	void drawMenu();

	bool acceptInput();

	bool OnEvent(const SEvent &event);

private:
	void init_keys();

	bool resetMenu();

	void add_key(int id, const wchar_t *button_name, const std::string &setting_name);

	bool shift_down;

	s32 activeKey;

	std::vector<KeyPress> key_used;
	gui::IGUIStaticText *key_used_text;
	std::vector<key_setting *> key_settings;
};

#endif


code:
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#ifndef GUIMAINMENU_HEADER
#define GUIMAINMENU_HEADER

#include "irrlichttypes_extrabloated.h"
#include "modalMenu.h"
#include <string>
#include <list>

struct MainMenuDataForScript {

	MainMenuDataForScript() :
		reconnect_requested(false)
	{}

	// Whether the server has requested a reconnect
	bool reconnect_requested;

	std::string errormessage;
};

struct MainMenuData {
	// Client options
	std::string servername;
	std::string serverdescription;
	std::string address;
	std::string port;
	std::string name;
	std::string password;
	// Whether to reconnect
	bool do_reconnect;

	// Server options
	bool enable_public;
	int selected_world;
	bool simple_singleplayer_mode;

	// Data to be passed to the script
	MainMenuDataForScript script_data;

	MainMenuData():
		do_reconnect(false),
		enable_public(false),
		selected_world(0),
		simple_singleplayer_mode(false)
	{}
};

#endif


code:
/*
Part of Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2013 Ciaran Gultnieks <ciaran@ciarang.com>

Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#include "guiPasswordChange.h"
#include "debug.h"
#include "serialization.h"
#include <string>
#include <IGUICheckBox.h>
#include <IGUIEditBox.h>
#include <IGUIButton.h>
#include <IGUIStaticText.h>
#include <IGUIFont.h>

#include "gettext.h"

const int ID_oldPassword = 256;
const int ID_newPassword1 = 257;
const int ID_newPassword2 = 258;
const int ID_change = 259;
const int ID_message = 260;
const int ID_cancel = 261;

GUIPasswordChange::GUIPasswordChange(gui::IGUIEnvironment* env,
		gui::IGUIElement* parent, s32 id,
		IMenuManager *menumgr,
		Client* client
):
	GUIModalMenu(env, parent, id, menumgr),
	m_client(client),
	m_oldpass(L""),
	m_newpass(L""),
	m_newpass_confirm(L"")
{
}

GUIPasswordChange::~GUIPasswordChange()
{
	removeChildren();
}

void GUIPasswordChange::removeChildren()
{
	const core::list<gui::IGUIElement *> &children = getChildren();
	core::list<gui::IGUIElement *> children_copy;
	for (core::list<gui::IGUIElement *>::ConstIterator i = children.begin();
			i != children.end(); i++) {
		children_copy.push_back(*i);
	}
	for (core::list<gui::IGUIElement *>::Iterator i = children_copy.begin();
			i != children_copy.end(); i++) {
		(*i)->remove();
	}
}
void GUIPasswordChange::regenerateGui(v2u32 screensize)
{
	/*
		save current input
	*/
	acceptInput();

	/*
		Remove stuff
	*/
	removeChildren();

	/*
		Calculate new sizes and positions
	*/
	core::rect<s32> rect(
			screensize.X/2 - 580/2,
			screensize.Y/2 - 300/2,
			screensize.X/2 + 580/2,
			screensize.Y/2 + 300/2
	);

	DesiredRect = rect;
	recalculateAbsolutePosition(false);

	v2s32 size = rect.getSize();
	v2s32 topleft_client(40, 0);

	const wchar_t *text;

	/*
		Add stuff
	*/
	s32 ypos = 50;
	{
		core::rect<s32> rect(0, 0, 150, 20);
		rect += topleft_client + v2s32(25, ypos + 6);
		text = wgettext("Old Password");
		Environment->addStaticText(text, rect, false, true, this, -1);
		delete[] text;
	}
	{
		core::rect<s32> rect(0, 0, 230, 30);
		rect += topleft_client + v2s32(160, ypos);
		gui::IGUIEditBox *e = Environment->addEditBox(
				m_oldpass.c_str(), rect, true, this, ID_oldPassword);
		Environment->setFocus(e);
		e->setPasswordBox(true);
	}
	ypos += 50;
	{
		core::rect<s32> rect(0, 0, 150, 20);
		rect += topleft_client + v2s32(25, ypos + 6);
		text = wgettext("New Password");
		Environment->addStaticText(text, rect, false, true, this, -1);
		delete[] text;
	}
	{
		core::rect<s32> rect(0, 0, 230, 30);
		rect += topleft_client + v2s32(160, ypos);
		gui::IGUIEditBox *e = Environment->addEditBox(
				m_newpass.c_str(), rect, true, this, ID_newPassword1);
		e->setPasswordBox(true);
	}
	ypos += 50;
	{
		core::rect<s32> rect(0, 0, 150, 20);
		rect += topleft_client + v2s32(25, ypos + 6);
		text = wgettext("Confirm Password");
		Environment->addStaticText(text, rect, false, true, this, -1);
		delete[] text;
	}
	{
		core::rect<s32> rect(0, 0, 230, 30);
		rect += topleft_client + v2s32(160, ypos);
		gui::IGUIEditBox *e = Environment->addEditBox(
				m_newpass_confirm.c_str(), rect, true, this, ID_newPassword2);
		e->setPasswordBox(true);
	}

	ypos += 50;
	{
		core::rect<s32> rect(0, 0, 100, 30);
		rect = rect + v2s32(size.X / 4 + 56, ypos);
		text = wgettext("Change");
		Environment->addButton(rect, this, ID_change, text);
		delete[] text;
	}
	{
		core::rect<s32> rect(0, 0, 100, 30);
		rect = rect + v2s32(size.X / 4 + 185, ypos);
		text = wgettext("Cancel");
		Environment->addButton(rect, this, ID_cancel, text);
		delete[] text;
	}

	ypos += 50;
	{
		core::rect<s32> rect(0, 0, 300, 20);
		rect += topleft_client + v2s32(35, ypos);
		text = wgettext("Passwords do not match!");
		IGUIElement *e =
			Environment->addStaticText(
			text, rect, false, true, this, ID_message);
		e->setVisible(false);
		delete[] text;
	}
}

void GUIPasswordChange::drawMenu()
{
	gui::IGUISkin *skin = Environment->getSkin();
	if (!skin)
		return;
	video::IVideoDriver *driver = Environment->getVideoDriver();

	video::SColor bgcolor(140, 0, 0, 0);
	driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect);

	gui::IGUIElement::draw();
}

void GUIPasswordChange::acceptInput()
{
	gui::IGUIElement *e;
	e = getElementFromId(ID_oldPassword);
	if (e != NULL)
		m_oldpass = e->getText();
	e = getElementFromId(ID_newPassword1);
	if (e != NULL)
		m_newpass = e->getText();
	e = getElementFromId(ID_newPassword2);
	if (e != NULL)
		m_newpass_confirm = e->getText();
}

bool GUIPasswordChange::processInput()
{
	if (m_newpass != m_newpass_confirm) {
		gui::IGUIElement *e = getElementFromId(ID_message);
		if (e != NULL)
			e->setVisible(true);
		return false;
	}
	m_client->sendChangePassword(wide_to_utf8(m_oldpass), wide_to_utf8(m_newpass));
	return true;
}

bool GUIPasswordChange::OnEvent(const SEvent &event)
{
	if (event.EventType == EET_KEY_INPUT_EVENT) {
		if (event.KeyInput.Key == KEY_ESCAPE && event.KeyInput.PressedDown) {
			quitMenu();
			return true;
		}
		if (event.KeyInput.Key == KEY_RETURN && event.KeyInput.PressedDown) {
			acceptInput();
			if (processInput())
				quitMenu();
			return true;
		}
	}
	if (event.EventType == EET_GUI_EVENT) {
		if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST &&
				isVisible()) {
			if (!canTakeFocus(event.GUIEvent.Element)) {
				dstream << "GUIPasswordChange: Not allowing focus change."
					<< std::endl;
				// Returning true disables focus change
				return true;
			}
		}
		if (event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED) {
			switch (event.GUIEvent.Caller->getID()) {
			case ID_change:
				acceptInput();
				if (processInput())
					quitMenu();
				return true;
			case ID_cancel:
				quitMenu();
				return true;
			}
		}
		if (event.GUIEvent.EventType == gui::EGET_EDITBOX_ENTER) {
			switch (event.GUIEvent.Caller->getID()) {
			case ID_oldPassword:
			case ID_newPassword1:
			case ID_newPassword2:
				acceptInput();
				if (processInput())
					quitMenu();
				return true;
			}
		}
	}

	return Parent ? Parent->OnEvent(event) : false;
}


code:
/*
Part of Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2013 Ciaran Gultnieks <ciaran@ciarang.com>

Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#ifndef GUIPASSWORDCHANGE_HEADER
#define GUIPASSWORDCHANGE_HEADER

#include "irrlichttypes_extrabloated.h"
#include "modalMenu.h"
#include "client.h"
#include <string>

class GUIPasswordChange : public GUIModalMenu
{
public:
	GUIPasswordChange(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id,
			IMenuManager *menumgr, Client *client);
	~GUIPasswordChange();

	void removeChildren();
	/*
		Remove and re-add (or reposition) stuff
	*/
	void regenerateGui(v2u32 screensize);

	void drawMenu();

	void acceptInput();

	bool processInput();

	bool OnEvent(const SEvent &event);

private:
	Client *m_client;
	std::wstring m_oldpass;
	std::wstring m_newpass;
	std::wstring m_newpass_confirm;
};

#endif


code:
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "guiTable.h"
#include <queue>
#include <sstream>
#include <utility>
#include <string.h>
#include <IGUISkin.h>
#include <IGUIFont.h>
#include <IGUIScrollBar.h>
#include "debug.h"
#include "log.h"
#include "client/tile.h"
#include "gettime.h"
#include "util/string.h"
#include "util/numeric.h"
#include "util/string.h" // for parseColorString()
#include "settings.h" // for settings
#include "porting.h" // for dpi
#include "guiscalingfilter.h"

/*
	GUITable
*/

GUITable::GUITable(gui::IGUIEnvironment *env,
		gui::IGUIElement* parent, s32 id,
		core::rect<s32> rectangle,
		ISimpleTextureSource *tsrc
):
	gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle),
	m_tsrc(tsrc),
	m_is_textlist(false),
	m_has_tree_column(false),
	m_selected(-1),
	m_sel_column(0),
	m_sel_doubleclick(false),
	m_keynav_time(0),
	m_keynav_buffer(L""),
	m_border(true),
	m_color(255, 255, 255, 255),
	m_background(255, 0, 0, 0),
	m_highlight(255, 70, 100, 50),
	m_highlight_text(255, 255, 255, 255),
	m_rowheight(1),
	m_font(NULL),
	m_scrollbar(NULL)
{
	assert(tsrc != NULL);

	gui::IGUISkin* skin = Environment->getSkin();

	m_font = skin->getFont();
	if (m_font) {
		m_font->grab();
		m_rowheight = m_font->getDimension(L"A").Height + 4;
		m_rowheight = MYMAX(m_rowheight, 1);
	}

	const s32 s = skin->getSize(gui::EGDS_SCROLLBAR_SIZE);
	m_scrollbar = Environment->addScrollBar(false,
			core::rect<s32>(RelativeRect.getWidth() - s,
					0,
					RelativeRect.getWidth(),
					RelativeRect.getHeight()),
			this, -1);
	m_scrollbar->setSubElement(true);
	m_scrollbar->setTabStop(false);
	m_scrollbar->setAlignment(gui::EGUIA_LOWERRIGHT, gui::EGUIA_LOWERRIGHT,
			gui::EGUIA_UPPERLEFT, gui::EGUIA_LOWERRIGHT);
	m_scrollbar->setVisible(false);
	m_scrollbar->setPos(0);

	setTabStop(true);
	setTabOrder(-1);
	updateAbsolutePosition();

	core::rect<s32> relative_rect = m_scrollbar->getRelativePosition();
	s32 width = (relative_rect.getWidth()/(2.0/3.0)) * porting::getDisplayDensity() *
			g_settings->getFloat("gui_scaling");
	m_scrollbar->setRelativePosition(core::rect<s32>(
			relative_rect.LowerRightCorner.X-width,relative_rect.UpperLeftCorner.Y,
			relative_rect.LowerRightCorner.X,relative_rect.LowerRightCorner.Y
			));
}

GUITable::~GUITable()
{
	for (size_t i = 0; i < m_rows.size(); ++i)
		delete[] m_rows[i].cells;

	if (m_font)
		m_font->drop();

	m_scrollbar->remove();
}

GUITable::Option GUITable::splitOption(const std::string &str)
{
	size_t equal_pos = str.find('=');
	if (equal_pos == std::string::npos)
		return GUITable::Option(str, "");
	else
		return GUITable::Option(str.substr(0, equal_pos),
				str.substr(equal_pos + 1));
}

void GUITable::setTextList(const std::vector<std::string> &content,
		bool transparent)
{
	clear();

	if (transparent) {
		m_background.setAlpha(0);
		m_border = false;
	}

	m_is_textlist = true;

	s32 empty_string_index = allocString("");

	m_rows.resize(content.size());
	for (s32 i = 0; i < (s32) content.size(); ++i) {
		Row *row = &m_rows[i];
		row->cells = new Cell[1];
		row->cellcount = 1;
		row->indent = 0;
		row->visible_index = i;
		m_visible_rows.push_back(i);

		Cell *cell = row->cells;
		cell->xmin = 0;
		cell->xmax = 0x7fff;  // something large enough
		cell->xpos = 6;
		cell->content_type = COLUMN_TYPE_TEXT;
		cell->content_index = empty_string_index;
		cell->tooltip_index = empty_string_index;
		cell->color.set(255, 255, 255, 255);
		cell->color_defined = false;
		cell->reported_column = 1;

		// parse row content (color)
		const std::string &s = content[i];
		if (s[0] == '#' && s[1] == '#') {
			// double # to escape
			cell->content_index = allocString(s.substr(2));
		}
		else if (s[0] == '#' && s.size() >= 7 &&
				parseColorString(
					s.substr(0,7), cell->color, false)) {
			// single # for color
			cell->color_defined = true;
			cell->content_index = allocString(s.substr(7));
		}
		else {
			// no #, just text
			cell->content_index = allocString(s);
		}

	}

	allocationComplete();

	// Clamp scroll bar position
	updateScrollBar();
}

void GUITable::setTable(const TableOptions &options,
		const TableColumns &columns,
		std::vector<std::string> &content)
{
	clear();

	// Naming conventions:
	// i is always a row index, 0-based
	// j is always a column index, 0-based
	// k is another index, for example an option index

	// Handle a stupid error case... (issue #1187)
	if (columns.empty()) {
		TableColumn text_column;
		text_column.type = "text";
		TableColumns new_columns;
		new_columns.push_back(text_column);
		setTable(options, new_columns, content);
		return;
	}

	// Handle table options
	video::SColor default_color(255, 255, 255, 255);
	s32 opendepth = 0;
	for (size_t k = 0; k < options.size(); ++k) {
		const std::string &name = options[k].name;
		const std::string &value = options[k].value;
		if (name == "color")
			parseColorString(value, m_color, false);
		else if (name == "background")
			parseColorString(value, m_background, false);
		else if (name == "border")
			m_border = is_yes(value);
		else if (name == "highlight")
			parseColorString(value, m_highlight, false);
		else if (name == "highlight_text")
			parseColorString(value, m_highlight_text, false);
		else if (name == "opendepth")
			opendepth = stoi(value);
		else
			errorstream<<"Invalid table option: \""<<name<<"\""
				<<" (value=\""<<value<<"\")"<<std::endl;
	}

	// Get number of columns and rows
	// note: error case columns.size() == 0 was handled above
	s32 colcount = columns.size();
	assert(colcount >= 1);
	// rowcount = ceil(cellcount / colcount) but use integer arithmetic
	s32 rowcount = (content.size() + colcount - 1) / colcount;
	assert(rowcount >= 0);
	// Append empty strings to content if there is an incomplete row
	s32 cellcount = rowcount * colcount;
	while (content.size() < (u32) cellcount)
		content.push_back("");

	// Create temporary rows (for processing columns)
	struct TempRow {
		// Current horizontal position (may different between rows due
		// to indent/tree columns, or text/image columns with width<0)
		s32 x;
		// Tree indentation level
		s32 indent;
		// Next cell: Index into m_strings or m_images
		s32 content_index;
		// Next cell: Width in pixels
		s32 content_width;
		// Vector of completed cells in this row
		std::vector<Cell> cells;
		// Stores colors and how long they last (maximum column index)
		std::vector<std::pair<video::SColor, s32> > colors;

		TempRow(): x(0), indent(0), content_index(0), content_width(0) {}
	};
	TempRow *rows = new TempRow[rowcount];

	// Get em width. Pedantically speaking, the width of "M" is not
	// necessarily the same as the em width, but whatever, close enough.
	s32 em = 6;
	if (m_font)
		em = m_font->getDimension(L"M").Width;

	s32 default_tooltip_index = allocString("");

	std::map<s32, s32> active_image_indices;

	// Process content in column-major order
	for (s32 j = 0; j < colcount; ++j) {
		// Check column type
		ColumnType columntype = COLUMN_TYPE_TEXT;
		if (columns[j].type == "text")
			columntype = COLUMN_TYPE_TEXT;
		else if (columns[j].type == "image")
			columntype = COLUMN_TYPE_IMAGE;
		else if (columns[j].type == "color")
			columntype = COLUMN_TYPE_COLOR;
		else if (columns[j].type == "indent")
			columntype = COLUMN_TYPE_INDENT;
		else if (columns[j].type == "tree")
			columntype = COLUMN_TYPE_TREE;
		else
			errorstream<<"Invalid table column type: \""
				<<columns[j].type<<"\""<<std::endl;

		// Process column options
		s32 padding = myround(0.5 * em);
		s32 tooltip_index = default_tooltip_index;
		s32 align = 0;
		s32 width = 0;
		s32 span = colcount;

		if (columntype == COLUMN_TYPE_INDENT) {
			padding = 0; // default indent padding
		}
		if (columntype == COLUMN_TYPE_INDENT ||
				columntype == COLUMN_TYPE_TREE) {
			width = myround(em * 1.5); // default indent width
		}

		for (size_t k = 0; k < columns[j].options.size(); ++k) {
			const std::string &name = columns[j].options[k].name;
			const std::string &value = columns[j].options[k].value;
			if (name == "padding")
				padding = myround(stof(value) * em);
			else if (name == "tooltip")
				tooltip_index = allocString(value);
			else if (name == "align" && value == "left")
				align = 0;
			else if (name == "align" && value == "center")
				align = 1;
			else if (name == "align" && value == "right")
				align = 2;
			else if (name == "align" && value == "inline")
				align = 3;
			else if (name == "width")
				width = myround(stof(value) * em);
			else if (name == "span" && columntype == COLUMN_TYPE_COLOR)
				span = stoi(value);
			else if (columntype == COLUMN_TYPE_IMAGE &&
					!name.empty() &&
					string_allowed(name, "0123456789")) {
				s32 content_index = allocImage(value);
				active_image_indices.insert(std::make_pair(
							stoi(name),
							content_index));
			}
			else {
				errorstream<<"Invalid table column option: \""<<name<<"\""
					<<" (value=\""<<value<<"\")"<<std::endl;
			}
		}

		// If current column type can use information from "color" columns,
		// find out which of those is currently active
		if (columntype == COLUMN_TYPE_TEXT) {
			for (s32 i = 0; i < rowcount; ++i) {
				TempRow *row = &rows[i];
				while (!row->colors.empty() && row->colors.back().second < j)
					row->colors.pop_back();
			}
		}

		// Make template for new cells
		Cell newcell;
		memset(&newcell, 0, sizeof newcell);
		newcell.content_type = columntype;
		newcell.tooltip_index = tooltip_index;
		newcell.reported_column = j+1;

		if (columntype == COLUMN_TYPE_TEXT) {
			// Find right edge of column
			s32 xmax = 0;
			for (s32 i = 0; i < rowcount; ++i) {
				TempRow *row = &rows[i];
				row->content_index = allocString(content[i * colcount + j]);
				const core::stringw &text = m_strings[row->content_index];
				row->content_width = m_font ?
					m_font->getDimension(text.c_str()).Width : 0;
				row->content_width = MYMAX(row->content_width, width);
				s32 row_xmax = row->x + padding + row->content_width;
				xmax = MYMAX(xmax, row_xmax);
			}
			// Add a new cell (of text type) to each row
			for (s32 i = 0; i < rowcount; ++i) {
				newcell.xmin = rows[i].x + padding;
				alignContent(&newcell, xmax, rows[i].content_width, align);
				newcell.content_index = rows[i].content_index;
				newcell.color_defined = !rows[i].colors.empty();
				if (newcell.color_defined)
					newcell.color = rows[i].colors.back().first;
				rows[i].cells.push_back(newcell);
				rows[i].x = newcell.xmax;
			}
		}
		else if (columntype == COLUMN_TYPE_IMAGE) {
			// Find right edge of column
			s32 xmax = 0;
			for (s32 i = 0; i < rowcount; ++i) {
				TempRow *row = &rows[i];
				row->content_index = -1;

				// Find content_index. Image indices are defined in
				// column options so check active_image_indices.
				s32 image_index = stoi(content[i * colcount + j]);
				std::map<s32, s32>::iterator image_iter =
					active_image_indices.find(image_index);
				if (image_iter != active_image_indices.end())
					row->content_index = image_iter->second;

				// Get texture object (might be NULL)
				video::ITexture *image = NULL;
				if (row->content_index >= 0)
					image = m_images[row->content_index];

				// Get content width and update xmax
				row->content_width = image ? image->getOriginalSize().Width : 0;
				row->content_width = MYMAX(row->content_width, width);
				s32 row_xmax = row->x + padding + row->content_width;
				xmax = MYMAX(xmax, row_xmax);
			}
			// Add a new cell (of image type) to each row
			for (s32 i = 0; i < rowcount; ++i) {
				newcell.xmin = rows[i].x + padding;
				alignContent(&newcell, xmax, rows[i].content_width, align);
				newcell.content_index = rows[i].content_index;
				rows[i].cells.push_back(newcell);
				rows[i].x = newcell.xmax;
			}
			active_image_indices.clear();
		}
		else if (columntype == COLUMN_TYPE_COLOR) {
			for (s32 i = 0; i < rowcount; ++i) {
				video::SColor cellcolor(255, 255, 255, 255);
				if (parseColorString(content[i * colcount + j], cellcolor, true))
					rows[i].colors.push_back(std::make_pair(cellcolor, j+span));
			}
		}
		else if (columntype == COLUMN_TYPE_INDENT ||
				columntype == COLUMN_TYPE_TREE) {
			// For column type "tree", reserve additional space for +/-
			// Also enable special processing for treeview-type tables
			s32 content_width = 0;
			if (columntype == COLUMN_TYPE_TREE) {
				content_width = m_font ? m_font->getDimension(L"+").Width : 0;
				m_has_tree_column = true;
			}
			// Add a new cell (of indent or tree type) to each row
			for (s32 i = 0; i < rowcount; ++i) {
				TempRow *row = &rows[i];

				s32 indentlevel = stoi(content[i * colcount + j]);
				indentlevel = MYMAX(indentlevel, 0);
				if (columntype == COLUMN_TYPE_TREE)
					row->indent = indentlevel;

				newcell.xmin = row->x + padding;
				newcell.xpos = newcell.xmin + indentlevel * width;
				newcell.xmax = newcell.xpos + content_width;
				newcell.content_index = 0;
				newcell.color_defined = !rows[i].colors.empty();
				if (newcell.color_defined)
					newcell.color = rows[i].colors.back().first;
				row->cells.push_back(newcell);
				row->x = newcell.xmax;
			}
		}
	}

	// Copy temporary rows to not so temporary rows
	if (rowcount >= 1) {
		m_rows.resize(rowcount);
		for (s32 i = 0; i < rowcount; ++i) {
			Row *row = &m_rows[i];
			row->cellcount = rows[i].cells.size();
			row->cells = new Cell[row->cellcount];
			memcpy((void*) row->cells, (void*) &rows[i].cells[0],
					row->cellcount * sizeof(Cell));
			row->indent = rows[i].indent;
			row->visible_index = i;
			m_visible_rows.push_back(i);
		}
	}

	if (m_has_tree_column) {
		// Treeview: convert tree to indent cells on leaf rows
		for (s32 i = 0; i < rowcount; ++i) {
			if (i == rowcount-1 || m_rows[i].indent >= m_rows[i+1].indent)
				for (s32 j = 0; j < m_rows[i].cellcount; ++j)
					if (m_rows[i].cells[j].content_type == COLUMN_TYPE_TREE)
						m_rows[i].cells[j].content_type = COLUMN_TYPE_INDENT;
		}

		// Treeview: close rows according to opendepth option
		std::set<s32> opened_trees;
		for (s32 i = 0; i < rowcount; ++i)
			if (m_rows[i].indent < opendepth)
				opened_trees.insert(i);
		setOpenedTrees(opened_trees);
	}

	// Delete temporary information used only during setTable()
	delete[] rows;
	allocationComplete();

	// Clamp scroll bar position
	updateScrollBar();
}

void GUITable::clear()
{
	// Clean up cells and rows
	for (size_t i = 0; i < m_rows.size(); ++i)
		delete[] m_rows[i].cells;
	m_rows.clear();
	m_visible_rows.clear();

	// Get colors from skin
	gui::IGUISkin *skin = Environment->getSkin();
	m_color          = skin->getColor(gui::EGDC_BUTTON_TEXT);
	m_background     = skin->getColor(gui::EGDC_3D_HIGH_LIGHT);
	m_highlight      = skin->getColor(gui::EGDC_HIGH_LIGHT);
	m_highlight_text = skin->getColor(gui::EGDC_HIGH_LIGHT_TEXT);

	// Reset members
	m_is_textlist = false;
	m_has_tree_column = false;
	m_selected = -1;
	m_sel_column = 0;
	m_sel_doubleclick = false;
	m_keynav_time = 0;
	m_keynav_buffer = L"";
	m_border = true;
	m_strings.clear();
	m_images.clear();
	m_alloc_strings.clear();
	m_alloc_images.clear();
}

std::string GUITable::checkEvent()
{
	s32 sel = getSelected();
	assert(sel >= 0);

	if (sel == 0) {
		return "INV";
	}

	std::ostringstream os(std::ios::binary);
	if (m_sel_doubleclick) {
		os<<"DCL:";
		m_sel_doubleclick = false;
	}
	else {
		os<<"CHG:";
	}
	os<<sel;
	if (!m_is_textlist) {
		os<<":"<<m_sel_column;
	}
	return os.str();
}

s32 GUITable::getSelected() const
{
	if (m_selected < 0)
		return 0;

	assert(m_selected >= 0 && m_selected < (s32) m_visible_rows.size());
	return m_visible_rows[m_selected] + 1;
}

void GUITable::setSelected(s32 index)
{
	s32 old_selected = m_selected;

	m_selected = -1;
	m_sel_column = 0;
	m_sel_doubleclick = false;

	--index; // Switch from 1-based indexing to 0-based indexing

	s32 rowcount = m_rows.size();
	if (rowcount == 0 || index < 0) {
		return;
	} else if (index >= rowcount) {
		index = rowcount - 1;
	}

	// If the selected row is not visible, open its ancestors to make it visible
	bool selection_invisible = m_rows[index].visible_index < 0;
	if (selection_invisible) {
		std::set<s32> opened_trees;
		getOpenedTrees(opened_trees);
		s32 indent = m_rows[index].indent;
		for (s32 j = index - 1; j >= 0; --j) {
			if (m_rows[j].indent < indent) {
				opened_trees.insert(j);
				indent = m_rows[j].indent;
			}
		}
		setOpenedTrees(opened_trees);
	}

	if (index >= 0) {
		m_selected = m_rows[index].visible_index;
		assert(m_selected >= 0 && m_selected < (s32) m_visible_rows.size());
	}

	if (m_selected != old_selected || selection_invisible) {
		autoScroll();
	}
}

GUITable::DynamicData GUITable::getDynamicData() const
{
	DynamicData dyndata;
	dyndata.selected = getSelected();
	dyndata.scrollpos = m_scrollbar->getPos();
	dyndata.keynav_time = m_keynav_time;
	dyndata.keynav_buffer = m_keynav_buffer;
	if (m_has_tree_column)
		getOpenedTrees(dyndata.opened_trees);
	return dyndata;
}

void GUITable::setDynamicData(const DynamicData &dyndata)
{
	if (m_has_tree_column)
		setOpenedTrees(dyndata.opened_trees);

	m_keynav_time = dyndata.keynav_time;
	m_keynav_buffer = dyndata.keynav_buffer;

	setSelected(dyndata.selected);
	m_sel_column = 0;
	m_sel_doubleclick = false;

	m_scrollbar->setPos(dyndata.scrollpos);
}

const c8* GUITable::getTypeName() const
{
	return "GUITable";
}

void GUITable::updateAbsolutePosition()
{
	IGUIElement::updateAbsolutePosition();
	updateScrollBar();
}

void GUITable::draw()
{
	if (!IsVisible)
		return;

	gui::IGUISkin *skin = Environment->getSkin();

	// draw background

	bool draw_background = m_background.getAlpha() > 0;
	if (m_border)
		skin->draw3DSunkenPane(this, m_background,
				true, draw_background,
				AbsoluteRect, &AbsoluteClippingRect);
	else if (draw_background)
		skin->draw2DRectangle(this, m_background,
				AbsoluteRect, &AbsoluteClippingRect);

	// get clipping rect

	core::rect<s32> client_clip(AbsoluteRect);
	client_clip.UpperLeftCorner.Y += 1;
	client_clip.UpperLeftCorner.X += 1;
	client_clip.LowerRightCorner.Y -= 1;
	client_clip.LowerRightCorner.X -= 1;
	if (m_scrollbar->isVisible()) {
		client_clip.LowerRightCorner.X =
				m_scrollbar->getAbsolutePosition().UpperLeftCorner.X;
	}
	client_clip.clipAgainst(AbsoluteClippingRect);

	// draw visible rows

	s32 scrollpos = m_scrollbar->getPos();
	s32 row_min = scrollpos / m_rowheight;
	s32 row_max = (scrollpos + AbsoluteRect.getHeight() - 1)
			/ m_rowheight + 1;
	row_max = MYMIN(row_max, (s32) m_visible_rows.size());

	core::rect<s32> row_rect(AbsoluteRect);
	if (m_scrollbar->isVisible())
		row_rect.LowerRightCorner.X -=
			skin->getSize(gui::EGDS_SCROLLBAR_SIZE);
	row_rect.UpperLeftCorner.Y += row_min * m_rowheight - scrollpos;
	row_rect.LowerRightCorner.Y = row_rect.UpperLeftCorner.Y + m_rowheight;

	for (s32 i = row_min; i < row_max; ++i) {
		Row *row = &m_rows[m_visible_rows[i]];
		bool is_sel = i == m_selected;
		video::SColor color = m_color;

		if (is_sel) {
			skin->draw2DRectangle(this, m_highlight, row_rect, &client_clip);
			color = m_highlight_text;
		}

		for (s32 j = 0; j < row->cellcount; ++j)
			drawCell(&row->cells[j], color, row_rect, client_clip);

		row_rect.UpperLeftCorner.Y += m_rowheight;
		row_rect.LowerRightCorner.Y += m_rowheight;
	}

	// Draw children
	IGUIElement::draw();
}

void GUITable::drawCell(const Cell *cell, video::SColor color,
		const core::rect<s32> &row_rect,
		const core::rect<s32> &client_clip)
{
	if ((cell->content_type == COLUMN_TYPE_TEXT)
			|| (cell->content_type == COLUMN_TYPE_TREE)) {

		core::rect<s32> text_rect = row_rect;
		text_rect.UpperLeftCorner.X = row_rect.UpperLeftCorner.X
				+ cell->xpos;
		text_rect.LowerRightCorner.X = row_rect.UpperLeftCorner.X
				+ cell->xmax;

		if (cell->color_defined)
			color = cell->color;

		if (m_font) {
			if (cell->content_type == COLUMN_TYPE_TEXT)
				m_font->draw(m_strings[cell->content_index],
						text_rect, color,
						false, true, &client_clip);
			else // tree
				m_font->draw(cell->content_index ? L"+" : L"-",
						text_rect, color,
						false, true, &client_clip);
		}
	}
	else if (cell->content_type == COLUMN_TYPE_IMAGE) {

		if (cell->content_index < 0)
			return;

		video::IVideoDriver *driver = Environment->getVideoDriver();
		video::ITexture *image = m_images[cell->content_index];

		if (image) {
			core::position2d<s32> dest_pos =
					row_rect.UpperLeftCorner;
			dest_pos.X += cell->xpos;
			core::rect<s32> source_rect(
					core::position2d<s32>(0, 0),
					image->getOriginalSize());
			s32 imgh = source_rect.LowerRightCorner.Y;
			s32 rowh = row_rect.getHeight();
			if (imgh < rowh)
				dest_pos.Y += (rowh - imgh) / 2;
			else
				source_rect.LowerRightCorner.Y = rowh;

			video::SColor color(255, 255, 255, 255);

			driver->draw2DImage(image, dest_pos, source_rect,
					&client_clip, color, true);
		}
	}
}

bool GUITable::OnEvent(const SEvent &event)
{
	if (!isEnabled())
		return IGUIElement::OnEvent(event);

	if (event.EventType == EET_KEY_INPUT_EVENT) {
		if (event.KeyInput.PressedDown && (
				event.KeyInput.Key == KEY_DOWN ||
				event.KeyInput.Key == KEY_UP   ||
				event.KeyInput.Key == KEY_HOME ||
				event.KeyInput.Key == KEY_END  ||
				event.KeyInput.Key == KEY_NEXT ||
				event.KeyInput.Key == KEY_PRIOR)) {
			s32 offset = 0;
			switch (event.KeyInput.Key) {
				case KEY_DOWN:
					offset = 1;
					break;
				case KEY_UP:
					offset = -1;
					break;
				case KEY_HOME:
					offset = - (s32) m_visible_rows.size();
					break;
				case KEY_END:
					offset = m_visible_rows.size();
					break;
				case KEY_NEXT:
					offset = AbsoluteRect.getHeight() / m_rowheight;
					break;
				case KEY_PRIOR:
					offset = - (s32) (AbsoluteRect.getHeight() / m_rowheight);
					break;
				default:
					break;
			}
			s32 old_selected = m_selected;
			s32 rowcount = m_visible_rows.size();
			if (rowcount != 0) {
				m_selected = rangelim(m_selected + offset, 0, rowcount-1);
				autoScroll();
			}

			if (m_selected != old_selected)
				sendTableEvent(0, false);

			return true;
		}
		else if (event.KeyInput.PressedDown && (
				event.KeyInput.Key == KEY_LEFT ||
				event.KeyInput.Key == KEY_RIGHT)) {
			// Open/close subtree via keyboard
			if (m_selected >= 0) {
				int dir = event.KeyInput.Key == KEY_LEFT ? -1 : 1;
				toggleVisibleTree(m_selected, dir, true);
			}
			return true;
		}
		else if (!event.KeyInput.PressedDown && (
				event.KeyInput.Key == KEY_RETURN ||
				event.KeyInput.Key == KEY_SPACE)) {
			sendTableEvent(0, true);
			return true;
		}
		else if (event.KeyInput.Key == KEY_ESCAPE ||
				event.KeyInput.Key == KEY_SPACE) {
			// pass to parent
		}
		else if (event.KeyInput.PressedDown && event.KeyInput.Char) {
			// change selection based on text as it is typed
			u64 now = porting::getTimeMs();
			if (now - m_keynav_time >= 500)
				m_keynav_buffer = L"";
			m_keynav_time = now;

			// add to key buffer if not a key repeat
			if (!(m_keynav_buffer.size() == 1 &&
					m_keynav_buffer[0] == event.KeyInput.Char)) {
				m_keynav_buffer.append(event.KeyInput.Char);
			}

			// find the selected item, starting at the current selection
			// don't change selection if the key buffer matches the current item
			s32 old_selected = m_selected;
			s32 start = MYMAX(m_selected, 0);
			s32 rowcount = m_visible_rows.size();
			for (s32 k = 1; k < rowcount; ++k) {
				s32 current = start + k;
				if (current >= rowcount)
					current -= rowcount;
				if (doesRowStartWith(getRow(current), m_keynav_buffer)) {
					m_selected = current;
					break;
				}
			}
			autoScroll();
			if (m_selected != old_selected)
				sendTableEvent(0, false);

			return true;
		}
	}
	if (event.EventType == EET_MOUSE_INPUT_EVENT) {
		core::position2d<s32> p(event.MouseInput.X, event.MouseInput.Y);

		if (event.MouseInput.Event == EMIE_MOUSE_WHEEL) {
			m_scrollbar->setPos(m_scrollbar->getPos() +
					(event.MouseInput.Wheel < 0 ? -3 : 3) *
					- (s32) m_rowheight / 2);
			return true;
		}

		// Find hovered row and cell
		bool really_hovering = false;
		s32 row_i = getRowAt(p.Y, really_hovering);
		const Cell *cell = NULL;
		if (really_hovering) {
			s32 cell_j = getCellAt(p.X, row_i);
			if (cell_j >= 0)
				cell = &(getRow(row_i)->cells[cell_j]);
		}

		// Update tooltip
		setToolTipText(cell ? m_strings[cell->tooltip_index].c_str() : L"");

		// Fix for #1567/#1806:
		// IGUIScrollBar passes double click events to its parent,
		// which we don't want. Detect this case and discard the event
		if (event.MouseInput.Event != EMIE_MOUSE_MOVED &&
				m_scrollbar->isVisible() &&
				m_scrollbar->isPointInside(p))
			return true;

		if (event.MouseInput.isLeftPressed() &&
				(isPointInside(p) ||
				 event.MouseInput.Event == EMIE_MOUSE_MOVED)) {
			s32 sel_column = 0;
			bool sel_doubleclick = (event.MouseInput.Event
					== EMIE_LMOUSE_DOUBLE_CLICK);
			bool plusminus_clicked = false;

			// For certain events (left click), report column
			// Also open/close subtrees when the +/- is clicked
			if (cell && (
					event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN ||
					event.MouseInput.Event == EMIE_LMOUSE_DOUBLE_CLICK ||
					event.MouseInput.Event == EMIE_LMOUSE_TRIPLE_CLICK)) {
				sel_column = cell->reported_column;
				if (cell->content_type == COLUMN_TYPE_TREE)
					plusminus_clicked = true;
			}

			if (plusminus_clicked) {
				if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
					toggleVisibleTree(row_i, 0, false);
				}
			}
			else {
				// Normal selection
				s32 old_selected = m_selected;
				m_selected = row_i;
				autoScroll();

				if (m_selected != old_selected ||
						sel_column >= 1 ||
						sel_doubleclick) {
					sendTableEvent(sel_column, sel_doubleclick);
				}

				// Treeview: double click opens/closes trees
				if (m_has_tree_column && sel_doubleclick) {
					toggleVisibleTree(m_selected, 0, false);
				}
			}
		}
		return true;
	}
	if (event.EventType == EET_GUI_EVENT &&
			event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED &&
			event.GUIEvent.Caller == m_scrollbar) {
		// Don't pass events from our scrollbar to the parent
		return true;
	}

	return IGUIElement::OnEvent(event);
}

/******************************************************************************/
/* GUITable helper functions                                                  */
/******************************************************************************/

s32 GUITable::allocString(const std::string &text)
{
	std::map<std::string, s32>::iterator it = m_alloc_strings.find(text);
	if (it == m_alloc_strings.end()) {
		s32 id = m_strings.size();
		std::wstring wtext = utf8_to_wide(text);
		m_strings.push_back(core::stringw(wtext.c_str()));
		m_alloc_strings.insert(std::make_pair(text, id));
		return id;
	}
	else {
		return it->second;
	}
}

s32 GUITable::allocImage(const std::string &imagename)
{
	std::map<std::string, s32>::iterator it = m_alloc_images.find(imagename);
	if (it == m_alloc_images.end()) {
		s32 id = m_images.size();
		m_images.push_back(m_tsrc->getTexture(imagename));
		m_alloc_images.insert(std::make_pair(imagename, id));
		return id;
	}
	else {
		return it->second;
	}
}

void GUITable::allocationComplete()
{
	// Called when done with creating rows and cells from table data,
	// i.e. when allocString and allocImage won't be called anymore
	m_alloc_strings.clear();
	m_alloc_images.clear();
}

const GUITable::Row* GUITable::getRow(s32 i) const
{
	if (i >= 0 && i < (s32) m_visible_rows.size())
		return &m_rows[m_visible_rows[i]];
	else
		return NULL;
}

bool GUITable::doesRowStartWith(const Row *row, const core::stringw &str) const
{
	if (row == NULL)
		return false;

	for (s32 j = 0; j < row->cellcount; ++j) {
		Cell *cell = &row->cells[j];
		if (cell->content_type == COLUMN_TYPE_TEXT) {
			const core::stringw &cellstr = m_strings[cell->content_index];
			if (cellstr.size() >= str.size() &&
					str.equals_ignore_case(cellstr.subString(0, str.size())))
				return true;
		}
	}
	return false;
}

s32 GUITable::getRowAt(s32 y, bool &really_hovering) const
{
	really_hovering = false;

	s32 rowcount = m_visible_rows.size();
	if (rowcount == 0)
		return -1;

	// Use arithmetic to find row
	s32 rel_y = y - AbsoluteRect.UpperLeftCorner.Y - 1;
	s32 i = (rel_y + m_scrollbar->getPos()) / m_rowheight;

	if (i >= 0 && i < rowcount) {
		really_hovering = true;
		return i;
	}
	else if (i < 0)
		return 0;
	else
		return rowcount - 1;

}

s32 GUITable::getCellAt(s32 x, s32 row_i) const
{
	const Row *row = getRow(row_i);
	if (row == NULL)
		return -1;

	// Use binary search to find cell in row
	s32 rel_x = x - AbsoluteRect.UpperLeftCorner.X - 1;
	s32 jmin = 0;
	s32 jmax = row->cellcount - 1;
	while (jmin < jmax) {
		s32 pivot = jmin + (jmax - jmin) / 2;
		assert(pivot >= 0 && pivot < row->cellcount);
		const Cell *cell = &row->cells[pivot];

		if (rel_x >= cell->xmin && rel_x <= cell->xmax)
			return pivot;
		else if (rel_x < cell->xmin)
			jmax = pivot - 1;
		else
			jmin = pivot + 1;
	}

	if (jmin >= 0 && jmin < row->cellcount &&
			rel_x >= row->cells[jmin].xmin &&
			rel_x <= row->cells[jmin].xmax)
		return jmin;
	else
		return -1;
}

void GUITable::autoScroll()
{
	if (m_selected >= 0) {
		s32 pos = m_scrollbar->getPos();
		s32 maxpos = m_selected * m_rowheight;
		s32 minpos = maxpos - (AbsoluteRect.getHeight() - m_rowheight);
		if (pos > maxpos)
			m_scrollbar->setPos(maxpos);
		else if (pos < minpos)
			m_scrollbar->setPos(minpos);
	}
}

void GUITable::updateScrollBar()
{
	s32 totalheight = m_rowheight * m_visible_rows.size();
	s32 scrollmax = MYMAX(0, totalheight - AbsoluteRect.getHeight());
	m_scrollbar->setVisible(scrollmax > 0);
	m_scrollbar->setMax(scrollmax);
	m_scrollbar->setSmallStep(m_rowheight);
	m_scrollbar->setLargeStep(2 * m_rowheight);
}

void GUITable::sendTableEvent(s32 column, bool doubleclick)
{
	m_sel_column = column;
	m_sel_doubleclick = doubleclick;
	if (Parent) {
		SEvent e;
		memset(&e, 0, sizeof e);
		e.EventType = EET_GUI_EVENT;
		e.GUIEvent.Caller = this;
		e.GUIEvent.Element = 0;
		e.GUIEvent.EventType = gui::EGET_TABLE_CHANGED;
		Parent->OnEvent(e);
	}
}

void GUITable::getOpenedTrees(std::set<s32> &opened_trees) const
{
	opened_trees.clear();
	s32 rowcount = m_rows.size();
	for (s32 i = 0; i < rowcount - 1; ++i) {
		if (m_rows[i].indent < m_rows[i+1].indent &&
				m_rows[i+1].visible_index != -2)
			opened_trees.insert(i);
	}
}

void GUITable::setOpenedTrees(const std::set<s32> &opened_trees)
{
	s32 old_selected = -1;
	if (m_selected >= 0)
		old_selected = m_visible_rows[m_selected];

	std::vector<s32> parents;
	std::vector<s32> closed_parents;

	m_visible_rows.clear();

	for (size_t i = 0; i < m_rows.size(); ++i) {
		Row *row = &m_rows[i];

		// Update list of ancestors
		while (!parents.empty() && m_rows[parents.back()].indent >= row->indent)
			parents.pop_back();
		while (!closed_parents.empty() &&
				m_rows[closed_parents.back()].indent >= row->indent)
			closed_parents.pop_back();

		assert(closed_parents.size() <= parents.size());

		if (closed_parents.empty()) {
			// Visible row
			row->visible_index = m_visible_rows.size();
			m_visible_rows.push_back(i);
		}
		else if (parents.back() == closed_parents.back()) {
			// Invisible row, direct parent is closed
			row->visible_index = -2;
		}
		else {
			// Invisible row, direct parent is open, some ancestor is closed
			row->visible_index = -1;
		}

		// If not a leaf, add to parents list
		if (i < m_rows.size()-1 && row->indent < m_rows[i+1].indent) {
			parents.push_back(i);

			s32 content_index = 0; // "-", open
			if (opened_trees.count(i) == 0) {
				closed_parents.push_back(i);
				content_index = 1; // "+", closed
			}

			// Update all cells of type "tree"
			for (s32 j = 0; j < row->cellcount; ++j)
				if (row->cells[j].content_type == COLUMN_TYPE_TREE)
					row->cells[j].content_index = content_index;
		}
	}

	updateScrollBar();

	// m_selected must be updated since it is a visible row index
	if (old_selected >= 0)
		m_selected = m_rows[old_selected].visible_index;
}

void GUITable::openTree(s32 to_open)
{
	std::set<s32> opened_trees;
	getOpenedTrees(opened_trees);
	opened_trees.insert(to_open);
	setOpenedTrees(opened_trees);
}

void GUITable::closeTree(s32 to_close)
{
	std::set<s32> opened_trees;
	getOpenedTrees(opened_trees);
	opened_trees.erase(to_close);
	setOpenedTrees(opened_trees);
}

// The following function takes a visible row index (hidden rows skipped)
// dir: -1 = left (close), 0 = auto (toggle), 1 = right (open)
void GUITable::toggleVisibleTree(s32 row_i, int dir, bool move_selection)
{
	// Check if the chosen tree is currently open
	const Row *row = getRow(row_i);
	if (row == NULL)
		return;

	bool was_open = false;
	for (s32 j = 0; j < row->cellcount; ++j) {
		if (row->cells[j].content_type == COLUMN_TYPE_TREE) {
			was_open = row->cells[j].content_index == 0;
			break;
		}
	}

	// Check if the chosen tree should be opened
	bool do_open = !was_open;
	if (dir < 0)
		do_open = false;
	else if (dir > 0)
		do_open = true;

	// Close or open the tree; the heavy lifting is done by setOpenedTrees
	if (was_open && !do_open)
		closeTree(m_visible_rows[row_i]);
	else if (!was_open && do_open)
		openTree(m_visible_rows[row_i]);

	// Change selected row if requested by caller,
	// this is useful for keyboard navigation
	if (move_selection) {
		s32 sel = row_i;
		if (was_open && do_open) {
			// Move selection to first child
			const Row *maybe_child = getRow(sel + 1);
			if (maybe_child && maybe_child->indent > row->indent)
				sel++;
		}
		else if (!was_open && !do_open) {
			// Move selection to parent
			assert(getRow(sel) != NULL);
			while (sel > 0 && getRow(sel - 1)->indent >= row->indent)
				sel--;
			sel--;
			if (sel < 0)  // was root already selected?
				sel = row_i;
		}
		if (sel != m_selected) {
			m_selected = sel;
			autoScroll();
			sendTableEvent(0, false);
		}
	}
}

void GUITable::alignContent(Cell *cell, s32 xmax, s32 content_width, s32 align)
{
	// requires that cell.xmin, cell.xmax are properly set
	// align = 0: left aligned, 1: centered, 2: right aligned, 3: inline
	if (align == 0) {
		cell->xpos = cell->xmin;
		cell->xmax = xmax;
	}
	else if (align == 1) {
		cell->xpos = (cell->xmin + xmax - content_width) / 2;
		cell->xmax = xmax;
	}
	else if (align == 2) {
		cell->xpos = xmax - content_width;
		cell->xmax = xmax;
	}
	else {
		// inline alignment: the cells of the column don't have an aligned
		// right border, the right border of each cell depends on the content
		cell->xpos = cell->xmin;
		cell->xmax = cell->xmin + content_width;
	}
}


code:
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#ifndef GUITABLE_HEADER
#define GUITABLE_HEADER

#include <map>
#include <set>
#include <string>
#include <vector>
#include <iostream>

#include "irrlichttypes_extrabloated.h"

class ISimpleTextureSource;

/*
	A table GUI element for GUIFormSpecMenu.

	Sends a EGET_TABLE_CHANGED event to the parent when
	an item is selected or double-clicked.
	Call checkEvent() to get info.

	Credits: The interface and implementation of this class are (very)
	loosely based on the Irrlicht classes CGUITable and CGUIListBox.
	CGUITable and CGUIListBox are licensed under the Irrlicht license;
	they are Copyright (C) 2002-2012 Nikolaus Gebhardt
*/
class GUITable : public gui::IGUIElement
{
public:
	/*
		Stores dynamic data that should be preserved
		when updating a formspec
	*/
	struct DynamicData
	{
		s32 selected;
		s32 scrollpos;
		s32 keynav_time;
		core::stringw keynav_buffer;
		std::set<s32> opened_trees;

		DynamicData()
		{
			selected = 0;
			scrollpos = 0;
			keynav_time = 0;
		}
	};

	/*
		An option of the form <name>=<value>
	*/
	struct Option
	{
		std::string name;
		std::string value;

		Option(const std::string &name_, const std::string &value_) :
			name(name_),
			value(value_)
		{}
	};

	/*
		A list of options that concern the entire table
	*/
	typedef std::vector<Option> TableOptions;

	/*
		A column with options
	*/
	struct TableColumn
	{
		std::string type;
		std::vector<Option> options;
	};
	typedef std::vector<TableColumn> TableColumns;

	GUITable(gui::IGUIEnvironment *env,
			gui::IGUIElement *parent, s32 id,
			core::rect<s32> rectangle,
			ISimpleTextureSource *tsrc);

	virtual ~GUITable();

	/* Split a string of the form "name=value" into name and value */
	static Option splitOption(const std::string &str);

	/* Set textlist-like options, columns and data */
	void setTextList(const std::vector<std::string> &content,
			bool transparent);

	/* Set generic table options, columns and content */
	// Adds empty strings to end of content if there is an incomplete row
	void setTable(const TableOptions &options,
			const TableColumns &columns,
			std::vector<std::string> &content);

	/* Clear the table */
	void clear();

	/* Get info about last event (string such as "CHG:1:2") */
	// Call this after EGET_TABLE_CHANGED
	std::string checkEvent();

	/* Get index of currently selected row (first=1; 0 if none selected) */
	s32 getSelected() const;

	/* Set currently selected row (first=1; 0 if none selected) */
	// If given index is not visible at the moment, select its parent
	// Autoscroll to make the selected row fully visible
	void setSelected(s32 index);

	/* Get selection, scroll position and opened (sub)trees */
	DynamicData getDynamicData() const;

	/* Set selection, scroll position and opened (sub)trees */
	void setDynamicData(const DynamicData &dyndata);

	/* Returns "GUITable" */
	virtual const c8* getTypeName() const;

	/* Must be called when position or size changes */
	virtual void updateAbsolutePosition();

	/* Irrlicht draw method */
	virtual void draw();

	/* Irrlicht event handler */
	virtual bool OnEvent(const SEvent &event);

protected:
	enum ColumnType {
		COLUMN_TYPE_TEXT,
		COLUMN_TYPE_IMAGE,
		COLUMN_TYPE_COLOR,
		COLUMN_TYPE_INDENT,
		COLUMN_TYPE_TREE,
	};

	struct Cell {
		s32 xmin;
		s32 xmax;
		s32 xpos;
		ColumnType content_type;
		s32 content_index;
		s32 tooltip_index;
		video::SColor color;
		bool color_defined;
		s32 reported_column;
	};

	struct Row {
		Cell *cells;
		s32 cellcount;
		s32 indent;
		// visible_index >= 0: is index of row in m_visible_rows
		// visible_index == -1: parent open but other ancestor closed
		// visible_index == -2: parent closed
		s32 visible_index;
	};

	// Texture source
	ISimpleTextureSource *m_tsrc;

	// Table content (including hidden rows)
	std::vector<Row> m_rows;
	// Table content (only visible; indices into m_rows)
	std::vector<s32> m_visible_rows;
	bool m_is_textlist;
	bool m_has_tree_column;

	// Selection status
	s32 m_selected; // index of row (1...n), or 0 if none selected
	s32 m_sel_column;
	bool m_sel_doubleclick;

	// Keyboard navigation stuff
	u64 m_keynav_time;
	core::stringw m_keynav_buffer;

	// Drawing and geometry information
	bool m_border;
	video::SColor m_color;
	video::SColor m_background;
	video::SColor m_highlight;
	video::SColor m_highlight_text;
	s32 m_rowheight;
	gui::IGUIFont *m_font;
	gui::IGUIScrollBar *m_scrollbar;

	// Allocated strings and images
	std::vector<core::stringw> m_strings;
	std::vector<video::ITexture*> m_images;
	std::map<std::string, s32> m_alloc_strings;
	std::map<std::string, s32> m_alloc_images;

	s32 allocString(const std::string &text);
	s32 allocImage(const std::string &imagename);
	void allocationComplete();

	// Helper for draw() that draws a single cell
	void drawCell(const Cell *cell, video::SColor color,
			const core::rect<s32> &rowrect,
			const core::rect<s32> &client_clip);

	// Returns the i-th visible row (NULL if i is invalid)
	const Row *getRow(s32 i) const;

	// Key navigation helper
	bool doesRowStartWith(const Row *row, const core::stringw &str) const;

	// Returns the row at a given screen Y coordinate
	// Returns index i such that m_rows[i] is valid (or -1 on error)
	s32 getRowAt(s32 y, bool &really_hovering) const;

	// Returns the cell at a given screen X coordinate within m_rows[row_i]
	// Returns index j such that m_rows[row_i].cells[j] is valid
	// (or -1 on error)
	s32 getCellAt(s32 x, s32 row_i) const;

	// Make the selected row fully visible
	void autoScroll();

	// Should be called when m_rowcount or m_rowheight changes
	void updateScrollBar();

	// Sends EET_GUI_EVENT / EGET_TABLE_CHANGED to parent
	void sendTableEvent(s32 column, bool doubleclick);

	// Functions that help deal with hidden rows
	// The following functions take raw row indices (hidden rows not skipped)
	void getOpenedTrees(std::set<s32> &opened_trees) const;
	void setOpenedTrees(const std::set<s32> &opened_trees);
	void openTree(s32 to_open);
	void closeTree(s32 to_close);
	// The following function takes a visible row index (hidden rows skipped)
	// dir: -1 = left (close), 0 = auto (toggle), 1 = right (open)
	void toggleVisibleTree(s32 row_i, int dir, bool move_selection);

	// Aligns cell content in column according to alignment specification
	// align = 0: left aligned, 1: centered, 2: right aligned, 3: inline
	static void alignContent(Cell *cell, s32 xmax, s32 content_width,
			s32 align);
};

#endif


code:
/*
Part of Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2013 Ciaran Gultnieks <ciaran@ciarang.com>
Copyright (C) 2013 RealBadAngel, Maciej Kasatkin <mk@realbadangel.pl>

Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#include "guiVolumeChange.h"
#include "debug.h"
#include "serialization.h"
#include <string>
#include <IGUICheckBox.h>
#include <IGUIButton.h>
#include <IGUIScrollBar.h>
#include <IGUIStaticText.h>
#include <IGUIFont.h>
#include "settings.h"

#include "gettext.h"

const int ID_soundText = 263;
const int ID_soundExitButton = 264;
const int ID_soundSlider = 265;

GUIVolumeChange::GUIVolumeChange(gui::IGUIEnvironment* env,
		gui::IGUIElement* parent, s32 id,
		IMenuManager *menumgr
):
	GUIModalMenu(env, parent, id, menumgr)
{
}

GUIVolumeChange::~GUIVolumeChange()
{
	removeChildren();
}

void GUIVolumeChange::removeChildren()
{
	if (gui::IGUIElement *e = getElementFromId(ID_soundText))
		e->remove();

	if (gui::IGUIElement *e = getElementFromId(ID_soundExitButton))
		e->remove();

	if (gui::IGUIElement *e = getElementFromId(ID_soundSlider))
		e->remove();
}

void GUIVolumeChange::regenerateGui(v2u32 screensize)
{
	/*
		Remove stuff
	*/
	removeChildren();

	/*
		Calculate new sizes and positions
	*/
	DesiredRect = core::rect<s32>(
		screensize.X/2 - 380/2,
		screensize.Y/2 - 200/2,
		screensize.X/2 + 380/2,
		screensize.Y/2 + 200/2
	);
	recalculateAbsolutePosition(false);

	v2s32 size = DesiredRect.getSize();
	int volume = (int)(g_settings->getFloat("sound_volume") * 100);

	/*
		Add stuff
	*/
	{
		core::rect<s32> rect(0, 0, 160, 20);
		rect = rect + v2s32(size.X / 2 - 80, size.Y / 2 - 35);

		const wchar_t *text = wgettext("Sound Volume: ");
		core::stringw volume_text = text;
		delete [] text;

		volume_text += core::stringw(volume) + core::stringw("%");
		Environment->addStaticText(volume_text.c_str(), rect, false,
				true, this, ID_soundText);
	}
	{
		core::rect<s32> rect(0, 0, 80, 30);
		rect = rect + v2s32(size.X/2-80/2, size.Y/2+55);
		const wchar_t *text = wgettext("Exit");
		Environment->addButton(rect, this, ID_soundExitButton,
			text);
		delete[] text;
	}
	{
		core::rect<s32> rect(0, 0, 300, 20);
		rect = rect + v2s32(size.X/2-150, size.Y/2);
		gui::IGUIScrollBar *e = Environment->addScrollBar(true,
			rect, this, ID_soundSlider);
		e->setMax(100);
		e->setPos(volume);
	}
}

void GUIVolumeChange::drawMenu()
{
	gui::IGUISkin* skin = Environment->getSkin();
	if (!skin)
		return;
	video::IVideoDriver* driver = Environment->getVideoDriver();
	video::SColor bgcolor(140, 0, 0, 0);
	driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect);
	gui::IGUIElement::draw();
}

bool GUIVolumeChange::OnEvent(const SEvent& event)
{
	if (event.EventType == EET_KEY_INPUT_EVENT) {
		if (event.KeyInput.Key == KEY_ESCAPE && event.KeyInput.PressedDown) {
			quitMenu();
			return true;
		}

		if (event.KeyInput.Key == KEY_RETURN && event.KeyInput.PressedDown) {
			quitMenu();
			return true;
		}
	}

	if (event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED) {
		if (event.GUIEvent.Caller->getID() == ID_soundExitButton) {
			quitMenu();
			return true;
		}
	}

	if (event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED) {
		if (event.GUIEvent.Caller->getID() == ID_soundSlider) {
			s32 pos = ((gui::IGUIScrollBar*)event.GUIEvent.Caller)->getPos();
			g_settings->setFloat("sound_volume", (float) pos / 100);

			gui::IGUIElement *e = getElementFromId(ID_soundText);
			const wchar_t *text = wgettext("Sound Volume: ");
			core::stringw volume_text = text;
			delete [] text;

			volume_text += core::stringw(pos) + core::stringw("%");
			e->setText(volume_text.c_str());
			return true;
		}
	}

	return Parent ? Parent->OnEvent(event) : false;
}


code:
/*
Part of Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2013 Ciaran Gultnieks <ciaran@ciarang.com>
Copyright (C) 2013 RealBadAngel, Maciej Kasatkin <mk@realbadangel.pl>

Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#ifndef GUIVOLUMECHANGE_HEADER
#define GUIVOLUMECHANGE_HEADER

#include "irrlichttypes_extrabloated.h"
#include "modalMenu.h"
#include "client.h"
#include <string>

class GUIVolumeChange : public GUIModalMenu
{
public:
	GUIVolumeChange(gui::IGUIEnvironment* env,
			gui::IGUIElement* parent, s32 id,
			IMenuManager *menumgr);
	~GUIVolumeChange();

	void removeChildren();
	/*
		Remove and re-add (or reposition) stuff
	*/
	void regenerateGui(v2u32 screensize);

	void drawMenu();

	bool OnEvent(const SEvent& event);

	bool pausesGame() { return true; }
};

#endif


code:
/*
Copyright (C) 2015 Aaron Suen <warr1024@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "guiscalingfilter.h"
#include "imagefilters.h"
#include "settings.h"
#include "util/numeric.h"
#include <stdio.h>

/* Maintain a static cache to store the images that correspond to textures
 * in a format that's manipulable by code.  Some platforms exhibit issues
 * converting textures back into images repeatedly, and some don't even
 * allow it at all.
 */
std::map<io::path, video::IImage *> g_imgCache;

/* Maintain a static cache of all pre-scaled textures.  These need to be
 * cleared as well when the cached images.
 */
std::map<io::path, video::ITexture *> g_txrCache;

/* Manually insert an image into the cache, useful to avoid texture-to-image
 * conversion whenever we can intercept it.
 */
void guiScalingCache(io::path key, video::IVideoDriver *driver, video::IImage *value)
{
	if (!g_settings->getBool("gui_scaling_filter"))
		return;
	video::IImage *copied = driver->createImage(value->getColorFormat(),
			value->getDimension());
	value->copyTo(copied);
	g_imgCache[key] = copied;
}

// Manually clear the cache, e.g. when switching to different worlds.
void guiScalingCacheClear(video::IVideoDriver *driver)
{
	for (std::map<io::path, video::IImage *>::iterator it = g_imgCache.begin();
			it != g_imgCache.end(); ++it) {
		if (it->second != NULL)
			it->second->drop();
	}
	g_imgCache.clear();
	for (std::map<io::path, video::ITexture *>::iterator it = g_txrCache.begin();
			it != g_txrCache.end(); ++it) {
		if (it->second != NULL)
			driver->removeTexture(it->second);
	}
	g_txrCache.clear();
}

/* Get a cached, high-quality pre-scaled texture for display purposes.  If the
 * texture is not already cached, attempt to create it.  Returns a pre-scaled texture,
 * or the original texture if unable to pre-scale it.
 */
video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver,
		video::ITexture *src, const core::rect<s32> &srcrect,
		const core::rect<s32> &destrect)
{
	if (src == NULL)
		return src;
	if (!g_settings->getBool("gui_scaling_filter"))
		return src;

	// Calculate scaled texture name.
	char rectstr[200];
	snprintf(rectstr, sizeof(rectstr), "%d:%d:%d:%d:%d:%d",
		srcrect.UpperLeftCorner.X,
		srcrect.UpperLeftCorner.Y,
		srcrect.getWidth(),
		srcrect.getHeight(),
		destrect.getWidth(),
		destrect.getHeight());
	io::path origname = src->getName().getPath();
	io::path scalename = origname + "@guiScalingFilter:" + rectstr;

	// Search for existing scaled texture.
	video::ITexture *scaled = g_txrCache[scalename];
	if (scaled)
		return scaled;

	// Try to find the texture converted to an image in the cache.
	// If the image was not found, try to extract it from the texture.
	video::IImage* srcimg = g_imgCache[origname];
	if (srcimg == NULL) {
		if (!g_settings->getBool("gui_scaling_filter_txr2img"))
			return src;
		srcimg = driver->createImageFromData(src->getColorFormat(),
			src->getSize(), src->lock(), false);
		src->unlock();
		g_imgCache[origname] = srcimg;
	}

	// Create a new destination image and scale the source into it.
	imageCleanTransparent(srcimg, 0);
	video::IImage *destimg = driver->createImage(src->getColorFormat(),
			core::dimension2d<u32>((u32)destrect.getWidth(),
			(u32)destrect.getHeight()));
	imageScaleNNAA(srcimg, srcrect, destimg);

#ifdef __ANDROID__
	// Android is very picky about textures being powers of 2, so expand
	// the image dimensions to the next power of 2, if necessary, for
	// that platform.
	video::IImage *po2img = driver->createImage(src->getColorFormat(),
			core::dimension2d<u32>(npot2((u32)destrect.getWidth()),
			npot2((u32)destrect.getHeight())));
	po2img->fill(video::SColor(0, 0, 0, 0));
	destimg->copyTo(po2img);
	destimg->drop();
	destimg = po2img;
#endif

	// Convert the scaled image back into a texture.
	scaled = driver->addTexture(scalename, destimg, NULL);
	destimg->drop();
	g_txrCache[scalename] = scaled;

	return scaled;
}

/* Convenience wrapper for guiScalingResizeCached that accepts parameters that
 * are available at GUI imagebutton creation time.
 */
video::ITexture *guiScalingImageButton(video::IVideoDriver *driver,
		video::ITexture *src, s32 width, s32 height)
{
	if (src == NULL)
		return src;
	return guiScalingResizeCached(driver, src,
		core::rect<s32>(0, 0, src->getSize().Width, src->getSize().Height),
		core::rect<s32>(0, 0, width, height));
}

/* Replacement for driver->draw2DImage() that uses the high-quality pre-scaled
 * texture, if configured.
 */
void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr,
		const core::rect<s32> &destrect, const core::rect<s32> &srcrect,
		const core::rect<s32> *cliprect, const video::SColor *const colors,
		bool usealpha)
{
	// Attempt to pre-scale image in software in high quality.
	video::ITexture *scaled = guiScalingResizeCached(driver, txr, srcrect, destrect);
	if (scaled == NULL)
		return;

	// Correct source rect based on scaled image.
	const core::rect<s32> mysrcrect = (scaled != txr)
		? core::rect<s32>(0, 0, destrect.getWidth(), destrect.getHeight())
		: srcrect;

	driver->draw2DImage(scaled, destrect, mysrcrect, cliprect, colors, usealpha);
}


code:
/*
Copyright (C) 2015 Aaron Suen <warr1024@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef _GUI_SCALING_FILTER_H_
#define _GUI_SCALING_FILTER_H_

#include "irrlichttypes_extrabloated.h"

/* Manually insert an image into the cache, useful to avoid texture-to-image
 * conversion whenever we can intercept it.
 */
void guiScalingCache(io::path key, video::IVideoDriver *driver, video::IImage *value);

// Manually clear the cache, e.g. when switching to different worlds.
void guiScalingCacheClear(video::IVideoDriver *driver);

/* Get a cached, high-quality pre-scaled texture for display purposes.  If the
 * texture is not already cached, attempt to create it.  Returns a pre-scaled texture,
 * or the original texture if unable to pre-scale it.
 */
video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver, video::ITexture *src,
		const core::rect<s32> &srcrect, const core::rect<s32> &destrect);

/* Convenience wrapper for guiScalingResizeCached that accepts parameters that
 * are available at GUI imagebutton creation time.
 */
video::ITexture *guiScalingImageButton(video::IVideoDriver *driver, video::ITexture *src,
		s32 width, s32 height);

/* Replacement for driver->draw2DImage() that uses the high-quality pre-scaled
 * texture, if configured.
 */
void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr,
		const core::rect<s32> &destrect, const core::rect<s32> &srcrect,
		const core::rect<s32> *cliprect = 0, const video::SColor *const colors = 0,
		bool usealpha = false);

#endif