/*
 * Copyright 2011, Mason Giles. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *  1. Redistributions of source code must retain the above copyright notice and this
 *     list of conditions.
 *  2. Redistributions in binary form must reproduce the above copyright notice and this
 *     list of conditions in the documentation and/or other materials provided with the
 *     distribution.
 */

#include "stdafx.h"
#include "mgui.h"
#include <map>
#include <vector>

#define WIN32_LEAN_AND_MEAN
#ifndef WINVER
	#define WINVER 0x0500
#endif
#include <windows.h>
#include <ShlObj.h>		// Dialogs
#include <commctrl.h>
#include <richedit.h>
#include <Uxtheme.h>	// Removing styles
#ifdef _MSC_VER
	#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
	#pragma comment(lib, "comctl32.lib")
	#pragma comment(lib, "uxtheme.lib")
#endif


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Class and struct definitions
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


struct MGControlData {
	HWND      ParentHWnd;
	HINSTANCE hInstance;
	HWND      hWnd;
	unsigned int ID;
};

struct MGWindowData {
	bool					bActive;
	HMENU					hMenu;
	HWND					hStatus;
	bool					bStatusBarVisible;
	std::vector<MGMenuItem*> childMenus;
	std::vector<ACCEL>		childMenuAccels;
	HACCEL					hAccelTable;
	std::map<unsigned int, MGObject*> controlIDs;
};

struct MGMenuItemData {
	std::vector<MGMenuItem*>	children;
	std::wstring				caption;
	HMENU						hMenu;
	unsigned int				ID;
	MGKeyCombination			shortcut;
	MGWindow*                   parentWnd;
	HWND                        parentHWnd;
};

struct MGMenuItemRelation {
	HMENU				parent;
	std::vector<ACCEL> *menuAccels;
};

struct MGListBoxData {
	std::map<int, unsigned int>	colors;
	unsigned int				default_color;
};

struct MGEventData {
	HWND   hwnd;
	UINT   uMsg;
	WPARAM wParam;
	LPARAM lParam;
};

class MGEventHandler {
private:
	MGControl *m_master;
	LONG       m_oldProc;
	HWND       m_hWnd;

public:
	MGEventHandler(MGControl *master);
	~MGEventHandler();
	void subclassWnd(HWND hWnd);

	LRESULT CALLBACK Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
	static LRESULT CALLBACK stProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);	
};

class MGFontManager {
private:
	MGFontManager();
	static MGFontManager* singleton;

	HFONT hfMain; // Caches global MessageFont as an HFONT
	TSTRING fontFace;
	int  fontTwipSize;
	bool  bICCE; // Has InitCommonControlsEx() been called for this application?	

public:
	static MGFontManager* getInstance();

	int  LogheightToTwips( int lfHeight);
	int  LogheightToPixels(int lfHeight);
	void SetControlFont(HWND hWnd);
	void EnsureCommonControls();

	const TCHAR* getFontFace() {
		return fontFace.c_str();
	}
	int getFontTwipHeight() {
		return fontTwipSize;
	}
	int getFontPixelHeight() {
		return fontTwipSize / 20;
	}	
};


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// MGVirtualKey
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


static unsigned int MGVKToNative(int mgvk) {
	static unsigned int mgvk_lookup[] = {
		// Misc. symbols
		0x09, 0x0D, 0x18, 0x20, 0x2D, 0xC0,
		// Numbers 0..9
		0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
		// Alphabet A..Z
		0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x50,
		0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x60,
		0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
		// F-keys F1..F12
		0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
		0x80, 0x81
	};
	if (mgvk < MGVK_MAX_VIRTKEYS) return mgvk_lookup[mgvk];
	return 0;
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// MGAction
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


struct MGActionData {
	bool       assigned;
	MGWindow*  object;
	MGCallback function;
};

MGAction::MGAction() {
	d = new MGActionData;
	d->assigned = false;
};

MGAction::~MGAction() {
	if (d) { delete d; d = NULL; }
}

void MGAction::assign(MGWindow* object, MGCallback function) {
	d->object = object;
	d->function = function;
	d->assigned = true;
}

void MGAction::invoke() {
	if (d->assigned) ((d->object) ->* (d->function))();
};


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Font manager
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


MGFontManager* MGFontManager::singleton = 0;

MGFontManager::MGFontManager() {
	hfMain = (HFONT)0;
	fontTwipSize = 0;
	bICCE = false;
	singleton = 0;
}

MGFontManager* MGFontManager::getInstance() {
	if (! singleton) singleton = new MGFontManager();
	return singleton;
}

int MGFontManager::LogheightToTwips(int lfHeight) {
	HDC hDC = GetDC(GetDesktopWindow() );
	int PPI_Y = GetDeviceCaps(hDC, LOGPIXELSY);
	ReleaseDC( GetDesktopWindow(), hDC);
	return abs( (lfHeight*72*20) / PPI_Y);
}

void MGFontManager::SetControlFont(HWND hWnd) {
	if (hfMain==(HFONT)0) {

		// Retrieve system font
		NONCLIENTMETRICS ncmSystem = {0}; // Changes with NT6, so ensure WINVER is set..
		ncmSystem.cbSize = sizeof(ncmSystem);
		SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(ncmSystem), &ncmSystem, 0);
		hfMain = CreateFontIndirect(&ncmSystem.lfMessageFont);

		// Cache facename and height in twips
		fontFace.assign( ncmSystem.lfMessageFont.lfFaceName );
		fontTwipSize = LogheightToTwips(ncmSystem.lfMessageFont.lfHeight);
	}
	
	//Send new font
	SendMessage(hWnd, WM_SETFONT, (WPARAM)hfMain, 0); 
}

