/* $Id: MuMenu.c,v 1.3 94/09/06 12:26:24 dot Exp $ */
#include <stdio.h>

/* $XConsortium: Template.c,v 1.2 88/10/25 17:40:25 swick Exp $ */
/* Copyright	Massachusetts Institute of Technology	1987, 1988 */
#include <Xm/XmP.h>
#include <X11/IntrinsicP.h>
#include <X11/StringDefs.h>
#include <X11/Shell.h>
#include <X11/ShellP.h>
#include <Xm/MenuShell.h>
#include <Xm/MenuShellP.h>
#include "MuMenuP.h"
#include <Xm/PushBG.h>
#include <Xm/ToggleBG.h>
#include <Xm/SeparatoG.h>
#include <Xm/CascadeB.h>
#include <ctype.h>

void MuRegisterWidget(
#if NeedFunctionPrototypes
			 Widget w, char *name
#endif
);

#define DEFAULT_RESOURCES "\"No resources have been loaded.\";"

#define offset(field) XtOffset(MuMenuWidget, muMenu.field)
static XtResource resources[] =
{
    {XmNitems, XmCItems, XtRString,
     sizeof(String), offset(items), XtRString, DEFAULT_RESOURCES}
};
#undef offset

#if NeedFunctionPrototypes
#define        P(s) s
#else
#define P(s) ()
#endif

static void ClassInitialize P((void));
static char *indexq P((char *where, int what));
static void Initialize P((MuMenuWidget request, MuMenuWidget new));
#undef P

#define superclass (&xmRowColumnClassRec)

MuMenuClassRec muMenuClassRec =
{
    {				/* core fields */
	/* superclass		*/ (WidgetClass) superclass,
	/* class_name		*/ "MuMenu",
	/* widget_size		*/ sizeof(MuMenuRec),
	/* class_initialize		*/ ClassInitialize,
	/* class_part_initialize	*/ NULL,
	/* class_inited		*/ FALSE,
	/* initialize		*/ (XtInitProc) Initialize,
	/* initialize_hook		*/ NULL,
	/* realize			*/ XtInheritRealize,
	/* actions			*/ NULL,
	/* num_actions		*/ 0,
	/* resources		*/ resources,
	/* num_resources		*/ XtNumber(resources),
	/* xrm_class		*/ NULLQUARK,
	/* compress_motion		*/ TRUE,
	/* compress_exposure	*/ TRUE,
	/* compress_enterleave	*/ FALSE,
	/* visible_interest		*/ FALSE,
	/* destroy			*/ NULL,
	/* resize			*/ XtInheritResize,
	/* expose			*/ XtInheritExpose,
	/* set_values		*/ NULL,
	/* set_values_hook		*/ NULL,
	/* set_values_almost	*/ XtInheritSetValuesAlmost,
	/* get_values_hook		*/ NULL,
	/* accept_focus		*/ NULL,
	/* version			*/ XtVersion,
	/* callback_private		*/ NULL,
	/* tm_table			*/ XtInheritTranslations,
	/* query_geometry		*/ XtInheritQueryGeometry,
	/* display_accelerator	*/ XtInheritDisplayAccelerator,
	/* extension		*/ NULL
    },
    {				/* composite class record */
	XtInheritGeometryManager,	/* childrens geo mgr proc */
	XtInheritChangeManaged,	/* set changed proc */
	XtInheritInsertChild,	/* add a child */
	XtInheritDeleteChild,	/* remove a child */
	NULL,			/* extension */
    },
    {				/* constraint class record */
	NULL,			/* constraint resources */
	0,			/* constraint resource_count */
	sizeof(XmRowColumnConstraintRec),	/* constraint_size */
	NULL,			/* initialize */
	NULL,			/* destroy */
	NULL,			/* set_values */
	NULL,			/* extension */
    },
    {				/* manager class record */
	XtInheritTranslations /*(XtTranslations)_XtInherit*/ ,	/* translations
*/
	NULL,			/* get_resources */
	0,			/* num_get_resources */
	NULL,			/* get_constraint_resources */
	0,			/* num_get_constraint_resources */
	NULL,			/* extension */
    },
    {				/* row column class record */
	NULL,			/* proc to interface with menu widgets*/
	NULL,			/* proc to arm&activate menu */
	NULL,			/* traversal handler */
	NULL,			/* extension */
    },
    {				/* muMenu fields */
	/* empty		     */ 0
    }
};

