/*  File: qbedisp.c
 *  Author: Gary Aochi (aochi@genome.lbl.gov)
 * -------------------------------------------------------------------
 * Acedb is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 * or see the on-line version at http://www.gnu.org/copyleft/gpl.txt
 * -------------------------------------------------------------------
 * This file is part of the ACEDB genome database package, written by
 * 	Richard Durbin (MRC LMB, UK) rd@mrc-lmba.cam.ac.uk, and
 *	Jean Thierry-Mieg (CRBM du CNRS, France) mieg@crbm1.cnusc.fr
 *
 * Description:
 *   Constructs general queries.                              **
 *   Gary Aochi modified version of qbedisp.c
 *   with on line help to preform complex queries.
 *     _DDtQuery   -g TEXT_SCROLL -t "Query" -w .73  -height 0.35 -help Query
 *
 * Exported functions:  see qbe.h
 * HISTORY:
 * Last edited: May  6 10:24 2003 (edgrif)
 * * Nov 19 15:02 1998 (edgrif): Fixed callback func. dec. for
 *              graphTextScrollEditor.
 * Last edited:Jun 6 11:00 1996 (rbrusk):
 *	-	graphTextScrollEntry() callback function (GraphFunc) casts removed
 * * Jun 4 15:49 1996 (rbrusk):
 *	-	changed fn() => fn(void) function declarations to please compiler
 * * Jun  3 12:11 1996 (rd)
 * * Jan 18 12:55 1993 (mieg): Changed MAGIC and added some static def
 *-------------------------------------------------------------------
 * gha 03/29/93  changed querEdit() to queryCommandEdit(), fixed execution
 * gha 04/02/93  redid tags as pickable, reverse-video, but need duplicate ctrl
 * gha 04/05/93  fix mistake for tag = * (boolean), added qbepick-qbeform call.
 * gha 04/08/93  improved AND/OR option and better query command formatting
 * gha 04/12/93  Added & NEXT capability with Int/Float checking for "
 * gha 04/29/93  jean cleaned up stuff, fixed classlist, adding item name box
 *               added code to unhighlight some buttons
 * gha 05/25/93  jean changes plus my own for formation and classlist ;
 * gha 05/28/93  added metadata function
 * gha 06/08/93  changed "Find" to "Search"
 * gha 06/10/93  added feature to search from active keyset
 * gha 06/14/93  split querydisp.c and querybuild.c from querydisp.c
 * gha 06/16/93  cleaned up #hash (not shown at all now), rather than
 *               badly displayed as a box.  Should try to properly fix later.
 * gha 6/23/93   moved metadata stuff to metadisp.c
 * kliu 9/2/93   added triangle box for class selection
 * Created: Feb 09 15:30 1993 (gha): split code from querydisp.c
 * CVS info:   $Id: qbedisp.c,v 1.35 2003/05/06 13:13:15 edgrif Exp $
 *-------------------------------------------------------------------
 */

/*
#define ARRAY_CHECK
#define MALLOC_CHECK
*/

#include <ctype.h>
#include <wh/acedb.h>
#include <wh/bs.h>         /* used for bsTagsInClass() procedure */
#include <wh/query_.h>
#include <wh/query.h>
#include <wh/qbe.h>
#include <wh/bs_.h>        /* 10/12/92 gha */
#include <wh/lex.h>
#include <wh/key.h> 
#include <whooks/systags.h>
#include <whooks/sysclass.h>
#include <wh/display.h>
#include <wh/keysetdisp.h>

/* ----- --- --- */

extern BOOL metaGetData(int metaclass, KEY classkey, BS tag) ;

static int  QBE_MAGIC =  29986 ;
static int nameBox ;
/* Query by example  typedef */
typedef struct QBESTUFF
{ int   magic ;        /* == QBE_MAGIC */
  KEY   key ;          /* key for model-class */
  OBJ   objet ;        /* data stored in structure */
  Graph graph ;        /* window graph id for QBE window */
  int   activebox ;    /* current active box in window */
  Array tagval ;       /* storage of tags and such for QBE interface */
  char* text ;	       /* for typing in things */
  int classe ;          /* for completion mechanism */
} *QBE ;

Graph qbeGraph = 0 ;

#include <wh/display.h>	/* must come after QUER definition */

static void qbeDestroy (void) ;
static char conjunction_op = '&';     
    

typedef struct TAGVALUESTUFF
{
  int  box;                /* current box id */
  char name[BUFFER_SIZE];  /* tag name or entry value */
  int  textformat;         /* for textformat of output (bold/plain) */
  BOOL tag_active;         /* used during query formation */
  BOOL tag_defined;        /* is the tag properly set up; valid? */
  char tv_type;            /* 'char' to save bits; TV_TAG, TV_TEXT_ENTRY.. */
  struct TAGVALUESTUFF *tag;  /* for value :  backwards pointer to tag */
  int  has_value ;         /* for tags...true if has value */
  int  value_number ;      /* for value enumeration */
  BS   bs ;                /* for metadata */
} *TV;


/* types for tv_type field of tagvaluestuff */
#define TV_TAG           0
#define TV_TEXT_ENTRY    1
#define TV_NUMERIC_ENTRY 2
#define TV_DATE_ENTRY 3

