/*
 * Electric(tm) VLSI Design System
 *
 * File: usrdiaedit.c
 * User interface tool: dialog editor
 * Written by: Steven M. Rubin, Static Free Software
 *
 * Copyright (c) 2001 Static Free Software.
 *
 * Electric(tm) 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.
 *
 * Electric(tm) 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 Electric(tm); see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, Mass 02111-1307, USA.
 *
 * Static Free Software
 * 4119 Alpine Road
 * Portola Valley, California 94028
 * support@staticfreesoft.com
 */

#include "global.h"
#include "egraphics.h"
#include "edialogs.h"
#include "usr.h"
#include "usrtrack.h"

#define TYPEUNKNOWN      0
#define TYPEBUTTON       1
#define TYPEDEFBUTTON    2
#define TYPECHECK        3
#define TYPERADIO        4
#define TYPESCROLL       5
#define TYPESCROLLMULTI  6
#define TYPEMESSAGE      7
#define TYPEEDITTEXT     8
#define TYPEICON         9
#define TYPEUSER        10
#define TYPEPOPUP       11
#define TYPEPROGRESS    12
#define TYPEDIVIDER     13

#define DIALOGFILE     "AllDialogs.c"
#define BORDER           2
#define MAXLINE        300
#define SINGLETOP       25
#define SINGLELEFT      61
#define GROWTHSLOP     100
#define SEPARATION       9
#define NUMTYPES        14
#define GROWBOXSIZE      6

static char *us_diaedittypes[] =
{
	"Unknown",
	"Button",
	"Default Button",
	"Check Box",
	"Radio Box",
	"Scroll Area",
	"Scroll Area (multi-select)",
	"Message",
	"Edit Text",
	"Icon",
	"User Drawn",
	"Popup",
	"Progress",
	"Divider"
};

typedef struct Idiaitem
{
	INTBIG           top, left, bottom, right;
	INTBIG           inactive;
	INTBIG           selected;
	INTBIG           type;
	char            *msg;
	struct Idiaitem *next;
} DIAITEM;

typedef struct Idia
{
	char        *comment;
	char        *itemstructname;
	char        *dialogstructname;
	char        *title;
	INTBIG       top, left, bottom, right;
	INTBIG       numitems;
	DIAITEM     *firstitem;
	struct Idia *next;
} DIA;

static void    us_diaeditaligntogrid(void);
static int     us_diaeditcommentascending(const void *e1, const void *e2);
static void    us_diaeditdeletedialog(void);
static char   *us_diaedittitlenextinlist(void);
static BOOLEAN us_diaedittitletoplist(char **a);
static void    us_diaeditduplicateitem(void);
static void    us_diaedititemhit(void);
static void    us_diaeditinvertitem(DIAITEM *item);
static void    us_diaeditmakenewdialog(void);
static void    us_diaeditnewitem(void);
static void    us_diaeditreaddialogs(void);
static void    us_diaeditredrawsingledia(void);
static void    us_diaeditsavedialogs(void);
static void    us_diaeditsetgridamount(void);
static void    us_diaeditsettitle(void);
static void    us_diaeditshowdetails(DIAITEM *item, INTBIG whichitem);
static void    us_diaeditwritequotedstring(char *string, FILE *out);
static void    us_diadrawframe(INTBIG left, INTBIG top, INTBIG right, INTBIG bottom);
static void    us_diaeditdragitem(INTBIG x, INTBIG y);
static void    us_diaeditgrowitem(INTBIG x, INTBIG y);
static void    us_diaeditareaselect(INTBIG x, INTBIG y);
static void    us_diaeditinvertoutline(INTBIG fx, INTBIG fy, INTBIG tx, INTBIG ty);
static void    us_diaeditgrowdialog(INTBIG x, INTBIG y);
static void    us_diaeditredisproutine(RECTAREA *r);
static INTBIG  us_diaeditensuressaved(void);
static void    us_diaeditdosingle(char *dianame);

static DIA      *us_diaeditfirstdia;				/* head of list of all dialogs being edited */
static DIA      *us_diaeditcurdia;					/* current dialog being edited */
static DIA      *us_diaeditloopdia;					/* for looping through dialog list */
static INTBIG    us_diaeditshownumbers;				/* nonzero to show item numbers */
static INTBIG    us_diaeditshowoutline;				/* nonzero to show item outlines */
static INTBIG    us_diaeditshowwidhei;				/* nonzero to use width/height instead of right/bottom */
static INTBIG    us_diaeditdirty;					/* nonzero if dialogs have changed */
static INTBIG    us_diaeditgridamt;					/* grid amount for aligning items */
static INTBIG    us_diaeditlastx, us_diaeditlasty;	/* last coordinate during tracking */
static INTBIG    us_diaeditinix, us_diaeditiniy;	/* initial coordinate during tracking */
static DIAITEM  *us_diaeditclickeditem;				/* current item during tracking */

/********************************** CONTROL **********************************/

/* Dialog Edit: All dialogs */
static DIALOGITEM us_diaeditalldialogitems[] =
{
 /*  1 */ {0, {112,228,136,284}, BUTTON, N_("Edit")},
 /*  2 */ {0, {8,8,336,220}, SCROLL, ""},
 /*  3 */ {0, {52,228,76,284}, BUTTON, N_("Save")},
 /*  4 */ {0, {16,228,40,284}, BUTTON, N_("Done")},
 /*  5 */ {0, {148,228,172,284}, BUTTON, N_("New")},
 /*  6 */ {0, {180,228,204,284}, BUTTON, N_("Delete")},
 /*  7 */ {0, {244,228,268,284}, BUTTON, N_("Title")},
 /*  8 */ {0, {276,228,300,284}, BUTTON, N_("Grid")}
};
static DIALOG us_diaeditalldialog = {{49,56,394,349}, N_("Dialog Editor"), 0, 8, us_diaeditalldialogitems};

#define DDIE_EDITDIA  1		/* edit dialog (button) */
#define DDIE_DIALIST  2		/* dialog list (scroll) */
#define DDIE_SAVE     3		/* save dialogs (button) */
#define DDIE_DONE     4		/* exit editor (button) */
#define DDIE_NEWDIA   5		/* new dialog (button) */
#define DDIE_DELDIA   6		/* delete dialog (button) */
#define DDIE_DIATITLE 7		/* set dialog title (button) */
#define DDIE_SETGRID  8		/* set item grid (button) */

/*
 * Routine called to edit all dialogs.
 */
void us_dialogeditor(void)
{
	INTBIG itemno, which;
	char *pt;

	us_diaeditreaddialogs();	
	
	if (DiaInitDialog(&us_diaeditalldialog)) return;
	DiaInitTextDialog(DDIE_DIALIST, us_diaedittitletoplist, us_diaedittitlenextinlist,
		DiaNullDlogDone, 0, SCSELMOUSE|SCDOUBLEQUIT);
	DiaSelectLine(DDIE_DIALIST, -1);

	us_diaeditshownumbers = 0;
	us_diaeditshowoutline = 1;
	us_diaeditshowwidhei = 0;
	us_diaeditgridamt = 4;
	us_diaeditdirty = 0;
	for(;;)
	{
		itemno = DiaNextHit();
		if (itemno == DDIE_DONE)
		{
			if (us_diaeditensuressaved() == 0) continue;
			break;
		}
		if (itemno == DDIE_DELDIA)
		{
			us_diaeditdeletedialog();
			continue;
		}
		if (itemno == DDIE_SAVE)
		{
			us_diaeditsavedialogs();
			continue;
		}
		if (itemno == DDIE_NEWDIA)
		{
			us_diaeditmakenewdialog();
			continue;
		}
		if (itemno == DDIE_DIATITLE)
		{
			us_diaeditsettitle();
			continue;
		}
		if (itemno == DDIE_SETGRID)
		{
			us_diaeditsetgridamount();
			continue;
		}

		if (itemno == DDIE_EDITDIA)
		{
			which = DiaGetCurLine(DDIE_DIALIST);
			pt = DiaGetScrollLine(DDIE_DIALIST, which);
			us_diaeditdosingle(pt);
			continue;
		}
	}
	DiaDoneDialog();
}

