Subject: Menu Code Improvements

From: Greg Badros <[email-masked]>
Date: 07 Apr 1997 14:37:54 -0700

The below is a patch which applies cleanly to FVWM2.0.45.  It improves
menus quite a bit, and adds some new features.  Specifically:

   Splitting of long (i.e. tall) menus into "More..." popup menus so
      that they the entries that don't fit are still available

   Fixed bugs w/ Escape leaving a menu item highlighted the next
      time it pops up

   Added Checking for reused hotkeys -- prints a warning message

   Integrated LEFT_MENUS compile-time option as a run-time option,
      and added new menu style "MWMLeft" to builtin.c

   Improved handling of keyboard-movement keystrokes --
      moves one item at a time, and for FVWM menus, does something
      reasonable w/ left and right

   Added SETENV command to change an environment variable for
      future-spawned child processes -- docs are the code right now.

(This is not guaranteed to work, and you use it at your own risk, etc.
I'm not responsible for anything bad that happens.)

But, it does work great for me.  Let me know about problems or if you
love it or hate it, etc.

Greg J. Badros
[email-masked]
Seattle, WA  USA
http://www.cs.washington.edu/homes/gjb

Index: fvwm/builtins.c
===================================================================
RCS file: /homes/rivers/gjb/.CVS/fvwm/fvwm/builtins.c,v
retrieving revision 1.1.1.3
retrieving revision 1.3
diff -u -r1.1.1.3 -r1.3
--- builtins.c	1997/04/06 18:15:23	1.1.1.3
+++ builtins.c	1997/04/07 08:04:45	1.3
@@ -472,6 +472,7 @@
 		      char *action, int *Module)
 {
   MenuRoot *mr;
+  MenuRoot *mrPrior;
 
   char *token, *rest,*item;
 
@@ -480,7 +481,7 @@
 #endif
 
   rest = GetNextToken(action,&token);
-  mr = FindPopup(token);
+  mr = FollowMenuContinuations(FindPopup(token),&mrPrior);
   if(mr == NULL)
     mr = NewMenuRoot(token, 0);
   if(last_menu != NULL)
@@ -496,22 +497,20 @@
 }

-#ifdef USEDECOR
 void add_another_item(XEvent *eventp,Window w,FvwmWindow *tmp_win,
 		      unsigned long context,
 		      char *action, int *Module)
 {
+#ifdef USEDECOR
   extern void AddToDecor(FvwmDecor *, char *);
+#endif
   MenuRoot *mr;
+  MenuRoot *mrPrior;
 
   char *rest,*item;
 
-  if((last_menu == NULL) && (last_decor == NULL))
-      return;
-
   if (last_menu != NULL) {
-      
-      mr = FindPopup(last_menu);
+      mr = FollowMenuContinuations(FindPopup(last_menu),&mrPrior);
       if(mr == NULL)
 	  return;
       rest = GetNextToken(action,&item);
@@ -521,6 +520,7 @@
       
       MakeMenu(mr);
   }
+#ifdef USEDECOR
   else if (last_decor != NULL) {
       FvwmDecor *tmp = &Scr.DefaultDecor;
       for (; tmp; tmp = tmp->next)
@@ -530,47 +530,28 @@
 	  return;
       AddToDecor(tmp, action);
   }
+#endif
 }
-#else /* ! USEDECOR */
-void add_another_item(XEvent *eventp,Window w,FvwmWindow *tmp_win,
-		      unsigned long context,
-		      char *action, int *Module)
-{
-  MenuRoot *mr;
-
-  char *rest,*item;
-
-  if(last_menu == NULL)
-    return;
-
-  mr = FindPopup(last_menu);
-  if(mr == NULL)
-    return;
-  rest = GetNextToken(action,&item);
-
-  AddToMenu(mr, item,rest);
-  free(item);
-  
-  MakeMenu(mr);
-  return;
-}
-#endif /* USEDECOR */
 
 void destroy_menu(XEvent *eventp,Window w,FvwmWindow *tmp_win,
                   unsigned long context,
                   char *action, int *Module)
 {
   MenuRoot *mr;
+  MenuRoot *mrContinuation;
 
   char *token, *rest;
 
   rest = GetNextToken(action,&token);
   mr = FindPopup(token);
-  if(mr == NULL)
-    return;
-  DestroyMenu(mr);
+  while (mr && (mrContinuation = mr->continuation))
+      {
+      DestroyMenu(mr);
+      mr = mrContinuation;
+      if (mr == NULL)
+	  break;
+      }
   return;
-
 }
 
 void add_item_to_func(XEvent *eventp,Window w,FvwmWindow *tmp_win,
@@ -1478,12 +1459,19 @@
   action = GetNextToken(action,&font);
   action = GetNextToken(action,&style);
 
-  if((style != NULL)&&(mystrncasecmp(style,"MWM",3)==0))
+  if((style != NULL)&&(mystrncasecmp(style,"MWM\0",4)==0))
   {
     Scr.flags |= MWMMenus;
   }
-  else
+  else if((style != NULL)&&(mystrncasecmp(style,"MWMLeft\0",8)==0))
+  {
+    Scr.flags |= MWMMenusLeft;
+    Scr.flags |= MWMMenus;
+  }
+  else {
     Scr.flags &= ~MWMMenus;
+    Scr.flags &= ~MWMMenusLeft;
+  }
 
   if(Scr.d_depth > 2)
   {
@@ -2834,6 +2822,29 @@
     }
 }
 #endif /* MULTISTYLE */