/* background and foreground colors */
#define NORMAL_FCOL BLACK
#define NORMAL_BCOL WHITE
#define SELECT_FCOL WHITE
#define SELECT_BCOL BLACK


static TV lasttag ;         /* for qbeDrawBS */
static int lasttag_idx;

static BOOL xref_tag ;      /* for qbeDrawBS */

/* Item Name box */
static int itemBox, itemEntryBox;
static BOOL item_enable ;
static char item_buffer[BUFFER_SIZE] ;

static BOOL from_keyset ;   /* for searching from active keyset */

static int boxMax;

static FREEOPT *classesMenu;

static int qbe_class;
static int qbe_classBox, qbe_classEntryBox, qbe_classTriBox;
static char qbe_class_buffer[BUFFER_SIZE];

static void qbeDisplay(void);
static void qbePick(int box, double x_unused, double y_unused, int modifier_unused);
static void qbeReKey(void);
static void qbeClear(void) ;
static void qbeMenuExec(void);  /* for QBE_Menu */
static void qbeMenuQuit(void);
static void qbeMenuDraw(void);
static void qbeMenuAnd(void);
static void qbeMenuOr(void);
/*
static void qbeMenuMetaLong(void);
static void qbeMenuMetaFull(void);
*/

static void qbeButtonUndo(void) {
  queryCommandUndo();
  qbeDisplay();  /* gha added to unhighlight undo button 4/29/93 */
}

/* call up query-commander */
static void queryCallup(void) {
  queryCreate();
  qbeDisplay();  /* unhighlight button */
}

/* call up query builder */
static void qbuildCallup(void) {
  qbuildCreate();
  qbeDisplay();  /* unhighlight button */
}

/* execute query */
static void qbeButtonExec(void) {
  qbeMenuExec();
  qbeDisplay();  /* unhighlight button */
}

static void myNewKeySet (void)
{ 
  KEYSET ks = keySetCreate();	/* new,empty keyset */
  keySetNewDisplay(ks, "QBE Answer") ;
}

static MENUOPT QBE_Menu[]=
{
  {qbeMenuQuit, "Quit"},
  {help, "Help"},
  {graphPrint, "Print"},
  {qbeMenuDraw, "Refresh"},
  {qbeClear, "Clear"},
  {menuSpacer,""},
  {myNewKeySet, "New KeySet"},
  {qbuildCreate, "Query Builder"},
  {queryCreate, "Query Command Editor"},
  {menuSpacer,""},
  {qbeMenuAnd, "AND"},
  {qbeMenuOr, "OR"},
  {menuSpacer, ""},
/*
  {qbeMenuMetaFull, "Show Full Description"},
  {qbeMenuMetaLong, "Show Long Description"},
  {menuSpacer, ""},
*/
  {qbeMenuExec, "Search"},
  {queryCommandUndo, "Undo"},
   {0,0}
  } ;

static MENUOPT QBE_Buttons[]=
{
  {qbeMenuQuit, "Quit"},
  {help, "Help"},
  {qbeClear, "Clear"},
  {myNewKeySet, "New KeySet"},
  {qbuildCallup, "Builder"},
  {queryCallup, "Command Edit"},
  {qbeButtonExec, "Search"},
  {qbeButtonUndo, "Undo"},
   {0,0}
  } ;

#define QBEGET(name)     QBE qbelook ; \
  if (!graphActivate(qbeGraph)) \
    messcrash("%s can't find qbeGraph",  name); \
  if (!graphAssFind (&QBE_MAGIC,&qbelook)) \
    messcrash ("%s can't find graph",name) ; \
  if (!qbelook) \
    messcrash ("%s received a null pointer",name) ; \
  if (qbelook->magic != QBE_MAGIC) \
    messcrash ("%s received a wrong pointer",name)

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


static Array classlist = 0;

/* retrieve a class name from db list, note that classlist is different
 * in qbedisp.c and querydisp.c
 */
#define CLASSLIST_NAME(x)  ARR2STRING(classlist, x)

static int graphDownSelTriangle(BOOL filled, float x, float y)
{
  int i = 3, box;
  Array temp ;

  box = graphBoxStart();

  graphLine(x, y+0.25, x+1, y+0.25);
  graphLine(x+1, y+0.25, x+0.5, y+0.75);
  graphLine(x+0.5, y+0.75, x, y+0.25);

  if (filled) {
    temp = arrayCreate(2*i, float) ;
    array(temp, 0, float) = x;
    array(temp, 1, float) = y+0.25;
    array(temp, 2, float) = x+1;
    array(temp, 3, float) = y+0.25;
    array(temp, 4, float) = x+0.5;
    array(temp, 5, float) = y+0.75;
    graphPolygon(temp);
    arrayDestroy(temp);
  }

  graphBoxEnd();

  return box;
}

/* find the total number of listed classes in the menu, if no 
 * list exists, initialize it and return the total number.
 */
static int qbeClassMax(void) 
{
  int n, i, k ;
  char *v, *v0 = 0 ;

  if (graphAssFind(&qbe_class, &v))
    return v - v0 ;

  n = 0;
  classlist = arrayCreate(30, Array);   /* init classlist */
  
  for (i = 1 ; i <= 256 ; i++) {
    if (pickList[i].name  && pickList[i].type == 'B' &&
	pickList[i].visible != 'h') {
      array(classlist, n, Array) = arrayCreate(BUFFER_SIZE, char);
      k = KEYMAKE(i, 0) ;
      strcpy(ARR2STRING(classlist, n), className(k));
      n++;
    }
  }

  arraySort(classlist, textAlphaOrder);

  v = v0 + n ;
  graphAssociate(&qbe_class, v);
  return n;
}