/********************************** CONTROL OF DIALOGS **********************************/

/* Dialog Edit: Single dialog */
static DIALOGITEM us_diaeditsingledialogitems[] =
{
 /*  1 */ {0, {32,4,48,51}, BUTTON, N_("Edit")},
 /*  2 */ {0, {4,4,20,52}, BUTTON, N_("Done")},
 /*  3 */ {0, {60,4,76,52}, BUTTON, N_("New")},
 /*  4 */ {0, {80,4,96,52}, BUTTON, N_("Dup")},
 /*  5 */ {0, {128,4,144,52}, BUTTON, N_("Align")},
 /*  6 */ {0, {4,60,20,156}, CHECK, N_("Numbers")},
 /*  7 */ {0, {4,160,20,256}, CHECK, N_("Outlines")},
 /*  8 */ {0, {25,61,200,300}, USERDRAWN, ""},
 /*  9 */ {0, {100,4,116,52}, BUTTON, N_("Del")}
};
static DIALOG us_diaeditsingledialog = {{100,360,309,669}, N_("Dialog"), 0, 9, us_diaeditsingledialogitems};

#define DONE_EDITITEM     1		/* edit item (button) */
#define DONE_DONEEDIT     2		/* done editing dialog (button) */
#define DONE_NEWITEM      3		/* new item (button) */
#define DONE_DUPITEM      4		/* duplicate item (button) */
#define DONE_ALIGNITEMS   5		/* align all items (button) */
#define DONE_SHOWNUMBERS  6		/* show item numbers (check) */
#define DONE_SHOWOUTLINE  7		/* show item outlines (check) */
#define DONE_ITEMAREA     8		/* item area (user) */
#define DONE_DELITEM      9		/* delete item (button) */

/*
 * Routine to edit a single dialog ("dianame") from the list.
 */
void us_diaeditdosingle(char *dianame)
{
	INTBIG itemno, origright, origbottom, numselected, i, whichitem;
	DIAITEM *item, *clickeditem, *lastitem, *nextitem;

	/* find the dialog */
	for(us_diaeditcurdia = us_diaeditfirstdia; us_diaeditcurdia != 0; us_diaeditcurdia = us_diaeditcurdia->next)
		if (strcmp(us_diaeditcurdia->comment, dianame) == 0) break;
	if (us_diaeditcurdia == 0) return;

	origright = us_diaeditcurdia->right;
	origbottom = us_diaeditcurdia->bottom;
	for(item = us_diaeditcurdia->firstitem; item != 0; item = item->next)
	{
		item->selected = 0;
		if (us_diaeditcurdia->right < item->right + us_diaeditcurdia->left + SEPARATION)
			us_diaeditcurdia->right = item->right + us_diaeditcurdia->left + SEPARATION;
		if (us_diaeditcurdia->bottom < item->bottom + us_diaeditcurdia->top + SEPARATION)
			us_diaeditcurdia->bottom = item->bottom + us_diaeditcurdia->top + SEPARATION;
	}
	if (origright != us_diaeditcurdia->right || origbottom != us_diaeditcurdia->bottom)
	{
		us_diaeditdirty = 1;
		DiaMessageInDialog("Warning: dialog right adjusted by %ld, bottom by %ld",
			us_diaeditcurdia->right - origright, us_diaeditcurdia->bottom - origbottom);
	}
	us_diaeditsingledialog.movable = us_diaeditcurdia->title;
	if (us_diaeditsingledialog.movable == 0)
		us_diaeditsingledialog.movable = "*** DIALOG HAS NO TITLE BAR ***";

	/* set the dialog to the right size */
	us_diaeditsingledialog.windowRect.right = us_diaeditsingledialog.windowRect.left +
		us_diaeditcurdia->right - us_diaeditcurdia->left + SINGLELEFT + GROWTHSLOP;
	us_diaeditsingledialog.windowRect.bottom = us_diaeditsingledialog.windowRect.top +
		us_diaeditcurdia->bottom - us_diaeditcurdia->top + SINGLETOP + GROWTHSLOP;

	/* set the userdraw area to the right size */
	us_diaeditsingledialogitems[DONE_ITEMAREA-1].r.right = (INTSML)(us_diaeditsingledialogitems[DONE_ITEMAREA-1].r.left +
		us_diaeditcurdia->right - us_diaeditcurdia->left + GROWTHSLOP);
	us_diaeditsingledialogitems[DONE_ITEMAREA-1].r.bottom = (INTSML)(us_diaeditsingledialogitems[DONE_ITEMAREA-1].r.top +
		us_diaeditcurdia->bottom - us_diaeditcurdia->top + GROWTHSLOP);

	if (DiaInitDialog(&us_diaeditsingledialog)) return;
	if (us_diaeditshownumbers != 0) DiaSetControl(DONE_SHOWNUMBERS, 1);
	if (us_diaeditshowoutline != 0) DiaSetControl(DONE_SHOWOUTLINE, 1);
	us_diaeditredrawsingledia();
	DiaRedispRoutine(DONE_ITEMAREA, us_diaeditredisproutine);
	DiaAllowUserDoubleClick();
	for(;;)
	{
		itemno = DiaNextHit();
		if (itemno == DONE_ITEMAREA)
		{
			us_diaedititemhit();
			continue;
		}
		if (itemno == DONE_EDITITEM)
		{
			numselected = 0;
			for(item = us_diaeditcurdia->firstitem, i = 1; item != 0; item = item->next, i++)
			{
				if (item->selected == 0) continue;
				numselected++;
				clickeditem = item;
				whichitem = i;
			}
			if (numselected != 1) continue;
			us_diaeditshowdetails(clickeditem, whichitem);
			us_diaeditredrawsingledia();
			continue;
		}
		if (itemno == DONE_DONEEDIT) break;
		if (itemno == DONE_NEWITEM)
		{
			us_diaeditnewitem();
			continue;
		}
		if (itemno == DONE_DUPITEM)
		{
			us_diaeditduplicateitem();
			continue;
		}
		if (itemno == DONE_ALIGNITEMS)
		{
			us_diaeditaligntogrid();
			continue;
		}
		if (itemno == DONE_SHOWNUMBERS)
		{
			us_diaeditshownumbers = 1 - us_diaeditshownumbers;
			DiaSetControl(itemno, us_diaeditshownumbers);
			us_diaeditredrawsingledia();
			continue;
		}
		if (itemno == DONE_SHOWOUTLINE)
		{
			us_diaeditshowoutline = 1 - us_diaeditshowoutline;
			DiaSetControl(itemno, us_diaeditshowoutline);
			us_diaeditredrawsingledia();
			continue;
		}
		if (itemno == DONE_DELITEM)
		{
			lastitem = 0;
			for(item = us_diaeditcurdia->firstitem; item != 0; item = nextitem)
			{
				nextitem = item->next;
				if (item->selected != 0)
				{
					if (lastitem == 0) us_diaeditcurdia->firstitem = nextitem; else
						lastitem->next = nextitem;
					efree((char *)item->msg);
					efree((char *)item);
					us_diaeditcurdia->numitems--;
					us_diaeditdirty++;
					continue;
				}
				lastitem = item;
			}
			us_diaeditredrawsingledia();
		}
	}
	DiaDoneDialog();
}

/* Dialog Edit: Dialog name */
static DIALOGITEM us_diaeditnewdialogitems[] =
{
 /*  1 */ {0, {56,8,76,68}, BUTTON, N_("OK")},
 /*  2 */ {0, {56,168,76,228}, BUTTON, N_("Cancel")},
 /*  3 */ {0, {8,8,24,78}, MESSAGE, N_("Name:")},
 /*  4 */ {0, {8,80,24,228}, EDITTEXT, ""},
 /*  5 */ {0, {32,8,48,98}, MESSAGE, N_("Short name:")},
 /*  6 */ {0, {32,100,48,228}, EDITTEXT, ""}
};
static DIALOG us_diaeditnewdialog = {{450,56,535,293}, N_("New Dialog"), 0, 6, us_diaeditnewdialogitems};

