/*  File: forest.c
 *  Author: Michel Potdevin et Jean Thierry-Mieg (mieg@mrc-lmba.cam.ac.uk)
 *  Copyright (C) J Thierry-Mieg and R Durbin, 1995
 * -------------------------------------------------------------------
 * 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@sanger.ac.uk and
 *	Jean Thierry-Mieg (CRBM du CNRS, France) mieg@crbm.cnrs-mop.fr
 *
 * Description: View multiple trees in a user configurable way
 * Exported functions:
 * HISTORY:
 * Last edited: Jul  9 11:55 2003 (rnc)
 * Created: Sept 20 1996 (mieg)
 *-------------------------------------------------------------------
 */

/* %W% %G% */

/*
#define ARRAY_CHECK
#define MALLOC_CHECK
*/

#include "acedb.h"
#include "aceio.h"
#include "pick.h"
#include "bs_.h"
#include "bs.h"
#include "a.h"
#include "biblio.h"
#include "lex.h"
#include "query.h"
#include "map.h"
#include "client.h"
#include "bindex.h"
#include "bitset.h"
#include "whooks/sysclass.h"
#include "whooks/classes.h"
#include "whooks/systags.h"
#include "whooks/tags.h"
#include "utils.h"               /* for lexstrcmp */

#include "display.h"
#include "keysetdisp.h"
#include "main.h"

BITSET_MAKE_BITFIELD		  /* define bitField for bitset.h ops */

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

#define graphBoxBox(_box) { \
               float _x1, _y1, _x2, _y2 ; \
               graphBoxDim (_box, &_x1, &_y1, &_x2, &_y2) ; \
               graphRectangle (_x1 - 0.4, _y1 - 0.1, _x2 + 0.4, _y2 + 0.1) ; \
                }

#define LINE_LENGTH 40 
#define MODELFLAG     0x0002
#define MODELNEXTFLAG 0x0004
#define MODELNEXTTAG  0x0008
#define DISCARDFLAG   0x0010

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

		/* addresses of next two used as unique ids */
static int GRAPH2FORESTLOOK_ASSOC ; /* used to find the forest LOOK 
				       from the active graph */
static int FORESTLOOK_MAGIC ;	/* used to verify the forest LOOK */

#define FORESTGET(name) FOREST forest ; \
	if (!graphAssFind (&GRAPH2FORESTLOOK_ASSOC, &forest)) \
          messcrash ("forest-graph not found in %s",name) ; \
        if (!forest) \
          messcrash ("%s received a null forest-pointer",name) ; \
        if (forest->magic != &FORESTLOOK_MAGIC) \
          messcrash ("%s received a non-magic forest-pointer",name)


/************************************************************/
/*  keep destroy and definition alike */

typedef struct segStruct
{
  KEY key ;
  int color, flag ;
  int n ;
  BOOL yellow ;
} SEG ;

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

typedef struct ForestStruct
{ 
  void *magic ;			/* == &FORESTLOOK_MAGIC */
  char mot[1024], *title, cq [20] ;
  KEY model ; int classe ;
  Graph graph ;
  MAP map ;

  BOOL showModel, hide, isDownRed, isPageDownRed, 
                  isUpRed, isPageUpRed, isSelect ;

  int  
   firstBox, isGoto,
   max, maxDiscard, nBottom, nTop, nUp,nBegin, nLast, nSelected, 
   lineBegin, lineBeginOld ; 
/*mhmp 27.11.97 lineBeginOld: 
  dessin identique pour un middleclick trop bas a la fin */
  Stack s ;
  KEYSET fSet, alphaSet, selectSet, greenTags, yellowTags, selectedTags,
         tagState, modelTags ;
  Array box2segs, segs, discard, unDiscard, lineNumber, size, topLines ;
  Array topLinesIndex ;
  BitSet show, unSelect, verified ;
} *FOREST ;

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

static int gotoBox, lightBox,upBox, downBox, 
           pageUpBox, pageDownBox, discardBox ;

static BOOL isRecentre = FALSE ;
static  int  halfHeight ;
static double yOld ;

static void forestUp (void) ;
static void forestDown (void) ;
static void forestPageUp (void) ;
static void forestPageDown (void) ;
static void forestUnverify (FOREST forest) ;
static BOOL forestMayDisplay (FOREST forest, int n) ;
static void forestAddTags (FOREST forest, int nn)  ;
static void forestKbd (int k, int modifier_unused) ;
static void forestSelect (void) ;
static void forestColor (FOREST  forest) ;

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

static void forestDestroy(void)
{ 
  FORESTGET ("forestDestroy") ;

  keySetDestroy (forest->fSet) ;
  keySetDestroy (forest->alphaSet) ;
  keySetDestroy (forest->selectSet) ;
  keySetDestroy (forest->greenTags) ;
  keySetDestroy (forest->yellowTags) ;
  arrayDestroy (forest->topLines) ;
  arrayDestroy (forest->topLinesIndex) ;/*mhmp 03.12.97*/
  stackDestroy (forest->s) ;
  arrayDestroy (forest->segs) ;
  arrayDestroy (forest->box2segs) ;
  arrayDestroy (forest->modelTags) ;
  arrayDestroy (forest->tagState) ;
  bitSetDestroy (forest->verified) ;
  bitSetDestroy (forest->discard) ;
  bitSetDestroy (forest->show) ;
  bitSetDestroy (forest->unSelect) ;
  arrayDestroy (forest->unDiscard) ;
  arrayDestroy (forest->lineNumber) ;
  arrayDestroy (forest->size) ;
  forest->magic = 0 ;
  messfree (forest->title) ;
  mapDestroy (forest->map) ;
  messfree (forest) ;

  return;
} /* forestDestroy */

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

static int fState(FOREST forest, KEY key)
{
  if (key > 0 && key < arrayMax(forest->tagState))
    return array (forest->tagState, key, char) ;
  return 0 ;
}

/***********************************************************************/
/********************** actions  ***************************************/
static void forestDoRedraw (FOREST forest) ;
static void forestDoRedraw2 (FOREST forest) ;
static void forestDraw (FOREST forest) ;
static void forestRedraw (void) ;

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

static void forestFollow (FOREST forest, int box)
{ 
  int i ;
  SEG *seg;
  BOOL isDisp = FALSE ;

  if (box < 1 ||  box >= arrayMax(forest->box2segs))
    return ;

  seg = arr(forest->box2segs,box,SEG*) ;
  if (seg->n)
    {
      graphPostBuffer (stackText(forest->s, seg->n)) ;
      if (seg->yellow)
	{
	  isDisp = TRUE ; /* mhmp 15.06.98 */
	  if (pickType(seg->key) == 'B')
	    display (seg->key, 0, "TREE") ;
	  else
	    display (seg->key, 0, 0) ;
	}
      else 
	for (i = forest->firstBox ; i < arrayMax(forest->box2segs) ; i++)
	  {
	    SEG *seg1 = arr(forest->box2segs,i,SEG*) ;
	    
	    if (seg1 && !(seg1->flag & MODELFLAG))
	      {
		if (seg1->yellow)
		  graphBoxDraw (i, BLACK, WHITE) ;
		seg1->yellow = FALSE ;
		if (!lexstrcmp(stackText(forest->s, seg1->n),
			       stackText(forest->s, seg->n)))
		  {
		    graphBoxDraw (i, BLACK, YELLOW) ;
		    seg1->yellow = TRUE ;
		  }
	      }
	  }
    }
  else if (seg->key == forest->model)
    {
      display (seg->key, 0, "TREE") ;
      isDisp = TRUE ; /* mhmp 05.05.98 */
    }
  if (!isDisp) 
    {
      /* mhmp 28.05.98 necessaire apres un mot "trop long" (winPos)*/
      memset(forest->mot, 0, 1024) ;
      strncpy (forest->mot,"", 1023) ;
      graphTextEntry (forest->mot, 0, 0, 0, 0) ;
      graphTextEntry (forest->mot, 0, 0, 0, 0) ;
      /**********/
      memset(forest->mot, 0, 1024) ;
      strncpy (forest->mot, stackText(forest->s, seg->n), 1023) ;
      graphTextEntry (forest->mot, 0, 0, 0, 0) ;
      graphEntryDisable() ; 
      graphRegister (KEYBOARD, forestKbd) ;    
      /* mhmp 07.01.98 */
      forestColor(forest) ;
    }
}

static void forestColor (FOREST  forest)
{
  int i ;
  SEG *seg ;
  char buf [200] ;
  
  *buf = 0 ;
  if (forest->mot && *forest->mot)
    {
      strcpy (buf, forest->mot) ;
      strcat (buf, "*") ;
    }
  for (i = forest->firstBox, seg = arr(forest->box2segs,i,SEG*) ; 
       i < arrayMax(forest->box2segs) ; seg++, i++) 
    {
      seg = arr(forest->box2segs,i,SEG*) ;
      if (seg) seg->yellow = FALSE ;
      if (seg && !(seg->flag & MODELFLAG))
	if (*buf && pickMatch (stackText(forest->s, seg->n), buf))
	  { graphBoxDraw (i, BLACK, YELLOW) ; seg->yellow = TRUE ; }
    }
  
  return;
} /* forestColor */


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


