/* X11menus - Low Level Menu Objects for X11                           */
/* XLISP-STAT 2.1 Copyright (c) 1990, by Luke Tierney                  */
/* Additions to Xlisp 2.1, Copyright (c) 1989 by David Michael Betz    */
/* You may give out copies of this software; for conditions see the    */
/* file COPYING included with this distribution.                       */
 
/***********************************************************************/
/**                                                                   **/
/**                    General Includes and Definitions               **/
/**                                                                   **/
/***********************************************************************/

#include "xlisp.h"
#include "xlstat.h"
#include "xlgraph.h"

#define NullWindow ((Window) 0)

extern Display *StX11Display();

extern LVAL slot_value();
extern caddr_t get_menu_address();

extern LVAL s_items, s_title, s_enabled, s_mark, sk_update;

typedef struct {
  char *title;
  int len, enabled, checked;
  Window pane;
} ItemEntry;

typedef struct {
  unsigned long fore, back;
} ColorPair;

/***********************************************************************/
/**                                                                   **/
/**                      Static Global Variables                      **/
/**                                                                   **/
/***********************************************************************/

/* gray stipple pattern bitmap data for disabled items */
#define gray_width 16
#define gray_height 16
static unsigned char gray_bits[] = {
   0x55, 0x55, 0xaa, 0xaa, 0x55, 0x55, 0xaa, 0xaa, 0x55, 0x55, 0xaa, 0xaa,
   0x55, 0x55, 0xaa, 0xaa, 0x55, 0x55, 0xaa, 0xaa, 0x55, 0x55, 0xaa, 0xaa,
   0x55, 0x55, 0xaa, 0xaa, 0x55, 0x55, 0xaa, 0xaa};
static Pixmap GrayPM;

/* check mark bitmap data */
#define check_offset 2
#define check_width 9
#define check_height 8
static unsigned char check_bits[] = {
  0x00, 0x01, 0x80, 0x01, 0xc0, 0x00, 0x60, 0x00,
  0x31, 0x00, 0x1b, 0x00, 0x0e, 0x00, 0x04, 0x00
};
static Pixmap CheckPM;

/* configuration parameters - should be set using the defaults database */
static char *MenuFontName = "9x15";
static XFontStruct *MenuFont;
static unsigned long BorderColor;
static ColorPair MenuC, MenuItemC, MenuTitleC;
static unsigned int border_width, item_border_width;
static int show_menu_title;

/* graphics contexts used for menu items */
static GC NormalGC, ReversedGC, TitleGC, GrayGC;

/* current menu item's window pane - used in event handling */
static Window current_item;

static Cursor MenuCursor;

/* forward declarations */
LOCAL VOID LoadMenuFont _((void));
LOCAL VOID MakeMenuGC _((void));
LOCAL VOID get_menu_size _((LVAL menu, LVAL items,
			    ItemEntry *entries,
			    unsigned int *pwidth, unsigned int *pheight));
LOCAL VOID draw_pane _((int n, ItemEntry *entries, Window win));

/***********************************************************************/
/**                                                                   **/
/**               Menu System Initialization and Cleanup              **/
/**                                                                   **/
/***********************************************************************/

VOID StX11InitMenus()
{
  Display *dpy = StX11Display();
  int screen = StX11Screen();
  char *option;

  border_width = 1;
  item_border_width = 0;

  option = StX11GetDefault("xlisp.menu.titles");
  if (option == NULL) option = "off";
  show_menu_title = is_option_on(option);

  MenuC.fore = BlackPixel(dpy, screen);
  MenuC.back = WhitePixel(dpy, screen);
  MenuItemC.fore = BlackPixel(dpy, screen);
  MenuItemC.back = WhitePixel(dpy, screen);
  MenuTitleC.fore = WhitePixel(dpy, screen);
  MenuTitleC.back = BlackPixel(dpy, screen);
  BorderColor = BlackPixel(dpy, screen);
  
  GrayPM = XCreateBitmapFromData(dpy, RootWindow(dpy, screen), 
				 (char *) gray_bits,
				 gray_width, gray_height);
  CheckPM = XCreateBitmapFromData(dpy, RootWindow(dpy, screen), 
				  (char *) check_bits,
				  check_width, check_height);

  LoadMenuFont();
  MakeMenuGC();

  MenuCursor = XCreateFontCursor(dpy, XC_sb_left_arrow);
}

LOCAL VOID LoadMenuFont()
{
  Display *dpy = StX11Display();
  char *font;

  font = StX11GetDefault("xlisp.menu.font");
  if (font == NULL) font = StX11GetDefault("xlisp.font");
  if (font == NULL) font = MenuFontName;
  if ((MenuFont = XLoadQueryFont(dpy, font)) == NULL) {
    fprintf(stderr, "xlisp: Can't open %s font\n", font);
    if ((MenuFont = XLoadQueryFont(dpy, MenuFontName)) == NULL) {
      fprintf(stderr, "xlisp: Can't open %s font\n", MenuFontName);
      if ((MenuFont = XLoadQueryFont(dpy, "fixed")) == NULL) {
	fprintf(stderr, "xlisp: Can't open %s font\n", "fixed");
	exit(-1);
      }
    }
  }
}

