/*
** Astrolog (Version 4.00) File: xdriver.c
**
** IMPORTANT NOTICE: the graphics database and chart display routines
** used in this program are Copyright (C) 1991-1993 by Walter D. Pullen
** (cruiser1@stein.u.washington.edu). Permission is granted to freely
** use and distribute these routines provided one doesn't sell,
** restrict, or profit from them in any way. Modification is allowed
** provided these notices remain with any altered or edited versions of
** the program.
**
** The main planetary calculation routines used in this program have
** been Copyrighted and the core of this program is basically a
** conversion to C of the routines created by James Neely as listed in
** Michael Erlewine's 'Manual of Computer Programming for Astrologers',
** available from Matrix Software. The copyright gives us permission to
** use the routines for personal use but not to sell them or profit from
** them in any way.
**
** The PostScript code within the core graphics routines are programmed
** and Copyright (C) 1992-1993 by Brian D. Willoughby
** (brianw@sounds.wa.com). Conditions are identical to those above.
**
** The extended accurate ephemeris databases and formulas are from the
** calculation routines in the program "Placalc" and are programmed and
** Copyright (C) 1989,1991,1993 by Astrodienst AG and Alois Treindl
** (alois@azur.ch). The use of that source code is subject to
** regulations made by Astrodienst Zurich, and the code is not in the
** public domain. This copyright notice must not be changed or removed
** by any user of this program.
**
** Initial programming 8/28,30, 9/10,13,16,20,23, 10/3,6,7, 11/7,10,21/1991.
** X Window graphics initially programmed 10/23-29/1991.
** PostScript graphics initially programmed 11/29-30/1992.
** Last code change made 12/31/1993.
*/

#include "astrolog.h"

#ifdef GRAPH

#ifdef X11
/* Size of the Astrolog X11 icon. These values are defined in xdata.c too. */

#define icon_width 63
#define icon_height 32
#endif

#ifdef MSG
/* PC specific global variables. */

int hiresmode = DEFHIRESMODE;    /* 'High-resolution' graphics mode. */
int loresmode = DEFLORESMODE;    /* 'Flicker-free' graphics mode.    */
int xscreen   = -1000;           /* Current graphics mode.           */
struct videoconfig config;       /* State of current graphics mode.  */
#endif


/*
******************************************************************************
** Interactive Screen Graphics Routines.
******************************************************************************
*/

#ifdef X11
/* Allocate a color from the present colormap. Given a string like "red" or */
/* "blue" allocate this color and return a value specifying it.             */

colrgb XMakeColor(name)
char *name;
{
  XColor col;
  XParseColor(disp, cmap, name, &col);
  XAllocColor(disp, cmap, &col);
  return col.pixel;
}
#endif


/* Set up all the colors used by the program, i.e. the foreground and   */
/* background colors, and all the colors in the object arrays, based on */
/* whether or not we are in monochrome and/or reverse video mode.       */

void XColorInit()
{
  int i;

#ifdef X11
  if (!xfile) {
    cmap = XDefaultColormap(disp, screen);
    for (i = 0; i < 16; i++)
      rgbind[i] = XMakeColor(rgbname[i]);
  }
#endif
  on  = mainansi[!xreverse];
  off = mainansi[xreverse];
  hilite = xcolor ? mainansi[2+xreverse] : on;
  gray   = xcolor ? mainansi[3-xreverse] : on;
  for (i = 0; i <= 6; i++)
    maincolor[i]    = xcolor ? mainansi[i]    : on;
  for (i = 0; i <= 7; i++)
    rainbowcolor[i] = xcolor ? rainbowansi[i] : on;
  for (i = 0; i < 4; i++)
    elemcolor[i]    = xcolor ? elemansi[i]    : on;
  for (i = 0; i <= ASPECTS; i++)
    aspectcolor[i]  = xcolor ? aspectansi[i]  : on;
  for (i = 0; i <= total; i++)
    objectcolor[i]  = xcolor ? objectansi[i]  : on;
#ifdef X11
  if (!xfile) {
    XSetBackground(disp, gc,   rgbind[off]);  /* Initialize X window colors. */
    XSetForeground(disp, pmgc, rgbind[off]);
  }
#endif
}


#ifdef ISG
/* This routine opens up and initializes a window and prepares it to be */
/* drawn upon, and gets various information about the display, too.     */

