/*
 * cbzone_gpr.c
 *  -- Todd W Mummert, December 1990, CMU
 * 
 * emulate gpr/ftn on top of X11.
 *
 * I don't know who originally wrote this emulation routine, but
 * I've seriously changed it anyway.  Therefore, it should not
 * be used with any expectation that it will do anything similiar
 * to the original gpr routines which I never saw.    (twm)
 * Almost all of the routines have had their arguments shortened.
 * And why bother returning status when it's always the same.  Most
 * importantly I removed the mallocs that were in polyline and
 * multiline.
 *
 * Added support for window managers, iconification, etc...
 * This code could be a lot neater.  If anyone can tell me how
 * to place a window at (0,0) regardless of the what the window
 * manager wants to do to it, I'd be appreciative.  As it is, I
 * now let the wm do what it wants, then attempt to adjust that.
 * No, I don't think I want a transient window, as I want the wm
 * to handle the iconification....
 * See placewindow() for the gory details.
 *
 * Now, it's basically a file full of one line functions...except
 * for the event handling and initialization.
 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/time.h>
#include <X11/Xlib.h>

#include <X11/Xatom.h>
#include <X11/Xutil.h>
typedef long int logical;

unsigned int colortrans[2];

typedef unsigned long raster_op_t;
typedef long coord_t;

typedef struct {
  coord_t x, y;
} position_t;

typedef struct {
  coord_t x, y;
} offset_t;

typedef struct {
  position_t base;
  offset_t size;
} window_t;

Display *d;
Window w;
GC FillGC, DrawGC, TextGC, EraseGC;

void gprinit ()
{
  /* (twm) I assume that the window will be mapped after this
   * routine is called.  I should then be free to move it to
   * (0,0).  However, this is not always the case...as I have
   * occasionally seen problems with the initial window
   * placement.  However, 'r' will fix them.
   */
  XEvent ev;

  XSelectInput(d, w, ExposureMask);
  XMapRaised(d, w);
    do 
      XWindowEvent(d, w, ExposureMask, &ev);
    while (((XExposeEvent*) &ev)->count);
}

int wid, hei;
Bool was_placed = False;
void placewindow()
{
  /*  the plan is to move the window to (0,0).  However, the window
   *  manager may not place it there due to borders, titles, etc.
   *  So we look at it again after movement and readjust.  This
   *  routine depends on the window already being mapped.
   *
   *  Now the major problem is that I really want to interract w/
   *  the window manager to handle iconification w/ use of icon
   *  managers and other niceties.  However, the wm intercepting
   *  events causes lots of problems.
   *  XSync can flush the xserver, but the wm may have some event it
   *  has picked up and is working on.  I would like someway to
   *  GUARANTEE that before the 2nd XGetWindowAttributes below,
   *  that the XMoveResizeWindow above it has completed. 
   *  or not).
   *
   * why the was_placed?  because when an expose event occurs this
   * routine gets called, which can generate yet more expose
   * events...taking time for things to settle down to a steady
   * state.  The raise window at the bottom may or may not generate
   * an expose event, so I can't wait for an expose event on it
   * or I hang if it is already at the top of the stack.
   *
   *  If anyone really knows how this should be done, please send
   *  me mail at mummert+@sam.cs.cmu.edu
   *
   * btw, I know I'm breaking the cardinal rule of window manager
   * interaction...namely let the wm control size and placement.
   * tough...
   *
   * okay, i took out the raisewindow at the bottom...now there may be
   * cases when moving the window that you expose another.
   */
  XEvent ev;
  XWindowAttributes wattr;
  Window dummywin;
  int rx, ry;

  XSelectInput(d, w, StructureNotifyMask);
  XSync(d, True);               /*  get rid of anything outstanding */

  /* i cheat and change width and height by 1....this almost guarantees
     i'll generate a ConfigureRequest event.
   */
  XMoveResizeWindow(d, w, 0, 0, wid+1, hei+1);  
  XWindowEvent(d, w, StructureNotifyMask, &ev);

  XGetWindowAttributes(d, w, &wattr);           /* check to see where the */
  XTranslateCoordinates(d, w, wattr.root, 0, 0, /* wm actually placed it */
			&rx, &ry, &dummywin);
    
  XMoveResizeWindow(d, w, -rx, -ry, wid, hei);  
  XWindowEvent(d, w, StructureNotifyMask, &ev);

  XSelectInput(d, w, NoEventMask);
  XSync(d, False);
  was_placed = True;
}