static void callBiblio (void)
{ 
  KEYSET ks ;
  char *cp ;
  ACEIN title_in = 0;
  FORESTGET ("callBiblio") ;

  cp = "From forest" ;
  if ((title_in = messPrompt("Title","","t", 0)))
    cp = aceInWord(title_in);
  ks = keySetCopy (forest->selectSet) ;
  keySetSort (ks) ;
  keySetCompress(ks) ;

  biblioKeySet (cp, ks) ;
  keySetDestroy (ks) ;
  if (title_in)
    aceInDestroy (title_in);

  return;
} /* callBiblio */


static void forestPick (int box, double x, double y, int modifier_unused) 
{ 
  KEY tag ;
  SEG *seg ;
  FORESTGET ("forestPick") ;
  
  if (box == gotoBox)
    { graphTextEntry (forest->cq, 0, 0, 0, 0) ;
      forest->isGoto = TRUE ;
    }
  else  if (box == lightBox)
    graphTextEntry (forest->mot, 0, 0, 0, 0) ;
  else if (box == upBox) 
    forestUp () ;
  else if (box == downBox) 
    forestDown () ;
  else if (box == pageUpBox) 
    forestPageUp () ;
  else if (box == pageDownBox) 
    forestPageDown () ;
  else  if (box < 1 ||                /* check valid box */
      box >= arrayMax(forest->box2segs) ||
      !(seg = arr(forest->box2segs, box, SEG*))
      )
    return ;
  else if (seg->flag & MODELFLAG)
    { tag = seg->key ;

      switch (fState (forest, tag))
	{
	case 0:
	  break ;
	case 1:
	  if (seg->flag & MODELNEXTFLAG)
	    array (forest->tagState, tag, char) = 2 ; 
	  else if (seg->flag & MODELNEXTTAG)
	    array (forest->tagState, tag, char) = 3 ;
	  else
	    array (forest->tagState, tag, char) = 4 ;
	  break ;
	case 2: case 3:
	  array (forest->tagState, tag, char) = 4 ;
	  break ;
	case 4: 
	  array (forest->tagState, tag, char) = 1 ;
	  break ;
	}
      forestUnverify(forest) ;
      forestDoRedraw (forest) ;
    }
  else if (seg->flag == DISCARDFLAG)
    { bitSet (forest->discard, seg->key) ;
      array (forest->unDiscard, forest->maxDiscard, int) = seg->key ;
      forest->maxDiscard ++ ;
   /* mhmp 27.11.97 pour eviter un ecran vide en discardant le dernier */
      if (forest->isDownRed && !forest->isUpRed)
	{
	  forest->lineBeginOld = 0 ; /* mhmp 17.12 */
	  forestUp () ;
	}
      else
	forestDoRedraw (forest) ;
    }
  else if (box > forest->firstBox) 
    forestFollow (forest, box) ;

  return;
} /* forestPick */

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

static void forestMiddleDragFast  (double x, double y)
{ 
  FORESTGET ("forestMiddleDragFast") ;

  graphXorLine (0, yOld, forest->map->thumb.x, yOld) ;
  yOld = y ;
  graphXorLine (0, yOld, forest->map->thumb.x, yOld) ;

  return;
} /* forestMiddleDragFast */


static void forestMiddleUpFast  (double x, double y)
{ 
  int n, nn = 0 ;
  KEY selectKey ;
  FORESTGET ("forestMiddleUpFast") ;

  n = WHOLE2MAP (forest->map, y) ;
  if (n < 0) n = 0 ;
  if (n >= forest->nSelected)
    n =  forest->nSelected -1 ; 

 /* mhmp 17.12.97 pb avec keyset melimelo Taich */
  selectKey = keySet (forest->selectSet, n) ;
  arrayFind (forest->alphaSet, &selectKey, &nn, keySetAlphaOrder) ;
  forestMayDisplay (forest,nn) ;
  forestDoRedraw2 (forest) ;
  /********************************
  mhmp 01.12.97 attention pour forestMayDisplay n index dans forest->alphaSet

  while (n > 0 && !forestMayDisplay (forest,n))
    n-- ;
  selectKey = keySet (forest->selectSet, n) ;
  arrayFind (forest->alphaSet, &selectKey, &n, keySetAlphaOrder) ;
  
  forest->nTop = n ;
  forestDoRedraw (forest) ;

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

  /* mhmp 02.12.97 pour avoir le "vrai" forest->nTop */
  while (n > 0)
    { 
      selectKey = keySet (forest->selectSet, n) ;
      arrayFind (forest->alphaSet, &selectKey, &nn, keySetAlphaOrder) ;
      if (forestMayDisplay (forest,nn))
	break ;
      n-- ;
    }
  if (n == 0)
    while (n < forest->nSelected)
      { 
	selectKey = keySet (forest->selectSet, n) ;
	arrayFind (forest->alphaSet, &selectKey, &nn, keySetAlphaOrder) ;
	if (forestMayDisplay (forest,nn))
	  break ;
	n++ ;
      }
  forest->nTop = nn ; 
  forestDoRedraw (forest) ;

  return;
} /* forestMiddleUpFast */


static void forestMiddleDrag  (double x, double y)
{ 
  FORESTGET ("forestMiddleDrag") ;

  graphXorLine (forest->map->thumb.x, yOld, mapGraphWidth, yOld) ;
  yOld = y ;
  graphXorLine (forest->map->thumb.x, y, mapGraphWidth, y) ;

  return;
} /* forestMiddleDrag */


static void forestMiddleUp  (double x, double y)
{
  int i, n, nn1 ;
  int exTop ;
  FORESTGET ("forestMiddleUp") ;

  if (!forest->isPageDownRed || !( y > halfGraphHeight))
    {
      n = y - halfGraphHeight ; 
      if (!arrayMax(forest->topLines)) return ;
      nn1 =  0 ; 
      /* mhmp 04.12.97 -1 a cause du decalage de 1 vers la gauche */
      for (i = 0 ; i < arrayMax(forest->topLines) - 1 ; i++)
	if (array (forest->topLines,i, int) > n)
	  break ; 
	else
	  nn1 = i ;
      forest->lineBegin =  n - array(forest->topLines,nn1, int) ;
      if (forest->lineBegin < 0)
	forest->lineBegin = 0 ;
      forest->nTop = array(forest->topLinesIndex, nn1,  int) ;
    }
  else
    forest->lineBegin = forest->lineBeginOld ;
  isRecentre = TRUE ;
  forest->showModel = FALSE ; 
  forest->lineBeginOld = forest->lineBegin ;
  exTop = forest->nTop ;
  forestDoRedraw2 (forest) ;
  /*mhmp bricolage 08.12.97 voir keyset bug */
  forest->nTop = exTop ;
  forest->lineBegin = forest->lineBeginOld ;
  isRecentre = TRUE ;
  forestDoRedraw (forest) ; /*mhmp 08.12.97*/

  return;
} /* forestMiddleUp */


static void forestMiddleDown (double x, double y)
{ 
  FORESTGET ("forestMiddleDown") ;

  if (!forest->nSelected)
    { graphRegister (MIDDLE_DRAG, 0) ;
      graphRegister (MIDDLE_UP, 0) ;
    }
  else if (x > forest->map->thumb.x)
    { 
      yOld = y ;
      graphXorLine (forest->map->thumb.x, y, mapGraphWidth, y) ;
      graphRegister (MIDDLE_DRAG, forestMiddleDrag) ;
      graphRegister (MIDDLE_UP, forestMiddleUp) ;
    }
  else
    { 
      yOld = y ;
      graphXorLine (forest->map->thumb.x, y, mapGraphWidth, y) ;
      graphRegister (MIDDLE_DRAG, forestMiddleDragFast) ;
      graphRegister (MIDDLE_UP, forestMiddleUpFast) ;
    }

  return;
} /* forestMiddleDown */
 
/***********************************************************************/

static void forestPrint(void)
{ 
  FORESTGET ("forestPrint") ;

  if (!forest->alphaSet || !keySetMax(forest->alphaSet))
    { messout ("Nothing to print, this forest is empty, sorry") ;
      return ;
    }
  forestDraw(forest);

  return;
} /* forestPrint */

/********************** menu actions  **********************************/

static void forestUnDiscard (void)
{
  int i ;
  FORESTGET ("forestUnDiscard") ;
  
  if (forest->maxDiscard)
    {
      i = array (forest->unDiscard, forest->maxDiscard - 1, int) ;
      bitUnSet (forest->discard, i) ;
      forest->maxDiscard -- ;
/*mhmp commencer par le dernier undiscarde 02.12.97*/
      forest->nTop = i ;
    }
  forestDoRedraw (forest) ;

  return;
}

/********************** menu actions  **********************************/

