/* menus - Hardware Independent Menu Objects                           */
/* XLISP-STAT 2.1 Copyright (c) 1990, by Luke Tierney                  */
/* Additions to Xlisp 2.1, Copyright (c) 1989 by David Michael Betz    */
/* You may give out copies of this software; for conditions see the    */
/* file COPYING included with this distribution.                       */
 
/***********************************************************************/
/**                                                                   **/
/**                    General Includes and Definitions               **/
/**                                                                   **/
/***********************************************************************/

#include <string.h>
#include "xlisp.h"
#include "osdef.h"
#ifdef ANSI
#include "xlproto.h"
#include "xlsproto.h"
#include "iviewproto.h"
#include "Stproto.h"
#else
#include "xlfun.h"
#include "xlsfun.h"
#include "iviewfun.h"
#include "Stfun.h"
#endif ANSI
#include "xlvar.h"
#include "xlsvar.h"

/* forward declarations */
#ifdef ANSI
LVAL rplac_end(LVAL,LVAL),remove_from_list(LVAL,LVAL),GetMenuList(void),
     simple_menu_message(int),menu_selector_message(int),
     check_item_ivar(int,LVAL),set_item_ivar(int,LVAL,LVAL),
     get_item_ivar(int,LVAL),item_ivar(int);
void SetMenuList(LVAL),append_items(LVAL,LVAL),delete_menu_item(LVAL,LVAL),
     update_menu(LVAL);
#else
LVAL rplac_end(),remove_from_list(),GetMenuList(),
     simple_menu_message(),menu_selector_message(),
     check_item_ivar(),set_item_ivar(),
     get_item_ivar(),item_ivar();
     void SetMenuList(),append_items(),delete_menu_item(),
     update_menu();
#endif

/***********************************************************************/
/**                                                                   **/
/**                        MENU-PROTO Definitions                     **/
/**                                                                   **/
/***********************************************************************/

# define menu_enabled_p(m) (slot_value(m, s_enabled) != NIL)

/***********************************************************************/
/**                                                                   **/
/**                     MENU-ITEM-PROTO Definitions                   **/
/**                                                                   **/
/***********************************************************************/

# define item_installed_p(i) (slot_value(i, s_menu) != NIL)

/***********************************************************************/
/**                                                                   **/
/**                          Utility Functions                        **/
/**                                                                   **/
/***********************************************************************/

/* append item to the end of list and return result. Cons item to NIL */
/* if list is NIL.                                                    */
LOCAL LVAL rplac_end(list, item)
	LVAL list, item; 
{
  LVAL next; 
  if (list == NIL) return(consa(item));
  else if (listp(list)) {
    for (next = list; consp(cdr(next)); next = cdr(next))
      ;
    rplacd(next, consa(item));
    return(list);
  }
  else xlerror("not a list", list);
}

LOCAL LVAL remove_from_list(item, list)
	LVAL item, list;
{
  return(xscallsubr2(xremove, item, list));
}

/***********************************************************************/
/**                                                                   **/
/**                         Menu List Functions                       **/
/**                                                                   **/
/***********************************************************************/

LOCAL LVAL GetMenuList()
{
  return(slot_value(getvalue(s_menu_proto), s_menu_list));
}

LOCAL void SetMenuList(list)
	LVAL list;
{
  set_slot_value(getvalue(s_menu_proto), s_menu_list, list);
}

/***********************************************************************/
/***********************************************************************/
/**                                                                   **/
/**                          MENU-PROTO Methods                       **/
/**                                                                   **/
/***********************************************************************/
/***********************************************************************/

/***********************************************************************/
/**                                                                   **/
/**                      Hardware Address Functions                   **/
/**                                                                   **/
/***********************************************************************/

/* check if menu is currently allocated. */
int StMObAllocated(menu)
	LVAL menu;
{
  return(valid_menu_address(slot_value(menu, s_hardware_address)));
}  

/***********************************************************************/
/**                                                                   **/
/**               Predicate and Argument Access Function              **/
/**                                                                   **/
/***********************************************************************/

