/*
 *    Ygl: Run 2d-GL programs with standard X11 routines.
 *    (C) Fred Hucht 1993-95
 *    EMail: fred@thp.Uni-Duisburg.DE
 */

#include "header.h"
#include <X11/bitmaps/gray1>
#include <X11/cursorfont.h>
#include <stdarg.h> 	/* defpup(...) */

#define MENUPADW 	14
#define MENUPADH 	4
#define MENUSHADE 	8
#define SUBMENUOFFSET	10
#define ON		1
#define OFF		0

typedef Int32(*YglMenuCallback)(Int32, ...);

typedef struct YglMenu_ {
  Window		win;
  int			nitem;
  int 			x, y;
  Uint			w, h;
  struct YglMenuItem_ 	*longest;
  struct YglMenuItem_ 	**item;
} YglMenu;

typedef struct YglMenuItem_ {
  char 			*name;
  YglMenuCallback	callback;
  struct YglMenu_ 	*submenu;
  Int32			retval;
  Int32			mode;
  Uint 			callF:1;
  Uint			line:1;
} YglMenuItem;

static YglMenu 	**YglMenus = NULL;
static int 	YglLastMenu;
static GC	gc = NULL;
static Cursor 	cursor;
static int 	fontascent;
static int	menuheight;
static int 	white, black;

static YglMenu 	*mid_to_m(Int32 mid, const char *caller);
static void 	add_to_pup(const char *caller, Int32 mid, const char *string, va_list argp);
static void 	create_menu(YglMenu *m);
static void 	map_menu(YglMenu *m, YglMenu *parent, int item);
static int 	do_pup(YglMenu *m, int issub);
static Int32 	displayitem(YglMenu *m, int i, int on, int dosub);

static void create_menu(YglMenu *m) {
  int mask;
  XSetWindowAttributes swa;
  
  swa.background_pixmap = None;
  swa.save_under        = True;
  swa.event_mask        = ExposureMask /*StructureNotifyMask*/;
  swa.override_redirect = True;
  swa.cursor            = cursor;
  mask = CWBackPixmap | CWEventMask | CWSaveUnder | CWOverrideRedirect | CWCursor;
  
  m->win = XCreateWindow(D, Ygl.PWID,
			 0, 0, 
			 m->w + MENUSHADE, 
			 m->h + MENUSHADE, 
			 0,
			 CopyFromParent, 
			 InputOutput, 
			 CopyFromParent,
			 mask, 
			 &swa);
  if(gc == NULL) {
    Ulong      mask = GCGraphicsExposures | GCLineWidth | GCForeground | GCBackground;
    XGCValues  values;
    Pixmap stipple;
    XColor fg, bg;
    
    values.graphics_exposures = False;
    values.line_width = 0;
    values.foreground = white = WhitePixel(D, YglScreen);
    values.background = black = BlackPixel(D, YglScreen);
    
    gc = XCreateGC(D, m->win, mask, &values);
    
    stipple = XCreatePixmapFromBitmapData(D, m->win,
					  gray1_bits, 
					  gray1_width, 
					  gray1_height,
					  black,
					  white,
					  1);
    XSetStipple(D, gc, stipple);

    cursor = XCreateFontCursor(D, XC_right_ptr);
    bg.red = 0     ; bg.green = 0     ; bg.blue  = 0xffff;
    fg.red = 0xffff; fg.green = 0xffff; fg.blue  = 0     ;
    XRecolorCursor(D, cursor, &fg, &bg);
  }
}

static YglMenu *mid_to_m(Int32 mid, const char *caller) {
  if(mid <= 0 || mid > YglLastMenu || YglMenus[mid]->win == 0) {
    fprintf(stderr, "Ygl: %s: invalid menu id: %d\n", caller, mid);
    exit(1);
  }
  return(YglMenus[mid]);
}

