/* KeyboardShortcuts.c- keyboard shortcut bindings
 *
 *  WPrefs - Window Maker Preferences Program
 *
 *  Copyright (c) 1998-2003 Alfredo K. Kojima
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 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 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 <https://www.gnu.org/licenses/>.
 */

#include "config.h"		/* for HAVE_XCONVERTCASE */

#include "WPrefs.h"
#include <ctype.h>
#include <sys/select.h>
#include <sys/time.h>

#include <X11/keysym.h>
#include <X11/XKBlib.h>

typedef struct _Panel {
	WMBox *box;

	char *sectionName;

	char *description;

	CallbackRec callbacks;

	WMWidget *parent;

	WMLabel *actL;
	WMList *actLs;

	WMFrame *shoF;
	WMTextField *shoT;
	WMButton *cleB;
	WMButton *defB;

	WMLabel *instructionsL;

	WMColor *white;
	WMColor *black;
	WMColor *gray;
	WMFont *font;

	Bool capturing;
	char **shortcuts;
	int actionCount;
} _Panel;

#define ICON_FILE	"keyshortcuts"

/*
 * List of user definable shortcut keys
 * First parameter is the internal keyword known by WMaker
 * Second is the text displayed to the user
 */
static struct keyOption {
	const char *key;
	const char *title;
} keyOptions[] = {
	{ "RootMenuKey",    N_("Open applications menu") },
	{ "WindowListKey",  N_("Open window list menu") },
	{ "WindowMenuKey",  N_("Open window commands menu") },
	{ "HideKey",        N_("Hide active application") },
	{ "HideOthersKey",  N_("Hide other applications") },
	{ "MiniaturizeKey", N_("Miniaturize active window") },
	{ "MinimizeAllKey", N_("Miniaturize all windows") },
	{ "CloseKey",       N_("Close active window") },
	{ "MaximizeKey",    N_("Maximize active window") },
	{ "VMaximizeKey",   N_("Maximize active window vertically") },
	{ "HMaximizeKey",   N_("Maximize active window horizontally") },
	{ "LHMaximizeKey",  N_("Maximize active window left half") },
	{ "RHMaximizeKey",  N_("Maximize active window right half") },
	{ "THMaximizeKey",  N_("Maximize active window top half") },
	{ "BHMaximizeKey",  N_("Maximize active window bottom half") },
	{ "TLCMaximizeKey", N_("Maximize active window top left corner") },
	{ "TRCMaximizeKey", N_("Maximize active window top right corner") },
	{ "BLCMaximizeKey", N_("Maximize active window bottom left corner") },
	{ "BRCMaximizeKey", N_("Maximize active window bottom right corner") },
	{ "MaximusKey",     N_("Tile active window") },
	{ "CenterKey",      N_("Center active window") },
	{ "KeepOnTopKey",   N_("Toggle window on top status") },
	{ "KeepAtBottomKey",N_("Toggle window at bottom status") },
	{ "OmnipresentKey", N_("Toggle window omnipresent status") },
	{ "RaiseKey",       N_("Raise active window") },
	{ "LowerKey",       N_("Lower active window") },
	{ "RaiseLowerKey",  N_("Raise/Lower window under mouse pointer") },
	{ "ShadeKey",       N_("Shade active window") },
	{ "MoveResizeKey",  N_("Move/Resize active window") },
	{ "SelectKey",      N_("Select active window") },
	{ "FocusNextKey",   N_("Focus next window") },
	{ "FocusPrevKey",   N_("Focus previous window") },
	/* Directional window focus */
	{ "FocusWindowLeftKey",  N_("Focus the window to the left") },
	{ "FocusWindowRightKey", N_("Focus the window to the right") },
	{ "FocusWindowUpKey",    N_("Focus the window above") },
	{ "FocusWindowDownKey",  N_("Focus the window below") },
	{ "GroupNextKey",   N_("Focus next group window") },
	{ "GroupPrevKey",   N_("Focus previous group window") },

	/* Vim-like Window Marking */
	{ "MarkSetKey",   N_("Mark window: set mark") },
	{ "MarkUnsetKey", N_("Mark window: unset mark") },
	{ "MarkBringKey", N_("Mark window: bring marked window here") },
	{ "MarkJumpKey",  N_("Mark window: jump to marked window") },
	{ "MarkSwapKey",  N_("Mark window: swap with marked window") },

	/* Workspace Related */
	{ "WorkspaceMapKey",  N_("Open workspace pager") },
	{ "NextWorkspaceKey", N_("Switch to next workspace") },
	{ "PrevWorkspaceKey", N_("Switch to previous workspace") },
	{ "LastWorkspaceKey", N_("Switch to last used workspace") },
	{ "NextWorkspaceLayerKey", N_("Switch to next ten workspaces") },
	{ "PrevWorkspaceLayerKey", N_("Switch to previous ten workspaces") },
	{ "Workspace1Key",  N_("Switch to workspace 1") },
	{ "Workspace2Key",  N_("Switch to workspace 2") },
	{ "Workspace3Key",  N_("Switch to workspace 3") },
	{ "Workspace4Key",  N_("Switch to workspace 4") },
	{ "Workspace5Key",  N_("Switch to workspace 5") },
	{ "Workspace6Key",  N_("Switch to workspace 6") },
	{ "Workspace7Key",  N_("Switch to workspace 7") },
	{ "Workspace8Key",  N_("Switch to workspace 8") },
	{ "Workspace9Key",  N_("Switch to workspace 9") },
	{ "Workspace10Key", N_("Switch to workspace 10") },
	{ "MoveToNextWorkspaceKey",      N_("Move window to next workspace") },
	{ "MoveToPrevWorkspaceKey",      N_("Move window to previous workspace") },
	{ "MoveToLastWorkspaceKey",      N_("Move window to last used workspace") },
	{ "MoveToNextWorkspaceLayerKey", N_("Move window to next ten workspaces") },
	{ "MoveToPrevWorkspaceLayerKey", N_("Move window to previous ten workspaces") },
	{ "MoveToWorkspace1Key",  N_("Move window to workspace 1") },
	{ "MoveToWorkspace2Key",  N_("Move window to workspace 2") },
	{ "MoveToWorkspace3Key",  N_("Move window to workspace 3") },
	{ "MoveToWorkspace4Key",  N_("Move window to workspace 4") },
	{ "MoveToWorkspace5Key",  N_("Move window to workspace 5") },
	{ "MoveToWorkspace6Key",  N_("Move window to workspace 6") },
	{ "MoveToWorkspace7Key",  N_("Move window to workspace 7") },
	{ "MoveToWorkspace8Key",  N_("Move window to workspace 8") },
	{ "MoveToWorkspace9Key",  N_("Move window to workspace 9") },
	{ "MoveToWorkspace10Key", N_("Move window to workspace 10") },

	/* Window Selection */
	{ "WindowShortcut1Key",  N_("Shortcut for window 1") },
	{ "WindowShortcut2Key",  N_("Shortcut for window 2") },
	{ "WindowShortcut3Key",  N_("Shortcut for window 3") },
	{ "WindowShortcut4Key",  N_("Shortcut for window 4") },
	{ "WindowShortcut5Key",  N_("Shortcut for window 5") },
	{ "WindowShortcut6Key",  N_("Shortcut for window 6") },
	{ "WindowShortcut7Key",  N_("Shortcut for window 7") },
	{ "WindowShortcut8Key",  N_("Shortcut for window 8") },
	{ "WindowShortcut9Key",  N_("Shortcut for window 9") },
	{ "WindowShortcut10Key", N_("Shortcut for window 10") },

	/* Head Selection */
	{ "MoveTo12to6Head",     N_("Move to right/bottom/left/top head") },
	{ "MoveTo6to12Head",    N_("Move to left/top/right/bottom head") },

	/* Misc. */
	{ "WindowRelaunchKey", N_("Launch new instance of application") },
	{ "ScreenSwitchKey",   N_("Switch to next screen/monitor") },
	{ "RunKey",            N_("Run application") },
	{ "ExitKey",            N_("Exit Window Maker") },
	{ "DockRaiseLowerKey", N_("Raise/Lower Dock") },
	{ "ClipRaiseLowerKey", N_("Raise/Lower Clip") },
	{ "ScreenCaptureKey", N_("Capture the entire screen") },
	{ "WindowCaptureKey", N_("Capture a window") },
	{ "PartialCaptureKey", N_("Capture a portion of the screen") }
#ifdef XKB_MODELOCK
	,{ "ToggleKbdModeKey", N_("Toggle keyboard language") }
#endif				/* XKB_MODELOCK */
};