WidgetClass muMenuWidgetClass = (WidgetClass) & muMenuClassRec;

/* Hash table declarations */

  struct bucket {
      struct bucket *next;
      int key;
      Widget data;
  };
  struct hash {
      int size;
      struct bucket **data;
  };

/* hashing routines should be with ints to be cast to Widgets
 * and XtCallbackProcs, not cast from Widgets...
 */
extern struct hash *create_hash();
extern Widget hash_lookup();
extern int hash_store();
static struct hash *table = NULL;
#define HASHSIZE 67

extern char *index(const char *, int);
extern int atoi(const char *);

void MuRegisterMenuCallbacks(MuCallbacksRec * c, int num)
{
    int i;
    XrmQuark quark;
    char err_buf[100];

    if (table == NULL)
	table = create_hash(HASHSIZE);

    for (i = 0; i < num; i++) {
	quark = XrmStringToQuark(c[i].name);
	if (1 == hash_store(table, quark, (Widget) c[i].proc)) {
	    sprintf(err_buf,
	    "MuRegisterMenuCallbacks: duplicate procedure registration: '%s'",
		    c[i].name);
	    XtWarning(err_buf);
	}
    }
}

XtCallbackProc MuGetMenuCallback(char *name)
{
    XrmQuark quark;

    quark = XrmStringToQuark(name);
    if (table != NULL)
	return (XtCallbackProc) (hash_lookup(table, quark));
    else
	return NULL;
}

static void ClassInitialize(void)
{
/* got to fix up inheritance myself, since RowColumn won't do it for me */
#define inherit(field) muMenuClassRec.row_column_class.field = \
					superclass->row_column_class.field
    inherit(menuProcedures);
    inherit(armAndActivate);
    inherit(traversalHandler);
    inherit(extension);
#undef inherit
}

#define ITYPENONE 0
#define ITYPEBUTTON 1
#define ITYPESEPARATOR 2
#define ITYPECASCADE 3
#define ITYPETOGGLEON 4
#define ITYPETOGGLEOFF 5

#define PTYPENULL 0
#define PTYPESTR 1
#define PTYPEINT 2

#define SHIFT 1
#define CTRL  2
#define ALT   4
#define META  8
#define LOCK  16

/*
 * Return first occurrence of what that's not in quotes
 * does the right thing for what == '"' :)
 */

static char *indexq(char *where, int what)
{
    int inquotes = 0, found = 0;

    while (!found && *where) {
	if (!inquotes && *where == (char)what)
	    found = 1;
	else {
	    if (*where == '"')
		inquotes = 1 - inquotes;
	    where++;
	}
    }
    return (char *)(found * (int)where);
}

#define min( a, b ) a < b ? a : b

#define MAXLINE 80

