/* Drop-down menus */
/* Copyright (c) 1999, 2000, 2002, 2003, 2004, 2005, 2006 John E. Davis
 * This file is part of JED editor library source.
 *
 * You may distribute this file under the terms the GNU General Public
 * License.  See the file COPYING for more information.
 */
#include "config.h"
#include "jed-feat.h"

#include <stdio.h>
#if JED_HAS_TTY_MENUS

#include <slang.h>

#include "jdmacros.h"

#include <string.h>

#include "buffer.h"
#include "screen.h"
#include "misc.h"
#include "sysdep.h"
#include "cmds.h"
#include "menu.h"
#include "colors.h"

typedef struct _Menu_Node_Type
{
   char *name;
   int type;
#define MENU_NODE_FUN_SLANG	1
#define MENU_NODE_FUN_C		2
#define MENU_NODE_POPUP		3
#define MENU_NODE_SEPARATOR	4
#define MENU_NODE_MENUBAR	5
#define MENU_NODE_KEYSTRING	6

   unsigned int flags;
#define MENU_ACTIVE		1
#define MENU_SELECTION		2
#define MENU_ITEM_UNAVAILABLE	4
#define MENU_POPUP_PREPARED	8
}
Menu_Node_Type;

typedef struct _Menu_Popup_Type
{
   char *name;
   int type;			       /* =MENU_NODE */
   unsigned int flags;

   /* Private data */
   /* These 5 must match Menu_Bar_Type */
   Menu_Node_Type **subnodes;
   unsigned int num_subnodes;
   unsigned int max_subnodes;
   struct _Menu_Popup_Type *parent;
   unsigned int active_node;

   SLang_Name_Type *select_popup_callback;
   SLang_Name_Type *tweak_popup_callback;

   int column;			       /* meaningful when active */
   int row;
   int max_row;
   int max_col;
   unsigned int min_width;
   int visible_node_offset;
}
Menu_Popup_Type;

struct _Menu_Bar_Type
{
   char *name;
   int type;			       /* =MENU_NODE */
   unsigned int flags;
   
   /* Private data */
   /* These 5 must match Menu_Popup_Type */
   Menu_Node_Type **subnodes;
   unsigned int num_subnodes;
   unsigned int max_subnodes;   
   struct _Menu_Popup_Type *parent;   
   unsigned int active_node;

   SLang_Name_Type *init_callback;
   SLang_Name_Type *select_callback;   /* Void select_callback (name) */

   int *item_columns;	       /* locations of items on menu bar */
   unsigned int num_refs;

#define DEFAULT_MENU_PREFIX "F10 key ==> "
   char *prefix_string;
   struct _Menu_Bar_Type *next;
};

typedef struct
{
   char *name;
   int type;
   unsigned int flags;

   SLang_Name_Type *slang_fun;
   SLang_Any_Type *client_data;
}
Menu_SLang_Fun_Type;

typedef struct
{
   char *name;
   int type;
   unsigned int flags;

   int (*c_fun)(void);
}
Menu_C_Fun_Type;

typedef struct
{
   char *name;
   int type;
   unsigned int flags;
   
   char *keystring;
}
Menu_Keystring_Fun_Type;

int Jed_Menus_Active;

static Menu_Bar_Type *Global_Menu_Bar;
static Menu_Bar_Type *Menu_Bar_Root;
static Menu_Bar_Type *Active_Menu_Bar;
static Menu_Popup_Type *Active_Popup;

static void free_menu_node (Menu_Node_Type *);
static int select_menu_cmd (void);

static void free_menu_popup_subnodes (Menu_Popup_Type *m)
{
   Menu_Node_Type **l, **lmax;

   l = m->subnodes;
   lmax = l + m->num_subnodes;

   while (l < lmax)
     {
	free_menu_node (*l);
	l++;
     }
   SLfree ((char *) m->subnodes);

   m->subnodes = NULL;
   m->num_subnodes = 0;
   m->max_subnodes = 0;
}


static void free_menu_popup_private_data (Menu_Popup_Type *m)
{
   free_menu_popup_subnodes (m);
#if SLANG_VERSION > 10400
   SLang_free_function (m->select_popup_callback);
   SLang_free_function (m->tweak_popup_callback);
   m->select_popup_callback = NULL;
   m->tweak_popup_callback = NULL;
#endif
}

static void free_menu_bar_private_data (Menu_Bar_Type *m)
{
   Menu_Node_Type **l, **lmax;

   if (m == Active_Menu_Bar)
     Active_Menu_Bar = NULL;

   if (m == Global_Menu_Bar)
     Global_Menu_Bar = NULL;

   if (m == Menu_Bar_Root)
     Menu_Bar_Root = m->next;
   else
     {
	Menu_Bar_Type *prev;
	
	prev = Menu_Bar_Root;
	while (prev->next != m)
	  prev = prev->next;
	
	prev->next = m->next;
     }

   l = m->subnodes;
   lmax = l + m->num_subnodes;

   while (l < lmax)
     {
	free_menu_node (*l);
	l++;
     }
   SLfree ((char *) m->subnodes);
   SLfree ((char *) m->item_columns);
   SLang_free_slstring (m->prefix_string);
#if SLANG_VERSION > 10400
   SLang_free_function (m->init_callback);
   SLang_free_function (m->select_callback);
#endif

}

static void free_keystring_private_data (Menu_Keystring_Fun_Type *m)
{
   SLang_free_slstring (m->keystring);
}

static void free_slangfun_private_data (Menu_SLang_Fun_Type *m)
{
   if (m->client_data != NULL)
     SLang_free_anytype (m->client_data);
#if SLANG_VERSION > 10400
   SLang_free_function (m->slang_fun);
#endif
}

static void free_menu_node (Menu_Node_Type *m)
{
   Menu_Bar_Type *b;

   if (m == NULL)
     return;

   switch (m->type)
     {
      case MENU_NODE_SEPARATOR:
      case MENU_NODE_FUN_C:
	break;

      case MENU_NODE_FUN_SLANG:
	free_slangfun_private_data ((Menu_SLang_Fun_Type *) m);
	break;

      case MENU_NODE_KEYSTRING:
	free_keystring_private_data ((Menu_Keystring_Fun_Type *) m);
	break;

      case MENU_NODE_MENUBAR:
	b = (Menu_Bar_Type *)m;
	if (b->num_refs > 1)
	  {
	     b->num_refs -= 1;
	     return;
	  }
	free_menu_bar_private_data ((Menu_Bar_Type *) m);
	break;

      case MENU_NODE_POPUP:
	free_menu_popup_private_data ((Menu_Popup_Type *) m);
	break;
     }

   SLang_free_slstring (m->name);
   SLfree ((char *) m);
}

static Menu_Node_Type *create_menu_node (char *name, int node_type,
					 unsigned int sizeof_node)
{
   Menu_Node_Type *m;

   m = (Menu_Node_Type *) jed_malloc0 (sizeof_node);
   if (m == NULL)
     return NULL;

   m->type = node_type;
   
   if (NULL == (m->name = SLang_create_slstring (name)))
     {
	SLfree ((char *)m);
	return NULL;
     }
   
   return m;
}


static Menu_Popup_Type *create_menu_popup (char *name, unsigned int num_items)
{
   Menu_Popup_Type *m;
   
   m = (Menu_Popup_Type *) create_menu_node (name, MENU_NODE_POPUP, sizeof (Menu_Popup_Type));
   if (m == NULL)
     return NULL;

   if (num_items == 0)
     num_items = 5;
   
   if (NULL == (m->subnodes = (Menu_Node_Type **)SLmalloc (num_items * sizeof (Menu_Node_Type *))))
     {
	free_menu_node ((Menu_Node_Type *) m);
	return NULL;
     }
   m->max_subnodes = num_items;
   return m;
}


static Menu_Bar_Type *create_menu_bar (char *name, unsigned int num_items)
{
   Menu_Bar_Type *m;
   char *prefix;

   m = (Menu_Bar_Type *) create_menu_node (name, MENU_NODE_MENUBAR, sizeof (Menu_Bar_Type));
   if (m == NULL)
     return NULL;

   if (num_items == 0)
     num_items = 5;

   if ((NULL == (m->subnodes = (Menu_Node_Type **)SLmalloc (num_items * sizeof (Menu_Node_Type *))))
       || (NULL == (m->item_columns = (int *)jed_malloc0 (num_items * sizeof (int)))))
     {
	free_menu_node ((Menu_Node_Type *) m);
	return NULL;
     }

   m->max_subnodes = num_items;

   if (Menu_Bar_Root != NULL)
     m->next = Menu_Bar_Root;
   Menu_Bar_Root = m;

   m->num_refs = 1;
   
   prefix = DEFAULT_MENU_PREFIX;
   if ((Global_Menu_Bar != NULL)
       && (Global_Menu_Bar->prefix_string != NULL))
     prefix = Global_Menu_Bar->prefix_string;
   
   prefix = SLang_create_slstring (prefix);
   if (prefix == NULL)
     {
	jed_delete_menu_bar (m);
	return NULL;
     }
   m->prefix_string = prefix;

   return m;
}