+
+
+
+void SetEnv(XEvent *eventp,Window junk,FvwmWindow *tmp_win,
+	    unsigned long context, char *action,int* Module)
+{
+    char *szVar = NULL;
+    char *szValue = NULL;
+    char *szNoOverwrite = NULL;
+    Bool fOverwrite = TRUE;
+    action = GetNextToken(action,&szVar);
+    action = GetNextToken(action,&szValue);
+    action = GetNextToken(action,&szNoOverwrite);
+    if (mystrcasecmp(szNoOverwrite,"NoOverwrite") == 0)
+	fOverwrite = FALSE;
+    setenv(szVar,szValue,fOverwrite?1:0);
+    /*
+    fprintf(stderr,"Setting %s to %s, overwrite = %d\n",
+	    szVar, szValue, fOverwrite);
+	    */
+}
+
+
 
 /**************************************************************************
  *
Index: fvwm/functions.c
===================================================================
RCS file: /homes/rivers/gjb/.CVS/fvwm/fvwm/functions.c,v
retrieving revision 1.1.1.3
retrieving revision 1.2
diff -u -r1.1.1.3 -r1.2
--- functions.c	1997/04/06 18:15:26	1.1.1.3
+++ functions.c	1997/04/07 08:04:46	1.2
@@ -119,6 +119,7 @@
   {"Stick",        stick_function,   F_STICK,               FUNC_NEEDS_WINDOW},
   {"Send_WindowList",send_list_func, F_SEND_WINDOW_LIST,    FUNC_NO_WINDOW},
   {"Send_ConfigInfo",SendDataToModule, F_CONFIG_LIST,       FUNC_NO_WINDOW},
+  {"SetEnv",       SetEnv,           F_SETENV,              FUNC_NO_WINDOW},
   {"set_mask",     set_mask_function,F_SET_MASK,            FUNC_NO_WINDOW},
   {"Style",        ProcessNewStyle,  F_STYLE,               FUNC_NO_WINDOW},
   {"Title",        Nop_func,         F_TITLE,               FUNC_TITLE},
Index: fvwm/menus.c
===================================================================
RCS file: /homes/rivers/gjb/.CVS/fvwm/fvwm/menus.c,v
retrieving revision 1.1.1.2
retrieving revision 1.2
diff -u -r1.1.1.2 -r1.2
--- menus.c	1997/04/06 18:15:30	1.1.1.2
+++ menus.c	1997/04/07 07:16:43	1.2
@@ -6,6 +6,21 @@
  *     copyright remains in the source code and all documentation
  ****************************************************************************/
 
+/* Lots of reworking by 
+   Greg J. Badros, Apr 6, 1997 
+   [email-masked]
+
+   Fixed bugs w/ Escape leaving a menu item highlighted the next
+      time it pops up
+   Added Checking for reused hotkeys
+   Splitting of long (i.e. tall) menus into "More..." popup menus
+   Integrated LEFT_MENUS compile-time option as a run-time option,
+      and added new menu style MWMLeft to builtin.c
+   Improved handling of keyboard-movement keystrokes --
+      moves one item at a time, and for FVWM menus, does something
+      reasonable w/ left and right
+   */
+    
 
 /***********************************************************************
  *
@@ -30,6 +45,12 @@
 #include "parse.h"
 #include "screen.h"
 
+#ifdef DEBUG
+#  define DEBUG_FPRINTF(x) fprintf##x
+#else
+#  define DEBUG_FPRINTF(x)
+#endif
+
 int menu_on=0;
 
 MenuRoot *ActiveMenu = NULL;		/* the active menu */
@@ -49,9 +70,9 @@
 int mouse_moved = 0;
 int menu_aborted = 0;
 
-#ifdef LEFT_MENUS
-static int PrevActiveMenuX = -1;
-#endif /* LEFT_MENUS */
+/* This whole Prev and PrevPrev garbage should be a stack! */
+static int PrevActiveMenuX = -1; 
+static MenuItem *PrevActiveItem = NULL;
 
 extern XContext MenuContext;
 /****************************************************************************
@@ -65,15 +86,13 @@
 {
   int prevStashedX=0,prevStashedY=0;
   MenuRoot *PrevActiveMenu=0;
-  MenuItem *PrevActiveItem=0;
   int retval=MENU_NOP;
   int x,y;
   Time t0;
   extern Time lastTimestamp;
-#ifdef LEFT_MENUS
   int PrevMenuX = PrevActiveMenuX;
-#endif /* LEFT_MENUS */
 