static void destroyClassesMenu(void)
{ int max;
  if (graphAssFind(&classesMenu, &max))  {
    destroyMenu(classesMenu, max);
    graphAssRemove(&classesMenu);
  }
}

/* initialize the classes menu */
static void classesMenuSet(void)
{ char *v, *v0 = 0 ;
  int i, max;
  
  max = qbeClassMax();
  
  /* max should be one larger for header-entry */
  classesMenu = (FREEOPT *)halloc((max + 1) *sizeof(FREEOPT), graphHandle() );
  classesMenu->key = max;
  classesMenu->text = halloc(BUFFER_SIZE, graphHandle()) ;
  
  strcpy(classesMenu->text, "CLASSES");
  
  for (i = 1; i <= max; i++)  {
    (classesMenu + i)->key = i;
    (classesMenu + i)->text = halloc(BUFFER_SIZE, graphHandle()) ;
    strcpy((classesMenu + i)->text, CLASSLIST_NAME(i - 1));
  }
  
  graphAssociate(&classesMenu, v = v0 + max);
}


/* handle the "RIGHT-MOUSE" (menu) selection of the class menu box */
static void classesMenuResult(KEY k, int box)
{
  int temp_class;

  temp_class = pickWord2Class((classesMenu+k)->text);

  if (pickType(KEYMAKE(temp_class, 0)) == 'B') {
    qbe_class = temp_class;
    strcpy(qbe_class_buffer, (classesMenu+k)->text);
    /*    graphTextScrollEntry(qbe_class_buffer, 0, 0, 0, 0, 0); */
    boxMax = 0 ; /* mhmp 17.09.98 */
    qbeReKey();
  }
  else {
    messout("Sorry, class %s has no model to display.", (classesMenu+k)->text);
    qbeDisplay();
  }
}     


static char *currentClassName(void) 
{
  return className(KEYMAKE(qbe_class,0));
}

static BOOL qbe_classEntryHandler(char *text, int unused)
{
  int i, temp_class;
  Graph old = graphActive () ;
  for (i = 1; i <= qbeClassMax(); i++)
    if (!strcasecmp((classesMenu+i)->text, text))
      break;

  if (i > qbeClassMax()) {
    messout("Invalid class name %s.", text);
    strcpy(text, pickClass2Word(qbe_class));
    /*    graphTextScrollEntry(text, 0, 0, 0, 0, 0); */
    graphActivate (old) ;
    return FALSE ;
  }

  temp_class = pickWord2Class((classesMenu+i)->text);
  if (pickType(KEYMAKE(temp_class, 0)) == 'B') {
    qbe_class = temp_class;
    qbeReKey();
  }
  else {
    messout("Chosen class %s has no model.", (classesMenu+i)->text);
    strcpy(text, pickClass2Word(qbe_class));
    qbeDisplay();
  }
  graphActivate (old) ;
  return TRUE ;
}

static FREEOPT fromMenu[] = 
{
  {2, "QUERY ORIGINS"},
  {1, "Active Keyset"},
  {2, "Specified Class"}
};

static void qbeShowQueryCommand(void) ;

static void fromMenuResult(KEY k, int box)
{
  if (k == 1)
    from_keyset = TRUE;
  else 
    from_keyset = FALSE;
  qbeShowQueryCommand();
  qbeDisplay();
}

static char *currentFromName(void) 
{
  if (from_keyset)
    return "KeySet";
  else
    return "Class";
}

static void fromButtonResult(void) 
{
  if (from_keyset)
    from_keyset = FALSE;
  else
    from_keyset = TRUE;
  qbeShowQueryCommand();
  qbeDisplay();
}     


/************************************************************/
static void qbeClear(void) /* mhmp 18.09.98 */
{
  QBEGET("qbeClear") ; 
  if (qbelook && qbelook->key)
    qbelook->key = 0 ;
  qbeReKey() ;
}

/* reset the display for a new class/model */
static void qbeReKey(void)
{ int oldKey = 0 ;
  QBEGET("qbeReKey") ; 
  if (qbelook && qbelook->key)
    oldKey = qbelook->key  ;
  qbelook->key = KEYMAKE(qbe_class, 0);
  if (oldKey == qbelook->key)
    return ;
  boxMax = 0 ;
  bsDestroy (qbelook->objet);
  
  qbelook->tagval = arrayReCreate(qbelook->tagval, 1, TV); /*mhmp 05.11.98 */
  array(qbelook->tagval, 0, TV) = NULL;

  if (pickType (qbelook->key) != 'B' 
      || 
      !(qbelook->objet = bsCreate(qbelook->key)))  {
    if(graphActivate(qbeGraph))  {
      graphDestroy () ;
      qbeGraph = 0 ;
    }
    messout("Bad model for class %s.\n  Removing QBE Window.", 
	    pickClass2Word(qbe_class));
    return;
  }
  graphRetitle (messprintf("Query By Example: %s", 
			   className(qbelook->key))) ;
  
  array(qbelook->tagval, 0, TV) = NULL;
  lasttag = NULL;
  lasttag_idx = 0;
  xref_tag = FALSE;
  /*  boxMax = 0; mhmp 16.09*/
  
  qbeDisplay();
}  


