/****************************************************************************
 * This module is all original code 
 * by Rob Nation 
 * Copyright 1993, Robert Nation
 *     You may use this code for any purpose, as long as the original
 *     copyright remains in the source code and all documentation
 ****************************************************************************/


/***********************************************************************
 *
 * fvwm menu code
 *
 ***********************************************************************/
#include <FVWMconfig.h>

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <X11/keysym.h>
#include <sys/types.h>
#include <sys/time.h>

#include "fvwm.h"
#include "menus.h"
#include "misc.h"
#include "parse.h"
#include "screen.h"

int menu_on=0;

MenuRoot *ActiveMenu = NULL;		/* the active menu */
MenuItem *ActiveItem = NULL;		/* the active menu item */

int menuFromFrameOrWindowOrTitlebar = FALSE;

extern int Context,Button;
extern FvwmWindow *ButtonWindow, *Tmp_win;
extern XEvent Event;
int Stashed_X, Stashed_Y, MenuY = 0;

void DrawTrianglePattern(Window,GC,int,int,int,int);
void DrawSeparator(Window, GC,GC,int, int,int,int,int);
void DrawUnderline(Window w, GC gc, int x, int y, char *txt, int off);
int UpdateMenu(int sticks); 
int mouse_moved = 0;
int menu_aborted = 0;

int PrevActiveMenuX = -1;

/* Menu side-picture support */
void PaintSidePic(MenuRoot *mr);

extern XContext MenuContext;
/****************************************************************************
 *
 * Initiates a menu pop-up
 *
 * Style = 1 = sticky menu, stays up on initial button release.
 * Style = 0 = transient menu, drops on initial release.
 ***************************************************************************/
int do_menu (MenuRoot *menu, int style)
{
  int prevStashedX=0,prevStashedY=0;
  MenuRoot *PrevActiveMenu=0;
  MenuItem *PrevActiveItem=0;
  int retval=MENU_NOP;
  int x,y;
  Time t0;
  extern Time lastTimestamp;
  int PrevMenuX = PrevActiveMenuX;

  /* this condition could get ugly */
  if(menu->in_use)
    return MENU_ERROR;

  /* In case we wind up with a move from a menu which is
   * from a window border, we'll return to here to start
   * the move */
  XQueryPointer( dpy, Scr.Root, &JunkRoot, &JunkChild,
		&x, &y, &JunkX, &JunkY, &JunkMask);    

  if(menu_on)
    {
      prevStashedX = Stashed_X;
      prevStashedY = Stashed_Y;

      PrevActiveMenu = ActiveMenu;
      PrevActiveItem = ActiveItem;

      if(ActiveMenu)
        x = Stashed_X + ActiveMenu->width - 3;

      if(ActiveItem)
	y = ActiveItem->y_offset + MenuY;
    }
  else
    {
      mouse_moved = 0;
      t0 = lastTimestamp;
      if(!GrabEm(MENU))
	{
	XBell(dpy,Scr.screen);
	return MENU_DONE;
	}
      x += 2;
    }
  if (PopUpMenu (menu, x, y))
    {
      retval = UpdateMenu(style);
    }
  else
    XBell (dpy, Scr.screen);

  ActiveMenu = PrevActiveMenu;
  ActiveItem = PrevActiveItem;
  if((ActiveItem)&&(menu_on))
    ActiveItem->state = 1;
  Stashed_X = prevStashedX;
  Stashed_Y = prevStashedY;


  if(!menu_on)
    {
      UngrabEm();
      WaitForButtonsUp();
    }
  if(((lastTimestamp-t0) < 3*Scr.ClickTime) && (mouse_moved == 0))
    menu_aborted = 1;
  else
    menu_aborted = 0;

  PrevActiveMenuX = PrevMenuX;
  return retval;
}

/***********************************************************************
 *
 *  Procedure:
 *	RelieveRectangle - add relief lines to a rectangular window
 *
 ***********************************************************************/
void RelieveRectangle(Window win,int x,int y,int w, int h,GC Hilite,GC Shadow)
  {
  XDrawLine(dpy, win, Hilite, x, y, w+x-1, y);
  XDrawLine(dpy, win, Hilite, x, y, x, h+y-1);

  XDrawLine(dpy, win, Shadow, x, h+y-1, w+x-1, h+y-1);
  XDrawLine(dpy, win, Shadow, w+x-1, y, w+x-1, h+y-1);
  }

/***********************************************************************
 *
 *  Procedure:
 *	RelieveHalfRectangle - add relief lines to the sides only of a
 *      rectangular window
 *
 ***********************************************************************/
void RelieveHalfRectangle(Window win,int x,int y,int w,int h,
			  GC Hilite,GC Shadow)
  {
  XDrawLine(dpy, win, Hilite, x+1, y, x+1, h+y-1);

  XDrawLine(dpy, win, Scr.BlackGC, w+x-1, y-1, w+x-1, h+y);
  XDrawLine(dpy, win, Shadow, w+x-2, y, w+x-2, h+y-1);
  }


/***********************************************************************
 *
 *  Procedure:
 *      PaintEntry - draws a single entry in a poped up menu
 *
 *  If a side picture specified, all entries are shifted to the right
 *
 ***********************************************************************/