#define DNEW_NAME   4		/* dialog name (edit text) */
#define DNEW_SNAME  6		/* short dialog name (edit text) */

/*
 * Routine to create a new dialog.
 */
void us_diaeditmakenewdialog(void)
{
	DIA *newDia, *lastdia;
	INTBIG itemno, i;
	char name[200], abbrev[200], *pt;

	/* prompt for dialog name and abbreviation */
	if (DiaInitDialog(&us_diaeditnewdialog)) return;
	for(;;)
	{
		itemno = DiaNextHit();
		if (itemno == OK || itemno == CANCEL) break;
	}
	if (itemno == OK)
	{
		strcpy(name, DiaGetText(DNEW_NAME));
		strcpy(abbrev, DiaGetText(DNEW_SNAME));
	}
	DiaDoneDialog();
	if (itemno == CANCEL) return;

	/* create the dialog */
	for(newDia = us_diaeditfirstdia; newDia != 0; newDia = newDia->next)
		lastdia = newDia;
	newDia = (DIA *)emalloc(sizeof (DIA), us_tool->cluster);
	if (us_diaeditfirstdia == 0) us_diaeditfirstdia = newDia; else
		lastdia->next = newDia;

	newDia->comment = (char *)emalloc(strlen(name)+1, us_tool->cluster);
	strcpy(newDia->comment, name);

	sprintf(name, "us_%sdialogitems", abbrev);
	newDia->itemstructname = (char *)emalloc(strlen(name)+1, us_tool->cluster);
	strcpy(newDia->itemstructname, name);

	sprintf(name, "us_%sdialog", abbrev);
	newDia->dialogstructname = (char *)emalloc(strlen(name)+1, us_tool->cluster);
	strcpy(newDia->dialogstructname, name);

	newDia->title = 0;
	newDia->top = 75;     newDia->left = 75;
	newDia->bottom = 300; newDia->right = 300;
	newDia->numitems = 0;
	newDia->firstitem = 0;
	newDia->next = 0;

	/* redisplay the list dialog with this one included */
	DiaLoadTextDialog(DDIE_DIALIST, us_diaedittitletoplist, us_diaedittitlenextinlist, DiaNullDlogDone, 0);
	for(i=0; ; i++)
	{
		pt = DiaGetScrollLine(DDIE_DIALIST, i);
		if (*pt == 0) break;
		if (strcmp(pt, newDia->comment) == 0) break;
	}
	if (*pt != 0) DiaSelectLine(DDIE_DIALIST, i);
	us_diaeditdirty = 1;
}

/*
 * Routine to delete the selected dialog.
 */
void us_diaeditdeletedialog(void)
{
	INTBIG which;
	DIA *lastdia;
	DIAITEM *item, *nextitem;
	char *pt;

	which = DiaGetCurLine(DDIE_DIALIST);
	pt = DiaGetScrollLine(DDIE_DIALIST, which);
	lastdia = 0;
	for(us_diaeditcurdia = us_diaeditfirstdia; us_diaeditcurdia != 0; us_diaeditcurdia = us_diaeditcurdia->next)
	{
		if (strcmp(us_diaeditcurdia->comment, pt) == 0) break;
		lastdia = us_diaeditcurdia;
	}
	if (us_diaeditcurdia == 0) return;
	if (lastdia == 0) us_diaeditfirstdia = us_diaeditcurdia->next; else
		lastdia->next = us_diaeditcurdia->next;

	/* delete the dialog data */
	for(item = us_diaeditcurdia->firstitem; item != 0; item = nextitem)
	{
		nextitem = item->next;
		efree((char *)item->msg);
		efree((char *)item);
	}
	efree(us_diaeditcurdia->comment);
	efree(us_diaeditcurdia->itemstructname);
	efree(us_diaeditcurdia->dialogstructname);
	if (us_diaeditcurdia->title != 0) efree(us_diaeditcurdia->title);
	efree((char *)us_diaeditcurdia);

	/* redisplay list of dialogs */
	DiaLoadTextDialog(DDIE_DIALIST, us_diaedittitletoplist, us_diaedittitlenextinlist, DiaNullDlogDone, 0);
	us_diaeditdirty = 1;
}

/* Dialog Edit: Set title */
static DIALOGITEM us_diaedittitledialogitems[] =
{
 /*  1 */ {0, {32,8,52,68}, BUTTON, N_("OK")},
 /*  2 */ {0, {32,168,52,228}, BUTTON, N_("Cancel")},
 /*  3 */ {0, {8,8,24,228}, EDITTEXT, ""}
};
static DIALOG us_diaedittitledialog = {{450,56,511,293}, N_("Dialog Title"), 0, 3, us_diaedittitledialogitems};

#define DTIT_DIANAME   3		/* dialog name (edit text) */

/*
 * Routine to change the title of the selected dialog.
 */
void us_diaeditsettitle(void)
{
	char *pt;
	INTBIG itemno, which;
	DIA *dia;

	/* get the current dialog */
	which = DiaGetCurLine(DDIE_DIALIST);
	pt = DiaGetScrollLine(DDIE_DIALIST, which);
	for(dia = us_diaeditfirstdia; dia != 0; dia = dia->next)
		if (strcmp(dia->comment, pt) == 0) break;
	if (dia == 0) return;

	/* prompt for the title */
	if (DiaInitDialog(&us_diaedittitledialog)) return;
	if (dia->title != 0)
		DiaSetText(DTIT_DIANAME, dia->title);
	for(;;)
	{
		itemno = DiaNextHit();
		if (itemno == OK || itemno == CANCEL) break;
	}

	/* change the title if requested */
	if (itemno == OK)
	{
		if (dia->title != 0) efree((char *)dia->title);
		dia->title = 0;
		pt = DiaGetText(DTIT_DIANAME);
		if (*pt != 0)
		{
			dia->title = (char *)emalloc(strlen(pt)+1, us_tool->cluster);
			strcpy(dia->title, pt);
		}
		us_diaeditdirty = 1;
	}
	DiaDoneDialog();
}

/*
 * Routine initialize a list of dialogs.
 */
BOOLEAN us_diaedittitletoplist(char **a)
{
	us_diaeditloopdia = us_diaeditfirstdia;
	return(TRUE);
}

/*
 * Routine to return the next in the list of dialogs.
 */
char *us_diaedittitlenextinlist(void)
{
	char *retval;

	if (us_diaeditloopdia == 0) retval = 0; else
	{
		retval = us_diaeditloopdia->comment;
		us_diaeditloopdia = us_diaeditloopdia->next;
	}
	return(retval);
}

/********************************** CONTROL OF ITEMS **********************************/

/*
 * Routine to handle the "single" dialog (the display of a dialog and its items).
 */