void jed_delete_menu_bar (Menu_Bar_Type *m)
{
   if (m == NULL)
     return;

   free_menu_node ((Menu_Node_Type *)m);
}


static int menu_name_eqs (char *a, char *b, char *bmax)
{
   while (*a)
     {
	if ((b == bmax)
	    || (*a != *b))
	  return 0;
	
	a++;
	b++;
     }
   
   return (b == bmax);
}

static Menu_Bar_Type *menu_find_menu_bar (char *name, int do_error)
{
   Menu_Bar_Type *b;
   char *name_max;

   name_max = strchr (name, '.');
   if (name_max == NULL)
     name_max = name + strlen (name);

   b = Menu_Bar_Root;
   while (b != NULL)
     {
	if (menu_name_eqs (b->name, name, name_max))
	  return b;

	b = b->next;
     }

   if (do_error)
     SLang_verror (SL_INTRINSIC_ERROR, 
		   "Unable to a find menu bar called %s", 
		   name);
   return NULL;
}


static Menu_Node_Type *find_subnode (Menu_Popup_Type *m, char *name)
{
   Menu_Node_Type **l, **lmax;

   if (m == NULL)
     return NULL;
   
   l = m->subnodes;
   lmax = l + m->num_subnodes;
   
   while (l < lmax)
     {
	if (0 == strcmp (name, (*l)->name))
	  return *l;
	
	l++;
     }
   return NULL;
}


static int check_subnode_space (Menu_Popup_Type *m, unsigned int dn)
{
   Menu_Node_Type **subnodes;
   unsigned int num;

   if (m->num_subnodes + dn <= m->max_subnodes)
     return 0;

   num = m->max_subnodes + dn;

   subnodes = (Menu_Node_Type **)SLrealloc ((char *)m->subnodes, num * sizeof (Menu_Node_Type *));
   if (subnodes == NULL)
     return -1;
   m->subnodes = subnodes;

   if (m->type == MENU_NODE_MENUBAR)
     {
	Menu_Bar_Type *b = (Menu_Bar_Type *)m;
	int *item_columns;
	unsigned int i;

	item_columns = (int *)SLrealloc ((char *)b->item_columns, num * sizeof(int));
	if (item_columns == NULL)
	  return -1;
	
	for (i = m->max_subnodes; i < num; i++)
	  item_columns [i] = 0;
	b->item_columns = item_columns;
     }

   m->max_subnodes = num;
   return 0;
}

/* This function assumes that there is enough space to insert one item */
static void insert_node_to_popup (Menu_Popup_Type *p, Menu_Node_Type *m, 
				  unsigned int where)
{
   unsigned int len;
   unsigned int n;
   
   n = p->num_subnodes;

   if (where > n)
     where = n;

   while (n > where)
     {
	p->subnodes[n] = p->subnodes[n-1];
	n--;
     }
   p->subnodes[where]  = m;
   p->num_subnodes += 1;

   /* p could be a menu-bar */

   if (p->type != MENU_NODE_POPUP)
     return;

   len = strlen (m->name);
   if (len > p->min_width)
     p->min_width = len;
}

#if 0
static void append_node_to_popup (Menu_Popup_Type *p, Menu_Node_Type *m)
{
   insert_node_to_popup (p, m, p->num_subnodes);
}
#endif
   
static Menu_Popup_Type *insert_popup_menu (Menu_Popup_Type *m, char *name, int where)
{
   Menu_Popup_Type *p;

   if (NULL != (p = (Menu_Popup_Type *)find_subnode (m, name)))
     return p;

   if (-1 == check_subnode_space (m, 1))
     return NULL;

   p = create_menu_popup (name, 5);
   if (p == NULL)
     return NULL;
   
   p->parent = m;
   
   insert_node_to_popup (m, (Menu_Node_Type *)p, where);
   return p;
}

static Menu_Popup_Type *append_popup_menu (Menu_Popup_Type *m, char *name)
{
   return insert_popup_menu (m, name, m->num_subnodes);
}


static Menu_Node_Type *insert_separator (Menu_Popup_Type *m, int where)
{
   Menu_Node_Type *l;

   if (-1 == check_subnode_space (m, 1))
     return NULL;

   l = create_menu_node ("", MENU_NODE_SEPARATOR, sizeof (Menu_Node_Type));
   if (l == NULL)
     return NULL;

   insert_node_to_popup (m, l, where);

   return l;
}

static Menu_Node_Type *append_separator (Menu_Popup_Type *m)
{
   return insert_separator (m, m->num_subnodes);
}


static Menu_SLang_Fun_Type *insert_slang_fun_item (Menu_Popup_Type *m, char *name, 
						   SLang_Name_Type *nt, SLang_Any_Type *cd,
						   int where)
{
   Menu_SLang_Fun_Type *l;

   if (NULL != (l = (Menu_SLang_Fun_Type *)find_subnode (m, name)))
     {
	if (l->type != MENU_NODE_FUN_SLANG)
	  return NULL;

	free_slangfun_private_data (l);
	l->slang_fun = nt;
	l->client_data = cd;
	
	return l;
     }

   if (-1 == check_subnode_space (m, 1))
     return NULL;

   l = (Menu_SLang_Fun_Type *)
     create_menu_node (name, MENU_NODE_FUN_SLANG, sizeof (Menu_SLang_Fun_Type));

   if (l == NULL)
     return NULL;

   l->slang_fun = nt;
   l->client_data = cd;

   insert_node_to_popup (m, (Menu_Node_Type *)l, where);
   return l;
}

static Menu_SLang_Fun_Type *append_slang_fun_item (Menu_Popup_Type *m, char *name,
						   SLang_Name_Type *nt, SLang_Any_Type *cd)
{
   return insert_slang_fun_item (m, name, nt, cd, m->num_subnodes);
}

static Menu_Keystring_Fun_Type *insert_keystring_item (Menu_Popup_Type *m, char *name, 
						       char *k, int where)
{
   Menu_Keystring_Fun_Type *l;

   if (NULL != (l = (Menu_Keystring_Fun_Type *)find_subnode (m, name)))
     {
	if (l->type != MENU_NODE_KEYSTRING)
	  return NULL;
	
	if (NULL == (k = SLang_create_slstring (k)))
	  return NULL;

	free_keystring_private_data (l);
	l->keystring = k;
	return l;
     }


   if (-1 == check_subnode_space (m, 1))
     return NULL;

   l = (Menu_Keystring_Fun_Type *)
     create_menu_node (name, MENU_NODE_KEYSTRING, sizeof (Menu_Keystring_Fun_Type));

   if (l == NULL)
     return NULL;

   if (NULL == (l->keystring = SLang_create_slstring (k)))
     {
	free_menu_node ((Menu_Node_Type *) l);
	return NULL;
     }

   insert_node_to_popup (m, (Menu_Node_Type *)l, where);
   return l;
}

static Menu_Keystring_Fun_Type *append_keystring_item (Menu_Popup_Type *m, char *name, char *k)
{
   return insert_keystring_item (m, name, k, m->num_subnodes);
}

static Menu_Node_Type *get_selected_menu_item (Menu_Popup_Type *p)
{
   Menu_Node_Type *m;

   if (p == NULL)
     return NULL;

   if (p->num_subnodes <= p->active_node)
     {
	p->active_node = 0;
	return NULL;
     }

   m = p->subnodes[p->active_node];
   if (m->flags & MENU_ITEM_UNAVAILABLE)
     {
	p->active_node = 0;
	return NULL;
     }

   /* Make sure this is selected. */
   m->flags |= MENU_SELECTION;
   return m;
}

static int unselect_active_node (Menu_Popup_Type *b)
{
   Menu_Node_Type *m;
   
   if (NULL == (m = get_selected_menu_item (b)))
     return -1;

   m->flags &= ~(MENU_SELECTION|MENU_ACTIVE);
   return 0;
}

static int select_next_active_node (Menu_Popup_Type *b, unsigned int active_node,
				    unsigned int flags)
{
   int wrapped;
   unsigned int num_subnodes;

   if (b == NULL)
     return -1;

   num_subnodes = b->num_subnodes;

   if (num_subnodes == 0)
     {
	b->active_node = 0;
	return -1;
     }

   unselect_active_node (b);

   wrapped = 0;

   while (1)
     {
	Menu_Node_Type *node;

	active_node++;
	if (active_node >= num_subnodes)
	  {
	     active_node = 0;
	     if (wrapped)
	       return -1;

	     wrapped = 1;
	  }

	node = b->subnodes[active_node];
	if ((node->type != MENU_NODE_SEPARATOR)
	    && (0 == (node->flags & MENU_ITEM_UNAVAILABLE)))
	  {
	     node->flags |= MENU_SELECTION|flags;
	     break;
	  }
     }

   b->active_node = active_node;
   return 0;
}

