/* DBWelement.c -
 *
 *
 *	This file provides a standard set of procedures for Magic
 *	commands to use to handle drawing of various elements on
 *	top of the layout.  Elements can be lines, rectangles,
 *	and text (and hopefully will be expanded to include polygons
 *	and arcs).  These operate very similarly to feedback regions,
 *	in that they are persistant until destroyed, and do not
 *	interact with the layout in any way.
 *
 * Copyright (C) 2003 Open Circuit Design, Inc., for MultiGiG, Ltd.
 */

#include <stdio.h>
#include <string.h>
#include <math.h>

#include "tcltk/tclmagic.h"
#include "utils/magic.h"
#include "utils/geometry.h"
#include "tiles/tile.h"
#include "utils/hash.h"
#include "database/database.h"
#include "windows/windows.h"
#include "graphics/graphics.h"
#include "dbwind/dbwind.h"
#include "utils/utils.h"
#include "utils/styles.h"
#include "utils/malloc.h"
#include "utils/signals.h"

/* C99 compat */
#include "textio/textio.h"

/* Types of elements */

#define ELEMENT_RECT	0
#define ELEMENT_LINE	1
#define ELEMENT_TEXT	2

/* Linked list definition for styles */

typedef struct _style *styleptr;

typedef struct _style
{
    int style;
    styleptr next;
} stylestruct;

/* Each element is stored in a record that looks like this: */

typedef struct _element
{
    int type;			/* Type of element (see list above) */
    unsigned char flags;	/* Special flags (depends on element type) */
    CellDef *rootDef;		/* Root definition of windows in which to
				 * display this element.
				 */
    stylestruct *stylelist;	/* Linked list of display styles with which
				 * to paint this element.  There must be at
				 * least one style, so first one is hardwired.
				 */
    Rect area;			/* The area of a rectangle element,
				 * or the two points of a line element,
				 * or the area box of a text element.
				 */
    char *text;			/* The text of a text element, or NULL. */

} DBWElement;

/* The following stuff describes all the feedback information we know
 * about.  The feedback is stored in a big array that grows whenever
 * necessary.
 */

HashTable elementTable;			/* Hash table holding elements info */

static CellDef *dbwelemRootDef;		/* To pass root cell definition from
					 * dbwelemGetTransform back up to
					 * DBWElementAdd.
					 */

/*
 * ----------------------------------------------------------------------------
 *
 * AppendString ---
 *
 *	Support function for DBWPrintElements().  Allocates memory for and
 *	adds string "newstr" to the string pointed to by "oldstr".  If
 *	"postfix" is non-NULL, this is also appended to "oldstr".
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Memory reallocation of *oldstr.
 *
 * ----------------------------------------------------------------------------
 */

void
AppendString(
    char **oldstr,
    const char *newstr,
    const char *postfix)
{
    char *tmpstr;
    int olen = 0;
    int nlen = strlen(newstr);
    int plen = 0;

    if (*oldstr != NULL) olen = strlen(*oldstr);
    if (postfix != NULL) plen = strlen(postfix);
    tmpstr = (char *)mallocMagic(olen + nlen + plen + 1);
    if (*oldstr == NULL)
	strcpy(tmpstr, newstr);
    else
    {
	strcpy(tmpstr, *oldstr);
	strcat(tmpstr, newstr);
	freeMagic(*oldstr);
    }
    if (postfix != NULL) strcat(tmpstr, postfix);
    *oldstr = tmpstr;
}

/*
 * ----------------------------------------------------------------------------
 *
 * AppendFlag --
 *
 *	Support function for DBWPrintElements to assist in generating
 *	the string of comma-separated flags for each element printed
 *	to the output.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	Space is allocated and text added to "rstr".  The pointer to
 *	boolean "flagset" is set to TRUE, indicating that at least one
 *	flag has been written to the output.
 *
 * ----------------------------------------------------------------------------
 */

