/*
 * xvevent.c - EventLoop and support routines for XV
 *
 *  Author:    John Bradley, University of Pennsylvania
 *                (bradley@cis.upenn.edu)
 *
 *  Contains:
 *            int  EventLoop()
 *            void DrawWindow(x,y,w,h)
 *            void WResize(w,h)
 *            void WRotate()
 *            void WCrop(w,h)
 *            void WUnCrop()
 *            void GetWindowPos(&xwa)
 *            void SetWindowPos(&xwa)
 *     static void TrackCrop(mx,my)
 *     static void CropKey(dx,dy,grow)
 *     static int  Rect(x,y,x1,y1)
 *            void InvCropRect()
 *     static void TrackPicValues(mx,my)
 *     static int  CheckForConfig()
 *            void SetEpicMode()
 *            int  xvErrorHandler(disp, err)
 */


/*
 * Copyright 1989, 1990, 1991, 1992 by John Bradley and
 *                       The University of Pennsylvania
 *
 * Permission to use, copy, and distribute for non-commercial purposes,
 * is hereby granted without fee, providing that the above copyright
 * notice appear in all copies and that both the copyright notice and this
 * permission notice appear in supporting documentation. 
 *
 * The software may be modified for your own purposes, but modified versions
 * may not be distributed.
 *
 * This software is provided "as is" without any expressed or implied warranty.
 *
 * The author may be contacted via:
 *    US Mail:   John Bradley
 *               GRASP Lab, Room 301C
 *               3401 Walnut St.  
 *               Philadelphia, PA  19104
 *
 *    Phone:     (215) 898-8813
 *    EMail:     bradley@cis.upenn.edu       
 */


#define NEEDSTIME    /* for -wait handling in eventloop */

#include "xv.h"

static int rotatesLeft = 0;
static int origcropx, origcropy, origcropvalid=0;
static int canstartwait;


/* local function pre-definitions */
#ifdef __STDC__
static void WMaximize(void);
static void TrackCrop(int, int);
static void CropKey(int, int, int);
static int  Rect(int, int, int, int);
static void TrackPicValues(int, int);
static int  CheckForConfig(void);
static void onInterrupt(void);
#else
static void WMaximize(), TrackCrop(), CropKey(), TrackPicValues();
static int  Rect(), CheckForConfig();
static void onInterrupt();
#endif




/****************/
int EventLoop()
/****************/
{
  XEvent event;
  int    retval,done,waiting;
  time_t orgtime, curtime;


#ifndef NOSIGNAL
  signal(SIGQUIT, onInterrupt);
#endif

  /* note: there's no special event handling if we're using the root window.
     if we're using the root window, we will recieve NO events for mainW */

  /* note: 'canstartwait' is magically turned 'true' in HandleEvent when I
     think I've finally gotten 'mainW' drawn.  It does not necessarily
     mean that any waiting is/will be done.  Also note that if we're
     using a root mode, canstartwait is instantly turned on, as we aren't
     going to be getting Expose/Configure events on the root window */

  done = retval = waiting = canstartwait = 0;

  if (useroot) canstartwait = 1;

  while (!done) {

    if (waitsec > -1 && canstartwait && !waiting && XPending(theDisp)==0) {
      /* we wanna wait, we can wait, we haven't started waiting yet, and 
	 all pending events (ie, drawing the image the first time) 
	 have been dealt with:  START WAITING */
      time((time_t *) &orgtime);
      waiting = 1;
    }


    if (waitsec == -1 || XPending(theDisp)>0) {
      XNextEvent(theDisp, &event);
      retval = HandleEvent(&event,&done);
    }

    else {                      /* no events.  check wait status */
      if (waitsec>-1 && waiting) {
	time((time_t *) &curtime);
	if (curtime - orgtime < waitsec) sleep(1);
	else {
	  if (waitloop) return NEXTLOOP;
	  else return NEXTQUIT;
	}
      }
    }
  }  /* while (!done) */

  if (!useroot && origcropvalid) WUnCrop();
  origcropvalid = 0;

  return(retval);
}



