/*
 * Electric(tm) VLSI Design System
 *
 * File: graphpccode.cpp
 * Interface for Win32 (Win9x/NT) computers
 * Written by: Steven M. Rubin, Static Free Software
 *
 * Copyright (c) 2000 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
 * info@staticfreesoft.com
 */

#include "graphpcstdafx.h"
#include "graphpc.h"
#include "graphpcdoc.h"
#include "graphpcview.h"
#include "graphpcmainframe.h"
#include "graphpcchildframe.h"
#include "graphpcmsgview.h"
#include "graphpcdialog.h"

extern "C"
{
#include "global.h"
#include "database.h"
#include "egraphics.h"
#include "usr.h"
#include "eio.h"
#include "edialogs.h"
#if LANGTCL
#  include "dblang.h"
#endif
}

#include <io.h>
#include <signal.h>
#include <sys/stat.h>
#include <direct.h>
#include <sys/timeb.h>

/****** windows and control ******/
extern CElectricApp theApp;
static WINDOWFRAME *gra_cureditwindowframe;
static WINDOWFRAME *gra_palettewindowframe = NOWINDOWFRAME;
static INTSML       gra_windowframeindex = 0;
static INTSML       gra_newwindowoffset = 0;
static char        *gra_initialdirectory;

void   gra_activateframe(CChildFrame *frame, BOOL bActivate);
INTSML gra_buildoffscreenbuffer(WINDOWFRAME *wf, INTSML wid, INTSML hei, CDC **hdc,
		HBITMAP *bitmap, BITMAPINFO **bminfo, char **databuffer, char ***rowstart);
INTSML gra_buildwindow(WINDOWFRAME*, INTSML, RECTAREA*);
int    gra_closeframe(CChildFrame *frame);
int    gra_closeworld(void);
void   gra_timerticked(void);
void   gra_freewindowframe(WINDOWFRAME *wf);
RECT  *gra_geteditorwindowlocation(void);
void   gra_getdevices(void);
HWND   gra_getwindow(WINDOWPART *win);
#if LANGTCL
INTBIG gra_initializetcl(void);
#endif
INTSML gra_makeeditwindow(WINDOWFRAME*);
void   gra_movedwindow(CChildFrame *frame, int x, int y);
void   gra_redrawdisplay(WINDOWFRAME*);
void   gra_reloadmap(void);
INTSML gra_remakeeditwindow(WINDOWFRAME*);
void   gra_repaint(CChildFrame *frame, CPaintDC *dc);
void   gra_resize(CChildFrame *frame, int cx, int cy);
void   gra_resizemain(int cx, int cy);
void   gra_setrect(WINDOWFRAME*, INTSML, INTSML, INTSML, INTSML);
void   gra_updateregistry(void);

/****** the messages window ******/
#define MAXTYPEDLINE        256				/* max chars on input line */

	   CChildFrame *gra_messageswindow;		/* the messages window */
static CEdit       *gra_editCtrl;
static INTSML       gra_messagescurrent;	/* nonzero if messages window is current */
static INTSML       gra_messagesleft;		/* left bound of messages window screen */
static INTSML       gra_messagesright;		/* right bound of messages window screen */
static INTSML       gra_messagestop;		/* top bound of messages window screen */
static INTSML       gra_messagesbottom;		/* bottom bound of messages window screen */
static CDC         *gra_texthdc;			/* device context for text buffer */
static HBITMAP      gra_textbitmap;			/* bitmap for text buffer */
static BITMAPINFO  *gra_textbitmapinfo;		/* bitmap information structure */
static char        *gra_textdatabuffer;		/* data in text buffer */
static char       **gra_textrowstart;		/* row starts for text buffer */
static INTSML       gra_textbufwid, gra_textbufhei;	/* size of text buffer */
static INTSML       gra_textbufinited = 0;	/* nonzero if text buffer is initialized */

INTSML gra_makemessageswindow(void);
INTSML gra_messagesnotvisible(void);

/****** the status bar ******/
#define MAXSTATUSLINES        1

STATUSFIELD *gra_statusfields[100];
char *gra_statusfieldtext[100];
INTSML gra_indicatorcount = 0;
static UINT gra_indicators[] = {ID_INDICATOR_ELECTRIC01, ID_INDICATOR_ELECTRIC02,
	ID_INDICATOR_ELECTRIC03, ID_INDICATOR_ELECTRIC04, ID_INDICATOR_ELECTRIC05,
	ID_INDICATOR_ELECTRIC06, ID_INDICATOR_ELECTRIC07, ID_INDICATOR_ELECTRIC08,
	ID_INDICATOR_ELECTRIC09, ID_INDICATOR_ELECTRIC10, ID_INDICATOR_ELECTRIC11,
	ID_INDICATOR_ELECTRIC12, ID_INDICATOR_ELECTRIC13, ID_INDICATOR_ELECTRIC14,
	ID_INDICATOR_ELECTRIC15, ID_INDICATOR_ELECTRIC16, ID_INDICATOR_ELECTRIC17};

void   gra_redrawstatusindicators(void);

/****** the dialogs ******/
#define PROGRESSOFFSET   100
#define MAXDIALOGS        3   /* maximum nested dialogs */

typedef struct
{
	CElectricDialog *window;
	DIALOG          *itemdesc;
	int              defaultbutton;
	POINT            firstpoint;
	INTBIG           redrawitem;
	void           (*redrawroutine)(RECTAREA*);
} TDIALOG;

TDIALOG          gra_dialogs[MAXDIALOGS];
TDIALOG         *gra_curdialog;
INTBIG           gra_curdialogindex = -1;
int              gra_dialogdbnx, gra_dialogdbny;
int              gra_dialogdbux, gra_dialogdbuy;
CBrush          *gra_dialogoffbrush = 0;

int              gra_dialoghit;
int              gra_dialoghitchar;
int              gra_dialogeditline;

void   gra_diaredrawitem(void);
INTSML gra_dodialogisinsideuserdrawn(int x, int y);
int    gra_dodialoglistkey(UINT nKey, CListBox* pListBox, UINT nIndex);
void   gra_dodialogtextchange(int nID);
HICON  gra_makeicon(char *data);

/****** events ******/
#define	CHARREAD           0177				/* character that was read */
#define	ISBUTTON           0200				/* set if button pushed (or released) */
#define	BUTTONUP           0400				/* set if button was released */
#define	SHIFTDOWN         01000				/* set if shift key was held down */
#define	COMMANDDOWN       02000				/* set if command key was held down */
#define	ALTDOWN           04000				/* set if alt key was held down */
#define	CONTROLDOWN      010000				/* set if control key was held down */
#define	DOUBLECLICK      020000				/* set if this is second click */
#define	MOTION           040000				/* set if mouse motion detected */
#define	WINDOWSIZE      0100000				/* set if window grown */
#define	WINDOWMOVE      0200000				/* set if window moved */
#define	MENUEVENT       0400000				/* set if menu entry selected (values in cursor) */
#define	FILEREPLY      01000000				/* set if file selected in standard file dialog */
#define	DIAITEMCLICK   02000000				/* set if item clicked in dialog */
#define	DIASCROLLSEL   04000000				/* set if scroll item selected in dialog */
#define	DIAEDITTEXT   010000000				/* set if edit text changed in dialog */
#define	DIAPOPUPSEL   020000000				/* set if popup item selected in dialog */
#define	DIASETCONTROL 040000000				/* set if control changed in dialog */
#define	DIAUSERMOUSE 0100000000				/* set if mouse moved in user area of dialog */
#define	DIAENDDIALOG 0200000000				/* set when dialog terminates */
#define ISLEFT       0400000000				/* set if left button */
#define ISMIDDLE    01000000000				/* set if middle button */
#define ISRIGHT     02000000000				/* set if right button */
#define	NOEVENT              -1				/* set if nothing happened */

struct
{
	INTBIG kind;
	INTSML x;
	INTSML y;
	INTSML frameindex;
} gra_action;

typedef struct
{
	INTSML  message;
	INTSML  what;
	UINTBIG when;
	INTSML  where;
	INTSML  modifiers;
} EventRecord;

static INTBIG       gra_inputstate;			/* current state of device input */
static INTBIG       gra_cursorx, gra_cursory;	/* current position of mouse */
static INTSML       gra_logrecordcount = 0;	/* count for session flushing */
static INTBIG       gra_playbackposition;	/* location in playback file */
static INTBIG       gra_playbacklength;		/* size in playback file */
static INTBIG       gra_lastloggedaction = NOEVENT;
static INTSML       gra_lastloggedx, gra_lastloggedy;
static INTSML       gra_lastloggedindex;
static char        *gra_logfile, *gra_logfilesave;

typedef struct
{
	INTBIG                cursorx, cursory;	/* current position of mouse */
	INTBIG                inputstate;		/* current state of device input */
} MYEVENTQUEUE;

#define	EVENTQUEUESIZE	100

static MYEVENTQUEUE  gra_eventqueue[EVENTQUEUESIZE];
static MYEVENTQUEUE *gra_eventqueuehead;	/* points to next event in queue */
static MYEVENTQUEUE *gra_eventqueuetail;	/* points to first free event in queue */

void   gra_addeventtoqueue(INTBIG state, INTBIG x, INTBIG y);
void   gra_buttonaction(int state, UINT nFlags, CPoint point, CWnd *frm);
void   gra_itemclicked(int nID);
void   gra_itemdoubleclicked(int nID);
void   gra_keyaction(UINT nChar, UINT nFlags, UINT nRepCnt);
INTSML gra_loggetnextaction(char *message);
void   gra_logreadaction(void);
char   gra_logreadbyte(void);
INTBIG gra_logreadlong(void);
INTSML gra_logreadshort(void);
void   gra_logreadstring(char *msg);
void   gra_logwriteaction(INTBIG inputstate, INTSML cursorx, INTSML cursory, void *extradata);
void   gra_logwritebyte(char data);
void   gra_logwritelong(INTBIG data);
void   gra_logwriteshort(INTSML data);
void   gra_logwritestring(char *msg);
INTSML gra_makebutton(INTBIG);
void   gra_mouseaction(UINT nFlags, CPoint point, CWnd *frm);
void   gra_nextevent(void);
void   gra_onint(void);

/****** pulldown menus ******/
#define IDR_MENU           3000				/* base ID for menus */

static CMenu       *gra_hMenu;				/* System Menu */
	   CMenu      **gra_pulldownmenus;		/* list of Windows pulldown menus */
static POPUPMENU  **gra_pulldowns;			/* list of Electric pulldown menus */
	   INTSML       gra_pulldownmenucount;	/* number of pulldown menus */
static int          gra_menures[] = {ID_ELECTRIC_MENU01, ID_ELECTRIC_MENU02,
	ID_ELECTRIC_MENU03, ID_ELECTRIC_MENU04, ID_ELECTRIC_MENU05, ID_ELECTRIC_MENU06,
	ID_ELECTRIC_MENU07, ID_ELECTRIC_MENU08, ID_ELECTRIC_MENU09, ID_ELECTRIC_MENU10,
	ID_ELECTRIC_MENU11, ID_ELECTRIC_MENU12, ID_ELECTRIC_MENU13, ID_ELECTRIC_MENU14,
	ID_ELECTRIC_MENU15, ID_ELECTRIC_MENU16, ID_ELECTRIC_MENU17, ID_ELECTRIC_MENU18,
	ID_ELECTRIC_MENU19, ID_ELECTRIC_MENU20, ID_ELECTRIC_MENU21, ID_ELECTRIC_MENU22,
	ID_ELECTRIC_MENU23, ID_ELECTRIC_MENU24, ID_ELECTRIC_MENU25, ID_ELECTRIC_MENU26,
	ID_ELECTRIC_MENU27, ID_ELECTRIC_MENU28, ID_ELECTRIC_MENU29};

CMenu *gra_makepdmenu(POPUPMENU *, INTSML );
void   gra_nativemenudoone(INTSML low, INTSML high);
INTSML gra_pulldownindex(POPUPMENU *);

/****** mouse buttons ******/
#define	BUTTONS              27				/* cannot exceed NUMBUTS in "usr.h" */

struct
{
	char  *name;			/* button name */
	INTSML unique;			/* number of letters that make it unique */
} gra_buttonname[BUTTONS] =
{
	{"LEFT" ,1},  {"MIDDLE",  1}, {"RIGHT",  1},	/* 0: unshifted */
	{"SLEFT",2},  {"SMIDDLE", 2}, {"SRIGHT", 2},	/* 3: shift held down */
	{"CLEFT",2},  {"CMIDDLE", 2}, {"CRIGHT", 2},	/* 6: control held down */
	{"ALEFT",2},  {"AMIDDLE", 2}, {"ARIGHT", 2},	/* 9: alt held down */
	{"SCLEFT",3}, {"SCMIDDLE",3}, {"SCRIGHT",3},	/* 12: control/shift held down */
	{"SALEFT",3}, {"SAMIDDLE",3}, {"SARIGHT",3},	/* 15: shift/alt held down */
	{"CALEFT",3}, {"CAMIDDLE",3}, {"CARIGHT",3},	/* 18: control/alt held down */
	{"SCALEFT",4},{"SCAMIDDLE",4},{"SCARIGHT",4},	/* 21: shift/control/alt held down */
	{"DLEFT",2},  {"DMIDDLE", 2}, {"DRIGHT", 2}		/* 24: double-click */
};

/****** cursors ******/
static HCURSOR      gra_normalCurs;			/* the default cursor */
static HCURSOR      gra_wantttyCurs;		/* a "use the TTY" cursor */
static HCURSOR      gra_penCurs;			/* a "draw with pen" cursor */
static HCURSOR      gra_menuCurs;			/* a menu selection cursor */
static HCURSOR      gra_handCurs;			/* a hand dragging cursor */
static HCURSOR      gra_techCurs;			/* a technology edit cursor */
static HCURSOR      gra_ibeamCurs;			/* a text edit cursor */
static HCURSOR      gra_waitCurs;			/* an hourglass cursor */
static HCURSOR      gra_lrCurs;				/* a left/right pointing cursor */
static HCURSOR      gra_udCurs;				/* an up/down pointing cursor */

void   gra_setdefaultcursor(void);

/****** rectangle saving ******/
#define	NOSAVEDBOX ((SAVEDBOX *)-1)
typedef struct Isavedbox
{
	HBITMAP           hBox;
	BITMAPINFO       *bminfo;
	CDC              *hMemDC;
	char             *data;
	char            **rowstart;
	WINDOWPART       *win;
	INTSML            lx, hx, ly, hy;
	struct Isavedbox *nextsavedbox;
} SAVEDBOX;

SAVEDBOX *gra_firstsavedbox = NOSAVEDBOX;

/****** polygon decomposition ******/
#define NOPOLYSEG ((POLYSEG *)-1)

typedef struct Isegment
{
	INTBIG fx,fy, tx,ty, direction, increment;
	struct Isegment *nextedge;
	struct Isegment *nextactive;
} POLYSEG;

static POLYSEG     *gra_polysegs;
static INTSML       gra_polysegcount = 0;

/****** curve drawing ******/
#define MODM(x) ((x<1) ? x+8 : x)
#define MODP(x) ((x>8) ? x-8 : x)
#define ODD(x)  (x&1)

static INTSML       gra_arcocttable[] = {0,0,0,0,0,0,0,0,0};
static INTBIG       gra_arccenterx, gra_arccentery, gra_arcradius, gra_curvemaxx, gra_curvemaxy;
static INTBIG       gra_arclx, gra_archx, gra_arcly, gra_archy;
static INTSML       gra_curvecol, gra_curvemask, gra_curvestyle, gra_arcfirst;
static WINDOWFRAME *gra_arcframe;			/* window frame for arc drawing */

void   gra_drawdiscrow(WINDOWFRAME *wf, INTBIG thisy, INTBIG startx, INTBIG endx, GRAPHICS *desc);

/****** fonts ******/
struct
{
	INTSML eFontIndex;		/* font index in Electric */
	char  *eFontName;		/* font name in Electric */
	char  *regFontName;		/* registry key name for font name */
	char  *regFontSize;		/* registry key name for font size */
	char  *fontName;		/* default font - will be overwritten by registry entries */
	INTSML fontSize;		/* default size - will be overwritten by registry entries */
} fontList[] =
{
	/* Small Fonts: 6 is good but tiny, 7,8,9 are good, 10,12 are bad */
	{TXT4P,     "4point",      "4p_font",     "4p_size", "Small Fonts", -7},
	{TXT6P,     "6point",      "6p_font",     "6p_size", "Small Fonts", -9},
	{TXT8P,     "8point",      "8p_font",     "8p_size", "MS Sans Serif", -12},
	{TXT10P,    "10point",    "10p_font",    "10p_size", "MS Sans Serif", -13},
	{TXT12P,    "12point",    "12p_font",    "12p_size", "MS Sans Serif", -16},
	{TXT14P,    "14point",    "14p_font",    "14p_size", "MS Sans Serif", -19},
	{TXT16P,    "16point",    "16p_font",    "16p_size", "MS Sans Serif", -24},
	{TXT18P,    "18point",    "18p_font",    "18p_size", "MS Sans Serif", -27},
	{TXT20P,    "20point",    "20p_font",    "20p_size", "MS Sans Serif", -32},
	{TXTSMALL,  "small",    "small_font",  "small_size", "MS Sans Serif", -12},
	{TXTMEDIUM, "medium",  "medium_font", "medium_size", "MS Sans Serif", -19},
	{TXTLARGE,  "large",    "large_font",  "large_size", "MS Sans Serif", -32},
	{TXTEDITOR, "editor",  "editor_font", "editor_size", "Lucida Console", -12},
	{TXTMENU,   "menu",      "menu_font",   "menu_size", "MS Sans Serif", -13},
	{TXTSTATUS, "status",  "status_font", "status_size", "MS Sans Serif", -13},
	{-1, NULL, NULL, NULL}
};

CFont *gra_createtextfont(INTSML);
CFont *gra_gettextfont(INTSML);

/****** miscellaneous ******/
#define	PALETTEWIDTH        136				/* width of palette */
#define MAXPATHLEN          256				/* max chars in file path */
#define	SBARWIDTH            15				/* width of scroll bars */

static INTSML       gra_screenleft,			/* left bound of any editor window screen */
                    gra_screenright,		/* right bound of any editor window screen */
                    gra_screentop,			/* top bound of any editor window screen */
                    gra_screenbottom;		/* bottom bound of any editor window screen */
static char         gra_localstring[256];	/* local string */
static void        *gra_fileliststringarray = 0;
static time_t       gra_timebasesec;
static INTBIG       gra_timebasems;

INTSML gra_addfiletolist(char *file);
void   gra_drawline(WINDOWFRAME *wf, INTSML x1, INTSML y1, INTSML x2, INTSML y2, INTSML col, INTSML mask);
void   gra_drawpatline(WINDOWFRAME *wf, INTSML x1, INTSML y1, INTSML x2, INTSML y2, INTSML col,
		INTSML mask, INTSML pattern);
void   gra_initfilelist(void);

/******************** INITIALIZATION ********************/

/*
 * routines to establish the default display
 */
void graphicsoptions(char *name, INTBIG *argc, char **argv)
{
	us_erasech = 010;
	us_killch = 025;
}

/*
 * routine to initialize the display device
 */
INTSML initgraphics(INTSML messages)
{
	char username[256];
	UINTBIG size;

	gra_inputstate = NOEVENT;
	gra_pulldownmenucount = 0;	/* number of pulldown menus */
	gra_hMenu = 0;

	/* setup globals that describe location of windows */
	gra_getdevices();

	/* remember the initial directory and the log file locations */
	(void)allocstring(&gra_initialdirectory, currentdirectory(), db_cluster);
	size = 256;
	GetUserName(username, &size);
	(void)initinfstr();
	(void)addstringtoinfstr(gra_initialdirectory);
	(void)addstringtoinfstr("electric_");
	(void)addstringtoinfstr(username);
	(void)addstringtoinfstr(".log");
	(void)allocstring(&gra_logfile, returninfstr(), db_cluster);
	(void)initinfstr();
	(void)addstringtoinfstr(gra_initialdirectory);
	(void)addstringtoinfstr("electriclast_");
	(void)addstringtoinfstr(username);
	(void)addstringtoinfstr(".log");
	(void)allocstring(&gra_logfilesave, returninfstr(), db_cluster);

	/* create the scrolling messages window */
	if (messages == 0) gra_messageswindow = 0; else
	{
	   if (gra_makemessageswindow() != 0) error("Cannot create messages window");
	}

	/* get cursors */
	gra_normalCurs = theApp.LoadStandardCursor(IDC_ARROW);
	gra_waitCurs = theApp.LoadStandardCursor(IDC_WAIT);
	gra_ibeamCurs = theApp.LoadStandardCursor(IDC_IBEAM);
	gra_wantttyCurs = theApp.LoadCursor(IDC_CURSORTTY);
	if (gra_wantttyCurs == 0) gra_wantttyCurs = gra_normalCurs;
	gra_penCurs = theApp.LoadCursor(IDC_CURSORPEN);
	if (gra_penCurs == 0) gra_penCurs = gra_normalCurs;
	gra_handCurs = theApp.LoadCursor(IDC_CURSORHAND);
	if (gra_handCurs == 0) gra_handCurs = gra_normalCurs;
	gra_menuCurs = theApp.LoadCursor(IDC_CURSORMENU);
	if (gra_menuCurs == 0) gra_menuCurs = gra_normalCurs;
	gra_techCurs = theApp.LoadCursor(IDC_CURSORTECH);
	if (gra_techCurs == 0) gra_techCurs = gra_normalCurs;
	gra_lrCurs = theApp.LoadStandardCursor(IDC_SIZEWE);
	if (gra_lrCurs == 0) gra_lrCurs = gra_normalCurs;
	gra_udCurs = theApp.LoadStandardCursor(IDC_SIZENS);
	if (gra_udCurs == 0) gra_udCurs = gra_normalCurs;

	/* initialize the cursor */
	SetCursor(0);
	us_normalcursor = NORMALCURSOR;
	us_cursorstate = us_normalcursor;

	gra_eventqueuehead = gra_eventqueuetail = gra_eventqueue;
	el_firstwindowframe = el_curwindowframe = NOWINDOWFRAME;
	gra_cureditwindowframe = NOWINDOWFRAME;

	return(0);
}

INTSML gra_makemessageswindow(void)
{
	RECT rmsg;
	CCreateContext context;

	/* create a context to tell this window to be an EditView */
	context.m_pNewViewClass = RUNTIME_CLASS(CElectricMsgView);
	context.m_pCurrentDoc = NULL;
	context.m_pNewDocTemplate = NULL;
	context.m_pLastView = NULL;
	context.m_pCurrentFrame = NULL;

	/* set messages window location */
	rmsg.left = gra_messagesleft;
	rmsg.right = gra_messagesright;
	rmsg.top = gra_messagestop;
	rmsg.bottom = gra_messagesbottom;

	/* create the window */
	gra_messageswindow = new CChildFrame();
	gra_messageswindow->Create(NULL, "Electric Messages",
		WS_CHILD|WS_OVERLAPPEDWINDOW|WS_VISIBLE, rmsg, NULL, &context);
	gra_messageswindow->InitialUpdateFrame(NULL, TRUE);
	CEditView *msgView = (CEditView *)gra_messageswindow->GetActiveView();
	gra_editCtrl = &msgView->GetEditCtrl();

	gra_messagescurrent = 0;
	return(0);
}

/*
 * Routine to examine the display devices available and to setup globals that describe
 * the editing windows and messages window extents.  On exit, the globals "gra_screenleft",
 * "gra_screenright", "gra_screentop", and "gra_screenbottom" will describe the area
 * for the editing windows and the variables "gra_messagesleft", "gra_messagesright",
 * "gra_messagestop", and "gra_messagesbottom" will describe the messages window.
 */
void gra_getdevices(void)
{
	RECT rDesktop;

	/* obtain the current device */
	theApp.m_pMainWnd->GetClientRect(&rDesktop);
	rDesktop.bottom -= 22;		/* status bar height? */

	gra_screenleft   = (INTSML)0;
	gra_screenright  = (INTSML)(rDesktop.right-rDesktop.left);
	gra_screentop    = (INTSML)0;
	gra_screenbottom = (INTSML)(rDesktop.bottom-rDesktop.top);

	gra_messagesleft   = gra_screenleft + PALETTEWIDTH;
	gra_messagesright  = gra_screenright - PALETTEWIDTH;
	gra_messagestop    = gra_screentop - 2 + (gra_screenbottom-gra_screentop) * 4 / 5;
	gra_messagesbottom = gra_screenbottom;
}

/******************** TERMINATION ********************/

void termgraphics(void)
{
	REGISTER WINDOWFRAME *wf;

	while (el_firstwindowframe != NOWINDOWFRAME)
	{
		wf = el_firstwindowframe;
		el_firstwindowframe = el_firstwindowframe->nextwindowframe;
		delete (CChildFrame *)wf->wndframe;
		gra_freewindowframe(wf);
	}
	if (gra_messageswindow != 0)
		delete gra_messageswindow;

	if (gra_textbufinited != 0)
	{
		efree((char *)gra_textrowstart);
		efree((char *)gra_textbitmapinfo);
		DeleteObject(gra_textbitmap);
		delete gra_texthdc;
	}

	if (gra_pulldownmenucount != 0)
	{
		efree((char *)gra_pulldownmenus);
		efree((char *)gra_pulldowns);
	}

	if (gra_polysegcount > 0) efree((char *)gra_polysegs);

	if (gra_fileliststringarray != 0)
		killstringarray(gra_fileliststringarray);
	efree((char *)gra_initialdirectory);
	efree((char *)gra_logfile);
	efree((char *)gra_logfilesave);
}

void exitprogram(void)
{
	exit(0);
}

/******************** WINDOW CONTROL ********************/

WINDOWFRAME *newwindowframe(INTSML floating, RECTAREA *r)
{
	WINDOWFRAME *wf, *oldlisthead;

	/* allocate one */
	wf = (WINDOWFRAME *)emalloc((sizeof (WINDOWFRAME)), us_aid->cluster);
	if (wf == 0) return(NOWINDOWFRAME);
	wf->numvar = 0;
	wf->windindex = gra_windowframeindex++;

	/* insert window-frame in linked list */
	oldlisthead = el_firstwindowframe;
	wf->nextwindowframe = el_firstwindowframe;
	el_firstwindowframe = wf;
	el_curwindowframe = wf;
	if (floating != 0) gra_palettewindowframe = wf; else
		gra_cureditwindowframe = wf;

	/* load an editor window into this frame */
	wf->pColorPalette = 0;
	if (gra_buildwindow(wf, floating, r) != 0)
	{
		efree((char *)wf);
		el_firstwindowframe = oldlisthead;
		return(NOWINDOWFRAME);
	}

	return(wf);
}

void killwindowframe(WINDOWFRAME *owf)
{
	WINDOWFRAME *wf, *lastwf;

	/* kill the actual window */
	((CChildFrame *)owf->wndframe)->DestroyWindow();

	/* find this frame in the list */
	lastwf = NOWINDOWFRAME;
	for(wf = el_firstwindowframe; wf != NOWINDOWFRAME; wf = wf->nextwindowframe)
	{
		if (wf == owf) break;
		lastwf = wf;
	}
	if (wf == NOWINDOWFRAME) return;
	if (lastwf == NOWINDOWFRAME) el_firstwindowframe = owf->nextwindowframe; else
		lastwf->nextwindowframe = owf->nextwindowframe;
	if (el_curwindowframe == owf) el_curwindowframe = NOWINDOWFRAME;
	if (gra_cureditwindowframe == owf) gra_cureditwindowframe = NOWINDOWFRAME;

	gra_freewindowframe(owf);
}

void gra_freewindowframe(WINDOWFRAME *wf)
{
	efree((char *)wf->rowstart);
	efree((char *)wf->bminfo);
	DeleteObject(wf->hBitmap);
	delete ((CDC *)wf->hDCOff);
	efree((char *)wf->pColorPalette);
	efree((char *)wf);
}

WINDOWFRAME *getwindowframe(INTSML canfloat)
{
	if (el_curwindowframe != NOWINDOWFRAME)
	{
		if (canfloat != 0 || el_curwindowframe->floating == 0)
			return(el_curwindowframe);
	}
	if (gra_cureditwindowframe != NOWINDOWFRAME)
		return(gra_cureditwindowframe);
	return(NOWINDOWFRAME);
}

/*
 * routine to return size of window frame "wf" in "wid" and "hei"
 */
void getwindowframesize(WINDOWFRAME *wf, INTSML *wid, INTSML *hei)
{
	*wid = wf->swid;
	*hei = wf->shei;
}

HWND gra_getwindow(WINDOWPART *win)
{
	return(win->frame->realwindow);
}