void us_diaedititemhit(void)
{
	DIAITEM *clickeditem, *item;
	INTBIG top, left, bottom, right, numselected, i, shiftclick;
	INTBIG x, y;

	shiftclick = 0;
	DiaGetMouse(&x, &y);

	/* see if entire dialog was grown */
	if (x > SINGLELEFT + us_diaeditcurdia->right - us_diaeditcurdia->left &&
		y > SINGLETOP + us_diaeditcurdia->bottom - us_diaeditcurdia->top)
	{
		us_diaeditlastx = x;   us_diaeditlasty = y;
		DiaTrackCursor(us_diaeditgrowdialog);
		return;
	}

	/* see what was clicked on */
	clickeditem = 0;
	for(item = us_diaeditcurdia->firstitem; item != 0; item = item->next)
	{
		top = item->top+SINGLETOP;   bottom = item->bottom+SINGLETOP;
		left = item->left+SINGLELEFT;   right = item->right+SINGLELEFT;
		if (x >= left && x <= right && y >= top && y <= bottom)
		{
			if (clickeditem == 0 || item->selected != 0)
				clickeditem = item;
		}
	}

	/* if shift key not held down, deselect everything */
	if (shiftclick == 0 && (clickeditem == 0 || clickeditem->selected == 0))
	{
		for(item = us_diaeditcurdia->firstitem; item != 0; item = item->next)
		{
			if (item->selected != 0) us_diaeditinvertitem(item);
			item->selected = 0;
		}
	}

	if (clickeditem == 0)
	{
		/* area select */
		us_diaeditlastx = us_diaeditinix = x;   us_diaeditlasty = us_diaeditiniy = y;
		us_diaeditinvertoutline(x, y, x, y);
		DiaTrackCursor(us_diaeditareaselect);

		us_diaeditinvertoutline(us_diaeditinix, us_diaeditiniy, us_diaeditlastx, us_diaeditlasty);
		if (us_diaeditinix > us_diaeditlastx)
		{
			i = us_diaeditinix;   us_diaeditinix = us_diaeditlastx;   us_diaeditlastx = i;
		}
		if (us_diaeditiniy > us_diaeditlasty)
		{
			i = us_diaeditiniy;   us_diaeditiniy = us_diaeditlasty;   us_diaeditlasty = i;
		}
		for(item = us_diaeditcurdia->firstitem; item != 0; item = item->next)
		{
			top = item->top+SINGLETOP;   bottom = item->bottom+SINGLETOP;
			left = item->left+SINGLELEFT;   right = item->right+SINGLELEFT;
			if (left < us_diaeditlastx && right > us_diaeditinix &&
				top < us_diaeditlasty && bottom > us_diaeditiniy)
			{
				if (shiftclick == 0)
				{
					item->selected = 1;
				} else
				{
					item->selected = 1 - item->selected;
				}
			}
		}
		us_diaeditredrawsingledia();
		return;
	}

	/* something selected: either add or subtract selection */
	if (shiftclick == 0)
	{
		clickeditem->selected = 1;
	} else
	{
		clickeditem->selected = 1 - clickeditem->selected;
	}
	us_diaeditinvertitem(clickeditem);
	if (clickeditem->selected == 0) return;

	/* if clicked on one item in corner, stretch it */
	numselected = 0;
	for(item = us_diaeditcurdia->firstitem; item != 0; item = item->next)
	{
		if (item->selected == 0) continue;
		numselected++;
		clickeditem = item;
	}
	if (numselected == 1 && x >= clickeditem->right+SINGLELEFT-GROWBOXSIZE &&
		y >= clickeditem->bottom+SINGLETOP-GROWBOXSIZE)
	{
		/* stretch the item */
		us_diaeditlastx = us_diaeditinix = x;   us_diaeditlasty = us_diaeditiniy = y;
		us_diaeditclickeditem = clickeditem;
		DiaTrackCursor(us_diaeditgrowitem);

		if (clickeditem->right < clickeditem->left)
		{
			i = clickeditem->right;
			clickeditem->right = clickeditem->left;
			clickeditem->left = i;
			us_diaeditredrawsingledia();
		}
		if (clickeditem->bottom < clickeditem->top)
		{
			i = clickeditem->bottom;
			clickeditem->bottom = clickeditem->top;
			clickeditem->top = i;
			us_diaeditredrawsingledia();
		}
		return;
	}

	/* allow dragging of selected items */
	us_diaeditlastx = us_diaeditinix = x;   us_diaeditlasty = us_diaeditiniy = y;
	DiaTrackCursor(us_diaeditdragitem);
}

/*
 * Tracking routine when selecting an area of items.
 */
void us_diaeditareaselect(INTBIG x, INTBIG y)
{
	RECTAREA r;

	DiaItemRect(DONE_ITEMAREA, &r);
	if (x < r.left || x > r.right || y < r.top || y > r.bottom) return;
	us_diaeditinvertoutline(us_diaeditinix, us_diaeditiniy, us_diaeditlastx, us_diaeditlasty);
	us_diaeditlastx = x;   us_diaeditlasty = y;
	us_diaeditinvertoutline(us_diaeditinix, us_diaeditiniy, us_diaeditlastx, us_diaeditlasty);
}

/*
 * Tracking routine when changing the size of an item.
 */
void us_diaeditgrowitem(INTBIG x, INTBIG y)
{
	REGISTER INTBIG dx, dy, right, bottom;

	x = us_diaeditinix + (x - us_diaeditinix) / us_diaeditgridamt * us_diaeditgridamt;
	y = us_diaeditiniy + (y - us_diaeditiniy) / us_diaeditgridamt * us_diaeditgridamt;
	dx = x - us_diaeditlastx;   dy = y - us_diaeditlasty;
	right = us_diaeditclickeditem->right + dx;
	bottom = us_diaeditclickeditem->bottom + dy;
	if (right > us_diaeditcurdia->right - us_diaeditcurdia->left)
		right = us_diaeditcurdia->right - us_diaeditcurdia->left;
	if (bottom > us_diaeditcurdia->bottom - us_diaeditcurdia->top)
		bottom = us_diaeditcurdia->bottom - us_diaeditcurdia->top;
	if (right == us_diaeditclickeditem->right && bottom == us_diaeditclickeditem->bottom)
		return;

	us_diaeditclickeditem->right = right;
	us_diaeditclickeditem->bottom = bottom;
	us_diaeditlastx = x;   us_diaeditlasty = y;
	us_diaeditredrawsingledia();
	us_diaeditdirty = 1;
}

/*
 * Tracking routine when moving an item.
 */
void us_diaeditdragitem(INTBIG x, INTBIG y)
{
	REGISTER INTBIG dx, dy, top, left, bottom, right;
	DIAITEM *item;

	x = us_diaeditinix + (x - us_diaeditinix) / us_diaeditgridamt * us_diaeditgridamt;
	y = us_diaeditiniy + (y - us_diaeditiniy) / us_diaeditgridamt * us_diaeditgridamt;
	dx = x - us_diaeditlastx;   dy = y - us_diaeditlasty;
	for(item = us_diaeditcurdia->firstitem; item != 0; item = item->next)
	{
		if (item->selected == 0) continue;
		left = item->left + dx;
		right = item->right + dx;
		top = item->top + dy;
		bottom = item->bottom + dy;

		if (left < 0) dx -= left;
		if (right > us_diaeditcurdia->right - us_diaeditcurdia->left)
			dx -= right - (us_diaeditcurdia->right - us_diaeditcurdia->left);
		if (top < 0) dy -= top;
		if (bottom > us_diaeditcurdia->bottom - us_diaeditcurdia->top)
			dy -= bottom - (us_diaeditcurdia->bottom - us_diaeditcurdia->top);

		item->left += dx;   item->right += dx;
		item->top += dy;   item->bottom += dy;
	}
	us_diaeditlastx = x;   us_diaeditlasty = y;
	us_diaeditredrawsingledia();
	us_diaeditdirty = 1;
}

/*
 * Tracking routine when changing the size of the dialog.
 */
void us_diaeditgrowdialog(INTBIG x, INTBIG y)
{
	REGISTER INTBIG dx, dy;
	RECTAREA r;

	DiaItemRect(DONE_ITEMAREA, &r);
	if (x < r.left || x > r.right || y < r.top || y > r.bottom) return;
	dx = x - us_diaeditlastx;   dy = y - us_diaeditlasty;
	us_diaeditcurdia->right += dx;
	us_diaeditcurdia->bottom += dy;
	us_diaeditlastx = x;   us_diaeditlasty = y;
	us_diaeditredrawsingledia();
	us_diaeditdirty = 1;
}