LOCAL VOID MakeMenuGC()
{
  unsigned long valuemask;
  XGCValues values;
  Display *dpy = StX11Display();
  int screen = StX11Screen();

  valuemask = 0; /* ignore XGCValues and use defaults */
  NormalGC = XCreateGC(dpy, RootWindow(dpy, screen), valuemask, &values);
  XSetFont(dpy, NormalGC, MenuFont->fid);
  XSetForeground(dpy, NormalGC, MenuItemC.fore);
  XSetBackground(dpy, NormalGC, MenuItemC.back);

  valuemask = 0; /* ignore XGCValues and use defaults */
  ReversedGC = XCreateGC(dpy, RootWindow(dpy, screen), valuemask, &values);
  XSetFont(dpy, ReversedGC, MenuFont->fid);
  XSetForeground(dpy, ReversedGC, MenuItemC.back);
  XSetBackground(dpy, ReversedGC, MenuItemC.fore);

  valuemask = GCStipple+GCFillStyle;
  values.stipple = GrayPM;
  values.fill_style = FillStippled;
  GrayGC = XCreateGC(dpy, RootWindow(dpy, screen), valuemask, &values);
  XSetFont(dpy, GrayGC, MenuFont->fid);
  XSetForeground(dpy, GrayGC, MenuItemC.fore);
  XSetBackground(dpy, GrayGC, MenuItemC.back);

  valuemask = 0; /* ignore XGCValues and use defaults */
  TitleGC = XCreateGC(dpy, RootWindow(dpy, screen), valuemask, &values);
  XSetFont(dpy, TitleGC, MenuFont->fid);
  XSetForeground(dpy, TitleGC, MenuTitleC.fore);
  XSetBackground(dpy, TitleGC, MenuTitleC.back);
}

VOID StX11FinishMenus()
{
  Display *dpy = StX11Display();

  XUnloadFont(dpy, MenuFont->fid);
  XFreeGC(dpy, NormalGC);
  XFreeGC(dpy, ReversedGC);
  XFreeGC(dpy, GrayGC);
  XFreeGC(dpy, TitleGC);
  XFreePixmap(dpy, GrayPM);
  XFreePixmap(dpy, CheckPM);
  XFreeCursor(dpy, MenuCursor);
}

/***********************************************************************/
/**                                                                   **/
/**                       MENU-PROTO Definitions                      **/
/**                                                                   **/
/***********************************************************************/

int StMObInstalled(m) LVAL m; { return(FALSE); }

/***********************************************************************/
/**                                                                   **/
/**                       MENU-PROTO Definitions                      **/
/**                                                                   **/
/***********************************************************************/

FORWARD char *get_item_string();

/***********************************************************************/
/**                                                                   **/
/**                      Public Menu Functions                        **/
/**                                                                   **/
/***********************************************************************/

/* unused routines in popup system */
VOID StMObDisposeMach (m) LVAL m; {}
VOID StMObAllocateMach (m) LVAL m; {}
VOID StMObDeleteItem (m, item) LVAL m, item; {}
VOID StMObSetItemProp (m, i) LVAL m; int i; {}
VOID StMObAppendItems (m, a) LVAL m, a; {}
VOID StMObRemove (m) LVAL m; {}
VOID StMObEnable (m, i) LVAL m; int i; {}
VOID StMObInstall (m) LVAL m; {}