static void Initialize(MuMenuWidget request, MuMenuWidget new)
{
    static char rtn[] = "MuCreateMenuBar:";
    char *i, *items, *endquote, *ptr, *ptr2;
    XmString label;
    char *end, *somethingtoparse;

    char un, itemname[100], subname[100], procname[100], scratch[100];
    char accelText[100], accelKey[100];
    int accelModifiers;
    char err_buf[300], thisline[100];

    int ptype, ishelp;
    char strparm[100];
    int intparm;
    char widgetname[100];
    char registered_name[100];
    Widget w;
    char *cbName;
    XtCallbackProc proc;
    caddr_t data;
    Arg args[5];
    int n;

    int numitems, type;

    items = new->muMenu.items;
    end = index(items, '\0');

    for (i = items, numitems = 0; i < end; numitems++) {
	sprintf(widgetname, "item%d", numitems);
	itemname[0] = '\0';	/* displayed menu item name */
	registered_name[0] = '\0';	/* name to register in hash table */
	subname[0] = '\0';	/* resource name of submenu */
	procname[0] = '\0';	/* name of procedure to call */
	accelText[0] = '\0';	/* accelerator string to display */
	accelKey[0] = '\0';	/* accelerator key */
	accelModifiers = 0;	/* accelerator modifier keys */
	type = ITYPENONE;	/* type of menu item */
	ishelp = 0;
	un = '\0';		/* what character to underline in menu item */

	somethingtoparse = indexq(i, ';');
	if (somethingtoparse) {
	    strncpy(thisline, i, min(100, somethingtoparse - i + 1));
	    thisline[min(99, somethingtoparse - i + 1)] = '\0';
	}
	else {
	    sprintf(err_buf, "%s no ';' found in:\n%s\n", rtn, i);
	    XtWarning(err_buf);
	    i = end;
	}

	/* skip over white space */
	while (isspace(*i))
	    i++;

	while (somethingtoparse) {
	    switch (*i) {
		case '"':	/* parse name */
		    i++;
		    endquote = index(i, '"');
		    /*
		 * there has to be an endquote in this parse interpretation
		 * so endquote guaranteed not == 0
		 */
		    if (!strncmp(i, "------------", endquote - i)) {
			type = ITYPESEPARATOR;
			i = endquote + 1;
		    }
		    else {	/* parse non-separator name */
			if (type == ITYPENONE)	/* don't override toggle */
			    type = ITYPEBUTTON;

			ptr = itemname;

			while (i < endquote) {
			    if (*i == '_')
				un = *(++i);
			    *ptr++ = *i++;
			}
			*ptr = '\0';
			i++;
		    }
		    break;

		case '-':	/* parse submenu stuff */
		    type = ITYPECASCADE;
		    endquote = index(i + 1, '>');
		    if (!endquote || endquote > somethingtoparse) {
			sprintf(err_buf,
				"%s missing symbol '>' in submenu:\n%s\n",
				rtn, thisline);
			XtWarning(err_buf);
			i = somethingtoparse;	/* ignore line */
			type = ITYPENONE;
		    }
		    else {
			i = endquote + 1;
			while (isspace(*i))
			    i++;
			ptr = subname;
			while (!isspace(*i) && i < somethingtoparse)
			    *ptr++ = *i++;
			*ptr = '\0';
		    }
		    break;

		case '[':	/* parse accelerator stuff */
		    endquote = index(i + 1, ']');
		    if (!endquote || endquote > somethingtoparse) {
			sprintf(err_buf,
				"%s missing symbol ']' in submenu:\n%s\n",
				rtn, thisline);
			XtWarning(err_buf);
			i = somethingtoparse;	/* ignore line */
			type = ITYPENONE;
		    }
		    else {
			i++;
			while (isspace(*i))
			    i++;
			ptr = accelText;
			while (i < endquote)
			    *ptr++ = *i++;
			*ptr = '\0';
			while (isspace(*(--ptr)))
			    *ptr = '\0';
			ptr = accelText;
			while (*ptr) {
			    while ((*ptr) && (!isalnum(*ptr))) {
				if (*ptr == '^')
				    accelModifiers |= CTRL;
				ptr++;
			    }
			    if (!*ptr)
				break;
			    if (!strncasecmp(ptr, "shift", 5)) {
				ptr += 5;
				accelModifiers |= SHIFT;
				continue;
			    }
			    else if (!strncasecmp(ptr, "ctrl", 4)) {
				ptr += 4;
				accelModifiers |= CTRL;
				continue;
			    }
			    else if (!strncasecmp(ptr, "alt", 3)) {
				ptr += 3;
				accelModifiers |= ALT;
				continue;
			    }
			    else if (!strncasecmp(ptr, "meta", 4)) {
				ptr += 4;
				accelModifiers |= META;
				continue;
			    }
			    else if (!strncasecmp(ptr, "lock", 4)) {
				ptr += 4;
				accelModifiers |= LOCK;
				continue;
			    }
			    else {
				ptr2 = accelKey;
				while (isalnum(*ptr))
				    *ptr2++ = *ptr++;
				*ptr2 = '\0';
				break;
			    }
			}
			i = endquote + 1;
		    }
		    break;

		default:	/* parse everything else; s.b. guaranteed not ';' */
		    ptr = scratch;
		    while (!isspace(*i)
			   && *i != '('
			   && *i != ':'
			   && i < somethingtoparse)
			*ptr++ = *i++;
		    *ptr = '\0';

		    switch (*i) {
			case ':':	/* It's a name for the widget. */
			    strcpy(registered_name, scratch);
			    i++;
			    break;

			case '(':	/* It's a procedure. */
			    if (type == ITYPENONE)	/* don't override toggle */
				type = ITYPEBUTTON;

			    strcpy(procname, scratch);

			    i++;
			    while (isspace(*i))
				i++;

			    if (*i == '"') {	/* string */
				ptype = PTYPESTR;
				i++;
				endquote = index(i, '"');

				strncpy(strparm, i, endquote - i);
				strparm[endquote - i] = '\0';

				i = endquote + 1;
				while (isspace(*i))
				    i++;
				if (*i != ')') {
				    sprintf(err_buf,
					    "%s missing ')' after parameter in:\n%s\n",
					    rtn, thisline);
				    XtWarning(err_buf);
				    i = somethingtoparse;	/* ignore line */
				    type = ITYPENONE;
				}
				else
				    i++;
			    }
			    else {	/* int or NULL */
				if (*i == ')') {	/* NULL */
				    ptype = PTYPENULL;
				    i++;
				}
				else {	/* int */
				    ptr = strparm;
				    while (*i != ')' && i < somethingtoparse)
					*ptr++ = *i++;
				    *ptr = '\0';

				    intparm = atoi(strparm);
				    ptype = PTYPEINT;

				    if (*i != ')') {
					sprintf(err_buf,
						"%s missing ')' after parameter in:\n%s\n",
						rtn, thisline);
					XtWarning(err_buf);
					i = somethingtoparse;	/* ignore line */
					type = ITYPENONE;
				    }
				    else
					i++;
				}
			    }
			    break;

			default:	/* then a flag */
			    if (!strcmp(scratch, "togon"))
				type = ITYPETOGGLEON;
			    else if (!strcmp(scratch, "togoff"))
				type = ITYPETOGGLEOFF;
			    else if (!strcmp(scratch, "help"))
				ishelp = 1;
			    else {
				sprintf(err_buf,
					"%s unknown flag: '%s' in:\n%s\n",
					rtn, scratch, thisline);
				XtWarning(err_buf);
				i = somethingtoparse;	/* ignore line */
				type = ITYPENONE;
			    }
		    }		/* end of inner switch */
	    }			/* end of switch */

	    while (isspace(*i))
		i++;
	    if (*i == ';') {
		i++;
		somethingtoparse = NULL;
	    }
	    while (isspace(*i))
		i++;

	    if (*i == '\0')
		somethingtoparse = NULL;
	}			/* end while (somethingtoparse) */
	/* now it's all been parsed into the proper variables */

	n = 0;
	/* choose the label */
	if (type != ITYPESEPARATOR) {
	    if (itemname[0] != '\0')
		ptr = itemname;
	    else if (itemname[0] != '\0')
		ptr = subname;
	    else if (itemname[0] != '\0')
		ptr = procname;
	    else
		ptr = widgetname;
	    label = XmStringCreateLtoR(ptr, XmSTRING_DEFAULT_CHARSET);
	    XtSetArg(args[n], XmNlabelString, label);
	    n++;
/*	    XmStringFree(label); */
	}

	if (un != '\0') {	/* not possible for separator (put inside anyway) */
	    XtSetArg(args[n], XmNmnemonic, un);
	    n++;
	}

	if (accelText[0] != '\0') {
	    label = XmStringCreateLtoR(accelText, XmSTRING_DEFAULT_CHARSET);
	    XtSetArg(args[n], XmNacceleratorText, label);
/*	    XmStringFree(label); */

	    n++;
	    scratch[0] = '\0';
	    if (accelModifiers & SHIFT)
		strcat(scratch, "Shift ");
	    if (accelModifiers & CTRL)
		strcat(scratch, "Ctrl ");
	    if (accelModifiers & META)
		strcat(scratch, "Meta ");
	    if (accelModifiers & ALT)
		strcat(scratch, "Alt ");
	    if (accelModifiers & LOCK)
		strcat(scratch, "Lock ");
	    strcat(scratch, "<Key>");
	    strcat(scratch, accelKey);
	    XtSetArg(args[n], XmNaccelerator, scratch);
	    n++;
	}

	switch (type) {
	    case ITYPESEPARATOR:
		w = (Widget) XmCreateSeparatorGadget((Widget) new,
						     widgetname, NULL, 0);
		break;
	    case ITYPEBUTTON:
		w = (Widget) XmCreatePushButtonGadget((Widget) new,
						      widgetname, args, n);
		cbName = XmNactivateCallback;
		break;
	    case ITYPETOGGLEON:
		XtSetArg(args[n], XmNset, (Boolean) True);
		n++;
	    case ITYPETOGGLEOFF:
		w = (Widget) XmCreateToggleButtonGadget((Widget) new,
							widgetname, args, n);
		cbName = XmNvalueChangedCallback;
		break;
	    case ITYPECASCADE:
		w = (Widget) MuCreateMenuPane((Widget) new,
					      subname, (Arg *) NULL, 0);
		XtSetArg(args[n], XmNsubMenuId, w);
		n++;
		sprintf(scratch, "%s_button", subname);
		w = (Widget) XmCreateCascadeButton((Widget) new,
						   scratch, args, n);
		break;
	    default:
		w = (Widget) NULL;
	}

	if (w) {
	    if (ishelp) {
		XtSetArg(args[0], XmNmenuHelpWidget, w);
		XtSetValues((Widget) new, args, 1);
	    }
	    if (procname[0] != '\0') {
		switch (ptype) {
		    case PTYPENULL:
			data = (caddr_t) NULL;
			break;
		    case PTYPESTR:
			data = (caddr_t) XtNewString((String) strparm);
			break;
		    case PTYPEINT:
			data = (caddr_t) intparm;
			break;
		}
		proc = MuGetMenuCallback(procname);
		if (proc != NULL)
		    XtAddCallback(w, cbName, proc, data);
		else {
		    sprintf(err_buf,
			    "%s unregistered function '%s'",
			    rtn, procname);
		    XtWarning(err_buf);
		}
	    }
	    if (registered_name[0] != '\0')
		MuRegisterWidget(w, registered_name);
	    XtManageChild(w);
	}
    }
}

