A New menu system

*** THIS IS A SPEC, CODE ONLY EXISTS AS A PROTOTYPE ***


Proposal for new menu system, based on Montpellier discussions and
Cyrus and Frank's letter.  For discussion.

/*******************/

#define OPTION_DISABLED		0x01
#define OPTION_TOGGLE		0x02
#define OPTION_TOGGLE_STATE	0x04
#define OPTION_RADIO_SET	0x08
#define OPTION_RADIO_STATE	0x10

typedef struct
{ char *label ;
  void (*callBack)(OPTION *) ;	/* gets called with entire option structure */
  char *callMessage ;		/* loose linking - see below */
  unsigned int flag ;		/* values from OPTION_* */
  int  value ;			/* for simple specialisation */
  void *ptr ;			/* for more complex specialisation */
  MENUOPT *submenu ;
	/* other stuff I am less sure about - see below */
  void *icon ;
  int  forecolor, backcolor ;	/* for buttons */
  char override ;		/* override character */
} OPTION ;

/********************/

We have 3 choices for the way to specify a menu from its options.  
Either we use the first item as a header, with its label becoming the 
title, so we get
	graphBoxMenu (int box, OPTION *options)
or we add the title to the command line
	graphBoxMenu (int box, OPTION *options, char *title)
  which would not allow titles on walking submenus if we have them.  But
  I don't think you need them on the Mac?
or we have a new structure
	typedef struct
	{ char *title ;
	  OPTION *options ;
	} MENU ; 
	graphBoxMenu (int box, MENU *menu)
  which is more of a pain to initialise, which is important (see below).  

In all cases the options can be zero terminated.

/*******************/

The callMessage field allows use of some loose-linking call-by-name system,
e.g. but not restricted to Simon's new call() system.

On making a menu:
if opt->callBack, then a line/button is created as normal
else if callExists(opt->callMessage) then a line is created
else no line created -- the function is not linked in.

On a menu selection:
  if (opt->callBack)
    (*opt->callBack)(opt) ;
  else
    call (opt->callMessage, opt) ;

Spacer lines should be uniform, i.e. always use the same symbol 
(could be different on Mac than Unix).
Similarly the mark for on/off should be standard on toggles, and also
a different standard one for radio options.
Maybe we should have two sorts of toggles, one for presence or absence of 
something, with a check mark, and one flipping between two text states. 
Or perhaps that is better handled by a two-item radio set.

->submenu can be used for menus on buttons, and also perhaps for walking 
menus.  Not sure whether to allow those in our "style guide".

Perhaps even a single button should take a single *OPTION argument.

The value and ptr fields are required for making menus on the fly.  In
that case the function must be fixed, and these fields will tell which
option was picked.  void* ptr allows arbitrary information to be attached.

The callback will not include the box number.  The user can get it from

  int box = graphBoxAt (graphEventX, graphEventY, 0, 0) ;

This works now.

/*******************/

Comments on Cyrus/Frank scheme:

(1) we need to be able to statically define menus, e.g.

OPTION menuOpts[] = {
  { "Quit", graphDestroy, 0, 0, 0, 0, 0, 0, 0},
  { "Print", graphPrint, 0, 0, 0, 0, 0, 0, 0},
  { "Assemble", 0, "miegAssemble", 0, 0, 0, 0, 0, 0},
  { 0, 0, 0, 0, 0, 0, 0, 0, 0}} ;
MENU menu = { "Test", menuOpts} ;

In your linked-list scheme this would be not really possible.

(2) keyboard overrides seem to present some problems.  I guess they
would not mean anything for boxmenus?, or would they apply to the 
menu on the innermost box.

(3) menu bars as you have them really don't exist in X.  You mention emacs,
but that is one window.  If I had them, I would have to have them per 
window, which would create a nightmare for you.  

==========================================================================
A quick response to your comments:

The programmer may not care about the box, but in some cases it is
very important.  They care when the action selected in the menu
applies to the box over which the menu action took place (most of the
uses of graphBoxMenu).  I proposed not specifying the box in the
generated return call.  It is easy and probably better to eliminate
graphEventX/Y with a convenience call:
	int box = graphEventBox() ;
to give the box for the last event.  In fact maybe we should also use
this for PICK events, to make things consistent.  I agree one should
be wary of globals, but I think that in fact globals for the last
event coordinates are natural.  Compare with the implicit "active
graph" global.

The "value" field is exactly to store a magic number.  The "ptr" field
is completely flexible -- it lets you store anything you want, though
of course you must then allocate space for it.  I think this caters
for what you want -- it was in fact missing in your original proposal,
and present in mine.  Note that the called function always gets as
an argument a pointer to the ENTIRE structure, so you get value, ptr
etc. to use or ignore as you want.

Aha - I see the point about menuInitialise.  It would allow us to add
extra fields in the future (e.g. icons), without changing the code
anywhere that doesn't have them.  The downside is that to add or
access those fields requires extra calls.  I guess we require labels
to be unique within a menu.  I started off referring to items by
label, but for reasons later on with buttons I switched to introducing
a special handle for items.