void sizewindowframe(WINDOWFRAME *wf, INTSML wid, INTSML hei)
{
	RECT fr, cr;
	int framewid, framehei;
	WINDOWPLACEMENT wp;

	/* if window frame already this size, stop now */
	if (wid == wf->swid && hei == wf->shei) return;

	/* resize window if needed */
	((CChildFrame *)wf->wndframe)->GetClientRect(&cr);
	if (wid != cr.right-cr.left || hei != cr.bottom-cr.top)
	{
		((CChildFrame *)wf->wndframe)->GetWindowPlacement(&wp);
		fr = wp.rcNormalPosition;

		framewid = (fr.right - fr.left) - (cr.right - cr.left);
		framehei = (fr.bottom - fr.top) - (cr.bottom - cr.top);

		/* determine new window size */
		wid += framewid;
		hei += framehei;

		/* resize the window */
		if (((CChildFrame *)wf->wndframe)->SetWindowPos(0, 0, 0, wid, hei, SWP_NOMOVE) == 0)
		{
			/* failed to resize window */
			return;
		}
	}

	/* rebuild the offscreen windows */
	if (gra_remakeeditwindow(wf) != 0)
	{
//		fr.left += 1;   fr.right -= 2;   fr.bottom -= 2;
//		SetWindowPos(wf->realwindow, NULL, 0, 0, fr.right-fr.left, fr.bottom-fr.top, SWP_NOZORDER | SWP_NOMOVE);
		return;
	}
	gra_reloadmap();
}

void movewindowframe(WINDOWFRAME *wf, INTSML left, INTSML top)
{
	RECT fr;
	WINDOWPLACEMENT wp;

	((CChildFrame *)wf->wndframe)->GetWindowPlacement(&wp);
	fr = wp.rcNormalPosition;

	/* determine new window location */
	if (left == fr.left && top == fr.top) return;
	((CChildFrame *)wf->wndframe)->SetWindowPos(0, left, top, 0, 0, SWP_NOSIZE);
}

/*
 * Routine to close the messages window if it is in front.  Returns nonzero if the
 * window was closed.
 */
INTSML closefrontmostmessages(void)
{
	if (gra_messagescurrent != 0 && gra_messageswindow != 0)
	{
		gra_messageswindow->DestroyWindow();
		gra_messagescurrent = 0;
		gra_messageswindow = 0;
		return(1);
	}
	return(0);
}

/*
 * Routine to bring window "win" to the front.
 */
void bringwindowtofront(WINDOWFRAME *wf)
{
	((CChildFrame *)wf->wndframe)->BringWindowToTop();
}

/*
 * routine to return the important pieces of information in placing the floating
 * palette with the fixed menu.  The maximum screen size is placed in "wid" and
 * "hei", and the amount of space that is being left for this palette on the
 * side of the screen is placed in "palettewidth".
 */
void getpaletteparameters(INTSML *wid, INTSML *hei, INTSML *palettewidth)
{
	RECT r;
	CMainFrame *wnd;

	wnd = (CMainFrame *)AfxGetMainWnd();
	wnd->GetClientRect(&r);
	*hei = r.bottom - r.top - 50;
	*wid = (INTSML)(r.right - r.left);
	*palettewidth = PALETTEWIDTH;
}

RECT *gra_geteditorwindowlocation(void)
{
	static RECT redit;

	redit.top = gra_newwindowoffset + gra_screentop;
	redit.bottom = redit.top + (gra_screenbottom-gra_screentop) * 4 / 5 - 1;
	redit.left = gra_newwindowoffset + gra_screenleft + PALETTEWIDTH;
	redit.right = redit.left + (gra_screenright-gra_screenleft-PALETTEWIDTH) * 4 / 5;

	/* is the editing window visible on this display? */
	if ((redit.bottom > gra_screenbottom) || (redit.right > gra_screenright))
	{
		gra_newwindowoffset = 0;
		redit.top = gra_screentop;
		redit.bottom = redit.top + (gra_screenbottom-gra_screentop) * 3 / 5;
		redit.left = gra_screenleft + PALETTEWIDTH;
		redit.right = redit.left + (gra_screenright-gra_screenleft-PALETTEWIDTH) * 4 / 5;
	}
	gra_newwindowoffset += 30;
	return(&redit);
}

/*
 * routine to build a new window frame
 */
INTSML gra_buildwindow(WINDOWFRAME *wf, INTSML floating, RECTAREA *r)
{
	RECT redit;
	int i;
	REGISTER VARIABLE *varred, *vargreen, *varblue;

	wf->floating = floating;
	wf->wndframe = 0;
	CChildFrame *tempFrame = new CChildFrame();
	if (r != 0)
	{
		redit.left = r->left;   redit.right = r->right;
		redit.top = r->top;     redit.bottom = r->bottom;
	}
	if (floating == 0)
	{
		/* get editor window location */
		if (r == 0) redit = *gra_geteditorwindowlocation();

		/* create the editing window */
		tempFrame->Create(NULL, "Electric",
			WS_CHILD|WS_OVERLAPPEDWINDOW|WS_VISIBLE, redit, NULL, NULL);
	} else
	{
		/* default palette window location */
		if (r == 0)
		{
			redit.top = gra_screentop + 2;
			redit.bottom = (gra_screentop+gra_screenbottom)/2;
			redit.left = gra_screenleft + 1;
			redit.right = redit.left + PALETTEWIDTH/2;
		}

		/* create the floating window */
#if 1
		tempFrame->Create(NULL, "",
			WS_CHILD|WS_VISIBLE|WS_EX_DLGMODALFRAME|WS_CAPTION,
			redit, NULL, NULL);
#else
		CMainFrame *wnd = (CMainFrame *)AfxGetMainWnd();
		tempFrame->CFrameWnd::Create(NULL, "",
			WS_CHILD|WS_VISIBLE|WS_CAPTION|WS_EX_TOPMOST,
			redit, wnd, NULL, WS_EX_TOOLWINDOW, NULL);
#endif
	}
	wf->wndframe = (CChildFrame *)tempFrame;
	wf->realwindow = tempFrame->m_hWnd;
	if (wf->realwindow == 0) return(1);

	wf->hDC = (CDC *)((CChildFrame *)wf->wndframe)->GetDC();
	((CChildFrame *)wf->wndframe)->ReleaseDC((CDC *)wf->hDC);
	((CChildFrame *)wf->wndframe)->ShowWindow(SW_SHOW);
	((CChildFrame *)wf->wndframe)->SetActiveWindow();
	wf->hPalette = 0;

	/* load any map the first time to establish the map segment length */
	el_maplength = 256;

	/* create a color palette for the buffer */
	varred   = getvalkey((INTBIG)us_aid, VAID, VINTEGER|VISARRAY, us_colormap_red);
	vargreen = getvalkey((INTBIG)us_aid, VAID, VINTEGER|VISARRAY, us_colormap_green);
	varblue  = getvalkey((INTBIG)us_aid, VAID, VINTEGER|VISARRAY, us_colormap_blue);
	if (varred != NOVARIABLE && vargreen != NOVARIABLE && varblue != NOVARIABLE)
	{
		wf->pColorPalette = (LOGPALETTE *)emalloc(256 * (sizeof (PALETTEENTRY)) + 2 * (sizeof (WORD)),
			db_cluster);
		if (wf->pColorPalette == 0) return(1);
		wf->pColorPalette->palVersion = 0x300;
		wf->pColorPalette->palNumEntries = 256;
		for(i=0; i<256; i++)
		{
			wf->pColorPalette->palPalEntry[i].peRed   = (unsigned char)((INTBIG *)varred->addr)[i];
			wf->pColorPalette->palPalEntry[i].peGreen = (unsigned char)((INTBIG *)vargreen->addr)[i];
			wf->pColorPalette->palPalEntry[i].peBlue  = (unsigned char)((INTBIG *)varblue->addr)[i];
			wf->pColorPalette->palPalEntry[i].peFlags = 0;
		}
		wf->pColorPalette->palPalEntry[255].peRed   = (unsigned char)(255-((INTBIG *)varred->addr)[255]);
		wf->pColorPalette->palPalEntry[255].peGreen = (unsigned char)(255-((INTBIG *)vargreen->addr)[255]);
		wf->pColorPalette->palPalEntry[255].peBlue  = (unsigned char)(255-((INTBIG *)varblue->addr)[255]);
		wf->hPalette = (CPalette *)new CPalette();
		((CPalette *)wf->hPalette)->CreatePalette(wf->pColorPalette);
	}

	/* build the offscreen buffer for the window */
	if (gra_makeeditwindow(wf) != 0)
	{
/*		DestroyWindow(wf->realwindow); */
		return(1);
	}

	return(0);
}

/*
 * Routine to redraw editing window "wf" due to a drag or grow
 */
void gra_redrawdisplay(WINDOWFRAME *wf)
{
	us_beginchanges();
	us_drawmenu(-1, wf);
	us_endchanges(NOWINDOWPART);
	us_state |= HIGHLIGHTSET;
	us_showallhighlight();
}

/*
 *  Routine to allocate the offscreen buffer that corresponds with the window
 *  "wf->realwindow".  The buffer is 8-bits deep and is stored in "wf->window".
 *  Returns nonzero if memory cannot be allocated.
 */
INTSML gra_makeeditwindow(WINDOWFRAME *wf)
{
	RECT r;

	/* get information about the window that was just created */
	((CChildFrame *)wf->wndframe)->GetClientRect(&r);

	/* set frame characteristics */
	wf->swid = (INTSML)(r.right - r.left);
	wf->shei = (INTSML)(r.bottom - r.top);
	wf->revy = wf->shei - 1;
	wf->offscreendirty = 0;
	wf->screenleft   = 0;
	wf->screenright  = wf->swid;
	wf->screentop    = 0;
	wf->screenbottom = wf->shei;

	/* allocate the main offscreen buffer */
	if (gra_buildoffscreenbuffer(wf, wf->swid, wf->shei, (CDC **)&wf->hDCOff, &wf->hBitmap,
		&wf->bminfo, &wf->data, &wf->rowstart) != 0) return(1);

	/* attach the color palette to drawing window section */
	if (wf->hPalette != 0)
	{
		(void)((CDC *)wf->hDCOff)->SelectPalette((CPalette *)wf->hPalette, TRUE);
		(void)((CDC *)wf->hDCOff)->RealizePalette();
	}

	return(0);
}

/*
 * Routine to build an offscreen buffer that is "wid" by "hei" and based on window frame "wf".
 * The device context is placed in "hdc", the bitmap in "bitmap", the actual offscreen memory in
 * "databuffer" and the row start array in "rowstart".  Returns nonzero on error.
 */
INTSML gra_buildoffscreenbuffer(WINDOWFRAME *wf, INTSML wid, INTSML hei, CDC **hdc,
	HBITMAP *bitmap, BITMAPINFO **bmiInfo, char **databuffer, char ***rowstart)
{
	BITMAPINFOHEADER bmiHeader;
	REGISTER VARIABLE *varred, *vargreen, *varblue;
	RGBQUAD bmiColors[256];
	REGISTER INTSML i, j, retval;
	REGISTER INTBIG size;
	REGISTER char *ptr;

	/* make the info structure */
	*bmiInfo = (BITMAPINFO *)emalloc(sizeof(bmiHeader) + sizeof(bmiColors), db_cluster);
	if (*bmiInfo == 0) return(1);

	/* grab the device context */
	wf->hDC = (CDC *)((CChildFrame *)wf->wndframe)->GetDC();

	/* setup the header block */
	bmiHeader.biSize = (DWORD)sizeof(bmiHeader);
	bmiHeader.biWidth = (LONG)wid;
	bmiHeader.biHeight = (LONG)-hei;
	bmiHeader.biPlanes = 1;
	bmiHeader.biBitCount = 8;
	bmiHeader.biCompression = (DWORD)BI_RGB;
	bmiHeader.biSizeImage = 0;
	bmiHeader.biXPelsPerMeter = 0;
	bmiHeader.biYPelsPerMeter = 0;
	bmiHeader.biClrUsed = 256;
	bmiHeader.biClrImportant = 0;

	/* get color map information */
	varred = getvalkey((INTBIG)us_aid, VAID, VINTEGER|VISARRAY, us_colormap_red);
	vargreen = getvalkey((INTBIG)us_aid, VAID, VINTEGER|VISARRAY, us_colormap_green);
	varblue = getvalkey((INTBIG)us_aid, VAID, VINTEGER|VISARRAY, us_colormap_blue);
	if (varred != NOVARIABLE && vargreen != NOVARIABLE && varblue != NOVARIABLE)
	{
		for(i=0; i<255; i++)
		{
			bmiColors[i].rgbRed = (unsigned char)((INTBIG *)varred->addr)[i];
			bmiColors[i].rgbGreen = (unsigned char)((INTBIG *)vargreen->addr)[i];
			bmiColors[i].rgbBlue = (unsigned char)((INTBIG *)varblue->addr)[i];
			bmiColors[i].rgbReserved = 0;
		}
		bmiColors[255].rgbRed = (unsigned char)(255-((INTBIG *)varred->addr)[255]);
		bmiColors[255].rgbGreen = (unsigned char)(255-((INTBIG *)vargreen->addr)[255]);
		bmiColors[255].rgbBlue = (unsigned char)(255-((INTBIG *)varblue->addr)[255]);
	}

	/* create a device context for this offscreen buffer */
	retval = 1;
	*hdc = new CDC();
	BOOL result = (*hdc)->CreateCompatibleDC((CDC *)wf->hDC);
	if (result)
	{
		/* prepare structure describing offscreen buffer */
		ptr =  (char *)*bmiInfo;
		memcpy(ptr, &bmiHeader, sizeof(bmiHeader));
		ptr += sizeof(bmiHeader);
		memcpy(ptr, &bmiColors, sizeof(bmiColors));

		/* allocate offscreen buffer */
		*bitmap = CreateDIBSection((*hdc)->m_hDC, *bmiInfo, DIB_RGB_COLORS, (void **)databuffer, NULL, 0);
		if (*bitmap != 0)
		{
			(*hdc)->SelectObject(CBitmap::FromHandle(*bitmap));

			/* setup row pointers to offscreen buffer */
			*rowstart = (char **)emalloc(hei * (sizeof (char *)), db_cluster);
			if (*rowstart == 0) return(1);
			size = (wid+3)/4*4;
			for(j=0; j<hei; j++) (*rowstart)[j] = (*databuffer) + (size * j);
			retval = 0;
		}
	}

	/* release the device context */
	((CChildFrame *)wf->wndframe)->ReleaseDC((CDC *)wf->hDC);
	return(retval);
}

/*
 * Routine to rebuild the offscreen buffer when its size or depth has changed.
 * Returns nonzero if the window cannot be rebuilt.
 */
INTSML gra_remakeeditwindow(WINDOWFRAME *wf)
{
	/* free memory associated with the frame */
	efree((char *)wf->rowstart);
	efree((char *)wf->bminfo);
	DeleteObject(wf->hBitmap);
	delete (CDC *)wf->hDCOff;

	return(gra_makeeditwindow(wf));
}

/******************** MISCELLANEOUS EXTERNAL ROUTINES ********************/

/*
 * return nonzero if the capabilities in "want" are present
 */
INTSML graphicshas(INTSML want)
{
	/* only 1 status area, shared by each frame */
	if ((want&CANSTATUSPERFRAME) != 0) return(0);

	/* cannot run subprocesses */
	if ((want&CANRUNPROCESS) != 0) return(0);

	return(1);
}

/*
 * internal routine to beep the status display
 */
void ttybeep(void)
{
	if (us_aid->aidstate & TERMBEEP) MessageBeep(MB_ICONASTERISK);
}

DIALOGITEM db_severeerrordialogitems[] =
{
 /*  1 */ {0, {80,8,104,72}, BUTTON, "Exit"},
 /*  2 */ {0, {80,96,104,160}, BUTTON, "Save"},
 /*  3 */ {0, {80,184,104,256}, BUTTON, "Continue"},
 /*  4 */ {0, {8,8,72,256}, MESSAGE, ""}
};
DIALOG db_severeerrordialog = {{50,75,163,341}, "Fatal Error", 4, db_severeerrordialogitems};

void error(char *s, ...)
{
	va_list ap;
	char line[500];
	REGISTER INTBIG itemHit;
	REGISTER INTBIG retval;
	REGISTER LIBRARY *lib;

	/* disable any session logging */
	if (us_logplay != NULL)
	{
		xclose(us_logplay);
		us_logplay = NULL;
	}

	/* build the error message */
	var_start(ap, s);
	(void)vsprintf(line, s, ap);
	va_end(ap);

	/* display the severe error dialog box */
	if (DiaInitDialog(&db_severeerrordialog) != 0) return;

	/* load the message */
	DiaSetText(4, line);

	/* loop until done */
	for(;;)
	{
		itemHit = DiaNextHit();
		if (itemHit == 1) exitprogram();
		if (itemHit == 2)
		{
			/* save libraries */
			for(lib = el_curlib; lib != NOLIBRARY; lib = lib->nextlibrary)
			{
				if ((lib->userbits&HIDDENLIBRARY) != 0) continue;
				if ((lib->userbits&(LIBCHANGEDMAJOR|LIBCHANGEDMINOR)) == 0) continue;

				/* save the library in binary format */
				makeoptionstemporary(lib);
				retval = askaid(io_aid, "write", (INTBIG)lib, (INTBIG)"binary");
				restoreoptionstate();
				if (retval != 0) return;
			}
		}
		if (itemHit == 3) break;
	}
	DiaDoneDialog();
}

/*
 * Routine to get the environment variable "name" and return its value.
 */
char *egetenv(char *name)
{
	return(0);
}

/*
 * Routine to fork a new process.  Returns the child process number if this is the
 * parent thread.  Returns 0 if this is the child thread.
 * Returns 1 if forking is not possible (process 1 is INIT on UNIX and can't possibly
 * be assigned to a normal process).
 */
INTBIG efork(void)
{
	return(1);
}

/*
 * Routine to execute the program "program" with the arguments "args"
 */
void eexec(char *program, char *args[])
{
}

/*
 * routine to send signal "signal" to process "process".
 */
INTBIG ekill(INTBIG process)
{
	return(1);
}

/*
 * routine to wait for the completion of child process "process"
 */
void ewait(INTBIG process)
{
}

/*
 * Routine to establish the library directories from the environment.
 * The registry entry for this is stored in:
 *   HKEY_LOCAL_MACHINE\Software\Static Free Software\Electric\ELECTRIC_LIBDIR
 * If this directory is valid (i.e. has the file "cadrc" in it) then it is
 * used.  If the directory is not valid, the registry entry is removed.
 */
void setupenvironment(void)
{
	HKEY hKey;
	DWORD size;
	unsigned char tmp[256];
	char kVal[256], testfile[256];
	INTSML i;
	FILE *io;

	/* GetModuleFileName(0, testfile, 256); */

	/* default library location */
	(void)allocstring(&el_libdir, LIBDIR, db_cluster);

	/* see if a registry key overrides the location */
	(void)sprintf(kVal, "Software\\Static Free Software\\Electric");
	if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, kVal, 0, KEY_READ, &hKey) == ERROR_SUCCESS)
	{
		if (RegQueryValueEx(hKey, "ELECTRIC_LIBDIR", NULL, NULL, NULL, &size) == ERROR_SUCCESS)
		{
			if (RegQueryValueEx(hKey, "ELECTRIC_LIBDIR", NULL, NULL, tmp, &size) == ERROR_SUCCESS)
			{
				if (tmp[0] != 0)
				{
					strcpy(testfile, (char *)tmp);
					strcat(testfile, "cadrc");
					io = fopen(testfile, "r");
					if (io != 0)
					{
						/* valid "cadrc" file in directory, use it */
						fclose(io);
						(void)reallocstring(&el_libdir, (char *)tmp, db_cluster);
					} else
					{
						/* key points to bogus directory: delete it */
						RegCloseKey(hKey);
						if (RegCreateKey(HKEY_LOCAL_MACHINE, kVal, &hKey) == ERROR_SUCCESS)
						{
							(void)RegSetValueEx(hKey, "ELECTRIC_LIBDIR", 0, REG_SZ,
								(unsigned char *)"", 1);
						}
					}
				}
			}
		}
		RegCloseKey(hKey);
	}

	/* load font information */
	(void)sprintf(kVal, "Software\\Static Free Software\\Electric\\Fonts");
	if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, kVal, 0, KEY_READ, &hKey) == ERROR_SUCCESS)
	{
		i=0;
		while (fontList[i].eFontIndex != -1)
		{
			if (RegQueryValueEx(hKey, fontList[i].regFontName, NULL, NULL, NULL, &size) == ERROR_SUCCESS)
				if (RegQueryValueEx(hKey, fontList[i].regFontName, NULL, NULL, tmp, &size) == ERROR_SUCCESS)
					strcpy(fontList[i].fontName, (char *)tmp);
			if (RegQueryValueEx(hKey, fontList[i].regFontSize, NULL, NULL, NULL, &size) == ERROR_SUCCESS)
				if (RegQueryValueEx(hKey, fontList[i].regFontSize, NULL, NULL, tmp, &size) == ERROR_SUCCESS)
					fontList[i].fontSize = (INTSML)myatoi((char *)tmp);
			i++;
		}
		RegCloseKey(hKey);
	}

	/* set machine name */
	nextvarchangequiet();
	(void)setvalkey((INTBIG)us_aid, VAID, makekey("USER_machine"),
		(INTBIG)"Windows", VSTRING|VDONTSAVE);
}

/*
 * Routine called when all is well with the world.  Saves library information in the
 * registry if it isn't alreay there.
 */
void gra_updateregistry(void)
{
	HKEY hKey;
	INTBIG err;
	DWORD size;
	unsigned char tmp[256];
	char kVal[256];
	INTSML doupdate;

	/* presume that registry must be updated */
	doupdate = 1;

	/* see what the current registry key says */
	(void)sprintf(kVal, "Software\\Static Free Software\\Electric");
	if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, kVal, 0, KEY_READ, &hKey) == ERROR_SUCCESS)
	{
		if (RegQueryValueEx(hKey, "ELECTRIC_LIBDIR", NULL, NULL, NULL, &size) == ERROR_SUCCESS)
		{
			if (RegQueryValueEx(hKey, "ELECTRIC_LIBDIR", NULL, NULL, tmp, &size) == ERROR_SUCCESS)
			{
				/* if the current registry entry is correct, do not update */
				if (strcmp(el_libdir, (char *)tmp) == 0) doupdate = 0;
			}
		}
		RegCloseKey(hKey);
	}

	/* if the key must be updated, do it now */
	if (doupdate != 0)
	{
		if (RegCreateKey(HKEY_LOCAL_MACHINE, kVal, &hKey) == ERROR_SUCCESS)
		{
			err = RegSetValueEx(hKey, "ELECTRIC_LIBDIR", 0, REG_SZ,
				(unsigned char *)el_libdir, strlen(el_libdir)+1);
			if (err != 0) ttyputerr("Error %d setting key", err);
			RegCloseKey(hKey);
		}
	}
}

void setlibdir(char *libdir)
{
	char *pp, abspath[256];

	/* make sure UNIX '/' isn't in use */
	for(pp = libdir; *pp != 0; pp++)
		if (*pp == '/') *pp = DIRSEP;

	(void)initinfstr();
	libdir = _fullpath(abspath, libdir, 256);
	(void)addstringtoinfstr(libdir);
	if (libdir[strlen(libdir)-1] != DIRSEP) (void)addtoinfstr(DIRSEP);
	(void)reallocstring(&el_libdir, returninfstr(), db_cluster);
}

/******************** MESSAGES WINDOW ROUTINES ********************/

/*
 * Routine to put the string "s" into the messages window.
 * Pops up the messages window if "important" is nonzero.
 */
void putmessagesstring(char *s, INTSML important)
{
	int pos, line;

	/* ensure window exists, is noniconic, and is on top */
	if (important != 0)
	{
		if (gra_messageswindow == 0)
		   (void)gra_makemessageswindow();
		if (gra_messageswindow->IsIconic())
			gra_messageswindow->ShowWindow(SW_RESTORE);
		if (gra_messagesnotvisible())
			gra_messageswindow->BringWindowToTop();
	}

	/* get next line and load it up */
	if (gra_messageswindow == 0) return;
	line = gra_editCtrl->GetLineCount();
	pos = gra_editCtrl->LineIndex(line-1);
	gra_editCtrl->SetSel(pos, pos);
	gra_editCtrl->ReplaceSel(s);
	gra_editCtrl->ReplaceSel("\r\n");
}

/*
 * Routine to return the name of the key that ends a session from the messages window.
 */
char *getmessageseofkey(void)
{
	return("ESC");
}

/*
 * Routine to get a string from the scrolling messages window.  Returns zero if end-of-file
 * (^D) is typed.
 */
char *getmessagesstring(char *prompt)
{
	int line, pos, startpos, startposonline, charcount, pos2;
	char cTmpCh, *pt, ch[2];
	static char typedline[MAXTYPEDLINE];

	/* ensure window exists, is noniconic, and is on top */
	if (gra_messageswindow == 0)
	   (void)gra_makemessageswindow();
	if (gra_messageswindow->IsIconic())
		gra_messageswindow->ShowWindow(SW_RESTORE);
	if (gra_messagesnotvisible())
		gra_messageswindow->BringWindowToTop();
	flushscreen();

	/* advance to next line and allocate maximum number of characters */
	gra_messageswindow->MDIActivate();
	line = gra_editCtrl->GetLineCount() - 1;
	pos = gra_editCtrl->LineIndex(line);
	pos += gra_editCtrl->LineLength(line);
	gra_editCtrl->SetSel(pos, pos);
	gra_editCtrl->ReplaceSel(prompt);
	gra_editCtrl->GetSel(startpos, pos2);
	startposonline = gra_editCtrl->GetLine(line, typedline, MAXTYPEDLINE);

	for(;;)
	{
		gra_nextevent();
		if (gra_inputstate == NOEVENT) continue;
		cTmpCh = gra_inputstate & CHARREAD;
		if (cTmpCh != 0)
		{
			if (cTmpCh == 0x04)
			{
				/* ESC (eof) */
				gra_editCtrl->GetSel(pos, pos2);
				if (startpos == pos)
				{
					gra_editCtrl->ReplaceSel("\r\n");
					gra_inputstate = NOEVENT;
					return(NULL);
				}
				cTmpCh = 0x0D;
			}
			if (cTmpCh == 0x08)
			{
				/* backspace */
				gra_editCtrl->GetSel(pos, pos2);
				if (pos <= startpos) continue;
				gra_editCtrl->SetSel(pos-1, pos);
				ch[0] = 0;
				gra_editCtrl->ReplaceSel(ch);
				continue;
			}
			if (cTmpCh == 0x0A || cTmpCh == 0x0D)
			{
				/* end of line */
				gra_editCtrl->ReplaceSel("\r\n");
				charcount = gra_editCtrl->GetLine(line, typedline, MAXTYPEDLINE);
				typedline[charcount] = 0;
				pt = &typedline[startposonline];
				gra_inputstate = NOEVENT;
				return(pt);
			}
			line = gra_editCtrl->GetLineCount() - 1;
			pos = gra_editCtrl->LineIndex(line);
			pos += gra_editCtrl->LineLength(line);
			gra_editCtrl->SetSel(pos, pos);
			ch[0] = cTmpCh;   ch[1] = 0;
			gra_editCtrl->ReplaceSel(ch);
		}
		gra_inputstate = NOEVENT;
	}
}

/*
 * routine to select fonts in the messages window
 */
void setmessagesfont(void)
{
	CFont *fnt;
	CFontDialog dlg;
	LOGFONT lf;

	if (dlg.DoModal() == IDOK)
	{
		/* Retrieve the dialog data */
		dlg.GetCurrentFont(&lf);
		fnt = new CFont();
		fnt->CreateFontIndirect(&lf);
		gra_editCtrl->SetFont(fnt);
	}
}

/*
 * routine to cut text from the messages window if it is current.  Returns nonzero
 * if sucessful.
 */
INTSML cutfrommessages(void)
{
	if (gra_messageswindow == 0) return(0);
	if (gra_messageswindow->IsIconic()) return(0);
	CMDIFrameWnd *parent = gra_messageswindow->GetMDIFrame();
	CWnd *wnd = parent->MDIGetActive();
	if (gra_messageswindow != wnd) return(0);

	/* do the copy */
	gra_editCtrl->Cut();
	return(1);
}

/*
 * routine to copy text from the messages window if it is current.  Returns nonzero
 * if sucessful.
 */
INTSML copyfrommessages(void)
{
	if (gra_messageswindow == 0) return(0);
	if (gra_messageswindow->IsIconic()) return(0);
	CMDIFrameWnd *parent = gra_messageswindow->GetMDIFrame();
	CWnd *wnd = parent->MDIGetActive();
	if (gra_messageswindow != wnd) return(0);

	/* do the copy */
	gra_editCtrl->Copy();
	return(1);
}

/*
 * routine to paste text to the messages window if it is current.  Returns nonzero
 * if sucessful.
 */