#ifndef HAVE_XCONVERTCASE
/* from Xlib */

static void XConvertCase(register KeySym sym, KeySym * lower, KeySym * upper)
{
	*lower = sym;
	*upper = sym;
	switch (sym >> 8) {
	case 0:		/* Latin 1 */
		if ((sym >= XK_A) && (sym <= XK_Z))
			*lower += (XK_a - XK_A);
		else if ((sym >= XK_a) && (sym <= XK_z))
			*upper -= (XK_a - XK_A);
		else if ((sym >= XK_Agrave) && (sym <= XK_Odiaeresis))
			*lower += (XK_agrave - XK_Agrave);
		else if ((sym >= XK_agrave) && (sym <= XK_odiaeresis))
			*upper -= (XK_agrave - XK_Agrave);
		else if ((sym >= XK_Ooblique) && (sym <= XK_Thorn))
			*lower += (XK_oslash - XK_Ooblique);
		else if ((sym >= XK_oslash) && (sym <= XK_thorn))
			*upper -= (XK_oslash - XK_Ooblique);
		break;
	case 1:		/* Latin 2 */
		/* Assume the KeySym is a legal value (ignore discontinuities) */
		if (sym == XK_Aogonek)
			*lower = XK_aogonek;
		else if (sym >= XK_Lstroke && sym <= XK_Sacute)
			*lower += (XK_lstroke - XK_Lstroke);
		else if (sym >= XK_Scaron && sym <= XK_Zacute)
			*lower += (XK_scaron - XK_Scaron);
		else if (sym >= XK_Zcaron && sym <= XK_Zabovedot)
			*lower += (XK_zcaron - XK_Zcaron);
		else if (sym == XK_aogonek)
			*upper = XK_Aogonek;
		else if (sym >= XK_lstroke && sym <= XK_sacute)
			*upper -= (XK_lstroke - XK_Lstroke);
		else if (sym >= XK_scaron && sym <= XK_zacute)
			*upper -= (XK_scaron - XK_Scaron);
		else if (sym >= XK_zcaron && sym <= XK_zabovedot)
			*upper -= (XK_zcaron - XK_Zcaron);
		else if (sym >= XK_Racute && sym <= XK_Tcedilla)
			*lower += (XK_racute - XK_Racute);
		else if (sym >= XK_racute && sym <= XK_tcedilla)
			*upper -= (XK_racute - XK_Racute);
		break;
	case 2:		/* Latin 3 */
		/* Assume the KeySym is a legal value (ignore discontinuities) */
		if (sym >= XK_Hstroke && sym <= XK_Hcircumflex)
			*lower += (XK_hstroke - XK_Hstroke);
		else if (sym >= XK_Gbreve && sym <= XK_Jcircumflex)
			*lower += (XK_gbreve - XK_Gbreve);
		else if (sym >= XK_hstroke && sym <= XK_hcircumflex)
			*upper -= (XK_hstroke - XK_Hstroke);
		else if (sym >= XK_gbreve && sym <= XK_jcircumflex)
			*upper -= (XK_gbreve - XK_Gbreve);
		else if (sym >= XK_Cabovedot && sym <= XK_Scircumflex)
			*lower += (XK_cabovedot - XK_Cabovedot);
		else if (sym >= XK_cabovedot && sym <= XK_scircumflex)
			*upper -= (XK_cabovedot - XK_Cabovedot);
		break;
	case 3:		/* Latin 4 */
		/* Assume the KeySym is a legal value (ignore discontinuities) */
		if (sym >= XK_Rcedilla && sym <= XK_Tslash)
			*lower += (XK_rcedilla - XK_Rcedilla);
		else if (sym >= XK_rcedilla && sym <= XK_tslash)
			*upper -= (XK_rcedilla - XK_Rcedilla);
		else if (sym == XK_ENG)
			*lower = XK_eng;
		else if (sym == XK_eng)
			*upper = XK_ENG;
		else if (sym >= XK_Amacron && sym <= XK_Umacron)
			*lower += (XK_amacron - XK_Amacron);
		else if (sym >= XK_amacron && sym <= XK_umacron)
			*upper -= (XK_amacron - XK_Amacron);
		break;
	case 6:		/* Cyrillic */
		/* Assume the KeySym is a legal value (ignore discontinuities) */
		if (sym >= XK_Serbian_DJE && sym <= XK_Serbian_DZE)
			*lower -= (XK_Serbian_DJE - XK_Serbian_dje);
		else if (sym >= XK_Serbian_dje && sym <= XK_Serbian_dze)
			*upper += (XK_Serbian_DJE - XK_Serbian_dje);
		else if (sym >= XK_Cyrillic_YU && sym <= XK_Cyrillic_HARDSIGN)
			*lower -= (XK_Cyrillic_YU - XK_Cyrillic_yu);
		else if (sym >= XK_Cyrillic_yu && sym <= XK_Cyrillic_hardsign)
			*upper += (XK_Cyrillic_YU - XK_Cyrillic_yu);
		break;
	case 7:		/* Greek */
		/* Assume the KeySym is a legal value (ignore discontinuities) */
		if (sym >= XK_Greek_ALPHAaccent && sym <= XK_Greek_OMEGAaccent)
			*lower += (XK_Greek_alphaaccent - XK_Greek_ALPHAaccent);
		else if (sym >= XK_Greek_alphaaccent && sym <= XK_Greek_omegaaccent &&
			 sym != XK_Greek_iotaaccentdieresis && sym != XK_Greek_upsilonaccentdieresis)
			*upper -= (XK_Greek_alphaaccent - XK_Greek_ALPHAaccent);
		else if (sym >= XK_Greek_ALPHA && sym <= XK_Greek_OMEGA)
			*lower += (XK_Greek_alpha - XK_Greek_ALPHA);
		else if (sym >= XK_Greek_alpha && sym <= XK_Greek_omega && sym != XK_Greek_finalsmallsigma)
			*upper -= (XK_Greek_alpha - XK_Greek_ALPHA);
		break;
	case 0x14:		/* Armenian */
		if (sym >= XK_Armenian_AYB && sym <= XK_Armenian_fe) {
			*lower = sym | 1;
			*upper = sym & ~1;
		}
		break;
	}
}
#endif

