/*
 * synergy -- mouse and keyboard sharing utility
 * Copyright (C) 2006 Chris Schoeneman, Nick Bolton, Sorin Sbarnea
 * 
 * This package is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * found in the file COPYING that should have accompanied this file.
 * 
 * This package is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "CArchMiscWindows.h"
#include "CMSWindowsKeyState.h"
#include "CConfig.h"
#include "CHotkeyOptions.h"
#include "CStringUtil.h"
#include "CLog.h"
#include "LaunchUtil.h"
#include "resource.h"

#if !defined(WM_XBUTTONDOWN)
#define WM_XBUTTONDOWN		0x020B
#define WM_XBUTTONUP		0x020C
#define WM_XBUTTONDBLCLK	0x020D
#define XBUTTON1			0x0001
#define XBUTTON2			0x0002
#endif

//
// CAdvancedOptions
//

CHotkeyOptions*			CHotkeyOptions::s_singleton = NULL;

CHotkeyOptions::CHotkeyOptions(HWND parent, CConfig* config) :
	m_parent(parent),
	m_config(config)
{
	assert(s_singleton == NULL);
	s_singleton = this;
}

CHotkeyOptions::~CHotkeyOptions()
{
	s_singleton = NULL;
}

void
CHotkeyOptions::doModal()
{
	// do dialog
	m_inputFilter = m_config->getInputFilter();
	DialogBoxParam(s_instance, MAKEINTRESOURCE(IDD_HOTKEY_OPTIONS),
								m_parent, (DLGPROC)dlgProc, (LPARAM)this);
}

void
CHotkeyOptions::doInit(HWND hwnd)
{
	m_activeRuleIndex = (UInt32)-1;
	fillHotkeys(hwnd);
	openRule(hwnd);
}

void
CHotkeyOptions::fillHotkeys(HWND hwnd, UInt32 select)
{
	HWND rules = getItem(hwnd, IDC_HOTKEY_HOTKEYS);

	SendMessage(rules, LB_RESETCONTENT, 0, 0);
	for (UInt32 i = 0, n = m_inputFilter->getNumRules(); i < n; ++i) {
		CInputFilter::CRule& rule = m_inputFilter->getRule(i);
		SendMessage(rules, LB_INSERTSTRING, (WPARAM)-1,
							(LPARAM)rule.getCondition()->format().c_str());
	}

	if (select < m_inputFilter->getNumRules()) {
		SendMessage(rules, LB_SETCURSEL, select, 0);
	}

	updateHotkeysControls(hwnd);
}

void
CHotkeyOptions::updateHotkeysControls(HWND hwnd)
{
	HWND child = getItem(hwnd, IDC_HOTKEY_HOTKEYS);
	bool selected = (SendMessage(child, LB_GETCURSEL, 0, 0) != LB_ERR);

	enableItem(hwnd, IDC_HOTKEY_ADD_HOTKEY, TRUE);
	enableItem(hwnd, IDC_HOTKEY_EDIT_HOTKEY, selected);
	enableItem(hwnd, IDC_HOTKEY_REMOVE_HOTKEY, selected);
}

void
CHotkeyOptions::addHotkey(HWND hwnd)
{
	closeRule(hwnd);
	CInputFilter::CCondition* condition = NULL;
	if (editCondition(hwnd, condition)) {
		m_inputFilter->addFilterRule(CInputFilter::CRule(condition));
		fillHotkeys(hwnd, m_inputFilter->getNumRules() - 1);
	}
	else {
		delete condition;
	}
	openRule(hwnd);
}

void
CHotkeyOptions::removeHotkey(HWND hwnd)
{
	UInt32 ruleIndex = m_activeRuleIndex;
	closeRule(hwnd);

	m_inputFilter->removeFilterRule(ruleIndex);
	UInt32 n = m_inputFilter->getNumRules();
	if (n > 0 && ruleIndex >= n) {
		ruleIndex = n - 1;
	}
	fillHotkeys(hwnd, ruleIndex);

	openRule(hwnd);
}

void
CHotkeyOptions::editHotkey(HWND hwnd)
{
	// save selected item in action list
	HWND actions = getItem(hwnd, IDC_HOTKEY_ACTIONS);
	LRESULT aIndex = SendMessage(actions, LB_GETCURSEL, 0, 0);

	UInt32 index = m_activeRuleIndex;
	closeRule(hwnd);

	CInputFilter::CRule& rule = m_inputFilter->getRule(index);
	CInputFilter::CCondition* condition = rule.getCondition()->clone();
	if (editCondition(hwnd, condition)) {
		rule.setCondition(condition);
		fillHotkeys(hwnd, index);
	}
	else {
		delete condition;
	}

	openRule(hwnd);

	// restore selected item in action list
	if (aIndex != LB_ERR) {
		SendMessage(actions, LB_SETCURSEL, aIndex, 0);
	}
}

void
CHotkeyOptions::fillActions(HWND hwnd, UInt32 select)
{
	HWND actions = getItem(hwnd, IDC_HOTKEY_ACTIONS);
	SendMessage(actions, LB_RESETCONTENT, 0, 0);
	if (m_activeRuleIndex != (UInt32)-1) {
		UInt32 n  = m_activeRule.getNumActions(true);
		UInt32 n2 = m_activeRule.getNumActions(false);
		for (UInt32 i = 0; i < n; ++i) {
			const CInputFilter::CAction& action =
				m_activeRule.getAction(true, i);
			CString line("A ");
			line += action.format();
			SendMessage(actions, LB_INSERTSTRING, (WPARAM)-1,
								(LPARAM)line.c_str());
			SendMessage(actions, LB_SETITEMDATA, (WPARAM)i, (LPARAM)i);
		}
		for (UInt32 i = 0; i < n2; ++i) {
			const CInputFilter::CAction& action =
				m_activeRule.getAction(false, i);
			CString line("D ");
			line += action.format();
			SendMessage(actions, LB_INSERTSTRING, (WPARAM)-1,
								(LPARAM)line.c_str());
			SendMessage(actions, LB_SETITEMDATA, (WPARAM)i + n,
								(LPARAM)(i | 0x80000000u));
		}

		if (select < n + n2) {
			SendMessage(actions, LB_SETCURSEL, select, 0);
		}
	}

	updateActionsControls(hwnd);
}

void
CHotkeyOptions::updateActionsControls(HWND hwnd)
{
	HWND child = getItem(hwnd, IDC_HOTKEY_HOTKEYS);
	bool active = (m_activeRuleIndex != (UInt32)-1);

	child = getItem(hwnd, IDC_HOTKEY_ACTIONS);
	bool selected =
		(active && (SendMessage(child, LB_GETCURSEL, 0, 0) != LB_ERR));

	enableItem(hwnd, IDC_HOTKEY_ADD_ACTION, active);
	enableItem(hwnd, IDC_HOTKEY_EDIT_ACTION, selected);
	enableItem(hwnd, IDC_HOTKEY_REMOVE_ACTION, selected);
}

void
CHotkeyOptions::addAction(HWND hwnd)
{
	CInputFilter::CAction* action = NULL;
	bool onActivate = true;
	if (editAction(hwnd, action, onActivate)) {
		m_activeRule.adoptAction(action, onActivate);

		UInt32 actionIndex = m_activeRule.getNumActions(true) - 1;
		if (!onActivate) {
			actionIndex += m_activeRule.getNumActions(false);
		}
		fillActions(hwnd, actionIndex);
	}
	else {
		delete action;
	}
}

void
CHotkeyOptions::removeAction(HWND hwnd)
{
	HWND child = getItem(hwnd, IDC_HOTKEY_ACTIONS);
	LRESULT index = SendMessage(child, LB_GETCURSEL, 0, 0);
	if (index != LB_ERR) {
		UInt32 actionIndex =
			(UInt32)SendMessage(child, LB_GETITEMDATA, index, 0);
		bool onActivate = ((actionIndex & 0x80000000u) == 0);
		actionIndex    &= ~0x80000000u;

		m_activeRule.removeAction(onActivate, actionIndex);

		actionIndex = static_cast<UInt32>(index);
		UInt32 n = m_activeRule.getNumActions(true) +
						m_activeRule.getNumActions(false);
		if (n > 0 && actionIndex >= n) {
			actionIndex = n - 1;
		}
		fillActions(hwnd, actionIndex);
	}
}

void
CHotkeyOptions::editAction(HWND hwnd)
{
	HWND child = getItem(hwnd, IDC_HOTKEY_ACTIONS);
	LRESULT index = SendMessage(child, LB_GETCURSEL, 0, 0);
	if (index != LB_ERR) {
		UInt32 actionIndex =
			(UInt32)SendMessage(child, LB_GETITEMDATA, index, 0);
		bool onActivate = ((actionIndex & 0x80000000u) == 0);
		actionIndex    &= ~0x80000000u;

		CInputFilter::CAction* action =
			m_activeRule.getAction(onActivate, actionIndex).clone();
		bool newOnActivate = onActivate;
		if (editAction(hwnd, action, newOnActivate)) {
			if (onActivate == newOnActivate) {
				m_activeRule.replaceAction(action, onActivate, actionIndex);
				actionIndex = static_cast<UInt32>(index);
			}
			else {
				m_activeRule.removeAction(onActivate, actionIndex);
				m_activeRule.adoptAction(action, newOnActivate);
				actionIndex = m_activeRule.getNumActions(true) - 1;
				if (!newOnActivate) {
					actionIndex += m_activeRule.getNumActions(false);
				}
			}
			fillActions(hwnd, actionIndex);
		}
		else {
			delete action;
		}
	}
}

bool
CHotkeyOptions::editCondition(HWND hwnd, CInputFilter::CCondition*& condition)
{
	return CConditionDialog::doModal(hwnd, condition);
}

bool
CHotkeyOptions::editAction(HWND hwnd, CInputFilter::CAction*& action,
				bool& onActivate)
{
	return CActionDialog::doModal(hwnd, m_config, action, onActivate);
}

void
CHotkeyOptions::openRule(HWND hwnd)
{
	// get the active rule and copy it, merging down/up pairs of keystroke
	// and mouse button actions into single actions for the convenience of
	// of the user.
	HWND rules = getItem(hwnd, IDC_HOTKEY_HOTKEYS);
	LRESULT index = SendMessage(rules, LB_GETCURSEL, 0, 0);
	if (index != LB_ERR) {
		// copy the rule as is
		m_activeRuleIndex = (SInt32)index;
		m_activeRule = m_inputFilter->getRule(m_activeRuleIndex);

		// look for actions to combine
		for (UInt32 i = 0, n = m_activeRule.getNumActions(true); i < n; ++i) {
			// get next activate action
			const CInputFilter::CAction* action =
				&m_activeRule.getAction(true, i);

			// check if it's a key or mouse action
			const CInputFilter::CKeystrokeAction* keyAction =
				dynamic_cast<const CInputFilter::CKeystrokeAction*>(action);
			const CInputFilter::CMouseButtonAction* mouseAction =
				dynamic_cast<const CInputFilter::CMouseButtonAction*>(action);
			if (keyAction == NULL && mouseAction == NULL) {
				continue;
			}

			// check for matching deactivate action
			UInt32 j = (UInt32)-1;
			CInputFilter::CAction* newAction = NULL;
			if (keyAction != NULL) {
				j = findMatchingAction(keyAction);
				if (j != (UInt32)-1) {
					// found a match
					const IPlatformScreen::CKeyInfo* oldInfo =
						keyAction->getInfo();
					IPlatformScreen::CKeyInfo* newInfo =
						IKeyState::CKeyInfo::alloc(*oldInfo);
					newAction = new CKeystrokeDownUpAction(newInfo);
				}
			}
			else if (mouseAction != NULL) {
				j = findMatchingAction(mouseAction);
				if (j != (UInt32)-1) {
					// found a match
					const IPlatformScreen::CButtonInfo* oldInfo =
						mouseAction->getInfo();
					IPlatformScreen::CButtonInfo* newInfo =
						IPrimaryScreen::CButtonInfo::alloc(*oldInfo);
					newAction = new CMouseButtonDownUpAction(newInfo);
				}
			}

			// perform merge
			if (newAction != NULL) {
				m_activeRule.replaceAction(newAction, true, i);
				m_activeRule.removeAction(false, j);
			}
		}
	}
	else {
		m_activeRuleIndex = (UInt32)-1;
	}
	fillActions(hwnd);
}

void
CHotkeyOptions::closeRule(HWND)
{
	// copy rule back to input filter, expanding merged actions into the
	// two component actions.
	if (m_activeRuleIndex != (UInt32)-1) {
		// expand merged rules
		for (UInt32 i = 0, n = m_activeRule.getNumActions(true); i < n; ++i) {
			// get action
			const CInputFilter::CAction* action =
				&m_activeRule.getAction(true, i);

			// check if it's a merged key or mouse action
			const CKeystrokeDownUpAction* keyAction =
				dynamic_cast<const CKeystrokeDownUpAction*>(action);
			const CMouseButtonDownUpAction* mouseAction =
				dynamic_cast<const CMouseButtonDownUpAction*>(action);
			if (keyAction == NULL && mouseAction == NULL) {
				continue;
			}

			// expand
			if (keyAction != NULL) {
				const IPlatformScreen::CKeyInfo* oldInfo =
					keyAction->getInfo();
				IPlatformScreen::CKeyInfo* newInfo =
					IKeyState::CKeyInfo::alloc(*oldInfo);
				CInputFilter::CKeystrokeAction* downAction =
					new CInputFilter::CKeystrokeAction(newInfo, true);
				newInfo = IKeyState::CKeyInfo::alloc(*oldInfo);
				CInputFilter::CKeystrokeAction* upAction =
					new CInputFilter::CKeystrokeAction(newInfo, false);
				m_activeRule.replaceAction(downAction, true, i);
				m_activeRule.adoptAction(upAction, false);
			}
			else if (mouseAction != NULL) {
				const IPlatformScreen::CButtonInfo* oldInfo =
					mouseAction->getInfo();
				IPlatformScreen::CButtonInfo* newInfo =
					IPrimaryScreen::CButtonInfo::alloc(*oldInfo);
				CInputFilter::CMouseButtonAction* downAction =
					new CInputFilter::CMouseButtonAction(newInfo, true);
				newInfo = IPrimaryScreen::CButtonInfo::alloc(*oldInfo);
				CInputFilter::CMouseButtonAction* upAction =
					new CInputFilter::CMouseButtonAction(newInfo, false);
				m_activeRule.replaceAction(downAction, true, i);
				m_activeRule.adoptAction(upAction, false);
			}
		}

		// copy it back
		m_inputFilter->getRule(m_activeRuleIndex) = m_activeRule;
	}
	m_activeRuleIndex = (UInt32)-1;
}

UInt32
CHotkeyOptions::findMatchingAction(
				const CInputFilter::CKeystrokeAction* src) const
{
	for (UInt32 i = 0, n = m_activeRule.getNumActions(false); i < n; ++i) {
		const CInputFilter::CKeystrokeAction* dst =
			dynamic_cast<const CInputFilter::CKeystrokeAction*>(
				&m_activeRule.getAction(false, i));
		if (dst != NULL &&
			IKeyState::CKeyInfo::equal(src->getInfo(), dst->getInfo())) {
			return i;
		}
	}
	return (UInt32)-1;
}

UInt32
CHotkeyOptions::findMatchingAction(
				const CInputFilter::CMouseButtonAction* src) const
{
	for (UInt32 i = 0, n = m_activeRule.getNumActions(false); i < n; ++i) {
		const CInputFilter::CMouseButtonAction* dst =
			dynamic_cast<const CInputFilter::CMouseButtonAction*>(
				&m_activeRule.getAction(false, i));
		if (dst != NULL &&
			IPrimaryScreen::CButtonInfo::equal(
								src->getInfo(), dst->getInfo())) {
			return i;
		}
	}
	return (UInt32)-1;
}

BOOL
CHotkeyOptions::doDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM)
{
	switch (message) {
	case WM_INITDIALOG:
		doInit(hwnd);
		return TRUE;

	case WM_COMMAND:
		switch (LOWORD(wParam)) {
		case IDOK:
		case IDCANCEL:
			closeRule(hwnd);
			EndDialog(hwnd, 0);
			return TRUE;

		case IDC_HOTKEY_HOTKEYS:
			switch (HIWORD(wParam)) {
			case LBN_DBLCLK:
				editHotkey(hwnd);
				return TRUE;

			case LBN_SELCHANGE: {
				HWND rules    = getItem(hwnd, IDC_HOTKEY_HOTKEYS);
				LRESULT index = SendMessage(rules, LB_GETCURSEL, 0, 0);
				if (m_activeRuleIndex != (UInt32)index) {
					closeRule(hwnd);
					updateHotkeysControls(hwnd);
					openRule(hwnd);
				}
				return TRUE;
			}
			}
			break;

		case IDC_HOTKEY_ADD_HOTKEY:
			addHotkey(hwnd);
			return TRUE;

		case IDC_HOTKEY_REMOVE_HOTKEY:
			removeHotkey(hwnd);
			return TRUE;

		case IDC_HOTKEY_EDIT_HOTKEY:
			editHotkey(hwnd);
			return TRUE;

		case IDC_HOTKEY_ACTIONS:
			switch (HIWORD(wParam)) {
			case LBN_DBLCLK:
				editAction(hwnd);
				return TRUE;

			case LBN_SELCHANGE:
				updateActionsControls(hwnd);
				return TRUE;
			}
			break;

		case IDC_HOTKEY_ADD_ACTION:
			addAction(hwnd);
			return TRUE;

		case IDC_HOTKEY_REMOVE_ACTION:
			removeAction(hwnd);
			return TRUE;

		case IDC_HOTKEY_EDIT_ACTION:
			editAction(hwnd);
			return TRUE;
		}
		break;

	default:
		break;
	}

	return FALSE;
}

BOOL CALLBACK
CHotkeyOptions::dlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	return s_singleton->doDlgProc(hwnd, message, wParam, lParam);
}


//
// CHotkeyOptions::CConditionDialog
//

CInputFilter::CCondition*
				CHotkeyOptions::CConditionDialog::s_condition         = NULL;
CInputFilter::CCondition*
				CHotkeyOptions::CConditionDialog::s_lastGoodCondition = NULL;
WNDPROC			CHotkeyOptions::CConditionDialog::s_editWndProc       = NULL;

bool
CHotkeyOptions::CConditionDialog::doModal(HWND parent,
				CInputFilter::CCondition*& condition)
{
	s_condition = condition;
	if (s_condition != NULL) {
		s_lastGoodCondition = s_condition->clone();
	}
	else {
		s_lastGoodCondition = NULL;
	}
	int n = (int)DialogBox(s_instance, MAKEINTRESOURCE(IDD_HOTKEY_CONDITION),
								parent, (DLGPROC) dlgProc);

	condition           = s_condition;
	delete s_lastGoodCondition;
	s_condition         = NULL;
	s_lastGoodCondition = NULL;

	// user effectively cancelled if the condition is NULL
	if (condition == NULL) {
		n = 0;
	}

	return (n == 1);
}

void
CHotkeyOptions::CConditionDialog::doInit(HWND hwnd)
{
	// subclass edit control
	HWND child = getItem(hwnd, IDC_HOTKEY_CONDITION_HOTKEY);
	s_editWndProc = (WNDPROC)GetWindowLongPtr(child, GWLP_WNDPROC);
	SetWindowLongPtr(child, GWLP_WNDPROC, (LONG_PTR) editProc);

	// fill control
	fillHotkey(hwnd);
}

void
CHotkeyOptions::CConditionDialog::fillHotkey(HWND hwnd)
{
	HWND child = getItem(hwnd, IDC_HOTKEY_CONDITION_HOTKEY);
	if (s_condition != NULL) {
		setWindowText(child, s_condition->format().c_str());
	}
	else {
		setWindowText(child, "");
	}
}

void
CHotkeyOptions::CConditionDialog::onButton(HWND hwnd, ButtonID button)
{
	delete s_condition;
	s_condition =
		new CInputFilter::CMouseButtonCondition(button, getModifiers());

	fillHotkey(GetParent(hwnd));
}

void
CHotkeyOptions::CConditionDialog::onKey(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
	// ignore key repeats
	if ((lParam & 0xc0000000u) == 0x40000000u) {
		return;
	}

	// ignore key releases if the condition is complete and for the tab
	// key (in case we were just tabbed to)
	if ((lParam & 0x80000000u) != 0) {
		if (isGoodCondition() || wParam == VK_TAB) {
			return;
		}
	}

	KeyID key            = kKeyNone;
	KeyModifierMask mask = getModifiers();
	switch (wParam) {
	case VK_SHIFT:
	case VK_LSHIFT:
	case VK_RSHIFT:
	case VK_CONTROL:
	case VK_LCONTROL:
	case VK_RCONTROL:
	case VK_MENU:
	case VK_LMENU:
	case VK_RMENU:
	case VK_LWIN:
	case VK_RWIN:
		break;

	case VK_TAB:
		// allow tabbing out of control
		if ((mask & (KeyModifierControl |
							KeyModifierAlt | KeyModifierSuper)) == 0) {
			HWND next = hwnd;
			if ((mask & KeyModifierShift) == 0) {
				do {
					next = GetWindow(next, GW_HWNDNEXT);
					if (next == NULL) {
						next = GetWindow(hwnd, GW_HWNDFIRST);
					}
				} while (next != hwnd &&
					(!IsWindowVisible(next) ||
					(GetWindowLong(next, GWL_STYLE) & WS_TABSTOP) == 0));
			}
			else {
				do {
					next = GetWindow(next, GW_HWNDPREV);
					if (next == NULL) {
						next = GetWindow(hwnd, GW_HWNDLAST);
					}
				} while (next != hwnd &&
					(!IsWindowVisible(next) ||
					(GetWindowLong(next, GWL_STYLE) & WS_TABSTOP) == 0));
			}
			SetFocus(next);
			return;
		}
		// fall through

	default:
		key = CMSWindowsKeyState::getKeyID((UINT)wParam,
						static_cast<KeyButton>((lParam & 0x1ff0000u) >> 16));
		switch (key) {
		case kKeyNone:
			// could be a character
			key = getChar(wParam, lParam);
			if (key == kKeyNone) {
				return;
			}
			break;

		case kKeyShift_L:
		case kKeyShift_R:
		case kKeyControl_L:
		case kKeyControl_R:
		case kKeyAlt_L:
		case kKeyAlt_R:
		case kKeyMeta_L:
		case kKeyMeta_R:
		case kKeySuper_L:
		case kKeySuper_R:
		case kKeyCapsLock:
		case kKeyNumLock:
		case kKeyScrollLock:
			// bogus
			return;
		}
		break;
	}

	delete s_condition;
	s_condition = new CInputFilter::CKeystrokeCondition(key, mask);

	fillHotkey(GetParent(hwnd));
}

KeyID
CHotkeyOptions::CConditionDialog::getChar(WPARAM wParam, LPARAM lParam)
{
	BYTE keyState[256];
	UINT virtualKey = (UINT)wParam;
	UINT scanCode   = (UINT)((lParam & 0x0ff0000u) >> 16);
	if (!GetKeyboardState(keyState)) {
		LOG((CLOG_WARN "GetKeyboardState failed on CHotkeyOptions::CConditionDialog::getChar"));
		return kKeyNone;
	}

	// reset modifier state
	keyState[VK_SHIFT]    = 0;
	keyState[VK_LSHIFT]   = 0;
	keyState[VK_RSHIFT]   = 0;
	keyState[VK_CONTROL]  = 0;
	keyState[VK_LCONTROL] = 0;
	keyState[VK_RCONTROL] = 0;
	keyState[VK_MENU]     = 0;
	keyState[VK_LMENU]    = 0;
	keyState[VK_RMENU]    = 0;
	keyState[VK_LWIN]     = 0;
	keyState[VK_RWIN]     = 0;

	// translate virtual key to character
	int n;
	KeyID id = kKeyNone;
	if (CArchMiscWindows::isWindows95Family()) {
		// XXX -- how do we get characters not in Latin-1?
		WORD ascii;
		n  = ToAscii(virtualKey, scanCode, keyState, &ascii, 0);
		id = static_cast<KeyID>(ascii & 0xffu);
	}
	else {
		typedef int (WINAPI *ToUnicode_t)(UINT wVirtKey,
											UINT wScanCode,
											PBYTE lpKeyState,
											LPWSTR pwszBuff,
											int cchBuff,
											UINT wFlags);
		ToUnicode_t s_ToUnicode = NULL;
		if (s_ToUnicode == NULL) {
			HMODULE userModule = GetModuleHandle("user32.dll");
			if(userModule == NULL) {
				LOG((CLOG_ERR "GetModuleHandle(\"user32.dll\") returned NULL"));
				return kKeyNone;
			}
			s_ToUnicode =
				(ToUnicode_t)GetProcAddress(userModule, "ToUnicode");
		}

		WCHAR unicode[2];
		n  = s_ToUnicode(virtualKey, scanCode, keyState,
								unicode, sizeof(unicode) / sizeof(unicode[0]),
								0);
		id = static_cast<KeyID>(unicode[0]);
	}
	switch (n) {
	case -1:
		// no hot keys on dead keys
		return kKeyNone;

	default:
	case 0:
		// unmapped
		return kKeyNone;

	case 1:
		return id;
	}
}

KeyModifierMask
CHotkeyOptions::CConditionDialog::getModifiers()
{
	KeyModifierMask mask = 0;
	if ((GetKeyState(VK_SHIFT) & 0x8000) != 0) {
		mask |= KeyModifierShift;
	}
	if ((GetKeyState(VK_CONTROL) & 0x8000) != 0) {
		mask |= KeyModifierControl;
	}
	if ((GetKeyState(VK_MENU) & 0x8000) != 0) {
		mask |= KeyModifierAlt;
	}
	if ((GetKeyState(VK_LWIN) & 0x8000) != 0 ||
		(GetKeyState(VK_RWIN) & 0x8000) != 0) {
		mask |= KeyModifierSuper;
	}
	return mask;
}

bool
CHotkeyOptions::CConditionDialog::isGoodCondition()
{
	CInputFilter::CKeystrokeCondition* keyCondition =
		dynamic_cast<CInputFilter::CKeystrokeCondition*>(s_condition);
	return (keyCondition == NULL || keyCondition->getKey() != kKeyNone);
}

BOOL CALLBACK
CHotkeyOptions::CConditionDialog::dlgProc(HWND hwnd,
				UINT message, WPARAM wParam, LPARAM)
{
	switch (message) {
	case WM_INITDIALOG:
		doInit(hwnd);
		return TRUE;

	case WM_COMMAND:
		switch (LOWORD(wParam)) {
		case IDOK:
			EndDialog(hwnd, 1);
			return TRUE;

		case IDCANCEL:
			EndDialog(hwnd, 0);
			return TRUE;
		}
		break;

	default:
		break;
	}

	return FALSE;
}

LRESULT CALLBACK
CHotkeyOptions::CConditionDialog::editProc(HWND hwnd,
				UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message) {
	case WM_LBUTTONDOWN:
		if (GetFocus() == hwnd) {
			onButton(hwnd, kButtonLeft);
		}
		else {
			SetFocus(hwnd);
		}
		return 0;

	case WM_MBUTTONDOWN:
		if (GetFocus() == hwnd) {
			onButton(hwnd, kButtonMiddle);
		}
		return 0;

	case WM_RBUTTONDOWN:
		if (GetFocus() == hwnd) {
			onButton(hwnd, kButtonRight);
		}
		return 0;

	case WM_XBUTTONDOWN:
		if (GetFocus() == hwnd) {
			switch (HIWORD(wParam)) {
			case XBUTTON1:
				onButton(hwnd, kButtonExtra0 + 0);
				break;

			case XBUTTON2:
				onButton(hwnd, kButtonExtra0 + 1);
				break;
			}
		}
		return 0;

	case WM_KEYDOWN:
	case WM_SYSKEYDOWN:
	case WM_KEYUP:
	case WM_SYSKEYUP:
		onKey(hwnd, wParam, lParam);
		return 0;

	case WM_LBUTTONUP:
	case WM_MBUTTONUP:
	case WM_RBUTTONUP:
	case WM_XBUTTONUP:
	case WM_CHAR:
	case WM_SYSCHAR:
	case WM_DEADCHAR:
	case WM_SYSDEADCHAR:
		return 0;

	case WM_SETFOCUS:
		if (s_condition != NULL) {
			delete s_lastGoodCondition;
			s_lastGoodCondition = s_condition->clone();
		}
		break;

	case WM_KILLFOCUS:
		if (!isGoodCondition()) {
			delete s_condition;
			if (s_lastGoodCondition != NULL) {
				s_condition = s_lastGoodCondition->clone();
			}
			else {
				s_condition = NULL;
			}
		}
		fillHotkey(GetParent(hwnd));
		break;

	case WM_GETDLGCODE:
		return DLGC_WANTALLKEYS;

	default:
		break;
	}
	return CallWindowProc(s_editWndProc, hwnd, message, wParam, lParam);
}


//
// CHotkeyOptions::CActionDialog
//

CConfig*		CHotkeyOptions::CActionDialog::s_config         = NULL;
bool			CHotkeyOptions::CActionDialog::s_onActivate     = false;
CInputFilter::CAction*
				CHotkeyOptions::CActionDialog::s_action         = NULL;
CInputFilter::CAction*
				CHotkeyOptions::CActionDialog::s_lastGoodAction = NULL;
std::set<CString>
				CHotkeyOptions::CActionDialog::s_screens;
WNDPROC			CHotkeyOptions::CActionDialog::s_editWndProc    = NULL;

bool
CHotkeyOptions::CActionDialog::doModal(HWND parent, CConfig* config,
				CInputFilter::CAction*& action, bool& onActivate)
{
	s_config     = config;
	s_onActivate = onActivate;
	s_action     = action;
	if (s_action != NULL) {
		s_lastGoodAction = s_action->clone();
	}
	else {
		s_lastGoodAction = NULL;
	}

	int n = (int)DialogBox(s_instance, MAKEINTRESOURCE(IDD_HOTKEY_ACTION),
								parent, (DLGPROC) dlgProc);

	onActivate       = s_onActivate;
	action           = s_action;
	delete s_lastGoodAction;
	s_action         = NULL;
	s_lastGoodAction = NULL;

	return (n == 1);
}

void
CHotkeyOptions::CActionDialog::doInit(HWND hwnd)
{
	// subclass edit control
	HWND child = getItem(hwnd, IDC_HOTKEY_ACTION_HOTKEY);
	s_editWndProc = (WNDPROC)GetWindowLongPtr(child, GWLP_WNDPROC);
	SetWindowLongPtr(child, GWLP_WNDPROC, (LONG_PTR)editProc);
	setWindowText(getItem(hwnd, IDC_HOTKEY_ACTION_HOTKEY), "");
	fillHotkey(hwnd);

	// fill screens
	child = getItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_TO_LIST);
	SendMessage(child, CB_RESETCONTENT, 0, 0);
	for (CConfig::const_iterator index = s_config->begin();
							index != s_config->end(); ) {
		const CString& name = *index;
		++index;
		if (index != s_config->end()) {
			SendMessage(child, CB_INSERTSTRING,
							(WPARAM)-1, (LPARAM)name.c_str());
		}
		else {
			SendMessage(child, CB_ADDSTRING, 0, (LPARAM)name.c_str());
		}
	}
	SendMessage(child, CB_SETCURSEL, 0, 0);

	// fill directions
	child = getItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_IN_LIST);
	SendMessage(child, CB_ADDSTRING, 0,
							(LPARAM)getString(IDS_EDGE_LEFT).c_str());
	SendMessage(child, CB_ADDSTRING, 0,
							(LPARAM)getString(IDS_EDGE_RIGHT).c_str());
	SendMessage(child, CB_ADDSTRING, 0,
							(LPARAM)getString(IDS_EDGE_TOP).c_str());
	SendMessage(child, CB_ADDSTRING, 0,
							(LPARAM)getString(IDS_EDGE_BOTTOM).c_str());
	SendMessage(child, CB_SETCURSEL, 0, 0);

	// fill lock modes
	child = getItem(hwnd, IDC_HOTKEY_ACTION_LOCK_LIST);
	SendMessage(child, CB_ADDSTRING, 0,
							(LPARAM)getString(IDS_MODE_OFF).c_str());
	SendMessage(child, CB_ADDSTRING, 0,
							(LPARAM)getString(IDS_MODE_ON).c_str());
	SendMessage(child, CB_ADDSTRING, 0,
							(LPARAM)getString(IDS_MODE_TOGGLE).c_str());
	SendMessage(child, CB_SETCURSEL, 0, 0);

	// fill keyboard broadcast modes
	child = getItem(hwnd, IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST_LIST);
	SendMessage(child, CB_ADDSTRING, 0,
							(LPARAM)getString(IDS_MODE_OFF).c_str());
	SendMessage(child, CB_ADDSTRING, 0,
							(LPARAM)getString(IDS_MODE_ON).c_str());
	SendMessage(child, CB_ADDSTRING, 0,
							(LPARAM)getString(IDS_MODE_TOGGLE).c_str());
	SendMessage(child, CB_SETCURSEL, 0, 0);

	// select when
	if (s_onActivate) {
		child = getItem(hwnd, IDC_HOTKEY_ACTION_ON_ACTIVATE);
	}
	else {
		child = getItem(hwnd, IDC_HOTKEY_ACTION_ON_DEACTIVATE);
	}
	setItemChecked(child, true);

	// no screens by default
	s_screens.clear();

	// select mode
	child = NULL;
	CInputFilter::CKeystrokeAction* keyAction =
		dynamic_cast<CInputFilter::CKeystrokeAction*>(s_action);
	CInputFilter::CMouseButtonAction* mouseAction =
		dynamic_cast<CInputFilter::CMouseButtonAction*>(s_action);
	CInputFilter::CLockCursorToScreenAction* lockAction =
		dynamic_cast<CInputFilter::CLockCursorToScreenAction*>(s_action);
	CInputFilter::CSwitchToScreenAction* switchToAction =
		dynamic_cast<CInputFilter::CSwitchToScreenAction*>(s_action);
	CInputFilter::CSwitchInDirectionAction* switchInAction =
		dynamic_cast<CInputFilter::CSwitchInDirectionAction*>(s_action);
	CInputFilter::CKeyboardBroadcastAction* keyboardBroadcastAction=
		dynamic_cast<CInputFilter::CKeyboardBroadcastAction*>(s_action);
	if (keyAction != NULL) {
		if (dynamic_cast<CKeystrokeDownUpAction*>(s_action) != NULL) {
			child = getItem(hwnd, IDC_HOTKEY_ACTION_DOWNUP);
		}
		else if (keyAction->isOnPress()) {
			child = getItem(hwnd, IDC_HOTKEY_ACTION_DOWN);
		}
		else {
			child = getItem(hwnd, IDC_HOTKEY_ACTION_UP);
		}
	}
	else if (mouseAction != NULL) {
		if (dynamic_cast<CMouseButtonDownUpAction*>(s_action) != NULL) {
			child = getItem(hwnd, IDC_HOTKEY_ACTION_DOWNUP);
		}
		else if (keyAction->isOnPress()) {
			child = getItem(hwnd, IDC_HOTKEY_ACTION_DOWN);
		}
		else {
			child = getItem(hwnd, IDC_HOTKEY_ACTION_UP);
		}
	}
	else if (lockAction != NULL) {
		child = getItem(hwnd, IDC_HOTKEY_ACTION_LOCK_LIST);
		SendMessage(child, CB_SETCURSEL, lockAction->getMode(), 0);
		child = getItem(hwnd, IDC_HOTKEY_ACTION_LOCK);
	}
	else if (switchToAction != NULL) {
		child = getItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_TO_LIST);
		DWORD i = (DWORD)SendMessage(child, CB_FINDSTRINGEXACT, (WPARAM)-1,
								(LPARAM)switchToAction->getScreen().c_str());
		if (i == CB_ERR) {
			i = 0;
		}
		SendMessage(child, CB_SETCURSEL, i, 0);
		child = getItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_TO);
	}
	else if (switchInAction != NULL) {
		child = getItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_IN_LIST);
		SendMessage(child, CB_SETCURSEL,
							switchInAction->getDirection() - kLeft, 0);
		child = getItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_IN);
	}
	else if (keyboardBroadcastAction != NULL) {
		// Save the screens we're broadcasting to
		s_screens = keyboardBroadcastAction->getScreens();

		child = getItem(hwnd, IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST_LIST);
		SendMessage(child, CB_SETCURSEL, keyboardBroadcastAction->getMode(), 0);
		child = getItem(hwnd, IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST);
	}
	if (child != NULL) {
		setItemChecked(child, true);
	}

	updateControls(hwnd);
}

void
CHotkeyOptions::CActionDialog::fillHotkey(HWND hwnd)
{
	HWND child = getItem(hwnd, IDC_HOTKEY_ACTION_HOTKEY);
	CInputFilter::CKeystrokeAction* keyAction =
		dynamic_cast<CInputFilter::CKeystrokeAction*>(s_action);
	CInputFilter::CMouseButtonAction* mouseAction =
		dynamic_cast<CInputFilter::CMouseButtonAction*>(s_action);
	if (keyAction != NULL || mouseAction != NULL) {
		setWindowText(child, s_action->format().c_str());
	}
	else {
		setWindowText(child, "");
	}

	// can only set screens in key actions
	enableItem(hwnd, IDC_HOTKEY_ACTION_SCREENS, keyAction != NULL);
}

void
CHotkeyOptions::CActionDialog::updateControls(HWND hwnd)
{
	// determine which mode we're in
	UInt32 mode = 0;
	if (isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_DOWNUP)) ||
		isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_DOWN)) ||
		isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_UP))) {
		mode = 1;
	}
	else if (isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_TO))) {
		mode = 2;
	}
	else if (isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_IN))) {
		mode = 3;
	}
	else if (isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_LOCK))) {
		mode = 4;
	}
	else if (isItemChecked(getItem(hwnd,
									IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST))) {
		mode = 5;
	}

	// enable/disable all mode specific controls
	enableItem(hwnd, IDC_HOTKEY_ACTION_HOTKEY, mode == 1);
	enableItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_TO_LIST, mode == 2);
	enableItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_IN_LIST, mode == 3);
	enableItem(hwnd, IDC_HOTKEY_ACTION_LOCK_LIST, mode == 4);
	enableItem(hwnd, IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST_LIST, mode == 5);
	enableItem(hwnd, IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST_SCREENS, mode == 5);

	// can only set screens in key actions
	CInputFilter::CKeystrokeAction* keyAction =
		dynamic_cast<CInputFilter::CKeystrokeAction*>(s_action);
	enableItem(hwnd, IDC_HOTKEY_ACTION_SCREENS, keyAction != NULL);
}

void
CHotkeyOptions::CActionDialog::onButton(HWND hwnd, ButtonID button)
{
	IPlatformScreen::CButtonInfo* info =
		IPrimaryScreen::CButtonInfo::alloc(button, getModifiers());
	delete s_action;
	HWND parent = GetParent(hwnd);
	if (isItemChecked(getItem(parent, IDC_HOTKEY_ACTION_DOWNUP))) {
		s_action = new CMouseButtonDownUpAction(info);
	}
	else if (isItemChecked(getItem(parent, IDC_HOTKEY_ACTION_DOWN))) {
		s_action = new CInputFilter::CMouseButtonAction(info, true);
	}
	else if (isItemChecked(getItem(parent, IDC_HOTKEY_ACTION_UP))) {
		s_action = new CInputFilter::CMouseButtonAction(info, false);
	}
	else {
		s_action = NULL;
	}

	fillHotkey(parent);
}

void
CHotkeyOptions::CActionDialog::onKey(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
	// ignore key repeats
	if ((lParam & 0xc0000000u) == 0x40000000u) {
		return;
	}

	// ignore key releases if the action is complete and for the tab
	// key (in case we were just tabbed to)
	if ((lParam & 0x80000000u) != 0) {
		if (isGoodAction() || wParam == VK_TAB) {
			return;
		}
	}

	KeyID key            = kKeyNone;
	KeyModifierMask mask = getModifiers();
	switch (wParam) {
	case VK_SHIFT:
	case VK_LSHIFT:
	case VK_RSHIFT:
	case VK_CONTROL:
	case VK_LCONTROL:
	case VK_RCONTROL:
	case VK_MENU:
	case VK_LMENU:
	case VK_RMENU:
	case VK_LWIN:
	case VK_RWIN:
		break;

	case VK_TAB:
		// allow tabbing out of control
		if ((mask & (KeyModifierControl |
							KeyModifierAlt | KeyModifierSuper)) == 0) {
			HWND next = hwnd;
			if ((mask & KeyModifierShift) == 0) {
				do {
					next = GetWindow(next, GW_HWNDNEXT);
					if (next == NULL) {
						next = GetWindow(hwnd, GW_HWNDFIRST);
					}
				} while (next != hwnd &&
					(!IsWindowVisible(next) ||
					(GetWindowLong(next, GWL_STYLE) & WS_TABSTOP) == 0));
			}
			else {
				do {
					next = GetWindow(next, GW_HWNDPREV);
					if (next == NULL) {
						next = GetWindow(hwnd, GW_HWNDLAST);
					}
				} while (next != hwnd &&
					(!IsWindowVisible(next) ||
					(GetWindowLong(next, GWL_STYLE) & WS_TABSTOP) == 0));
			}
			SetFocus(next);
			return;
		}
		// fall through

	default:
		key = CMSWindowsKeyState::getKeyID((UINT)wParam,
						static_cast<KeyButton>((lParam & 0x1ff0000u) >> 16));
		switch (key) {
		case kKeyNone:
			// could be a character
			key = getChar(wParam, lParam);
			if (key == kKeyNone) {
				return;
			}
			break;

		case kKeyShift_L:
		case kKeyShift_R:
		case kKeyControl_L:
		case kKeyControl_R:
		case kKeyAlt_L:
		case kKeyAlt_R:
		case kKeyMeta_L:
		case kKeyMeta_R:
		case kKeySuper_L:
		case kKeySuper_R:
		case kKeyCapsLock:
		case kKeyNumLock:
		case kKeyScrollLock:
			// bogus
			return;
		}
		break;
	}

	// get old screen list
	std::set<CString> screens;
	CInputFilter::CKeystrokeAction* keyAction =
		dynamic_cast<CInputFilter::CKeystrokeAction*>(s_action);
	if (keyAction == NULL) {
		keyAction =
			dynamic_cast<CInputFilter::CKeystrokeAction*>(s_lastGoodAction);
	}
	if (keyAction != NULL) {
		IKeyState::CKeyInfo::split(keyAction->getInfo()->m_screens, screens);
	}

	// create new action
	IPlatformScreen::CKeyInfo* info =
		IKeyState::CKeyInfo::alloc(key, mask, 0, 0, screens);
	delete s_action;
	HWND parent = GetParent(hwnd);
	if (isItemChecked(getItem(parent, IDC_HOTKEY_ACTION_DOWNUP))) {
		s_action = new CKeystrokeDownUpAction(info);
	}
	else if (isItemChecked(getItem(parent, IDC_HOTKEY_ACTION_DOWN))) {
		s_action = new CInputFilter::CKeystrokeAction(info, true);
	}
	else if (isItemChecked(getItem(parent, IDC_HOTKEY_ACTION_UP))) {
		s_action = new CInputFilter::CKeystrokeAction(info, false);
	}
	else {
		s_action = NULL;
	}

	fillHotkey(parent);
}

void
CHotkeyOptions::CActionDialog::onLockAction(HWND hwnd)
{
	HWND child = getItem(hwnd, IDC_HOTKEY_ACTION_LOCK_LIST);
	LRESULT index = SendMessage(child, CB_GETCURSEL, 0, 0);
	if (index != CB_ERR) {
		delete s_action;
		s_action = new CInputFilter::CLockCursorToScreenAction(
			(CInputFilter::CLockCursorToScreenAction::Mode)index);
	}
}

void
CHotkeyOptions::CActionDialog::onSwitchToAction(HWND hwnd)
{
	HWND child = getItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_TO_LIST);
	CString screen = getWindowText(child);
	delete s_action;
	s_action = new CInputFilter::CSwitchToScreenAction(screen);
}

void
CHotkeyOptions::CActionDialog::onSwitchInAction(HWND hwnd)
{
	HWND child = getItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_IN_LIST);
	LRESULT index = SendMessage(child, CB_GETCURSEL, 0, 0);
	if (index != CB_ERR) {
		delete s_action;
		s_action = new CInputFilter::CSwitchInDirectionAction(
							(EDirection)(index + kLeft));
	}
}

void
CHotkeyOptions::CActionDialog::onKeyboardBroadcastAction(HWND hwnd)
{
	HWND child = getItem(hwnd, IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST_LIST);
	LRESULT index = SendMessage(child, CB_GETCURSEL, 0, 0);
	if (index != CB_ERR) {
		delete s_action;
		s_action = new CInputFilter::CKeyboardBroadcastAction(
			(CInputFilter::CKeyboardBroadcastAction::Mode)index, s_screens);
	}
}

KeyID
CHotkeyOptions::CActionDialog::getChar(WPARAM wParam, LPARAM lParam)
{
	BYTE keyState[256];
	UINT virtualKey = (UINT)wParam;
	UINT scanCode   = (UINT)((lParam & 0x0ff0000u) >> 16);
	if (!GetKeyboardState(keyState)) {
		LOG((CLOG_WARN "GetKeyboardState failed on CHotkeyOptions::CActionDialog::getChar"));
		return kKeyNone;
	}
	// reset modifier state
	keyState[VK_SHIFT]    = 0;
	keyState[VK_LSHIFT]   = 0;
	keyState[VK_RSHIFT]   = 0;
	keyState[VK_CONTROL]  = 0;
	keyState[VK_LCONTROL] = 0;
	keyState[VK_RCONTROL] = 0;
	keyState[VK_MENU]     = 0;
	keyState[VK_LMENU]    = 0;
	keyState[VK_RMENU]    = 0;
	keyState[VK_LWIN]     = 0;
	keyState[VK_RWIN]     = 0;

	// translate virtual key to character
	int n;
	KeyID id;
	if (CArchMiscWindows::isWindows95Family()) {
		// XXX -- how do we get characters not in Latin-1?
		WORD ascii;
		n  = ToAscii(virtualKey, scanCode, keyState, &ascii, 0);
		id = static_cast<KeyID>(ascii & 0xffu);
	}
	else {
		typedef int (WINAPI *ToUnicode_t)(UINT wVirtKey,
											UINT wScanCode,
											PBYTE lpKeyState,
											LPWSTR pwszBuff,
											int cchBuff,
											UINT wFlags);
		ToUnicode_t s_ToUnicode = NULL;
		if (s_ToUnicode == NULL) {
			HMODULE userModule = GetModuleHandle("user32.dll");
			if(userModule==NULL) {
				LOG((CLOG_ERR "GetModuleHandle(\"user32.dll\") returned NULL"));
				return kKeyNone;
			}
			s_ToUnicode =
				(ToUnicode_t)GetProcAddress(userModule, "ToUnicode");
		}

		WCHAR unicode[2];
		n  = s_ToUnicode(virtualKey, scanCode, keyState,
								unicode, sizeof(unicode) / sizeof(unicode[0]),
								0);
		id = static_cast<KeyID>(unicode[0]);
	}
	switch (n) {
	case -1:
		// no hot keys on dead keys
		return kKeyNone;

	default:
	case 0:
		// unmapped
		return kKeyNone;

	case 1:
		return id;
	}
}

KeyModifierMask
CHotkeyOptions::CActionDialog::getModifiers()
{
	KeyModifierMask mask = 0;
	if ((GetKeyState(VK_SHIFT) & 0x8000) != 0) {
		mask |= KeyModifierShift;
	}
	if ((GetKeyState(VK_CONTROL) & 0x8000) != 0) {
		mask |= KeyModifierControl;
	}
	if ((GetKeyState(VK_MENU) & 0x8000) != 0) {
		mask |= KeyModifierAlt;
	}
	if ((GetKeyState(VK_LWIN) & 0x8000) != 0 ||
		(GetKeyState(VK_RWIN) & 0x8000) != 0) {
		mask |= KeyModifierSuper;
	}
	return mask;
}

bool
CHotkeyOptions::CActionDialog::isGoodAction()
{
	CInputFilter::CMouseButtonAction* mouseAction =
		dynamic_cast<CInputFilter::CMouseButtonAction*>(s_action);
	CInputFilter::CKeystrokeAction* keyAction =
		dynamic_cast<CInputFilter::CKeystrokeAction*>(s_action);
	return (mouseAction == NULL || keyAction == NULL ||
			keyAction->getInfo()->m_key != kKeyNone);
}

void
CHotkeyOptions::CActionDialog::convertAction(HWND hwnd)
{
	if (s_lastGoodAction != NULL) {
		CInputFilter::CMouseButtonAction* mouseAction =
			dynamic_cast<CInputFilter::CMouseButtonAction*>(s_lastGoodAction);
		CInputFilter::CKeystrokeAction* keyAction =
			dynamic_cast<CInputFilter::CKeystrokeAction*>(s_lastGoodAction);
		if (mouseAction != NULL) {
			IPlatformScreen::CButtonInfo* info =
				IPrimaryScreen::CButtonInfo::alloc(*mouseAction->getInfo());
			delete s_action;
			if (isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_DOWNUP))) {
				s_action = new CMouseButtonDownUpAction(info);
			}
			else if (isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_DOWN))) {
				s_action = new CInputFilter::CMouseButtonAction(info, true);
			}
			else if (isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_UP))) {
				s_action = new CInputFilter::CMouseButtonAction(info, false);
			}
			else {
				free(info);
				s_action = NULL;
			}
		}
		else if (keyAction != NULL) {
			IPlatformScreen::CKeyInfo* info =
				IKeyState::CKeyInfo::alloc(*keyAction->getInfo());
			delete s_action;
			if (isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_DOWNUP))) {
				s_action = new CKeystrokeDownUpAction(info);
			}
			else if (isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_DOWN))) {
				s_action = new CInputFilter::CKeystrokeAction(info, true);
			}
			else if (isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_UP))) {
				s_action = new CInputFilter::CKeystrokeAction(info, false);
			}
			else {
				free(info);
				s_action = NULL;
			}
		}
	}
}

bool
CHotkeyOptions::CActionDialog::isDownUpAction()
{
	return (dynamic_cast<CKeystrokeDownUpAction*>(s_action) != NULL ||
			dynamic_cast<CMouseButtonDownUpAction*>(s_action) != NULL);
}

BOOL CALLBACK
CHotkeyOptions::CActionDialog::dlgProc(HWND hwnd,
				UINT message, WPARAM wParam, LPARAM)
{
	switch (message) {
	case WM_INITDIALOG:
		doInit(hwnd);
		return TRUE;

	case WM_COMMAND:
		switch (LOWORD(wParam)) {
		case IDOK:
			if (isDownUpAction()) {
				s_onActivate = true;
			}
			EndDialog(hwnd, 1);
			return TRUE;

		case IDCANCEL:
			EndDialog(hwnd, 0);
			return TRUE;

		case IDC_HOTKEY_ACTION_ON_ACTIVATE:
			s_onActivate = true;
			return TRUE;

		case IDC_HOTKEY_ACTION_ON_DEACTIVATE:
			s_onActivate = false;
			return TRUE;

		case IDC_HOTKEY_ACTION_DOWNUP:
		case IDC_HOTKEY_ACTION_DOWN:
		case IDC_HOTKEY_ACTION_UP:
			convertAction(hwnd);
			fillHotkey(hwnd);
			updateControls(hwnd);
			return TRUE;

		case IDC_HOTKEY_ACTION_LOCK:
			onLockAction(hwnd);
			updateControls(hwnd);
			return TRUE;

		case IDC_HOTKEY_ACTION_SWITCH_TO:
			onSwitchToAction(hwnd);
			updateControls(hwnd);
			return TRUE;

		case IDC_HOTKEY_ACTION_SWITCH_IN:
			onSwitchInAction(hwnd);
			updateControls(hwnd);
			return TRUE;

		case IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST:
			onKeyboardBroadcastAction(hwnd);
			updateControls(hwnd);
			return TRUE;

		case IDC_HOTKEY_ACTION_LOCK_LIST:
			switch (HIWORD(wParam)) {
			case LBN_SELCHANGE:
				onLockAction(hwnd);
				return TRUE;
			}
			break;

		case IDC_HOTKEY_ACTION_SWITCH_TO_LIST:
			switch (HIWORD(wParam)) {
			case LBN_SELCHANGE:
				onSwitchToAction(hwnd);
				return TRUE;
			}
			break;

		case IDC_HOTKEY_ACTION_SWITCH_IN_LIST:
			switch (HIWORD(wParam)) {
			case LBN_SELCHANGE:
				onSwitchInAction(hwnd);
				return TRUE;
			}
			break;

		case IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST_LIST:
			switch (HIWORD(wParam)) {
			case LBN_SELCHANGE:
				onKeyboardBroadcastAction(hwnd);
				return TRUE;
			}
			break;

		case IDC_HOTKEY_ACTION_SCREENS:
			CScreensDialog::doModal(hwnd, s_config,
				dynamic_cast<CInputFilter::CKeystrokeAction*>(s_action));
			fillHotkey(hwnd);
			return TRUE;

		case IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST_SCREENS: {
			// convert screens to form that CScreenDialog::doModal() wants
			IPlatformScreen::CKeyInfo* tmpInfo =
				IPlatformScreen::CKeyInfo::alloc(0, 0, 0, 1, s_screens);
			CInputFilter::CKeystrokeAction tmpAction(tmpInfo, true);

			// get the screens
			CScreensDialog::doModal(hwnd, s_config, &tmpAction);

			// convert screens back
			IPlatformScreen::CKeyInfo::split(
								tmpAction.getInfo()->m_screens, s_screens);

			// update
			onKeyboardBroadcastAction(hwnd);
			return TRUE;
		}
		}
		break;

	default:
		break;
	}

	return FALSE;
}

LRESULT CALLBACK
CHotkeyOptions::CActionDialog::editProc(HWND hwnd,
				UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message) {
	case WM_LBUTTONDOWN:
		if (GetFocus() == hwnd) {
			onButton(hwnd, kButtonLeft);
		}
		else {
			SetFocus(hwnd);
		}
		return 0;

	case WM_MBUTTONDOWN:
		if (GetFocus() == hwnd) {
			onButton(hwnd, kButtonMiddle);
		}
		return 0;

	case WM_RBUTTONDOWN:
		if (GetFocus() == hwnd) {
			onButton(hwnd, kButtonRight);
		}
		return 0;

	case WM_XBUTTONDOWN:
		if (GetFocus() == hwnd) {
			switch (HIWORD(wParam)) {
			case XBUTTON1:
				onButton(hwnd, kButtonExtra0 + 0);
				break;

			case XBUTTON2:
				onButton(hwnd, kButtonExtra0 + 1);
				break;
			}
		}
		return 0;

	case WM_KEYDOWN:
	case WM_SYSKEYDOWN:
	case WM_KEYUP:
	case WM_SYSKEYUP:
		onKey(hwnd, wParam, lParam);
		return 0;

	case WM_LBUTTONUP:
	case WM_MBUTTONUP:
	case WM_RBUTTONUP:
	case WM_XBUTTONUP:
	case WM_CHAR:
	case WM_SYSCHAR:
	case WM_DEADCHAR:
	case WM_SYSDEADCHAR:
		return 0;

	case WM_SETFOCUS:
		if (s_action != NULL) {
			delete s_lastGoodAction;
			s_lastGoodAction = s_action->clone();
		}
		break;

	case WM_KILLFOCUS:
		if (!isGoodAction()) {
			delete s_action;
			if (s_lastGoodAction != NULL) {
				s_action = s_lastGoodAction->clone();
			}
			else {
				s_action = NULL;
			}
		}
		else if (s_action != NULL) {
			delete s_lastGoodAction;
			s_lastGoodAction = s_action->clone();
		}
		fillHotkey(GetParent(hwnd));
		break;

	case WM_GETDLGCODE:
		return DLGC_WANTALLKEYS;

	default:
		break;
	}
	return CallWindowProc(s_editWndProc, hwnd, message, wParam, lParam);
}


//
// CHotkeyOptions::CScreensDialog
//

CConfig*				CHotkeyOptions::CScreensDialog::s_config = NULL;
CInputFilter::CKeystrokeAction*
						CHotkeyOptions::CScreensDialog::s_action = NULL;
CHotkeyOptions::CScreensDialog::CScreens
						CHotkeyOptions::CScreensDialog::s_nonTargets;
CHotkeyOptions::CScreensDialog::CScreens
						CHotkeyOptions::CScreensDialog::s_targets;
CString				CHotkeyOptions::CScreensDialog::s_allScreens;

void
CHotkeyOptions::CScreensDialog::doModal(HWND parent, CConfig* config,
				CInputFilter::CKeystrokeAction* action)
{
	s_allScreens = getString(IDS_ALL_SCREENS);
	s_config = config;
	s_action = action;
	DialogBox(s_instance, MAKEINTRESOURCE(IDD_HOTKEY_SCREENS),
								parent, (DLGPROC) dlgProc);
	s_config = NULL;
	s_action = NULL;
}

void
CHotkeyOptions::CScreensDialog::doInit(HWND hwnd)
{
	s_nonTargets.clear();
	s_targets.clear();

	// get screens from config
	s_nonTargets.insert("*");
	for (CConfig::const_iterator i = s_config->begin();
							i != s_config->end(); ++i) {
		s_nonTargets.insert(*i);
	}

	// get screens in action
	IKeyState::CKeyInfo::split(s_action->getInfo()->m_screens, s_targets);

	// remove screens in action from screens in config
	for (CScreens::const_iterator i = s_targets.begin();
								i != s_targets.end(); ++i) {
		s_nonTargets.erase(*i);
	}

	// fill dialog
	fillScreens(hwnd);
	updateControls(hwnd);
}

void
CHotkeyOptions::CScreensDialog::doFini(HWND)
{
	// put screens into action
	const IPlatformScreen::CKeyInfo* oldInfo = s_action->getInfo();
	IPlatformScreen::CKeyInfo* newInfo =
		IKeyState::CKeyInfo::alloc(oldInfo->m_key,
								oldInfo->m_mask, 0, 0, s_targets);
	s_action->adoptInfo(newInfo);
}

void
CHotkeyOptions::CScreensDialog::fillScreens(HWND hwnd)
{
	HWND child = getItem(hwnd, IDC_HOTKEY_SCREENS_SRC);
	SendMessage(child, LB_RESETCONTENT, 0, 0);
	for (CScreens::const_iterator i = s_nonTargets.begin();
								i != s_nonTargets.end(); ++i) {
		CString name = *i;
		if (name == "*") {
			name = s_allScreens;
		}
		SendMessage(child, LB_INSERTSTRING, (WPARAM)-1,
							(LPARAM)name.c_str());
	}

	child = getItem(hwnd, IDC_HOTKEY_SCREENS_DST);
	SendMessage(child, LB_RESETCONTENT, 0, 0);
	for (CScreens::const_iterator i = s_targets.begin();
								i != s_targets.end(); ++i) {
		CString name = *i;
		if (name == "*") {
			name = s_allScreens;
		}
		SendMessage(child, LB_INSERTSTRING, (WPARAM)-1,
							(LPARAM)name.c_str());
	}
	if (s_targets.empty()) {
		// if no targets then add a special item so the user knows
		// what'll happen
		CString activeScreenLabel = getString(IDS_ACTIVE_SCREEN);
		SendMessage(child, LB_INSERTSTRING, (WPARAM)-1,
							(LPARAM)activeScreenLabel.c_str());
	}
}

void
CHotkeyOptions::CScreensDialog::updateControls(HWND hwnd)
{
	HWND child     = getItem(hwnd, IDC_HOTKEY_SCREENS_SRC);
	bool canAdd    = (SendMessage(child, LB_GETSELCOUNT, 0, 0) != 0);
	child          = getItem(hwnd, IDC_HOTKEY_SCREENS_DST);
	bool canRemove = (!s_targets.empty() &&
						(SendMessage(child, LB_GETSELCOUNT, 0, 0) != 0));

	enableItem(hwnd, IDC_HOTKEY_SCREENS_ADD, canAdd);
	enableItem(hwnd, IDC_HOTKEY_SCREENS_REMOVE, canRemove);
}

void
CHotkeyOptions::CScreensDialog::add(HWND hwnd)
{
	CScreens selected;
	getSelected(hwnd, IDC_HOTKEY_SCREENS_SRC, s_nonTargets, selected);
	for (CScreens::const_iterator i = selected.begin();
								i != selected.end(); ++i) {
		s_targets.insert(*i);
		s_nonTargets.erase(*i);
	}
	fillScreens(hwnd);
	updateControls(hwnd);
}

void
CHotkeyOptions::CScreensDialog::remove(HWND hwnd)
{
	CScreens selected;
	getSelected(hwnd, IDC_HOTKEY_SCREENS_DST, s_targets, selected);
	for (CScreens::const_iterator i = selected.begin();
								i != selected.end(); ++i) {
		s_nonTargets.insert(*i);
		s_targets.erase(*i);
	}
	fillScreens(hwnd);
	updateControls(hwnd);
}

void
CHotkeyOptions::CScreensDialog::getSelected(HWND hwnd, UINT id,
				const CScreens& inScreens, CScreens& outScreens)
{
	// get the selected item indices
	HWND child = getItem(hwnd, id);
	UInt32 n   = (UInt32)SendMessage(child, LB_GETSELCOUNT, 0, 0);
	int* index = new int[n];
	SendMessage(child, LB_GETSELITEMS, (WPARAM)n, (LPARAM)index);

	// get the items in a vector
	std::vector<CString> tmpList;
	for (CScreens::const_iterator i = inScreens.begin();
								i != inScreens.end(); ++i) {
		tmpList.push_back(*i);
	}

	// get selected items into the output set
	outScreens.clear();
	for (UInt32 i = 0; i < n; ++i) {
		outScreens.insert(tmpList[index[i]]);
	}

	// clean up
	delete[] index;
}

BOOL CALLBACK
CHotkeyOptions::CScreensDialog::dlgProc(HWND hwnd,
				UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message) {
	case WM_INITDIALOG:
		doInit(hwnd);
		return TRUE;

	case WM_COMMAND:
		switch (LOWORD(wParam)) {
		case IDOK:
			doFini(hwnd);
			EndDialog(hwnd, 0);
			return TRUE;

		case IDCANCEL:
			EndDialog(hwnd, 0);
			return TRUE;

		case IDC_HOTKEY_SCREENS_ADD:
			add(hwnd);
			return TRUE;

		case IDC_HOTKEY_SCREENS_REMOVE:
			remove(hwnd);
			return TRUE;

		case IDC_HOTKEY_SCREENS_SRC:
		case IDC_HOTKEY_SCREENS_DST:
			switch (HIWORD(wParam)) {
			case LBN_SELCANCEL:
			case LBN_SELCHANGE:
				updateControls(hwnd);
				return TRUE;
			}
			break;
		}
		break;

	case WM_CTLCOLORLISTBOX:
		if (s_targets.empty() &&
			(HWND)lParam == getItem(hwnd, IDC_HOTKEY_SCREENS_DST)) {
			// override colors
			HDC dc = (HDC)wParam;
			SetTextColor(dc, GetSysColor(COLOR_GRAYTEXT));
			return (BOOL)GetSysColorBrush(COLOR_WINDOW);
		}
		break;

	default:
		break;
	}

	return FALSE;
}