/****************/
int HandleEvent(event, donep)
XEvent *event;
int *donep;
{
  static int wasInfoUp=0, wasCtrlUp=0, wasDirUp=0, wasGamUp=0, wasPsUp=0;
  static int wasJpegUp=0, wasTiffUp=0;

  int done=0, retval=0;

  switch (event->type) {

  case ColormapNotify: {
    XColormapEvent *cev = (XColormapEvent *) event;
    if (cev->window == mainW || cev->window == rootW ||
	(cev->window == gamW && cmapInGam)) {

      if (LocalCmap) {
	if (cev->colormap == LocalCmap)
	  cmapinstalled = (cev->state == ColormapInstalled);

	else if (cev->window == rootW && 
		 cev->colormap != LocalCmap &&
		 cev->state == ColormapInstalled)
	  cmapinstalled = 0;

	else if (cev->window == mainW &&
		 cev->colormap != LocalCmap &&
		 cev->state == ColormapInstalled) {
	  cmapinstalled = 1;    /* KLUDGE */
	}

	if (DEBUG) fprintf(stderr,"Cmapinstalled = %d\n", cmapinstalled);
      }

      if (DEBUG) {
	fprintf(stderr, "Colormap event:  window = %s",
		(cev->window == mainW) ? "mainW" :
		((cev->window == rootW) ? "rootW" : "gamW/other"));
	
	fprintf(stderr, "  colormap = %s, state = %s\n",
		(cev->colormap == None) ? "None" :
		(cev->colormap == LocalCmap) ? "LocalCmap" : "???",
		(cev->state == ColormapInstalled) ?
		"installed" : "deinstalled");
      }
    }
  }
    break;

  case ClientMessage: {
    Atom proto, delwin;
    XClientMessageEvent *client_event = (XClientMessageEvent *) event;

    if (PUCheckEvent (event)) break;   /* event has been processed */

    proto = XInternAtom(theDisp, "WM_PROTOCOLS", FALSE);
    delwin = XInternAtom(theDisp, "WM_DELETE_WINDOW", FALSE);

    if (client_event->message_type == proto &&
	client_event->data.l[0]    == delwin) {
      /* it's a WM_DELETE_WINDOW event */

      if      (client_event->window == infoW) InfoBox(0);
      else if (client_event->window == gamW)  GamBox(0);
      else if (client_event->window == ctrlW) CtrlBox(0);
      else if (client_event->window == dirW)  DirBox(0);
      else if (client_event->window == psW)   PSDialog(0);

#ifdef HAVE_JPEG
      else if (client_event->window == jpegW) JPEGDialog(0);
#endif

#ifdef HAVE_TIFF
      else if (client_event->window == tiffW) TIFFDialog(0);
#endif

      else if (client_event->window == mainW) exit(0);
    }
  }
    break;


  case Expose: {
    XExposeEvent *exp_event = (XExposeEvent *) event;
    int x,y,w,h;
    Window win;

#ifdef VMS
    static int borders_sized = 0;
  
    if (!borders_sized  && !useroot && exp_event->window == mainW) {
      /*
       * Initial expose of main window, find the size of the ancestor
       * window just prior to the root window and adjust saved size
       * of display so that maximize functions will allow for window
       * decorations.
       */
      int status, count, mwid, mhgt, x, y, w, h, b, d, mbrd;
      Window root, parent, *children, crw = exp_event->window;
      borders_sized = 1;
      status = XGetGeometry(theDisp, crw, 
			    &root, &x, &y, &mwid, &mhgt, &mbrd, &d);
      
      for ( parent = crw, w=mwid, h=mhgt;
	   status && (parent != root) && (parent != vrootW); ) {
	crw = parent;
	status = XQueryTree ( theDisp, crw, &root, &parent, 
			     &children, &count );
	if ( children != NULL ) XFree ( children );
      }
      status = XGetGeometry(theDisp, crw, &root, &x, &y, &w, &h, &b, &d);
      if ( status ) {
	dispWIDE = dispWIDE + mwid - w + (2*b);
	dispHIGH = dispHIGH + mhgt - h + b;
	/*printf("New display dims: %d %d\n", dispWIDE, dispHIGH ); */
      }
    }
#endif


    win = exp_event->window;
    x = exp_event->x;      y = exp_event->y;
    w = exp_event->width;  h = exp_event->height;
    
    if (PUCheckEvent  (event)) break;   /* event has been processed */
    if (PSCheckEvent  (event)) break;   /* event has been processed */

#ifdef HAVE_JPEG
    if (JPEGCheckEvent(event)) break;   /* event has been processed */
#endif

#ifdef HAVE_TIFF
    if (TIFFCheckEvent(event)) break;   /* event has been processed */
#endif

    if (GamCheckEvent (event)) break;   /* event has been processed */

    /* if the window doesn't do intelligent redraw, drop all-1 exposes */
    if (exp_event->count>0 && exp_event->window != mainW 
	                   && exp_event->window != ctrlW
	                   && exp_event->window != dirW
	                   && exp_event->window != infoW) break;

    if (win == mainW) {
      if (DEBUG) fprintf(stderr,"EXPOSE:  ");
      if (!CheckForConfig()) {
	if (DEBUG) fprintf(stderr,"No configs. Expose %d,%d %dx%d\n",x,y,w,h);
	if (DEBUG) XClearArea(theDisp, mainW, x,y,w,h, False);

	DrawWindow(x,y,w,h);
	    
	if (but[BCROP].active) {
	  XRectangle xr;
	  xr.x = x;  xr.y = y;  xr.width = w;  xr.height = h;
	  
	  XSetClipRectangles(theDisp,theGC,0,0,&xr,1,Unsorted);
	  InvCropRect();
	  XSetClipMask(theDisp,theGC,None);
	}

	if (exp_event->count == 0) {
	  canstartwait = 1;  /* finished drawing */
	  XSync(theDisp, False);
	}
      }
      else
	if (DEBUG) 
	  fprintf(stderr,"Ignoring expose event.  Config pending\n");
    }

    else if (win == infoW)          RedrawInfo(x,y,w,h);
    else if (win == ctrlW)          RedrawCtrl(x,y,w,h);
    else if (win == nList.win)      LSRedraw(&nList);
    else if (win == nList.scrl.win) SCRedraw(&nList.scrl);
    else if (win == dirW)           RedrawDirW(x,y,w,h);
    else if (win == dList.win)      LSRedraw(&dList);
    else if (win == dList.scrl.win) SCRedraw(&dList.scrl);
    else if (win == dnamW)          RedrawDNamW();
  }      
    break;
	

  case ButtonPress: {
    XButtonEvent *but_event = (XButtonEvent *) event;
    int i,x,y;
    Window win;

    win = but_event->window;
    x = but_event->x;  y = but_event->y;

    if (win == mainW && !useroot && showzoomcursor) {
      DoZoom(x, y, but_event->button);
      break;
    }

    if (PUCheckEvent  (event)) break;
    if (PSCheckEvent  (event)) break;

#ifdef HAVE_JPEG
    if (JPEGCheckEvent(event)) break;
#endif

#ifdef HAVE_TIFF
    if (TIFFCheckEvent(event)) break;
#endif

    if (GamCheckEvent (event)) break;

    switch (but_event->button) {

    case Button1:  
      if      (win == mainW) TrackPicValues(x,y);

      else if (win == ctrlW) {
	int   w,h;

	if (MBClick(&dispMB, x,y)) {
	  if (MBTrack(&dispMB)) HandleDispMode();
	  break;
	}

	if (MBClick(&conv24MB, x,y)) {
	  if (MBTrack(&conv24MB)) { conv24 = conv24MB.selected; }
	  break;
	}

	i=ClickCtrl(x,y);

	switch (i) {
	case BNEXT:   retval= NEXTPIC;  done=1;  break;
	case BPREV:   retval= PREVPIC;  done=1;  break;
	case BLOAD:   DirBox(BLOAD);    break;
	case BSAVE:   DirBox(BSAVE);    break;
	case BQUIT:   retval= QUIT;     done=1;  break;
	    
	case BCROP:   DoCrop();  break;
	case BUNCROP: UnCrop();  break;
	case BNORM:   WResize(cWIDE/normFact, cHIGH/normFact);  break;
	case BMAX:    WMaximize();  break;
	case BUP10:   w = (eWIDE*11)/10;  h = (eHIGH*11)/10;
	              if (w==eWIDE) w++;
	              if (h==eHIGH) h++;
	              WResize(w,h);
	              break;

	case BDN10:   WResize((eWIDE*9)/10, (eHIGH*9)/10);  break;
	case BUP2:    WResize(eWIDE*2, eHIGH*2);  break;
	case BDN2:    WResize(eWIDE/2, eHIGH/2);  break;
	case B4BY3:   w = eWIDE;  h = (w * 3) / 4;
                      if (h>maxHIGH) { h = eHIGH;  w = (h*4)/3; }
                      WResize(w,h);
                      break;

	case BASPECT: FixAspect(1,&w,&h);  WResize(w,h);  break;
	case BMAXPECT: { int w1,h1;
			 w1 = eWIDE;  h1 = eHIGH;
			 eWIDE = dispWIDE;  eHIGH = dispHIGH;
			 FixAspect(0,&w,&h);
			 eWIDE = w1;  eHIGH = h1;  /* play it safe */
			 WResize(w,h);
		       }   break;

	case BROTL:   Rotate(1);  break;
	case BROTR:   Rotate(0);  break;

	case BACROP:  AutoCrop();  break;

	case BFLIPH:  Flip(0);   break;
	case BFLIPV:  Flip(1);   break;
	case BSMOOTH: Smooth();  break; 
	case BDITH:   ColorDither(NULL, eWIDE, eHIGH);  break;
	case BRAW:    xvDestroyImage(theImage);
		      theImage = NULL;
		      Resize(eWIDE,eHIGH);
		      break;

	case BINFO:   InfoBox(!infoUp); break;
	case BGAMMA:  GamBox(!gamUp);   break;

	case BDELETE: if (DeleteCmd()) { done = 1;  retval = DELETE; }
	              break;

	case BGRAB:   if (Grab()) { done = 1;  retval = GRABBED; }
	              break;

	default:      break;
	}

	if (i==BFLIPH || i==BFLIPV || i==BSMOOTH || i==BDITH || i==BRAW) {
	  if (useroot) MakeRootPic();
	          else DrawWindow(0,0,eWIDE,eHIGH);
	  if (but[BCROP].active) InvCropRect();
	  SetCursors(-1);
	}
      }

      else if (win == nList.win) {
	i=LSClick(&nList,but_event);
	if (i>=0) { done = 1;  retval = i; }
      }

      else if (win == nList.scrl.win) SCTrack(&nList.scrl, x, y);

      else if (win == dirW) {
	i=ClickDirW(x,y);
	    
	switch (i) {
	case S_BOK:   if (dirUp == BLOAD) {
	                retval = LOADPIC;
		        done=1;
		      }
	              else if (dirUp == BSAVE) {
		        DoSave();
		      }
	              break;

	case S_BCANC: DirBox(0);  break;

	case S_BRESCAN:
	              WaitCursor();  LoadCurrentDirectory();  SetCursors(-1);
	              break;
	}
      }

      else if (win == dList.win) {
	i=LSClick(&dList,but_event);
	SelectDir(i);
      }

      else if (win == dList.scrl.win) SCTrack(&dList.scrl, x,y);
      else if (win == infoW)          InfoBox(0);  /* close info */

      break;


    case Button2:  if (win == mainW) TrackCrop(x,y);
                   break;

    case Button3:  /* if using root, MUST NOT get rid of ctrlbox. */
	           if (!useroot) CtrlBox(!ctrlUp); 
                   break;

    default:       break;
    }
  }
    break;

	
  case KeyRelease: {
    XKeyEvent *key_event = (XKeyEvent *) event;
    char buf[128];  KeySym ks;
    int stlen, dealt, shift;
	
    stlen = XLookupString(key_event,buf,128,&ks,(XComposeStatus *) NULL);
    dealt = 0;

    if (key_event->window == mainW &&
	(ks == XK_Control_L || ks == XK_Control_R)) {
      if (showzoomcursor) {
	showzoomcursor = 0;
	SetCursors(-1);
      }
    }
  }
    break;

  case KeyPress: {
    XKeyEvent *key_event = (XKeyEvent *) event;
    char buf[128];  KeySym ks;
    int stlen, dealt, shift;
	
    stlen = XLookupString(key_event,buf,128,&ks,(XComposeStatus *) NULL);
    dealt = 0;

    if (PUCheckEvent  (event)) break;
    if (PSCheckEvent  (event)) break;

    if (key_event->window == mainW &&
	(ks == XK_Control_L || ks == XK_Control_R)) {
      if (!showzoomcursor) {
	showzoomcursor = 1;
	SetCursors(-1);
      }
    }

#ifdef HAVE_JPEG
    if (JPEGCheckEvent(event)) break;
#endif

#ifdef HAVE_TIFF
    if (TIFFCheckEvent(event)) break;
#endif

    if (GamCheckEvent (event)) break;

    /* check for crop rect keys */
    if (key_event->window == mainW  /*  && !dirUp */) {
      dealt = 1;  shift = key_event->state & ShiftMask;
      if      (ks==XK_Left  || ks==XK_KP_4 || ks==XK_F30) CropKey(-1, 0,shift);
      else if (ks==XK_Right || ks==XK_KP_6 || ks==XK_F32) CropKey( 1, 0,shift);
      else if (ks==XK_Up    || ks==XK_KP_8 || ks==XK_F28) CropKey( 0,-1,shift);
      else if (ks==XK_Down  || ks==XK_KP_2 || ks==XK_F34) CropKey( 0, 1,shift);
      else dealt = 0;
      if (dealt) break;
    }


    /* check for List keys */
    if (key_event->window == ctrlW || key_event->window == dirW /*||dirUp*/) {
      LIST *theList;

      if (key_event->window == dirW /*|| dirUp*/) theList = &dList;
      else theList = &nList;

      dealt = 1;
      if      (ks==XK_Prior) LSKey(theList,LS_PAGEUP);
      else if (ks==XK_Next)  LSKey(theList,LS_PAGEDOWN);
      else if (ks==XK_Up)    LSKey(theList,LS_LINEUP);
      else if (ks==XK_Down)  LSKey(theList,LS_LINEDOWN);
      else if (ks==XK_Home)  LSKey(theList,LS_HOME);
      else if (ks==XK_End)   LSKey(theList,LS_END);
      else dealt = 0;

      if (theList == &dList && dealt) {  /* changed dir selection */
	SelectDir(-1);  /* nothing was double-clicked */
      }
      
      if (dealt) break;
    }

    /* check dir filename arrows */
    if (key_event->window == dirW && ks==XK_Left)  { DirKey('\002'); break; }
    if (key_event->window == dirW && ks==XK_Right) { DirKey('\006'); break; }


    if (!stlen) break;

    /* if dirUp, send all keystrokes to it OR NOT */
    if (key_event->window == dirW /* || dirUp */) {
      if (DirKey(buf[0])) XBell(theDisp,0);
    }

    else {
      /* commands valid in any window */
	  
      switch (buf[0]) {
      case '\t':
      case ' ':    FakeButtonPress(&but[BNEXT]);    break;

      case '\r':
      case '\n':   if (nList.selected >= 0 && nList.selected < nList.nstr) {
	             done = 1;  retval = nList.selected; 
		   }
	           break;

      case '\010':
      case '\177': FakeButtonPress(&but[BPREV]);    break;

      case '\004': FakeButtonPress(&but[BDELETE]);  break;  /* ^D */
      case '\014': FakeButtonPress(&but[BLOAD]);    break;  /* ^L */
      case '\023': FakeButtonPress(&but[BSAVE]);    break;  /* ^S */
      case '\007': FakeButtonPress(&but[BGRAB]);    break;  /* ^G */

      case 'q':    FakeButtonPress(&but[BQUIT]);    break;
	
      case '?':    if (!useroot) CtrlBox(!ctrlUp);  break;

      case 's':    FakeButtonPress(&but[BSMOOTH]);  break;
      case 'd':    FakeButtonPress(&but[BDITH]);    break;
      case 'r':    FakeButtonPress(&but[BRAW]);     break;
	    
      case 'a':    FakeButtonPress(&but[BASPECT]);  break;
      case 'A':    FakeButtonPress(&but[BACROP]);   break;

      case 'T':    FakeButtonPress(&but[BROTL]);    break;
      case 't':    FakeButtonPress(&but[BROTR]);    break;
      case 'h':    FakeButtonPress(&but[BFLIPH]);   break;
      case 'v':    FakeButtonPress(&but[BFLIPV]);   break;
      case '4':    FakeButtonPress(&but[B4BY3]);    break;
      case 'c':    FakeButtonPress(&but[BCROP]);    break;
      case 'u':    FakeButtonPress(&but[BUNCROP]);  break;
      case 'n':    FakeButtonPress(&but[BNORM]);    break;
      case 'm':    FakeButtonPress(&but[BMAX]);     break;
      case 'M':    FakeButtonPress(&but[BMAXPECT]); break;
      case ',':    FakeButtonPress(&but[BDN10]);    break;
      case '.':    FakeButtonPress(&but[BUP10]);    break;
      case '<':    FakeButtonPress(&but[BDN2]);     break;
      case '>':    FakeButtonPress(&but[BUP2]);     break;

      case 'i':    FakeButtonPress(&but[BINFO]);    break;
      case 'e':    FakeButtonPress(&but[BGAMMA]);   break;

      case 'R':    FakeButtonPress(&gbut[G_BRESET]);   break;
      case 'p':    FakeButtonPress(&gbut[G_BAPPLY]);   break;
      case 'H':    FakeButtonPress(&gbut[G_BHISTEQ]);  break;
      case 'N':    FakeButtonPress(&gbut[G_BMAXCONT]); break;

      default:     break;
      }
    }
  }
    break;
	


  case ConfigureNotify: {
    XConfigureEvent *conf_event = (XConfigureEvent *) event;

    if (conf_event->window == ctrlW ||
	conf_event->window == gamW  ||
	conf_event->window == infoW ||
	conf_event->window == mainW ||
	conf_event->window == dirW) {
      XSizeHints hints;
      if (DEBUG) fprintf(stderr,"got configure event.  %d,%d %dx%d\n",
	      conf_event->x, conf_event->y, conf_event->width,
	      conf_event->height);

      /* if there's a virtual window manager running (e.g. tvtwm), 
	 we're going to get 'conf_event' values in terms of the 
	 'real' root window (the one that is the size of the screen).
	 We'll want to translate them into values that are in terms of
	 the 'virtual' root window (the 'big' one) */

      if (vrootW != rootW) { /* virtual window manager running */
	int x1,y1;
	Window child;
	XTranslateCoordinates(theDisp, rootW, vrootW, 
			      conf_event->x, conf_event->y, 
			      &x1, &y1, &child);
	if (DEBUG) fprintf(stderr,"  conf translate:  -> %d,%d\n", x1,y1);
	conf_event->x = x1;  conf_event->y = y1;
      }

#ifndef VMS
      /* read hints for this window and adjust any position hints */
      if (XGetNormalHints(theDisp, conf_event->window, &hints)) {
	if (DEBUG) fprintf(stderr,"  got hints (0x%x  %d,%d)\n",
		hints.flags, hints.x, hints.y);
	hints.x = conf_event->x;
	hints.y = conf_event->y;
	XSetNormalHints(theDisp, conf_event->window, &hints);
	if (DEBUG) fprintf(stderr,"  set hints (0x%x  %d,%d)\n",
		hints.flags, hints.x, hints.y);
      }
#endif
    }


    if (conf_event->window == mainW) {
      if (!rotatesLeft) {
	if (DEBUG) fprintf(stderr,"CONFIG: (%d,%d %dx%d) ", 
			   conf_event->x, conf_event->y,
			   conf_event->width, conf_event->height);

	if (CheckForConfig()) {
	  if (DEBUG) fprintf(stderr,"more configs pending.  ignored\n");
	}
	else {
	  XEvent xev;
	  if (DEBUG) fprintf(stderr,"No configs pend.");
	  
	  if (conf_event->width == eWIDE && conf_event->height == eHIGH) {
	    if (DEBUG) fprintf(stderr,"No redraw\n");
	  }
	  else {
	    if (DEBUG) fprintf(stderr,"Do full redraw\n");
	    Resize(conf_event->width, conf_event->height);
	    
	    /* eat any pending expose events and do a full redraw */
	    while (XCheckTypedWindowEvent(theDisp, mainW, Expose, &xev)) {
	      XExposeEvent *exp = (XExposeEvent *) &xev;
	      if (DEBUG) 
		fprintf(stderr,"  ate expose (%s) (count=%d) %d,%d %dx%d\n",
			exp->send_event ? "program" : "user", exp->count,
			exp->x, exp->y, exp->width, exp->height);
	    }

	    DrawWindow(0,0,conf_event->width, conf_event->height);
            canstartwait=1;
	    XSync(theDisp, False);
	    SetCursors(-1);
	  }
	}
      }

      if (rotatesLeft>0) rotatesLeft--;
      if (!rotatesLeft) SetCursors(-1);
    }

  }
    break;
	

	
  case CirculateNotify:
  case DestroyNotify:
  case GravityNotify:       break;

  case MapNotify: {
    XMapEvent *map_event = (XMapEvent *) event;

    if (map_event->window == mainW ||
	(map_event->window == ctrlW && dispMB.selected != 0)) {
      if (DEBUG) fprintf(stderr,"map event received on mainW/ctrlW\n");

      if (autoclose) {
	if (wasInfoUp) { InfoBox(wasInfoUp);     wasInfoUp=0; }
	if (wasCtrlUp) { CtrlBox(wasCtrlUp);     wasCtrlUp=0; }
	if (wasDirUp)  { DirBox(wasDirUp);       wasDirUp=0; }
	if (wasGamUp)  { GamBox(wasGamUp);       wasGamUp=0; }
	if (wasPsUp)   { PSDialog(wasPsUp);      wasPsUp=0; }
#ifdef HAVE_JPEG
	if (wasJpegUp) { JPEGDialog(wasJpegUp);  wasJpegUp=0; }
#endif

#ifdef HAVE_TIFF
	if (wasTiffUp) { TIFFDialog(wasTiffUp);  wasTiffUp=0; }
#endif
      }
    }
  }
    break;


  case UnmapNotify: {
    XUnmapEvent *unmap_event = (XUnmapEvent *) event;

    if (unmap_event->window == mainW ||
	(unmap_event->window == ctrlW && dispMB.selected != 0)) {
      if (DEBUG) fprintf(stderr,"unmap event received on mainW/ctrlW\n");
      if (DEBUG) fprintf(stderr,"dispMB.selected = %d\n", dispMB.selected);

      /* don't do it if we've just switched to a root mode */
      if ((unmap_event->window == mainW && dispMB.selected == 0) ||
	  (unmap_event->window == ctrlW && dispMB.selected != 0)) {  

	if (autoclose) {
	  if (unmap_event->window == mainW) {
	    if (ctrlUp) { wasCtrlUp = ctrlUp;  CtrlBox(0); }
	  }

	  if (infoUp) { wasInfoUp = infoUp;  InfoBox(0); }
	  if (dirUp)  { wasDirUp  = dirUp;   DirBox(0); }
	  if (gamUp)  { wasGamUp  = gamUp;   GamBox(0); }
	  if (psUp)   { wasPsUp   = psUp;    PSDialog(0); }
#ifdef HAVE_JPEG
	  if (jpegUp) { wasJpegUp = jpegUp;  JPEGDialog(0); }
#endif

#ifdef HAVE_TIFF
	  if (tiffUp) { wasTiffUp = tiffUp;  TIFFDialog(0); }
#endif
	}
      }
    }
  }
    break;

  case ReparentNotify: {
    XReparentEvent *reparent_event = (XReparentEvent *) event;

    if (DEBUG) {
      fprintf(stderr,"Reparent: mainW=%x ->win=%x ->ev=%x  ->parent=%x  ", 
	      mainW, reparent_event->window, reparent_event->event, 
	      reparent_event->parent);
      fprintf(stderr,"%d,%d\n", reparent_event->x, reparent_event->y);
    }

    if (reparent_event->window == mainW) {
      ch_offx = reparent_event->x;  /* offset required for ChangeAttr call */
      ch_offy = reparent_event->y;

      p_offx = p_offy = 0;          /* topleft correction for WMs titlebar */

      if (ch_offx == 0 && ch_offy == 0) {  
	/* looks like the user is running MWM or OLWM */

	XWindowAttributes xwa;

	/* first query the attributes of mainW.  x,y should be the offset
	   from the parent's topleft corner to the windows topleft.
	   OLWM puts the info here */

	XSync(theDisp, False);
	XGetWindowAttributes(theDisp, mainW, &xwa);
	
	if (DEBUG) 
	  fprintf(stderr,"XGetAttr: mainW %d,%d %dx%d\n", xwa.x, xwa.y,
		  xwa.width, xwa.height);

	if (xwa.x == 0 && xwa.y == 0) {
	  /* MWM, at least mine, puts 0's in those fields.  To get the
	     info, we'll have to query the parent window */

	  XSync(theDisp, False);
	  XGetWindowAttributes(theDisp, reparent_event->parent, &xwa);
	
	  if (DEBUG) 
	    fprintf(stderr,"XGetAttr: parent %d,%d %dx%d\n", xwa.x, xwa.y,
		    xwa.width, xwa.height);
	}
	else {
	  /* KLUDGE:  if we're running olwm, xwa.{x,y} won't be 0,0.
	     in olwm, the window drifts down and right each time
	     SetWindowPos() is called.  God knows why.  Anyway, I'm
	     inserting a kludge here to increase 'ch_offx' and 'ch_offy'
	     by bwidth so that this drifting won't happen.  No doubt this'll
	     screw up behavior on some *other* window manager, but it should
	     work with TWM, OLWM, and MWM (the big three) */
	  ch_offx += bwidth;
	  ch_offy += bwidth;
	}

	p_offx = xwa.x;
	p_offy = xwa.y;
      }
    }
  }
    break;
    

  case EnterNotify:
  case LeaveNotify: {
    XCrossingEvent *cross_event = (XCrossingEvent *) event;
    if (cross_event->window == mainW || 0
	/* (cross_event->window == gamW && cmapInGam) */ ) {

      if (cross_event->type == EnterNotify && cross_event->window == mainW) {
	if (cross_event->state & ControlMask) {  /* ctrl pressed */
	  if (!showzoomcursor) {
	    showzoomcursor = 1;
	    SetCursors(-1);
	  }
	}
	else {
	  if (showzoomcursor) {
	    showzoomcursor = 0;
	    SetCursors(-1);
	  }
	}
      }


      if (cross_event->type == EnterNotify && LocalCmap && !ninstall) 
	XInstallColormap(theDisp,LocalCmap);

      if (cross_event->type == LeaveNotify && LocalCmap && !ninstall) 
	XUninstallColormap(theDisp,LocalCmap);
    }
  }
    break;
	
	
  default: break;		/* ignore unexpected events */
  }  /* switch */

  *donep = done;
  return(retval);
}