static int select_prev_active_node (Menu_Popup_Type *b, unsigned int active_node,
				    unsigned int flags)
{
   int wrapped;
   unsigned int num_subnodes;

   if (b == NULL)
     return -1;

   num_subnodes = b->num_subnodes;

   if (num_subnodes == 0)
     {
	b->active_node = 0;
	return -1;
     }

   unselect_active_node (b);

   wrapped = 0;

   while (1)
     {
	Menu_Node_Type *node;

	if (active_node == 0)
	  {
	     active_node = num_subnodes;
	     if (wrapped)
	       return -1;
	     
	     wrapped = 1;
	  }
	active_node--;

	node = b->subnodes[active_node];
	if ((node->type != MENU_NODE_SEPARATOR)
	    && (0 == (node->flags & MENU_ITEM_UNAVAILABLE)))
	  {
	     node->flags |= MENU_SELECTION|flags;
	     break;
	  }
     }

   b->active_node = active_node;
   return 0;
}



static int set_node_selection (Menu_Popup_Type *p, Menu_Node_Type *m)
{  
   unsigned int i, imax;
   Menu_Node_Type **subnodes;

   if ((p == NULL) || (m == NULL)
       || (m->type == MENU_NODE_SEPARATOR)
       || (m->flags & MENU_ITEM_UNAVAILABLE))
     return -1;

   subnodes = p->subnodes;
   imax = p->num_subnodes;
   for (i = 0; i < imax; i++)
     {
	if (subnodes[i] == m)
	  {
	     unselect_active_node (p);
	     p->active_node = i;
	     m->flags |= MENU_SELECTION;
	     return 0;
	  }
     }
   return -1;
}

   
static unsigned int get_active_node_flags (Menu_Popup_Type *p)
{
   Menu_Node_Type *m;
   
   m = get_selected_menu_item (p);
   if (m == NULL)
     return 0;
   
   return m->flags;
}

static int menu_delete_node (Menu_Popup_Type *p, Menu_Node_Type *m)
{
   unsigned int i, imax;

   if ((p == NULL) || (m == NULL))
     return -1;
   
   imax = p->num_subnodes;
   for (i = 0; i < imax; i++)
     {
	if (p->subnodes[i] != m)
	  continue;
	
	if (i == p->active_node)
	  p->active_node = 0;
	
	while (++i < imax)
	  p->subnodes[i-1] = p->subnodes[i];
	
	p->num_subnodes -= 1;
	
	(void) unselect_active_node (p);
	free_menu_node (m);
	return 0;
     }
   return -1;
}

static int menu_delete_nodes (Menu_Popup_Type *p)
{
   unsigned int i, imax;

   if (p == NULL)
     return -1;

   imax = p->num_subnodes;
   for (i = 0; i < imax; i++)
     free_menu_node (p->subnodes[i]);

   p->num_subnodes = 0;
   p->active_node = 0;
   return 0;
}


static int copy_menu (Menu_Popup_Type *dest, Menu_Node_Type *src)
{
   Menu_Node_Type **l, **lmax;
   Menu_Popup_Type *p;
   Menu_Node_Type *m;
   SLang_Any_Type *any;
   Menu_SLang_Fun_Type *sl;

   if (src == (Menu_Node_Type *)dest)
     {
	SLang_verror (SL_INTRINSIC_ERROR,
		      "Unable to copy a menu onto itself");
	return -1;
     }

   switch (src->type)
     {
      case MENU_NODE_POPUP:
	p = (Menu_Popup_Type *) src;
	l = p->subnodes;
	lmax = l + p->num_subnodes;
	
	p = append_popup_menu (dest, src->name);
	if (p == NULL)
	  return -1;
	
	while (l < lmax)
	  {
	     if (-1 == copy_menu (p, *l))
	       {
		  menu_delete_node (dest, (Menu_Node_Type *)p);
		  return -1;
	       }
	     l++;
	  }
	m = (Menu_Node_Type *)p;
	break;
	
      case MENU_NODE_SEPARATOR:
	m = append_separator (dest);
	break;
	
      case MENU_NODE_FUN_SLANG:
	sl = (Menu_SLang_Fun_Type *) src;
	/* Need a s-lang fun for this !!! */
	if ((-1 == SLang_push_anytype (sl->client_data))
	    || (-1 == SLang_pop_anytype (&any)))
	  return -1;

	m = (Menu_Node_Type *) append_slang_fun_item (dest, sl->name,
							   sl->slang_fun, any);
	if (m == NULL)
	  SLang_free_anytype (any);
	break;
	
      case MENU_NODE_KEYSTRING:
	m = (Menu_Node_Type *) append_keystring_item (dest, src->name, ((Menu_Keystring_Fun_Type *)src)->keystring);
	break;
	
      case MENU_NODE_MENUBAR:
	SLang_verror (SL_INTRINSIC_ERROR, "Unable to copy a menu-bar");
	return -1;

      case MENU_NODE_FUN_C:
      default:
	SLang_verror (SL_APPLICATION_ERROR,
		      "Menu Type %d not supported", src->type);
	return -1;
     }
   
   if (m == NULL)
     return -1;
   
   m->flags = src->flags;
   return 0;
}


/*
 *  SLsmg MenuBar interface
 */

static void simulate_hline (unsigned int n)
{
   while (n--)
     SLsmg_write_string ("-");
}

static void simulate_box (int r, int c, unsigned int dr, unsigned int dc)
{
   int rr, rmax;

   if ((dr < 1) || (dc < 2)) return;

   dr--; 
   dc--;
   SLsmg_gotorc (r, c);
   SLsmg_write_string ("+");
   simulate_hline (dc-1);
   SLsmg_write_string ("+");
   SLsmg_gotorc (r + dr, c);
   SLsmg_write_string ("+");
   simulate_hline (dc-1);
   SLsmg_write_string ("+");
   
   rmax = r + dr;
   for (rr = r + 1; rr < rmax; rr++)
     {
	SLsmg_gotorc (rr, c);
	SLsmg_write_string ("|");
	SLsmg_gotorc (rr, c + dc);
	SLsmg_write_string ("|");
     }
}

/* I would prefer to use real up/down arrows but I cannot depend upon their
 * portability.
 */
static void draw_up_arrow (int r, int c)
{
   SLsmg_gotorc (r, c);
   SLsmg_write_string ("(-)");
}

static void draw_down_arrow (int r, int c)
{
   SLsmg_gotorc (r, c);
   SLsmg_write_string ("(+)");
}

static void draw_name (char *name, int color0, int color1, unsigned int field_width)
{
   char *s;
   unsigned int n;

   s = name;

   while (*s && (*s != '&'))
     s++;

   n = (unsigned int) (s - name);
   if (n)
     {
	SLsmg_set_color (color0);
	SLsmg_write_nchars (name, n);
     }

   if (*s != 0)
     {
	unsigned int dn;

	s++;
	SLsmg_set_color (color1);
	SLsmg_write_nchars (s, 1);
	n++;
	SLsmg_set_color (color0);
	if (*s != 0)
	  {
	     s++;
	     dn = strlen (s);
	     SLsmg_write_nchars (s, dn);
	     n += dn;
	  }
     }

   if (n < field_width)
     SLsmg_write_nstring ("", field_width - n);
}

	  
static int draw_menu_bar (Menu_Bar_Type *b)
{
   Menu_Node_Type **m;
   unsigned int i, imax;
   int active;

   SLsmg_gotorc (0, 0);

   SLsmg_set_color (JMENU_COLOR);
   SLsmg_write_string (b->prefix_string);

   m = b->subnodes;
   imax = b->num_subnodes;
   active = -1;

   for (i = 0; i < imax; i++)
     {
	Menu_Popup_Type *p;
	char *name;

	p = (Menu_Popup_Type *) m[i];
	if (p->flags & MENU_ITEM_UNAVAILABLE)
	  continue;

	b->item_columns[i] = SLsmg_get_column ();

	if (p->type == MENU_NODE_SEPARATOR)
	  name = "          ";
	else
	  name = p->name;

	if (p->flags & MENU_SELECTION)
	  {
	     if (p->type == MENU_NODE_POPUP)
	       active = i;

	     draw_name (name, JMENU_SELECTION_COLOR, JMENU_SELECTED_CHAR_COLOR, 0);
	  }
	else
	  draw_name (name, JMENU_COLOR, JMENU_CHAR_COLOR, 0);

	SLsmg_set_color (JMENU_COLOR);
	SLsmg_write_string ("   ");
     }

   SLsmg_set_color (JMENU_COLOR);
   SLsmg_erase_eol ();
   
   return active;
}