static void add_to_pup(const char *caller, Int32 mid, const char *s, va_list argp) {
  YglMenu *m;
  int i;
  
  m = mid_to_m(mid, caller);
  
  for(i = 0; s[i];) {
    YglMenuItem *next;
    char buf[1024], *bufp = buf;
    int tflag = False;
    
    next = m->item[m->nitem];
    next->callF    = True;
    next->callback = NULL;
    next->submenu  = NULL;
    next->retval   = m->nitem;
    next->mode     = PUP_NONE;
    next->line     = False;
#ifdef DEBUG
    fprintf(stderr, "add_to_pup: caller = '%s', mid = %d, i = %d, string = '%s'\n",
	    caller, mid, i, &s[i]);
#endif
    for(;s[i] && s[i] != '|'; i++) {
      switch(s[i]) {
      case '%':
	i++;
	switch(s[i]) {
	case '%':
	  *bufp++ = s[i];
	  break;
	case 't':
	  tflag = True;
	  next = m->item[0];
	  break;
	case 'F':
	  /* m->item[0]->callback = (Int32(*)(Int32, ...)) va_arg(argp, void*);*/
	  m->item[0]->callback = va_arg(argp, YglMenuCallback);
#ifdef DEBUG
	  fprintf(stderr, "added %%F callback = 0x%x\n",
		  m->item[0]->callback);
#endif
	  break;
	case 'n':
	case 'm':
	  next->callF = False;
	  /* No break */
	case 'f':
	case 'M': /* %M is not available in SGI's gl. See examples/popup.c. */
	  if(tflag) {
	    fprintf(stderr,
		    "Ygl: %s: invalid combination of '%%t' and '%%%c' in '%s', position %d\n",
		    caller, s[i], s, i);
	    exit(1);
	  }
	  if(s[i] == 'm' || s[i] == 'M') {
	    Int32 mid = va_arg(argp, Int32);
	    next->submenu = mid_to_m(mid, caller);
#ifdef DEBUG
	    fprintf(stderr, "added %%m submenu = %d\n", mid);
#endif
	  } else { /* 'f' or 'n' */
	    next->callback = va_arg(argp, YglMenuCallback);
	    /* (Int32(*)(Int32, ...)) va_arg(argp, void*); */
#ifdef DEBUG
	    fprintf(stderr, "added %%fn callback = 0x%x\n", next->callback);
#endif
	  }
	  break;
	case 'x': {
	  char *endp;
	  i++;
	  next->retval = (Int32) strtol(&s[i], &endp, 0);
	  i = endp - s - 1;
	}
	  break;
	case 'l':
	  next->line = True;
	  break;
	default:
	  fprintf(stderr, 
		  "Ygl: %s: unknown token '%%%c' in '%s', position %d\n", 
		  caller, s[i], s, i);
	  /* exit(1); */
	  break;
	}
	break;
      default:
	*bufp++ = s[i];
	break;
      }
    } /* for(;...) */
    *bufp = '\0';
    
    if(NULL == (next->name = (char*)malloc(strlen(buf) + 1))) {
      fprintf(stderr, "Ygl: %s: can't allocate memory.\n", caller);
      exit(-1);
    }
    
    strcpy(next->name, buf);
    
    if (strlen(next->name) > strlen(m->longest->name)) m->longest = next;
    
    if(!tflag) {
      m->nitem++;
      if(NULL == (m->item = (YglMenuItem**)realloc(m->item, (m->nitem + 1) * sizeof(YglMenuItem*))) ||
	 NULL == (m->item[m->nitem] = (YglMenuItem*)malloc(sizeof(YglMenuItem)))) {
	fprintf(stderr, "Ygl: %s: can't allocate memory.\n", caller);
	exit(-1);
      }
    }
    
    if(s[i] == '|') i++;
  } /* for */
}

static Int32 displayitem(YglMenu *m, int i, int on, int dosub) {
  int fg, bg, r = -1;
  YglMenuItem *item;
  
  if(i == -1) return(-1);
  
  item = m->item[i];
  
  switch(item->mode) {
  case PUP_GREY:
    XSetFillStyle(D, gc, FillStippled);
    fg = bg = black;
    break;
  case PUP_NONE:
    if(on) {
      fg = white; bg = black;
    } else {
      bg = white; fg = black;
    }
    break;
  }
  
  XSetForeground(D, gc, bg);
  XFillRectangle(D, m->win, gc,
		 1, 
		 menuheight * i + 5,
		 m->w - 1, 
		 menuheight);
  
  XSetForeground(D, gc, fg);
  XSetFillStyle(D, gc, FillSolid);
  XDrawString(D, m->win, gc,
	      MENUPADW, 
	      menuheight * i + (menuheight + fontascent)/2 + 3,
	      item->name, strlen(item->name));
  if(item->line) 
    XDrawLine(D, m->win, gc, 
	      1, 
	      menuheight * (i + 1) + 4,
	      m->w - 1,
	      menuheight * (i + 1) + 4);
  
  if(item->submenu) {
    XPoint arrow[] = {{0, 0}, {-6, -3}, {0, 6}, {6, -3}};
    arrow[0].x = m->w - SUBMENUOFFSET + 6;
    arrow[0].y = menuheight * i + menuheight/2 + 4;
    XFillPolygon(D, m->win, gc, arrow, 4, Convex, CoordModePrevious);
    
    if(dosub && item->mode != PUP_GREY) if(on) {
      map_menu(item->submenu, m, i);
      r = do_pup(item->submenu, True);
    } else {
      XUnmapWindow(D, item->submenu->win);
    }
  }
  return(r);
}