void PaintEntry(MenuRoot *mr, MenuItem *mi)
  {
  int y_offset, text_y, d, y_height, y, x;
  GC ShadowGC, ReliefGC, TextGC;

  y_offset = mi->y_offset;
  y_height = mi->y_height;
  text_y = y_offset + Scr.StdFont.y;

  if (mi->picture) text_y += mi->picture->height;

  /* center text vertically if the pixmap is taller */
  if (mi->lpicture)
    {
    y = mi->lpicture->height - Scr.StdFont.height;
    if (y > 1) text_y += y >> 1;
    }    

  ShadowGC = Scr.MenuShadowGC;
  if(Scr.d_depth<2)
    ReliefGC = Scr.MenuShadowGC;
  else
    ReliefGC = Scr.MenuReliefGC;

  TextGC = Scr.MenuGC;

  /* active cursor over entry? */
  if ((mi->state)&&(mi->func_type != F_TITLE)&&
      (mi->func_type != F_NOP)&&*mi->item)
    {
    Globalgcv.foreground = Scr.SelColors.back;
    Globalgcm = GCForeground;
    XChangeGC(dpy, Scr.ScratchGC1, Globalgcm, &Globalgcv);
    XFillRectangle(dpy, mr->w, Scr.ScratchGC1, mr->xoffset+3, y_offset,
                   mr->width - mr->xoffset-6, y_height);
    TextGC = Scr.SelGC;
    }
  else
    {
    if((!mi->prev)||(!mi->prev->state))
      XClearArea(dpy, mr->w, mr->xoffset, y_offset-1, mr->width,y_height+2,0);
    else
      XClearArea(dpy, mr->w, mr->xoffset, y_offset+1, mr->width,y_height-1,0);
    }

  RelieveHalfRectangle(mr->w, 0, y_offset-1, mr->width,
                       y_height+2, ReliefGC, ShadowGC);

  if(mi->func_type == F_TITLE)
    {
    text_y += HEIGHT_EXTRA>>1;

    if(mi->next != NULL)
      {
      DrawSeparator(mr->w,ShadowGC,ReliefGC,5+mr->xoffset, y_offset+y_height-3,
		    mr->width-6, y_offset+y_height-3,1);
      }
    if(mi != mr->first)
      {
      text_y += HEIGHT_EXTRA_TITLE>>1;
      DrawSeparator(mr->w,ShadowGC,ReliefGC,5+mr->xoffset, y_offset+1,
		    mr->width-6, y_offset+1,1);
      }
    }
  else
    {
    text_y += HEIGHT_EXTRA>>1;
    }

  if(mi->func_type == F_NOP && *mi->item==0) 
    {
    DrawSeparator(mr->w, ShadowGC, ReliefGC, 2+mr->xoffset, 
                      y_offset-1+(HEIGHT_SEPARATOR >> 1),
		      mr->width-3,y_offset-1+(HEIGHT_SEPARATOR >> 1), 0);
    }

  /* Top of the menu */
  if(mi == mr->first) 
    /*DrawSeparator(mr->w,ReliefGC,ReliefGC,0,0, mr->width-1,0,-1);*/
    XDrawLine(dpy, mr->w, ReliefGC, 1, 1, mr->width-3, 1);

  /* Botton of the menu */
  if(mi->next == NULL) 
    DrawSeparator(mr->w,ShadowGC,Scr.BlackGC,1,mr->height-2,
		  mr->width-2, mr->height-2,1);

#ifdef I18N
#undef FONTSET
#define FONTSET	Scr.StdFont.fontset
#endif
  if (*mi->item)
    if (check_allowed_function(mi))
      {
      XDrawString(dpy, mr->w, TextGC, 
                  mi->x + mr->xoffset, text_y, mi->item, mi->strlen);
      }
    else
      {
      /* should be a shaded out word, no just re-colored. */
      if (!mi->state)
        XDrawString(dpy, mr->w, ReliefGC,
                    mi->x + mr->xoffset+1, text_y+1, mi->item, mi->strlen);
      XDrawString(dpy, mr->w, ShadowGC,
                  mi->x+mr->xoffset, text_y, mi->item, mi->strlen);
      }

  if (mi->strlen2>0)
    if (check_allowed_function(mi))
      {
      XDrawString(dpy, mr->w, TextGC,
                  mi->x2+mr->xoffset, text_y, mi->item2, mi->strlen2);
      }
    else
      {
      /* should be a shaded out word, no just re-colored. */
      if (!mi->state)
        XDrawString(dpy, mr->w, ReliefGC,
                    mi->x2+1 + mr->xoffset, text_y+1, mi->item2, mi->strlen2);
      XDrawString(dpy, mr->w, ShadowGC,
                  mi->x2 + mr->xoffset, text_y, mi->item2, mi->strlen2);
      }

  /* pete@tecc.co.uk: If the item has a hot key, underline it */
  if (mi->hotkey > 0)
    if (check_allowed_function(mi))
      {
      DrawUnderline(mr->w, TextGC, mi->x + mr->xoffset, text_y,
                    mi->item, mi->hotkey - 1);
      }
    else
      {
      /* should be a shaded out underline, no just re-colored. */
      if (!mi->state)
        DrawUnderline(mr->w, ReliefGC, mi->x+1 + mr->xoffset, text_y+1,
                      mi->item, mi->hotkey - 1);
      DrawUnderline(mr->w, ShadowGC, mi->x + mr->xoffset, text_y,
                    mi->item, mi->hotkey - 1);
      }

  if (mi->hotkey < 0)
    if (check_allowed_function(mi))
      {
      DrawUnderline(mr->w, TextGC, mi->x2 + mr->xoffset, text_y,
                    mi->item2, -1 - mi->hotkey);
      }
    else
      {
      /* should be a shaded out underline, no just re-colored. */
      if (!mi->state)
        DrawUnderline(mr->w, ReliefGC, mi->x2+1 + mr->xoffset, text_y+1,
                      mi->item2, -1 - mi->hotkey);
      DrawUnderline(mr->w, ShadowGC, mi->x2 + mr->xoffset, text_y,
                    mi->item2, -1 - mi->hotkey);
      }

  d=(Scr.EntryHeight-7) >> 1;
  if(mi->func_type == F_POPUP)
    {
    int ym;

    ym = y_offset + (y_height >> 1) -1;
    DrawTrianglePattern(mr->w, TextGC, mr->width-d-8,
		        ym-4, mr->width-d-4, ym+4);
    }

  if(mi->picture)
    {
      x = (mr->width - mi->picture->width) >> 1;
      if(mi->lpicture && x < mr->width0 + 5)
	x = mr->width0 + 5;
	
      if(mi->picture->depth > 0) /* pixmap? */
	{
	  Globalgcm = GCClipMask | GCClipXOrigin | GCClipYOrigin;
	  Globalgcv.clip_mask = mi->picture->mask;
	  Globalgcv.clip_x_origin = x;
	  Globalgcv.clip_y_origin = y_offset+1;
	  XChangeGC(dpy,ReliefGC,Globalgcm,&Globalgcv);
	  XCopyArea(dpy,mi->picture->picture,mr->w,ReliefGC,
		    0,0,mi->picture->width, mi->picture->height,
		    x,y_offset+1);
	  Globalgcm = GCClipMask;
	  Globalgcv.clip_mask = None;
	  XChangeGC(dpy,ReliefGC,Globalgcm,&Globalgcv);
	}
      else
	{
	  XCopyPlane(dpy,mi->picture->picture,mr->w,
		     TextGC,0,0,mi->picture->width,mi->picture->height,
		     x,y_offset+1,1);
	}
    }

  if(mi->lpicture)
    {
      if(mi->picture && *mi->item != 0)
	y = y_offset + mi->y_height - mi->lpicture->height-1;
      else 
	y = y_offset + (mi->y_height >> 1) - (mi->lpicture->height >> 1);
      if(mi->lpicture->depth > 0) /* pixmap? */
	{
	  Globalgcm = GCClipMask | GCClipXOrigin | GCClipYOrigin;
	  Globalgcv.clip_mask = mi->lpicture->mask;
	  Globalgcv.clip_x_origin = 9 + mr->xoffset;
	  Globalgcv.clip_y_origin = y;

	  XChangeGC(dpy, ReliefGC, Globalgcm, &Globalgcv);
	  XCopyArea(dpy, mi->lpicture->picture, mr->w, ReliefGC, 0, 0,
		    mi->lpicture->width, mi->lpicture->height,
                    9 + mr->xoffset, y);
	  Globalgcm = GCClipMask;
	  Globalgcv.clip_mask = None;
	  XChangeGC(dpy, ReliefGC, Globalgcm, &Globalgcv);
	}
      else
	{
	  XCopyPlane(dpy, mi->lpicture->picture, mr->w,
		     TextGC, 0, 0, mi->lpicture->width, mi->lpicture->height,
		     9 + mr->xoffset, y, 1);
	}
    }
  return;
}