/* Destroys qbe data structure, but not qbe graph  */
static void qbeDestroy (void)
{
  QBE qbelook;
  int i;
  
  if (graphAssFind (&QBE_MAGIC,&qbelook)
      && qbelook
      && qbelook->magic == QBE_MAGIC) {
    bsDestroy (qbelook->objet);
    
    
    if (!graphExists(qbuildGraph) && !graphExists(qbuildGraph))
      arrayDestroy(query_undo_set);

    arrayDestroy(qbelook->tagval);
    
    if (querGraph == 0 || !graphExists(querGraph)) {
      for (i = 0; i < qbeClassMax(); i++)
	arrayDestroy(arr(classlist, i, Array));
      arrayDestroy(classlist);
    }
    
    destroyClassesMenu();

    qbelook->magic = 0 ;
    messfree (qbelook) ;
  }
}

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

/* QBE Menu responses */

static char *qbeFormQuery(char *buffer)
{
  int i;
  TV tagval, oldtag;
  BOOL notfirst = FALSE, tagnotvalue = TRUE;
  QBEGET("qbeFormQuery") ;

  if (from_keyset)
    strcpy(buffer, "");
  else
    strcpy(buffer, messprintf("Find %s",pickClass2Word(qbe_class)));

  if (item_enable) {
    strcat(buffer, messprintf(" %s", item_buffer));
    notfirst = TRUE ;
  }

  tagval = NULL;

  for (i = 0; i < arrayMax(qbelook->tagval); i++)  {
    oldtag = tagval;
    tagval = array(qbelook->tagval, i, TV);

    /* check if is an entry (rather than a tag (if tag, tagval->tag = NULL)) */
    if (tagval && oldtag != tagval) 
      {
	if (tagval->tag_active) {   /* tag is active */
	  tagnotvalue = TRUE;
	  strcat(buffer,
		 (notfirst ?
		  messprintf(" %c %s", conjunction_op, tagval->name) :
		  messprintf(" %s", tagval->name)));
	  notfirst = TRUE;
	}
	else if (tagval->tag && tagval->tag->tag_active)
	  {  /* value part */
	    if (!strcmp(tagval->name, "*") && !tagnotvalue) {      /* just add a tag (boolean) */
	      strcat(buffer, messprintf(" %c NEXT", conjunction_op));
	      tagnotvalue = FALSE;
	    }
	    else if (strcmp(tagval->name,""))    /* add an  'tag equals value' */
	      { char *cq = tagval->name ; /* mieg allow comparators in qbe */

		while (*cq == ' ') cq++ ;
		if (*cq == '>' || *cq == '<' || *cq == '=') { /* jtm */
		  strcat(buffer, ((tagnotvalue) ? messprintf(" %s", tagval->name) :
				  messprintf(" %c NEXT %s", conjunction_op, tagval->name)));
		  tagnotvalue = FALSE;
		}
		else {
		  strcat(buffer, ((tagnotvalue) ? 
				  ((tagval->tv_type == TV_TEXT_ENTRY) ?
				   messprintf(" = \"%s\"", tagval->name) :
				   messprintf(" = %s", tagval->name)) :
				  ((tagval->tv_type == TV_TEXT_ENTRY) ?
				   messprintf(" %c NEXT = \"%s\"", conjunction_op, 
					      tagval->name) :
				   messprintf(" %c NEXT = %s", conjunction_op, 
					      tagval->name))));
		  tagnotvalue = FALSE;
		}
	      }
	    else if (tagval->value_number < tagval->tag->has_value) {  /* filler */
	      strcat(buffer, messprintf(" %c NEXT", conjunction_op));
	      tagnotvalue = FALSE;
	    }
	  }
      }
  }
  return buffer;
}

static void qbeMenuAnd(void) {
  conjunction_op = '&';
  qbeShowQueryCommand();
}

static void qbeMenuOr(void) {
  conjunction_op = '|';
  qbeShowQueryCommand();
}


static void qbeMenuExec(void)
{
  KEYSET oldSet, newSet ;
  KeySetDisp look ;
  QBEGET("qbeMenuExec");

  graphCheckEditors(qbeGraph, TRUE) ;
  /* form on query interface window */
  if (graphExists(querGraph)) {
    messStatus ("Forming Query Command");
    if(!graphActivate(querGraph))
      messcrash("Cannot reactivate querGraph in qbeMenuExec.\n");
      
    queryWindowShowQuery(qbeFormQuery(resbuffer)); /* calls QUERGET inside*/
    queryCommandEdit();
    if(!graphActivate(qbeGraph))
      messcrash("Cannot reactivate qbeGraph in qbeMenuExec.");
    return;
  }

  /* Execute query */
  if(!keySetActive(&oldSet, &look))
    myNewKeySet() ;
  if(keySetActive(&oldSet, &look)) { 
    messStatus ("Searching") ;
    newSet = query(oldSet, qbeFormQuery(resbuffer)) ;
    if (newSet != oldSet )  {
      if(keySetExists(query_undo_set) &&
	 query_undo_set != oldSet )
	keySetDestroy(query_undo_set) ;
      query_undo_set = oldSet ;
    }
    keySetShow(newSet,look) ;
  }
  else
    messout("First select a keySet, thank you") ;
}