static int NumLockMask(Display *dpy)
{
	int i, mask;
	XModifierKeymap *map;
	static int mask_table[8] = {
		ShiftMask, LockMask, ControlMask, Mod1Mask,
		Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask
	};
	KeyCode numlock_keycode = XKeysymToKeycode(dpy, XK_Num_Lock);

	if (numlock_keycode == NoSymbol)
		return 0;

	map = XGetModifierMapping(dpy);
	if (!map)
		return 0;

	mask = 0;
	for (i = 0; i < 8 * map->max_keypermod; i++) {
		if (map->modifiermap[i] == numlock_keycode && mask == 0) {
			mask = mask_table[i/map->max_keypermod];
			break;
		}
	}

	if (map)
		XFreeModifiermap(map);

	return mask;
}

/* Append the modifier prefix and key name to the keybuf */
static void build_key_combo(unsigned int xkstate, const char *keyname,
                             unsigned int numlock_mask, char keybuf[64])
{
	if (xkstate & ControlMask)
		strcat(keybuf, "Control+");
	if (xkstate & ShiftMask)
		strcat(keybuf, "Shift+");
	if ((numlock_mask != Mod1Mask) && (xkstate & Mod1Mask))
		strcat(keybuf, "Mod1+");
	if ((numlock_mask != Mod2Mask) && (xkstate & Mod2Mask))
		strcat(keybuf, "Mod2+");
	if ((numlock_mask != Mod3Mask) && (xkstate & Mod3Mask))
		strcat(keybuf, "Mod3+");
	if ((numlock_mask != Mod4Mask) && (xkstate & Mod4Mask))
		strcat(keybuf, "Mod4+");
	if ((numlock_mask != Mod5Mask) && (xkstate & Mod5Mask))
		strcat(keybuf, "Mod5+");
	wstrlcat(keybuf, keyname, 64);
}