/****************************************************************************
 * Procedure:
 *	DrawUnderline() - Underline a character in a string (pete@tecc.co.uk)
 *
 * Calculate the pixel offsets to the start of the character position we
 * want to underline and to the next character in the string.  Shrink by
 * one pixel from each end and the draw a line that long two pixels below
 * the character...
 *
 ****************************************************************************/
void  DrawUnderline(Window w, GC gc, int x, int y, char *txt, int posn) 
  {
  int off1 = XTextWidth(Scr.StdFont.font, txt, posn);
  int off2 = XTextWidth(Scr.StdFont.font, txt, posn + 1) - 1;

  XDrawLine(dpy, w, gc, x + off1, y + 1, x + off2, y + 1);
  }

/****************************************************************************
 *
 *  Draws two horizontal lines to form a separator
 *
 ****************************************************************************/
void DrawSeparator(Window w, GC TopGC, GC BottomGC,int x1,int y1,int x2,int y2,
		   int extra_off)
  {
  XDrawLine(dpy, w, TopGC   , x1,           y1,  x2,          y2);
  XDrawLine(dpy, w, BottomGC, x1-extra_off, y1+1,x2+extra_off,y2+1);
  }
    
/****************************************************************************
 *
 *  Draws a little Triangle pattern within a window
 *
 ****************************************************************************/
void DrawTrianglePattern(Window w, GC gc, int l, int t, int r, int b)
{
  int m;
  XPoint points[3];

  m = (t + b) >> 1;

  points[0].x = l;
  points[0].y = t;
  points[1].x = l;
  points[1].y = b;
  points[2].x = r;
  points[2].y = m;

  XFillPolygon(dpy, w, gc, points, 3, Convex, CoordModeOrigin);
}

/************************************************************
 *
 * Draws a picture on the left side of the menu
 *
 ************************************************************/

void PaintSidePic(MenuRoot *mr)
{
  GC ReliefGC, TextGC;

  if(mr->sidePic) {
    if(Scr.d_depth<2)
      ReliefGC = Scr.WinShadowGC;
    else
      ReliefGC = Scr.WinReliefGC;
    TextGC = Scr.WinGC;

    if(mr->colorize) {
      Globalgcv.foreground = mr->sideColor;
      Globalgcm = GCForeground;
      XChangeGC(dpy, Scr.ScratchGC1, Globalgcm, &Globalgcv);
      XFillRectangle(dpy, mr->w, Scr.ScratchGC1, 3, 3,
                   mr->sidePic->width, mr->height - 6);
    }
    
    if(mr->sidePic->depth > 0) /* pixmap? */
      {
      Globalgcm = GCClipMask | GCClipXOrigin | GCClipYOrigin;
      Globalgcv.clip_mask = mr->sidePic->mask;
      Globalgcv.clip_x_origin = 3;
      Globalgcv.clip_y_origin = mr->height - mr->sidePic->height -3;
      
      XChangeGC(dpy,ReliefGC,Globalgcm,&Globalgcv);
      XCopyArea(dpy, mr->sidePic->picture, mr->w,
                ReliefGC, 0, 0,
                mr->sidePic->width, mr->sidePic->height,
                Globalgcv.clip_x_origin, Globalgcv.clip_y_origin);
      Globalgcm = GCClipMask;
      Globalgcv.clip_mask = None;
      XChangeGC(dpy,ReliefGC,Globalgcm,&Globalgcv);    
    } else {
      XCopyPlane(dpy, mr->sidePic->picture, mr->w,
                 TextGC, 0, 0,
                 mr->sidePic->width, mr->sidePic->height,
                 1, mr->height - mr->sidePic->height, 1);
      }
  }
}

/***********************************************************************
 *
 *  Procedure:
 *	PaintMenu - draws the entire menu
 *
 ***********************************************************************/
