/*
 *		SUBWIN.C											vi:ts=4
 *
 *      Copyright (c) Eddy Carroll, September 1994.
 *
 *		This module handles all the windows other than the main window.
 *		That is, the functions window, settings window and format
 *		window.
 */		

#include "system.h"
#include "snoopdos.h"
#include "gui.h"

#if 0
#define DB(s)	KPrintF(s)
#else
#define DB(s)
#endif

/*
 *		These are used by all the requesters to decide what options to do
 *		when leaving a requester (make current settings permanent or
 *		restore the old settings)
 */
#define SEL_NONE  		0		/* User didn't select an option yet		*/
#define SEL_USE			1		/* User selected USE in a requester		*/
#define SEL_CANCEL		2		/* User selected CANCEL in a requester	*/

/*
 *		These control which items are displayed in the System and DOS
 *		columns in the function window.
 */
#define COL_SELECTED	0		/* Column shows selected items			*/
#define COL_ALL			1		/* Column shows all items				*/
#define COL_NONE		2		/* Column shows no items				*/

#define SETBUTTON_FILE	0		/* Little image of a file folder		*/
#define SETBUTTON_FONT	1		/* Little image of a font folder		*/
#define SETBUTTON_MAX	2		/* Anything above this is a string ID	*/

/*
 *		These two are imported from SNOOPDOS.C
 */
extern char	SnoopDosTitle[];

/*
 *		Variables for the format editor
 */
int FBoxA_Left;				/* X pos of left box in format window	*/
int FBoxA_Top;				/* Y pos of left box in format window	*/
int FBoxA_Width;			/* Width of left box in format window	*/
int FBoxA_Height;			/* Height of left box in format window	*/
int FBoxA_NumChars;			/* No. of characters across left box	*/
int FBoxA_CurLines;			/* No. of lines currently in left box	*/

int FBoxB_Left;				/* X pos of right box in format window	*/
int FBoxB_Top;				/* Y pos of right box in format window	*/
int FBoxB_Width;			/* Hidth of right box in format window	*/
int FBoxB_Height;			/* Weight of right box in format window	*/
int FBoxB_NumChars;			/* No. of characters across right box	*/
int FBoxB_CurLines;			/* No. of lines currently in right box	*/

int FBoxSpacing;			/* Distance between box text baselines	*/
int FBoxSelected;			/* Line number of right box selection	*/
							/* (or FBOX_NOSELECT / FBOX_SELECTLEFT) */

int FBoxDeltaX;				/* Offset from mouse to corner of drag	*/
int FBoxDeltaY;				/* Offset from mouse to corner of drag	*/
int FBoxDragSelect;			/* Currently selected drag drop area	*/
int FormatCurLMB;			/* Current state of left mouse button	*/
int FormatMovingField;		/* If true, currently dragging field	*/
int FormatDragBox;			/* Box we're dragging from				*/
int FormatDragLine;			/* Line we're dragging from				*/

char FBoxA_FormString[20];	/* sprintf format string for left box	*/
char FBoxB_FormString[20];	/* sprintf format string for right box	*/

char FormatSaveFormat[MAX_FORM_LEN];	/* Save area for format ed	*/

/*
 *		These next two arrays are indices into the FieldTypes[] array
 *		of format initialisers.
 */
typedef struct {
	int	type;							/* Type of this entry		*/
	int width;							/* Width of this entry		*/
} EditEvent;
	
extern FieldInit FieldTypes[];			/* Pulled in from BUFFER.C	*/

EditEvent CurrentFields[EF_COUNT+1];	/* Fields in current format	*/
EditEvent AvailableFields[EF_COUNT+1];	/* Fields still available	*/

Settings			SavedFuncSets;		/* Saved function settings	*/
Settings			SavedSetupSets;		/* Saved setup settings		*/

struct Gadget		*SetGadList;		/* Settings window gadgets	*/
struct Gadget		*FuncGadList;		/* Function window gadgets	*/
struct Gadget		*FormGadList;		/* Format window gadgets	*/
struct Image		*FileButtonImage;	/* Image of a file button	*/
struct Image		*FontButtonImage;	/* Image of a font button	*/

struct TextFont		*FormBufFont;		/* Format window listview	*/
struct TextFont		*FormGadFont;		/* Format window gadgets	*/

/*
 *		We don't usually free the Function window gadgets when we close
 *		the window (since it takes a long time to generate them). However,
 *		if we change window font, then we need a way to be able to change
 *		them. This variable shows you how.
 */
int		FuncSystemCol;		/* Mode of system column (all/none/sel)	*/
int		FuncDOSCol;			/* Mode of DOS column (all/none/select)	*/

UBYTE	SavedCols[GID_NUMFUNCSETS];	/* Currently selected settings	*/
UBYTE	TempCols[GID_NUMFUNCSETS];	/* Currently selected settings	*/

UBYTE	SetKeyboard[KB_SHORTCUT_SIZE];	/* For keyboard equivs		*/
UBYTE	FuncKeyboard[KB_SHORTCUT_SIZE];	/* For keyboard equivs		*/
UBYTE	FormKeyboard[KB_SHORTCUT_SIZE];	/* For keyboard equivs		*/

/*
 *		Now our font structures.
 *
 *		We do everything in terms of FontAttr structures, and whenever a
 *		window needs a font, it OpenFont's it itself. This has several
 *		advantages: it allows each window to dynamically select the
 *		best-fitting font, and it also ensures no problems if we have a
 *		window open with one font and the user than changes the font
 *		to something else -- the first window will still have a valid
 *		lock on the old font.
 */
extern struct TextAttr TopazFontAttr;
extern struct TextAttr SystemFontAttr;
extern struct TextAttr WindowFontAttr;
extern struct TextAttr BufferFontAttr;

extern struct {
	struct TextAttr *gadgetfa;
	struct TextAttr *bufferfa;
} MainWindowFontList[];				/* Imported from MAINWIN.C */

struct TextAttr *SubWindowFontList[] = {
	&WindowFontAttr,
	&SystemFontAttr,
	&TopazFontAttr,
	NULL
};

/*
 *		Next, all our gadget structures -- these are used to build the
 *		font-sensitive gadget windows at runtime.
 */
struct FuncGadgets {
	ULONG	gadgid;			/* Gadget ID					*/
	UWORD	stringid;		/* Name of checkmark gadget 	*/
	UBYTE	col;			/* Column (0, 1 or 2)			*/
	UBYTE	row;			/* Row (0 -- 13)				*/
};

struct FuncGadgets FuncGadgs[] = {
	/*
	 *		General controls
	 */
	GID_ONLYFAILS,		MSG_SHOWFAILS_GAD,		0, 0,
	GID_SHOWCLINUM,		MSG_SHOWCLI_GAD,		0, 1,
	GID_SHOWPATHS,		MSG_SHOWFULLPATHS_GAD,	0, 2,
	GID_USEDEVNAMES,	MSG_USEDEVNAMES_GAD,	0, 3,
	GID_MONPACKETS,		MSG_MONPACKETS_GAD,		0, 4,
	GID_MONALLPACKETS,	MSG_MONALLPACKETS_GAD,	0, 5,
	GID_MONROMCALLS,	MSG_MONROMCALLS_GAD,	0, 6,
	GID_IGNOREWB,		MSG_IGNOREWB_GAD,		0, 7,
	/*
	 *		System functions
	 */
	GID_FINDPORT,		MSG_FINDPORT_GAD,		1, 0,
	GID_FINDRESIDENT,	MSG_FINDRESIDENT_GAD,	1, 1,
	GID_FINDSEMAPHORE,	MSG_FINDSEMAPHORE_GAD,	1, 2,
	GID_FINDTASK,		MSG_FINDTASK_GAD,		1, 3,
	GID_LOCKSCREEN,		MSG_LOCKSCREEN_GAD,		1, 4,
	GID_OPENDEVICE,		MSG_OPENDEVICE_GAD,		1, 5,
	GID_OPENFONT,		MSG_OPENFONT_GAD,		1, 6,
	GID_OPENLIBRARY,	MSG_OPENLIBRARY_GAD,	1, 7,
	GID_OPENRESOURCE,	MSG_OPENRESOURCE_GAD,	1, 8,
	GID_READTOOLTYPES,	MSG_READTOOLTYPES_GAD,	1, 9,
	GID_SENDREXX,    	MSG_SENDREXX_GAD,		1, 10,

	/*
	 *		AmigaDOS Functions
	 */
	GID_CHANGEDIR,		MSG_CHANGEDIR_GAD,		2, 0,
	GID_DELETE,			MSG_DELETE_GAD,			2, 1,
	GID_EXECUTE,		MSG_EXECUTE_GAD,		2, 2,
	GID_GETVAR,			MSG_GETVAR_GAD,			2, 3,
	GID_LOADSEG,		MSG_LOADSEG_GAD,		2, 4,
	GID_LOCKFILE,		MSG_LOCKFILE_GAD,		2, 5,
	GID_MAKEDIR,		MSG_MAKEDIR_GAD,		2, 6,
	GID_MAKELINK,		MSG_MAKELINK_GAD,		2, 7,
	GID_OPENFILE,		MSG_OPENFILE_GAD,		2, 8,
	GID_RENAME,			MSG_RENAME_GAD,			2, 9,
	GID_RUNCOMMAND,		MSG_RUNCOMMAND_GAD,		2, 10,
	GID_SETVAR,			MSG_SETVAR_GAD,			2, 11,
	GID_SYSTEM,			MSG_SYSTEM_GAD,			2, 12,

	0,					0,						0, 0
};

/*
 *		This structure defines the gadgets in the settings window. There
 *		are three basic types of gadgets supported: cycle, string and
 *		display. For cycle gadgets, data1 is the index of the current
 *		selection, data2 is a 0-terminated array of message IDs that
 *		will hold the possible ListView choices, and data3 is a pointer
 *		to an array of string pointers of the same size (including
 *		terminating NULL) that will be filled in by the code with actual
 *		pointers to those strings.
 *		
 *		For string and display gadgets, data2 is a pointer to the string
 *		to be displayed inside the gadget and data1/data3 are unused.
 *		However, if promptid is specified, then data1 is the message
 *		number of the prompt to be used in the button instead of defaulting
 *		to a question mark (the width is automatically adjusted).
 */		
struct SetGadgets {
	UWORD	gadgid;			/* Gadget ID								*/
	UWORD	stringid;		/* ID of label name         				*/
	ULONG	gtype;			/* Gadget type (cycle, string, or display)	*/
	UWORD	promptid;		/* If non-zero, ID of optional "?" button  	*/
	ULONG	data1;			/* See above								*/
	void	*data2;			/* See above								*/
	void	*data3;			/* See above								*/
	UBYTE	col;			/* Start col (0 or 1)						*/
	UBYTE	cwidth;			/* Column width (0, 1 or 2)					*/
	UBYTE	row;			/* Row (0 to 6)								*/
};

int SetCylHide[] = {
	MSG_CYL_HIDE_GAD, MSG_CYL_ICONIFY_GAD, MSG_CYL_TOOLSMENU_GAD,
	MSG_CYL_NONE_GAD, 0
};

int SetCylOpenOn[] = {
	MSG_CYL_DEFSCREEN_GAD, MSG_CYL_FRONTSCREEN_GAD, MSG_CYL_NAMEDSCREEN_GAD, 0
};

int SetCylLogMode[] = {
	MSG_CYL_PROMPT_USER_GAD, MSG_CYL_APPEND_GAD, MSG_CYL_OVERWRITE_GAD,
	MSG_CYL_SERIALPORT_GAD, 0
};

int SetCylFileIO[] = {
	MSG_CYL_AUTOMATIC_GAD, MSG_CYL_IMMEDIATE_GAD, MSG_CYL_BUFFERED_GAD, 0
};

char *SetCylHideText[HIDE_MAX+1];
char *SetCylOpenOnText[SCREEN_MAX+1];
char *SetCylLogModeText[LOGMODE_MAX+1];
char *SetCylFileIOText[FILE_MAX+1];


/*
 *		If you re-arrange the order of these gadget definitions,
 *		make sure you re-order the enum's that follow below as well.
 */
struct SetGadgets SetGadgs[] = {
	GID_BUFFORMAT,	MSG_BUFFORMAT_GAD,	STRING_KIND,	GID_FORMATEDIT,	
														MSG_FORMATEDIT_GAD,
					NULL,				0,					0,	2,	5,

	GID_LOGFORMAT,	MSG_LOGFORMAT_GAD,	STRING_KIND,	GID_FORMATCOPY,
														MSG_FORMATCOPYF_GAD,
					NULL,				0,					0,	2,	6,
	
	GID_HIDEMETHOD,	MSG_HIDEMETHOD_GAD,	CYCLE_KIND,		0,	0,
					SetCylHide,			SetCylHideText,	 	0,	0,	0,
	
	GID_OPENON,		MSG_OPENON_GAD,		CYCLE_KIND,		0,	0,
					SetCylOpenOn,		SetCylOpenOnText,	0,	0,	1,

	GID_LOGMODE,	MSG_LOGMODE_GAD,	CYCLE_KIND,		0,	0,
					SetCylLogMode,		SetCylLogModeText,	0,	0,	2,

	GID_FILEIO,		MSG_FILEIO_GAD,		CYCLE_KIND,		0,	0,
					SetCylFileIO,		SetCylFileIOText,	0,	0,	3,
	
	GID_HOTKEY,		MSG_HOTKEY_GAD,		STRING_KIND,	0,	0,
					NULL,				0,					1,	1,	0,
	
	GID_SCREENNAME,	MSG_SCREENNAME_GAD,	STRING_KIND,	0,	0,
					NULL,				0,					1,	1,	1,
	
	GID_LOGFILE,	MSG_LOGFILE_GAD,	STRING_KIND,	GID_FILEPROMPT,
														SETBUTTON_FILE,
					NULL,				0,					1,	1,	2,

	GID_WINDOWFONT,	MSG_WINDOWFONT_GAD,	TEXT_KIND,		GID_WFONTPROMPT,
														SETBUTTON_FONT,
					NULL,				0,					1,	1,	3,
	
	GID_BUFFERFONT,	MSG_BUFFERFONT_GAD,	TEXT_KIND,		GID_BFONTPROMPT,
														SETBUTTON_FONT,
					NULL,				0,					1,	1,	4,

	0
};

/*
 *		The following gadgets should match the _order_ that the above
 *		gadgets are laid out in, since they are used as indices into the
 *		above array.
 */
typedef enum {
	SG_BufferFormat,
	SG_LogfileFormat,
	SG_HideMethod,
	SG_ScreenType,
	SG_LogMode,
	SG_FileIOType,
	SG_HotKey,
	SG_ScreenName,
	SG_LogFile,
	SG_WindowFont,
	SG_BufferFont,
} SetGadgetEnums;

/*
 *		Arrays of gadget names for calculating minimum width
 */

/*
 *		Miscellaneous gadgets in functions window
 */
int MiscColNames[] = {
	MSG_SHOWFAILS_GAD,
	MSG_SHOWCLI_GAD,
	MSG_SHOWFULLPATHS_GAD,
	MSG_USEDEVNAMES_GAD,
	MSG_MONPACKETS_GAD,
	MSG_MONALLPACKETS_GAD,
	MSG_IGNOREWB_GAD,
	0
};

/*
 *		System gadgets in functions window
 */
int SysColNames[] = {
	MSG_FINDPORT_GAD,     
	MSG_FINDRESIDENT_GAD, 
	MSG_FINDSEMAPHORE_GAD,
	MSG_FINDTASK_GAD,     
	MSG_LOCKSCREEN_GAD,   
	MSG_OPENDEVICE_GAD,   
	MSG_OPENFONT_GAD,     
	MSG_OPENLIBRARY_GAD,  
	MSG_OPENRESOURCE_GAD, 
	MSG_READTOOLTYPES_GAD,
	MSG_SENDREXX_GAD, 
	0
};

/*
 *		AmigaDOS gadgets in functions window
 */
int DOSColNames[] = {
	MSG_CHANGEDIR_GAD, 
	MSG_DELETE_GAD,    
	MSG_EXECUTE_GAD,   
	MSG_GETVAR_GAD,    
	MSG_LOADSEG_GAD,   
	MSG_LOCKFILE_GAD,  
	MSG_MAKEDIR_GAD,   
	MSG_MAKELINK_GAD,  
	MSG_OPENFILE_GAD,  
	MSG_RENAME_GAD,    
	MSG_RUNCOMMAND_GAD,
	MSG_SETVAR_GAD,    
	MSG_SYSTEM_GAD,
	0
};

/*
 *		Aditional gadgets in function window
 */
int FuncCycleText[]		= { MSG_ALL_GAD, MSG_NONE_GAD, MSG_SELECTED_GAD, 0 };
int FuncCycleName[]		= { MSG_SELSYSTEM_GAD, MSG_SELDOS_GAD, 0 };
int UseCancelUndoText[] = { MSG_USE_GAD, MSG_CANCEL_GAD, MSG_UNDO_GAD, 0 };

/*
 *		Left column gadgets names in settings window
 */
int SettingsLeftNames[] = {
	MSG_HIDEMETHOD_GAD,
	MSG_OPENON_GAD,
	MSG_LOGMODE_GAD,
	MSG_FILEIO_GAD,
	MSG_BUFFORMAT_GAD,
	MSG_LOGFORMAT_GAD,
	0
};