static void forestSaveAs (void)
{ 
  KEY key ;
  char *cp ;
  KEYSET ks ;
  int ii,j, nn ;
  ACEIN name_in;
  FORESTGET ("forestSaveAs") ;

  if (!forest->nSelected)
    messout ("Nothing to save") ;
  else 
    { 
      if (!checkWriteAccess())
	return ;

      if (!(name_in = messPrompt("Save the current set in the KeySet class\n"
				 "under the name:","","t", 0)))
	return ;
      cp = aceInWord(name_in) ;
      if (strlen(cp) == 0)
	{
	  aceInDestroy (name_in);
	  return ;
	}
      if (!lexaddkey (cp,&key,_VKeySet) &&
	  !messQuery ("This keyset already exists, "
		      "should I overwrite it ?"))
	{
	  aceInDestroy (name_in);
	  return ;
	}

      aceInDestroy (name_in);

      if (!key)
	return ;
      
      if (!lexlock(key))
	{ messout ("Sorry, this key is locked elsewhere") ;
	  return ;
	}
      ks = keySetCreate () ;
      ii = keySetMax(forest->alphaSet) ;
      nn = 0 ; j = -1 ;
      while (j++,ii--)
	if (!bit (forest->discard, j) && !bit(forest->unSelect, j))
	  keySet(ks, nn++) = keySet(forest->alphaSet,j) ;
      keySetSort (ks) ;
      keySetCompress(ks) ;
      arrayStore (key,ks,"k") ;
      lexunlock (key) ;

      keySetNewDisplay (ks,name(key)) ;
    }

  return;
} /* forestSaveAs */

/***********************************************************************/
static void forestShowModel (void)
{
  FORESTGET ("forestShowModel") ;
  forest->showModel = !forest->showModel ;
  forestDoRedraw (forest) ;
}

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

static void forestDoHide (BOOL hh)
{
  int i ;
  char *cp ;
  FORESTGET ("forestDoHide") ;

  if ((forest->hide = hh))
    {
      i = arrayMax(forest->tagState) ;
      cp = arrp(forest->tagState, 0, char) - 1 ;
      while(cp++, i--)
	if (*cp) *cp = 4 ;
    }
  else
    {
       i = arrayMax(forest->tagState) ;
      cp = arrp(forest->tagState, 0, char) - 1 ;
      while(cp++, i--)
	if (*cp) *cp = 1 ;
    }
  forestUnverify(forest) ;
  forestDoRedraw (forest) ;

  return;
} /* forestDoHide */

static void forestTagsHide (void)
{ forestDoHide(TRUE) ; }

static void forestTagsUnHide (void)
{ forestDoHide(FALSE) ; }

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

static void forestUp (void)
{ 
  int ii ;  
  FORESTGET ("forestUp") ;

  if (forest->isUpRed) return ;
  ii = forest->nTop ;
  while (ii--)
    if (forestMayDisplay (forest,ii))
      {
	forest->nTop = ii ;
	break ;
      }
  forestDoRedraw(forest) ;
} /* forestUp */

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

static void forestDown (void)
{ 
  int ii ;
  FORESTGET ("forestDown") ;
  
  if (forest->isDownRed) return ;
  ii = forest->nTop ;
  while (++ii < forest->max)
    if (forestMayDisplay (forest,ii))
      { forest->nTop = ii ; break ; }  
  forestDoRedraw(forest) ;
}

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

static void forestGoto (char *txt)
{ 
  char *cc ;
  KEY key ;
  int i1, i2, i3, jmax, cmp ;
  FORESTGET ("forestGoto") ;
 
  jmax = keySetMax (forest->alphaSet) ;

  /* mhmp 17.12.97 solution la moins mauvaise */
  /* lexstrcmp pour tomber sur ceux qui existent Bond7 Bond68 Bond007*/
    i1 = 0 ;
  i3 = jmax -1 ;
  
  while (TRUE)
    {
      i2 = (i1 + i3) / 2 ;
      key = keySet (forest->alphaSet, i2) ;
      cc = name (key) ;
      cmp = lexstrcmp (cc, forest->cq) ;
      if (!cmp)
	goto found ;
      else
      if (i2 == i1 || i2 == i3)
	break ;
      else
      if (cmp < 0)
	i1 = i2 ;
      else
	i3 = i2 ;

    }
  /* strcasecmp pour B03 B04 B041 B054  */
  i1 = 0 ;
  i3 = jmax -1 ;

  while (TRUE)
    {
      i2 = (i1 + i3) / 2 ;
      key = keySet (forest->alphaSet, i2) ;
      cc = name (key) ;
      cmp = strcasecmp (cc, forest->cq) ;
      if (!cmp)
	goto found ;
      else
      if (i2 == i1 || i2 == i3)
	break ;
      else
      if (cmp < 0)
	i1 = i2 ;
      else
	i3 = i2 ;

    }

    /* strncasecmp  pour passer au suivant */
    i2++ ;
    i3 = forest->nTop + 1 ;
    while (i3 < jmax && !forestMayDisplay (forest,i3))
      i3++ ;    
    if (i3 < jmax)  
      {
	key = keySet (forest->alphaSet, i3) ;
	cc = name (key) ; 
	if (!strncasecmp(cc,txt,strlen(txt)))
	  i2 = i3 ;
      }
found:
    forestMayDisplay (forest,i2) ;
    forestDoRedraw2 (forest) ; /*mhmp 18.12  pb avec keyset melimelo Taich*/
    while (i2> 0 && !forestMayDisplay (forest,i2))
      i2-- ;
    forest->nTop = i2 ;
    forest->isGoto = 2 ;  /* survives one redraw */
    forestDoRedraw (forest) ;
} /* forestGoto */


static void forestLight (char *txt)
{ 
  FORESTGET ("forestLight") ;

  forestDoRedraw (forest) ;

  return;
} /* forestLight */

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

static void forestPageUp (void)
{
  FORESTGET ("forestPageUp") ;

  if (forest->isPageUpRed)
    return ;

  forestMiddleUp (mapGraphWidth, topMargin + 2) ;/* mhmp 04.12.97*/

  return;
} /* forestPageUp */

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

static void forestPageDown (void)
{
  FORESTGET ("forestPageDown") ;
  
  if (forest->isPageDownRed)
    return ;

  forestMiddleUp (mapGraphWidth, mapGraphHeight) ;
  
  return;
} /* forestPageDown */

/***********************************************************************/
static void forestKbd (int k, int modifier_unused) /*mh*/
{
  FORESTGET ("forestKbd") ;

  switch (k)
    {
    case UP_KEY :
      forestUp () ;
      break ;
    case DOWN_KEY :
      forestDown () ;
      break ;
    case PAGE_UP_KEY :
    case LEFT_KEY :
      forestPageUp () ;
      break ;
    case PAGE_DOWN_KEY :
    case RIGHT_KEY :
      forestPageDown () ;
      break ;
    default:
      return ;
    }
} /* forestKbd */

/***********************************************************************/
/*******************  query/select system ******************************/

/***********************************************************************/
/* i order the green tags by depth, this will optimize bIndexSearch
  by hitting the top tags first
  */
static void forestTagsSort (int classe, KEYSET ks)
{
  KEY *kp, tag ;
  KEYSET path ;
  int i, n = keySetMax(ks), p ;

  if (n<2) return ;
  i = n ;
  kp = arrp(ks, 0, KEY) - 1 ;
  while (kp++, i--)
    {
      tag = *kp ;
      path = bsGetPath (classe, tag) ;
      p = path ? keySetMax(path) : 0 ; /* p is the length of the path to the tag */
      if (p > 250) p = 250 ;
      *kp = KEYMAKE (p, tag) ;
      keySetDestroy (path) ;
    }
  keySetSort (ks) ; /* sorts on class then tag value */
  i = n ;
  kp = arrp(ks, 0, KEY) - 1 ;
  while (kp++, i--)
    *kp = KEYKEY (*kp) ;

  return;
} /* forestTagsSort */

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

static void forestGetYellowTags (FOREST forest)
{
  KEY *kp ;
  int i , ii = 0, classe ;
  KEYSET ks = keySetCreate();
  KEYSET tmp;
  
  keySetDestroy (forest->yellowTags) ;
  classe = class(keySet(forest->alphaSet, 0)) ; /* should be current model */
  tmp = bsTagsInClass (classe) ;
  if (!tmp)
    return ;
  ii = 0 ; i = keySetMax(tmp) ; kp = arrp(tmp, i - 1, KEY) + 1 ;  
  while (kp--, i--)
    switch  (fState(forest,*kp))
      {
      case 1: case 2:
	keySet(ks, ii++) = *kp ;
	break ;
      default:
	break ;
      }
  forestTagsSort(classe, ks) ;
  forest->yellowTags = ks ;
  keySetDestroy (tmp) ;

  return;
} /* forestGetYellowTags */

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

static BOOL forestQueryKey (KEYSET tags, KEY key)
{
  KEY *gtp ;
  int i ;
  OBJ obj = 0 ;
  int found = 1 ;
  
  if (!key || pickType(key) != 'B')
    return 2 ;

  if (bIndexVersion (-1))
    {
      found = 0 ;
      i = keySetMax(tags) ;
      gtp = arrp(tags, 0, KEY) - 1 ;
      while (gtp++, found < 2 && i--)
	{
	  switch (bIndexFind (key, *gtp))
	    {
	    case 0: /* absent */
	      break ;
	    case 1:
	      found = 1 ;
	      break ;
	    case 2:  /* found */
	      found = 2 ;
	      return TRUE ;
	    }
	}
    }
  
  if (!found)
    return 0 ;

  if ((obj = bsCreate (key)))
    {
      i = keySetMax(tags) ;
      gtp = arrp(tags, 0, KEY) - 1 ;
      found = 0 ;
      while (gtp++, found < 2 && i--)
	if (bsFindTag (obj, *gtp))
	  { bsDestroy (obj) ; return TRUE ; }
      bsDestroy (obj) ;
    }
  return 0 ;
} /* forestQueryKey */