static void map_menu(YglMenu *m, YglMenu *parent, int item) {
  int i;
  XEvent ev;
  
  if(parent == NULL) { /* Mainmenu */
    m->x = Ygl.lastreadevent.xbutton.x_root;
    m->y = Ygl.lastreadevent.xbutton.y_root - menuheight;
  } else { /* Submenu */
    m->x = parent->x + parent->w - SUBMENUOFFSET;
    m->y = parent->y + item * menuheight + 4;
  }
  
  m->w = strwidth(m->longest->name) + 2 * MENUPADW;
  m->h = m->nitem * menuheight + 5;
  
#ifdef DEBUG
  fprintf(stderr, "map_menu: width = %d height = %d longest = %s\n",
	  m->w, m->h, m->longest->name);
#endif
    
  XMoveResizeWindow(D, 
		    m->win,
		    MIN(m->x, YglScreenWidth  - (m->w + MENUSHADE)) - 2,
		    MIN(m->y, YglScreenHeight - (m->h + MENUSHADE)) - 2,
		    m->w + MENUSHADE,
		    m->h + MENUSHADE);
  
  XMapRaised(D, m->win);
  XSync(D, False);
  
  do { /* SaveUnder doesn't work properly when we only wait for MapNotify. Strange */
    XWindowEvent(D, m->win, ExposureMask /*StructureNotifyMask*/, &ev);
  } while (ev.type != Expose/*MapNotify*/);
  
  XSetForeground(D, gc, white);
  XFillRectangle(D, m->win, gc, 0, 0, m->w, m->h);
  
  XSetForeground(D, gc, black);
  XDrawRectangle(D, m->win, gc, 0, 0, m->w, m->h);
  
  XSetFillStyle(D, gc, FillStippled);
  XFillRectangle(D, m->win, gc, 
		 m->w, MENUSHADE, MENUSHADE, m->h);
  XFillRectangle(D, m->win, gc, 
		 MENUSHADE, m->h, m->w - MENUSHADE, MENUSHADE);
  XSetFillStyle(D, gc, FillSolid);
  
  XDrawString(D, m->win, gc,
	      (m->w - strwidth(m->item[0]->name)) / 2, 
	      (menuheight + fontascent)/2 + 2,
	      m->item[0]->name, strlen(m->item[0]->name));
  
  XDrawRectangle(D, m->win, gc, 
		 2, 2,
		 m->w - 4,
		 menuheight);
  
  XDrawLine(D, m->win, gc, 
	    0,    menuheight + 4,
	    m->w, menuheight + 4);
  
  for (i = 1; i < m->nitem; i++) {
    displayitem(m, i, OFF, 0);
  }
}

static int do_pup(YglMenu *m, int issub) {
  int menuval = -1, oldmenuval = -1, r, subr = -1, olddosub = -1;
  Uint mask;
  YglMenuItem *item;
  
  do {
    Window root, win;
    int rx, ry, wx, wy, dosub;
    subr = -1;
#if DEBUG > 1
    fprintf(stderr, "XQ: win = 0x%x\n", m->win);
#endif
    XQueryPointer(D, m->win, &root, &win, &rx, &ry, &wx, &wy, &mask);
    
    dosub = wx > m->w - SUBMENUOFFSET;
    
    if(issub && wx < 0) { /* Close submenus on left leave */
      menuval = -1;
      break;
    }
    
    if(wx > 0 &&
       wx < m->w &&
       wy > menuheight + 4 &&
       wy < menuheight * m->nitem + 4) {
      menuval = (wy - 4) / menuheight;
      item    = m->item[menuval];
      
      if(oldmenuval != menuval || olddosub != dosub) { /* Something changed */
#ifdef DEBUG
	fprintf(stderr, "menuval = %d old = %d\n", 
		menuval, oldmenuval);
#endif
	/*  */ displayitem(m, oldmenuval, OFF, dosub);
	subr = displayitem(m,    menuval,  ON, dosub);
	oldmenuval = menuval;
	olddosub   = dosub;
      }
    } else if(oldmenuval != -1) {
      displayitem(m, oldmenuval, OFF, dosub);
      oldmenuval = menuval = -1;
    }
  } while(subr == -1 && mask & Button3Mask);
  
#ifdef DEBUG
  fprintf(stderr, "while done, subr = %d, menuval = %d, retval = %d, callback = 0x%x\n", 
	  subr, menuval, item->retval, item->callback);
#endif
  
  XUnmapWindow(D, m->win);
  
  if(subr != -1) {
    if(item->callF && m->item[0]->callback) {
#ifdef DEBUG
      Int32 prev = subr;
#endif
      subr = m->item[0]->callback(menuval, subr);
#ifdef DEBUG
      fprintf(stderr, "do_pupx: %%F(%d, %d) = %d\n", 
	      menuval, prev, subr);
#endif
    }
    return(subr);
  }
  
  if(menuval != -1 && item->submenu == NULL && item->mode != PUP_GREY) {
    /* Valid item selected */
    r = item->retval;
    if(item->callback) {
#ifdef DEBUG
      Int32 prev = r;
#endif
      r = item->callback(r);
#ifdef DEBUG
      fprintf(stderr, "do_pup: %%f(%d) = %d\n", prev, r);
#endif
    }
    if(item->callF && m->item[0]->callback) {
#ifdef DEBUG
      Int32 prev = r;
#endif
      r = m->item[0]->callback(r);
#ifdef DEBUG
      fprintf(stderr, "do_pup: %%F(%d) = %d\n", prev, r);
#endif
    }
  } else {
    r = -1;
  }      
  return(r);
}