/*
 * Interactively capture a key shortcut or keychain,
 * function waits KeychainTimeoutDelay or 300 ms after
 * each key press for another key in the chain,
 * and returns the full key specification string.
 */
char *capture_shortcut(Display *dpy, Bool *capturing, Bool convert_case)
{
	XEvent ev;
	KeySym ksym, lksym, uksym;
	/* Large enough for several chained chords */
	char buffer[512];
	char keybuf[64];
	char *key;
	unsigned int numlock_mask;
	Bool have_key = False;
	Bool chain_mode;
	int timeout_ms;

	timeout_ms = GetIntegerForKey("KeychainTimeoutDelay");
	if (timeout_ms <= 0)
		timeout_ms = 300;

	buffer[0] = '\0';

	/* ---- Phase 1: capture the first key (blocking) ---- */
	while (*capturing) {
		XAllowEvents(dpy, AsyncKeyboard, CurrentTime);
		WMNextEvent(dpy, &ev);
		if (ev.type == KeyPress && ev.xkey.keycode != 0) {
			numlock_mask = NumLockMask(dpy);

			/* conditional mask check to get numeric keypad keys */
			ksym = W_KeycodeToKeysym(dpy, ev.xkey.keycode, ev.xkey.state & numlock_mask?1:0);

			if (!IsModifierKey(ksym)) {
				if (convert_case) {
					XConvertCase(ksym, &lksym, &uksym);
					key = XKeysymToString(uksym);
				} else {
					key = XKeysymToString(ksym);
				}

				keybuf[0] = '\0';
				build_key_combo(ev.xkey.state, key, numlock_mask, keybuf);
				wstrlcat(buffer, keybuf, sizeof(buffer));
				have_key = True;
				break;
			}
		}
		WMHandleEvent(&ev);
	}

	/* ---- Phase 2: collect additional chain keys with timeout ---- */
	chain_mode = (timeout_ms > 0);
	while (*capturing && chain_mode) {
		int xfd = ConnectionNumber(dpy);
		fd_set rfds;
		struct timeval tv;

		if (!XPending(dpy)) {
			FD_ZERO(&rfds);
			FD_SET(xfd, &rfds);
			tv.tv_sec  = timeout_ms / 1000;
			tv.tv_usec = (timeout_ms % 1000) * 1000;
			XFlush(dpy);
			if (select(xfd + 1, &rfds, NULL, NULL, &tv) == 0)
				break;  /* timeout: the chain is complete */
		}

		XAllowEvents(dpy, AsyncKeyboard, CurrentTime);
		WMNextEvent(dpy, &ev);
		if (ev.type == KeyPress && ev.xkey.keycode != 0) {
			numlock_mask = NumLockMask(dpy);
			ksym = W_KeycodeToKeysym(dpy, ev.xkey.keycode,
			                         ev.xkey.state & numlock_mask ? 1 : 0);

			if (!IsModifierKey(ksym)) {
				if (convert_case) {
					XConvertCase(ksym, &lksym, &uksym);
					key = XKeysymToString(uksym);
				} else {
					key = XKeysymToString(ksym);
				}

				keybuf[0] = '\0';
				build_key_combo(ev.xkey.state, key, numlock_mask, keybuf);
				wstrlcat(buffer, " ", sizeof(buffer));
				wstrlcat(buffer, keybuf, sizeof(buffer));
			}
		} else {
			WMHandleEvent(&ev);
		}
	}

	if (!have_key || !*capturing)
		return NULL;

	*capturing = 0;
	return wstrdup(buffer);
}

