/*LINTLIBRARY*/

/*  @(#)x11.c 1.6 90/04/10
 *
 *  X11 dependent graphics routines used by othello.
 *
 *  Copyright (c) - Rich Burridge - Sun Microsystems.
 *                                  All rights reserved.
 *
 *  Permission is given to distribute these sources, as long as the
 *  introductory messages are not removed, and no monies are exchanged.
 *
 *  No responsibility is taken for any errors or inaccuracies inherent
 *  either to the comments or the code of this program, but if reported
 *  to me, then an attempt will be made to fix them.
 */

#include "othello.h"
#include "color.h"
#include "extern.h"
#include "images.h"
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/cursorfont.h>

#define  BOLDFONT    "lucidasanstypewriter-bold-12"
#define  DEFFONT     "fixed"
#define  NORMALFONT  "lucidasanstypewriter-12"

#define  FRAME_MASK  (ButtonPressMask | ButtonReleaseMask | ExposureMask    | \
                      EnterWindowMask | KeyPressMask      | LeaveWindowMask | \
                      PointerMotionMask)

#define  OTHELLO_BORDER_WIDTH  2

Atom protocol_atom, kill_atom ;
Cursor cursor[MAXCURSORS] ;
Display *dpy ;
GC gc ;                         /* Graphics context for text and lines. */
GC ropgc ;                      /* Graphics context for rops. */
GC stencilgc ;                  /* Graphics context for stencils. */
Pixmap images[MAXIMAGES] ;
Pixmap no_pixmap ;
Pixmap load_color_icon(), load_image(), othello_icon ;
Window frame, root ;
XColor BGcolor, FGcolor ;
XEvent event ;
XFontStruct *font[MAXFONTS] ;
XSizeHints size ;
XWMHints wm_hints ;
XGCValues gc_val ;              /* Used to setup graphics context values. */
int gc_flags ;                  /* Used to set up graphics context flags. */
int screen ;                    /* Default graphics display screen. */
unsigned long backgnd ;         /* Default background color. */
unsigned long foregnd ;         /* Default foreground color. */
unsigned long gc_mask ;         /* Mask for setting graphic context values. */
unsigned long palette[OTH_COLORSIZE] ;     /* Xlib color palette. */

/*  256-byte table for quickly reversing the bits in an unsigned 8-bit char,
 *  used to convert between MSBFirst and LSBFirst image formats.
 */

char revtable[256] = {
        0, -128,   64,  -64,   32,  -96,   96,  -32,
       16, -112,   80,  -48,   48,  -80,  112,  -16,
        8, -120,   72,  -56,   40,  -88,  104,  -24,
       24, -104,   88,  -40,   56,  -72,  120,   -8,
        4, -124,   68,  -60,   36,  -92,  100,  -28,
       20, -108,   84,  -44,   52,  -76,  116,  -12,
       12, -116,   76,  -52,   44,  -84,  108,  -20,
       28, -100,   92,  -36,   60,  -68,  124,   -4,
        2, -126,   66,  -62,   34,  -94,   98,  -30,
       18, -110,   82,  -46,   50,  -78,  114,  -14,
       10, -118,   74,  -54,   42,  -86,  106,  -22,
       26, -102,   90,  -38,   58,  -70,  122,   -6,
        6, -122,   70,  -58,   38,  -90,  102,  -26,
       22, -106,   86,  -42,   54,  -74,  118,  -10,
       14, -114,   78,  -50,   46,  -82,  110,  -18,
       30,  -98,   94,  -34,   62,  -66,  126,   -2,
        1, -127,   65,  -63,   33,  -95,   97,  -31,
       17, -111,   81,  -47,   49,  -79,  113,  -15,
        9, -119,   73,  -55,   41,  -87,  105,  -23,
       25, -103,   89,  -39,   57,  -71,  121,   -7,
        5, -123,   69,  -59,   37,  -91,  101,  -27,
       21, -107,   85,  -43,   53,  -75,  117,  -11,
       13, -115,   77,  -51,   45,  -83,  109,  -19,
       29,  -99,   93,  -35,   61,  -67,  125,   -3,
        3, -125,   67,  -61,   35,  -93,   99,  -29,
       19, -109,   83,  -45,   51,  -77,  115,  -13,
       11, -117,   75,  -53,   43,  -85,  107,  -21,
       27, -101,   91,  -37,   59,  -69,  123,   -5,
        7, -121,   71,  -57,   39,  -89,  103,  -25,
       23, -105,   87,  -41,   55,  -73,  119,   -9,
       15, -113,   79,  -49,   47,  -81,  111,  -17,
       31,  -97,   95,  -33,   63,  -65,  127,   -1,
} ;