static int draw_keystring (Menu_Keystring_Fun_Type *k, int color0, int color1, unsigned int field_width)
{
   int i;
   SLang_Key_Type *key, *key_root;
   FVOID_STAR fp;
   unsigned char type;
   char buf[3];
   unsigned int best_len;
   char *best_keystring;
   SLang_Key_Type *best_key;
   char *name;

   draw_name (k->name, color0, color1, field_width);

   name = k->keystring;
   /* Now try to draw the binding */

   if (NULL == (fp = (FVOID_STAR) SLang_find_key_function(name, CBuf->keymap)))
     type = SLKEY_F_INTERPRET;
   else type = SLKEY_F_INTRINSIC;

   best_keystring = NULL;
   best_len = 0xFFFF;
   best_key = NULL;

   key_root = CBuf->keymap->keymap;

   for (i = 0; i < 256; i++, key_root++)
     {
#ifdef IBMPC_SYSTEM
	if ((i == 0) || (i == 0xE0))
	  continue;
#endif

	key = key_root->next;
	if ((key == NULL) && (type == key_root->type))
	  {
	     if (type == SLKEY_F_INTERPRET)
	       {
		  if (strcmp (name, key_root->f.s))
		    continue;
	       }
	     else if ((type != SLKEY_F_INTRINSIC) || (fp != key_root->f.f))
	       continue;

	     buf[0] = i;
	     buf[1] = 0;
#ifndef IBMPC_SYSTEM
	     if (i == 0)
	       {
		  buf[0] = '^';
		  buf[1] = '@';
		  buf[2] = 0;
	       }
#endif
	     best_keystring = buf;
	     break;
	  }

	while (key != NULL)
	  {
	     char *s;
	     SLang_Key_Type *this_key = key;
	     
	     key = key->next;

	     if (this_key->type != type)
	       continue;

	     if (type == SLKEY_F_INTERPRET)
	       {
		  if (strcmp (name, this_key->f.s))
		    continue;
	       }
	     else if ((type != SLKEY_F_INTRINSIC) || (fp != this_key->f.f))
	       continue;

	     s = SLang_make_keystring (this_key->str);
	     if (s == NULL)
	       continue;

	     if (strlen (s) < best_len)
	       {
		  best_key = this_key;
		  best_len = strlen (s);
	       }
	  }
     }
   
   if (best_keystring == NULL)
     {
	if (best_key == NULL)
	  return 0;
	
	best_keystring = SLang_make_keystring (best_key->str);
	if (best_keystring == NULL)
	  return 0;
     }
   
   best_len = strlen (best_keystring);
   if (best_len > 4)
     return 0;

   SLsmg_forward (-4);
   SLsmg_set_color (color0);
   SLsmg_write_nchars (best_keystring, best_len);
   return 0;
}

static int draw_popup_menu (Menu_Popup_Type *p, int r, int c)
{
   int active_row, active_col;
   unsigned int dr, dc;
   Menu_Node_Type **l, **lmax;
   Menu_Popup_Type *p_active;

   Active_Popup = p;

   if (r == 0)
     r = 1;
   
   dr = p->num_subnodes + 2;
   dc = p->min_width + 5 + 4;	       /* allow room for keystring */
   
   if (c + (int)dc >= Jed_Num_Screen_Cols)
     {
	if ((int)dc > Jed_Num_Screen_Cols)
	  c = 0;
	else
	  c = Jed_Num_Screen_Cols - (int)dc;
     }

   if (r + (int)dr >= Jed_Num_Screen_Rows)
     {
	if ((int)dr >= Jed_Num_Screen_Rows)
	  {
	     r = 1;
	     dr = Jed_Num_Screen_Rows - 1;
	  }
	else
	  r = Jed_Num_Screen_Rows - (int)dr;
     }

   SLsmg_set_color_in_region (JMENU_SHADOW_COLOR, r + 1, c + 1, dr, dc);
   SLsmg_set_color (JMENU_POPUP_COLOR);
   /* SLsmg_fill_region (r, c, dr, dc, ' '); */
   if (Jed_Simulate_Graphic_Chars)
     simulate_box (r, c, dr, dc);
   else
     SLsmg_draw_box (r, c, dr, dc);

   p->row = r;
   p->column = c;
   p->max_row = r + dr;
   p->max_col = c + dc;

   /* Make sure a selection is present */
   (void) get_selected_menu_item (p);

   l = p->subnodes;
   lmax = l + p->num_subnodes;
   
   if (p->num_subnodes + 2 > dr)
     {
	unsigned int page;
	unsigned int nr = dr - 2;
	unsigned int col;
	
	col = c + dc - 4;
	page = p->active_node / nr;
	if (page)
	  {
	     l += page * nr;
	     draw_up_arrow (r, col);
	  }
	if (lmax > l + nr)
	  {
	     lmax = l + nr;
	     draw_down_arrow (r + dr - 1, col);
	  }
	else
	  l = lmax - nr;
     }
   
   p->visible_node_offset = (int) (l - p->subnodes);

   c++;
   r++;
   p_active = NULL;
   active_row = r;
   active_col = c;

   dc -= 3;

   while (l < lmax)
     {
	Menu_Node_Type *m;
	int color0, color1;

	m = *l;

	SLsmg_gotorc (r, c);

	color0 = JMENU_POPUP_COLOR;
	color1 = JMENU_CHAR_COLOR;
	
	if (m->flags & MENU_SELECTION)
	  {
	     active_row = r;
	     active_col = c + dc;
	     color0 = JMENU_SELECTION_COLOR;
	     color1 = JMENU_SELECTED_CHAR_COLOR;
	  }

	if ((m->flags & MENU_ACTIVE)
	    && (m->type == MENU_NODE_POPUP))
	  {
	     p_active = (Menu_Popup_Type *)m;
	     p_active->row = active_row;
	     p_active->column = active_col - dc/2;
	  }

	SLsmg_set_color (color0);
	switch (m->type)
	  {
	   case MENU_NODE_SEPARATOR:
	     SLsmg_write_nchars (" ", 1);
	     if (Jed_Simulate_Graphic_Chars)
	       simulate_hline (dc - 1);
	     else
	       SLsmg_draw_hline (dc-1);
	     SLsmg_write_nchars (" ", 1);
	     break;
	     
	   case MENU_NODE_POPUP:
	     SLsmg_write_nchars (" ", 1);
	     draw_name (m->name, color0, color1, dc);
	     SLsmg_gotorc (r, c + (dc - 2));
	     SLsmg_write_nchars (">>>", 3);
	     break;

	   case MENU_NODE_KEYSTRING:
	     SLsmg_write_nchars (" ", 1);
	     draw_keystring ((Menu_Keystring_Fun_Type *)m, color0, color1, dc);
	     break;

	   default:
	     SLsmg_write_nchars (" ", 1);
	     draw_name (m->name, color0, color1, dc);
	     break;
	  }
	l++;
	r++;
     }
   
   if (p_active != NULL)
     return draw_popup_menu (p_active, p_active->row, p_active->column);
   
   SLsmg_gotorc (active_row, active_col);
   return 0;
}

static Menu_Bar_Type *get_active_menubar (void)
{
   Menu_Bar_Type *b;

   /* Active_Popup = NULL; */
   b = Active_Menu_Bar;

   if (b == NULL)
     {
	Buffer *buf = CBuf;

	if (IS_MINIBUFFER)
	  {
	     if (NULL == (buf = jed_get_mini_action_buffer ()))
	       return NULL;
	  }

	if (NULL == (b = buf->menubar))
	  b = Global_Menu_Bar;

	if (b == NULL)
	  return NULL;
	
     }
   /* Active_Popup = (Menu_Popup_Type *) b; */
   return b;
}

int jed_redraw_menus (void)
{
   Menu_Popup_Type *p;
   Menu_Bar_Type *b;
   int active;

#if 0
   if (NULL == (b = CBuf->menubar))
     b = Global_Menu_Bar;
#else
   b = get_active_menubar ();
#endif

   Active_Popup = (Menu_Popup_Type *) b;

   if (b == NULL)
     {
	SLsmg_gotorc (0, 0);
	SLsmg_set_color (0);
	SLsmg_erase_eol ();
	return 0;
     }
   
   active = draw_menu_bar (b);
   if (active == -1)
     return 0;

   touch_screen ();

   p = (Menu_Popup_Type *)b->subnodes[active];

   if (0 == (p->flags & MENU_ACTIVE))
     {
	SLsmg_gotorc (0, b->item_columns[active]);
	return 1;
     }

   draw_popup_menu (p, 0, b->item_columns[active]);
   return 1;
}


/* Keyboard Interface */