/*
 * check if the keystr entered is already set to another action
 * if found it returns the position in the keyOptions
 */
static int isKeySet(_Panel *panel, char *keystr)
{
	int i;
	char *str;

	for (i = 0; i < panel->actionCount; i++) {
		str = NULL;
		if (panel->shortcuts[i]) {
			str = wtrimspace(panel->shortcuts[i]);
			if (strlen(str) == 0) {
				wfree(str);
				str = NULL;
			}
		}
		if (str) {
			if (strcmp(keystr, str) == 0) {
				wfree(str);
				return i;
			}
			wfree(str);
		}
	}

	return -1;
}

static void captureClick(WMWidget * w, void *data)
{
	_Panel *panel = (_Panel *) data;
	Display *dpy = WMScreenDisplay(WMWidgetScreen(panel->parent));
	char *shortcut;

	if (!panel->capturing) {
		panel->capturing = 1;
		WMSetButtonText(w, _("Cancel"));
		WMSetLabelText(panel->instructionsL,
			       _("Press the desired shortcut key(s) or click Cancel to stop capturing."));
		XGrabKeyboard(dpy, WMWidgetXID(panel->parent), True, GrabModeAsync, GrabModeAsync, CurrentTime);
		shortcut = capture_shortcut(dpy, &panel->capturing, 1);
		if (shortcut) {
			int key_idx = -1;
			int row = WMGetListSelectedItemRow(panel->actLs);

			key_idx = isKeySet(panel, shortcut);
			if (key_idx >= 0 && (key_idx != row)) {
				char *msg;

				msg = wstrconcat(_("Key shortcut already in use by the "), _(keyOptions[key_idx].title));
				WMRunAlertPanel(WMWidgetScreen(w), GetWindow(),
						_("Error"),
						msg,
						_("OK"), NULL, NULL);
				wfree(msg);
				wfree(shortcut);
			} else {
				WMSetTextFieldText(panel->shoT, shortcut);
				if (row >= 0) {
					if (panel->shortcuts[row])
						wfree(panel->shortcuts[row]);
					panel->shortcuts[row] = shortcut;

					WMRedisplayWidget(panel->actLs);
				} else {
					wfree(shortcut);
				}
			}
		}
	}
	panel->capturing = 0;
	WMSetButtonText(w, _("Capture"));
	WMSetLabelText(panel->instructionsL, _("Click on Capture to interactively define the shortcut key(s)."));
	XUngrabKeyboard(dpy, CurrentTime);
}