/***********************************/
void DrawWindow(x,y,w,h)
int x,y,w,h;
{
  if (x+w < eWIDE) w++;  /* add one for broken servers (?) */
  if (y+h < eHIGH) h++;

  if (theImage)
    XPutImage(theDisp,mainW,theGC,theImage,x,y,x,y,w,h);
  else 
    if (DEBUG) fprintf(stderr,"Tried to DrawWindow when theImage was NIL\n");
}


/***********************************/
void WResize(w,h)
int w,h;
{
  XWindowAttributes xwa;

  RANGE(w,1,maxWIDE);  RANGE(h,1,maxHIGH);

  if (useroot) {
    Resize(w,h);
    MakeRootPic();
    SetCursors(-1);
    return;
  }

  /* determine if new size goes off edge of screen.  if so move window so it
     doesn't go off screen */

  GetWindowPos(&xwa);
  if (xwa.x + w > vrWIDE) xwa.x = vrWIDE - w;
  if (xwa.y + h > vrHIGH) xwa.y = vrHIGH - h;

  if (DEBUG) fprintf(stderr,"%s: resizing window to %d,%d at %d,%d\n",
		     cmd,w,h,xwa.x,xwa.y);

  /* resize the window */
  xwa.width = w;  xwa.height = h;

  SetWindowPos(&xwa);
}




