/*
 *   xmcd - Motif(tm) CD Audio Player
 *
 *   Copyright (C) 1993-2001  Ti Kan
 *   E-mail: xmcd@amb.org
 *
 *   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, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */
#ifndef LINT
static char *_hotkey_c_ident_ = "@(#)hotkey.c	6.38 01/04/17";
#endif

#include "common_d/appenv.h"
#include "common_d/util.h"
#include "xmcd_d/xmcd.h"
#include "xmcd_d/widget.h"
#include "xmcd_d/cdfunc.h"
#include "xmcd_d/hotkey.h"


#define TOTAL_GRABLISTS	2
#define MAIN_LIST	0
#define KEYPAD_LIST	1

#define MAX_MODSTR_LEN	24
#define MAX_KEYSTR_LEN	24
#define MAX_BTNSTR_LEN	48


typedef struct grablist {
	Widget		gr_widget;
	KeyCode		gr_keycode;
	KeySym		gr_keysym;
	Modifiers	gr_modifier;
	char		*gr_modstr;
	char		*gr_keystr;
	char		*gr_acc;
	struct grablist	*next;
} grablist_t;


typedef struct {
	char		*name;
	Modifiers	mask;
} modtab_t;


extern appdata_t	app_data;
extern widgets_t	widgets;
extern FILE		*errfp;

STATIC modtab_t		modtab[] = {
	{ "Shift",	ShiftMask	},
	{ "Lock",	LockMask	},
	{ "Ctrl",	ControlMask	},
	{ "Mod1",	Mod1Mask	},
	{ "Mod2",	Mod2Mask	},
	{ "Mod3",	Mod3Mask	},
	{ "Mod4",	Mod4Mask	},
	{ "Mod5",	Mod5Mask	},
	{ NULL,		0		}
};

STATIC grablist_t	*grablists[TOTAL_GRABLISTS] = {
	NULL, NULL
};


/***********************
 *  internal routines  *
 ***********************/


/*
 * hotkey_override_mnemonic
 *	Apply override mnemonic rules, if any.
 *
 * Args:
 *	g - Pointer to the button's associated grablist structure.
 *
 * Return:
 *	Nothing.
 */
STATIC void
hotkey_override_mnemonic(grablist_t *g)
{
	char		*lstr;
	XmString	xs;

	if (g->gr_widget == NULL) {
		/* No associated button */
		g->gr_keysym = XK_VoidSymbol;
		return;
	}

	XtVaGetValues(g->gr_widget, XmNlabelString, &xs, NULL);

	if (XmStringGetLtoR(xs, XmSTRING_DEFAULT_CHARSET, &lstr)) {
		/* Make the first letter of the button label
		 * match if possible, even if the
		 * capitalization is wrong.
		 */
		if (g->gr_keystr != NULL &&
		    toupper(g->gr_keystr[0]) == toupper(lstr[0]) &&
		    !(g->gr_modifier & ~ShiftMask)) {
			char	s[2];

			s[0] = lstr[0];
			s[1] = '\0';

			g->gr_keysym = XStringToKeysym(s);
			return;
		}
	}
}


/*
 * hotkey_set_mnemonics
 *	Set the mnemonic and accelerator of buttons with hotkey support.
 *
 * Args:
 *	Nothing.
 *
 * Return:
 *	Nothing.
 */
STATIC void
hotkey_set_mnemonics(void)
{
	int		i;
	grablist_t	*p;
	Widget		prev_w = (Widget) NULL;

	/* Set mnemonics and accelerators */
	for (i = 0; i < TOTAL_GRABLISTS; i++) {
		for (p = grablists[i]; p != NULL; p = p->next) {
			if (p->gr_widget != (Widget) NULL) {
				if (prev_w == p->gr_widget)
					continue;

				if (p->gr_keysym != XK_VoidSymbol) {
				    if (isdigit((int) p->gr_keystr[0])) {
					    /* Just set the accelerator */
					    XtVaSetValues(p->gr_widget,
						    XmNaccelerator, p->gr_acc,
						    NULL
					    );
				    }
				    else {
					    /* Set the accelerator and
					     * mnemonic
					     */
					    XtVaSetValues(p->gr_widget,
						    XmNaccelerator, p->gr_acc,
						    XmNmnemonic, p->gr_keysym,
						    NULL
					    );
				    }
				}
			}
			prev_w = p->gr_widget;
		}
	}
}


