/* 
 * xvpopup.c - popup "Are you sure?  Yes/No/Maybe" sort of dialog box
 *
 * callable functions:
 *
 *   CenterMapWindow(win,x,y)  -  maps and centers a window around the mouse
 *   PopUp(str,...)        -  maps, sets up popW
 *   OpenAlert(str)        -  maps a button-less window
 *   CloseAlert()          -  closes a button-less window
 *   PUCheckEvent(event)   -  called by event handler
 *   TextRect()            -  draws semi-complex strings in a rectangle
 */

/*
 * 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
 */

#include "xv.h"
#include "bitmaps.h"

#define PUWIDE 400
#define PUHIGH 170
#define BUTTH 24


#ifdef __STDC__
static void createPUD(void);
static void drawPUD(int, int, int, int);
static void clickPUD(int, int);
#else
static void createPUD(), drawPUD(), clickPUD();
#endif


/* local variables */
Window popW;
int    nbts, selected, popUp=0, firsttime=1;
BUTT  *bts;
char  *text;
char   accel[8];


/***************************************************/
void CenterMapWindow(win, dx, dy, w, h)
     Window win;
     int    dx, dy, w, h;
{
  XSizeHints hints;
  Window       rW,cW;
  int          rx,ry,x,y,wx,wy;
  unsigned int mask;


  if (!XQueryPointer(theDisp,rootW,&rW,&cW,&rx,&ry,&x,&y,&mask)) {
    /* couldn't query mouse.  just center on screen */
    wx = (dispWIDE-w)/2;   wy = (dispHIGH-h)/2;
  }
  else {
    wx = x - dx;
    wy = y - dy;
    if (wx<0) wx = 0;
    if (wy<0) wy = 0;
    if (wx + w > dispWIDE) wx = dispWIDE - w;
    if (wy + h > dispHIGH) wy = dispHIGH - h;
  }

  wx -= (p_offx + ch_offx);
  wy -= (p_offy + ch_offy);

  if (!XGetNormalHints(theDisp, win, &hints)) hints.flags = 0;
  hints.width  = hints.min_width  = hints.max_width  = w;
  hints.height = hints.min_height = hints.max_height = h;
  hints.x = wx;  hints.y = wy;
  hints.flags  |= (USSize | PMinSize | PMaxSize | USPosition);
  XSetNormalHints(theDisp, win, &hints);

  XMoveWindow(theDisp, win, wx, wy);
  XMapRaised(theDisp, win);
}


/***************************************************/
int PopUp(txt, labels, n)
     char *txt, *labels[];
     int   n;
{
  int    i;
  XEvent event;


  if (firsttime) createPUD();

  XStoreName(theDisp, popW, "xv confirm");
  XSetIconName(theDisp, popW, "xv confirm");

  bts = (BUTT *) malloc(n * sizeof(BUTT));
  if (!bts) FatalError("unable to malloc buttons in popup\n");
  nbts = n;
  selected = 0;
  text = txt;

  for (i=0; i<n; i++) {
    BTCreate(&bts[i], popW, PUWIDE - (n-i) * (80 + 10), PUHIGH - 10 - BUTTH,
	     80, BUTTH, labels[i]+1, infofg, infobg);
    accel[i] = labels[i][0];
  }

  /* center first button in window around mouse position, with constraint that 
     window be fully on the screen */

  CenterMapWindow(popW, 40 + bts[0].x, BUTTH/2 + bts[0].y,
		  PUWIDE, PUHIGH);
  popUp = 1;

  /* MUST wait for VisibilityNotify event to come in, else we run the risk
     of UnMapping the window *before* the Map request completed.  This 
     appears to be bad, (It leaves an empty window frame up.) though it
     generally only happens on slow servers.  Better safe than screwed... */

  XWindowEvent(theDisp, popW, VisibilityChangeMask, &event);

  /* block until this window gets closed */
  while (popUp) {
    XNextEvent(theDisp, &event);
    HandleEvent(&event, &i);
  }

  /* free stuff */
  XUnmapWindow(theDisp, popW);
  free(bts);

  return(selected);
}