/*
 *		Right column gadget names in settings window
 */
int SettingsRightNames[] = {
	MSG_HOTKEY_GAD,
	MSG_SCREENNAME_GAD,
	MSG_LOGFILE_GAD,
	MSG_WINDOWFONT_GAD,
	MSG_BUFFERFONT_GAD,
	0
};

/*
 *		Two mini buttons attached to format gadgets in settings window
 *		Note that we have two types of possibility: one for fixed point
 *		fonts and one for proportional fonts. This is so that we can
 *		arrange for the two strings "Edit..." and "Copy" to always
 *		look properly aligned, regardless of font type. If we didn't do
 *		this, then with fixed point fonts, "Copy" looks mis-aligned
 *		with respect to "Edit..." because they are different lengths.
 */
int SetMiniButtonsP[] = {
	MSG_FORMATCOPYP_GAD,
	MSG_FORMATEDIT_GAD,
	0
};

int SetMiniButtonsF[] = {
	MSG_FORMATCOPYF_GAD,
	MSG_FORMATEDIT_GAD,
	0
};

/*
 *		Left column gadget contents in settings
 */
int SettingsLeftContents[] = {
	MSG_CYL_HIDE_GAD,
	MSG_CYL_ICONIFY_GAD,
	MSG_CYL_TOOLSMENU_GAD,
	MSG_CYL_DEFSCREEN_GAD,
	MSG_CYL_FRONTSCREEN_GAD,
	MSG_CYL_NAMEDSCREEN_GAD,
	MSG_CYL_PROMPT_USER_GAD,
	MSG_CYL_APPEND_GAD,
	MSG_CYL_OVERWRITE_GAD,
	MSG_CYL_AUTOMATIC_GAD,
	MSG_CYL_IMMEDIATE_GAD,
	MSG_CYL_BUFFERED_GAD,
	0
};

/*
 *		Mega struct that collects all the data needed to create useful BOBs.
 */
typedef struct MyGel {
	struct GelsInfo  gelinfo;			/* GelInfo for entire structure	*/
	WORD			 nextline[8];		/* Nextline data				*/ 
	WORD			 *lastcolor[8];		/* Last colour data				*/
	struct RastPort	 *mainrast;			/* Rastport bob is displayed in	*/
	struct collTable colltable;			/* Collision table				*/
	struct VSprite	 vshead;			/* Head sprite anchor			*/
	struct VSprite	 vstail;			/* Tail sprite anchor			*/
	struct VSprite	 bobvsprite;		/* Vsprite used for bob			*/
	struct Bob		 bob;				/* Data for a single bob		*/
	struct RastPort  rastport;			/* Rastport for our BOB			*/
	struct BitMap	 bitmap;			/* Bitmap for our BOB			*/
	ULONG			 planesize;			/* Size of one plane in bob		*/
	UBYTE			*planedata;			/* Pointer to first plane		*/
	UBYTE	 		*chipdata;			/* Pointer to all CHIP RAM data	*/
	ULONG			 chipsize;			/* Total size of allocated CHIP	*/
} MyGel;

MyGel	*TextBob;						/* Info about GEL/BOBs			*/

/*
 *		Some prototypes private to this file
 */
MyGel *CreateBob(struct RastPort *rport, int width, int height, int transp);
void FreeBob(MyGel *mygel);
void UpdateBob(int x, int y);
int  PickupBob(int hit, int x, int y);
void DropBob();

/*****************************************************************************
 *
 *		Start of functions!
 *
 *****************************************************************************/

/*
 *		CleanupSubWindows()
 *
 *		Frees any resources associated with this module
 */
void CleanupSubWindows(void)
{
	PurgeFuncGadgets = 1;
	CloseFunctionWindow();
	CloseSettingsWindow();
	CloseFormatWindow();
}

/*
 *		GetFuncName(gadgetid)
 *
 *		Returns a pointer to a string describing the function name which
 *		matches the gadget ID passed in. This is used by PATCHES.C when
 *		it is unpatching the patched functions at exit time to identify
 *		which function can't be unpatched in the event of a problem.
 */
char *GetFuncName(int gadgetid)
{
	struct FuncGadgets *fg = FuncGadgs;

	if (gadgetid == GID_SENDREXX)	/* Special-case this multi-function one */
		return MSG(MSG_PACKET_NAME);

	for (fg = &FuncGadgs[0]; fg->gadgid; fg++) {
		if (fg->gadgid == gadgetid)
			return MSG(fg->stringid);
	}
	return ("<Unknown Function>");
}

/*
 *		CreateFunctionGadgets(fontattr, getsize, &width, &height)
 *
 *		Creates all the gadgets for the function window. If getsize
 *		is true, then the width and height values are filled in with
 *		the dimensions of the window needed to hold the gadgets. In
 *		this case, NULL is returned if the window would be too big to
 *		fit on the current screen, or non-NULL if width and height
 *		were successfully initialised.
 *
 *		If getsize is zero, then the actual gadgets are created and
 *		a pointer to the gadget list is returned, or NULL if an
 *		error occurred (typically, out of memory).
 */
struct Gadget *CreateFunctionGadgets(struct TextAttr *fontattr,
									 int getsize, int *pwidth, int *pheight)
{
	static char *cyltext[4];
	struct TextFont *font;
	struct FuncGadgets *fg;
	struct NewGadget ng;
	struct Gadget *gadlist;
	struct Gadget *gad;
	UWORD spacing[3];
	UWORD yoffset[3]; 
	UWORD colpos[3];
	int heightadjust = 0;
	int width;
	int height;
	int gwidth;
	int gheight;
	int fonty;
	int colspace = 30;
	int topline;
	int w1, w2;

	if (!FuncVI) {
		FuncVI = GetVisualInfoA(SnoopScreen, NULL);
		if (!FuncVI)
			return (NULL);
	}
	font = MyOpenFont(fontattr);
	if (!font)
		return (NULL);

	fonty		= font->tf_YSize;
	gheight		= fonty   + 3;
	gwidth 		= gheight + 15;
	
	spacing[0]  = fonty + 4;
	spacing[1]  = fonty + 4;
	spacing[2]  = fonty + 4;
	yoffset[1]	= TitlebarHeight + fonty/2;
	yoffset[2]	= yoffset[1];
	yoffset[0]	= yoffset[1] + 10 * spacing[1] - 7 * spacing[0];

	colpos[0]	= BorderLeft + FUNC_MARGIN;
	colpos[1]	= colpos[0]  + MaxTextLen(font, MiscColNames)
				  + gwidth + colspace;
	w1			= MaxTextLen(font, SysColNames) + gwidth;
	w2			= GetTextLen(font, MSG(MSG_SYSFUNCS_GAD));
	colpos[2]	= colpos[1]  + MAX(w1, w2) + colspace;
	w1			= MaxTextLen(font, DOSColNames) + gwidth;
	w2			= GetTextLen(font, MSG(MSG_DOSFUNCS_GAD));
	width		= colpos[2]  + MAX(w1, w2) + FUNC_MARGIN + BorderRight + 8;
	height      = yoffset[2] + spacing[2] * 15 + BorderBottom;

	topline = yoffset[2];
	if (ScreenHeight < 256 && height > ScreenHeight) {
		/*
		 *		For medium and low resolution screens, shave another
		 *		few pixels from the height. Our aim is to be able to
		 *		open on a 640 x 200 screen with a 15 point screen font.
		 */
		heightadjust = -2;
		height      += heightadjust * 2;
	}
	if ((height + spacing[2] * 2) <= ScreenHeight) {
		int disp = (spacing[2] * 3) / 2;

		yoffset[0] += disp;
		yoffset[1] += disp;
		yoffset[2] += disp;
		height     += disp;
	}

	if (width > ScreenWidth || height > ScreenHeight) {
		CloseFont(font);
		return (NULL);
	}
	if (getsize) {
		CloseFont(font);
		*pwidth  = width;
		*pheight = height;
		return (struct Gadget *)(-1);
	}

	/*
	 *		Now create our new gadgets
	 */	
	ng.ng_Height		= gheight; 
	ng.ng_Width			= gwidth;  
	ng.ng_TextAttr		= fontattr;
	ng.ng_VisualInfo	= FuncVI;
	ng.ng_Flags         = PLACETEXT_RIGHT;

	gadlist = NULL;
	gad = CreateContext(&gadlist);
	if (!gad)
		goto fgad_failed;

	for (fg = &FuncGadgs[0]; fg->gadgid; fg++) {
		int col = fg->col;

		ng.ng_LeftEdge  	= colpos[col];
		ng.ng_TopEdge   	= fg->row * spacing[col] + yoffset[col];
		ng.ng_GadgetText	= MSG(fg->stringid);
		ng.ng_GadgetID		= fg->gadgid;

		gad = CreateGadget(CHECKBOX_KIND, gad, &ng,
						   GT_Underscore,	'_',
						   GTCB_Scaled,     TRUE,
						   TAG_DONE);
		if (!gad)
			goto fgad_failed;
		Gadget[fg->gadgid] = gad;
		AddKeyShortcut(FuncKeyboard, fg->gadgid, fg->stringid);
	}

	/*
	 *		Now create the remaining window gadgets
	 */
	ng.ng_LeftEdge 		= colpos[0]
						  + GetTextLen(font, MSG(MSG_MATCHNAME_GAD)) + 10;
	ng.ng_TopEdge  		= yoffset[1] + (23 * spacing[1]) / 2;
	ng.ng_GadgetText	= MSG(MSG_MATCHNAME_GAD);
	ng.ng_GadgetID		= GID_MATCHNAME;
	ng.ng_Width         = colpos[2] - ng.ng_LeftEdge - colspace + 8;
	ng.ng_Height		= gheight + 3;
	ng.ng_Flags         = PLACETEXT_LEFT;

	gad = CreateGadget(STRING_KIND,		gad, &ng,
					   GT_Underscore,	'_',
					   GTST_MaxChars,	MAX_STR_LEN,
					   GTST_String,	 	CurSettings.Func.Pattern,
					   TAG_DONE);
	if (!gad)
		goto fgad_failed;
	Gadget[GID_MATCHNAME] = gad;
	AddKeyShortcut(FuncKeyboard, GID_MATCHNAME, MSG_MATCHNAME_GAD);

	ng.ng_GadgetText	= MSG(MSG_SELSYSTEM_GAD);
	ng.ng_GadgetID		= GID_SELSYSTEM;
	ng.ng_Height		= gheight + 3;
	ng.ng_Width			= MaxTextLen(font, FuncCycleText) + 40;
	ng.ng_LeftEdge		= colpos[0] + MaxTextLen(font, FuncCycleName) + 10;
	ng.ng_TopEdge		= yoffset[1];
	cyltext[0]			= MSG(MSG_SELECTED_GAD);
	cyltext[1]			= MSG(MSG_ALL_GAD);
	cyltext[2]			= MSG(MSG_NONE_GAD);
	cyltext[3]			= NULL;

	gad = CreateGadget(CYCLE_KIND,		gad, &ng,
					   GT_Underscore,	'_',
					   GTCY_Labels,		cyltext,
					   GTCY_Active,		COL_SELECTED,
					   TAG_DONE);
	if (!gad)
		goto fgad_failed;
	Gadget[GID_SELSYSTEM] = gad;
	AddKeyShortcut(FuncKeyboard, GID_SELSYSTEM, MSG_SELSYSTEM_GAD);

	ng.ng_GadgetText	= MSG(MSG_SELDOS_GAD);
	ng.ng_GadgetID		= GID_SELDOS;
	ng.ng_TopEdge		= (yoffset[0] + yoffset[1]) / 2;

	gad = CreateGadget(CYCLE_KIND,		gad, &ng,
					   GT_Underscore,	'_',
					   GTCY_Labels,		cyltext,
					   GTCY_Active,		COL_SELECTED,
					   TAG_DONE);
	if (!gad)
		goto fgad_failed;
	Gadget[GID_SELDOS] = gad;
	AddKeyShortcut(FuncKeyboard, GID_SELDOS, MSG_SELDOS_GAD);

	ng.ng_Width			= MaxTextLen(font, UseCancelUndoText) + 32;
	ng.ng_Height		= fonty + GadgetHeight;
	ng.ng_GadgetText	= MSG(MSG_USE_GAD);
	ng.ng_GadgetID		= GID_FUNCUSE;
	ng.ng_LeftEdge		= colpos[0];
	ng.ng_TopEdge		= yoffset[2] + heightadjust + (27 * spacing[2]) / 2;
	ng.ng_Flags			= PLACETEXT_IN;

	gad = CreateGadget(BUTTON_KIND,		gad, &ng,
					   GT_Underscore,	'_',
					   TAG_DONE);
	if (!gad)
		goto fgad_failed;
	Gadget[GID_FUNCUSE] = gad;
	AddKeyShortcut(FuncKeyboard, GID_FUNCUSE, MSG_USE_GAD);
	
	ng.ng_LeftEdge		= width - BorderRight - FUNC_MARGIN - ng.ng_Width;
	ng.ng_GadgetText	= MSG(MSG_CANCEL_GAD);
	ng.ng_GadgetID		= GID_FUNCCANCEL;

	gad = CreateGadget(BUTTON_KIND,		gad, &ng,
					   GT_Underscore,	'_',
					   TAG_DONE);
	if (!gad)
		goto fgad_failed;
	Gadget[GID_FUNCCANCEL] = gad;
	AddKeyShortcut(FuncKeyboard, GID_FUNCCANCEL, MSG_CANCEL_GAD);

	ng.ng_LeftEdge		= (ng.ng_LeftEdge + BorderLeft + FUNC_MARGIN) / 2;
	ng.ng_GadgetText	= MSG(MSG_UNDO_GAD);
	ng.ng_GadgetID		= GID_FUNCUNDO;

	gad = CreateGadget(BUTTON_KIND,		gad, &ng,
					   GT_Underscore,	'_',
					   TAG_DONE);
	if (!gad)
		goto fgad_failed;
	Gadget[GID_FUNCUNDO] = gad;
	AddKeyShortcut(FuncKeyboard, GID_FUNCUNDO, MSG_UNDO_GAD);
	
	if (topline != yoffset[2]) {
		/*
		 *		If there's room at the top of the window, create two text
		 *		gadgets with the column headings for the System and DOS cols.
		 */
		ng.ng_TopEdge		= topline;
		ng.ng_LeftEdge		= colpos[1];
		ng.ng_GadgetText	= "";
		ng.ng_GadgetID		= 0;

		gad = CreateGadget(TEXT_KIND,	gad, &ng,
						   GTTX_Text,	MSG(MSG_SYSFUNCS_GAD),
						   TAG_DONE);
		if (!gad)
			goto fgad_failed;

		ng.ng_LeftEdge		= colpos[2];
		gad = CreateGadget(TEXT_KIND,	gad, &ng,
						   GTTX_Text,	MSG(MSG_DOSFUNCS_GAD),
						   TAG_DONE);
		if (!gad)
			goto fgad_failed;
	}
	PurgeFuncGadgets = 0;	/* No need to purge now */
	CloseFont(font);
	return (gadlist);

fgad_failed:
	if (gadlist)
		FreeGadgets(gadlist);
	CloseFont(font);
	return (NULL);
}

/*
 *		OpenFunctionWindow()
 *
 *		Opens the functions window with all the functions gadgets.
 *		Returns TRUE for success, FALSE for failure.
 *		The window will contain all necessary gadgets.
 *
 *		As with the Settings window, we try a variety of fonts until we
 *		find one that fits onto the screen.
 */