void gprinqconfig(config)
     long *config;
{
  XColor color;
  Cursor c;
  Font f;
  XGCValues xgcv;
  unsigned long fcolor, bcolor;
  XWMHints wmhints;
  
  d = XOpenDisplay (0);
  if (d == 0) {
    printf("can't open display! bye.\n");
    exit(1);
  }
  
  colortrans[1] = fcolor = WhitePixelOfScreen(DefaultScreenOfDisplay(d));
  colortrans[0] = bcolor = BlackPixelOfScreen(DefaultScreenOfDisplay(d));

  wid = WidthOfScreen(DefaultScreenOfDisplay(d));
  hei = HeightOfScreen(DefaultScreenOfDisplay(d));

  /* full screen, no border, I'd like it at (0,0)  */
  w = XCreateSimpleWindow (d, DefaultRootWindow(d),
                           0, 0,
                           wid, hei,
                           0, bcolor, bcolor);
  
  /* Tell the WM that we want input... */
  wmhints.input = True;
  wmhints.flags = InputHint;
  XSetWMHints(d, w, &wmhints);

  /* Name and raise the window */
  XStoreName(d, w,"Cbzone");
  XSetIconName(d, w, "Cbzone");

  xgcv.background = bcolor;
  xgcv.foreground = fcolor;
  FillGC = XCreateGC(d, w, GCForeground | GCBackground, &xgcv);
  xgcv.background = bcolor;
  xgcv.foreground = bcolor;
  EraseGC = XCreateGC(d, w, GCForeground | GCBackground, &xgcv);
  xgcv.background = bcolor;
  xgcv.foreground = fcolor;
  DrawGC = XCreateGC(d, w, GCForeground | GCBackground, &xgcv);
  TextGC = XCreateGC(d, w, GCForeground | GCBackground, &xgcv);
  f = XLoadFont (d, "fixed");
  c = XCreateGlyphCursor (d, f, f, ' ', ' ',
                          &color, &color);
  XDefineCursor(d, w, c);
  XSetGraphicsExposures (d, FillGC, False);
  XSetGraphicsExposures (d, DrawGC, False);
  XSetGraphicsExposures (d, TextGC, False);
  
  *config = 1;		/* XXX should kludge depending on number
                         * of planes */
  gprinit();
  placewindow();
}

void gprterminate()
{
  XCloseDisplay(d);
}

void gprsettextfont(font)
     long font;
{
  XSetFont(d, TextGC, font);
}

XRectangle clipr;

void printstring(x, y, string, nchars)
     int x, y;
     char *string;
     long nchars;
{
  XDrawImageString (d, w, TextGC, x, y, string, nchars);
}

void polyline(points, number)
     XPoint *points;
     int number;
{
  XDrawLines(d, w, DrawGC, points, number, CoordModeOrigin);
}

void multiline(segments, number)
     XSegment *segments;
     int number;
{
  XDrawSegments (d, w, DrawGC, segments, number);
}

void drawrectangle(x, y, width, height)
     int x, y, width, height;
{
  XDrawRectangle (d, w, DrawGC, x, y, width, height);
}

void fillrectangle(x, y, width, height)
     int x, y, width, height;
{
  XFillRectangle (d, w, FillGC, x, y, width, height);
}

static long lop;
void gprbitblt(window, dsto)
     window_t *window;
     position_t *dsto;
{
  if(lop==6)
    XFillRectangle(d,w,EraseGC,dsto->x, dsto->y,
                   window->size.x, window->size.y);
  else 
    XCopyArea (d, w, w, FillGC, window->base.x, window->base.y,
               window->size.x, window->size.y, dsto->x, dsto->y);
}

void gprsetrasterop(op)
     raster_op_t op;
{
  lop = op;
  XSetFunction(d, FillGC, lop);
  XSetFunction(d, TextGC, lop);
  XSetFunction(d, DrawGC, lop);
}

void gprsetclippingactive(flag)
     long flag;
{
  if (flag) {
    XSetClipRectangles (d, FillGC, 0, 0, &clipr, 1, YXBanded);
    XSetClipRectangles (d, DrawGC, 0, 0, &clipr, 1, YXBanded);
    XSetClipRectangles (d, TextGC, 0, 0, &clipr, 1, YXBanded);
  } else {
    XSetClipMask (d, FillGC, None);
    XSetClipMask (d, DrawGC, None);
    XSetClipMask (d, TextGC, None);		
  }
}

void tonetime()
{
  XBell(d, 0);
}

void timeclock(tval)
     struct timeval* tval;
{
  XSync(d, 0);
  gettimeofday(tval, 0);
}

position_t mouse_posn;

void gprinqcursor(posn)
     position_t *posn;
{
  *posn = mouse_posn;
}