int opvals[3] ;              /* Pixrect rasterop values. */
unsigned int scr_depth ;


/*ARGSUSED*/
batch(state)     /* Turn graphics batching on or off. */
enum bltype state ;
{
  XFlush(dpy) ;
}


close_frame()            /* Iconise Othello window. */
{
  XEvent event ;

  iconic = 1 ;
  event.xclient.type = ClientMessage ;
  event.xclient.display = dpy ;
  event.xclient.window = frame ;
  event.xclient.message_type = XInternAtom(dpy, "WM_CHANGE_STATE", False) ;
  event.xclient.format = 32 ;
  event.xclient.data.l[0] = IconicState ;
  XSendEvent(dpy, DefaultRootWindow(dpy), False,
              SubstructureRedirectMask | SubstructureNotifyMask, &event) ;
}


color_area(x, y, width, height, color)
int x, y, width, height, color ;
{
  if (iscolor) gc_val.foreground = palette[color] ;
  else
    { 
      if (color == C_WHITE) gc_val.foreground = backgnd ;
      else gc_val.foreground = foregnd ;
    }
  gc_val.function = GXcopy ;
  XChangeGC(dpy, gc, GCForeground | GCFunction, &gc_val) ;
  XFillRectangle(dpy, frame, gc, x, y,
                 (unsigned int) width, (unsigned int) height) ;
}


create_menu(mtype, values)    /* Create the various popup menus. */
enum panel_type mtype ;
char *values[] ;
{}


destroy_frame()       /* Destroy Othello window. */
{
  XDestroyWindow(dpy, frame) ;
}


do_menu(mtype)          /* Display popup menu for cyclic item. */
enum item_type mtype ;
{
  return(0) ;
}


draw_image(x, y, width, height, image)
int x, y, width, height ;
enum image_type image ;
{
  gc_mask = GCStipple | GCTileStipXOrigin | GCTileStipYOrigin ;
  gc_val.stipple = images[(int) image] ;
  gc_val.ts_x_origin = x ;
  gc_val.ts_y_origin = y ;
  XChangeGC(dpy, ropgc, gc_mask, &gc_val) ;
  XFillRectangle(dpy, frame, ropgc, x, y, width, height) ;
}


draw_line(x1, y1, x2, y2, op, color)
int x1, y1, x2, y2, color ;
enum optype op ;
{
  if (iscolor) gc_val.foreground = palette[color] ;
  else         gc_val.foreground = foregnd ;
  gc_val.function = opvals[(int) op] ;
  XChangeGC(dpy, gc, GCForeground | GCFunction, &gc_val) ;
  XDrawLine(dpy, frame, gc, x1, y1, x2, y2) ;
}


draw_stencil(x, y, width, height, op, color, stencil, image)
int x, y, width, height, color ;
enum optype op ;
enum image_type stencil, image ;
{
  if (iscolor) gc_val.foreground = palette[color] ;
  else         gc_val.foreground = foregnd ;
  gc_val.function = opvals[(int) op] ;
  gc_val.clip_x_origin = x ;
  gc_val.clip_y_origin = y ;
  gc_val.clip_mask = images[(int) stencil] ;
  gc_val.stipple = images[(int) image] ;
  gc_val.ts_x_origin = x ;
  gc_val.ts_y_origin = y ;
  gc_mask = GCForeground | GCFunction | GCClipMask |
            GCClipXOrigin | GCClipYOrigin |
            GCStipple | GCTileStipXOrigin | GCTileStipYOrigin ;
  XChangeGC(dpy, stencilgc, gc_mask, &gc_val) ;
  XFillRectangle(dpy, frame, stencilgc, x, y, width, height) ;
}