/***********************************/
static void WMaximize()
{
  if (useroot) WResize(dispWIDE, dispHIGH);
  else {
    XWindowAttributes xwa;
    bzero((char *) &xwa, sizeof(XWindowAttributes));
    xwa.x = xwa.y = 0;
    xwa.width  = dispWIDE;  
    xwa.height = dispHIGH;
    SetWindowPos(&xwa);
  }
}




/***********************************/
void WRotate()
{
  /* rotate the window and redraw the contents  */

  if (but[BCROP].active) BTSetActive(&but[BCROP],0);
  if (useroot) {
    MakeRootPic();
    SetCursors(-1);
    return;
  }

  if (eWIDE == eHIGH) {     /* no configure events will be gen'd */
    Resize(eWIDE, eHIGH);   /* to regen Ximage */
    DrawWindow(0, 0, eWIDE, eHIGH);
    SetCursors(-1);
  }
  else {
    rotatesLeft++;
    XClearWindow(theDisp, mainW);  /* get rid of old bits */
    GenExpose(mainW, 0, 0, eWIDE, eHIGH);
    WResize(eWIDE, eHIGH);
  }
}


/***********************************/
void WCrop(w,h)
int w,h;
{
  XWindowAttributes xwa;

  if (useroot) {
    MakeRootPic();
    SetCursors(-1);
  }

  else {
    /* we want to move window to old x,y + crx1,cry1 */
    GetWindowPos(&xwa);
  
    if (!origcropvalid) {  /* first crop.  remember win pos */
      origcropvalid = 1;
      origcropx = xwa.x;
      origcropy = xwa.y;
    }

    xwa.x += crx1;  xwa.y += cry1;
    xwa.width = w;  xwa.height = h;
    GenExpose(mainW, 0, 0, eWIDE, eHIGH);
    SetWindowPos(&xwa);
  }
}