int OpenFunctionWindow(void)
{
	static struct TextAttr funcfontattr =
		{ "empty-storage-for-func-fonts...", 0, FS_NORMAL, FPB_DISKFONT};

	static int width;		/* Maintained from call to call */
	static int height;

	int left = CurSettings.FuncWinLeft;
	int top  = CurSettings.FuncWinTop;
	struct TextAttr *fontattr = NULL;
	struct Window *win;
	int i;

	CheckSegTracker();
	if (FuncWindow) {
		WindowToFront(FuncWindow);
		ActivateWindow(FuncWindow);
		return (TRUE);
	}
	if (!CheckForScreen())
		return (FALSE);

	SavedFuncSets = CurSettings;		/* Save so we can restore later */

	if (!FuncGadList) {
		/*
		 *		Find out what dimensions our new window should be; to do
		 *		this, we calculate the size with a variety of fonts until
		 *		we find one that works.
		 */
		int i;

		for (i = 0; fontattr = SubWindowFontList[i]; i++) {
			if (CreateFunctionGadgets(fontattr, TRUE, &width, &height))
				break;
		}
		if (!fontattr) {
			ShowError(MSG(MSG_ERROR_OPENFUNC));
			return (FALSE);
		}
		/*
		 *		We need to make a copy of the fontattr now so that if
		 *		the user changes the font, all our gadgets don't suddenly
		 *		inherit the new font (at least until the window has been
		 *		closed and re-opened!)
		 */
		strcpy(funcfontattr.ta_Name, fontattr->ta_Name);
		funcfontattr.ta_YSize = fontattr->ta_YSize;
		fontattr = &funcfontattr;
	}
	if (left == -1)  left = (ScreenWidth  - width)  / 2;
	if (top  == -1)  top  = (ScreenHeight - height) / 2;

	win = OpenWindowTags(NULL,
						 WA_Title,			MSG(MSG_FUNCTION_TITLE),
						 WA_IDCMP,			IDCMP_CLOSEWINDOW	 |
						 					IDCMP_REFRESHWINDOW	 |
											IDCMP_NEWSIZE		 |
											IDCMP_INACTIVEWINDOW |
											IDCMP_RAWKEY		 |
											BUTTONIDCMP,
						 WA_Left,  			left,
						 WA_Top,  			top,
						 WA_Width,			width,
						 WA_Height,			height,
						 WA_Flags,			WFLG_DRAGBAR		 |
						 					WFLG_DEPTHGADGET	 |
											WFLG_ACTIVATE		 |
											WFLG_RMBTRAP		 |
											WFLG_NEWLOOKMENUS,
						 RefreshTag,		TRUE,
						 WA_NoCareRefresh,	NoCareRefreshBool,
						 WA_PubScreen,		SnoopScreen,
						 TAG_DONE);
	if (!win) {
		ShowError(MSG(MSG_ERROR_OPENFUNC));
		return (FALSE);
	}

	if (!FuncGadList) {
		FuncGadList = CreateFunctionGadgets(fontattr, 0, 0, 0);
		if (!FuncGadList) {
			CloseWindow(win);
			ShowError(MSG(MSG_ERROR_OPENFUNC));
			return (FALSE);
		}
	}

	/*
	 *		Now update function gadgets to reflect current settings
	 *		for AmigaDOS functions. Under V39+, we can do this before
	 *		adding them to the window. Under V37, we have to do it
	 *		after adding them to the window.
	 */
	if (GadToolsBase->lib_Version < 39) {
		AddGList(win, FuncGadList, -1, -1, NULL);
		RefreshGList(FuncGadList, win, NULL, -1);
		GT_RefreshWindow(win, NULL);
		for (i = FIRST_BOOL_GADGET; i <= LAST_BOOL_GADGET; i++) {
			if (Gadget[i]) {
				GT_SetGadgetAttrs(Gadget[i], win, NULL,
								  GTCB_Checked, CurSettings.Func.Opts[i],
								  TAG_DONE);
			}
		}
	} else { /* GadToolsBase->lib_Version >= 39 */
		for (i = FIRST_BOOL_GADGET; i <= LAST_BOOL_GADGET; i++) {
			if (Gadget[i]) {
				GT_SetGadgetAttrs(Gadget[i], NULL, NULL,
								  GTCB_Checked, CurSettings.Func.Opts[i],
								  TAG_DONE);
			}
		}
		AddGList(win, FuncGadList, -1, -1, NULL);
		RefreshGList(FuncGadList, win, NULL, -1);
		GT_RefreshWindow(win, NULL);
	}

	/*
	 *		All done, so initialise some globals and return
	 */
	FuncWindow		= win;
	FuncWindowPort	= win->UserPort;
	FuncWindowMask	= 1 << FuncWindowPort->mp_SigBit;
	FuncSystemCol   = COL_SELECTED;
	FuncDOSCol		= COL_SELECTED;
	if (DisableNestCount)
		DisableWindow(FuncWindow, &FuncRequester);
	return (TRUE);
}

/*
 *		CreateSettingsGadgets(fontattr, getsize, &pwidth, &pheight)
 *
 *		Creates the gadgets list for the settings window using the specified
 *		window font. If the window would be to big for the current screen
 *		using this font, or if there is a problem creating the gadgets,
 *		returns NULL.
 *
 *		The returned gadget list should be passed to FreeGadgets() when
 *		it is no longer required.
 *
 *		If getsize is true, then pwidth and pheight are filled in with the
 *		dimensions of the window size needed to hold the gadgets, but
 *		no actual gadget creation is carried out. In this case, NULL is
 *		returned for a failure (couldn't open font, or window size would
 *		exceed current screen size) and non-NULL for success.
 */
struct Gadget *CreateSettingsGadgets(struct TextAttr *fontattr,
									 int getsize, int *pwidth, int *pheight)
{
	struct SetGadgets *sg;
	struct NewGadget ng;
	struct NewGadget ngbut;
	struct TextFont *font;
	struct Gadget *gadlist;
	struct Gadget *gad;
	UWORD spacing;
	UWORD yoffset;
	UWORD colpos[2];
	UWORD colwidth[3];
	int miniwidth;
	int width;
	int height;
	int gheight;
	int fonty;
	int fontx;

	if (!SetVI) {
		SetVI = GetVisualInfoA(SnoopScreen, NULL);
		if (!SetVI)
			return (NULL);
	}
	font = MyOpenFont(fontattr);
	if (!font)
		return (NULL);
	
	fonty		= font->tf_YSize;
	fontx		= font->tf_XSize;

	gheight		= fonty + GadgetHeight;
	spacing     = fonty + GadgetSpacing;
	yoffset		= TitlebarHeight + fonty/2;
	colpos[0]	= BorderLeft + SET_MARGIN
				  + MaxTextLen(font, SettingsLeftNames) + 7;
	colwidth[0] = MaxTextLen(font, SettingsLeftContents) + 40;
	colpos[1]	= colpos[0] + colwidth[0] + SET_MARGIN*2
							  + MaxTextLen(font, SettingsRightNames);
	colwidth[1] = fontx * 24; /* Room for about 24 chars in string gadgets */
	colwidth[2] = colwidth[1] + colpos[1] - colpos[0];	/* Dual width */

	/*
	 *		See SetMiniButtons[] definitions for explanation of this bit
	 */
	if (font->tf_Flags & FPF_PROPORTIONAL) {
		miniwidth = MaxTextLen(font, SetMiniButtonsP) + 20;
		SetGadgs[1].data1 = MSG_FORMATCOPYP_GAD;
	} else {
		miniwidth = MaxTextLen(font, SetMiniButtonsF) + 20;
		SetGadgs[1].data1 = MSG_FORMATCOPYF_GAD;
	}

	width		= colpos[1] + colwidth[1] + SET_MARGIN + BorderRight;
	height      = yoffset + spacing * 8 + BorderBottom;

	if (width > ScreenWidth || height >ScreenHeight) {
		CloseFont(font);
		return (NULL);
	}
	if (getsize) {
		*pwidth  = width;
		*pheight = height;
		CloseFont(font);
		return (struct Gadget *)(-1);
	}

	/*
	 *		Okay, looks like our size is okay so now prime our gadget
	 *		table with the current settings values to use when the
	 *		gadget is created.
	 *
	 */
#define DEF_CYCLE(x)	SetGadgs[SG_##x].data1
#define DEF_STRING(x)	SetGadgs[SG_##x].data2

	DEF_CYCLE(HideMethod)	  = CurSettings.Setup.HideMethod;
	DEF_CYCLE(ScreenType)	  = CurSettings.Setup.ScreenType;
	DEF_CYCLE(LogMode)		  = CurSettings.Setup.LogMode;
	DEF_CYCLE(FileIOType)	  = CurSettings.Setup.FileIOType;
	DEF_STRING(HotKey)		  = CurSettings.Setup.HotKey;
	DEF_STRING(ScreenName)	  = CurSettings.Setup.ScreenName;
	DEF_STRING(LogFile)		  = CurSettings.Setup.LogFile;
	DEF_STRING(WindowFont)	  = GetFontDesc(WindowFontDesc,
										    WindowFontName, WindowFontSize);
	DEF_STRING(BufferFont)	  = GetFontDesc(BufferFontDesc,
									     	BufferFontName, BufferFontSize);
	DEF_STRING(BufferFormat)  = BufFormat;
	DEF_STRING(LogfileFormat) = LogFormat;

	/*
	 *		Next, onto the gadget creation itself
	 */
	ng.ng_Height		= gheight; 
	ng.ng_TextAttr		= fontattr;
	ng.ng_VisualInfo	= SetVI;
	ng.ng_Flags         = PLACETEXT_LEFT;

	ngbut.ng_Height		= gheight; 
	ngbut.ng_TextAttr	= fontattr;
	ngbut.ng_VisualInfo	= SetVI;
	ngbut.ng_Flags		= PLACETEXT_IN;

	FileButtonImage = CreateCustomImage(IMAGE_FILE, gheight+2-2*SquareAspect);
	FontButtonImage = CreateCustomImage(IMAGE_FONT, gheight);
	if (!FileButtonImage || !FontButtonImage) {
		CloseFont(font);
		return (NULL);
	}
	gadlist = NULL;
	gad = CreateContext(&gadlist);
	if (!gad) {
		CloseFont(font);
		return (NULL);
	}

	for (sg = &SetGadgs[0]; sg->gadgid; sg++) {
		ng.ng_LeftEdge  	= colpos[sg->col];
		ng.ng_Width			= colwidth[sg->cwidth];
		ng.ng_TopEdge   	= yoffset + sg->row * spacing;
		ng.ng_GadgetText	= MSG(sg->stringid);
		ng.ng_GadgetID		= sg->gadgid;
		ng.ng_Height		= gheight;

		if (sg->promptid) {
			/*
			 *		Create optional mini gadget to right of main gadget.
			 *		If data1 == 0 or data1 == 1 then use a file or font
			 *		BOOPSI gadget image, else use a Gadtools text button
			 *		containing the label corresponding to MSG(data1) else
			 *		use string ID indicated by data1
			 */
			ngbut.ng_GadgetID  = sg->promptid;
			ngbut.ng_TopEdge   = ng.ng_TopEdge;
			ngbut.ng_Height    = gheight;
			if (sg->gtype == STRING_KIND && !SquareAspect) {
				ngbut.ng_Height  += 2;
				ngbut.ng_TopEdge -= 1;
			}
			if (sg->data1 > SETBUTTON_MAX) {
				/*
				 *		Creating a text gadget
				 */
				ngbut.ng_LeftEdge   = ng.ng_LeftEdge + ng.ng_Width - miniwidth;
				ngbut.ng_Width		= miniwidth;
				ngbut.ng_GadgetText = MSG(sg->data1);
				gad = CreateGadget(BUTTON_KIND, gad, &ngbut,
								   GT_Underscore, '_',
								   TAG_DONE);
				if (!gad)
					goto sgad_failed;
				AddKeyShortcut(SetKeyboard, sg->promptid, sg->data1);
			} else {
				/*
				 *		Creating an image gadget
				 */
				struct Image *img = FileButtonImage;

				if (sg->data1 == SETBUTTON_FONT)
					img = FontButtonImage;

				ngbut.ng_Width		= img->Width;
				ngbut.ng_GadgetText = NULL;
				ngbut.ng_LeftEdge   = ng.ng_LeftEdge + ng.ng_Width -img->Width;
				gad = CreateGadget(GENERIC_KIND, gad, &ngbut, TAG_DONE);
				if (!gad)
					goto sgad_failed;

				/*
				 *		Now fill in the details of our gadget
				 */
				gad->Flags			|= GFLG_GADGIMAGE | GFLG_GADGHIMAGE;
				gad->Activation 	|= GACT_IMMEDIATE | GACT_RELVERIFY;
				gad->GadgetType		|= GTYP_BOOLGADGET;
				gad->GadgetRender	= img;
				gad->SelectRender	= img + 1;
			}
			Gadget[sg->promptid] = gad;
			ng.ng_Width	-= ngbut.ng_Width + 2;
		}
		switch (sg->gtype) {
			case STRING_KIND:
				if (!SquareAspect) {
					ng.ng_Height  += 2;
					ng.ng_TopEdge -= 1;
				}
				gad = CreateGadget(STRING_KIND, gad, &ng,
								   GT_Underscore,	'_',
								   GTST_String,		sg->data2,
								   GTST_MaxChars,	MAX_STR_LEN,
								   TAG_DONE);
				break;

			case TEXT_KIND:
				ng.ng_Width-= 2;
				ng.ng_LeftEdge++;
				gad = CreateGadget(TEXT_KIND, gad, &ng,
								   GT_Underscore,	'_',
								   GTTX_Text,		sg->data2,
								   GTTX_Border,		TRUE,
								   TAG_DONE);
				break;

			case CYCLE_KIND:
			{
				int *msgid = sg->data2;
				char **msg = sg->data3;
				int i;

				for (i = 0; msgid[i]; i++)
					msg[i] = MSG(msgid[i]);
				msg[i] = NULL;
				gad = CreateGadget(CYCLE_KIND, gad, &ng,
								   GT_Underscore,	'_',
								   GTCY_Active,		sg->data1,
								   GTCY_Labels,		msg,
								   TAG_DONE);
				break;
			}
		}
		if (!gad)
			goto sgad_failed;
		Gadget[sg->gadgid] = gad;
		AddKeyShortcut(SetKeyboard, sg->gadgid, sg->stringid);
	}

	ng.ng_GadgetText	= MSG(MSG_BUFFERSIZE_GAD);
	ng.ng_GadgetID		= GID_BUFFERSIZE;
	ng.ng_TopEdge		= yoffset + spacing * 4 + (1 - SquareAspect);
	ng.ng_Height		= gheight + (2 - 2 * SquareAspect);
	ng.ng_Width			= GetTextLen(font, "10000") + 14;
	ng.ng_LeftEdge		= colpos[0] + colwidth[0] - ng.ng_Width;

	gad = CreateGadget(INTEGER_KIND,	gad, &ng,
					   GT_Underscore,	'_',
					   GTIN_Number,		CurSettings.Setup.BufferSize,
					   TAG_DONE);
	if (!gad)
		goto sgad_failed;
	Gadget[GID_BUFFERSIZE] = gad;
	AddKeyShortcut(SetKeyboard, GID_BUFFERSIZE, MSG_BUFFERSIZE_GAD);

	ng.ng_Width			= MaxTextLen(font, UseCancelUndoText) + 32;
	ng.ng_Height		= fonty + GadgetHeight;
	ng.ng_GadgetText	= MSG(MSG_USE_GAD);
	ng.ng_GadgetID		= GID_SETUSE;
	ng.ng_LeftEdge		= BorderLeft + SET_MARGIN;
	ng.ng_TopEdge		= yoffset + 7 * spacing;
	ng.ng_Flags			= PLACETEXT_IN;

	gad = CreateGadget(BUTTON_KIND,		gad, &ng,
					   GT_Underscore,	'_',
					   TAG_DONE);
	if (!gad)
		goto sgad_failed;
	Gadget[GID_SETUSE] = gad;
	AddKeyShortcut(SetKeyboard, GID_SETUSE, MSG_USE_GAD);
	
	ng.ng_LeftEdge		= width - BorderRight - SET_MARGIN - ng.ng_Width;
	ng.ng_GadgetText	= MSG(MSG_CANCEL_GAD);
	ng.ng_GadgetID		= GID_SETCANCEL;

	gad = CreateGadget(BUTTON_KIND,		gad, &ng,
					   GT_Underscore,	'_',
					   TAG_DONE);
	if (!gad)
		goto sgad_failed;
	Gadget[GID_SETCANCEL] = gad;
	AddKeyShortcut(SetKeyboard, GID_SETCANCEL, MSG_CANCEL_GAD);
	
	ng.ng_LeftEdge		= (ng.ng_LeftEdge + BorderLeft + SET_MARGIN) / 2;
	ng.ng_GadgetText	= MSG(MSG_UNDO_GAD);
	ng.ng_GadgetID		= GID_SETUNDO;

	gad = CreateGadget(BUTTON_KIND,		gad, &ng,
					   GT_Underscore,	'_',
					   TAG_DONE);
	if (!gad)
		goto sgad_failed;
	Gadget[GID_SETUNDO] = gad;
	AddKeyShortcut(SetKeyboard, GID_SETUNDO, MSG_UNDO_GAD);

	CloseFont(font);
	return (gadlist);

sgad_failed:
	if (gadlist)
		FreeGadgets(gadlist);
	CloseFont(font);
	return (FALSE);
}

/*
 *		OpenSettingsWindow()
 *
 *		Opens the settings window with all the settings gadgets.
 *		The window will contain all necessary gadgets. Note that
 *		since gadgets can take some time to create on 68000 systems,
 *		we cache the gadget list so that on subsequent re-opens of
 *		the window, we don't need to recreate them all.
 *
 *		Returns TRUE for success, false for failure.
 */
