/* mswmenus - Low Level Menu Objects for Microsoft Windows             */
/* 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 "xlisp.h"
#include "xlstat.h"

/* external variables */
extern LVAL s_title, s_items, s_enabled, s_id, s_menu_list,
  s_mark, s_menu, s_menu_proto, s_menu_item_proto, sk_select,
  sk_update, sk_do_action, s_hardware_address;

extern HWND hWndFrame, hWndClient;
extern HMENU hMainMenu, hWinMenu;

/* static variables */
static int InPopup = FALSE, PopupItem = 0;

#define get_menu_id(m) ((int) getfixnum(slot_value(m, s_id)))

#define FIRSTMENU 32
#define MAXITEMS  128
#define LASTMENU  256
#define MENUITEMMENU(x) ((x)/MAXITEMS)
#define MENUITEMITEM(x) ((x)%MAXITEMS)
#define MAKEITEMINDEX(m,i) (((m)*MAXITEMS)+(i))

/* forward declaration */
static char *get_item_string(LVAL item);

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

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

/* find the position of the item in the menu */
static int get_item_position(LVAL menu, LVAL item)
{
  int i;
  LVAL items;
  
  for (items = slot_value(menu, s_items), i = 0;
       consp(items) && car(items) != item; i++, items = cdr(items))
    ;
  if (item != car(items)) xlfail("item not in the menu");
  return(i);
}

/* find index in menu bar; returns -1 if not installed */
static int get_menu_position(HMENU addr)
{
  int n, i;

  if (addr == NULL) return -1;  
  n = GetMenuItemCount(hMainMenu);
  for (i = 0; i < n; i++)
    if (addr == GetSubMenu(hMainMenu, i))
      return(i);
  return(-1);
}

void MSWResetMenus(void)
{
  InPopup = FALSE;
  PopupItem = 0;
}

/* ### this should be in the hardware objects file ### */
static void zero_hardware_address(LVAL menu)
{
  LVAL addr = slot_value(menu, s_hardware_address);
  if (consp(addr) && consp(cdr(addr)))
    car(cdr(addr)) = cvfixnum((FIXTYPE) 0);
}

/***********************************************************************/
/**                                                                   **/
/**                      Event Loop Interface                         **/
/**                                                                   **/
/***********************************************************************/

BOOL IsLispMenuItem(WORD wParam)
{
  LVAL next;
  int m;

  m = MENUITEMMENU(wParam);
  for (next = GetMenuList(); consp(next); next = cdr(next))
    if (StMObAllocated(car(next)) && m == get_menu_id(car(next)))
      return(TRUE);
  return(FALSE);
}

BOOL IsLispMenuHandle(HMENU wParam)
{
  LVAL next;

  for (next = GetMenuList(); consp(next); next = cdr(next))
    if (StMObAllocated(car(next)) && wParam == get_menu_address(car(next)))
      return(TRUE);
  return(FALSE);
}

void LispMenuSelect(WORD wParam)
{
  LVAL next;
  int m, i;

  m = MENUITEMMENU(wParam);
  i = MENUITEMITEM(wParam);
  for (next = GetMenuList(); consp(next); next = cdr(next)) {
    if (StMObAllocated(car(next)) && m == get_menu_id(car(next))) {
      if (InPopup) PopupItem = i + 1;
      else send_callback_message1(car(next), sk_select, i + 1);
      return;
    }
  }
}

void LispMenuUpdate(HMENU wParam)
{
  if (! InPopup) send_callback_message(get_menu_by_hardware(wParam), sk_update);
}

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

int StMObInstalled(LVAL m)
{
  return(StMObAllocated(m) && get_menu_position(get_menu_address(m)) != -1);
}

/* find menu object with given hardware address */
LVAL get_menu_by_hardware(HMENU m)
{
  LVAL menu = NIL, next;

  for (next = GetMenuList();
       menu == NIL && consp(next); next = cdr(next))
    if (StMObAllocated(car(next)) && m == get_menu_address(car(next)))
      menu = car(next);

  if (menu == NIL) xlfail("can't find menu with this handle");
  return(menu);
}

/* allocate an internal menu */
static int id_in_use(int id)
{
  LVAL next;
  
  for (next = GetMenuList(); consp(next); next = cdr(next)) {
    if (id == get_menu_id(car(next))) return(TRUE);
  }
  return(FALSE);
}
  
static int unique_id(void)
{
  int id;

  for (id = FIRSTMENU; id < LASTMENU; id++) {
    if (! id_in_use(id)) return(id);
  }
  xlfail("too many menus");
  return(0);
}