draw_text(x, y, ftype, color, str)
enum font_type ftype ;
int x, y, color ;
char *str ;
{
  if (iscolor) gc_val.foreground = palette[color] ;
  else
    { 
      if (color == C_WHITE) gc_val.foreground = backgnd ;
      else gc_val.foreground = foregnd ;
    }
  gc_val.font = font[(int) ftype]->fid ;
  gc_val.function = GXcopy ;
  XChangeGC(dpy, gc, GCFont | GCForeground | GCFunction, &gc_val) ;
  XDrawString(dpy, frame, gc, x, y, str, strlen(str)) ;
}
 
 
get_event()         /* Get the next canvas event. */
{
  XFlush(dpy) ;
  if (!XCheckMaskEvent(dpy, ExposureMask, &event))
    XNextEvent(dpy, &event) ;
}


XFontStruct *
get_font(name)
char *name ;
{
  XFontStruct *f ;

  if (!(f = XLoadQueryFont(dpy, name)))
    if (!(f = XLoadQueryFont(dpy, DEFFONT)))
      {
        FPRINTF(stderr, "%s: couldn't get the default font.", progname) ;
        exit(1) ;
      }
  return(f) ;
}


get_strwidth(ftype, str)    /* Get width in pixels of string value. */
enum font_type ftype ;
char *str ;
{
  return(XTextWidth(font[(int) ftype], str, strlen(str))) ;
}


init_fonts()             /* Open the normal and bold fonts. */
{
  font[(int) BFONT] = get_font(BOLDFONT) ;
  font[(int) NFONT] = get_font(NORMALFONT) ;
  bfont_height = font[(int) BFONT]->max_bounds.ascent +
                 font[(int) BFONT]->max_bounds.descent ;
  nfont_height = font[(int) NFONT]->max_bounds.ascent +
                 font[(int) NFONT]->max_bounds.descent ;
}


init_ws_type()
{
  if ((dpy = XOpenDisplay(x11_display)) == NULL)
    {
      FPRINTF(stderr,"%s: Couldn't open display %s\n", progname,
              (getenv ("DISPLAY") ? getenv("DISPLAY") : x11_display)) ;
      exit(1) ;
    }
  screen = DefaultScreen(dpy) ;
  root = RootWindow(dpy, screen) ;

  if (!geometry)
    STRCPY(geometry, XGetDefault(dpy, progname, "Geometry")) ;

  foregnd = BlackPixel(dpy, screen) ;
  backgnd = WhitePixel(dpy, screen) ;
  scr_depth = DefaultDepth(dpy, screen) ;
  move_delta = 10 ;
  return 0 ;
}


load_colors()     /* Create and load Othello color map. */
{
  XColor ccol ;
  u_char red[OTH_COLORSIZE], green[OTH_COLORSIZE], blue[OTH_COLORSIZE] ;
  int i, numcolors ;

  iscolor = 0 ;
  if (DisplayCells(dpy, screen) > 2)
    {
      oth_colorsetup(red, green, blue) ;
      iscolor = 1 ;
      numcolors = 0 ;
      for (i = 0; i < OTH_COLORSIZE; i++)
        {
          ccol.flags = DoRed | DoGreen | DoBlue ;
          ccol.red = (unsigned short) (red[i] << 8) ;
          ccol.green = (unsigned short) (green[i] << 8) ;
          ccol.blue = (unsigned short) (blue[i] << 8) ;
          if (XAllocColor(dpy, DefaultColormap(dpy, screen), &ccol) == True)
            palette[numcolors++] = ccol.pixel ;
        }
      if (numcolors < 2)
        {
          FPRINTF(stderr, "%s: cannot allocate colors.\n", progname) ;
          exit(1) ;
        }
    }
}


