Subject: FvwmButtons Patch: animated menu + autohide

From: Jui-Hsuan Joshua Feng <[email-masked]>
Date: Wed, 11 Feb 1998 22:48:52 -0600

Hi there,

The following is a patch to the fvwm-2.0.46's FvwmButtons.
There was a minor bug in my previous patch. It's fixed now. I am
not totally satisfied with the autohide feature. I will post
the patch for animated menu alone later. So if you don't want to
try the 'FvwmButtonsAutoHide', you can grab that patch instead.
Or visit:

http://www.cae.wisc.edu/~fengj/P/unix.html
(Note: Still under construction)

The difficulty in present FvwmButtonsAutoHide is that fvwm somehow
change the mouse index mode when it get close to the boundary. So
fvwm won't detect the 'EnterNotify' signal. Furthermore, FvwmButtons
is a large collection of windows, when the index wanders in it,
fvwm will detect several EnterNotify signals. Fvwm2 experts help
us out here.

-- 
Jui-Hsuan Joshua Feng                         (O)608/262-3640 
Electrical & Computer Engr, University of Wisconsin - Madison    
http://www.cae.wisc.edu/~fengj           [email-masked]
diff -u -b -B old/fvwm-2.0.46/modules/FvwmButtons/FvwmButtons.c fvwm-2.0.46/modules/FvwmButtons/FvwmButtons.c
--- old/fvwm-2.0.46/modules/FvwmButtons/FvwmButtons.c	Thu Jun 26 16:32:31 1997
+++ fvwm-2.0.46/modules/FvwmButtons/FvwmButtons.c	Wed Feb 11 22:27:20 1998
@@ -58,7 +58,8 @@
 #define MW_EVENTS   (ExposureMask |\
 		     StructureNotifyMask |\
 		     ButtonReleaseMask | ButtonPressMask |\
-		     KeyReleaseMask | KeyPressMask)
+		     KeyReleaseMask | KeyPressMask |\
+                     EnterWindowMask | LeaveWindowMask)
 /* SW_EVENTS are for swallowed windows... */
 #define SW_EVENTS   (PropertyChangeMask | StructureNotifyMask |\
 		     ResizeRedirectMask | SubstructureNotifyMask)
@@ -100,6 +101,9 @@
 
 void DebugEvents(XEvent*);
 
+panel_info *seekpanel(button_info *);
+void Slide(panel_info *, button_info *);
+
 /* -------------------------------- globals ---------------------------------*/
 
 Display *Dpy;
@@ -136,6 +140,10 @@
 
 button_info *UberButton=NULL;
 
+panel_info *MainPanel = NULL, *CurrentPanel = NULL, *PanelIndex;
+int AutoHide = 0, HoldMainPanel = 1, dpw, dph;
+#define ShowWidth 4
+
 /* ------------------------------ Misc functions ----------------------------*/
 
 #ifdef DEBUG
@@ -485,11 +493,26 @@
   UberButton->BHeight=1;
   MakeContainer(UberButton);
 
+  dpw = DisplayWidth(Dpy,screen);
+  dph = DisplayHeight(Dpy,screen);
+
 # ifdef DEBUG_INIT
   fprintf(stderr,"%s: Parsing...",MyName);
 # endif
 
+  CurrentPanel = MainPanel
+               = (panel_info *) mymalloc(sizeof(panel_info));
+  MainPanel->next = NULL;
+  MainPanel->uber = UberButton;
+  UberButton->title   = MyName;
+  UberButton->swallow = 1; /* the panel is shown */
+
   ParseOptions(UberButton);