INTSML pastetomessages(void)
{
	if (gra_messageswindow == 0) return(0);
	if (gra_messageswindow->IsIconic()) return(0);
	CMDIFrameWnd *parent = gra_messageswindow->GetMDIFrame();
	CWnd *wnd = parent->MDIGetActive();
	if (gra_messageswindow != wnd) return(0);

	/* do the copy */
	gra_editCtrl->Paste();
	return(1);
}

/*
 * routine to get the contents of the system cut buffer
 */
char *getcutbuffer(void)
{
	char *ptr;

	if (OpenClipboard(NULL) == 0) return("");
	ptr = (char *)GetClipboardData(CF_TEXT);
	(void)initinfstr();
	if (ptr != 0)
		(void)addstringtoinfstr(ptr);
	CloseClipboard();
	return(returninfstr());
}

/*
 * routine to set the contents of the system cut buffer to "msg"
 */
void setcutbuffer(char *msg)
{
	HGLOBAL hnd;

	if (OpenClipboard(NULL) == 0) return;

	/* Remove the current Clipboard contents */
	if (EmptyClipboard() == 0)
	{
		CloseClipboard();
		return;
	}

	hnd = GlobalAlloc(GHND, strlen(msg)+1);
	strcpy(*((char **)hnd), msg);
	(void)SetClipboardData(CF_TEXT, hnd);

	CloseClipboard();
}

/*
 * Routine to return nonzero if the messages window is obscured by
 * an editor window.  Should ignore edit windows below this!!!
 */
INTSML gra_messagesnotvisible(void)
{
	RECT mr, r;
	CWnd *wnd;

	gra_messageswindow->GetWindowRect(&mr);
	mr.top += 2;   mr.bottom -= 2;
	mr.left += 2;  mr.right -= 2;

	/* look for "previous" windows that may obscure this one */
	wnd = gra_messageswindow;
	for(;;)
	{
		wnd = wnd->GetNextWindow(GW_HWNDPREV);
		if (wnd == 0) break;
		wnd->GetWindowRect(&r);
		if (r.left < mr.right && r.right > mr.left &&
			r.top < mr.bottom && r.bottom > mr.top) return(1);
	}
	return(0);
}

/******************** STATUS BAR ROUTINES ********************/

/*
 * Routine to return the number of status lines on the display.
 */
INTSML ttynumstatuslines(void)
{
	return(MAXSTATUSLINES);
}

/*
 * Routine to free status field object "sf".
 */
void ttyfreestatusfield(STATUSFIELD *sf)
{
	INTSML i, j;

	for(i=0; i<gra_indicatorcount; i++)
	{
		if (gra_statusfields[i] != sf) continue;

		/* remove the field */
		efree(gra_statusfieldtext[i]);
		for(j=i+1; j<gra_indicatorcount; j++)
		{
			gra_statusfields[j-1] = gra_statusfields[j];
			gra_statusfieldtext[j-1] = gra_statusfieldtext[j];
		}
		gra_indicatorcount--;
		gra_redrawstatusindicators();
		break;
	}

	efree(sf->label);
	efree((char *)sf);
}

/*
 * Routine to display "message" in the status "field" of window "wf" (uses all windows
 * if "wf" is zero).  If "cancrop" is zero, field cannot be cropped and should be
 * replaced with "*" if there isn't room.
 */
void ttysetstatusfield(WINDOWFRAME *wf, STATUSFIELD *field, char *message, INTSML cancrop)
{
	INTSML len, i, j, pos, newpos;
	CMainFrame *wnd;

	/* figure out which indicator this is */
	if (field == 0) return;

	/* construct the status line */
	gra_localstring[0] = 0;
	(void)strcat(gra_localstring, field->label);
	(void)strcat(gra_localstring, message);
	len = strlen(gra_localstring);
	while (len > 0 && gra_localstring[len-1] == ' ') gra_localstring[--len] = 0;

	/* if this is a title bar setting, do it now */
	if (field->line == 0)
	{
		if (wf == NOWINDOWFRAME) return;
		if (*gra_localstring == 0) strcpy(gra_localstring, "*** NO FACET ***");
		((CChildFrame *)wf->wndframe)->SetWindowText(gra_localstring);
		return;
	}

	/* ignore null fields */
	if (*field->label == 0) return;

	/* see if this indicator is in the list */
	wnd = (CMainFrame *)AfxGetMainWnd();
	for(i=0; i<gra_indicatorcount; i++)
	{
		if (gra_statusfields[i]->line != field->line ||
			gra_statusfields[i]->startper != field->startper ||
			gra_statusfields[i]->endper != field->endper) continue;

		/* load the string */
		(void)reallocstring(&gra_statusfieldtext[i], gra_localstring, db_cluster);
		wnd->m_wndStatusBar.SetPaneText(i+1, gra_localstring);
		return;
	}

	/* not found: find where this field fits in the order */
	newpos = (field->line-1) * 100 + field->startper;
	for(i=0; i<gra_indicatorcount; i++)
	{
		pos = (gra_statusfields[i]->line-1) * 100 + gra_statusfields[i]->startper;
		if (newpos < pos) break;
	}
	for(j=gra_indicatorcount; j>i; j--)
	{
		gra_statusfieldtext[j] = gra_statusfieldtext[j-1];
		gra_statusfields[j] = gra_statusfields[j-1];
	}
	gra_statusfields[i] = field;
	(void)allocstring(&gra_statusfieldtext[i], gra_localstring, db_cluster);
	gra_indicatorcount++;

	/* reload all indicators */
	gra_redrawstatusindicators();
}

void gra_redrawstatusindicators(void)
{
	INTSML i, wid, screenwid, totalwid;
	RECT r;
	CMainFrame *wnd;

	wnd = (CMainFrame *)AfxGetMainWnd();
	wnd->GetClientRect(&r);
	screenwid = r.right - r.left - gra_indicatorcount * 5;
	wnd->m_wndStatusBar.SetIndicators(gra_indicators, gra_indicatorcount+1);
	totalwid = 0;
	for(i=0; i<gra_indicatorcount; i++)
		totalwid += gra_statusfields[i]->endper - gra_statusfields[i]->startper;
	for(i=0; i<=gra_indicatorcount; i++)
	{
		if (i == 0)
		{
			wnd->m_wndStatusBar.SetPaneInfo(i, gra_indicators[i], SBPS_NOBORDERS, 0);
			wnd->m_wndStatusBar.SetPaneText(i, "");
		} else
		{
			wid = gra_statusfields[i-1]->endper - gra_statusfields[i-1]->startper;
			wid = wid * screenwid / totalwid;
			wnd->m_wndStatusBar.SetPaneInfo(i, gra_indicators[i], SBPS_NORMAL, wid);
			wnd->m_wndStatusBar.SetPaneText(i, gra_statusfieldtext[i-1]);
		}
	}
}

/******************** GRAPHICS CONTROL ROUTINES ********************/

void flushscreen(void)
{
	WINDOWFRAME *wf;

	for(wf = el_firstwindowframe; wf != NOWINDOWFRAME; wf = wf->nextwindowframe)
	{
		/* if screen has not changed, stop now */
		if (wf->offscreendirty == 0) continue;
		wf->offscreendirty = 0;

		/* refresh screen */
		wf->hDC = (CDC *)((CChildFrame *)wf->wndframe)->GetDC();
		((CDC *)wf->hDC)->BitBlt(wf->copyrect.left, wf->copyrect.top, wf->copyrect.right-wf->copyrect.left,
			   wf->copyrect.bottom-wf->copyrect.top, (CDC *)wf->hDCOff, wf->copyrect.left, wf->copyrect.top, SRCCOPY);
		((CChildFrame *)wf->wndframe)->ReleaseDC((CDC *)wf->hDC);
	}
}

/*
 * Routine to accumulate the rectangle of change to the offscreen PixMap
 */
void gra_setrect(WINDOWFRAME *wf, INTSML lx, INTSML hx, INTSML ly, INTSML hy)
{
	if (wf->offscreendirty == 0)
	{
		wf->copyrect.left = lx;
		wf->copyrect.right = hx;
		wf->copyrect.top = ly;
		wf->copyrect.bottom = hy;
		wf->offscreendirty = 1;
	} else
	{
		if (lx < wf->copyrect.left)   wf->copyrect.left = lx;
		if (hx > wf->copyrect.right)  wf->copyrect.right = hx;
		if (ly < wf->copyrect.top)    wf->copyrect.top = ly;
		if (hy > wf->copyrect.bottom) wf->copyrect.bottom = hy;
	}
}

void gra_reloadmap(void)
{
	REGISTER VARIABLE *varred, *vargreen, *varblue;

	varred = getvalkey((INTBIG)us_aid, VAID, VINTEGER|VISARRAY, us_colormap_red);
	vargreen = getvalkey((INTBIG)us_aid, VAID, VINTEGER|VISARRAY, us_colormap_green);
	varblue = getvalkey((INTBIG)us_aid, VAID, VINTEGER|VISARRAY, us_colormap_blue);
	if (varred == NOVARIABLE || vargreen == NOVARIABLE || varblue == NOVARIABLE) return;
	colormapload((INTBIG *)varred->addr, (INTBIG *)vargreen->addr, (INTBIG *)varblue->addr, 0, 255);
}

void colormapload(INTBIG *red, INTBIG *green, INTBIG *blue, INTSML low, INTSML high)
{
	INTSML i;
	REGISTER WINDOWFRAME *wf;
	RGBQUAD bmiColors[256];

	for(wf = el_firstwindowframe; wf != NOWINDOWFRAME; wf = wf->nextwindowframe)
	{
		/* do not touch the color map when in B&W mode */
		if (wf->pColorPalette == 0) continue;

		i = GetDIBColorTable(((CDC *)wf->hDCOff)->m_hDC, 0, 256, bmiColors);
		for(i=low; i<=high; i++)
		{
			bmiColors[i].rgbRed = (unsigned char)red[i-low];
			bmiColors[i].rgbGreen = (unsigned char)green[i-low];
			bmiColors[i].rgbBlue = (unsigned char)blue[i-low];
			bmiColors[i].rgbReserved = 0;
			if (i == 255)
			{
				bmiColors[i].rgbRed = (unsigned char)(255-red[i-low]);
				bmiColors[i].rgbGreen = (unsigned char)(255-green[i-low]);
				bmiColors[i].rgbBlue = (unsigned char)(255-blue[i-low]);
			}

			wf->pColorPalette->palPalEntry[i].peRed = bmiColors[i].rgbRed;
			wf->pColorPalette->palPalEntry[i].peGreen = bmiColors[i].rgbGreen;
			wf->pColorPalette->palPalEntry[i].peBlue = bmiColors[i].rgbBlue;
			wf->pColorPalette->palPalEntry[i].peFlags = 0;
		}
		i = SetDIBColorTable(((CDC *)wf->hDCOff)->m_hDC, 0, 256, bmiColors);

		if (wf->hPalette != 0)
			delete (CPalette *)wf->hPalette;

		wf->hPalette = (CPalette *)new CPalette();
		((CPalette *)wf->hPalette)->CreatePalette(wf->pColorPalette);

		/* graphics window */
		((CDC *)wf->hDCOff)->SelectPalette((CPalette *)wf->hPalette, TRUE);
		((CDC *)wf->hDCOff)->RealizePalette();
		gra_setrect(wf, 0, wf->swid-1, 0, wf->shei-1);
	}
}

/*
 * helper routine to set the cursor shape to "state"
 */
void setdefaultcursortype(INTSML state)
{
	if (us_cursorstate == state) return;

	switch (state)
	{
		case NORMALCURSOR:  SetCursor(gra_normalCurs);   break;
		case WANTTTYCURSOR: SetCursor(gra_wantttyCurs);  break;
		case PENCURSOR:     SetCursor(gra_penCurs);      break;
		case NULLCURSOR:    SetCursor(gra_normalCurs);   break;
		case MENUCURSOR:    SetCursor(gra_menuCurs);     break;
		case HANDCURSOR:    SetCursor(gra_handCurs);     break;
		case TECHCURSOR:    SetCursor(gra_techCurs);     break;
		case IBEAMCURSOR:   SetCursor(gra_ibeamCurs);    break;
		case WAITCURSOR:    SetCursor(gra_waitCurs);     break;
		case LRCURSOR:      SetCursor(gra_lrCurs);       break;
		case UDCURSOR:      SetCursor(gra_udCurs);       break;
	}
	ShowCursor(TRUE);
	us_cursorstate = state;
}

/*
 * Routine to change the default cursor (to indicate modes).
 */
void setnormalcursor(INTSML curs)
{
	us_normalcursor = curs;
	setdefaultcursortype(us_normalcursor);
}

/******************** GRAPHICS LINES ********************/

void screendrawline(WINDOWPART *win, INTSML x1, INTSML y1, INTSML x2, INTSML y2, GRAPHICS *desc, INTSML texture)
{
	REGISTER INTSML col, mask, lx, hx, ly, hy;
	static INTSML pat[] = {0xFF, 0x88, 0xE7, 0x80};
	REGISTER WINDOWFRAME *wf;

	/* make sure the image buffer has not moved */
	wf = win->frame;

	/* get line type parameters */
	col = desc->col;      mask = ~desc->bits;
	y1 = wf->revy - y1;   y2 = wf->revy - y2;

	if (texture != 0) gra_drawpatline(wf, x1, y1, x2, y2, col, mask, pat[texture]); else
		gra_drawline(wf, x1, y1, x2, y2, col, mask);
	if (x1 < x2) { lx = x1;   hx = x2; } else { lx = x2;   hx = x1; }
	if (y1 < y2) { ly = y1;   hy = y2; } else { ly = y2;   hy = y1; }
	gra_setrect(wf, lx, (INTSML)(hx+1), ly, (INTSML)(hy+1));
}

void screeninvertline(WINDOWPART *win, INTSML x1, INTSML y1, INTSML x2, INTSML y2)
{
	REGISTER INTSML lx, hx, ly, hy, dx, dy, d, incr1, incr2, x, y, xend, yend, yincr, xincr;
	REGISTER WINDOWFRAME *wf;

	/* get line type parameters */
	wf = win->frame;
	y1 = wf->revy - y1;   y2 = wf->revy - y2;

	/* initialize the Bresenham algorithm */
	dx = abs(x2-x1);
	dy = abs(y2-y1);
	if (dx > dy)
	{
		/* initialize for lines that increment along X */
		incr1 = 2 * dy;
		d = incr2 = 2 * (dy - dx);
		if (x1 > x2)
		{
			x = x2;   y = y2;   xend = x1;   yend = y1;
		} else
		{
			x = x1;   y = y1;   xend = x2;   yend = y2;
		}
		if (yend < y) yincr = -1; else yincr = 1;
		wf->rowstart[y][x] = ~wf->rowstart[y][x];

		/* draw line that increments along X */
		while (x < xend)
		{
			x++;
			if (d < 0) d += incr1; else
			{
				y += yincr;   d += incr2;
			}
			wf->rowstart[y][x] = ~wf->rowstart[y][x];
		}
	} else
	{
		/* initialize for lines that increment along Y */
		incr1 = 2 * dx;
		d = incr2 = 2 * (dx - dy);
		if (y1 > y2)
		{
			x = x2;   y = y2;   xend = x1;   yend = y1;
		} else
		{
			x = x1;   y = y1;   xend = x2;   yend = y2;
		}
		if (xend < x) xincr = -1; else xincr = 1;
		wf->rowstart[y][x] = ~wf->rowstart[y][x];

		/* draw line that increments along X */
		while (y < yend)
		{
			y++;
			if (d < 0) d += incr1; else
			{
				x += xincr;   d += incr2;
			}
			wf->rowstart[y][x] = ~wf->rowstart[y][x];
		}
	}
	if (x1 < x2) { lx = x1;   hx = x2; } else { lx = x2;   hx = x1; }
	if (y1 < y2) { ly = y1;   hy = y2; } else { ly = y2;   hy = y1; }
	gra_setrect(wf, lx, (INTSML)(hx+1), ly, (INTSML)(hy+1));
}

void gra_drawpatline(WINDOWFRAME *wf, INTSML x1, INTSML y1, INTSML x2, INTSML y2, INTSML col,
	INTSML mask, INTSML pattern)
{
	short dx, dy, d, incr1, incr2, x, y, xend, yend, yincr, xincr, i;

	/* initialize counter for line style */
	i = 0;

	/* initialize the Bresenham algorithm */
	dx = abs(x2-x1);
	dy = abs(y2-y1);
	if (dx > dy)
	{
		/* initialize for lines that increment along X */
		incr1 = 2 * dy;
		d = incr2 = 2 * (dy - dx);
		if (x1 > x2)
		{
			x = x2;   y = y2;   xend = x1;   yend = y1;
		} else
		{
			x = x1;   y = y1;   xend = x2;   yend = y2;
		}
		if (yend < y) yincr = -1; else yincr = 1;
		wf->rowstart[y][x] = (wf->rowstart[y][x]&mask) | col;

		/* draw line that increments along X */
		while (x < xend)
		{
			x++;
			if (d < 0) d += incr1; else
			{
				y += yincr;
				d += incr2;
			}
			if (i == 7) i = 0; else i++;
			if ((pattern & (1 << i)) == 0) continue;
			wf->rowstart[y][x] = (wf->rowstart[y][x]&mask) | col;
		}
	} else
	{
		/* initialize for lines that increment along Y */
		incr1 = 2 * dx;
		d = incr2 = 2 * (dx - dy);
		if (y1 > y2)
		{
			x = x2;   y = y2;   xend = x1;   yend = y1;
		} else
		{
			x = x1;   y = y1;   xend = x2;   yend = y2;
		}
		if (xend < x) xincr = -1; else xincr = 1;
		wf->rowstart[y][x] = (wf->rowstart[y][x]&mask) | col;

		/* draw line that increments along X */
		while (y < yend)
		{
			y++;
			if (d < 0) d += incr1; else
			{
				x += xincr;
				d += incr2;
			}
			if (i == 7) i = 0; else i++;
			if ((pattern & (1 << i)) == 0) continue;
			wf->rowstart[y][x] = (wf->rowstart[y][x]&mask) | col;
		}
	}
}

void gra_drawline(WINDOWFRAME *wf, INTSML x1, INTSML y1, INTSML x2, INTSML y2, INTSML col, INTSML mask)
{
	short dx, dy, d, incr1, incr2, x, y, xend, yend, yincr, xincr;

	/* initialize the Bresenham algorithm */
	dx = abs(x2-x1);
	dy = abs(y2-y1);
	if (dx > dy)
	{
		/* initialize for lines that increment along X */
		incr1 = 2 * dy;
		d = incr2 = 2 * (dy - dx);
		if (x1 > x2)
		{
			x = x2;   y = y2;   xend = x1;   yend = y1;
		} else
		{
			x = x1;   y = y1;   xend = x2;   yend = y2;
		}
		if (yend < y) yincr = -1; else yincr = 1;
		wf->rowstart[y][x] = (wf->rowstart[y][x]&mask) | col;

		/* draw line that increments along X */
		while (x < xend)
		{
			x++;
			if (d < 0) d += incr1; else
			{
				y += yincr;
				d += incr2;
			}
			wf->rowstart[y][x] = (wf->rowstart[y][x]&mask) | col;
		}
	} else
	{
		/* initialize for lines that increment along Y */
		incr1 = 2 * dx;
		d = incr2 = 2 * (dx - dy);
		if (y1 > y2)
		{
			x = x2;   y = y2;   xend = x1;   yend = y1;
		} else
		{
			x = x1;   y = y1;   xend = x2;   yend = y2;
		}
		if (xend < x) xincr = -1; else xincr = 1;
		wf->rowstart[y][x] = (wf->rowstart[y][x]&mask) | col;

		/* draw line that increments along X */
		while (y < yend)
		{
			y++;
			if (d < 0) d += incr1; else
			{
				x += xincr;
				d += incr2;
			}
			wf->rowstart[y][x] = (wf->rowstart[y][x]&mask) | col;
		}
	}
}

/******************** GRAPHICS POLYGONS ********************/

void screendrawpolygon(WINDOWPART *win, INTBIG *x, INTBIG *y, INTSML count, GRAPHICS *desc)
{
	REGISTER INTBIG i, j, k, l, ycur, yrev, wrap, lx, hx, ly, hy;
	REGISTER char *row;
	REGISTER INTSML col, mask, style, pat;
	REGISTER POLYSEG *a, *active, *edge, *lastedge, *left, *edgelist;
	REGISTER WINDOWFRAME *wf;

	/* get parameters */
	wf = win->frame;
	col = desc->col;   mask = ~desc->bits;
	style = desc->colstyle & NATURE;

	/* set redraw area */
	for(i=0; i<count; i++)
	{
		if (i == 0)
		{
			lx = hx = x[i];
			ly = hy = y[i];
		} else
		{
			lx = mini(lx, x[i]);
			hx = maxi(hx, x[i]);
			ly = mini(ly, y[i]);
			hy = maxi(hy, y[i]);
		}
	}
	gra_setrect(wf, (INTSML)lx, (INTSML)(hx + 1), (INTSML)(wf->revy-hy),
		(INTSML)(wf->revy-ly + 1));

	/* make sure there is room in internal structures */
	if (count > gra_polysegcount)
	{
		if (gra_polysegcount > 0) efree((char *)gra_polysegs);
		gra_polysegcount = 0;
		gra_polysegs = (POLYSEG *)emalloc(count * (sizeof (POLYSEG)), us_aid->cluster);
		if (gra_polysegs == 0) return;
		gra_polysegcount = count;
	}

	/* fill in internal structures */
	edgelist = NOPOLYSEG;
	for(i=0; i<count; i++)
	{
		if (i == 0)
		{
			gra_polysegs[i].fx = x[count-1];
			gra_polysegs[i].fy = y[count-1];
		} else
		{
			gra_polysegs[i].fx = x[i-1];
			gra_polysegs[i].fy = y[i-1];
		}
		gra_polysegs[i].tx = x[i];   gra_polysegs[i].ty = y[i];

		/* draw the edge lines to make the polygon clean */
		gra_drawline(wf, (INTSML)gra_polysegs[i].fx, (INTSML)(wf->revy - gra_polysegs[i].fy),
			(INTSML)gra_polysegs[i].tx, (INTSML)(wf->revy - gra_polysegs[i].ty), col, mask);

		/* compute the direction of this edge */
		j = gra_polysegs[i].ty - gra_polysegs[i].fy;
		if (j > 0) gra_polysegs[i].direction = 1; else
			if (j < 0) gra_polysegs[i].direction = -1; else
				gra_polysegs[i].direction = 0;

		/* compute the X increment of this edge */
		if (j == 0) gra_polysegs[i].increment = 0; else
		{
			gra_polysegs[i].increment = gra_polysegs[i].tx - gra_polysegs[i].fx;
			if (gra_polysegs[i].increment != 0) gra_polysegs[i].increment =
				(gra_polysegs[i].increment * 65536 - j + 1) / j;
		}
		gra_polysegs[i].tx <<= 16;   gra_polysegs[i].fx <<= 16;

		/* make sure "from" is above "to" */
		if (gra_polysegs[i].fy > gra_polysegs[i].ty)
		{
			j = gra_polysegs[i].tx;
			gra_polysegs[i].tx = gra_polysegs[i].fx;
			gra_polysegs[i].fx = j;
			j = gra_polysegs[i].ty;
			gra_polysegs[i].ty = gra_polysegs[i].fy;
			gra_polysegs[i].fy = j;
		}

		/* insert this edge into the edgelist, sorted by ascending "fy" */
		if (edgelist == NOPOLYSEG)
		{
			edgelist = &gra_polysegs[i];
			gra_polysegs[i].nextedge = NOPOLYSEG;
		} else
		{
			/* insert by ascending "fy" */
			if (edgelist->fy > gra_polysegs[i].fy)
			{
				gra_polysegs[i].nextedge = edgelist;
				edgelist = &gra_polysegs[i];
			} else for(a = edgelist; a != NOPOLYSEG; a = a->nextedge)
			{
				if (a->nextedge == NOPOLYSEG ||
					a->nextedge->fy > gra_polysegs[i].fy)
				{
					/* insert after this */
					gra_polysegs[i].nextedge = a->nextedge;
					a->nextedge = &gra_polysegs[i];
					break;
				}
			}
		}
	}

	/* scan polygon and render */
	active = NOPOLYSEG;
	while (active != NOPOLYSEG || edgelist != NOPOLYSEG)
	{
		if (active == NOPOLYSEG)
		{
			active = edgelist;
			active->nextactive = NOPOLYSEG;
			edgelist = edgelist->nextedge;
			ycur = active->fy;
		}

		/* introduce edges from edge list into active list */
		while (edgelist != NOPOLYSEG && edgelist->fy <= ycur)
		{
			/* insert "edgelist" into active list, sorted by "fx" coordinate */
			if (active->fx > edgelist->fx ||
				(active->fx == edgelist->fx && active->increment > edgelist->increment))
			{
				edgelist->nextactive = active;
				active = edgelist;
				edgelist = edgelist->nextedge;
			} else for(a = active; a != NOPOLYSEG; a = a->nextactive)
			{
				if (a->nextactive == NOPOLYSEG ||
					a->nextactive->fx > edgelist->fx ||
						(a->nextactive->fx == edgelist->fx &&
							a->nextactive->increment > edgelist->increment))
				{
					/* insert after this */
					edgelist->nextactive = a->nextactive;
					a->nextactive = edgelist;
					edgelist = edgelist->nextedge;
					break;
				}
			}
		}

		/* generate regions to be filled in on current scan line */
		wrap = 0;
		left = active;
		for(edge = active; edge != NOPOLYSEG; edge = edge->nextactive)
		{
			wrap = wrap + edge->direction;
			if (wrap == 0)
			{
				j = (left->fx + 32768) >> 16;
				k = (edge->fx + 32768) >> 16;
				yrev = wf->revy - ycur;
				row = wf->rowstart[yrev];
				if (style == PATTERNED)
				{
					/* patterned fill */
					pat = desc->raster[yrev&7];
					if (pat != 0)
					{
						for(l=j; l<=k; l++)
						{
							if ((pat & (1 << (15-(l&15)))) != 0)
								row[l] = (row[l] & mask) | col;
						}
					}
				} else
				{
					/* solid fill */
					for(l=j; l<=k; l++)
						row[l] = (row[l] & mask) | col;
				}
				left = edge->nextactive;
			}
		}
		ycur++;

		/* update edges in active list */
		lastedge = NOPOLYSEG;
		for(edge = active; edge != NOPOLYSEG; edge = edge->nextactive)
		{
			if (ycur >= edge->ty)
			{
				if (lastedge == NOPOLYSEG) active = edge->nextactive;
					else lastedge->nextactive = edge->nextactive;
			} else
			{
				edge->fx += edge->increment;
				lastedge = edge;
			}
		}
	}
}

/******************** GRAPHICS BOXES ********************/

void screendrawbox(WINDOWPART *win, INTSML lowx, INTSML highx, INTSML lowy, INTSML highy, GRAPHICS *desc)
{
	REGISTER INTSML col, mask, style, x, y, pat, top, bottom, left, right;
	REGISTER char *thisrow;
	REGISTER WINDOWFRAME *wf;

	wf = win->frame;

	/* get graphics parameters */
	col = desc->col;   mask = ~desc->bits;
	style = desc->colstyle & NATURE;
	left = lowx;                  right = highx + 1;
	bottom = wf->revy-lowy + 1;   top = wf->revy-highy;

	if (style == PATTERNED)
	{
		/* special case the patterned fill */
		for(y=top; y<bottom; y++)
		{
			pat = desc->raster[y&7];
			if (pat == 0) continue;
			thisrow = wf->rowstart[y];
			for(x=left; x<right; x++)
			{
				if ((pat & (1 << (15-(x&15)))) != 0)
					thisrow[x] = (thisrow[x] & mask) | col;
			}
		}
	} else
	{
		for(y=top; y<bottom; y++)
		{
			thisrow = wf->rowstart[y];
			for(x=left; x<right; x++)
				thisrow[x] = (thisrow[x] & mask) | col;
		}
	}

	gra_setrect(wf, left, right, top, bottom);
}

/*
 * routine to invert the bits in the box from (lowx, lowy) to (highx, highy)
 */
void screeninvertbox(WINDOWPART *win, INTSML lowx, INTSML highx, INTSML lowy, INTSML highy)
{
	REGISTER INTSML top, bottom, left, right, x, y;
	REGISTER char *thisrow;
	REGISTER WINDOWFRAME *wf;

	wf = win->frame;
	left = lowx;
	right = highx + 1;
	bottom = wf->revy - lowy + 1;
	top = wf->revy - highy;

	for(y=top; y<bottom; y++)
	{
		thisrow = wf->rowstart[y];
		for(x=left; x<right; x++) thisrow[x] = ~thisrow[x];
	}

	gra_setrect(wf, left, right, top, bottom);
}

/*
 * routine to move bits on the display starting with the area at
 * (sx,sy) and ending at (dx,dy).  The size of the area to be
 * moved is "wid" by "hei".
 */