int OpenSettingsWindow(void)
{
	static struct TextAttr setfontattr =
		{ "empty-storage-for-setup-fonts..", 0, FS_NORMAL, FPB_DISKFONT};

	static int width;		/* Maintained from call to call */
	static int height;

	int left = CurSettings.SetupWinLeft;
	int top  = CurSettings.SetupWinTop;
	struct TextAttr *fontattr = NULL;
	struct Window *win;

	CheckSegTracker();
	if (SetWindow) {
		WindowToFront(SetWindow);
		ActivateWindow(SetWindow);
		return (TRUE);
	}
	if (!CheckForScreen())
		return (FALSE);

	SavedSetupSets = CurSettings;		/* Save so we can restore later */

	if (!SetGadList) {
		/*
		 *		Find out what dimensions our new window should be; to do
		 *		this, we calculate the size with a variety of fonts until
		 *		we find one that works.
		 */
		int i;

		for (i = 0; fontattr = SubWindowFontList[i]; i++) {
			if (CreateSettingsGadgets(fontattr, TRUE, &width, &height))
				break;
		}
		if (!fontattr) {
			ShowError(MSG(MSG_ERROR_OPENSET));
			return (FALSE);
		}
		/*
		 *		We need to make a copy of the fontattr now so that if
		 *		the user changes the font, all our gadgets don't suddenly
		 *		inherit the new font (at least until the window has been
		 *		closed and re-opened!)
		 */
		strcpy(setfontattr.ta_Name, fontattr->ta_Name);
		setfontattr.ta_YSize = fontattr->ta_YSize;
		fontattr = &setfontattr;
	}
	if (left == -1)  left = (ScreenWidth  - width)  / 2;
	if (top  == -1)  top  = (ScreenHeight - height) / 2;

	win = OpenWindowTags(NULL,
						 WA_Title,			MSG(MSG_SETTINGS_TITLE),
						 WA_IDCMP,			IDCMP_CLOSEWINDOW	 |
						 					IDCMP_REFRESHWINDOW	 |
											IDCMP_NEWSIZE		 |
											IDCMP_RAWKEY		 |
											IDCMP_INACTIVEWINDOW |
											BUTTONIDCMP,
						 WA_Left,  			left,
						 WA_Top,  			top,
						 WA_Width,			width,
						 WA_Height,			height,
						 WA_Flags,			WFLG_DRAGBAR		 |
						 					WFLG_DEPTHGADGET	 |
											WFLG_ACTIVATE		 |
											WFLG_RMBTRAP		 |
											WFLG_NEWLOOKMENUS,
						 RefreshTag,      	TRUE,
						 WA_NoCareRefresh,	NoCareRefreshBool,
						 WA_PubScreen,		SnoopScreen,
						 TAG_DONE);
	if (!win) {
		ShowError(MSG(MSG_ERROR_OPENSET));
		return (FALSE);
	}
	if (!SetGadList) {
		SetGadList = CreateSettingsGadgets(fontattr, 0, 0, 0);
		if (!SetGadList) {
			CloseWindow(win);
			ShowError(MSG(MSG_ERROR_OPENSET));
			return (FALSE);
		}
	}
	AddGList(win, SetGadList, -1, -1, NULL);
	RefreshGList(SetGadList, win, NULL, -1);
	GT_RefreshWindow(win, NULL);

	SetWindow		= win;
	SetWindowPort	= win->UserPort;
	SetWindowMask	= 1 << SetWindowPort->mp_SigBit;
	if (DisableNestCount)
		DisableWindow(SetWindow, &SetRequester);
	return (TRUE);
}

/*
 *		CloseFunctionWindow(void)
 *
 *		Closes the functions window. Note that we do NOT free the gadget
 *		list in this case; since it takes so long to build, we leave it
 *		alone for the benefit of future opens.
 */
void CloseFunctionWindow(void)
{
	if (FuncWindow) {
		RecordWindowSizes();
		CloseWindow(FuncWindow);
		FuncWindow     = NULL;
		FuncWindowMask = 0;
	}
	if (PurgeFuncGadgets && FuncGadList) {
		FreeGadgets(FuncGadList);
		FuncGadList	   = NULL;
	}
	if (PurgeFuncGadgets && FuncVI) {
		FreeVisualInfo(FuncVI);
		FuncVI = NULL;
	}
	PurgeFuncGadgets = 0;
}

/*
 *		CloseSettingsWindow(void)
 *
 *		Closes the settings window, and free gadgets associated with window.
 */
void CloseSettingsWindow(void)
{
	if (SetWindow) {
		RecordWindowSizes();
		CloseWindow(SetWindow);
		SetWindow     = NULL;
		SetWindowMask = 0;
	}
	if (SetGadList) {
		FreeGadgets(SetGadList);
		SetGadList = NULL;
	}
	if (FileButtonImage) {
		FreeCustomImage(FileButtonImage);
		FileButtonImage = NULL;
	}
	if (FontButtonImage) {
		FreeCustomImage(FontButtonImage);
		FontButtonImage = NULL;
	}
	if (SetVI) {
		FreeVisualInfo(SetVI);
		SetVI = NULL;
	}
}
 
/*
 *		HandleSettingsMsgs()
 *
 *		Processes all outstanding messages associated with the Settings
 *		gadget window. To be called whenever we get a settings event.
 */

#define STRVAL(gid)	(((struct StringInfo *)(Gadget[gid]->SpecialInfo))->Buffer)

void HandleSettingsMsgs(void)
{
	static int activegad;			/* Non-zero if gadget is depressed   */
	static int activekey;			/* Keycode shortcut of activegad     */

	struct IntuiMessage *imsg;
	int doneset = SEL_NONE;
	char *str;
	int val;

	if (!SetWindow)
		return;

	while ((imsg = GT_GetIMsg(SetWindowPort)) != NULL) {
		struct Gadget *gad;
		int gadid;
		int shift  = (imsg->Qualifier & IE_SHIFT);
		int newval;

		switch (imsg->Class) {
			
			case IDCMP_CLOSEWINDOW:
				doneset = SEL_USE;
				break;

			case IDCMP_REFRESHWINDOW:
				GT_BeginRefresh(SetWindow);
				GT_EndRefresh(SetWindow, TRUE);
				break;

			case IDCMP_MOUSEMOVE: 
			case IDCMP_GADGETDOWN:
			case IDCMP_GADGETUP:
				gad    = (struct Gadget *)imsg->IAddress;
				gadid  = gad->GadgetID;
				newval = imsg->Code;

handle_set_gads:
				switch (gadid) {

					case GID_FILEPROMPT:
					{
						char *defname = "";

						/*
						 *		Choose a default name for the logfile
						 */
						if (IsFileSystem(DefaultLogName))
							defname = DefaultLogName;

						if (SelectFile(DefaultLogName, defname, SetWindow,
									   FILESEL_DEFLOGNAME))
						{
							/*
							 *		Got a new default filename, so update
							 *		the logfile gadget accordingly
							 */
							GT_SetGadgetAttrs(Gadget[GID_LOGFILE],
											  SetWindow, NULL,
								  			  GTST_String, DefaultLogName,
								  			  TAG_DONE);
						}
						break;
					}

					case GID_WFONTPROMPT:
						/*
						 *		Choose a new gadget (window) font
						 */
						if (SelectFont(SetWindow, FONTSEL_WINDOW)) {
							strcpy(WindowFontName, WindowFR->fo_Attr.ta_Name);
							WindowFontSize = WindowFR->fo_Attr.ta_YSize;
							WindowFontAttr.ta_YSize	= WindowFontSize;

							GT_SetGadgetAttrs(
								Gadget[GID_WINDOWFONT], SetWindow, NULL,
								GTTX_Text, GetFontDesc(WindowFontDesc,
													   WindowFontName,
													   WindowFontSize),
								TAG_DONE);
							ReOpenMainWindow();
							WindowToFront(SetWindow);
							/*
							 *		Since we allow the function gadgets
							 *		to hang around even when the function
							 *		window closes, we need to free the
							 *		gadgets when we change fonts to force
							 *		them to be regenerated in the new
							 *		font.
							 */		
							if (!FuncWindow) {
								FreeGadgets(FuncGadList);
								FuncGadList		 = NULL;
							} else {
								PurgeFuncGadgets = TRUE;
							}
						}
						break;

					case GID_BFONTPROMPT:
						/*
						 *		Choose a new buffer font
						 */
						if (SelectFont(SetWindow, FONTSEL_BUFFER)) {
							strcpy(BufferFontName, BufferFR->fo_Attr.ta_Name);
							BufferFontSize = BufferFR->fo_Attr.ta_YSize;
							BufferFontAttr.ta_YSize	= BufferFontSize;

							GT_SetGadgetAttrs(
								Gadget[GID_BUFFERFONT],
								SetWindow, NULL,
								GTTX_Text, GetFontDesc(BufferFontDesc,
													   BufferFontName,
											  		   BufferFontSize),
								TAG_DONE);
							ReOpenMainWindow();
							WindowToFront(SetWindow);
						}
						break;

					case GID_FORMATEDIT:
						OpenFormatWindow();
						break;

					case GID_FORMATCOPY:
						if (shift)
							*LogFormat = '\0';
						else
							strcpy(LogFormat, BufFormat);
						GT_SetGadgetAttrs(Gadget[GID_LOGFORMAT],
										  SetWindow, NULL,
										  GTST_String, LogFormat,
										  TAG_DONE);
						break;

					case GID_FILEIO:
						CurSettings.Setup.FileIOType = newval;
						break;

					case GID_OPENON:
						CurSettings.Setup.ScreenType = newval;
						break;

					case GID_LOGMODE:
						CurSettings.Setup.LogMode	 = newval;
						SetLogGadget(newval, LG_REFRESH);
						break;

					case GID_HIDEMETHOD:
						if (!CxBase)
							newval = 0;

						if (newval != CurSettings.Setup.HideMethod) {
							int oldval = CurSettings.Setup.HideMethod;

							CurSettings.Setup.HideMethod = newval;
							if (newval == HIDE_NONE)
								CleanupHotKey();
							else if (oldval == HIDE_NONE) {
								/*
								 *		Switching out of hide state so
								 *		re-activate hotkey
								 */
								InstallHotKey(CurSettings.Setup.HotKey);
							}
							SetMainHideState(newval);
						}
						break;

					case GID_HOTKEY:
					{
						char *msg = CurSettings.Setup.HotKey;

						strcpy(msg, STRVAL(GID_HOTKEY));
						InstallHotKey(msg);
						SetMainHideState(CurSettings.Setup.HideMethod);
						break;
					}

					case GID_SCREENNAME:
						strcpy(CurSettings.Setup.ScreenName,
							   STRVAL(GID_SCREENNAME));
						break;

					case GID_LOGFILE:
						strcpy(CurSettings.Setup.LogFile, STRVAL(GID_LOGFILE));
						break;

					case GID_LOGFORMAT:
						strcpy(CurSettings.Setup.LogfileFormat,
							   STRVAL(GID_LOGFORMAT));
						break;

					case GID_BUFFORMAT:
						str = STRVAL(GID_BUFFORMAT);
						if (strcmp(BufFormat, str) != 0) {
							strcpy(BufFormat, str);
							InstallNewFormat(NEW_STRING);
						}
						break;

					case GID_BUFFERSIZE:
						/*
						 *		We don't do anything here at all ... instead,
						 *		the update is handled when the user clicks
						 *		on USE.
						 */
						break;

					case GID_SETUSE:
						doneset = SEL_USE;
						break;

					case GID_SETCANCEL:
						doneset = SEL_CANCEL;
						break;

					case GID_SETUNDO:
						/*
						 *		Major bummer -- we need to manually update
						 *		ALL the string gadgets, since if the user
						 *		modified one of them and didn't press RETURN
						 *		we won't have picked it up and so we won't
						 *		know to undo it :-(
						 */
						InstallSettings(&SavedSetupSets, SET_SETUP);

#define UndoSet SavedSetupSets.Setup

						GT_SetGadgetAttrs(Gadget[GID_HOTKEY],
										  SetWindow, NULL,
										  GTST_String, UndoSet.HotKey,
										  TAG_DONE);
						GT_SetGadgetAttrs(Gadget[GID_SCREENNAME],
										  SetWindow, NULL,
										  GTST_String, UndoSet.ScreenName,
										  TAG_DONE);
						GT_SetGadgetAttrs(Gadget[GID_LOGFILE],
										  SetWindow, NULL,
										  GTST_String, UndoSet.LogFile,
										  TAG_DONE);
						GT_SetGadgetAttrs(Gadget[GID_BUFFORMAT],
										  SetWindow, NULL,
										  GTST_String, UndoSet.BufferFormat,
										  TAG_DONE);
						GT_SetGadgetAttrs(Gadget[GID_LOGFORMAT],
										  SetWindow, NULL,
										  GTST_String, UndoSet.LogfileFormat,
										  TAG_DONE);
						GT_SetGadgetAttrs(Gadget[GID_BUFFERSIZE],
										  SetWindow, NULL,
								  		  GTIN_Number, UndoSet.BufferSize,
										  TAG_DONE);
						break;
				}
				break;

			case IDCMP_INACTIVEWINDOW:
				if (activegad) {
					ShowGadget(SetWindow, Gadget[activegad], GADGET_UP);
					activegad = 0;
				}
				break;

			case IDCMP_RAWKEY:
			{
				int upstroke = imsg->Code & 0x80;
				int keypress = ConvertIMsgToChar(imsg);

				gadid  = SetKeyboard[keypress];
				if (activegad) {
					/*
					 *		We're releasing a gadget that was pressed
					 *		earlier, so handle it now
					 */
					int samekey = (imsg->Code & 0x7f) == activekey;

					if (samekey && !upstroke)	/* Ignore repeated keys */
						break;

					ShowGadget(SetWindow, Gadget[activegad], GADGET_UP);
					if (samekey) {
						/*
						 *		Just released the key that was originally
						 *		pressed for this gadget, so now go and
						 *		handle the gadget action.
						 */
						gadid     = activegad;
						gad       = Gadget[gadid];
						activegad = 0;
						goto handle_set_gads;
					}

					/*
					 *		If the above check didn't kick in, it means
					 *		we got a downpress of a different key, so
					 *		disable the gadget for this key and continue
					 *		to press the other key.
					 */
					activegad = 0;
				}

				if (imsg->Code == HELPKEY) {
					ShowAGuide(MSG(MSG_LINK_SETUPWIN));
					break;
				}

				/*
				 *		Handle keyboard equivalent keypresses for gadgets
				 */
				if (gadid) {
					int numopts;

					gad = Gadget[gadid];

					switch (gadid) {
						case GID_HIDEMETHOD:
							newval  = CurSettings.Setup.HideMethod;
							numopts = HIDE_MAX;
							goto handle_set_cycle;

						case GID_OPENON:
							newval  = CurSettings.Setup.ScreenType;
							numopts = SCREEN_MAX;
							goto handle_set_cycle;

						case GID_LOGMODE:
							newval  = CurSettings.Setup.LogMode;
							numopts = LOGMODE_MAX;
							goto handle_set_cycle;

						case GID_FILEIO:
							newval  = CurSettings.Setup.FileIOType;
							numopts = FILE_MAX;
handle_set_cycle:
							newval++;
							if (shift)
								newval -= 2;
							if (newval < 0)			newval += numopts;
							if (newval >= numopts)	newval -= numopts;
							GT_SetGadgetAttrs(gad, SetWindow, NULL,
								  			  GTCY_Active, newval,
								  			  TAG_DONE);
							goto handle_set_gads;

						case GID_BUFFERSIZE:
						case GID_BUFFORMAT:
						case GID_LOGFORMAT:
						case GID_HOTKEY:
						case GID_SCREENNAME:
						case GID_LOGFILE:
							if (gadid != GID_LOGFILE || !shift) {
								ActivateGadget(gad, SetWindow, NULL);
								break;
							}
							/*
							 *		User pressed GID_LOGFILE with SHIFT
							 *		pressed, so change gadget type to
							 *		GID_FILEPROMPT and fall through to
							 *		button code.
							 */
							gadid = GID_FILEPROMPT;
							/* Fall through */

						case GID_SETUSE:
						case GID_SETUNDO:
						case GID_SETCANCEL:
						case GID_FORMATCOPY:
						case GID_FORMATEDIT:
						case GID_WINDOWFONT:
						case GID_BUFFERFONT:
						case GID_WFONTPROMPT:
						case GID_BFONTPROMPT:
							if (gadid == GID_WINDOWFONT)
								gadid = GID_WFONTPROMPT;
							if (gadid == GID_BUFFERFONT)
								gadid = GID_BFONTPROMPT;
							gad = Gadget[gadid];
							ShowGadget(SetWindow, gad, GADGET_DOWN);
							activegad = gadid;
							activekey = imsg->Code;
							break;
					}
				}
				break;
			}
		}
		GT_ReplyIMsg(imsg);
	}
	/*
	 *		Handled all messages. Now check for possible USE/CANCEL
	 */
	if (doneset == SEL_USE) {
		/*
		 *		Use the current settings. To be safe, we update all
		 *		our string gadgets in case they were modified but
		 *		we didn't catch the modification (i.e. user didn't press
		 *		Return).
		 */
		str = STRVAL(GID_BUFFORMAT);
		if (strcmp(BufFormat, str) != 0) {
			strcpy(BufFormat, str);
			InstallNewFormat(NEW_STRING);
		}
		strcpy(CurSettings.Setup.LogFile,	 	 STRVAL(GID_LOGFILE));
		strcpy(CurSettings.Setup.ScreenName, 	 STRVAL(GID_SCREENNAME));
		strcpy(CurSettings.Setup.HotKey,	 	 STRVAL(GID_HOTKEY));
		strcpy(CurSettings.Setup.LogfileFormat,	 STRVAL(GID_LOGFORMAT));
		strcpy(CurSettings.Setup.LogfileFormat,  STRVAL(GID_LOGFORMAT));
		val = atoi(STRVAL(GID_BUFFERSIZE));
		CloseSettingsWindow();
		/*
		 *		Now check if we've got a new buffer size --
		 *		if so, install it.
		 */
		if (val < 1)
			val = 1;

		if (val != SavedSetupSets.Setup.BufferSize) {
			CurSettings.Setup.BufferSize = val;
			ClearWindowBuffer();
			if (!SetTotalBufferSize(val * 1024, SETBUF_FORCENEW)) {
				ShowError(MSG(MSG_ERROR_NOBUFMEM), val);
				Cleanup(20);
			}
		}
	} else if (doneset == SEL_CANCEL) {
		/*
		 *		Cancel the current settings, and restore those that
		 *		were in effect before we started this. We close
		 *		the settings window first in case the main window
		 *		needs to close and re-open (change of font) since
		 *		this looks cleaner.
		 *
		 *		Note that even though we saved the entire set of settings,
		 *		we only restore the SETUP settings, not all of them.
		 */
		CloseSettingsWindow();
		InstallSettings(&SavedSetupSets, SET_SETUP);
	}
}