/***********************************/
void WUnCrop()
{
  int w,h;
  XWindowAttributes xwa;

  /* a proper epic has been generated.  eWIDE,eHIGH are the new window size */


  if (useroot) {
    MakeRootPic();
    SetCursors(-1);
  }

  else {   /* !useroot */
    GetWindowPos(&xwa);

    w = eWIDE;  h = eHIGH;

    /* restore to position when originally cropped */
    if (origcropvalid) {  /* *should* always be true... */
      origcropvalid = 0;
      xwa.x = origcropx;
      xwa.y = origcropy;
    }

    if (xwa.x + w > vrWIDE) xwa.x = vrWIDE - w;   /* keep on screen */
    if (xwa.y + h > vrHIGH) xwa.y = vrHIGH - h;

    if (xwa.x<0) xwa.x = 0;
    if (xwa.y<0) xwa.y = 0;
    xwa.width = w;  xwa.height = h;
    
    if (!useroot) {
      SetWindowPos(&xwa);
      GenExpose(mainW, 0, 0, eWIDE, eHIGH);
    }
  }
}



/***********************************/
void GetWindowPos(xwa)
XWindowAttributes *xwa;
{
  Window child;
  
  /* returns the x,y,w,h coords of mainW.  x,y are relative to rootW 
     the border is not included (x,y map to top-left pixel in window) */

  /* Get the window width/height */
  XGetWindowAttributes(theDisp,mainW,xwa);

  /* Get the window origin */
  XTranslateCoordinates(theDisp,mainW,rootW,0,0,&xwa->x,&xwa->y,&child);
}