#define IsPopup(m)     \
    (((XmRowColumnWidget) (m))->row_column.type == XmMENU_POPUP)
#define IsPulldown(m)  \
    (((XmRowColumnWidget) (m))->row_column.type == XmMENU_PULLDOWN)
#define IsOption(m)    \
    (((XmRowColumnWidget) (m))->row_column.type == XmMENU_OPTION)
#define IsBar(m)       \
    (((XmRowColumnWidget) (m))->row_column.type == XmMENU_BAR)

Widget
MuCreateMenuBar(Widget p, String name, Arg * args, int count)
{
    Arg al[50];
    int ac = 0;
    Widget m;
    int i;

    /* copy arglist and add the rowColumnType resource */
    for (i = 0; i < count; i++)
	al[ac++] = args[i];
    XtSetArg(al[ac], XmNrowColumnType, XmMENU_BAR);
    ac++;
    m = XtCreateWidget(name, muMenuWidgetClass, p, al, ac);
    return (m);
}

Widget
MuCreateMenuPane(Widget p, String name, Arg * args, int count)
{
    Arg al[50];
    int ac = 0;
    Widget m;
    int i;
    Arg s_al[25];
    XmMenuShellWidget pop = NULL;
    Widget pw;
    int s_ac = 0;
    char b[200];

    for (i = 0; i < count; i++)
	al[ac++] = args[i];	/* copy into our list */

    /* force it to be a menu pane */
    XtSetArg(al[ac], XmNrowColumnType, XmMENU_PULLDOWN);
    ac++;

    /*
     * if this is a pulldown of a pulldown or popup then the parent
     * should really be the shell of the parent not the indicated
     * parent, this keeps the cascade tree correct
     */

    if ((XtParent(p) != NULL) && XmIsMenuShell(XtParent(p)))
	pw = XtParent(p);
    else
	pw = p;

    /* now we need a shell widget */

    /*
     * Shared menupanes are supported for all menu types but the option
     * menu.  If this is not an option menupane, then see if a shell is
     * already present; if so, then we'll use it.
     */
    if (XmIsRowColumn(p) && (IsBar(p) || IsPopup(p) || IsPulldown(p))) {
	for (i = 0; i < pw->core.num_popups; i++) {
	    if ((XmIsMenuShell(pw->core.popup_list[i])) &&
		(((XmMenuShellWidget) pw->core.popup_list[i])->menu_shell.
		 private_shell) &&
		(!(pw->core.popup_list[i])->core.being_destroyed)) {
		pop = (XmMenuShellWidget) pw->core.popup_list[i];
		break;
	    }
	}
    }

    /* No shell - create a new one */
    if (pop == NULL) {
	XtSetArg(s_al[s_ac], XmNwidth, 5);
	s_ac++;
	XtSetArg(s_al[s_ac], XmNheight, 5);
	s_ac++;
	XtSetArg(s_al[s_ac], XmNallowShellResize, TRUE);
	s_ac++;
	XtSetArg(s_al[s_ac], XtNoverrideRedirect, TRUE);
	s_ac++;

	sprintf(b, "popup_%s", name);

	pop = (XmMenuShellWidget)
	    XtCreatePopupShell(b, xmMenuShellWidgetClass, pw, s_al, s_ac);

	/* Mark the shell as having been created by us */
	pop->menu_shell.private_shell = True;
    }

    m = XtCreateWidget(name, muMenuWidgetClass, (Widget) pop, al, ac);

    return (m);
}