void PaintMenu(MenuRoot *mr, XEvent *e)
{
  MenuItem *mi;
  
  for (mi = mr->first; mi != NULL; mi = mi->next)
    {
      /* be smart about handling the expose, redraw only the entries
       * that we need to
       */
      if (e->xexpose.y < (mi->y_offset + mi->y_height) &&
	  (e->xexpose.y + e->xexpose.height) > mi->y_offset)
	{
	  PaintEntry(mr, mi);
	}
    }

  /* Side picture support */
  PaintSidePic(mr);

  XSync(dpy, 0);
  return;
}

MenuRoot *PrevMenu = NULL;
MenuItem *PrevItem = NULL;
int PrevY=0;


/***********************************************************************
 *
 *  Procedure:
 *	Updates menu display to reflect the highlighted item
 *
 ***********************************************************************/
int FindEntry(void)
{
  MenuItem *mi;
  MenuRoot *actual_mr;
  int retval = MENU_NOP;
  MenuRoot *PrevPrevMenu;
  MenuItem *PrevPrevItem;
  int PrevPrevY;
  int x, y, ChildY;
  Window Child;

  XQueryPointer( dpy, Scr.Root, &JunkRoot, &Child,
		&JunkX,&ChildY, &x, &y, &JunkMask);
  XQueryPointer( dpy, ActiveMenu->w, &JunkRoot, &JunkChild,
		&JunkX, &ChildY, &x, &y, &JunkMask);

  /* look for the entry that the mouse is in */
  for(mi=ActiveMenu->first; mi; mi=mi->next)
    if(y>=mi->y_offset && y<mi->y_offset+mi->y_height)
      break;
  if(x<3 || x>ActiveMenu->width-3)
    mi = NULL;


  /* if we weren't on the active entry, let's turn the old active one off */
  if ((ActiveItem)&&(mi!=ActiveItem))
    {
      ActiveItem->state = 0;
      PaintEntry(ActiveMenu, ActiveItem);
    }
  /* if we weren't on the active item, change the active item and turn it on */
  if ((mi!=ActiveItem)&&(mi != NULL))
    {
      mi->state = 1;
      PaintEntry(ActiveMenu, mi);
    }
  ActiveItem = mi;

  if(ActiveItem)
    {
      MenuRoot *menu;
      /* create a new sub-menu */
      if((ActiveItem->func_type == F_POPUP) /* use this to prevent automatic
                                               popup, so you have to place
                                               the cursor near the |> symbol 
         && (x > (3*ActiveMenu->width>>2)) */)
	{
	  PrevPrevMenu = PrevMenu;
	  PrevPrevItem = PrevItem;
	  PrevPrevY = PrevY;
	  PrevY = MenuY;
	  PrevMenu = ActiveMenu;
	  PrevItem = ActiveItem;
	  /*******************************************/
	  menu = FindPopup(&ActiveItem->action[5]);
	  if(menu != NULL)
	    {/*
              if (ActiveMenu->has_coords) {
                menu->x = ActiveMenu->x + ActiveMenu->width;
                menu->y = ActiveMenu->y;
              }*/
	      retval = do_menu(menu,0);
	      /* Unfortunately, this is needed (why?) for multi-screen 
		 operation */
	      /* flush_expose(ActiveMenu->w);*/
	      for (mi = ActiveMenu->first; mi != NULL; mi = mi->next)
		{
		  PaintEntry(ActiveMenu, mi);
		}
	      XSync(dpy, 0);
	      MenuY = PrevY;
	      PrevMenu = PrevPrevMenu;
	      PrevItem = PrevPrevItem;
	      PrevY = PrevPrevY;
	    }
	}
    }
  /* end a sub-menu */ 
  if (XFindContext (dpy, Child,MenuContext,(caddr_t *)&actual_mr)==XCNOENT)
    {
      return retval;
    }

  if(actual_mr != ActiveMenu)
    {
      if(actual_mr == PrevMenu)
	{
	  if((PrevItem->y_offset + PrevY > ChildY)||
	     ((PrevItem->y_offset+PrevItem->y_height + PrevY) < ChildY))
	    {
	      return SUBMENU_DONE;
	    }
	}
      else
	return SUBMENU_DONE;
    }
  return retval;
}

/***********************************************************************
 * Procedure
 * 	menuShortcuts() - Menu keyboard processing (pete@tecc.co.uk)
 *
 * Function called from UpdateMenu instead of Keyboard_Shortcuts()
 * when a KeyPress event is received.  If the key is alphanumeric,
 * then the menu is scanned for a matching hot key.  Otherwise if
 * it was the escape key then the menu processing is aborted.
 * If none of these conditions are true, then the default processing
 * routine is called.
 ***********************************************************************/
void menuShortcuts(XEvent *ev) 
{
  MenuItem *mi;
  KeySym keysym = XLookupKeysym(&ev->xkey,0);
  
  /* Try to match hot keys */
  if (((keysym >= XK_a) && (keysym <= XK_z)) ||	/* Only consider alphabetic */
      ((keysym >= XK_0) && (keysym <= XK_9)))	/* ...or numeric keys	*/
    {
      /* Search menu for matching hotkey */
      for (mi = ActiveMenu->first; mi; mi = mi->next) 
	{
	  char key;
	  if (mi->hotkey == 0) continue;	/* Item has no hotkey	*/
	  key = (mi->hotkey > 0) ?		/* Extract hot character */
	    mi->item[mi->hotkey - 1] : mi->item2[-1 - mi->hotkey];
	  
	  /* Convert to lower case to match the keysym */
	  if (isupper(key)) key = tolower(key);
	  
	  if (keysym == key)
	    {		/* Are they equal?		*/
	      ActiveItem = mi;		/* Yes: Make this the active item */
	      ev->type = ButtonRelease;	/* Force a menu exit		*/
	      return;
	    }
	}
    }
  
  switch(keysym)		/* Other special keyboard handling	*/
    {
    case XK_Escape:		/* Escape key pressed. Abort		*/
      ActiveItem = NULL;	/* No selection				*/
      ev->type = ButtonRelease;	/* Make the menu exit			*/
      break;
      
      /* Nothing special --- Allow other shortcuts (cursor movement)	*/
    default:
      Keyboard_shortcuts(ev,ButtonRelease);
      break;
    }
}