+  PrevActiveItem=NULL;
 
   /* this condition could get ugly */
   if(menu->in_use)
@@ -134,9 +153,7 @@
     menu_aborted = 1;
   else
     menu_aborted = 0;
-#ifdef LEFT_MENUS
   PrevActiveMenuX = PrevMenuX;
-#endif /* LEFT_MENUS */
   return retval;
 }
 
@@ -542,10 +559,13 @@
  * If none of these conditions are true, then the default processing
  * routine is called.
  ***********************************************************************/
-void menuShortcuts(XEvent *ev) 
+void menuShortcuts(XEvent *Event) 
 {
   MenuItem *mi;
-  KeySym keysym = XLookupKeysym(&ev->xkey,0);
+  int x,y,x_root,y_root;
+  int move_size = Scr.EntryHeight;
+  int x_move = 0,y_move = 0;
+  KeySym keysym = XLookupKeysym(&Event->xkey,0);
   
   /* Try to match hot keys */
   if (((keysym >= XK_a) && (keysym <= XK_z)) ||	/* Only consider alphabetic */
@@ -565,24 +585,151 @@
 	  if (keysym == key)
 	    {		/* Are they equal?		*/
 	      ActiveItem = mi;		/* Yes: Make this the active item */
-	      ev->type = ButtonRelease;	/* Force a menu exit		*/
+	      Event->type = ButtonRelease;	/* Force a menu exit		*/
 	      return;
 	    }
 	}
     }
   
+
+  XQueryPointer( dpy, Scr.Root, &JunkRoot, &Event->xany.window,
+		&x_root, &y_root, &x, &y, &JunkMask);
+  XQueryPointer( dpy, Event->xany.window, &JunkRoot, &JunkChild,
+		&x_root, &y_root, &x, &y, &JunkMask);
+
   switch(keysym)		/* Other special keyboard handling	*/
     {
     case XK_Escape:		/* Escape key pressed. Abort		*/
-      ActiveItem = NULL;	/* No selection				*/
-      ev->type = ButtonRelease;	/* Make the menu exit			*/
+      /* First turn off the highlight */
+      if (ActiveItem)
+	{
+	ActiveItem->state = 0;
+	PaintEntry(ActiveMenu, ActiveItem);
+	}
+      ActiveItem = NULL;	/* No selection	*/
+      Event->type = ButtonRelease;	/* Make the menu exit*/
+      goto NOT_A_MOVE;
       break;
       
+    case XK_Up:
+    case XK_k:
+    case XK_p:
+	if ((ActiveItem != NULL) ||
+	    ((Scr.flags & MWMMenus) && PrevActiveItem != NULL)) {
+	    MenuItem *mi = (ActiveItem?ActiveItem:PrevActiveItem);
+	    MenuItem *priorItem = mi->prev;
+	    /* Skip back thru separators */
+	    while (priorItem && priorItem->fIsSeparator == TRUE) {
+		DEBUG_FPRINTF((stderr,"skipping up\n"));
+		priorItem = priorItem->prev;
+	    }
+	    if (priorItem) {
+		y_move = priorItem->y_offset - mi->y_offset;
+		DEBUG_FPRINTF((stderr,"using up move_size = %d\n",y_move));
+	    }
+	    break;
+	}
+	else {
+	    DEBUG_FPRINTF((stderr,"using up move_size\n"));
+	    y_move = -move_size;
+	    }
+	break;
+
+    case XK_Down:
+    case XK_n:
+    case XK_j:
+	if ((ActiveItem != NULL) ||
+	    ((Scr.flags & MWMMenus) && PrevActiveItem != NULL)) {
+	    MenuItem *mi = (ActiveItem?ActiveItem:PrevActiveItem);
+	    MenuItem *nextItem = mi->next; 
+	    /* Skip forward thru separators */
+	    while (nextItem && nextItem->fIsSeparator) {
+		DEBUG_FPRINTF((stderr,"skipping down\n"));
+		nextItem = nextItem->next;
+	    }
+	    if (nextItem) {
+		y_move = nextItem->y_offset - mi->y_offset;
+		DEBUG_FPRINTF((stderr,"using down move_size = %d\n",y_move));
+	    }
+	    break;
+	}
+	else {
+	    DEBUG_FPRINTF((stderr,"using down move_size\n"));
+	    y_move = move_size;
+	    }
+	break;
+
+    /* FIX: Left and right should handle popping up and out of child menus
+       accurately instead of just moving the pointer around; this
+       doesn't really work as is */
+    case XK_Left:
+    case XK_b:
+    case XK_h:
+      if (PrevMenu != NULL && ActiveMenu != NULL)
+	{
+	DEBUG_FPRINTF((stderr,"x = %d, am->width = %d, pm->width = %d\n",x,ActiveMenu->width, PrevMenu->width));
+	}
+      if (Scr.flags & MWMMenusLeft ||
+	  Scr.flags & MWMMenus)
+	{
+	/* Haven't gotten MWM menu modes to work well w/ keyboard yet,
+	   so just do a simple movement */
+	x_move = -move_size;
+	}
+      else 
+	{ /* closing a child menu */
+	x_move = -x-5;
+	}
+      break;
+
+    case XK_Right:
+    case XK_f:
+    case XK_l:
+      if (ActiveMenu != NULL && PrevMenu != NULL) 
+	{
+	DEBUG_FPRINTF((stderr,"x = %d, am->width = %d, pm->width = %d\n",x,ActiveMenu->width, PrevMenu->width));
+	/*x_move = ActiveMenu->width/2; */ 
+	}
+      if (Scr.flags & MWMMenusLeft ||
+	  Scr.flags & MWMMenus)
+	{
+	/* Haven't gotten MWM menu modes to work well w/ keyboard yet,
+	   so just do a simple movement */
+	x_move = move_size;
+	}
+      else 
+	{ /* popping up a child menu */
+	x_move = (ActiveMenu->width-x)-5;
+	}
+      break;
+
       /* Nothing special --- Allow other shortcuts (cursor movement)	*/
     default:
-      Keyboard_shortcuts(ev,ButtonRelease);
+      Keyboard_shortcuts(Event,ButtonRelease);
+      goto NOT_A_MOVE;
       break;
     }