int gprcondeventwait(key, posn)
     char *key;
     position_t *posn;
{
  static unsigned long flag[16]={0,0,0,0};
  XEvent ev;
  int paused = 0;
  char keystr;
  int early_return;

  if (was_placed) {
    XSync(d, False);
    XSelectInput(d, w, ExposureMask|PointerMotionMask|
                 ButtonPressMask|ButtonReleaseMask
                 |KeyPressMask
                 |StructureNotifyMask
                 );
    was_placed = False;
  }

  XFlush(d);
  while (XPending(d) || paused) {
    if (!paused)
      XNextEvent(d, &ev);
    else
      XWindowEvent(d,w,KeyPressMask|StructureNotifyMask,&ev);
    switch(ev.type) {
    case Expose:
      if (((XExposeEvent*) &ev)->count) break;
      placewindow();
      mouse_posn.x = ev.xexpose.x;
      mouse_posn.y = ev.xexpose.y;
      *key = 'R';
      *posn = mouse_posn;
      return 1;
    case UnmapNotify:
      paused = 1;
      break;
    case MapNotify:
      paused = 0;
      break;
    case KeyPress:
      early_return = False;
      if (XLookupString(&ev.xkey,&keystr,1,(KeySym *) 0,
                        (XComposeStatus *) 0) == 1)
        switch (keystr) {
        case 'p': case 'P':
          paused = 1;
          break;
        case 'c': case 'C':
          paused = 0;
          break;
        case 'i': case 'I': case ' ':
          XIconifyWindow(d, w, d->default_screen);
          break;
        case 'r': case 'R':
          placewindow();
          *key = 'R';
          early_return = True;
          break;
        case '\003': case 'q': case 'Q':
          *key = 'Q';
          early_return = True;
          break;
        default:
          break;
        }
      if (early_return) {
        mouse_posn.x = ev.xkey.x;
        mouse_posn.y = ev.xkey.y;
        *posn = mouse_posn;
        return 1;
      }
      break;
    case MotionNotify:
      mouse_posn.x = ev.xmotion.x;
      mouse_posn.y = ev.xmotion.y;
      break;
    case ButtonPress:
      mouse_posn.x = ev.xbutton.x;
      mouse_posn.y = ev.xbutton.y;	    
      *key = ev.xbutton.button + 'a' - 1;
      flag[ev.xbutton.button]=1;
      *posn = mouse_posn;
      return 1;
    case ButtonRelease:
      mouse_posn.x = ev.xbutton.x;
      mouse_posn.y = ev.xbutton.y;
      *key = ev.xbutton.button + 'A' - 1;
      flag[ev.xbutton.button]=0;
      *posn = mouse_posn;
      return 1;
    }
  }
  *posn = mouse_posn;
  if(flag[1]&&flag[3]) {
    mouse_posn.x = ev.xkey.x;
    mouse_posn.y = ev.xkey.y;
    *key = 'Q';
    *posn = mouse_posn;
    return 1;
  }
  return 0;
}

void gprsetcursorposition(posn)
     position_t *posn;
{
  XWarpPointer (d, None, w, 0, 0, 0, 0,
                posn->x, posn->y);
}

void gprsetcolormap()
{
  printf("setcolormap\n");
}

void gprloadfontfile(path, fontid)
     char *path;
     long *fontid;
{
  int i;
  
  static struct {
    char *gprname;
    char *xname;
  } fontmap[] = { {"f5x9", "6x10" },
                    {"i.12", "-*-*-medium-i-*-*-14-*-*-*-*-*-*-*"},
                    {"f7x13", "8x13" },
                    {"bzonefont", "-*-*-medium-r-*-*-24-*-*-*-*-*-*-*"},
                    {0, 0 }};
  
  for (i=0; fontmap[i].gprname; i++) 
    if (strcmp(path, fontmap[i].gprname) == 0) {
      *fontid = XLoadFont (d, fontmap[i].xname);
      return;
    }
  *fontid = XLoadFont(d, "fixed");
}

void gprsettextvalue(pixel)
     long pixel;
{
  XSetForeground(d, TextGC, colortrans[pixel]);
}

void gprsettextbackgroundvalue(pixel)
     long pixel;
{
  XSetBackground(d, TextGC, colortrans[pixel]);
}

void gprsetfillvalue(pixel)
     long pixel;
{
  XSetForeground(d, FillGC, colortrans[pixel]);
}

void gprsetdrawvalue(pixel)
     long pixel;
{
  XSetForeground(d, DrawGC, colortrans[pixel]);
}

void gprcircle(center, radius)
     position_t *center;
     long radius;
{
  XDrawArc (d, w, DrawGC,
            center->x - radius, center->y - radius,
            radius+radius, radius+radius, 0, 360*64);
}

void gprcirclefilled(center, radius)
     position_t *center;
     long radius;
{
  XFillArc (d, w, DrawGC,
            center->x - radius, center->y - radius,
            radius+radius, radius+radius, 0, 360*64);
}

void gprsetclipwindow(window)
     window_t *window;
{
  clipr.x = window->base.x;
  clipr.y = window->base.y;
  clipr.width = window->size.x;
  clipr.height =  window->size.y;
}

void clearentirescreen()
{
  XClearWindow(d, w);
}