/*
 * hotkey_parse_xlat_line
 *	A limited translation string parser.
 *
 * Args:
 *	line - The translation text string line to be parsed
 *	modp - The key modifier return string
 *	keyp - The key event return string
 *	widp - The button event return string
 *
 * Return:
 *	Nothing.
 */
STATIC bool_t
hotkey_parse_xlat_line(
	char *line,
	char **modp,
	char **keyp,
	char **widp
)
{
	char		*p,
			*q;
	static char	modstr[MAX_MODSTR_LEN],
			keystr[MAX_KEYSTR_LEN],
			widstr[MAX_BTNSTR_LEN];

	keystr[0] = modstr[0] = widstr[0] = '\0';

	/* Get modifier specification */
	p = line;
	q = strchr(p, '<');

	if (q == NULL)
		return FALSE;
	else if (q > p) {
		*q = '\0';
		(void) strncpy(modstr, p, MAX_MODSTR_LEN-1);
		modstr[MAX_MODSTR_LEN-1] = '\0';
		*modp = modstr;
		*q = '<';
	}

	/* Get event specification */
	p = q + 1;
	q = strchr(p, '>');

	if (q == NULL || q == p)
		return FALSE;
	else {
		*q = '\0';

		/* We are interested only in key events here */
		if (strncmp(p, "Key", 3) != 0)
			return FALSE;

		*q = '>';
	}

	/* Get key specification */
	p = q + 1;
	q = strchr(p, ':');

	if (q == NULL || q == p)
		return FALSE;
	else {
		*q = '\0';
		(void) strncpy(keystr, p, MAX_KEYSTR_LEN-1);
		keystr[MAX_KEYSTR_LEN-1] = '\0';
		*keyp = keystr;
		*q = ':';
	}

	/* Get associated widget name */
	p = q + 1;
	q = strchr(p, '(');

	if (q == NULL || q == p)
		return FALSE;
	else {
		char	sav;

		p = q + 1;
		q = strchr(p, ',');
		if (q == NULL)
			q = strchr(p, ')');

		if (q == NULL || q == p)
			return FALSE;

		sav = *q;
		*q = '\0';
		(void) strncpy(widstr, p, MAX_BTNSTR_LEN-1);
		widstr[MAX_BTNSTR_LEN-1] = '\0';
		*widp = widstr;
		*q = sav;
	}

	return TRUE;
}


/*
 * hotkey_build_grablist
 *	Build a linked list of widgets which have associated
 *	hotkey support, and information about the hotkey.  These
 *	keys will be grabbed when the parent form window has input
 *	focus.
 *
 * Args:
 *	form - The parent form widget
 *	str - The translation string specifying the hotkey
 *	listhead - The list head (return)
 *
 * Return:
 *	Nothing.
 */