/* Dialog Edit: Grid alignment */
static DIALOGITEM us_diaeditgriddialogitems[] =
{
 /*  1 */ {0, {32,8,52,68}, BUTTON, N_("OK")},
 /*  2 */ {0, {32,168,52,228}, BUTTON, N_("Cancel")},
 /*  3 */ {0, {8,58,24,178}, EDITTEXT, ""}
};
static DIALOG us_diaeditgriddialog = {{450,56,511,293}, N_("Grid Amount"), 0, 3, us_diaeditgriddialogitems};

#define DGRI_AMOUNT   3		/* grid amount (edit text) */

/*
 * Routine to set the grid amount.
 */
void us_diaeditsetgridamount(void)
{
	INTBIG itemno;
	char line[50];

	if (DiaInitDialog(&us_diaeditgriddialog)) return;
	sprintf(line, "%ld", us_diaeditgridamt);
	DiaSetText(DGRI_AMOUNT, line);
	for(;;)
	{
		itemno = DiaNextHit();
		if (itemno == OK || itemno == CANCEL) break;
	}
	if (itemno == OK)
		us_diaeditgridamt = atoi(DiaGetText(DGRI_AMOUNT));
	DiaDoneDialog();
}

/*
 * Routine to create a new item.
 */
void us_diaeditnewitem(void)
{
	DIAITEM *item, *lastitem;
	char *pt;

	for(item = us_diaeditcurdia->firstitem; item != 0; item = item->next)
	{
		item->selected = 0;
		lastitem = item;
	}
	item = (DIAITEM *)emalloc(sizeof (DIAITEM), us_tool->cluster);
	if (us_diaeditcurdia->firstitem == 0) us_diaeditcurdia->firstitem = item; else
		lastitem->next = item;
	item->next = 0;

	item->top = 20;
	item->left = 20;
	if (us_diaeditcurdia->firstitem == item) item->bottom = 44; else
		item->bottom = 36;
	item->right = 100;
	item->inactive = 0;
	item->selected = 1;
	item->type = TYPEBUTTON;
	pt = "item";
	item->msg = (char *)emalloc(strlen(pt)+1, us_tool->cluster);
	strcpy(item->msg, pt);
	us_diaeditcurdia->numitems++;
	us_diaeditredrawsingledia();
	us_diaeditdirty = 1;
}

/*
 * Routine to duplicate the selected item(s).
 */
void us_diaeditduplicateitem(void)
{
	DIAITEM *item, *newitem, *lastitem;

	for(item = us_diaeditcurdia->firstitem; item != 0; item = item->next)
		lastitem = item;
	for(item = us_diaeditcurdia->firstitem; item != 0; item = item->next)
	{
		if (item->selected != 1) continue;
		newitem = (DIAITEM *)emalloc(sizeof (DIAITEM), us_tool->cluster);
		if (us_diaeditcurdia->firstitem == 0) us_diaeditcurdia->firstitem = newitem; else
			lastitem->next = newitem;
		lastitem = newitem;
		newitem->next = 0;

		newitem->top = item->top + us_diaeditgridamt;
		newitem->left = item->left + us_diaeditgridamt;
		newitem->bottom = item->bottom + us_diaeditgridamt;
		newitem->right = item->right + us_diaeditgridamt;
		newitem->inactive = item->inactive;
		item->selected = 0;
		newitem->selected = 2;
		newitem->type = item->type;
		newitem->msg = (char *)emalloc(strlen(item->msg)+1, us_tool->cluster);
		strcpy(newitem->msg, item->msg);
		us_diaeditcurdia->numitems++;
	}
	for(item = us_diaeditcurdia->firstitem; item != 0; item = item->next)
		if (item->selected != 0) item->selected = 1;
	us_diaeditredrawsingledia();
	us_diaeditdirty = 1;
}

/*
 * Routine to align all items in this dialog to the grid.
 */
void us_diaeditaligntogrid(void)
{
	DIAITEM *item;

	for(item = us_diaeditcurdia->firstitem; item != 0; item = item->next)
	{
		if (item->selected == 0) continue;
		item->top = item->top / us_diaeditgridamt * us_diaeditgridamt;
		item->left = item->left / us_diaeditgridamt * us_diaeditgridamt;
		item->bottom = item->bottom / us_diaeditgridamt * us_diaeditgridamt;
		item->right = item->right / us_diaeditgridamt * us_diaeditgridamt;
		us_diaeditdirty = 1;
	}
	us_diaeditredrawsingledia();
}

/********************************** ITEM CONTROL **********************************/

/* Dialog Edit: Item details */
static DIALOGITEM us_diaedititemdialogitems[] =
{
 /*  1 */ {0, {104,260,124,320}, BUTTON, N_("OK")},
 /*  2 */ {0, {104,20,124,80}, BUTTON, N_("Cancel")},
 /*  3 */ {0, {56,8,72,332}, EDITTEXT, ""},
 /*  4 */ {0, {8,8,24,58}, MESSAGE, N_("Top:")},
 /*  5 */ {0, {8,60,24,90}, EDITTEXT, ""},
 /*  6 */ {0, {32,8,48,58}, MESSAGE, N_("Left:")},
 /*  7 */ {0, {32,60,48,90}, EDITTEXT, ""},
 /*  8 */ {0, {8,110,24,160}, MESSAGE, N_("Bottom:")},
 /*  9 */ {0, {8,162,24,192}, EDITTEXT, ""},
 /* 10 */ {0, {32,110,48,160}, MESSAGE, N_("Right:")},
 /* 11 */ {0, {32,162,48,192}, EDITTEXT, ""},
 /* 12 */ {0, {80,8,96,214}, POPUP, ""},
 /* 13 */ {0, {80,228,97,332}, CHECK, N_("Inactive")},
 /* 14 */ {0, {20,200,36,332}, CHECK, N_("Width and Height")}
};
static DIALOG us_diaedititemdialog = {{450,56,583,397}, N_("Item Information"), 0, 14, us_diaedititemdialogitems};

#define DITD_MESSAGE  3			/* item text (edit text) */
#define DITD_TOP      5			/* top coordinate (edit text) */
#define DITD_LEFT     7			/* left coordinate (edit text) */
#define DITD_BOTTOM_L 8			/* bottom coordinate label (message) */
#define DITD_BOTTOM   9			/* bottom coordinate (edit text) */
#define DITD_RIGHT_L  10		/* right coordinate label (message) */
#define DITD_RIGHT    11		/* right coordinate (edit text) */
#define DITD_ITEMTYPE 12		/* item type (popup) */
#define DITD_INACTIVE 13		/* inactive item (check) */
#define DITD_WIDHEI   14		/* show width&height (check) */

/*
 * Routine to display the details of a single item.
 * returns nonzero if something changed.
 */