void StMObAllocateMach(LVAL menu)
{
  HMENU theMenu;
  int menuID;

  menuID = unique_id();
  theMenu = CreatePopupMenu();
  if (! theMenu) xlfail("menu allocation failed");
  set_menu_address(theMenu, menu);
  set_slot_value(menu, s_id, cvfixnum((FIXTYPE) menuID));
}

/* dispose of a macintosh menu */
void StMObDisposeMach(LVAL menu)
{
  HWND addr;
  if (StMObInstalled(menu)) StMObRemove(menu);
  if (StMObAllocated(menu)) {
    /* test is needed because menu is destroyed after deleting from menu bar */
    addr = get_menu_address(menu);
    if (addr) DestroyMenu((HMENU) addr);
  }
}

/* add items to a macintosh internal menu */
void StMObAppendItems(LVAL menu, LVAL items)
{
  LVAL item;
  char *s;
  int i, flags, id;
  HMENU theMenu;
  
  if (StMObAllocated(menu)) {
    theMenu = get_menu_address(menu);
    id = get_menu_id(menu);
    i = llength(slot_value(menu, s_items)) - llength(items);
    if (i < 0) xlfail("append list should not exceed item list");
    
    for (; consp(items); items = cdr(items), i++) {
      item = car(items);
      s = get_item_string(item);
      if (s[0] == '-') AppendMenu((HMENU) theMenu, MF_SEPARATOR, 0, NULL);
      else {
	flags = MF_STRING;
	if (slot_value(item, s_mark) != NIL) flags |= MF_CHECKED;
	if (slot_value(item, s_enabled) == NIL) flags |= MF_GRAYED;
	AppendMenu((HMENU) theMenu, flags, MAKEITEMINDEX(id, i), s);
      }
    }
  }
}

/* remove item from a menu */
/* this thing is so involved because the item id's have to be fixed up */
/* to reflect the shift in position */
void StMObDeleteItem(LVAL menu, LVAL item)
{
  HMENU addr;
  int n, i, j, id, flags;
  LVAL items;
  char *s;

  if (StMObAllocated(menu)) {
    addr = get_menu_address(menu);
    id = get_menu_id(menu);
    i = get_item_position(menu, item);
    for (j = 0, items = slot_value(menu, s_items);
	 j < i && consp(items);
	 j++, items = cdr(items));
    n = GetMenuItemCount((HMENU) addr);
    for (; i < n; n--) DeleteMenu((HMENU) addr, i, MF_BYPOSITION);
    if (consp(items)) items = cdr(items);
    for (; consp(items); items = cdr(items), i++) {
      item = car(items);
      s = get_item_string(item);
      if (s[0] == '-') AppendMenu((HMENU) addr, MF_SEPARATOR, 0, NULL);
      else {
	flags = MF_STRING;
	if (slot_value(item, s_mark) != NIL) flags |= MF_CHECKED;
	if (slot_value(item, s_enabled) == NIL) flags |= MF_GRAYED;
	AppendMenu((HMENU) addr, flags, MAKEITEMINDEX(id, i), s);
      }
    }
  }
}

/* install a menu */
void StMObInstall(LVAL menu)
{
  int pos, enabled;
  LVAL title;

  title = slot_value(menu, s_title);
  if (! stringp(title)) xlfail("title must be a string");
  pos = get_menu_position(hWinMenu);
  if (pos != -1 && ! StMObInstalled(menu)) {
    if (! StMObAllocated(menu)) StMObAllocate(menu);
    InsertMenu(hMainMenu,
	       pos,
	       MF_POPUP|MF_BYPOSITION,
	       (UINT) get_menu_address(menu),
	       (char *) getstring(title));
    enabled = (slot_value(menu, s_enabled) != NIL) ? TRUE : FALSE;
    EnableMenuItem(hMainMenu,
		   pos,
		   (enabled ? MF_ENABLED : MF_GRAYED) | MF_BYPOSITION);
    SendMessage(hWndClient, WM_MDISETMENU, (WPARAM)hMainMenu,(LPARAM)hWinMenu);
    DrawMenuBar(hWndFrame);
  }
}