void MGFontManager::EnsureCommonControls() {
	if (!bICCE) {
		INITCOMMONCONTROLSEX icc = {0};
		icc.dwSize = sizeof(INITCOMMONCONTROLSEX);
		icc.dwICC = ICC_WIN95_CLASSES | ICC_STANDARD_CLASSES | ICC_TAB_CLASSES;
		InitCommonControlsEx(&icc);
		bICCE = true;
	}
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// MGEventHandler
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


MGEventHandler::MGEventHandler(MGControl *master) {
	m_master = master;
	m_oldProc = 0;	
}

void MGEventHandler::subclassWnd(HWND hWnd) {
	if (m_oldProc) return; //already subclassed
	
	// Subclass and store old proc
	m_hWnd = hWnd;
	SetWindowLongPtr(m_hWnd, GWLP_USERDATA, (LONG)this);
	m_oldProc = SetWindowLongPtr(m_hWnd, GWLP_WNDPROC, (LONG)(&stProc));
}

LRESULT CALLBACK MGEventHandler::stProc( // Static
	HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam
) {
	if (uMsg == WM_NCCREATE) {
		SetWindowLongPtr(  // Store..
			hwnd, GWLP_USERDATA, (long)((LPCREATESTRUCT(lParam))->lpCreateParams)
		);
	}

	// ..Retrieve
	MGEventHandler* e = (MGEventHandler*) GetWindowLongPtr(hwnd, GWLP_USERDATA);
	
	if (e) return e->Proc(hwnd, uMsg, wParam, lParam);
	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

LRESULT CALLBACK MGEventHandler::Proc(
	HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam
) {
	MGEventData evdata = {0};
	evdata.hwnd   = hwnd;
	evdata.uMsg   = uMsg;
	evdata.wParam = wParam;
	evdata.lParam = lParam;

	// call control's own eventhandler function
	if (m_master->_parseEvent(&evdata)) {
		// The replacement procedure consumed the message.
		return 0;
	} else {
		// The replacement procedure didn't consume the message.
		if (m_oldProc) {
			// We overrode the proc;
			return CallWindowProc((WNDPROC)(m_oldProc), hwnd, uMsg, wParam, lParam);
		} else {
			// We didn't override the proc
			return DefWindowProc(hwnd, uMsg, wParam, lParam);
		}
	}
}

MGEventHandler::~MGEventHandler() {
	// TODO: If there was an original subclass proc, restore it
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// MGWindow
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


MGWindow::MGWindow(void* hIcon, TCHAR* caption) {
	
	this->_controlType = MGCT_WINDOW;
	e = new MGEventHandler(this);
	d = new MGControlData;
	w = new MGWindowData;
	d->hInstance = GetModuleHandle(0); //(HINSTANCE)hInstance;
	d->ID = 100; // Next control ID
	d->ParentHWnd = 0; // MGWindow classes don't have parents. ;_;
	
	w->controlIDs.clear();
	
	MGFontManager::getInstance()->EnsureCommonControls();

	// TODO: only register this class once per MGui
	WNDCLASS wcx = {0}; 
		wcx.style = CS_HREDRAW | CS_VREDRAW;
		wcx.lpfnWndProc = e->stProc;
		wcx.hInstance = d->hInstance;
		wcx.hIcon = (HICON)LoadIcon(d->hInstance, MAKEINTRESOURCE(hIcon)); //(HICON)hIcon
		wcx.hCursor = LoadCursor(NULL, IDC_ARROW);
		wcx.hbrBackground = (HBRUSH)(COLOR_WINDOW);
		wcx.lpszMenuName = NULL;
		wcx.lpszClassName = _T("MGWindow");
	RegisterClass(&wcx);

	// Create window

	d->hWnd = CreateWindow(
		_T("MGWindow"), caption, WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL,
		d->hInstance, (void*)e
	);
	w->bActive = d->hWnd?true:false; 
	if (! w->bActive ) return;

	MGFontManager::getInstance()->SetControlFont(d->hWnd);

	// Create status bar
	w->hStatus = CreateWindow(STATUSCLASSNAME, L"", WS_CHILD | WS_VISIBLE,
		CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
		d->hWnd, 0, d->hInstance, 0
	);
	SendMessage(w->hStatus, SB_SIMPLE, TRUE, 0);
	MGFontManager::getInstance()->SetControlFont(w->hStatus);
	this->showStatusBar(false);

	// Create menu
	w->hAccelTable = 0;
	w->hMenu = CreateMenu();
	w->childMenus.clear();
	SetMenu(d->hWnd, w->hMenu);

	// Show 
	ShowWindow(d->hWnd, true);
	UpdateWindow(d->hWnd);
}

void MGWindow::close() {
	PostQuitMessage(0);
	w->bActive = false;
}

MGWindow::~MGWindow() {
	SendMessage(d->hWnd, WM_DESTROY, 0, 0);
	w->bActive = false;
	if (w->hAccelTable) {
		DestroyAcceleratorTable(w->hAccelTable);
		w->hAccelTable = 0;
	}
	// TODO: does w need deleting as well?
	delete e;
}

void MGWindow::doEvents() {
	MSG msg;
	if (GetMessage(&msg, NULL, 0, 0) ) {
		if (! TranslateAccelerator( d->hWnd, w->hAccelTable, &msg ) ) {
			TranslateMessage(&msg); 
			DispatchMessage(&msg); 
		}
	}
}

bool MGWindow::isActive() {
	return w->bActive;
}

void MGWindow::_prepareChild(MGControlData *pCtrlData, MGControl* pCtrl) {
	pCtrlData->hInstance = d->hInstance;
	pCtrlData->ParentHWnd = d->hWnd;
	pCtrlData->ID = d->ID ++;
	w->controlIDs[pCtrlData->ID] = pCtrl;
}

void MGWindow::_prepareChild(MGMenuItemData *pMenuData, MGMenuItem* pMenu) {
	pMenuData->ID = d->ID ++;
	pMenuData->parentHWnd = d->hWnd;
	w->controlIDs[pMenuData->ID] = pMenu;
}

bool MGWindow::_parseEvent(MGEventData *ev) {
	switch (ev->uMsg) {
		case WM_DESTROY: {
			onClosing.invoke();
			close();
		} break;

		case WM_COMMAND: {
			// wParam contains the ID, so look it up
			std::map<unsigned int, MGObject*>::iterator pObj =
				w->controlIDs.find(LOWORD(ev->wParam))
			;
			if (pObj != w->controlIDs.end()) {
				MGObject* ctrl = (*pObj).second;
				switch (ctrl->_controlType) {
					case MGCT_BUTTON: {
						((MGButton*)ctrl)->onClick.invoke();
					} break;
					case MGCT_MENUITEM: {
						((MGMenuItem*)ctrl)->onClick.invoke();
					} break;
					case MGCT_LISTBOX: {
						if (HIWORD(ev->wParam) == LBN_SELCHANGE) {
							((MGListBox*)ctrl)->onChange.invoke();
						} else if (HIWORD(ev->wParam) == LBN_DBLCLK) {
							((MGListBox*)ctrl)->onDoubleClick.invoke();
						}
					} break;
					case MGCT_COMBOBOX: {
						if (HIWORD(ev->wParam) == CBN_SELCHANGE) {
							((MGComboBox*)ctrl)->onChange.invoke();
						}
					} break;
				}
			}
		} break;
		
		case WM_KEYUP: {
		} break;

		case WM_SIZE: {
			onResize.invoke();
			SendMessage(w->hStatus, WM_SIZE, 0, 0);
		} break;

		case WM_TIMER: {
			std::map<unsigned int, MGObject*>::iterator pObj =
				w->controlIDs.find(LOWORD(ev->wParam))
			;
			if (pObj != w->controlIDs.end()) {
				MGObject* ctrl = (*pObj).second;
				if (ctrl->_controlType == MGCT_TIMER) {
					((MGTimer*)ctrl)->onTick.invoke();
				}
			}
		} break;

		case WM_HOTKEY: {
			std::map<unsigned int, MGObject*>::iterator pObj =
				w->controlIDs.find(LOWORD(ev->wParam))
			;
			if (pObj != w->controlIDs.end()) {
				MGObject* ctrl = (*pObj).second;
				if (ctrl->_controlType == MGCT_HOTKEY) {
					((MGHotkey*)ctrl)->onPress.invoke();
					return true;
				}
			}
		} break;

		case WM_DRAWITEM: {
			std::map<unsigned int, MGObject*>::iterator pObj =
				w->controlIDs.find(LOWORD(ev->wParam))
			;
			if (pObj != w->controlIDs.end()) {
				MGObject* ctrl = (*pObj).second;
				if (ctrl->_controlType == MGCT_LISTBOX) {
					((MGListBox*)ctrl)->_parseRequest(ev);
					return true;
				}
			}
		} break;

		case WM_MEASUREITEM: {
			std::map<unsigned int, MGObject*>::iterator pObj =
				w->controlIDs.find(LOWORD(ev->wParam))
			;
			if (pObj != w->controlIDs.end()) {
				MGObject* ctrl = (*pObj).second;
				if (ctrl->_controlType == MGCT_LISTBOX) {
					MEASUREITEMSTRUCT *mis = (MEASUREITEMSTRUCT*)ev->lParam;
					mis->itemHeight =
						MGFontManager::getInstance()->getFontPixelHeight() + 5
					;
					return true;
				}
			}
		} break;

		default: {
		} break;
	}
	return false; // not handled
}

void MGWindow::addChildMenu(MGMenuItem* menuItem) {
	w->childMenus.push_back(menuItem);
}

void MGWindow::updateMenu() {
	// Clear all existing items
	if (w->hMenu) DestroyMenu(w->hMenu);
	w->hMenu = CreateMenu();

	// Destroy current accelerator table
	if (w->hAccelTable) {
		DestroyAcceleratorTable(w->hAccelTable);
		w->hAccelTable = 0;
	}
	w->childMenuAccels.clear();

	// Add all child elements, and all their children, etc
	MGMenuItemRelation relation;
	relation.parent = w->hMenu;
	relation.menuAccels = &w->childMenuAccels;
	for (auto i = w->childMenus.begin(), e = w->childMenus.end(); i != e; i++) {
		(*i)->_updateSelf(&relation);
	}

	// Recreate accelerator table
	if ( w->childMenuAccels.size() ) {
		w->hAccelTable = CreateAcceleratorTable(
			&w->childMenuAccels[0], w->childMenuAccels.size()
		);
	}

	// Re-associate to window
	SetMenu(d->hWnd, w->hMenu);
}

void MGWindow::focus() {
	BringWindowToTop(d->hWnd);
	SetForegroundWindow(d->hWnd);
}

void MGWindow::setResizable(bool resizable) {
	SetWindowLongPtr(d->hWnd, GWL_STYLE,
		resizable ?
			  WS_VISIBLE | WS_OVERLAPPEDWINDOW
			: WS_VISIBLE | WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX | WS_BORDER
	);
}

void MGWindow::showStatusBar(bool show) {
	ShowWindow(w->hStatus, show ? SW_SHOW : SW_HIDE);
	w->bStatusBarVisible = show;
}

void MGWindow::setStatusBar(const TCHAR* text) {
	SendMessage(w->hStatus, SB_SETTEXT, SB_SIMPLEID, (LPARAM)text);	
}

int MGWindow::getInnerHeight() {
	// We may have to take into account statusbar height
	RECT rct; GetClientRect( d->hWnd, &rct );
	
	if (w->bStatusBarVisible) {
		RECT rctStatus;
		GetClientRect( w->hStatus, &rctStatus);
		return rct.bottom - rct.top - rctStatus.bottom + rctStatus.top;
	} else {
		return rct.bottom - rct.top;
	}
}

void MGWindow::setCaption(const TCHAR* caption) {
	SetWindowText(d->hWnd, caption);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// MGWindow: Dialog calls
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


void MGWindow::dlgAlert(TCHAR* message) {
	TCHAR captionBuffer[256] = {0};
	GetWindowText(d->hWnd, captionBuffer, 255);
	MessageBox(d->hWnd, message, captionBuffer, MB_OK);
}

bool MGWindow::dlgConfirm(TCHAR* prompt) {
	TCHAR captionBuffer[256] = {0};
	GetWindowText(d->hWnd, captionBuffer, 255);
	return MessageBox(d->hWnd, prompt, captionBuffer, MB_YESNO) == IDYES;
}

bool MGWindow::dlgFolderSelect(TCHAR* title, TSTRING &result) {
	// TODO: MSDN says we need to intialise COM first..?

	TCHAR resultBuffer[MAX_PATH] = {0};
	
	BROWSEINFO binf = {0};
	binf.hwndOwner = d->hWnd;
	binf.lpszTitle = title;
	binf.ulFlags = BIF_NEWDIALOGSTYLE | BIF_NONEWFOLDERBUTTON;

	ITEMIDLIST* items = SHBrowseForFolder(&binf);
	if (! items) return false; // User cancelled
	
	if(! SHGetPathFromIDList(items, resultBuffer)) return false;

	IMalloc *imalloc = 0;
	if(SUCCEEDED(SHGetMalloc(&imalloc))) {
		imalloc->Free(items);
		imalloc->Release();
	}

	result.assign(resultBuffer);
	return result.length() > 0 ? true : false;
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// MGMenuItem
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


MGMenuItem::MGMenuItem(MGWindow* parent, TCHAR* text) {
	_initialise(parent, text);
	parent->addChildMenu(this);
}

MGMenuItem::MGMenuItem(MGMenuItem* parent, TCHAR* text) {
	_initialise(parent->m->parentWnd, text);
	parent->addChildMenu(this);
}

MGMenuItem::MGMenuItem(MGWindow* parent, TCHAR* text, MGCallback onClick) {
	_initialise(parent, text);
	this->onClick.assign(parent, onClick);
	parent->addChildMenu(this);
}

MGMenuItem::MGMenuItem(	MGMenuItem* parent, TCHAR* text, MGCallback onClick) {
	_initialise(parent->m->parentWnd, text);
	this->onClick.assign(parent->m->parentWnd, onClick);
	parent->addChildMenu(this);
}

void MGMenuItem::_initialise(MGWindow* parent, TCHAR* text) {
	this->_controlType = MGCT_MENUITEM;
	m = new MGMenuItemData;
	parent->_prepareChild(m, this);
	m->caption.assign(text);
	m->hMenu = 0; // For child menus
	m->shortcut.virtkey = 0;
	m->parentWnd = parent;
}

MGMenuItem::~MGMenuItem() {
	if (m) {
		if (m->hMenu) {
			DestroyMenu(m->hMenu);
			m->hMenu = 0;
		}
		delete m;
		m = 0;
	}
}

MGMenuItem* MGMenuItem::setShortcut(bool alt, bool ctrl, bool shift, int virtkey) {
	m->shortcut.alt = alt;
	m->shortcut.ctrl = ctrl;
	m->shortcut.shift = shift;	
	m->shortcut.virtkey = virtkey;
	return this; // Chainable!
}

void MGMenuItem::addChildMenu(MGMenuItem* menuItem) {
	m->children.push_back(menuItem);
}

void MGMenuItem::addChildMenuSeparator() {
	m->children.push_back( (MGMenuItem*) 0 );	
}

void MGMenuItem::setChecked(bool isChecked) {	
	CheckMenuItem(GetMenu(m->parentHWnd), m->ID, MF_BYCOMMAND | (isChecked ? MF_CHECKED : MF_UNCHECKED));
	DrawMenuBar(m->parentHWnd);
}

void MGMenuItem::setEnabled(bool isEnabled) {
	EnableMenuItem(GetMenu(m->parentHWnd), m->ID, MF_BYCOMMAND | (isEnabled ? MF_ENABLED : MF_GRAYED));
	DrawMenuBar(m->parentHWnd);
}

void MGMenuItem::_updateSelf(MGMenuItemRelation *relation) {
	// Add shortcut accelerator (if necessary)
	if (m->shortcut.virtkey) {
		ACCEL myAccel = {
			FVIRTKEY | 
			(m->shortcut.alt ? FALT : 0) |
			(m->shortcut.ctrl ? FCONTROL : 0) |
			(m->shortcut.shift ? FSHIFT : 0),
			MGVKToNative( m->shortcut.virtkey ),
			m->ID
		};
		relation->menuAccels->push_back(myAccel);
	}

	DWORD dFlags = MF_ENABLED | MF_UNCHECKED | MF_STRING;
	if (m->children.size() > 0) {
		if (! m->hMenu) m->hMenu = CreatePopupMenu();
		// create all children
		for (auto i = m->children.begin(), e = m->children.end(); i != e; i++) {
			if ( (*i) == 0 ) { // it's a separator
				AppendMenu(m->hMenu, dFlags | MF_SEPARATOR, 0, 0);
			} else { // a real child
				MGMenuItemRelation childRelation;
				childRelation.parent = m->hMenu;
				childRelation.menuAccels = relation->menuAccels;
				(*i)->_updateSelf(&childRelation);
			}
		}
		// create as foldout menu
		AppendMenu(
			relation->parent, dFlags | MF_POPUP, (UINT_PTR)m->hMenu, m->caption.c_str()
		);
	} else {
		// create as link
		AppendMenu(relation->parent, dFlags, m->ID, m->caption.c_str() );
	}
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// MGControl
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


void MGControl::resize(int x, int y, int width, int height) {
	SetWindowPos(d->hWnd, 0, x, y, width, height, SWP_NOZORDER);
}

void MGControl::resize(int width, int height) {
	SetWindowPos(d->hWnd, 0, 0, 0, width, height, SWP_NOZORDER | SWP_NOMOVE);
}

void MGControl::focus() {
	if (d->hWnd) SetFocus(d->hWnd);
}

void MGControl::setVisible(bool bVisible) {
	ShowWindow(d->hWnd, (bVisible? SW_SHOW : SW_HIDE) );
}

int MGControl::getX() {
	RECT rct; GetWindowRect( d->hWnd, &rct );
	return rct.left;
}

int MGControl::getY() {
	RECT rct; GetWindowRect( d->hWnd, &rct );
	return rct.top;
}

int MGControl::getWidth()  {
	RECT rct; GetWindowRect( d->hWnd, &rct );
	return rct.right - rct.left;
}

int MGControl::getHeight() {
	RECT rct; GetWindowRect( d->hWnd, &rct );
	return rct.bottom - rct.top;
}

int MGControl::getInnerWidth() {
	RECT rct; GetClientRect( d->hWnd, &rct );
	return rct.right - rct.left;
}

int MGControl::getInnerHeight() {
	RECT rct; GetClientRect( d->hWnd, &rct );
	return rct.bottom - rct.top;
}

bool MGControl::_parseEvent(MGEventData* ev) {
	return false; // Default; no action
}

void MGControl::removeStyles() {
	SetWindowTheme(d->hWnd, _T(""), _T(""));
}

void MGControl::removeBorder() {
	long style = GetWindowLong(d->hWnd, GWL_EXSTYLE);
	SetWindowLong(d->hWnd, GWL_EXSTYLE, style & ~WS_EX_STATICEDGE);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// MGEdit
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


MGEdit::MGEdit(MGWindow* parent, bool bMultiline) {
	this->_controlType = MGCT_EDIT;
	d = new MGControlData;
	parent->_prepareChild(d, this);
	
	d->hWnd = CreateWindowEx(WS_EX_CLIENTEDGE, _T("EDIT"), 0,
		WS_VISIBLE | WS_CHILD | ES_AUTOVSCROLL |
		ES_AUTOHSCROLL | (bMultiline? ES_MULTILINE:0),
		0, 0, 320, 240, d->ParentHWnd, (HMENU) d->ID, d->hInstance, NULL);
	if (!d->hWnd) return;

	e = new MGEventHandler(this);
	e->subclassWnd(d->hWnd);

	MGFontManager::getInstance()->SetControlFont(d->hWnd);
}

bool MGEdit::_parseEvent(MGEventData *ev) {	
	switch (ev->uMsg) {
		case WM_KEYUP:
			onKey.invoke();
		break;
	}
	return false; // unhandled, pass on
}

void MGEdit::textSet(TCHAR* szText) {
	SetDlgItemText(d->ParentHWnd, d->ID, szText);
}

void MGEdit::textGet(TCHAR *outBuff, int charcount) {
	memset(outBuff, 0, charcount * sizeof(TCHAR));
	GetDlgItemText(d->ParentHWnd, d->ID, outBuff, charcount - 1);
}

int MGEdit::textLength() {
	return GetWindowTextLength(d->hWnd);
}

void MGEdit::selRange(int start, int end) {
	SendMessageW(d->hWnd, EM_SETSEL, start, end);
}

void MGEdit::selEnd() {
	// This trick is necessary (and correct)
	SendMessageW(d->hWnd, EM_SETSEL, 0,  -1);
	SendMessageW(d->hWnd, EM_SETSEL, -1, -1);
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// MGRichEdit
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


MGRichEdit::MGRichEdit(MGWindow* parent, bool bReadOnly) {
	this->_controlType = MGCT_RICHEDIT;
	d = new MGControlData;
	parent->_prepareChild(d, this);
	
	if ( !LoadLibrary(_T("riched20.dll")) ) {
		parent->dlgAlert(_T("Couldn't load richedit20.dll!"));
		return;
	}
	
	d->hWnd = CreateWindowEx(WS_EX_STATICEDGE, _T("RichEdit20W"), 0, 
		ES_MULTILINE | ES_NOHIDESEL | WS_VISIBLE | WS_CHILD |
		WS_TABSTOP | WS_VSCROLL | (bReadOnly?ES_READONLY:0),
		0, 0, 1, 1, d->ParentHWnd, (HMENU)d->ID, d->hInstance, NULL);
	if (!d->hWnd) return;

	SelSetToEnd();
	SelFontSet(RGB(0, 0, 0), false, false, false);
}

void MGRichEdit::Clear() {
	SendMessage(d->hWnd, WM_SETTEXT, 0, (LPARAM) L"" );
}

void MGRichEdit::SelTextGet(TCHAR* outbuff, int charcount) {
	TEXTRANGE tr; CHARRANGE cr;
	SendMessage(d->hWnd, EM_EXGETSEL, 0, (LPARAM)&cr);
	tr.chrg = cr;
	memset(outbuff, 0, charcount * sizeof(TCHAR));
	if (charcount > (cr.cpMax - cr.cpMin)) {
		tr.lpstrText = outbuff;
		SendMessage(d->hWnd, EM_GETTEXTRANGE, 0, (LPARAM)&tr);
	}
}

void MGRichEdit::SelTextSet(TCHAR* szText) {
	SendMessage(d->hWnd, EM_REPLACESEL, TRUE, (LPARAM)szText);
}

void MGRichEdit::SelSetRange(LONG lMin, LONG lMax) {
	SendMessage(d->hWnd, EM_SETSEL, (WPARAM)lMin, (LPARAM)lMax);
}

int  MGRichEdit::SelGetStart() {
	CHARRANGE cr;
	SendDlgItemMessage(d->ParentHWnd, d->ID, EM_EXGETSEL, 0, (LPARAM)&cr);
	return cr.cpMin;
}

int  MGRichEdit::SelGetEnd() {
	CHARRANGE cr;
	SendDlgItemMessage(d->ParentHWnd, d->ID, EM_EXGETSEL, 0, (LPARAM)&cr);
	return cr.cpMax;
}

void MGRichEdit::SelSetToEnd() {
	SendMessage(d->hWnd, EM_SETSEL, (WPARAM) -1, (LPARAM) -1);
}

void MGRichEdit::SelFontSet(
	long rgb, bool bBold, bool bIta, bool bUL, const TCHAR* szFont, long size
) {
	CHARFORMAT cf = {0};
	cf.cbSize = sizeof(CHARFORMAT);
	SendMessage(d->hWnd, EM_GETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf);
	cf.dwEffects = (bBold?CFE_BOLD:0) | (bIta?CFE_ITALIC:0) | (bUL?CFE_UNDERLINE:0);
	cf.crTextColor = (COLORREF)rgb;
	cf.dwMask = CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE | CFM_COLOR | CFM_FACE | CFM_SIZE;
	_tcscpy_s(cf.szFaceName, LF_FACESIZE-1, szFont);
	cf.yHeight = size;
	SendMessage(d->hWnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf);
}

void MGRichEdit::SelFontSet(long rgb, bool bBold, bool bIta, bool bUL) {
	MGFontManager *fontman = MGFontManager::getInstance();
	SelFontSet(
		rgb, bBold, bIta, bUL, fontman->getFontFace(), fontman->getFontTwipHeight()
	);
}

int  MGRichEdit::TextLength() {
	GETTEXTLENGTHEX gtl;
	gtl.codepage = 1200; // Unicode codepage, as per msdn
	gtl.flags = GTL_CLOSE | GTL_NUMCHARS;
	return (int) SendMessage(d->hWnd, EM_GETTEXTLENGTHEX, (WPARAM) &gtl, 0);
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// MGButton
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


MGButton::MGButton(MGWindow *parent, TCHAR *szCaption) {
	this->_controlType = MGCT_BUTTON;
	d = new MGControlData;
	parent->_prepareChild(d, this);
	
	d->hWnd = CreateWindow(
		_T("BUTTON"), szCaption, 
		WS_VISIBLE | WS_CHILD | BS_TEXT | BS_VCENTER,
		0, 0, 1, 1, d->ParentHWnd, (HMENU) d->ID, d->hInstance, NULL
	);

	MGFontManager::getInstance()->SetControlFont(d->hWnd);
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// MGListBox
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


MGListBox::MGListBox(MGWindow *parent, bool bAutoSort, bool bAllowColors) {
	this->_controlType = MGCT_LISTBOX;
	d = new MGControlData;
	parent->_prepareChild(d, this);
	
	d->hWnd = CreateWindowEx(
		WS_EX_STATICEDGE, _T("LISTBOX"), 0, 
		WS_CHILD | WS_VISIBLE | WS_VSCROLL |
		LBS_NOINTEGRALHEIGHT | LBS_NOTIFY | LBS_USETABSTOPS | LBS_HASSTRINGS |
		(bAutoSort ? LBS_SORT : 0) | (bAllowColors ? LBS_OWNERDRAWFIXED : 0),
		0, 0, 1, 1, d->ParentHWnd, (HMENU) d->ID, d->hInstance, NULL
	);
	if (!d->hWnd) return;

	MGFontManager::getInstance()->SetControlFont(d->hWnd);

	if (bAllowColors) {
		z = new MGListBoxData;
		z->default_color = COLOR_WINDOWTEXT;
	} else {
		z = 0;
	}
}

MGListBox::~MGListBox() {
	if (z) { delete z; z = NULL; }
}

int MGListBox::getSelected() {
	return (int) SendMessage(d->hWnd, LB_GETCURSEL, 0, 0);
}

void MGListBox::setSelected(int idx) {
	SendMessage(d->hWnd, LB_SETCURSEL, idx, 0);
}

void MGListBox::clear() {
	SendMessage(d->hWnd, LB_RESETCONTENT, 0, 0);
}

int MGListBox::addItem(TCHAR* szItem) {
	return SendMessage(d->hWnd, LB_ADDSTRING, 0, (LPARAM)szItem);
}

int MGListBox::addItem(TCHAR* szItem, int pos) {
	return SendMessage(d->hWnd, LB_INSERTSTRING, (WPARAM)pos, (LPARAM)szItem);
}

int MGListBox::findItem(TCHAR* szItem) {
	return (int) SendMessage(d->hWnd, LB_FINDSTRING, -1, (LPARAM)szItem); // -1 on error
}

int MGListBox::getItemCount() {
	return (int) SendMessage(d->hWnd, LB_GETCOUNT, 0, 0);
}

void MGListBox::removeItem(int idx) {
	SendMessage(d->hWnd, LB_DELETESTRING, idx, 0);
}

TSTRING MGListBox::getItemText(int idx) {
	TSTRING ret;

	int len = (int)SendMessage(d->hWnd, LB_GETTEXTLEN, idx, 0); // charcound, or -1 error
	if (len == -1) {
		ret.assign(_T(""));
	} else {	
		TCHAR* buff = new TCHAR[len+1];
		SendMessage(d->hWnd, LB_GETTEXT, idx, (LPARAM)buff);
		ret.assign(buff);
		delete[] buff;
	}
	return ret;
}

void MGListBox::setItemText(int idx, TCHAR* szItem) {
	void* d = this->getItemData(idx);
	this->removeItem(idx);
	int real_idx = this->addItem(szItem, idx);
	this->setItemData(real_idx, d);
}

void MGListBox::setItemData(int idx, void* data) {
	SendMessageW(d->hWnd, LB_SETITEMDATA, idx, (WPARAM)data);
}

void* MGListBox::getItemData(int idx) {
	return (void*) SendMessageW(d->hWnd, LB_GETITEMDATA, (WPARAM)idx, 0);
}

void MGListBox::setItemColor(int idx, unsigned char r, unsigned char b, unsigned char g) {
	z->colors[idx] = RGB(r,g,b);
}

void MGListBox::_parseRequest(MGEventData *ev) {
	if (ev->uMsg != WM_DRAWITEM) return;

	DRAWITEMSTRUCT* dis = (DRAWITEMSTRUCT*)ev->lParam;
	TSTRING text = getItemText(dis->itemID);

	HBRUSH brBack = 0;
	if (dis->itemState & ODS_FOCUS) {

		// Item has focus, so highlight it using default focus colours
		brBack = GetSysColorBrush(COLOR_HIGHLIGHT);
		FillRect(dis->hDC, &(dis->rcItem), brBack);
		SetBkColor(dis->hDC, GetSysColor(COLOR_HIGHLIGHT));
		SetTextColor(dis->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
		// DrawFocusRect is also needed for the "correct" way of drawing the highlight,
		// but Billy/VCL doesn't do it, and i don't really like the way it looks anyway

	} else {

		// Item doesn't have focus, draw it with the specified colour
		brBack = GetSysColorBrush(COLOR_WINDOW);
		FillRect(dis->hDC, &(dis->rcItem), brBack);
		SetBkColor(dis->hDC, GetSysColor(COLOR_WINDOW));

		// Map lookup current color
		std::map<int, unsigned int>::iterator pColor = z->colors.find(dis->itemID);
		SetTextColor(
			dis->hDC,
			(pColor == z->colors.end()) ? z->default_color : pColor->second
		);
	}
	TextOut(
		dis->hDC,
		dis->rcItem.left + 2, dis->rcItem.top,
		text.c_str(), text.length()
	);
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// MGProgressBar
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


MGProgressBar::MGProgressBar(MGWindow* parent, bool isHorizontal, bool isSmooth) {
	this->_controlType = MGCT_PROGRESSBAR;
	d = new MGControlData;
	parent->_prepareChild(d, this);

	d->hWnd = CreateWindow(PROGRESS_CLASS, 0,
		WS_CHILD | WS_VISIBLE		
		| (isHorizontal ? 0 : PBS_VERTICAL)
		| (isSmooth ? 0 : PBS_SMOOTH),
		0, 0, CW_USEDEFAULT, CW_USEDEFAULT,
		d->ParentHWnd, (HMENU)d->ID, d->hInstance, 0);
	if (! d->hWnd) return;
	SendMessage(d->hWnd, PBM_SETRANGE32, 0, 10000);

	e = new MGEventHandler(this);
	e->subclassWnd(d->hWnd);
}

bool MGProgressBar::_parseEvent(MGEventData *ev) {
	switch(ev->uMsg) {
		case WM_MOUSEMOVE: {
			if (ev->wParam & MK_LBUTTON) {
				onDrag.data.asLong = ev->lParam;
				onDrag.invoke();
			}
		} break;
		case WM_LBUTTONDOWN: {
			onMouseDown.data.asLong = ev->lParam;
			onMouseDown.invoke();
		} break;
	}
	return false; // message not handled
}

void MGProgressBar::setValue(float fraction) {
	SendMessage(d->hWnd, PBM_SETPOS, (WPARAM)(fraction * 10000.0f), 0);
}

float MGProgressBar::getValue() {
	return (float)SendMessage(d->hWnd, PBM_GETPOS, 0, 0) / 10000.0f;
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// MGLabel
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


MGLabel::MGLabel(MGWindow* parent, bool alignLeft) {
	this->_controlType = MGCT_LABEL;
	d = new MGControlData;
	parent->_prepareChild(d, this);

	d->hWnd = CreateWindow(WC_STATIC, 0,
		WS_CHILD | WS_VISIBLE | (alignLeft ? SS_LEFT : SS_RIGHT), 
		0, 0, CW_USEDEFAULT, CW_USEDEFAULT,
		d->ParentHWnd, (HMENU)d->ID, d->hInstance, 0);
	if (! d->hWnd) return;

	MGFontManager::getInstance()->SetControlFont(d->hWnd);
}

void MGLabel::setText(TCHAR* text) {
	SetWindowText(d->hWnd, text);
}

void MGLabel::getText(TSTRING &text) {
	TCHAR resultBuffer[256] = {0};
	GetWindowText(d->hWnd, resultBuffer, 255);
	text.assign(resultBuffer);
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// MGGroup
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


MGGroup::MGGroup(MGWindow* parent) {
	this->_controlType = MGCT_GROUP;
	d = new MGControlData;
	parent->_prepareChild(d, this);

	d->hWnd = CreateWindow(_T("BUTTON"), _T(""), 
		WS_CHILD | WS_VISIBLE | BS_GROUPBOX,
		CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
		d->ParentHWnd, (HMENU)d->ID, d->hInstance, 0);
	if (! d->hWnd) return;

	MGFontManager::getInstance()->SetControlFont(d->hWnd);
}

void MGGroup::setText(TCHAR* text) {
	SetWindowText(d->hWnd, text);
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// MGComboBox
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


MGComboBox::MGComboBox(MGWindow* parent, bool bAutoSort) {
	this->_controlType = MGCT_COMBOBOX;
	d = new MGControlData;
	parent->_prepareChild(d, this);

	d->hWnd = CreateWindow(_T("COMBOBOX"), _T(""),
		WS_CHILD | WS_VISIBLE | WS_VSCROLL | 
		CBS_NOINTEGRALHEIGHT | CBS_DROPDOWNLIST | (bAutoSort ? CBS_SORT : 0),
		CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
		d->ParentHWnd, (HMENU)d->ID, d->hInstance, 0);
	if (! d->hWnd) return;

	MGFontManager::getInstance()->SetControlFont(d->hWnd);
}

void MGComboBox::allowTextEntry(bool bEnable) { // FIXME
	long style = GetWindowLong(d->hWnd, GWL_STYLE);
	if (bEnable) {
		style &= ~ CBS_DROPDOWNLIST;
		style |= CBS_DROPDOWN;
	} else {
		style &= ~ CBS_DROPDOWN;
		style |= CBS_DROPDOWNLIST;
	}
	SetWindowLong(d->hWnd, GWL_STYLE, style);
}

void MGComboBox::addItem(TCHAR* text) {
	SendMessage(d->hWnd, CB_ADDSTRING, 0, (LPARAM)text);
}

void MGComboBox::clear() {
	SendMessage(d->hWnd, CB_RESETCONTENT, 0, 0);
}

void MGComboBox::setSelected(int idx) {
	SendMessage(d->hWnd, CB_SETCURSEL, idx, 0);
}

int MGComboBox::getSelected() {
	int cursel = (int)SendMessage(d->hWnd, CB_GETCURSEL, 0, 0);
	return (cursel == CB_ERR ? -1 : cursel);
}

TSTRING MGComboBox::getSelectedText() { //UNTESTED
	TCHAR buff[1024] = {0};
	GetWindowText(d->hWnd, buff, 1024);
	TSTRING ret = buff;
	return ret;
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// MGTimer
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


MGTimer::MGTimer(MGWindow* parent, int interval_ms) {
	this->_controlType = MGCT_TIMER;
	d = new MGControlData;
	parent->_prepareChild(d, this);

	SetTimer(d->ParentHWnd, d->ID, interval_ms, 0);
}

MGTimer::~MGTimer() {
	KillTimer(d->ParentHWnd, d->ID);	
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// MGHotkey
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


MGHotkey::MGHotkey(MGWindow* parent, MGKeyCombination kc) {
	_initialise(parent, kc.alt, kc.ctrl, kc.shift, kc.virtkey);
}

MGHotkey::MGHotkey(
	MGWindow* parent, bool alt, bool ctrl, bool shift, int virtkey
) {
	_initialise(parent, alt, ctrl, shift, virtkey);
}

MGHotkey::MGHotkey(
	MGWindow* parent, bool alt, bool ctrl, bool shift, int virtkey, MGCallback onPress
) {
	_initialise(parent, alt, ctrl, shift, virtkey);
	this->onPress.assign(parent, onPress);
}

void MGHotkey::_initialise(
	MGWindow* parent, bool alt, bool ctrl, bool shift, int virtkey
) {
	this->_controlType = MGCT_HOTKEY;
	d = new MGControlData;
	parent->_prepareChild(d, this);

	RegisterHotKey(d->ParentHWnd, d->ID,
		(alt ? MOD_ALT : 0) | (ctrl ? MOD_CONTROL : 0) | (shift ? MOD_SHIFT : 0),
		MGVKToNative(virtkey)
	);
}

MGHotkey::~MGHotkey() {
	UnregisterHotKey(d->ParentHWnd, d->ID);
	if (d) { delete d; d = 0; }
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// MGHotkeyControl
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


MGHotkeyControl::MGHotkeyControl(MGWindow* parent) {
	this->_controlType = MGCT_HOTKEYCONTROL;
	d = new MGControlData;
	parent->_prepareChild(d, this);

	d->hWnd = CreateWindow(HOTKEY_CLASS, _T(""),
		WS_CHILD | WS_VISIBLE,
		CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
		d->ParentHWnd, (HMENU)d->ID, d->hInstance, 0
	);

	MGFontManager::getInstance()->SetControlFont(d->hWnd);
}

MGHotkeyControl::~MGHotkeyControl() {
	if (d) {delete d; d = 0;}
}

void MGHotkeyControl::setValue(MGKeyCombination val) {
	SendMessage(
		d->hWnd, HKM_SETHOTKEY,
		MAKEWORD(
			val.virtkey,
			(val.alt ? HOTKEYF_ALT : 0) |
			(val.ctrl ? HOTKEYF_CONTROL : 0) |
			(val.shift ? HOTKEYF_SHIFT : 0)
		),
		0
	);
}

MGKeyCombination MGHotkeyControl::getValue() {
	MGKeyCombination val;
	long res = SendMessage(d->hWnd, HKM_GETHOTKEY, 0, 0);

	// Decode response
	val.virtkey = LOBYTE(LOWORD(res));
	char kmod =   HIBYTE(LOWORD(res));
	val.alt =   (kmod & HOTKEYF_ALT    ) ? 1 : 0;
	val.ctrl =  (kmod & HOTKEYF_CONTROL) ? 1 : 0;
	val.shift = (kmod & HOTKEYF_SHIFT  ) ? 1 : 0;
	return val;
}