/***********************************/
void SetWindowPos(xwa)
XWindowAttributes *xwa;
{
  /* sets window x,y,w,h values */
  XWindowChanges    xwc;

  /* Adjust from window origin, to border origin */
  xwc.x = xwa->x - xwa->border_width - ch_offx;
  xwc.y = xwa->y - xwa->border_width - ch_offy;

  if (!xwa->border_width) xwa->border_width = bwidth;
  xwc.border_width = xwa->border_width;

  /* if we're less than max size in one axis, allow window manager doohickeys
     on the screen */
  
  if (xwa->width  < dispWIDE && xwc.x < p_offx) xwc.x = p_offx;
  if (xwa->height < dispHIGH && xwc.y < p_offy) xwc.y = p_offy;

  xwc.width  = xwa->width;
  xwc.height = xwa->height;

  if (DEBUG) {
    fprintf(stderr,"SWP: xwa=%d,%d %dx%d  xwc=%d,%d %dx%d  off=%d,%d bw=%d\n",
	    xwa->x, xwa->y, xwa->width, xwa->height,
	    xwc.x, xwc.y, xwc.width, xwc.height, p_offx, p_offy, 
	    xwa->border_width);
  }

#ifdef DXWM  /* dxwm seems to *only* pay attention to the hints */
  {
    XSizeHints hints;
    if (DEBUG) fprintf(stderr,"SWP: doing the DXWM thing\n");
    /* read hints for this window and adjust any position hints */
    if (XGetNormalHints(theDisp, mainW, &hints)) {
      hints.flags |= USPosition | USSize;
      hints.x = xwc.x;  hints.y = xwc.y;
      hints.width = xwc.width; hints.height = xwc.height;
      XSetNormalHints(theDisp, mainW, &hints);
    }

    xwc.x -= 5;   xwc.y -= 25;    /* EVIL KLUDGE */
  }
#endif

  /* all non-DXWM window managers (?) */
  /* Move/Resize the window. */
  XConfigureWindow(theDisp, mainW, 
		   CWX | CWY | CWWidth | CWHeight /*| CWBorderWidth*/, &xwc);
}