/*
 *		ShowFuncOpts(options, firstid, lastid)
 *
 *		Updates the function window gadgets to reflect the new values
 *		in the specified options array.
 *
 *		The new options are compared with the existing options to see
 *		if they've changed, so you should make sure to call this function
 *		_before_ copying the new options onto the current settings.
 *
 *		Only gadgets from the first to the last ID given are affected.
 */
void ShowFuncOpts(UBYTE newopts[], int firstid, int lastid)
{
	int i;

	for (i = firstid; i <= lastid; i++) {
		if (Gadget[i] != NULL && newopts[i] != CurSettings.Func.Opts[i]) {
			GT_SetGadgetAttrs(Gadget[i], FuncWindow, NULL,
							  GTCB_Checked, newopts[i],
							  TAG_DONE);
		}
	}
}

/*
 *		ResetFuncToSelected()
 *
 *		Changes the two cycle gadgets in the function window back to the
 *		SELECTED state, and updates the variables accordingly. Note
 *		that doesn't change the actual boolean settings themselves; in
 *		effect, it makes the current settings permanent by stopping you
 *		switching back to the SELECTED state to restore an older set
 *		by clicking on the cycle gadget yourself.
 */
void ResetFuncToSelected(void)
{
	if (FuncSystemCol != COL_SELECTED) {
		GT_SetGadgetAttrs(Gadget[GID_SELSYSTEM],
						  FuncWindow, NULL,
						  GTCY_Active, COL_SELECTED,
						  TAG_DONE);
	}
	if (FuncDOSCol != COL_SELECTED) {
		GT_SetGadgetAttrs(Gadget[GID_SELDOS],
						  FuncWindow, NULL,
						  GTCY_Active, COL_SELECTED,
						  TAG_DONE);
	}
	FuncSystemCol = COL_SELECTED;
	FuncDOSCol    = COL_SELECTED;
}

/*
 *		HandleFuncMsgs()
 *
 *		Processes all outstanding messages associated with the Functions
 *		gadget window. To be called whenever we get a functions event.
 *
 *		The only slightly complicated bit here concerns the two cycle
 *		gadgets which can be used to select all or none of either of
 *		the DOS and System columns, or to restore a previous setting
 *		installed before.
 */
void HandleFuncMsgs(void)
{
	static int activegad;			/* Non-zero if gadget is depressed   */
	static int activekey;			/* Keycode shortcut of activegad     */

	struct IntuiMessage *imsg;
	int donefunc = SEL_NONE;

	if (!FuncWindow)
		return;

	while ((imsg = GT_GetIMsg(FuncWindowPort)) != NULL) {
		struct Gadget *gad;
		int shift = (imsg->Qualifier & IE_SHIFT);
		LONG gadid;
		int newval;

		switch (imsg->Class) {
			
			case IDCMP_REFRESHWINDOW:
				GT_BeginRefresh(FuncWindow);
				GT_EndRefresh(FuncWindow, TRUE);
				break;

			case IDCMP_CLOSEWINDOW:
				donefunc = SEL_USE;
				break;

			case IDCMP_MOUSEMOVE: 
			case IDCMP_GADGETDOWN:
			case IDCMP_GADGETUP:
				gad    = (struct Gadget *)imsg->IAddress;
				gadid  = gad->GadgetID;
				newval = imsg->Code;

handle_func_gads:

				switch (gadid) {

					case GID_MATCHNAME:
						strcpy(MatchName, STRVAL(GID_MATCHNAME));
						SetPattern(MatchName, IgnoreWBShell);
						break;

					case GID_SELSYSTEM:
					case GID_SELDOS:
						/*
						 *		Changing system column cycle gadget.
						 *		Update to show ALL, NONE or SELECTED
						 *		system gadgets.
						 */
					{
						int first    = FIRST_SYS_GADGET;
						int last     = LAST_SYS_GADGET;
						int numgads  = NUM_SYS_GADGETS;
						int *pcurval = &FuncSystemCol;

						if (gadid == GID_SELDOS) {
							/*
							 *		Override above defaults with new
							 *		defaults for DOS cycle gadget
							 */
							first    = FIRST_DOS_GADGET;
							last     = LAST_DOS_GADGET;
							numgads  = NUM_DOS_GADGETS;
							pcurval	 = &FuncDOSCol;
						}
						if (newval == *pcurval)
							break;		/* Ignore no-op events */

						if (newval == COL_SELECTED) {
							/*
							 *		Restore previously saved system col values
							 */
							ShowFuncOpts(SavedCols, first, last);
							memcpy(&CurSettings.Func.Opts[first],
								   &SavedCols[first], numgads);
						} else {
							/*
							 *		Select either ALL or NONE of the settings
							 *		in the system column.
							 */
							int fillval = (newval == COL_ALL);

							memset(&TempCols[first], fillval, numgads);
							ShowFuncOpts(TempCols, first, last);
									
							if (*pcurval == COL_SELECTED) {
							   /*
							    *		If we are moving out of the
								*		selected state, then save the
								*		existing settings for later.
								*/
							   memcpy(&SavedCols[first],
							   		  &CurSettings.Func.Opts[first], numgads);
							}
							memset(&CurSettings.Func.Opts[first],
								   fillval, numgads);
						}
						LoadFuncSettings(&CurSettings.Func);
						*pcurval = newval;
						break;
					}

					case GID_FUNCUSE:
						/*
						 *		Retain existing settings, and exit
						 */
						donefunc = SEL_USE;
						break;

					case GID_FUNCCANCEL:
						/*
						 *		Restore original settings and exit
						 */
						donefunc = SEL_CANCEL;
						break;

					case GID_FUNCUNDO:
						/*
						 *		Restore original settings but don't exit
						 */
						InstallSettings(&SavedFuncSets, SET_FUNC);
						break;

					default:
						if (gadid >= FIRST_BOOL_GADGET &&
												gadid <= LAST_BOOL_GADGET) {
							/*
							 *		Getting a new boolean gadget so update
							 *		CurSettings to reflect this.
							 */
							CurSettings.Func.Opts[gadid] =
										(gad->Flags & GFLG_SELECTED) != 0;
							LoadFuncSettings(&CurSettings.Func);
							/*
							 *		Now check to see if we need to put
							 *		either column back into the selected
							 *		state again.
							 */
							if (FuncSystemCol != COL_SELECTED &&
								gadid >= FIRST_SYS_GADGET	  &&
							    gadid <= LAST_SYS_GADGET)
							{
								FuncSystemCol = COL_SELECTED;
								GT_SetGadgetAttrs(Gadget[GID_SELSYSTEM],
												  FuncWindow, NULL,
												  GTCY_Active, COL_SELECTED,
												  TAG_DONE);
							}
							if (FuncDOSCol != COL_SELECTED	&&
								gadid >= FIRST_DOS_GADGET	&&
							    gadid <= LAST_DOS_GADGET)
							{
								FuncDOSCol = COL_SELECTED;
								GT_SetGadgetAttrs(Gadget[GID_SELDOS],
												  FuncWindow, NULL,
												  GTCY_Active, COL_SELECTED,
												  TAG_DONE);
							}
						}
						break;
				}
				break;

			case IDCMP_INACTIVEWINDOW:
				if (activegad) {
					ShowGadget(FuncWindow, Gadget[activegad], GADGET_UP);
					activegad = 0;
				}
				break;

			case IDCMP_RAWKEY:
			{
				int upstroke = imsg->Code & 0x80;
				int keypress = ConvertIMsgToChar(imsg);

				gadid  = FuncKeyboard[keypress];
				if (activegad) {
					/*
					 *		We're releasing a gadget that was pressed
					 *		earlier, so handle it now
					 */
					int samekey = (imsg->Code & 0x7f) == activekey;

					if (samekey && !upstroke)	/* Ignore repeated keys */
						break;

					ShowGadget(FuncWindow, Gadget[activegad], GADGET_UP);
					if (samekey) {
						/*
						 *		Just released the key that was originally
						 *		pressed for this gadget, so now go and
						 *		handle the gadget action.
						 */
						gadid     = activegad;
						gad       = Gadget[gadid];
						activegad = 0;
						goto handle_func_gads;
					}
					/*
					 *		If the above check didn't kick in, it means
					 *		we got a downpress of a different key, so
					 *		disable the gadget for this key and continue
					 *		to press the other key.
					 */
					activegad = 0;
				}

				if (imsg->Code == HELPKEY) {
					ShowAGuide(MSG(MSG_LINK_FUNCWIN));
					break;
				}

				/*
				 *		Handle keyboard equivalents for gadgets
				 */
				gadid = FuncKeyboard[keypress];
				if (gadid) {
					gad = Gadget[gadid];

					if (gadid >= FIRST_BOOL_GADGET &&
										gadid <= LAST_BOOL_GADGET) {
						/*
						 *		It's a boolean gadget so just invert its
						 *		current setting
						 */
						newval = !CurSettings.Func.Opts[gadid];
						GT_SetGadgetAttrs(Gadget[gadid], FuncWindow, NULL,
										  GTCB_Checked, newval, TAG_DONE);
						goto handle_func_gads;
					} else if (gadid == GID_SELSYSTEM || gadid == GID_SELDOS) {
						/*
						 *		One of the two cycle gadgets. Cycle forward
						 *		if the unshifted key was pressed or back
						 *		if the shifted version was pressed
						 */
						newval = FuncSystemCol;
						if (gadid == GID_SELDOS)
							newval = FuncDOSCol;
						newval++;
						if (shift)
							newval -= 2;
						if (newval < 0)	newval = 2;
						if (newval > 2) newval = 0;
						GT_SetGadgetAttrs(gad, FuncWindow, NULL,
										  GTCY_Active, newval, TAG_DONE);
						goto handle_func_gads;
					} else if (gadid == GID_MATCHNAME) {
						/* 
						 *		The string gadget, so simply activate it
						 */
						ActivateGadget(gad, FuncWindow, NULL);
					} else {
						/*
						 *		It must be a button gadget so show it
						 *		selected first before processing it
						 */
						ShowGadget(FuncWindow, gad, GADGET_DOWN);
						activegad = gadid;
						activekey = imsg->Code;
					}
				}
				break;
			}
		}
		GT_ReplyIMsg(imsg);
	}
	if (donefunc != SEL_NONE) {
		/*
		 *		Since we cache the function gadgets between window opens,
		 *		we need to ensure the two cycle gadgets are reset to
		 *		SELECTED mode ready for the next time we open the window.
		 *		(Ideally, we would remember the cycle state and use the
		 *		same state next time, but that's too much work!)
		 */
		ResetFuncToSelected();
	}
	if (donefunc == SEL_USE) {
		strcpy(MatchName, STRVAL(GID_MATCHNAME));
		SetPattern(MatchName, IgnoreWBShell);
		CloseFunctionWindow();
	} else if (donefunc == SEL_CANCEL) {
		CloseFunctionWindow();
		/*
		 *		Note that though we saved all the settings, we only
		 *		re-install the original function settings, not all
		 *		of them.
		 */
		InstallSettings(&SavedFuncSets, SET_FUNC);
	}
}

/*
 *		CreateFormatGadgets(gadgetfa, bufferfa, getsize, &width, &height)
 *
 *		Creates all the gadgets for the format window. If getsize
 *		is true, then the width and height values are filled in with
 *		the dimensions of the window needed to hold the gadgets. In
 *		this case, NULL is returned if the window would be too big to
 *		fit on the current screen, or non-NULL if width and height
 *		were successfully initialised.
 *
 *		gadgetfa gives the gadget font to use, bufferfa gives the buffer
 *		font (for the format listings).
 *
 *		If getsize is zero, then the actual gadgets are created and
 *		a pointer to the gadget list is returned, or NULL if an
 *		error occurred (typically, out of memory).
 */
struct Gadget *CreateFormatGadgets(struct TextAttr *gadgetfa,
								   struct TextAttr *bufferfa,
								   int getsize, int *pwidth, int *pheight)
{
	struct TextFont *gfont;
	struct TextFont *bfont;
	struct NewGadget ng;
	struct Gadget *gadlist;
	struct Gadget *gad;
	FieldInit *fi;
	int width;
	int height;
	int gwidth;
	int gheight;
	int gfonty;
	int bfontx;
	int bfonty;
	int topline;
	int maxtitlelen;
	int buttonwidth;
	int buttonspace;
	int headingspace;
	int outmargin;

	if (!FormVI) {
		FormVI = GetVisualInfoA(SnoopScreen, NULL);
		if (!FormVI)
			return (NULL);
	}
	gfont = MyOpenFont(gadgetfa);
	if (!gfont)
		return (NULL);

	bfont = MyOpenFont(bufferfa);
	if (!bfont) {
		CloseFont(gfont);
		return (NULL);
	}
	if (bfont->tf_Flags & FPF_PROPORTIONAL) {
		CloseFont(bfont);
		CloseFont(gfont);
		return (NULL);
	}
	bfontx		= bfont->tf_XSize;
	bfonty		= bfont->tf_YSize;
	gfonty		= gfont->tf_YSize;
	gheight		= gfonty  + 3;
	gwidth 		= gheight + 15;
	topline     = TitlebarHeight + gfonty/2;

	/*
	 *		Now calculate the width of the window boxes which will contain
	 *		the format sequences being displayed. The defaults box must be
	 *		wide enough to hold "titlemsg..  %x" whereas the current format
	 *		box must be wide enough to hold "titlemsg..  %20x".
	 *
	 *		Since the message titles may be localised, we calculate the
	 *		longest string and work from that.
	 */
	maxtitlelen = 0;
	for (fi = FieldTypes; fi->type != EF_END; fi++) {
		int len = strlen(MSG(fi->titlemsgid));

		if (len > maxtitlelen)
			maxtitlelen = len;
	}
	/*
	 *		The next two vars give the number of characters that will fit
	 *		across each box. These is used when producing output.
	 *
	 *		For the left box, this is the length of the longest heading title,
	 *		plus 2 for spacing after the heading name, another 2 for the
	 *		format ID (%x) and a final 2 for the spacing on either side.
	 *
	 *		The right box is similar, but uses an additional 2 characters to
	 *		display the field width of the selected item.
	 */
	FBoxA_NumChars	= maxtitlelen   + 6;
	FBoxB_NumChars	= FBoxA_NumChars + 2;
	FBoxA_Width 	= FBoxA_NumChars * bfontx + 4;
	FBoxB_Width 	= FBoxB_NumChars * bfontx + 4;

	/*
	 *		Create format strings for sprintf formatting later on. These
	 *		look like this:
	 *
	 *			FBoxA_FormString:	" %-??s  %%%lc "		(2 parameters)
	 *			FBoxB_FormString:	" %-??s  %%%02ld%lc "	(3 parameters)
	 *
	 *		and when strings are output using these templates, they look like:
	 *
	 *			FBoxA_FormString:	" PaddedTitleName....  %x "
	 *			FBoxB_FormString:   " PaddedTitleName....  %12x "
	 */
	mysprintf(FBoxA_FormString, " %%-%lds  %%%%%%lc ",		 maxtitlelen);
	mysprintf(FBoxB_FormString, " %%-%lds  %%%%%%02ld%%lc ", maxtitlelen);

	/*
	 *		Make sure we never have overlapping button gadgets (this might
	 *		happen if we had a big gadget font and small buffer font).
	 */
	buttonwidth = MaxTextLen(gfont, UseCancelUndoText) + 32;
	buttonspace = buttonwidth * 3 + FORM_MARGIN * 4 + BorderLeft + BorderRight;

	width = FBoxA_Width + FBoxB_Width + FORM_MARGIN * 4 +
						  BorderLeft  + BorderRight;

	outmargin = FORM_MARGIN;
	if (buttonspace > width)
		width = buttonspace;

	headingspace =	GetTextLen(gfont, MSG(MSG_AVAILABLE_GAD)) +
					GetTextLen(gfont, MSG(MSG_CURFORMAT_GAD)) +
					BorderLeft + BorderRight + FORM_MARGIN*3;

	if (headingspace > width)
		width = headingspace;

	/*
	 *		Now calculate the dimensions of our two windows boxes. The left
	 *		box displays the available format headings, and the right box
	 *		displays the currently selected format headings.
	 */
	FBoxSpacing		= bfonty + 2;
	FBoxA_Top		= topline + gfonty + 4;
	FBoxA_Height	= EF_COUNT * FBoxSpacing + 4;
	FBoxA_Left  	= BorderLeft + outmargin;

	FBoxB_Top		= FBoxA_Top;
	FBoxB_Height	= FBoxA_Height;
	FBoxB_Left  	= width - BorderRight - outmargin - FBoxB_Width;
	 
	height = FBoxA_Top + FBoxA_Height + BorderBottom +
			 (gfonty + GadgetHeight) * 3;

	if (width > ScreenWidth || height > ScreenHeight) {
		CloseFont(gfont);
		CloseFont(bfont);
		return (NULL);
	}
	if (getsize) {
		CloseFont(gfont);
		CloseFont(bfont);
		*pwidth  = width;
		*pheight = height;
		return (struct Gadget *)(-1);
	}

	/*
	 *		Now create our window gadgets. We have a total of 4 -- the
	 *		Use, Undo, Cancel buttons, and the field width slider.
	 */	
	gadlist = NULL;
	gad = CreateContext(&gadlist);
	if (!gad)
		goto fogad_failed;

	ng.ng_TextAttr		= gadgetfa;
	ng.ng_VisualInfo	= FormVI;
	ng.ng_Flags			= PLACETEXT_IN;
	ng.ng_TopEdge  		= height - BorderBottom - (gfonty+GadgetHeight) * 3/2;
	ng.ng_Height		= gfonty + GadgetHeight;
	ng.ng_Width			= buttonwidth;

	ng.ng_LeftEdge		= FORM_MARGIN + BorderLeft;
	ng.ng_GadgetText	= MSG(MSG_USE_GAD);
	ng.ng_GadgetID		= GID_FORMUSE;

	gad = CreateGadget(BUTTON_KIND,		gad, &ng,
					   GT_Underscore,	'_',
					   TAG_DONE);
	if (!gad)
		goto fogad_failed;
	Gadget[GID_FORMUSE] = gad;
	AddKeyShortcut(FormKeyboard, GID_FORMUSE, MSG_USE_GAD);
	
	ng.ng_LeftEdge		= width - BorderRight - FORM_MARGIN - ng.ng_Width;
	ng.ng_GadgetText	= MSG(MSG_CANCEL_GAD);
	ng.ng_GadgetID		= GID_FORMCANCEL;

	gad = CreateGadget(BUTTON_KIND,		gad, &ng,
					   GT_Underscore,	'_',
					   TAG_DONE);
	if (!gad)
		goto fogad_failed;
	Gadget[GID_FORMCANCEL] = gad;
	AddKeyShortcut(FormKeyboard, GID_FORMCANCEL, MSG_CANCEL_GAD);
	
	ng.ng_LeftEdge		= (ng.ng_LeftEdge + BorderLeft + FORM_MARGIN) / 2;
	ng.ng_GadgetText	= MSG(MSG_UNDO_GAD);
	ng.ng_GadgetID		= GID_FORMUNDO;

	gad = CreateGadget(BUTTON_KIND,		gad, &ng,
					   GT_Underscore,	'_',
					   TAG_DONE);
	if (!gad)
		goto fogad_failed;
	Gadget[GID_FORMUNDO] = gad;
	AddKeyShortcut(FormKeyboard, GID_FORMUNDO, MSG_UNDO_GAD);

	/*
	 *		Create the slider gadget. We need to leave room on the left for
	 *		the title, and on the right for the current value.
	 */
	ng.ng_LeftEdge		= FORM_MARGIN + BorderLeft + 8 +
						  GetTextLen(gfont, MSG(MSG_FIELDWIDTH_GAD));
	ng.ng_Width			= width - FORM_MARGIN - ng.ng_LeftEdge -
						  BorderLeft - BorderRight - GetTextLen(gfont, "99 ");
	ng.ng_Height		= gfonty + 3;
	ng.ng_TopEdge	 	= (ng.ng_TopEdge + FBoxA_Top + FBoxA_Height -
						   ng.ng_Height) / 2;
	ng.ng_GadgetText	= MSG(MSG_FIELDWIDTH_GAD);
	ng.ng_GadgetID		= GID_FORMWIDTH;
	ng.ng_Flags			= PLACETEXT_LEFT;

	gad = CreateGadget(SLIDER_KIND,			gad, &ng,
					   GTSL_LevelFormat,	"%2ld ",
					   GTSL_LevelPlace,		PLACETEXT_RIGHT,
					   GTSL_Level,			1,
					   GTSL_Min,			1,
					   GTSL_Max,			MAX_FIELD_LEN,
					   GTSL_MaxLevelLen,	2,
					   GT_Underscore,		'_',
					   GA_Disabled,			TRUE,
					   GA_RelVerify,		TRUE,
					   TAG_DONE);
	if (!gad)
		goto fogad_failed;
	Gadget[GID_FORMWIDTH] = gad;
	AddKeyShortcut(FormKeyboard, GID_FORMWIDTH, MSG_FIELDWIDTH_GAD);

	FormGadFont = gfont;
	FormBufFont = bfont;
	return (gadlist);

fogad_failed:
	if (gadlist)
		FreeGadgets(gadlist);
	CloseFont(gfont);
	CloseFont(bfont);
	return (NULL);
}