/***********************************************************************
 *
 *  Procedure:
 *	Updates menu display to reflect the highlighted item
 * 
 *  Input
 *      sticks = 0, transient style menu, drops on button release
 *      sticks = 1, sticky style, stays up if initial release is close to initial press.
 *  Returns:
 *      0 on error condition
 *      1 on return from submenu to parent menu
 *      2 on button release return
 *
 ***********************************************************************/
int UpdateMenu(int sticks)
{
  int done;
  int retval;
  MenuItem *InitialMI;
  MenuRoot *actual_mr;
  int x_init, y_init,x,y;

  XQueryPointer( dpy, Scr.Root, &JunkRoot, &JunkChild,
		&JunkX,&JunkY, &x_init, &y_init, &JunkMask);

  FindEntry();
  InitialMI = ActiveItem;

  while (TRUE)
    {
      /* block until there is an event */
      XMaskEvent(dpy, ButtonPressMask|ButtonReleaseMask|ExposureMask | 
		 KeyPressMask|VisibilityChangeMask|ButtonMotionMask, &Event);
      StashEventTime(&Event);
      done = 0;
      if (Event.type == MotionNotify) 
	{
	  /* discard any extra motion events before a release */
	  while((XCheckMaskEvent(dpy,ButtonMotionMask|ButtonReleaseMask,
				 &Event))&&(Event.type != ButtonRelease));
	}
      /* Handle a limited number of key press events to allow mouseless
       * operation */
      if(Event.type == KeyPress)
	menuShortcuts(&Event);

      switch(Event.type)
	{
	case ButtonRelease:
	  /* The following lines holds the menu when the button is released */
	  if(sticks)
	    {
	      sticks = 0;
	      break;
	    }
	  if (!ActiveItem && (menu_on > 1))
	    {
	      int x,y;
	      XQueryPointer( dpy, Scr.Root, &JunkRoot, &JunkChild,
			    &JunkX, &JunkY, &x, &y, &JunkMask);
	      if((XFindContext(dpy, JunkChild,MenuContext,
			      (caddr_t *)&actual_mr)!=XCNOENT)&&
		 (actual_mr != ActiveMenu))
		{
		  done = 1;
		  break;
		}
	    }
	  PopDownMenu();
	  if(ActiveItem)
	    {
	      done = 1;
	      ExecuteFunction(ActiveItem->action,
			      ButtonWindow, &Event, Context,-1);
	    }
	  ActiveItem = NULL;
	  ActiveMenu = NULL;
	  menuFromFrameOrWindowOrTitlebar = FALSE;
	  return MENU_DONE;

	case KeyPress:
	case VisibilityNotify:
	case ButtonPress:
	  done=1;
	  break;

	case MotionNotify:
	  XQueryPointer( dpy, Scr.Root, &JunkRoot, &JunkChild,
			&JunkX,&JunkY, &x, &y, &JunkMask);
	  if(((x-x_init > 3)||(x_init-x > 3))&&
	     ((y-y_init > 3)||(y_init-y > 3)))
	    mouse_moved = 1;
	  done = 1;

	  retval = FindEntry();
	  if(ActiveItem != InitialMI)
	    sticks = 0;
	  if((retval == MENU_DONE)||(retval == SUBMENU_DONE))
	    {
	      PopDownMenu();	      
	      ActiveItem = NULL;
	      ActiveMenu = NULL;
	      menuFromFrameOrWindowOrTitlebar = FALSE;
	    }

	  if(retval == MENU_DONE)
	    return MENU_DONE;
	  else if (retval == SUBMENU_DONE)
	    return MENU_NOP;

	  break;

	case Expose:
	  /* grab our expose events, let the rest go through */
	  if((XFindContext(dpy, Event.xany.window,MenuContext,
			   (caddr_t *)&actual_mr)!=XCNOENT))
	    {
	      PaintMenu(actual_mr,&Event);
	      done = 1;
	    }
	  break;

	default:
	  break;
	}
      
      if(!done)DispatchEvent();
      XFlush(dpy);
    }
}


/***********************************************************************
 *
 *  Procedure:
 *	PopUpMenu - pop up a pull down menu
 *
 *  Inputs:
 *	menu	- the root pointer of the menu to pop up
 *	x, y	- location of upper left of menu
 *      center	- whether or not to center horizontally over position
 *
 ***********************************************************************/
