Subject: How do I redraw menuitems in an active menu?

From: Chris Ross <[email-masked]>
Date: Wed, 07 Jan 2004 16:08:20 -0500

Hey there.  I'm trying to add a new feature that I'd been
looking for, and have it mostly working.  I would like to
be able to bind certain menuitems to react differently if
you're holding down a modifier.  I've developed a syntax
for this, by way of a whole new menuitem type.  And, when
I open the menu, it maps correctly.  And when I hold down
the "shift" key, it adds the suffix to the normal menuitem
label ([0]).  However, when I release that modifier, it doesn't
revert as it should, until I pass over it with my mouse
thus highlighting and dehilighting that item.

   Does anyone know what I'm doing wrong?

   I'm modifying things in the MenuItem* directly, and then
calling:

get_menu_paint_item_parameters(&mpip, mr, mi, NULL, NULL, False);
menuitem_paint(mi, &mpip);

   As noted, it works just fine when I'm appending to
the previous label, but not when I changing the label
to the previous shorter string.

   Perhaps I'm misunderstanding what the array of labels
are to be used for?  Why are there an array of labels?
In what situation would they be used?

   Thanks.  Any help would be appriciated...

                       - Chris

--
Visit the official FVWM web page at <URL:http://www.fvwm.org/>.
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: [email-masked]
Date: Fri, 9 Jan 2004 12:53:33 +0100

>   Thanks.  Any help would be appriciated...

Why don't you post your patch so that I can take a look (include
instructions to show the problem).

Ciao

Dominik ^_^  ^_^

 --
Dominik Vogt, [email-masked]
Reply-To: [email-masked]
--
Visit the official FVWM web page at <URL:http://www.fvwm.org/>.
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: Chris Ross <[email-masked]>
Date: Wed, 14 Jan 2004 12:44:00 -0500

[email-masked] wrote; in a message with the
> instructions to show the problem).

   Well, as it happens, I figured out the problem with
modifying the menu item.  It was just an issue of
the code not clearing the menuitem window area before
drawing a substring.  I got that fixed now, so it clears
the whole area before drawing in the case of the new
feature I added.

   Okay, included here are a description and patches for
the new keyword I added.  This keyword will allow me
to define menuitems that change based on modifier
key-presses.  The patch is based on the HEAD of the
CVS tree as of today (14-Jan-2004).

   Much feedback welcome from all!  Please, let me know
any thoughts you have!  Thanks!

                            - Chris
The new ModSwitch keyword
-------------------------

  This is intended to solve a problem I've been looking to
solve.  I sometimes have menu items duplicated, with only
one minor change (as an example, ssh'ing into a remote host,
one without X port forwarding, one with.)  What I decided
the cleanest solution would be, to reduce the size of my
menu's, would be to have menuitems that would have different
labels and/or actions when you held a modifier key.

  To implement this, my first pass was the here-described
ModSwitch keyword.  When used as follows:

AddToMenu RemoteLogins	"Menu"	Title
+ "Tomcat"	ModSwitch N ( "" Exec exec xterm -e ssh tomcat ) \
			S ( " w/ X fwd" Exec exec xterm -e ssh -R 6000:127.0.0.1:6000 tomcat )
+ "Sphynx"	Exec exec xterm -e ssh sphynx

  Would cause the top [non-Title] menuitem in that menu
to ssh in without extra command line args, unless you
hold Shift, in which case it will do X forwarding
through 127.0.0.1.

  This syntax is just what I came up with.  If anyone
has any better ideas for syntax, then please convince
me.  I'd be happy to change it if a better option
presents itself.

  Caveats/ToDo's:

  - When the menu draws, the correct string isn't
displayed until you move the mouse into the menu.
I must be missing a place where I should put a
call to dink with the menuitems.
  - When you hold modifiers that don't match any
listed ones, it maintains the previous state,
instead of doing "something else".  Should there
be a concept of a "default"?  Same as N?
  - I only mess with label[0].  Why do MenuItems