static int find_active_popup (void)
{
   Menu_Popup_Type *p;

   if (Active_Menu_Bar == NULL)
     return -1;
   
   p = (Menu_Popup_Type *) Active_Menu_Bar;
   
   while (1)
     {
	Active_Popup = p;
	p = (Menu_Popup_Type *) get_selected_menu_item (p);

	if ((p == NULL) || (p->type != MENU_NODE_POPUP))
	  return 0;

	if (0 == (p->flags & MENU_ACTIVE))
	  return 0;

	if (0 == (p->flags & MENU_POPUP_PREPARED))
	  return select_menu_cmd ();
     }
}
	
	
static int next_menubar_cmd (void)
{
   unsigned int flags;
   Menu_Popup_Type *p;
   
   if (NULL == (p = (Menu_Popup_Type *) Active_Menu_Bar))
     return -1;

   flags = get_active_node_flags (p) & MENU_ACTIVE;
   
   /* (void) unselect_active_node ((Menu_Popup_Type *)Active_Menu_Bar); */
   (void) select_next_active_node (p, p->active_node, flags);
   (void) find_active_popup ();
   return 1;
}

static int prev_menubar_cmd (void)
{
   unsigned int flags;
   Menu_Popup_Type *p;
   
   if (NULL == (p = (Menu_Popup_Type *) Active_Menu_Bar))
     return -1;

   flags = get_active_node_flags (p) & MENU_ACTIVE;
   /* (void) unselect_active_node ((Menu_Popup_Type *)Active_Menu_Bar); */
   (void) select_prev_active_node (p, p->active_node, flags);
   (void) find_active_popup ();
   return 1;
}

int jed_exit_menu_bar (void)
{
   while (Active_Popup != NULL)
     {
	unselect_active_node (Active_Popup);
	Active_Popup->active_node = 0;
	Active_Popup->flags &= ~MENU_POPUP_PREPARED;
	Active_Popup = Active_Popup->parent;
     }

   Active_Menu_Bar = NULL;
   Jed_Menus_Active = 0;
   return 1;
}

static int down_menu_cmd (void)
{
   if (Active_Popup == NULL)
     return -1;
   
   /* unselect_active_node (Active_Popup); */
   select_next_active_node (Active_Popup, Active_Popup->active_node, 0);
   return 1;
}

static int up_menu_cmd (void)
{
   if (Active_Popup == NULL)
     return -1;
   
   /* unselect_active_node (Active_Popup); */
   select_prev_active_node (Active_Popup, Active_Popup->active_node, 0);
   return 1;
}

static int pgup_menu_cmd (void)
{
   if (Active_Popup == NULL)
     return -1;

   select_next_active_node (Active_Popup, Active_Popup->num_subnodes, 0);
   return 1;
}

static int pgdn_menu_cmd (void)
{
   if (Active_Popup == NULL)
     return -1;

   select_prev_active_node (Active_Popup, 0, 0);
   return 1;
}

static int execute_keystring (char *s)
{
   int (*f)(void);

   if (NULL != (f = (int (*)(void)) (SLang_find_key_function(s, CBuf->keymap))))
     return (*f) ();
   
   if ((*s == '.')
       || !SLang_execute_function(s))
     SLang_load_string(s);

   return 1;
}

static char *get_full_popup_name (Menu_Popup_Type *p)
{
   Menu_Popup_Type *parent;
   unsigned int len;
   char *name;
   char *s;

   len = strlen (p->name);
   parent = p->parent;
   while (parent != NULL)
     {
	len += 1 + strlen (parent->name);
	parent = parent->parent;
     }
   
   name = SLmalloc (len + 1);
   if (name == NULL)
     return NULL;
   
   s = (name + len) - strlen (p->name);
   strcpy (s, p->name);
   parent = p->parent;
   while (parent != NULL)
     {
	s--;
	*s = '.';
	len = strlen (parent->name);
	s -= len;
	strncpy (s, parent->name, len);
	parent = parent->parent;
     }
   
   return name;
}

static int run_popup_callback (Menu_Popup_Type *p, SLang_Name_Type *cb)
{
   char *name;

   if (NULL == (name = get_full_popup_name (p)))
     return -1;

   if (-1 == SLang_push_string (name))
     {
	SLfree (name);
	return -1;
     }
   SLfree (name);

   if (0 != SLexecute_function (cb))
     return -1;

   return 0;
}

   
static int prepare_popup (Menu_Popup_Type *p)
{
   SLang_Name_Type *cb;

   if (NULL != (cb = p->select_popup_callback))
     {
	free_menu_popup_subnodes (p);
	if (-1 == run_popup_callback (p, cb))
	  return -1;
     }
   
   if (NULL != (cb = p->tweak_popup_callback))
     {
	if (-1 == run_popup_callback (p, cb))
	  return -1;
     }
   
   p->flags |= MENU_POPUP_PREPARED;
   return 0;
}


static int select_menu_cmd (void)
{
   Menu_Node_Type *m;
   Menu_Popup_Type *p;
   Menu_C_Fun_Type *c;
   SLang_Name_Type *nt;
   char *str;

   m = get_selected_menu_item (Active_Popup);
   if (m == NULL)
     return -1;

   switch (m->type)
     {
      case MENU_NODE_POPUP:
#if 0
	m->flags |= MENU_ACTIVE;
	p = (Menu_Popup_Type *) m;
#else
	/* Do it this way to avoid DEC C ansi-aliasing warning. */
	p = (Menu_Popup_Type *) m;
	p->flags |= MENU_ACTIVE;
#endif
	if (-1 == prepare_popup (p))
	  return -1;
	Active_Popup = p;
	select_next_active_node (p, p->num_subnodes, 0);
	return 1;

      case MENU_NODE_FUN_SLANG:
	nt = ((Menu_SLang_Fun_Type *) m)->slang_fun;
	jed_exit_menu_bar ();
	if (nt == NULL)
	  return -1;
	if ((-1 == SLang_start_arg_list ())
	    || (-1 == SLang_push_anytype (((Menu_SLang_Fun_Type*)m)->client_data))
	    || (-1 == SLang_end_arg_list ()))
	  return -1;

	return SLexecute_function (nt);

      case MENU_NODE_KEYSTRING:
	str = ((Menu_Keystring_Fun_Type *) m)->keystring;
	/* Grab a copy while it is still available */
	if (NULL == SLang_create_slstring (str))
	  return -1;
	jed_exit_menu_bar ();
	(void) execute_keystring (str);
	SLang_free_slstring (str);
	break;

      case MENU_NODE_FUN_C:
	c = (Menu_C_Fun_Type *) m;
	jed_exit_menu_bar ();
	if (c->c_fun != NULL)
	  return -1;
	return c->c_fun ();
	
     }
   return 0;
}

static int back_menu_cmd (void)
{
   if ((Active_Popup == NULL)
       || (Active_Popup == (Menu_Popup_Type *)Active_Menu_Bar)
       || (Active_Popup->parent == (Menu_Popup_Type *) Active_Menu_Bar))
     return jed_exit_menu_bar ();

   Active_Popup->flags &= ~(MENU_ACTIVE|MENU_POPUP_PREPARED);
   Active_Popup = Active_Popup->parent;
   return 1;
}

static Menu_Node_Type *find_subnode_via_char (Menu_Popup_Type *p, char ch)
{
   Menu_Node_Type **l, **lmax;
   unsigned int two;

   if (p == NULL)
     return NULL;
   
   two = 2;

   while (two)
     {
	l = p->subnodes;
	lmax = l + p->num_subnodes;
   
	while (l < lmax)
	  {
	     Menu_Node_Type *m;
	     char *s;
	     
	     m = *l++;
	     
	     if (m->type == MENU_NODE_SEPARATOR)
	       continue;
	     
	     if (m->flags & MENU_ITEM_UNAVAILABLE)
	       continue;

	     s = strchr (m->name, '&');
	     if (s == NULL)
	       continue;
	
	     if (s[1] == ch)
	       return m;
	  }
	
	ch = (char) CHANGE_CASE(ch);
	two--;
     }
   
   return NULL;
}

static int select_via_char_cmd (void)
{
   Menu_Node_Type *m;
   
   m = find_subnode_via_char (Active_Popup, (char) SLang_Last_Key_Char);
   if (m == NULL)
     return -1;

   set_node_selection (Active_Popup, m);
   return select_menu_cmd ();
}

   
static SLKeyMap_List_Type *Menu_Keymap;

static SLKeymap_Function_Type Menu_Keymap_Functions [] =
{
     {"next_menubar_menu", next_menubar_cmd},
     {"prev_menubar_menu", prev_menubar_cmd},
     {"exit_menubar",	jed_exit_menu_bar},
     {"next_menu_item",	down_menu_cmd},
     {"prev_menu_item",	up_menu_cmd},
     {"top_menu_item",	pgup_menu_cmd},
     {"bot_menu_item",	pgdn_menu_cmd},
     {"select_menu_item", select_menu_cmd},
     {"back_menu",	back_menu_cmd},

     {NULL, NULL}
};