static void qbeMenuQuit(void)
{
  QBEGET("qbeMenuQuit") ; 
  if(graphActivate(qbeGraph))
    graphDestroy () ;
  qbeGraph = 0;
  qbeDestroy();
}

static void qbeMenuDraw(void)
{
  QBEGET("qbeMenuDraw") ; 
  
  qbeDisplay () ;
  qbePick(qbelook->activebox, 0, 0, 0);
}


/*
 * qbeMetadata:
 * using current bs node in display, traverse paternity and
 * form metadata query of type ">search_class paternity_label"
 */

/*
static void qbeMetadata(int search_class) {
  BS temp ;
  TV tagval ;
  QBEGET("qbeMetadata");

  if (!qbelook->activebox || qbelook->activebox <= itemEntryBox)
    temp = qbelook->objet->root;
  else {
    tagval = array(qbelook->tagval, qbelook->activebox, TV) ;
    while (tagval->tag) {
      tagval = tagval->tag;
    }
    temp = tagval->bs;
  }

  metaGetData(search_class, qbelook->key, temp);
}


static void qbeMenuMetaFull(void) {
  qbeMetadata(_VElement);
}

static void qbeMenuMetaLong(void) {
  qbeMetadata(_VLongText);
}
*/

/*************** drawing package ***********************/


static QBE drawLook ;
static int xmax = 0 ;
static BOOL isModel ;

/* show query command on query command line */
static void qbeShowQueryCommand(void) {
  if (graphExists(querGraph)) 
    { Graph old = graphActive() ;
      graphActivate(querGraph) ;
      queryWindowShowQuery(qbeFormQuery(resbuffer)); /*calls QUERGET inside */

      graphActivate(old) ;
    }
}  

static BOOL notNumeric (char *text, char *c)
{
  char *t = text;
  while (*t) {
    if ((*t > '9' || *t < '0') && *t != '.' && *t != '-') {
      *c = *t;
      return TRUE ;
    }
    t++;
  }
  *c = 0 ;
  return FALSE ;
}

static BOOL itemEntryHandler(char *text, int unused)
{
  Graph old = graphActive() ;
  QBEGET("itemEntryHandler");
  if (strcmp(text, ""))
    item_enable = TRUE ;
  else
    item_enable = FALSE;
  /*   qbeDisplay () ; mhmp 26.01.99 */
  /*    qbePick(qbelook->activebox); mhmp 16.09*/
  /*  qbeShowQueryCommand(); mhmp 26.01.99*/
  graphActivate(old) ;
  return TRUE ;
}


static BOOL  qbeEntryHandler (char *text, int unused)
{
  TV tagval ;
  char c ;
  Graph old = graphActive() ;
  QBEGET("qbeEntryHandler");
  
  tagval = array(qbelook->tagval, nameBox /* qbelook->activebox */ , TV) ;
  if (tagval->tv_type == TV_NUMERIC_ENTRY && notNumeric(text, &c)) {
    messout ("Warning: Non-numeric character %c in numeric entry for tag (%s).",
	     c, tagval->tag ? tagval->tag->name : "no tag");
  }
  /*  qbeDisplay () ;
       qbePick(qbelook->activebox);*/
  qbePick (nameBox, 0, 0, 0) ; /* mhmp 17.09.98 */
  /*  qbeShowQueryCommand(); mhmp 26.01.99*/
  graphActivate (old) ;
  return TRUE ;
}

extern void bsSubFuseType(OBJ obj, BS bs) ;  /* gha debug 6/15/93 */

/*
 * This recursive routine traces thru BS structure and draws the 
 * tree display for a class model, with the value fields and boolean
 * tag-attributes having entry boxes.
 */