static void forestUnverify (FOREST forest) 
{
  int ii ;
 
  ii = keySetMax(forest->alphaSet) ; 
  while (ii--)
    bitUnSet(forest->verified, ii) ;

  return;
} /* forestUnverify */


static BOOL forestQueryN (FOREST forest, int n)
{
  if (!keySetMax(forest->yellowTags))
    { forestAddTags (forest, n) ;
      return TRUE ;
    }

  /*    if (!bit(forest->verified,n)) mhmp 05.12.97*/
    {
      if (forestQueryKey (forest->yellowTags, keySet(forest->alphaSet,n)))
	bitSet (forest->show,n) ;
      else
	bitUnSet (forest->show,n) ;
      forestAddTags (forest, n) ;
    }
  bitSet (forest->verified, n) ;

  return bit (forest->show,n) ;
} /* forestQueryN */


static BOOL forestMayDisplay (FOREST forest, int n)
{
  BOOL isOK;

  if (n < 0 || n >= keySetMax(forest->alphaSet))
    messcrash ("Bad n in forestMayDisplay") ;

  isOK = !bit (forest->unSelect, n) &&
    !bit (forest->discard, n) &&
    forestQueryN (forest, n);

  return isOK;
} /* forestMayDisplay */

/***********************************************************************/
/* looping */
static int forestDoSelect (FOREST forest, BOOL doSelect)
{
  int ii, j, nn ;
  KEY *kp ;

  ii = keySetMax(forest->alphaSet) ; 
  while (ii--)
    bitUnSet(forest->unSelect, ii) ;
  if (!doSelect) 
    {
      keySetDestroy (forest->selectSet) ;
      forest->selectSet = keySetCopy (forest->alphaSet) ;
    }
  else
    {
      forest->selectSet = keySetReCreate(forest->selectSet) ;
      keySetSort (forest->greenTags) ;
      keySetCompress (forest->greenTags) ;
      forestTagsSort(forest->classe, forest->greenTags) ;
      ii = keySetMax(forest->alphaSet) ; nn = 0 ; j = -1 ;
      kp = arrp(forest->alphaSet, 0, KEY) - 1 ;
      while (kp++, j++, ii--)
	{
	  if (!forestQueryKey (forest->greenTags, *kp))
	    bitSet(forest->unSelect, j) ;     /*mhmp ii --> j 28.11.97*/
	  else  /* This is it ! */
	    keySet(forest->selectSet, nn++) = keySet(forest->alphaSet,j) ;
	  if (messIsInterruptCalled())
	    break ;
	}
    }
  keySetDestroy (forest->selectedTags) ; 
  forest->selectedTags = keySetCopy (forest->greenTags) ;
  forest->nSelected = keySetMax(forest->selectSet) ; 
  forest->map->min = 0 ;
  forest->map->max = forest->nSelected ;

  return forest->nSelected ;
} /* forestDoSelect */

/***********************************************************************/
/*  graphic interface */

static void forestSelect (void)
{
  FORESTGET ("forestSelect") ;
  
  if (!forest->showModel)
   {
     forestDoRedraw (forest) ; /* pour garder le bouton Select bleu mhmp*/
     return ;
   }
  forest->isSelect = !forest->isSelect ;
  if (forest->isSelect)
    { 
      if (!keySetMax(forest->greenTags))
	{ 
	  messout ("Please first select some tags from the model") ;
	  forest->isSelect = FALSE ;
	  forestDoSelect (forest, FALSE) ;
	}
      else
	{
	  messout("%s%s%s\n%s",
		  "With the Select mode, only the objects with at ",
		  "least one green (or yellow) tag are selected. ",
		  "Objects with only red tags are hidden. ",
		  "F4 to interrupt" ) ;
	  forestDoSelect (forest, TRUE) ;
	}
    }
  else
    forestDoSelect (forest, FALSE) ;
  /*  graphUnMessage () ; mhmp 26.11.97*/

  forestDoRedraw(forest) ;

  return;
} /* forestSelect */

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

static void forestExport (void) /* mhmp 20.02.98 */
{
  char *cp ;
  KEYSET ks ;
  int ii,j, nn ;
  FORESTGET ("forestExport") ;

  ks = keySetCreate () ;
  ii = keySetMax(forest->alphaSet) ;
  nn = 0 ; j = -1 ;
  while (j++,ii--)
    if (!bit (forest->discard, j) && !bit(forest->unSelect, j))
      keySet(ks, nn++) = keySet(forest->alphaSet,j) ;
  keySetSort (ks) ;
  keySetCompress(ks) ;
  if (keySetMax(ks) == 0)
    messout ("The active forest does not contain keys") ;
  else
    {
      ACEIN title_in;
      if ((title_in = messPrompt("Title","","t", 0)))
	{
	  cp = aceInWord(title_in) ;
	  keySetNewDisplay (ks,cp) ;
	  aceInDestroy (title_in);
	}
    }

  return;
} /* forestExport */

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

static MENUOPT forestMenu[]={
  { graphDestroy,"Quit"},
  { help, "Help"},
  { forestPrint, "Print"},
  { displayPreserve, "Preserve"},
  { 0,0 } };

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

static MENUOPT forestMenuButt1[]={
  { graphDestroy,"Quit"},
  { help, "Help"},
  { forestPrint, "Print"},
  { forestUnDiscard, "UnDiscard"},  /* attention a discardBox = 4th button */
  { forestExport, "Export"},
  { forestSaveAs, "Save as"},
  { 0,0 } };

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

static MENUOPT forestMenuButt2[]={
  { forestShowModel, "Model show/hide"},
  { forestTagsHide, "Tags hide"},
  { forestTagsUnHide, "Tags show"},
  { forestSelect, "Select"},
  { 0,0 } };

/***********************************************************************/
/* drawing */
/***********************************************************************/
static float oldOffset ;

static void forestDisplayMark (LOOK look, float *offset, MENU *unused)
     /* used as a MapColDrawFunc for mapInsertCol() */
{
  float oldTextHeight ;
  char *cc, dd[8], ee[8] ;
  int i, j ;
  float y, yOld = 3 ;
  BOOL idem ;
  KEY key ;
  FOREST forest = (FOREST)look;
  
  if (!forest->nSelected)
    return ;

  oldOffset = *offset ;
  *offset += 1 ;                       /*mhmp -6 ?????????*/
  forest->map->thumb.fac =
    (mapGraphHeight - topMargin - 6) / (forest->map->max);

  oldTextHeight = graphTextHeight (0.75) ;
  idem = TRUE ;
  for (j = 0 ; j < forest->nSelected ; j+= 1)  
    { 
      key = keySet(forest->selectSet, j) ;
      cc = name(key) ;
      memset(dd,0,8) ;
      for (i = 0 ; i < 5 ; i+= 1)
	{
	  dd [i] = cc [i] ;
	  if (dd[i] != ee [i])
	    {
	      idem = FALSE ;
	      ee[i] = dd[i] ;
	    }
	}
      if (idem)
	continue ;
      else
	{ idem = TRUE ;
	  y = MAP2WHOLE (forest->map, j) ;
	  if (y > mapGraphHeight)
	    break ;
	  if (y - yOld < 2)
	    continue ;
	  else
	    {
	      yOld = y ;
	      graphLine (0, y, *offset + 2, y) ;
	      graphText (messprintf("%s", dd), 0, y + 0.2) ;
	    }
	}
    }
  graphTextHeight (oldTextHeight) ;
  *offset += 2 ;

  return;
} /* forestDisplayMark */

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

static void forestAddTags (FOREST forest, int nn) 
{
  KEY key, *kp1 ;
  int i ;
  KEYSET ks1 ;

  key = keySet (forest->alphaSet, nn) ;
  if (pickType(key) != 'B')
    return;
  ks1 = bsKeySet (key) ;
  if (!ks1) return ;
  i = keySetMax(ks1) ;
  kp1= arrp(ks1, 0, KEY) - 1 ;
  while
    (kp1++, i--)
      if (!class(*kp1) && *kp1 >= _Date && /* a real tag */
	  !array(forest->tagState, *kp1, char))
	array(forest->tagState, *kp1, char) = 1 ; 
  keySetDestroy (ks1) ;

  return;
} /* forestAddTags */

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

static char* forestBStext (BS bs)
{
  if (bs->key <= _LastC)
    return bsText(bs) ;
  else
    if (bs->key ==_Int)
      return messprintf ("%d", bs->n.i) ;
    else if (bs->key ==_Float)
      return messprintf ("%g", bs->n.f) ;
    else
      if (bs->key ==_DateType)
	return timeShow (bs->n.time) ;
      else
	return name (bs->key);
} /* forestBStext */



