/*
 * Copyright 1992 Eric R. Smith. All rights reserved.
 * Redistribution is permitted only if the distribution
 * is not for profit, and only if all documentation
 * (including, in particular, the file "copying")
 * is included in the distribution in unmodified form.
 * THIS PROGRAM COMES WITH ABSOLUTELY NO WARRANTY, NOT
 * EVEN THE IMPLIED WARRANTIES OF MERCHANTIBILITY OR
 * FITNESS FOR A PARTICULAR PURPOSE. USE AT YOUR OWN
 * RISK.
 */
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <osbind.h>
#include <ctype.h>
#include <keycodes.h>
#include "xgem.h"

char *about_string = "About...";
char *desk_string = "TOSWIN";
void (*about_func)() = 0;

/* local variables */
static MENU 	*curmenu = 0;	/* currently displayed menu */
static MENU	*deskmenu = 0;	/* the whole menu bar, include " Desk " */
static OBJECT	*menuobj = 0;	/* menu object for the displayed menu */

/*
 * Create a menu with the given title. Under GEM, we automatically
 * add a space before and after the title, for aesthetics. (This is
 * done in fixmenu.)
 */

MENU *
create_menu(title)
	const char *title;
{
	MENU *m;

	m = malloc(sizeof(MENU));
	if (!m) return m;
	m->next = 0;
	m->title = strdup(title);
	m->width = 0;
	m->contents = 0;
	m->index = 0;
	return m;
}

/*
 * Destroy a menu, freeing any memory allocated for it.
 */

void
destroy_menu(menu)
	MENU *menu;
{
	MENU *m;
	ENTRY *e;

	if (curmenu && menu == curmenu) {
		hide_menu();
	}

	while (menu) {
		m = menu; menu = m->next;
		while ((e = m->contents)) {
			m->contents = e->next;
			free(e->entry);
			free(e);
		}
		free(m);
	}
}

/*
 * Add a new entry to a menu. We automatically add 2 spaces before and
 * 2 after the menu entry string, for aesthetics, in fixmenu, and
 * any special key symbols are also placed there; so the user doesn't
 * need to worry about these details.
 * NOTE: the string given as `entry' need not remain around permanently;
 * we duplicate it just to be on the safe side.
 * Returns: the newly added entry, or NULL on failure.
 */

ENTRY *
add_entry( m, entry, f, arg, key, state)
	MENU *m;
	char *entry;
	void (*f)();
	void *arg;
	int key, state;
{
	ENTRY *e, **ep;

	e = malloc(sizeof(ENTRY));
	if (!e) return 0;
	ep = &m->contents;
	while (*ep) {
		ep = &((*ep)->next);
	}
	*ep = e;
	e->next = 0;
	e->entry = strdup(entry);
	e->func = f;
	e->arg = arg;
	e->state = state;
	e->keycode = key;
	e->index = 0;
	return e;
}

/* calculate the length of an entry; this is:
 * 2 (leading blanks) + strlen(e->entry) + 2 (trailing blanks) +
 * (optional) some number of bytes for any key symbols
 */

#define MAXKEYSYMLEN 4

int
entrylen(e)
	ENTRY *e;
{
	return 4 + strlen(e->entry) + (e->keycode ? 1 + strlen(UNALT(e->keycode)) :
					 0);
}

/*
 * make a string representing a menu entry
 */

char *
entrystr(e, wide)
	ENTRY *e;
	int wide;
{
	int i, n;
	char *s, *ret, c;

	n = wide + 1;
	ret = malloc(n);
	if (!ret) return 0;
	i = 2;
	c = '-';
	for (s = e->entry; *s; s++) {
		ret[i++] = *s;
		if (*s != '-') c = ' ';
		if (i == n) break;
	}
	ret[0] = c; ret[1] = c;			/* leading blanks or dashes */

	while (i < n-1)
		ret[i++] = c;			/* trailing blanks or dashes */

	ret[n-1] = 0;
/* special case for '-----' type strings */
	if (c == '-') {
		return ret;
	}

/* special symbols for keyboard equivalents */
	if (e->keycode) {
		s = UNALT(e->keycode);
		i = (n-2) - strlen(s);		/* right justify, trailing blank */
		if (i > 3) {
			while (*s)
				ret[i++] = *s++;
		}
	}
	return ret;
}

