/*
 * xvmisc.c - random 'handy' routines used in XV
 *
 *  Author:    John Bradley, University of Pennsylvania
 *                (bradley@cis.upenn.edu)
 *
 *  Contains:
 *     Window CreateWindow(name, geom, w, h, fg, bg)
 *     void   CenterString(win, str, x, y)
 *     void   ULineString(win, str, x, y)
 *     int    StringWidth(str)
 *     void   FakeButtonPress(bptr)
 *     void   GenExpose(win, x, y, w, h);
 *     void   xvDestroyImage(XImage *);
 *     void   DimRect(win, x, y, w, h, bg);
 *     void   SetCropString(active)
 *     void   Warning()
 *     void   FatalError(str)
 *     void   Quit(int)
 *     void   LoadFishCursors()
 *     void   WaitCursor()
 *     void   SetCursors(int)
 *     void   Timer(milliseconds)
 */

/*
 * 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
#include "xv.h"
#include "bitmaps.h"

static void set_cursors();

static Atom atom_DELWIN = 0;
static Atom atom_PROTOCOLS = 0;

/***************************************************/
void StoreDeleteWindowProp (win)
     Window win;
{
  if (! atom_DELWIN)
    atom_DELWIN = XInternAtom (theDisp, "WM_DELETE_WINDOW", FALSE);

  /* the following fakes 'XSetWMProtocols(theDisp, win, &atom_DELWIN, 1);' */

  if (! atom_PROTOCOLS) 
    atom_PROTOCOLS = XInternAtom (theDisp, "WM_PROTOCOLS", False);

  if (atom_PROTOCOLS == None) return;

  XChangeProperty(theDisp, win, atom_PROTOCOLS, XA_ATOM, 32, 
		  PropModeReplace, (unsigned char *) &atom_DELWIN, 1);
}


/***************************************************/
Window CreateWindow(name,geom,w,h,fg,bg)
char         *name, *geom;
unsigned int  w,h;
unsigned long fg, bg;
{
  Window               win;
  XSetWindowAttributes xswa;
  unsigned long        xswamask;
  XWMHints             xwmh;
  XSizeHints           hints;
  int                  i,x,y;

  /* note that only x,y are gotten from geom spec.  w,h are fixed */
  x = y = 1;
  i = XParseGeometry(geom,&x,&y,&w,&h);

  if ((i&XValue || i&YValue)) hints.flags = USPosition;  
                         else hints.flags = PPosition;

  hints.flags |= USSize;

  if (i&XValue && i&XNegative)
    x = dispWIDE - w - abs(x);
  if (i&YValue && i&YNegative) 
    y = dispHIGH - h - abs(y);

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

  hints.x = x;                  hints.y = y;
  hints.width = w;              hints.height = h;
  hints.min_width  = w;         hints.min_height = h;
  hints.max_width  = w;         hints.max_height = h;
  hints.flags |= PMaxSize | PMinSize;

  xswa.background_pixel = bg;
  xswa.border_pixel     = fg;
  xswa.colormap         = theCmap;
  xswamask = CWBackPixel | CWBorderPixel | CWColormap;

  win = XCreateWindow(theDisp, rootW, x, y, w, h, 
			bwidth, dispDEEP, InputOutput,
			theVisual, xswamask, &xswa);
  if (!win) return(win);   /* leave immediately if couldn't create */

  XSetStandardProperties(theDisp, win, name, name, None, NULL, 0, &hints);

  xwmh.input = True;
  xwmh.flags = InputHint;
  if (iconPix) { xwmh.icon_pixmap = iconPix;  xwmh.flags |= IconPixmapHint; }
  XSetWMHints(theDisp, win, &xwmh);

  return(win);
}
  