void screenmovebox(WINDOWPART *win, INTSML sx, INTSML sy, INTSML wid, INTSML hei, INTSML dx, INTSML dy)
{
	REGISTER INTSML totop, tobottom, toleft, toright, fromleft, fromtop;
	REGISTER WINDOWFRAME *wf;

	wf = win->frame;

	/* setup source rectangle */
	fromleft = sx;
	fromtop = wf->revy + 1 - sy - hei;

	/* setup destination rectangle */
	toleft = dx;
	toright = toleft + wid;
	totop = wf->revy + 1 - dy - hei;
	tobottom = totop + hei;

	((CDC *)wf->hDCOff)->BitBlt(toleft, totop, wid, hei, (CDC *)wf->hDCOff,
		fromleft, fromtop, SRCCOPY);

	gra_setrect(wf, toleft, toright, totop, tobottom);
}

/*
 * routine to save the contents of the box from "lx" to "hx" in X and from
 * "ly" to "hy" in Y.  A code is returned that identifies this box for
 * overwriting and restoring.  The routine returns -1 if there is a error.
 */
INTBIG screensavebox(WINDOWPART *win, INTSML lx, INTSML hx, INTSML ly, INTSML hy)
{
	SAVEDBOX *box;
	REGISTER INTBIG xsize, ysize;
	REGISTER INTSML i, x, y;
	REGISTER WINDOWFRAME *wf;
	REGISTER char *source, *dest;

	wf = win->frame;
	i = ly;
	ly = wf->revy-hy;
	hy = wf->revy-i;
	xsize = hx-lx+1;
	ysize = hy-ly+1;
	box = (SAVEDBOX *)emalloc((sizeof (SAVEDBOX)), us_aid->cluster);
	if (box == 0) return(-1);

	if (gra_buildoffscreenbuffer(wf, (INTSML)xsize, (INTSML)ysize, &box->hMemDC,
		&box->hBox, &box->bminfo, &box->data, &box->rowstart) != 0) return(-1);

	box->win = win;
	box->nextsavedbox = gra_firstsavedbox;
	gra_firstsavedbox = box;
	box->lx = lx;           box->hx = hx;
	box->ly = ly;           box->hy = hy;

	/* move the bits */
	for(y=0; y<ysize; y++)
	{
		source = &wf->rowstart[ly+y][lx];
		dest = box->rowstart[y];
		for(x=0; x<xsize; x++) *dest++ = *source++;
	}

	return((INTBIG)box);
}

/*
 * routine to shift the saved box "code" so that it is restored in a different location,
 * offset by (dx,dy)
 */
void screenmovesavedbox(INTBIG code, INTSML dx, INTSML dy)
{
	REGISTER SAVEDBOX *box;

	if (code == -1) return;
	box = (SAVEDBOX *)code;
	box->lx += dx;       box->hx += dx;
	box->ly -= dy;       box->hy -= dy;
}

/*
 * routine to restore saved box "code" to the screen.  "destroy" is:
 *  0   restore box, do not free memory
 *  1   restore box, free memory
 * -1   free memory
 * Returns nonzero if there is an error.
 */
INTSML screenrestorebox(INTBIG code, INTSML destroy)
{
	REGISTER SAVEDBOX *box, *lbox, *tbox;
	REGISTER INTSML x, y, xsize, ysize;
	REGISTER char *source, *dest;
	REGISTER WINDOWFRAME *wf;

	/* get the box */
	if (code == -1) return(1);
	box = (SAVEDBOX *)code;

	/* move the bits */
	if (destroy >= 0)
	{
		wf = box->win->frame;
		xsize = box->hx - box->lx + 1;
		ysize = box->hy - box->ly + 1;
		for(y=0; y<ysize; y++)
		{
			dest = &wf->rowstart[box->ly+y][box->lx];
			source = box->rowstart[y];
			for(x=0; x<xsize; x++) *dest++ = *source++;
		}
		efree((char *)box->rowstart);
		efree((char *)box->bminfo);
		DeleteObject(box->hBox);
		delete box->hMemDC;
		gra_setrect(wf, box->lx, (INTSML)(box->hx+1), box->ly, (INTSML)(box->hy+1));
	}

	/* destroy this box's memory if requested */
	if (destroy != 0)
	{
		lbox = NOSAVEDBOX;
		for(tbox = gra_firstsavedbox; tbox != NOSAVEDBOX; tbox = tbox->nextsavedbox)
		{
			if (tbox == box)
				break;
			lbox = tbox;
		}
		if (lbox == NOSAVEDBOX)
			gra_firstsavedbox = box->nextsavedbox;
		else
			lbox->nextsavedbox = box->nextsavedbox;
		efree((char *)box);
	}
	return(0);
}

/******************** GRAPHICS TEXT ********************/

void screensettextsize(WINDOWPART *win, INTSML fnt)
{
	REGISTER WINDOWFRAME *wf;

	wf = win->frame;
	wf->hFont = 0;
	wf->hFont = gra_gettextfont(fnt);
	if (wf->hFont == 0) return;
	((CDC *)wf->hDCOff)->SelectObject((CFont *)wf->hFont);
}

CFont *gra_gettextfont(INTSML whichFont)
{
	static CFont *txt4p = 0, *txt6p = 0, *txt8p = 0, *txt10p = 0,
		*txt12p = 0, *txt14p = 0, *txt16p = 0, *txt18p = 0, *txt20p = 0,
		*txtsmall = 0, *txtmedium = 0, *txtlarge = 0,
		*txtstatus = 0, *txteditor = 0, *txtmenu = 0;

	switch (whichFont)
	{
		case TXT4P:
			if (txt4p == 0)     txt4p = gra_createtextfont(TXT4P);
			return(txt4p);
		case TXT6P:
			if (txt6p == 0)     txt6p = gra_createtextfont(TXT6P);
			return(txt6p);
		case TXT8P:
			if (txt8p == 0)     txt8p = gra_createtextfont(TXT8P);
			return(txt8p);
		case TXT10P:
			if (txt10p == 0)    txt10p = gra_createtextfont(TXT10P);
			return(txt10p);
		case TXT12P:
			if (txt12p == 0)    txt12p = gra_createtextfont(TXT12P);
			return(txt12p);
		case TXT14P:
			if (txt14p == 0)    txt14p = gra_createtextfont(TXT14P);
			return(txt14p);
		case TXT16P:
			if (txt16p == 0)    txt16p = gra_createtextfont(TXT16P);
			return(txt16p);
		case TXT18P:
			if (txt18p == 0)    txt18p = gra_createtextfont(TXT18P);
			return(txt18p);
		case TXT20P:
			if (txt20p == 0)    txt20p = gra_createtextfont(TXT20P);
			return(txt20p);

		case TXTSMALL:
			if (txtsmall == 0)  txtsmall = gra_createtextfont(TXTSMALL);
			return(txtsmall);
		case TXTMEDIUM:
			if (txtmedium == 0) txtmedium = gra_createtextfont(TXTMEDIUM);
			return(txtmedium);
		case TXTLARGE:
			if (txtlarge == 0)  txtlarge = gra_createtextfont(TXTLARGE);
			return(txtlarge);

		case TXTSTATUS:
			if (txtstatus == 0) txtstatus = gra_createtextfont(TXTSTATUS);
			return(txtstatus);
		case TXTEDITOR:
			if (txteditor == 0) txteditor = gra_createtextfont(TXTEDITOR);
			return(txteditor);
		case TXTMENU:
			if (txtmenu == 0)   txtmenu = gra_createtextfont(TXTMENU);
			return(txtmenu);
	}
	return(0);
}

CFont *gra_createtextfont(INTSML whichFont)
{
	LOGFONT lf;
	CFont *hf;
	INTSML i;

	/* special case for text editor font */
	for(i=0; fontList[i].eFontIndex != -1; i++)
	{
		if (fontList[i].eFontIndex == whichFont)
		{
			lf.lfHeight = fontList[i].fontSize;
			strcpy(lf.lfFaceName, fontList[i].fontName);
			break;
		}
	}

	lf.lfWidth = 0;
	lf.lfEscapement = 0;
	lf.lfOrientation = 0;
	lf.lfWeight = FW_NORMAL;
	lf.lfItalic = 0;
	lf.lfUnderline = 0;
	lf.lfStrikeOut = 0;
	lf.lfCharSet = 0;
	lf.lfOutPrecision = OUT_STROKE_PRECIS;
	lf.lfClipPrecision = CLIP_STROKE_PRECIS;
	lf.lfQuality = 1;
	lf.lfPitchAndFamily = FF_DONTCARE | FF_SWISS;

	hf = new CFont();
	hf->CreateFontIndirect(&lf);

	return(hf);
}

void screengettextsize(WINDOWPART *win, char *str, INTSML *x, INTSML *y)
{
	INTSML len;
	CSize textSize;
	REGISTER WINDOWFRAME *wf;

	wf = win->frame;
	len = strlen(str);
	textSize = ((CDC *)wf->hDCOff)->GetTextExtent((LPCTSTR)str, len);
	*x = (INTSML)textSize.cx;
	*y = (INTSML)textSize.cy+1;
}

void screendrawtext(WINDOWPART *win, INTSML atx, INTSML aty, char *s, GRAPHICS *desc)
{
	REGISTER INTSML i, col, top, bottom, left, right, mask, j, pos, dpos,
		startx, endx, lx, hx, ly, hy;
	INTSML wid, hei;
	char **rowstart;
	CSize textSize;
	REGISTER WINDOWFRAME *wf;
	REGISTER char *ptr, *dest;

	/* copy the string and correct it */
	gettextbits(win, s, &wid, &hei, &rowstart);
	if (wid == 0 || hei == 0) return;

	/* get parameters */
	wf = win->frame;
	col = desc->col;   mask = ~desc->bits;
	aty = wf->revy - aty;
	lx = win->uselx;   hx = win->usehx;
	ly = wf->revy - win->usehy;
	hy = wf->revy - win->usely;

	/* text is too high - rescale it? */
	if (aty-hei <= 0) return;

	/* copy text buffer to the main offscreen buffer */
	if (atx < lx) startx = lx-atx; else startx = 0;
	if (wid + atx >= hx) endx = hx - atx; else endx = wid;
	pos = aty - hei;
	for(i=0; i<hei; i++)
	{
		if (pos+i < ly || pos+i > hy) continue;
		ptr = rowstart[i];
		dest = wf->rowstart[pos+i];
		for(j=startx; j<endx; j++)
		{
			if (ptr[j] == 0) continue;
			dpos = atx + j;
			dest[dpos] = (dest[dpos] & mask) | col;
		}
	}

	/* set redraw area */
	left = atx;     right = left + wid;
	bottom = aty;   top = bottom - hei;
	gra_setrect(wf, left, right, top, bottom);
}

/*
 * Routine to convert the string "msg" (to be drawn into window "win") into an
 * array of pixels.  The size of the array is returned in "wid" and "hei", and
 * the pixels are returned in an array of character vectors "rowstart".
 * The routine returns nonzero if the operation cannot be done.
 */
INTSML gettextbits(WINDOWPART *win, char *msg, INTSML *wid, INTSML *hei, char ***rowstart)
{
	REGISTER INTSML i, len, j;
	CSize textSize;
	REGISTER WINDOWFRAME *wf;
	REGISTER char *ptr;

	/* copy the string and correct it */
	len = strlen(msg);
	if (len == 0)
	{
		*wid = *hei = 0;
		return(0);
	}
	for(i=0; i<len; i++)
	{
		gra_localstring[i] = msg[i] & 0177;
		if (gra_localstring[i] < ' ') gra_localstring[i] = ' ';
	}
	gra_localstring[i] = 0;

	/* determine size of string */
	wf = win->frame;
	textSize = ((CDC *)wf->hDCOff)->GetTextExtent((LPCTSTR)gra_localstring, len);
	*wid = (INTSML)textSize.cx;
	*hei = (INTSML)textSize.cy;

	/* see if text buffer needs to be (re)initialized */
	if (gra_textbufinited != 0)
	{
		if (*wid > gra_textbufwid || *hei > gra_textbufhei)
		{
			efree((char *)gra_textrowstart);
			efree((char *)gra_textbitmapinfo);
			DeleteObject(gra_textbitmap);
			delete gra_texthdc;
			gra_textbufinited = 0;
		}
	}

	/* allocate text buffer if needed */
	if (gra_textbufinited == 0)
	{
		if (gra_buildoffscreenbuffer(wf, *wid, *hei, &gra_texthdc, &gra_textbitmap,
			&gra_textbitmapinfo, &gra_textdatabuffer, &gra_textrowstart) != 0) return(1);
		gra_texthdc->SetTextColor(PALETTEINDEX(1));
		gra_texthdc->SetROP2(R2_COPYPEN);
		gra_texthdc->SetBkMode(TRANSPARENT);
		gra_texthdc->SetTextAlign(TA_TOP<<8);

		/* remember information about text buffer */
		gra_textbufwid = *wid;   gra_textbufhei = *hei;
		gra_textbufinited = 1;
	}

	/* clear the text buffer */
	for(i=0; i < *hei; i++)
	{
		ptr = gra_textrowstart[i];
		for(j=0; j < *wid; j++) *ptr++ = 0;
	}

	/* write to the text buffer */
	gra_texthdc->SelectObject((CFont *)wf->hFont);
	gra_texthdc->TextOut(0, 0, (LPCTSTR)gra_localstring, len);

	*rowstart = gra_textrowstart;
	return(0);
}

/******************** CIRCLES ********************/

void screendrawcircle(WINDOWPART *win, INTBIG atx, INTBIG aty, INTBIG radius, GRAPHICS *desc)
{
	REGISTER INTSML col, mask, top, bottom, left, right;
	REGISTER INTBIG x, y, d, maxx, maxy, thisx, thisy;
	REGISTER WINDOWFRAME *wf;
	REGISTER char *thisrow;

	/* get parameters */
	wf = win->frame;
	col = desc->col;   mask = ~desc->bits;
	aty = wf->revy - aty;

	/* set redraw area */
	left = (INTSML)(atx - radius);
	right = (INTSML)(atx + radius + 1);
	top = (INTSML)(aty - radius);
	bottom = (INTSML)(aty + radius + 1);
	gra_setrect(wf, left, right, top, bottom);

	maxx = wf->swid;
	maxy = wf->shei;
	x = 0;   y = radius;
	d = 3 - 2 * radius;
	if (left >= 0 && right < maxx && top >= 0 && bottom < maxy)
	{
		/* no clip version is faster */
		while (x <= y)
		{
			thisrow = wf->rowstart[aty + y];
			thisrow[atx + x] = (thisrow[atx + x] & mask) | col;
			thisrow[atx - x] = (thisrow[atx - x] & mask) | col;

			thisrow = wf->rowstart[aty - y];
			thisrow[atx + x] = (thisrow[atx + x] & mask) | col;
			thisrow[atx - x] = (thisrow[atx - x] & mask) | col;

			thisrow = wf->rowstart[aty + x];
			thisrow[atx + y] = (thisrow[atx + y] & mask) | col;
			thisrow[atx - y] = (thisrow[atx - y] & mask) | col;

			thisrow = wf->rowstart[aty - x];
			thisrow[atx + y] = (thisrow[atx + y] & mask) | col;
			thisrow[atx - y] = (thisrow[atx - y] & mask) | col;

			if (d < 0) d += 4*x + 6; else
			{
				d += 4 * (x-y) + 10;
				y--;
			}
			x++;
		}
	} else
	{
		/* clip version */
		while (x <= y)
		{
			thisy = aty + y;
			if (thisy >= 0 && thisy < maxy)
			{
				thisrow = wf->rowstart[thisy];
				thisx = atx + x;
				if (thisx >= 0 && thisx < maxx) thisrow[thisx] = (thisrow[thisx] & mask) | col;
				thisx = atx - x;
				if (thisx >= 0 && thisx < maxx) thisrow[thisx] = (thisrow[thisx] & mask) | col;
			}

			thisy = aty - y;
			if (thisy >= 0 && thisy < maxy)
			{
				thisrow = wf->rowstart[thisy];
				thisx = atx + x;
				if (thisx >= 0 && thisx < maxx) thisrow[thisx] = (thisrow[thisx] & mask) | col;
				thisx = atx - x;
				if (thisx >= 0 && thisx < maxx) thisrow[thisx] = (thisrow[thisx] & mask) | col;
			}

			thisy = aty + x;
			if (thisy >= 0 && thisy < maxy)
			{
				thisrow = wf->rowstart[thisy];
				thisx = atx + y;
				if (thisx >= 0 && thisx < maxx) thisrow[thisx] = (thisrow[thisx] & mask) | col;
				thisx = atx - y;
				if (thisx >= 0 && thisx < maxx) thisrow[thisx] = (thisrow[thisx] & mask) | col;
			}

			thisy = aty - x;
			if (thisy >= 0 && thisy < maxy)
			{
				thisrow = wf->rowstart[thisy];
				thisx = atx + y;
				if (thisx >= 0 && thisx < maxx) thisrow[thisx] = (thisrow[thisx] & mask) | col;
				thisx = atx - y;
				if (thisx >= 0 && thisx < maxx) thisrow[thisx] = (thisrow[thisx] & mask) | col;
			}

			if (d < 0) d += 4*x + 6; else
			{
				d += 4 * (x-y) + 10;
				y--;
			}
			x++;
		}
	}
}

/*
 * routine to draw a filled-in circle of radius "radius"
 */
void gra_drawdiscrow(WINDOWFRAME *wf, INTBIG thisy, INTBIG startx, INTBIG endx, GRAPHICS *desc)
{
	REGISTER char *thisrow;
	REGISTER INTBIG x;
	REGISTER INTSML pat;

	if (thisy < 0 || thisy >= gra_curvemaxy) return;
	thisrow = wf->rowstart[thisy];
	if (startx < 0) startx = 0;
	if (endx >= gra_curvemaxx) endx = gra_curvemaxy - 1;
	if (gra_curvestyle == PATTERNED)
	{
		pat = desc->raster[thisy&7];
		if (pat != 0)
		{
			for(x=startx; x<=endx; x++)
				if ((pat & (1 << (15-(x&15)))) != 0)
					thisrow[x] = (thisrow[x] & gra_curvemask) | gra_curvecol;
		}
	} else
	{
		for(x=startx; x<=endx; x++)
			thisrow[x] = (thisrow[x] & gra_curvemask) | gra_curvecol;
	}
}

/*
 * routine to draw a filled-in circle of radius "radius"
 */
void screendrawdisc(WINDOWPART *win, INTBIG atx, INTBIG aty, INTBIG radius, GRAPHICS *desc)
{
	REGISTER WINDOWFRAME *wf;
	REGISTER INTBIG x, y, d;
	REGISTER INTSML top, bottom, left, right;

	/* get parameters */
	wf = win->frame;
	gra_curvestyle = desc->colstyle & NATURE;
	gra_curvecol = desc->col;   gra_curvemask = ~desc->bits;

	/* set redraw area */
	aty = wf->revy - aty;
	left = (INTSML)(atx - radius);
	right = (INTSML)(atx + radius + 1);
	top = (INTSML)(aty - radius);
	bottom = (INTSML)(aty + radius + 1);
	gra_setrect(wf, left, right, top, bottom);

	gra_curvemaxx = wf->swid;
	gra_curvemaxy = wf->shei;
	x = 0;   y = radius;
	d = 3 - 2 * radius;
	while (x <= y)
	{
		gra_drawdiscrow(wf, aty+y, atx-x, atx+x, desc);
		gra_drawdiscrow(wf, aty-y, atx-x, atx+x, desc);
		gra_drawdiscrow(wf, aty+x, atx-y, atx+y, desc);
		gra_drawdiscrow(wf, aty-x, atx-y, atx+y, desc);

		if (d < 0) d += 4*x + 6; else
		{
			d += 4 * (x-y) + 10;
			y--;
		}
		x++;
	}
}

/******************** ARC DRAWING ********************/

INTSML gra_arcfindoctant(INTBIG x, INTBIG y)
{
	if (x > 0)
		if (y >= 0)
			if (y >= x)	 return 7;
			else         return 8;
		else
			if (x >= -y) return 1;
			else         return 2;
	else
		if (y > 0)
			if (y > -x)  return 6;
			else         return 5;
		else
			if (y > x)   return 4;
			else         return 3;
}

void gra_arcxformoctant(INTBIG x, INTBIG y, INTBIG oct, INTBIG *ox, INTBIG *oy)
{
	switch (oct)
	{
		case 1 : *ox = -y;   *oy = x;   break;
		case 2 : *ox = x;    *oy = -y;  break;
		case 3 : *ox = -x;   *oy = -y;  break;
		case 4 : *ox = -y;   *oy = -x;  break;
		case 5 : *ox = y;    *oy = -x;  break;
		case 6 : *ox = -x;   *oy = y;   break;
		case 7 : *ox = x;    *oy = y;   break;
		case 8 : *ox = y;    *oy = x;   break;
	}
}

void gra_arcdopixel(INTBIG x, INTBIG y)
{
	if (x < 0 || x >= gra_curvemaxx || y < 0 || y >= gra_curvemaxy) return;
	if (gra_arcfirst != 0)
	{
		gra_arcfirst = 0;
		gra_arclx = gra_archx = x;
		gra_arcly = gra_archy = y;
	} else
	{
		if (x < gra_arclx) gra_arclx = x;
		if (x > gra_archx) gra_archx = x;
		if (y < gra_arcly) gra_arcly = y;
		if (y > gra_archy) gra_archy = y;
	}
	gra_arcframe->rowstart[y][x] = (gra_arcframe->rowstart[y][x] & gra_curvemask) | gra_curvecol;
}

void gra_arcoutxform(INTBIG x, INTBIG y)
{
	if (gra_arcocttable[1]) gra_arcdopixel( y + gra_arccenterx, -x + gra_arccentery);
	if (gra_arcocttable[2]) gra_arcdopixel( x + gra_arccenterx, -y + gra_arccentery);
	if (gra_arcocttable[3]) gra_arcdopixel(-x + gra_arccenterx, -y + gra_arccentery);
	if (gra_arcocttable[4]) gra_arcdopixel(-y + gra_arccenterx, -x + gra_arccentery);
	if (gra_arcocttable[5]) gra_arcdopixel(-y + gra_arccenterx,  x + gra_arccentery);
	if (gra_arcocttable[6]) gra_arcdopixel(-x + gra_arccenterx,  y + gra_arccentery);
	if (gra_arcocttable[7]) gra_arcdopixel( x + gra_arccenterx,  y + gra_arccentery);
	if (gra_arcocttable[8]) gra_arcdopixel( y + gra_arccenterx,  x + gra_arccentery);
}

void gra_arcbrescw(INTBIG x, INTBIG y, INTBIG x1, INTBIG y1)
{
	REGISTER INTBIG d;

	d = 3 - 2 * y + 4 * x;
	while (x < x1 && y > y1)
	{
		gra_arcoutxform(x, y);
		if (d < 0) d += 4*x+6; else
		{
			d += 4*(x-y)+10;
			y--;
		}
		x++;
	}

	/* get to the end */
	for ( ; x < x1; x++) gra_arcoutxform(x, y);
	for ( ; y > y1; y--) gra_arcoutxform(x, y);
   gra_arcoutxform(x1, y1);
}

void gra_arcbresmidcw(INTBIG x, INTBIG y)
{
	REGISTER INTBIG d;

	d = 3 - 2 * y + 4 * x;
	while (x < y)
	{
		gra_arcoutxform(x, y);
		if (d < 0) d += 4*x+6; else
		{
			d += 4*(x-y)+10;
			y--;
		}
		x++;
   }
   if (x == y) gra_arcoutxform(x, y);
}

void gra_arcbresmidccw(INTBIG x, INTBIG y)
{
	REGISTER INTBIG d;

	d = 3 + 2 * y - 4 * x;
	while (x > 0)
	{
		gra_arcoutxform(x, y);
		if (d > 0) d += 6-4*x; else
		{
			d += 4*(y-x)+10;
			y++;
		}
		x--;
   }
   gra_arcoutxform(0, gra_arcradius);
}

void gra_arcbresccw(INTBIG x, INTBIG y, INTBIG x1, INTBIG y1)
{
	REGISTER INTBIG d;

	d = 3 + 2 * y + 4 * x;
	while(x > x1 && y < y1)
	{
		/* not always correct */
		gra_arcoutxform(x, y);
		if (d > 0) d += 6 - 4*x; else
		{
			d += 4*(y-x)+10;
			y++;
		}
		x--;
	}

	/* get to the end */
	for ( ; x > x1; x--) gra_arcoutxform(x, y);
	for ( ; y < y1; y++) gra_arcoutxform(x, y);
	gra_arcoutxform(x1, y1);
}

/*
 * draws an arc centered at (centerx, centery), clockwise,
 * passing by (x1,y1) and (x2,y2)
 */
void screendrawcirclearc(WINDOWPART *win, INTBIG centerx, INTBIG centery, INTBIG p1_x, INTBIG p1_y,
	INTBIG p2_x, INTBIG p2_y, GRAPHICS *desc)
{
	REGISTER INTBIG alternate, pa_x, pa_y, pb_x, pb_y, i, diff;
	INTBIG x, y;
	REGISTER INTSML start_oct, end_oct;

	/* ignore tiny arcs */
	if (p1_x == p2_x && p1_y == p2_y) return;

	/* get parameters */
	gra_arcframe = win->frame;
	gra_curvecol = desc->col;   gra_curvemask = ~desc->bits;
	gra_curvemaxx = gra_arcframe->swid;
	gra_curvemaxy = gra_arcframe->shei;
	p1_y = gra_arcframe->revy - p1_y;
	p2_y = gra_arcframe->revy - p2_y;
	gra_arccentery = gra_arcframe->revy - centery;
	gra_arccenterx = centerx;
	pa_x = p2_x - gra_arccenterx;
	pa_y = p2_y - gra_arccentery;
	pb_x = p1_x - gra_arccenterx;
	pb_y = p1_y - gra_arccentery;
	gra_arcradius = computedistance(gra_arccenterx, gra_arccentery, p2_x, p2_y);
	alternate = computedistance(gra_arccenterx, gra_arccentery, p1_x, p1_y);
	start_oct = gra_arcfindoctant(pa_x, pa_y);
	end_oct   = gra_arcfindoctant(pb_x, pb_y);
	gra_arcfirst = 1;

	/* move the point */
	if (gra_arcradius != alternate)
	{
		diff = gra_arcradius-alternate;
		switch (end_oct)
		{
			case 6:
			case 7: /*  y >  x */ pb_y += diff;  break;
			case 8: /*  x >  y */
			case 1: /*  x > -y */ pb_x += diff;  break;
			case 2: /* -y >  x */
			case 3: /* -y > -x */ pb_y -= diff;  break;
			case 4: /* -y < -x */
			case 5: /*  y < -x */ pb_x -= diff;  break;
		}
	}

	for(i=1; i<9; i++) gra_arcocttable[i] = 0;

	if (start_oct == end_oct)
	{
		INTBIG x1, y1, x2, y2;

		gra_arcocttable[start_oct] = 1;
		gra_arcxformoctant(pa_x, pa_y, start_oct, &x1, &y1);
		gra_arcxformoctant(pb_x, pb_y, start_oct, &x2 ,&y2);

		if (ODD(start_oct)) gra_arcbrescw(x1, y1, x2, y2);
		else				gra_arcbresccw(x1 ,y1, x2, y2);
		gra_arcocttable[start_oct] = 0;
	} else
	{
		gra_arcocttable[start_oct] = 1;
		gra_arcxformoctant(pa_x, pa_y, start_oct, &x, &y);
		if (ODD(start_oct)) gra_arcbresmidcw(x, y);
		else				gra_arcbresmidccw(x, y);
		gra_arcocttable[start_oct] = 0;

		gra_arcocttable[end_oct] = 1;
		gra_arcxformoctant(pb_x, pb_y, end_oct, &x, &y);
		if (ODD(end_oct)) gra_arcbresmidccw(x, y);
		else			  gra_arcbresmidcw(x, y);
		gra_arcocttable[end_oct] = 0;

		if (MODP(start_oct+1) != end_oct)
		{
			if (MODP(start_oct+1) == MODM(end_oct-1))
				gra_arcocttable[MODP(start_oct+1)] = 1; else
					for(i = MODP(start_oct+1); i != end_oct; i = MODP(i+1))
						gra_arcocttable[i] = 1;
			gra_arcbresmidcw(0, gra_arcradius);
		}
	}

	/* set redraw area */
	if (gra_arcfirst == 0)
		gra_setrect(gra_arcframe, (INTSML)(gra_arclx), (INTSML)(gra_archx+1), (INTSML)(gra_arcly),
			(INTSML)(gra_archy+1));
}

/******************** GRID CONTROL ********************/

/*
 * grid drawing routine
 */