static void clearShortcut(WMWidget * w, void *data)
{
	_Panel *panel = (_Panel *) data;
	int row = WMGetListSelectedItemRow(panel->actLs);

	/* Parameter not used, but tell the compiler that it is ok */
	(void) w;

	/* Cancel any ongoing capture so the keychain loop is unblocked */
	panel->capturing = 0;

	WMSetTextFieldText(panel->shoT, NULL);

	if (row >= 0) {
		if (panel->shortcuts[row])
			wfree(panel->shortcuts[row]);
		panel->shortcuts[row] = NULL;
		WMRedisplayWidget(panel->actLs);
	}
}

static void typedKeys(void *observerData, WMNotification * notification)
{
	_Panel *panel = (_Panel *) observerData;
	int row = WMGetListSelectedItemRow(panel->actLs);

	/* Parameter not used, but tell the compiler that it is ok */
	(void) notification;

	if (row < 0)
		return;

	if (panel->shortcuts[row])
		wfree(panel->shortcuts[row]);
	panel->shortcuts[row] = WMGetTextFieldText(panel->shoT);
	if (strlen(panel->shortcuts[row]) == 0) {
		wfree(panel->shortcuts[row]);
		panel->shortcuts[row] = NULL;
	}
	WMRedisplayWidget(panel->actLs);
}