static int countSiblings (BS bs)
{
  int n = 0 ;

  while (bs)
    { if (class(bs->key) != _VUserSession)
        n++ ;
      bs = bs->down ;
    }
  return n ;
} /* countSiblings */



static BOOL drawTriangle(BS bs, int x, int y, BOOL isModel)
{
  BOOL isArrow ;
  int n = countSiblings(bs->right) ;

  if (n > 0) 
    {
      graphColor(BLUE) ;
      if (isModel)
	graphText(messprintf("  -----> COUNT"), x, y) ;
      else
	graphText(messprintf("  -----> %d ",n), x, y) ;
      graphColor(BLACK) ;
      isArrow = TRUE ;
    }
  else
    isArrow = FALSE ;
  return isArrow ;
} /* drawTriangle */

static FOREST myForest ;
static BOOL showTimeStamps = FALSE ; /* could go in menu choice */
static int topLine = 0 ;
/* recursive routine */

/* forestBS (graphic) forestBBS (printer) */

static int forestBS (BS bs, BS bsm, int x, int y, int *yy, 
		     BOOL isRoot, BOOL isModel)	
{
  int yMe = y , y3 ;
  int yyMe = *yy ;
  int xPlus = 0 ;
  BOOL isArrow = FALSE ;
  char *text, *cp ;
  int box;
  int oldTextFormat ;
  SEG *seg ;
  KEY tag ;

  graphFitBounds (&mapGraphWidth,&mapGraphHeight) ;

  if (y > mapGraphHeight + myForest->lineBegin)
    return y ;

  switch (fState(myForest, bs->key))
    {
    case 2:
      isArrow = TRUE ;
      break ;
    case 3: 
      if (!isModel)
	goto right ;
    case 4:
      if (!isModel)
	goto down ;
    };
  
  bsModelMatch (bs, &bsm) ; /* will fail on Time stamp, ignore returned value */

  if (!showTimeStamps && class(bs->key) == _VUserSession)
    goto right;
  
  text = forestBStext (bs) ;
  if (!text) text = "" ; 

  yMe = y ; /* must do this here again: y can change */
  yyMe = *yy ;
  seg = arrayp (myForest->segs, arrayMax(myForest->segs), SEG) ;
  seg->key = bs->key ;
  seg->color = WHITE ;
  seg->flag = 0 ;
  seg->n = stackMark (myForest->s) ;
  pushText (myForest->s, text) ;
  if (isModel)
    { seg->color = BLUE ;
      tag = seg->key ;
      if (!keySetFind (myForest->modelTags, tag, 0)) 
	keySetInsert (myForest->modelTags, tag) ;
      if (tag < arrayMax(myForest->tagState) &&
	  array (myForest->tagState,tag, char))
	{ 
	  seg->flag = MODELFLAG ;
	  if (bs->right && bs->right->key != _UNIQUE) 
	    seg->flag |= bsIsTag (bs->right) ? MODELNEXTTAG : MODELNEXTFLAG ;
	  switch (fState(myForest, tag))
	    {
	    case 1:
	      seg->color = GREEN ;
	      /* mhmp 10.07.98 */
	      if (myForest->isSelect && myForest->selectedTags &&  
		  keySetFind (myForest->selectedTags, tag, 0))
		seg->color = LIGHTBLUE ;
	      if (isModel)
		keySet(myForest->greenTags, keySetMax(myForest->greenTags)) = tag ;
	      break ;
	    case 2:
	      seg->color = YELLOW ;
	      break ;
	    case 3:
	      seg->color = PALEORANGE ;
	      break ;
	    case 4:
	      seg->color = LIGHTRED ;
	      break ;
	    }
	}
    }
  if (seg->color == BLUE)
    goto right ;

  { 
    oldTextFormat = PLAIN_FORMAT; /* init for compiler happiness */

    box = graphBoxStart() ;
    if ((iskey (bs->key) == 2 &&
	 class(bs->key) != _VText)) 
      oldTextFormat = graphTextFormat(BOLD) ;

    if (bs->key == _Greek)
      oldTextFormat = graphTextFormat(GREEK) ;

    uLinesText (text,LINE_LENGTH) ;
    if ((cp = uNextLine(text)))          
      { 
	y3 = yMe++ - myForest->lineBegin ;
	if (y3 >= topLine)
	  graphText (cp,x,y3) ;	/* write out this node */
	if (strlen(cp) > xPlus)
	  xPlus = strlen(cp) ;
	while ((cp = uNextLine(text)))	/* indent following lines */
	  { 
	    y3 = yMe++ - myForest->lineBegin ;
	    if (y3 >= topLine )
	      graphText (cp,x+2, y3) ;
	    if (strlen(cp)+2 >xPlus)
	      xPlus = strlen(cp)+2 ;
	  }
      }
    if ( bs->right && !isRoot && isArrow)
      /*    if (isDecal)
	{ yyMe++ ;
	  if (*yy >= myForest->lineBegin)
	    isDecal = FALSE ;
	}
      else  mhmp 05.05.98 */
      {
	y3 =  yMe-1- myForest->lineBegin ;
	if  (y3 >= topLine)
	  isArrow = drawTriangle (bs, x+xPlus, y3, isModel) ;
      }
    /*isArrow = drawTriangle (bs, x+xPlus, yMe-1, isModel) ;*/
    if ((iskey (bs->key) ==2 && class(bs->key) != _VText) ||
 	( (bs->size & ON_FLAG) && bs->right ) ||
 	bs->key == _Greek )
      graphTextFormat(oldTextFormat) ;
    graphBoxEnd() ;
    array(myForest->box2segs, box, SEG*) = seg ;

    if (isModel) graphBoxDraw(box, BLACK, seg->color) ;
  }
 right:
  xPlus += x ;
  xPlus = xPlus + 6 - xPlus%4 ;
  if (isRoot)
    { xPlus = x + 2 ;
      y += 1 ;
      *yy +=1 ;
    }
  if (bs->right &&  fState(myForest, bs->key) != 4 && bs->right->key !=_XREF &&  ( !isArrow || isRoot))
    y = forestBS (bs->right, bsm ? bsModelRight(bsm) : 0, xPlus, y, yy, 
		  FALSE, isModel) ; /* to the right at same y */

  if (yMe > y) y  = yMe ;
  if (yyMe > *yy) *yy = yyMe ;
 down:
  if (bs->down) /* && !(bs->size & ATTACH_FLAG)) attach only rightwards */
    y = forestBS (bs->down, bsm, x, y, yy, FALSE, isModel) ;		/* below at new y location */

  return y ;
} /* forestBS */


static int forestBBS (BS bs, BS bsm, int x, int y, 
		      BOOL isRoot) /* recursive routine */
{
  int yMe = y ;
  int xPlus = 0 ;
  BOOL isArrow = FALSE ;
  char *text, *cp ;
  int box;
  int oldTextFormat ;
  SEG *seg ;
  
  switch (fState(myForest, bs->key))
    {
    case 2:
      isArrow = TRUE ;
      break ;
    case 3: 
      goto right ;
    case 4:
      goto down ;
    }
  
  bsModelMatch (bs, &bsm) ; /* will fail on Time stamp, ignore returned value */

  if (!showTimeStamps && class(bs->key) == _VUserSession)
    goto right;
  
  text = forestBStext (bs) ;

  yMe = y ; /* must do this here again: y can change */

  seg = arrayp (myForest->segs, arrayMax(myForest->segs), SEG) ;
  seg->key = bs->key ;
  seg->color = WHITE ;
  seg->flag = 0 ;

  {
    oldTextFormat = PLAIN_FORMAT; /* init for compiler happiness */

    box = graphBoxStart() ;
    if ((iskey (bs->key) == 2 &&
	 class(bs->key) != _VText)) 
      oldTextFormat = graphTextFormat(BOLD) ;

    if (bs->key == _Greek)
      oldTextFormat = graphTextFormat(GREEK) ;

    uLinesText (text,LINE_LENGTH) ;
    if ((cp = uNextLine(text)))
      { graphText (cp,x,yMe++) ;	/* write out this node */
	if (strlen(cp) > xPlus)
	  xPlus = strlen(cp) ;
	while ((cp = uNextLine(text)))	/* indent following lines */
	  { graphText (cp,x+2,yMe++) ;
	    if (strlen(cp)+2 >xPlus)
	      xPlus = strlen(cp)+2 ;
	  }
      }
    if ( bs->right && !isRoot && isArrow)
      isArrow = drawTriangle (bs, x+xPlus, yMe-1, FALSE) ;
    if ((iskey (bs->key) ==2 && class(bs->key) != _VText) ||
 	( (bs->size & ON_FLAG) && bs->right ) ||
 	bs->key == _Greek )
      graphTextFormat(oldTextFormat) ;
    graphBoxEnd() ;
    array(myForest->box2segs, box, SEG*) = seg ;
  }
 right: 
  xPlus += x ;
  xPlus = xPlus + 6 - xPlus%4 ;
  if (isRoot)
    { xPlus = x + 2 ;
      y += 1 ;
    }
  if (bs->right &&  fState(myForest, bs->key) != 4 &&  (!isArrow || isRoot))
    y = forestBBS (bs->right, bsm ? bsModelRight(bsm) : 0, xPlus, y, 
		   FALSE) ; /* to the right at same y */

  if (yMe > y) y = yMe ;
 down:
  if (bs->down) /* && !(bs->size & ATTACH_FLAG)) attach only rightwards */
    y = forestBBS (bs->down, bsm, x, y, FALSE) ;		/* below at new y location */

  return y ;
} /* forestBBS */