void XBegin()
{
#ifdef X11
  disp = XOpenDisplay(dispname);
  if (!disp) {
    PrintError("Can't open display.");
    Terminate(_FATAL);
  }
  screen = DefaultScreen(disp);
  bg = BlackPixel(disp, screen);
  fg = WhitePixel(disp, screen);
  hint.x = offsetx; hint.y = offsety;
  hint.width = chartx; hint.height = charty;
  hint.min_width = BITMAPX1; hint.min_height = BITMAPY1;
  hint.max_width = BITMAPX;  hint.max_height = BITMAPY;
  hint.flags = PPosition | PSize | PMaxSize | PMinSize;
#if FALSE
  wmhint = XGetWMHints(disp, window);
  wmhint->input = True;
  XSetWMHints(disp, window, wmhint);
#endif
  depth = DefaultDepth(disp, screen);
  if (depth < 5) {
    xmono = TRUE;      /* Is this a monochrome monitor? */
    xcolor = FALSE;
  }
  root = RootWindow(disp, screen);
  if (xroot)
    window = root;     /* If -XB in effect, we'll use the root window. */
  else
    window = XCreateSimpleWindow(disp, DefaultRootWindow(disp),
      hint.x, hint.y, hint.width, hint.height, 5, fg, bg);
  pixmap = XCreatePixmap(disp, window, chartx, charty, depth);
  icon = XCreateBitmapFromData(disp, DefaultRootWindow(disp),
    icon_bits, icon_width, icon_height);
  if (!xroot)
    XSetStandardProperties(disp, window, appname, appname, icon,
      (char PTR PTR)xkey, 0, &hint);

  /* We have two graphics workareas. One is what the user currently sees in */
  /* the window, and the other is what we are currently drawing on. When    */
  /* done, we can quickly copy this to the viewport for a smooth look.      */

  gc = XCreateGC(disp, window, 0, 0);
  XSetGraphicsExposures(disp, gc, 0);
  pmgc = XCreateGC(disp, window, 0, 0);
  XColorInit();                            /* Go set up colors. */
  if (!xroot)
    XSelectInput(disp, window, KeyPressMask | StructureNotifyMask |
      ExposureMask | ButtonPressMask | ButtonReleaseMask | ButtonMotionMask);
  XMapRaised(disp, window);
  XSync(disp, 0);
  XFillRectangle(disp, pixmap, pmgc, 0, 0, chartx, charty);

#else /* MSG */

  if (!IsValidResmode(xscreen))    /* Initialize graphics mode to hi-res. */
    xscreen = hiresmode;
  _setvideomode(xscreen);
  if (_grstatus()) {
    PrintError("Can't enter graphics mode.");
    Terminate(_FATAL);
  }
  _getvideoconfig((struct videoconfig far *) &config);
  if (config.numcolors < 16) {
    xmono = TRUE;
    xcolor = FALSE;
  }
  _remapallpalette((long FAR *) rgb);
  _setactivepage(0);
  _setvisualpage(0);
  XColorInit();
#ifdef MOUSE
  if (MouseInit() > 0)
    SetPtrVis(SHOW);
#endif
  textrows = abs(textrows);    /* Make sure we reset textrows upon restart. */
#endif /* MSG */
}


/* Add a certain amount of time to the current hour/day/month/year quantity */
/* defining the present chart. This is used by the chart animation feature. */
/* We can add or subtract anywhere from 1 to 9 seconds, minutes, hours,     */
/* days, months, years, decades, centuries, or millenia in any one call.    */
/* This is mainly just addition to the appropriate quantity, but we have    */
/* to check for overflows, e.g. Dec 30 + 3 days = Jan 2 of Current year + 1 */

void AddTime(mode, toadd)
int mode, toadd;
{
  int d;
  real h, m;

  h = floor(TT);
  m = FRACT(TT)*100.0;
  if (mode == 1)
    m += 1.0/60.0*(real)toadd;    /* Add seconds. */
  else if (mode == 2)
    m += (real)toadd;             /* add minutes. */

  /* Add hours, either naturally or if minute value overflowed. */

  if (m < 0.0 || m >= 60.0 || mode == 3) {
    if (m >= 60.0) {
      m -= 60.0; toadd = SGN(toadd);
    } else if (m < 0.0) {
      m += 60.0; toadd = SGN(toadd);
    }
    h += (real)toadd;
  }

  /* Add days, either naturally or if hour value overflowed. */

  if (h >= 24.0 || h < 0.0 || mode == 4) {
    if (h >= 24.0) {
      h -= 24.0; toadd = SGN(toadd);
    } else if (h < 0.0) {
      h += 24.0; toadd = SGN(toadd);
    }
    DD += toadd;
  }

  /* Add months, either naturally or if day value overflowed. */

  if (DD > (d = DayInMonth(MM, YY)) || DD < 1 || mode == 5) {
    if (DD > d) {
      DD -= d; toadd = SGN(toadd);
    } else if (DD < 1) {
      DD += DayInMonth(Mod12(MM - 1), YY);
      toadd = SGN(toadd);
    }
    MM += toadd;
  }

  /* Add years, either naturally or if month value overflowed. */

  if (MM > 12 || MM < 1 || mode == 6) {
    if (MM > 12) {
      MM -= 12; toadd = SGN(toadd);
    } else if (MM < 1) {
      MM += 12; toadd = SGN(toadd);
    }
    YY += toadd;
  }
  if (mode == 7)
    YY += 10 * toadd;      /* Add decades.   */
  else if (mode == 8)
    YY += 100 * toadd;     /* Add centuries. */
  else if (mode == 9)
    YY += 1000 * toadd;    /* Add millenia.  */
  TT = h+m/100.0;          /* Recalibrate hour time. */
}


/* Animate the current chart based on the given values indicating how much  */
/* to update by. We update and recast the current chart info appropriately. */

void Animate(mode, toadd)
int mode, toadd;
{
  if (modex == MODEW || modex == MODEG || modex == MODEP) {
    degree += toadd;
    if (degree >= DEGR)     /* For animating globe display, add */
      degree -= DEGR;       /* in appropriate degree value.     */
    else if (degree < 0)
      degree += DEGR;
  } else {
    if (mode == 10) {
#ifdef TIME
      /* For the continuous chart update to present moment */
      /* animation mode, go get whatever time it is now.   */
      InputData("now");
#else
      SetCore(Mon, Day, Yea, Tim, Zon, Lon, Lat);
      AddTime(1, toadd);
#endif
    } else {  /* Otherwise add on appropriate time vector to chart info. */
      SetCore(Mon, Day, Yea, Tim, Zon, Lon, Lat);
      AddTime(mode, toadd);
    }
    SetMain(MM, DD, YY, TT, ZZ, OO, AA);
    if (relation)
      CastRelation(FALSE);
    else
      CastChart(TRUE);
  }
}


/* Print a list of every key that one can press in an X window to do a */
/* certain function, and a description of what it does. This list gets */
/* displayed whenever one presses the 'H' or '?' key in the window.    */