/* Is this a menu? */
int menu_p(x)
	LVAL x;
{
  return (kind_of_p(x, getvalue(s_menu_proto)));
}

/* get and check a menu from the argument list */
LVAL xsgetmenu()
{
  LVAL menu;
  menu = xlgaobject();
  if (! menu_p(menu)) xlerror("not a menu", menu);
  return(menu);
}

/***********************************************************************/
/**                                                                   **/
/**                         Support Functions                         **/
/**                                                                   **/
/***********************************************************************/

/* append list of items to the menu */
static void append_items(menu, new_items)
	LVAL menu, new_items;
{
  LVAL next, item, item_list;
  
  /* Check all items are menu items and not installed */
  for (next = new_items; consp(next); next = cdr(next)) {
    item = car(next);
    if (! menu_item_p(item)) xlerror("not a menu item", item);
    if (item_installed_p(item)) xlerror("item already installed", item);
  }
  
  /* add items to the item list and set items menus to menu */
  for (next = new_items; consp(next); next = cdr(next)) {
    item = car(next);
    item_list = rplac_end(slot_value(menu, s_items), item);
    set_slot_value(menu, s_items,item_list);
    set_slot_value(item, s_menu, menu);
  }
            
  if (StMObAllocated(menu)) StMObAppendItems(menu, new_items);
}

/* delete item from the list */
static void delete_menu_item(menu, item)
	LVAL menu, item;
{
  LVAL item_list;
   
  StMObDeleteItem(menu, item);
  
  item_list = slot_value(menu, s_items);
  item_list = remove_from_list(item, item_list);
  set_slot_value(menu, s_items,item_list);
  set_slot_value(item, s_menu, NIL);
}
   
/* allocate a menu and enter it into the list of allocated menus */
void StMObAllocate(menu)
	LVAL menu;
{
  LVAL menu_list;
  
  StMObDispose(menu);

  StMObAllocateMach(menu);
  
  StMObEnable(menu, menu_enabled_p(menu));
  StMObAppendItems(menu, slot_value(menu, s_items));
    
  menu_list = GetMenuList();
  menu_list = xscallsubr2(xsadjoin, menu, menu_list);
  SetMenuList(menu_list);
}

/* send :UPDATE message to menu items */
static void update_menu(menu)
	LVAL menu;
{
  LVAL list;
  
  for (list = slot_value(menu, s_items); consp(list); list = cdr(list))
    send_message(car(list), sk_update);
}

/* dispose of a menu */
void StMObDispose(menu)
	LVAL menu;
{
  LVAL menu_list;
  
  if (StMObAllocated(menu)) StMObDisposeMach(menu);
  standard_hardware_clobber(menu);

  menu_list = GetMenuList();
  menu_list = remove_from_list(menu, menu_list);
  SetMenuList(menu_list);
}

/* handle simple imperative messages with no arguments */
static LVAL simple_menu_message(which)
	int which;
{
  LVAL menu;
  LVAL arg;
  int set = FALSE;
	
  menu = xlgaobject();
  if (which == 'E') {
    if (moreargs()) {
      set = TRUE;
      arg = (xlgetarg() != NIL) ? s_true : NIL;
    }
  }
  xllastarg();
  
  switch (which) {
  case 'A': StMObAllocate(menu); break;
  case 'D': StMObDispose(menu); break;
  case 'E': if (set) {
              set_slot_value(menu, s_enabled, arg);
              StMObEnable(menu, (arg != NIL));
            }
            return(slot_value(menu, s_enabled));
  case 'I': StMObInstall(menu); break;
  case 'R': StMObRemove(menu); break;
  case 'U': update_menu(menu); break;
  default:  xlfail("unknown message");
  }
  
  return(NIL);
}

/* handle instance variable selectors/status inquiries */
static LVAL menu_selector_message(which)
	int which;
{
  LVAL menu, result;
  
  menu = xlgaobject();
  xllastarg();

  switch (which) {
  case 'A': result = (StMObAllocated(menu)) ? s_true : NIL; break;
  case 'I': result = slot_value(menu, s_items); break;
  case 'i': result = (StMObInstalled(menu)) ? s_true : NIL; break;
  default:  xlfail("unknown message");
  }
  return(result);
}