/*
 *		ShowFormatLine(line, select)
 *
 *		Outputs the numbered line of text in the right-hand box. If the
 *		number given is too high, then a blank line is output instead.
 *		The text will be of the form "HeadingName.... %02x" according
 *		to the current contents of the line.
 *
 *		If select is FORMAT_SELECTED, the line will be highlighted, else
 *		it will be output normally.
 */
void ShowFormatLine(int line, int select)
{
	int bfontbaseline	   = FormBufFont->tf_Baseline;
	int boxwidth	  	   = FormBufFont->tf_XSize * FBoxB_NumChars;
	struct RastPort *rport = FormWindow->RPort;
	char viewbuf[60];
	int fillpen = 0;
	int textpen = 1;
	int textrow;
	
	if (select == FORMAT_SELECTED) {
		fillpen = ScreenDI->dri_Pens[FILLPEN];
		textpen = ScreenDI->dri_Pens[FILLTEXTPEN];
	}
	SetFont(rport, FormBufFont);
	SetAPen(rport, textpen);
	SetBPen(rport, fillpen);
	SetDrMd(rport, JAM2);

	if (line < 0 || line >= EF_COUNT)
		return;

	if (line < FBoxB_CurLines) {
		FieldInit *fi = &FieldTypes[CurrentFields[line].type];

		mysprintf(viewbuf, FBoxB_FormString, MSG(fi->titlemsgid),
						   CurrentFields[line].width, fi->idchar);
	} else {
		memset(viewbuf, ' ', FBoxB_NumChars);
	}
	textrow = FBoxB_Top + line*FBoxSpacing + 3;
	Move(rport, FBoxB_Left + 2, textrow + bfontbaseline);
	Text(rport, viewbuf, FBoxB_NumChars);

	/*
	 *		Now draw highlight on top and bottom of selected line
	 *		(or erase, as the case may be). This fills out the
	 *		selection area and makes it look much better.
	 */
	SetAPen(rport, fillpen);
	Move(rport, FBoxB_Left + 2, textrow - 1);
	Draw(rport, FBoxB_Left + 1 + boxwidth, textrow - 1);
	Move(rport, FBoxB_Left + 2, textrow + FormBufFont->tf_YSize);
	Draw(rport, FBoxB_Left + 1 + boxwidth, textrow + FormBufFont->tf_YSize);
}

/*
 *		ShowDragSelect(pos, select)
 *
 *		Highlights the specified region to indicate where the currently
 *		dragged field will end up if it is released here.
 *
 *		If pos is FBOX_NOSELECT, no drop area is active and no output is
 *		produced. If pos is FBOX_SELECTLEFT, then the left box is active
 *		and it is given an inverse highlight around the interior of the
 *		box giving a bezel effect.
 *
 *		If pos is in the range 0 ... EF_COUNT, then it corresponds to an
 *		insertion point between two lines in the right-most box. This is
 *		show by a two-pixel high highlight line drawn between those two
 *		lines.
 *
 *		If select is FORMAT_SELECTED, the feature is drawn; if select is
 *		FORMAT_UNSELECTED, the feature is instead erased. Note that after
 *		calling this function, it may be necessary to redisplay any
 *		current highlight bar in the right box (see ShowFormatLine) since
 *		there is a one-pixel overlap between the highlight bar and the
 *		rendering done here.
 */
void ShowDragSelect(int pos, int select)
{
	struct RastPort *rport = FormWindow->RPort;
	int black = 1;
	int white = 2; 

	if (pos == FBOX_NOSELECT)
		return;
	
	if (select == FORMAT_UNSELECTED) {
		black = 0;
		white = 0;
	}
	SetDrMd(rport, JAM2);
	if (pos == FBOX_SELECTLEFT) {
		/*
		 *		Draw bezel inside left box
		 */
		int boxleft  = FBoxA_Left + 2;
		int boxtop	 = FBoxA_Top  + 1;
		int boxright = FBoxA_Left + FBoxA_Width  - 3;
		int boxbot   = FBoxA_Top  + FBoxA_Height - 2;

		SetAPen(rport, black);
		Move(rport, boxleft,	boxbot);
		Draw(rport, boxleft,	boxtop);
		Move(rport, boxleft+1,	boxbot-1);
		Draw(rport, boxleft+1,	boxtop);
		Draw(rport, boxright-1,	boxtop);

		SetAPen(rport, white);
		Move(rport, boxright,	boxtop);
		Draw(rport, boxright,	boxbot);
		Move(rport, boxright-1,	boxtop);
		Draw(rport, boxright-1,	boxbot);
		Draw(rport, boxleft-1,	boxbot);
	} else if (pos >= 0 && pos <= EF_COUNT) {
		/*
		 *		Draw thin selection line in right box
		 */
		int line = FBoxB_Top + 1 + pos * FBoxSpacing;

		SetAPen(rport, white);
		Move(rport, FBoxB_Left+2,				line);
		Draw(rport, FBoxB_Left+FBoxB_Width-3,	line);
		SetAPen(rport, black);
		Move(rport, FBoxB_Left+2,				line+1);
		Draw(rport, FBoxB_Left+FBoxB_Width-3,	line+1);
	}
}

/*
 *		RedrawFormatWindow()
 *
 *		Redraws all the parts of the format window that are generated by
 *		hand: this includes the two bevelled boxes containing the available
 *		format fields, the headings for those boxes, and the current contens
 *		of those boxes, including highlighting where appropriate.
 */
void RedrawFormatWindow(void)
{
	char viewbuf[60];
	struct RastPort *rport = FormWindow->RPort;
	int gfonty		  = FormGadFont->tf_YSize;
	int gfontbaseline = FormGadFont->tf_Baseline;
	int bfontbaseline = FormBufFont->tf_Baseline;
	int line;
	int headpos;
	EditEvent *ee;

	DrawBevelBox(rport, FBoxA_Left, FBoxA_Top, FBoxA_Width, FBoxA_Height,
						GT_VisualInfo, FormVI,
						TAG_DONE);

	DrawBevelBox(rport, FBoxB_Left, FBoxB_Top, FBoxB_Width, FBoxB_Height,
						GT_VisualInfo, FormVI,
						TAG_DONE);

	SetAPen(rport, 1);
	SetBPen(rport, 0);
	SetDrMd(rport, JAM2);
	SetFont(rport, FormGadFont);

	/*
	 *		When outputting the box headings, we adjust the centering
	 *		to ensure that headings wider than the boxes don't get
	 *		pushed into the window borders.
	 */
#define CENTRE(left,width,msgid)	\
	    	((width - GetTextLen(FormGadFont, MSG(msgid)))/2 + left)

	headpos = CENTRE(FBoxA_Left, FBoxA_Width, MSG_AVAILABLE_GAD);
	if (headpos < (BorderLeft + 4))
		headpos = FBoxA_Left; /* was: BorderLeft + 4; */

	Move(rport, headpos, FBoxA_Top - gfonty + gfontbaseline - 4);
	Text(rport, MSG(MSG_AVAILABLE_GAD), strlen(MSG(MSG_AVAILABLE_GAD)));

	headpos = CENTRE(FBoxB_Left, FBoxB_Width, MSG_CURFORMAT_GAD);
	if (headpos < (FBoxB_Left - 4))
		headpos = FBoxB_Left + FBoxB_Width -
							   GetTextLen(FormGadFont, MSG(MSG_CURFORMAT_GAD));

	Move(rport, headpos, FBoxB_Top - gfonty + gfontbaseline - 4);
	Text(rport, MSG(MSG_CURFORMAT_GAD), strlen(MSG(MSG_CURFORMAT_GAD)));

	/*
	 *		Now redraw the text inside the left box (available headings)
	 */
	SetFont(rport, FormBufFont);
	ee = &AvailableFields[0];
	for (line = 0; line < (FBoxB_Height - 4); line += FBoxSpacing) {
		if (ee->type != END_EDITLIST) {
			FieldInit *fi = &FieldTypes[ee->type];
			mysprintf(viewbuf, FBoxA_FormString, MSG(fi->titlemsgid),
						   						 fi->idchar);
			ee++;
		} else {
			memset(viewbuf, ' ', FBoxA_NumChars);
		}
		Move(rport, FBoxA_Left + 2, FBoxA_Top + line + bfontbaseline + 3);
		Text(rport, viewbuf, FBoxA_NumChars);
	}

	/*
	 *		Redraw the text inside the right box (currently selected headings)
	 */
	ee = &CurrentFields[0];
	for (line = 0; line < EF_COUNT; line++) {
		if (line == FBoxSelected)
			ShowFormatLine(line, FORMAT_SELECTED);
		else
			ShowFormatLine(line, FORMAT_UNSELECTED);
	}
}

/*
 *		ConvertListToFormat(list, format)
 *
 *		Converts the passed in EditList to a format list which can be
 *		used for updating the main SnoopDos display. Returns the
 *		total width (in characters) occupied by the format.
 */
int ConvertListToFormat(EditEvent *ee, EventFormat *evformat)
{
	int width = 0;

	while (ee->type != END_EDITLIST) {
		width               += ee->width + 1; /* Include space between cols */
		evformat->type		 = FieldTypes[ee->type].type;
		evformat->width		 = ee->width;
		evformat->titlemsgid = FieldTypes[ee->type].titlemsgid;
		evformat++;
		ee++;
	}
	evformat->type = EF_END;
	if (width)
		width--;		/* Don't count extra space at end */
	return (width);
}

/*
 *		CreateFormatLists(format)
 *
 *		Parses the passed-in format string and creates the two lists
 *		used by the format editor window to allow the user to re-arrange
 *		the format.
 *
 *		Should be called before RedrawFormatWindow() is called for the
 *		first time.
 *
 *		Also initialises some format globals related to the list boxes.
 */
void CreateFormatLists(EventFormat *evformat)
{
	EditEvent *ee;
	FieldInit *fi;
	EventFormat *ef;

	/*
	 *		Now build the lists for the two boxes. The right box contains
	 *		all items in the format string (and in the same order that they
	 *		appear in the format string). The left box contains all the
	 *		items that are NOT in the format string, in the presorted
	 *		order that they appear in the FieldTypes[] initialiser array.
	 *
	 *		Note that the two edit lists contain indexes directly into
	 *		the FieldTypes array, rather than specific event types.
	 *		Thus, we store the offset into the array when we find a match
	 *		rather than fi->type.
	 */
	ee = &CurrentFields[0];
	for (ef = evformat; ef->type != EF_END; ef++) {
		for (fi = FieldTypes; fi->type != ef->type && fi->type != EF_END; fi++)
			;
		ee->type  = fi - FieldTypes;
		ee->width = ef->width;
		ee++;
	}
	ee->type = END_EDITLIST;
	FBoxB_CurLines = ee - &CurrentFields[0];

	ee = &AvailableFields[0];
	for (fi = FieldTypes; fi->type != EF_END; fi++) {
		for (ef = BufferEFormat; ef->type != fi->type && ef->type != EF_END;
								ef++)
			;
		if (ef->type != fi->type) {	/* Not in current format so add it here */
			ee->type = fi - &FieldTypes[0];
			ee++;
		}
	}
	ee->type = END_EDITLIST;

	FBoxA_CurLines		= ee - &AvailableFields[0];
	FBoxSelected		= FBOX_NOSELECT;
	FBoxDragSelect		= FBOX_NOSELECT;
	FormatCurLMB		= 0;	/* Left mouse button not selected at start */
	FormatMovingField	= 0;
	/*
	 *		Ensure slider gadget is disabled (it might have been left
	 *		enabled from an earlier time, which can be confusing).
	 */
	if (FormWindow) {
		GT_SetGadgetAttrs(Gadget[GID_FORMWIDTH], FormWindow,  NULL,
						  GA_Disabled, TRUE,
						  TAG_DONE);
	}
}

/*
 *		OpenFormatWindow()
 *
 *		Opens the format editor window with all the format gadgets.
 *		Returns TRUE for success, FALSE for failure.
 *		The window will contain all necessary gadgets.
 *
 *		As with the Settings window, we try a variety of fonts until we
 *		find one that fits onto the screen.
 */