void XDisplayKeys()
{
  char string[STRING];

  sprintf(string, "\n%s window keypress options (version %s):", appname,
    VERSION);
  Prints(string);
  Prints(" Press 'H' or '?' to display this list of key options.");
  Prints(" Press 'p' to toggle pause status on or off.");
  Prints(" Press 'x' to toggle fg/bg colors on screen.");
  Prints(" Press 'm' to toggle color/monochrome display on screen.");
  Prints(" Press 'i' to toggle status of the minor chart modification.");
  Prints(" Press 'T' to toggle header info on current chart on screen.");
  Prints(" Press 'b' to toggle drawing of a border around the chart.");
  Prints(" Press 'l' to toggle labeling of object points in chart.");
  Prints(" Press 'v' to display current chart positions on text screen.");
  Prints(" Press 'R', 'C', 'u', 'U' to toggle restriction status of minor");
  Prints("       objects, minor house cusps, uranian planets, and stars.");
  Prints(" Press 'c' to toggle relationship comparison chart mode.");
  Prints(" Press 's', 'h', 'f', 'F' to toggle status of sidereal zodiac,");
  Prints("       heliocentric charts, domal charts, and decan charts.");
  Prints(" Press 'O' and 'o' to recall/store a previous chart from memory.");
#ifdef X11
  Prints(" Press 'B' to dump current window contents to root background.");
#else
  Prints(" Press 'B' to resize chart display to full size of screen.");
#endif
  Prints(" Press 'Q' to resize chart display to a square.");
  Prints(" Press '<' and '>' to decrease/increase the scale size of the");
  Prints("       glyphs and the size of world map.");
  Prints(" Press '[' and ']' to decrease/increase tilt in globe display.");
  Prints(" Press '+' and '-' to add/subtract a day from current chart.");
#ifdef TIME
  Prints(" Press 'n' to set chart information to current time now.");
#endif
  Prints(" Press 'N' to toggle animation status on or off. Charts will");
  Prints("       be updated to current status and globe will rotate.");
  Prints(" Press '!'-'(' to begin updating current chart by adding times.");
  Prints("       !: seconds, @: minutes, #: hours, $: days, %: months,");
  Prints("       ^: years, &: years*10, *: years*100, (: years*1000.");
  Prints(" Press 'r' to reverse direction of time-lapse or animation.");
  Prints(" Press '1'-'9' to set rate of animation to 'n' degrees, etc.");
#ifdef MSG
  Prints(" Press '1'-'9' to determine section of chart to show if clipped.");
#endif
  Prints(
    " Press 'V','L','A','Z','S','E','W','G','P' to switch to normal (_v),");
  Prints(
    "       astro-graph (_L), grid (_g), local (_Z), space (_S), ephemeris");
  Prints("       (_E), world map (_XW), globe (_XG), and polar (_XP) modes.");
  Prints(" Press '0' to toggle between _Z,_Z0 & _XW,_XW0 & _E,_Ey modes.");
  Prints(" Press 'space' to force update of current graphics display.");
#ifdef MSG
  Prints(" Press 'tab' to toggle between graphics resolutions.");
#endif
  Prints(" Press 'q' to terminate the window and program.");
#ifdef MOUSE
  printl();
#ifdef X11
  Prints(" Left   mouse button: Draw line strokes on chart in window.");
  Prints(" Middle mouse button: Print coordinates of pointer on world map.");
  Prints(" Right  mouse button: Terminate the window and program.");
#endif
#ifdef MSG
  Prints(" Left  mouse button: Draw line strokes on chart in screen.");
  Prints(" Right mouse button: Set coordinates to pointer on world map.");
#endif
#endif /* MOUSE */
}


/* This routine gets called after an X window is brought up and displayed   */
/* on the screen. It loops, processing key presses, mouse clicks, etc, that */
/* the window receives, until the user specifies they want to exit program. */