/***********************************/
static void TrackCrop(mx,my)
int mx,my;
{
  Window       rW,cW;
  int          rx,ry,ox,oy,x,y,active;
  unsigned int mask;

  if (but[BCROP].active) {             /* turn off old cropping rectangle */
    XSetFunction(theDisp,theGC,GXinvert);
    Rect(crx1,cry1,crx2,cry2);
    XSetFunction(theDisp,theGC,GXcopy);
  }
  active = 0;
  SetCropString(active);

  crx1 = ox = mx;  cry1 = oy = my;         /* nail down one corner */

  while (1) {
    if (XQueryPointer(theDisp,mainW,&rW,&cW,&rx,&ry,&x,&y,&mask)) {
      if (!(mask & Button2Mask)) break;    /* button released */

      if (x!=ox || y!=oy) {                /* moved.  erase and redraw */
	crx2 = x;  cry2 = y;
	XSetFunction(theDisp,theGC,GXinvert);
	Rect(crx1,cry1,ox,oy);
	active = Rect(crx1,cry1,crx2,cry2);
	XSetFunction(theDisp,theGC,GXcopy);
	XFlush(theDisp);
	ox=crx2;  oy=cry2;
	if (infoUp) SetCropString(active);
      }
    }
  }

  RANGE(crx1,0,eWIDE);  RANGE(cry1,0,eHIGH);
  RANGE(crx2,0,eWIDE);  RANGE(cry2,0,eHIGH);
  BTSetActive(&but[BCROP],active);
  SetCropString(active);
}


/***********************************/
static void CropKey(dx,dy,grow)
int dx,dy,grow;
{
  int x1,x2,y1,y2,active;

  if (!but[BCROP].active) return;

  /* x1,y1 = top-left,  x2,y2 = bot-right */
  if (crx1<crx2) { x1=crx1;  x2=crx2; }   else { x1=crx2;  x2=crx1; }
  if (cry1<cry2) { y1=cry1;  y2=cry2; }   else { y1=cry2;  y2=cry1; }

  if (!grow) {    /* move the rectangle */
    x1 += dx;  x2 += dx;  y1 += dy;  y2 += dy;
    if (x1<0 || x2>=eWIDE) { x1 -= dx;  x2 -= dx; }
    if (y1<0 || y2>=eHIGH) { y1 -= dy;  y2 -= dy; }
  }
  else {          /* grow the rectangle, pref. keeping top-left anchored */
    x2 += dx;  y2 += dy;
    if (x2>=eWIDE) { 
      x1 -= dx;  x2 -= dx;
      if (x1<0) x1=0;
    }

    if (y2>=eHIGH) { 
      y1 -= dy;  y2 -= dy;
      if (y1<0) y1=0;
    }
  }
    
  XSetFunction(theDisp,theGC,GXinvert);
  Rect(crx1,cry1,crx2,cry2);
  crx1 = x1;  cry1 = y1;  crx2 = x2;  cry2 = y2;
  active = Rect(crx1,cry1,crx2,cry2);
  XSetFunction(theDisp,theGC,GXcopy);

  BTSetActive(&but[BCROP], active);
  SetCropString(active);
}

  
/***********************************/
static int Rect(x,y,x1,y1)
int x,y,x1,y1;
{
  int w,h;

  /* returns 0 if it didn't draw anything (rect is too small), 1 if it did */
  w = abs(x-x1);  h = abs(y-y1);
  if (x>x1) x = x1;
  if (y>y1) y = y1;

  /* keep rectangle inside window */  
  if (x<0) { w+=x; x=0; }
  if (y<0) { h+=y; y=0; }
  if (x+w>=eWIDE) w=eWIDE-x-1;  
  if (y+h>=eHIGH) h=eHIGH-y-1;

  if (w<4 || h<4) return 0;   /* too small */

  XSetPlaneMask(theDisp, theGC, AllPlanes);
  XDrawRectangle(theDisp, mainW, theGC, x, y, w, h);
  XSetPlaneMask(theDisp, theGC, 1L);
  XDrawRectangle(theDisp, mainW, theGC, x+1, y+1, w-2, h-2);
  XSetPlaneMask(theDisp, theGC, AllPlanes);
  return 1;
}


/***********************************/
void InvCropRect()
{
  XSetFunction(theDisp,theGC,GXinvert);
  Rect(crx1,cry1,crx2,cry2);
  XSetFunction(theDisp,theGC,GXcopy);
}