+
+  if((x_move != 0)||(y_move != 0))
+    {
+      /* beat up the event */
+      XWarpPointer(dpy, None, Scr.Root, 0, 0, 0, 0, x_root+x_move,
+		   y_root+y_move);
+
+      /* beat up the event */
+      Event->type = MotionNotify;
+      Event->xkey.x += x_move;
+      Event->xkey.y += y_move;
+      Event->xkey.x_root += x_move;
+      Event->xkey.y_root += y_move;
+    }
+
+NOT_A_MOVE:
+  DEBUG_FPRINTF((stderr,"Active = %s; ",ActiveMenu?ActiveMenu->name:"NULL"));
+  DEBUG_FPRINTF((stderr,"Prev = %s; ",PrevMenu?PrevMenu->name:"NULL"));
+  DEBUG_FPRINTF((stderr,"menu_on=%d\n",menu_on));
+
+  return;
 }
 
 /***********************************************************************
@@ -640,6 +787,7 @@
 	      sticks = 0;
 	      break;
 	    }
+#ifdef GJB_WONDERS_WHY_THIS_IS_HERE
 	  if ((Scr.flags & MWMMenus) && !ActiveItem && (menu_on > 1))
 	    {
 	      int x,y;
@@ -653,6 +801,7 @@
 		  break;
 		}
 	    }
+#endif
 	  PopDownMenu();
 	  if(ActiveItem)
 	    {
@@ -717,6 +866,25 @@
 }

+/* Unhighlight any already highlighted menu items */
+void RepaintAlreadyReversedMenuItems(MenuRoot *menu)
+{
+  MenuItem *mi = menu->first;
+  while (mi) {
+    if (mi->state) {
+    /* I'm not going to break, even though we really
+       shouldn't have two entries highlighted -- we
+       really should track the full ActiveItem
+       path correctly so this would be unnecessary,
+       but alas, only two items deep are tracked */
+      mi->state = 0;
+      PaintEntry(menu,mi);
+    }
+    mi=mi->next;
+  }
+}
+  
+
 /***********************************************************************
  *
  *  Procedure:
@@ -734,6 +902,8 @@
   if ((!menu)||(menu->w == None)||(menu->items == 0)||(menu->in_use))
     return False;
 
+  RepaintAlreadyReversedMenuItems(menu);
+
   menu_on++;
   InstallRootColormap();
 
@@ -770,12 +940,10 @@
 	  menu->width +1;
     }
 
-#ifdef LEFT_MENUS
-  if (PrevActiveMenuX != -1)
+  if (Scr.flags & MWMMenusLeft && PrevActiveMenuX != -1)
       if (x + menu->width > Scr.MyDisplayWidth - 2) {
           x = PrevActiveMenuX - menu->width + 2;
       }
-#endif /* LEFT_MENUS */
   /* clip to screen */
   if (x + menu->width > Scr.MyDisplayWidth-2) 
     x = Scr.MyDisplayWidth - menu->width-2;
@@ -793,10 +961,7 @@
     }
   if (y < 0) y = 0;
 