static void forestKey (FOREST forest, int nn, int *xp, int *yp)
{
  KEY key ;
  OBJ obj = 0 ;
  int box ;
  SEG *seg ;

  key = keySet (forest->alphaSet, nn) ;

  seg = arrayp (forest->segs, arrayMax(forest->segs), SEG) ;
  array(forest->box2segs, box = graphBoxStart(), SEG*) = seg ;
  seg->key = key ;
  seg->color = WHITE ;
  graphBoxEnd () ;

  if (pickType(key) != 'B' || ! (obj = bsCreate(key)))
    return ;
  myForest = forest ;
  *yp += 1 ;
  *yp = forestBBS (obj->root, bsModelRoot(obj), *xp, *yp, TRUE) ;
  bsDestroy (obj) ;

  return;
} /* forestKey */



static void forestDrawModel (FOREST forest, int *xp, int *yp)
{
 KEY key ;
  OBJ obj = 0 ;
  int box, old, yy ;
  SEG *seg ;

  forest->greenTags = keySetReCreate(forest->greenTags) ;
  key = forest->model ;

  seg = arrayp (forest->segs, arrayMax(forest->segs), SEG) ;
  seg->key = key ;
  seg->color = WHITE ;

  array(forest->box2segs, box = graphBoxStart(), SEG*) = seg ;
  old = graphTextFormat (BOLD) ;
  graphText (messprintf("%s", name(key)+ 1), *xp, *yp) ;
  graphTextFormat (old) ;
  graphBoxEnd () ;

  graphText ("Click tag to: show->green, count->yellow, hide->red",*xp + 10, *yp) ;

  if ((obj = bsCreate(key))) /* garanteed in fact */
    { 
      myForest = forest ;
      yy = 1 ;
      *yp = forestBS (obj->root, bsModelRoot(obj), *xp, *yp, &yy, TRUE, TRUE) ;
      bsDestroy (obj) ;
    }

  return;
} /* forestDrawModel */

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

static void forestDrawKeyUp (FOREST forest, int nn, int *xp, int *yp)
{ 
  KEY key ;
  OBJ obj = 0 ;
  int yy ;

  if (!forestMayDisplay(forest, nn))
    return ;

  key = keySet (forest->alphaSet, nn) ;
 
  array(forest->topLines,arrayMax(forest->topLines), int) =
    *yp - forest->lineBegin ;
  array(forest->topLinesIndex,arrayMax(forest->topLinesIndex), int) = nn ;  
  *yp += 1 ;
  if ((obj = bsCreate(key)))
    { 
      myForest = forest ;
      yy = 1 ;
      *yp = forestBS (obj->root, bsModelRoot(obj), *xp, *yp, &yy, TRUE, FALSE) ;
      bsDestroy (obj);
    }

  return;
} /* forestDrawKeyUp */

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

static void forestDrawKey (FOREST forest,
			   int nn, int *xp, int *yp, BOOL doDraw)
{ 
  KEY key ;
  OBJ obj = 0 ;
  int box, yy, yDis ;
  float hh ;
  SEG *seg ;

  if (!forestMayDisplay(forest, nn))
    return ;

  key = keySet (forest->alphaSet, nn) ;
  
  if (doDraw)
    {	
      array(forest->topLines, arrayMax(forest->topLines), int) =
	*yp -  forest->lineBegin ;
      array(forest->topLinesIndex, arrayMax(forest->topLinesIndex), int) = nn ;
      seg = arrayp (forest->segs, arrayMax(forest->segs), SEG) ;
      seg->key = key ;
      seg->color = WHITE ;
      array(forest->box2segs, box = graphBoxStart(), SEG*) = seg ;
      graphBoxEnd () ;
      
      seg = arrayp (forest->segs, arrayMax(forest->segs), SEG) ;
      array(forest->box2segs, box = graphBoxStart(), SEG*) = seg ;
      seg->key = nn ;
      seg->flag = DISCARDFLAG ;
      hh = graphTextHeight (0.9) ;
      /* mhmp 26.11.97*/
      yDis = *yp -  forest->lineBegin > topMargin + 1 ? 
	*yp + 1 -  forest->lineBegin : topMargin + 2 ;
	graphText ("Discard", 50, yDis) ;

      graphBoxBox (box) ;
      graphBoxEnd () ;
      graphTextHeight (hh) ;
    }
      
   *yp += 1 ;
   if ((obj = bsCreate(key)))
     { 
       myForest = forest ;
       yy = 1 ;
       *yp = forestBS (obj->root, bsModelRoot(obj), *xp, *yp, &yy, TRUE, FALSE) ;
       bsDestroy (obj);
     }

   return;
} /* forestDrawKey */

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

static void forestDraw (FOREST forest)
{
  int box, max, n = 0, x, y = 0, limit = 1250 ;
  float oldy, yy ;
  Graph old = graphActive() ;

  graphClear () ;
  max = forest->max ;
  box = graphBoxStart() ;
  graphText(forest->title, 0, y) ;
  forestGetYellowTags (forest) ;

  for (x = 0, n= 0, y = 1 ;
       n < forest->max ; n++)
    { 
      if (!forestMayDisplay (forest,n))
	continue ;

      forestKey (forest, n, &x, &y) ;
      if (y > limit)
	{ 
	  if (!messQuery("%d objects already use %d lines, "
			 "do you want to continue ?",  n, y))
	    break ;
	  limit *= 4 ;
	}
    }
  graphBoxEnd() ;

  yy = y ; /* cast to float */
  oldy = graphFakeBounds (yy) ;
  graphRedraw () ; /* michel: should come last */
  graphPrint () ;
  graphActivate (old);
  graphFakeBounds (oldy) ;
  forestRedraw();

  return;
} /* forestDraw */


static void forestDoRedraw2 (FOREST forest)
{ 
  graphFitBounds (&mapGraphWidth,&mapGraphHeight) ; 

  topMargin = 5 ;
  stackClear (forest->s) ;
  pushText (forest->s, "") ;
  graphClear () ;
  forest->segs = arrayReCreate (forest->segs, 256, SEG) ;
  forest->box2segs = arrayReCreate (forest->box2segs, 256, SEG*) ;
  mapDrawColumns (forest->map) ;

  return;
} /* forestDoRedraw2 */


static void forestDoRedraw (FOREST forest)
{ 
  graphFitBounds (&mapGraphWidth, &mapGraphHeight);

  topMargin = 5 ;
  stackClear (forest->s) ;
  pushText (forest->s, "") ;
  graphClear () ;
  forest->segs = arrayReCreate (forest->segs, 256, SEG) ;
  forest->box2segs = arrayReCreate (forest->box2segs, 256, SEG*) ;
  mapDrawColumns (forest->map) ;
  graphRegister (PICK, forestPick) ; /* redo because of ColControl */
  graphMenu (forestMenu) ;
  forestDisplayMark ((LOOK)forest, &oldOffset, NULL) ; 
  graphRedraw () ;
  forestColor (forest) ; /* mhmp 24.03.98*/

  return;
} /* forestDoRedraw */



/* mhmp 04.12.97 *********************************/
/* calcul des forest->linesTop au-dessus de nTop */

static void forestDisplayTextUp (FOREST forest, float *offset)
{ 
  int nbUp ;
  int n = 0, line = 0, x, y ;
  forest->nUp = forest->nTop - 1 ;
  nbUp = 0 ;
  while ((nbUp < halfGraphHeight) && (forest->nUp >= 0))
    {
      if (forestMayDisplay (forest, forest->nUp))
	nbUp++ ;
      forest->nUp-- ;
    }
  if (forest->nUp < 0) /* mhmp 05.12.97 A voir*/
    forest->nUp = 0 ;

  /*  if (nbUp ==0)
    forest->nUp = forest->nTop ;*/
  /* on decale de -10000 */
  line = topMargin + 1 - 100000 ;
  if (forest->isSelect) line++ ;   
  y = line ;
  topLine = y + 1; /* needed to not block drawing model */
  if (forest->showModel)
    { x = *offset ;
    forestDrawModel (forest, &x, &y) ;
    }
  topLine = y ;
  if (keySetMax(forest->yellowTags) && forest->nUp >= 0)
    for (x = *offset, n = forest->nUp ;
	 n <= forest->nTop ; n++)
      forestDrawKeyUp (forest, n, &x, &y) ;

  return;
} /* forestDisplayTextUp */