/***************************************************/
void OpenAlert(txt)
     char *txt;
{
  /* pops up a window with txt displayed in it (*no buttons*).  
     returns immediately.  window is close by 'CloseAlert()'.
     No 'PopUp()' calls are allowed while an Alert is displayed. */

  XEvent event;

  if (firsttime) createPUD();

  XStoreName(theDisp, popW, "xv notice");
  XSetIconName(theDisp, popW, "xv notice");

  nbts = 0;
  selected = 0;
  text = txt;

  /* center last button in window around mouse position, with constraint that 
     window be fully on the screen */

  CenterMapWindow(popW, PUWIDE/2, PUHIGH/2, PUWIDE, PUHIGH);
  popUp = 1;

  /* MUST wait for VisibilityNotify event to come in, else we run the risk
     of UnMapping the window *before* the Map request completed.  This 
     appears to be bad, (It leaves an empty window frame up.) though it
     generally only happens on slow servers.  Better safe than screwed... */

  XWindowEvent(theDisp, popW, VisibilityChangeMask, &event);
  drawPUD(0, 0, PUWIDE, PUHIGH);
  XFlush(theDisp);
}


/***************************************************/
void CloseAlert()
{
  popUp = 0;
  XUnmapWindow(theDisp, popW);
}


/***************************************************/
int PUCheckEvent(xev)
XEvent *xev;
{
  /* check event to see if it's for us.  If so, return 1, otherwise 0 */

  int rv = 0;

  if (!popUp) return(0);

  if (xev->type == Expose) {
    XExposeEvent *e = (XExposeEvent *) xev;
    if (e->window == popW) {
      drawPUD(e->x, e->y, e->width, e->height);
      rv = 1;
    }
  }

  else if (xev->type == ButtonPress) {
    XButtonEvent *e = (XButtonEvent *) xev;

    if (e->button == Button1 && e->window == popW) {
      clickPUD(e->x,e->y);
      rv = 1;
    }
  }


  else if (xev->type == KeyPress) {
    XKeyEvent *e = (XKeyEvent *) xev;
    char buf[128];  KeySym ks;
    int stlen, i;
	
    stlen = XLookupString(e,buf,128,&ks,(XComposeStatus *) NULL);
    buf[stlen] = '\0';

    /* note: we accept keyboard accellerators in *any* window */
    if (stlen) {
      if (buf[0] == '\r') buf[0] = '\n';

      /* search for character in accel table */
      for (i=0; i<nbts; i++) {
	if (buf[0] == accel[i] && buf[0] != ' ') {
	  FakeButtonPress(&bts[i]);
	  rv = 1;
	}
      }

      if (!rv && buf[0]=='\033' && nbts==1) { /* ESC accepted in 1-but pu's */
	FakeButtonPress(&bts[0]);
	rv = 1;
      }
    }
    else rv = 1;    /* 'accept' non-char strings (ie, shift or ctrl press) */
  }

  else if (xev->type == ClientMessage) {
    Atom proto, delwin;
    XClientMessageEvent *client_event = (XClientMessageEvent *) xev;

    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 == popW) {
	FakeButtonPress(&bts[(nbts>1) ? nbts-1 : 0]);
	rv = 1;
      }
    }
  }

  if (rv==0 && (xev->type == KeyPress || xev->type == ButtonPress)) {
    XBell(theDisp, 0);
    rv = 1;            /* eat it */
  }

  return rv;
}



#define TR_MAXLN 10