#ifndef IBMPC_SYSTEM
# ifdef HAS_MOUSE
static int xterm_mouse_cmd (void);
# endif
#endif

static int init_menu_keymap (void)
{
   int ch;
   char simple[3];

   if (NULL == (Menu_Keymap = SLang_create_keymap ("menu", NULL)))
     return -1;

   Menu_Keymap->functions = Menu_Keymap_Functions;
   
   simple[1] = 0;
   for (ch = 0; ch < 256; ch++)
     {
	simple[0] = (char) ch;
	SLkm_define_key (simple, (FVOID_STAR) select_via_char_cmd, Menu_Keymap);
     }
   simple [0] = 27;
   simple [2] = 0;
   for (ch = 'a'; ch <= 'z'; ch++)
     {
	simple [1] = (char) ch;
	SLkm_define_key (simple, (FVOID_STAR) select_via_char_cmd, Menu_Keymap);
     }

   
   SLkm_define_key (" ", (FVOID_STAR) back_menu_cmd, Menu_Keymap);
   SLkm_define_key ("^?", (FVOID_STAR) back_menu_cmd, Menu_Keymap);
   SLkm_define_key ("^H", (FVOID_STAR) back_menu_cmd, Menu_Keymap);
   SLkm_define_key ("^G", (FVOID_STAR) jed_exit_menu_bar, Menu_Keymap);
   SLkm_define_key ("^Z", (FVOID_STAR) sys_spawn_cmd, Menu_Keymap);
   SLkm_define_key ("^M", (FVOID_STAR) select_menu_cmd, Menu_Keymap);

#ifndef IBMPC_SYSTEM
   SLkm_define_key ("\033[A", (FVOID_STAR) up_menu_cmd, Menu_Keymap);
   SLkm_define_key ("\033OA", (FVOID_STAR) up_menu_cmd, Menu_Keymap);
   SLkm_define_key ("\033[B", (FVOID_STAR) down_menu_cmd, Menu_Keymap);
   SLkm_define_key ("\033OB", (FVOID_STAR) down_menu_cmd, Menu_Keymap);
   SLkm_define_key ("\033[C", (FVOID_STAR) next_menubar_cmd, Menu_Keymap);
   SLkm_define_key ("\033OC", (FVOID_STAR) next_menubar_cmd, Menu_Keymap);
   SLkm_define_key ("\033[D", (FVOID_STAR) prev_menubar_cmd, Menu_Keymap);
   SLkm_define_key ("\033OD", (FVOID_STAR) prev_menubar_cmd, Menu_Keymap);
   SLkm_define_key ("\033[5~", (FVOID_STAR) pgup_menu_cmd, Menu_Keymap);
   SLkm_define_key ("\033[6~", (FVOID_STAR) pgdn_menu_cmd, Menu_Keymap);
# ifdef __unix__
   SLkm_define_key ("^(ku)", (FVOID_STAR) up_menu_cmd, Menu_Keymap);
   SLkm_define_key ("^(kd)", (FVOID_STAR) down_menu_cmd, Menu_Keymap);
   SLkm_define_key ("^(kr)", (FVOID_STAR) next_menubar_cmd, Menu_Keymap);
   SLkm_define_key ("^(kl)", (FVOID_STAR) prev_menubar_cmd, Menu_Keymap);
   SLkm_define_key ("^(kP)", (FVOID_STAR) pgup_menu_cmd, Menu_Keymap);
   SLkm_define_key ("^(kN)", (FVOID_STAR) pgdn_menu_cmd, Menu_Keymap);
# endif
# ifdef HAS_MOUSE
   SLkm_define_key ("\033[M", (FVOID_STAR) xterm_mouse_cmd, Menu_Keymap);
# endif
#else				       /* IBMPC_SYSTEM */
#include "doskeys.h"
   
   SLkm_define_key (PC_UP, (FVOID_STAR) up_menu_cmd, Menu_Keymap);
   SLkm_define_key (PC_UP1, (FVOID_STAR) up_menu_cmd, Menu_Keymap);
   SLkm_define_key (PC_DN, (FVOID_STAR) down_menu_cmd, Menu_Keymap);
   SLkm_define_key (PC_DN1, (FVOID_STAR) down_menu_cmd, Menu_Keymap);
   SLkm_define_key (PC_RT, (FVOID_STAR) next_menubar_cmd, Menu_Keymap);
   SLkm_define_key (PC_RT1, (FVOID_STAR) next_menubar_cmd, Menu_Keymap);
   SLkm_define_key (PC_LT, (FVOID_STAR) prev_menubar_cmd, Menu_Keymap);
   SLkm_define_key (PC_LT1, (FVOID_STAR) prev_menubar_cmd, Menu_Keymap);
   SLkm_define_key (PC_PGUP, (FVOID_STAR) pgup_menu_cmd, Menu_Keymap);
   SLkm_define_key (PC_PGUP1, (FVOID_STAR) pgup_menu_cmd, Menu_Keymap);
   SLkm_define_key (PC_PGDN, (FVOID_STAR) pgdn_menu_cmd, Menu_Keymap);
   SLkm_define_key (PC_PGDN1, (FVOID_STAR) pgdn_menu_cmd, Menu_Keymap);
#endif

#ifdef HAS_MOUSE
   SLkm_define_key ("\033^@", (FVOID_STAR) jed_mouse_cmd, Menu_Keymap);
#endif
   return 0;
}


SLang_Key_Type *jed_menu_do_key (void)
{
   int ch;

   if (Executing_Keyboard_Macro
       || (Read_This_Character != NULL))
     return SLang_do_key (Menu_Keymap, jed_getkey);

   ch = my_getkey ();

   if (SLKeyBoard_Quit || SLang_get_error ())
     {
	jed_exit_menu_bar ();
	return NULL;
     }

   if (ch == 27)		       /* ESC */
     {
	int tsecs = 2;
	if (0 == input_pending (&tsecs))
	  ch = 127;
     }
   ungetkey (&ch);
   
   return SLang_do_key (Menu_Keymap, jed_getkey);
}

static int exec_menubar_callback (Menu_Bar_Type *b, SLang_Name_Type **ntp)
{
   SLang_Name_Type *nt;
   
   nt = *ntp;
   if (nt == NULL)
     return 0;

   if (SLang_get_error ())
     return -1;

   if (-1 == SLang_push_string (b->name))
     return -1;
   
   if (-1 == SLexecute_function (nt))
     {
	if (Active_Menu_Bar == b)      /* disable callback */
	  {
#if SLANG_VERSION > 10400
	     SLang_free_function (nt);
#endif
	     *ntp = NULL;
	  }
	return -1;
     }

   return 0;
}


void jed_notify_menu_buffer_changed (void)
{
   Menu_Bar_Type *b;
   int err;

   if (NULL == (b = get_active_menubar ()))
     return;

   err = SLang_get_error ();
   SLang_set_error (0);
   (void) exec_menubar_callback (b, &b->init_callback);
   if (SLang_get_error () == 0)
     SLang_set_error (err);
}


int jed_select_menu_bar (void)
{
   Menu_Popup_Type *p;
   Menu_Node_Type **l, **lmax;

   if (IS_MINIBUFFER)
     {
	/* For now do not allow access to menus from the minibuffer
	 * since the minibuffer is not recursive
	 */
	return -1;
     }
   
   if (NULL == (Active_Menu_Bar = get_active_menubar ()))
     return -1;
   
   p = (Menu_Popup_Type *) Active_Menu_Bar;
   Active_Popup = p;

   l = p->subnodes;
   lmax = l + p->num_subnodes;

   /* Reset each sub-node to its default value.  This is necessary 
    * to ensure that keyboard macros will work when one switches from
    * one node to another via next/prev_menubar_cmd functions.
    */
   while (l < lmax)
     {
	Menu_Popup_Type *pp = (Menu_Popup_Type *) *l++;

	if (pp->type == MENU_NODE_POPUP)
	  {
	     (void) unselect_active_node (pp);
	     pp->active_node = 0;
	  }
     }

   p->active_node = 0;
   
   if (-1 == exec_menubar_callback (Active_Menu_Bar, &Active_Menu_Bar->select_callback))
     {
	jed_exit_menu_bar ();
	return -1;
     }
   
   (void) get_selected_menu_item (p);
   touch_screen ();
   Jed_Menus_Active = 1;
   return 0;
}

/* Interpreter interface */