static OBJECT *
fixmenu( bar )
	MENU *bar;
{
	OBJECT *obj;
	int i, wide, y, place;
	int numobjects, num_titles, num_entries;
	int menubox;
	MENU *m; ENTRY *e;
	char *s;

	numobjects = 4;	/* all menu bars needs some invisible boxes */
	num_titles = 0;

/* count up the number of objects necessary */
	for (m = bar; m; m = m->next) {
		num_titles++;
		numobjects++;		/* for the title */
		numobjects++;		/* for the menu box */
		for (e = m->contents; e; e = e->next) {
			numobjects++;	/* for the entry */
		}
	}

	obj = malloc(numobjects * sizeof(OBJECT));
	if (!obj) return obj;

/* now we create the various objects we need */
/* first, the root menu bar object */
	obj[0].ob_next = -1;
	obj[0].ob_head = 1;
	obj[0].ob_tail = num_titles + 3;
	obj[0].ob_type = G_IBOX;
	obj[0].ob_flags = NONE; obj[0].ob_state = NORMAL;
	obj[0].ob_spec = 0L;
	obj[0].ob_x = 0; obj[0].ob_y = 0;
	obj[0].ob_width = 90; obj[0].ob_height = 25;

/* now the menu bar box itself */
	obj[1].ob_next = obj[0].ob_tail;
	obj[1].ob_head = 2;
	obj[1].ob_tail = 2;
	obj[1].ob_type = G_BOX;
	obj[1].ob_flags = NONE; obj[1].ob_state = NORMAL;
	obj[1].ob_spec = 0x00001100L;
	obj[1].ob_x = 0; obj[1].ob_y = 0;
	obj[1].ob_width = 90; obj[1].ob_height = 513;

	obj[2].ob_next = 1;
	obj[2].ob_head = 3;
	obj[2].ob_tail = 2 + num_titles;
	obj[2].ob_type = G_IBOX;
	obj[2].ob_flags = NONE; obj[2].ob_state = NORMAL;
	obj[2].ob_spec = 0L;
	obj[2].ob_x = 2; obj[2].ob_y = 0;
	obj[2].ob_width = 0;		/* will be adjusted later */
	obj[2].ob_height = 769;

	i = 3;
	for (m = bar; m; m = m->next) {
		m->index = i;
		obj[i].ob_next = (m->next  == 0) ? 2 : i+1;
		obj[i].ob_head = obj[i].ob_tail = -1;
		obj[i].ob_type = G_TITLE;
		obj[i].ob_flags = NONE; obj[i].ob_state = NORMAL;
		obj[i].ob_width = strlen(m->title) + 2;
		s = malloc(obj[i].ob_width + 1);
		if (!s) return NULL;
		s[0] = ' ';
		strcpy(s+1, m->title);
		strcat(s, " ");
		obj[i].ob_spec = (long)s;
		obj[i].ob_height = 769;
		obj[i].ob_x = obj[2].ob_width;
		obj[2].ob_width += obj[i].ob_width;
		obj[i].ob_y = 0;
		i++;
	}
	obj[i].ob_next = 0;
	obj[i].ob_head = i+1;
	obj[i].ob_tail = 0;		/* to be adjusted later */
	menubox = i;
	obj[i].ob_type = G_IBOX;
	obj[i].ob_flags = NONE; obj[i].ob_state = NORMAL;
	obj[i].ob_spec = 0L;
	obj[i].ob_x = 0; obj[i].ob_y = 769;
	obj[i].ob_width = 80; obj[i].ob_height = 19;
	i++;

/* now, for each menu we calculate the number of entries and
 * the size of the necessary box
 */
	place = 2;

	for (m = bar; m; m = m->next) {
		int box;

		box = i;
		num_entries = wide = 0;
		for(e = m->contents; e; e = e->next) {
			num_entries++;
			if (m == deskmenu) {
				if (strlen(e->entry) > wide)
					wide = strlen(e->entry);
			} else {
				if (entrylen(e) > wide)
					wide = entrylen(e);
			}
		}
		if (m->next)
			obj[i].ob_next = i + num_entries + 1;
		else {
			obj[i].ob_next = menubox;
			obj[menubox].ob_tail = i;
		}
		obj[i].ob_head = i+1;
		obj[i].ob_tail = i + num_entries;
		obj[i].ob_type = G_BOX;
		obj[i].ob_flags = NONE; obj[i].ob_state = NORMAL;
		obj[i].ob_spec = 0x00ff1100L;
		obj[i].ob_x = place; obj[i].ob_y = 0;
		place += strlen(m->title)+2;
		obj[i].ob_width = wide; obj[i].ob_height = num_entries;
		i++;
		y = 0;

		for (e = m->contents; e; e = e->next) {
			e->index = i;
			obj[i].ob_next = (e->next) ? i+1 : box;
			obj[i].ob_head = obj[i].ob_tail = -1;
			obj[i].ob_type = G_STRING;
			obj[i].ob_flags = NONE;
			obj[i].ob_state = e->state;
	/* Do NOT malloc the strings for the Desk menu, or else there
	 * will be a memory leak!
	 */
			if (m != deskmenu)
				s = entrystr(e, wide);
			else {
				s = e->entry;
			}
			if (!s) return NULL;
			obj[i].ob_spec = (long)s;
			obj[i].ob_x = 0; obj[i].ob_y = y++;
			obj[i].ob_width = wide; obj[i].ob_height = 1;
			i++;
		}
	}
	obj[i-1].ob_flags = LASTOB;

/* now, fix the object tree up */
	for (i = 0; i < numobjects; i++)
		rsrc_obfix(obj, i);

	return obj;
}