/**************************************************/
void CenterString(win,str,x,y)
Window win;
char *str;
int x,y;
{
  XDrawString(theDisp, win, theGC, CENTERX(mfinfo, x, str),
	    CENTERY(mfinfo, y), str, strlen(str));
}

  
/**************************************************/
void ULineString(win,str,x,y)
Window win;
char *str;
int x,y;
{
  XDrawString(theDisp, win, theGC, x, y, str, strlen(str));
  XDrawLine(theDisp, win, theGC, x, y+DESCENT-1, 
	    x+StringWidth(str), y+DESCENT-1);
}

  
/**************************************************/
int StringWidth(str)
char *str;
{
  return(XTextWidth(mfinfo, str, strlen(str)));
}

  
/***********************************/
void FakeButtonPress(bp)
BUTT *bp;
{
  /* called when a button keyboard equivalent has been pressed.
     'fakes' a ButtonPress event in the button, which A) makes the button
     blink, and B) falls through to ButtonPress command dispatch code */

  Window       rW, cW;
  int          x, y, rx, ry;
  unsigned int mask;
  XButtonEvent ev;

  ev.type = ButtonPress;
  ev.send_event = True;
  ev.display = theDisp;
  ev.window = bp->win;
  ev.root = rootW;
  ev.subwindow = (Window) NULL;
  ev.x = bp->x;
  ev.y = bp->y;
  ev.state = 0;
  ev.button = Button1;
  XSendEvent(theDisp, bp->win, False, NoEventMask, (XEvent *) &ev);

  /* if button1 is pressed, loop until RELEASED to avoid probs in BTTrack */
  while (XQueryPointer(theDisp, rootW, &rW,&cW,&rx,&ry,&x,&y,&mask)) {
    if (!(mask & Button1Mask)) break;    /* button released */
  }
}

/***********************************/
void GenExpose(win, x, y, w, h)
     Window win;
     int x,y,w,h;
{
  /* generates an expose event on 'win' of the specified rectangle.  Unlike
     XClearArea(), it doesn't clear the rectangular region */

  XExposeEvent ev;

  ev.type = Expose;
  ev.send_event = True;
  ev.display = theDisp;
  ev.window = win;
  ev.x = x;  ev.y = y;  ev.width = w;  ev.height = h;
  ev.count = 0;
  
  XSendEvent(theDisp, win, False, NoEventMask, (XEvent *) &ev);
}    


/***********************************/
void xvDestroyImage(image)
     XImage *image;
{
  /* called in place of XDestroyImage().  Explicitly destroys *BOTH* the
     data and the structure.  XDestroyImage() doesn't seem to do this on all
     systems.  Also, can be called with a NULL image pointer */

  if (image) {
    /* free data by hand, since XDestroyImage is vague about it */
    if (image->data) free(image->data);
    image->data = NULL;
    XDestroyImage(image);
  }
}


/***********************************/
void DimRect(win, x, y, w, h, bg)
Window win;
int x,y,w,h;
u_long bg;
{
  /* stipple a rectangular region by drawing 'bg' where there's 1's 
     in the stipple pattern */

  XSetFillStyle(theDisp, theGC, FillStippled);
  XSetStipple(theDisp, theGC, grayStip);
  XSetForeground(theDisp, theGC, bg);
  XFillRectangle(theDisp,win,theGC,x,y,w,h);
  XSetFillStyle(theDisp, theGC, FillSolid);
}





/**************************************************/
void SetCropString(active)
int active;
{
  /* sets the crop string in the info box to be correct.  should
     be called whenever 'but[BCROP].active', cXOFF,cYOFF,cWIDE,cHIGH 
     are changed */

  /* if 'active' there's a cropping rectangle drawn on the picture.
     the string should reflect that.  */

  if (active) {
    /* figure out current cropping rectangle in 'pic' coordinates */
    int x,y,x1,y1,w,h;
    int cx,cy,dx,dy;
    
    if (crx1<crx2) cx=crx1; else cx=crx2;
    if (cry1<cry2) cy=cry1; else cy=cry2;
    dx = abs(crx1-crx2);  dy = abs(cry1-cry2);

    x = cXOFF + (cx * cWIDE) / eWIDE;
    y = cYOFF + (cy * cHIGH) / eHIGH;
    x1 = cXOFF + ((cx+dx) * cWIDE) / eWIDE;
    y1 = cYOFF + ((cy+dy) * cHIGH) / eHIGH;
    w = (x1 - x) + 1;
    h = (y1 - y) + 1;

    if (w<1) w = 1;
    if (x+w > pWIDE) w = pWIDE - x;
    if (h<1) h = 1;
    if (y+h > pHIGH) h = pHIGH - y;

    SetISTR(ISTR_CROP, "%dx%d rectangle starting at %d,%d", w, h, x, y);
  }

  else {   /* cropping rectangle is turned off */
    if (cpic != pic)
      SetISTR(ISTR_CROP, "%dx%d rectangle starting at %d,%d", 
	      cWIDE, cHIGH, cXOFF, cYOFF);
    else
      SetISTR(ISTR_CROP, "<none>");
  }
}