static Menu_Node_Type *find_menu_node1 (Menu_Popup_Type *m, 
					char *name,
					int stop_at_last_popup)
{
   char *name_end;
   Menu_Node_Type **l, **lmax;

   name_end = strchr (name, '.');
   if (name_end == NULL)
     name_end = name + strlen (name);

   l = m->subnodes;
   lmax = l + m->num_subnodes;
   
   while (l < lmax)
     {
	Menu_Popup_Type *p;
	
	p = (Menu_Popup_Type *) *l++;

	if (0 == menu_name_eqs (p->name, name, name_end))
	  continue;

	if (*name_end == 0)
	  {
	     if (stop_at_last_popup
		 && ((p->type != MENU_NODE_POPUP) 
		     || (p->type != MENU_NODE_MENUBAR)))
	       return (Menu_Node_Type *)m;

	     return (Menu_Node_Type *)p;
	  }
	     
	if (p->type == MENU_NODE_POPUP)
	  return find_menu_node1 (p, name_end + 1, stop_at_last_popup);

	return NULL;
     }

   return NULL;
}

   

static Menu_Node_Type *find_menu_node (char *name, int stop_at_last_popup)
{
   char *s;
   Menu_Node_Type *m;

   m = (Menu_Node_Type *) menu_find_menu_bar (name, 1);
   if (m == NULL)
     return m;

   s = strchr (name, '.');
   if (s == NULL)
     return m;

   m = find_menu_node1 ((Menu_Popup_Type *)m, s + 1, stop_at_last_popup);

   if (m == NULL)
     SLang_verror (SL_INTRINSIC_ERROR,
		   "Unable to find menu node %s",
		   name);
   return m;
}

static Menu_Popup_Type *find_menu_popup (char *name)
{
   Menu_Popup_Type *m;
   
   m = (Menu_Popup_Type *) find_menu_node (name, 0);
   if (m == NULL)
     return m;

   if ((m->type != MENU_NODE_POPUP)
       && (m->type != MENU_NODE_MENUBAR))
     {
	SLang_verror (SL_INVALID_PARM, 
		      "%s is not a menu node", name);
	return NULL;
     }

   return m;
}

/* If name is NULL or non-existent, then insert at end */
static unsigned int find_where_to_insert (Menu_Popup_Type *p, char *name)
{
   unsigned int i, num;

   num = p->num_subnodes;

   if (name != NULL) for (i = 0; i < num; i++)
     {
	if (0 == strcmp (name, p->subnodes[i]->name))
	  return i;
     }
   return num;
}

static void create_menu_bar_cmd (char *name)
{
   if (0 == strcmp ("Global", name))
     {
	Menu_Bar_Type *g = create_menu_bar (name, 5);
	if (g != NULL)
	  jed_delete_menu_bar (Global_Menu_Bar);
	Global_Menu_Bar = g;
	return;
     }

   if (NULL != menu_find_menu_bar (name, 0))
     return;

   (void) create_menu_bar (name, 5);
}

static void set_buffer_menu_bar_cmd (char *name)
{
   Menu_Bar_Type *b;

   b = menu_find_menu_bar (name, 1);
   if (b == NULL)
     return;
   
   jed_delete_menu_bar (CBuf->menubar);
   
   CBuf->menubar = b;
   b->num_refs += 1;
}

static int pop_where_to_insert (Menu_Popup_Type *p, unsigned int *where)
{
   if (SLang_peek_at_stack () == SLANG_STRING_TYPE)
     {
	char *s;
	
	if (-1 == SLang_pop_slstring (&s))
	  return -1;
	*where = find_where_to_insert (p, s);
	SLang_free_slstring (s);
	return 0;
     }
   
   if (-1 == SLang_pop_uinteger (where))
     return -1;
   
   return 0;
}

static void insert_popup_menu_cmd (char *dest, char *menu)
{
   Menu_Popup_Type *m;
   unsigned int where;

   m = find_menu_popup (dest);

   if (m == NULL)
     return;

   if (-1 == pop_where_to_insert (m, &where))
     return;

   insert_popup_menu (m, menu, where);
}

   
static void append_popup_menu_cmd (char *dest, char *menu)
{
   Menu_Popup_Type *m;

   m = find_menu_popup (dest);

   if (m == NULL)
     return;

   insert_popup_menu (m, menu, m->num_subnodes);
}

static void insert_separator_cmd (char *name)
{
   Menu_Popup_Type *m;
   unsigned int where;

   m = find_menu_popup (name);

   if (m == NULL)
     return;

   if (-1 == pop_where_to_insert (m, &where))
     return;

   insert_separator (m, where);
}

static void append_separator_cmd (char *name)
{
   Menu_Popup_Type *m;

   m = find_menu_popup (name);

   if (m != NULL)
     insert_separator (m, m->num_subnodes);
}

static void insert_menu_item_cmd_internal (int is_fun, int do_append)
{
   Menu_Popup_Type *m;
   SLang_Name_Type *nt = NULL;
   SLang_Any_Type *client_data = NULL;
   char *str = NULL;
   char *menu = NULL;
   char *name = NULL;
   unsigned int where;

   if (is_fun)
     {
	if (-1 == SLang_pop_anytype (&client_data))
	  return;

	if (NULL == (nt = SLang_pop_function ()))
	  goto free_and_return;
     }
   else if (-1 == SLang_pop_slstring (&str))
     return;

   if (-1 == SLang_pop_slstring (&name))
     goto free_and_return;

   if (-1 == SLang_pop_slstring (&menu))
     goto free_and_return;
   
   if (NULL == (m = find_menu_popup (menu)))
     goto free_and_return;
   
   if (do_append)
     where = m->num_subnodes;
   else if (-1 == pop_where_to_insert (m, &where))
     goto free_and_return;

   if (nt != NULL)
     {
	if (NULL != insert_slang_fun_item (m, name, nt, client_data, where))
	  {
	     client_data = NULL;
	     nt = NULL;
	  }
     }
   else
     (void) insert_keystring_item (m, name, str, where);
   /* drop */

   free_and_return:

   if (client_data != NULL) 
     SLang_free_anytype (client_data);
   SLang_free_slstring (str);
   SLang_free_slstring (menu);
   SLang_free_slstring (name);
#if SLANG_VERSION > 10400
   SLang_free_function (nt);
#endif
}

static void insert_menu_item_cmd (void)
{
   insert_menu_item_cmd_internal ((SLang_Num_Function_Args == 5), 0);
}

static void append_menu_item_cmd (void)
{
   insert_menu_item_cmd_internal ((SLang_Num_Function_Args == 4), 1);
}

static void menu_delete_item_cmd (char *menu)
{
   Menu_Popup_Type *parent;
   Menu_Node_Type *child;

   parent = (Menu_Popup_Type *)find_menu_node (menu, 1);
   child = find_menu_node (menu, 0);
   if ((parent == NULL) || (child == NULL))
     {
	SLang_verror (SL_INVALID_PARM, 
		      "Menu %s does not exist", menu);
	return;
     }
   menu_delete_node (parent, child);
}

static void menu_delete_items_cmd (char *menu)
{
   Menu_Popup_Type *p;

   if (NULL == (p = find_menu_popup (menu)))
     return;

   menu_delete_nodes (p);
}

static void set_object_available_cmd (char *name, int *flag)
{
   Menu_Node_Type *m;
   
   if (NULL == (m = find_menu_node (name, 0)))
     return;
   
   if (*flag) m->flags &= ~MENU_ITEM_UNAVAILABLE;
   else
     m->flags |= MENU_ITEM_UNAVAILABLE;
}

static int pop_popup_callback (Menu_Popup_Type **pp, int type,
			       SLang_Name_Type **ntp)
{
   SLang_Name_Type *nt;
   char *popup;
   Menu_Popup_Type *p;

   if (SLang_peek_at_stack () == SLANG_NULL_TYPE)
     {
	(void) SLang_pop_null ();
	nt = NULL;
     }
   else if (NULL == (nt = SLang_pop_function ()))
     return -1;

   if (-1 == SLang_pop_slstring (&popup))
     {
#if SLANG_VERSION > 10400
	SLang_free_function (nt);
#endif
	return -1;
     }

   p = find_menu_popup (popup);
   if (p == NULL)
     {
#if SLANG_VERSION > 10400
	SLang_free_function (nt);
#endif
	SLang_free_slstring (popup);
	return -1;
     }
   
   if (type && (p->type != type))
     {
	SLang_verror (SL_INVALID_PARM, "%s does not specify the proper menu type", popup);
#if SLANG_VERSION > 10400
	SLang_free_function (nt);
#endif
	SLang_free_slstring (popup);
	return -1;
     }
   
   SLang_free_slstring (popup);

   *ntp = nt;
   *pp = p;
   return 0;
}

static int pop_menubar_callback (Menu_Bar_Type **bp, SLang_Name_Type **nt)
{
   return pop_popup_callback ((Menu_Popup_Type **)bp, MENU_NODE_MENUBAR, nt);
}