-#ifdef LEFT_MENUS
   PrevActiveMenuX = x;
-#endif /* LEFT_MENUS */
-
   MenuY = y;
   XMoveWindow(dpy, menu->w, x, y);
   XMapRaised(dpy, menu->w);
@@ -819,11 +984,9 @@
   
   menu_on--;
 
-#ifdef LEFT_MENUS
   if (menu_on == 0) {                   /* last menu down? */
     PrevActiveMenuX = -1;               /* Return to no prev item state */
   }                                     /* end last menu down */
-#endif /* LEFT_MENUS */
 
   if (ActiveItem)
     ActiveItem->state = 0;
@@ -946,9 +1109,11 @@
 void MakeMenu(MenuRoot *mr)
 {
   MenuItem *cur;
+  MenuItem *cur_prev;
   unsigned long valuemask;
   XSetWindowAttributes attributes;
   int y,width;
+  int cItems;
 
   if((mr->func != F_POPUP)||(!Scr.flags & WindowsCaptured))
     return;
@@ -987,8 +1152,11 @@
   if(mr->width2 > 0)
     mr->width += 5;
   
-
-  for (y=2, cur = mr->first; cur != NULL; cur = cur->next)
+  /* cur_prev trails one behind cur, since we need to move that
+     into a newly-made menu if we run out of space */
+  for (y=2, cItems = 0, cur = mr->first, cur_prev = NULL; 
+       cur != NULL; 
+       cur_prev = cur, cur = cur->next, cItems++)
     {
       cur->y_offset = y;
       cur->x = 5+mr->width0;
@@ -1014,15 +1182,22 @@
 		      HEIGHT_EXTRA_TITLE;
 		}
 	    }
-	  else
+	  else {
 	    cur->y_height = HEIGHT_SEPARATOR;
+	  }
+	  /* Titles are separators, too */
+	  cur->fIsSeparator = TRUE;
 	}
-      else if((cur->strlen==0)&&(cur->strlen2 == 0))
+      else if((cur->strlen==0)&&(cur->strlen2 == 0)) {
 	/* Separator */
 	cur->y_height = HEIGHT_SEPARATOR;
-      else
+	cur->fIsSeparator = TRUE;
+	}
+      else {
 	/* Normal text entry */
+	cur->fIsSeparator = FALSE;
 	cur->y_height = Scr.EntryHeight;
+	}
       if(cur->picture)
 	cur->y_height += cur->picture->height;
       if(cur->lpicture && cur->y_height < cur->lpicture->height+4)
@@ -1036,6 +1211,50 @@
 	{
 	  cur->x2 = mr->width -5 + mr->width0;
 	}
+      /* this item would have to be the last item, or else
+	 we need to add a "More..." entry pointing to a new menu */
+      if (y+Scr.EntryHeight > Scr.MyDisplayHeight &&
+	  cur->next != NULL)
+	{
+	char *szMenuContinuationActionAndName = 
+	  (char *) safemalloc((8+strlen(mr->name))*sizeof(char));
+	MenuRoot *menuContinuation;
+	if (mr->continuation != NULL) {
+	  fvwm_msg(ERR,"MakeMenu","Confused-- expected continuation to be null");
+	  break;
+	}
+	strcpy(szMenuContinuationActionAndName,"Popup ");
+	strcat(szMenuContinuationActionAndName, mr->name);
+	strcat(szMenuContinuationActionAndName,"$");
+	/* NewMenuRoot inserts at the head of the list of menus 
+	   but, we need it at the end */
+	/* (Give it just the name, with is 6 chars passed the action
+	   since strlen("Popup ")==6 )*/
+	DEBUG_FPRINTF((stderr,"Inserting new menu with action: %s\n", szMenuContinuationActionAndName));
+	menuContinuation = NewMenuRoot(szMenuContinuationActionAndName+6,0);
+	mr->continuation = menuContinuation;
+
+	/* Now move this item and the remaining items into the new menu */
+	cItems--;
+	menuContinuation->first = cur;
+	menuContinuation->last = mr->last;
+	menuContinuation->items = mr->items - cItems;
+	DEBUG_FPRINTF((stderr,"New menu has %d items\n",menuContinuation->items));
+
+	/* cur_prev is now the last item in the current menu */
+	mr->last = cur_prev;
+	mr->items = cItems;
+	cur_prev->next = NULL;
+
+	/* Go back one, so that this loop will process the new item */
+	y -= cur->y_height;
+	cur = cur_prev;
+
+	/* And add the entry pointing to the new menu */
+	AddToMenu(mr,"More&...",szMenuContinuationActionAndName);
+	MakeMenu(menuContinuation);
+
+	}
     }
   mr->in_use = 0;
   mr->height = y+2;
@@ -1066,6 +1285,28 @@
   return;
 }
 