Pixmap
load_color_icon(sbuf)
short sbuf[] ;
{
  GC pix_gc ;
  Pixmap pixmap ;
  XImage *image ;
  char cbuf[512*8] ;
  int i ;

  for (i = 0; i < (256*8); i++)
    {
      cbuf[i*2+0] = palette[(sbuf[i] >> 8) & 0xFF] ;
      cbuf[i*2+1] = palette[sbuf[i] & 0xFF] ;
    }
  pix_gc = DefaultGC(dpy, screen) ;
  image = XCreateImage(dpy, DefaultVisual(dpy, screen),
                       scr_depth, ZPixmap, 0, cbuf, 64, 64, 8, 64) ;
  pixmap = XCreatePixmap(dpy, root,
                         ICONWIDTH, (unsigned) image->height, scr_depth) ;
  XPutImage(dpy, pixmap, pix_gc, image, 0, 0, 0, 0,
            ICONWIDTH, (unsigned) image->height) ;
  return(pixmap) ;
}


Pixmap
load_image(sbuf)
unsigned short sbuf[256] ;
{
  char cbuf[512] ;
  int i ;

  for (i = 0; i < 256; i++)
    {
      cbuf[i*2+0] = revtable[(sbuf[i] >> 8) & 0xFF] ;
      cbuf[i*2+1] = revtable[sbuf[i] & 0xFF] ;
    }
  return(XCreateBitmapFromData(dpy, root, cbuf, 64, 64)) ;
}


/*ARGSUSED*/
lock_screen(state)     /* Turn graphics locking on or off - null routine. */
enum bltype state ;
{}


make_canvas() {}            /* Null routine, see the make_frame routine. */


make_frame(argc, argv)      /* Create othello window/icon. */
int argc ;
char *argv[] ;
{
  unsigned int h, w ;       /* Window dimensions. */
  int flags ;
  int x, y ;                /* Window position. */

  opvals[(int) RCLR] = GXclear ;
  opvals[(int) RSRC] = GXcopy ;
  opvals[(int) RINV] = GXxor ;

  load_colors() ;
  if (iscolor) othello_icon = load_color_icon(cicon_image) ;
  else othello_icon = load_image(icon_image) ;

  size.flags = PMinSize | PMaxSize | PPosition | PSize ;
  size.x = 0 ;
  size.y = 0 ;
  size.max_width = size.min_width = size.width = TOTAL_WIDTH ;
  size.max_height = size.min_height = size.height = TOTAL_HEIGHT ;

  if (strlen(geometry))
    {
      flags = XParseGeometry(geometry, &x, &y, &w, &h) ;
      if (XValue & flags)
        {
          if (XNegative & flags)
            x = DisplayWidth(dpy, screen) + x - size.width ;
            size.flags |= USPosition ;
            size.x = x ;
        }
      if (YValue & flags)
        {
          if (YNegative & flags)
            y = DisplayHeight(dpy, screen) + y - size.height ;
            size.flags |= USPosition ;
            size.y = y ;
        }
    }

  frame = XCreateSimpleWindow(dpy, root,
                              size.x, size.y, size.width, size.height,
                              OTHELLO_BORDER_WIDTH, foregnd, backgnd) ;

  protocol_atom = XInternAtom(dpy, "WM_PROTOCOLS", False) ;
  kill_atom = XInternAtom(dpy, "WM_DELETE_WINDOW", False) ;
         
  XSetStandardProperties(dpy, frame, "othello", NULL, othello_icon,
                         argv, argc, &size) ;

  wm_hints.icon_x = ix ;
  wm_hints.icon_y = iy ;
  wm_hints.input = True ;
  wm_hints.icon_pixmap = othello_icon ;
  wm_hints.flags = InputHint | IconPixmapHint ;
  if (iconic)
    {    
      wm_hints.initial_state = IconicState ;
      wm_hints.flags |= StateHint ;
    }    
  XSetWMHints(dpy, frame, &wm_hints) ;
         
  gc_mask = GCForeground | GCBackground | GCGraphicsExposures ;
  gc_val.foreground = foregnd ;
  gc_val.background = backgnd ;
  gc_val.graphics_exposures = False ;
  gc = XCreateGC(dpy, root, gc_mask, &gc_val) ;

  ropgc = XCreateGC(dpy, root, gc_mask, &gc_val) ;
  XSetFillStyle(dpy, ropgc, FillStippled) ;

  stencilgc = XCreateGC(dpy, root, gc_mask, &gc_val) ;
  XSetFillStyle(dpy, stencilgc, FillOpaqueStippled) ;

  images[(int) BUT_STENCIL] = load_image(button_stencil_image) ;
  images[(int) BUT_INVERT]  = load_image(button_invert_image) ;
  images[(int) BUT_NORMAL]  = load_image(button_normal_image) ;
  images[(int) CY_NORMAL]   = load_image(cycle_glyph_image) ;
  images[(int) CY_STENCIL]  = load_image(cycle_stencil_image) ;
  images[(int) CY_LINVERT]  = load_image(cycle_linvert_image) ;
  images[(int) CY_RINVERT]  = load_image(cycle_rinvert_image) ;
  images[(int) P_WHITE]     = load_image(white_image) ;
  images[(int) P_BLACK]     = load_image(black_image) ;

  cursor[(int) CANVASCUR] = XCreateFontCursor(dpy, XC_top_left_arrow) ;
  cursor[(int) HOURGLASS] = XCreateFontCursor(dpy, XC_watch) ;
  no_pixmap = XCreateBitmapFromData(dpy, root, nocur_image, 16, 16) ;
  cursor[(int) NOCURSOR]  = XCreatePixmapCursor(dpy, no_pixmap, no_pixmap,
                                                &FGcolor, &BGcolor, 0, 0) ;
}