STATIC void
hotkey_build_grablist(
	Widget		form,
	char		*str,
	grablist_t	**listhead
)
{
	int		i;
	char		*p,
			*q,
			*end,
			*modstr,
			*keystr,
			*widstr,
			accstr[MAX_MODSTR_LEN + MAX_KEYSTR_LEN + 8];
	grablist_t	*g,
			*prevg;;

	p = str;
	end = p + strlen(p);
	g = prevg = NULL;

	do {
		while (isspace((int) *p))
			p++;
		q = strchr(p, '\n');

		if (p >= end)
			break;

		if (q == NULL) {
			if (q > end)
				break;
		}
		else
			*q = '\0';

		modstr = keystr = widstr = NULL;

		/* Parse translation line */
		if (*p != '#' &&
		    hotkey_parse_xlat_line(p, &modstr, &keystr, &widstr) &&
		    keystr != NULL) {

			/* Allocate new list element */
			g = (grablist_t *)(void *) MEM_ALLOC(
				"grablist_ent", sizeof(grablist_t)
			);
			if (g == NULL) {
				CD_FATAL(app_data.str_nomemory);
				return;
			}
			(void) memset(g, 0, sizeof(grablist_t));

			if (*listhead == NULL)
				*listhead = g;
			else
				prevg->next = g;
			prevg = g;

			if (!util_newstr(&g->gr_modstr, modstr)) {
				CD_FATAL(app_data.str_nomemory);
				return;
			}

			if (!util_newstr(&g->gr_keystr, keystr)) {
				CD_FATAL(app_data.str_nomemory);
				return;
			}

			(void) sprintf(accstr, "%s<Key>%s",
				modstr == NULL ? "" : modstr, keystr);
			if (!util_newstr(&g->gr_acc, accstr)) {
				CD_FATAL(app_data.str_nomemory);
				return;
			}

			g->gr_keysym = XStringToKeysym(keystr);
			g->gr_keycode = XKeysymToKeycode(
				XtDisplay(widgets.toplevel),
				g->gr_keysym
			);

			g->gr_modifier = 0;
			if (modstr != NULL) {
				for (i = 0; modtab[i].name != NULL; i++) {
					if (strcmp(modtab[i].name,
						   modstr) == 0) {
						g->gr_modifier =
							modtab[i].mask;
						break;
					}
				}
			}

			g->gr_widget = (Widget) NULL;
			if (widstr != NULL) {
				g->gr_widget = XtNameToWidget(form, widstr);

				if (g->gr_widget == (Widget) NULL) {
					char	*cp;

					cp = MEM_ALLOC(
					    "widstr", strlen(widstr) + 2
					);
					if (cp == NULL) {
					    CD_FATAL(app_data.str_nomemory);
					    return;
					}

					(void) sprintf(cp, "*%s", widstr);
					g->gr_widget = XtNameToWidget(
						form, cp
					);

					MEM_FREE(cp);
				}
			}

			/* Special mnemonic handling */
			hotkey_override_mnemonic(g);
		}

		if (q != NULL) {
			*q = '\n';
			p = q + 1;
		}
		else
			p = end;

	} while (p < end);
}


/***********************
 *   public routines   *
 ***********************/


/*
 * hotkey_init
 *	Top level setup function for the hotkey subsystem.  Called
 *	once at program startup.
 *
 * Args:
 *	None.
 *
 * Return:
 *	Nothing.
 */
void
hotkey_init(void)
{
	/* Set up hot keys for main window */
	if (app_data.main_hotkeys != NULL &&
	    app_data.main_hotkeys[0] != '\0') {
		XtOverrideTranslations(
			widgets.main.form,
			XtParseTranslationTable(app_data.main_hotkeys)
		);

		hotkey_build_grablist(
			widgets.main.form,
			app_data.main_hotkeys,
			&grablists[MAIN_LIST]
		);
	}

	/* Set up hot keys for keypad window */
	if (app_data.keypad_hotkeys != NULL &&
	    app_data.keypad_hotkeys[0] != '\0') {
		XtOverrideTranslations(
			widgets.keypad.form,
			XtParseTranslationTable(app_data.keypad_hotkeys)
		);

		hotkey_build_grablist(
			widgets.keypad.form,
			app_data.keypad_hotkeys,
			&grablists[KEYPAD_LIST]
		);
	}

	/* Set mnemonics on hotkey button faces */
	hotkey_set_mnemonics();
}


/*
 * hotkey_grabkeys
 *	Grab all keys used as hotkeys in the specified window form.
 *
 * Args:
 *	form - The parent form widget.
 *
 * Return:
 *	Nothing.
 */
void
hotkey_grabkeys(Widget form)
{
	grablist_t	*list,
			*p;

	if (form == (Widget) NULL)
		return;

	if (form == widgets.main.form)
		list = grablists[MAIN_LIST];
	else if (form == widgets.keypad.form)
		list = grablists[KEYPAD_LIST];
	else
		list = NULL;

	for (p = list; p != NULL; p = p->next) {
		XtGrabKey(
			form,
			p->gr_keycode,
			p->gr_modifier,
			True,
			GrabModeAsync,
			GrabModeAsync
		);
	}
}


/*
 * hotkey_ungrabkeys
 *	Ungrab all keys used as hotkeys in the specified window form.
 *
 * Args:
 *	form - The parent form widget.
 *
 * Return:
 *	Nothing.
 */