+/* FHotKeyUsedBefore
+ * Return true if any item already in the menu has
+ * used the given hotkey already
+ * This means that it doesn't check the last element of the menu
+ */
+int FHotKeyUsedBefore(MenuRoot *menu, char ch) {
+  int f = FALSE;
+  MenuItem *currentMenuItem = menu->first;
+  /* we want to stop just before the last item in the menu */
+  while (currentMenuItem != 0 && currentMenuItem->next != 0)
+    {
+    if (currentMenuItem->chHotkey == ch)
+      {
+      f = TRUE;
+      break;
+      }
+    currentMenuItem = currentMenuItem->next;
+    }
+  return f;
+}
+
+
 /***********************************************************************
  * Procedure:
  *	scanForHotkeys - Look for hotkey markers in a MenuItem
@@ -1076,7 +1317,7 @@
  * 	which 	- +1 to look in it->item1 and -1 to look in it->item2.
  *
  ***********************************************************************/
-void scanForHotkeys(MenuItem *it, int which) 
+char scanForHotkeys(MenuItem *it, int which) 
 {
   char *start, *txt;
 
@@ -1092,13 +1333,17 @@
 	      for (tmp = txt; *tmp != '\0'; tmp++) tmp[0] = tmp[1];
 	      continue;		/* ...And skip to the key char		*/
 	    }
-	  /* It's a hot key marker - work out the offset value		*/
-	  it->hotkey = (1 + (txt - start)) * which;
-	  for (; *txt != '\0'; txt++) txt[0] = txt[1];	/* Copy down..	*/
-	  return;			/* Only one hotkey per item...	*/
+	  else {
+	    char ch = txt[1]; 
+	    /* It's a hot key marker - work out the offset value */
+	    it->hotkey = (1 + (txt - start)) * which;
+	    for (; *txt != '\0'; txt++) txt[0] = txt[1];	/* Copy down..	*/
+	    return ch;			/* Only one hotkey per item...	*/
+	    }
 	}
     }
   it->hotkey = 0;		/* No hotkey found.  Set offset to zero	*/
+  return '\0';
 }

@@ -1165,6 +1410,22 @@
     }
 }
 
+/* FollowMenuContinuations
+ * Given an menu root, return the menu root to add to by
+ * following continuation links until there are no more
+ */
+MenuRoot *FollowMenuContinuations(MenuRoot *mr, MenuRoot **pmrPrior )
+{
+  *pmrPrior = NULL;
+  while ((mr != NULL) &&
+	 (mr->continuation != NULL))
+    {
+    DEBUG_FPRINTF((stderr,"Following continuation, skipping %s\n",mr->name));
+    *pmrPrior = mr;
+    mr = mr->continuation;
+    }
+  return mr;
+}

 /***********************************************************************
@@ -1194,6 +1455,8 @@
      return;
 
   tmp = (MenuItem *)safemalloc(sizeof(MenuItem));
+  tmp->chHotkey = '\0';
+  tmp->next = NULL;
   if (menu->first == NULL)
     {
       menu->first = tmp;
@@ -1235,9 +1498,23 @@
 
   if (item != (char *)0)
     {
+      char ch;
       scanForPixmap(tmp->item,&tmp->picture,'*');
       scanForPixmap(tmp->item,&tmp->lpicture,'%');
-      scanForHotkeys(tmp, 1);				/* [email-masked] */
+      ch = scanForHotkeys(tmp, 1);	/* [email-masked] */
+      if (ch != '\0')
+	{
+	if (FHotKeyUsedBefore(menu,ch))
+	  {
+	  fvwm_msg(WARN,"AddToMenu","Hotkey %c is reused in menu %s; second binding ignored.",ch,
+		   menu->name);
+	  tmp->hotkey = 0;
+	  }
+	else
+	  {
+	  tmp->chHotkey = ch;
+	  }
+	}
       tmp->strlen = strlen(tmp->item);
     }
   else
@@ -1249,15 +1526,28 @@
         scanForPixmap(tmp->item2,&tmp->picture,'*');
       if(!tmp->lpicture)
         scanForPixmap(tmp->item2,&tmp->lpicture,'%');
-      if (tmp->hotkey == 0)
-	scanForHotkeys(tmp, -1);	/* [email-masked] */
+      if (tmp->hotkey == 0) {
+        char ch = scanForHotkeys(tmp, -1);	/* [email-masked] */
+	if (ch != '\0')
+	  {
+	  if (FHotKeyUsedBefore(menu,ch))
+	    {
+	    fvwm_msg(WARN,"AddToMenu","Hotkey %c is reused in menu %s; second binding ignored.",ch,
+		     menu->name);
+	    tmp->hotkey = 0;
+	    }
+	  else
+	    {
+	    tmp->chHotkey = ch;
+	    }
+	  }
+      }
       tmp->strlen2 = strlen(tmp->item2);
     }
   else
     tmp->strlen2 = 0;
 
   tmp->action = stripcpy(action);