void
handle_menu(title, index)
	int title, index;
{
	MENU *m;
	ENTRY *e;

	for (m = deskmenu; m; m = m->next) {
		if (m->index == title) break;
	}
	if (m) {
	    for (e = m->contents; e; e = e->next) {
		if (e->index == index) {
			(*e->func)(e->arg);
			menu_tnormal(menuobj, title, 1);
			return;
		}
	    }
	}

/* strange. Let's just punt and return */
}

/*
 * erase menu currently on screen (if one exists), and frees the memory
 * allocated for its strings
 */

void
hide_menu()
{
	int i;
	int firststring;
	extern short _app;

	if (!_app) {
		if (deskmenu)
			deskmenu->next = curmenu = 0;
		return;
	}

	if (curmenu && curmenu->contents) {
		firststring = curmenu->contents->index;
	} else {
		firststring = 0x7fff;	/* ridiculously big number */
	}

	if (menuobj) {
		menu_bar(menuobj, 0);
		i = 0;
		for(;;) {
			if (menuobj[i].ob_type == G_TITLE)
				free((void *)menuobj[i].ob_spec);
			else if (menuobj[i].ob_type == G_STRING) {
	/* Something to watch out for here: the desktop will replace the 6
	 * .ACC strings with its own strings (eek). So, we can only
	 * free G_STRINGS with the "right" indices
	 */
				if (i >= firststring)
					free((void *)menuobj[i].ob_spec);
			}
			if (menuobj[i].ob_flags & LASTOB) break;
			i++;
		}
		free(menuobj);
		menuobj = 0;
		deskmenu->next = curmenu = 0;
	}
}

/*
 * Show a menu bar; this will erase any previous menu. It will also
 * automatically construct a "Desk" menu.
 */

void
show_menu( m )
	MENU *m;
{
	static void dflt_about(), nullfunc();
	static char about_buf[22] = "  Your message here";
	int i; char *s;
	extern short _app;

	hide_menu();

	if (deskmenu == 0) {
		deskmenu = create_menu(desk_string);
		add_entry(deskmenu, about_buf, dflt_about, NULL, 0, NORMAL);
		add_entry(deskmenu, "--------------------", nullfunc, NULL, 0,
			DISABLED);
		add_entry(deskmenu, "1", nullfunc, NULL, 0, NORMAL);
		add_entry(deskmenu, "2", nullfunc, NULL, 0, NORMAL);
		add_entry(deskmenu, "3", nullfunc, NULL, 0, NORMAL);
		add_entry(deskmenu, "4", nullfunc, NULL, 0, NORMAL);
		add_entry(deskmenu, "5", nullfunc, NULL, 0, NORMAL);
		add_entry(deskmenu, "6", nullfunc, NULL, 0, NORMAL);
	}

	s = about_string;
	for (i = 2; i < 20; i++) {
		if (*s) {
			about_buf[i] = *s++;
		} else {
			about_buf[i] = ' ';
		}
	}

	deskmenu->contents->entry = about_buf;
	deskmenu->contents->func = about_func ? about_func : dflt_about;

	deskmenu->next = curmenu = m;

	if (_app && (menuobj = fixmenu(deskmenu)) != NULL) {
		menu_bar(menuobj, 1);
	}
}

/*
 * Look for a keyboard equivalent for a given menu; if found,
 * execute the associated action and return 1, otherwise return 0.
 */

int
menu_key(code, shift)
	int code;	/* keyboard code we're looking for */
	int shift;	/* not used */
{
	MENU *m;
	ENTRY *e;
	extern short _app;

	for (m = curmenu; m; m = m->next) {
		for (e = m->contents; e; e = e->next) {
			if (e->keycode == code) {
				if (_app) {
					menu_tnormal(menuobj, m->index, 0);
					handle_menu(m->index, e->index);
				} else {
					(*e->func)(e->arg);
				}
				return 1;
			}
		}
	}
	return 0;
}

static void
nullfunc()
{
}

/* not actually needed, TOSWIN sets up an about() function properly */

static void
dflt_about()
{
}

/*
 * routines for checking/unchecking menu entries
 */

void
check_entry(m, e)
	MENU *m;
	ENTRY *e;
{
	MENU *n;

	e->state = CHECKED;
	if (curmenu && menuobj) {
		for (n = curmenu; n; n = n->next) {
			if (n == m) {
				menu_icheck(menuobj, e->index, 1);
				return;
			}
		}
	}
}