void us_diaeditshowdetails(DIAITEM *item, INTBIG whichitem)
{
	char line[50], *pt;
	INTBIG itemno;

	sprintf(line, "Item %ld Details", whichitem);
	us_diaedititemdialog.movable = line;
	if (DiaInitDialog(&us_diaedititemdialog)) return;
	DiaSetPopup(DITD_ITEMTYPE, NUMTYPES, us_diaedittypes);
	DiaSetPopupEntry(DITD_ITEMTYPE, item->type);
	if (item->inactive != 0) DiaSetControl(DITD_INACTIVE, 1);
	sprintf(line, "%ld", item->top);    DiaSetText(DITD_TOP, line);
	sprintf(line, "%ld", item->left);   DiaSetText(DITD_LEFT, line);
	if (us_diaeditshowwidhei != 0)
	{
		DiaSetControl(DITD_WIDHEI, 1);
		DiaSetText(DITD_BOTTOM_L, "Height:");
		DiaSetText(DITD_RIGHT_L, "Width:");
		sprintf(line, "%ld", item->bottom - item->top);   DiaSetText(DITD_BOTTOM, line);
		sprintf(line, "%ld", item->right - item->left);   DiaSetText(DITD_RIGHT, line);
	} else
	{
		sprintf(line, "%ld", item->bottom);   DiaSetText(DITD_BOTTOM, line);
		sprintf(line, "%ld", item->right);    DiaSetText(DITD_RIGHT, line);
	}
	DiaSetText(-DITD_MESSAGE, item->msg);
	for(;;)
	{
		itemno = DiaNextHit();
		if (itemno == OK || itemno == CANCEL) break;
		if (itemno == DITD_INACTIVE)
		{
			DiaSetControl(itemno, 1 - DiaGetControl(itemno));
			continue;
		}
		if (itemno == DITD_WIDHEI)
		{
			us_diaeditshowwidhei = 1 - us_diaeditshowwidhei;
			DiaSetControl(itemno, us_diaeditshowwidhei);
			if (us_diaeditshowwidhei != 0)
			{
				DiaSetText(DITD_BOTTOM_L, "Height:");
				DiaSetText(DITD_RIGHT_L, "Width:");
				sprintf(line, "%ld", item->bottom - item->top);   DiaSetText(DITD_BOTTOM, line);
				sprintf(line, "%ld", item->right - item->left);   DiaSetText(DITD_RIGHT, line);
			} else
			{
				DiaSetText(DITD_BOTTOM_L, "Bottom:");
				DiaSetText(DITD_RIGHT_L, "Right:");
				sprintf(line, "%ld", item->bottom);   DiaSetText(DITD_BOTTOM, line);
				sprintf(line, "%ld", item->right);    DiaSetText(DITD_RIGHT, line);
			}
			continue;
		}
	}
	if (itemno == OK)
	{
		item->top = atoi(DiaGetText(DITD_TOP));
		item->left = atoi(DiaGetText(DITD_LEFT));
		if (us_diaeditshowwidhei != 0)
		{
			item->bottom = item->top + atoi(DiaGetText(DITD_BOTTOM));
			item->right = item->left + atoi(DiaGetText(DITD_RIGHT));
		} else
		{
			item->bottom = atoi(DiaGetText(DITD_BOTTOM));
			item->right = atoi(DiaGetText(DITD_RIGHT));
		}
		efree((char *)item->msg);
		pt = DiaGetText(DITD_MESSAGE);
		item->msg = (char *)emalloc(strlen(pt)+1, us_tool->cluster);
		strcpy(item->msg, pt);
		item->type = DiaGetPopupEntry(DITD_ITEMTYPE);
		item->inactive = DiaGetControl(DITD_INACTIVE);
		us_diaeditdirty = 1;
	}
	DiaDoneDialog();
}

/********************************** DISPLAY **********************************/

/*
 * Routine to highlight item "item" by inverting a rectangle around it.
 */
void us_diaeditinvertitem(DIAITEM *item)
{
	INTBIG top, left, bottom, right;
	RECTAREA r;

	top = item->top+SINGLETOP-1;   bottom = item->bottom+SINGLETOP;
	left = item->left+SINGLELEFT-1;   right = item->right+SINGLELEFT;
	DiaDrawLine(DONE_ITEMAREA, left, top, left, bottom, DLMODEINVERT);
	DiaDrawLine(DONE_ITEMAREA, left, bottom, right, bottom, DLMODEINVERT);
	DiaDrawLine(DONE_ITEMAREA, right, bottom, right, top, DLMODEINVERT);
	DiaDrawLine(DONE_ITEMAREA, right, top, left, top, DLMODEINVERT);
	r.left = (INTSML)(right - GROWBOXSIZE);   r.right = (INTSML)right;
	r.top = (INTSML)(bottom - GROWBOXSIZE);   r.bottom = (INTSML)bottom;
	DiaInvertRect(DONE_ITEMAREA, &r);
}

/*
 * Routine called to redisplay the dialog items.
 */
void us_diaeditredisproutine(RECTAREA *r)
{
	us_diaeditredrawsingledia();
}

/*
 * Routine to redraw all of the items in the dialog.
 */
void us_diaeditredrawsingledia(void)
{
	DIAITEM *item;
	RECTAREA r;
	char *msg, line[50];
	INTBIG top, left, bottom, right, x, y, height, i;

	DiaItemRect(DONE_ITEMAREA, &r);
	DiaDrawRect(DONE_ITEMAREA, &r, 255, 255, 255);
	r.left = SINGLELEFT;
	r.right = SINGLELEFT + us_diaeditcurdia->right-us_diaeditcurdia->left + BORDER;
	r.top = SINGLETOP;
    r.bottom = SINGLETOP + us_diaeditcurdia->bottom-us_diaeditcurdia->top + BORDER;
	DiaFrameRect(DONE_ITEMAREA, &r);
	r.left = r.right;   r.right = (INTSML)(r.left + GROWBOXSIZE);
	r.top = r.bottom;   r.bottom = (INTSML)(r.top + GROWBOXSIZE);
	DiaDrawRect(DONE_ITEMAREA, &r, 0, 0, 0);
	DiaSetTextSize(12);

	for(item = us_diaeditcurdia->firstitem, i = 1; item != 0; item = item->next, i++)
	{
		top = item->top+SINGLETOP;   bottom = item->bottom+SINGLETOP;
		left = item->left+SINGLELEFT;   right = item->right+SINGLELEFT;
		switch (item->type)
		{
			case TYPEBUTTON:
			case TYPEDEFBUTTON:
				us_diadrawframe(left, top, right, bottom);
				DiaGetTextInfo(item->msg, &x, &y);
				DiaPutText(DONE_ITEMAREA, item->msg, left + (right-left-x)/2, bottom - (bottom-top+y)/2);
				break;
			case TYPECHECK:
				height = bottom - top - 4;
				if (us_diaeditshowoutline != 0)
					us_diadrawframe(left, top, right, bottom);
				us_diadrawframe(left, top+2, left+height, bottom-2);
				DiaDrawLine(DONE_ITEMAREA, left, bottom-2, left+height, top+2, DLMODEON);
				DiaDrawLine(DONE_ITEMAREA, left, top+2, left+height, bottom-2, DLMODEON);
				DiaGetTextInfo(item->msg, &x, &y);
				DiaPutText(DONE_ITEMAREA, item->msg, left+height+5, bottom - (bottom-top+y)/2);
				break;
			case TYPERADIO:
				height = bottom - top - 4;
				if (us_diaeditshowoutline != 0)
					us_diadrawframe(left, top, right, bottom);
				DiaDrawLine(DONE_ITEMAREA, left+height/2, top+2, left+height, (top+bottom)/2, DLMODEON);
				DiaDrawLine(DONE_ITEMAREA, left+height, (top+bottom)/2, left+height/2, bottom-2, DLMODEON);
				DiaDrawLine(DONE_ITEMAREA, left+height/2, bottom-2, left, (top+bottom)/2, DLMODEON);
				DiaDrawLine(DONE_ITEMAREA, left, (top+bottom)/2, left+height/2, top+2, DLMODEON);
				DiaGetTextInfo(item->msg, &x, &y);
				DiaPutText(DONE_ITEMAREA, item->msg, left+height+5, bottom - (bottom-top+y)/2);
				break;
			case TYPESCROLL:
				msg = "SCROLL AREA";
				us_diadrawframe(left+2, top+2, right-2, bottom-2);
				us_diadrawframe(left, top, right, bottom);
				DiaGetTextInfo(msg, &x, &y);
				DiaPutText(DONE_ITEMAREA, msg, left + (right-left-x)/2, bottom - (bottom-top+y)/2);
				break;
			case TYPESCROLLMULTI:
				msg = "SCROLL AREA (MULTI)";
				us_diadrawframe(left+2, top+2, right-2, bottom-2);
				us_diadrawframe(left, top, right, bottom);
				DiaGetTextInfo(msg, &x, &y);
				DiaPutText(DONE_ITEMAREA, msg, left + (right-left-x)/2, bottom - (bottom-top+y)/2);
				break;
			case TYPEMESSAGE:
				if (us_diaeditshowoutline != 0)
					us_diadrawframe(left, top, right, bottom);
				DiaGetTextInfo(item->msg, &x, &y);
				DiaPutText(DONE_ITEMAREA, item->msg, left+5, bottom - (bottom-top+y)/2);
				break;
			case TYPEEDITTEXT:
				us_diadrawframe(left-2, top-2, right+2, bottom+2);
				us_diadrawframe(left, top, right, bottom);
				DiaGetTextInfo(item->msg, &x, &y);
				DiaPutText(DONE_ITEMAREA, item->msg, left+5, bottom - (bottom-top+y)/2);
				break;
			case TYPEICON:
				msg = "ICON";
				us_diadrawframe(left, top, right, bottom);
				DiaGetTextInfo(msg, &x, &y);
				DiaPutText(DONE_ITEMAREA, msg, left + (right-left-x)/2, bottom - (bottom-top+y)/2);
				break;
			case TYPEUSER:
				us_diadrawframe(left, top, right, bottom);
				DiaDrawLine(DONE_ITEMAREA, left, top, right, bottom, DLMODEON);
				DiaDrawLine(DONE_ITEMAREA, left, bottom, right, top, DLMODEON);
				break;
			case TYPEPOPUP:
				msg = "POPUP";
				us_diadrawframe(left, top, right, bottom);
				height = (bottom - top) / 3;
				DiaDrawLine(DONE_ITEMAREA, right-height, top+height, right-height*3, top+height, DLMODEON);
				DiaDrawLine(DONE_ITEMAREA, right-height*3, top+height, right-height*2, bottom-height, DLMODEON);
				DiaDrawLine(DONE_ITEMAREA, right-height*2, bottom-height, right-height, top+height, DLMODEON);
				DiaGetTextInfo(msg, &x, &y);
				DiaPutText(DONE_ITEMAREA, msg, left + (right-left-x)/2, bottom - (bottom-top+y)/2);
				break;
			case TYPEPROGRESS:
				msg = "PROGRESS";
				us_diadrawframe(left, top, right, bottom);
				DiaGetTextInfo(msg, &x, &y);
				DiaPutText(DONE_ITEMAREA, msg, left + (right-left-x)/2, bottom - (bottom-top+y)/2);
				break;
			case TYPEDIVIDER:
				us_diadrawframe(left, top, right, bottom);
				break;
		}
		if (us_diaeditshownumbers != 0)
		{
			DiaSetTextSize(10);
			sprintf(line, "%ld", i);
			DiaGetTextInfo(line, &x, &y);
			DiaPutText(DONE_ITEMAREA, line, right - x, top);
			DiaSetTextSize(12);
		}
		if (item->selected != 0) us_diaeditinvertitem(item);
	}
}