void screendrawgrid(WINDOWPART *win, POLYGON *obj)
{
	REGISTER INTBIG i, j, xnum, xden, ynum, yden, x0,y0, x1,y1, x2,y2, x3,y3,
		x4,y4, x5,y5, x10, y10, y10mod, xspacing, yspacing, y1base, x1base;
	REGISTER INTSML x, y, fatdots;
	REGISTER WINDOWFRAME *wf;
	REGISTER VARIABLE *var;

	wf = win->frame;
	x0 = obj->xv[0];   y0 = obj->yv[0];		/* screen space grid spacing */
	x1 = obj->xv[1];   y1 = obj->yv[1];		/* screen space grid start */
	x2 = obj->xv[2];   y2 = obj->yv[2];		/* display space low */
	x3 = obj->xv[3];   y3 = obj->yv[3];		/* display space high */
	x4 = obj->xv[4];   y4 = obj->yv[4];		/* screen space low */
	x5 = obj->xv[5];   y5 = obj->yv[5];		/* screen space high */

	var = getvalkey((INTBIG)us_aid, VAID, -1, us_gridboldspacing);
	if (var == NOVARIABLE) xspacing = yspacing = 10; else
	{
		if ((var->type&VISARRAY) == 0)
			xspacing = yspacing = var->addr; else
		{
			xspacing = ((INTBIG *)var->addr)[0];
			yspacing = ((INTBIG *)var->addr)[1];
		}
	}

	xnum = x3 - x2;
	xden = x5 - x4;
	ynum = y3 - y2;
	yden = y5 - y4;
	x10 = x0*xspacing;       y10 = y0*yspacing;
	y1base = y1 - (y1 / y0 * y0);
	x1base = x1 - (x1 / x0 * x0);

	/* adjust grid placement according to scale */
	fatdots = 0;
	if (muldiv(x0, xnum, xden) < 5 || muldiv(y0, ynum, yden) < 5)
	{
		x1 = x1base - (x1base - x1) / x10 * x10;   x0 = x10;
		y1 = y1base - (y1base - y1) / y10 * y10;   y0 = y10;
	} else if (muldiv(x0, xnum, xden) > 75 && muldiv(y0, ynum, yden) > 75)
	{
		fatdots = 1;
	}

	/* draw the grid to the offscreen buffer */
	for(i = y1; i < y5; i += y0)
	{
		y = (INTSML)(muldiv(i-y4, ynum, yden) + y2);
		if (y < y2 || y > y3) continue;
		y = wf->revy - y;
		y10mod = (i-y1base) % y10;
		for(j = x1; j < x5; j += x0)
		{
			x = (INTSML)(muldiv(j-x4, xnum, xden) + x2);
			if (x >= x2 && x <= x3) wf->rowstart[y][x] |= GRID;

			/* special case every "spacing" grid points in each direction */
			if (fatdots != 0 || ((j-x1base)%x10) == 0 && y10mod == 0)
			{
				if (x > x2) wf->rowstart[y][x-1] |= GRID;
				if (x < x3) wf->rowstart[y][x+1] |= GRID;
				if (y > y2) wf->rowstart[y-1][x] |= GRID;
				if (y < y3) wf->rowstart[y+1][x] |= GRID;
				if (fatdots != 0 && ((j-x1base)%x10) == 0 && y10mod == 0)
				{
					if (x-1 > x2) wf->rowstart[y][x-2] |= GRID;
					if (x+1 < x3) wf->rowstart[y][x+2] |= GRID;
					if (y-1 > y2) wf->rowstart[y-2][x] |= GRID;
					if (y+1 < y3) wf->rowstart[y+2][x] |= GRID;
					if (x > x2 && y > y2) wf->rowstart[y-1][x-1] |= GRID;
					if (x > x2 && y < y3) wf->rowstart[y+1][x-1] |= GRID;
					if (x < x3 && y > y2) wf->rowstart[y-1][x+1] |= GRID;
					if (x < x3 && y < y3) wf->rowstart[y+1][x+1] |= GRID;
				}
			}
		}
	}

	/* copy it back to the screen */
	gra_setrect(wf, (INTSML)x2, (INTSML)x3, (INTSML)(wf->revy-y3), (INTSML)(wf->revy-y2));
}

/******************** MOUSE CONTROL ********************/

/*
 * routine to return the number of buttons on the mouse
 */
INTSML buttoncount(void)
{
	return((INTSML)mini(BUTTONS, NUMBUTS));
}

/*
 * routine to tell whether button "but" is a double-click
 */
INTSML doublebutton(INTSML b)
{
	if (b >= 24 && b <= 26) return(1);
	return(0);
}

/*
 * routine to tell whether button "but" has the "shift" key held
 */
INTSML shiftbutton(INTSML b)
{
	if ((b >= 3 && b <= 5) || (b >= 12 && b <= 17) ||
		(b >= 21 && b <= 23)) return(1);
	return(0);
}

/*
 * routine to return the name of button "b" (from 0 to "buttoncount()").
 * The number of letters unique to the button is placed in "important".
 */
char *buttonname(INTSML b, INTSML *important)
{
	*important = gra_buttonname[b].unique;
	return(gra_buttonname[b].name);
}

/*
 * routine to convert from "gra_inputstate" (the typical input parameter)
 * to button numbers (the table "gra_buttonname")
 */
INTSML gra_makebutton(INTBIG state)
{
	REGISTER INTSML base;

	if ((state&ISLEFT) != 0) base = 0; else
		if ((state&ISMIDDLE) != 0) base = 1; else
			base = 2;

	if ((state&DOUBLECLICK) != 0) return(base+24);
	switch (state & (SHIFTDOWN|CONTROLDOWN|ALTDOWN))
	{
		case SHIFTDOWN                    : return(base+3);
		case           CONTROLDOWN        : return(base+6);
		case                       ALTDOWN: return(base+9);
		case SHIFTDOWN|CONTROLDOWN        : return(base+12);
		case SHIFTDOWN|            ALTDOWN: return(base+15);
		case           CONTROLDOWN|ALTDOWN: return(base+18);
		case SHIFTDOWN|CONTROLDOWN|ALTDOWN: return(base+21);
	}
	return(base);
}

/*
 * routine to wait for a button push and return its index (0 based) in "*but".
 * The coordinates of the cursor are placed in "*x" and "*y".  If there is no
 * button push, the value of "*but" is negative.
 */
void waitforbutton(INTSML *x, INTSML *y, INTSML *but)
{
	if (gra_inputstate != NOEVENT && (gra_inputstate&(ISBUTTON|BUTTONUP)) == ISBUTTON)
	{
		*but = gra_makebutton(gra_inputstate);
		*x = (INTSML)gra_cursorx;
		*y = (INTSML)gra_cursory;
		gra_inputstate = NOEVENT;
		if (us_cursorstate != IBEAMCURSOR) setdefaultcursortype(us_normalcursor);
		return;
	}
	gra_nextevent();
	if (gra_inputstate != NOEVENT && (gra_inputstate&(ISBUTTON|BUTTONUP)) == ISBUTTON)
	{
		*but = gra_makebutton(gra_inputstate);
		*x = (INTSML)gra_cursorx;
		*y = (INTSML)gra_cursory;
		gra_inputstate = NOEVENT;
		if (us_cursorstate != IBEAMCURSOR) setdefaultcursortype(us_normalcursor);
		return;
	}
	*but = -1;
}

/*
 * routine to track the cursor until a button is released, calling "whileup" for
 * each co-ordinate when the mouse moves before the first button push, calling
 * "whendown" once when the button goes down, calling "eachdown" for each
 * co-ordinate when the mouse moves after the button is pushed, calling
 * "eachchar" for each key that is typed at any time, and calling "done" once
 * when done.  The "whendown" and "done" routines are called with no parameters;
 * "whileup" and "eachdown" are called with the X and Y coordinates of the
 * cursor; and "eachchar" is called with the X, Y, and character value that was
 * typed.  The "whileup", "eachdown", and "eachchar" routines return nonzero to
 * abort tracking.
 * If "waitforpush" is nonzero then the routine will wait for a button to
 * actually be pushed before tracking (otherwise it will begin tracking
 * immediately).  The value of "purpose" determines what the cursor will look
 * like during dragging: 0 for normal (the standard cursor), 1 for drawing (a pen),
 * 2 for dragging (a hand), 3 for popup menu selection (a horizontal arrow), 4 for
 * hierarchical popup menu selection (arrow, stays at end).
 */
void trackcursor(INTSML waitforpush, INTSML (*whileup)(INTBIG, INTBIG),
	void (*whendown)(void), INTSML (*eachdown)(INTBIG, INTBIG),
	INTSML (*eachchar)(INTBIG, INTBIG, INTSML), void (*done)(void), INTSML purpose)
{
	REGISTER INTSML keepon, oldcursor;
	INTBIG action;

	/* change the cursor to an appropriate icon */
	oldcursor = us_normalcursor;
	switch (purpose)
	{
		case TRACKDRAWING:
			setnormalcursor(PENCURSOR);
			break;
		case TRACKDRAGGING:
			setnormalcursor(HANDCURSOR);
			break;
		case TRACKSELECTING:
		case TRACKHSELECTING:
			setnormalcursor(MENUCURSOR);
			break;
	}

	/* now wait for a button to go down, if requested */
	keepon = 0;
	if (waitforpush != 0)
	{
		while (keepon == 0)
		{
			gra_nextevent();
			if (gra_inputstate == NOEVENT) continue;
			action = gra_inputstate;
			gra_inputstate = NOEVENT;

			/* if button just went down, stop this loop */
			if ((action&ISBUTTON) != 0 && (action&BUTTONUP) == 0) break;
			if ((action&MOTION) != 0)
			{
				keepon = (*whileup)(gra_cursorx, gra_cursory);
			} else if ((action&CHARREAD) != 0)
			{
				keepon = (*eachchar)(gra_cursorx, gra_cursory, (INTSML)(action&CHARREAD));
			}
			if (el_pleasestop != 0) keepon = 1;
		}
	}

	/* button is now down, real tracking begins */
	if (keepon == 0)
	{
		(*whendown)();
		keepon = (*eachdown)(gra_cursorx, gra_cursory);
	}

	/* now track while the button is down */
	while (keepon == 0)
	{
		gra_nextevent();

		/* for each motion, report the coordinates */
		if (gra_inputstate == NOEVENT) continue;
		action = gra_inputstate;
		gra_inputstate = NOEVENT;
		if ((action&ISBUTTON) != 0 && (action&BUTTONUP) != 0) break;

		if ((action&MOTION) != 0)
		{
			keepon = (*eachdown)(gra_cursorx, gra_cursory);
		} else if ((action&CHARREAD) != 0)
		{
			keepon = (*eachchar)(gra_cursorx, gra_cursory, (INTSML)(action&CHARREAD));
		}
		if (el_pleasestop != 0) keepon = 1;
	}

	/* inform the user that all is done */
	(*done)();

	/* restore the state of the world */
	setnormalcursor(oldcursor);
}

/*
 * routine to read the current co-ordinates of the tablet and return them
 * in "*x" and "*y".
 */
void readtablet(INTSML *x, INTSML *y)
{
	*x = (INTSML)gra_cursorx;
	*y = (INTSML)gra_cursory;
}

/*
 * routine to turn off the cursor tracking if it is on
 */
void stoptablet(void)
{
	if (us_cursorstate != IBEAMCURSOR)
		setdefaultcursortype(us_normalcursor);
}

/******************** KEYBOARD CONTROL ********************/

/*
 * routine to get the next character from the keyboard
 */
INTSML getnxtchar(void)
{
	REGISTER INTSML i;

	if (gra_inputstate != NOEVENT && (gra_inputstate&CHARREAD) != 0)
	{
		i = gra_inputstate & CHARREAD;
		if ((gra_inputstate&COMMANDDOWN) != 0) i |= 0200;
		gra_inputstate = NOEVENT;
		return(i);
	}
	if (us_cursorstate != IBEAMCURSOR)
		setdefaultcursortype(WANTTTYCURSOR);
	for(;;)
	{
		gra_nextevent();
		if (gra_inputstate != NOEVENT && (gra_inputstate&CHARREAD) != 0)
			break;
	}
	i = gra_inputstate & CHARREAD;
	if ((gra_inputstate&COMMANDDOWN) != 0) i |= 0200;
	gra_inputstate = NOEVENT;

	if (us_cursorstate != IBEAMCURSOR)
		setdefaultcursortype(us_normalcursor);
	return(i);
}

