/*
 *	$Source: /u1/X/xman/RCS/dialogue.c,v $
 *	$Header: dialogue.c,v 1.1 87/01/28 15:49:23 swick Exp $
 */

#ifndef lint
static char *rcsid_dialogue_c = "$Header: dialogue.c,v 1.1 87/01/28 15:49:23 swick Exp $";
#endif	lint

#include <stdio.h>
#include <X/Xlib.h>
#include <Sx/sx.h>

/*
 * Copyright (c) 1987 Barry Shein (BZS@BU-CS.BU.EDU)
 * Right is hereby granted to distribute either modified
 * or unmodified versions of this program in source form
 * as long as this copyright notice is intact. May not
 * be re-sold for profit without explicit arrangement
 * with the author.
 */

/*
 * char *
 * DialogueBox(relw,label,input,maxinput,entryfont,buttonfont)
 * Window relw;
 * char *label;
 * char *input;
 * int maxinput;
 * FontInfo *entryfont, *buttonfont;
 *
 * Where:
 *   relw:	position of dialogue box is calculated relative to this window
 *   label:	Label to give entry area, such as "Name? "
 *   input:	An input buffer, any string in input buffer will be used as
 *		a starting value (see Sx_CreateEntry())
 *   maxinput:	Maximum chars, including terminating null, to accept
 *   entryfont, buttonfont:
 *		fonts to use for respective fields, defaults are used
 *		if supplied as NULL.
 *
 * Note that until a button is pressed the mouse and server are "grabbed",
 * see Sx_Notify for a similar facility. Returns either the original buffer
 * pointer if user clicks ok (which holds result) or NULL meaning user hit
 * the CANCEL button.
 *
 * Example:
 *	char buf[40];
 *
 *	if(DialogueBox(RootWindow,"Name:",buf,40,NULL,NULL,NULL) != NULL)
 *		printf("User typed %s\n",buf);
 *
 * Author: Barry Shein, BZS@BU-CS.BU.EDU
 * Date: 1/6/86
 */

/*
 * Todo:
 *	more sane placement of dialogue window
 */
/*
 * Cursor to use while mouse is grabbed, small version of xterm.cursor
 */
#include "dialogue.cursor"
#include "dialogue_mask.cursor"

/*
 * These are the default fonts to use if user passes NULLs, change at will
 */
#define DEF_ENTRY_FONT "9x15"
#define DEF_BUTTON_FONT "timrom12b"

/*
 * Put the dialogue box down and to the left this %age
 * (that is, divide by these)
 */
#define XRIGHT 10
#define XDOWN 10

/*
 * Make the text entry box this much more
 * (ie. (maxinput*font->width)+((maxinput*font->width)/ENTRYSLOP))
 * see the code, it's a hack.
 */
#define ENTRYSLOP 5
/*
 * A few little macros to clean up the looks of some code
 */
#define max(x,y) (((x) > (y)) ? (x) : (y))
#define min(x,y) (((x) < (y)) ? (x) : (y))
#define ifnull(x,y) (((x) == NULL) ? y : x)

/*
 * This is the text for the two buttons
 */
#define OK	"OK"
#define CANCEL	"CANCEL"
/*
 * Used internally, zero means no button pressed yet
 */
#define OKVAL 1
#define CANCELVAL 2
/*
 * I dunno, aesthetics, minimum window width/height
 */
#define MINX 200
#define MINY 100
/*
 * Offsets from relative window (another kludge)
 */
#define OFFX 50
#define OFFY 50
/*
 * Used variously to spread things out a little
 */
#define XSLOP 10
#define YSLOP 10

/*
 * Most of these are static globals for the convenience
 * of writing some subrs and/or redisplay.
 */

static Window okbut, canbut;		/* the two button windows */
static int okxoff, okyoff;		/* button window offsets */
static int canxoff, canyoff;
static int butht, butwd;		/* both buttons same size */

static int okinvert = 0;		/* is OK window inverted? */
static int caninvert = 0;		/* is CANCEL window inverted? */