/***********************************/
void Warning()
{
  char *st;

  if (infoUp || ctrlUp) sleep(5);   /* give 'em time to read message */
  else {
    st = GetISTR(ISTR_INFO);
    fprintf(stderr,"%s: %s\n", cmd, st);
  }
}
    

/***********************************/
void FatalError (identifier)
      char *identifier;
{
  fprintf(stderr, "%s: %s\n",cmd, identifier);
  Quit(-1);
}


/***********************************/
void Quit(i)
int i;
{ 
  /* called when the program exits.  frees everything explictly created
     EXCEPT allocated colors.  This is used when 'useroot' is in operation,
     as we have to keep the alloc'd colors around, but we don't want anything
     else to stay */

  if (!theDisp) exit(i);   /* called before connection opened */

  if (useroot && i==0) {   /* save the root info */
    SaveRootInfo();

    /* kill the various windows, since we're in RetainPermanent mode now */
    if (dirW)  XDestroyWindow(theDisp, dirW);
    if (infoW) XDestroyWindow(theDisp, infoW);
    if (ctrlW) XDestroyWindow(theDisp, ctrlW);
    if (gamW)  XDestroyWindow(theDisp, gamW);

    XFlush(theDisp);
  }

  exit(i);
}


static Cursor flcurs, fl1curs, fmcurs, fr1curs, frcurs;

/***********************************/
void LoadFishCursors()
{
#define fc_w 16
#define fc_h 16 

  Pixmap flpix,flmpix,fmpix,fmmpix,frpix,frmpix;
  Pixmap fl1pix, fl1mpix, fr1pix, fr1mpix;
  XColor fg, bg;

  flcurs = fl1curs = fmcurs = fr1curs = frcurs = (Pixmap) NULL;

  flpix = XCreatePixmapFromBitmapData(theDisp, ctrlW, fc_left_bits,
	     fc_w, fc_h, 1, 0, 1);
  flmpix= XCreatePixmapFromBitmapData(theDisp, ctrlW, fc_leftm_bits,
	     fc_w, fc_h, 1, 0, 1);

  fl1pix = XCreatePixmapFromBitmapData(theDisp, ctrlW, fc_left1_bits,
	     fc_w, fc_h, 1, 0, 1);
  fl1mpix= XCreatePixmapFromBitmapData(theDisp, ctrlW, fc_left1m_bits,
	     fc_w, fc_h, 1, 0, 1);

  fmpix = XCreatePixmapFromBitmapData(theDisp, ctrlW, fc_mid_bits,
	     fc_w, fc_h, 1, 0, 1);
  fmmpix= XCreatePixmapFromBitmapData(theDisp, ctrlW, fc_midm_bits,
	     fc_w, fc_h, 1, 0, 1);

  fr1pix = XCreatePixmapFromBitmapData(theDisp, ctrlW, fc_right1_bits,
	     fc_w, fc_h, 1, 0, 1);
  fr1mpix = XCreatePixmapFromBitmapData(theDisp, ctrlW, fc_right1m_bits,
	     fc_w, fc_h, 1, 0, 1);

  frpix = XCreatePixmapFromBitmapData(theDisp, ctrlW, fc_right_bits,
	     fc_w, fc_h, 1, 0, 1);
  frmpix = XCreatePixmapFromBitmapData(theDisp, ctrlW, fc_rightm_bits,
	     fc_w, fc_h, 1, 0, 1);

  if (!flpix || !flmpix || !fmpix || !fmmpix || !frpix || !frmpix
      || !fl1pix || !fl1mpix || !fr1pix || !fr1mpix) return;

  fg.red = fg.green = fg.blue = 0;
  bg.red = bg.green = bg.blue = 0xffff;

  flcurs = XCreatePixmapCursor(theDisp, flpix, flmpix, &fg, &bg, 8,8);
  fl1curs= XCreatePixmapCursor(theDisp, fl1pix,fl1mpix,&fg, &bg, 8,8);
  fmcurs = XCreatePixmapCursor(theDisp, fmpix, fmmpix, &fg, &bg, 8,8);
  fr1curs= XCreatePixmapCursor(theDisp, fr1pix,fr1mpix,&fg, &bg, 8,8);
  frcurs = XCreatePixmapCursor(theDisp, frpix, frmpix, &fg, &bg, 8,8);

  if (!flcurs || !fmcurs || !frcurs || !fl1curs || !fr1curs) 
    { flcurs = fmcurs = frcurs = (Cursor) NULL; }
}