/* remove a menu */
void StMObRemove(LVAL menu)
{
  int m;

  if (! StMObAllocated(menu)) return;
  m = get_menu_position(get_menu_address(menu));
  if (m != -1) {
    DeleteMenu(hMainMenu, m, MF_BYPOSITION);
    SendMessage(hWndClient, WM_MDISETMENU, (WPARAM)hMainMenu,(LPARAM)hWinMenu);
    DrawMenuBar(hWndFrame);
    /*
     * DeleteMenu disposes of the popup menu, so this is added to avoid
     * disposing of an invalid handle -- hopefully it does not cause
     * other problems.
     */
    zero_hardware_address(menu);
  }
  if (StMObAllocated(menu)) StMObDispose(menu);
}

/* enable or disable a menu */
void StMObEnable(LVAL menu, int enable)
{
  int m;
  HMENU addr;

  if (StMObAllocated(menu) && StMObInstalled(menu)) {
    addr = get_menu_address(menu);
    m = get_menu_position(addr);
    EnableMenuItem(hMainMenu,
		   m,
		   (enable ? MF_ENABLED : MF_GRAYED) | MF_BYPOSITION);
    DrawMenuBar(hWndFrame);
  }
  set_slot_value(menu, s_enabled, (enable) ? s_true : NIL);
}

int StMObPopup(LVAL menu, int left, int top, LVAL window)
{
  HMENU theMenu;
  HWND w;
  POINT pt;

  if (window == NIL || (w = GETWINDOWADDRESS(window)) == 0)
    w = hWndFrame;

  pt.x = left; pt.y = top;
  ClientToScreen((HWND) w, &pt);
  left = pt.x; top = pt.y;

  InPopup = TRUE;
  PopupItem = 0;
  StMObAllocate(menu);
  theMenu = get_menu_address(menu);
  if (TrackPopupMenu((HMENU) theMenu, 0, left, top, 0, (HWND) w, NULL)) {
    MSG msg;
    extern HWND hWndClient;
    extern HACCEL hAccel;
    if (PeekMessage(&msg, w, WM_COMMAND, WM_COMMAND, PM_REMOVE) &&
        ! TranslateMDISysAccel(hWndClient, &msg) &&
        ! TranslateAccelerator(hWndFrame, hAccel, &msg)) {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
  }
  StMObDispose(menu);
  InPopup = FALSE;

  return(PopupItem);
}
  
/***********************************************************************/
/**                                                                   **/
/**                         Menu Item Functions                       **/
/**                                                                   **/
/***********************************************************************/

/* Get a string for use by AppendMenu. */
static char *get_item_string(LVAL item)
{
  LVAL title;

  if (! menu_item_p(item)) xlerror("not a menu item", item);
  title = slot_value(item, s_title);
  if (! stringp(title)) xlerror("title is not a string", title);
  return((char *) getstring(title));
}

/* adjust internal implementation of allocated menu to new instance value */
void StMObSetItemProp(LVAL item, int which)
{
  char *s;
  HMENU theMenu;
  LVAL menu, title;
  int i, flags, id;

  menu = slot_value(item, s_menu);
  if (menu != NIL && StMObAllocated(menu)) {
    theMenu = get_menu_address(menu);
    id = get_menu_id(menu);
    i = get_item_position(menu, item);
    title = slot_value(item, s_title);
    if (! stringp(title))
      xlerror("title is not a string", title);
    s = (char *) getstring(title);
    if (s[0] == '-' && which != 'T') return;
    switch (which) {
    case 'T':
      if (s[0] == '-') {
	flags = MF_SEPARATOR | MF_BYPOSITION;
	ModifyMenu((HMENU) theMenu, i, flags, 0, NULL);
      }
      else {
	flags = MF_STRING | MF_BYPOSITION;
	flags |= (slot_value(item, s_mark) != NIL) ? MF_CHECKED : MF_UNCHECKED;
	flags |= (slot_value(item, s_enabled) != NIL) ? MF_ENABLED : MF_GRAYED;
	ModifyMenu((HMENU) theMenu, i, flags, MAKEITEMINDEX(id, i), s);
      }
      break;
    case 'K': break;
    case 'M':
      flags = (slot_value(item, s_mark) != NIL) ? MF_CHECKED : MF_UNCHECKED;
      flags |= MF_BYPOSITION;
      CheckMenuItem((HMENU) theMenu, i, flags);
      break;
    case 'A': break;
    case 'E':
      flags = (slot_value(item, s_enabled) != NIL) ? MF_ENABLED : MF_GRAYED;
      flags |= MF_BYPOSITION;
      EnableMenuItem((HMENU) theMenu, i, flags);
      break;
    default:  xlfail("unknown item instance variable");
    }
  }
}


syntax highlighted by Code2HTML, v. 0.9.1