int OpenFormatWindow(void)
{
	static struct TextAttr formgfontattr =
		{ "empty-storage-for-format1-fonts", 0, FS_NORMAL, FPB_DISKFONT};
	static struct TextAttr formbfontattr =
		{ "empty-storage-for-format2-fonts", 0, FS_NORMAL, FPB_DISKFONT};

	static int width;		/* Maintained from call to call */
	static int height;

	int left = CurSettings.FormWinLeft;
	int top  = CurSettings.FormWinTop;
	struct TextAttr *gadgetfa;
	struct TextAttr *bufferfa;
	struct Window *win;
	int i;

	CheckSegTracker();
	if (FormWindow) {
		WindowToFront(FormWindow);
		ActivateWindow(FormWindow);
		return (TRUE);
	}
	if (!CheckForScreen())
		return (FALSE);

	strcpy(FormatSaveFormat, BufFormat);	/* Save for possible Undo */

	/*
	 *		Now try all combinations of gadget and buffer font until we
	 *		find a font combination that fits on our screen
	 */
	for (i = 0; gadgetfa = MainWindowFontList[i].gadgetfa; i++) {
		bufferfa = MainWindowFontList[i].bufferfa;
		if (CreateFormatGadgets(gadgetfa, bufferfa, TRUE, &width, &height))
			break;
	}
	if (!gadgetfa) {
		ShowError(MSG(MSG_ERROR_OPENFORMAT));
		return (FALSE);
	}
	/*
	 *		Now copy our font attributes so that our gadgets won't strangely
	 *		get changed behind the scenes if we update our fonts
	 */
	strcpy(formgfontattr.ta_Name, gadgetfa->ta_Name);
	formgfontattr.ta_YSize = gadgetfa->ta_YSize;
	gadgetfa = &formgfontattr;

	strcpy(formbfontattr.ta_Name, bufferfa->ta_Name);
	formbfontattr.ta_YSize = bufferfa->ta_YSize;
	bufferfa = &formbfontattr;

	if (left == -1)  left = (ScreenWidth  - width)  / 2;
	if (top  == -1)  top  = (ScreenHeight - height) / 2;

	win = OpenWindowTags(NULL,
						 WA_Title,			MSG(MSG_FORMAT_TITLE),
						 WA_IDCMP,			IDCMP_CLOSEWINDOW	 |
											IDCMP_REFRESHWINDOW	 |
											IDCMP_NEWSIZE		 |
											IDCMP_RAWKEY		 |
											IDCMP_MOUSEBUTTONS	 |
											IDCMP_INACTIVEWINDOW |
											BUTTONIDCMP			 |
											SLIDERIDCMP,
						 WA_Left,  			left,
						 WA_Top,  			top,
						 WA_Width,			width,
						 WA_Height,			height,
						 WA_Flags,			WFLG_DRAGBAR		 |
						 					WFLG_DEPTHGADGET	 |
											WFLG_ACTIVATE		 |
											WFLG_RMBTRAP		 |
											WFLG_NEWLOOKMENUS,
						 RefreshTag,      	TRUE,
						 WA_NoCareRefresh,	NoCareRefreshBool,
						 WA_PubScreen,		SnoopScreen,
						 TAG_DONE);
	if (!win) {
		ShowError(MSG(MSG_ERROR_OPENFORMAT));
		return (FALSE);
	}

	if (!FormGadList) {
		FormGadList = CreateFormatGadgets(gadgetfa, bufferfa, 0, 0, 0);
		if (!FormGadList) {
			CloseWindow(win);
			ShowError(MSG(MSG_ERROR_OPENFORMAT));
			return (FALSE);
		}
	}

	/*
	 *		Now try and create our BOBs for the window
	 */
	TextBob = CreateBob(win->RPort, FBoxB_Width + 8,
								    FormBufFont->tf_YSize + 4, 1);
	if (!TextBob) {
		ShowError(MSG(MSG_ERROR_OPENFORMAT));
		FreeGadgets(FormGadList);
		FormGadList	= NULL;
		CloseWindow(win);
		return (FALSE);
	}
	AddBob(&TextBob->bob, win->RPort);

	/*
	 *		Now add gadgets to screen window, and then setup the current
	 *		format string for editing.
	 */
	AddGList(win, FormGadList, -1, -1, NULL);
	RefreshGList(FormGadList, win, NULL, -1);
	GT_RefreshWindow(win, NULL);

	/*
	 *		All done, so initialise some globals and return
	 */
	FormWindow		= win;
	FormWindowPort	= win->UserPort;
	FormWindowMask	= 1 << FormWindowPort->mp_SigBit;

	CreateFormatLists(BufferEFormat);
	/*
	 *		Finally, draw our window to reflect this
	 */
	RedrawFormatWindow();
	if (DisableNestCount)
		DisableWindow(FormWindow, &FormRequester);
	return (TRUE);
}

/*
 *		CloseFormatWindow(void)
 *
 *		Close the format window and free gadgets associated with the window.
 */
void CloseFormatWindow(void)
{
	if (FormWindow) {
		if (TextBob) {
			FreeBob(TextBob);
			TextBob = NULL;
		}
		RecordWindowSizes();
		CloseWindow(FormWindow);
		CloseFont(FormGadFont);
		CloseFont(FormBufFont);
		FormWindow     = NULL;
		FormWindowMask = 0;
		FormGadFont	   = NULL;
		FormBufFont	   = NULL;
	}
	if (FormGadList) {
		FreeGadgets(FormGadList);
		FormGadList = NULL;
	}
	if (FormVI) {
		FreeVisualInfo(FormVI);
		FormVI = NULL;
	}
}
 
/*
 *		FormatHitDetect(x, y)
 *
 *		Does a bounds check for the specified X/Y co-ordinates against the
 *		format editor window. A return value in the range 0..EF_COUNT-1
 *		indicates that line number in the right hand window was hit.
 *
 *		A return of FBOX_SELECTLEFT indicates that the left box was selected
 *		(not sure which line; doesn't really matter).
 *
 *		A return of FBOX_NOSELECT indicates neither box was selected.
 */
int FormatHitDetect(int x, int y)
{
	if (x >= FBoxA_Left && x <= (FBoxA_Left + FBoxA_Width  - 1) &&
		y >= FBoxA_Top  && y <= (FBoxA_Top  + FBoxA_Height - 1))
	{
		return (FBOX_SELECTLEFT);
	}
	
	if (x >= FBoxB_Left && x <= (FBoxB_Left + FBoxB_Width  - 1) &&
		y >= FBoxB_Top  && y <= (FBoxB_Top  + FBoxB_Height - 1))
	{
		if (y <= FBoxB_Top + FBoxSpacing + 2)
			return (0);
		else
			return (y - FBoxB_Top - 2) / FBoxSpacing;
	}
	return (FBOX_NOSELECT);
}

/*
 *		InstallNewFormat(type)
 *
 *		Installs the currently defined format into the main window for
 *		the user to see immediately.
 *
 *		If type is NEW_LISTVIEW, then the new format is assumed to be
 *		from the format editor window's CurrentFields list. The
 *		setup window, if open, will have its own format string updated
 *		accordingly.
 *
 *		If type is NEW_STRING, then the new format is assumed to be
 *		stored in the BufFormat string. The format window, if open,
 *		will have its own format listview updated accordingly.
 *
 *		If the buffer string is completely invalid (i.e. evaluates to
 *		an empty format) then it will be replaced with an empty string.
 */
void InstallNewFormat(int type)
{
	if (type == NEW_LISTVIEW) {
		BufferWidth = ConvertListToFormat(CurrentFields, BufferEFormat);
	} else /* type == NEW_STRING */ {
		BufferWidth = ParseFormatString(BufFormat, BufferEFormat,
												   MAX_FORM_LEN);
		if (BufferWidth == 0) {
			BufferWidth = ParseFormatString(DefaultSettings.Setup.BufferFormat,
											BufferEFormat, MAX_FORM_LEN);
		}
		if (FormWindow) {
			CreateFormatLists(BufferEFormat);
			RedrawFormatWindow();
		}
	}
	BuildFormatString(BufferEFormat, BufFormat, MAX_FORM_LEN);
	if (SetWindow) {
		GT_SetGadgetAttrs(Gadget[GID_BUFFORMAT], SetWindow, NULL,
						  GTST_String,	BufFormat,
						  TAG_DONE);
	}
	ClearMainRHS = 1;
	InitMainMargins();
	UpdateMainHScroll();
	RedrawMainWindow();
}

/*
 *		HandleFormatMsgs()
 *
 *		Processes all outstanding messages associated with the Format
 *		editor gadget window. To be called whenever we get a format event.
 */
void HandleFormatMsgs(void)
{
	static int activegad;			/* Non-zero if gadget is depressed   */
	static int activekey;			/* Keycode shortcut of activegad     */

	struct IntuiMessage *imsg;
	int doneform = SEL_NONE;
	int hit;
	int lmb;
	int rmb;

	if (!FormWindow)
		return;

	while ((imsg = GT_GetIMsg(FormWindowPort)) != NULL) {
		struct Gadget *gad;
		int dxwidth = 0;				/* Amount to adjust width by */
		int newrow  = FBoxSelected;		/* Currently selected row	 */
		int gadid;

		switch (imsg->Class) {
			
			case IDCMP_CLOSEWINDOW:
				doneform = SEL_USE;
				break;

			case IDCMP_REFRESHWINDOW:
				GT_BeginRefresh(FormWindow);
				RedrawFormatWindow();
				GT_EndRefresh(FormWindow, TRUE);
				break;

			case IDCMP_MOUSEBUTTONS:
				lmb = imsg->Qualifier & IEQUALIFIER_LEFTBUTTON;
				rmb = imsg->Qualifier & IEQUALIFIER_RBUTTON;
				/*
				 *		If we're already dragging something, then the
				 *		right mouse button allows it to be cancelled.
				 */
				if (FormatMovingField && rmb)
					goto cancel_moving_field;

				if (lmb == FormatCurLMB)	/* Ignore spurious events */
					break;

				hit = FormatHitDetect(imsg->MouseX, imsg->MouseY);
				if (lmb) {
					/*
					 *		User is trying to select or pick-up a new
					 *		event.
					 */
					if (hit >= 0 && hit < FBoxB_CurLines &&
												hit != FBoxSelected) {
						if (FBoxSelected >= 0 && FBoxSelected < FBoxB_CurLines)
							ShowFormatLine(FBoxSelected, FORMAT_UNSELECTED);
						FBoxSelected = hit;
						ShowFormatLine(hit, FORMAT_SELECTED);
						GT_SetGadgetAttrs(Gadget[GID_FORMWIDTH],
										  FormWindow,  NULL,
										  GA_Disabled, FALSE,
										  GTSL_Level, CurrentFields[hit].width,
										  TAG_DONE);
					}
					if (hit != FBOX_NOSELECT) {
						/*
						 *		Pick-up the icon under mouse
						 */
						if (PickupBob(hit, imsg->MouseX, imsg->MouseY)) {
							FormatMovingField = 1;
							ReportMouse(1, FormWindow);	/* Enable mouse moves */
						}
					}
				} else {
					/*
					 *		We might be finished doing a drag of a field
					 *		from one position to another -- if so, drop the
					 *		field in it's new place.
					 */
					if (FormatMovingField) {
						int curdragselect = FBoxDragSelect;

						FormatMovingField = 0;
						ReportMouse(0, FormWindow);	/* Disable mouse moves  */
						DropBob();
						if (curdragselect != FBOX_NOSELECT)
							InstallNewFormat(NEW_LISTVIEW);
					}
				}
				FormatCurLMB = lmb;
				break;
						
			case IDCMP_MOUSEMOVE: 
				if (FormatMovingField) {
					UpdateBob(imsg->MouseX, imsg->MouseY);
					break;
				}

				/*
				 *		Fall through to handle MOUSEMOVE for slider gadget
				 */

			case IDCMP_GADGETDOWN:
			case IDCMP_GADGETUP:
				gad   = (struct Gadget *)imsg->IAddress;
				gadid = gad->GadgetID;

handle_form_gads:
				switch (gadid) {

					case GID_FORMUSE:
						doneform = SEL_USE;
						break;

					case GID_FORMCANCEL:
						doneform = SEL_CANCEL;
						break;

					case GID_FORMUNDO:
						strcpy(BufFormat, FormatSaveFormat);
						InstallNewFormat(NEW_STRING);
						break;

					case GID_FORMWIDTH:
						/*
				 		 *		The slider has been moved, so update the
				 		 *		currently selected item accordingly.
				 		 */
						if (FBoxSelected >= 0 &&
											FBoxSelected < FBoxB_CurLines) {
							CurrentFields[FBoxSelected].width = imsg->Code;
							ShowFormatLine(FBoxSelected, FORMAT_SELECTED);
							if (imsg->Class == IDCMP_GADGETUP)
								InstallNewFormat(NEW_LISTVIEW);
						}
						break;
				}
				break;

			case IDCMP_INACTIVEWINDOW:
				/*
				 *		Cancel any currently pressed button
				 */
				if (activegad) {
					ShowGadget(FormWindow, Gadget[activegad], GADGET_UP);
					activegad = 0;
				}
				/*
				 *		If we're dragging something and the window
				 *		goes inactive, drop it. Otherwise, when the
				 *		window is reactivated, it will still be attached
				 *		to the mouse pointer!
				 */
				if (FormatMovingField) {
					FormatCurLMB      = 0;
cancel_moving_field:
					ReportMouse(0, FormWindow);
					UpdateBob(0, -9999);		/* Make position invalid */
					DropBob();
					FormatMovingField = 0;
					if (imsg->Class == IDCMP_INACTIVEWINDOW) {
						/*
						 *		If we went inactive, then the act of
						 *		erasing the BOB has probably resulted
						 *		in a portion of the window frame being
						 *		restored in its active colours, even
						 *		though the window is now inactive -- so
						 *		we refresh it to get around this.
						 */
						RefreshWindowFrame(FormWindow);
					}
				}
				break;
					
			case IDCMP_RAWKEY:
			{
				int shift    = imsg->Qualifier & IE_SHIFT;
				int altctrl  = imsg->Qualifier & (IE_ALT | IE_CTRL);
				int upstroke = imsg->Code & 0x80;
				int keypress = ConvertIMsgToChar(imsg);

				/*
				 *		Handle keyboard shortcuts
				 */
				if (FormatMovingField && keypress == '\033') {
					/*
					 *		ESC cancels any drag currently in operation
					 */
					goto cancel_moving_field;
				}

				gadid  = FormKeyboard[keypress];
				if (activegad) {
					/*
					 *		We're releasing a gadget that was pressed
					 *		earlier, so handle it now
					 */
					int samekey = (imsg->Code & 0x7f) == activekey;

					if (samekey && !upstroke)	/* Ignore repeated keys */
						break;

					ShowGadget(FormWindow, Gadget[activegad], GADGET_UP);
					if (samekey) {
						/*
						 *		Just released the key that was originally
						 *		pressed for this gadget, so now go and
						 *		handle the gadget action.
						 */
						gadid     = activegad;
						gad       = Gadget[gadid];
						activegad = 0;
						goto handle_form_gads;
					}

					/*
					 *		If the above check didn't kick in, it means
					 *		we got a downpress of a different key, so
					 *		disable the gadget for this key and continue
					 *		to press the other key.
					 */
					activegad = 0;
				}

				/*
				 *		Check for normal gadget keyboard shortcuts
				 */
				if (gadid) {
					gad = Gadget[gadid];
					if (gadid == GID_FORMWIDTH) {
						/*
						 *		Got a slider adjustment request
						 */
						if (imsg->Qualifier & IE_SHIFT)
							dxwidth = -1;
						else
							dxwidth = 1;

						if (newrow == FBOX_NOSELECT)
							newrow = 0;	/* If no row active, pick first row */

						if (FBoxB_CurLines == 0)  /* Ignore if box is empty */
							break;

						goto format_move_slider;
					}
					/*
					 *		Must be a simple button press so highlight
					 *		the button and handle accordingly
					 */
					ShowGadget(FormWindow, gad, GADGET_DOWN);
					activegad = gadid;
					activekey = imsg->Code;
					break;
				}

				/*
				 *		Handle cursor movement. This lets us choose a
				 *		new item in the format box, or adjust the width
				 *		of an existing item.
				 */
				if (!FBoxB_CurLines)	/* A format must exist */
					break;
				
				if (newrow == FBOX_NOSELECT)
					newrow = -1;	/* Ensure cursor down selects item 0 */

				switch (imsg->Code) {
					case CURSORUP:
						if (newrow > 0 && !(shift | altctrl))
							newrow--;
						else
							newrow = 0;
						break;

					case CURSORDOWN:
						if (shift | altctrl)
							newrow = FBoxB_CurLines - 1;
						else if (newrow < (FBoxB_CurLines-1))
							newrow++;
						break;

					case CURSORLEFT:
						dxwidth = -1;
						if (shift)   dxwidth  = -5;
						if (altctrl) dxwidth  = -MAX_FIELD_LEN;
						break;

					case CURSORRIGHT:
						dxwidth = 1;
						if (shift)   dxwidth = 5;
						if (altctrl) dxwidth = MAX_FIELD_LEN;
						break;

					case HELPKEY:
						ShowAGuide(MSG(MSG_LINK_FORMATWIN));
						goto done_keys;

					default:
						goto done_keys;		/* Ignore all other keys */
				}

format_move_slider:
				/*
				 *		First check if the row has changed. (As well as
				 *		changing via cursor up/down, this can also happen
				 *		with left/right if there was no item selected to
				 *		begin with
				 */
				if (newrow < 0)
					newrow = 0;

				if (newrow != FBoxSelected && FBoxB_CurLines > 0) {
					/*
					 *		Unactivate old row, activate a new row
					 */
					if (FBoxSelected >= 0 && FBoxSelected < FBoxB_CurLines)
						ShowFormatLine(FBoxSelected, FORMAT_UNSELECTED);
					FBoxSelected = newrow;
					ShowFormatLine(newrow, FORMAT_SELECTED);
					GT_SetGadgetAttrs(Gadget[GID_FORMWIDTH],
									  FormWindow,  NULL,
									  GA_Disabled, FALSE,
									  GTSL_Level, CurrentFields[newrow].width,
									  TAG_DONE);
				}

				/*
				 *		Now check if the horizontal position of the
				 *		slider has changed.
				 */
				if (dxwidth) {
					int newwidth = CurrentFields[FBoxSelected].width + dxwidth;

					if (newwidth < 1)				newwidth = 1;
					if (newwidth > MAX_FIELD_LEN)	newwidth = MAX_FIELD_LEN;
					if (FBoxSelected != FBOX_NOSELECT) {
						if (newwidth != CurrentFields[FBoxSelected].width) {
							GT_SetGadgetAttrs(Gadget[GID_FORMWIDTH],
											  FormWindow,  NULL,
											  GTSL_Level,  newwidth,
											  TAG_DONE);
							CurrentFields[FBoxSelected].width = newwidth;
							ShowFormatLine(FBoxSelected, FORMAT_SELECTED);
							InstallNewFormat(NEW_LISTVIEW);
						}
					}
				}
done_keys:
				break;
			}
		}
		GT_ReplyIMsg(imsg);
	}
	if (doneform != SEL_NONE) {
		CloseFormatWindow();
		if (doneform == SEL_CANCEL) {
			strcpy(BufFormat, FormatSaveFormat);
			InstallNewFormat(NEW_STRING);
		}
	}
}