void
hotkey_ungrabkeys(Widget form)
{
	grablist_t	*list,
			*p;

	if (form == (Widget) NULL)
		return;

	if (form == widgets.main.form)
		list = grablists[MAIN_LIST];
	else if (form == widgets.keypad.form)
		list = grablists[KEYPAD_LIST];
	else
		list = NULL;

	for (p = list; p != NULL; p = p->next)
		XtUngrabKey(form, p->gr_keycode, p->gr_modifier);
}


/*
 * hotkey_tooltip_mnemonic
 *	Set the tooltip mnemonic for the specified pushbutton
 *
 * Args:
 *	w - The widget for which the tooltip is being displayed
 *
 * Return:
 *	Nothing.
 */
void
hotkey_tooltip_mnemonic(Widget w)
{
	grablist_t	*p;

	for (p = grablists[MAIN_LIST]; p != NULL; p = p->next) {
		if (p->gr_widget == w) {
			XtVaSetValues(widgets.tooltip.tooltip_lbl,
				XmNmnemonic, p->gr_keysym,
				NULL
			);
			return;
		}
	}

	/* If not found, simply unset the mnemonic */
	XtVaSetValues(widgets.tooltip.tooltip_lbl,
		XmNmnemonic, XK_VoidSymbol,
		NULL
	);
}


/*
 * hotkey_ckkey
 *	Given a modifier/key pair, check to see if they are unused
 *	in the main window hot key system.
 *
 * Args:
 *	modstr - The modifier string (or NULL if not applicable)
 *	keystr - The key string (must not be NULL)
 *
 * Return:
 *	FALSE - The modifier/key pair is used in main window hotkeys
 *	TRUE - The modifier/key pair is unused in main window hotkeys
 */
bool_t
hotkey_ckkey(char *modstr, char *keystr)
{
	grablist_t	*p;
	int		i;

	if (keystr == NULL)
		return FALSE;

	for (i = 0; i < TOTAL_GRABLISTS; i++) {
		for (p = grablists[i]; p != NULL; p = p->next) {
			if (p->gr_keystr != NULL &&
			    util_strcasecmp(p->gr_keystr, keystr) == 0) {
				if (p->gr_modstr == NULL &&
				    modstr == NULL)
					return FALSE;

				if (p->gr_modstr != NULL &&
				    modstr != NULL &&
				    strcmp(p->gr_modstr, modstr) == 0)
					return FALSE;
			}
		}
	}

	return TRUE;
}


/*
 * hotkey
 *	Widget action routine to handle hotkey events
 */
void
hotkey(Widget w, XEvent *ev, String *args, Cardinal *num_args)
{
	int		i;
	Widget		aw;
	grablist_t	*p;
	bool_t		doit;

	if ((int) *num_args <= 0)
		return;	/* Error: should have at least one arg */

	if (ev->xany.type != KeyPress)
		return;	/* Not a keypress event */

	if ((aw = XtNameToWidget(w, args[0])) == (Widget) NULL) {
		char	*cp;

		cp = MEM_ALLOC("widstr", strlen(args[0]) + 2);
		if (cp == NULL) {
			CD_FATAL(app_data.str_nomemory);
			return;
		}

		(void) sprintf(cp, "*%s", args[0]);
		aw = XtNameToWidget(w, cp);

		MEM_FREE(cp);

		if (aw == (Widget) NULL)
			return;	/* Can't find widget */
	}

	/* Find the associated grablist entry */
	doit = FALSE;
	for (i = 0; i < TOTAL_GRABLISTS; i++) {
		for (p = grablists[i]; p != NULL; p = p->next) {
			if (p->gr_widget != aw)
				continue;

			/* Check to see if we're called as a result of
			 * the user invoking the correct modifier and key.
			 */

			if ((int) p->gr_keycode == (int) ev->xkey.keycode &&
			    (int) p->gr_modifier == (int) ev->xkey.state) {
				doit = TRUE;
				break;
			}
		}
	}

	if (!doit)
		return;

	/* Switch keyboard focus to the widget of interest */
	XmProcessTraversal(aw, XmTRAVERSE_CURRENT);

	for (i = 1; i < (int) *num_args; i++) {
		/* Invoke the named action of the specified widget */
		XtCallActionProc(aw, args[i], ev, NULL, 0);
	}
}