-  tmp->next = NULL;
   tmp->state = 0;
   tmp->func_type = find_func_type(tmp->action);
   tmp->item_num = menu->items++;
@@ -1273,17 +1563,19 @@
  *
  *  Inputs:
  *	name	- the name of the menu root
+ *      fFunction - non-zero if we shoot set func to F_FUNCTION, 
+ *                  F_POPUP otherwise
  *
  ***********************************************************************/
-MenuRoot *NewMenuRoot(char *name,int junk)
+MenuRoot *NewMenuRoot(char *name,int fFunction)
 {
   MenuRoot *tmp;
   
   tmp = (MenuRoot *) safemalloc(sizeof(MenuRoot));
-  if(junk == 0)
-    tmp->func = F_POPUP;
-  else
+  if(fFunction != 0)
     tmp->func = F_FUNCTION;
+  else
+    tmp->func = F_POPUP;
 
   tmp->name = stripcpy(name);
   tmp->first = NULL;
@@ -1293,6 +1585,7 @@
   tmp->width2 = 0;
   tmp->w = None;
   tmp->next  = Scr.AllMenus;
+  tmp->continuation = NULL;
   Scr.AllMenus = tmp;
   return (tmp);
 }
Index: fvwm/menus.h
===================================================================
RCS file: /homes/rivers/gjb/.CVS/fvwm/fvwm/menus.h,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -u -r1.1.1.1 -r1.2
--- menus.h	1997/01/07 03:53:28	1.1.1.1
+++ menus.h	1997/04/07 07:16:43	1.2
@@ -79,6 +79,8 @@
 				   (offsets have 1 added, so +1 or -1
 				   refer to the *first* character)
 				   */
+    char chHotkey;
+    short fIsSeparator;          /* 1 if this is a separator */
 } MenuItem;
 
 typedef struct MenuRoot
@@ -86,6 +88,7 @@
     struct MenuItem *first;	/* first item in menu */
     struct MenuItem *last;	/* last item in menu */
     struct MenuRoot *next;	/* next in list of root menus */
+    struct MenuRoot *continuation; /* continuation of this menu (too tall for screen */
     char *name;			/* name of root */
     Window w;			/* the window of the menu */
     short height;		/* height of the menu */
@@ -125,5 +128,6 @@
 extern MenuRoot *ActiveMenu;
 extern MenuItem *ActiveItem;
 
+MenuRoot *FollowMenuContinuations(MenuRoot *mr,MenuRoot **pmrPrior);
 
 #endif /* _MENUS_ */
Index: fvwm/misc.h
===================================================================
RCS file: /homes/rivers/gjb/.CVS/fvwm/fvwm/misc.h,v
retrieving revision 1.1.1.2
retrieving revision 1.2
diff -u -r1.1.1.2 -r1.2
--- misc.h	1997/01/07 04:14:32	1.1.1.2
+++ misc.h	1997/04/07 08:04:48	1.2
@@ -538,6 +538,8 @@
                       unsigned long context, char *action,int* Module);
 void AddModConfig(XEvent *eventp,Window junk,FvwmWindow *tmp_win,
                   unsigned long context, char *action,int* Module);
+void SetEnv(XEvent *eventp,Window junk,FvwmWindow *tmp_win,
+	    unsigned long context, char *action,int* Module);
 
 /*
 ** message levels for fvwm_msg:
Index: fvwm/parse.h
===================================================================
RCS file: /homes/rivers/gjb/.CVS/fvwm/fvwm/parse.h,v
retrieving revision 1.1.1.2
retrieving revision 1.2
diff -u -r1.1.1.2 -r1.2
--- parse.h	1997/01/07 04:14:33	1.1.1.2
+++ parse.h	1997/04/07 08:04:49	1.2
@@ -101,6 +101,7 @@
 #define F_DESTROY_DECOR         124
 #define F_UPDATE_DECOR          125
 #define F_WINDOW_SHADE          126
+#define F_SETENV                127
 
 /* Functions for use by modules only! */
 #define F_SEND_WINDOW_LIST     1000
Index: fvwm/screen.h
===================================================================
RCS file: /homes/rivers/gjb/.CVS/fvwm/fvwm/screen.h,v
retrieving revision 1.1.1.3
retrieving revision 1.2
diff -u -r1.1.1.3 -r1.2
--- screen.h	1997/04/06 18:15:32	1.1.1.3
+++ screen.h	1997/04/07 07:20:04	1.2
@@ -349,4 +349,5 @@
 #define EdgeWrapX                 (64) /* Should EdgeScroll wrap around? */
 #define EdgeWrapY                (128) 
 #define MWMMenus                (1024)
+#define MWMMenusLeft            (2048)
 #endif /* _SCREEN_ */