This is what I have ended up with:

typedef struct
{ char *label ;
  void (*func)() ;	/* NB can be 0 if using menuSetCall() below */
} MENUSPEC ;

typedef void* MENU ;		/* public menu handle */
typedef void* MENUITEM ;	/* public handle for menu items */

/* a menu is maintained internally as a header and a linked list of
   items */

MENU menuInitialise (char *title, MENUSPEC *spec) ;
	/* makes a simple menu from a spec terminated with label = 0 */
	/* if called on same spec, give existing menu */
MENU menuCreate (char *title) ;		/* makes a blank menu */
MENUITEM menuCreateItem (char *label, void (*func)()) ;
MENUITEM menuItem (MENU menu, char *label) ;  /* find item from label */
BOOL menuAddItem (MENU menu, MENUITEM item, char *beforeLabel) ;
	/* add an item; if before == 0 then add at end */
BOOL menuDeleteItem (MENU menu, char *label) ; /* also destroys item */
void menuDestroy (MENU menu) ;	/* also destroys items */

The behaviour of menuInitialise when repeatedly called with the same
spec allows simple cases to be handled like now, e.g.
	static MENUSPEC spec[] = 
	  { "Quit", graphDestroy,
	    "Print", graphPrint,
	    0, 0
	  } ;
	graphMenu (menuInitialise ("Toto", spec)) ;

It seems always simpler to use labels for deleting and the before
field of add, rather than items.  We should definitely enforce that
labels must be unique within a menu.  Note that the item can be
obtained transiently, e.g. typical use would be
	menuAddItem (menu, menuCreateItem (label, func), 0) ;


To set extra information on an item:

BOOL menuSetCall (MENUITEM item, char *callFuncName) ;
BOOL menuSetFunc (MENUITEM item, void (*Func) ;
BOOL menuSetFlags (MENUITEM item, int flags) ;
BOOL menuUnsetFlags (MENUITEM item, int flags) ;
BOOL menuSetValue (MENUITEM item, int value) ;
BOOL menuSetPtr (MENUITEM item, void *ptr) ;
	with perhaps menuSetIcon(), menuSetOverride() etc. as needed
The BOOL's all return success or failure.  e.g. failure if label or
menu do not exist when they should.  Again, it is easy to write
	menuSetValue (menuItem (menu, "Frame 3"), 3) ;

Now what happens when an item is picked?  We get called back with
	func (MENUITEM item)
and can get the other information with
	int menuGetFlags (MENUITEM item)
etc.

We have a rule that if the function for a menu item is zero and it doesn't
have a Call routine set which exists then the item becomes invisible.
This makes is easy to have options which only appear when the relevant 
code has been linked in.

I still want graphBoxMenu (MENU menu, int box) to register the menu.  I
need to know where the menu's domain of action is.  For popup menus
you need to know where on the screen it can pop up.  That is a box in
the graph library.  We'll keep graphMenu (MENU menu) as a #define to
graphBoxMenu (menu, 0) ;

What about buttons?  I like the idea of them using the same structure.
For a single button we could have
	int graphButton (MENUITEM item, float x, float y) ;
allowing for simple cases the single call:
	graphButton (menuCreateItem (label, func), x, y) ;
For more complex ones you would explicitly create the item and
decorate it.  For a row of buttons, including for example radio button
features, one would want
	int graphButtons (MENU menu, float x0, float y0, float xmax) ;
and to add a menu to a button, you would use
	BOOL menuSetMenu (MENUITEM item, MENU menu) ;
I guess graphMenu() on the box would still work, but would be strongly
frowned upon.
	
I like this so much that I am going to start coding it (the standalone
menu handling side of it).  But that does not mean it is fixed -
please feel free to comment.  In most cases, as you say, this is an
overhead of at most one call (menuInitialise) above what we do now.

Note that all our horrible menu-adjusting stuff used for the main menu
becomes MUCH easier.  Now you just say
  menuAddItem (menu, menuCreateItem ("Save", saveFunc), "Write Access") ;
  menuDeleteItem (menu, "Write Access") ;
to toggle them.


Altenatively, using the zero functions are invisible rule, we can create
a menu using menuInitialise which had both Write Access and Save, and do

  menuSetFunc(menuItem(menu,"Save"), saveFunc);
  menuSetFunc(menuitem(menu,"Write Access", 0);

There are some technical implementation problems: I have, and want to
keep, cached X menu structures.  This is for two reasons: first I am
always using the same menu for new versions of the same display, and
worse I often have the same menu attached to thousands of boxes even
in one display.  But what is "same" in this case?  It can't be just
the header, because that stays the same when I add and delete items,
nor even the header and list of labels.  Ah, maybe it should be the
header, and I should do the following: when I call graphMenu() I
create a copy of the state in the menu.  Then when called again on the
same header, I can check if there are any changes since last time, and
if so bring the X menu up to date.  There is also an issue of how to
update buttons.  I guess toggles and radio sets will maintain
themselves.

I guess for these reasons, and also for the user who wants to
simultaneously show variant menus, I need

MENU menuCopy (MENU menu) ;