static float qbeDrawBS (BS bs, int x, float y, int prev_x, float prev_y)
{
  float yMe, old_y ;
  int xPlus = 0 ;
  char *text = 0 ;
  int box;
  int oldstyle, newstyle;
  int length = 0;
  TV tagval;
  BOOL new_tag, downNotRight;
  
  if (y == prev_y && x == prev_x)
    downNotRight = FALSE;
  else
    downNotRight = TRUE;

  old_y =  yMe = y ;
  new_tag = FALSE ;
  
  if (isModel)
    text =  name (bs->key) ; /* IS A MODEL */
  if (bs->key <= _LastC)
    text = bsText(bs) ; 
  else if (bs->key == _Int)  /* gha here */
    text = messprintf ("%d", bs->n.i) ; 
  else if (bs->key == _Float)
    text = messprintf ("%f", bs->n.f) ; 
  else if (bs->key == _DateType)
    text = bs->n.time ?  timeShow (bs->n.time) : "" ;
  else
    text = name (bs->key) ;   /* gha: need this...tag names */
  
  if (iskey (bs->key) == 2 && class(bs->key) != _VText)  {
    newstyle = BOLD;
    oldstyle = graphTextFormat(newstyle) ;
  }
  else {
    newstyle = PLAIN_FORMAT;
    oldstyle = graphTextFormat(newstyle);
  }
  
  if (text)  { 
    if (bsIsTag(bs))  
      {  /* is a tag  see systags.wrm */
	if (!class(bs->key) && (bs->key) >= 50 && !xref_tag) 
	  {  
	    box = graphBoxStart() ;
	    if (box > boxMax) 
	      {
		boxMax = box;
		new_tag = TRUE;
	      }
	    
	    tagval = array(drawLook->tagval, box, TV) ;
	    /* mhmp 16.09*/
	    if (!tagval ||/* !tagval->tag ||*/ !tagval->tag_defined) {
	      tagval = (TV) halloc(sizeof(struct TAGVALUESTUFF),graphHandle());
	      new_tag = TRUE;
	    }
	    
	    if (new_tag) {
	      tagval->box = box;
	      strcpy(tagval->name, text);
	      tagval->textformat = -1;  /* like a NULL for TextFormat */
	      tagval->tag_active = FALSE;
	      tagval->tag_defined = TRUE;
	      tagval->tv_type = TV_TAG;
	      tagval->tag = 0 ;
	      tagval->has_value = 0;
	      tagval->value_number = 0;
	      tagval->bs = bs;
	      arr(drawLook->tagval, box, TV) = tagval;
	    }
	    
	    lasttag = tagval;
	    lasttag_idx = 0;
	    
	    graphText (tagval->name, x, yMe) ;
	    
	    graphBoxEnd() ;
	    length = strlen(tagval->name);  
	    if (tagval->tag_active == TRUE)
	      graphBoxDraw(tagval->box, SELECT_FCOL, SELECT_BCOL);
	    
	  }  /* end of is tag within range */
	else
	  {
	    if (bs->key == _XREF)
	      xref_tag = TRUE;
	    else if (xref_tag == TRUE)
	      xref_tag = FALSE;
	    length = 0;  /* system and XREF tags ignored */
	  }
      }
    else {   /* is not a tag, i.e. is a value field  */

      /* gha debug 6/15/93:  eventually should add # to QBE here */
      /* if (class(bs->key) && bs->key == KEYMAKE(class(bs->key), 1))
       *	bsSubFuseType(drawLook->objet, bs);
       * else { 
       */

      /* do not show #subtypes at all */
      if (!class(bs->key) 
	  ||
	  (class(bs->key) && bs->key != KEYMAKE(class(bs->key), 1))) 
	{
	  length = 12;
	  box = graphBoxStart() ;
	  tagval = array(drawLook->tagval, box, TV); 
	  if (!tagval || !tagval->tag || !tagval->tag_defined) 
	    {
	      tagval = (TV) halloc(sizeof(struct TAGVALUESTUFF),graphHandle());
	      
	      tagval->box = box;
	      strcpy(tagval->name, "");
	      tagval->textformat = newstyle;
	      tagval->tag_active = FALSE;
	      tagval->tag_defined = TRUE;
	      if (bs->key == _Int || bs->key == _Float)        /* NUMERIC VALUE */
		tagval->tv_type = TV_NUMERIC_ENTRY;
	      else if (bs->key == _DateType)        /* DATE VALUE */
		tagval->tv_type = TV_DATE_ENTRY;
	      else
		tagval->tv_type = TV_TEXT_ENTRY;    /* else assume TEXT */
	      
	      /* tagval->tag = lasttag->tag ;mhmp 17.09.98*/
	      tagval->tag = lasttag;
	      tagval->has_value = 0 ;
	      tagval->value_number = ++lasttag_idx ;
	      tagval->bs = bs;
	      array(drawLook->tagval, box, TV) = tagval;
	    }
	  else if (tagval->tag) 
	    {
	      if (strcmp(tagval->name,"")) 
		tagval->tag->has_value = tagval->value_number;
	      else if (lasttag_idx == 1)
		tagval->tag->has_value = 0;
	    }

	if (x + length > xmax)
	  xmax = x + length ;
      
	/* mieg -> editor
	box = graphTextScrollEntry (tagval->name, BUFFER_SIZE -1,
				    length, x, yMe, qbeEntryHandler) ;
	*/
	nameBox =  graphTextScrollEditor ("",tagval->name,BUFFER_SIZE -1, length, x, yMe, qbeEntryHandler) ;
	box = nameBox ;
	graphAssociate (tagval->name, assVoid(box)) ;

	array(drawLook->tagval, box-1, TV) = tagval;
	array(drawLook->tagval, box, TV) = tagval; 
	array(drawLook->tagval, box+1, TV) = tagval;
	if (box+1 > boxMax)  /* add 1 for cursor */
	  boxMax = box+1;
	
	drawLook->classe = class(bs->key) ; 
	graphBoxEnd() ;
      }   /* end of class stuff */
    } /* end of else (is not a tag) */
    
    yMe += 1.2; /*mhmp 27.10.98   1.5 --> 1.2*/
    
    if (length > xPlus)
      xPlus = length;
  }
  
  graphTextFormat(oldstyle) ;
  
  xPlus += x;
  if (xPlus > xmax)
    xmax = xPlus ;
  
  xPlus = xPlus + 6 - xPlus%4 ;   /* "Tabify" the next position to the right */

  if (bs->right) /* to the right at same y */
    y = qbeDrawBS (bs->right, xPlus, y, xPlus, y) ; 

  if (yMe > y) {
    y = yMe ;
    yMe -= 1.2; /* mhmp 27.10.98 1.5 --> 1.2 */
  }

  if (bs->down)      /* go to previous x position */
    y = qbeDrawBS (bs->down, x, y, xPlus, yMe) ;

  return y ;
}