Bool PopUpMenu (MenuRoot *menu, int x, int y)
{
  if ((!menu)||(menu->w == None)||(menu->items == 0)||(menu->in_use))
    return False;

  menu_on++;
  InstallRootColormap();

  Stashed_X = x;
  Stashed_Y = y;
  
  /* pop up the menu */
  ActiveMenu = menu;
  ActiveItem = NULL;
  
  if((Tmp_win)&&(menu_on == 1)&&(Context&C_LALL))
    {
      y = Tmp_win->frame_y+Tmp_win->boundary_width+Tmp_win->title_height;
      x = Tmp_win->frame_x + Tmp_win->boundary_width + 
	ButtonPosition(Context,Tmp_win)*Tmp_win->title_height+1;
    }
  if((Tmp_win)&&(menu_on==1)&&(Context&C_RALL))
    {
      y = Tmp_win->frame_y+Tmp_win->boundary_width+Tmp_win->title_height;
      x = Tmp_win->frame_x +Tmp_win->frame_width - Tmp_win->boundary_width-
	ButtonPosition(Context,Tmp_win)*Tmp_win->title_height - menu->width+1;
    }
  if((Tmp_win)&&(menu_on==1)&&(Context&C_TITLE))
    {
      y = Tmp_win->frame_y+Tmp_win->boundary_width+Tmp_win->title_height;
      if(x < Tmp_win->frame_x + Tmp_win->title_x)
	x = Tmp_win->frame_x + Tmp_win->title_x;
      if((x + menu->width) >
	 (Tmp_win->frame_x + Tmp_win->title_x +Tmp_win->title_width))	
	x = Tmp_win->frame_x + Tmp_win->title_x +Tmp_win->title_width-
	  menu->width +1;
    }

  if (menu->has_coords)
    {
    /* we already have coordinates explicitly given */
    if (menu->x >= 0)
      x = menu->x;
    else
      x = Scr.MyDisplayWidth - menu->width + menu->x;

    if (menu->y >= 0)
      y = menu->y; /* - menu->height; */
    else
      y = Scr.MyDisplayHeight - menu->height + menu->y;

    Stashed_X = x;
    Stashed_Y = y;
    }

  if (PrevActiveMenuX != -1)
    if (x + menu->width > Scr.MyDisplayWidth - 2) {
       x = PrevActiveMenuX - menu->width + 2;
    }

  /* clip to screen */
  if (x + menu->width > Scr.MyDisplayWidth-2) 
    x = Scr.MyDisplayWidth - menu->width-2;
  if (x < 0) x = 0;

  if (y + menu->height > Scr.MyDisplayHeight-2) 
    {
      y = Scr.MyDisplayHeight - menu->height-2;
      /* Warp pointer to middle of top line */
      /* No with MWM menus! at least only in this case */
      if (menu_on < 1) /* was 2 */
	XWarpPointer(dpy, Scr.Root, Scr.Root, 0, 0, Scr.MyDisplayWidth, 
		     Scr.MyDisplayHeight, 
		     x + (menu->width>>1), (y + (Scr.EntryHeight >> 1)));
    }
  if (y < 0) y = 0;

  PrevActiveMenuX = x;

  MenuY = y;
  XMoveWindow(dpy, menu->w, x, y);
  XMapRaised(dpy, menu->w);
  menu->in_use = True;
  return True;
}


/***********************************************************************
 *
 *  Procedure:
 *	PopDownMenu - unhighlight the current menu selection and
 *		take down the menus
 *
 ***********************************************************************/
void PopDownMenu()
{
  if (ActiveMenu == NULL)
    return;
  
  menu_on--;
  if (ActiveItem)
    ActiveItem->state = 0;
  
  XUnmapWindow(dpy, ActiveMenu->w);

  UninstallRootColormap();
  XFlush(dpy);
  if (Context & (C_WINDOW | C_FRAME | C_TITLE | C_SIDEBAR))
    menuFromFrameOrWindowOrTitlebar = TRUE;
  else
    menuFromFrameOrWindowOrTitlebar = FALSE;
  ActiveMenu->in_use = FALSE;
}

/***************************************************************************
 * 
 * Wait for all mouse buttons to be released 
 * This can ease some confusion on the part of the user sometimes 
 * 
 * Discard superflous button events during this wait period.
 *
 ***************************************************************************/
void WaitForButtonsUp()
{
  Bool AllUp = False;
  XEvent JunkEvent;
  unsigned int mask;

  while(!AllUp)
    {
      XAllowEvents(dpy,ReplayPointer,CurrentTime);
      XQueryPointer( dpy, Scr.Root, &JunkRoot, &JunkChild,
		    &JunkX, &JunkY, &JunkX, &JunkY, &mask);    
      
      if((mask&
	  (Button1Mask|Button2Mask|Button3Mask|Button4Mask|Button5Mask))==0)
	AllUp = True;
    }
  XSync(dpy,0);
  while(XCheckMaskEvent(dpy,
			ButtonPressMask|ButtonReleaseMask|ButtonMotionMask,
			&JunkEvent))
    {
      StashEventTime (&JunkEvent);
      XAllowEvents(dpy,ReplayPointer,CurrentTime);
    }

}


void DestroyMenu(MenuRoot *mr)
{
  MenuItem *mi,*tmp2;
  MenuRoot *tmp, *prev;

  if(mr == NULL)
    return;

  tmp = Scr.AllMenus;
  prev = NULL;
  while((tmp != NULL)&&(tmp != mr))
    {
      prev = tmp;
      tmp = tmp->next;
    }
  if(tmp != mr)
    return;

  if(prev == NULL)
    Scr.AllMenus = mr->next;
  else
    prev->next = mr->next;

  XDestroyWindow(dpy,mr->w);
  XDeleteContext(dpy, mr->w, MenuContext);  
  
  if(mr->sidePic)
    DestroyPicture(dpy, mr->sidePic);

  /* need to free the window list ? */
  mi = mr->first;
  while(mi != NULL)
    {
      tmp2 = mi->next;
      if (mi->item != NULL) free(mi->item);
      if (mi->item2 != NULL) free(mi->item2);
      if (mi->action != NULL) free(mi->action);
      if(mi->picture)
	DestroyPicture(dpy,mi->picture);
      if(mi->lpicture)
	DestroyPicture(dpy,mi->lpicture);
      free(mi);
      mi = tmp2;
    }
  free(mr);
}

/****************************************************************************
 * 
 * Generates the windows for all menus
 *
 ****************************************************************************/ 
void MakeMenus(void)
{
  MenuRoot *mr;

  mr = Scr.AllMenus;
  while(mr != NULL)
    {
      MakeMenu(mr);
      mr = mr->next;
    }
}

/****************************************************************************
 * 
 * Generates the window for a menu
 *
 ****************************************************************************/ 