even have an array of labels?  I couldn't figure
that out while I was in there...
  - I don't pre-initialize the label or action
when the config is parsed.  So, for a little while,
ModSwitch is the action.  I've got it mapped to
CMD_Beep right now, since it shouldn't really do
anything as an "action" or "command", but.  Perhaps
I should just ensure that it can never be called
in the first place by reconfiguring action and
label when it's initially scanned.
  - I'm not careful to figure out how to initialize
or not, and free if necessary, the copies of the
original label and action.
  - Right now the top-level label (before the ModSwitch
keyword) is a base, and the other string it appended
to it.  You could do "" at top, and use the new string
in each mod-switch-spec, to do whole replacement.
Should I code it to work that way instead?  This
falls back under syntax...

  Anyway.  Lots of questions, and I'd love to
hear opinions...

		- Chris Ross
		  [email-masked]
		  14-Jan-2004

Index: fvwm/commands.h
===================================================================
RCS file: /home/cvs/fvwm/fvwm/fvwm/commands.h,v
retrieving revision 1.41
diff -u -p -u -r1.41 commands.h
--- fvwm/commands.h	16 Jul 2003 09:10:40 -0000	1.41
+++ fvwm/commands.h	14 Jan 2004 17:32:15 -0000
@@ -80,6 +80,7 @@ enum
 	F_LAYER,
 	F_LOCALE_PATH,
 	F_MENUSTYLE,
+	F_MOD_SWITCH,
 	F_MODULE,
 	F_MODULE_PATH,
 	F_MODULE_SYNC,
Index: fvwm/functable.c
===================================================================
RCS file: /home/cvs/fvwm/fvwm/fvwm/functable.c,v
retrieving revision 1.29
diff -u -p -u -r1.29 functable.c
--- fvwm/functable.c	16 Jul 2003 09:10:40 -0000	1.29
+++ fvwm/functable.c	14 Jan 2004 17:32:15 -0000
@@ -348,6 +348,9 @@ const func_t func_table[] =
 	CMD_ENT("menustyle", CMD_MenuStyle, F_MENUSTYLE, 0, 0),
 	/* - Control appearance and behavior of a menu */
 
+	CMD_ENT("modswitch", CMD_Beep, F_MOD_SWITCH, 0, 0),
+	/* - Never called directly; sets up menu entry based on modif. keys */
+
 	CMD_ENT("module", CMD_Module, F_MODULE, 0, 0),
 	/* - Invoke an fvwm module */
 
Index: fvwm/menuitem.c
===================================================================
RCS file: /home/cvs/fvwm/fvwm/fvwm/menuitem.c,v
retrieving revision 1.32
diff -u -p -u -r1.32 menuitem.c
--- fvwm/menuitem.c	29 Jun 2003 19:53:23 -0000	1.32
+++ fvwm/menuitem.c	14 Jan 2004 17:32:15 -0000
@@ -150,6 +150,7 @@ void menuitem_free(MenuItem *mi)
 			free(MI_LABEL(mi)[i]);
 		}
 	}