/*
 *		-----------------------------------------------------------------------
 *		Now our BOB/GEL routines for the drag-and-drop format editor.
 *		These should really be in their own module, but they're being
 *		put here for now for convenience.
 *		-----------------------------------------------------------------------
 */

/*
 *		InitGel(rastport, width, height, transp)
 *
 *		This function does all the initialisation necessary to allow us
 *		to use a single BOB in the given rastport. A new bitmap is allocated
 *		of dimensions width x height into which data can be rendered.
 *
 *		If successful, this function returns a pointer to a MyGel structure
 *		which contains, among other things, the rastport and bob structure
 *		you will use when calling system routines to manipulate the bob.
 *
 *		Note that the bob must be removed from the Gel list before rendering
 *		into it.
 *
 *		If transp is true, then the BOB will be transparent on colour zero.
 *		In this case, you must call InitMasks(mygel->vsprite) whenever
 *		you make a change to the image, to recalculate the mask.
 */
MyGel *CreateBob(struct RastPort *rport, int width, int height, int transp)
{
	struct GelsInfo *gInfo;
	struct Bob		*bob;
	struct VSprite	*vsprite;
	MyGel			*mygel;
	ULONG			linesize;
	ULONG			planesize;
	ULONG			imagesize;
	ULONG			chipsize;
	UBYTE 			*chipdata;
	int				wordwidth;
	int				depth;
	int				i;

	wordwidth	= (width + 15) >> 4;
	depth		= rport->BitMap->Depth;

	mygel = (MyGel *)AllocMem(sizeof(MyGel), MEMF_CLEAR);
	if (!mygel)
		return (NULL);
		
	mygel->mainrast		= rport;
	gInfo				= &mygel->gelinfo;
	gInfo->nextLine		= mygel->nextline;
	gInfo->lastColor	= mygel->lastcolor;
	gInfo->collHandler	= &mygel->colltable;
	gInfo->sprRsrvd		= 0x03;

	/*
	 *		Set left-most and top-most to 1 to better keep items
	 *		inside the display boundaries.
	 */
	gInfo->leftmost   = 1;
	gInfo->topmost	  = 1;
	gInfo->rightmost  = (rport->BitMap->BytesPerRow << 3) - 1;
	gInfo->bottommost = rport->BitMap->Rows - 1;
	rport->GelsInfo	  = gInfo;
	InitGels(&mygel->vshead, &mygel->vstail, gInfo);

	/*
	 *		Now allocate a new BOB to be linked into this structure.
	 *		We also allocate a bitmap associated with the bob, and
	 *		give it its own RastPort so that we can render into it.
	 */
	linesize	= sizeof(WORD)  * wordwidth;
	planesize	= linesize      * height;
	imagesize	= planesize		* depth;
	chipsize	= imagesize*2 + linesize + planesize;
	bob			= &mygel->bob;
	vsprite		= &mygel->bobvsprite;

	if (!(chipdata = AllocMem(chipsize, MEMF_CHIP | MEMF_CLEAR))) {
		rport->GelsInfo = NULL;
		FreeMem(mygel, sizeof(MyGel));
		return (NULL);
	}
	mygel->chipdata		= (void *)(chipdata);
	mygel->chipsize		= chipsize;

	mygel->planedata	= (void *)(chipdata);
	bob->SaveBuffer		= (void *)(chipdata + imagesize);
	vsprite->BorderLine = (void *)(chipdata + imagesize*2);
	vsprite->CollMask	= (void *)(chipdata + imagesize*2 + linesize);

	vsprite->Y			= -9999;
	vsprite->X			= -9999;
	vsprite->Flags      = SAVEBACK | (transp ? OVERLAY : 0);
	vsprite->Width      = wordwidth;
	vsprite->Depth      = depth;
	vsprite->Height     = height;
	vsprite->MeMask     = 0;
	vsprite->HitMask    = 0;
	vsprite->ImageData  = (void *)mygel->planedata;
	vsprite->SprColors  = NULL;
	vsprite->PlanePick  = -1;
	vsprite->PlaneOnOff = 0x00;
	InitMasks(vsprite);

	vsprite->VSBob      = bob;
	bob->BobVSprite     = vsprite;
	bob->ImageShadow    = vsprite->CollMask;
	bob->Flags          = 0;
	bob->Before         = NULL;
	bob->After          = NULL;
	bob->BobComp        = NULL;
	bob->DBuffer		= NULL;

	/*
	 *		Now setup the Rastport and Bitmap so we can render into the BOB
	 */
	InitRastPort(&mygel->rastport);
	InitBitMap(&mygel->bitmap, depth, width, height);
	mygel->rastport.BitMap = &mygel->bitmap;

	for (i = 0; i < depth; i++)
		mygel->bitmap.Planes[i] = mygel->planedata + i * planesize;

	return (mygel);
}

/*
 *		FreeBob(mygel)
 *
 *		Frees the bob allocated earlier, along with all other associated info 
 *		Also removes all GELs from the system rastport.
 */
void FreeBob(MyGel *mygel)
{
	mygel->mainrast->GelsInfo = NULL;
	FreeMem(mygel->chipdata, mygel->chipsize);
	FreeMem(mygel, sizeof(MyGel));
}

/*
 *		PickupBob(hit, x, y)
 *
 *		Picks up the line and copies it into our bob's bitmap, then adds
 *		the bob to the screen at the current mouse location so the
 *		user can drag it around.
 *
 *		If the hit is correct, we can ignore Y since the field being
 *		dragged is in the right box and we already know which line.
 *		Otherwise, Y is a pixel co-ordinate that we use to calculate
 *		the line of the box that's being chosen.
 *
 *		If hit is FBOX_SELECTLEFT, then we need to calculate which line
 *		we are dealing with.
 *
 *		We also record the start line we use.
 *
 *		Returns true for success, false if we didn't have a hit after all.
 */
int PickupBob(int hit, int x, int y)
{
	struct RastPort *rport    = FormWindow->RPort;
	struct RastPort *bobrport = &TextBob->rastport;
	char viewbuf[50];
	int line;
	int box;
	int xpos;
	int ypos;
	int len;

	if (hit == FBOX_NOSELECT) {
		return (FALSE);
	} else if (hit == FBOX_SELECTLEFT) {
		box  = FORMAT_LEFTBOX;
		line = (y - FBoxA_Top - 2);
		if (line < FBoxSpacing)
			line = 0;
		else
			line /= FBoxSpacing;
		if (line >= FBoxA_CurLines)
			return (FALSE);
	} else {
		/* Right box */
		box  = FORMAT_RIGHTBOX;
		line = hit;
		if (line >= FBoxB_CurLines)
			return (FALSE);
	}

	/*
	 *		Okay, got a definite hit so go ahead and start the drag
	 */
	FormatDragBox	= box;
	FormatDragLine	= line;
	
	SetRast(bobrport, 0);				/* Erase rast bitmap 	*/
	SetAPen(bobrport, 2);				/* White foreground		*/
	SetBPen(bobrport, 1);				/* Black background		*/
	SetDrMd(bobrport, JAM2);			/* Solid text			*/
	SetFont(bobrport, FormBufFont);

	if (box == FORMAT_LEFTBOX) {
		FieldInit *fi = &FieldTypes[AvailableFields[line].type];

		mysprintf(viewbuf, FBoxA_FormString, MSG(fi->titlemsgid), fi->idchar);
		len  = FBoxA_NumChars;
		xpos = FBoxA_Left + 2;
		ypos = FBoxA_Top  + 2 + line * FBoxSpacing;
	} else {
		FieldInit *fi = &FieldTypes[CurrentFields[line].type];

		mysprintf(viewbuf, FBoxB_FormString, MSG(fi->titlemsgid),
						   CurrentFields[line].width, fi->idchar);
		len  = FBoxB_NumChars;
		xpos = FBoxB_Left + 2;
		ypos = FBoxB_Top  + 2 + line * FBoxSpacing;
	}
	TextBob->bob.BobVSprite->X = xpos;
	TextBob->bob.BobVSprite->Y = ypos;
	FBoxDeltaX = xpos - x;
	FBoxDeltaY = ypos - y;

	Move(bobrport, 0, FormBufFont->tf_Baseline+1);
	Text(bobrport, viewbuf, len);

	/*
	 *		Now draw highlight above and below the text
	 */
	SetAPen(bobrport, 1);
	Move(bobrport, 0, 0);
	Draw(bobrport, len * FormBufFont->tf_XSize - 1, 0);
	Move(bobrport, 0, FormBufFont->tf_YSize + 1);
	Draw(bobrport, len * FormBufFont->tf_XSize - 1, FormBufFont->tf_YSize + 1);

	InitMasks(TextBob->bob.BobVSprite);
	SortGList(rport);
	DrawGList(rport, ViewPortAddress(FormWindow));
	return (1);
}

/*
 *		DropBob()
 *
 *		Releases the current bob at the target, and updates the two
 *		lists/displays accordingly.
 */
void DropBob(void)
{
	EditEvent savedev;
	EditEvent *ee;
	int destpos;
	int seltype;

	destpos = FBoxDragSelect;			/* Save destination drop selection	*/
	UpdateBob(0, -9999);				/* Kill any highlighted region		*/

	/*
	 *		Now re-arrange the edit lists to reflect the new position of
	 *		the dragged field. There are three cases to consider:
	 *
	 *			Left box	-> Right box
	 *			Right box	-> Right box
	 *			Right box	-> Left box
	 */
	if (destpos == FBOX_NOSELECT)
		return;
	
	ShowFormatLine(FBoxSelected, FORMAT_UNSELECTED);
	if (FBoxSelected >= 0 && FBoxSelected < EF_COUNT)
		seltype = CurrentFields[FBoxSelected].type;
	else
		seltype = -1;

	/*
	 *		First, delete item from its current list
	 */
	if (FormatDragBox == FORMAT_LEFTBOX) {
		/*
		 *		Deleting from left box
		 */
		ee		= &AvailableFields[FormatDragLine];
		savedev.type  = ee->type;
		savedev.width = FieldTypes[ee->type].defwidth;

		while (ee->type != END_EDITLIST) {
			ee[0] = ee[1];
			ee++;
		}
		FBoxA_CurLines--;
		seltype = savedev.type;	/* New selection is item we just dragged */
	} else {
		/*
		 *		Deleting from right box. If the drop position is after
		 *		the point where we're deleting, we adjust it to reflect
		 *		the deleted line.
		 */

		if (destpos > FormatDragLine && destpos <= EF_COUNT)
			destpos--;

		ee		= &CurrentFields[FormatDragLine];
		savedev = *ee;
		while (ee->type != END_EDITLIST) {
			ee[0] = ee[1];
			ee++;
		}
		FBoxB_CurLines--;
	}
	/*
	 *		Now add the item (savedev) to the new list in the appropriate
	 *		place
	 */
	if (destpos == FBOX_SELECTLEFT) {
		/*
		 *		Search the existing left box list looking for the best place
		 *		to insert the item -- we do this in strict alphabetical
		 *		order (based on the % identifer)
		 */
		int newid = FieldTypes[savedev.type].idchar;
		EditEvent tempev;

		for (ee = &AvailableFields[0]; ee->type != END_EDITLIST; ee++)
			if (FieldTypes[ee->type].idchar > newid)
				break;

		do {
			tempev	= *ee;
			*ee 	= savedev;
			savedev	= tempev;
			ee++;
		} while (ee[-1].type != END_EDITLIST);
		FBoxA_CurLines++;
	} else {
		/* 
		 *		Adding line to the right box. Simply insert it at the
		 *		specified location.
		 */
		EditEvent tempev;

		ee = &CurrentFields[destpos];
		do {
			tempev	= *ee;
			*ee 	= savedev;
			savedev	= tempev;
			ee++;
		} while (ee[-1].type != END_EDITLIST);
		FBoxB_CurLines++;
	}
	/*
	 *		Now search our right box list to see can we match the item
	 *		that was currently highlighted when we entered this routine.
	 */
	FBoxSelected = 0;
	for (ee = &CurrentFields[0]; ee->type != END_EDITLIST; ee++) {
		if (ee->type == seltype)
			break;
		FBoxSelected++;
	}
	if (ee->type == END_EDITLIST) {
		FBoxSelected = FBOX_NOSELECT;
		GT_SetGadgetAttrs(Gadget[GID_FORMWIDTH], FormWindow,  NULL,
						  GA_Disabled, TRUE,
						  TAG_DONE);
	} else {
		GT_SetGadgetAttrs(Gadget[GID_FORMWIDTH], FormWindow,  NULL,
						  GA_Disabled, FALSE,
						  GTSL_Level,  ee->width,
						  TAG_DONE);
	}

	/*
	 *		Now, finally, update our window to show the new arrangement.
	 */
	RedrawFormatWindow();
}

/*
 *		UpdateBob(x, y)
 *
 *		Updates the current position of the bob on the screen, and
 *		draws any highlights etc. that are deemed necessary.
 */
void UpdateBob(int x, int y)
{
	int bfonty = FormBufFont->tf_YSize;
	int pos;

	/*
	 *		Now see if we are overlapping any of the drop regions, and if
	 *		so, highlight the region appropriately.
	 *
	 *		When dragging into the left box, we highlight the entire box
	 *		since the order of items is pre-determined and it doesn't
	 *		make any sense to drop a field into one particular place.
	 *
	 *		When dragging into the right box, we highlight a line in
	 *		between two other lines, which shows where the item will
	 *		be inserted.
	 *
	 *		For neatness, we don't show highlighting which would have
	 *		no effect on the current settings (i.e. moving a field to
	 *		the same position it's already in).
	 */
	pos = FBOX_NOSELECT;
	if (x >= FBoxA_Left && x <= (FBoxA_Left + FBoxA_Width  - 1) &&
	    y >= FBoxA_Top  && y <= (FBoxA_Top  + FBoxA_Height - 1) &&
		FormatDragBox != FORMAT_LEFTBOX) {
		/*
		 *		Got a hit in left-hand box
		 */
		pos = FBOX_SELECTLEFT;
	} else if (x >= FBoxB_Left && x <= (FBoxB_Left + FBoxB_Width - 1) &&
			   y >= (FBoxB_Top - 10) && y <= (FBoxB_Top + FBoxB_Height + 10)) {
		/*
		 *		Got a hit in the right-hand box, so now calculate which line
		 */
		pos = (y - FBoxB_Top - 2 + bfonty/2) / FBoxSpacing;

		if (pos < 0)				pos = 0;
		if (pos > FBoxB_CurLines)	pos = FBoxB_CurLines;
		if (FormatDragBox == FORMAT_RIGHTBOX &&
						(pos == FormatDragLine || pos == (FormatDragLine+1))) {
			pos = FBOX_NOSELECT;	/* Don't highlight line we dragged from */
		}
	}

	if (pos != FBoxDragSelect) {
		/*
		 *		Currently selected item has changed, so erase current one,
		 *		then draw new one. We also refresh the currently selected
		 *		line, in case it got trashed by the highlighting.
		 *
		 *		Note that we must first move the bob off-screen while we
		 *		render the changes, otherwise it gets confused and tends
		 *		to obscure the output.
		 */
		TextBob->bob.BobVSprite->X = 0;
		TextBob->bob.BobVSprite->Y = -9999;
		SortGList(FormWindow->RPort);
		DrawGList(FormWindow->RPort, ViewPortAddress(FormWindow));
		ShowDragSelect(FBoxDragSelect, FORMAT_UNSELECTED);
		ShowFormatLine(FBoxSelected, FORMAT_SELECTED);
		ShowDragSelect(pos, FORMAT_SELECTED);
		FBoxDragSelect = pos;
	}

	TextBob->bob.BobVSprite->X = x + FBoxDeltaX;
	TextBob->bob.BobVSprite->Y = y + FBoxDeltaY;
	SortGList(FormWindow->RPort);
	DrawGList(FormWindow->RPort, ViewPortAddress(FormWindow));
}