static void forestTopLines (FOREST forest)
/*********** mhmp 04.12.97 rearrangement des topLines *****/
{ 
  int i, ii = 0 ;

/* ii: rang du 2eme nTop */
  for (i=0 ; i < arrayMax(forest->topLines) ; i++)
    if (array (forest->topLines,i, int) > -1000)
      { 
	ii = i ;
	break ;
      }

/* calcul des topLines au-dessus de nTop en egalant les 2 nTop */ 
  for (i=0 ; i < ii ; i++)
    array (forest->topLines,i, int) = array (forest->topLines,i, int) -
      array (forest->topLines,ii-1, int) + array (forest->topLines,ii, int) ;

/* decalage de 1 a gauche a partir du 1er nTop */
  if (ii > 0)
    for (i=ii-1; i < arrayMax(forest->topLines) - 1 ; i++)
      {
	array (forest->topLines,i, int) = 
	  array (forest->topLines,i+1, int) ;
	array (forest->topLinesIndex,i, int) =
	  array (forest->topLinesIndex,i+1, int) ;
      }

  return;
} /* forestTopLines */

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

static void forestDisplayText (LOOK look, float *offset, MENU *unused)
     /* used as a MapColDrawFunc for mapInsertCol() */
{
  int box, max, n = 0, line = 0 , x, y , ii, first, last ;
  int selectKey ;
  char *cp ;
  float x1, y1, x2, y2, dx, dx2, dx4, dy, dy2, dy4, dec;
  float oldCentre, delta ;
  KEY key ;
  FOREST forest = (FOREST)look;

  max = forest->max ;
  oldOffset = *offset ;
  *offset += 5 ;
 
  first = last = -1 ;
  forestGetYellowTags(forest) ;
  forest->topLines = arrayReCreate (forest->topLines,30, int) ;
  forest->topLinesIndex = arrayReCreate (forest->topLinesIndex,30, int) ;
  if (!isRecentre)
    forest->lineBegin = 0 ;
  isRecentre = FALSE ;
  line = topMargin + 1 ;

  /*************************************/
  /* dry run, search for non empty objects, select the relevant model */
    
  y = line ;
  box = graphBoxStart() ;

  if (forest->isGoto > 0)
    forest->isGoto-- ;
  /* mhmp 02.12.97 pour avoir le bon forest->nTop */
  ii = forest->nTop ;
  while (ii < forest->max)
      {
	if (forestMayDisplay (forest,ii))
	  { 
	    forest->nTop = ii ;
	    break ;
	  }
	ii++ ;
      }

  forest->map->centre = forest->nTop + halfGraphHeight - 1 ; 
  if (forest->map->centre < halfHeight)
    forest->map->centre = halfHeight ;

  forest->isDownRed = TRUE ; forest->isPageDownRed = FALSE ;
  if (keySetMax(forest->yellowTags))
    {
      ii = forest->nTop ;
      while (++ii < forest->max)
	if (forestMayDisplay (forest,ii))
	  { forest->isDownRed = FALSE ; break ; }
    }

  key = keySet(forest->alphaSet, forest->nTop) ;
  if (key)
    {
      forest->model =  pickModel (key) ;
      forest->classe = class (key) ;
    }

  for (x = *offset, n = forest->nTop ;
       y < mapGraphHeight + forest->lineBegin &&  n < forest->max ; n++)
    { 
      if (!forestMayDisplay (forest,n))
	continue ;
      if (first == -1)
	first = n ;
      last = n ; 
      if (keySetMax(forest->yellowTags))
	forestDrawKey (forest, n, &x, &y, FALSE) ;
      else
	y+= 2 ;
    }
  forest->nBottom = n ;
  graphBoxEnd() ;
  graphBoxClear (box) ;
    /*************************************/
  /* calcul des forest->topLines  au-dessus de forest->nTop mhmp 04.12.97 */

  forestDisplayTextUp (forest, &oldOffset) ;

  /*************************************/
  /* real drawing */

  box = graphBoxStart() ;
  forest->firstBox = box ;
  line = topMargin + 1 ;
  if (forest->isSelect) line++ ; 
  if (forest->lineBegin != 0) line++ ; /*mhmp 01.12.97*/
  y = line ;
  topLine = y + 1; /* needed to not block drawing model */
  if (forest->showModel)
    { x = *offset ;
    forestDrawModel (forest, &x, &y) ;
    }
  topLine = y ;
  if (keySetMax(forest->yellowTags))
    { 
      for (x = *offset, n = forest->nTop ;
	   y < mapGraphHeight + forest->lineBegin &&  n < forest->max ; n++)
      {
	forestDrawKey (forest, n, &x, &y,TRUE) ;
      } 
/********** rearrangement des forest->linesTop *********/

      forestTopLines (forest) ;

/*******************************************/
      /*mhmp 27.11.97*/
      if (/*forest->isDownRed &&*/ y < mapGraphHeight + forest->lineBegin)
	forest->isPageDownRed = TRUE ;
    }
  graphBoxEnd() ; 

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

  /* reports */
 /*mhmp 28.11.97 forest->max-1  --> forest->max */
  cp = messprintf("%d items, %d discarded",
		  forest->max, forest->maxDiscard) ;
  graphText (cp,*offset, topMargin) ;
  ii = strlen(cp) + 2 ;
  if (forest->isSelect)
    graphText(messprintf(", %d selected", forest->nSelected), 
	      *offset + ii, topMargin) ;
  
  /* buttons */
  box = graphButtons (forestMenuButt1, *offset, 0.5, 1000) ;
  discardBox = box + 3 ;  /* 4th box */
  if (forest->maxDiscard)
    graphBoxDraw (discardBox, BLACK, LIGHTBLUE) ;

  box = graphButtons (forestMenuButt2, *offset, 2.2, 1000) ;
  if (forest->showModel)
    graphBoxDraw (box, BLACK, LIGHTBLUE) ;
  if (forest->hide)
    graphBoxDraw (box + 1, BLACK, LIGHTBLUE) ;  
  else
    graphBoxDraw (box + 2, BLACK, LIGHTBLUE) ;  
  if (forest->isSelect)
    graphBoxDraw (box + 3, BLACK, LIGHTBLUE) ;
  if (biblioPossible (forest->fSet))
    graphButton ("Biblio", callBiblio, 51.0, 0.5) ;

  /* mouvements up/down*/
  /*  x1 = 50 ;*/
  /*  y1 = 4 ;*/
  x1 = 52 ;
  y1 = 2 ;
  dx = 2 ;
  dy = 1.2 ;
  dec = 0.05 ;
  dx2 = dx/2 ;
  dx4 = dx2/2 ;
  dy2 = dy/2 ;
  dy4 = dy2/2 ;
  x2 = x1 + dx ;
  y2 = y1 + dy ;

  pageDownBox  = graphBoxStart () ;
  graphRectangle (x1, y1, x2, y2) ;
  graphLine (x1 + dx4, y1 + dec, x1 + dx2, y1 + dy2 + dec) ;
  graphLine (x1 + dx2, y1 + dy2 + dec, x2 - dx4, y1 + dec) ;
  graphLine (x1 + dx4, y1 + dy2 - dec, x1 + dx2, y2 - dec) ;
  graphLine (x1 + dx2, y2 - dec, x2 - dx4, y1 + dy2 - dec) ;
  graphBoxEnd() ;
  graphBoxDraw (pageDownBox, BLACK, GREEN) ;
  downBox  = graphBoxStart () ;
  x1 = x2 + dx2 ;
  x2 = x1 + dx ;
  graphRectangle (x1, y1, x2, y2) ;
  graphLine (x1 + dx4, y1 + dy4, x1 + dx2, y2 - dy4) ;
  graphLine (x1 + dx2, y2 - dy4, x2 - dx4, y1 + dy4) ;
  graphBoxEnd() ;
  graphBoxDraw (downBox, BLACK, GREEN) ;
  x1 = x2 + dx2 ;
  x2 = x1 + dx ;
  upBox  = graphBoxStart () ;
  graphRectangle (x1, y1, x2, y2) ;
  graphLine (x1 + dx4, y2 - dy4, x1 + dx2, y1 + dy4) ;
  graphLine (x1 + dx2, y1 + dy4, x2 - dx4, y2 - dy4) ;
  graphBoxEnd() ;
  graphBoxDraw (upBox, BLACK, GREEN) ;
  x1 = x2 + dx2 ;
  x2 = x1 + dx ;
  
  pageUpBox  = graphBoxStart () ;

  graphRectangle (x1, y1, x2, y2) ;
  graphLine (x1 + dx4, y1 + dy2 + dec, x1 + dx2, y1 + dec) ;
  graphLine (x1 + dx2, y1 + dec, x2 - dx4, y1 + dy2 + dec) ;
  graphLine (x1 + dx4, y2 - dec, x1 + dx2, y1 + dy2 - dec) ;
  graphLine (x1 + dx2, y1 + dy2 - dec, x2 - dx4, y2 - dec) ;

  graphBoxEnd() ;  
  graphBoxDraw (pageUpBox, BLACK, GREEN) ;
  graphText ("Light:", 34, 4) ; /* mhmp 05.05.98 20-->50*/
  lightBox = graphTextScrollEntry (forest->mot, 1023, 50, 41, 4, forestLight) ;
  graphText ("Go to:", 5, 4) ;
  gotoBox = graphTextEntry (forest->cq, 20, 12, 4, forestGoto) ;
  if (!forest->isGoto)
    { graphEntryDisable() ; graphRegister (KEYBOARD, forestKbd) ;}
 
  forest->isUpRed = TRUE ; 
  forest->isPageUpRed = FALSE ;
  if (keySetMax(forest->yellowTags))
    {
      ii = forest->nTop ;
      while (ii--)
	if (forestMayDisplay (forest,ii))
	  { forest->isUpRed = FALSE ; break ; }
    }

  if (forest->isUpRed)
    {
      graphBoxDraw (upBox, BLACK, LIGHTRED) ;
      if (!forest->lineBegin)
	{ 
	  forest->isPageUpRed = TRUE ;
	  graphBoxDraw (pageUpBox, BLACK, LIGHTRED) ;
	}
    }

  if (forest->isDownRed)
    graphBoxDraw (downBox, BLACK, LIGHTRED) ;
  if (forest->isPageDownRed)
    graphBoxDraw (pageDownBox, BLACK, LIGHTRED) ; 

  if (forest->nLast)
    graphBoxDraw (pageDownBox, BLACK, LIGHTRED) ;
  forest->nLast = 0 ;
  *offset -= 2  ;
  if (first != -1)
    { selectKey = keySet (forest->alphaSet, first) ;
      arrayFind (forest->selectSet, &selectKey, &first, 
		 keySetAlphaOrder) ;  
    }
  if (last != -1)
    { selectKey = keySet (forest->alphaSet, last) ;
      arrayFind (forest->selectSet, &selectKey, &last, 
		 keySetAlphaOrder) ;  
    }
  oldCentre = forest->map->centre ;
  if (first == -1) { first = -10000 ; last = -10001 ; }
  forest->map->centre = (first + last + 1)/2.0 ;
  delta = forest->map->max - forest->map->min ;
  if (!delta)
    delta = 1 ;
  forest->map->thumb.fac = (mapGraphHeight - 6 - topMargin) / delta ;
  forest->map->thumb.halfwidth = .5 * (last - first + 1) *
    (MAP2WHOLE(forest->map, forest->map->max + 1) 
     - MAP2WHOLE(forest->map, forest->map->min))/(1+keySetMax (forest->selectSet)) ;
  mapShowLocator ((void*)forest, offset, NULL) ;
  forest->map->centre = oldCentre ; 
  graphRegister (MIDDLE_DOWN, forestMiddleDown) ;
  /*  forest->lineBegin = 0 ;*/

  return;
} /* forestDisplayText */