void MakeMenu(MenuRoot *mr)
{
  MenuItem *cur;
  unsigned long valuemask;
  XSetWindowAttributes attributes;
  int y,width;

  if((mr->func != F_POPUP)||(!Scr.flags & WindowsCaptured))
    return;

  /* allow two pixels for top border */
  mr->width = 0;
  mr->width2 = 0;
  mr->width0 = 0;
  for (cur = mr->first; cur != NULL; cur = cur->next)
    {
      width = XTextWidth(Scr.StdFont.font, cur->item, cur->strlen);
      if(cur->picture && width < cur->picture->width)
	width = cur->picture->width;
      if(cur->func_type == F_POPUP)
	width += 15;
      if (width <= 0)
	width = 1;
      if (width > mr->width)
	mr->width = width;

      width = XTextWidth(Scr.StdFont.font, cur->item2, cur->strlen2);
      if (width < 0)
	width = 0;
      if (width > mr->width2)
	mr->width2 = width;
      if((width==0)&&(cur->strlen2>0))
	mr->width2 = 1;

      if(cur->lpicture)
	if(mr->width0 < (cur->lpicture->width+3))
	  mr->width0 = cur->lpicture->width+3;
    }

  /* lets first size the window accordingly */
  mr->width += 10;
  if(mr->width2 > 0)
    mr->width += 5;

  mr->width2 += 15; /*22*/;
  mr->width0 += 7;

  for (y=2, cur = mr->first; cur != NULL; cur = cur->next)
    {
      cur->y_offset = y;
      cur->x = 5+mr->width0;
      if(cur->func_type==F_TITLE)
	{
	  /* Title */
	  if(cur->strlen2  == 0)
	    cur->x = (mr->width+mr->width2+mr->width0
		      - XTextWidth(Scr.StdFont.font, cur->item,
					     cur->strlen))>>1;
	  
	  if((cur->strlen > 0)||(cur->strlen2 > 0))
            cur->y_height = Scr.EntryHeight + HEIGHT_EXTRA_TITLE;
	  else
	    cur->y_height = HEIGHT_SEPARATOR;
	}
      else if((cur->strlen==0)&&(cur->strlen2 == 0))
	/* Separator */
	cur->y_height = HEIGHT_SEPARATOR;
      else
	/* Normal text entry */
	cur->y_height = Scr.EntryHeight;
      if(cur->picture)
	cur->y_height += cur->picture->height;
      if(cur->lpicture && cur->y_height < cur->lpicture->height+4)
	cur->y_height = cur->lpicture->height+4;
      y += cur->y_height;
      if(mr->width2 == 0)
	{
	  cur->x2 = cur->x;
	}
      else
	{
	  cur->x2 = mr->width -5 + mr->width0;
	}
    }
  mr->in_use = 0;
  mr->height = y+3;  /* for Win95 look, was y+2 */

  
#ifndef NO_SAVEUNDERS   
  valuemask = (CWBackPixel | CWEventMask | CWCursor | CWSaveUnder);
#else
  valuemask = (CWBackPixel | CWEventMask | CWCursor);
#endif
  attributes.background_pixel = Scr.MenuColors.back;
  attributes.event_mask = (ExposureMask | EnterWindowMask);
  attributes.cursor = Scr.FvwmCursors[MENU];
#ifndef NO_SAVEUNDERS   
  attributes.save_under = TRUE;
#endif
  if(mr->w != None)
    XDestroyWindow(dpy,mr->w);


  /* Side picture support: this makes the window wider for the picture
     and calculates offset */
  mr->xoffset = 0;
  if(mr->sidePic)
    if(mr->sidePic->height < mr->height) {
      mr->xoffset = mr->sidePic->width + 3;
    } else {
      DestroyPicture(dpy, mr->sidePic);
      mr->sidePic = NULL;
    }

  mr->width = mr->width0 + mr->width + mr->width2 + mr->xoffset;

  mr->w = XCreateWindow (dpy, Scr.Root, 0, 0, (unsigned int) (mr->width),
			 (unsigned int) mr->height, (unsigned int) 0,
			 CopyFromParent, (unsigned int) InputOutput,
			 (Visual *) CopyFromParent,
			 valuemask, &attributes);
  XSaveContext(dpy,mr->w,MenuContext,(caddr_t)mr);
  
  return;
}

/***********************************************************************
 * Procedure:
 *	scanForHotkeys - Look for hotkey markers in a MenuItem
 * 							(pete@tecc.co.uk)
 * 
 * Inputs:
 *	it	- MenuItem to scan
 * 	which 	- +1 to look in it->item1 and -1 to look in it->item2.
 *
 ***********************************************************************/