void checkforinterrupt(void)
{
	MSG msg;

	while ( ::PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
	{
		if (!theApp.PumpMessage())
		{
			::PostQuitMessage(0);
			break;
		}
	}
	setdefaultcursortype(WAITCURSOR);
}

/*
 * routine to tell whether data is waiting at the terminal.  Returns nonzero
 * if data is ready.
 */
INTSML ttydataready(void)
{
	/* see if something is already pending */
	if (gra_inputstate != NOEVENT)
	{
		if ((gra_inputstate&CHARREAD) != 0) return(1);
	}

	/* wait for something and analyze it */
	gra_nextevent();
	if (gra_inputstate != NOEVENT && (gra_inputstate&CHARREAD) != 0) return(1);
	return(0);
}

/****************************** FILES ******************************/

/*
 * Routine to prompt for multiple files of type "filetype", giving the
 * message "msg".  Returns a string that contains all of the file names,
 * separated by the NONFILECH (a character that cannot be in a file name).
 */
#define MAXMULTIFILECHARS 4000
char *multifileselectin(char *msg, INTSML filetype)
{
	char fs[MAXMULTIFILECHARS], prompt[256], *extension, *filter, *shortname,
		*longname, *pt;
	CFileDialog *fileDlg;
	REGISTER INTSML which;
	REGISTER INTBIG ret;

	/* build filter string */
	describefiletype(filetype, &extension, &filter, &shortname, &longname);
	strcpy(fs, longname);
	strcat(fs, " (");
	strcat(fs, filter);
	strcat(fs, ")|");
	strcat(fs, filter);
	strcat(fs, "|All Files (*.*)|*.*||");

	fileDlg = new CFileDialog(TRUE, NULL, NULL, NULL, fs, NULL);
	if (fileDlg == NULL) return(0);
	fs[0] = 0;
	fileDlg->m_ofn.lpstrFile = fs;
	fileDlg->m_ofn.nMaxFile = MAXMULTIFILECHARS;
	fileDlg->m_ofn.Flags |= OFN_HIDEREADONLY | OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT |
		OFN_EXPLORER;
	sprintf(prompt, "%s Selection", msg);
	fileDlg->m_ofn.lpstrTitle = prompt;
	pt = 0;
	ret = fileDlg->DoModal();
	if (ret == IDOK)
	{
		pt = fs;
		(void)initinfstr();
		which = 0;
		while (*pt != 0)
		{
			if (which == 0)
			{
				strcpy(gra_localstring, pt);
			} else
			{
				if (which > 1) (void)addtoinfstr(NONFILECH);
				(void)addstringtoinfstr(gra_localstring);
				(void)addtoinfstr(DIRSEP);
				(void)addstringtoinfstr(pt);
			}
			which++;
			while (*pt != 0) pt++;
			pt++;
		}
		if (which == 1) (void)addstringtoinfstr(gra_localstring);
		pt = returninfstr();
	}
	delete fileDlg;
	return(pt);
}

/*
 * Routine to display a standard file prompt dialog and return the selected file.
 * The prompt message is in "msg" and the kind of file is in "filetype".  The default
 * output file name is in "defofile" (only used if "filetype" is negative).
 */
char *fileselect(char *msg, INTSML filetype, char *defofile)
{
	REGISTER INTSML i;
	char ofile[256], fs[256], prompt[256], *extension, *filter, *shortname,
		*longname;
	CFileDialog *fileDlg;

	/* build filter string */
	describefiletype(filetype, &extension, &filter, &shortname, &longname);
	strcpy(fs, longname);
	strcat(fs, " (");
	strcat(fs, filter);
	strcat(fs, ")|");
	strcat(fs, filter);
	strcat(fs, "|All Files (*.*)|*.*||");

	if (us_logplay != NULL)
	{
		if (gra_loggetnextaction(gra_localstring) != 0) return(0);
		if (gra_action.kind != FILEREPLY) gra_localstring[0] = 0;
	} else
	{
		gra_localstring[0] = 0;
		if ((filetype & FILETYPEWRITE) == 0)
		{
			/* open file dialog */
			fileDlg = new CFileDialog(TRUE, NULL, NULL, NULL, fs, NULL);
			if (fileDlg == NULL) gra_localstring[0] = 0; else
			{
				fileDlg->m_ofn.Flags |= OFN_HIDEREADONLY | OFN_FILEMUSTEXIST;
				sprintf(prompt, "%s Selection", msg);
				fileDlg->m_ofn.lpstrTitle = prompt;
				if (fileDlg->DoModal() == IDOK)
					strcpy(gra_localstring, fileDlg->GetPathName());
				delete fileDlg;
			}
		} else
		{
			/* save file dialog */
			for(i = strlen(defofile)-1; i > 0; i--)
				if (defofile[i] == ':' || defofile[i] == '/' || defofile[i] == '\\') break;
			if (i > 0) i++;
			(void)strcpy(ofile, &defofile[i]);
			fileDlg = new CFileDialog(FALSE, extension, ofile, NULL, fs, NULL);
			if (fileDlg != NULL)
			{
				fileDlg->m_ofn.Flags |= OFN_OVERWRITEPROMPT;
				sprintf(prompt, "%s Creation", msg);
				fileDlg->m_ofn.lpstrTitle = prompt;
				if (fileDlg->DoModal() == IDOK)
					strcpy(gra_localstring, fileDlg->GetPathName());
				delete fileDlg;
			}
		}
	}

	/* log this result */
	gra_logwriteaction(FILEREPLY, 0, 0, gra_localstring);

	if (gra_localstring[0] == 0) return ((char *)NULL); else
		return(gra_localstring);
}

/*
 * Helper routine to initialize the list of files in a directory.
 */
void gra_initfilelist(void)
{
	if (gra_fileliststringarray == 0)
	{
		gra_fileliststringarray = newstringarray(db_cluster);
		if (gra_fileliststringarray == 0) return;
	}
	clearstrings(gra_fileliststringarray);
}

/*
 * Helper routine to add "file" to the list of files in a directory.
 * Returns nonzero on error.
 */
INTSML gra_addfiletolist(char *file)
{
	addtostringarray(gra_fileliststringarray, file);
	return(0);
}

/*
 * Routine to search for all of the files/directories in directory "directory" and
 * return them in the array of strings "filelist".  Returns the number of files found.
 */
INTSML filesindirectory(char *directory, char ***filelist)
{
	HANDLE handle;
	WIN32_FIND_DATA data;
	BOOL found;
	char *dir;
	INTBIG len;

	/* search for all files in directory */
	(void)initinfstr();
	(void)addstringtoinfstr(directory);
	(void)addstringtoinfstr("*.*");
	dir = returninfstr();

	handle = FindFirstFile(dir, &data);
	if (handle == INVALID_HANDLE_VALUE) return(0);

	gra_initfilelist();
	for (found = 1; found; found = FindNextFile(handle, &data))
	{
		if (gra_addfiletolist(data.cFileName) != 0) return(0);
	}
	FindClose(handle);

	*filelist = getstringarray(gra_fileliststringarray, &len);
	return((INTSML)len);
}

/* routine to convert a path name with "~" to a real path */
char *truepath(char *line)
{
	/* only have tilde parsing on UNIX */
	return(line);
}

/*
 * routine to find the actual file name in a path/file specification.  The
 * input path name is in "file" and a pointer to the file name is returned.
 */
char *skippath(char *file)
{
	REGISTER char *pt;

	pt = &file[strlen(file)-1];
	while (pt != file && pt[-1] != DIRSEP && pt[-1] != '/' && pt[-1] != ':') pt--;
	return(pt);
}

/*
 * Routine to turn the file name "file" into a unique one by changing the
 * "XXX" part to a number that makes the file unique.
 */
void emaketemp(char *file)
{
	mktemp(file);
}

/*
 * routine to rename file "file" to "newfile"
 * returns nonzero on error
 */
INTBIG erename(char *file, char *newfile)
{
	return(rename(file, newfile));
}

/*
 * routine to delete file "file"
 */
INTBIG eunlink(char *file)
{
	return(_unlink(file));
}

/*
 * Routine to return information about the file or directory "name":
 *  0: does not exist
 *  1: is a file
 *  2: is a directory
 */
INTSML fileexistence(char *name)
{
	struct stat buf;

	if (stat(name, &buf) < 0) return(0);
	if ((buf.st_mode & S_IFMT) == S_IFDIR) return(2);
	return(1);
}

/*
 * Routine to create a directory.
 * Returns nonzero on error.
 */
INTSML createdirectory(char *dirname)
{
	if (mkdir(dirname) == 0) return(0);
	return(1);
}

/*
 * Routine to return the current directory name
 */
char *currentdirectory(void)
{
	char line[MAXPATHLEN];

	(void)initinfstr();
	(void)addstringtoinfstr(getcwd(line, MAXPATHLEN+1));
	(void)addtoinfstr(DIRSEP);
	return(returninfstr());
}

/*
 * Routine to return the home directory (returns 0 if it doesn't exist)
 */
char *hashomedir(void)
{
	return(0);
}

/*
 * Routine to return the path to the "options" library.
 */
char *optionsfilepath(void)
{
	char username[256];
	UINTBIG size;

	(void)initinfstr();
	(void)addstringtoinfstr(el_libdir);
	(void)addstringtoinfstr("electricoptions_");
	size = 256;
	GetUserName(username, &size);
	(void)addstringtoinfstr(username);
	(void)addstringtoinfstr(".elib");
	return(returninfstr());
}

UINTBIG filedate(char *filename)
{
	struct stat buf;

	stat(filename, &buf);
	buf.st_mtime -= machinetimeoffset();
	return(buf.st_mtime);
}

/*
 * Routine to lock a resource called "lockfilename" by creating such a file
 * if it doesn't exist.  Returns nonzero if successful, zero if unable to
 * lock the file.
 */
INTSML lockfile(char *lockfilename)
{
	int fd;

	fd = creat(lockfilename, 0);
	if (fd == -1) return(0);
	if (close(fd) == -1) return(0);
	return(1);
}

/*
 * Routine to unlock a resource called "lockfilename" by deleting such a file.
 */
void unlockfile(char *lockfilename)
{
	INTBIG attrs;
	attrs = GetFileAttributes(lockfilename);
	if ((attrs & FILE_ATTRIBUTE_READONLY) != 0)
	{
		if (SetFileAttributes(lockfilename, attrs & ~FILE_ATTRIBUTE_READONLY) == 0)
		{
			ttyputerr("Error removing readonly bit on %s (error %d)",
				lockfilename, GetLastError());
		}
	}

	if (DeleteFile(lockfilename) == 0)
		ttyputerr("Error unlocking %s (error %d)", lockfilename, GetLastError());
}

/*
 * Routine to show file "filename" in a browser window.
 * Returns nonzero if the operation cannot be done.
 */
INTSML browsefile(char *filename)
{
	CMainFrame *wnd;

	wnd = (CMainFrame *)AfxGetMainWnd();
	ShellExecute(wnd->m_hWnd, "open", filename, "",
		"C:\\Temp\\", SW_SHOWNORMAL);
	return(0);
}

/****************************** CHANNELS ******************************/

/*
 * routine to create a pipe connection between the channels in "channels"
 */
INTBIG epipe(int channels[2])
{
	return(0);
}

/*
 * Routine to set channel "channel" into an appropriate mode for single-character
 * interaction (i.e. break mode).
 */
void setinteractivemode(int channel)
{
}

/*
 * Routine to replace channel "channel" with a pointer to file "file".
 * Returns a pointer to the original channel.
 */
INTBIG channelreplacewithfile(int channel, char *file)
{
	return(0);
}

/*
 * Routine to replace channel "channel" with new channel "newchannel".
 * Returns a pointer to the original channel.
 */
INTBIG channelreplacewithchannel(int channel, int newchannel)
{
	return(0);
}

/*
 * Routine to restore channel "channel" to the pointer that was returned
 * by "channelreplacewithfile" or "channelreplacewithchannel" (and is in "saved").
 */
void channelrestore(int channel, INTBIG saved)
{
}

/*
 * Routine to read "count" bytes from channel "channel" into "addr".
 * Returns the number of bytes read.
 */
INTBIG eread(int channel, char *addr, INTBIG count)
{
	return(0);
}

/*
 * Routine to write "count" bytes to channel "channel" from "addr".
 * Returns the number of bytes written.
 */
INTBIG ewrite(int channel, char *addr, INTBIG count)
{
	return(0);
}

/*
 * routine to close a channel in "channel"
 */
INTBIG eclose(int channel)
{
	return(0);
}

/*************************** TIME ROUTINES ***************************/

UINTBIG machinetimeoffset(void)
{
	return(0);
}

/* returns the current time in 60ths of a second */
UINTBIG ticktime(void)
{
	UINTBIG msTime;

	/* convert milliseconds to ticks */
	msTime = GetTickCount();
	return(msTime*6/100);
}

/* returns the double-click interval in 60ths of a second */
INTBIG doubleclicktime(void)
{
	INTBIG dct;

	dct = GetDoubleClickTime();
	dct = dct * 60 / 1000;
	return(dct);
}

/*
 * Routine to wait "ticks" sixtieths of a second and then return.
 */
void gotosleep(INTBIG ticks)
{
	Sleep(ticks * 100 / 6);
}

/*
 * Routine to start counting time.
 */
void starttimer(void)
{
	struct timeb timeptr;

	ftime(&timeptr);
	gra_timebasesec = timeptr.time;
	gra_timebasems = timeptr.millitm;
}

/*
 * Routine to stop counting time and return the number of elapsed seconds
 * since the last call to "starttimer()".
 */
float endtimer(void)
{
	float seconds;
	INTBIG milliseconds;
	struct timeb timeptr;

	ftime(&timeptr);
	milliseconds = (timeptr.time - gra_timebasesec) * 1000 + (timeptr.millitm-gra_timebasems);
	seconds = ((float)milliseconds) / 1000.0f;
	return(seconds);
}

/*************************** EVENT ROUTINES ***************************/

#define INITIALTIMERDELAY 8

INTBIG gra_lasteventstate = 0;
INTBIG gra_lasteventx;
INTBIG gra_lasteventy;
INTBIG gra_lasteventtime;
INTBIG gra_cureventtime = 0;

void gra_timerticked(void)
{
	gra_cureventtime++;
	if (gra_cureventtime > gra_lasteventtime+INITIALTIMERDELAY)
	{
		/* see if the last event was a mouse button going down */
		if ((gra_lasteventstate&(ISBUTTON|BUTTONUP)) == ISBUTTON)
		{
			/* turn it into a null motion */
			gra_addeventtoqueue((gra_lasteventstate & ~ISBUTTON) | MOTION,
				gra_lasteventx, gra_lasteventy);
			gra_lasteventtime -= INITIALTIMERDELAY;
			return;
		}

		/* see if the last event was a motion with button down */
		if ((gra_lasteventstate&(MOTION|BUTTONUP)) == MOTION)
		{
			/* repeat the last event */
			gra_addeventtoqueue(gra_lasteventstate, gra_lasteventx, gra_lasteventy);
			gra_lasteventtime -= INITIALTIMERDELAY;
			return;
		}
	}
}

void gra_addeventtoqueue(INTBIG state, INTBIG x, INTBIG y)
{
	MYEVENTQUEUE *next, *prev;

	if (us_logplay != NULL) return;

	next = gra_eventqueuetail + 1;
	if (next >= &gra_eventqueue[EVENTQUEUESIZE])
		next = gra_eventqueue;
	if (next == gra_eventqueuehead)
	{
		/* queue is full: see if last event was repeated */
		if (gra_eventqueuetail == gra_eventqueue)
			prev = &gra_eventqueue[EVENTQUEUESIZE-1]; else
				prev = gra_eventqueuetail - 1;
		if (prev->inputstate == state)
		{
			prev->cursorx = x;
			prev->cursory = y;
			return;
		}
		MessageBeep(MB_ICONASTERISK);
		return;
	}

	gra_lasteventstate = gra_eventqueuetail->inputstate = state;
	gra_lasteventx = gra_eventqueuetail->cursorx = x;
	gra_lasteventy = gra_eventqueuetail->cursory = y;
	gra_lasteventtime = gra_cureventtime;
	gra_eventqueuetail = next;
}

/*
 * Routine to get the next Electric input action and set the global "gra_inputstate"
 * accordingly.
 */
void gra_nextevent(void)
{
	MSG msg;
	REGISTER INTBIG windowindex, i, j, trueItem, saveslice;
	REGISTER INTSML x, y;
	POPUPMENU *pm;
	REGISTER POPUPMENUITEM *mi;
	extern INTBIG el_inslice;

	flushscreen();

	/* process events */
	while ( ::PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
	{
		if (!theApp.PumpMessage())
		{
			::PostQuitMessage(0);
			break;
		}
	}

	if (us_logplay != NULL)
	{
		/* playing back log file: get event from disk */
		gra_logreadaction();
	} else
	{
		/* normal interaction: get next event */
		if (gra_eventqueuehead != gra_eventqueuetail)
		{
			gra_inputstate = gra_eventqueuehead->inputstate;
			gra_cursorx = gra_eventqueuehead->cursorx;
			gra_cursory = gra_eventqueuehead->cursory;
			us_state &= ~GOTXY;
			gra_eventqueuehead++;
			if (gra_eventqueuehead >= &gra_eventqueue[EVENTQUEUESIZE])
				gra_eventqueuehead = gra_eventqueue;
		} else gra_inputstate = NOEVENT;
	}

	/* record valid events */
	if (gra_inputstate != NOEVENT)
	{
		if (gra_inputstate == WINDOWSIZE || gra_inputstate == WINDOWMOVE)
		{
			windowindex = gra_cursorx;
			x = (INTSML)(gra_cursory >> 16);
			y = (INTSML)gra_cursory;
			gra_logwriteaction(gra_inputstate, x, y, (void *)windowindex);
			gra_inputstate = NOEVENT;
		} else
		{
			windowindex = -1;
			if (el_curwindowframe != NOWINDOWFRAME) windowindex = el_curwindowframe->windindex;
			gra_logwriteaction(gra_inputstate, (INTSML)gra_cursorx, (INTSML)gra_cursory,
				(void *)windowindex);
		}

		/* handle menu events */
		if (gra_inputstate == MENUEVENT)
		{
			gra_inputstate = NOEVENT;
			i = gra_cursorx;
			if (i >= 0 && i < gra_pulldownmenucount)
			{
				pm = gra_pulldowns[i];
				for(trueItem=j=0; j < pm->total; j++)
				{
					mi = &pm->list[j];
					if (mi->response->active < 0 && *mi->attribute == 0) continue;
					trueItem++;
					if (trueItem != gra_cursory) continue;
					us_state |= DIDINPUT;
					us_state &= ~GOTXY;
					setdefaultcursortype(us_normalcursor);

					/* special case for "system" commands": don't erase */
					us_forceeditchanges();
					saveslice = el_inslice;
					el_inslice = 1;
					us_execute(mi->response, us_aid->aidstate&ECHOBIND, 1, 1);
					el_inslice = saveslice;
					db_setcurrenttool(us_aid);
					setactivity(mi->attribute);
					break;
				}
			}
		}
	}
}

void gra_buttonaction(int state, UINT nFlags, CPoint point, CWnd *frm)
{
	WINDOWFRAME *wf;
	INTBIG event;
	INTBIG x, y, item;

	for(wf = el_firstwindowframe; wf != NOWINDOWFRAME; wf = wf->nextwindowframe)
		if ((CChildFrame *)wf->wndframe == frm) break;
	if (wf == NOWINDOWFRAME)
	{
		if (gra_curdialogindex < 0 ||
			frm != gra_dialogs[gra_curdialogindex].window) return;
		item = gra_dodialogisinsideuserdrawn(point.x, point.y);
		if (item == 0) return;
		if (state != 1)
		{
			gra_itemclicked(item-1+ID_DIALOGITEM_0);
			return;
		}
	}

	/* set appropriate button */
	if ((nFlags & MK_LBUTTON) != 0) event = ISBUTTON|ISLEFT;
	if ((nFlags & MK_MBUTTON) != 0) event = ISBUTTON|ISMIDDLE;
	if ((nFlags & MK_RBUTTON) != 0) event = ISBUTTON|ISRIGHT;

	/* add in extras */
	if ((nFlags & MK_SHIFT) != 0) event |= SHIFTDOWN;
	if ((nFlags & MK_CONTROL) != 0) event |= CONTROLDOWN;
	if ((GetKeyState(VK_MENU)&0x8000) != 0) event |= ALTDOWN;
	if (state == 2 && (event&(SHIFTDOWN|CONTROLDOWN|ALTDOWN)) == 0)
		event |= DOUBLECLICK;
	if (state == 1) event |= BUTTONUP; else
	{
		if (us_logplay != NULL)
		{
			ttyputerr("Session playback aborted by mouse click");
			xclose(us_logplay);
			us_logplay = NULL;
			return;
		}
	}

	x = point.x;
	if (wf == NOWINDOWFRAME) y = point.y; else
		y = wf->revy - point.y;
	us_state |= DIDINPUT;
	gra_addeventtoqueue(event, x, y);
}

void gra_mouseaction(UINT nFlags, CPoint point, CWnd *frm)
{
	WINDOWFRAME *wf;
	INTBIG event, x, y, setcursor, inmenu, lx, hx, ly, hy;
	REGISTER WINDOWPART *w;
	REGISTER EDITOR *e;
	REGISTER VARIABLE *var;
	char *str;
	static INTSML overrodestatus = 0;
	COMMANDBINDING commandbinding;

	/* find the appropriate window */
	for(wf = el_firstwindowframe; wf != NOWINDOWFRAME; wf = wf->nextwindowframe)
		if ((CChildFrame *)wf->wndframe == frm) break;
	inmenu = 0;
	if (wf != NOWINDOWFRAME)
	{
		/* report the menu if over one */
		if ((nFlags & (MK_LBUTTON|MK_MBUTTON|MK_RBUTTON)) == 0 && wf->floating != 0)
		{
			gra_cursorx = (INTSML)point.x;
			gra_cursory = (INTSML)(wf->revy - point.y);
			us_state &= ~GOTXY;
			if (us_menuxsz > 0 && us_menuysz > 0)
			{
				x = (gra_cursorx-us_menulx) / us_menuxsz;
				y = (gra_cursory-us_menuly) / us_menuysz;
				if (x >= 0 && y >= 0 && x < us_menux && y < us_menuy)
				{
					inmenu = 1;
					var = getvalkey((INTBIG)us_aid, VAID, VSTRING|VISARRAY, us_binding_menu);
					if (var != NOVARIABLE)
					{
						if (us_menupos <= 1) str = ((char **)var->addr)[y * us_menux + x]; else
							str = ((char **)var->addr)[x * us_menuy + y];
						us_parsebinding(str, &commandbinding);
						if (*commandbinding.command != 0)
						{
							if (commandbinding.nodeglyph != NONODEPROTO)
							{
								ttysetstatusfield(NOWINDOWFRAME, us_statusarc, describearcproto(us_curarcproto), 1);
								ttysetstatusfield(NOWINDOWFRAME, us_statusnode, us_describemenunode(&commandbinding), 1);
								overrodestatus = 1;
							}
							if (commandbinding.arcglyph != NOARCPROTO)
							{
								ttysetstatusfield(NOWINDOWFRAME, us_statusarc, describearcproto(commandbinding.arcglyph), 1);
								if (us_curnodeproto == NONODEPROTO) str = ""; else
									str = describenodeproto(us_curnodeproto);
								ttysetstatusfield(NOWINDOWFRAME, us_statusnode, str, 1);
								overrodestatus = 1;
							}
						}
						us_freebindingparse(&commandbinding);
					}
				}
			}
		}

		/* if the window is known, set the cursor appropriately */
		setcursor = 0;
		for(w = el_topwindowpart; w != NOWINDOWPART; w = w->nextwindowpart)
		{
			if (w->frame != wf) continue;
			x = (INTSML)point.x;
			y = (INTSML)(wf->revy - point.y);

			/* see if the cursor is over a window partition separator */
			us_gettruewindowbounds(w, &lx, &hx, &ly, &hy);
			if (x >= lx-1 && x <= lx+1 && y > ly+1 && y < hy-1 && us_hasotherwindowpart(lx-10, y, w) != 0)
			{
				setdefaultcursortype(LRCURSOR);
				setcursor = 1;
				break;
			} else if (x >= hx-1 && x <= hx+1 && y > ly+1 && y < hy-1 && us_hasotherwindowpart(hx+10, y, w) != 0)
			{
				setdefaultcursortype(LRCURSOR);
				setcursor = 1;
				break;
			} else if (y >= ly-1 && y <= ly+1 && x > lx+1 && x < hx-1 && us_hasotherwindowpart(x, ly-10, w) != 0)
			{
				setdefaultcursortype(UDCURSOR);
				setcursor = 1;
				break;
			} else if (y >= hy-1 && y <= hy+1 && x > lx+1 && x < hx-1 && us_hasotherwindowpart(x, hy+10, w) != 0)
			{
				setdefaultcursortype(UDCURSOR);
				setcursor = 1;
				break;
			}
			if (x < w->uselx || x > w->usehx || y < w->usely || y > w->usehy) continue;
			if ((w->state&WINDOWTYPE) == POPTEXTWINDOW ||
				(w->state&WINDOWTYPE) == TEXTWINDOW)
			{
				e = w->editor;
				if ((e->state&EDITORTYPE) == PACEDITOR)
				{
					if (x <= w->usehx - SBARWIDTH && y >= w->usely + SBARWIDTH && y < e->revy)
					{
						setdefaultcursortype(IBEAMCURSOR);
						setcursor = 1;
					}
				}
			}
			break;
		}
		if (setcursor == 0)
			setdefaultcursortype(us_normalcursor);
	}
	if (inmenu == 0 && overrodestatus != 0)
	{
		ttysetstatusfield(NOWINDOWFRAME, us_statusarc, describearcproto(us_curarcproto), 1);
		if (us_curnodeproto == NONODEPROTO) str = ""; else
			str = describenodeproto(us_curnodeproto);
		ttysetstatusfield(NOWINDOWFRAME, us_statusnode, str, 1);
		overrodestatus = 0;
	}

	if (gra_inputstate != NOEVENT) return;

	if (wf == NOWINDOWFRAME)
	{
		if (gra_curdialogindex < 0 ||
			frm != gra_dialogs[gra_curdialogindex].window) return;
		if (gra_dodialogisinsideuserdrawn(point.x, point.y) == 0) return;
	}

	x = (INTSML)point.x;
	if (wf == NOWINDOWFRAME) y = (INTSML)point.y; else
		y = (INTSML)(wf->revy - point.y);
	event = MOTION;

	if ((nFlags & (MK_LBUTTON|MK_MBUTTON|MK_RBUTTON)) == 0)
		event |= BUTTONUP;
	gra_addeventtoqueue(event, x, y);
}

void gra_keyaction(UINT nChar, UINT nFlags, UINT nRepCnt)
{
	POINT pt, p2;
	CWnd *wnd;
	INTBIG event, x, y;

	/* determine corner of window */
	if (el_curwindowframe == NOWINDOWFRAME) wnd = AfxGetMainWnd(); else
		wnd = (CWnd *)el_curwindowframe->wndframe;
	p2.x = p2.y = 0;
	wnd->MapWindowPoints(0, &p2, 1);

	/* determine current cursor coordinates */
	GetCursorPos(&pt);
	x = pt.x - p2.x;
	y = pt.y - p2.y;
	if (el_curwindowframe != NOWINDOWFRAME)
		y = el_curwindowframe->revy - y;

	/* queue the event */
	if (nChar == 015) nChar = 012;
	event = nChar & CHARREAD;
	if ((nChar&0200) != 0) event |= COMMANDDOWN;
	us_state |= DIDINPUT;
	gra_addeventtoqueue(event, x, y);
}

void gra_setdefaultcursor(void)
{
	int curstate;

	curstate = us_cursorstate;
	us_cursorstate++;
	setdefaultcursortype(curstate);
}

void gra_activateframe(CChildFrame *frame, BOOL bActivate)
{
	WINDOWFRAME *wf;
	WINDOWPART *w;

	if (frame == gra_messageswindow)
	{
		if (bActivate) gra_messagescurrent = 1; else
			gra_messagescurrent = 0;
		return;
	}

	if (!bActivate)
	{
		el_curwindowframe = NOWINDOWFRAME;
		return;
	}

	for(wf = el_firstwindowframe; wf != NOWINDOWFRAME; wf = wf->nextwindowframe)
	{
		if ((CChildFrame *)wf->wndframe != frame) continue;
		el_curwindowframe = wf;
		if (wf->floating != 0) continue;
		gra_cureditwindowframe = wf;

		/* see if the change of window frame invalidates the current window */
		if (el_curwindowpart == NOWINDOWPART || el_curwindowpart->frame != wf)
		{
			/* must choose new window */
			for(w = el_topwindowpart; w != NOWINDOWPART; w = w->nextwindowpart)
			{
				if (w->frame == wf)
				{
					(void)setvalkey((INTBIG)us_aid, VAID, us_current_window, (INTBIG)w,
						VWINDOWPART|VDONTSAVE);
					(void)setval((INTBIG)el_curlib, VLIBRARY, "curnodeproto",
						(INTBIG)w->curnodeproto, VNODEPROTO);
					break;
				}
			}
		}
	}
}

int gra_closeframe(CChildFrame *frame)
{
	WINDOWFRAME *wf;
	char *par[2];

	if (frame == gra_messageswindow)
	{
		gra_messagescurrent = 0;
		gra_messageswindow = 0;
		return(1);
	}

	for(wf = el_firstwindowframe; wf != NOWINDOWFRAME; wf = wf->nextwindowframe)
	{
		if (frame == (CChildFrame *)wf->wndframe) break;
	}
	if (wf == NOWINDOWFRAME) return(0);

	((CChildFrame *)wf->wndframe)->SetActiveWindow();
	par[0] = "delete";
	us_window(1, par);
	return(0);
}

int gra_closeworld(void)
{
	if (us_preventloss(NOLIBRARY, "quit", 1)) return(1);  /* keep working */
	bringdown();
	return(0);
}

void gra_repaint(CChildFrame *frame, CPaintDC *dc)
{
	WINDOWFRAME *wf;

	for(wf = el_firstwindowframe; wf != NOWINDOWFRAME; wf = wf->nextwindowframe)
	{
		if (frame == (CChildFrame *)wf->wndframe)
		{
			dc->BitBlt(0, 0, wf->swid, wf->shei, (CDC *)wf->hDCOff, 0, 0, SRCCOPY);
			break;
		}
	}
}

void gra_resize(CChildFrame *frame, int cx, int cy)
{
	WINDOWFRAME *wf;
	RECT r;
	WINDOWPLACEMENT wp;

	if (frame->IsIconic()) return;

	if (frame == gra_messageswindow)
	{
		gra_messageswindow->GetWindowPlacement(&wp);
		r = wp.rcNormalPosition;
		gra_messagesleft = (INTSML)r.left;
		gra_messagesright = (INTSML)r.right;
		gra_messagestop = (INTSML)r.top;
		gra_messagesbottom = (INTSML)r.bottom;
		return;
	}

	for(wf = el_firstwindowframe; wf != NOWINDOWFRAME; wf = wf->nextwindowframe)
	{
		if (frame == (CChildFrame *)wf->wndframe)
		{
			sizewindowframe(wf, cx, cy);
			gra_redrawdisplay(wf);
			gra_addeventtoqueue(WINDOWSIZE, wf->windindex, ((cx & 0xFFFF) << 16) | (cy & 0xFFFF));
			break;
		}
	}
}

void gra_movedwindow(CChildFrame *frame, int x, int y)
{
	RECT fr, cr, r;
	int framewid, framehei;
	WINDOWPLACEMENT wp;
	WINDOWFRAME *wf;

	if (frame == gra_messageswindow)
	{
		gra_messageswindow->GetWindowPlacement(&wp);
		r = wp.rcNormalPosition;
		gra_messagesleft = (INTSML)r.left;
		gra_messagesright = (INTSML)r.right;
		gra_messagestop = (INTSML)r.top;
		gra_messagesbottom = (INTSML)r.bottom;
		return;
	}

	for(wf = el_firstwindowframe; wf != NOWINDOWFRAME; wf = wf->nextwindowframe)
	{
		if (frame == (CChildFrame *)wf->wndframe)
		{
			((CChildFrame *)wf->wndframe)->GetWindowPlacement(&wp);
			fr = wp.rcNormalPosition;
			((CChildFrame *)wf->wndframe)->GetClientRect(&cr);
			framewid = (fr.right - fr.left) - (cr.right - cr.left);
			framehei = (fr.bottom - fr.top) - (cr.bottom - cr.top);
			x -= framewid/2;   y -= (framehei-framewid/2);
			gra_addeventtoqueue(WINDOWMOVE, wf->windindex, ((x & 0xFFFF) << 16) | (y & 0xFFFF));
			break;
		}
	}
}

void gra_resizemain(int cx, int cy)
{
	gra_redrawstatusindicators();
	if (gra_palettewindowframe != NOWINDOWFRAME)
		us_drawmenu(-1, gra_palettewindowframe);
}

/* handle interrupts */
void gra_onint(void)
{
	el_pleasestop = 1;
	ttyputerr("Interrupted...");
}

/*************************** SESSION LOGGING ROUTINES ***************************/

/* Session Playback */
DIALOGITEM gra_sesplaydialogitems[] =
{
 /*  1 */ {0, {100,132,124,212}, BUTTON, "Yes"},
 /*  2 */ {0, {100,8,124,88}, BUTTON, "No"},
 /*  3 */ {0, {8,8,24,232}, MESSAGE, "Electric has found a session log file"},
 /*  4 */ {0, {24,8,40,232}, MESSAGE, "which may be from a recent crash."},
 /*  5 */ {0, {52,8,68,232}, MESSAGE, "Do you wish to replay this session"},
 /*  6 */ {0, {68,8,84,232}, MESSAGE, "and reconstruct the lost work?"}
};
DIALOG gra_sesplaydialog = {{75,75,208,316}, "Replay Log?", 6, gra_sesplaydialogitems};

/*
 * routine to create a session logging file
 */
void logstartrecord(void)
{
	unsigned char count;
	REGISTER INTBIG itemhit;
	REGISTER LIBRARY *lib;
	REGISTER WINDOWFRAME *wf;
	REGISTER WINDOWPART *w;
	RECT fr;
	WINDOWPLACEMENT wp;
	REGISTER VARIABLE *var;

	/* if there is already a log file, it may be from a previous crash */
	if (fileexistence(gra_logfile) == 1)
	{
		DiaInitDialog(&gra_sesplaydialog);
		for(;;)
		{
			itemhit = DiaNextHit();
			if (itemhit == 1 || itemhit == 2) break;
		}
		DiaDoneDialog();
		if (itemhit == 1)
		{
			if (fileexistence(gra_logfilesave) == 1)
				eunlink(gra_logfilesave);
			rename(gra_logfile, gra_logfilesave);
			(void)logplayback(gra_logfilesave);
		}
	}

	us_logrecord = xcreate(gra_logfile, FILETYPELOG, 0, 0);
	if (us_logrecord == 0) return;

	/* log current libraries */
	count = 0;
	for(lib = el_curlib; lib != NOLIBRARY; lib = lib->nextlibrary)
		if ((lib->userbits&HIDDENLIBRARY) == 0) count++;
	gra_logwritebyte((char)count);
	for(lib = el_curlib; lib != NOLIBRARY; lib = lib->nextlibrary)
	{
		if ((lib->userbits&HIDDENLIBRARY) != 0) continue;
		if ((lib->userbits&READFROMDISK) != 0) gra_logwritebyte(1); else
			gra_logwritebyte(0);
		gra_logwritestring(lib->libname);
		gra_logwritestring(lib->libfile);
	}

	/* log current windows */
	gra_logwriteshort(gra_newwindowoffset);
	count = 0;
	for(wf = el_firstwindowframe; wf != NOWINDOWFRAME; wf = wf->nextwindowframe) count++;
	gra_logwritebyte((char)count);
	for(wf = el_firstwindowframe; wf != NOWINDOWFRAME; wf = wf->nextwindowframe)
	{
		gra_logwriteshort((char)wf->windindex);
		gra_logwritebyte((char)wf->floating);
		((CChildFrame *)wf->wndframe)->GetWindowPlacement(&wp);
		fr = wp.rcNormalPosition;
		gra_logwriteshort((INTSML)fr.left);
		gra_logwriteshort((INTSML)fr.top);
		gra_logwriteshort((INTSML)(fr.right-fr.left));
		gra_logwriteshort((INTSML)(fr.bottom-fr.top));
		count = 0;
		for(w = el_topwindowpart; w != NOWINDOWPART; w = w->nextwindowpart)
			if (w->frame == wf) count++;
		gra_logwritebyte((char)count);
		for(w = el_topwindowpart; w != NOWINDOWPART; w = w->nextwindowpart)
		{
			if (w->frame != wf) continue;
			if (w == el_curwindowpart) gra_logwritebyte(1); else
				gra_logwritebyte(0);
			gra_logwriteshort(w->uselx);
			gra_logwriteshort(w->usehx);
			gra_logwriteshort(w->usely);
			gra_logwriteshort(w->usehy);
			gra_logwritelong(w->screenlx);
			gra_logwritelong(w->screenhx);
			gra_logwritelong(w->screenly);
			gra_logwritelong(w->screenhy);
			gra_logwritelong(w->state);
			gra_logwritestring(describenodeproto(w->curnodeproto));
			gra_logwritestring(w->location);
		}
	}
	gra_logwriteshort(gra_windowframeindex);

	/* log current technology (macros store this in %H) */
	var = getval((INTBIG)us_aid, VAID, VSTRING, "USER_local_caph");
	if (var != NOVARIABLE)
	{
		/* technology name found in local variable */
		gra_logwritestring((char *)var->addr);
	} else
	{
		/* just write the current technology name */
		gra_logwritestring(el_curtech->techname);
	}
}

/*
 * routine to begin playback of session logging file "file".  The routine
 * returns nonzero if there is an error.
 */
INTSML logplayback(char *file)
{
	char *filename, tempstring[300];
	unsigned char count, wcount, i, j, floating, cur, fromdisk;
	REGISTER WINDOWFRAME *wf;
	RECTAREA r;
	REGISTER FILE *saveio;
	REGISTER WINDOWPART *w, *nextw;
	REGISTER INTSML wid, hei, uselx, usehx, usely, usehy, sindex;
	REGISTER INTBIG screenlx, screenhx, screenly, screenhy, state;
	REGISTER LIBRARY *lib, *firstlib;
	REGISTER USERCOM *uc;

	us_logplay = xopen(file, FILETYPELOG, "", &filename);
	if (us_logplay == NULL) return(1);
	ttyputmsg("Playing log file...");
	ttyputmsg("   Move mouse continuously to advance playback");
	ttyputmsg("   Click mouse to abort playback");
	gra_playbacklength = filesize(us_logplay);
	gra_playbackposition = 0;

	/* get current libraries */
	count = (unsigned char)gra_logreadbyte();
	firstlib = NOLIBRARY;
	for(i=0; i<count; i++)
	{
		fromdisk = (unsigned char)gra_logreadbyte();
		gra_logreadstring(tempstring);
		gra_logreadstring(gra_localstring);
		lib = getlibrary(tempstring);
		if (lib == NOLIBRARY)
		{
			/* read library file "gra_localstring" */
			lib = newlibrary(tempstring, gra_localstring);
			if (lib == NOLIBRARY) continue;
		}
		if (fromdisk != 0)
		{
			saveio = us_logplay;
			us_logplay = 0;
			(void)askaid(io_aid, "read", (INTBIG)lib, (INTBIG)"binary", 0);
			us_logplay = saveio;
		}
		if (firstlib == NOLIBRARY) firstlib = lib;
	}
	selectlibrary(firstlib);

	/* delete all existing windows */
	for(w = el_topwindowpart; w != NOWINDOWPART; w = nextw)
	{
		nextw = w->nextwindowpart;
		db_retractwindowpart(w);
	}
	el_curwindowpart = NOWINDOWPART;

	/* get current windows */
	gra_newwindowoffset = gra_logreadshort();
	count = (unsigned char)gra_logreadbyte();
	for(i=0; i<count; i++)
	{
		sindex = gra_logreadshort();
		floating = gra_logreadbyte();
		r.left = gra_logreadshort();
		r.top = gra_logreadshort();
		wid = gra_logreadshort();
		hei = gra_logreadshort();
		r.right = r.left + wid;
		r.bottom = r.top + hei;
		if (floating != 0)
		{
			/* get the floating window frame */
			for(wf = el_firstwindowframe; wf != NOWINDOWFRAME; wf = wf->nextwindowframe)
				if (wf->floating != 0) break;
			if (wf == NOWINDOWFRAME) wf = newwindowframe(1, &r);
		} else
		{
			/* create a new window frame */
			wf = newwindowframe(0, &r);
		}
		wf->windindex = sindex;

		wcount = (unsigned char)gra_logreadbyte();
		for(j=0; j<wcount; j++)
		{
			cur = gra_logreadbyte();
			uselx = gra_logreadshort();
			usehx = gra_logreadshort();
			usely = gra_logreadshort();
			usehy = gra_logreadshort();
			screenlx = gra_logreadlong();
			screenhx = gra_logreadlong();
			screenly = gra_logreadlong();
			screenhy = gra_logreadlong();
			state = gra_logreadlong();
			gra_logreadstring(gra_localstring);
			gra_logreadstring(tempstring);
			w = newwindowpart(tempstring, NOWINDOWPART);
			w->buttonhandler = DEFAULTBUTTONHANDLER;
			w->charhandler = DEFAULTCHARHANDLER;
			w->changehandler = DEFAULTCHANGEHANDLER;
			w->termhandler = DEFAULTTERMHANDLER;
			w->redisphandler = DEFAULTREDISPHANDLER;
			w->uselx = uselx;   w->usehx = usehx;
			w->usely = usely;   w->usehy = usehy;
			w->screenlx = screenlx;   w->screenhx = screenhx;
			w->screenly = screenly;   w->screenhy = screenhy;
			computewindowscale(w);
			w->state = state;
			w->curnodeproto = getnodeproto(gra_localstring);
			w->frame = wf;
			if (cur != 0) el_curwindowpart = w;
			us_redisplay(w);
		}
	}
	gra_windowframeindex = gra_logreadshort();

	/* get current technology (presumes a technology switching macro "pmtesetup") */
	gra_logreadstring(gra_localstring);
	(void)initinfstr();
	(void)addstringtoinfstr("pmtesetup \"");
	(void)addstringtoinfstr(gra_localstring);
	(void)addstringtoinfstr("\"");
	uc = us_makecommand(returninfstr());
	if (uc != NOUSERCOM)
	{
		us_execute(uc, 0, 0, 0);
		us_freeusercom(uc);
	}

	return(0);
}

/*
 * routine to terminate session logging
 */
void logfinishrecord(void)
{
	if (us_logrecord != NULL)
	{
		xclose(us_logrecord);
		if (fileexistence(gra_logfilesave) == 1)
			eunlink(gra_logfilesave);
		rename(gra_logfile, gra_logfilesave);
	}
	us_logrecord = NULL;
}

/*
 * Routine to log an event (if logging) of type "inputstate".
 * The event has parameters (cursorx,cursory) and "extradata", depending on "inputstate":
 *   WINDOWSIZE:    window "extradata" is now "cursorx" x "cursory"
 *   WINDOWMOVE:    window "extradata" is now at (cursorx, cursory)
 *   MENUEVENT:     selected menu "cursorx", item "cursory"
 *   FILEREPLY:     file selected by standard-file dialog is in "extradata"
 *   DIAITEMCLICK:  dialog item "cursorx" clicked
 *   DIASCROLLSEL:  dialog scroll item "cursorx" set to entry "cursory"
 *   DIAEDITTEXT:   dialog edit item "cursorx" addec char "cursory" and changed to "extradata"
 *   DIAPOPUPSEL:   dialog popup item "cursorx" set to entry "cursory"
 *   DIASETCONTROL: dialog control item "cursorx" changed to "cursory"
 *   DIAENDDIALOG:  dialog terminated
 *   all others:    cursor in (cursorx,cursory) and window index in "extradata"
 */
void gra_logwriteaction(INTBIG inputstate, INTSML cursorx, INTSML cursory, void *extradata)
{
	REGISTER char *filename;

	if (us_logrecord == NULL) return;

	/* load the event structure */
	gra_action.kind = inputstate;
	gra_action.x = cursorx;
	gra_action.y = cursory;
	gra_action.frameindex = (INTSML)extradata;

	/* ignore redundant cursor motion */
	if (inputstate == MOTION || inputstate == (MOTION|BUTTONUP))
	{
		if (inputstate == gra_lastloggedaction && cursorx == gra_lastloggedx &&
			cursory == gra_lastloggedy && gra_action.frameindex == gra_lastloggedindex)
				return;
	}

	if (xfwrite((char *)&gra_action, 1, sizeof (gra_action), us_logrecord) == 0)
	{
		ttyputerr("Error writing session log file: recording disabled");
		logfinishrecord();
		return;
	}

	/* further annotate special types of change */
	if (inputstate == FILEREPLY || inputstate == DIAEDITTEXT)
	{
		filename = (char *)extradata;
		gra_logwritestring(filename);
	}

	/* flush the log file every so often */
	gra_logrecordcount++;
	if (gra_logrecordcount >= us_logflushfreq)
	{
		gra_logrecordcount = 0;
		xflushbuf(us_logrecord);
	}
}

INTSML gra_loggetnextaction(char *message)
{
	REGISTER INTBIG amtread;

	if (gra_playbackposition + 100 > gra_playbacklength) amtread = 0; else
	{
		amtread = xfread((char *)&gra_action, 1, sizeof (gra_action), us_logplay);
		gra_playbackposition += amtread;
		if (stopping("Playback") != 0) amtread = 0;
	}
	if (amtread == 0)
	{
		/* stop playback */
		ttyputmsg("End of session playback file");
		xclose(us_logplay);
		us_logplay = NULL;
		return(1);
	}
	if (gra_action.kind == FILEREPLY || gra_action.kind == DIAEDITTEXT)
	{
		if (message == 0)
		{
			ttyputerr("Inconsistent log file");
			return(1);
		}
		gra_logreadstring(message);
	}
	return(0);
}

void gra_logreadaction(void)
{
	REGISTER WINDOWFRAME *wf;

	if (gra_loggetnextaction(gra_localstring) != 0) return;

	/* turn it into an event */
	gra_inputstate = NOEVENT;
	if (gra_action.kind == WINDOWSIZE)
	{
		/* window resized */
		for(wf = el_firstwindowframe; wf != NOWINDOWFRAME; wf = wf->nextwindowframe)
			if (wf->windindex == gra_action.frameindex) break;
		if (wf != NOWINDOWFRAME)
			sizewindowframe(wf, (INTSML)gra_action.x, (INTSML)gra_action.y);
	} else if (gra_action.kind == WINDOWMOVE)
	{
		/* window moved */
		for(wf = el_firstwindowframe; wf != NOWINDOWFRAME; wf = wf->nextwindowframe)
			if (wf->windindex == gra_action.frameindex) break;
		if (wf != NOWINDOWFRAME)
			movewindowframe(wf, (INTSML)gra_action.x, (INTSML)gra_action.y);
	} else if (gra_action.kind == DIAITEMCLICK)
	{
		/* dialog item clicked */
		gra_dialoghit = gra_action.x;
	} else if (gra_action.kind == DIASCROLLSEL)
	{
		/* dialog scroll item selected */
		DiaSelectLine(gra_action.x, gra_action.y);
	} else if (gra_action.kind == DIAPOPUPSEL)
	{
		/* dialog popup item selected */
		DiaSetPopupEntry(gra_action.x, gra_action.y);
	} else if (gra_action.kind == DIASETCONTROL)
	{
		/* dialog control item changed */
		DiaSetControl(gra_action.x, gra_action.y);
	} else if (gra_action.kind == DIAEDITTEXT)
	{
		/* dialog edit item got text change */
		gra_dialoghit = gra_action.x;
		gra_dialoghitchar = gra_action.y;
		DiaSetText(gra_dialoghit, gra_localstring);
	} else
	{
		/* standard click/key event */
		for(wf = el_firstwindowframe; wf != NOWINDOWFRAME; wf = wf->nextwindowframe)
			if (wf->windindex == gra_action.frameindex) break;
		if (wf != NOWINDOWFRAME && wf != el_curwindowframe)
			((CChildFrame *)wf->wndframe)->ActivateFrame();
		gra_inputstate = gra_action.kind;
		gra_cursorx = gra_action.x;
		gra_cursory = gra_action.y;
		us_state &= ~GOTXY;
	}
}

/*
 * Routine to write "msg" to the log file.
 */
void gra_logwritestring(char *msg)
{
	INTBIG leng;

	leng = strlen(msg);
	gra_logwritebyte((char)leng);
	(void)xfwrite(msg, 1, leng, us_logrecord);
}

/*
 * Routine to read a string from the log file into "msg".
 */
void gra_logreadstring(char *msg)
{
	char len;
	INTBIG amtread;

	len = gra_logreadbyte();
	amtread = xfread(msg, 1, len, us_logplay);
	gra_playbackposition += amtread;
	msg[len] = 0;
}

/*
 * Routine to write a short integer "data" to the log file.
 */
void gra_logwriteshort(INTSML data)
{
	(void)xfwrite((char *)&data, SIZEOFINTSML, 1, us_logrecord);
}

/*
 * Routine to read a short integer from the log file.
 */
INTSML gra_logreadshort(void)
{
	INTBIG amtread;
	INTSML data;

	amtread = xfread((char *)&data, SIZEOFINTSML, 1, us_logplay);
	gra_playbackposition += amtread;
	return(data);
}

/*
 * Routine to write a long integer "data" to the log file.
 */
void gra_logwritelong(INTBIG data)
{
	(void)xfwrite((char *)&data, SIZEOFINTBIG, 1, us_logrecord);
}

/*
 * Routine to read a long integer from the log file.
 */
INTBIG gra_logreadlong(void)
{
	INTBIG amtread;
	INTBIG data;

	amtread = xfread((char *)&data, SIZEOFINTBIG, 1, us_logplay);
	gra_playbackposition += amtread;
	return(data);
}

/*
 * Routine to write a single byte "data" to the log file.
 */
void gra_logwritebyte(char data)
{
	(void)xfwrite((char *)&data, 1, 1, us_logrecord);
}

/*
 * Routine to read a single byte from the log file.
 */
char gra_logreadbyte(void)
{
	INTBIG amtread;
	char data;

	amtread = xfread(&data, 1, 1, us_logplay);
	gra_playbackposition += amtread;
	return(data);
}

/****************************** MENUS ******************************/

void getacceleratorstrings(char **acceleratorstring, char **acceleratorprefix)
{
	*acceleratorstring = "Ctrl";
	*acceleratorprefix = "Ctrl-";
}

char *getinterruptkey(void)
{
	return("Windows-C");
}

INTSML nativepopupmenu(POPUPMENU *menu, INTSML header, INTSML left, INTSML top)
{
	CMainFrame *wnd;
	int j, pindex;
	POPUPMENUITEM *mi;
	HMENU popmenu;
	POINT p, p2;
	UINT flags;

	wnd = (CMainFrame *)AfxGetMainWnd();
	flags = TPM_NONOTIFY|TPM_RETURNCMD|TPM_LEFTALIGN|TPM_LEFTBUTTON;
	if (left < 0 || top < 0)
	{
		p2.x = p2.y = 0;
		wnd->MapWindowPoints(0, &p2, 1);
		GetCursorPos(&p);
		left = (INTSML)(p.x - p2.x);
		top = (INTSML)(p.y - p2.y);
		flags = TPM_NONOTIFY|TPM_RETURNCMD|TPM_CENTERALIGN|TPM_VCENTERALIGN|TPM_LEFTBUTTON;
	}
	popmenu = CreatePopupMenu();
	if (popmenu == 0) return(-1);
	if (header != 0)
	{
		if (AppendMenu(popmenu, MF_STRING, 0, menu->header) == 0) return(-1);
		if (AppendMenu(popmenu, MF_SEPARATOR, 0, 0) == 0) return(-1);
	}
	for(j=0; j < menu->total; j++)
	{
		mi = &menu->list[j];
		mi->changed = 0;
		if (AppendMenu(popmenu, MF_STRING, j+1, mi->attribute) == 0) return(-1);
	}
	pindex = TrackPopupMenu(popmenu, flags, left, top, 0, wnd->m_hWnd, 0) - 1;
 	DestroyMenu(popmenu);
	return(pindex);
}

/*
 * routine to handle the menu
 */
void gra_nativemenudoone(INTSML item, INTSML menuindex)
{
	gra_addeventtoqueue(MENUEVENT, menuindex, item);
}

/*
 * Routine to establish the "count" pulldown menu names in "par" as the pulldown menu bar
 */
INTSML nativemenuload(INTSML count, char *par[])
{
	REGISTER INTSML i, menuindex;
	REGISTER POPUPMENU *pm;
	POPUPMENU *pulls[25];

	if (gra_hMenu == 0)
	{
		gra_hMenu = new CMenu();
		gra_hMenu->CreateMenu();
	}

	for(i=0; i<count; i++)
	{
		pm = us_getpopupmenu(par[i]);
		if (pm == NOPOPUPMENU) continue;
		pulls[i] = pm;

		menuindex = gra_pulldownindex(pm);
		if (menuindex < 0) continue;
		if (gra_hMenu->InsertMenu(menuindex, MF_ENABLED | MF_POPUP | MF_BYPOSITION,
			(DWORD)gra_pulldownmenus[menuindex]->m_hMenu, pm->header) == 0)
				return(1);
	}
	CWnd* parent = AfxGetMainWnd();
	CMenu* lastMenu = parent->GetMenu();
	if (parent->SetMenu(gra_hMenu) == 0) return(1);
	gra_hMenu->Detach();
	parent->DrawMenuBar();
	return(0);
}

/*
 * Routine to create a pulldown menu from popup menu "pm".  Returns an index to
 * the table of pulldown menus (-1 on error).
 */
INTSML gra_pulldownindex(POPUPMENU *pm)
{
	REGISTER INTSML i, pindex;
	CMenu **newpulldownmenus;
	POPUPMENU **newpulldowns;

	for(i=0; i<gra_pulldownmenucount; i++)
		if (gra_pulldowns[i] == pm) return(i);

	/* allocate new space with one more */
	newpulldownmenus = (CMenu **)emalloc((gra_pulldownmenucount+1) *
		(sizeof (CMenu *)), us_aid->cluster);
	if (newpulldownmenus == 0) return(-1);
	newpulldowns = (POPUPMENU **)emalloc((gra_pulldownmenucount+1) *
		(sizeof (POPUPMENU *)), us_aid->cluster);
	if (newpulldowns == 0) return(-1);

	/* copy former arrays then delete them */
	for(i=0; i<gra_pulldownmenucount; i++)
	{
		newpulldownmenus[i] = gra_pulldownmenus[i];
		newpulldowns[i] = gra_pulldowns[i];
	}
	if (gra_pulldownmenucount != 0)
	{
		efree((char *)gra_pulldownmenus);
		efree((char *)gra_pulldowns);
	}

	gra_pulldownmenus = newpulldownmenus;
	gra_pulldowns = newpulldowns;

	pindex = gra_pulldownmenucount++;
	gra_pulldownmenus[pindex] = gra_makepdmenu(pm, pindex);
	if (gra_pulldownmenus[pindex] == 0)
		return(-1);
	gra_pulldowns[pindex] = pm;
	return(pindex);
}

CMenu *gra_makepdmenu(POPUPMENU *pm, INTSML pindex)
{
	CMenu *hPrevMenu, *hMenu;
	REGISTER USERCOM *uc;
	REGISTER POPUPMENUITEM *mi;
	REGISTER INTSML j, submenuindex, len, idIndex;
	REGISTER char *pt;
	char keychr, myline[100];

	hMenu = new CMenu();
	if (hMenu->CreatePopupMenu() == 0) return(0);

	idIndex = 0;
	for(j=0; j < pm->total; j++)
	{
		mi = &pm->list[j];
		uc = mi->response;
		if (uc->active < 0)
		{
			if (*mi->attribute == 0)
			{
				if (hMenu->AppendMenu(MF_SEPARATOR) == 0) return(0);
			} else
			{
				if (hMenu->AppendMenu(MF_STRING | MF_DISABLED, gra_menures[idIndex++], mi->attribute) == 0) return(0);
			}
			continue;
		}

		if (uc->menu != NOPOPUPMENU)
		{
			hPrevMenu = hMenu;
			idIndex++;
			submenuindex = gra_pulldownindex(uc->menu);
			if (hPrevMenu->InsertMenu(-1, MF_POPUP | MF_BYPOSITION,
				(DWORD)gra_pulldownmenus[submenuindex]->m_hMenu, mi->attribute) == 0)
					return(0);
			continue;
		}
		strcpy(myline, mi->attribute);
		len = strlen(myline);
		if (myline[len-1] == '<') myline[len-1] = 0;
		for(pt = myline; *pt != 0; pt++) if (*pt == '/') break;
		if (*pt != 0)
		{
			*pt++ = 0;
			keychr = *pt & 0xFF;
			len = strlen(myline);
			if (keychr >= FUNCTIONF1 && keychr <= FUNCTIONF12)
				sprintf(&myline[len], "\tF%d", keychr-FUNCTIONF1+1); else
					sprintf(&myline[len], "\tCtrl-%c", keychr);
			if (hMenu->AppendMenu(MF_STRING, gra_menures[idIndex++], myline) == 0) return(0);
		} else if (myline[0] == '>')
		{
			if (hMenu->AppendMenu(MF_CHECKED | MF_STRING, gra_menures[idIndex++], &myline[1]) == 0) return(0);
		} else
		{
			if (hMenu->AppendMenu(MF_STRING, gra_menures[idIndex++], myline) == 0) return(0);
		}
	}
	return(hMenu);
}

/* routine to redraw entry "pindex" of popupmenu "pm" because it changed */
void nativemenurename(POPUPMENU *pm, INTSML pindex)
{
	INTSML i, j, len, key, idIndex;
	char line[100], *pt;
	USERCOM *uc;
	REGISTER POPUPMENUITEM *mi;

	for(i=0; i<gra_pulldownmenucount; i++)
		if (gra_pulldowns[i] == pm)
	{
		idIndex = 0;
		for(j=0; j < pm->total; j++)
		{
			mi = &pm->list[j];
			uc = mi->response;
			if (j == pindex) break;
			if (uc->active < 0 && *mi->attribute == 0) continue;
			idIndex++;
		}

		if (uc->active < 0)
		{
			if (*pm->list[i].attribute == 0)
			{
				gra_pulldownmenus[i]->ModifyMenu(pindex, MF_BYPOSITION|MF_SEPARATOR);
			} else
			{
				gra_pulldownmenus[i]->EnableMenuItem(pindex, MF_BYPOSITION|MF_GRAYED);
			}
		} else
		{
			gra_pulldownmenus[i]->EnableMenuItem(pindex, MF_BYPOSITION|MF_ENABLED);
		}

		/* copy and examine the menu string */
		(void)strcpy(line, pm->list[pindex].attribute);
		len = strlen(line);
		if (line[len-1] == '<') line[len-1] = 0;
		for(pt = line; *pt != 0; pt++) if (*pt == '/') break;

		/* handle single-key equivalents */
		if (*pt == '/')
		{
			*pt++ = 0;
			key = *pt & 0xFF;
			len = strlen(line);
			if (key >= FUNCTIONF1 && key <= FUNCTIONF12)
				sprintf(&line[len], "\tF%d", key-FUNCTIONF1+1); else
					sprintf(&line[len], "\tCtrl-%c", key);
			gra_pulldownmenus[i]->ModifyMenu(pindex, MF_BYPOSITION, gra_menures[idIndex], line);
		} else
		{
			/* see if entry is checked */
			if (line[0] != '>')
			{
				/* not checked: reload the entry */
				gra_pulldownmenus[i]->ModifyMenu(pindex, MF_BYPOSITION, gra_menures[idIndex], line);
			} else
			{
				/* remove "check" marks and check the entry properly */
				gra_pulldownmenus[i]->ModifyMenu(pindex, MF_CHECKED | MF_BYPOSITION,
					gra_menures[idIndex], &line[1]);
			}
		}
		break;
	}
}

/****************************** DIALOGS ******************************/

/*
 * Routine to initialize a dialog described by "dialog".
 * Returns nonzero if dialog cannot be initialized.
 */
INTSML DiaInitDialog(DIALOG *dialog)
{
	DLGTEMPLATE *dtheader;
	WORD *dtmenu, *dtclass, *dttitle;
	int headerlength, menulength, classlength, titlelength, itemslength,
		totallen;
	DLGITEMTEMPLATE **dtitems;
	RECT r;
	char *descblock, *title, *pt;
	int *itemlengths, i, j, len, itemtype, style, itemclass, iconxpos;
	WORD output[100];
	CProgressCtrl *prog;
	CListBox *list;
	CStatic *stat;
	HICON map;
	CWnd *wnd;

	/* get the current dialog structure */
	gra_curdialogindex++;
	gra_curdialog = &gra_dialogs[gra_curdialogindex];

#if 0
	long dbu = GetDialogBaseUnits();	/* returns 16 (high) and 8 (low) */
	gra_dialogdbnx = 4;   gra_dialogdbux = dbu & 0xFFFF;
	gra_dialogdbny = 8;   gra_dialogdbuy = (dbu >> 16) & 0xFFFF;
#else
	gra_dialogdbnx = 19;    gra_dialogdbux = 28;
	gra_dialogdbny = 131;   gra_dialogdbuy = 210;
#endif
	gra_curdialog->window = new CElectricDialog();
	gra_curdialog->itemdesc = dialog;
	gra_curdialog->redrawroutine = 0;
	gra_dialogeditline = -1;

	/* determine item lengths */
	itemlengths = (int *)emalloc(dialog->items * (sizeof (int)), us_aid->cluster);
	if (itemlengths == 0) return(1);
	dtitems = (DLGITEMTEMPLATE **)emalloc(dialog->items * (sizeof (DLGITEMTEMPLATE *)), us_aid->cluster);
	if (dtitems == 0) return(1);
	itemslength = 0;
	for(i=0; i<dialog->items; i++)
	{
		itemlengths[i] = (sizeof DLGITEMTEMPLATE) + 3 * (sizeof (WORD));
		itemtype = dialog->list[i].type;
		switch (itemtype&ITEMTYPE)
		{
			case SCROLL:
			case ICON:
			case POPUP:
				itemlengths[i] += 1 * (sizeof (WORD));
				break;
			default:
				itemlengths[i] += (strlen(dialog->list[i].msg) + 1) * (sizeof (WORD));
				break;
		}
		itemlengths[i] = (itemlengths[i] + 3) & ~3;
		itemslength += itemlengths[i];
	}

	/* allocate space for entire dialog template */
	headerlength = sizeof (DLGTEMPLATE);
	menulength = sizeof (WORD);
	classlength = sizeof (WORD);
	if (dialog->movable != 0) title = dialog->movable; else
		title = "";
	titlelength = (strlen(title) + 1) * (sizeof (WORD));
	i = headerlength + menulength + classlength + titlelength;
	totallen = (i + 3) & ~3;
	descblock = (char *)emalloc(totallen + itemslength, us_aid->cluster);
	if (descblock == 0) return(1);
	pt = descblock;
	dtheader = (DLGTEMPLATE *)pt;  pt += headerlength;
	dtmenu = (WORD *)pt;           pt += menulength;
	dtclass = (WORD *)pt;          pt += classlength;
	dttitle = (WORD *)pt;          pt += titlelength;
	for(i=0; i<dialog->items; i++)
	{
		pt = (char *)(((DWORD)pt + 3) & ~3);
		dtitems[i] = (DLGITEMTEMPLATE *)pt;
		pt += itemlengths[i];
	}

	/* load dialog template header */
	dtheader->style = WS_VISIBLE | WS_DLGFRAME | WS_POPUP /* | DS_ABSALIGN */;
	if (dialog->movable != 0) dtheader->style |= WS_CAPTION | DS_MODALFRAME;
	dtheader->dwExtendedStyle = 0;
	dtheader->cdit = (unsigned short)dialog->items;
	dtheader->x = dialog->windowRect.left /* * gra_dialogdbnx / gra_dialogdbux */ ;
	dtheader->y = dialog->windowRect.top /* * gra_dialogdbny / gra_dialogdbuy */ ;
	dtheader->cx = (dialog->windowRect.right - dialog->windowRect.left) * gra_dialogdbnx / gra_dialogdbux;
	dtheader->cy = (dialog->windowRect.bottom - dialog->windowRect.top) * gra_dialogdbny / gra_dialogdbuy;

	/* no menu or class in this dialog */
	dtmenu[0] = 0;
	dtclass[0] = 0;

	/* load the dialog title */
	len = MultiByteToWideChar(CP_ACP, 0, title, strlen(title), output, 100);
	for(j=0; j<len; j++) *dttitle++ = output[j];
	*dttitle++ = 0;

	/* find the default button */
	gra_curdialog->defaultbutton = 1;
	for(i=0; i<dialog->items; i++)
	{
		itemtype = dialog->list[i].type;
		if ((itemtype&ITEMTYPE) == DEFBUTTON) gra_curdialog->defaultbutton = i+1;
	}

	/* load the items */
	iconxpos = -1;
	for(i=0; i<dialog->items; i++)
	{
		dtitems[i]->x = dialog->list[i].r.left * gra_dialogdbnx / gra_dialogdbux;
		dtitems[i]->y = dialog->list[i].r.top * gra_dialogdbny / gra_dialogdbuy;
		dtitems[i]->cx = (dialog->list[i].r.right - dialog->list[i].r.left) * gra_dialogdbnx / gra_dialogdbux;
		dtitems[i]->cy = (dialog->list[i].r.bottom - dialog->list[i].r.top) * gra_dialogdbny / gra_dialogdbuy;
		dtitems[i]->dwExtendedStyle = 0;
		dtitems[i]->id = i+ID_DIALOGITEM_0;
		itemtype = dialog->list[i].type;
		switch (itemtype&ITEMTYPE)
		{
			case BUTTON:
			case DEFBUTTON:
				itemclass = 0x80;
				style = WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON | WS_TABSTOP;
				if (i+1 == gra_curdialog->defaultbutton) style |= BS_DEFPUSHBUTTON;
				break;
			case CHECK:
				itemclass = 0x80;
				style = WS_VISIBLE | WS_CHILD | BS_CHECKBOX | WS_TABSTOP;
				break;
			case RADIO:
				itemclass = 0x80;
				style = WS_VISIBLE | WS_CHILD | BS_RADIOBUTTON | WS_TABSTOP;
				break;
			case EDITTEXT:
				itemclass = 0x81;
				style = WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT | WS_TABSTOP;
				if (dtitems[i]->cy > 10) style |= ES_MULTILINE;
				dtitems[i]->y--;
				dtitems[i]->cy += 2;
				break;
			case MESSAGE:
				itemclass = 0x82;
				style = WS_CHILD | WS_VISIBLE | SS_LEFT;
				if ((itemtype&INACTIVE) == 0) style |= SS_NOTIFY;
				break;
			case PROGRESS:
				itemclass = 0x82;
				style = WS_CHILD | WS_VISIBLE;
				dtitems[i]->id += PROGRESSOFFSET;
				break;
			case USERDRAWN:
				itemclass = 0x82;
				style = WS_CHILD | WS_VISIBLE;
				dtitems[i]->x += 1000;
				dtitems[i]->y += 1000;
				break;
			case POPUP:
				itemclass = 0x85;
				dtitems[i]->cy *= 8;
				dtitems[i]->y -= 2;
				style = WS_CHILD | WS_VISIBLE | WS_VSCROLL | CBS_DROPDOWNLIST | WS_TABSTOP;
				break;
			case ICON:
				itemclass = 0x82;
				style = WS_CHILD | WS_VISIBLE | SS_ICON;
				if ((itemtype&INACTIVE) == 0) style |= SS_NOTIFY;
				break;
			case SCROLL:
				itemclass = 0x83;
				style = WS_BORDER | WS_CHILD | WS_VISIBLE | LBS_USETABSTOPS |
					WS_VSCROLL | WS_HSCROLL | LBS_WANTKEYBOARDINPUT | LBS_SORT | WS_TABSTOP;
				if ((itemtype&INACTIVE) == 0) style |= LBS_NOTIFY;
				break;
			default:
				itemclass = 0x82;
				style = WS_CHILD | SS_LEFT;
				break;
		}
		dtitems[i]->style = style;
		pt = ((char *)dtitems[i]) + (sizeof (DLGITEMTEMPLATE));
		dtclass = (WORD *)pt;
		*dtclass++ = 0xFFFF;
		*dtclass++ = itemclass;
		switch (itemtype&ITEMTYPE)
		{
			case POPUP:
			case ICON:
			case SCROLL:
				*dtclass++ = 0;
				break;
			default:
				len = MultiByteToWideChar(CP_ACP, 0, dialog->list[i].msg,
					strlen(dialog->list[i].msg), output, 100);
				for(j=0; j<len; j++) *dtclass++ = output[j];
				*dtclass++ = 0;
				break;
		}
		*dtclass++ = 0;
	}

	/* create the dialog */
	if (gra_curdialog->window->CreateIndirect(dtheader) == 0)
	{
		return(1);
	}

	/* finish initialization */
	wnd = 0;
	for(i=0; i<dialog->items; i++)
	{
		itemtype = dialog->list[i].type;
		switch (itemtype&ITEMTYPE)
		{
			case EDITTEXT:
				if (wnd == 0)
					wnd = gra_curdialog->window->GetDlgItem(i+ID_DIALOGITEM_0);
				allocstring((char **)&dialog->list[i].data, dialog->list[i].msg, el_tempcluster);
				break;
			case ICON:
				stat = (CStatic *)gra_curdialog->window->GetDlgItem(i+ID_DIALOGITEM_0);
				if (stat == 0) break;
				map = gra_makeicon(dialog->list[i].msg);
				stat->SetIcon(map);
				break;
			case PROGRESS:
				stat = (CStatic *)gra_curdialog->window->GetDlgItem(i+ID_DIALOGITEM_0+PROGRESSOFFSET);
				stat->GetWindowRect(&r);
				gra_curdialog->window->ScreenToClient(&r);
				prog = new CProgressCtrl();
				prog->Create(WS_CHILD | WS_VISIBLE, r, gra_curdialog->window, i+ID_DIALOGITEM_0);
				break;
			case SCROLL:
				list = (CListBox *)gra_curdialog->window->GetDlgItem(i+ID_DIALOGITEM_0);
				if (list == 0) break;
				list->SetTabStops(1);
				break;
		}
	}
	if (wnd == 0)
		wnd = gra_curdialog->window->GetDlgItem(gra_curdialog->defaultbutton-1+ID_DIALOGITEM_0);
	gra_curdialog->window->GotoDlgCtrl(wnd);
	gra_curdialog->window->SetDefID(gra_curdialog->defaultbutton-1+ID_DIALOGITEM_0);

	/* determine location of dialog in screen coordinates */
	gra_curdialog->window->GetWindowRect(&r);
	gra_curdialog->firstpoint.x = r.left;
	gra_curdialog->firstpoint.y = r.top;

	efree((char *)itemlengths);
	efree((char *)dtitems);
	efree((char *)descblock);

	gra_dialoghit = -1;
	return(0);
}

/*
 * Routine to handle actions and return the next item hit.
 */
INTBIG DiaNextHit(void)
{
	INTBIG item;

	for(;;)
	{
		flushscreen();
		gra_nextevent();
		if (gra_dialoghit != -1)
		{
			item = gra_dialoghit;
			gra_dialoghit = -1;
			return(item);
		}
	}
}

/*
 * Routine to parse the next input event and return the next character typed.
 * If the routine returns -1, nothing has happened.  If the
 * routine returns -2, an item has been hit (and is in "itemHit").
 */
INTSML DiaGetNextCharacter(INTBIG *itemHit)
{
	INTSML chr;

	gra_nextevent();
	if (gra_dialoghit == -1) return(-1);
	*itemHit = gra_dialoghit;
	if (gra_dialoghitchar == 0) return(-2);
	chr = gra_dialoghitchar;
	gra_dialoghitchar = 0;
	return(chr);
}

void DiaDoneDialog(void)
{
	REGISTER INTBIG dx, dy, i, itemtype;
	RECT rect;

	/* if playing back, search for end-dialog marker */
	while (us_logplay != NULL)
	{
		gra_logreadaction();
		if (gra_inputstate == DIAENDDIALOG) break;
		gra_inputstate = NOEVENT;
	}
	gra_inputstate = NOEVENT;
	gra_logwriteaction(DIAENDDIALOG, 0, 0, 0);

	/* free memory used by edit text fields */
	for(i=0; i<gra_curdialog->itemdesc->items; i++)
	{
		itemtype = gra_curdialog->itemdesc->list[i].type;
		if ((itemtype&ITEMTYPE) == EDITTEXT)
			efree((char *)gra_curdialog->itemdesc->list[i].data);
	}

	/* update dialog location if it was moved */
	gra_curdialog->window->GetWindowRect(&rect);
	dx = rect.left - gra_curdialog->firstpoint.x;
	dy = rect.top - gra_curdialog->firstpoint.y;
	dx = (dx * gra_dialogdbnx) / gra_dialogdbux;
	dy = (dy * gra_dialogdbny) / gra_dialogdbuy;

	gra_curdialog->itemdesc->windowRect.left += (INTSML)dx;
	gra_curdialog->itemdesc->windowRect.right += (INTSML)dx;
	gra_curdialog->itemdesc->windowRect.top += (INTSML)dy;
	gra_curdialog->itemdesc->windowRect.bottom += (INTSML)dy;

	gra_curdialog->window->EndDialog(0);

	gra_curdialogindex--;
	if (gra_curdialogindex >= 0)
		gra_curdialog = &gra_dialogs[gra_curdialogindex];
	gra_dialogeditline = -1;
}

/*
 * Routine to set the text in item "item" to "msg"
 */
void DiaSetText(INTBIG item, char *msg)
{
	INTBIG type, highlight;
	REGISTER char *pt;
	CWnd *wnd;
	CEdit *edit;

	highlight = 0;
	if (item < 0)
	{
		item = -item;
		highlight = 1;
	}
	item--;
	for(pt = msg; *pt != 0; pt++) if (strncmp(pt, "(c)", 3) == 0)
	{
		(void)strcpy(pt, "©");		/* "copyright" character */
		(void)strcpy(&pt[1], &pt[3]);
		break;
	}
	wnd = gra_curdialog->window->GetDlgItem(item+ID_DIALOGITEM_0);
	if (wnd == 0) return;
	wnd->SetWindowText(msg);
	type = gra_curdialog->itemdesc->list[item].type;
	if ((type&ITEMTYPE) == EDITTEXT)
	{
		edit = (CEdit *)wnd;
		int len = strlen(msg);
		if (highlight != 0) edit->SetSel(0, len); else
			edit->SetSel(len, len);
		reallocstring((char **)&gra_curdialog->itemdesc->list[item].data, msg, el_tempcluster);
	}
}

/*
 * Routine to return the text in item "item"
 */
char *DiaGetText(INTBIG item)
{
	static int bufnum = 0;
	static char line[10][300];
	CWnd *wnd;

	item--;
	wnd = gra_curdialog->window->GetDlgItem(item+ID_DIALOGITEM_0);
	if (wnd == 0) return("");
	bufnum++;
	if (bufnum >= 10) bufnum = 0;
	wnd->GetWindowText(line[bufnum], 300);
	return(line[bufnum]);
}

/*
 * Routine to set the value in item "item" to "value"
 */
void DiaSetControl(INTBIG item, INTBIG value)
{
	CButton *but;

	item--;
	but = (CButton *)gra_curdialog->window->GetDlgItem(item+ID_DIALOGITEM_0);
	if (but == 0) return;
	but->SetCheck(value);
	gra_logwriteaction(DIASETCONTROL, item + 1, (INTSML)value, 0);
}

/*
 * Routine to return the value in item "item"
 */
INTBIG DiaGetControl(INTBIG item)
{
	CButton *but;

	item--;
	but = (CButton *)gra_curdialog->window->GetDlgItem(item+ID_DIALOGITEM_0);
	if (but == 0) return(0);
	return(but->GetCheck());
}

/*
 * Routine to check item "item" to make sure that there is
 * text in it.  If so, it returns nonzero.  Otherwise it beeps and returns zero.
 */
INTSML DiaValidEntry(INTBIG item)
{
	char *msg;

	msg = DiaGetText(item);
	while (*msg == ' ') msg++;
	if (*msg != 0) return(1);
	ttybeep();
	return(0);
}

/*
 * Routine to dim item "item"
 */
void DiaDimItem(INTBIG item)
{
	CWnd *wnd;

	item--;
	wnd = gra_curdialog->window->GetDlgItem(item+ID_DIALOGITEM_0);
	wnd->EnableWindow(0);
}

/*
 * Routine to un-dim item "item"
 */
void DiaUnDimItem(INTBIG item)
{
	CWnd *wnd;

	item--;
	wnd = gra_curdialog->window->GetDlgItem(item+ID_DIALOGITEM_0);
	wnd->EnableWindow(1);
}

/*
 * Routine to change item "item" to be a message rather
 * than editable text
 */
void DiaNoEditControl(INTBIG item)
{
	DiaDimItem(item);
}

/*
 * Routine to change item "item" to be editable text rather
 * than a message
 */
void DiaEditControl(INTBIG item)
{
	DiaUnDimItem(item);
}

void DiaOpaqueEdit(INTBIG item)
{
	CEdit *edit;

	item--;
	edit = (CEdit *)gra_curdialog->window->GetDlgItem(item+ID_DIALOGITEM_0);
	edit->ModifyStyle(0, ES_PASSWORD);
	edit->SetPasswordChar('*');
}

/*
 * Routine to cause item "item" to report character hits
 */
void DiaCharacterEdit(INTBIG item)
{
	item--;
	gra_dialogeditline = item;
}

/*
 * Routine to cause item "item" to be the default button
 */
void DiaDefaultButton(INTBIG item)
{
	gra_curdialog->defaultbutton = item;
	gra_curdialog->window->SetDefID(gra_curdialog->defaultbutton-1+ID_DIALOGITEM_0);
}

/*
 * Routine to change item "item" into a popup with "count" entries
 * in "names".
 */
void DiaSetPopup(INTBIG item, INTBIG count, char **names)
{
	INTBIG i;
	CComboBox *cb;

	item--;
	cb = (CComboBox *)gra_curdialog->window->GetDlgItem(item+ID_DIALOGITEM_0);
	if (cb == 0) return;
	cb->ResetContent();
	for(i=0; i<count; i++) cb->AddString(names[i]);
	cb->SetCurSel(0);
}

/*
 * Routine to change popup item "item" so that the current entry is "entry".
 */
void DiaSetPopupEntry(INTBIG item, INTBIG entry)
{
	CComboBox *cb;

	item--;
	cb = (CComboBox *)gra_curdialog->window->GetDlgItem(item+ID_DIALOGITEM_0);
	if (cb == 0) return;
	cb->SetCurSel(entry);
}

/*
 * Routine to return the current item in popup menu item "item".
 */
INTBIG DiaGetPopupEntry(INTBIG item)
{
	CComboBox *cb;

	item--;
	cb = (CComboBox *)gra_curdialog->window->GetDlgItem(item+ID_DIALOGITEM_0);
	if (cb == 0) return(0);
	return(cb->GetCurSel());
}

void DiaInitTextDialog(INTBIG item, INTSML (*toplist)(char **), char *(*nextinlist)(void),
	void (*donelist)(void), INTBIG sortpos, INTBIG flags)
{
	long add, remove;
	static CFont *fnt = 0;
	LOGFONT lf;
	CListBox *list;
	static int firstregistryhack = 1;

	/*
	 * this little hack updates the registry to remember the current library path.
	 * The premise here is that if a dialog with a scroll area appears (and that
	 * would be the "about" box), then all must be well and the path to the
	 * library directory must be valid.  So save it.
	 */
	if (firstregistryhack != 0)
	{
		gra_updateregistry();
		firstregistryhack = 0;
	}

	item--;
	list = (CListBox *)gra_curdialog->window->GetDlgItem(item+ID_DIALOGITEM_0);
	if (list == 0) return;

	add = remove = 0;
	if ((flags&SCHORIZBAR) == 0) remove |= WS_HSCROLL; else
	{
		add |= WS_HSCROLL;
		list->SetHorizontalExtent(1000);
	}
	list->ModifyStyle(remove, add, SWP_NOSIZE);
	gra_curdialog->itemdesc->list[item].data = flags;
	if ((flags&SCFIXEDWIDTH) != 0)
	{
		if (fnt == 0)
		{
			lf.lfHeight = -10;
			strcpy(lf.lfFaceName, "Lucida Console");
			lf.lfWidth = 0;
			lf.lfEscapement = 0;
			lf.lfOrientation = 0;
			lf.lfWeight = FW_NORMAL;
			lf.lfItalic = 0;
			lf.lfUnderline = 0;
			lf.lfStrikeOut = 0;
			lf.lfCharSet = 0;
			lf.lfOutPrecision = OUT_STROKE_PRECIS;
			lf.lfClipPrecision = CLIP_STROKE_PRECIS;
			lf.lfQuality = 1;
			lf.lfPitchAndFamily = FF_DONTCARE | FF_SWISS;

			fnt = new CFont();
			fnt->CreateFontIndirect(&lf);
		}
		list->SetFont(fnt);
	}
	DiaLoadTextDialog(item+1, toplist, nextinlist, donelist, sortpos);
}

void DiaLoadTextDialog(INTBIG item, INTSML (*toplist)(char **), char *(*nextinlist)(void),
	void (*donelist)(void), INTBIG sortpos)
{
	char *next, line[256];
	INTBIG i;
	CListBox *list;

	item--;
	list = (CListBox *)gra_curdialog->window->GetDlgItem(item+ID_DIALOGITEM_0);
	if (list == 0) return;

	/* clear the list */
	list->ResetContent();

	/* load the list */
	line[0] = 0;
	next = line;
	(void)(*toplist)(&next);
	for(i=0; ; i++)
	{
		next = (*nextinlist)();
		if (next == 0) break;
		if (sortpos < 0) list->InsertString(-1, next); else
			list->AddString(next);
	}
	(*donelist)();
	if (i > 0) list->SetCurSel(0);
}

/*
 * Routine to stuff line "line" at the end of the edit buffer.
 */
void DiaStuffLine(INTBIG item, char *line)
{
	CListBox *list;

	item--;
	list = (CListBox *)gra_curdialog->window->GetDlgItem(item+ID_DIALOGITEM_0);
	if (list == 0) return;
	list->InsertString(-1, line);
}

void DiaSelectLine(INTBIG item, INTBIG line)
{
	CListBox *list;
	INTBIG numitems, botitem, topitem, visibleitems;
	CPoint pt;
	BOOL outside;

	item--;
	list = (CListBox *)gra_curdialog->window->GetDlgItem(item+ID_DIALOGITEM_0);
	if (list == 0) return;
	pt.x = 0;   pt.y = 0;
	topitem = list->ItemFromPoint(pt, outside); 
	pt.x = 0;   pt.y = 9999;
	botitem = list->ItemFromPoint(pt, outside);
	visibleitems = botitem - topitem;
	numitems = list->GetCount();
	if (line < topitem || line >= botitem)
	{
		topitem = line - visibleitems/2;
		list->SetTopIndex(topitem);
	}
	list->SetCurSel(line);
}

INTBIG DiaGetCurLine(INTBIG item)
{
	INTBIG line;
	CListBox *list;

	item--;
	list = (CListBox *)gra_curdialog->window->GetDlgItem(item+ID_DIALOGITEM_0);
	if (list == 0) return(-1);
	line = list->GetCurSel();
	if (line == LB_ERR) return(-1);
	return(line);
}

char *DiaGetScrollLine(INTBIG item, INTBIG line)
{
	static char text[300];
	CListBox *list;

	item--;
	list = (CListBox *)gra_curdialog->window->GetDlgItem(item+ID_DIALOGITEM_0);
	if (list == 0) return("");
	if (list->GetText(line, text) == -1) return("");
	return(text);
}

void DiaSetScrollLine(INTBIG item, INTBIG line, char *msg)
{
	CListBox *list;

	item--;
	list = (CListBox *)gra_curdialog->window->GetDlgItem(item+ID_DIALOGITEM_0);
	if (list == 0) return;
	list->DeleteString(line);
	list->InsertString(line, msg);
	list->SetCurSel(line);
}

void DiaItemRect(INTBIG item, RECTAREA *rect)
{
	item--;
	if (item < 0 || item >= gra_curdialog->itemdesc->items) return;
	*rect = gra_curdialog->itemdesc->list[item].r;
}

void DiaPercent(INTBIG item, INTBIG percent)
{
	CProgressCtrl *prog;

	item--;
	prog = (CProgressCtrl *)gra_curdialog->window->GetDlgItem(item+ID_DIALOGITEM_0);
	prog->SetPos(percent);
}

void DiaRedispRoutine(INTBIG item, void (*routine)(RECTAREA*))
{
	gra_curdialog->redrawroutine = routine;
	gra_curdialog->redrawitem = item;
}

void DiaFrameRect(INTBIG item, RECTAREA *ur)
{
	RECT r;
	CDC *dc;

	r.left = ur->left;
	r.right = ur->right-1;
	r.top = ur->top;
	r.bottom = ur->bottom-1;

	if (gra_dialogoffbrush == 0) gra_dialogoffbrush = new CBrush((COLORREF)0xFFFFFF);
	dc = gra_curdialog->window->GetDC();
	dc->FillRect(&r, gra_dialogoffbrush);
	dc->MoveTo(r.left, r.top);
	dc->LineTo(r.right, r.top);
	dc->LineTo(r.right, r.bottom);
	dc->LineTo(r.left, r.bottom);
	dc->LineTo(r.left, r.top);
	gra_curdialog->window->ReleaseDC(dc);
}

void DiaDrawLine(INTBIG item, INTBIG fx, INTBIG fy, INTBIG tx, INTBIG ty, INTBIG mode)
{
	CDC *dc;

	dc = gra_curdialog->window->GetDC();
	switch (mode)
	{
		case DLMODEON:     dc->SetROP2(R2_BLACK);   break;
		case DLMODEOFF:    dc->SetROP2(R2_WHITE);   break;
		case DLMODEINVERT: dc->SetROP2(R2_NOT);     break;
	}
	dc->MoveTo(fx, fy);
	dc->LineTo(tx, ty);
	dc->SetROP2(R2_COPYPEN);
	gra_curdialog->window->ReleaseDC(dc);
}

void DiaFillPoly(INTBIG item, INTBIG *x, INTBIG *y, INTBIG count, INTBIG r, INTBIG g, INTBIG b)
{
	CBrush *polybrush;
	CPen *polypen;
	COLORREF brushcolor;
	POINT points[50];
	INTBIG i;
	CDC *dc;

	for(i=0; i<count; i++)
	{
		points[i].x = x[i];
		points[i].y = y[i];
	}

	brushcolor = (COLORREF)((b << 16) | (g << 8) | r);
	polybrush = new CBrush(brushcolor);
	polypen = new CPen(PS_SOLID, 0, brushcolor);
	dc = gra_curdialog->window->GetDC();
	dc->SelectObject(polybrush);
	dc->SelectObject(polypen);
	dc->Polygon(points, count);
	delete polybrush;
	delete polypen;
	gra_curdialog->window->ReleaseDC(dc);
}

void DiaDrawRect(INTBIG item, RECTAREA *ur, INTBIG r, INTBIG g, INTBIG b)
{
	RECT rr;
	CDC *dc;
	CBrush *brush;
	COLORREF color;

	rr.left = ur->left;
	rr.right = ur->right;
	rr.top = ur->top;
	rr.bottom = ur->bottom;

	color = (b << 16) | (g << 8) | r;
	brush = new CBrush(color);
	dc = gra_curdialog->window->GetDC();
	dc->FillRect(&rr, brush);
	gra_curdialog->window->ReleaseDC(dc);
	delete brush;
}

void DiaPutText(INTBIG item, char *msg, INTBIG x, INTBIG y)
{
	CFont *font;
	CDC *dc;

	font = gra_gettextfont(TXT8P);
	dc = gra_curdialog->window->GetDC();
	dc->SelectObject(font);
	dc->SetBkMode(TRANSPARENT);
	dc->TextOut(x, y, (LPCTSTR)msg, strlen(msg));
	gra_curdialog->window->ReleaseDC(dc);
}

void DiaGetTextInfo(char *msg, INTBIG *wid, INTBIG *hei)
{
	CFont *font;
	CSize textSize;
	CDC *dc;

	font = gra_gettextfont(TXT8P);
	dc = gra_curdialog->window->GetDC();
	dc->SelectObject(font);
	textSize = dc->GetTextExtent((LPCTSTR)msg, strlen(msg));
	*wid = textSize.cx;
	*hei = textSize.cy+1;
	gra_curdialog->window->ReleaseDC(dc);
}

void DiaGetMouse(INTSML *x, INTSML *y)
{
	POINT p, p2;

	if (us_logplay != NULL)
	{
		if (gra_loggetnextaction(0) == 0)
		{
			*x = (INTSML)gra_action.x;
			*y = (INTSML)gra_action.y;
		}
	}
	if (us_logplay == NULL)
	{
		p2.x = p2.y = 0;
		gra_curdialog->window->MapWindowPoints(0, &p2, 1);
		GetCursorPos(&p);
		*x = (INTSML)(p.x - p2.x);   *y = (INTSML)(p.y - p2.y);
	}
	gra_logwriteaction(DIAUSERMOUSE, *x, *y, 0);
}

INTSML DiaNullDlogList(char **c) { return(0); }

char *DiaNullDlogItem(void) { return(0); }

void DiaNullDlogDone(void) {}

/************************* DIALOG SUPPORT *************************/

/*
 * Routine to redraw user items and divider lines
 */
void gra_diaredrawitem(void)
{
	RECTAREA ra;
	INTBIG i, itemtype;

	if (gra_curdialog->redrawroutine != 0)
	{
		DiaItemRect(gra_curdialog->redrawitem, &ra);
		(*gra_curdialog->redrawroutine)(&ra);
	}

	/* redraw any divider lines */
	for(i=0; i<gra_curdialog->itemdesc->items; i++)
	{
		itemtype = gra_curdialog->itemdesc->list[i].type;
		if ((itemtype&ITEMTYPE) != DIVIDELINE) continue;
		DiaDrawRect(i+1, &gra_curdialog->itemdesc->list[i].r, 0, 0, 0);
	}
}

/*
 * Routine called when an item is clicked
 */
void gra_itemclicked(int nID)
{
	int itemtype, line;
	CListBox *list;
	CComboBox *cb;

	gra_dialoghitchar = 0;
	if (nID == 1)
	{
		gra_dialoghit = gra_curdialog->defaultbutton;
		gra_logwriteaction(DIAITEMCLICK, gra_dialoghit, 0, 0);
		return;
	}
	if (nID == 2)
	{
		gra_dialoghit = 2;
		gra_logwriteaction(DIAITEMCLICK, gra_dialoghit, 0, 0);
		return;
	}

	/* ignore clicks in scroll areas that do not want hits reported */
	itemtype = gra_curdialog->itemdesc->list[nID-ID_DIALOGITEM_0].type;
	if ((itemtype&ITEMTYPE) == SCROLL)
	{
		list = (CListBox *)gra_curdialog->window->GetDlgItem(nID);
		line = list->GetCurSel();
		if (line != LB_ERR) gra_logwriteaction(DIASCROLLSEL, nID - ID_DIALOGITEM_0 + 1, line, 0);
		if ((gra_curdialog->itemdesc->list[nID-ID_DIALOGITEM_0].data&SCSELMOUSE) == 0)
		{
			/* deselect the scroll list (leaves an outline, but oh well) */
			if (list == 0) return;
			list->SetCurSel(-1);
			return;
		}
		if ((gra_curdialog->itemdesc->list[nID-ID_DIALOGITEM_0].data&SCREPORT) == 0)
			return;
	}
	if ((itemtype&ITEMTYPE) == POPUP)
	{
		cb = (CComboBox *)gra_curdialog->window->GetDlgItem(nID);
		gra_logwriteaction(DIAPOPUPSEL, nID - ID_DIALOGITEM_0 + 1, cb->GetCurSel(), 0);
	}

	gra_dialoghit = nID - ID_DIALOGITEM_0 + 1;
	gra_logwriteaction(DIAITEMCLICK, gra_dialoghit, 0, 0);
}

/*
 * Routine called when an item is double-clicked
 */
void gra_itemdoubleclicked(int nID)
{
	int itemtype;

	if (gra_curdialog->window == 0) return;
	itemtype = gra_curdialog->itemdesc->list[nID].type;
	if ((itemtype&ITEMTYPE) != SCROLL) return;
	if ((gra_curdialog->itemdesc->list[nID].data&SCDOUBLEQUIT) == 0) return;

	gra_dialoghit = gra_curdialog->defaultbutton;
	gra_dialoghitchar = 0;
	gra_logwriteaction(DIAITEMCLICK, gra_dialoghit, 0, 0);
}

/*
 * Called when a key is typed to a list box.  Returns nonzero to accept the
 * keystroke, zero to ignore typed keys in the list box.
 */
int gra_dodialoglistkey(UINT nKey, CListBox* pListBox, UINT nIndex)
{
	CListBox *list;
	INTBIG i, itemtype, line;

	/* always allow arrow keys */
	if (nKey == VK_DOWN || nKey == VK_UP) return(1);

	for(i=0; i<gra_curdialog->itemdesc->items; i++)
	{
		itemtype = gra_curdialog->itemdesc->list[i].type;
		if ((itemtype&ITEMTYPE) != SCROLL) continue;
		list = (CListBox *)gra_curdialog->window->GetDlgItem(i+ID_DIALOGITEM_0);
		if (list != pListBox) continue;
		if ((gra_curdialog->itemdesc->list[i].data&SCSELKEY) == 0) return(0);
		line = list->GetCurSel();
		if (line != LB_ERR) gra_logwriteaction(DIASCROLLSEL, i+1, (INTSML)line, 0);
		break;
	}

	return(1);
}

/*
 * Routine called when a character is typed
 */
void gra_dodialogtextchange(int nID)
{
	char line[300], *lastline;
	CWnd *wnd;
	CEdit *edit;
	int len;

	/* get current contents of the edit field */
	gra_dialoghit = nID + 1;
	wnd = gra_curdialog->window->GetDlgItem(nID+ID_DIALOGITEM_0);
	if (wnd == 0) return;
	wnd->GetWindowText(line, 300);

	/* if this field is being character-edited, undo the change */
	if (nID == gra_dialogeditline)
	{
		/* get the previous contents of the edit field */
		lastline = (char *)gra_curdialog->itemdesc->list[nID].data;

		/* determine which character was typed (hack!!!) */
		if (strlen(line) > strlen(lastline))
		{
			gra_dialoghitchar = line[strlen(line)-1];
		} else if (strlen(line) < strlen(lastline))
		{
			gra_dialoghitchar = 010;
		}

		/* reset to previous state */
		wnd->SetWindowText(lastline);
		edit = (CEdit *)wnd;
		len = strlen(lastline);
		edit->SetSel(len, len);
	}
	gra_logwriteaction(DIAEDITTEXT, gra_dialoghit, gra_dialoghitchar, line);
}

/*
 * Routine to return nonzero if point (x,y) is inside a user-drawn item
 */
INTSML gra_dodialogisinsideuserdrawn(int x, int y)
{
	int i, itemtype;

	if (gra_curdialog->window == 0) return(0);
	for(i=0; i<gra_curdialog->itemdesc->items; i++)
	{
		itemtype = gra_curdialog->itemdesc->list[i].type;
		if ((itemtype&ITEMTYPE) != USERDRAWN) continue;
		if (x < gra_curdialog->itemdesc->list[i].r.left) continue;
		if (x > gra_curdialog->itemdesc->list[i].r.right) continue;
		if (y < gra_curdialog->itemdesc->list[i].r.top) continue;
		if (y > gra_curdialog->itemdesc->list[i].r.bottom) continue;
		return(i+1);
	}
	return(0);
}

/*
 * Routine to make an icon from data
 */
HICON gra_makeicon(char *data)
{
	unsigned char zero[128];
	int i;

	for(i=0; i<128; i++)
	{
		zero[i] = 0;
		data[i] = ~data[i];
	}
	HICON icon = CreateIcon(0, 32, 32, 1, 1, (unsigned char *)data, zero);
	for(i=0; i<128; i++) data[i] = ~data[i];
	return(icon);
}

/****************************** TCL SUPPORT ******************************/

#if LANGTCL
INTBIG gra_initializetcl(void)
{
	INTBIG err;
	char *newArgv[2];

	/* set the program name/path */
	newArgv[0] = "Electric";
	newArgv[1] = NULL;
	(void)Tcl_FindExecutable(newArgv[0]);

	tcl_interp = Tcl_CreateInterp();
	if (tcl_interp == 0) error("from Tcl_CreateInterp");

	/* tell Electric the TCL interpreter handle */
	el_tclinterpreter(tcl_interp);

	/* Make command-line arguments available in the Tcl variables "argc" and "argv" */
	Tcl_SetVar(tcl_interp, "argv", "", TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp, "argc", "0", TCL_GLOBAL_ONLY);
	Tcl_SetVar(tcl_interp, "argv0", "electric", TCL_GLOBAL_ONLY);

	/* Set the "tcl_interactive" variable */
	Tcl_SetVar(tcl_interp, "tcl_interactive", "1", TCL_GLOBAL_ONLY);

	/* initialize the interpreter */
	err = Tcl_Init(tcl_interp);
	if (err != TCL_OK) error("(from Tcl_Init) %s", tcl_interp->result);

	return(err);
}
#endif

/****************************** PRINTING ******************************/

extern "C"
{
	void gra_printwindow(void);
}

/* call this routine to print the current window */
void gra_printwindow(void)
{
	INTBIG pagewid, pagehei, pagehpixperinch, pagevpixperinch,
		marginx, marginy, slx, shx, sly, shy, hlx, hhx, hly, hhy,
		ulx, uhx, uly, uhy, width, height, centerx, centery, prod1, prod2;
	INTSML i;
	REGISTER char *ptr;
	HCURSOR hCursorBusy, hCursorOld;
	static DOCINFO di = {sizeof(DOCINFO), "Electric", NULL};
	RGBQUAD bmiColors[256];
	BITMAPINFO *bmiInfo;
	CDC printDC, *pDCPrint;
	HDC hPrnDC;
	WINDOWPART *win;
	NODEPROTO *np, *onp;
	REGISTER VARIABLE *varstate;
	WINDOWFRAME *wf;

	/* get facet to plot */
	win = el_curwindowpart;
	if (win == NOWINDOWPART || win->curnodeproto == NONODEPROTO)
	{
		ttyputerr("No current facet to plot");
		return;
	}
	wf = win->frame;
	np = win->curnodeproto;

	/* determine area to plot (in database units) */
	if (framesize(&shx, &shy, np) == 0)
	{
		shx /= 2;   slx = -shx;
		shy /= 2;   sly = -shy;
	} else
	{
		slx = np->lowx;   shx = np->highx;
		sly = np->lowy;   shy = np->highy;
	}

	/* see if focusing on highlighted area */
	varstate = getvalkey((INTBIG)io_aid, VAID, VINTEGER, io_state);
	if (varstate != NOVARIABLE && (varstate->addr&PLOTFOCUS) != 0)
	{
		onp = (NODEPROTO *)askaid(us_aid, "get-highlighted-area", (INTBIG)&hlx,
			(INTBIG)&hhx, (INTBIG)&hly, (INTBIG)&hhy);
		if (onp == NONODEPROTO)
			ttyputerr("Warning: no highlighted area; printing entire facet"); else
		{
			slx = hlx;   shx = hhx;
			sly = hly;   shy = hhy;
		}
	}

	/* convert to screen units */
	(void)us_makescreen(&slx, &sly, &shx, &shy, win);

	/* Let user select printer */
	CPrintDialog printDlg(FALSE);
	if (printDlg.DoModal() != IDOK) return;

	/* Set cursor to busy */
	hCursorBusy = LoadCursor(NULL, IDC_WAIT);
	hCursorOld = SetCursor(hCursorBusy);

	/* Collect size information about the Printer DC */
	hPrnDC = printDlg.GetPrinterDC();
	if (hPrnDC == 0)
	{
		ttyputerr("Printing error: could not start print job");
		return;
	}
	pDCPrint = printDC.FromHandle(hPrnDC);
	pagewid = pDCPrint->GetDeviceCaps(HORZRES);
	pagehei = pDCPrint->GetDeviceCaps(VERTRES);
	pagehpixperinch = pDCPrint->GetDeviceCaps(LOGPIXELSX);
	pagevpixperinch = pDCPrint->GetDeviceCaps(LOGPIXELSY);
	marginx = pagehpixperinch / 2;
	marginy = pagevpixperinch / 2;

	/* make the plot have square pixels */
	ulx = marginx;
	uly = marginy;
	uhx = pagewid-marginx;
	uhy = pagehei-marginy;
	prod1 = (shx - slx) * (uhy - uly);
	prod2 = (shy - sly) * (uhx - ulx);
	if (prod1 != prod2)
	{
		/* adjust the scale */
		if (prod1 > prod2)
		{
			/* make it fill the width of the screen */
			height = muldiv(uhx - ulx, shy - sly, shx - slx);
			centery = (uly + uhy) / 2;
			uly = centery - height/2;
			uhy = uly + height;
		} else
		{
			/* make it fill the height of the screen */
			width = muldiv(uhy - uly, shx - slx, shy - sly);
			centerx = (ulx + uhx) / 2;
			ulx = centerx - width/2;
			uhx = ulx + width;
		}
	}

	/* adjust the background color to white */
	i = GetDIBColorTable(((CDC *)wf->hDCOff)->m_hDC, 0, 256, bmiColors);
	bmiColors[0].rgbRed = 0xFF;
	bmiColors[0].rgbGreen = 0xFF;
	bmiColors[0].rgbBlue = 0xFF;
	bmiColors[0].rgbReserved = 0;

	bmiInfo = (BITMAPINFO *)emalloc(sizeof(BITMAPINFOHEADER) + sizeof(bmiColors), db_cluster);
	if (bmiInfo == 0) return;
	ptr =  (char *)bmiInfo;
	memcpy(ptr, wf->bminfo, sizeof(BITMAPINFOHEADER));
	ptr += sizeof(BITMAPINFOHEADER);
	memcpy(ptr, &bmiColors, sizeof(bmiColors));

	/* Start printing */
	pDCPrint->StartDoc(&di);
	pDCPrint->StartPage();

	/* print the window */
	StretchDIBits(hPrnDC, ulx, uly, uhx-ulx, uhy-uly,
		slx, sly, shx-slx+1, shy-sly+1,
		wf->data, bmiInfo, DIB_RGB_COLORS, SRCCOPY);

	/* finish printing */
	pDCPrint->EndPage();
	pDCPrint->EndDoc();
	pDCPrint->Detach();
	DeleteDC(hPrnDC);

	/* Restore original cursor */
	SetCursor(hCursorOld);

	efree((char *)bmiInfo);
}