/***********************************/
static void TrackPicValues(mx,my)
int mx,my;
{
  Window       rW,cW;
  int          rx,ry,ox,oy,x,y;
  unsigned int mask;
  int          ty, w, ecol;
  char         foo[64];
  unsigned long wh, bl;
  char         *str  = "8888,8888 = 123,123,123  (123,123,123 HSV)";

  wh = infobg;  bl = infofg;

  /* do a colormap search for black and white if LocalCmap 
     and use those colors instead of infobg and infofg */
  if (LocalCmap) {
    XColor ctab[256];
    int  i;
    long cval;

    for (i=0; i<nfcols; i++) ctab[i].pixel = freecols[i];
    XQueryColors(theDisp,LocalCmap,ctab,nfcols);
    
    /* find 'blackest' pixel */
    cval = 0x10000 * 3;
    for (i=0; i<nfcols; i++)
      if (ctab[i].red + ctab[i].green + ctab[i].blue < cval) {
	cval = ctab[i].red + ctab[i].green + ctab[i].blue;
	bl = ctab[i].pixel;
      }

    /* find 'whitest' pixel */
    cval = -1;
    for (i=0; i<nfcols; i++)
      if ((long)ctab[i].red + (long)ctab[i].green + (long)ctab[i].blue >cval) {
	cval = ctab[i].red + ctab[i].green + ctab[i].blue;
	wh = ctab[i].pixel;
      }
  }

  XSetFont(theDisp, theGC, monofont);
  w = XTextWidth(monofinfo, str, strlen(str));

  if (my > eHIGH/2) ty = 0;
               else ty = eHIGH-(monofinfo->ascent + mfinfo->descent)-4;

  ox = oy = -1;  /* kludge to force redraw first time through */

  XSetForeground(theDisp, theGC, bl);
  XFillRectangle(theDisp, mainW, theGC, 0, ty, w + 8, 
		 (monofinfo->ascent+monofinfo->descent) + 4);
  XSetForeground(theDisp, theGC, wh);
  XSetBackground(theDisp, theGC, bl);
  foo[0] = '\0';

  x = mx;  y = my;
  RANGE(x,0,eWIDE-1);   RANGE(y,0,eHIGH-1);
  rx = cXOFF + (x * cWIDE) / eWIDE;
  ry = cYOFF + (y * cHIGH) / eHIGH;
  ecol = pic[ry * pWIDE + rx];

  while (1) {
    int px, py, pix;

    if (XQueryPointer(theDisp,mainW,&rW,&cW,&rx,&ry,&x,&y,&mask)) {
      if (!(mask & Button1Mask)) break;    /* button released */

      RANGE(x,0,eWIDE-1);  
      RANGE(y,0,eHIGH-1);

      px = cXOFF + (x * cWIDE) / eWIDE;
      py = cYOFF + (y * cHIGH) / eHIGH;

      if (px!=ox || py!=oy) {                /* moved.  erase and redraw */
	double h1, s1, v1;

	ecol = pix = pic[py * pWIDE + px];

	rgb2hsv(rcmap[pix],gcmap[pix],bcmap[pix],&h1,&s1,&v1);
	if (h1<0.0) h1 = 0.0;   /* map 'NOHUE' to 0.0 */

	sprintf(foo,"%4d,%4d = %3d,%3d,%3d  (%3d %3d %3d HSV)",
		px, py, rcmap[pix],gcmap[pix],bcmap[pix], 
		(int) h1, (int) (s1 * 100), (int) (v1 * 100));
	
	XDrawImageString(theDisp,mainW,theGC, 4, ty + 2 + monofinfo->ascent, 
		    foo, strlen(foo));
	ox = px;  oy = py;
      }
    }
  }

  if (foo[0]) {
    strcat(foo, "\n");
    XStoreBytes(theDisp, foo, strlen(foo));
  }

  XSetFont(theDisp, theGC, mfont);
  DrawWindow(0,ty,eWIDE,(monofinfo->ascent+monofinfo->descent)+4);
  if (ecol != editColor) ChangeEC(ecol);
}


/***********************************/
static Bool IsConfig(dpy, ev, arg)
Display *dpy;
XEvent  *ev;
char    *arg;
{
  XConfigureEvent *cev;

  if (ev->type == ConfigureNotify) {
    cev = (XConfigureEvent *) ev;
    if (cev->window == mainW && (cev->width != eWIDE || cev->height != eHIGH))
      *arg = 1;
  }
  return False;
}

/***********************************/
static int CheckForConfig()
{
  XEvent ev;
  char   foo;

  /* returns true if there's a config event in which mainW changes size
     in the event queue */
  
  XSync(theDisp, False);
  foo = 0;
  XCheckIfEvent(theDisp, &ev, IsConfig, &foo);
  return foo;
}


/************************************************************************/
void SetEpicMode()
{
  if (epicmode == EM_RAW) {
    BTSetActive(&but[BRAW],   0);
    BTSetActive(&but[BDITH],  (ncols>0) );  /* only enable dith if ncols>0 */
    BTSetActive(&but[BSMOOTH],1);
  }

  else if (epicmode == EM_DITH) {
    BTSetActive(&but[BRAW],   1);
    BTSetActive(&but[BDITH],  0);
    BTSetActive(&but[BSMOOTH],1);
  }

  else if (epicmode == EM_SMOOTH) {
    BTSetActive(&but[BRAW],   1);
    BTSetActive(&but[BDITH],  0);
    BTSetActive(&but[BSMOOTH],1);  /* color editing may require a re-smooth */
  }
}


/************************************************************************/
int xvErrorHandler(disp, err)
Display *disp;
XErrorEvent *err;
{
  char buf[128];

  XUngrabPointer(theDisp, CurrentTime);   /* in case error occurred in Grab */

  xerrcode = err->error_code;

  /* BadAlloc errors (on a XCreatePixmap() call)
     and BadAccess errors on XFreeColors are 'ignoreable' errors */

  if (xerrcode == BadAlloc || 
      (xerrcode == BadAccess && err->request_code==88)) return 0;

  else {
    /* all other errors are 'fatal' */
    XGetErrorText(disp, xerrcode, buf, 128);
    fprintf(stderr,"X Error: %s\n",buf);
    fprintf(stderr,"  Major Opcode:  %d\n",err->request_code);
    
    exit(-1);
  }

  return 0;
}


static void onInterrupt()
{
  /* make the interrupt signal look like a '\n' keypress in ctrlW */
  XKeyEvent ev;

  ev.type = KeyPress;
  ev.send_event = True;
  ev.display = theDisp;
  ev.window = ctrlW;
  ev.root = rootW;
  ev.subwindow = (Window) NULL;
  ev.time = CurrentTime;
  ev.x = ev.y = ev.x_root = ev.y_root = 0;
  ev.state = 0;
  ev.keycode = XKeysymToKeycode(theDisp, XK_Return);
  ev.same_screen = True;
  XSendEvent(theDisp, ctrlW, False, NoEventMask, (XEvent *) &ev);
  XFlush(theDisp);
}