static void forestRedraw (void)
{
  FORESTGET ("forestRedraw") ;

  graphFitBounds (&mapGraphWidth, &mapGraphHeight) ;

  halfHeight =  (mapGraphHeight - topMargin) / 2 ;
  forestDoRedraw (forest) ;

  return;
} /* forestRedraw */

/***********************************************************************/
/* global setup */
/***********************************************************************/


static BOOL forestAnticipate (KEYSET fSet)
{
  KEY key ;
  int i , j ;
  BOOL ok = TRUE ;

  if (externalServer)
    { 
      oldSetForServer =  keySetCreate () ;
      for (i = 0, j = 0 ; i < keySetMax(fSet) ; i++)
	{
	  key = keySet (fSet, i) ;
	  if (KEYKEY(key) && pickType(key) == 'B')
	    keySet(oldSetForServer,j++) = keySet(fSet,i) ;
	}
      if (j > 500 &&
	  !messQuery("You are requiring %d objects from the "
		     "remote server, this may be long, "
		     "do you wish to proceed", j))
	ok = FALSE ;
      else
	externalServer (-1, 0, 0, TRUE) ;
      keySetDestroy (oldSetForServer) ;
    }

  return ok ;
} /* forestAnticipate */


BOOL forestDisplayKeySet (char *title, KEYSET fSet, BOOL isOldGraph)
{ 
  FOREST forest ;
  int i, j, ii, max ;
  KEY key, *kp , *kp1;
  KEYSET alphaSet = 0 ;

  if (!title)
    title = "" ;
  if (!keySetExists (fSet))
    messcrash ("Bad call to forestDisplay") ;
  ii = 0 ; i = keySetMax(fSet) ; kp = arrayp(fSet,0,KEY) - 1 ;
  while (kp++, i--) 
    {
      key = lexAliasOf(*kp) ;
      if (key!= *kp) 
	{ *kp = key ; ii = 1 ; }
    }
  if (ii) { keySetSort(fSet) ; keySetCompress(fSet) ; }
  if (!forestAnticipate(fSet))
    goto abort ;

  kp = kp1 = arrp(fSet,0,KEY) ; 
  kp-- ; i = keySetMax(fSet) ; j = 0 ;
  while (kp++, i--)
    if (pickType (*kp) == 'B' &&
	iskey(*kp) ==  2)	
      { *kp1++ = *kp ; j++ ; }
  keySetMax(fSet) = j ;

  if (!keySetMax (fSet) || 
      !(alphaSet = keySetAlphaHeap(fSet, keySetMax(fSet))) ||
      !keySetMax (alphaSet))
    { messout ("Sorry, no associated forest") ;
      goto abort ;
    }

  if (isOldGraph && !graphAssFind (&GRAPH2FORESTLOOK_ASSOC, &forest))
    isOldGraph = FALSE ;

  if (isOldGraph) /* called by display.c, activeGraph is ok */
    {
      forestDestroy () ;	/* clears memory of the forest-LOOK 
				   on the active graph */
      graphClear () ;
      graphGoto (0,0) ;
    }
  else
    {
      displayCreate ("DtForest");

      graphRegister (DESTROY, forestDestroy) ;
      graphRegister (PICK, forestPick) ;
      graphRegister (RESIZE, forestRedraw) ;
      graphRegister (MIDDLE_DOWN, forestMiddleDown) ;
      graphMenu (forestMenu) ;
    }

  graphRetitle (messprintf("More info: %s", title)) ;
  graphHelp ("Forest") ;

  forest = (FOREST) messalloc (sizeof(struct ForestStruct)) ;

  graphAssociate (&GRAPH2FORESTLOOK_ASSOC, forest) ;
  graphAssociate (&MAP2LOOK_ASSOC, forest) ; /* make sure the map routine find this look */

  forest->magic = &FORESTLOOK_MAGIC ;
  forest->graph = graphActive() ;
  max = arrayMax (alphaSet) ;
  forest->s = stackCreate (3000) ;
  forest->max = arrayMax(alphaSet) ;
  forest->fSet = fSet ;
  forest->alphaSet = alphaSet ;
  forest->maxDiscard = 0 ;
  forest->lineBegin = 0 ;
  forest->isSelect = FALSE ;
  forest->hide = FALSE ;
  forest->showModel = TRUE ;
  forest->nTop = 0 ; /* start on first object */
  forest->verified = bitSetCreate (arrayMax(forest->fSet),0) ;
  forest->discard = bitSetCreate (arrayMax(forest->fSet),0) ;
  forest->show = bitSetCreate (arrayMax(forest->fSet), 0) ;
  forest->unSelect = bitSetCreate (arrayMax(forest->fSet), 0) ;
  forest->unDiscard = arrayCreate (arrayMax(forest->fSet), int) ;
  forest->lineNumber = arrayCreate (forest->max, int) ;
  forest->size = arrayCreate (forest->max, int) ;
  forest->segs = arrayCreate (256, SEG) ;
  forest->box2segs = arrayCreate (256, SEG*) ;
  forest->tagState = arrayCreate (arrayMax(fSet), char) ;
  forest->modelTags = keySetCreate() ;
  forest->title = strnew(title, 0) ;
  forest->map = mapCreate (forestRedraw) ; /* drawing func for map */
  mapAttachToGraph (forest->map) ;
  mapInsertCol (forest->map, 1, FALSE, "Mark", forestDisplayMark),
  mapInsertCol (forest->map, 2, TRUE,  "Text", forestDisplayText) ;

  graphRegister (MIDDLE_DOWN, forestMiddleDown) ;
  forest->map->mag = 1  ;

  /*
  graphFitBounds (&graphWidth,&graphHeight) ;
  halfHeight =  (graphHeight - 2) / 2 ;
  forest->map->centre = halfHeight ;
  */
  ii = keySetMax(forest->alphaSet) ; 
  while (ii--)
    bitUnSet(forest->discard, ii) ;
  forestDoSelect (forest, FALSE) ;
  mapNoMag() ; /* no mag with forest */
  forestDoRedraw2 (forest) ;
  forestDoRedraw2 (forest) ;
  forestDoRedraw (forest) ; /* probleme avec pickModel */
  return TRUE ;

abort:
  keySetDestroy (fSet) ; /* manque dans biblio.c */ 
  keySetDestroy (alphaSet) ; 
  return FALSE ;
} /* forestDisplayKeySet */


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

/* Standard DisplayFunc prototype for acedb display */
/* return true means a forest graph was constructed */
BOOL forestDisplay (KEY key, KEY from, BOOL isOldGraph, void *unused)
{
  KEYSET ks = 0 ;

  if (iskey(key) != 2) return FALSE ;
  if (class(key) == _VKeySet)
    { /* get ks from key */
    }
  
  if (!ks) ks = keySetCreate () ;
  keySet(ks,0) = key ;
  return forestDisplayKeySet (messprintf("Forest %s", name(key)), ks, isOldGraph) ;
} /* forestDisplay */

/***********************************************************************/
/*************************** eof ***************************************/