int StMObPopup (menu,x, y, window)
     LVAL menu, window;
     int x, y;
{
  Window win, w;
  unsigned int width, height;
  XSetWindowAttributes setwinattr;
  unsigned long valuemask;
  XEvent report;
  int done = FALSE;
  LVAL items;
  int i, n, pane_height, left, top;
  ItemEntry *entries;
  int item_selected;
  Display *dpy = StX11Display();
  int screen = StX11Screen();

  /* adjust coordinates to the root window */
  if (window != NIL && (w = (Window) GETWINDOWADDRESS(window)) != NullWindow) {
    StWGetLocation(w, &left, &top, FALSE);
    x += left;
    y += top;
  }

  /* have the menu update itself */
  send_message(menu, sk_update);

  /* get the item list and make sure there are some items to use */
  items = slot_value(menu, s_items);
  n = (consp(items)) ? llength(items) : 0;
  if (n == 0) return(0);
  if (show_menu_title) n++;

  /* set up the internal item entry array */
  entries = (ItemEntry *) StCalloc(n, sizeof(ItemEntry));
  get_menu_size(menu, items, entries, &width, &height);

  /* create opaque menu window */
  x = max(0, min(x, DisplayWidth(dpy, screen) - width));
  y = max(0, min(y, DisplayHeight(dpy, screen) - height));
  win = XCreateSimpleWindow(dpy, RootWindow(dpy, screen),
			    x, y, width, height, border_width,
			    BorderColor, MenuC.back);

  /* set the override_redirect and save_under attributes for a menu */
  valuemask = CWOverrideRedirect | CWSaveUnder;
  setwinattr.override_redirect = TRUE;
  setwinattr.save_under = TRUE;
  XChangeWindowAttributes(dpy, win, valuemask, &setwinattr);
  XDefineCursor(dpy, win, MenuCursor);
  
  /* create the title and item windows */
  pane_height = height / n;
  for (i = 0; i < n; i++) {
    entries[i].pane = XCreateSimpleWindow(dpy, win, 0, pane_height * i, 
					  width, pane_height,
					  item_border_width, 
					  BorderColor, MenuItemC.back);
    XSelectInput(dpy, entries[i].pane, 
		 ExposureMask | EnterWindowMask | LeaveWindowMask);
  }
				      
  /* Display (map) the windows */
  XMapSubwindows(dpy, win);
  XMapWindow(dpy, win);

  /* grap the pointer */
  StX11ReleaseButton();
  XGrabPointer(dpy, win, TRUE, 
	       ButtonReleaseMask,
	       GrabModeAsync, GrabModeAsync, None, None, CurrentTime);

  /* Loop until button is released, examining each event */
  current_item = NullWindow;
  while (! done) {
    XNextEvent(dpy, &report);
    switch (report.type) {
    case Expose:
      draw_pane(n, entries, report.xexpose.window);
      break;
    case ButtonPress:
      break;
    case ButtonRelease:
      done = TRUE;
      break;
    case EnterNotify:
      current_item = report.xcrossing.window;
      draw_pane(n, entries, current_item);
      break;
    case LeaveNotify:
      current_item = NullWindow;
      draw_pane(n, entries, report.xcrossing.window);
      break;
    default:
      break;
    }
  }

  /* find the item selected */
  for (item_selected = 0, i = 0; i < n; i++) {
    if (current_item == entries[i].pane) {
      if (entries[i].enabled) item_selected =  (show_menu_title) ? i : i + 1;
      break;
    }
  }

  /* clean up */
  XDestroyWindow(dpy, win);
  XFlush(dpy);
  StFree(entries);
  entries = NULL;

  return(item_selected);
}

LOCAL VOID get_menu_size(menu, items, entries, pwidth, pheight)
     LVAL menu, items;
     ItemEntry *entries;
     unsigned int *pwidth, *pheight;
{
  int font_height, margin, text_width, len;
  LVAL title;
  char *str;

  font_height = MenuFont->max_bounds.ascent
              + MenuFont->max_bounds.descent;
  margin = MenuFont->max_bounds.descent / 2;
  
  if (show_menu_title) {
    title = slot_value(menu, s_title);
    if (! stringp(title)) xlerror("not a string", title);
    str = getstring(title);
    len = strlen(str);
    text_width = XTextWidth(MenuFont, str, len);
    entries->title = str;
    entries->len = len;
    entries->enabled = FALSE;
    entries->checked = FALSE;
    *pwidth = text_width;
    *pheight = font_height + 2 * margin;
    entries++;
  }
  else {
    *pwidth = 0;
    *pheight = 0;
  }
  for (; consp(items); items = cdr(items), entries++) {
    *pheight += font_height + 2 * margin;
    title = slot_value(car(items), s_title);
    if (! stringp(title)) xlerror("not a string", title);
    str = getstring(title);
    len = strlen(str);
    text_width = XTextWidth(MenuFont, str, len);
    if (*pwidth < text_width) *pwidth = text_width;
    entries->title = str;
    entries->len = len;
    entries->enabled = (slot_value(car(items), s_enabled) != NIL);
    entries->checked = (slot_value(car(items), s_mark) != NIL);
  }
  *pwidth += margin + check_width + 2 * check_offset;
}

LOCAL VOID draw_pane(n, entries, win)
     int n;
     ItemEntry *entries;
     Window win;
{
  int margin, x, y, i;
  GC gc;
  Display *dpy = StX11Display();

  margin = MenuFont->max_bounds.descent / 2;

  x = check_width + 2 * check_offset;
  y = MenuFont->max_bounds.ascent + margin; 
  
  for (i = 0; i < n; i++) {
    if (entries[i].pane == win) {
      if (show_menu_title && i == 0) {
	XSetWindowBackground(dpy, win, MenuTitleC.back);
	gc = TitleGC;
      }
      else if (win == current_item && entries[i].enabled) {
	XSetWindowBackground(dpy, win, MenuItemC.fore);
	gc = ReversedGC;
      }
      else {
	XSetWindowBackground(dpy, win, MenuItemC.back);
	gc = (entries[i].enabled) ? NormalGC : GrayGC;
      }
      XClearWindow(dpy, win);
      if (entries[i].checked)
	XCopyPlane(dpy, CheckPM, win, gc, 
		   0, 0, check_width, check_height,
		   check_offset, y - check_height, 1);
      XDrawString(dpy, win, gc, x, y, entries[i].title, entries[i].len);    
      break;
    }
  }
}

#ifdef TODO
  special dividing lines
  use resource database to get defaults
#endif /* TODO */


syntax highlighted by Code2HTML, v. 0.9.1