#define BUTTON_OFFSET 4.3  /* should be at least 4 */
#define CHOICE_OFFSET 2  /* should be about 2 */
#define ITEM_OFFSET   2.5    /* should be about 2 */
static void qbeDisplay (void)
{
  int ymax = 1 ;
  float x1, y1, x2, y2;
  int old;
  int fromBox ;
  QBEGET("qbeDisplay");
  
  graphClear () ;
  
  graphButtons(QBE_Buttons, 1, 0.5, 62) ;

  graphText("From:", 1, BUTTON_OFFSET + 0.2);
  old = graphTextFormat(BOLD);
  fromBox = graphButton(currentFromName(), 
			 fromButtonResult, 
			 8, BUTTON_OFFSET);
  graphTextFormat(old);
  graphBoxFreeMenu(fromBox,(FreeMenuFunction) fromMenuResult, fromMenu);
  graphBoxDim(fromBox, &x1, &y1, &x2, &y2);
  graphRectangle(x1-0.4, y1-0.2, x2+0.4, y2+0.3);
  
  graphText("Class:", 18, BUTTON_OFFSET + 0.2);
  qbe_classBox = graphBoxStart();
  graphRectangle(25-0.4, BUTTON_OFFSET-0.1, 47+0.4, BUTTON_OFFSET+1.5);
  graphBoxEnd();
  graphBoxFreeMenu(qbe_classBox, (FreeMenuFunction)classesMenuResult,
		   classesMenu);
  strcpy(qbe_class_buffer, currentClassName());
  qbe_classEntryBox = graphTextScrollEditor("",qbe_class_buffer, BUFFER_SIZE - 1,
					   20, 25, BUTTON_OFFSET + 0.2,
					   qbe_classEntryHandler);
  qbe_classTriBox = graphDownSelTriangle(TRUE, 45.8, BUTTON_OFFSET + 0.2);

  isModel = (KEYKEY(qbelook->key) == 0) ;

  if (qbelook->objet->root && qbelook->objet->root->right)  { 
    drawLook = qbelook ;

    itemBox = graphBoxStart() ;
    graphText("ITEM NAME :", 1, BUTTON_OFFSET + CHOICE_OFFSET );
    graphBoxEnd();

    itemEntryBox = 
      graphTextScrollEditor("",item_buffer,
			   BUFFER_SIZE-1, 30, 13,
			   BUTTON_OFFSET + CHOICE_OFFSET, 
			   itemEntryHandler);
    if (itemEntryBox > boxMax)
      boxMax = itemEntryBox ;

    graphLine(1, BUTTON_OFFSET + CHOICE_OFFSET + 1.3,
	      59, BUTTON_OFFSET + CHOICE_OFFSET + 1.3);

    if (item_enable)
      graphBoxDraw(itemBox, SELECT_FCOL, SELECT_BCOL);
    else
      graphBoxDraw(itemBox, NORMAL_FCOL, NORMAL_BCOL);

    if (!drawLook->text)
      drawLook->text = (char *) halloc (256, graphHandle()) ;
    else
      memset(drawLook->text, 0, (mysize_t) 256) ;
    
    xmax = 0 ;
    ymax = (int) qbeDrawBS (qbelook->objet->root->right, 
			    2, BUTTON_OFFSET + CHOICE_OFFSET + ITEM_OFFSET, 
			    2, BUTTON_OFFSET + CHOICE_OFFSET + ITEM_OFFSET ) ;
  }
  
  graphTextBounds (xmax, ymax) ;
  graphRedraw() ;
  
  if (qbelook->activebox >= boxMax)
    qbelook->activebox = 0 ;
  
  isModel = FALSE ;
}

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