+
+  CurrentPanel = MainPanel; /* reassign CurrentPanel */
+  while (CurrentPanel)
+  { UberButton = CurrentPanel->uber;
+
   if(UberButton->c->num_buttons==0)
     {
       fprintf(stderr,"%s: No buttons defined. Quitting\n", MyName);
@@ -518,6 +541,10 @@
 
   CreateWindow(UberButton,maxx,maxy);
 
+    CurrentPanel->uber->IconWinParent = MyWindow;
+    CurrentPanel->uber->icon_w = maxx;
+    CurrentPanel->uber->icon_h = maxy;
+
 # ifdef DEBUG_INIT
   fprintf(stderr,"OK\n%s: Creating icon windows...",MyName);
 # endif
@@ -548,6 +575,13 @@
     SetTransparentBackground(UberButton,Width,Height);
 # endif
 
+    CurrentPanel = CurrentPanel->next;
+  }
+  CurrentPanel = MainPanel;
+  UberButton   = CurrentPanel->uber;
+  MyWindow     = UberButton->IconWinParent;
+
+
 # ifdef DEBUG_INIT
   fprintf(stderr,"OK\n%s: Mapping windows...",MyName);
 # endif
@@ -594,8 +628,16 @@
 	switch(Event.type)
 	  {
 	  case Expose:
-	    if(Event.xany.window==MyWindow)
-	      {
+            PanelIndex = MainPanel;
+            while (PanelIndex && (PanelIndex->uber->IconWinParent != Event.xany.window))
+              PanelIndex = PanelIndex->next;
+            if (PanelIndex)
+            { UberButton = PanelIndex->uber;
+              MyWindow   = UberButton->IconWinParent;
+            }
+            else
+              break;
+
 #ifdef OLD_EXPOSE
 		if(Event.xexpose.count == 0)
 		  {
@@ -644,11 +686,10 @@
 		    ex=ey=10000;ex2=ey2=0;
 		  }
 #endif
-              }
 	    break;
 
 	  case ConfigureNotify:
-	    XGetGeometry(Dpy,MyWindow,&root,&x,&y,
+	    /* XGetGeometry(Dpy, MyWindow, &root, &x, &y,
 			 (ushort*)&tw,(ushort*)&th,
 			 (ushort*)&border_width,(ushort*)&depth);
 	    if(tw!=Width || th!=Height)
@@ -660,7 +701,7 @@
 		while(NextButton(&ub,&b,&button,0))
 		  MakeButton(b);
                 RedrawWindow(NULL);
-	      }
+	      } */ /* I don't like to change its size after it started */
 	    break;
 
 	  case KeyPress:
@@ -668,30 +709,68 @@
 	    if(keysym!=XK_Return && keysym!=XK_KP_Enter && keysym!=XK_Linefeed)
 	      break;	                /* fall through to ButtonPress */
 	  case ButtonPress:
+            PanelIndex = MainPanel;
+            b = NULL;
+            do
+              if (PanelIndex->uber->swallow)
+              {
+                UberButton = PanelIndex->uber;
+                MyWindow   = UberButton->IconWinParent;
+                if (Event.xany.window == MyWindow)
 	    CurrentButton = b = 
 	      select_button(UberButton,Event.xbutton.x,Event.xbutton.y);
+
+              }
+            while (!b && PanelIndex->next && (PanelIndex = PanelIndex->next));
+
 	    if(!b || !(b->flags&b_Action))
 	      {
 		CurrentButton=NULL;
 		break;
 	      }
+
+            CurrentPanel = PanelIndex; /* record which panel, the button pressed */
+            UberButton = CurrentPanel->uber;
+            MyWindow   = UberButton->IconWinParent;
+
 	    RedrawButton(b,0);
 	    if(!(act=GetButtonAction(b,Event.xbutton.button)))
 	      act=GetButtonAction(b,0);
 	    if(mystrncasecmp(act,"popup",5)!=0)
+	    { if (mystrncasecmp(act, "panel", 5) == 0)
+                Slide(seekpanel(b), b);
 	      break;
+            }
 	    else /* i.e. action is Popup */
 	      XUngrabPointer(Dpy,CurrentTime); /* And fall through */
 
 	  case KeyRelease:
 	  case ButtonRelease:
+            PanelIndex = MainPanel;
+            b = NULL;
+            while (PanelIndex && !b)
+            { if (PanelIndex->uber->swallow)
+              { UberButton = PanelIndex->uber;
+                MyWindow   = UberButton->IconWinParent;
+                if (Event.xany.window == MyWindow)
 	    b=select_button(UberButton,Event.xbutton.x,Event.xbutton.y);
+              }
+              PanelIndex = PanelIndex->next;
+            }
+
 	    if(!(act=GetButtonAction(b,Event.xbutton.button)))
 	      act=GetButtonAction(b,0);
 	    if(b && b==CurrentButton && act)
 	      {
 		if(mystrncasecmp(act,"Exec",4)==0)
 		  {
+                    /* close all subpanels */
+                    PanelIndex = MainPanel->next;
+                    while (PanelIndex)
+                    { if (PanelIndex->uber->swallow) Slide(PanelIndex, NULL);
+                      PanelIndex = PanelIndex->next;
+                    }
+
 		    /* Look for Exec "identifier", in which case the button
 		       stays down until window "identifier" materializes */
 		    i=4;
@@ -728,9 +807,14 @@
 		  DumpButtons(UberButton);
 		else if(mystrncasecmp(act,"SaveButtons",11)==0)
 		  SaveButtons(UberButton);
-		else
+		else if(mystrncasecmp(act,"panel",5))
 		  MySendText(fd,act,0);
 	      }
+
+            /* recover the old record */
+            UberButton = CurrentPanel->uber; /* the panel, the button pressed */
+            MyWindow   = UberButton->IconWinParent;
+
 	    b=CurrentButton;
 	    CurrentButton=NULL;
 	    if(b)
@@ -806,6 +890,38 @@
 		}
 	    break;
 
+          case EnterNotify:
+            if (AutoHide && HoldMainPanel)
+            {
+              GetRealGeometry(Dpy, MainPanel->uber->IconWinParent, &x, &y,
+		   	      (ushort*)&tw, (ushort*)&th,
+			      (ushort*)&border_width, (ushort*)&depth);
+              if (((AutoHide == 2) && (x+tw > dpw) && (x = dpw - tw)) ||
+                  ((AutoHide == 1) && (y+th > dph) && (y = dph - th)))
+              { XMoveWindow(Dpy, MainPanel->uber->IconWinParent, x, y);
+                HoldMainPanel = 0;
+              }
+            }
+            break;
+          case LeaveNotify:
+            if (AutoHide && !HoldMainPanel)
+            {
+              GetRealGeometry(Dpy, MainPanel->uber->IconWinParent, &x, &y,
+                              (ushort*)&tw, (ushort*)&th,
+                              (ushort*)&border_width, (ushort*)&depth);
+              if (((0  >  Event.xcrossing.x) || (0  >  Event.xcrossing.y) ||
+                   (tw <= Event.xcrossing.x) || (th <= Event.xcrossing.y)
+                  ) &&
+                  (((AutoHide == 2) && (x+tw >= dpw) && (x = dpw-ShowWidth)) ||
+                   ((AutoHide == 1) && (y+th >= dph) && (y = dph-ShowWidth))
+                  )
+                 )
+              { XMoveWindow(Dpy, MainPanel->uber->IconWinParent, x, y);
+                HoldMainPanel = 1;
+              }
+            }
+            break;
+
 	  default:
 #           ifdef DEBUG_EVENTS
 	    fprintf(stderr,"%s: Event fell through unhandled\n",MyName);
@@ -1041,6 +1157,11 @@
   unsigned long gcm;
   XClassHint myclasshints;
 
+  x = CurrentPanel->uber->x; /* Geometry x where to put the panel */
+  y = CurrentPanel->uber->y; /* Geometry y where to put the panel */
+  xneg = CurrentPanel->uber->w;
+  yneg = CurrentPanel->uber->h;
+
   if(maxx<16)
     maxx=16;
   if(maxy<16)
@@ -1160,13 +1281,15 @@
 # endif
   XSetWMProtocols(Dpy,MyWindow,&_XA_WM_DEL_WIN,1);
 
-  myclasshints.res_name=strdup(MyName);
-  myclasshints.res_class=strdup("FvwmButtons");
+  myclasshints.res_name=strdup((CurrentPanel == MainPanel)
+                                 ? MyName : CurrentPanel->uber->title);
+  myclasshints.res_class=strdup((CurrentPanel == MainPanel)
+                                 ? "FvwmButtons" : "FvwmButtonsPanel");
 
   {
     XTextProperty mynametext;
     char *list[]={NULL,NULL};
-    list[0]=MyName;
+    list[0]=(CurrentPanel == MainPanel) ? MyName : CurrentPanel->uber->title;
     if(!XStringListToTextProperty(list,1,&mynametext))
       {
 	fprintf(stderr,"%s: Failed to convert name to XText\n",MyName);
@@ -1652,3 +1775,116 @@
       }
 }
 
+/*
+ * Button
+ *   b->flags     |= b_Action (though b->hangon is ilegally used)
+ *   b->action[0]  = "panel-h" or "panel-v"
+ *   b->hangon     = panel title (case sensitive)
+ * Panel
+ *   uber->flags  |= b_Container (though the following fields are ilegally used)
+ *   uber->title   = the title of the panel
+ *   uber->IconWinParent = the panel window, will be assigned to MyWindow
+ *   uber->swallow = 0:hidden 1:shown
+ *   uber->icon_w  = panel width
+ *   uber->icon_h  = panel height
+ *   uber->x       = x position to start the panel wrt the button b
+ *   uber->y       = y position to start the panel wrt the button b
+ *   uber->w       = xneg
+ *   uber->h       = yneg
+ *   CurrentPanel  = the panel where the button b was pressed to popup a new panel
+ */
+
+#define PanelPopUpStep 32
+
+panel_info *seekpanel (button_info *b)
+{
+  panel_info *PanelIndex = MainPanel->next; /* skip the main panel */
+
+  while (PanelIndex && strcmp(b->hangon, PanelIndex->uber->title))
+    PanelIndex = PanelIndex->next;
+  
+  return PanelIndex;
+}
+
+void Slide (panel_info *p, button_info *b)
+{
+  Window PanelWin;
+  Window root;
+  int x, y, iw, ih, BW, depth;
+
+  if (!p) return;                            /* no such panel */
+  else    PanelWin = p->uber->IconWinParent; /* PanelWin is found */
+
+  if (p->uber->swallow)
+  { /* shown ---> hidden */
+    root = GetRealGeometry(Dpy, PanelWin, &x, &y,
+                           (ushort*)&iw, (ushort*)&ih,
+                           (ushort*)&BW, (ushort*)&depth);
+
+    if (b && b->action[0][6] == 'h')
+      while (PanelPopUpStep < iw)  /* horizontal */
+      { iw -= PanelPopUpStep;
+        x  += PanelPopUpStep;
+        XMoveResizeWindow(Dpy, PanelWin, x, y, iw, ih);
+      }
+    else
+      while (PanelPopUpStep < ih)  /* vertical */
+      { ih -= PanelPopUpStep;
+        y  += PanelPopUpStep;
+        XMoveResizeWindow(Dpy, PanelWin, x, y, iw, ih);
+      }
+
+    XUnmapWindow(Dpy, PanelWin);
+    p->uber->swallow = 0;
+    HoldMainPanel--;               /* facilitate AutoHide */
+  } 
+  else
+  { /* hidden ---> shown */
+    int ix = buttonXPos(b, b->n);  /* button in the CurrentPanel */
+    int iy = buttonYPos(b, b->n);  /* button in the CurrentPanel */
+
+    int mw = p->uber->icon_w; /* panel menu width */
+    int mh = p->uber->icon_h; /* panel menu height */
+    int w;                    /* current width */
+    int h;                    /* current height */
+
+    root = GetRealGeometry(Dpy, CurrentPanel->uber->IconWinParent, &x, &y,
+                           (ushort*)&iw, (ushort*)&ih,
+                           (ushort*)&BW, (ushort*)&depth);
+
+    x += p->uber->x + ix;
+    y += p->uber->y + iy;
+
+    /* initial position and size */
+    if (b->action[0][6] == 'h')
+    { w = mw % PanelPopUpStep;      /* horizontal */
+      if (w == 0) w = PanelPopUpStep;
+      x -= w;
+      XMoveResizeWindow(Dpy, PanelWin, x, y, w, mh);
+    }
+    else
+    { h = mh % PanelPopUpStep;      /* vertical */
+      if (h == 0) h = PanelPopUpStep;
+      y -= h;
+      XMoveResizeWindow(Dpy, PanelWin, x, y, mw, h);
+    }
+ 
+    XMapSubwindows(Dpy, PanelWin);
+    XMapWindow(Dpy, PanelWin);
+
+    if (b->action[0][6] == 'h')
+      while (w < mw)            /* horizontal */
+      { x -= PanelPopUpStep;
+        w += PanelPopUpStep;
+        XMoveResizeWindow(Dpy, PanelWin, x, y, w, mh);
+      }
+    else
+      while (h < mh)            /* vertical */
+      { y -= PanelPopUpStep;
+        h += PanelPopUpStep;
+        XMoveResizeWindow(Dpy, PanelWin, x, y, mw, h);
+      }
+    p->uber->swallow = 1;
+    HoldMainPanel++;            /* hinder AutoHide */
+  }
+}
diff -u -b -B old/fvwm-2.0.46/modules/FvwmButtons/FvwmButtons.h fvwm-2.0.46/modules/FvwmButtons/FvwmButtons.h
--- old/fvwm-2.0.46/modules/FvwmButtons/FvwmButtons.h	Thu Jun 26 16:32:32 1997
+++ fvwm-2.0.46/modules/FvwmButtons/FvwmButtons.h	Mon Feb  2 22:19:39 1998
@@ -69,6 +69,7 @@
 #define b_TitleHoriz  0x03 /* Mask for title x positioning info */
 #define b_Horizontal  0x04 /* HACK: stack title and iconwin horizontally */
 
+typedef struct panel_info_struct panel_info;
 typedef struct button_info_struct button_info;
 typedef struct container_info_struct container_info;
 #define ushort unsigned int
@@ -143,6 +144,11 @@
   ushort w,h,bw;           /* b_Swallow */
 };
 
+struct panel_info_struct
+{ button_info  *uber;      /* panel */
+  panel_info   *next;
+};
+
 #include "button.h"
 
 /* -------------------------------- other macros --------------------------- */
@@ -173,6 +179,7 @@
 extern Window MyWindow;
 extern char *MyName;
 extern button_info *UberButton,*CurrentButton;
+extern panel_info *MainPanel, *CurrentPanel;
 
 extern char *iconPath;
 extern char *pixmapPath;
Only in fvwm-2.0.46/modules/FvwmButtons: Makefile
diff -u -b -B old/fvwm-2.0.46/modules/FvwmButtons/parse.c fvwm-2.0.46/modules/FvwmButtons/parse.c
--- old/fvwm-2.0.46/modules/FvwmButtons/parse.c	Thu Jun 26 16:32:33 1997
+++ fvwm-2.0.46/modules/FvwmButtons/parse.c	Wed Feb 11 22:04:49 1998
@@ -28,8 +28,9 @@
 #include "FvwmButtons.h"
 #include "button.h"
 
-extern int w,h,x,y,xneg,yneg;
+extern int w,h,x,y,xneg,yneg; /* used in ParseConfigLine */ 
 extern char *config_file;
+extern AutoHide;
 
 /* ----------------------------------- macros ------------------------------ */
 
@@ -400,6 +401,7 @@
     {
       char *opts[]={"back","fore","font","title","icon","frame","padding",
 		      "swallow","action","container","end","nosize","size",
+                      "panel",
 		      NULL};
       trimleft(s);
       while(*s && *s!=')')
@@ -620,6 +622,42 @@
 		fprintf(stderr,"%s: Illegal size arguments\n",MyName);
 	      break;
 
+	      /* --------------------------- panel ------------------------ */
+	     
+	    case 13: /* Panel */
+	      trimleft(s);
+	      i=0;
+	      if(*s=='(')
+		{
+		  s++;
+		  i=strtol(s,&t,10);
+		  s=t;
+		  while(*s && *s!=')') 
+		    s++;
+		  if(*s==')')s++;
+		}
+              if (i == 1)
+              { /* horizontal */
+                AddButtonAction(b,0,strdup("panel-h"));
+                if ((~b->flags)&(b_Title | b_Icon))
+		{ b->icon_file = strdup("arrow_left.xpm");
+		  b->IconWin   = None;
+		  b->flags    |= b_Icon;
+                }
+              }
+	      else
+              { /* vertical */
+                AddButtonAction(b,0,strdup("panel-v"));
+                if ((~b->flags)&(b_Title | b_Icon))
+		{ b->icon_file = strdup("arrow_up.xpm");
+		  b->IconWin   = None;
+		  b->flags    |= b_Icon;
+                }
+              }
+	      t = seekright(&s);
+              b->hangon = strdup(t);  /* which panel to popup */
+	      break;
+
 	    default:
 	      t=seekright(&s);
 	      fprintf(stderr,"%s: Illegal button option \"%s\"\n",MyName,t);
@@ -695,7 +733,7 @@
 {
   button_info *ub=*ubb;
   char *opts[]={"geometry","font","padding","columns","rows","back","fore",
-		  "frame","file","pixmap",NULL};
+		  "frame","file","pixmap","panel","autohide",NULL};
   int i,j,k;
 
   switch(MatchSeveralLines(s,opts,&s))
@@ -711,10 +749,10 @@
 	    flags=XParseGeometry(geom,&g_x,&g_y,&width,&height);
 	    if(flags&WidthValue) w=width;
 	    if(flags&HeightValue) h=height;
-	    if(flags&XValue) x=g_x;
-	    if(flags&YValue) y=g_y;
-	    if(flags&XNegative) xneg=1;
-	    if(flags&YNegative) yneg=1;
+	    if(flags&XValue)    UberButton->x = g_x;
+	    if(flags&YValue)    UberButton->y = g_y;
+	    if(flags&XNegative) UberButton->w = 1;
+	    if(flags&YNegative) UberButton->h = 1;
 	  }
 	break;
       }
@@ -755,6 +793,25 @@
       else
         CopyString(&(ub->c->back_file),s);
       ub->c->flags|=b_IconBack;
+      break;
+    case 10:/* Panel */
+      trimleft(s);
+      CurrentPanel->next = (panel_info *) mymalloc(sizeof(panel_info));
+      CurrentPanel = CurrentPanel->next;
+      CurrentPanel->next = NULL;
+      CurrentPanel->uber = UberButton
+                         = (button_info *) mymalloc(sizeof(button_info));
+      UberButton->title = seekright(&s);
+      UberButton->flags = 0;
+      UberButton->parent = NULL;
+      UberButton->BWidth = 1;
+      UberButton->BHeight = 1;
+      UberButton->swallow = 0; /* subpanel is hidden initially */
+      MakeContainer(UberButton);
+      *ubb = UberButton;
+      break;
+    case 11:/* AutoHide */
+      if (sscanf(s, "%d", &j)) AutoHide = j;
       break;
     default:
       trimleft(s);