/***********************************************************************/
/**                                                                   **/
/**                              Methods                              **/
/**                                                                   **/
/***********************************************************************/

/* :ISNEW Method */
LVAL xsmenu_isnew()
{
  LVAL menu, title;
	
  menu = xlgaobject();
  title = xlgastring();
  xllastarg();

  if (strlen(getstring(title)) <= 0) xlerror("title is too short", title);
  
  object_isnew(menu);
  set_slot_value(menu, s_title, title);
  set_slot_value(menu, s_enabled, s_true);

  return(menu);
}

LVAL xsallocate_menu() { return(simple_menu_message('A')); }
LVAL xsdispose_menu()  { return(simple_menu_message('D')); }
LVAL xsupdate_menu()   { return(simple_menu_message('U')); }
LVAL xsallocated_p()  { return(menu_selector_message('A')); }
LVAL xsmenu_items()   { return(menu_selector_message('I')); }

LVAL xsinstall_menu()  { return(simple_menu_message('I')); }
LVAL xsremove_menu()   { return(simple_menu_message('R')); }
LVAL xsinstalled_p()  { return(menu_selector_message('i')); }

LVAL xsmenu_enabled()   { return(simple_menu_message('E')); }

/* :APPEND-ITEMS Method */
LVAL xsappend_items()
{
  LVAL menu, new_items;
	
  xlsave1(new_items);
  menu = xlgaobject();
  new_items = makearglist(xlargc, xlargv);
  append_items(menu, new_items);
  xlpop();
  return(NIL);
}

/* :DELETE-ITEMS Method */
LVAL xsdelete_items()
{
  LVAL menu;
	
  menu = xlgaobject();
  while (moreargs())
    delete_menu_item(menu, xlgaobject());
  return(NIL);
}

/* :SELECT Method */
LVAL xsmenu_select()
{
  LVAL menu, item, next;
  int i;

  menu = xsgetmenu();
  i = getfixnum(xlgafixnum());
  xllastarg();

  for (next = slot_value(menu, s_items);
       i > 1 && consp(next); i--, next = cdr(next))
    ;
  if (! consp(next)) xlfail("no item with this index in the menu");
  else item = car(next);
  
  send_message(item, sk_do_action);
  
  return(NIL);
}

LVAL xsmenu_title()
{
  LVAL menu, title;

  menu = xlgaobject();
  if (moreargs()) {
    title = xlgastring();
    if (strlen(getstring(title)) <= 0)
      xlerror("title is too short", title);
    if (StMObAllocated(menu))
      xlfail("can't change title of an allocated menu");
    set_slot_value(menu, s_title, title);
  }
  return(slot_value(menu, s_title));
}

LVAL xsmenu_popup()
{
  LVAL menu, window;
  int left, top, item;
  
  menu = xsgetmenu();
  left = getfixnum(xlgafixnum());
  top = getfixnum(xlgafixnum());
  window = (moreargs()) ? xlgaobject() : NIL;
  xllastarg();
  
  send_message(menu, sk_update);
  item = StMObPopup(menu, left, top, window);
  if (item > 0) send_message1(menu, sk_select, item);
  return(cvfixnum((FIXTYPE) item));
}
	

/***********************************************************************/
/***********************************************************************/
/**                                                                   **/
/**                     MENU-ITEM-PROTO Methods                       **/
/**                                                                   **/
/***********************************************************************/
/***********************************************************************/

/***********************************************************************/
/**                                                                   **/
/**              Predicate and Argument Access Function               **/
/**                                                                   **/
/***********************************************************************/

/* is this a menu item ? */
int menu_item_p(x)
	LVAL x;
{
  return(kind_of_p(x, getvalue(s_menu_item_proto)));
}

/* get and check a menu item from the argument stack */
LVAL xsgetmenuitem()
{
	LVAL item;
	
	item = xlgaobject();
	if (! menu_item_p(item)) xlerror("not a menu item", item);
	return(item);
}