static int fishno=0;


/***********************************/
void WaitCursor()
{
  SetCursors(fishno);
  fishno = (fishno+1) % 8;
}


/***********************************/
void SetCursors(n)
int n;
{
  Cursor c;

  c = cross;
  /* if n < 0   sets normal cursor in all windows
     n = 0..6   cycles through fish cursors */

  if (n<0) {
    if (showzoomcursor) set_cursors(zoom, arrow);
                   else set_cursors(cross, arrow);
    fishno = 0;
  }

  else if (flcurs) {    /* was able to load the cursors */
    switch (n%8) {
    case 0: c = flcurs;   break;
    case 1: c = fl1curs;  break;
    case 2: c = fmcurs;   break;
    case 3: c = fr1curs;  break;
    case 4: c = frcurs;   break;
    case 5: c = fr1curs;  break;
    case 6: c = fmcurs;   break;
    case 7: c = fl1curs;  break;
    }

    set_cursors(c,c);
  }

  XFlush(theDisp);
}
  

static void set_cursors(mainc, otherc)
     Cursor mainc, otherc;
{    
  if (!useroot && mainW) XDefineCursor(theDisp, mainW, mainc);
  if (infoW) XDefineCursor(theDisp, infoW, otherc);
  if (ctrlW) XDefineCursor(theDisp, ctrlW, otherc);
  if (dirW)  XDefineCursor(theDisp, dirW, otherc);
  if (gamW)  XDefineCursor(theDisp, gamW, otherc);
  if (psW)   XDefineCursor(theDisp, psW, otherc);
  
#ifdef HAVE_JPEG
  if (jpegW) XDefineCursor(theDisp, jpegW, otherc);
#endif
  
#ifdef HAVE_TIFF
  if (tiffW) XDefineCursor(theDisp, tiffW, otherc);
#endif
}


/***************************************************/

static int timerdone;

/*******/
static void onalarm()
/*******/
{
  timerdone=1;
}


/*******/
void Timer(msec)   /* waits for 'n' milliseconds */
 int  msec;
/*******/
{
#ifdef VMS
  float ftime;
  ftime = msec / 1000.0;
  lib$wait(&ftime);

#else

  long usec;
#if !defined(USLEEP) && !defined(sco) && !defined(NOTIMER)
  struct itimerval it;
#endif

  if (msec <= 0) return;
  usec = (long) msec * 1000;

#if defined(sco)
  {
    int dummy;
    poll(&dummy, 0, msec);
    return;
  }
#endif

#ifdef USLEEP
  usleep(usec);  return;
#endif

#ifdef NOTIMER
  return;
#endif


#if !defined(USLEEP) && !defined(sco) && !defined(NOTIMER)
  memset(&it, 0, sizeof(it));
  if (usec>=1000000L) {  /* more than 1 second */
    it.it_value.tv_sec = usec / 1000000L;
    usec %= 1000000L;
  }

  it.it_value.tv_usec = usec;
  timerdone=0;
  signal(SIGALRM,onalarm);
  setitimer(ITIMER_REAL, &it, (struct itimerval *)0);
  while (1) {
    HOLD_SIG;                    /* note:  have to block, so that ALRM */
    if (timerdone) break;        /* doesn't occur between 'if (timerdone)' */
    else PAUSE_SIG;              /* and calling PAUSE_SIG */
  }

  RELEASE_SIG;                   /* turn ALRM blocking off */
  signal(SIGALRM,SIG_DFL);
#endif
#endif  /* !VMS */
}