void XSpin()
{
#ifdef X11
  XEvent event;
  int xresize = FALSE, xredraw = TRUE;
#else
#ifdef MOUSE
  EVENT event;
#endif
  int xresize = TRUE, xredraw = FALSE;
#endif
  int xbreak = FALSE, xpause = FALSE, xcast = FALSE, xcorner = 7,
    buttonx = -1, buttony = -1, dir = 1, length, i;
  colpal coldrw = hilite;

  xnow = -xnow;
  while (!xbreak) {

    /* Some chart windows, like the world maps and aspect grids, should */
    /* always be a certian size, so correct if a resize was attempted.  */

    if (modex == MODEL || modex == MODEW) {
      length = DEGR*SCALE+2;
      if (chartx != length) {
        chartx = length;
        xresize = TRUE;
      }
      length = (90*2+1)*SCALE+2;
      if (charty != length) {
        charty = length;
        xresize = TRUE;
      }
    } else if (modex == MODEg) {
      if (chartx !=
        (length = (gridobjects + (relation <= DASHr0))*CELLSIZE*SCALE+1)) {
        chartx = length;
        xresize = TRUE;
      } if (charty != length) {
        charty = length;
        xresize = TRUE;
      }

    /* Make sure the window isn't too large or too small. */

    } else {
	    if (chartx < BITMAPX1) {
	      chartx = BITMAPX1;
	      xresize = TRUE;
	    } else if (chartx > BITMAPX) {
	      chartx = BITMAPX;
	      xresize = TRUE;
	    }
	    if (charty < BITMAPY1) {
	      charty = BITMAPY1;
	      xresize = TRUE;
	    } else if (charty > BITMAPY) {
	      charty = BITMAPY;
	      xresize = TRUE;
	    }
		}

    /* If in animation mode, ensure we are in the flicker free resolution. */

    if (xnow < 0) {
      xnow = -xnow;
#ifdef MSG
      if (xscreen == hiresmode) {
        xscreen = loresmode;
        XBegin();
        chartx = config.numxpixels;
        charty = config.numypixels;
        if (chartx > charty)
          chartx = charty;
        if (ISEGA(xscreen))
          chartx = EGATOVGA(chartx);
				else if (ISCGA(xscreen))
					chartx = CGATOVGA(chartx);
        if ((modex == MODEv || modex == MODEw) && xtext &&
          !(exdisplay & DASHv0))
          chartx += SIDET;
        xresize = TRUE;
      }
#endif
    }

    /* Physically resize window if we've changed the size parameters. */

    if (xresize) {
      xresize = FALSE;
#ifdef X11
      XResizeWindow(disp, window, chartx, charty);
      XFreePixmap(disp, pixmap);
      pixmap = XCreatePixmap(disp, window, chartx, charty, depth);
#else
      if (config.numxpixels > chartx)
        offsetx = (config.numxpixels - chartx) >> 1;
      else {
        if (xcorner % 3 == 1)
          offsetx = 0;
        else if (xcorner % 3 == 0)
          offsetx = -chartx + config.numxpixels;
        else
          offsetx = -(chartx - config.numxpixels) / 2;
      }
      if (config.numypixels > charty)
        offsety = (config.numypixels - charty) >> 1;
      else {
        if (xcorner > 6)
          offsety = 0;
        else if (xcorner < 4)
          offsety = -charty + config.numypixels;
        else
          offsety = -(charty - config.numypixels) / 2;
      }
#endif
      xredraw = TRUE;
    }

    /* Recast chart if the chart information has changed any. */

    if (xcast) {
      xcast = FALSE;
      SetCore(Mon, Day, Yea, Tim, Zon, Lon, Lat);
      if (relation)
        CastRelation(FALSE);
      else
        CastChart(TRUE);
      xredraw = TRUE;
    }
    if (xnow && !xpause)
      xredraw = TRUE;

    /* Update the screen if anything has changed since last time around. */

    if (xredraw) {
      xredraw = FALSE;

      /* If we're in animation mode, change the chart info appropriately. */

      if (xnow)
        Animate(xnow, dir);
#ifdef X11
      XFillRectangle(disp, pixmap, pmgc, 0, 0, chartx, charty);
#else /* MSG */
#ifdef MOUSE
      SetPtrVis(HIDE);
#endif
      if (config.numvideopages > 1)
        _setactivepage(_getactivepage() == 0);
#endif /* MSG */
      XChart();
#ifdef X11
      XSync(disp, 0);
      XCopyArea(disp, pixmap, window, gc, 0, 0, chartx, charty, 0, 0);
#else /* MSG */
      if (config.numvideopages > 1)
        _setvisualpage(_getactivepage());
#ifdef MOUSE
      if (!xnow)
        SetPtrVis(SHOW);
#endif
#endif /* MSG */
    }  /* if */

    /* Now process what's on the event queue, i.e. any keys pressed, etc. */

#ifdef X11
    if (XEventsQueued(disp, QueuedAfterFlush /*QueuedAfterReading*/ )) {
      XNextEvent(disp, &event);

      /* Restore what's on window if a part of it gets uncovered. */

      if (event.type == Expose && event.xexpose.count == 0) {
        XSync(disp, 0);
        XCopyArea(disp, pixmap, window, gc, 0, 0, chartx, charty, 0, 0);
      }
      switch (event.type) {

      /* Check for a manual resize of window by user. */

      case ConfigureNotify:
        chartx = event.xconfigure.width;
        charty = event.xconfigure.height;
        XFreePixmap(disp, pixmap);
        pixmap = XCreatePixmap(disp, window, chartx, charty, depth);
        xredraw = TRUE;
        break;
      case MappingNotify:
        XRefreshKeyboardMapping((XMappingEvent PTR)&event);
        break;

#ifdef MOUSE
      /* Process any mouse buttons the user pressed. */

      case ButtonPress:
        buttonx = event.xbutton.x; buttony = event.xbutton.y;
        if (event.xbutton.button == Button1) {
          DrawColor(hilite);
          DrawPoint(buttonx, buttony);
          XSync(disp, 0);
          XCopyArea(disp, pixmap, window, gc, 0, 0, chartx, charty, 0, 0);
        } else if (event.xbutton.button == Button2 &&
          (modex == MODEL || modex == MODEW) && degree == 0) {
          Lon = DEGHALF-(real)(event.xbutton.x-1)/(real)(chartx-2)*DEGREES;
          Lat =  DEGQUAD-(real)(event.xbutton.y-1)/(real)(charty-2)*181.0;
          fprintf(S, "Mouse is at %s.\n", CharLocation(Lon, Lat, 60.0));
        } else if (event.xbutton.button == Button3)
          xbreak = TRUE;
        break;

      /* Check for user dragging any of the mouse buttons across window. */

      case MotionNotify:
        DrawColor(coldrw);
        DrawLine(buttonx, buttony, event.xbutton.x, event.xbutton.y);
        XSync(disp, 0);
        XCopyArea(disp, pixmap, window, gc, 0, 0, chartx, charty, 0, 0);
        buttonx = event.xbutton.x; buttony = event.xbutton.y;
        break;
#endif

      /* Process any keys user pressed in window. */

      case KeyPress:
        length = XLookupString((XKeyEvent PTR)&event, xkey, 10, &key, 0);
        if (length == 1) {
          i = xkey[0];
#else /* MSG */
#ifdef MOUSE
      if (!xnow && GetMouseEvent((EVENT *)&event)) {
        if (event.fsBtn == LEFT_DOWN && buttonx >= 0) {
          SetPtrVis(HIDE);
          DrawColor(coldrw);
          _moveto(buttonx, buttony);
          buttonx = event.x; buttony = event.y;
          _lineto(buttonx, buttony);
        } else if (event.fsBtn == RIGHT_DOWN &&
          (modex == MODEL || modex == MODEW) && degree == 0) {
          Lon = DEGHALF-(real)(event.x-offsetx)/(real)(chartx-2)*DEGREES;
          if (Lon < -DEGHALF)
            Lon = -DEGHALF;
          else if (Lon > DEGHALF)
            Lon = DEGHALF;
          Lat =  DEGQUAD-(real)(event.y-offsety)/(real)(charty-2)*181.0;
          if (Lat < -DEGQUAD)
            Lat = -DEGQUAD;
          else if (Lat > DEGQUAD)
            Lat = DEGQUAD;
          xcast = TRUE;
        } else if (event.fsBtn == MIDDLE_DOWN)
          xbreak = TRUE;
        else {
          buttonx = event.x; buttony = event.y;
        }
        SetPtrVis(SHOW);
      } else
#endif
        if (kbhit()) {
          i = getch();
#endif /* MSG */
          switch (i) {
          case ' ':
            xredraw = TRUE;
            break;
          case 'p':
            xpause = !xpause;
            break;
          case 'r':
            dir = -dir;
            break;
          case 'x':
            xreverse = !xreverse;
            XColorInit();
            xredraw = TRUE;
            break;
          case 'm':
            if (!xmono) {
              xcolor = !xcolor;
#ifdef MSG
              _getvideoconfig((struct videoconfig far *) &config);
#endif
              XColorInit();
              xredraw = TRUE;
            }
            break;
          case 'B':
#ifdef X11
            XSetWindowBackgroundPixmap(disp, root, pixmap);
            XClearWindow(disp, root);
#else
            chartx = config.numxpixels;
            charty = config.numypixels;
            if (modex == MODEv || modex == MODEw || modex == MODEg ||
              (modex == MODEZ && (exdisplay & DASHZ0) > 0) ||
              modex == MODES || modex == MODEG || modex == MODEP) {
              if (chartx > charty)
                chartx = charty;
              if (ISEGA(xscreen))
                chartx = EGATOVGA(chartx);
							else if (ISCGA(xscreen))
                chartx = CGATOVGA(chartx);
              if ((modex == MODEv || modex == MODEw) && xtext &&
                !(exdisplay & DASHv0))
                chartx += SIDET;
            }
            xresize = TRUE;
#endif
            break;
          case 'T':
            xtext = !xtext;
            xredraw = TRUE;
            break;
          case 'i':
            xbonus = !xbonus;
            xredraw = TRUE;
            break;
          case 'b':
            xborder = !xborder;
            xredraw = TRUE;
            break;
          case 'l':
            xlabel = !xlabel;
            xredraw = TRUE;
            break;
          case '<':
            if (scale > 100) {
              scale -= 100;
              xresize = TRUE;
            }
            break;
          case '>':
            if (scale < 300) {
              scale += 100;
              xresize = TRUE;
            }
            break;
          case '[':
            if (modex == MODEG) {
              tilt = tilt > -DEGQUAD ? tilt-11.25 : -DEGQUAD;
              xredraw = TRUE;
            }
            break;
          case ']':
            if (modex == MODEG) {
              tilt = tilt < DEGQUAD ? tilt+11.25 : DEGQUAD;
              xredraw = TRUE;
            }
            break;
          case 'Q':
            if (chartx > charty)
              chartx = charty;
            else
              charty = chartx;
#ifdef MSG
            if (ISEGA(xscreen))
              chartx = EGATOVGA(chartx);
						else if (ISCGA(xscreen))
              chartx = CGATOVGA(chartx);
#endif
            xresize = TRUE;
            break;
          case 'R':
            for (i = 11; i <= 15; i++)
              ignore[i] = !ignore[i];
            ignore[_FOR] = !ignore[_FOR]; ignore[_VTX] = !ignore[_VTX];
            xredraw = TRUE;
            break;
          case 'C':
            operation ^= DASHC;
            for (i = C_LO; i <= C_HI; i++)
              ignore[i] = !ignore[i];
            xcast = TRUE;
            break;
          case 'u':
            operation ^= DASHu;
            for (i = U_LO; i <= U_HI; i++)
              ignore[i] = !ignore[i];
            xcast = TRUE;
            break;
          case 'U':
            universe = !universe;
            for (i = S_LO; i <= S_HI; i++)
              ignore[i] = !ignore[i];
            xcast = TRUE;
            break;
          case 'c':
            if (!relation) {
              relation = DASHr0;
              SetTwin(Mon, Day, Yea, Tim, Zon, Lon, Lat);
            } else
              relation = 0;
            xcast = TRUE;
            break;
          case 's':
            operation ^= DASHs;
            xcast = TRUE;
            break;
          case 'h':
            centerplanet = centerplanet ? 0 : 1;
            xcast = TRUE;
            break;
          case 'f':
            operation ^= DASHf;
            xcast = TRUE;
            break;
          case 'F':
            operation ^= DASH3;
            xcast = TRUE;
            break;
          case '+':
            Animate(4, abs(dir));
            xcast = TRUE;
            break;
          case '-':
            Animate(4, -abs(dir));
            xcast = TRUE;
            break;
          case 'o':
            SetSave(Mon, Day, Yea, Tim, Zon, Lon, Lat);
            break;
          case 'O':
            SetMain(MonX, DayX, YeaX, TimX, ZonX, LonX, LatX);
            xcast = TRUE;
            break;
#ifdef TIME
          case 'n':
            InputData("now");
            SetMain(MM, DD, YY, TT, ZZ, OO, AA);
            xcast = TRUE;
            break;
#endif
          case 'N':                      /* The continuous update animation. */
            xnow = xnow ? 0 : -10;
            break;
          case '!': xnow = -1; break;    /* These are the nine different     */
          case '@': xnow = -2; break;    /* "add time to chart" animations.  */
          case '#': xnow = -3; break;
          case '$': xnow = -4; break;
          case '%': xnow = -5; break;
          case '^': xnow = -6; break;
          case '&': xnow = -7; break;
          case '*': xnow = -8; break;
          case '(': xnow = -9; break;
          case 'V': modex = MODEv; xredraw = TRUE; break; /* Should we go    */
          case 'L': modex = MODEL; xredraw = TRUE; break; /* switch to a     */
          case 'A': modex = MODEg; xredraw = TRUE; break; /* new chart type? */
          case 'Z': modex = MODEZ; xredraw = TRUE; break;
          case 'S': modex = MODES; xredraw = TRUE; break;
          case 'E': modex = MODEE; xredraw = TRUE; break;
          case 'W': modex = MODEW; xredraw = TRUE; break;
          case 'G': modex = MODEG; xredraw = TRUE; break;
          case 'P': modex = MODEP; xredraw = TRUE; break;
#ifdef BIORHYTHM
          case 'Y':               /* Should we switch to biorhythm chart? */
            if (!relation) {
              SetTwin(Mon, Day, Yea, Tim, Zon, Lon, Lat);
            }
            relation = DASHrb;
            modex = MODEb;
            xcast = TRUE;
            break;
#endif
          case '0':
            exdisplay ^= DASHZ0 | DASHEy | DASHXW0;
            modex = (modex == MODEv ? MODEw : (modex == MODEw ? MODEv :
              modex));
            xredraw = TRUE;
            break;
          case 'v': case 'H': case '?':
#ifdef MSG
            _setvideomode(_DEFAULTMODE);
            if (i != 'v')
              _settextrows(43);
#endif
            if (i == 'v')
              ChartLocation();
            else
              XDisplayKeys();
#ifdef MSG
            while (!kbhit())
              ;
            i = getch();
            if (i == 'q' || i == ESCAPE || i == '\3') {
              xbreak = TRUE;
              break;
            }
            XBegin();
            xresize = TRUE;
#endif
            break;
#ifdef MSG
          case '\t':
            if (xscreen == hiresmode)
              xscreen = loresmode;
            else
              xscreen = hiresmode;
            XBegin();
            chartx = config.numxpixels;
            charty = config.numypixels;
            if (chartx > charty)
              chartx = charty;
            if (ISEGA(xscreen))
              chartx = EGATOVGA(chartx);
						else if (ISCGA(xscreen))
              chartx = CGATOVGA(chartx);
            if ((modex == MODEv || modex == MODEw) && xtext &&
              !(exdisplay & DASHv0))
              chartx += SIDET;
            xresize = TRUE;
            break;
#endif
          case '\b':
#ifdef MSG
#ifdef MOUSE
            SetPtrVis(HIDE);
#endif
#endif /* MSG */
            DrawClearScreen();
            break;
#ifdef MOUSE
          case 'z'-'`': coldrw = BLACK;   break;
          case 'e'-'`': coldrw = MAROON;  break;
          case 'f'-'`': coldrw = DKGREEN; break;
          case 'o'-'`': coldrw = ORANGE;  break;
          case 'n'-'`': coldrw = DKBLUE;  break;
          case 'u'-'`': coldrw = PURPLE;  break;
          case 'k'-'`': coldrw = DKCYAN;  break;
          case 'l'-'`': coldrw = LTGRAY;  break;
          case 'd'-'`': coldrw = DKGRAY;  break;
          case 'r'-'`': coldrw = RED;     break;
          case 'g'-'`': coldrw = GREEN;   break;
          case 'y'-'`': coldrw = YELLOW;  break;
          case 'b'-'`': coldrw = BLUE;    break;
          case 'v'-'`': coldrw = MAGENTA; break;
          case 'j'-'`': coldrw = CYAN;    break;
          case 'a'-'`': coldrw = WHITE;   break;
#endif
          case 'q': case ESCAPE: case '\3':
            xbreak = TRUE;
            break;
          default:
            if (i > '0' && i <= '9') {
#ifdef MSG
              if (xnow)
#endif
                /* Process numbers 1..9 signifying animation rate. */
                dir = (dir > 0 ? 1 : -1)*(i-'0');
#ifdef MSG
              else {
                /* If we aren't in animation mode, then 1..9 refers to the */
                /* clipping "quadrant" to use if chart size > screen size. */
                xcorner = i-'0';
                xresize = TRUE;
              }
#endif
            break;
            }
            printc(BELL);    /* Any key not bound will sound a beep. */
          }  /* switch */
        }  /* if */
#ifdef X11
      default:
        ;
      }  /* switch */
    }  /* if */
#endif
  }  /* while */
}


/* This is called right before program termination to get rid of the window. */

void XEnd()
{
#ifdef X11
  XFreeGC(disp, gc);
  XFreeGC(disp, pmgc);
  XFreePixmap(disp, pixmap);
  XDestroyWindow(disp, window);
  XCloseDisplay(disp);
#else
  _setvideomode(_DEFAULTMODE);
#endif
}
#endif /* ISG */


/*
******************************************************************************
** Main graphics processing
******************************************************************************
*/

/* Print a list of every command switch dealing with the graphics features  */
/* that can be passed to the program, and a description of what it does.    */
/* This is part of what the -H switch prints, if graphics were compiled in. */

void XDisplaySwitches()
{
  Prints(" _X: Create a graphics chart instead of displaying it as text.");
#ifdef ISG
  Prints(" _Xb: Create bitmap file instead of putting graphics on screen.");
#endif
  Prints(" _Xb[n,c,v,a,b]: Set bitmap file output mode to X11 normal,");
  Prints("     compacted, very compact, Ascii (bmtoa), or Windows bmp.");
#ifdef PS
  Prints(" _Xp: Create PostScript stroke graphic instead of bitmap file.");
  Prints(" _Xp0: Like _Xp but create complete instead of encapsulated file.");
#endif
#ifdef META
  Prints(" _XM[0]: Create Windows metafile stroke graphic instead of bitmap.");
#endif
  Prints(" _Xo <file>: Write output bitmap or graphic to specified file.");
#ifdef X11
  Prints(" _XB: Display X chart on root instead of in a separate window.");
#endif
  Prints(" _Xm: Create monochrome graphic instead of one in color.");
  Prints(" _Xr: Create chart graphic in reversed colors (white background).");
#ifdef X11
  Prints(" _Xw <hor> [<ver>], _ge[..]: Change the size of chart graphic.");
#else
  Prints(" _Xw <hor> [<ver>]: Change the size of chart graphic.");
#endif
  Prints(" _Xs <100,200,300>: Change the size of map or characters by n%.");
  Prints(" _Xi: Create chart graphic in slightly modified form.");
  Prints(" _XT: Inhibit display of chart info at bottom of graphic.");
  Prints(" _Xl: Inhibit labeling of object points in chart graphic.");
  Prints(" _X1 <object>: Rotate wheel charts so object is at left edge.");
  Prints(" _X2 <object>: Rotate wheel charts so object is at top edge.");
#ifdef X11
  Prints(" _Xd <name>, _di[..] <name>: Open X window on specified display.");
#endif
  Prints(" _XW: Simply create an image of the world map.");
  Prints(" _XW0: Like _XW but do a non-rectangular Mollewide projection.");
  Prints(" _XP: Create just the world map, but from a polar projection.");
  Prints(" _XG [<degrees>]: Display the image of the world as a globe.");
#ifdef ISG
  Prints(" _Xn [<mode>]: Start up chart or globe display in animation mode.");
  Prints("Also, press 'H' while running for list of key press options.");
#endif
}


/* Process a command line switch passed to the program dealing with the      */
/* graphics features. This is just like the processing of each switch in the */
/* main program; however, here each switch has been prefixed with an 'X'.    */

int XProcessSwitches(argc, argv, pos)
int argc, pos;
char **argv;
{
  int i = 0;
  char string[STRING], c;

  switch (argv[0][pos]) {
  case '\0':
    break;

  case 'b':
    c = CAP(argv[0][pos+1]);
    if (IsValidBmpmode(c))
      bitmapmode = c;
    xbitmap = TRUE;
    psfile = metafile = FALSE;
    break;

#ifdef PS
  case 'p':
    psfile = TRUE + (argv[0][pos+1] != '0');
    xbitmap = metafile = FALSE;
    break;
#endif

#ifdef META
  case 'M':
    if (argv[0][pos+1] == '0')
      xfont = !xfont;
    metafile = TRUE;
    xbitmap = psfile = FALSE;
    break;
#endif

  case 'o':
    if (argc <= 1) {
      TooFew("Xo");
      return -1;
    }
    if (!xbitmap && !psfile && !metafile)
      xbitmap = TRUE;
    outputfile = argv[1];
    i++;
    break;

#ifdef X11
  case 'B':
    xroot = !xroot;
    break;
#endif

  case 'm':
    xcolor = !xcolor;
    break;

  case 'r':
    xreverse = !xreverse;
    break;

  case 'w':
    if (argc <= 1) {
      TooFew("Xw");
      return -1;
    }
    chartx = atoi(argv[1]);
    if (argc > 2 && (charty = atoi(argv[2]))) {
      argc--; argv++;
      i++;
    } else
      charty = chartx;
    if (!IsValidGraphx(chartx)) {
      BadVal("Xw", chartx);
      return -1;
    }
    if (!IsValidGraphy(charty)) {
      BadVal("Xw", charty);
      return -1;
    }
    i++;
    break;

  case 's':
    if (argc <= 1) {
      TooFew("Xs");
      return -1;
    }
    scale = atoi(argv[1]);
    if (scale < 100)
      scale *= 100;
    if (!IsValidScale(scale)) {
      BadVal("Xs", scale);
      return -1;
    }
    i++;
    break;

  case 'i':
    xbonus = !xbonus;
    break;

  case 'T':
    xtext = !xtext;
    break;

  case 'l':
    xlabel = !xlabel;
    break;

  case '1':
    if (argc <= 1) {
      TooFew("X1");
      return -1;
    }
    xeast = atoi(argv[1]);
    if (!IsItem(xeast)) {
      BadVal("X1", xeast);
      return -1;
    }
    i++;
    break;

  case '2':
    if (argc <= 1) {
      TooFew("X2");
      return -1;
    }
    xeast = atoi(argv[1]);
    if (!IsItem(xeast)) {
      BadVal("X2", xeast);
      return -1;
    }
    xeast = -xeast;
    i++;
    break;

  case 'd':
    if (argc <= 1) {
      TooFew("Xd");
      return -1;
    }
    dispname = argv[1];
    i++;
    break;

  case 'W':
    if (argc > 1 && ((degree = atoi(argv[1])) || argv[1][0] == '0')) {
      i++;
      if (degree < 0 || degree >= DEGR) {
        BadVal("XW", degree);
        return -1;
      }
    } else
      degree = 0;
    modex = MODEW;
    if (argv[0][pos+1] == '0')
      exdisplay |= DASHXW0;
    autom = TRUE;
    break;

  case 'P':
    if (argc > 1 && ((degree = atoi(argv[1])) || argv[1][0] == '0')) {
      i++;
      if (degree < 0 || degree >= DEGR) {
        BadVal("XP", degree);
        return -1;
      }
    } else
      degree = 0;
    modex = MODEP;
    if (argv[0][pos+1] == '0')
      exdisplay |= DASHXP0;
    autom = TRUE;
    break;

  case 'G':
    if (argc > 1 && ((degree = atoi(argv[1])) || argv[1][0] == '0')) {
      i++;
      if (degree < 0 || degree >= DEGR) {
        BadVal("XG", degree);
        return -1;
      }
      if (argc > 2 && ((tilt = atof(argv[2])) || argv[2][0] == '0')) {
        i++;
        if (tilt < -DEGQUAD || tilt > DEGQUAD) {
          BadVal2("XG", tilt);
          return -1;
        }
      }
    }
    modex = MODEG;
    autom = TRUE;
    break;

#ifdef ISG
  case 'n':
    if (argc > 1 && (xnow = atoi(argv[1])))
      i++;
    else
      xnow = 10;
    if (xnow < 1 || xnow > 10) {
      BadVal("Xn", xnow);
      return -1;
    }
    break;
#endif

  default:
    sprintf(string, "Unknown switch '%s'", argv[0]);
    PrintError(string);
    return -1;
  }
  return i;    /* 'i' contains the value to be added to argc when we return. */
}


/* This is the main interface to all the graphics features. This routine     */
/* is called from the main program if any of the -X switches were specified, */
/* and it sets up for and goes and generates the appropriate graphics chart. */
/* We return TRUE if successfull, FALSE if some non-fatal error occurred.    */

bool XAction()
{
  char string[STRING];

  xfile = (xbitmap || psfile || metafile);

  /* First figure out what graphic mode to generate the chart in, based on */
  /* various non-X command switches, e.g. -L combined with -X, -g combined */
  /* with -X, and so on, and determine the size the window is to be, too.  */

  if (modex == MODEv) {
    if (todisplay & DASHw)
      modex = MODEw;
    else if (todisplay & DASHL)
      modex = MODEL;
    else if ((todisplay & DASHg) | (todisplay & DASHm)) {
      modex = MODEg;
      if (relation <= DASHr0 &&
        (todisplay & DASHm) > 0 && (exdisplay & DASHm0) == 0)
        exdisplay |= DASHg0;
      chartx = charty =
				(gridobjects + (relation <= DASHr0))*CELLSIZE*SCALE + 1;
    } else if (todisplay & DASHZ)
      modex = MODEZ;
    else if (todisplay & DASHS)
      modex = MODES;
    else if (todisplay & DASHE)
      modex = MODEE;
    else if (relation == DASHrb)
      modex = MODEb;
  }
  if ((modex == MODEv || modex == MODEw) && !(exdisplay & DASHv0))
    chartx += SIDESIZE;
  if (modex == MODEL || modex == MODEW) {
    chartx = DEGR*SCALE+2;
    charty = (90*2+1)*SCALE+2;
  }
  if (xfile) {                       /* Initialize bitmap or window. */
    if (chartx > BITMAPX)
      chartx = BITMAPX;
    if (charty > BITMAPY)
      charty = BITMAPY;
    if (xbitmap) {
      bitmaprow = (chartx + 1) >> 1;
      Allocate(bm, (long)bitmaprow * charty, bitmap);
      if (bm == NULL) {
        sprintf(string,
          "Not enough memory for %d by %d bitmap (%ld bytes).",
          chartx, charty, (long)bitmaprow * charty);
        PrintError(string);
        return FALSE;
      }
    }
#ifdef PS
    else if (psfile)
    	PSbegin();
#endif
    else {
      Allocate(bm, MAXMETA, bitmap);
      if (bm == NULL) {
        sprintf(string,
          "Not enough memory for metafile. (%ld bytes.)", MAXMETA);
        PrintError(string);
        return FALSE;
      }
      chartx *= METAMUL;    /* Increase chart sizes and scales behind the */
      charty *= METAMUL;    /* scenes to make graphics look smoother.     */
      scale  *= METAMUL;
    }
    XColorInit();
  }
#ifdef ISG
  else
    XBegin();
#endif
  if (xroot || xfile)    /* Go draw the graphic chart. */
    XChart();
  if (xfile) {    /* Write bitmap to file if in that mode. */
    WriteFile();
    if (!psfile)
      Deallocate(bm);
  }
#ifdef ISG
  else {
#ifdef X11
    if (xroot) {
      XSetWindowBackgroundPixmap(disp, root, pixmap);    /* Process -XB. */
      XClearWindow(disp, root);

      /* If -Xn in effect with -XB, then enter infinite loop where we */
      /* calculate and animate chart, displaying on the root window.  */
      if (xnow)
        loop {
          Animate(xnow, 1);
          if (relation > DASHr0 || (modex != MODEZ && modex != MODES))
            XFillRectangle(disp, pixmap, pmgc, 0, 0, chartx, charty);
          XChart();
          XSetWindowBackgroundPixmap(disp, root, pixmap);
          XClearWindow(disp, root);
        }
    } else
#endif
      XSpin();    /* Window's up; process commands given to window now. */
    XEnd();
    return TRUE;
  }
#endif /* ISG */
}
#endif /* GRAPH */

/* xdriver.c */