void
uncheck_entry(m, e)
	MENU *m;
	ENTRY *e;
{
	MENU *n;

	e->state = NORMAL;
	if (curmenu && menuobj) {
		for (n = curmenu; n; n = n->next) {
			if (n == m) {
				menu_icheck(menuobj, e->index, 0);
				return;
			}
		}
	}
}

/*
 * routines for enabling/disabling menu items
 */

void
enable_entry(m, e)
	MENU *m;
	ENTRY *e;
{
	MENU *n;

	e->state = NORMAL;
	if (curmenu && menuobj) {
		for (n = curmenu; n; n = n->next) {
			if (n == m) {
				menu_ienable(menuobj, e->index, 1);
				return;
			}
		}
	}
}

void
disable_entry(m, e)
	MENU *m;
	ENTRY *e;
{
	MENU *n;

	e->state = DISABLED;
	if (curmenu && menuobj) {
		for (n = curmenu; n; n = n->next) {
			if (n == m) {
				menu_ienable(menuobj, e->index, 0);
				return;
			}
		}
	}
}

/*
 * ALT: finds the appropriate key-code for, e.g., ALT-A based upon
 * the current settings of the keyboard caps lock table
 */

int
ALT(c)
	int c;
{
	char *capstab;
	int i;

	c = toupper(c);
	if (c == '0') return (ALT_0 << 8);
	if (c >= '1' && c <= '9') {
		return (ALT_1 + (c - '1')) << 8;
	}

	capstab = *( ((char **)Keytbl(-1L, -1L, -1L)) + 2 );
	for (i = 0; i < 127; i++) {
		if (capstab[i] == c)
			return (i << 8);
	}
	return 0;
}

/*
 * UNALT: somewhat misnamed; finds an ASCII string representation for
 * the given keycode. Note that the returned string is overwritten
 * by subsequent UNALT calls.
 */

#define ALTSYM '\007'
#define CTRLSYM '^'
#define SHIFTSYM '\001'

struct kdef {
	int scan;
	char *name;
} keyname[] = {
	K_INS, "Ins",
	K_HOME, "Home",
	K_UNDO, "Undo",
	K_HELP, "Help",
	CURS_UP, "\001",
	CURS_DN, "\002",
	CURS_RT, "\003",
	CURS_LF, "\004",
	0, 0
};

char *
UNALT(code)
	int code;
{
	char *capstab, *s;
	static char retbuf[8];
	int scan, c;
	struct kdef *k;

	s = retbuf;
	if (!code) {
		*s++ = 0;
		return retbuf;
	}

	c = code & 0x00ff;
	scan = (code & 0xff00) >> 8;

	if ((scan == CURS_UP && c == '8') ||
	    (scan == CURS_DN && c == '2') ||
	    (scan == CURS_RT && c == '6') ||
	    (scan == CURS_LF && c == '4')) {
		*s++ = 'S'; *s++ = 'h'; *s++ = 'f'; *s++ = 't';
		c = 0;
	} else if (scan == K_HOME && c == '7') {
		strcpy(s, "Clr");
		return retbuf;
	}

	if (c) {
		if (c == 0x7f) {	/* DEL */
			*s++ = 'D';
			*s++ = 'E';
			c = 'L';
		} else if (c == '\033') {	/* ESC */
			*s++ = 'E';
			*s++ = 'S';
			c = 'C';
		}
		if (c < ' ') {		/* control characters */
			*s++ = CTRLSYM;
			c += '@';
		}
		*s++ = c;
		*s++ = 0;
		return retbuf;
	}

/* Special cases go here... */
	for (k = keyname; k->name; k++) {
		if (scan == k->scan) {
			strcpy(s, k->name);
			return retbuf;
		}
	}

/* shifted function keys */
	if (scan >= SHF_1 && scan <= SHF_10) {
		scan = (scan - SHF_1) + F_10 + 1;
		/* fall through */
	}

/* function keys */
	if (scan >= F_1 && scan <= F_10 + 10) {
		*s++ = 'F';
		c = scan - F_1 + 1;
		if (c >= 10) {
			*s++ = '1';
			c -= 10;
			if (c == 10) {
				s[-1] = '2';
				c = 0;
			}
		}
		*s++ = '0'+c;
		*s++ = 0;
		return retbuf;
	}

/* Now check for ALT keys */
	if (scan >= ALT_1 && scan <= ALT_0) {
		*s++ = ALTSYM;
		scan = (scan - ALT_1) + 1;
		if (scan == 10) scan = 0;
		*s++ = '0'+scan;
		*s++ = 0;
		return retbuf;
	}

/* look for ALT+key */
	capstab = *( ((char **)Keytbl(-1L, -1L, -1L)) + 2 );
	c = capstab[scan];
	if (c >= 'A' && c <= 'Z') {
		*s++ = ALTSYM;
		*s++ = c;
	}
	*s++ = 0;
	return retbuf;
}