static void listClick(WMWidget * w, void *data)
{
	_Panel *panel = (_Panel *) data;
	int row = WMGetListSelectedItemRow(w);

	WMSetTextFieldText(panel->shoT, panel->shortcuts[row]);
}

static void showData(_Panel * panel)
{
	char *str;
	int i;

	for (i = 0; i < panel->actionCount; i++) {

		str = GetStringForKey(keyOptions[i].key);
		if (panel->shortcuts[i])
			wfree(panel->shortcuts[i]);
		if (str)
			panel->shortcuts[i] = wtrimspace(str);
		else
			panel->shortcuts[i] = NULL;

		if (panel->shortcuts[i] &&
		    (strcasecmp(panel->shortcuts[i], "none") == 0 || strlen(panel->shortcuts[i]) == 0)) {
			wfree(panel->shortcuts[i]);
			panel->shortcuts[i] = NULL;
		}
	}
	WMRedisplayWidget(panel->actLs);
}

static void paintItem(WMList * lPtr, int index, Drawable d, char *text, int state, WMRect * rect)
{
	int width, height, x, y;
	_Panel *panel = (_Panel *) WMGetHangedData(lPtr);
	WMScreen *scr = WMWidgetScreen(lPtr);
	Display *dpy = WMScreenDisplay(scr);
	WMColor *backColor = (state & WLDSSelected) ? panel->white : panel->gray;

	width = rect->size.width;
	height = rect->size.height;
	x = rect->pos.x;
	y = rect->pos.y;

	XFillRectangle(dpy, d, WMColorGC(backColor), x, y, width, height);

	if (panel->shortcuts[index]) {
		WMPixmap *pix = WMGetSystemPixmap(scr, WSICheckMark);
		WMSize size = WMGetPixmapSize(pix);

		WMDrawPixmap(pix, d, x + (20 - size.width) / 2, (height - size.height) / 2 + y);
		WMReleasePixmap(pix);
	}

	WMDrawString(scr, d, panel->black, panel->font, x + 20, y, text, strlen(text));
}

static int cmpKeyOptions(const void *v1, const void *v2)
{
	int rc;
	const struct keyOption *opt1 = (struct keyOption *)v1;
	const struct keyOption *opt2 = (struct keyOption *)v2;

	if ((rc = strncmp(opt1->title, opt2->title, 20)) < 0)
		return -1;
	else if (rc > 0)
		return 1;
	return 0;
}