+	/* XXX - deal with MI_IS_MOD_SWITCH ? */
 	if (MI_ACTION(mi) != NULL)
 	{
 		free(MI_ACTION(mi));
@@ -410,6 +411,9 @@ void menuitem_paint(
 	{
 		xft_clear = True;
 	}
+	/* Also clear if the item is a modswitch, as the text might change */
+	if (MI_IS_MOD_SWITCH(mi))
+		xft_clear = True;
 
 	/* Hilight or clear the background. */
 	lit_x_start = -1;
@@ -642,11 +646,33 @@ void menuitem_paint(
 			}
 			fws->str = MI_LABEL(mi)[i];
 			b.x = fws->x = MI_LABEL_OFFSET(mi)[i];
-			b.width = text_width = FlocaleTextWidth(
+			text_width = FlocaleTextWidth(
 				ST_PSTDFONT(ms), fws->str, strlen(fws->str));
+			if (MI_IS_MOD_SWITCH(mi))
+			{
+				/* A ModSwitch might be shortening the string, */
+				/* so clear the whole width of the thing... */
+				if (is_item_selected) {
+					b.width = MDIM_HILIGHT_WIDTH(*dim) -
+						relief_thickness * 4;
+				}
+				else
+				{
+					b.width = MDIM_ITEM_WIDTH(*dim) -
+						relief_thickness * 2;
+				}
+			}
+			else
+			{
+				b.width = text_width;
+			}
 			if (!item_cleared && mpip->ev)
 			{
 				int s_x,s_w;
+				/* XXX - do we need to make sure to account */
+				/* for changing text (IS_MOD_SWITCH) here */
+				/* by changing the value in text_width, or */
+				/* using b.width instead?      - cross */
 				if (frect_get_seg_intersection(
 					mpip->ev->xexpose.x,
 					mpip->ev->xexpose.width,
Index: fvwm/menuitem.h
===================================================================
RCS file: /home/cvs/fvwm/fvwm/fvwm/menuitem.h,v
retrieving revision 1.13
diff -u -p -u -r1.13 menuitem.h
--- fvwm/menuitem.h	29 Jun 2003 19:53:23 -0000	1.13
+++ fvwm/menuitem.h	14 Jan 2004 17:32:15 -0000
@@ -30,6 +30,7 @@
 #define MI_IS_POPUP(i)          ((i)->flags.is_popup)
 #define MI_IS_MENU(i)           ((i)->flags.is_menu)
 #define MI_IS_CONTINUATION(i)   ((i)->flags.is_continuation)
+#define MI_IS_MOD_SWITCH(i)     ((i)->flags.is_mod_switch)
 #define MI_HAS_TEXT(i)          ((i)->flags.has_text)
 #define MI_HAS_PICTURE(i)       ((i)->flags.has_picture)
 #define MI_HAS_HOTKEY(i)        ((i)->flags.has_hotkey)
@@ -72,6 +73,12 @@ typedef struct MenuItem
 	short hotkey_coffset;
 	/* The column number the hotkey is defined in*/
 	char hotkey_column;
+
+	/* In the case of is_mod_switch, and *only* for mod_switch, */
+	/* these values are set to what was originally read from config */
+	char *ms_labelbase;
+	char *ms_action;
+
 	struct
 	{
 		unsigned is_continuation : 1;
@@ -86,6 +93,7 @@ typedef struct MenuItem
 		unsigned has_hotkey : 1;
 		unsigned is_hotkey_automatic : 1;
 		unsigned is_selectable : 1;
+		unsigned is_mod_switch : 1;
 		/* temporary flags */
 		unsigned was_deselected : 1;
 	} flags;
Index: fvwm/menus.c
===================================================================
RCS file: /home/cvs/fvwm/fvwm/fvwm/menus.c,v
retrieving revision 1.376
diff -u -p -u -r1.376 menus.c
--- fvwm/menus.c	6 Jan 2004 14:05:01 -0000	1.376
+++ fvwm/menus.c	14 Jan 2004 17:32:17 -0000
@@ -41,6 +41,7 @@
 #include "libs/PictureUtils.h"
 #include "libs/PictureGraphics.h"
 #include "libs/charmap.h"
+#include "libs/modifiers.h"
 #include "libs/wcontext.h"
 #include "fvwm.h"
 #include "externs.h"
@@ -4396,6 +4397,171 @@ static void pop_menu_down_and_repaint_pa
 	return;
 }
 
+/*
+ *
+ *  Procedure:
+ *      reconfig_mod_switch_entries - Reconfigure the MenuRoot passed
+ *	in with any new modifiers passed in, or read from the XEvent.
+ *	The current set of modifers will be used to update any and
+ *	all IS_MOD_SWITCH MenuItem's to instantiate the correct entry
+ *	for the modifiers in use.
+ *
+ *	mr       - menu to scan for MOD_SWITCH menuitems to [possibly] change
+ *	new_mods - any new modifiers to consider depressed
+ *	e        - An XEvent, which is expected to be a KeyPress or
+ *	           KeyRelease event.
+ *
+ */
+static void reconfig_mod_switch_entries(MenuRoot *mr, short new_mods,
+					XEvent *e)
+{
+    MenuPaintItemParameters mpip;
+    MenuItem *mi;
+    KeySym keysym;
+    char *modspec;
+    int modmask;
+    static short hold_mods = 0;
+    short modifiers;
+
+    modifiers = hold_mods | new_mods;
+
+    if (e) {
+	keysym = XLookupKeysym(&(e->xkey), 0);
+		    
+	switch (keysym) {
+	  case XK_Shift_L:
+	  case XK_Shift_R:
+	    /* Shift key */
+	    if (e->type == KeyPress)
+		modifiers |= ShiftMask;
+	    else
+		modifiers &= ~ShiftMask;
+	    break;
+	  case XK_Control_L:
+	  case XK_Control_R:
+	    /* Control key */
+	    if (e->type == KeyPress)
+		modifiers |= ControlMask;
+	    else
+		modifiers &= ~ControlMask;
+	    break;
+	  case XK_Meta_L:
+	  case XK_Meta_R:
+	    /* Meta key */
+	    if (e->type == KeyPress)
+		modifiers |= Mod1Mask;
+	    else
+		modifiers &= ~Mod1Mask;
+	    break;
+	  case XK_Alt_L:
+	  case XK_Alt_R:
+	    /* Alt key */
+	    if (e->type == KeyPress)
+		modifiers |= Mod2Mask;
+	    else
+		modifiers &= ~Mod2Mask;
+	    break;
+	  default:
+	    /* No modifier we know to track */
+	    break;
+	}
+    }
+
+    fprintf(stderr, "Modifiers active: ");
+    if (modifiers & ShiftMask)
+	fprintf(stderr, "+Shift");
+    if (modifiers & ControlMask)
+	fprintf(stderr, "+Control");
+    if (modifiers & Mod1Mask)
+	fprintf(stderr, "+Meta(Mod1)");
+    if (modifiers & Mod2Mask)
+	fprintf(stderr, "+Alt(Mod2)");
+    fprintf(stderr, " (0x%02x)\n", modifiers);
+
+    
+    for (mi = MR_FIRST_ITEM(mr) ; mi ; mi = MI_NEXT_ITEM(mi)) {
+	char *action;
+	char *switchcmd;
+	char *postfix;
+
+	if (!MI_IS_MOD_SWITCH(mi))
+	    continue;
+
+	fprintf(stderr, "Found a MOD_SWITCH MenuItem (0x%p): %s\n",
+		mi, mi->ms_action);
+	action = mi->ms_action;
+	/* Process the ModSwitch to see if we have a matching */
+	/* modifier specification... */
+	action = SkipNTokens(action, 1); /* Skip 'ModSwitch' */
+	while ((action = GetNextToken(action, &modspec)) != NULL) {
+	    if (!modspec) {
+		/* Are we done?  Exit quietly with no match?  */
+		break;
+	    }
+
+	    //fprintf(stderr, "action is '%s'\n", action);
+	    if (modifiers_string_to_modmask(modspec, &modmask)) {
+		fvwm_msg(WARN, "__mloop_handle_event",
+			 "Illegal modifier string '%s'", modspec);
+		continue;
+	    }
+	    fprintf(stderr, "Got modmask 0x%x in mi (modspec '%s')\n", modmask, modspec);
+
+	    // Get the parenthesized expression that
+	    // that matches the pressed modifier(s)
+	    action = GetQuotedString(action, &switchcmd, " \t\r\n\0", NULL, "(", ")");
+	    if (modmask != modifiers) {
+		// Try the next one...
+		free(switchcmd);
+		continue;
+	    }
+
+	    // XXX - Will the first char at *switchcmd
+	    // ever *not* be '(' ?
+	    if (*switchcmd != '(') {
+		fvwm_msg(WARN, "__mloop_handle_event",
+			 "Illegal ModSwitch value '%s'", switchcmd);
+		free(switchcmd);
+		continue;
+	    } else {
+		// Prune the ()'s
+		switchcmd = SkipSpaces(switchcmd, "(", 1);
+		postfix = strrchr(switchcmd, ')');
+		if (postfix)
+		    *postfix = '\0';
+	    }
+	    //fprintf(stderr, "\nswitchcmd is '%s', action is '%s'\n", switchcmd, action);
+
+	    switchcmd = GetNextToken(switchcmd, &postfix);
+	    if (!postfix)
+		postfix = "";
+
+	    //fprintf(stderr, "postfix is '%s', switchcmd is now '%s'\n", postfix, switchcmd);
+
+	    // Change the label, and the action
+	    // XXX - should these all be MI_LABEL()[0] ?!?
+	    if (strcmp(MI_LABEL(mi)[0], mi->ms_labelbase))
+		free(MI_LABEL(mi)[0]);
+	    MI_LABEL(mi)[0] = safemalloc(strlen(mi->ms_labelbase) +
+					 strlen(postfix) + 1);
+	    strcpy(MI_LABEL(mi)[0], mi->ms_labelbase);
+	    strcat(MI_LABEL(mi)[0], postfix);
+	    if (strcmp(MI_ACTION(mi), mi->ms_action))
+		free(MI_ACTION(mi));
+	    MI_ACTION(mi) = strdup(switchcmd);
+	    MR_IS_UPDATED(mr) = 1;
+	    fprintf(stderr, "Updated ModSwitch in menu\n");
+	    get_menu_paint_item_parameters(&mpip, mr, mi, NULL, NULL, False);
+	    menuitem_paint(mi, &mpip);
+	    // We're done this menuitem.  Skip to the next one...
+	    break;
+	}
+    }
+
+    hold_mods = modifiers;
+    return;
+}
+
 /* ---------------------------- menu main loop ------------------------------ */
 
 static void __mloop_init(
@@ -4779,6 +4945,7 @@ static mloop_ret_code_t __mloop_handle_e
 		return MENU_MLOOP_RET_LOOP;
 
 	case KeyRelease:
+		reconfig_mod_switch_entries(pmp->menu, 0, (*pmp->pexc)->x.elast);
 		if ((*pmp->pexc)->x.elast->xkey.keycode !=
 		    MST_SELECT_ON_RELEASE_KEY(pmp->menu))
 		{
@@ -4822,6 +4989,9 @@ static mloop_ret_code_t __mloop_handle_e
 			fev_fake_event(&e);
 		}
 
+		/* find any ModSwitch menuitems, and fix them up... */
+		reconfig_mod_switch_entries(pmp->menu, 0, (*pmp->pexc)->x.elast);
+
 		/* now handle the actual key press */
 		menuShortcuts(
 			pmp->menu, pmp, pmret, (*pmp->pexc)->x.elast, &med->mi,
@@ -6336,6 +6506,9 @@ void do_menu(MenuParameters *pmp, MenuRe
 	{
 		do_warp = False;
 	}
+	/* Adjust any ModSwitch menuitems based on state of modifiers... */
+	reconfig_mod_switch_entries(pmp->menu, 0, NULL);
+
 	/* Figure out where we should popup, if possible */
 	if (!pmp->flags.is_already_mapped)
 	{
@@ -7132,6 +7305,15 @@ void AddToMenu(
 		break;
 	case F_TITLE:
 		MI_IS_TITLE(tmp) = True;
+		break;
+	case F_MOD_SWITCH:
+		MI_IS_MOD_SWITCH(tmp) = True;
+		/* Set the ms_ parameters to the original values, so they */
+		/* can be referred to later... */
+		tmp->ms_labelbase = MI_LABEL(tmp)[0];	/* XXX */
+		tmp->ms_action = MI_ACTION(tmp);
+		/* XXX - should we also look for a no-modifier switchcmd, */
+		/* and initialize MI_LABEL and MI_ACTION to that? */
 		break;
 	default:
 		break;