Widget MuCreateOptionMenu(Widget p, String name)
{
    Widget pane, option;
    char panename[100];
    Arg a;

    strcpy(panename, name);
    strcat(panename, "Pane");
    pane = MuCreateMenuPane(p, panename, NULL, 0);
    XtSetArg(a, XmNsubMenuId, pane);
    option = XmCreateOptionMenu(p, name, &a, 1);
    return option;
}

void MuOptionMenuSetSelectedItem(Widget w, int n)
{
    XmRowColumnWidget option = (XmRowColumnWidget) w;
    CompositeWidget pane;
    Widget selected;
    Arg a;

    pane = (CompositeWidget) option->row_column.option_submenu;
    if (n >= pane->composite.num_children)
	n = pane->composite.num_children - 1;
    if (n < 0)
	n = 0;
    selected = pane->composite.children[n];
    XtSetArg(a, XmNmenuHistory, selected);
    XtSetValues(w, &a, 1);
}

int MuOptionMenuGetSelectedItem(Widget w)
{
    XmRowColumnWidget option = (XmRowColumnWidget) w;
    CompositeWidget pane;
    Widget selected;
    int i;

    pane = (CompositeWidget) option->row_column.option_submenu;
    selected = option->row_column.memory_subwidget;
    for (i = 0; i < pane->composite.num_children; i++)
	if (pane->composite.children[i] == selected)
	    break;
    if (i == pane->composite.num_children)
	i = -1;
    return i;
}