/*
 * Routine to draw a rectangular outline.
 */
void us_diadrawframe(INTBIG left, INTBIG top, INTBIG right, INTBIG bottom)
{
	RECTAREA r;

	r.left = (INTSML)left;   r.right = (INTSML)right;
	r.top = (INTSML)top;     r.bottom = (INTSML)bottom;
	DiaFrameRect(DONE_ITEMAREA, &r);
}

/*
 * Routine to invert a rectangular outline.
 */
void us_diaeditinvertoutline(INTBIG fx, INTBIG fy, INTBIG tx, INTBIG ty)
{
	DiaDrawLine(DONE_ITEMAREA, fx, fy, fx, ty, DLMODEINVERT);
	DiaDrawLine(DONE_ITEMAREA, fx, ty, tx, ty, DLMODEINVERT);
	DiaDrawLine(DONE_ITEMAREA, tx, ty, tx, fy, DLMODEINVERT);
	DiaDrawLine(DONE_ITEMAREA, tx, fy, fx, fy, DLMODEINVERT);
}

/********************************** INPUT/OUTPUT **********************************/

/*
 * Routine to read the dialogs into memory.
 */
void us_diaeditreaddialogs(void)
{
	FILE *in;
	DIA *dia, *lastdia;
	DIAITEM *item, *lastitem;
	char *pt, *ept, *filename;
	INTBIG len;
	char line[MAXLINE];

	us_diaeditfirstdia = 0;
	(void)initinfstr();
	(void)addstringtoinfstr(el_libdir);
	(void)addstringtoinfstr(DIALOGFILE);
	pt = truepath(returninfstr());
	in = xopen(pt, el_filetypetext, "", &filename);
	if (in == 0) return;
	for(;;)
	{
		if (xfgets(line, MAXLINE, in)) break;
		if (line[0] == 0) continue;
		if (strncmp(line, "/*", 2) == 0)
		{
			/* comment before a dialog */
			dia = (DIA *)emalloc(sizeof (DIA), us_tool->cluster);
			len = strlen(line);
			line[len-3] = 0;
			dia->comment = (char *)emalloc(strlen(&line[3])+1, us_tool->cluster);
			strcpy(dia->comment, &line[3]);
			dia->firstitem = 0;
			dia->numitems = 0;
			if (us_diaeditfirstdia == 0) us_diaeditfirstdia = dia; else
				lastdia->next = dia;
			lastdia = dia;
			dia->next = 0;
			continue;
		}
		if (strncmp(line, "static DIALOGITEM", 17) == 0)
		{
			/* header for items */
			len = strlen(line);
			line[len-4] = 0;
			dia->itemstructname = (char *)emalloc(strlen(&line[18])+1, us_tool->cluster);
			strcpy(dia->itemstructname, &line[18]);
			continue;
		}
		if (*line == '{') continue;
		if (strncmp(line, " /* ", 4) == 0)
		{
			/* an item */
			item = (DIAITEM *)emalloc(sizeof (DIAITEM), us_tool->cluster);
			if (dia->firstitem == 0) dia->firstitem = item; else
				lastitem->next = item;
			item->next = 0;
			lastitem = item;
			pt = &line[15];
			item->top = atoi(pt);
			while (*pt != ',' && *pt != 0) pt++;
			if (*pt == ',') pt++;
			item->left = atoi(pt);
			while (*pt != ',' && *pt != 0) pt++;
			if (*pt == ',') pt++;
			item->bottom = atoi(pt);
			while (*pt != ',' && *pt != 0) pt++;
			if (*pt == ',') pt++;
			item->right = atoi(pt);
			while (*pt != '}' && *pt != 0) pt++;
			if (*pt == 0) continue;
			pt += 3;
			item->type = TYPEUNKNOWN;
			if (strncmp(pt, "BUTTON", 6) == 0) item->type = TYPEBUTTON; else
			if (strncmp(pt, "DEFBUTTON", 9) == 0) item->type = TYPEDEFBUTTON; else
			if (strncmp(pt, "CHECK", 5) == 0) item->type = TYPECHECK; else
			if (strncmp(pt, "RADIO", 5) == 0) item->type = TYPERADIO; else
			if (strncmp(pt, "SCROLLMULTI", 11) == 0) item->type = TYPESCROLLMULTI; else
			if (strncmp(pt, "SCROLL", 6) == 0) item->type = TYPESCROLL; else
			if (strncmp(pt, "MESSAGE", 7) == 0) item->type = TYPEMESSAGE; else
			if (strncmp(pt, "EDITTEXT", 8) == 0) item->type = TYPEEDITTEXT; else
			if (strncmp(pt, "ICON", 4) == 0) item->type = TYPEICON; else
			if (strncmp(pt, "USERDRAWN", 9) == 0) item->type = TYPEUSER; else
			if (strncmp(pt, "POPUP", 5) == 0) item->type = TYPEPOPUP; else
			if (strncmp(pt, "PROGRESS", 8) == 0) item->type = TYPEPROGRESS; else
			if (strncmp(pt, "DIVIDELINE", 10) == 0) item->type = TYPEDIVIDER;
			while (*pt != ',' && *pt != '|' && *pt != 0) pt++;
			item->inactive = 0;
			if (strncmp(pt, "|INACTIVE", 9) == 0)
			{
				item->inactive = 1;
				pt += 9;
			}
			if (*pt == 0) continue;
			if (item->type == TYPEICON)
			{
				pt += 2;
				ept = pt;
				while (*ept != '}' && *ept != 0) ept++;
				*ept = 0;
				item->msg = (char *)emalloc(strlen(pt)+1, us_tool->cluster);
				strcpy(item->msg, pt);
			} else
			{
				pt += 3;
				if (pt[-1] == 'N' && pt[0] == '_' && pt[1] == '(') pt += 3;
				ept = pt;
				while (*ept != '"' && *ept != 0) ept++;
				*ept = 0;
				item->msg = (char *)emalloc(strlen(pt)+1, us_tool->cluster);
				strcpy(item->msg, pt);
			}
			dia->numitems++;
			continue;
		}
		if (*line == '}') continue;
		if (strncmp(line, "static DIALOG ", 14) == 0)
		{
			pt = &line[14];
			ept = pt;
			while (*ept != ' ' && *ept != 0) ept++;
			*ept = 0;
			dia->dialogstructname = (char *)emalloc(strlen(pt)+1, us_tool->cluster);
			strcpy(dia->dialogstructname, pt);
			ept += 5;
			dia->top = atoi(ept);
			while (*ept != ',' && *ept != 0) ept++;
			if (*ept == ',') ept++;
			dia->left = atoi(ept);
			while (*ept != ',' && *ept != 0) ept++;
			if (*ept == ',') ept++;
			dia->bottom = atoi(ept);
			while (*ept != ',' && *ept != 0) ept++;
			if (*ept == ',') ept++;
			dia->right = atoi(ept);
			while (*ept != '}' && *ept != 0) ept++;
			ept += 3;
			if (*ept == '0') dia->title = 0; else
			{
				if (ept[0] == 'N' && ept[1] == '_' && ept[2] == '(') ept += 3;
				if (*ept == '"')
				{
					ept++;
					pt = ept;
					while (*pt != '"' && *pt != 0) pt++;
					*pt = 0;
					dia->title = (char *)emalloc(strlen(ept)+1, us_tool->cluster);
					strcpy(dia->title, ept);
				}
			}
			continue;
		}
	}
	xclose(in);
}