/***********************************************************************/
/**                                                                   **/
/**                        Support Function                           **/
/**                                                                   **/
/***********************************************************************/

/* check an item instance variable */
static LVAL check_item_ivar(which, value)
	int which;
	LVAL value;
{
  int good;
  
  switch (which) {
  case 'T': good = (stringp(value) && strlen(getstring(value)) > 0); break;
  case 'K': good = (charp(value) || value == NIL); break;
  case 'M': good = (charp(value) || value == NIL || value == s_true); break;
  case 'S': good = (symbolp(value) || listp(value)); break;
  case 'A': good = (value == NIL || symbolp(value) || closurep(value) || subrp(value)); break;
  case 'E': good = TRUE; value = (value != NIL) ? s_true : NIL; break;
  default:  xlfail("unknown item instance variable");
  }
  if (! good) xlerror("bad instance variable value", value);
  return(value);
}

/* set an item instance variable; item and value supplied or on the stack */
static LVAL set_item_ivar(which, item, value)
	int which;
	LVAL item, value;
{
  value = check_item_ivar(which, value);
  
  switch (which) {
  case 'T': set_slot_value(item, s_title, value); break;
  case 'K': set_slot_value(item, s_key, value); break;
  case 'M': set_slot_value(item, s_mark, value); break;
  case 'S': set_slot_value(item, s_style, value); break;
  case 'A': set_slot_value(item, s_action, value); break;
  case 'E': set_slot_value(item, s_enabled, value); break;
  default:  xlfail("unknown item instance variable");
  }
  
  StMObSetItemProp(item, which);
  return(value);
}

/* get an item instance variable; item and value supplied or on the stack */
static LVAL get_item_ivar(which, item)
	int which;
	LVAL item;
{
  LVAL value;
    
  switch (which) {
  case 'T': value = slot_value(item, s_title); break;
  case 'K': value = slot_value(item, s_key); break;
  case 'M': value = slot_value(item, s_mark); break;
  case 'S': value = slot_value(item, s_style); break;
  case 'A': value = slot_value(item, s_action); break;
  case 'E': value = slot_value(item, s_enabled); break;
  default:  xlfail("unknown item instance variable");
  }
  return(check_item_ivar(which, value));
}

static LVAL item_ivar(which)
	int which;
{
  LVAL item;
  
  item = xlgaobject();
  if (moreargs()) set_item_ivar(which, item, xlgetarg());
  return(get_item_ivar(which, item));
}

/***********************************************************************/
/**                                                                   **/
/**                            Methods                                **/
/**                                                                   **/
/***********************************************************************/

/* :ISNEW Method */
LVAL xsitem_isnew()
{ 
  LVAL item, title, value;
  
  item = xlgaobject();
  title = xlgastring();
  
  set_item_ivar('T', item, title);
  object_isnew(item);
  
  if (xlgetkeyarg(sk_enabled, &value)) set_item_ivar('E', item, value);
  else set_item_ivar('E', item, s_true);
  return(NIL);  /* to keep compilers happy - L. Tierney */
}

LVAL xsitem_title()       { return(item_ivar('T')); }
LVAL xsitem_key()         { return(item_ivar('K')); }
LVAL xsitem_mark()        { return(item_ivar('M')); }
LVAL xsitem_style()       { return(item_ivar('S')); }
LVAL xsitem_action()      { return(item_ivar('A')); }
LVAL xsitem_enabled()     { return(item_ivar('E')); }

/* :INSTALLED-P Method */
LVAL xsitem_installed_p() 
{
  LVAL item;
  item = xsgetmenuitem();
  xllastarg();
  
  return((item_installed_p(item)) ? s_true :  NIL);
  
}

LVAL xsitem_update()      { return(NIL); }

/* :DO-ACTION Method */
LVAL xsitem_do_action()
{ 
  LVAL item, action, result;
  item = xsgetmenuitem();
  xllastarg();
  
  action = slot_value(item, s_action);
  result = (action != NIL) ? xlapply(pushargs(action, NIL)) : NIL;
  return(result);
}