void MuOptionMenuSetSelectedString(Widget w, char *str)
{
    XmRowColumnWidget option = (XmRowColumnWidget) w;
    CompositeWidget pane;
    Widget selected;
    Arg a;
    XmString cs, value;
    int i;

    pane = (CompositeWidget) option->row_column.option_submenu;
    cs = XmStringCreateLtoR(str, XmSTRING_DEFAULT_CHARSET);
    XtSetArg(a, XmNlabelString, &value);
    for (i = 0; i < pane->composite.num_children; i++) {
	XtGetValues(pane->composite.children[i], &a, 1);
	if (XmStringCompare(value, cs))
	    break;
    }
    XmStringFree(cs);
    if (i < pane->composite.num_children) {
	selected = pane->composite.children[i];
	XtSetArg(a, XmNmenuHistory, selected);
	XtSetValues(w, &a, 1);
    }
}

char *MuOptionMenuGetSelectedString(Widget w)
{
    XmRowColumnWidget option = (XmRowColumnWidget) w;
    Widget selected;
    Arg a;
    XmString cs;
    char *str;

    selected = option->row_column.memory_subwidget;
    XtSetArg(a, XmNlabelString, &cs);
    XtGetValues(selected, &a, 1);
    XmStringGetLtoR(cs, XmSTRING_DEFAULT_CHARSET, &str);
    return str;
}