/*
 * Routine to save the dialogs to disk.
 */
void us_diaeditsavedialogs(void)
{
	FILE *out;
	DIA *dia, **diaList;
	char *truename;
	DIAITEM *item;
	INTBIG i, total;

	/* sort the dialogs */
	for(total = 0, dia = us_diaeditfirstdia; dia != 0; dia = dia->next) total++;
	diaList = (DIA **)emalloc(total * (sizeof (DIA *)), us_tool->cluster);
	for(i = 0, dia = us_diaeditfirstdia; dia != 0; dia = dia->next) diaList[i++] = dia;
	esort(diaList, total, sizeof (DIA *), us_diaeditcommentascending);
	for(i=0; i<total-1; i++) diaList[i]->next = diaList[i+1];
	diaList[total-1]->next = 0;
	us_diaeditfirstdia = diaList[0];
	efree((char *)diaList);

	(void)initinfstr();
	(void)addstringtoinfstr(el_libdir);
	(void)addstringtoinfstr(DIALOGFILE);
	out = xcreate(returninfstr(), el_filetypetext, 0, &truename);
	if (out == 0) return;
	for(dia = us_diaeditfirstdia; dia != 0; dia = dia->next)
	{
		fprintf(out, "/* %s */\n", dia->comment);
		fprintf(out, "static DIALOGITEM %s[] =\n", dia->itemstructname);
		fprintf(out, "{\n");
		for(i=1, item = dia->firstitem; item != 0; item = item->next, i++)
		{
			fprintf(out, " /* %2ld */ {0, {%ld,%ld,%ld,%ld}, ", i, item->top, item->left,
				item->bottom, item->right);
			switch (item->type)
			{
				case TYPEBUTTON:      fprintf(out, "BUTTON");      break;
				case TYPEDEFBUTTON:   fprintf(out, "DEFBUTTON");   break;
				case TYPECHECK:       fprintf(out, "CHECK");       break;
				case TYPERADIO:       fprintf(out, "RADIO");       break;
				case TYPESCROLL:      fprintf(out, "SCROLL");      break;
				case TYPESCROLLMULTI: fprintf(out, "SCROLLMULTI"); break;
				case TYPEMESSAGE:     fprintf(out, "MESSAGE");     break;
				case TYPEEDITTEXT:    fprintf(out, "EDITTEXT");    break;
				case TYPEICON:        fprintf(out, "ICON");        break;
				case TYPEUSER:        fprintf(out, "USERDRAWN");   break;
				case TYPEPOPUP:       fprintf(out, "POPUP");       break;
				case TYPEPROGRESS:    fprintf(out, "PROGRESS");    break;
				case TYPEDIVIDER:     fprintf(out, "DIVIDELINE");  break;
			}
			if (item->inactive != 0) fprintf(out, "|INACTIVE");
			fprintf(out, ", ");
			if (item->type == TYPEICON)
			{
				fprintf(out, "%s", item->msg);
			} else
			{
				us_diaeditwritequotedstring(item->msg, out);
			}
			fprintf(out, "}");
			if (item->next != 0) fprintf(out, ",");
			fprintf(out, "\n");
		}
		fprintf(out, "};\n");
		fprintf(out, "static DIALOG %s = {{%ld,%ld,%ld,%ld}, ", dia->dialogstructname, dia->top, dia->left,
			dia->bottom, dia->right);
		if (dia->title == 0) fprintf(out, "0"); else
			us_diaeditwritequotedstring(dia->title, out);
		fprintf(out, ", 0");
		fprintf(out, ", %ld, %s};\n", i-1, dia->itemstructname);
		fprintf(out, "\n");
	}
	xclose(out);
	us_diaeditdirty = 0;
}

/*
 * Sorting coroutine to order the dialogs by their comment field.
 */
int us_diaeditcommentascending(const void *e1, const void *e2)
{
	DIA *d1, *d2;

	d1 = *((DIA **)e1);
	d2 = *((DIA **)e2);
	return(strcmp(d1->comment, d2->comment));
}

/*
 * Routine to write "string" to "out", quoting appropriately for language translation.
 */
void us_diaeditwritequotedstring(char *string, FILE *out)
{
	char *pt;

	if (*string == 0)
	{
		fprintf(out, "\"\"");
		return;
	}
	for(pt = string; *pt != 0; pt++)
		if (isalpha(*pt) != 0) break;
	if (*pt != 0) fprintf(out, "N_(\"%s\")", string); else
		fprintf(out, "\"%s\"", string);
}

/* Dialog Edit: Save */
static DIALOGITEM us_diaeditsavedialogitems[] =
{
 /*  1 */ {0, {8,8,32,68}, BUTTON, N_("Save")},
 /*  2 */ {0, {8,88,32,148}, BUTTON, N_("Cancel")},
 /*  3 */ {0, {8,168,32,228}, BUTTON, N_("No Save")}
};
static DIALOG us_diaeditsavedialog = {{450,56,491,293}, N_("Dialogs changed.  Save?"), 0, 3, us_diaeditsavedialogitems};

#define DSAV_SAVE    1		/* save dialogs before exiting (button) */
#define DSAV_CANCEL  2		/* cancel exit (button) */
#define DSAV_NOSAVE  3		/* do not save dialogs before exiting (button) */

/*
 * Routine to ensure that the dialogs are saved.  Returns nonzero if
 * they are saved, zero if the editor should not quit.
 */
INTBIG us_diaeditensuressaved(void)
{
	REGISTER INTBIG itemno;

	if (us_diaeditdirty != 0)
	{
		if (DiaInitDialog(&us_diaeditsavedialog)) return(1);
		for(;;)
		{
			itemno = DiaNextHit();
			if (itemno == DSAV_SAVE)
			{
				us_diaeditsavedialogs();
				break;
			}
			if (itemno == DSAV_CANCEL || itemno == DSAV_NOSAVE) break;
		}
		DiaDoneDialog();
		if (itemno == DSAV_CANCEL) return(0);
	}
	return(1);
}