static void qbePick (int box, double x_unused, double y_unused, int modifier_unused)
{
  TV tagval;
  int oldstyle;
  int i;
  KEY key;

  QBEGET("qbePick") ;

  if (box <= 0) {
      graphBoxDraw(qbelook->activebox, NORMAL_FCOL, NORMAL_BCOL);
      qbelook->activebox = 0;
    return ;
  }
  else
    if (box > boxMax) { 
      messerror("qbePick received a box number, %d, beyond range.\n",box);
      return ;
    }
  
  qbelook->activebox = box;
  
  if (qbelook->activebox == qbe_classEntryBox)
    /*    boxMax = 0 ;  mhmp 17.09.98 */
    /* graphTextScrollEntry(qbe_class_buffer, 0, 0, 0, 0, 0) */  ;
  else if (qbelook->activebox == qbe_classTriBox) 
    {
      /* graphTextScrollEntry(qbe_class_buffer, 0, 0, 0, 0, 0); */
      if (graphSelect(&key, classesMenu))
	classesMenuResult(key, 0);
    }
  else if (qbelook->activebox == qbe_classBox)
    {
      /*      boxMax = 0 ;  mhmp 17.09.98 */
      return;
    }
  else if (qbelook->activebox == itemBox) 
    {
      if (item_enable) {
	graphBoxDraw(qbelook->activebox, NORMAL_FCOL, NORMAL_BCOL);
      item_enable = FALSE;
      }
      else {
	graphBoxDraw(qbelook->activebox, SELECT_FCOL, SELECT_BCOL);
	item_enable = TRUE;
      }
    }
  else if (qbelook->activebox == itemEntryBox)
    /* graphTextScrollEntry(item_buffer, 0, 0, 0, 0, 0)  */ ;
  else 
    {
      /* every text entry box is 3 boxes, outer, shading, cursor */
      for (i = qbelook->activebox; i > qbelook->activebox - 3; i--)  
	{
	  tagval = arr(qbelook->tagval, i, TV);
	  if (tagval) break;
	}
      if (tagval && tagval->tag_defined)  
	{
	  if (tagval->tv_type == TV_TEXT_ENTRY ||
	      tagval->tv_type == TV_NUMERIC_ENTRY ||
	      tagval->tv_type == TV_DATE_ENTRY
	      ) 
	    {         /* is a value */
	      oldstyle = graphTextFormat(tagval->textformat);
	      /* mieg->editor 	graphTextScrollEntry(tagval->name, 0, 0, 0, 0, 0); */
	      graphTextFormat(oldstyle);
	      
	      if (tagval->tag && strcmp(tagval->name, "")) {
		graphBoxDraw(tagval->tag->box, SELECT_FCOL, SELECT_BCOL);
		tagval->tag->tag_active = TRUE;
	      }
	    }
	  else {                   /* is a tag */
	    if (tagval->tag_active) {
	      graphBoxDraw(qbelook->activebox, NORMAL_FCOL, NORMAL_BCOL);
	      tagval->tag_active = FALSE;
	    }
	    else {
	      graphBoxDraw(qbelook->activebox, SELECT_FCOL, SELECT_BCOL);
	      tagval->tag_active = TRUE;
	    }	
	    qbeShowQueryCommand();
	  }
	}
      else
	messerror("Bad Item Picked--not a tag or entry box %d\n", 
		  qbelook->activebox);
    }
}


/*********************************************************************/
/********************  public routines   *****************************/

static BOOL qbeCreate2 (void)
{
  QBE newQbeLook;
  KEYSET oldSet = 0 ;
  KeySetDisp look;
  int i;
  
  if (graphActivate(qbeGraph)) { 
      graphPop() ;
      return TRUE ;
    }
  
  qbeGraph = displayCreate ("DtQueryByExample"); 
  
  newQbeLook=(QBE)messalloc(sizeof(struct QBESTUFF));
  
  newQbeLook->magic = QBE_MAGIC;
  
  if (graphExists(qbuildGraph) && (qbuildGraph != 0)
      && (pickType(KEYMAKE(qbuild_selected_class, 0)) == 'B')) 
    qbe_class = qbuild_selected_class;
  else  
    {
      keySetActive(&oldSet,&look);
      if (!oldSet ||
	  (!(qbe_class = class(keySet(oldSet, 0))) 
	   || pickType(KEYMAKE(qbe_class, 0)) != 'B')) 
	{
	  for (i = 1; i <= qbeClassMax(); i++) 
	    {
	      qbe_class = pickWord2Class(CLASSLIST_NAME(i));
	      if ( pickType(KEYMAKE(qbe_class, 0)) == 'B' &&
		  iskey(KEYMAKE(qbe_class, 0))==2) 
		break ;
	    }
	}
    }

  newQbeLook->key = KEYMAKE(qbe_class, 0);

  if (pickType (newQbeLook->key) != 'B' 
      || 
      !(newQbeLook->objet = bsCreate(newQbeLook->key))) {

    if(graphActivate(qbeGraph))
      graphDestroy () ;
    messout("Bad model for class %s.\n Removing QBE Window.", 
	    pickClass2Word(qbe_class));
    return FALSE;
  }
  
  newQbeLook->graph = qbeGraph;
  
  /* init classlist */
  qbeClassMax() ;

  graphRetitle (messprintf("Query By Example:   %s", 
			   className(newQbeLook->key), name(newQbeLook->key)));
  graphRegister (DESTROY, qbeDestroy) ;
  graphRegister (PICK,(GraphFunc) qbePick) ;
  graphMenu (QBE_Menu) ;
  graphHelp ("Query_By_Example") ;
  
  graphAssociate (&QBE_MAGIC, newQbeLook) ;
  newQbeLook->activebox = 0 ;
  newQbeLook->tagval = arrayCreate(1, TV);
  array(newQbeLook->tagval, 0, TV) = NULL;
  
  classesMenuSet();
  lasttag = NULL;
  lasttag_idx = 0;
  xref_tag = FALSE;
  boxMax = 0;
  item_enable = FALSE;

  return TRUE;
}

void qbeCreate (void)
{
  if (!qbeCreate2 ())
    return;
  from_keyset = FALSE ;
  qbeDisplay () ;
}

void qbeCreateFromKeySet (KEYSET ksAlpha, int i0)
{ KEY *kp ;
  int i = keySetMax (ksAlpha) - i0 ;

  from_keyset = TRUE ;
  if (i0 >= 0 && i > 0)
    { kp = arrp (ksAlpha, i0, KEY) - 1 ;
      while (kp++, i--)
	if (pickType (*kp) == 'B')
	  { qbe_class = class (*kp) ;
	    break ;
	  }
    }
  if (!qbeCreate2 ())
    return;
  from_keyset = TRUE ;
  qbeDisplay () ;
}

/*****  end of qbedisp.c *****/


 