static void createPanel(Panel * p)
{
	_Panel *panel = (_Panel *) p;
	WMScreen *scr = WMWidgetScreen(panel->parent);
	WMColor *color;
	WMFont *boldFont;
	int i;

	panel->capturing = 0;

	panel->white = WMWhiteColor(scr);
	panel->black = WMBlackColor(scr);
	panel->gray = WMGrayColor(scr);
	panel->font = WMSystemFontOfSize(scr, 12);

	panel->box = WMCreateBox(panel->parent);
	WMSetViewExpandsToParent(WMWidgetView(panel->box), 2, 2, 2, 2);

	boldFont = WMBoldSystemFontOfSize(scr, 12);

	/* **************** Actions **************** */
	panel->actL = WMCreateLabel(panel->box);
	WMResizeWidget(panel->actL, 314, 20);
	WMMoveWidget(panel->actL, 9, 9);
	WMSetLabelFont(panel->actL, boldFont);
	WMSetLabelText(panel->actL, _("Actions"));
	WMSetLabelRelief(panel->actL, WRSunken);
	WMSetLabelTextAlignment(panel->actL, WACenter);
	color = WMDarkGrayColor(scr);
	WMSetWidgetBackgroundColor(panel->actL, color);
	WMReleaseColor(color);
	WMSetLabelTextColor(panel->actL, panel->white);

	panel->actLs = WMCreateList(panel->box);
	WMResizeWidget(panel->actLs, 314, 191);
	WMMoveWidget(panel->actLs, 9, 31);
	WMSetListUserDrawProc(panel->actLs, paintItem);
	WMHangData(panel->actLs, panel);

	qsort(keyOptions, wlengthof(keyOptions), sizeof(keyOptions[0]), cmpKeyOptions);
	for (i = 0; i < wlengthof(keyOptions); i++) {
		WMAddListItem(panel->actLs, _(keyOptions[i].title));
	}
	WMSetListAction(panel->actLs, listClick, panel);

	panel->actionCount = WMGetListNumberOfRows(panel->actLs);
	panel->shortcuts = wmalloc(sizeof(char *) * panel->actionCount);

    /***************** Shortcut ****************/

	panel->shoF = WMCreateFrame(panel->box);
	WMResizeWidget(panel->shoF, 178, 214);
	WMMoveWidget(panel->shoF, 333, 8);
	WMSetFrameTitle(panel->shoF, _("Shortcut"));

	panel->shoT = WMCreateTextField(panel->shoF);
	WMResizeWidget(panel->shoT, 160, 20);
	WMMoveWidget(panel->shoT, 9, 65);
	WMAddNotificationObserver(typedKeys, panel, WMTextDidChangeNotification, panel->shoT);

	panel->cleB = WMCreateCommandButton(panel->shoF);
	WMResizeWidget(panel->cleB, 75, 24);
	WMMoveWidget(panel->cleB, 9, 95);
	WMSetButtonText(panel->cleB, _("Clear"));
	WMSetButtonAction(panel->cleB, clearShortcut, panel);

	panel->defB = WMCreateCommandButton(panel->shoF);
	WMResizeWidget(panel->defB, 75, 24);
	WMMoveWidget(panel->defB, 94, 95);
	WMSetButtonText(panel->defB, _("Capture"));
	WMSetButtonAction(panel->defB, captureClick, panel);

	panel->instructionsL = WMCreateLabel(panel->shoF);
	WMResizeWidget(panel->instructionsL, 160, 55);
	WMMoveWidget(panel->instructionsL, 9, 140);
	WMSetLabelTextAlignment(panel->instructionsL, WACenter);
	WMSetLabelWraps(panel->instructionsL, True);
	WMSetLabelText(panel->instructionsL, _("Click on Capture to interactively define the shortcut key."));

	WMMapSubwidgets(panel->shoF);

	WMReleaseFont(boldFont);

	WMRealizeWidget(panel->box);
	WMMapSubwidgets(panel->box);

	showData(panel);
}

static void storeData(_Panel * panel)
{
	int i;
	char *str;

	for (i = 0; i < panel->actionCount; i++) {
		str = NULL;
		if (panel->shortcuts[i]) {
			str = wtrimspace(panel->shortcuts[i]);
			if (strlen(str) == 0) {
				wfree(str);
				str = NULL;
			}
		}
		if (str) {
			SetStringForKey(str, keyOptions[i].key);
			wfree(str);
		} else {
			SetStringForKey("None", keyOptions[i].key);
		}
	}
}

Panel *InitKeyboardShortcuts(WMWidget *parent)
{
	_Panel *panel;

	panel = wmalloc(sizeof(_Panel));

	panel->sectionName = _("Keyboard Shortcut Preferences");

	panel->description = _("Change the keyboard shortcuts for actions such\n"
			       "as changing workspaces and opening menus.");

	panel->parent = parent;

	panel->callbacks.createWidgets = createPanel;
	panel->callbacks.updateDomain = storeData;

	AddSection(panel, ICON_FILE);

	return panel;
}