/***************************************************/
void TextRect(win, txt, x, y, w, h, fg)
     Window  win;
     char   *txt;
     int     x,y,w,h;
     u_long  fg;
{
  char *sp, *ep, *oldep, *start[TR_MAXLN];
  int   i, inbreak, lineno, top, hardcr, maxln, len[TR_MAXLN];

  XSetForeground(theDisp, theGC, fg);
  
  sp = txt;  lineno = hardcr = 0;

  maxln = h / LINEHIGH;  
  RANGE(maxln,0,TR_MAXLN);
  while (*sp && lineno<maxln) {

    /* drop off any leading spaces (except on first line or after \n) */
    if (sp!=txt && !hardcr) {
      while(*sp==' ') sp++;
    }

    hardcr = 0;   ep = sp;

    /* increment ep until we   A) get too wide, B) hit eos or
       C) hit a '\n' character */

    /* NOTE: ep points to the character AFTER the end of the line */

    while (XTextWidth(mfinfo, sp, ep-sp) <= w && *ep && *ep!='\n') ep++;
    if (*ep=='\n') { ep++;  hardcr=1; }   /* eat newline */

    /* if we got too wide, back off until we find a break position 
       (last char before a space or a '/') */

    if (XTextWidth(mfinfo, sp, ep-sp) > w) {
      oldep = ep;  inbreak = 0;
      while (ep!=sp) {
	ep--;
	if ( inbreak && *ep!=' ') { ep++;  break; }
	if (!inbreak && *ep==' ') inbreak = 1;
	if (*ep=='/') { ep++; break; }
      }
      if (ep==sp) ep = oldep-1;  /* can't break this line.  oh well */
    }

    start[lineno] = sp;  len[lineno] = ep-sp;
    
    /* make sure we don't print a trailing '\n' character! */
    if (len[lineno] > 0) {
      while (sp[len[lineno]-1] == '\n') len[lineno] = len[lineno] - 1;
    }

    sp = ep;
    lineno++;
  }

  top = y + h/2 + (ASCENT-DESCENT)/2 - ((lineno-1)*LINEHIGH)/2;
  if (top<y+ASCENT) top = y+ASCENT;

  for (i=0, y=top; i<lineno; i++, y+=LINEHIGH) {
    if (start[i][0] != '\n')
      XDrawString(theDisp, win, theGC, x, y, start[i], len[i]);
  }
}


/***************************************************/
static void createPUD()
{
  popW = CreateWindow("xv confirm", "+0+0", PUWIDE, PUHIGH, infofg, infobg);
  if (!popW) FatalError("can't create popup window!");
  StoreDeleteWindowProp(popW);

  XSelectInput(theDisp, popW, ExposureMask | ButtonPressMask | KeyPressMask
	       | VisibilityChangeMask);
  XSetTransientForHint(theDisp, popW, mainW);

  bts = (BUTT *) NULL;
  nbts = selected = firsttime = 0;
}
  

/***************************************************/
static void drawPUD(x,y,w,h)
int x,y,w,h;
{
  int  i,xt,yt;
  XRectangle xr;

  xr.x = x;  xr.y = y;  xr.width = w;  xr.height = h;
  XSetClipRectangles(theDisp, theGC, 0,0, &xr, 1, Unsorted);

  XSetForeground(theDisp, theGC, infofg);
  XSetBackground(theDisp, theGC, infobg);

  XCopyPlane(theDisp, iconPix, popW, theGC, 0,0, icon_width, icon_height,
	     10,10+(PUHIGH-30-BUTTH-icon_height)/2,1);

  xt = 10+icon_width+20;  yt = 10;
  TextRect(popW, text, xt, yt, PUWIDE-10-xt, PUHIGH-10-BUTTH-20, infofg);

  for (i=0; i<nbts; i++) BTRedraw(&bts[i]);

  XSetClipMask(theDisp, theGC, None);
}


/***************************************************/
static void clickPUD(x,y)
int x,y;
{
  int i;
  BUTT *bp;

  for (i=0; i<nbts; i++) {
    bp = &bts[i];
    if (PTINRECT(x, y, bp->x, bp->y, bp->w, bp->h)) break;
  }

  if (i<nbts && BTTrack(bp)) {
    popUp = 0;  selected = i;
  }
}