make_icon() {}        /* Null routine - icon created in make_frame. */


process_event()         /* Determine event type. */
{
  XClientMessageEvent *ev ;
  XKeyPressedEvent *key_event ;
  char chs[2] ;
 
  switch (event.type)
    {
      case ClientMessage    : /* Catch ICCCM kill from WM. */

                              ev = (XClientMessageEvent *) &event ;
                              if (ev->message_type == protocol_atom &&
                                  ev->data.l[0] == kill_atom)
                                exit(0) ;
                              nextc = IGNORE_EVENT ;
                              break ;
      case Expose           : nextc = process_expose(&event) ;
                              break ;
      case KeyPress         : key_event = (XKeyPressedEvent *) &event ;
                              curx = key_event->x ;
                              cury = key_event->y ;
                              (void) XLookupString(key_event, chs, 1,
                                                   (KeySym *)  NULL,
                                                   (XComposeStatus *) NULL) ;
                              cur_ch = chs[0] ;
                              nextc = KEYBOARD ;
                              break ;
      case EnterNotify      : nextc = ENTER_WINDOW ;
                              break ;
      case LeaveNotify      : nextc = EXIT_WINDOW ;
                              break ;
      case MotionNotify     : nextc = MOUSE_MOVING ;
                              curx = event.xbutton.x ;
                              cury = event.xbutton.y ;
                              break ;
      case ButtonPress      : curx = event.xbutton.x ;
                              cury = event.xbutton.y ;
                              if (event.xbutton.button == Button1)
                                nextc = LEFT_DOWN ;
                              else if (event.xbutton.button == Button2)
                                nextc = MIDDLE_DOWN ;
                              else if (event.xbutton.button == Button3)
                                nextc = RIGHT_DOWN ;
                              break ;
      case ButtonRelease    : curx = event.xbutton.x ;
                              cury = event.xbutton.y ;
                              if (event.xbutton.button == Button1)
                                nextc = LEFT_UP ;
                              else if (event.xbutton.button == Button2)
                                nextc = MIDDLE_UP ;
                              else if (event.xbutton.button == Button3)
                                nextc = RIGHT_UP ;
    }
}


process_expose(event)
XExposeEvent *event ;
{
  int doframe = 0 ;

  do
    if (event->count == 0 && event->window == frame) doframe++ ;
  while (XCheckMaskEvent(dpy, ExposureMask, event)) ;

  return(doframe ? FRAME_REPAINT : IGNORE_EVENT) ;
}


set_cursor(ctype)
enum curtype ctype ;
{
  XDefineCursor(dpy, frame, cursor[(int) ctype]) ;
  XFlush(dpy) ;
}


start_tool()          /* Start event dispatcher and display. */
{
  XSelectInput(dpy, frame, FRAME_MASK) ;
  XMapWindow(dpy, frame) ;

  for (;;)
    {
      get_event() ;          /* Get next canvas event. */
      process_event() ;      /* Find out what kind it is. */
      handle_event() ;       /* And do the apropriate action. */
    }
}