void scanForHotkeys(MenuItem *it, int which) 
{
  char *start, *txt;

  start = (which > 0) ? it->item : it->item2;	/* Get start of string	*/
  for (txt = start; *txt != '\0'; txt++) 
    {
      /* Scan whole string	*/
      if (*txt == '&') 
	{		/* A hotkey marker?			*/
	  if (txt[1] == '&') 
	    {	/* Just an escaped &			*/
	      char *tmp;		/* Copy the string down over it	*/
	      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...	*/
	}
    }
  it->hotkey = 0;		/* No hotkey found.  Set offset to zero	*/
}


void scanForPixmap(char *instring, Picture **p, char identifier) 
{
  char *tstart, *txt, *save_instring;
  int i;
  Picture *pp;
  char name[100];
  extern char *IconPath;
#ifdef XPM
  extern char *PixmapPath;
#endif

  /* save instring in case can't find pixmap */
  save_instring = (char *)safemalloc(strlen(instring)+1);
  strcpy(save_instring,instring);

  /* Scan whole string	*/
  for (txt = instring; *txt != '\0'; txt++) 
    {
      /* A hotkey marker? */
      if (*txt == identifier) 
	{
	  /* Just an escaped &	*/
	  if (txt[1] == identifier) 
	    {	
	      char *tmp;		/* Copy the string down over it	*/
	      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		*/
	  tstart = txt;
	  txt++;
	  i=0;
	  while((*txt != identifier)&&(*txt != '\0')&&(i<99))
	    {
	      name[i] = *txt;
	      txt++;
	      i++;
	    }
	  name[i] = 0;

	  /* Next, check for a color pixmap */
#ifdef XPM
	  pp=CachePicture(dpy,Scr.Root,IconPath,PixmapPath,name);
#else
	  pp=CachePicture(dpy,Scr.Root,IconPath,IconPath,name);
#endif
	  if (*txt != '\0') txt++;
	  while(*txt != '\0')
	    {
	      *tstart++ = *txt++;
	    }
	  *tstart = 0;
          if(!pp)
            strcpy(instring,save_instring);
	  else
	    *p=pp;
          free(save_instring);
	  return;
	}
    }
}

/* Side picture support: this scans for a color int the menu name 
   for colorization */
void scanForColor(char *instring, Pixel *p, Bool *c, char identifier) 
{
  char *tstart, *txt, *save_instring;
  int i;
  char name[100];

  *c = False;

  /* save instring in case can't find pixmap */
  save_instring = (char *)safemalloc(strlen(instring)+1);
  strcpy(save_instring,instring);

  /* Scan whole string        */
  for (txt = instring; *txt != '\0'; txt++) 
    {
      /* A hotkey marker? */
      if (*txt == identifier) 
      {
        /* Just an escaped '^'  */
        if (txt[1] == identifier) 
          {   
            char *tmp;                /* Copy the string down over it */
            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          */
        tstart = txt;
        txt++;
        i=0;
        while((*txt != identifier)&&(*txt != '\0')&&(i<99))
          {
            name[i] = *txt;
            txt++;
            i++;
          }
        name[i] = 0;

        *p = GetColor(name);
        *c = True;
        
        if(*txt != '\0')txt++;
        while(*txt != '\0')
          {
            *tstart++ = *txt++;
          }
        *tstart = 0;
          free(save_instring);
        return;
      }
    }
}

/***********************************************************************
 *
 *  Procedure:
 *	AddToMenu - add an item to a root menu
 *
 *  Returned Value:
 *	(MenuItem *)
 *
 *  Inputs:
 *	menu	- pointer to the root menu to add the item
 *	item	- the text to appear in the menu
 *	action	- the string to possibly execute
 *	func	- the numeric function
 *
 * ckh - need to add boolean to say whether or not to expand for pixmaps,
 *       so built in window list can handle windows w/ * and % in title.
 *
 ***********************************************************************/
void AddToMenu(MenuRoot *menu, char *item, char *action)
{
  MenuItem *tmp;
  char *start,*end;

  if(item == NULL)
     return;

  tmp = (MenuItem *)safemalloc(sizeof(MenuItem));
  if (menu->first == NULL)
    {
      menu->first = tmp;
      tmp->prev = NULL;
    }
  else
    {
      menu->last->next = tmp;
      tmp->prev = menu->last;
    }
  menu->last = tmp;
  tmp->picture=NULL;
  tmp->lpicture=NULL;

  /* skip leading spaces */
  /*while(isspace(*item)&&(item != NULL))
    item++;*/
  /* up to first tab goes in "item" field */
  start = item;
  end=item;
  while((*end != '\t')&&(*end != 0))
    end++;
  tmp->item = safemalloc(end-start+1);
  strncpy(tmp->item,start,end-start);
  tmp->item[end-start] = 0;
  tmp->item2 = NULL;
  if(*end=='\t')
    {
      start = end+1;
      while(*end != 0)
	end++;  
      if(end-start != 0)
	{
	  tmp->item2 = safemalloc(end-start+1);
	  strncpy(tmp->item2,start,end-start);
	  tmp->item2[end-start] = 0;
	}
    }

  if (item != (char *)0)
    {
      scanForPixmap(tmp->item,&tmp->picture,'*');
      scanForPixmap(tmp->item,&tmp->lpicture,'%');
      scanForHotkeys(tmp, 1);				/* pete@tecc.co.uk */
      tmp->strlen = strlen(tmp->item);
    }
  else
    tmp->strlen = 0;

  if (tmp->item2 != (char *)0)
    {
      if(!tmp->picture)
        scanForPixmap(tmp->item2,&tmp->picture,'*');
      if(!tmp->lpicture)
        scanForPixmap(tmp->item2,&tmp->lpicture,'%');
      if (tmp->hotkey == 0)
	scanForHotkeys(tmp, -1);	/* pete@tecc.co.uk */
      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++;
}

/***********************************************************************
 *
 *  Procedure:
 *	NewMenuRoot - create a new menu root
 *
 *  Returned Value:
 *	(MenuRoot *)
 *
 *  Inputs:
 *	name	- the name of the menu root
 *
 ***********************************************************************/
MenuRoot *NewMenuRoot(char *name, int function_or_popup)
{
  MenuRoot *tmp;
  
  tmp = (MenuRoot *) safemalloc(sizeof(MenuRoot));

  tmp->func = function_or_popup;
  tmp->name = stripcpy(name);

  /* Side picture support */
  tmp->sidePic = NULL;
  scanForPixmap(tmp->name, &tmp->sidePic, '@');
  scanForColor(tmp->name, &tmp->sideColor, &tmp->colorize, '^');

  tmp->first = NULL;
  tmp->last = NULL;
  tmp->items = 0;
  tmp->width = 0;
  tmp->width2 = 0;
  tmp->w = None;
  tmp->next  = Scr.AllMenus;
  Scr.AllMenus = tmp;

  return (tmp);
}



/***********************************************************************
 * change by KitS@bartley.demon.co.uk to correct popups off title buttons
 *
 *  Procedure:
 *ButtonPosition - find the actual position of the button
 *                 since some buttons may be disabled
 *
 *  Returned Value:
 *The button count from left or right taking in to account
 *that some buttons may not be enabled for this window
 *
 *  Inputs:
 *      context - context as per the global Context
 *      t       - the window (FvwmWindow) to test against
 *
 ***********************************************************************/
int ButtonPosition(int context, FvwmWindow * t)
{
  int i;
  int buttons = -1;
  
  if (context&C_RALL)
    {
      for(i=0;i<Scr.nr_right_buttons;i++)
	{
	  if(t->right_w[i])
	    {
	      buttons++;
	    }
	  /* is this the button ? */
	  if (((1<<i)*C_R1) & context)
	    return(buttons);
	}
    }
  else
    {
      for(i=0;i<Scr.nr_left_buttons;i++)
	{
	  if(t->left_w[i])
	    {
	      buttons++;
	    }
	  /* is this the button ? */
	  if (((1<<i)*C_L1) & context)
	    return(buttons);
	}
    }
  /* you never know... */
  return 0;
}



syntax highlighted by Code2HTML, v. 0.9.1