static FontInfo *bfont;			/* button font, for re-display */
/*
 * Redraw the OK button (inv != 0 ? Reverse)
 */
#define putOk(inv) \
  XPixSet(okbut,0,0,butwd,butht,inv ? BlackPixel : WhitePixel); \
  putButton(okbut,okxoff,okyoff,OK,bfont,inv);

/*
 * Redraw the CANCEL button (inv != 0 ? Reverse)
 */
#define putCan(inv) \
  XPixSet(canbut,0,0,butwd,butht,inv ? BlackPixel : WhitePixel); \
  putButton(canbut,canxoff,canyoff,CANCEL,bfont,inv);

/*
 * Redraw some button
 */
static void
putButton(but,x,y,str,font,inv)
Window but; int x,y; char *str; FontInfo *font; int inv;
{
  XText(but,x,y,str,strlen(str),font->id,
	inv ? WhitePixel : BlackPixel,
	inv ? BlackPixel : WhitePixel);
}

/*
 * Redisplay the window (Exposure)
 */
static void
ReWindowProc()
{
  putOk(okinvert);
  putCan(caninvert);
}

/*
 * The main routine
 */
char *
DialogueBox(relw,label,input,maxinput,entryfont,buttonfont)
Window relw;
char *label;
char *input;
int maxinput;
FontInfo *entryfont,*buttonfont;
{
  WindowInfo r_info;	/* Info about the relative window passed */
  Window w;		/* The main dialogue window */
  int i;
  int wd, ht;		/* width and height of dialogue window */
  int x,y;		/* x/y position of dialogue window */

  /*
   * This is the cursor while grabbed, static
   * so we only calculate it once
   */
  static Cursor wcursor = (Cursor) NULL;

  /* position in main window of buttons */

  int mx, my;		/* for getting mouse coordinates */
  Window mw;		/* subwindow mouse may be in */
  int buttonpressed;	/* have they pressed a button yet? which? */
  int lxoff, lyoff;	/* position of label */

  /* fonts finally decided upon */
  FontInfo *efont;	/* bfont has to be external */

  efont = ifnull(entryfont,XOpenFont(DEF_ENTRY_FONT));
  bfont = ifnull(buttonfont,XOpenFont(DEF_BUTTON_FONT));

  if((efont == NULL) || (bfont == NULL)) {
    fprintf(stderr,"Font problems?\n");
    return(NULL);
  }

  /* Find position of window this is to be realitive to */
  XQueryWindow(relw,&r_info);

  /*
   * I found that just allowing maxinput*efont->width
   * was never quite enough so up it a little, ugh, call it
   * a heuristic...
   */
  i = (maxinput+(maxinput/ENTRYSLOP))*efont->width;
  i += XStringWidth(label,efont,0,0);
  wd = max(i,MINX)+2*XSLOP;		/* main window width */

  /* main window height */
  ht = (YSLOP+bfont->height)+(YSLOP+max(efont->height,efont->height))+2*YSLOP;
  ht = max(ht,MINY);

  /* label position */
  lxoff = XSLOP;
  lyoff = YSLOP+bfont->height+2*YSLOP;

  /*
   * This should be fixed, position a little down and to the right
   * of the window suggested by the caller.
   */
  x = r_info.x + (r_info.width/XRIGHT);
  y = r_info.y + (r_info.height/XDOWN);

  /* create cursor to use while mouse is grabbed, only once */
  if(wcursor == ((Cursor) NULL))
    wcursor = XCreateCursor(dialogue_width,dialogue_height,
			    dialogue_bits,dialogue_mask_bits,
			    dialogue_x_hot,dialogue_y_hot,
			    BlackPixel,WhitePixel,GXcopy);

  /* make main window */
  w = XCreateWindow(RootWindow,x,y,wd,ht,4,BlackPixmap,WhitePixmap);

  /*
   * Obviously we know that CANCEL is longer, if this becomes
   * dynamic it would be easy enough to change to
   * max(strlen(OK),strlen(CANCEL)) or something equivalent.
   */
  butwd = XStringWidth(CANCEL,bfont,0,0) + 2*XSLOP;
  butht = bfont->height;

  /* create the two button windows */
  okbut = XCreateWindow(w,XSLOP,YSLOP,
			butwd,butht,
			2,BlackPixmap,WhitePixmap);
  canbut = XCreateWindow(w,XSLOP+butwd+XSLOP,YSLOP,
			 butwd,butht,
			 2,BlackPixmap,WhitePixmap);

  /* Position of strings within the button windows (centered) */
  okxoff = (butwd/2) - (XStringWidth(OK,bfont,0,0)/2);
  okyoff = 0;
  canxoff = (butwd/2) - (XStringWidth(CANCEL,bfont,0,0)/2);
  canyoff = okyoff;

  /* The text entry window */
  Sx_EntryCreate(w,
		 lxoff,lyoff,
		 wd-2*XSLOP,max(bfont->height,efont->height),
		 1,
		 label,efont,
		 BlackPixel,WhitePixel,
		 input,maxinput);

  XSelectInput(w,ExposeWindow|MouseMoved|ButtonPressed);
  XGrabMouse(w,wcursor,ExposeWindow|MouseMoved|ButtonPressed);
  XGrabServer();
  XMapWindow(w);
  XMapSubwindows(w);

  /*
   * Loop until they press a button, text entry automatically
   * taken care of by Sx_Entry
   */
  for(buttonpressed = 0; buttonpressed == 0;) {
    XEvent xevent;

    XNextEvent(&xevent);
    switch(xevent.type) {

    case ExposeWindow:
      ReWindowProc();
      /* Needed to update the Entry window */
      Sx_HandleEvent(&xevent);
      break;

    case MouseMoved:
      XQueryMouse(w,&mx,&my,&mw);	/* get mouse position */

      /*
       * mw will be the subwindow the mouse is in if any,
       * see if it is in one of the button windows. If so
       * then invert the button (and, if necessary, uninvert other etc.)
       */
      if(mw != 0) {
	if(mw == okbut) {
	  if(!okinvert) {
	    putOk(1);
	    okinvert = 1;
	  }
	  if(caninvert) {
	    putCan(0);
	    caninvert = 0;
	  }
	}
	else if(mw == canbut) {	/* it better be */
	  if(!caninvert) {
	    putCan(1);
	    caninvert = 1;
	  }
	  if(okinvert) {
	    putOk(0);
	    okinvert = 0;
	  }
	}
      }
      else {	/* not in any subwindow */
	if(okinvert) {
	  putOk(0);
	  okinvert = 0;
	}
	if(caninvert) {
	  putCan(0);
	  caninvert = 0;
	}
      }
      break;

    case ButtonPressed:
      /* See if pressed while in button window */
      XQueryMouse(w,&mx,&my,&mw);
      if(mw == 0) break;
      if(mw == okbut) {
	buttonpressed = OKVAL;
	putOk(0);
	okinvert = 0; /* not necessary */
	break;
      }
      else if(mw == canbut) {
	buttonpressed = CANCELVAL;
	putCan(0);
	caninvert = 0; /* not necessary */
	break;
      }

    default:
      Sx_HandleEvent(&xevent);
      break;
    }
  }
  XUngrabMouse();
  XUngrabServer();
  XDestroyWindow(w);
  return((buttonpressed == OKVAL) ? input : NULL);
}
#ifdef notdef
main()
{
  char buf[BUFSIZ];

  if(XOpenDisplay(NULL) == NULL) {
    fprintf(stderr,"No X?\n");
    exit(1);
  }
  buf[0] = '\0';
  if(DialogueBox(RootWindow,"Test: ",buf,40,NULL,NULL) != NULL)
    printf("You entered '%s'\n",buf);
  else printf("CANCELLED\n");
  exit(0);
}
#endif