static void set_select_menubar_callback (void)
{
   Menu_Bar_Type *b;
   SLang_Name_Type *nt;

   if (-1 == pop_menubar_callback (&b, &nt))
     return;

   b->select_callback = nt;
}

static void set_init_menubar_callback (void)
{
   Menu_Bar_Type *b;
   SLang_Name_Type *nt;
   
   if (-1 == pop_menubar_callback (&b, &nt))
     return;

   b->init_callback = nt;
}


static void set_select_popup_callback (void)
{
   Menu_Popup_Type *p;
   SLang_Name_Type *nt;

   if (-1 == pop_popup_callback (&p, MENU_NODE_POPUP, &nt))
     return;

   p->select_popup_callback = nt;
}

static void set_tweak_popup_callback (void)
{
   Menu_Popup_Type *p;
   SLang_Name_Type *nt;

   if (-1 == pop_popup_callback (&p, MENU_NODE_POPUP, &nt))
     return;

   p->tweak_popup_callback = nt;
}

static void copy_menu_cmd (char *destname, char *srcname)
{
   Menu_Popup_Type *dest;
   Menu_Node_Type *src;

   dest = find_menu_popup (destname);
   if (dest == NULL)
     return;
   
   src = find_menu_node (srcname, 0);
   if (src == NULL)
     return;
   
   (void) copy_menu (dest, src);
}

static void set_menu_bar_prefix_string (char *menubar, char *s)
{
   Menu_Bar_Type *b;
   
   if (NULL == (b = menu_find_menu_bar (menubar, 1)))
     return;

   s = SLang_create_slstring (s);
   if (s == NULL)
     return;
   
   SLang_free_slstring (b->prefix_string);
   b->prefix_string = s;
}

   
static void popup_menu_cmd (char *menu)
{
   char *popup;

   (void) jed_exit_menu_bar ();
   if (-1 == jed_select_menu_bar ())
     return;

   if (NULL == (menu = SLmake_string (menu)))
     return;

   /* for now ignore the menubar name and use default */
   popup = strchr (menu, '.');
   if (popup != NULL)
     popup++;

   while ((popup != NULL) && (Active_Popup != NULL))
     {
	Menu_Node_Type *m;
	char *next_popup;
	
	next_popup = strchr (popup, '.');
	if (next_popup != NULL)
	  *next_popup++ = 0;

	if (NULL == (m = find_subnode (Active_Popup, popup)))
	  {
	     SLang_verror (SL_INVALID_PARM, 
			   "Unable to find a popup menu called %s", menu);
	     break;
	  }
	set_node_selection (Active_Popup, m);
	if (-1 == select_menu_cmd ())
	  break;
	
	popup = next_popup;
     }
   
   SLfree (menu);
}


static SLang_Intrin_Fun_Type Menu_Table[] =
{
   MAKE_INTRINSIC_S("menu_create_menu_bar", create_menu_bar_cmd, VOID_TYPE),
   MAKE_INTRINSIC_SS("menu_append_popup", append_popup_menu_cmd, VOID_TYPE),
   MAKE_INTRINSIC_SS("menu_insert_popup", insert_popup_menu_cmd, VOID_TYPE),
   MAKE_INTRINSIC_S("menu_use_menu_bar", set_buffer_menu_bar_cmd, VOID_TYPE),
   MAKE_INTRINSIC_S("menu_append_separator", append_separator_cmd, VOID_TYPE),
   MAKE_INTRINSIC_S("menu_insert_separator", insert_separator_cmd, VOID_TYPE),
   MAKE_INTRINSIC_0("menu_append_item", append_menu_item_cmd, VOID_TYPE),
   MAKE_INTRINSIC_0("menu_insert_item", insert_menu_item_cmd, VOID_TYPE),
   MAKE_INTRINSIC_S("menu_delete_item", menu_delete_item_cmd, VOID_TYPE),
   MAKE_INTRINSIC_S("menu_delete_items", menu_delete_items_cmd, VOID_TYPE),
   MAKE_INTRINSIC_SI("menu_set_object_available", set_object_available_cmd, VOID_TYPE),
   MAKE_INTRINSIC_0("menu_set_select_menubar_callback", set_select_menubar_callback, VOID_TYPE),
   MAKE_INTRINSIC_0("menu_set_init_menubar_callback", set_init_menubar_callback, VOID_TYPE),
   MAKE_INTRINSIC_0("menu_set_select_popup_callback", set_select_popup_callback, VOID_TYPE),
   MAKE_INTRINSIC_0("menu_set_tweak_popup_callback", set_tweak_popup_callback, VOID_TYPE),
   MAKE_INTRINSIC_SS("menu_copy_menu", copy_menu_cmd, VOID_TYPE),
   MAKE_INTRINSIC_SS("menu_set_menu_bar_prefix", set_menu_bar_prefix_string, VOID_TYPE),
   MAKE_INTRINSIC_S("menu_select_menu", popup_menu_cmd, VOID_TYPE),
   MAKE_INTRINSIC(NULL,NULL,0,0)
};

int jed_init_menus (void)
{
   if (-1 == init_menu_keymap ())
     return -1;

   if (-1 == SLadd_intrin_fun_table (Menu_Table, NULL))
     return -1;
#if 0
   if (-1 == make_global_menubar ())
     return -1;
#endif
   return 0;
}

#ifdef HAS_MOUSE
static int select_menu_via_rc (int type, int r, int c)
{
   Menu_Popup_Type *p;
   
   p = Active_Popup;
   while (p != (Menu_Popup_Type *)Active_Menu_Bar)
     {
	if ((r >= p->row) 
	    && (r < p->max_row)
	    && (c >= p->column)
	    && (c < p->max_col))
	  break;
	
	p = p->parent;
     }
   
   if ((p == NULL) || (p->type == MENU_NODE_MENUBAR))
     {
	unsigned int i;
	int *item_columns;

	if (Active_Popup == NULL)
	  return -1;
	
	if (r != 0)
	  {
	     if (type != JMOUSE_DRAG)
	       return -1;

	     if (Active_Popup->type != MENU_NODE_MENUBAR)
	       return 0;
	  }
	
	unselect_active_node ((Menu_Popup_Type *) Active_Menu_Bar);

	i = Active_Menu_Bar->num_subnodes;
	item_columns = Active_Menu_Bar->item_columns;
	while (i > 0)
	  {
	     i--;

	     if ((i != 0) && (item_columns[i] > c))
	       continue;

	     p = (Menu_Popup_Type *)Active_Menu_Bar->subnodes[i];
	     if (p->type == MENU_NODE_SEPARATOR)
	       continue;
		  
	     if (p->flags & MENU_ITEM_UNAVAILABLE)
	       continue;

	     if (-1 == set_node_selection ((Menu_Popup_Type *) Active_Menu_Bar,
					   (Menu_Node_Type *) p))
	       return -1;
	     
	     if (-1 == find_active_popup ())
	       return -1;

	     if (p->type == MENU_NODE_POPUP)
	       return select_menu_cmd ();
	  }
	return -1;
     }
 
   if (p == Active_Popup)
     {
	r -= (p->row + 1);
	r += p->visible_node_offset;
	if ((r >= (int)p->num_subnodes) || (r < 0))
	  return 0;
	
	if (-1 == set_node_selection (p, p->subnodes[r]))
	  return 0;
	
	if ((type != JMOUSE_DRAG)
	    || ((p->subnodes[r]->type == MENU_NODE_POPUP)
		&& (c + 1 >= p->max_col)))
	  select_menu_cmd ();

	return 0;
     }
   
   while (Active_Popup != p)
     back_menu_cmd ();

   return 0;
}

int jed_menu_handle_mouse (unsigned int type, 
			   int x, int y, int button, int shift)
{
   (void) shift; (void) button;

   if ((type != JMOUSE_UP) && (type != JMOUSE_DRAG))
     return -1;

   if ((Jed_Menus_Active == 0)
       && (-1 == jed_select_menu_bar ()))
     return -1;

   if (-1 == select_menu_via_rc (type, y-1, x-1))
     {
	if (type != JMOUSE_DRAG)
	  jed_exit_menu_bar ();
     }

   return 0;
}

#ifndef IBMPC_SYSTEM
static int xterm_mouse_cmd (void)
{
   int x, y, b;
   
   b = my_getkey ();
   x = (unsigned char) my_getkey () - 32;
   y = (unsigned char) my_getkey () - 32;
   
   /* We need to trigger on the button release event */
   
   b -= 32;
   if ((b & 3) != 3)
     return -1;
   
   return jed_menu_handle_mouse (JMOUSE_UP, x, y, 0, 0);
}


#endif 				       /* !IBMPC_SYSTEM */
#endif				       /* HAS_MOUSE */
#endif				       /* JED_HAS_TTY_MENUS */