void
AppendFlag(
    char **rstr,
    bool *flagset,
    const char *fname)
{
    AppendString(rstr, *flagset ? "," : " ", fname);
    *flagset = TRUE;
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBWPrintElements --
 *
 *	Generate a string containing the names of all elements which are
 *	defined in CellDef "cellDef" and have flags matching "flagmask".
 *	Format is appropriate for writing output to a .mag file.
 *
 * Results:
 *	An allocated string, or NULL if no elements were found.
 *
 * Side Effects:
 *	Memory is allocated for the string and must be freed by the calling
 *	procedure.
 * ----------------------------------------------------------------------------
 */

char *
DBWPrintElements(
    CellDef *cellDef,
    unsigned char flagmask,
    int reducer)
{
    DBWElement *elem;
    HashSearch hs;
    HashEntry *he;
    char *rstr = NULL;	/* allocated return string */
    char istr[10];
    styleptr sptr;
    bool flagset;

    /* These must match the order of text sizes in graphics/graphics.h */
    static char *textSizes[] = {"small", "medium", "large", "xlarge",
	"default", NULL};

    /* These must match the order of element type definitions above! */
    char *etypes[] = {"rectangle", "line", "text"};

    HashStartSearch(&hs);
    while ((he = HashNext(&elementTable, &hs)))
    {
	if ((elem = (DBWElement *)HashGetValue(he)))
	{
	    if ((elem->rootDef == cellDef) && (elem->flags & flagmask))
	    {
		/* print element type */
		AppendString(&rstr, etypes[elem->type], " ");
		/* print element name */
		AppendString(&rstr, (char *)he->h_key.h_name, " ");

		/* print dstyle(s) */
		for (sptr = elem->stylelist; sptr != NULL; sptr = sptr->next)
		    AppendString(&rstr, GrStyleTable[sptr->style].longname,
			((sptr->next == NULL) ? " " : ","));

		/* print start point */
		sprintf(istr, "%d", elem->area.r_xbot / reducer);
		AppendString(&rstr, istr, " ");
		sprintf(istr, "%d", elem->area.r_ybot / reducer);
		AppendString(&rstr, istr, " ");
		switch (elem->type)
		{
		    case ELEMENT_RECT:
		        /* end point */
			sprintf(istr, "%d", elem->area.r_xtop / reducer);
			AppendString(&rstr, istr, " ");
			sprintf(istr, "%d", elem->area.r_ytop / reducer);
			AppendString(&rstr, istr, "\n");
			/* no flags to write.  Only applicable flag is  */
			/* temporary/persistent, and temporary elements */
			/* don't get written to the file.		*/
		        break;
		    case ELEMENT_LINE:
		        /* end point */
			sprintf(istr, "%d", elem->area.r_xtop / reducer);
			AppendString(&rstr, istr, " ");
			sprintf(istr, "%d", elem->area.r_ytop / reducer);
			AppendString(&rstr, istr, NULL);
			/* any non-default flags? */
			flagset = FALSE;
			if (elem->flags & DBW_ELEMENT_LINE_HALFX)
			    AppendFlag(&rstr, &flagset, "halfx");
			if (elem->flags & DBW_ELEMENT_LINE_HALFY)
			    AppendFlag(&rstr, &flagset, "halfy");
			if (elem->flags & DBW_ELEMENT_LINE_ARROWL)
			    AppendFlag(&rstr, &flagset, "arrowleft");
			if (elem->flags & DBW_ELEMENT_LINE_ARROWR)
			    AppendFlag(&rstr, &flagset, "arrowright");
			AppendString(&rstr, "\n", NULL);
		        break;
		    case ELEMENT_TEXT:
		        /* label text */
		        AppendString(&rstr, "\"", NULL);
		        AppendString(&rstr, elem->text, NULL);
		        AppendString(&rstr, "\"", NULL);
			/* any non-default flags? */
			flagset = FALSE;
			if (((elem->flags & DBW_ELEMENT_TEXT_POS) >> 4) !=
					GEO_CENTER)
			    AppendFlag(&rstr, &flagset, GeoPosToName((elem->flags &
					DBW_ELEMENT_TEXT_POS) >> 4));
			if (((elem->flags & DBW_ELEMENT_TEXT_SIZE) >> 1) !=
					GR_TEXT_MEDIUM)
			    AppendFlag(&rstr, &flagset, textSizes[(elem->flags &
					DBW_ELEMENT_TEXT_SIZE) >> 1]);
		        AppendString(&rstr, "\n", NULL);
		        break;
		}
	    }
	}
    }
    return rstr;
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBWScaleElements --
 *
 *	Scale each element by the given integer numerator and denominator
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	Element values are modified.
 * ----------------------------------------------------------------------------
 */

void
DBWScaleElements(
    int n,
    int d)
{
    DBWElement *elem;
    HashSearch hs;
    HashEntry *he;
    extern bool DBScalePoint();	    /* Forward declaration */

    HashStartSearch(&hs);
    while ((he = HashNext(&elementTable, &hs)))
    {
	if ((elem = (DBWElement *)HashGetValue(he)))
	{
	    /* scale area rectangle */
	    DBScalePoint(&elem->area.r_ll, n, d);
	    DBScalePoint(&elem->area.r_ur, n, d);
	}
    }
}


/*
 * ----------------------------------------------------------------------------
 *
 * DBWElementRedraw --
 *
 * 	This procedure is called by the highlight manager to redisplay
 *	feedback highlights.  The window is locked before entry.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Any feedback information that overlaps a non-space tile in
 *	plane is redrawn.
 *
 * Tricky stuff:
 *	Redisplay is numerically difficult, particularly when feedbacks
 *	have a large internal scale factor:  the tendency is to get
 *	integer overflow and get everything goofed up.  Be careful
 *	when making changes to the code below.
 *
 * ----------------------------------------------------------------------------
 */

void
DBWElementRedraw(
    MagWindow *window,		/* Window in which to redraw. */
    Plane *plane)		/* Non-space tiles on this plane mark what
				 * needs to be redrawn.
				 */
{
    int curStyle, newStyle;
    styleptr stylePtr;
    CellDef *windowRoot;
    Rect screenArea;
    DBWElement *elem;
    HashSearch hs;
    HashEntry *entry;
    Point p;

    windowRoot = ((CellUse *) (window->w_surfaceID))->cu_def;
    curStyle = -1;

    HashStartSearch(&hs);
    while ((entry = HashNext(&elementTable, &hs)) != NULL)
    {
	elem = (DBWElement *) HashGetValue(entry);
	if (!elem) continue;
	else if (elem->rootDef != windowRoot) continue;

	/* Transform the feedback area to screen coords.  This is
	 * very similar to the code in WindSurfaceToScreen, except
	 * that there's additional scaling involved.
	 */

	WindSurfaceToScreenNoClip(window, &elem->area, &screenArea);

	/* Deal with half-point-offset flags for the line element */
	if ((elem->type == ELEMENT_LINE) &&
		(elem->flags & (DBW_ELEMENT_LINE_HALFX | DBW_ELEMENT_LINE_HALFY)))
	{
	    static Rect unitArea = {{0, 0}, {1, 1}};
	    Rect transArea;
	    int offx, offy;

	    WindSurfaceToScreenNoClip(window, &unitArea, &transArea);
	    offx = (transArea.r_xtop - transArea.r_xbot) >> 1;
	    offy = (transArea.r_ytop - transArea.r_ybot) >> 1;

	    if (elem->flags & DBW_ELEMENT_LINE_HALFX)
	    {
		screenArea.r_xbot += offx;
		screenArea.r_xtop += offx;
	    }
	    if (elem->flags & DBW_ELEMENT_LINE_HALFY)
	    {
		screenArea.r_ybot += offy;
		screenArea.r_ytop += offy;
	    }
	}

	if ((screenArea.r_xbot <= screenArea.r_xtop) &&
		(screenArea.r_ybot <= screenArea.r_ytop))
	{
	    for (stylePtr = elem->stylelist; stylePtr != NULL;
			stylePtr = stylePtr->next)
	    {
		newStyle = stylePtr->style;
		if (newStyle != curStyle)
		{
		    curStyle = newStyle;
		    GrSetStuff(curStyle);
		}
		switch (elem->type)
		{
		    case ELEMENT_RECT:
			GrFastBox(&screenArea);
			break;
		    case ELEMENT_TEXT:
			p.p_x = screenArea.r_xbot;
			p.p_y = screenArea.r_ybot;
			GrPutText(elem->text, curStyle, &p,
				((elem->flags & DBW_ELEMENT_TEXT_POS) >> 4),
				((elem->flags & DBW_ELEMENT_TEXT_SIZE) >> 1),
				FALSE, &(window->w_screenArea), (Rect *)NULL);
			break;
		    case ELEMENT_LINE:
			GrClipLine(screenArea.r_xbot, screenArea.r_ybot,
				screenArea.r_xtop, screenArea.r_ytop);
			/* Draw arrowheads on endpoints, if flags are set */

			if (elem->flags & (DBW_ELEMENT_LINE_ARROWL |
					DBW_ELEMENT_LINE_ARROWR)) {
			    static Rect unitArea = {{0, 0}, {1, 1}};
			    Rect transArea;
			    Point polyp[4];
			    double theta, r;
			    int i, offx, offy;

			    WindSurfaceToScreenNoClip(window, &unitArea, &transArea);
			    offx = (transArea.r_xtop - transArea.r_xbot) >> 1;
			    offy = (transArea.r_ytop - transArea.r_ybot) >> 1;
			    WindSurfaceToScreenNoClip(window, &elem->area, &screenArea);
			    if (elem->flags & DBW_ELEMENT_LINE_HALFX)
			    {
                                screenArea.r_xbot += offx;
                                screenArea.r_xtop += offx;
			    }
			    if (elem->flags & DBW_ELEMENT_LINE_HALFY)
			    {
                                screenArea.r_ybot += offy;
                                screenArea.r_ytop += offy;
			    }
			    theta = atan2((double)(screenArea.r_ytop
					- screenArea.r_ybot), (double)
                                         (screenArea.r_xtop - screenArea.r_xbot));
			    r = (double)(transArea.r_xtop - transArea.r_xbot);
			    if (elem->flags & DBW_ELEMENT_LINE_ARROWL)
			    {
				for (i = 0; i < 4; i++)
				{
				    polyp[i].p_x = screenArea.r_xbot;
				    polyp[i].p_y = screenArea.r_ybot;
				}
				polyp[1].p_x += (int)(r * cos(theta + 0.2));
				polyp[1].p_y += (int)(r * sin(theta + 0.2));
				polyp[2].p_x += (int)(r * 0.9 * cos(theta));
				polyp[2].p_y += (int)(r * 0.9 * sin(theta));
				polyp[3].p_x += (int)(r * cos(theta - 0.2));
				polyp[3].p_y += (int)(r * sin(theta - 0.2));
				GrFillPolygon(polyp, 4);
			    }

			    if (elem->flags & DBW_ELEMENT_LINE_ARROWR)
			    {
				for (i = 0; i < 4; i++)
				{
				    polyp[i].p_x = screenArea.r_xtop;
				    polyp[i].p_y = screenArea.r_ytop;
				}
				polyp[1].p_x -= (int)(r * cos(theta + 0.2));
				polyp[1].p_y -= (int)(r * sin(theta + 0.2));
				polyp[2].p_x -= (int)(r * 0.9 * cos(theta));
				polyp[2].p_y -= (int)(r * 0.9 * sin(theta));
				polyp[3].p_x -= (int)(r * cos(theta - 0.2));
				polyp[3].p_y -= (int)(r * sin(theta - 0.2));
				GrFillPolygon(polyp, 4);
			    }
			}
			break;
		}
	    }
	}
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBWElementUndraw --
 *
 *	Paint the element in style ERASE_ALL to erase it.
 *
 * ----------------------------------------------------------------------------
 */

void
dbwElementUndraw(
    MagWindow *mw,
    DBWElement *elem)		/* The element to erase */
{
    CellDef *windowRoot;
    Rect screenArea, textArea;

    if (mw == NULL) return;	/* No window; can't undraw */
    windowRoot = ((CellUse *) (mw->w_surfaceID))->cu_def;

    GrLock(mw, TRUE);

    /* Deal with half-point-offset flags for the line element	*/
    /* by enlarging the area by 1 unit.				*/

    if ((elem->type == ELEMENT_LINE) &&
		(elem->flags & (DBW_ELEMENT_LINE_HALFX | DBW_ELEMENT_LINE_HALFY)))
    {
	Rect newArea = elem->area;

	if (elem->flags & DBW_ELEMENT_LINE_HALFX) newArea.r_xtop++;
	if (elem->flags & DBW_ELEMENT_LINE_HALFY) newArea.r_ytop++;

	WindSurfaceToScreen(mw, &newArea, &screenArea);
    }
    else
	WindSurfaceToScreen(mw, &elem->area, &screenArea);

    /* For text, determine the size of the text and expand the	*/
    /* screenArea rectangle accordingly.			*/

    if (elem->type == ELEMENT_TEXT)
    {
	int tpos = (elem->flags & DBW_ELEMENT_TEXT_POS) >> 4;
	int tsize = (elem->flags & DBW_ELEMENT_TEXT_SIZE) >> 1;

	GrLabelSize(elem->text, tpos, tsize, &textArea);
	screenArea.r_xbot += textArea.r_xbot;
	screenArea.r_ybot += textArea.r_ybot;
	screenArea.r_xtop += textArea.r_xtop;
	screenArea.r_ytop += textArea.r_ytop;
    }

    if ((screenArea.r_xbot <= screenArea.r_xtop) &&
		(screenArea.r_ybot <= screenArea.r_ytop))
    {
	GrSetStuff(STYLE_ERASEALL);
	GrFastBox(&screenArea);
        WindAreaChanged(mw, &screenArea);
    }
    GrUnlock(mw, TRUE);
}


/*
 * ----------------------------------------------------------------------------
 *
 * DBWElementDelete --
 *
 * 	This procedure deletes an element.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	An element is found in the hash table by name, and if it exists,
 *	it is deleted.
 *
 * ----------------------------------------------------------------------------
 */

void
DBWElementDelete(
    MagWindow *w,
    char *name)
{
    DBWElement *elem;
    CellDef *currentRoot;
    HashEntry *entry;
    styleptr stylePtr;

    entry = HashFind(&elementTable, name);

    if (entry == NULL) return;

    elem = (DBWElement *)HashGetValue(entry);

    if (elem == NULL) return;

    dbwElementUndraw(w, elem);

    /* mark element's cell as having been modified */
    if (elem->flags & DBW_ELEMENT_PERSISTENT)
	elem->rootDef->cd_flags |= CDMODIFIED;

    free_magic1_t mm1 = freeMagic1_init();
    for (stylePtr = elem->stylelist; stylePtr != NULL; stylePtr = stylePtr->next)
    {
	freeMagic1(&mm1, stylePtr);
    }
    freeMagic1_end(&mm1);
    if (elem->type == ELEMENT_TEXT)
	freeMagic(elem->text);

    HashSetValue(entry, NULL);
    freeMagic(elem);

    /* Area of elem->area is set to be updated by dbwElementUndraw().	*/
    /* Can't do WindUpdate(), though, until the hash table entry is	*/
    /* removed, or DBWdisplay will try to draw it again.		*/

    WindUpdate();
}

/*
 * Initialize the Element hash table.
 */

void
dbwElementInit()
{
    HashInit(&elementTable, 10, HT_STRINGKEYS);
    DBWHLAddClient(DBWElementRedraw);
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBWElementNames --
 *
 *	Go through the hash table and print the names of all elements
 *
 * ----------------------------------------------------------------------------
 */

void
DBWElementNames()
{
    DBWElement *elem;
    HashSearch hs;
    HashEntry *he;

#ifndef MAGIC_WRAPPER
    TxPrintf("Known elements:");
#endif

    HashStartSearch(&hs);
    while ((he = HashNext(&elementTable, &hs)))
    {
	if ((elem = (DBWElement *)HashGetValue(he)))
	{
#ifdef MAGIC_WRAPPER
	    Tcl_AppendElement(magicinterp, he->h_key.h_name);
#else
	    TxPrintf(" %s", he->h_key.h_name);
#endif
	}
    }

#ifndef MAGIC_WRAPPER
    TxPrintf("/n");
#endif

}

/*
 * ----------------------------------------------------------------------------
 *
 * DBWElementInbox --
 *
 *	Find the element that is nearest the box.
 *
 * ----------------------------------------------------------------------------
 */

void
DBWElementInbox(
    Rect *area)
{
    DBWElement *elem;
    HashSearch hs;
    HashEntry *he;
    int sqdist;

#ifndef MAGIC_WRAPPER
    TxPrintf("Element(s) inside box: ");
#endif

    HashStartSearch(&hs);
    while ((he = HashNext(&elementTable, &hs)))
    {
	if ((elem = (DBWElement *)HashGetValue(he)))
	{
	    if (GEO_SURROUND(area, &elem->area))
	    {
#ifdef MAGIC_WRAPPER
		Tcl_AppendElement(magicinterp, he->h_key.h_name);
#else
		TxPrintf(" %s", he->h_key.h_name);
#endif
	    }
	}
    }

#ifndef MAGIC_WRAPPER
    TxPrintf("/n");
#endif
}


/*
 * ----------------------------------------------------------------------------
 *
 * DBWElementAdd* --
 *
 * 	Adds a new element to the element hash table.
 *
 * Results:
 *	None, except
 *	DBWElementAdd(): returns Pointer to a DBWElement structure.
 *
 * Side effects:
 *	CellDef's ancestors are searched until its first root definition
 *	is found, and the coordinates of area are transformed into the
 *	root.  Then the area is added to the element structure.
 *	This stuff will be displayed on the screen at the end of the
 *	current command.
 * ----------------------------------------------------------------------------
 */

/* Set up everything is generic to all element types */

DBWElement *
DBWElementAdd(
    MagWindow *w,
    char *name,			/* Name of this element for the hash table */
    Rect *area,			/* The area of the element */
    CellDef *cellDef,		/* The cellDef in whose coordinates area
				 * is given.
				 */
    int style)			/* An initial display style to use */
{
    Transform transform;
    DBWElement *elem;
    HashEntry *entry;
    extern int dbwelemGetTransform();	/* Forward declaration. */

    /* Find a transform from this cell to the root, and use it to
     * transform the area.  If the root isn't an ancestor, just
     * return.
     */

    if (!DBSrRoots(cellDef, &GeoIdentityTransform,
		dbwelemGetTransform, (ClientData) &transform))
	if (w != NULL)
	    return NULL;

    /* SigInterruptPending screws up DBSrRoots */
    if (SigInterruptPending)
	return NULL;

    /* If there is already an entry by this name, delete it */
    DBWElementDelete(w, name);

    entry = HashFind(&elementTable, name);
    elem = (DBWElement *)mallocMagic(sizeof(DBWElement));
    HashSetValue(entry, elem);

    GeoCanonicalRect(area, &elem->area);
    elem->stylelist = (styleptr)mallocMagic(sizeof(stylestruct));
    elem->stylelist->style = style;
    elem->stylelist->next = NULL;

    /* For .mag file loads, w will be NULL and cellDef will be	*/
    /* the root.						*/
    if (w == NULL)
        elem->rootDef = cellDef;
    else
	elem->rootDef = dbwelemRootDef;
    elem->text = NULL;
    elem->flags = 0;

    return elem;
}

void
DBWElementAddRect(
    MagWindow *w,
    char *name,			/* Name of this element for the hash table */
    Rect *area,			/* The area to be highlighted. */
    CellDef *cellDef,		/* The cellDef in whose coordinates area
				 * is given.
				 */
    int style)			/* An initial display style to use */
{
    DBWElement *elem;

    elem = DBWElementAdd(w, name, area, cellDef, style);
    if (elem == NULL) return;
    elem->type = ELEMENT_RECT;
}

void
DBWElementAddLine(
    MagWindow *w,
    char *name,			/* Name of this element for the hash table */
    Rect *area,			/* The area to be highlighted. */
    CellDef *cellDef,		/* The cellDef in whose coordinates area
				 * is given.
				 */
    int style)			/* An initial display style to use */
{
    DBWElement *elem;

    elem = DBWElementAdd(w, name, area, cellDef, style);
    if (elem == NULL) return;
    elem->type = ELEMENT_LINE;
}

void
DBWElementAddText(
    MagWindow *w,
    char *name,			/* Name of this element for the hash table */
    int x,
    int y,
    char *text,			/* The text of the label */
    CellDef *cellDef,		/* The cellDef in whose coordinates area
				 * is given.
				 */
    int style)			/* An initial display style to use */
{
    DBWElement *elem;
    Rect area;

    area.r_xbot = x;
    area.r_xtop = x;
    area.r_ybot = y;
    area.r_ytop = y;

    elem = DBWElementAdd(w, name, &area, cellDef, style);
    if (elem == NULL) return;
    elem->type = ELEMENT_TEXT;
    elem->text = StrDup((char **)NULL, text);
    elem->flags |= (GEO_CENTER << 4);		/* default centered */
    elem->flags |= (GR_TEXT_MEDIUM << 1);	/* default medium size */
}

/* This utility procedure is invoked by DBSrRoots.  Save the root definition
 * in dbwRootDef, save the transform in the argument, and abort the search.
 * Make sure that the root we pick is actually displayed in a window
 * someplace (there could be root cells that are no longer displayed
 * anywhere).
 */

int
dbwelemGetTransform(
    CellUse *use,			/* A root use that is an ancestor
					 * of cellDef in DBWElementAdd.
					 */
    Transform *transform,		/* Transform up from cellDef to use. */
    Transform *cdarg)			/* Place to store transform from
					 * cellDef to its root def.
					 */
{
    extern int dbwElementAlways1();
    if (use->cu_def->cd_flags & CDINTERNAL) return 0;
    if (!WindSearch((ClientData) DBWclientID, (ClientData) use,
	    (Rect *) NULL, dbwElementAlways1, (ClientData) NULL)) return 0;
    if (SigInterruptPending)
	return 0;
    dbwelemRootDef = use->cu_def;
    *cdarg = *transform;
    return 1;
}

/*ARGSUSED*/
int
dbwElementAlways1(
    MagWindow *w,		/* UNUSED */
    ClientData clientData)	/* UNUSED */
{
    return 1;
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBWElementText --
 *
 * 	Configures text of a text element
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Text element's text string is reallocated, or string is printed.
 *	If altered, the element is erased and redrawn.
 *
 * ----------------------------------------------------------------------------
 */

void
DBWElementText(
    MagWindow *w,
    char *ename,
    char *text)
{
    DBWElement *elem;
    HashEntry *entry;

    entry = HashFind(&elementTable, ename);
    if (entry == NULL)
    {
	TxError("No such element %s\n", ename);
	return;
    }

    elem = (DBWElement *)HashGetValue(entry);
    if (elem == NULL) return;

    if (elem->type != ELEMENT_TEXT)
    {
	TxError("Element %s is not a text element\n", ename);
	return;
    }

    if (text == NULL)	/* get text */
    {
#ifdef MAGIC_WRAPPER
	Tcl_SetResult(magicinterp, elem->text, NULL);
#else
	TxPrintf("%s\n", elem->text);
#endif
    }
    else  /* replace text */
    {
	dbwElementUndraw(w, elem);
	freeMagic(elem->text);
	elem->text = StrDup((char **)NULL, text);
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBWElementParseFlags --
 *
 * 	Configures flags of any element
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Element's flags are set.
 *	If altered, the element is erased and redrawn.
 *
 * ----------------------------------------------------------------------------
 */

void
DBWElementParseFlags(
    MagWindow *w,
    char *ename,
    char *flagstr)
{
    DBWElement *elem;
    HashEntry *entry;
    int flidx, newflags;

    static const char * const lineOffset[] = {"halfx", "halfy", "exactx", "exacty",
		"arrowleft", "arrowbottom", "arrowright", "arrowtop",
		"plainleft", "plainbottom", "plainright", "plaintop", NULL};
    static const char * const textSizes[] = {"small", "medium", "large", "xlarge",
	"default", NULL};
    static const char * const genFlags[] = {"persistent", "temporary", NULL};

    entry = HashFind(&elementTable, ename);
    if (entry == NULL)
    {
	TxError("No such element %s\n", ename);
	return;
    }

    elem = (DBWElement *)HashGetValue(entry);
    if (elem == NULL) return;
    newflags = elem->flags;

    /* Return list of known flags */
    if (flagstr == NULL)
    {
#ifdef MAGIC_WRAPPER
	Tcl_AppendElement(magicinterp, "(flags)");
#else
	TxPrintf("%s\n", "(flags)");
#endif
    }
    else
    {
	/* Parse string for known flags */
	flidx = Lookup(flagstr, genFlags);
	switch (flidx)
	{
	    case 0:
		newflags |= DBW_ELEMENT_PERSISTENT;
		break;
	    case 1:
		newflags &= ~DBW_ELEMENT_PERSISTENT;
		break;
	    default:
		switch (elem->type)
		{
		    case ELEMENT_TEXT:
			flidx = Lookup(flagstr, textSizes);
			if (flidx >= 0)
			{
			    newflags &= ~DBW_ELEMENT_TEXT_SIZE;
			    newflags |= (flidx << 1) & DBW_ELEMENT_TEXT_SIZE;
			}
			else
			{
			    flidx = GeoNameToPos(flagstr, FALSE, FALSE);
			    if (flidx >= 0)
			    {
				newflags &= ~DBW_ELEMENT_TEXT_POS;
				newflags |= (flidx << 4) & DBW_ELEMENT_TEXT_POS;
			    }
			    else
				TxError("No such text element flag \"%s\"\n",
					flagstr);
			}
			break;
		    case ELEMENT_LINE:
			flidx = Lookup(flagstr, lineOffset);
			switch(flidx)
			{
			    case 0:
				newflags |= DBW_ELEMENT_LINE_HALFX;
				break;
			    case 1:
				newflags |= DBW_ELEMENT_LINE_HALFY;
				break;
			    case 2:
				newflags &= ~(DBW_ELEMENT_LINE_HALFX);
				break;
			    case 3:
				newflags &= ~(DBW_ELEMENT_LINE_HALFY);
				break;
			    case 4:
			    case 5:
				newflags |= DBW_ELEMENT_LINE_ARROWL;
				break;
			    case 6:
			    case 7:
				newflags |= DBW_ELEMENT_LINE_ARROWR;
				break;
			    case 8:
			    case 9:
				newflags &= ~(DBW_ELEMENT_LINE_ARROWL);
				break;
			    case 10:
			    case 11:
				newflags &= ~(DBW_ELEMENT_LINE_ARROWR);
				break;
			    default:
				TxError("No such line element flag \"%s\"\n",
					flagstr);
				break;
			}
			break;
		    case ELEMENT_RECT:
			TxError("No such rect element flag \"%s\"\n",
					flagstr);
			break;
		}
		break;
	}

	if (newflags != elem->flags)
	{
	    dbwElementUndraw(w, elem);

	    /* Mark element's cell as having been modified either if	*/
	    /* it is persistent or if its persistence has changed.	*/

	    if (elem->flags & DBW_ELEMENT_PERSISTENT ||
			newflags & DBW_ELEMENT_PERSISTENT)
		elem->rootDef->cd_flags |= CDMODIFIED;

	    elem->flags = newflags;
	}
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBWElementStyle --
 *
 * 	Configures style of any element
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Element's style list is reallocated, or printed.
 *	If altered, the element is erased and redrawn.
 *
 * ----------------------------------------------------------------------------
 */

void
DBWElementStyle(
    MagWindow *w,
    char *ename,
    int style,
    bool add)
{
    DBWElement *elem;
    HashEntry *entry;
    styleptr sptr, newstyle;

    entry = HashFind(&elementTable, ename);
    if (entry == NULL)
    {
	TxError("No such element %s\n", ename);
	return;
    }

    elem = (DBWElement *)HashGetValue(entry);
    if (elem == NULL) return;

    if (style== -1)	/* get style(s) */
    {
#ifdef MAGIC_WRAPPER
	for (sptr = elem->stylelist; sptr != NULL; sptr = sptr->next)
	{
	    Tcl_AppendElement(magicinterp, GrStyleTable[sptr->style].longname);
	}
#else
	for (sptr = elem->stylelist; sptr != NULL; sptr = sptr->next)
	{
	    TxPrintf("%s ", GrStyleTable[sptr->style].longname);
	}
	TxPrintf("\n");
#endif
    }
    else
    {
	dbwElementUndraw(w, elem);
	if (add == TRUE)
	{
	    /* add style */
	    for (sptr = elem->stylelist; sptr != NULL && sptr->next != NULL;
			sptr = sptr->next);

	    newstyle = (styleptr)mallocMagic(sizeof(stylestruct));
	    newstyle->style = style;
	    newstyle->next = NULL;
	    if (sptr == NULL)
		elem->stylelist = newstyle;
	    else
		sptr->next = newstyle;
	}
	else
	{
	    /* find style in list */
	    for (sptr = elem->stylelist; sptr != NULL; sptr = sptr->next)
	    {
		if (sptr->next != NULL)
		    if (sptr->next->style == style)
			break;
	    }

	    /* delete style (if it is in the list) */

	    if ((sptr == NULL) && (elem->stylelist != NULL) &&
			(elem->stylelist->style == style))
	    {
		dbwElementUndraw(w, elem);
		free_magic1_t mm1 = freeMagic1_init();
		freeMagic1(&mm1, elem->stylelist);
		elem->stylelist = elem->stylelist->next;
		freeMagic1_end(&mm1);
		if (elem->stylelist == NULL)
		    TxPrintf("Warning:  Element %s has no styles!\n", ename);
	    }
	    else if (sptr == NULL)
	    {
		TxError("Style %d is not in the style list for element %s\n",
			style, ename);
	    }
	    else if (sptr->next != NULL)
	    {
		dbwElementUndraw(w, elem);
		free_magic1_t mm1 = freeMagic1_init();
		freeMagic1(&mm1, sptr->next);
		sptr->next = sptr->next->next;
		freeMagic1_end(&mm1);
	    }
	}
	/* mark element's cell as having been modified */
	if (elem->flags & DBW_ELEMENT_PERSISTENT)
	    elem->rootDef->cd_flags |= CDMODIFIED;
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBWElementPos --
 *
 * 	Configures position of a rect or line element
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Rect or line element's rect structure is altered, or else the
 *	position is printed.  If altered, the element is erased and redrawn.
 *
 * ----------------------------------------------------------------------------
 */

void
DBWElementPos(
    MagWindow *w,
    char *ename,
    Rect *crect)
{
    DBWElement *elem;
    HashEntry *entry;
    Rect prect;
    char ptemp[22];

    entry = HashFind(&elementTable, ename);
    if (entry == NULL)
    {
	TxError("No such element %s\n", ename);
	return;
    }

    elem = (DBWElement *)HashGetValue(entry);
    if (elem == NULL) return;

    if (crect == NULL)	/* Get position */
    {
#ifdef MAGIC_WRAPPER
	snprintf(ptemp, 20, "%d", elem->area.r_xbot);
	Tcl_AppendElement(magicinterp, ptemp);
	snprintf(ptemp, 20, "%d", elem->area.r_ybot);
	Tcl_AppendElement(magicinterp, ptemp);
	if (elem->type == ELEMENT_RECT || elem->type == ELEMENT_LINE)
	{
	    snprintf(ptemp, 20, "%d", elem->area.r_xtop);
	    Tcl_AppendElement(magicinterp, ptemp);
	    snprintf(ptemp, 20, "%d", elem->area.r_ytop);
	    Tcl_AppendElement(magicinterp, ptemp);
	}
#else
	TxPrintf("(%d, %d)", elem->area.r_xbot, elem->area.r_ybot);

	if (elem->type == ELEMENT_RECT || elem->type == ELEMENT_LINE)
	    TxPrintf(" to (%d, %d)", elem->area.r_xtop, elem->area.r_ytop);

	TxPrintf("\n");
#endif
    }
    else		/* Change position */
    {
	dbwElementUndraw(w, elem);
	elem->area = *crect;
	/* mark element's cell as having been modified */
	if (elem->flags & DBW_ELEMENT_PERSISTENT)
	    elem->rootDef->cd_flags |= CDMODIFIED;
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBWElementClearDef --
 *
 * 	Removes all elements associated with the given CellDef.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Elements are removed from the element hash.
 *
 * ----------------------------------------------------------------------------
 */

void
DBWElementClearDef(
    CellDef *cellDef)
{
    DBWElement *elem;
    HashEntry *entry;
    styleptr stylePtr;
    HashSearch hs;

    HashStartSearch(&hs);
    while ((entry = HashNext(&elementTable, &hs)) != NULL)
    {
	elem = (DBWElement *) HashGetValue(entry);
	if (!elem) continue;
	if (elem->rootDef != cellDef) continue;

	free_magic1_t mm1 = freeMagic1_init();
	for (stylePtr = elem->stylelist; stylePtr != NULL; stylePtr = stylePtr->next)
	    freeMagic1(&mm1, stylePtr);
	freeMagic1_end(&mm1);

	if (elem->type == ELEMENT_TEXT)
	    freeMagic(elem->text);

	HashSetValue(entry, NULL);
	freeMagic(elem);
    }
}