Int32 newpup(void) {
  YglMenu *m;
  I("newpup");
  
  if(YglMenus == NULL) { /* initialize */
    YglLastMenu = 1;
    YglMenus = (YglMenu**)malloc(2 * sizeof(YglMenu*)); /* First el. never used */
  } else {
    YglLastMenu++;
    YglMenus = (YglMenu**)realloc(YglMenus, (YglLastMenu + 1) * sizeof(YglMenu*));
  }
  
  if(YglMenus == NULL ||
     NULL == (m = YglMenus[YglLastMenu] = (YglMenu*)malloc(sizeof(YglMenu)))) {
    fprintf(stderr, "Ygl: newpup: can't allocate memory.\n");
    exit(-1);
  }
  
  memset(m, 0, sizeof(YglMenu));
  
  if(NULL == (m->item = (YglMenuItem**)malloc(2 * sizeof(YglMenuItem*))) ||
     NULL == (m->item[0] = (YglMenuItem*)malloc(sizeof(YglMenuItem))) ||
     NULL == (m->item[1] = (YglMenuItem*)malloc(sizeof(YglMenuItem))) ||
     NULL == (m->item[0]->name = malloc(16))) {
    fprintf(stderr, "Ygl: newpup: can't allocate memory.\n");
    exit(-1);
  }
  
  sprintf(m->item[0]->name, "Menu %d", YglLastMenu);
  m->longest	= m->item[0];
  m->nitem	= 1;
  m->w 		= 10;
  m->h 		= 10;
  
  create_menu(m);
  
  return(YglLastMenu);
}

Int32 defpup(Char8 *string, ... ) {
  Int32 mid;
  va_list Argp;
  I("defpup");
  mid = newpup();
  va_start(Argp, string);
  add_to_pup("defpup", mid, string, Argp);
  va_end(Argp);
  return(mid);
}

void addtopup(Int32 mid, Char8 *string, ... ) {
  va_list Argp;
  I("addtopup");
  va_start(Argp, string);
  add_to_pup("addtopup", mid, string, Argp);
  va_end(Argp);
}

void freepup(Int32 mid) {
  YglMenu *m;
  int i;
  I("freepup");
  m = mid_to_m(mid, "freepup");
  XDestroyWindow(D, m->win);
  m->win = 0;
  for(i = 0; i <= m->nitem; i++) {
    free(m->item[i]->name);
    free(m->item[i]);
  }
  free(m->item);
}

void setpup(Int32 mid, Int32 entry, Int32 mode) {
  YglMenu *m;
  I("setpup");
  m = mid_to_m(mid, "setpup");
  if(entry < 1 || entry > m->nitem - 1) {
    fprintf(stderr, "Ygl: setpup: Entry %d in menu %d does not exist.\n",
	    entry, mid);
    return;
  }
  switch(mode) {
  case PUP_NONE:
  case PUP_GREY:
    m->item[entry]->mode = mode;
    break;
  default:
    fprintf(stderr, "Ygl: setpup: Invalid mode: %d.\n", mode);
    return;
  }
}

Int32 dopup(Int32 mid) {
  YglMenu *m;
  Int32 r;
  I("dopup");
  m = mid_to_m(mid, "dopup");
  if(Ygl.lastreadevent.type != ButtonPress) {
    fprintf(stderr, "Ygl: dopup: last event was not ButtonPress.\n");
    return(-1);
  }
  XSetFont(D, gc, Ygl.Fonts[W->font].fs->fid);
  fontascent = Ygl.Fonts[W->font].fs->ascent;
  menuheight = MENUPADH + Ygl.Fonts[W->font].fs->descent + fontascent;
  
  XChangeActivePointerGrab(D, 0, cursor, CurrentTime); /* Switch cursor on */
  
  map_menu(m, NULL, 0);
  r = do_pup(m, False);
  return(r);
}