--
Visit the official FVWM web page at <URL:http://www.hpc.uh.edu/fvwm/>.
To unsubscribe from the list, send "unsubscribe fvwm-workers" in the
body of a message to [email-masked].
To report problems, send mail to [email-masked].

---

From: Dan Espen <[email-masked]>
Date: Thu, 10 Apr 1997 13:51:54 -0400

Greg Badros <[email-masked]> writes:

>       and added new menu style "MWMLeft" to builtin.c

With the current "left menu" feature, the left side pop-up is used for
both  mwm  and fvwm menus.  With  your  patch, it looked like  it only
takes effect if you set the menustyle to "mwmleft".

I view the "left menu feature" as a bug fix that affects both fvwm and
mwm menus, and I don't think it should be either a compile time or run
time option.  It should just be a fix to a bug.  I'm  hoping that in a
future release, this will just be the built-in behavior.

I call this a bug because if you pop-up a small menu on the right side
of the screen and then pop-up a large menu  that completely covers the
small  menu, you  can't get  back to the  small menu  without starting
over.  Of course its worse  with mwm menus which automatically pop-up,
but either way its a usability problem.

>       reasonable w/ left and right

I definitely saw improved keyboard menu traversal with fvwm menus, but
with mwm menus, it just seemed to be taking larger  steps.  I did like
the way the  cursor is held inside the  menus.  You can get the cursor
outside a menu when you  come out of a sub  menu toward the parent.  I
was able to end up with  2 entries in a  menu raised by navigating out
of one menu  thru  another and back   at another location.    It still
worked right, it just looked weird.

If  you could get  mwm left  and right  working better,  that would be
great.

>       future-spawned child processes -- docs are the code right now.

Your  SetEnv routine calls "setenv".  I  guess  this is something like
"putenv"?  "setenv" doesn't seem to exist in Solaris.

Since  I couldn't link this I  couldn't test it,  but I didn't see any
description of what it is for.  If you are  going to "Exec" something,
you can always "Exec export VAR=value;runit", so I  have to guess this
is for exporting environment variables to modules?

> I'm not responsible for anything bad that happens.)

OK, I survived.

> But, it does work great for me.  Let me know about problems or if you

One of my menus would  pop-up at +0+0 when I  tried to pop it up  near
the bottom right edge.   Why this was  only happening  with one  of my
menus, wasn't clear.  (I haven't studied the code.)

--
Dan Espen                                    Phone: (908) 699-5570
[email-masked]
444 Hoes Lane  Room RRC 1C-214
Piscataway, NJ 08854
--
Visit the official FVWM web page at <URL:http://www.hpc.uh.edu/fvwm/>.
To unsubscribe from the list, send "unsubscribe fvwm-workers" in the
body of a message to [email-masked].
To report problems, send mail to [email-masked].

---

From: Greg Badros <[email-masked]>
Date: 10 Apr 1997 13:45:46 -0700

> takes effect if you set the menustyle to "mwmleft".

Correct.

> future release, this will just be the built-in behavior.

I do too, but as I understood it, LEFT_MENUS was left *not* the default
because it interacted poorly with respect to FVWM menus.  This is why I
made it only a "fix" to MWMMenus.

> but either way its a usability problem.

Though this is not unlike using keyboard hotkeys-- they popup the child
menu and remove the parent menu.  That behaviour is at least non standard.

> worked right, it just looked weird.

Yes---I've noticed this too.  

> If  you could get  mwm left  and right  working better,  that would be

Yes.. left and right still don't do anything really useful for either.
I'm doing a much more substantial rewrite that will make this stuff
easier to get right, and more flexible, too.

> is for exporting environment variables to modules?

First, yes, setenv is the same as putenv on Linux. I don't know what
it's called on other platforms, and would appreciate help with this.
I guess I didn't explain the purpose very well.  Basically, the intent
is to affect the environment of all processes started under the window
manager.  Many users have their window manager exec processes, not just
their shell.  This is fine as long as environment settings for
user-programs (i.e. those that don't affect only your shell) are put in,
say, a ~/.env file and sourced by .xsession/.xinitrc before running
fvwm.  However, when you need to dynamically change the environment that
programs run in, you ideally don't want to have to restart the window
manager just so that its environment (and hence the environment of its
child processes) is slightly different.

> > I'm not responsible for anything bad that happens.)

Good!

> 444 Hoes Lane  Room RRC 1C-214

Thanks for trying this, Dan.  Like I mentioned, I'm still doing more to
improve the code, mostly for correctness and improved keyboard support.
Depending on how busy things are for me, I may have a better version by
Monday morning.  If anybody wants to contribute other
concerns/complaints about either the current menu interfaces or my
changes to it, please do so soon.  In particular, what about FVWM-style
menus do people like (I've always much preferred MWM-style, but the
implementation of them is more broken so I've never been happy with
them).

Thanks,
Greg