/*
 *		MAINWIN.C											vi:ts=4
 *
 *      Copyright (c) Eddy Carroll, September 1994.
 *
 *		This module handles the main SnoopDos window, outputting new events
 *		to the window, menu options, etc.
 */		

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

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

/*
 *		In earlier versions, we used to lock layers whenever we rendered
 *		event output to the SnoopDos main window buffer. This avoided
 *		deadlocks with input.device. Now we use a different work around,
 *		but for the sake of documentation, we leave the original calls
 *		in (you never know when they might come in handy)
 *
 *		(The different workaround is to never monitor tasks that have
 *		locked layers on the SnoopDos screen/window).
 */
#if 0
#define LOCK_LAYERS	\
	(LockLayerInfo(&MainWindow->WScreen->LayerInfo),	\
	 LockLayer(0, MainWindow->RPort->Layer))

#define UNLOCK_LAYERS \
	(UnlockLayer(MainWindow->RPort->Layer),	\
	 UnlockLayerInfo(&MainWindow->WScreen->LayerInfo))

#else
#define LOCK_LAYERS
#define UNLOCK_LAYERS
#endif


/*
 *		Interface to RawPutChar() in exec.library
 *		(It takes a single parameter in D0)
 */
#pragma libcall SysBase RawPutChar 204 001

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

/*
 *		Global vars used by the main window
 */
struct Gadget		*MainGadList;		/* Main gadget list			*/
struct Menu			*MainWinMenu;		/* Main window menu			*/
struct TextFont		*BufferFont;		/* Handle for buffer font	*/
struct TextAttr		*CurMainGadgetFA;	/* Current gadget font attr	*/
struct TextAttr		*CurMainBufferFA;	/* Current buffer font attr	*/

struct RastPort		InvertRP;			/* Used when sizing columns	*/

int NumBufLines;			/* Current number of lines in buffer	*/
int ChangedSpacing;			/* 1 = edited spacing since last redraw	*/
int ScrollDirection;		/* ID of current scroll gadget, or 0	*/
int ScrollCount;			/* Used for pacing INTUITICKS in scroll	*/
int RemovedGadgets;			/* True if gadgets removed from window	*/
int AwaitingResize;			/* True if waiting for window resize	*/

#define RESIZE_DONE		0	/* Resize is now complete				*/
#define RESIZE_MIDDLE	1	/* Resize happened in middle of buffer	*/
#define RESIZE_BOTTOM	2	/* Resize happened at end of buffer		*/

#define MOVECOL_FREE	0	/* Reposition column freely				*/
#define MOVECOL_FIXED	1	/* Reposition column fixed wrt RHS col	*/

#define INVERT_HEADER	0	/* Invert only the header part of col	*/
#define INVERT_FULLBOX	1	/* Invert entire column              	*/

int	BoxLeft;				/* Pixel offset of left edge of box		*/
int	BoxTop;					/* Pixel offset of top edge of box		*/
int	BoxWidth;				/* Width of box in pixels				*/
int	BoxHeight;				/* Height of box in pixels				*/
int	BoxHeaderLine;			/* Baseline for box header text			*/
int	BoxInLeft;				/* Pixel offset of inside top of box	*/
int	BoxInTop;				/* Pixel offset of inside left of box	*/
int	BoxInWidth;				/* Inside width of box					*/
int	BoxInHeight;			/* Inside height of box					*/
int	BoxCharWidth;			/* Width of a single character in box	*/
int BoxCharHeight;			/* Height of a single character in box	*/
int	BoxCols;				/* Number of columns of text in box		*/
int	BoxRows;				/* Number of rows of text in box		*/
int	BoxSpacing;				/* Height between font baselines in box	*/
int	BoxLowest;				/* Bottom point of box scroll arrow		*/
int	BoxBaseline;			/* Baseline of text line 0 in box		*/
int	VBorderGap;				/* Aspect-adjusted space betw hrz lines	*/
int	LogButtonLeft;			/* Left (X) Position of Logfile button	*/
int LogButtonID;			/* Gadget ID of log button on view		*/

int	LeftCol;				/* Leftmost displayed column			*/
int	RightCol;				/* Rightmost displayed column			*/

int DraggingRow;			/* 1 if dragging a row with mouse		*/
int SelectRow;    			/* Row to draw highlighted				*/
int DraggingColumn;			/* 1 if dragging a column with mouse	*/
int ClickStartCol;			/* Col pos where mouse first clicked	*/
int ClickCurCol;			/* Current column where mouse clicked	*/
int SelectStartCol;			/* Start position of selected column	*/
int DragOrigColWidth;		/* Starting width of widening column	*/
int NextOrigColWidth;		/* Starting width of following column	*/
int OrigLeftCol;			/* Starting position of left column		*/
int OrigBufWidth;			/* Starting width of buffer				*/
int MovedColumn;			/* True if column position was altered	*/

EventFormat *DragEvent;		/* Points to column being widened		*/
EventFormat *SelectEvent;	/* Points to column being highlighted	*/

BPTR   LogFile;				/* Handle of output log file			*/
UBYTE *LogBuffer;			/* Buffer for buffered i/o (or NULL)	*/
ULONG  LogBufferSize;		/* Size of logfile output buffer		*/
ULONG  LogBufferPos;		/* Current position in output buffer	*/

UBYTE  MainKeyboard[KB_SHORTCUT_SIZE];	/* For keyboard equivs		*/

/*
 *		This table is used to select the appropriate row qualifier
 *		for the row selection mechanism
 */
UWORD RowQualTable[] = {
	0,				/* ROWQUAL_ANY	 -- ignore qualifiers				*/
	0,				/* ROWQUAL_NONE	 -- don't allow any qualifiers		*/
	IE_SHIFT,		/* ROWQUAL_SHIFT -- only select if SHIFT is pressed	*/
	IE_ALT,			/* ROWQUAL_ALT   -- only select if ALT is pressed	*/
	IE_CTRL,		/* ROWQUAL_CTRL	 -- only select if CTRL is pressed	*/
	IE_ALL			/* ROWQUAL_ALL   -- only select if any qual pressed	*/
};

/*
 *		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.
 */
char   BufferLine[MAX_BUFFER_WIDTH];  /* Storage area for buffer lines */

struct TextAttr TopazFontAttr  = {"topaz.font",	  8, FS_NORMAL, FPB_ROMFONT };
struct TextAttr SystemFontAttr = {SystemFontName, 0, FS_NORMAL, FPB_DISKFONT};
struct TextAttr WindowFontAttr = {WindowFontName, 0, FS_NORMAL, FPB_DISKFONT};
struct TextAttr BufferFontAttr = {BufferFontName, 0, FS_NORMAL, FPB_DISKFONT};

struct {
	struct TextAttr *gadgetfa;
	struct TextAttr *bufferfa;
} MainWindowFontList[] = {
	&WindowFontAttr,	&BufferFontAttr,
	&SystemFontAttr,	&BufferFontAttr,
	&WindowFontAttr,	&SystemFontAttr,
	&SystemFontAttr,	&SystemFontAttr,
	&TopazFontAttr,		&SystemFontAttr,
	&TopazFontAttr,		&TopazFontAttr,
	NULL,				NULL
};

/*
 *		This is used by the boopsi proportional gadgets
 */
static struct TagItem RZ_MapTags[] = {
	PGA_Top, ICSPECIAL_CODE,
	TAG_END
};

/*
 *		This holds the image pointers for our BOOPSI scroll gadgets
 */
struct Image		*ScrollImage[4];

/*
 *		This structure is used to define our four scroll gadgets. We
 *		define the relative positions of each gadget in terms of the
 *		formula (Scale * Width + offset) or (Scale * Height + offset)
 *		where Width and Height are taken from the image structure.
 */
struct ScrollData {
	int		gadgetid;			/* ID of the gadget						*/
	int		imagetype;			/* Which type of image we're creating	*/
	LONG	widthscale;			/* Scale applied to width				*/
	LONG	widthoffset;		/* Offset applied to width 				*/
	LONG	heightscale;		/* Scale applied to height				*/
	LONG	heightoffset;		/* Offset applied to offset				*/
	LONG	borderpos;			/* Tag for border containing gadget		*/
} ScrollArrows[] = {
	GID_LEFTARROW,	LEFTIMAGE,	-2, -17,	-1, 1,		GA_BottomBorder,
	GID_RIGHTARROW,	RIGHTIMAGE,	-1, -17,	-1, 1,		GA_BottomBorder,
	GID_UPARROW,	UPIMAGE,	-1, 1,		-2, -9,		GA_RightBorder,
	GID_DOWNARROW,	DOWNIMAGE,	-1, 1,		-1, -9,		GA_RightBorder
};
	
/*
 *		Now the gadgets for the main window
 */
struct MainGadgets {
	UWORD	gadgid;			/* Gadget ID					*/
	UWORD	stringid;		/* ID of label name         	*/
	UBYTE	col4;			/* Column in 4 x 2 layout		*/
	UBYTE	row4;			/* Row in 4 x 2 layout			*/
	UBYTE	col6;			/* Column in 4 x 2 layout		*/
	UBYTE	row6;			/* Row in 4 x 2 layout			*/
	UBYTE	col8;			/* Column in 4 x 2 layout		*/
	UBYTE	col12;			/* Column in 12 x 1 layout		*/
	UBYTE	widthtype;		/* 0=narrow, 1=wide, 2=status 	*/
};


struct MainGadgets MainGadgs[] = {
 GID_HIDE,		MSG_HIDE_GAD,	  	0, 2,	 0,	1,	0,	4,	MAIN_NARROW,
 GID_QUIT,		MSG_QUIT_GAD,	  	1, 2,	 1,	1,	1,	5,	MAIN_NARROW,
 GID_PAUSE,		MSG_PAUSE_GAD,	  	0, 1,	 2,	1,	2,	6,	MAIN_NARROW_TOGGLE,
 GID_DISABLE,	MSG_DISABLE_GAD,  	1, 1,	 3,	1,	3,	7,	MAIN_NARROW_TOGGLE,
 GID_OPENLOG,	MSG_OPENLOG_GAD,	2, 1,	 4, 0,	4,	8,  MAIN_WIDE,
 GID_APPENDLOG,	MSG_APPENDLOG_GAD,	2, 1,	 4,	0,	4,	8,	MAIN_WIDE_INVIS,
 GID_STARTLOG,	MSG_STARTLOG_GAD,	2, 1,	 4,	0,	4,	8,	MAIN_WIDE_INVIS,
 GID_SERIALLOG,	MSG_SERIALLOG_GAD,	2, 1,	 4, 0,  4,  8,	MAIN_WIDE_INVIS,
 GID_CLOSELOG,	MSG_CLOSELOG_GAD,	2, 1,	 4, 0,	4,	8,  MAIN_WIDE_INVIS,
 GID_SETUP,		MSG_SETUP_GAD,	  	3, 1,	 5,	0,	6,	10,	MAIN_WIDE,
 GID_SAVESET,	MSG_SAVESET_GAD,  	2, 2,	 4,	1,	5,	9,	MAIN_WIDE,
 GID_FUNCTION,	MSG_FUNCTION_GAD,	3, 2,	 5,	1,	7,	11,	MAIN_WIDE,
 GID_STATUS,	MSG_STATUS_GAD,	  	0, 0,	 0,	0,	0,	0,	MAIN_STATUS,
  0, 0, 0, 0, 0, 0, 0, 0, 0
};


/*
 *		Names of gadgets in main window for calculating minimum width
 */
int MainWidthLeftText[] = {
	MSG_PAUSE_GAD,
	MSG_DISABLE_GAD,
	MSG_HIDE_GAD,
	MSG_QUIT_GAD,
	0
};

int MainWidthRightText[] = {
	MSG_OPENLOG_GAD,
	MSG_APPENDLOG_GAD,
	MSG_STARTLOG_GAD,
	MSG_SERIALLOG_GAD,
	MSG_CLOSELOG_GAD,
	MSG_SAVESET_GAD,
	MSG_FUNCTION_GAD,
	MSG_SETUP_GAD,
	0
};


/*
 *		SnoopDos Menus
 *
 *		-- Warning -- 
 *
 *		If you re-arrange the order of menu items or sub-items, you must
 *		make sure to update the MENU_xxxx #define's at the end to reflect
 *		the new ordering!
 *
 *		(Remember to count bars when working out item numbers, and that
 *		menu/item numbers start from zero, not one.)
 */

#define DIS			NM_ITEMDISABLED
#define TICK		(CHECKIT | MENUTOGGLE)
#define TICKED		(CHECKIT | MENUTOGGLE | CHECKED)
#define BAR			NM_ITEM, NM_BARLABEL, NULL, 0, 0, NULL
#define MENU_END	NM_END,  NULL, NULL, 0,	0, NULL

#define M(type,title_id,flags,mid) \
	type,	(UBYTE *)(title_id), 0, flags, 0, (APTR)(mid)

#define MX(type,title_id,flags,ex,mid) \
	type,	(UBYTE *)(title_id), 0, flags, ex,(APTR)(mid)

struct NewMenu MainMenu[] = {

	/*
	 *		Project menu
	 */
	M(	NM_TITLE,	MSG_PROJECT_MENU,			0,		0),
	M(   NM_ITEM,   MSG_PROJECT_OPENLOG,		0,		MID_OPENLOG),
	M(	 NM_ITEM,	MSG_PROJECT_CLOSELOG,		DIS,	MID_CLOSELOG),
	     BAR,
	MX(   NM_ITEM,	MSG_PROJECT_PAUSE,			TICK,   ~8,		MID_PAUSE),
	MX(   NM_ITEM,	MSG_PROJECT_DISABLE,		TICK,   ~16,	MID_DISABLE),
	     BAR,
	M(    NM_ITEM,  MSG_PROJECT_STEP,           0,      MID_STEP),
	     BAR,
	M(    NM_ITEM,  MSG_PROJECT_PRI,            0,      0),
	MX(    NM_SUB,	MSG_PROJECT_PRI_A,			TICK,   ~1,		MID_CHANGEPRI),
	MX(    NM_SUB,	MSG_PROJECT_PRI_B,			TICK,   ~2,		MID_CHANGEPRI),
	MX(    NM_SUB,	MSG_PROJECT_PRI_C,			TICK,   ~4,		MID_CHANGEPRI),
	MX(    NM_SUB,	MSG_PROJECT_PRI_D,			TICK,   ~8,		MID_CHANGEPRI),
	MX(    NM_SUB,	MSG_PROJECT_PRI_E,			TICK,   ~16,	MID_CHANGEPRI),
	MX(    NM_SUB,	MSG_PROJECT_PRI_F,			TICK,   ~32,	MID_CHANGEPRI),
	MX(    NM_SUB,	MSG_PROJECT_PRI_G,			TICK,   ~64,	MID_CHANGEPRI),
		 BAR,
	M(	 NM_ITEM,	MSG_PROJECT_HELP,			0,		MID_HELP),
	M(	 NM_ITEM,	MSG_PROJECT_ABOUT,			0,		MID_ABOUT),
	     BAR,
	M(	 NM_ITEM,	MSG_PROJECT_HIDE,			0,		MID_HIDE),
	     BAR,
	M(	 NM_ITEM,	MSG_PROJECT_QUIT,			0,		MID_QUIT),

	/*
	 *		Windows menu
	 */
	M(  NM_TITLE,	MSG_WINDOWS_MENU,			0,		0),
	M(   NM_ITEM,	MSG_WINDOWS_SETUP,			0,		MID_SETUP),
	M(   NM_ITEM,	MSG_WINDOWS_FUNCTION,		0,		MID_FUNCTION),
	M(   NM_ITEM,	MSG_WINDOWS_FORMAT,			0,		MID_FORMAT),
	     BAR,
	M(   NM_ITEM,   MSG_WINDOWS_WIDTH,			0,      0),
	M(	  NM_SUB,	MSG_WINDOWS_WIDTH_1,		0, 		MID_SETWIDTH),
	M(	  NM_SUB,	MSG_WINDOWS_WIDTH_2,		0, 		MID_SETWIDTH),
	M(	  NM_SUB,	MSG_WINDOWS_WIDTH_3,		0, 		MID_SETWIDTH),
	M(	  NM_SUB,	MSG_WINDOWS_WIDTH_4,		0, 		MID_SETWIDTH),
	M(	  NM_SUB,	MSG_WINDOWS_WIDTH_5,		0, 		MID_SETWIDTH),
	     BAR,
	M(	 NM_ITEM,	MSG_WINDOWS_SPACING,		0,		0),
	MX(	  NM_SUB,	MSG_WINDOWS_SPACING_NONE,	TICKED, ~1,	MID_SPACE_NONE),
	MX(	  NM_SUB,	MSG_WINDOWS_SPACING_1P,		TICK,   ~2,	MID_SPACE_1P),
	MX(	  NM_SUB,	MSG_WINDOWS_SPACING_2P,		TICK,   ~4,	MID_SPACE_2P),

	M(	 NM_ITEM,	MSG_WINDOWS_REFRESH,		0,		0),
	MX(   NM_SUB,	MSG_WINDOWS_REF_SIMPLE,		TICK,   ~1, MID_REF_SIMPLE),
	MX(   NM_SUB,	MSG_WINDOWS_REF_SMART,		TICKED, ~2, MID_REF_SMART),

	M(   NM_ITEM,	MSG_WINDOWS_ALIGN,			0,		0),
	MX(	  NM_SUB,	MSG_WINDOWS_ALIGN_LEFT,		TICKED, ~1, MID_ALIGN_LEFT),
	MX(	  NM_SUB,	MSG_WINDOWS_ALIGN_RIGHT,	TICK,   ~2, MID_ALIGN_RIGHT),

	M(   NM_ITEM,   MSG_WINDOWS_ROW_QUAL,		0,      0),
	MX(	  NM_SUB,	MSG_WINDOWS_ROW_ANY,		TICKED,	~1,  MID_ROWQUAL),
	MX(	  NM_SUB,	MSG_WINDOWS_ROW_NONE,		TICK, 	~2,  MID_ROWQUAL),
	MX(	  NM_SUB,	MSG_WINDOWS_ROW_SHIFT,		TICK, 	~4,  MID_ROWQUAL),
	MX(	  NM_SUB,	MSG_WINDOWS_ROW_ALT,		TICK, 	~8,  MID_ROWQUAL),
	MX(	  NM_SUB,	MSG_WINDOWS_ROW_CTRL,		TICK, 	~16, MID_ROWQUAL),
	MX(	  NM_SUB,	MSG_WINDOWS_ROW_ALL,		TICK, 	~32, MID_ROWQUAL),

	     BAR,
	M(	 NM_ITEM,	MSG_WINDOWS_SHOWSTATUS,		TICKED, MID_STATUS),
	M(   NM_ITEM,   MSG_WINDOWS_SHOWGADGETS,    TICKED, MID_GADGETS),
	     BAR,
	M(	 NM_ITEM,	MSG_WINDOWS_AUTO_OPEN,		TICK,	MID_AUTO_OPEN),
	M(   NM_ITEM,   MSG_WINDOWS_DISABLE_HIDDEN, TICK,   MID_DISABLE_HIDDEN),

	/*
	 *		Settings menu
	 */
	M(	NM_TITLE,	MSG_SETTINGS_MENU,			0,		0),
	M(	 NM_ITEM,	MSG_SETTINGS_LOAD,			0,		MID_LOAD),
	M(	 NM_ITEM,	MSG_SETTINGS_SAVE,			0,		MID_SAVE),
	M(	 NM_ITEM,	MSG_SETTINGS_SAVEAS,		0,		MID_SAVEAS),
	     BAR,
	M(	 NM_ITEM,	MSG_SETTINGS_RESETDEFAULTS,	0,		MID_RESET),
	M(	 NM_ITEM,	MSG_SETTINGS_LASTSAVED,    	0,		MID_LASTSAVED),
	M(	 NM_ITEM,	MSG_SETTINGS_RESTORE,		0,		MID_RESTORE),
	     BAR,
	M(	 NM_ITEM,	MSG_SETTINGS_CREATEICONS,	TICK,   MID_ICONS),

	/*
	 *		Buffer menu
	 */
	M(	NM_TITLE,	MSG_BUFFER_MENU,			0,		0),
	M(	 NM_ITEM,	MSG_BUFFER_COPYWIN,			0,		MID_COPYWIN),
	M(	 NM_ITEM,	MSG_BUFFER_COPYBUF,			0,		MID_COPYBUF),
	     BAR,
	M(   NM_ITEM,   MSG_BUFFER_SAVEWIN,			0,		MID_SAVEWIN),
	M(	 NM_ITEM,	MSG_BUFFER_SAVEBUF,			0,		MID_SAVEBUF),
	     BAR,
	M(	 NM_ITEM,	MSG_BUFFER_CLEARBUF,    	0,		MID_CLEARBUF),

	MENU_END
};

/*
 *		These defines are used when adjusting the menu settings on the fly
 */

#define MENU_OPENLOG		FULLMENUNUM(0,  0, NOSUB)
#define MENU_CLOSELOG		FULLMENUNUM(0,  1, NOSUB)
#define MENU_PAUSE			FULLMENUNUM(0,  3, NOSUB)
#define MENU_DISABLE		FULLMENUNUM(0,  4, NOSUB)
#define MENU_CHANGEPRI		FULLMENUNUM(0,  8, 0)
#define MENU_NUMPRI			7				/* Number of menu priorities */
#define MENU_HIDE			FULLMENUNUM(0,  13, NOSUB)

#define MENU_SPACING0		FULLMENUNUM(1,  6, 0)
#define MENU_SPACING1		FULLMENUNUM(1,  6, 1)
#define MENU_SPACING2		FULLMENUNUM(1,  6, 2)
#define MENU_SIMPLE			FULLMENUNUM(1,  7, 0)
#define MENU_SMART			FULLMENUNUM(1,  7, 1)
#define MENU_AL_LEFT		FULLMENUNUM(1,  8, 0)
#define MENU_AL_RIGHT		FULLMENUNUM(1,  8, 1)
#define MENU_ROWQUAL_ANY	FULLMENUNUM(1,  9, 0)
#define MENU_ROWQUAL_NONE	FULLMENUNUM(1,  9, 1)
#define MENU_ROWQUAL_SHIFT	FULLMENUNUM(1,  9, 2)
#define MENU_ROWQUAL_ALT	FULLMENUNUM(1,  9, 3)
#define MENU_ROWQUAL_CTRL	FULLMENUNUM(1,  9, 4)
#define MENU_ROWQUAL_ALL	FULLMENUNUM(1,  9, 5)
#define MENU_STATUS			FULLMENUNUM(1, 11, NOSUB)
#define MENU_GADGETS		FULLMENUNUM(1, 12, NOSUB)
#define MENU_AUTO_OPEN		FULLMENUNUM(1, 14, NOSUB)
#define MENU_DIS_HIDDEN 	FULLMENUNUM(1, 15, NOSUB)

#define MENU_ICONS			FULLMENUNUM(2,  8, NOSUB)

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

/*
 *		GetTimeStr(datestamp)
 *
 *		Returns a pointer to a string giving the current time in
 *		hours, mins, and seconds. The string remains valid until
 *		the next time this function is called.
 *
 *		If the datestamp parameter is NULL, then the current date is
 *		used instead.
 */
char *GetTimeStr(struct DateStamp *ds)
{
	static char timestr[20];
	struct DateStamp ourds;
	struct DateTime datetime;

	if (!ds) {
		ds = &ourds;
		DateStamp(ds);
	}
	datetime.dat_Stamp	 = *ds;
	datetime.dat_Format	 = FORMAT_DOS;
	datetime.dat_Flags	 = 0;
	datetime.dat_StrDay	 = NULL;
	datetime.dat_StrDate = NULL;
	datetime.dat_StrTime = timestr;
	DateToStr(&datetime);
	return (timestr);
}

/*
 *		UpdateStatus(void)
 *
 *		Determines what the current status is (by examining various
 *		variables) and sets the status line to display an appropriate
 *		message. Call whenever the status may have changed.
 *
 *		Things which can cause a status change: pause/unpause, disable/enable,
 *		open log/close log
 */
void UpdateStatus(void)
{
	if (Paused || Disabled) {
		char *timestr;
		char *merid;
		char *msg;
		int hour;
		int min;

		if (Paused) {
			timestr = GetTimeStr(&PauseDateStamp);
			msg		= MSG(MSG_STATUS_PAUSED);
		} else {
			timestr = GetTimeStr(&DisableDateStamp);
			msg		= MSG(MSG_STATUS_DISABLED);
		}
		hour     = atoi(timestr);
		min      = atoi(timestr+3);
		merid	 = "AM";
		if (hour >= 12) {
			hour -= 12;
			merid = "PM";
		}
		mysprintf(StatusLineText, msg, (hour ? hour : 12), min, merid);
	} else if (LogActive) {
		int msgid;

		switch (CurrentLogType) {
			case LT_FILE:			msgid = MSG_STATUS_LOGFILE;		break;
			case LT_DEVICE:			msgid = MSG_STATUS_LOGDEVICE;	break;
			case LT_DEBUG:			msgid = MSG_STATUS_LOGDEBUG;	break;
		}
		mysprintf(StatusLineText, MSG(msgid), CurrentLogName);
	} else
		strcpy(StatusLineText, MSG(MSG_STATUS_NORMAL));
	
	if (MainWindow) {
		GT_SetGadgetAttrs(Gadget[GID_STATUS], MainWindow, NULL,
						  GTTX_Text, StatusLineText,
						  TAG_DONE);
	}
}

/*
 *		SetLogGadget(logmode, refresh)
 *
 *		Displays the appropriate gadget in the main window according to the
 *		type of logging method chosen. If the logfile is currently open,
 *		the "close log" gadget is displayed, otherwise the correct "open"
 *		gadget is displayed according to the logmode: LOGMODE_PROMPT,
 *		LOGMODE_APPEND, LOGMODE_OVERWRITE, or LOGMODE_SERIALPORT
 *
 *		If the refresh parameter is LG_REFRESH, then refresh gadgets.
 *		LG_NOREFRESH means don't refresh gadgets (i.e. assume we're being
 *		called from the gadget creation routine).
 */
void SetLogGadget(int logmode, int refresh)
{
	static int loggadgets[] = {
		GID_OPENLOG, GID_APPENDLOG, GID_STARTLOG, GID_SERIALLOG,
		GID_CLOSELOG, 0
	};
	int gadid;
	int i;

	if (refresh == LG_REFRESH && !MainWindow)
		return;

	switch (logmode) {
		case LOGMODE_PROMPT:		gadid = GID_OPENLOG;		break;
		case LOGMODE_APPEND:		gadid = GID_APPENDLOG;		break;
		case LOGMODE_SERIALPORT:	gadid = GID_SERIALLOG;		break;
		case LOGMODE_OVERWRITE:		gadid = GID_STARTLOG;		break;
	}
	if (LogActive)
		gadid = GID_CLOSELOG;

	for (i = 0; loggadgets[i]; i++) {
		struct Gadget *gad = Gadget[loggadgets[i]];
		int gadpos;
		
		if (refresh == LG_REFRESH)
			 gadpos = RemoveGadget(MainWindow, gad);

		if (loggadgets[i] == gadid)
			gad->LeftEdge = LogButtonLeft;
		else
			gad->LeftEdge = INVIS_LEFT_EDGE;

		if (refresh == LG_REFRESH) {
			AddGadget(MainWindow, gad, gadpos);
			RefreshGList(gad, MainWindow, NULL, 1);
		}
	}
	LogButtonID = gadid;
}

/*
 *		WriteLog(string)
 *
 *		Writes the specified string to the logfile, handling buffering
 *		etc. as required.
 *
 *		If string is NULL, then flushes any buffers as necessary.
 */
void WriteLog(char *string)
{
	int len;

	if (!LogActive)
		return;
	
	if (string == NULL) {
		/*
		 *		Flush output
		 */
		if (LogFile && LogBuffer && LogBufferPos) {
			Write(LogFile, LogBuffer, LogBufferPos);
			LogBufferPos = 0;
		}
		return;
	}

	if (CurrentLogType == LT_DEBUG) {
		/*
		 *		Output string to serial terminal, expanding LF's to CR/LF's
		 *		as we go.
		 */
		while (*string) {
			if (*string == '\n')
				RawPutChar('\r');
			RawPutChar(*string++);
		}
		return;
	}
	len = strlen(string);
	if (LogBuffer) {
		/*
		 *		Buffered i/o so add string to buffer; if necessary, flush
		 *		buffer first.
		 */
		if ((LogBufferPos + len) > LogBufferSize) {
			Write(LogFile, LogBuffer, LogBufferPos);
			LogBufferPos = 0;
		}
		memcpy(LogBuffer + LogBufferPos, string, len);
		LogBufferPos += len;
	} else {
		/*
		 *		Plain unbuffered i/o
		 */
		Write(LogFile, string, strlen(string));
	}
}

/*
 *		CloseLog()
 *
 *		Closes the currently open logfile (if any)
 */
void CloseLog(void)
{
	char msg[100];

	if (!LogActive)
		return;
	
	mysprintf(msg, MSG(MSG_LOG_STOP), GetTimeStr(NULL));
	WriteLog(msg);
	WriteLog(NULL);	/* Flush output */
	LogActive = 0;
	SetLogGadget(DefaultLogMode, LG_REFRESH);
	UpdateStatus();
	SetMenuOptions();
	if (LogFile) {
		int oldpaused = Paused;

		Paused = 0;
		Close(LogFile);
		LogFile = NULL;
		Paused = oldpaused;
	}
	if (LogBuffer) {
		FreeMem(LogBuffer, LogBufferSize);
		LogBuffer = NULL;
	}
}

/*
 *		WriteLogHeader()
 *
 *		Writes the standard SnoopDos header line to the logfile using the
 *		current logfile format
 */
void WriteLogHeader(void)
{
	FormatEvent(LogEFormat, NULL, BufferLine+1, 0, LogWidth - 1);
	BufferLine[0]		   = ' ';
	BufferLine[LogWidth+1] = '\n';
	BufferLine[LogWidth+2] = 0;
	WriteLog(BufferLine);
	UnderlineTitles(LogEFormat, BufferLine+1, '-');
	BufferLine[LogWidth+1] = '\n';
	WriteLog(BufferLine);
}

/*
 *		OpenLog(mode, filename)
 *
 *		Opens the named file for logging. If a log file is already open,
 *		then closes the existing file first.
 *
 *		Mode is set to:
 *
 *			LOGMODE_PROMPT	   - Prompt user if file exits for append/overwrite
 *			LOGMODE_APPEND	   - Automatically append to file if it exists
 *			LOGMODE_OVERWRITE  - Automatically overwrite file it if exitsts
 *			LOGMODE_SERIALPORT - Direct output to debugger on serial port
 *
 *		Filename is ignored if mode is set to LOGMODE_SERIALPORT.
 *
 *		Returns 1 for success, 0 for failure (couldn't open file?)
 */
int OpenLog(int logmode, char *filename)
{
	APTR oldwinptr = *TaskWindowPtr;
	char startmsg[100];
	char timestr[20];
	char datestr[20];
	char weekday[20];
	struct DateTime datetime;
	int pausestate = Paused;

	Paused = 0;
	if (LogActive)
		CloseLog();
	
	if (logmode == LOGMODE_SERIALPORT) {
		CurrentLogType = LT_DEBUG;
	} else {
		int filemode;
		int buffer = 0;

		strcpy(CurrentLogName, filename);
		*TaskWindowPtr = (APTR)-1;
		if (IsFileSystem(filename))
			CurrentLogType = LT_FILE;
		else
			CurrentLogType = LT_DEVICE;
		*TaskWindowPtr = oldwinptr;

		if (logmode == LOGMODE_PROMPT && CurrentLogType == LT_FILE) {
			BPTR lock = Lock(filename, ACCESS_READ);

			if (lock) {
				UnLock(lock);
				switch (GetResponse(MSG(MSG_REQ_APPEND_OVERWRITE_CANCEL),
									MSG(MSG_REQ_FILE_EXISTS), filename)) {

					case 1:		filemode = MODE_READWRITE;		break;
					case 2:		filemode = MODE_NEWFILE;		break;
					default:	goto open_okay;
				}
			} else
				filemode = MODE_NEWFILE;
		} else if (logmode == LOGMODE_APPEND)
			filemode = MODE_READWRITE;
		else
			filemode = MODE_NEWFILE;

		LogFile = Open(filename, filemode);
		if (!LogFile)
			goto open_failed;

		if (filemode == MODE_READWRITE) {
			Seek(LogFile, 0, OFFSET_END);
			Write(LogFile, "\n", 1); /* Leave space between multiple logs */
		}

		/*
		 *		Now select buffer type for file according to gadget and
		 *		file type.
		 */
		switch (CurSettings.Setup.FileIOType) {
			case FILE_AUTOMATIC:
				if (CurrentLogType == LT_FILE)
					buffer = 1;
				break;

			case FILE_IMMEDIATE:		buffer = 0;	break;
			case FILE_BUFFERED:			buffer = 1; break;
		}
		if (buffer) {
			/*
			 *		Allocate a buffer for file i/o. Doesn't matter if
			 *		it fails -- we just drop back to unbuffered file
			 *		i/o instead.
			 */
			LogBufferSize = LOGBUFFER_SIZE;
			LogBufferPos  = 0;
			LogBuffer     = AllocMem(LogBufferSize, MEMF_ANY);
		}
	}
	LogActive = 1;
	SetLogGadget(DefaultLogMode, LG_REFRESH);
	UpdateStatus();
	SetMenuOptions();

	DateStamp(&datetime.dat_Stamp);
	datetime.dat_Format	 = FORMAT_DOS;
	datetime.dat_Flags	 = 0;
	datetime.dat_StrDay	 = weekday;
	datetime.dat_StrDate = datestr;
	datetime.dat_StrTime = timestr;
	DateToStr(&datetime);
	mysprintf(startmsg, MSG(MSG_LOG_START), weekday, datestr, timestr);

	/*
	 *		Now select appropriate output format for log
	 */
	LogWidth = ParseFormatString(LogFormat, LogEFormat, MAX_FORM_LEN);
	if (!LogWidth)
		LogWidth = ParseFormatString(BufFormat, LogEFormat, MAX_FORM_LEN);
	WriteLog(startmsg);
	WriteLogHeader();
open_okay:
	Paused = pausestate;
	return (1);

open_failed:
	Paused = pausestate;
	return (0);
}

/*
 *		SaveBuffer(savetype, savename, overwrite)
 *
 *		This function saves the contents of some or all of the current
 *		buffer to a disk file or to the clipboard.
 *
 *		savetype is SAVEBUF_WINDOW to save only that portion of the
 *		buffer that is currently displayed in the main window, or
 *		SAVEBUF_ALL to save the entire buffer.
 *
 *		savename is a pointer to a disk filename, or SAVEBUF_CLIPBOARD to
 *		save to the clipboard.device.
 *
 *		overwrite is SAVEBUF_OVERWRITE to force an existing file to be
 *		overwritten automatically or SAVEBUF_PROMPT to let the user
 *		choose whether to overwrite using a requester. If the user
 *		chooses not to overwrite, then success is returned.
 *
 *		Returns 1 for success or 0 for failure.
 */
int SaveBuffer(int savetype, char *savename, int overwrite)
{
	struct IOClipReq *clipreq;
	struct MsgPort	 *clipport;
	BPTR  savefile;
	UBYTE *savebuf;
	Event *curev;
	ULONG curpos;
	int startseq;
	int curseq;
	int endseq;
	int leftcol;
	int rightcol;
	int totalsize;
	int width;
	int numlines;

	if (savetype == SAVEBUF_WINDOW) {
		/*
		 *		Save only visible portion of buffer
		 */
		startseq	= TopSeq;
		curev		= TopEvent;
		endseq		= BottomSeq;
		leftcol		= LeftCol;
		rightcol	= RightCol;
	} else {
		/*
		 *		Save entire buffer
		 */
		startseq	= RealFirstSeq;
		curev		= HeadNode(&EventList);
		endseq		= EndSeq;
		leftcol		= 0;
		rightcol	= BufferWidth - 1;
	}

	savebuf = AllocMem(SAVEBUFFER_SIZE, MEMF_ANY);
	if (!savebuf)
		return (0);

	curpos = 0;

	/*
	 *		Now try and open our output device
	 */
	if (savename == SAVEBUF_CLIPBOARD) {
		/*
		 *		Open clipboard
		 */
		clipport = CreateMsgPort();
		if (!clipport) {
			FreeMem(savebuf, SAVEBUFFER_SIZE);
			return (0);
		}
		clipreq = (struct IOClipReq *)CreateExtIO(clipport, sizeof(*clipreq));
		if (!clipreq) {
			FreeMem(savebuf, SAVEBUFFER_SIZE);
			DeleteMsgPort(clipport);
			return (0);
		}
		if (OpenDevice("clipboard.device", 0, (void *)clipreq, 0)) {
			FreeMem(savebuf, SAVEBUFFER_SIZE);
			DeleteExtIO((struct IORequest *)clipreq);
			DeleteMsgPort(clipport);
			return (0);
		}
		clipreq->io_Error	= 0;
		clipreq->io_Offset	= 0;
		clipreq->io_ClipID	= 0;
	} else {
		/*
		 *		Open log file
		 */
		int filemode = MODE_NEWFILE;;

		if (overwrite == SAVEBUF_PROMPT) {
			BPTR lk = Lock(savename, ACCESS_READ);

			if (lk) {
				UnLock(lk);
				switch (GetResponse(MSG(MSG_REQ_APPEND_OVERWRITE_CANCEL),
								    MSG(MSG_REQ_FILE_EXISTS), savename)) {

					case 1:		filemode = MODE_READWRITE;		break;
					case 2:		filemode = MODE_NEWFILE;		break;
					default:	FreeMem(savebuf, SAVEBUFFER_SIZE);
								return (1);
				}
			}
		}
		savefile = Open(savename, filemode);
		if (!savefile) {
			FreeMem(savebuf, SAVEBUFFER_SIZE);
			return (0);
		}
		if (filemode == MODE_READWRITE) {
			Seek(savefile, 0, OFFSET_END);
			Write(savefile, "\n", 1);	/* Leave blank line between appends */
		}
	}

	/*
	 *		Now calculate how many lines there are to be saved in the
	 *		buffer. There will be as many lines as were selected from
	 *		startseq to endseq (inclusive) plus two additional lines for
	 *		the header and underline. Each line will have (rightcol-leftcol+1)
	 *		characters on it, plus one additional character for the newline.
	 *		We need to calculate all this in advance because the header
	 *		written for the clipboard includes the number of characters of
	 *		text to be written.
	 */
	width = (rightcol - leftcol + 1);

	if (IsListEmpty(&EventList))
		numlines = 2;
	else
		numlines = (endseq - startseq + 1) + 2;

	totalsize = (width + 1) * numlines;

	if (savename == SAVEBUF_CLIPBOARD) {
		/*
		 *		Write the clipboard FTXT header into the buffer. We
		 *		need to write the amount of text, but we also need
		 *		to write the total size of the clip (including header)
		 *		rounded to a word boundary.
		 */
		int cliplen = (totalsize + 13) & ~1;	/* Includes header */

		memcpy(savebuf, "FORM....FTXTCHRS....", 20);
		*((ULONG *)&savebuf[4])  = cliplen;
		*((ULONG *)&savebuf[16]) = totalsize;
		curpos += 20;
	}
	/*
	 *		Next, output the two header lines (the column headings plus
	 *		the underline)
	 */
	FormatEvent(BufferEFormat, NULL, savebuf + curpos, leftcol, rightcol);
	curpos += width;
	savebuf[curpos++] = '\n';

	UnderlineTitles(BufferEFormat, BufferLine, '-');
	memcpy(savebuf + curpos, BufferLine + leftcol, width);
	curpos += width;
	savebuf[curpos++] = '\n';

	/*
	 *		Now walk through the lines in the buffer, outputting each line
	 *		one at a time.
	 */
	curseq     = startseq;
	totalsize -= (width + width + 2);	/* Allow for header */

	while (totalsize > 0) {
		/*
		 *		First check if we have enough room in the buffer for
		 *		the next line -- if not, then flush the buffer
		 */
		if ((curpos + width + 2) >= SAVEBUFFER_SIZE) {
			if (savename == SAVEBUF_CLIPBOARD) {
				clipreq->io_Command = CMD_WRITE;
				clipreq->io_Data	= savebuf;
				clipreq->io_Length	= curpos;
				DoIO((struct IORequest *)clipreq);
			} else
				Write(savefile, savebuf, curpos);
			curpos = 0;
		}
		/*
		 *		The next section must be completely enclosed in
		 *		ObtainSemaphore/ReleaseSemaphore to ensure the
		 *		buffer doesn't change under our feet
		 */
		ObtainSemaphore(&BufSem);
		if (curseq < RealFirstSeq) {
			/*
			 *		Uhoh -- the list has scrolled past us while we
			 *		were outputting it; catch up as best we can.
			 */
			curev = HeadNode(&EventList);
			if (NextNode(curev))
				 curseq = curev->seqnum;
			else
				 curseq = endseq + 1;
		}
		if (curseq > endseq) {
			/*
			 *		We've somehow run out of lines -- this should never
			 *		happen, but you never know. For disk files, we can
			 *		just avoid outputting anything else. For the
			 *		clipboard, we need to output something, so we just
			 *		output a line of spaces.
			 */
			if (savename != SAVEBUF_CLIPBOARD)
				break;
			memset(savebuf + curpos, ' ', width);
		} else {
			FormatEvent(BufferEFormat, curev, savebuf + curpos,
						leftcol, rightcol);
			curev = NextNode(curev);
			curseq++;
		}
		curpos += width;
		savebuf[curpos++] = '\n';
		totalsize -= (width + 1);
		ReleaseSemaphore(&BufSem);
	}

	/*
	 *		Now we've output the entire save region, so just flush anything
	 *		left in the save buffer and we're done.
	 */
	if (savename == SAVEBUF_CLIPBOARD) {
		/*
		 *		Make sure out total output is word-aligned, otherwise
		 *		Bad Things will happen
		 */
		if (totalsize & 1)
			savebuf[curpos++] = '\0';

		if (curpos > 0) {
			clipreq->io_Command = CMD_WRITE;
			clipreq->io_Data	= savebuf;
			clipreq->io_Length	= curpos;
			DoIO((struct IORequest *)clipreq);
		}
		clipreq->io_Command = CMD_UPDATE;
		DoIO((struct IORequest *)clipreq); /* Make data publically available */

		CloseDevice((struct IORequest *)clipreq);
		DeleteExtIO((struct IORequest *)clipreq);
		DeleteMsgPort(clipport);
	} else {
		if (curpos)
			Write(savefile, savebuf, curpos);
		Close(savefile);
	}
	FreeMem(savebuf, SAVEBUFFER_SIZE);
	return (1);
}

/*
 *		SetMonitorMode(type)
 *
 *		Sets the current monitor mode to the specified type. This will
 *		be one of the following three:
 *
 *			MONITOR_NORMAL
 *			MONITOR_PAUSED
 *			MONITOR_DISABLED
 *
 *		Automatically takes care of updating the gadget imagery and
 *		menu options to reflect the mode. Also ignores attempts to
 *		Pause() if the main window isn't currently open. Note that
 *		the three modes are mutually exclusive.
 *
 *		The status line is also updated to reflect the current mode.
 *		The global MonitorType reflects the currently selected mode
 *		after this call.
 *
 *		Important: any call which may possible spawn a subtask
 *		(e.g. showing an ASL file requester etc.) must force the Paused
 *		variable to 0 before hand and then restore it to its original
 *		setting afterwards. This ensures that any other tasks that were
 *		already paused remain paused, but any new tasks that make calls
 *		during the activity will not be paused.
 *
 *		This isn't perfect, but it gives about 99% certainty of avoiding
 *		deadlocks, while still ensuring that any existing paused tasks
 *		(normally just one or two) remain paused. Thus, if someone has
 *		paused some stuff so they can open a log file, turn on the
 *		printer, or whatever, it will remain paused. (The possibility of
 *		deadlock comes about if our external file i/o somehow causes
 *		another process to make a call to one of the monitored SnoopDos
 *		functions, e.g. OpenLibrary or OpenDevice).
 *
 *		The alternative is to completely disable pausing on such calls, in
 *		which case vast quantities of output might be generated while the
 *		person is messing around in the file requester oblivious.
 *
 *		NOTE: It is impossible to enter PAUSED mode unless the SnoopDos
 *		window is open -- this is to prevent situations where you freeze
 *		the system (e.g. it's waiting for you to unpause, but you can't
 *		because you can't call up the main window due to something else.)
 */
void SetMonitorMode(int modetype)
{
	int paused   = 0;
	int disabled = 0;
	int changed  = 0;

	if (!MainWindow && modetype == MONITOR_PAUSED)
		return;

	if (modetype == MONITOR_DISABLED)
		disabled = 1;
	else if (modetype == MONITOR_PAUSED)
		paused = 1;

	/*
	 *		Now update main window gadgets if necessary
	 */
	if (MainWindow) {
		if (paused != Paused) {
			ShowGadget(MainWindow, Gadget[GID_PAUSE],
					   (paused ? GADGET_DOWN : GADGET_UP));
		}
		if (disabled != Disabled) {
			ShowGadget(MainWindow, Gadget[GID_DISABLE],
					   (disabled ? GADGET_DOWN : GADGET_UP));
		}
	}

	/*
	 *		Next,  make the changes actually take effect.
	 */
	if (paused != Paused) {
		DateStamp(&PauseDateStamp);
		if (paused)
			ObtainSemaphore(&PauseSem);
		else
			ReleaseSemaphore(&PauseSem);
		Paused  = paused;
		changed = 1;
	}
	if (disabled != Disabled) {
		DateStamp(&DisableDateStamp);
		Disabled = disabled;
		changed  = 1;
		LoadFuncSettings(&CurSettings.Func);	/* Update patches state */

		if (LogActive) {
			char msg[100];

			mysprintf(msg, MSG(Disabled ? MSG_LOG_DISABLED : MSG_LOG_ENABLED),
						   GetTimeStr(NULL));
			WriteLog(msg);
			WriteLog(NULL);	/* Flush output to keep everything up-to-date */
		}
	}
	if (changed) {
		if (LogActive)		/* Flush log file when Paused */
			WriteLog(NULL);
		UpdateStatus();
		SetMenuOptions();
	}
	MonitorType = modetype;
}

/*
 *		SingleStep()
 *
 *		Undoes Pause just long enough for all currently waiting tasks
 *		to execute a single monitored event. Usually, this will just
 *		produce a single event in the event buffer.
 *
 *		Why would you want to do this? Because it's a real handy way
 *		of single-stepping through a program's actions at startup,
 *		especially if it happens to be crashing after doing a particular
 *		action!
 *
 *		We implement the singlestep by freeing up the Pause semaphore
 *		and then immediately trying to recapture it. We rely on the
 *		fact that Exec schedules semaphore operations on a strictly
 *		first-come first-served basis, so as soon as we free it,
 *		all the waiting tasks are guaranteed to run before we can
 *		recapture it. We move ourselves to a high priority temporarily
 *		to ensure that nobody gets a chance to run two events before
 *		we can regain control again.
 *
 *		If we weren't in pause mode before, we will be when we exit.
 *
 */
void SingleStep(void)
{
	int oldpri;

	if (!MainWindow)
		return;

	if (!Paused)
		SetMonitorMode(MONITOR_PAUSED);
	
	WriteLog(NULL);		/* Make sure logfile is up to date! */

	/*
	 *		To ensure that we get the semaphore back as soon as we 
	 *		possibly can (i.e. let all the waiting tasks run but nobody
	 *		else), we bump our priority up temporarily
	 */
	oldpri = SetTaskPri(SysBase->ThisTask, 25);
	ReleaseSemaphore(&PauseSem);
	ObtainSemaphore(&PauseSem);
	SetTaskPri(SysBase->ThisTask, oldpri);
}

/*
 *		CheckForScreen()
 *
 *		Checks if we've already got a SnoopDos screen open -- if we do,
 *		then returns TRUE. If we don't, then tries to lock the current
 *		screen and returns TRUE. If we can't lock the screen, then
 *		displays an error message and returns FALSe.
 */
int CheckForScreen(void)
{
	if (!SnoopScreen) {
		SetupScreen();
		if (!SnoopScreen) {
			ShowError(MSG(MSG_ERROR_NO_SCREEN));
			return (FALSE);
		}
	}
	return (TRUE);
}

/*
 *		ShowSnoopDos()
 *
 *		Attempts to open SnoopDos on the user's current preferred screen.
 *		If SnoopDos is already running, but on a different screen, then
 *		it is moved to this screen. Any open SnoopDos windows are moved,
 *		though any AmigaGuide help remains on the previous screen until
 *		the next time help is requested.
 *
 *		Fails if we can't lock the new screen for some reason (this
 *		should never happen!)
 */
int ShowSnoopDos(void)
{
	struct Screen *prevscr  = SnoopScreen;
	struct Window *prevfunc = FuncWindow;
	struct Window *prevform = FormWindow;
	struct Window *prevset  = SetWindow;
 
	if (!SetupScreen()) {
		ShowError(MSG(MSG_ERROR_NO_SCREEN));
		return (FALSE);
	}
	RemoveProgramFromWorkbench();
	ScreenToFront(SnoopScreen);
	if (prevscr == SnoopScreen) {
		/*
		 *		We were already running, and we're on the
		 *		same screen, so nothing else to do. For good
		 *		measure, we bring the main window to the
		 *		foreground, however.
		 */
		OpenMainWindow();
		return (TRUE);
	}
	if (prevscr != NULL) {
		/*
		 *		We're moving from a previous screen to this new screen,
		 *		so bring all the windows with us
		 */
		CleanupSubWindows();
		CloseMainWindow();
		OpenMainWindow();
		if (prevset)	OpenSettingsWindow();
		if (prevfunc)	OpenFunctionWindow();
		if (prevform)	OpenFormatWindow();
	} else {
		/*
		 *		We're opening on an entirely new screen -- if
		 *		DisableOnHide is true, then we need to restore
		 *		the saved state of Disable/Enable (since it will
		 *		have been disabled while in the background)
		 */
		if (DisableOnHide)
			SetMonitorMode(LastKnownState);
		OpenMainWindow();
	}
	return (TRUE);
}

/*
 *		HideSnoopDos()
 *
 *		Removes all SnoopDos windows from the screen. If DisableOnHide
 *		is true, then SnoopDos is put into a disabled state. Otherwise,
 *		if we're currently paused, then SnoopDos is unpaused (since
 *		otherwise, it would be easy to get into a situation where
 *		everything was frozen and you could do nothing to bring
 *		SnoopDos back to life).
 *
 *		If commodities library hasn't been opened, then we don't hide
 *		since that would make it difficult to resurrect SnoopDos again.
 *		However, if commodities library is open but the user has given
 *		an invalid hotkey, we do hide -- they can always use commodities
 *		exchange to reactivate it.
 */
void HideSnoopDos(void)
{
	LastKnownState = MonitorType;

	if (CurSettings.Setup.HideMethod == HIDE_NONE)
		return;

	CleanupSubWindows();
	CloseMainWindow();
	CleanupScreen();
	if (DisableOnHide)
		SetMonitorMode(MONITOR_DISABLED);
	else if (Paused)
		SetMonitorMode(MONITOR_NORMAL);
	if (CurSettings.Setup.HideMethod != HIDE_INVIS)
		AddProgramToWorkbench(CurSettings.Setup.HideMethod);
}

/*
 *		InitMenus()
 *
 *		Should be called once as soon as the language file has been
 *		read in. Walks down the menu structure and replaces the
 *		message ID's with actual pointers to the message.
 */
void InitMenus(void)
{
	struct NewMenu *menu;

	for (menu = &MainMenu[0]; menu->nm_Type != NM_END; menu++) {
		if (menu->nm_Label && menu->nm_Label != NM_BARLABEL) {
			char *msg = MSG((ULONG)(menu->nm_Label));

			menu->nm_Label = msg + 2;
			if (*msg && *msg != ' ')
				menu->nm_CommKey = msg;
		}
	}
}

/*
 *		CalcMinMainSize(gadgetfa, bufferfa, &width, &height)
 *
 *		Fills in width and height with the minimum allowable dimensions
 *		the main window can obtain using the specified gadget and buffer
 *		fonts. Returns TRUE for success, FALSE for failure (e.g. window
 *		to big to fit on screen using current fonts).
 */
BOOL CalcMinMainSize(struct TextAttr *gadgetfa, struct TextAttr *bufferfa,
					 int *pwidth, int *pheight)
{
	struct TextFont *gfont;
	struct TextFont *bfont;
	int widthleft;
	int widthright;
	int width;
	int height;
	int fonty;
	int bfonty;
	int bfontx;
	int bordright = SizeImage->Width;
	int bordbot   = SizeImage->Height;

	gfont = MyOpenFont(gadgetfa);
	if (!gfont)
		return (FALSE);

	bfont = MyOpenFont(bufferfa);
	if (!bfont) {
		CloseFont(gfont);
		return (FALSE);
	}
	if (bfont->tf_Flags & FPF_PROPORTIONAL) {
		CloseFont(bfont);
		CloseFont(gfont);
		return (FALSE);
	}
	fonty		= gfont->tf_YSize;
	bfonty		= bfont->tf_YSize;
	bfontx      = bfont->tf_XSize;

	widthleft   = MaxTextLen(gfont, MainWidthLeftText)  + 12;
	widthright  = MaxTextLen(gfont, MainWidthRightText) + 12;

	width  = (widthleft + widthright + MAIN_MARGIN) * 2 +
			 BorderLeft + bordright;
	height = TitlebarHeight + (bfonty + BoxInterGap) * 3 +
			 bordbot + 20 + (fonty + 10) * 3;

	CloseFont(bfont);
	CloseFont(gfont);

	if (width > ScreenWidth || height > ScreenHeight)
		return (FALSE);

	*pwidth  = width;
	*pheight = height;
	return (TRUE);
}

/*
 *		SetupBufferRastPort()
 *
 *		Sets up the main window rastport to have the correct font and
 *		colours for rendering. Should always be called before OutputBufLine
 *		if gadget operations may have occurred since the last redraw.
 */
void SetupBufferRastPort(void)
{
	struct RastPort *rport = MainWindow->RPort;

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

/*
 *		DrawHeaderLine()
 *
 *		Redraws the current event header line in the top part of the window
 *
 *		If a column is currently selected for dragging, then draws the
 *		column heading in white to indicate it's active.
 */
void DrawHeaderLine(void)
{
	struct RastPort *rport = MainWindow->RPort;
	int numcols = RightCol - LeftCol + 1;

	if (ClearMainRHS)
		numcols = BoxCols;

	SetupBufferRastPort();
	FormatEvent(BufferEFormat, NULL, BufferLine, LeftCol, LeftCol+numcols-1);

	/*
	 *		We mark the very last position on the header line with a single
	 *		dot, which can then be grabbed by the user to resize the width
	 *		of the last column. We don't overwrite the actual text of the
	 *		final header, if it's narrow.
	 *
	 *		If the columns are currently being dragged, we display a | instead
	 *		of a dot, to make it easier to spot, and we display it even if
	 *		it would overwrite some of the final heading.
	 */
	if (DraggingColumn) {
		/*
		 *		Draw the currently selected column with a white heading,
		 *		and the other stuff with a black heading
		 */
		int selectwidth = SelectEvent->width;

		if ((SelectEvent+1)->type != EF_END) {
			/*
			 *		If we're not highlighting the last column already
			 *		then indicate the end of the last column with a
			 *		little vertical bar
			 */
			BufferLine[BufferWidth - LeftCol - 1] = '|';
		}
		if (SelectStartCol > LeftCol) {
			/*
			 *		Draw black portion to left of selected column
			 */
			int width = MIN(SelectStartCol - LeftCol, numcols);

			Move(rport, BoxInLeft, BoxHeaderLine);
			Text(rport, BufferLine, width);
		}
		if ((SelectStartCol + selectwidth) < (LeftCol + numcols)) {
			/*
			 *		Draw black portion to right of selected column
			 */
			int xoffset = SelectStartCol + selectwidth - LeftCol;

			if (xoffset < 0)
				xoffset = 0;

			Move(rport, BoxInLeft + BoxCharWidth * xoffset, BoxHeaderLine);
			Text(rport, BufferLine + xoffset, numcols - MAX(xoffset,0));
		}
		if (SelectStartCol >= LeftCol && SelectStartCol < (LeftCol+numcols)) {
			/*
			 *		Draw selected column heading in white
			 */
			int xoffset = SelectStartCol - LeftCol;
			int fwidth  = MIN(selectwidth, numcols - xoffset);

			if (fwidth > 0) {
				SetAPen(rport, 2);
				Move(rport, BoxInLeft  + xoffset * BoxCharWidth,
					 BoxHeaderLine);
				Text(rport, BufferLine + xoffset, fwidth);
				SetAPen(rport, 1);
			}
		} else if (SelectStartCol < LeftCol &&
				   (SelectStartCol + selectwidth) >= LeftCol) {
			/*
			 *		Draw portion of selected column heading in white
			 */
			int colwidth = SelectStartCol + selectwidth - LeftCol;

			SetAPen(rport, 2);
			Move(rport, BoxInLeft, BoxHeaderLine);
			Text(rport, BufferLine, MIN(colwidth, numcols));
			SetAPen(rport, 1);
		}
	} else {
		/*
		 *		Not dragging a column, so draw entire line in black
		 */
		Move(rport, BoxInLeft, BoxHeaderLine);
		Text(rport, BufferLine, numcols);
	}
}

/*
 *		RedrawMainWindow()
 *
 *		Redraws all the main parts of the window, except gadgets.
 */
void RedrawMainWindow(void)
{
	struct RastPort *rport;
	
	if (!MainWindow)
		return;

	rport = MainWindow->RPort;
	DrawBevelBox(rport, BoxLeft, MainWindow->BorderTop,
						BoxWidth, BoxTop - MainWindow->BorderTop,
						GT_VisualInfo,	MainVI,
						TAG_DONE);
	DrawBevelBox(rport, BoxLeft, BoxTop, BoxWidth, BoxHeight,
						GT_VisualInfo,	MainVI,
						TAG_DONE);
	/*
	 *		We only draw the third bevel box if either the status line or
	 *		gadget line is enabled.
	 */
	if (StatusLine || GadgetsLine) {
		DrawBevelBox(rport, BoxLeft, BoxTop + BoxHeight, BoxWidth,
							MainWindow->Height - MainWindow->BorderBottom -
												 BoxTop - BoxHeight,
							GT_VisualInfo,	MainVI,
							TAG_DONE);
	}
	DrawHeaderLine();
	ShowBuffer(TopSeq, DISPLAY_ALL);
	if (ClearMainRHS) {
		if ((RightCol-LeftCol+1) < BoxCols) {
			/*
			 *		Erase strip to right of our rendered text
			 */
			SetAPen(rport, 0);
			SetDrMd(rport, JAM1);
			RectFill(rport, BoxInLeft + (RightCol-LeftCol+1) * BoxCharWidth,
							BoxInTop,
							BoxInLeft + BoxInWidth - 1,
							BoxInTop + BoxInHeight - 1);
		}
		ClearMainRHS = 0;
	}
}

/*
 *		SetTextSpacing(newspacing)
 *
 *		Updates the spacing of the main window to reflect the new spacing
 *		value passed in, and adjust all the global variables accordingly.
 */
void SetTextSpacing(int newspacing)
{
	int bfonty		  = BufferFont->tf_YSize;
	int bfontbaseline = BufferFont->tf_Baseline;
	int maxheight;

	if (BoxInterGap == newspacing)
		return;		/* No change needed */
	
	ChangedSpacing = 1;			/* Flag for resize routine */
	BoxInterGap    = newspacing;

	if (!MainWindow)
		return;

	SetAPen(MainWindow->RPort, 0);
	SetDrMd(MainWindow->RPort, JAM1);
	RectFill(MainWindow->RPort, BoxInLeft, BoxInTop,
								BoxInLeft + BoxInWidth - 1,
								BoxInTop + BoxInHeight - 1);
	
	maxheight	= BoxHeight - VBorderGap * 2 - 2;
	BoxSpacing	= bfonty + BoxInterGap;
	BoxRows		= (maxheight + BoxInterGap) / BoxSpacing;
	BoxInHeight	= BoxRows * BoxSpacing - BoxInterGap;
	BoxInTop	= BoxTop   + VBorderGap + 1;
	BoxBaseline	= BoxInTop + bfontbaseline;

	/*
	 *		We now check to see if we're currently at the end of the buffer,
	 *		and if so, make sure we stay that way by repositioning after the
	 *		line change.
	 */
	if (BottomSeq >= EndSeq)
		ShowBuffer(EndSeq, DISPLAY_NONE);
		
	LOCK_LAYERS;
	RedrawMainWindow();
	UNLOCK_LAYERS;
	UpdateMainVScroll();
}
	
/*
 *		InitMainMargins()
 *
 *		Initialises the window margins according to the currently selected
 *		left offset and the current buffer width (as determined by the
 *		format string). Automatically ensures margins are kept within
 *		the bounds of the displayable area, adjusting them as necessary.
 *
 *		On entry, LeftCol should contain the current left margin, BoxCols
 *		should be initialised to the current width of the displayable
 *		buffer area, and BufferWidth should be the maximum width of the
 *		currently selected format string.
 */
void InitMainMargins(void)
{
	if ((LeftCol + BoxCols) > BufferWidth)
		LeftCol = BufferWidth - BoxCols;

	if (LeftCol < 0)
		LeftCol = 0;

	RightCol = LeftCol + BoxCols - 1;
	if (RightCol >= BufferWidth) {
		RightCol = BufferWidth - 1;
		if (RightCol < 0)
			RightCol = 0;
	}
}

/*
 *		FreeScrollGadgets()
 *
 *		Frees all associated BOOPSI objects and images used by the
 *		scroll gadgets. Must only be called after the main window
 *		has closed.
 */
void FreeScrollGadgets(void)
{
	int i;

	for (i = GID_STARTSCROLL; i <= GID_ENDSCROLL; i++) {
		if (Gadget[i]) {
			DisposeObject(Gadget[i]);
			Gadget[i] = NULL;
		}
	}
	for (i = 0; i < 4; i++) {
		if (ScrollImage[i]) {
			DisposeObject(ScrollImage[i]);
			ScrollImage[i] = NULL;
		}
	}
}

/*
 *		FreeMainGadgets()
 *
 *		Frees all the gadgets allocated for the main window.
 */
void FreeMainGadgets(void)
{
	if (BufferFont) {
		CloseFont(BufferFont);
		BufferFont  = NULL;
	}
	if (MainGadList) {
		FreeGadgets(MainGadList);
		MainGadList = NULL;
	}
}

/*
 *		CreateScrollGadgets()
 *
 *		Creates the list of six gadgets that are used to give our window
 *		scroll bars and scroll arrows. These have to be created independently
 *		of the GadTools button gadgets, because since they are in the border,
 *		they must be added to the window at the time it is opened, and not
 *		afterwards.
 *
 *		The GadTools gadgets are removed and then re-added (with new
 *		size-sensitive positions) whenever the window is resized; if we
 *		try and do this with the BOOPSI border gadgets however, Intuition
 *		gets the positioning subtely wrong the second and subsequent times.
 *
 *		Hence you must call this function before opening the main window. All
 *		the gadgets are created relative to the current window dimensions, and
 *		so do not depend on specific hardcoded with/height values.
 *
 *		Returns a pointer to the gadget list, or NULL if the gadgets
 *		couldn't be created.
 */
struct Gadget *CreateScrollGadgets(void)
{
	struct Gadget *gadlist	= NULL;
	struct Gadget *gad		= (struct Gadget *)&gadlist;
	int sizew	= SizeImage->Width;
	int sizeh	= SizeImage->Height;
	int bw      = (ScreenResolution == SYSISIZE_LOWRES) ? 1 : 2;
	int i;

	/*
	 *		First, create the arrows for the scroll bars. Gad starts off
	 *		pointing to gadlist, which will receive the pointer to the
	 *		first gadget we create.
	 */
	ScrollArrows[0].widthoffset  =
	ScrollArrows[1].widthoffset  = 1 - sizew;
	ScrollArrows[2].heightoffset =
	ScrollArrows[3].heightoffset = 1 - sizeh;

	for (i = 0; i < 4; i++) {
		struct ScrollData *scd = &ScrollArrows[i];
		int gadid = scd->gadgetid;
		struct Image *img;

		img = (struct Image *)
				NewObject(NULL,	"sysiclass",
						  SYSIA_DrawInfo,	ScreenDI,
						  SYSIA_Which,		scd->imagetype,
						  SYSIA_Size,		ScreenResolution,
						  TAG_END);
		if (!img)
			goto sgad_failed;
		ScrollImage[i] = img;

		gad = (struct Gadget *)
				NewObject(NULL,				"buttongclass",
						  GA_ID,			gadid,
						  GA_Immediate,		TRUE,
						  GA_Image,			img,
						  GA_Width,			img->Width,
						  GA_Height,		img->Height,
						  GA_Previous,		gad,
						  GA_RelVerify,		TRUE,
						  scd->borderpos,	TRUE,
						  GA_RelRight,		scd->widthscale * img->Width +
						  					scd->widthoffset,
						  GA_RelBottom,		scd->heightscale * img->Height +
						  					scd->heightoffset,
						  ICA_TARGET,		ICTARGET_IDCMP,
						  TAG_END);
		if (!gad)
			goto sgad_failed;
		Gadget[gadid] = gad;
	}
	/*
	 *		Now create our two BOOPSI scroll gadgets in the window border
	 */
	gad = (struct Gadget *)NewObject(NULL, 				"propgclass",
									 GA_ID,				GID_HSCROLLER,
									 PGA_Freedom,		FREEHORIZ,
									 PGA_NewLook,		TRUE,
									 PGA_Borderless,	TRUE,
									 PGA_Top,			LeftCol,
									 PGA_Visible,		BoxCols,
									 PGA_Total,			BufferWidth,
									 GA_Left,			3,
									 GA_RelBottom,		3 - sizeh,
									 GA_RelWidth,		-sizew - 5 -
														ScrollImage[0]->Width -
														ScrollImage[1]->Width,
									 GA_Height,			sizeh - 4,
									 GA_Previous,		gad,
									 GA_BottomBorder,	TRUE,
									 ICA_TARGET,		ICTARGET_IDCMP,
									 ICA_MAP,			RZ_MapTags,
									 TAG_END);
	if (!gad)
		goto sgad_failed;
	Gadget[GID_HSCROLLER] = gad;

	gad = (struct Gadget *)NewObject(NULL, 				"propgclass",
									 GA_ID,				GID_VSCROLLER,
									 PGA_Freedom,		FREEVERT,
									 PGA_NewLook,		TRUE,
									 PGA_Borderless,	TRUE,
									 PGA_Top,			TopSeq - FirstSeq,
									 PGA_Visible,		BoxRows,
									 PGA_Total,			EndSeq - FirstSeq + 1,
									 GA_RelRight,		bw - sizew + 3,
									 GA_Top,			TitlebarHeight + 1,
									 GA_Width,			sizew - bw - bw - 4,
									 GA_RelHeight,		-TitlebarHeight -
														ScrollImage[2]->Height-
														ScrollImage[3]->Height-
														sizeh - 2,
									 GA_RightBorder,	TRUE,
									 GA_Previous,		gad,
									 ICA_TARGET,		ICTARGET_IDCMP,
									 ICA_MAP,			RZ_MapTags,
									 TAG_END);
	if (!gad)
		goto sgad_failed;
	Gadget[GID_VSCROLLER] = gad;
	return (gadlist);

sgad_failed:
	FreeScrollGadgets();
	return (NULL);
}

/*
 *		CreateMainGadgets(width, height, statusline)
 *
 *		Creates the gadgets for the main SnoopDos window, assuming a window
 *		of size width x height. MainGadList will point to the list of
 *		gadgets when completed. Returns NULL if unsuccessful, or MainGadList
 *		if successful.
 *
 *		Statusline is a flag that says whether or not the status gadget is
 *		to be created.
 *
 *		There are basically seven possible gadget layouts:
 *
 *			- 4 x 3 grid with status line on top
 *			- 4 x 2 grid with no status line
 *			- 6 x 2 grid with status line occupying the upper 4 left spaces
 *			- 8 x 1 grid with no status line
 *			- 12 x 1 grid with status line
 *          - 0 grid with no gadgets or status line
 *          - 1 line grid with status line but no gadgets
 *
 *		The first two are used when the window is narrow; the second two
 *		when the window is wider. We use colwidth[0] to hold the width
 *		of the narrow gadgets, colwidth[1] for the wide gadgets, and
 *		colwidth[2] for the status line gadget.
 *
 *		To allow us to conveniently replace one gadget definition with
 *		another, we use the concept of invisible buttons -- these are
 *		like normal buttons, but their X position is set way off to the
 *		left of the window where they won't be displayed; to reveal
 *		the buttons, we simply restore their X position and make the
 *		X position of a different button negative.
 *
 *		Finally, we have toggle buttons which flip flop between on and off.
 *		These are defined as MAIN_NARROW_TOGGLE.
 */
struct Gadget *CreateMainGadgets(struct TextAttr *gadgetfa,
								 struct TextAttr *bufferfa,
								 int width, int height, int statusline)
{
	struct MainGadgets *mg;
	struct NewGadget ng;
	struct TextFont *font;
	struct Gadget *gadlist;
	struct Gadget *gad;
	UWORD colwidth[MAIN_NUMWIDTHS];
	WORD  colpos[12];
	UWORD spacing;				/* Spacing between gadget tops				*/
	UWORD yoffset;				/* Offset of first gadget top in window		*/
	int fonty;					/* Y height of gadget font					*/
	int bfontx;					/* X height of buffer font					*/
	int bfonty;					/* Y height of buffer font					*/
	int bfontbaseline;			/* Baseline of buffer font					*/
	int innerwidth;				/* Usable width of window					*/
	int extraspace;				/* Spare space inbetween buffer and gadgets	*/
	int gheight;				/* Height of a single gadget				*/
	int width4;					/* Minimum width needed for 4 x 3 gadgets	*/
	int width6;					/* Minimum width needed for 6 x 2 gadgets	*/
	int width8;					/* Minimum width needed for 8 x 1 gadgets	*/
	int width12;				/* Minimum width needed for 12 x 1 gadgets	*/
	int gridtype;				/* Grid type for gadget layout				*/
	int numrows;				/* Number of rows of gadgets involved		*/
	int gadg_gap;				/* Aspect-corrected gap between gadget rows	*/
	int margin = MAIN_MARGIN;	/* Gadget margin at left edge of window		*/
	int boxmaxwidth;			/* Temporary width of listview box			*/
	int boxheight;				/* Temporary height of listview box			*/

	int bordright  = SizeImage->Width;
	int bordbot    = SizeImage->Height;

/*
 *		Quick defines for our internal grid arrangements
 */
#define FOUR_BY_THREE	0
#define SIX_BY_TWO		1
#define EIGHT_BY_ONE	2
#define TWELVE_BY_ONE	3

	if (MainGadList)
		FreeMainGadgets();

	if (!MainVI) {
		MainVI = GetVisualInfoA(SnoopScreen, NULL);
		if (!MainVI)
			return (NULL);
	}
	font = MyOpenFont(gadgetfa);
	if (!font)
		return (NULL);

	BufferFont = MyOpenFont(bufferfa);
	if (!BufferFont) {
		CloseFont(font);
		return (NULL);
	}
	fonty			= font->tf_YSize;
	bfontx			= BufferFont->tf_XSize;
	bfonty			= BufferFont->tf_YSize;
	bfontbaseline	= BufferFont->tf_Baseline;

	if (SquareAspect) {
		VBorderGap   = 2;
		gadg_gap	 = 3;
	} else {
		VBorderGap   = 1;
		gadg_gap	 = 2;
	}

	gheight		= fonty + GadgetHeight;
	spacing     = gheight + gadg_gap;

	colwidth[MAIN_NARROW]		 = MaxTextLen(font, MainWidthLeftText)  + 12;
	colwidth[MAIN_WIDE]			 = MaxTextLen(font, MainWidthRightText) + 12;

	/*
	 *		First, check if window is wide enough for gadgets. If not,
	 *		then fail.
	 */
	innerwidth = width - BorderLeft - bordright;

	if ((colwidth[MAIN_NARROW]+colwidth[MAIN_WIDE]+margin) * 2 > innerwidth) {
		CloseFont(font);
		CloseFont(BufferFont);
		BufferFont = NULL;
		return (NULL);
	}

	/*
	 *		Now calculate how many columns our fake listview can hold using
	 *		the current font, and use this to adjust our margins accordingly.
	 *
	 *		The '4' in boxmaxwidth comes from 2 pixels for the left and right
	 *		of the box (the borders)
	 */
	boxmaxwidth	= innerwidth - 2*BOX_LEFT_MARGIN - 4;
	BoxCols		= (boxmaxwidth / bfontx);

	width4		= colwidth[MAIN_NARROW] * 2 + colwidth[MAIN_WIDE] * 2;
	width6		= colwidth[MAIN_NARROW] * 4 + colwidth[MAIN_WIDE] * 2;
	width8		= colwidth[MAIN_NARROW] * 4 + colwidth[MAIN_WIDE] * 4;
	width12		= colwidth[MAIN_NARROW] * 8 + colwidth[MAIN_WIDE] * 4;
	colpos[0]	= BorderLeft + margin;
	innerwidth -= margin * 2;

	if (GadgetsLine) {
		if (statusline && width12 <= innerwidth) {
			/*
			 *		Setup layout for 12 x 1 grid
			 */
			int totalspace = innerwidth - width12;
			int i;

			for (i = 1; i < 8; i++)
				colpos[i] = colpos[0] + colwidth[MAIN_NARROW] * i
									  + (totalspace*i)/11;
			for (i = 8; i < 12; i++)
				colpos[i] = colpos[0] + colwidth[MAIN_NARROW] * 8
									  + colwidth[MAIN_WIDE]   * (i-8)
									  + (totalspace*i)/11;

			/*
			 *		We always ensure that there is at least two pixels
			 *		between the end of the status line and the beginning
			 *		of the next gadget, otherwise we get a nasty artifact
			 *		where the identically-coloured white borders meet.
			 */
			colwidth[MAIN_STATUS] = colpos[3] - colpos[0] +
									colwidth[MAIN_NARROW];
			if ((colpos[0] + colwidth[MAIN_STATUS] + 2) > colpos[4])
				colwidth[MAIN_STATUS] = colpos[4] - colpos[0] - 2;

			gridtype = TWELVE_BY_ONE;
			numrows  = 1;
		} else if (statusline && width6 <= innerwidth) {
			/*
			 *		Setup layout for 6 x 2 grid.
			 */
			int totalspace = innerwidth - width6;
			int i;

			for (i = 1; i < 4; i++)
				colpos[i] = colpos[0] + colwidth[MAIN_NARROW]*i +
				 			(totalspace*i)/5;

			colwidth[MAIN_STATUS] = colpos[3] - colpos[0] +
									colwidth[MAIN_NARROW];

			colpos[5]	= width - bordright - margin - colwidth[MAIN_WIDE];
			colpos[4]	= colpos[5] - colwidth[MAIN_WIDE] - totalspace/5;

			/*
			 *		If there isn't at least 2 pixels between the status line
			 *		and the adjacent wide gadget, reduce the size of each
			 *		wide gadget by 1 pixel (2 pixels in total) to prevent
			 *		a nasty clash between the two gadget edges.
			 */
			if ((colpos[0] + colwidth[MAIN_STATUS] + 2) > colpos[4]) {
				colpos[4] += 2;
				colpos[5]++;
				colwidth[MAIN_WIDE]--;
			}
			gridtype    = SIX_BY_TWO;
			numrows  	= 2;

		} else if (!statusline && width8 <= innerwidth) {
			/*
			 *		Setup layout for 8 x 1 grid
			 */
			int totalspace = innerwidth - width8;
			int i;

			for (i = 1; i < 4; i++)
				colpos[i] = colpos[0] + colwidth[MAIN_NARROW]*i +
							(totalspace*i)/7;

			for (i = 4; i < 8; i++)
				colpos[i] = colpos[0] + colwidth[MAIN_NARROW] * 4
									  + colwidth[MAIN_WIDE]   * (i-4)
									  + (totalspace*i)/7;
			gridtype = EIGHT_BY_ONE;
			numrows	 = 1;

		} else {
			/*
			 *		Setup layout for 4 x 3 grid
			 */
			int totalspace = (innerwidth - width4);

			colwidth[MAIN_STATUS] = width - bordright - margin - colpos[0];
			colpos[3]	= width - bordright - margin - colwidth[MAIN_WIDE];
			colpos[1]	= colpos[0] + colwidth[MAIN_NARROW] + totalspace/3;
			colpos[2]	= colpos[3] - colwidth[MAIN_WIDE]   - totalspace/3;
			gridtype	= FOUR_BY_THREE;
			if (statusline)
				numrows	 = 3;
			else
				numrows  = 2;
		}
	} else {
		/*
		 *		No gadget line, so set all the gadget positions to a
		 *		negative value to ensure the gadgets stay hidden, and
		 *		create or don't create a status line as appropriate.
		 */
		int i;

		colwidth[MAIN_STATUS] = innerwidth;
		gridtype              = TWELVE_BY_ONE;
		numrows               = 1;
		for (i = 1; i < 12; i++)
			colpos[i] = INVIS_LEFT_EDGE;	/* Gadget off left-hand edge */

		if (!statusline) {
			numrows   = 0;
			colpos[0] = INVIS_LEFT_EDGE;
		}
	}
	colwidth[MAIN_NARROW_TOGGLE] = colwidth[MAIN_NARROW];
	colwidth[MAIN_WIDE_INVIS]	 = colwidth[MAIN_WIDE];

	/*
	 *		Next, calculate how many lines we can fit in the main buffer
	 *		display. The total vertical margin is fonty + 8 + BarHeight
	 *		pixels, which is distributed as follows:
	 *
	 *					----titlebar----
	 *                  <1 pixel border>
	 *					4 pixel gap
	 *					Column headings
	 *					<VBorderGap>
	 *					2 pixels of border
	 *					<VBorderGap>
	 *					Lines of text
	 *					<VBorderGap>
	 *					(1 pixel border)
	 *
	 *		And in addition, if we have gadgets enabled:
	 *
	 *					(1 pixel border)
	 *					<gadg_gap>
	 *					---gadget area---
	 *					<gadg_gap>
	 *					(1 pixel border)
	 *					---bottom border---
	 *
	 *		The minimum spacing between gadgets is also gadg_gap.
	 *		Each individual gadget has a height 6 pixels higher than
	 *		the font size.
	 *
	 *		The catch is to take the <any additional space> and distribute
	 *		it equally between the various gadget spaces etc. For N rows
	 *		of gadgets, this implies that there are (N+1) "gaps" that can
	 *		have vertical space added to them.
	 */
	BoxLeft			= BorderLeft;
	BoxWidth		= width - bordright - BoxLeft;
	BoxInLeft		= BoxLeft + BOX_LEFT_MARGIN + 2;
	BoxInWidth		= bfontx * BoxCols;
	BoxCharWidth	= bfontx;
	BoxCharHeight	= bfonty;

	yoffset			= height - bordbot - spacing * numrows - gadg_gap;
	BoxSpacing  	= bfonty + BoxInterGap;
	if (SquareAspect) {
		BoxTop			= TitlebarHeight + 6 + bfonty;
		BoxHeaderLine 	= TitlebarHeight + 4 + bfontbaseline;
	} else {
		BoxTop			= TitlebarHeight + 5 + bfonty;
		BoxHeaderLine 	= TitlebarHeight + 3 + bfontbaseline;
	}
	if (numrows)
		boxheight   = yoffset - gadg_gap - VBorderGap*3 - BoxTop-2;
	else
		boxheight	= height - bordbot - BoxTop - 2*VBorderGap - 2;
	BoxRows	= (boxheight + BoxInterGap) / BoxSpacing;
	if (numrows)
		BoxHeight	= BoxRows * BoxSpacing - BoxInterGap + 2*VBorderGap + 2;
	else
		BoxHeight	= height - bordbot - BoxTop;

	BoxLowest	= BoxTop   + BoxHeight + VBorderGap;
	BoxInTop	= BoxTop   + VBorderGap + 1;
	BoxBaseline	= BoxInTop + bfontbaseline;
	BoxInHeight	= BoxRows * BoxSpacing - BoxInterGap;

	InitMainMargins();

	/*
	 *		Now get ready to create the button and status line gadgets
	 *		We always create the gadgets, even if they're not currently
	 *		being displayed.
	 */	
	ng.ng_TextAttr		= gadgetfa;
	ng.ng_VisualInfo	= MainVI;
	ng.ng_Flags         = PLACETEXT_IN;

	ng.ng_GadgetText	= "";
	ng.ng_GadgetID		= 0;

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

	/*
	 *		Now, the buttons and status line gadget. At this point,
	 *		we distribute any additional space that we have left between
	 *		the lower scroll bar and the current start of the gadget
	 *		grid (aka yoffset).
	 */
	extraspace		= yoffset - (BoxTop + BoxHeight);
	yoffset		   -= extraspace - extraspace/(numrows+1) -
					  ((extraspace % (numrows+1)) + 1)/2;
	spacing		   += extraspace / (numrows + 1);
	yoffset		   += gadg_gap;
	ng.ng_Height	= gheight; 

	if (gridtype == FOUR_BY_THREE && !statusline)
		yoffset -= spacing;	/* Bias to allow for invisible status bar */

	for (mg = &MainGadgs[0]; mg->gadgid; mg++) {
		switch (gridtype) {
			case FOUR_BY_THREE:
				ng.ng_LeftEdge 	= colpos[mg->col4];
				ng.ng_TopEdge  	= yoffset + spacing * mg->row4;
				ng.ng_Width		= colwidth[mg->widthtype];
				break;

			case SIX_BY_TWO:
				ng.ng_LeftEdge 	= colpos[mg->col6];
				ng.ng_TopEdge  	= yoffset + spacing * mg->row6;
				ng.ng_Width		= colwidth[mg->widthtype];
				break;

			case EIGHT_BY_ONE:
				ng.ng_LeftEdge 	= colpos[mg->col8];
				ng.ng_TopEdge  	= yoffset;
				ng.ng_Width		= colwidth[mg->widthtype];
				break;
		
			case TWELVE_BY_ONE:
				ng.ng_LeftEdge 	= colpos[mg->col12];
				ng.ng_TopEdge  	= yoffset;
				ng.ng_Width		= colwidth[mg->widthtype];
				break;
		}
		ng.ng_GadgetText	= MSG(mg->stringid);
		ng.ng_GadgetID		= mg->gadgid;

		if (mg->gadgid == GID_STATUS) {
			if (statusline) {
				int len = GetTextLen(font, MSG(mg->stringid)) + 9;

				ng.ng_LeftEdge += len;
				ng.ng_Width	   -= len;
				ng.ng_Flags		= PLACETEXT_LEFT;
				gad = CreateGadget(TEXT_KIND, gad, &ng,
								   GT_Underscore,	'_',
								   GTTX_Text,		StatusLineText,
								   GTTX_Border,		TRUE,
								   TAG_DONE);
				ng.ng_Flags		   = PLACETEXT_IN;
				if (!gad)
					goto mgad_failed;
				Gadget[GID_STATUS] = gad;
			} else {
				Gadget[GID_STATUS] = NULL;
			}
		} else {
			gad = CreateGadget(BUTTON_KIND, gad, &ng,
							   GT_Underscore,	'_',
							   TAG_DONE);
			if (!gad)
				goto mgad_failed;

			if (mg->widthtype == MAIN_NARROW_TOGGLE)
				gad->Activation |= GACT_TOGGLESELECT;

			if (mg->widthtype == MAIN_WIDE_INVIS)
				gad->LeftEdge = INVIS_LEFT_EDGE;

			Gadget[mg->gadgid] = gad;
		}
		AddKeyShortcut(MainKeyboard, mg->gadgid, mg->stringid);
	}
	LogButtonLeft = Gadget[GID_OPENLOG]->LeftEdge;
	SetLogGadget(DefaultLogMode, LG_NOREFRESH);

	/*
	 *		Now some additional initialisation for the Pause
	 *		and Disable gadgets in case they're initially set
	 */
	if (Paused)		Gadget[GID_PAUSE  ]->Flags |= GFLG_SELECTED;
	if (Disabled)	Gadget[GID_DISABLE]->Flags |= GFLG_SELECTED;
		
	/*
	 *		Now a quick check to see if we have opened commodities.library
	 *		yet -- if we haven't, then disable the hide gadget.
	 */
	if (CurSettings.Setup.HideMethod == HIDE_NONE)
		Gadget[GID_HIDE]->Flags |= GFLG_DISABLED;

	return (gadlist);

mgad_failed:
	FreeMainGadgets();
	CloseFont(font);
	return (NULL);
}

/*
 *		RecalcMainWindow(width, height, refresh)
 *
 *		Updates the gadgets for the main window to reflect the new size,
 *		and renders the changes to the screen. If the refresh parameter
 *		is REDRAW_GADGETS, then the function will automatically redraw
 *		the gadgets in the window, otherwise this must be done by hand
 *		(or perhaps automatically if Intuition generates an IDCMP_REFRESH
 *		message after a resize event!)
 *
 *		Returns FALSE if something went wrong (in which case the caller
 *		is responsible for closing the main window) and displaying an
 *		error message.
 */
BOOL RecalcMainWindow(int width, int height, int dorefresh)
{
	struct RastPort *rport = MainWindow->RPort;
	int saveleft	= BoxLeft;
	int savewidth	= BoxWidth;
	int saveheight	= BoxHeight;
	int oldrows     = BoxRows;
	int startrow;

	if (!MainWindow)
		return (TRUE);

	if (MainGadList && !RemovedGadgets)
		RemoveGList(MainWindow, MainGadList, -1);

	MainGadList = CreateMainGadgets(CurMainGadgetFA, CurMainBufferFA,
						   			width, height, StatusLine);
	if (!MainGadList)
		return (FALSE);

	UpdateMainHScroll();
	UpdateMainVScroll();
	if (BoxWidth == savewidth && BoxLeft == saveleft && !ChangedSpacing) {
		if (BoxHeight == saveheight)
			/*
			 *		Only erase from bottom of scroll box down since the
			 *		rest hasn't actually changed at all.
			 */
			startrow = BoxLowest;
		else {
			startrow = BoxTop + MIN(BoxHeight, saveheight) - 1;
			startrow = MAX(startrow, TitlebarHeight);
		}
	} else
		startrow = TitlebarHeight;

	SetDrMd(rport, JAM1);
	SetAPen(rport, 0);
	AddGList(MainWindow, MainGadList, -1, -1, NULL);

	if (startrow == BoxLowest) {
		/*
		 *		If we are only refeshing the bottom part of the screen,
		 *		don't bother erasing the top half -- it looks much
		 *		cleaner.
		 */
		if (StatusLine || GadgetsLine) {
			// SetAPen(rport, 4);	/* Makes gadget bground gray (nice!) */
			RectFill(rport, BoxLeft + 2, BoxTop + BoxHeight + 1,
							BoxLeft + BoxWidth - 3,
							MainWindow->Height - MainWindow->BorderBottom - 1);
			DrawBevelBox(rport, BoxLeft, BoxTop + BoxHeight, BoxWidth,
								MainWindow->Height - MainWindow->BorderBottom -
													 BoxTop - BoxHeight,
								GT_VisualInfo,	MainVI,
								TAG_DONE);
		}
		if (dorefresh)
			RefreshGList(MainGadList, MainWindow, NULL, -1);
	} else {
		startrow = MAX(TitlebarHeight, startrow - BoxSpacing - 3);
		RectFill(rport, MainWindow->BorderLeft, startrow,
				 width  - MainWindow->BorderRight  - 1,
				 height - MainWindow->BorderBottom - 1);
		if (dorefresh)
			RefreshGList(MainGadList, MainWindow, NULL, -1);
		/*
		 *		If we are currently at the end of the display, we want to
		 *		ensure that we scroll to the end after refreshing the window.
		 */
		if (TopSeq + oldrows > EndSeq) {
			/*
			 *		Force redraw from buffer end after resize
			 */
			TopSeq   = EndSeq;
			TopEvent = EndEvent;
		}
		RedrawMainWindow();
		UpdateMainVScroll();
	}
	GT_RefreshWindow(MainWindow, NULL);
	CurWindowWidth  = MainWindow->Width;
	CurWindowHeight = MainWindow->Height;
	ChangedSpacing  = 0;
	return (TRUE);
}

/*
 *		SetMenuOptions()
 *
 *		Configures the main window's menu strip to match the currently
 *		selected options (e.g. Pause/Disable, Text Spacing, window type,
 *		icon type, etc.)
 *
 *		Call whenever settings change.
 *
 */
void SetMenuOptions(void)
{
	struct MenuItem *item;

#define SetMenuState(menunum, val) \
	item = ItemAddress(MainWinMenu, menunum); \
	item->Flags = (item->Flags & ~CHECKED) | ((val) ? CHECKED : 0)

	if (MainWindow && MainWinMenu) {
		int curpri = SysBase->ThisTask->tc_Node.ln_Pri;
		int i;

		SetMenuState(MENU_PAUSE,	  Paused);
		SetMenuState(MENU_DISABLE,	  Disabled);
		SetMenuState(MENU_AUTO_OPEN,  AutoOpen);
		SetMenuState(MENU_DIS_HIDDEN, DisableOnHide);
		SetMenuState(MENU_STATUS,	  StatusLine);
		SetMenuState(MENU_GADGETS,	  GadgetsLine);
		SetMenuState(MENU_ICONS,	  CreateIcons);
		SetMenuState(MENU_SIMPLE,	  CurSettings.SimpleRefresh);
		SetMenuState(MENU_SMART,	  !CurSettings.SimpleRefresh);
		SetMenuState(MENU_AL_LEFT,	  !RightAligned);
		SetMenuState(MENU_AL_RIGHT,	  RightAligned);
		SetMenuState(MENU_SPACING0,	  BoxInterGap == 0);
		SetMenuState(MENU_SPACING1,	  BoxInterGap == 1);
		SetMenuState(MENU_SPACING2,	  BoxInterGap == 2);

		SetMenuState(MENU_ROWQUAL_ANY,	  RowQual == ROWQUAL_ANY);
		SetMenuState(MENU_ROWQUAL_NONE,	  RowQual == ROWQUAL_NONE);
		SetMenuState(MENU_ROWQUAL_SHIFT,  RowQual == ROWQUAL_SHIFT);
		SetMenuState(MENU_ROWQUAL_ALT,	  RowQual == ROWQUAL_ALT);
		SetMenuState(MENU_ROWQUAL_CTRL,	  RowQual == ROWQUAL_CTRL);
		SetMenuState(MENU_ROWQUAL_ALL,	  RowQual == ROWQUAL_ALL);

		if (CurSettings.Setup.HideMethod == HIDE_NONE)
			OffMenu(MainWindow, MENU_HIDE);
		else
			OnMenu(MainWindow,	MENU_HIDE);

		if (LogActive) {
			OffMenu(MainWindow,	MENU_OPENLOG);
			OnMenu(MainWindow,	MENU_CLOSELOG);
		} else {
			OnMenu(MainWindow,	MENU_OPENLOG);
			OffMenu(MainWindow,	MENU_CLOSELOG);
		}

		/*
		 *		We calculate our current priority on the fly. This
		 *		means we'll always be up-to-date, even if someone
		 *		changes our priority from outside the program.
		 */
		for (i = 0; i < MENU_NUMPRI; i++) {
			int subnum = MENU_CHANGEPRI | SHIFTSUB(i);
			struct MenuItem  *item  = ItemAddress(MainWinMenu, subnum);
			struct IntuiText *itext = (void *)(item->ItemFill);

			if (atoi(itext->IText) == curpri)
				item->Flags |= CHECKED;
			else
				item->Flags &= ~CHECKED;
		}
	}
}
		
/*
 *		SetMainHideState(state)
 *
 *		Updates the current main window titlebar, hide gadget, and
 *		menu HIDE option to reflect the given hide state.
 *
 *		It works as follows. If state is HIDE_NONE, then the titlebar
 *		is set to <none> and the gadget/menu item are ghosted. If
 *		state is not HIDE_NONE, the gadget/menu item are unghosted,
 *		and the titlebar is set to <key sequence>, unless HotKeyActive
 *		is set to 0, in which case it is set to <invalid>.
 */
void SetMainHideState(int hidestate)
{
	char title[200];
	char *keyname = CurSettings.Setup.HotKey;

	if (!HotKeyActive)
		keyname = MSG(MSG_INVALID_HOTKEY);
	
	if (hidestate == HIDE_NONE)
		strcpy(title, SnoopDosTitle);
	else
		mysprintf(title, SnoopDosTitleKey, keyname);

	if (strcmp(CurrentTitle, title) != 0)
		strcpy(CurrentTitle, title);
	
	if (MainWindow) {
		struct Gadget *gad = Gadget[GID_HIDE];
		int oldstate	   = gad->Flags & GFLG_DISABLED;
		int gadpos;

		SetWindowTitles(MainWindow, CurrentTitle, (UBYTE *)-1);

		gadpos = RemoveGadget(MainWindow, gad);
		if (hidestate == HIDE_NONE)
			gad->Flags |= GFLG_DISABLED;
		else
			gad->Flags &= ~GFLG_DISABLED;

		AddGadget(MainWindow, gad, gadpos);
		if ((gad->Flags & GFLG_DISABLED) != oldstate) {
			/*
			 *		Only refresh the gadget if its state changed
			 */
			RefreshGList(gad, MainWindow, NULL, 1);
		}
		if (MainWinMenu) {
			if (CurSettings.Setup.HideMethod == HIDE_NONE)
				OffMenu(MainWindow, MENU_HIDE);
			else
				OnMenu(MainWindow,	MENU_HIDE);
		}
	}
}

/*
 *		OpenMainWindow()
 *
 *		Opens the main window with the scroll buffer display, arrow gadgets,
 *		and buttons. Also creates the status line gadget if necessary.
 *
 *		Returns true for success, false for failure.
 */
BOOL OpenMainWindow(void)
{
	static WORD initzoomdims[] = { 0, 0, -1, -1 };
	struct Window *win;
	struct Gadget *scrollgadlist;
	struct TextAttr *gadgetfa;
	struct TextAttr *bufferfa;
	int minx, miny;
	int menupen;
	int i;
	int width  = CurSettings.MainWinWidth;
	int height = CurSettings.MainWinHeight;
	int left   = CurSettings.MainWinLeft;
	int top    = CurSettings.MainWinTop;

	CheckSegTracker();
	if (MainWindow) {
		WindowToFront(MainWindow);
		ActivateWindow(MainWindow);
		return (TRUE);
	}
	if (!CheckForScreen())
		goto open_main_failed;

	for (i = 0; gadgetfa = MainWindowFontList[i].gadgetfa; i++) {
		bufferfa = MainWindowFontList[i].bufferfa;

		if (CalcMinMainSize(gadgetfa, bufferfa, &minx, &miny))
			break;
	}
	if (!gadgetfa)
		goto open_main_failed;

	if (width  == -1)					width  = DEF_WINDOW_WIDTH;
	if (height == -1)					height = DEF_WINDOW_HEIGHT;
	if (width  == 0)					width  = CurSettings.MainWinWidth;
	if (height == 0)					height = CurSettings.MainWinHeight;
	if (width  < minx) 					width  = minx;
	if (height < miny)					height = miny;
	if (width  > ScreenWidth)			width  = ScreenWidth;
	if (height > ScreenHeight)			height = ScreenHeight;
	if (left   == -1)					left   = 0;
	if (top    == -1)					top    = SnoopScreen->BarHeight + 2;

	initzoomdims[2] = ScreenWidth;
	initzoomdims[3] = ScreenHeight;
		
	ShowBuffer(TopSeq, DISPLAY_NONE);	/* Ensure scroll vars are up to date */

	MainGadList   = CreateMainGadgets(gadgetfa, bufferfa,
									 width, height, StatusLine);
	scrollgadlist = CreateScrollGadgets();
	if (!scrollgadlist || !MainGadList)
		goto open_main_failed;

	Gadget[GID_ENDSCROLL]->NextGadget = MainGadList;

	CurMainGadgetFA = gadgetfa;
	CurMainBufferFA = bufferfa;

	SetMainHideState(CurSettings.Setup.HideMethod);	/* Make title up to date */
	win = OpenWindowTags(NULL,
						 WA_Title,			CurrentTitle,
						 WA_IDCMP,			IDCMP_CLOSEWINDOW	 |
						 					IDCMP_REFRESHWINDOW	 |
											IDCMP_NEWSIZE		 |
											IDCMP_CHANGEWINDOW	 |
											IDCMP_RAWKEY		 |
											IDCMP_MENUPICK		 |
											IDCMP_IDCMPUPDATE	 |
											IDCMP_GADGETDOWN	 |
											IDCMP_MOUSEBUTTONS	 |
											IDCMP_MOUSEMOVE		 |
											IDCMP_MENUHELP 		 |
											IDCMP_SIZEVERIFY	 |
											IDCMP_INACTIVEWINDOW |
											BUTTONIDCMP,
						 WA_Left,  			left,
						 WA_Top,  			top,
						 WA_Width,			width,
						 WA_Height,			height,
						 WA_Flags,			WFLG_CLOSEGADGET	 |
						 					WFLG_DRAGBAR		 |
											WFLG_DEPTHGADGET	 |
											WFLG_ACTIVATE		 |
											WFLG_SIZEGADGET		 |
											WFLG_SIZEBBOTTOM	 |
											WFLG_SIZEBRIGHT		 |
											WFLG_NEWLOOKMENUS,
						 WA_MenuHelp,		TRUE,
						 RefreshTag,      	TRUE,
						 WA_NoCareRefresh,	NoCareRefreshBool,
						 WA_PubScreen,		SnoopScreen,
						 WA_MinWidth,		minx,
						 WA_MinHeight,		miny,
						 WA_MaxWidth,		-1,
						 WA_MaxHeight,		-1,
						 WA_Zoom,			initzoomdims,
						 WA_Gadgets,		scrollgadlist,
						 TAG_DONE);
	if (!win)
		goto open_main_failed;

	MainWindow	   = win;
	MainWindowPort = win->UserPort;
	MainWindowMask = 1 << MainWindowPort->mp_SigBit;

	if (DraggingColumn || DraggingRow) {
		/*
		 *		If we were previously closed while a column was being dragged,
		 *		reset the drag flag and signal ourselves to ensure we catch
		 *		any events that arrived while output was suspended.
		 */
		DraggingColumn = 0;
		DraggingRow    = 0;
		Signal(SysBase->ThisTask, NewEventMask);
	}
	RedrawMainWindow();
	// RefreshGList(MainGadList, MainWindow, NULL, -1);	/* Already done */
	GT_RefreshWindow(MainWindow, NULL);
	UpdateStatus();

	/*
	 *		Now create menus for window
	 */
	if (IntuitionBase->LibNode.lib_Version >= 39)
		menupen = 1;
	else
		menupen = 0;

	MainWinMenu = CreateMenus(MainMenu, GTMN_FrontPen, menupen, TAG_DONE);

	if (!MainWinMenu)
		ShowError(MSG(MSG_ERROR_CREATEMENUS));
	else {
		LayoutMenus(MainWinMenu, MainVI, GTMN_NewLookMenus, TRUE, TAG_DONE);
		SetMenuStrip(MainWindow, MainWinMenu);
		SetMenuOptions();
	}
	CurWindowWidth  = MainWindow->Width;
	CurWindowHeight = MainWindow->Height;
	AwaitingResize  = RESIZE_DONE;
	if ((SnoopScreen->Flags & SCREENTYPE) != WBENCHSCREEN) {
		/*
		 *		We're running on a custom screen, so redirect system
		 *		requesters to appear on this screen instead of the
		 *		Workbench screen. We don't do it unconditionally, because
		 *		our window title is so long that it makes the requesters
		 *		look very lopsided -- still, if we're on a custom screen,
		 *		it's better than nothing.
		 */
		*TaskWindowPtr = MainWindow;
	}
	if (DisableNestCount)
		DisableWindow(MainWindow, &MainRequester);

	return (TRUE);

open_main_failed:
	ShowError(MSG(MSG_ERROR_OPENMAIN));
	return (FALSE);
}

/*
 *		CloseMainWindow(void)
 *
 *		Closes the main SnoopDos window
 */
void CloseMainWindow(void)
{
	*TaskWindowPtr = SaveWindowPtr;		/* Restore previous screen address */
	if (MainWindow) {
		if (MainWinMenu) {
			ClearMenuStrip(MainWindow);
			FreeMenus(MainWinMenu);
			MainWinMenu = NULL;
		}
		RecordWindowSizes();
		CloseWindow(MainWindow);
		FreeMainGadgets();
		MainWindow		= NULL;
		MainWindowMask	= 0;
	}
	FreeScrollGadgets();
	if (MainVI) {
		FreeVisualInfo(MainVI);
		MainVI = NULL;
	}
}

/*
 *		ShowStatus(msg)
 *
 *		Displays the specified message in the status line (if enabled)
 */
void ShowStatus(char *msg)
{
	strcpy(StatusLineText, msg);

	if (MainWindow) {
		GT_SetGadgetAttrs(Gadget[GID_STATUS], MainWindow, NULL,
						  GTTX_Text,	StatusLineText,
						  TAG_DONE);
	}
}

/*
 *		ReOpenMainWindow()
 *
 *		Closes and re-opens main window retaining current size but
 *		taking into account new font, spacing, refresh type, etc.
 *		If another window is specified, then that window is brought
 *		to the front after the main window is reopened.
 */
void ReOpenMainWindow(void)
{
	if (MainWindow) {
		/*
		 *		If we've changed spacing but not yet updated the window to
		 *		reflect this, then if we are going from 0-spacing to 2-spacing,
		 *		the buffer may loose a few lines. To ensure we remain at the
		 *		end of the buffer if we were there to start with, we do an
		 *		invisible scroll to the _very_ end, just to be safe.
		 */
		if (BottomSeq >= EndSeq) {
			TopSeq	 = EndSeq;
			TopEvent = EndEvent;
		}
		CloseMainWindow();
		OpenMainWindow();
	}
}

/*
 *		CheckForDirtyMainWindow()
 *
 *		Checks to see if the main Window's rastport is dirty (i.e. we did
 *		a scrollraster which was partially obscured by another window, and
 *		now we need to refresh) and redraw the window if necessary.
 *
 *		Typically called after ShowBuffer()
 */
void CheckForDirtyMainWindow(void)
{
	if (MainWindow && MainWindow->RPort->Layer->Flags & LAYERREFRESH) {
		GT_BeginRefresh(MainWindow);
		RedrawMainWindow();
		GT_EndRefresh(MainWindow, TRUE);
	}
}

/*
 *		UpdateMainHScroll(void)
 *
 *		Updates the main horizontal scrollbar to reflect the current
 *		margin settings and buffer width.
 */
void UpdateMainHScroll(void)
{
	if (MainWindow)
		SetGadgetAttrs(Gadget[GID_HSCROLLER], MainWindow, NULL,
					   PGA_Top,		LeftCol,
					   PGA_Total,	BufferWidth,
					   PGA_Visible,	BoxCols,
					   TAG_DONE);
}

/*
 *		UpdateMainVScroll(void)
 *
 *		Updates the main scrollbar to reflect the current state of play.
 *		N.b. Depends on the parameters calculated during the last call
 *		to ShowBuffer.
 */
void UpdateMainVScroll(void)
{
	if (MainWindow)
		SetGadgetAttrs(Gadget[GID_VSCROLLER], MainWindow, NULL,
					   PGA_Top,		TopSeq - FirstSeq,
					   PGA_Total,	EndSeq - FirstSeq + 1,
					   PGA_Visible,	BoxRows,
					   TAG_DONE);
}

/*
 *		ScrollHorizontal(amount)
 *
 *		Scrolls the window horizontally to the left (-ve amount) or right
 *		(+ve amount) and does the minimum amount of refresh necessary to
 *		ensure the screen is correctly updated. i.e., we use ScrollRaster()
 *		to perform the bulk of the scroll.
 */
void ScrollHorizontal(int amount)
{
	struct RastPort *rport = MainWindow->RPort;
	int	   oldpos = LeftCol;
	Event *firstevent;
	int x0, y0, x1, y1;
	int h0, h1;
	int dx;
	int dxwidth;
	int saveboxleft;
	int saveleftcol;
	int saverightcol;
	int saveboxcols;

	LeftCol += amount;
	InitMainMargins();
	if (!MainWindow || LeftCol == oldpos)
		return;

	UpdateMainHScroll();

	/*
	 *		Okay, we're scrolling left or right, so do a ScrollRaster for
	 *		part of the amount and then use ShowBuffer() to fill in the
	 *		refreshed area. If there are no events or the buffer display
	 *		is now out of date, do a full refresh instead.
	 *
	 *		We lock semaphores for the duration to ensure that the buffer
	 *		doesn't change under our feet which might lead to mismatched
	 *		lines of output.
	 */
	LOCK_LAYERS;
	ObtainSemaphore(&BufSem);

	dx = LeftCol - oldpos;
	dxwidth = dx * BoxCharWidth;

	firstevent = HeadNode(&EventList);
	if (!firstevent || TopSeq < firstevent->seqnum || abs(dx) > (BoxCols/2)) {
		RedrawMainWindow();
		goto unlockall;
	}

	/*
	 *		Calculate co-ordinates for scroll
	 */
	h0 = BoxHeaderLine + BoxInTop - BoxBaseline;
	h1 = h0 + BoxCharHeight - 1;
	y0 = BoxInTop;
	y1 = BoxInTop + BoxInHeight - 1;
	x0 = BoxInLeft;
	x1 = BoxInLeft + (RightCol - LeftCol + 1) * BoxCharWidth - 1;

	if (GfxBase->LibNode.lib_Version >= 39) {
		/*
		 *		Do optimised flicker-free scroll under V39
		 */
		struct Hook *oldhook;

		oldhook = InstallLayerHook(MainWindow->WLayer, LAYERS_NOBACKFILL);
		ScrollRasterBF(rport, dxwidth, 0, x0, h0, x1, y1);
		InstallLayerHook(MainWindow->WLayer, oldhook);
	} else {
		/*
		 *		Do a somewhat flickering scroll under V37 -- unfortunately,
		 *		we have to do the scroll in two separate parts, to avoid
		 *		erasing part of the separator bar between the header and
		 *		the buffer text when it is scrolled
		 */
		ScrollRaster(rport, dxwidth, 0, x0, h0, x1, h1);
		ScrollRaster(rport, dxwidth, 0, x0, y0, x1, y1);
	}
	/*
	 *		Finally, refresh the window section we just vacated by
	 *		temporarily changing the margins
	 */
	saveboxleft  = BoxInLeft;
	saveleftcol  = LeftCol;
	saverightcol = RightCol;
	saveboxcols  = BoxCols;

	if (dx < 0) {
		/*
		 *		Scrolling to left so refresh left side of window
		 */
		if (RightCol > -dx)
			RightCol = LeftCol - dx - 1;
		BoxCols = RightCol - LeftCol + 1;
	} else {
		/*
		 *		Scrolling to right so refresh right side of window
		 */
		int offset = RightCol - LeftCol + 1 - dx;
		if (offset < 0)
			goto done_scroll;

		BoxInLeft += offset * BoxCharWidth;
		LeftCol   += offset;
		BoxCols   -= offset;
	}
	if (RightCol < LeftCol)
		goto done_scroll;
	DrawHeaderLine();
	ShowBuffer(TopSeq, DISPLAY_ALL);

done_scroll:
	BoxInLeft = saveboxleft;
	LeftCol   = saveleftcol;
	RightCol  = saverightcol;
	BoxCols   = saveboxcols;
	
	/*
	 *		All done, now we can release semaphores and exit
	 */
unlockall:
	ReleaseSemaphore(&BufSem);
	UNLOCK_LAYERS;
	CheckForDirtyMainWindow();
}

/*
 *		DoArrowScrolling()
 *
 *		Scrolls window in the direction of the specified arrow
 *		(GID_UPARROW, GID_DOWNARROW, GID_LEFTARROW or GID_RIGHTARROW)
 *		and by the specified amount (usually 1 but sometimes more).
 */
void DoArrowScrolling(int arrowtype, int amount)
{
	int changedvert = 0;
	int horizadjust = 0;
	int newseq;

	/*
	 *		If we're currently dragging columns, we ignore any attempts
	 *		at scrolling since it can cause problems with the format
	 *		being screwed up.
	 */
	if (DraggingColumn || DraggingRow)
		return;

	switch (arrowtype) {
		case GID_UPARROW:
			/*
			 *		Move up in a message
			 */
			if (TopSeq > FirstSeq) {
				changedvert = 1;
				newseq		= TopSeq - amount;
				if (newseq < 1)
					newseq = 1;
			}
			break;

		case GID_DOWNARROW:
			/*		We do a clever optimisation here for power users:
			 *		if SnoopDos is Paused and is at the end of the
			 *		buffer, then clicking on the down arrow (normally
			 *		a no-op) will have the same effect as selecting
			 *		single step.
			 */
			if (Paused && BottomSeq >= EndSeq)
				SingleStep();

			changedvert = 1;
			newseq		= TopSeq + amount;
			break;

		case GID_LEFTARROW:
			if (LeftCol > 0)
				horizadjust =- amount;
			break;

		case GID_RIGHTARROW:
			if (LeftCol + BoxCols <= BufferWidth)
				horizadjust = amount;
			break;
	}
	if (MainWindow) {
		if (horizadjust)
			ScrollHorizontal(horizadjust);

		if (changedvert) {
			InitMainMargins();
			ShowBuffer(newseq, DISPLAY_QUICK);
			UpdateMainVScroll();
			CheckForDirtyMainWindow();
		}
	}
}

/*
 *		InvertColumn(pos)
 *
 *		Inverts the column drawn at the specified column position, relative
 *		to the left margin of the box (i.e. 0 ... BoxCols-1)
 *
 *		mode is INVERT_FULLBOX or INVERT_HEADER, depending on whether just
 *		the top of the column or the entire column is to be inverted. This
 *		allows for optimised updates if a column's position hasn't actually
 *		changed but the header still needs to be redrawn.
 */
void InvertColumn(int col, int mode)
{
	struct RastPort *rport = &InvertRP;
	int bfontbaseline	   = BufferFont->tf_Baseline;
	int y1 = BoxHeaderLine - bfontbaseline;
	int y2 = (mode == INVERT_HEADER ? (BoxTop - 1) : (BoxInTop + BoxInHeight));

	if (col >= 0 && col < BoxCols) {
		int x = BoxInLeft + col * BoxCharWidth + BoxCharWidth / 2;

		RectFill(rport, x, y1, x+1, y2);
	}
}

/*
 *		StartDragCol(x)
 *
 *		Starts dragging a column displayed in the main window at absolute
 *		coordinate X.
 *
 *		Initialises all the variables associated with this and returns
 *		with dragging enabled.
 */
void StartDragCol(int x)
{
	EventFormat *ef;
	int clickcol;

	if (BufferEFormat[0].type == EF_END)	/* Skip empty event formats */
		return;

	/*
	 *		Now scan event buffer and figure out which heading we match
	 *		Note that the user must click on the event heading itself,
	 *		or it won't count (i.e. clicking on space between columns
	 *		doesn't work) -- this is to prevent confusion.
	 */
	clickcol = LeftCol + ((x - BoxInLeft) / BoxCharWidth);
	{
		/*
		 *		Select the column to drag
		 */
		int colpos = 0;
		int len    = 0;

		for (ef = BufferEFormat; ef->type != EF_END; ef++) {
			if (clickcol >= colpos && clickcol < (colpos + ef->width + 1))
				break;
			
			colpos += ef->width + 1;
		}
		/*
		 *		Now check if the clickcol should affect the left or right
		 *		sides of the selected column. The leftmost column is a
		 *		special case (in that we always choose the righthand column
		 *		rather  the preceding lefthand, since you can't drag the
		 *		lefthand column any further to the left).
		 *
		 *		In the normal case, if there is any blank area to the right of
		 *		the column, then clicking in the blank area activates the
		 *		the right column adjustment, else the left column adjustment.
		 *		If there is no blank area, then clicking in the rightmost
		 *		column activates the right column, else the left column.
		 */
		if (ef->type != EF_END)
			len = strlen(MSG(ef->titlemsgid));

		if (ef == BufferEFormat || ef->type == EF_END ||
			clickcol >= (colpos + MIN(ef->width-1, len)))
		{
			/*
			 *		Adjusting the righthand side of the selected column
			 */
			if (ef->type == EF_END) {
				DragEvent	   = ef-1;
				SelectEvent	   = ef-1;
				SelectStartCol = colpos - SelectEvent->width - 1;
			} else {
				SelectEvent    = ef;
				SelectStartCol = colpos;
				DragEvent		 = ef;
			}
			DragOrigColWidth = DragEvent->width;
			NextOrigColWidth = (ef+1)->width;
		} else {
			/*
			 *		Adjusting the lefthand side of the selected column
			 */
			SelectEvent      = ef;
			SelectStartCol	 = colpos;
			DragEvent	  	 = ef-1;
			DragOrigColWidth = DragEvent->width;
			NextOrigColWidth = ef->width;
		}
	}
	OrigLeftCol		= LeftCol;
	OrigBufWidth	= BufferWidth;
	ClickStartCol	= clickcol;
	ClickCurCol		= clickcol;
	DraggingColumn 	= 1;
	MovedColumn		= 0;
	DrawHeaderLine();
	/*
	 *		Now initialise the rastport we use for the inverted column
	 *		(we use a separate rastport for speed; otherwise, we have
	 *		to keep initialising it, which is rather slow.)
	 */
	InvertRP = *(MainWindow->RPort);
	if (GfxBase->LibNode.lib_Version >= 39)
		SetWriteMask(&InvertRP, 1);
	else
		InvertRP.Mask = 1;
	SetAPen(&InvertRP, 1);
	SetBPen(&InvertRP, 0);
	SetDrMd(&InvertRP, COMPLEMENT);

	InvertColumn(SelectStartCol-LeftCol-1,		 			INVERT_FULLBOX);
	InvertColumn(SelectStartCol-LeftCol+SelectEvent->width, INVERT_FULLBOX);

	ReportMouse(TRUE, MainWindow);
	MainWindow->IDCMPFlags |= IDCMP_INTUITICKS;
	MainWindow->Flags      |= WFLG_RMBTRAP;	/* Need to detect RMB for cancel */
}

/*
 *		SizeColumn(eventptr, newwidth, movetype)
 *
 *		Adjusts the width of the specified event entry to the new
 *		value given, performing any scrolling and updating of the
 *		main window that may be required.
 *
 *		movetype is MOVECOL_FREE to allow free movement between the
 *		columns to the left and right, or MOVECOL_FIXED to force
 *		the columns to the right to move along with the column being
 *		sized.
 */
void SizeColumn(EventFormat *ef, int newwidth, int movetype)
{
	EventFormat *nextef = ef+1;
	int oldleftcol	 	= LeftCol;
	int dwidth			= newwidth - ef->width;
	int oldbufwidth		= BufferWidth;
	int oldpos1			= SelectStartCol - LeftCol - 1;
	int oldpos2			= SelectStartCol - LeftCol + SelectEvent->width;
	int newpos1;
	int newpos2;
	int mode1;
	int mode2;

	if (dwidth == 0)
		return;

	if (movetype == MOVECOL_FREE &&  nextef->type    != EF_END	 &&
									(nextef+1)->type != EF_END) {
		/*
		 *		Bound movement so that we don't make the column to the
		 *		right of the main column exceed its bounds (in either
		 *		direction).
		 */
		int nextwidth = nextef->width - dwidth;

		if (nextwidth < 1)				nextwidth = 1;
		if (nextwidth > MAX_FIELD_LEN)	nextwidth = MAX_FIELD_LEN;

		if (nextwidth != (nextef->width - dwidth)) {
			dwidth   = nextef->width - nextwidth;
			newwidth = ef->width + dwidth;
		}
		nextef->width = nextwidth;
	} else {
		/*
		 *		Dragging all the columns to the right of this one too so
		 *		update width to reflect the change
		 */
		BufferWidth   += dwidth;
	}
	ef->width      = newwidth;
	if (ef < SelectEvent)
		SelectStartCol += dwidth;
	ClickCurCol   += dwidth;
	ClearMainRHS   = 1;
	InitMainMargins();

	/*
	 *		Check if the drag mouse position is off the right edge of the
	 *		window; if it is, then scroll the window to keep it in view.
	 */
	if (ClickCurCol >= (LeftCol + BoxCols)) {
		LeftCol = ClickCurCol - BoxCols + 1;
		InitMainMargins();
	}
	if (BufferWidth != oldbufwidth || LeftCol != oldleftcol)
		UpdateMainHScroll();

	newpos1 = SelectStartCol - LeftCol - 1;
	newpos2 = SelectStartCol - LeftCol + SelectEvent->width;

	mode1 = mode2 = INVERT_FULLBOX;

	if (newpos1 == oldpos1) mode1 = INVERT_HEADER;
	if (newpos2 == oldpos2) mode2 = INVERT_HEADER;

	InvertColumn(oldpos1, mode1);
	InvertColumn(oldpos2, mode2);
	DrawHeaderLine();
	InvertColumn(newpos1, mode1);
	InvertColumn(newpos2, mode2);

	MovedColumn = 1;	/* Show we updated a column for FinishDragCol() */
}

/*
 *		MoveColumn(xpos, movetype)
 *
 *		Mini function used when dragging columns with the mouse --
 *		changes the width of the current column to pixel position xpos,
 *		as much as possible.
 *
 *		movetype is either MOVECOL_FREE or MOVECOL_FIXED -- see the
 *		description of SizeColumn() for more details.
 */
void MoveColumn(int xpos, int movetype)
{
	int colpos = LeftCol + ((xpos - BoxInLeft) / BoxCharWidth);

	if (DragEvent) {
		int disp	 = colpos - ClickStartCol;
		int newwidth = DragOrigColWidth + disp;
		int dwidth   = newwidth - DragEvent->width;

		/*
		 *		Now check to see if the change we're making will
		 *		cause us to scroll off the left edge of the window.
		 *		If it will, then reduce the change so that we scroll
		 *		at a steady 1-character rate (this works out around
		 *		10 chars a second, which should be fast enough).
		 */
		if (LeftCol > 0 && RightCol > (BufferWidth + dwidth))
		{
			dwidth   = RightCol - BufferWidth;
			newwidth = DragEvent->width + dwidth;
		}
		if (newwidth < 1)				newwidth = 1;
		if (newwidth > MAX_FIELD_LEN)	newwidth = MAX_FIELD_LEN;

		SizeColumn(DragEvent, newwidth, movetype);
	}
}

/*
 *		FinishDragCol()
 *
 *		Finishes up a drag operation (just resets a few variables)
 */
void FinishDragCol(void)
{
	if (!DraggingColumn)
		return;

	ReportMouse(FALSE, MainWindow);
	MainWindow->IDCMPFlags &= ~IDCMP_INTUITICKS;
	MainWindow->Flags      &= ~WFLG_RMBTRAP;	/* Re-enable menus	*/
	DraggingColumn = 0;

	/*
	 *		We can optimise our update a bit by checking MovedColumn.
	 *		This will only be true if the user actually resized one
	 *		of the columns, so it saves us having to do a redraw
	 *		if a column heading was accidentally clicked but not
	 *		moved.
	 */
	if (SelectEvent) {
		InvertColumn(SelectStartCol-LeftCol-1,		 	 	   INVERT_FULLBOX);
		InvertColumn(SelectStartCol-LeftCol+SelectEvent->width,INVERT_FULLBOX);
	}
	if (MovedColumn) {
		/*
		 *		Now update the settings window and format window with the
		 *		new format. This will also redraw the main window and
		 *		header line for us.
		 */
		BuildFormatString(BufferEFormat, BufFormat, MAX_FORM_LEN);
		InstallNewFormat(NEW_STRING);
	} else {
		/*
		 *		Nothing else to update, so just redraw the header line
		 *		(which will now be completely black again with no
		 *		highlighted item.)
		 */
		ClearMainRHS = 1;
		DrawHeaderLine();
	}
	/*
	 *		Since we disable event scanning when columns are being dragged,
	 *		we signal ourselves to check for new events in case any
	 *		arrived while we were playing.
	 */
	Signal(SysBase->ThisTask, NewEventMask);
}

/*
 *		StartDragRow(int y)
 *
 *		Starts dragging a highlight at y (pixel row) in the event output.
 *		This allows the user to use the mouse to highlight an entire line,
 *		making it easy to see all the information on the line related to
 *		the event. (This can sometimes be difficult to see in a normal
 *		window when there is a lot of white space between columns).
 *
 *		Initialises all the variables associated with this and returns
 *		with dragging enabled.
 */
void StartDragRow(int y)
{
	if (BufferEFormat[0].type == EF_END)	/* Skip empty event formats */
		return;

	SelectRow = 0;
	if (y > BoxInTop)
		SelectRow = (y - BoxInTop) / BoxSpacing;

	if (SelectRow >= BoxRows)
		SelectRow = BoxRows - 1;

	if ((TopSeq + SelectRow) > BottomSeq)
		SelectRow = BottomSeq - TopSeq;

	DraggingRow = 1;
	ReportMouse(TRUE, MainWindow);
	MainWindow->IDCMPFlags |= IDCMP_INTUITICKS;
	DrawSelectedLine(SelectRow, TRUE);
}

/*
 *		MoveToRow(y)
 *
 *		Moves the row highlight from its current position to a new position
 *		if necessary (doesn't render anything if the row position remains
 *		unchanged.)
 */
void MoveToRow(int y)
{
	int newrow = 0;

	if (y > BoxInTop)
		newrow = (y - BoxInTop) / BoxSpacing;

	if (newrow >= BoxRows)
		newrow = BoxRows - 1;

	if ((TopSeq + newrow) > BottomSeq)
		newrow = BottomSeq - TopSeq;

	if (newrow != SelectRow) {
		DrawSelectedLine(SelectRow, FALSE);
		DrawSelectedLine(newrow,    TRUE);
		SelectRow = newrow;
	}
}

/*
 *		FinishDragRow()
 *
 *		Stops highlighting the selected row and restores everything to normal
 */
void FinishDragRow(void)
{
	if (!DraggingRow)
		return;

	DrawSelectedLine(SelectRow, FALSE);
	ReportMouse(FALSE, MainWindow);
	MainWindow->IDCMPFlags &= ~IDCMP_INTUITICKS;
	DraggingRow = 0;

	/*
	 *		It's possible that while we were playing with the row highlight,
	 *		the entire buffer may have been swept away under our feet,
	 *		leading to a slightly corrupt display (events out of sequence).
	 *		We don't mind this while we're dragging the highlight, but we want
	 *		to ensure it's sorted out properly afterwards. So, we do a quick
	 *		check to see if the current display is no longer valid, and if so,
	 *		then we redraw it. We need to handle new events first, else lots
	 *		of important internal variables will be out of sync.
	 */
	if (TopSeq < RealFirstSeq) {
		HandleNewEvents();
		ShowBuffer(TopSeq, DISPLAY_ALL);
	}
}

/*
 *		BeginResize()
 *
 *		Removes gadget list from window and does a few other things
 *		in preparation for a window resize occurring in the very
 *		near future.
 */
void BeginResize(void)
{
	if (!RemovedGadgets)
		RemoveGList(MainWindow, MainGadList, -1);
	RemovedGadgets = TRUE;
	if (BottomSeq >= EndSeq)
		AwaitingResize = RESIZE_BOTTOM;
	else
		AwaitingResize = RESIZE_MIDDLE;
}

/*
 *		EndResize(void)
 *
 *		Tiedies up after the window has been resized -- recalculates
 *		gadget list, refreshes display, etc.
 *
 *		Returns true for success, false for failure.
 */
int EndResize(void)
{
	if (MainWindow->Width  == CurWindowWidth &&
		MainWindow->Height == CurWindowHeight)
	{
		/*
		 *		Window didn't change size after all, so don't make any
		 *		changes, just restore the status quo
		 */
		if (RemovedGadgets)
			AddGList(MainWindow, MainGadList, ~0, -1, NULL);
		AwaitingResize = RESIZE_DONE;
	} else {
		/*
		 *		Window was resized. If necessary, reposition the buffer
		 *		at the end of the new window (otherwise, if we were at
		 *		the end of the buffer and the window got smaller, there
		 *		would now be a few lines below the bottom of the window.)
		 */
		if (AwaitingResize == RESIZE_BOTTOM)
			TopSeq = EndSeq;
		AwaitingResize = RESIZE_DONE;
		if (!RecalcMainWindow(MainWindow->Width,
							  MainWindow->Height,
							  REDRAW_GADGETS)) {
			ShowError(MSG(MSG_ERROR_RESIZE));
			return (0);
		}
		// RefreshWindowFrame(MainWindow);
	}
	RemovedGadgets = FALSE;

	/*
	 *		Now, since we disabled monitoring of events during
	 *		the resize, some may have accumulated that we missed.
	 *		So we generate a fake new events signal to force a
	 *		recheck in case anything did arrive.
	 */
	Signal(SysBase->ThisTask, NewEventMask);
	return (1);
}

/*
 *		SetMainWindowWidth(int width)
 *
 *		Sets the width of the main window to the specified number of
 *		characters. Note that this call won't take effect immediately;
 *		it just tells Intuition to resize our window, which will generate
 *		a NEWSIZE event that we can trap.
 *
 *		Note: has no effect if main window isn't open.
 */
void SetMainWindowWidth(int colwidth)
{
	if (colwidth == 0)
		colwidth = BufferWidth;

	if (MainWindow) {
		int newleft	  = MainWindow->LeftEdge;
		int newtop	  = MainWindow->TopEdge;
		int newheight = MainWindow->Height;
		int newwidth  = MainWindow->Width - BoxWidth +
						colwidth*BoxCharWidth + 2 * BOX_LEFT_MARGIN + 5;

		if (newwidth < MainWindow->MinWidth)
			newwidth = MainWindow->MinWidth;
		
		if ((newleft + newwidth) > ScreenWidth)
			newleft  = ScreenWidth - newwidth;

		if (newleft < 0) {
			newleft  = 0;
			newwidth = ScreenWidth;
		}
		/*
		 *		Now check if we're so close to the edge of the screen that
		 *		a single additional character space would push us over the
		 *		edge .. if so, then expand the width to the full screen
		 *		width, since it looks neater when the window completely
		 *		fills the screen, instead of "almost" filling it.
		 */
		if (newleft == 0 && (newwidth + BoxCharWidth) > ScreenWidth)
			newwidth = ScreenWidth;

		BeginResize();
		ChangeWindowBox(MainWindow, newleft, newtop, newwidth, newheight);
	}
}

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

	struct IntuiMessage *imsg;
	Settings *newsettings = NULL;
	int loadlastsaved = 0;
	int donemain = 0;
	int origtopseq;
	int updateslider;
	int changedhoriz;
	int changedvert;
	int amount;
	ULONG newval;
	int refreshtype 	= CurSettings.SimpleRefresh;
	int newspacing  	= BoxInterGap;
	int newstatus		= StatusLine;
	int newgadgets		= GadgetsLine;
	int newmode         = MonitorType;
	int newalign		= RightAligned;
	int dohide          = 0;

	if (!MainWindow)
		return;

	while ((imsg = GT_GetIMsg(MainWindowPort)) != NULL) {
		ULONG code  = imsg->Code;
		int shift   = imsg->Qualifier & IE_SHIFT;
		int altctrl = imsg->Qualifier & (IE_ALT | IE_CTRL);
		struct Gadget *gad;

		switch (imsg->Class) {
			
			case IDCMP_CLOSEWINDOW:
				if (CurSettings.Setup.HideMethod == HIDE_NONE) {
					donemain = 1;
					QuitFlag = 1;
				} else {
					dohide = 1;
				}
				break;

			case IDCMP_REFRESHWINDOW:
				/*
				 *		Since the events currently displayed in our buffer
				 *		window may have scrolled off the top of the buffer
				 *		in the meantime, if we just blindly refresh, we
				 *		might end up with the refreshed portions not matching
				 *		the rest of the display. So, first try and redraw the
				 *		buffer to ensure it contains current data. Normally,
				 *		this will end up being a NOP.
				 *
				 *		See HandleNewEvents() for details as to why we need
				 *		to lock our layers first.
				 */
				DB("Begin Refresh Semapore\n");
				origtopseq = LastDrawnTopSeq;
				LOCK_LAYERS;
				ObtainSemaphore(&BufSem);
				if (TopSeq != origtopseq)
					ShowBuffer(TopSeq, DISPLAY_ALL);
				GT_BeginRefresh(MainWindow);
				RedrawMainWindow();
				GT_EndRefresh(MainWindow, TRUE);
				DB("End refresh semaphore\n");
				ReleaseSemaphore(&BufSem);
				UNLOCK_LAYERS;
				if (TopSeq != origtopseq)
					UpdateMainVScroll();
				break;

			case IDCMP_MENUHELP:
				/*
				 *		Display simple help for whatever menu was
				 *		active when the HELP key was pressed. To decide
				 *		which menu item to display help on, we simply
				 *		build a description string from the menu name
				 *		itself (replacing spaces with underscores and
				 *		skipping over any dots).
				 *
				 *		We also strip out sub-menu items and just show
				 *		the help for the parent item instead.
				 */
				if (code != MENUNULL) {
					static int menunames[] = {
						MSG_LINK_MENU_PROJECT,
						MSG_LINK_MENU_WINDOW,
						MSG_LINK_MENU_SETTINGS,
						MSG_LINK_MENU_BUFFER
					};
					char msgname[50];
					char *msg;
					int menunum = MENUNUM(code);
					
					if (menunum == NOMENU)
						msg = MSG(MSG_LINK_MAINWIN);
					else if (ITEMNUM(code) == NOITEM) {
						msg = MSG(menunames[menunum]);
					} else {
						struct MenuItem *item;

						item = ItemAddress(MainWinMenu,code | SHIFTSUB(NOSUB));
						if (!item || !(item->Flags & ITEMTEXT)) {
							msg = MSG(menunames[menunum]);
						} else {
							char *p;

							msg = ((struct IntuiText *)item->ItemFill)->IText;
							strcpy(msgname, "LINK Menu_");
							p = msgname + strlen(msgname);

							while (*msg) {
								if (*msg == ' ' || *msg == '-')
									*p++ = '_';
								else if (*msg != '.' && *msg != '?')
									*p++ = *msg;
								msg++;
							}
							*p = '\0';
							msg = msgname;
						}
					}
					ShowAGuide(msg);
				}
				/*
				 *		It's possible the user may have played with some
				 *		menu toggles while the menu was displayed ... to
				 *		avoid us missing any items that were changed, we
				 *		simply reset them all back to the original state.
				 */		
				SetMenuOptions();
				break;

			case IDCMP_MENUPICK:
				while (code != MENUNULL) {
					struct MenuItem *item;
					int checked;
					
					item    = ItemAddress(MainWinMenu, code);
					checked = (item->Flags & CHECKED) == CHECKED;

					switch ((ULONG)GTMENUITEM_USERDATA(item)) {

						case MID_OPENLOG:
							/*
							 *		Open a new logfile
							 */
							if (SelectFile(ChosenLogName, ChosenLogName,
										   MainWindow, FILESEL_NEWLOGNAME))
							{
								if (!OpenLog(LOGMODE_PROMPT, ChosenLogName))
									ShowError(MSG(MSG_ERROR_STARTLOG),
											  DefaultLogName);
							}
							break;

						case MID_CLOSELOG:
							CloseLog();
							break;

						case MID_PAUSE:
							if (checked)
								newmode = MONITOR_PAUSED;
							else
								newmode = MONITOR_NORMAL;
							break;

						case MID_DISABLE:
							if (checked)
								newmode = MONITOR_DISABLED;
							else
								newmode = MONITOR_NORMAL;
							break;

						case MID_STEP:
							SingleStep();
							newmode = MonitorType;
							break;

						case MID_CHANGEPRI:
							/*
							 *		New task priority selected -- let's
							 *		read the priority directly from the
							 *		menu item itself.
							 */
							if (checked) {
								struct IntuiText *itext;
								int newpri;
								
								itext  = (void *)item->ItemFill;
								newpri = atoi(itext->IText);
								SetTaskPri(SysBase->ThisTask, newpri);
							}
							break;

						case MID_HELP:
							ShowAGuide(MSG(MSG_LINK_MAIN));
							break;

						case MID_ABOUT:
							ShowError(MSG(MSG_ABOUT_SNOOPDOS), Version+6,
									  PORT_NAME);
							break;

						case MID_HIDE:
							dohide = 1;
							break;

						case MID_QUIT:
							donemain = 1;
							QuitFlag = 1;
							break;

						/*
						 *		Windows menu
						 */
						case MID_SETUP:		
							if (shift && SetWindow)
								CloseSettingsWindow();
							else
								OpenSettingsWindow();
							break;

						case MID_FUNCTION:
							if (shift && FuncWindow)
								CloseFunctionWindow();
							else
								OpenFunctionWindow();
							break;

						case MID_FORMAT:
							if (shift && FormWindow)
								CloseFormatWindow();
							else
								OpenFormatWindow();
							break;

						case MID_SETWIDTH:
						{
							struct IntuiText *itext = (void *)item->ItemFill;
							int newwidth			= atoi(itext->IText);
								
							SetMainWindowWidth(newwidth);
							break;
						}
						case MID_ROWQUAL:
						{
							/*
							 *		Changing to a new row qualifier
							 */
							int newqual = SUBNUM(code);

							if (newqual != NOSUB)
								RowQual = newqual;
							break;
						}
						case MID_STATUS:	newstatus  = checked;	break;
						case MID_GADGETS:	newgadgets = checked;	break;
						case MID_AUTO_OPEN:	AutoOpen   = checked;	break;
						case MID_DISABLE_HIDDEN: DisableOnHide = checked;break;

						case MID_SPACE_NONE:	if (checked) newspacing  = 0;
												break;
						case MID_SPACE_1P:		if (checked) newspacing  = 1;
												break;
						case MID_SPACE_2P:		if (checked) newspacing  = 2;
												break;
						case MID_REF_SIMPLE:	if (checked) refreshtype = 1;
												break;
						case MID_REF_SMART:		if (checked) refreshtype = 0;
												break;
						case MID_ALIGN_LEFT:	if (checked) newalign    = 0;
												break;
						case MID_ALIGN_RIGHT:	if (checked) newalign    = 1;
												break;

						/*
						 *		Settings menu
						 */
						case MID_LOAD:
							if (SelectFile(ConfigFileName, ConfigFileName,
										   MainWindow, FILESEL_LOADCONFIG))
							{
								/*
								 *		This new settings file will becomoe
								 *		the last saved file, so just load
								 *		it in at the end instead
								 */
								newsettings	  = 0;
								loadlastsaved = 1;
							}
							break;
							
						case MID_SAVE:
							if (!SaveConfig(DefaultConfigName, SAVE_NOICON))
								ShowError(MSG(MSG_ERROR_SAVING_SETTINGS),
										  DefaultConfigName);
							break;
						
						case MID_SAVEAS:
							if (SelectFile(ConfigFileName, ConfigFileName,
										   MainWindow, FILESEL_SAVECONFIG))
							{
								if (!SaveConfig(ConfigFileName, SAVE_ICON)) {
									ShowError(MSG(MSG_ERROR_SAVING_SETTINGS),
											  ConfigFileName);
								}
							}
							break;
							
						case MID_RESET:
							newsettings = &DefaultSettings;
							break;

						case MID_RESTORE:
							newsettings = &RestoreSettings;
							break;

						case MID_LASTSAVED:
							/*
							 *		If we successfully read in the defaults
							 *		file _or_ saved out a settings file, then
							 *		go ahead and read back in the last saved
							 *		file; otherwise, it's the very first
							 *		time we've ever been run by this user,
							 *		so just restore the factory settings
							 *		instead of producing an error message.
							 */
							if (GotLastSaved) {
								newsettings   = 0;
								loadlastsaved = 1;
							} else {
								newsettings   = &DefaultSettings;
							}
							break;

						case MID_ICONS:
							CreateIcons	= checked;
							break;

						/*
						 *		Buffer menu
						 */
						case MID_COPYWIN:
							/*
							 *		Copy current window to clipboard
							 */
							DisableAllWindows();
							if (!SaveBuffer(SAVEBUF_WINDOW, SAVEBUF_CLIPBOARD,
											SAVEBUF_OVERWRITE))
								ShowError(MSG(MSG_ERROR_COPY_WIN_TO_CLIP));
							EnableAllWindows();
							break;

						case MID_COPYBUF:
							/*
							 *		Copy current buffer to clipboard
							 */
							DisableAllWindows();
							if (!SaveBuffer(SAVEBUF_ALL, SAVEBUF_CLIPBOARD,
											SAVEBUF_OVERWRITE))
								ShowError(MSG(MSG_ERROR_COPY_ALL_TO_CLIP));
							EnableAllWindows();
							break;

						case MID_SAVEWIN:
							if (SelectFile(BufferFileName, BufferFileName,
										   MainWindow, FILESEL_SAVEWINDOW))
							{
								DisableAllWindows();
								if (!SaveBuffer(SAVEBUF_WINDOW, BufferFileName,
												SAVEBUF_PROMPT))
								{
									ShowError(MSG(MSG_ERROR_SAVING_WINDOW),
											  BufferFileName);
								}
								EnableAllWindows();
							}
							break;
							
						case MID_SAVEBUF:
							if (SelectFile(BufferFileName, BufferFileName,
										   MainWindow, FILESEL_SAVEBUFFER))
							{
								DisableAllWindows();
								if (!SaveBuffer(SAVEBUF_ALL, BufferFileName,
												SAVEBUF_PROMPT))
								{
									ShowError(MSG(MSG_ERROR_SAVING_BUFFER),
											  BufferFileName);
								}
								EnableAllWindows();
							}
							break;

						case MID_CLEARBUF:
							ClearWindowBuffer();
							break;

					}
					code = item->NextSelect;
				}
				break;

			case IDCMP_IDCMPUPDATE:
				/*
				 *		Handle BOOPSI slider movement. BOOPSI arrow 
				 *		gadgets are no longer handled here but are
				 *		treated as ordinary button gadgets since
				 *		otherwise, we can't get the desired behaviour
				 *		(with a delay after the first click before it
				 *		starts repeating).
				 */
				changedhoriz = 0;
				changedvert  = 0;
				updateslider = 0;

				GetAttr(PGA_Top, Gadget[GID_HSCROLLER], &newval);
				if (newval != LeftCol)
					ScrollHorizontal(newval - LeftCol);

				GetAttr(PGA_Top, Gadget[GID_VSCROLLER], &newval);
				if (newval != (TopSeq - FirstSeq)) {
					ShowBuffer(FirstSeq + newval, DISPLAY_QUICK);
					CheckForDirtyMainWindow();
				}
				break;

			case IDCMP_INTUITICKS:
				if (ScrollDirection) {
					/*
					 *		Handle possible arrow gadget scrolling
					 */
					if (ScrollCount >= 3) {
						if (Gadget[ScrollDirection]->Flags & GFLG_SELECTED)
							DoArrowScrolling(ScrollDirection, 1);
					} else {
						ScrollCount++;
					}
				}
				if (DraggingColumn) {
					/*
					 *		Handle horizontal scrolling to left or right;
					 *		we just treat it as a normal mouse move -- in
					 *		the general case, the displacement will be
					 *		zero, so no updating will take place.
					 */
					MoveColumn(imsg->MouseX,
							   shift ? MOVECOL_FIXED : MOVECOL_FREE);
				}
				if (DraggingRow) {
					/*
					 *		Dragging a highlight across rows, so we want to
					 *		automatically scroll if we are off the top or
					 *		bottom of the window. We also check to make sure
					 *		we're not off the top or bottom of the buffer
					 *		(no harm would come if we were, but it would
					 *		lead to unnecessary flashing of the display
					 *		highlight)
					 */
					int scrolldir = 0;

					if (imsg->MouseY < BoxInTop && TopSeq > FirstSeq)
					{
						scrolldir = GID_UPARROW;
					} else if (imsg->MouseY > (BoxInTop + BoxInHeight) &&
							   BottomSeq    < EndSeq)
					{
						scrolldir = GID_DOWNARROW;
					}
					if (scrolldir) {
						FinishDragRow();
						DoArrowScrolling(scrolldir, 1);
						StartDragRow(imsg->MouseY);
					}
				}
				break;

			case IDCMP_MOUSEBUTTONS:
			{
				static int lastrmb;

				int lmb = imsg->Qualifier & IEQUALIFIER_LEFTBUTTON;
				int rmb = imsg->Qualifier & IEQUALIFIER_RBUTTON;

				if (lmb && !rmb && !lastrmb) {
					static WORD lastx, lasty;
					static ULONG lastsecs, lastmicros;
					int curx = imsg->MouseX;
					int cury = imsg->MouseY;

					if (curx >= (lastx - 1) && curx <= (lastx + 1) &&
						cury >= (lasty - 1) && cury <= (lasty + 1))
					{
						if (DoubleClick(lastsecs, lastmicros,
										imsg->Seconds, imsg->Micros)) {
							/*
							 *		Got a double-click. Now check if it's
							 *		in the window header area. If so,
							 *		open or close the format window.
							 */
							if (cury < BoxTop) {
								if (shift && FormWindow)
									CloseFormatWindow();
								else
									OpenFormatWindow();
								break;
							}
						}
					}
					lastx		= curx;
					lasty		= cury;
					lastsecs	= imsg->Seconds;
					lastmicros	= imsg->Micros;
				}
				if (DraggingColumn) {
					if (lmb == 0) {
						/*
						 *		We've finished dragging our column
						 */
						FinishDragCol();
					} else if (rmb) {
						/*
						 *		User clicked right button to cancel the
						 *		drag operation, so restore column width
						 *		to its original position and cancel drag.
						 */
cancel_dragging_column:
						/* Stop highlight flash when restoring orig header */


						if (DraggingColumn) {
							EventFormat *nextev = DragEvent + 1;

							/*
							 *		We invert the columns now and then
							 *		set SelectEvent to NULL to let
							 *		FinishDragCol() know we have already
							 *		unhighlighted them (this is because
							 *		we're hacking the window format
							 *		directly).
							 */
							InvertColumn(SelectStartCol - LeftCol - 1,
										 INVERT_FULLBOX);
							InvertColumn(SelectStartCol - LeftCol +
										 SelectEvent->width, INVERT_FULLBOX);
							SelectEvent = NULL;
							DragEvent->width = DragOrigColWidth;
							if (nextev->type != EF_END)
								nextev->width = NextOrigColWidth;
							BufferWidth = OrigBufWidth;
							if (LeftCol != OrigLeftCol) {
								LeftCol = OrigLeftCol;
								InitMainMargins();
								UpdateMainHScroll();
							}
							FinishDragCol();
						}
						lastrmb = 1;
						break;
					}
				} else if (lmb && !lastrmb) {
					/*
					 *		Could be starting to drag a new column or row!
					 *		Check bounds to see if we are. If lastrmb was
					 *		true, then user has just released right mouse
					 *		button but hasn't yet released left mouse button,
					 *		so we ignore this attempt..
					 */
					int x = imsg->MouseX;
					int y = imsg->MouseY;

					if (y < BoxTop && x >= BoxInLeft &&
									  x < (BoxInLeft + BoxInWidth)) {
						StartDragCol(x);
					} else if (y > BoxInTop && y < (BoxInTop + BoxInHeight)) {
						/*
						 *		Now check if correct qualifier is held down
						 *		to activate the row selection. We need to use
						 *		a slighty tricky boolean equation to handle
						 *		ROWQUAL_NONE, wher 
						 */
						int qual = imsg->Qualifier & IE_ALL;

						if (RowQual == ROWQUAL_ANY							 ||
						    (RowQual == ROWQUAL_NONE && !qual)				 ||
						    (qual && (qual & RowQualTable[RowQual]) == qual))
						{
							StartDragRow(y);
						}
					}
				}
				if (DraggingRow && !lmb)
					FinishDragRow();

				lastrmb = rmb;
				break;
			}

			case IDCMP_MOUSEMOVE:
				if (DraggingColumn) {
					MoveColumn(imsg->MouseX,
							   shift ? MOVECOL_FIXED : MOVECOL_FREE);
				}
				if (DraggingRow)
					MoveToRow(imsg->MouseY);
				break;

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

handle_maingadgets:
				switch (gad->GadgetID) {
					case GID_UPARROW:
					case GID_DOWNARROW:
					case GID_LEFTARROW:
					case GID_RIGHTARROW:
						if (imsg->Class == IDCMP_GADGETDOWN) {
							/*
							 *		User clicked on a scroll button so
							 *		kick-off the scrolling.
							 */
							ScrollDirection = gad->GadgetID;
							DoArrowScrolling(ScrollDirection, 1);
							MainWindow->IDCMPFlags |= IDCMP_INTUITICKS;
							ScrollCount = 0;
						} else {
							/*
							 *		User let go of a scroll button so turn
							 *		off scrolling.
							 */
							ScrollDirection = 0;
							MainWindow->IDCMPFlags &= ~IDCMP_INTUITICKS;
						}
						break;

					case GID_SETUP:
						if (shift && SetWindow)
							CloseSettingsWindow();
						else
							OpenSettingsWindow();
						break;

					case GID_FUNCTION:
						if (shift && FuncWindow)
							CloseFunctionWindow();
						else
							OpenFunctionWindow();
						break;

					case GID_SAVESET:
						/*
						 *		Save settings to configuration file
						 */
						ShowStatus(MSG(MSG_STATUS_SAVESET));
						DisableAllWindows();
						if (!SaveConfig(DefaultConfigName, SAVE_NOICON))
							ShowError(MSG(MSG_ERROR_SAVING_SETTINGS),
									  DefaultConfigName);
						EnableAllWindows();
						UpdateStatus();
						break;

					case GID_PAUSE:
						if (gad->Flags & GFLG_SELECTED)
							newmode = MONITOR_PAUSED;
						else
							newmode = MONITOR_NORMAL;
						break;

					case GID_DISABLE:
						if (gad->Flags & GFLG_SELECTED)
							newmode = MONITOR_DISABLED;
						else
							newmode = MONITOR_NORMAL;
						break;

					case GID_APPENDLOG:
					case GID_STARTLOG:
					case GID_SERIALLOG:
						if (!OpenLog(DefaultLogMode, DefaultLogName))
							ShowError(MSG(MSG_ERROR_STARTLOG), DefaultLogName);
						break;

					case GID_OPENLOG:
#if 0
						/*
						 *		Quick and dirty benchmark code to scroll
						 *		the buffer 10 times to check speed
						 */
						{ int i, l;
						
						  for (i = 0; i < 10; i++)
						    for (l = FirstSeq; l < EndSeq; l += BoxRows)
							  ShowBuffer(l, DISPLAY_QUICK);
						  break;
						}
#endif
						/*
						 *		Back to normal: open a new log file
						 */
						if (SelectFile(ChosenLogName, ChosenLogName,
									   MainWindow, FILESEL_NEWLOGNAME))
						{
							OpenLog(LOGMODE_PROMPT, ChosenLogName);
						}
						break;
						
					case GID_CLOSELOG:
						CloseLog();
						break;

					case GID_HIDE:
						dohide = 1;
						break;

					case GID_QUIT:
						donemain = 1;
						QuitFlag = 1;
						break;
				}
				break;

			case IDCMP_SIZEVERIFY:
				BeginResize();
				break;

			case IDCMP_CHANGEWINDOW:
			case IDCMP_NEWSIZE:
				if (!EndResize())
					donemain = 1;
				break;
			
			case IDCMP_INACTIVEWINDOW:
				/*
				 *		If window becomes inactive, cancel any column
				 *		drag currently in operation, since it could
				 *		confuse things otherwise when the window is
				 *		later re-activated.
				 */
				if (activegad) {
					ShowGadget(MainWindow, Gadget[activegad], GADGET_UP);
					activegad = 0;
				}
				if (DraggingRow)
					FinishDragRow();

				if (DraggingColumn)
					goto cancel_dragging_column;
				break;

			case IDCMP_RAWKEY:
			{
				/*
				 *		Handle all keyboard shortcuts
				 */
				int upstroke = imsg->Code & 0x80;
				int keypress = ConvertIMsgToChar(imsg);
				UBYTE gadid  = MainKeyboard[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(MainWindow, 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_maingadgets;
					}
					/*
					 *		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;
				}

				/*
				 *		Handle simple keyboard input (i.e. vanilla keys)
				 *
				 *		In addition to the gadget shortcuts in MainKeyboard[]
				 *		we also use TAB to unconditionally pause, and Space/
				 *		Return to singlestep if we're already paused.
				 *
				 *		(If we made Space always single step, then if someone
				 *		hit it by accident when the SnoopDos window was open,
				 *		it would go into pause mode which would be annoying.)
				 */
				if (gadid) {
					/*
					 *		Handle a gadget shortcut
					 */
					gad = Gadget[gadid];
					if (gadid == GID_PAUSE || gadid == GID_DISABLE) {
						/*
						 *		PAUSE and DISABLE are toggle gadgets
						 *		which will highlight in the gadget code.
						 *		So, we handle them immediately, to give
						 *		instant feedback.
						 */
						gad->Flags ^= GFLG_SELECTED;
						goto handle_maingadgets;
					} else {
						/*
						 *		Now a slight problem ... since our logfile
						 *		gadget can cycle between multiple values,
						 *		we had better make sure that the same
						 *		shortcut will activate whichever one is
						 *		currently displayed rather than the
						 *		default hotkey (since all three opens
						 *		may share the same hotkey). If the logfile
						 *		is already open, we do nothing.
						 */
						if (gadid == GID_OPENLOG  || gadid == GID_APPENDLOG ||
							gadid == GID_STARTLOG || gadid == GID_SERIALLOG)
						{
							if (LogActive)
								break;

							gadid = LogButtonID;
							gad   = Gadget[gadid];
						}

						/*
						 *		All these gadget are button gadgets so we
						 *		just highlight them now, and set the
						 *		activegad flag -- on the upstroke, they'll
						 *		be released.
						 */
						ShowGadget(MainWindow, gad, GADGET_DOWN);
						activegad = gadid;
						activekey = imsg->Code;
					}
					break;
				}

				/*
				 *		Didn't match one of the gadget hotkeys,
				 *		now check for some other special keys
				 */
				switch (keypress) {
					case 0x0D:		/* Return */
						DoArrowScrolling(GID_DOWNARROW, 1);
						break;

					case ' ':		/* Space */
						if (DraggingColumn) {
							/*
							 *		Redraw window with current format
							 *		to date so user can see how it looks
							 */
							InvertColumn(SelectStartCol - LeftCol - 1,
										 INVERT_FULLBOX);
							InvertColumn(SelectStartCol - LeftCol +
										 SelectEvent->width, INVERT_FULLBOX);
							BuildFormatString(BufferEFormat,
											  BufFormat, MAX_FORM_LEN);
							InstallNewFormat(NEW_STRING);
							InvertColumn(SelectStartCol - LeftCol - 1,
										 INVERT_FULLBOX);
							InvertColumn(SelectStartCol - LeftCol +
										 SelectEvent->width, INVERT_FULLBOX);
						} else if (MonitorType == MONITOR_PAUSED) {
							/*
							 *		Singlestep, but only if we were
							 *		already paused.
							 */
							SingleStep();
							newmode = MonitorType;
						}
						break;

					case 0x09:		/* Tab */
						SingleStep();
						newmode = MonitorType;
						break;

					case 0x03:		/* CTRL-C -- quit		*/
						gadid = GID_QUIT;
						ShowGadget(MainWindow, Gadget[gadid], GADGET_DOWN);
						activegad = gadid;
						activekey = imsg->Code;
						break;
						
					case 0x04:		/* CTRL-D -- disable	*/
						newmode = MONITOR_DISABLED;
						break;
					
					case 0x05:		/* CTRL-E -- enable		*/
						newmode = MONITOR_NORMAL;
						break;

					case 0x06:		/* CTRL-F -- to front	*/
						OpenMainWindow();
						break;

					case '\033':	/* ESC -- cancel column drag */
						if (DraggingColumn)
							goto cancel_dragging_column;
						break;
				}

				/*
				 *		Finally, finally, we do a quick check to see
				 *		if the keystroke matches the format editor
				 *		menu shortcut; if it does, we open the format
				 *		editor. (If this key has been used by one of
				 *		the other gadgets, e.g. due to localisation,
				 *		then we will never get here, so not a problem.)
				 *
				 *		As with the settings and functions windows, if
				 *		the keypress is shifted, then we close the
				 *		window if it was already open.
				 */
				if ((keypress & 0x5F) == (*MSG(MSG_WINDOWS_FORMAT) & 0x5F)) {
					if (shift && FormWindow)
						CloseFormatWindow();
					else
						OpenFormatWindow();
					break;
				}

				/*
				 *		Handle cursor keys etc. We allow scrolling by
				 *		varying numbers of steps, depending on the
				 *		qualifier key used.
				 */
				amount  = 1;
				switch (imsg->Code) {
					case CURSORUP:
						if (shift)   amount = BoxRows;
						if (altctrl) amount = TopSeq - FirstSeq;
						DoArrowScrolling(GID_UPARROW, amount);
						break;

					case CURSORDOWN:
						if (shift)   amount = BoxRows;
						if (altctrl) amount = EndSeq - TopSeq;
						DoArrowScrolling(GID_DOWNARROW, amount);
						break;

					case CURSORLEFT:
						if (shift)   amount = HSCROLL_SHIFT_JUMP;
						if (altctrl) amount = LeftCol;
						DoArrowScrolling(GID_LEFTARROW, amount);
						break;

					case CURSORRIGHT:
						if (shift)   amount = HSCROLL_SHIFT_JUMP;
						if (altctrl) amount = BufferWidth - LeftCol;
						DoArrowScrolling(GID_RIGHTARROW, amount);
						break;

					case TABKEY:
						if (shift)
							newmode = MONITOR_NORMAL;
						break;

					case HELPKEY:
						ShowAGuide(MSG(MSG_LINK_MAINWIN));
						break;
				}
				break;
			}
		}
		GT_ReplyIMsg(imsg);
	}
	if (newmode != MonitorType)
		SetMonitorMode(newmode);

	if (dohide)
		HideSnoopDos();
	else if (donemain)
		CloseMainWindow();
	else if (refreshtype != CurSettings.SimpleRefresh) {
		BoxInterGap = newspacing;
		StatusLine  = newstatus;
		GadgetsLine = newgadgets;
		CurSettings.SimpleRefresh = refreshtype;
		ReOpenMainWindow();
	} else if (newsettings) {
		InstallSettings(newsettings, SET_ALL);
	} else if (loadlastsaved) {
		/*
		 *		We read the last-saved configuration from a file rather
		 *		than just from a memory copy so that the user can edit
		 *		the disk version by hand first if they so choose
		 */
		if (!LoadConfig(ConfigFileName, MODE_INTERNAL, NULL))
			ShowError(MSG(MSG_ERROR_LOADING_SETTINGS), ConfigFileName);
	} else {
		if (newspacing != BoxInterGap)
			SetTextSpacing(newspacing);
		else if (newalign != RightAligned) {
			RightAligned = newalign;
			ShowBuffer(TopSeq, DISPLAY_ALL);
		}
		if (newgadgets != GadgetsLine || newstatus != StatusLine) {
			StatusLine  = newstatus;
			GadgetsLine = newgadgets;

			if (!RecalcMainWindow(MainWindow->Width, MainWindow->Height,
								  REDRAW_GADGETS))
				ShowError(MSG(MSG_ERROR_RESIZE));
		}
	}
}

/*
 *		DrawSelectedLine(row, highlight)
 *
 *		Calculates what event is currently positioned at the specified row
 *		in the main window and redraws that row either highlighted or
 *		unhighlighted. If the event is now complete, its state is updated
 *		accordingly. If no event can be found at that line, then the nearest
 *		row is highlighted instead.
 *
 *		If highlight is true, the row is highlighted, else it is unhighlighted.
 */
void DrawSelectedLine(int row, int highlight)
{
	struct RastPort *rport = MainWindow->RPort;
	Event *ev;
	Event *firstevent;
	int   firstseq;
	int   fillpen = 0;
	int   textpen = 1;
	int   i;

	if (IsListEmpty(&EventList) || BufferEFormat[0].type == EF_END)
		return;

	/*
	 *		First, find the event
	 */
	LOCK_LAYERS;
	ObtainSemaphore(&BufSem);
	firstevent = HeadNode(&EventList);
	firstseq   = firstevent->seqnum;
	ev         = TopEvent;
	if (TopSeq < firstseq)
		ev = firstevent;
	
	for (i = 0; i < row && ev != TailNode(&EventList); i++, ev = NextNode(ev))
		;
	if (ev->status == ES_READY)
		ev->status = ES_ACCEPTED;

	FormatEvent(BufferEFormat, ev, BufferLine, LeftCol, LeftCol + BoxCols - 1);
	/*
	 *		Now setup rastport according to whether or not we want it
	 *		highlighted
	 */
	if (highlight) {
		fillpen = ScreenDI->dri_Pens[FILLPEN];
		textpen = ScreenDI->dri_Pens[FILLTEXTPEN];
	}
	SetAPen(rport, textpen);
	SetBPen(rport, fillpen);
	SetDrMd(rport, JAM2);
	Move(rport, BoxInLeft, BoxBaseline + i * BoxSpacing);
	Text(rport, BufferLine, BoxCols);
	ReleaseSemaphore(&BufSem);
	UNLOCK_LAYERS;
}

/*
 *		OutputBufLine(event, row)
 *
 *		Outputs the specified event to the buffer display at the given
 *		row, using the current format, left and right col indicators.
 *		See also DrawSelectedLine() above.
 */
void OutputBufLine(Event *event, int row)
{
	struct RastPort *rport = MainWindow->RPort;

	if (event->status == ES_READY)
		event->status = ES_ACCEPTED;

	FormatEvent(BufferEFormat, event, BufferLine, LeftCol, RightCol);
	Move(rport, BoxInLeft, BoxBaseline + row*BoxSpacing);
	Text(rport, BufferLine, RightCol - LeftCol + 1);
}

/*
 *		ClearWindowBuffer()
 *
 *		Clears the buffer area on the screen, and empties the internal
 *		buffer as well (called by the Clear Buffer menu option).
 */
void ClearWindowBuffer(void)
{
	ClearBuffer();
	if (MainWindow) {
		struct RastPort *rport = MainWindow->RPort;

		/*
		 *		Erase strip to right of our rendered text
		 */
		SetAPen(rport, 0);
		SetDrMd(rport, JAM1);
		RectFill(rport, BoxInLeft, BoxInTop,
						BoxInLeft+BoxInWidth-1, BoxInTop+BoxInHeight-1);

		UpdateMainVScroll();
	}
	ClearMainRHS = 0;
}

/*
 *		ShowBuffer(seqnum, displaytype)
 *
 *		Adjusts the buffer display so that it begins at event numbered
 *		seqnum. If more than half of the needed events are currently
 *		on display, the buffer is scrolled to put them in the correct
 *		position. Any lines not currently on display are redrawn.
 *
 *		Updates TopSeq and BottomSeq accordingly.
 *
 *		Also does validity checking to ensure that if our current set of
 *		events has scrolled off the top of the buffer, we will still be
 *		displayed.
 *
 *		Displaytype is DISPLAY_ALL or DISPLAY_QUICK, to force either
 *		the entire buffer to be redrawn dumbly at the new position, or
 *		to optimise it by scrolling, only drawing changed items, etc.
 *
 *		If displaytype is DISPLAY_NONE, then all the Seq variables are
 *		recalculated to take account of events scrolling off the top
 *		etc. but no new output is displayed. This allows us to update
 *		the scrollbar without interrupting the current screen display
 *		while the user is reading it.
 *
 *		Note: it is save to call ShowBuffer with DISPLAY_NONE when the
 *		main window is closed, but for all the others, it should be open.
 */
void ShowBuffer(LONG seqnum, int displaytype)
{
	struct RastPort *rport;
	LONG  oldtopseq = TopSeq;		/* Saved old top position				*/
	LONG  firstseq;					/* First sequence number on list		*/
	Event *firstevent;				/* First event on list					*/
	Event *topev;					/* New top event displayed on screen	*/
	int   row = 0;					/* Current output row in buffer			*/
	int   scrollthresh;				/* If moving less than this, use scroll	*/

	/*
	 *		If there is a lot of activity, then sometimes we can get
	 *		output messages in between an IDCMP_VERIFY and an IDCMP_NEWSIZE
	 *		which can lead to screen artefacts appearing in the border.
	 *		One way to avoid these is to always refresh the borders when
	 *		we get an IDCMP_NEWSIZE, but a neater way is to suspend output
	 *		while we're between the two types of message -- this is less
	 *		visually jarring on the user.
	 */
	if (AwaitingResize != RESIZE_DONE)
		displaytype = DISPLAY_NONE;

	/*
	 *		Important that we lock our layers -- see HandleNewEvents()
	 *		for a more detailed explanation of why.
	 */
	DB("ShowBuffer-Semaphore\n");
	LOCK_LAYERS;
	ObtainSemaphore(&BufSem);

	if (IsListEmpty(&EventList) ||
					((Event *)HeadNode(&EventList))->status == ES_CREATING)
		goto done_show_buffer;

	firstevent = HeadNode(&EventList);
	firstseq   = firstevent->seqnum;

	if (TopSeq < firstseq) {
		TopEvent    = firstevent;
		TopSeq      = firstseq;
		BottomSeq   = firstseq;
		/*
		 *		The events currently on display in the window must have
		 *		scrolled off the top of the buffer, so we can't assume
		 *		any of them still exist. So, force a full redraw instead.
		 */
		if (displaytype == DISPLAY_QUICK)
			displaytype = DISPLAY_ALL;
	}
	if (EndSeq < firstseq) {
		EndSeq  	= firstseq;
		EndEvent	= firstevent;
	}
	if (seqnum < firstseq)	seqnum = firstseq;
	if (seqnum > EndSeq)	seqnum = EndSeq;
	
	/*
	 *		Work out if we need to scan forwards or backwards from the
	 *		current event at the top of the screen
	 */
	if (seqnum <= TopSeq) {
		/*
		 *		Scanning backwards from current top of screen. Now see if
		 *		it would be faster to scan forward from the start of the
		 *		list.
		 */
		if ((TopSeq - seqnum) < (seqnum - firstseq)) {
			for (topev = TopEvent;
				 	topev != HeadNode(&EventList) && topev->seqnum > seqnum;
			     	topev = PrevNode(topev))
				;
		} else {
			for (topev = firstevent;
					topev != TailNode(&EventList) && topev->seqnum < seqnum;
			     	topev = NextNode(topev))
				;
		}
	} else { /* seqnum > TopSeq */
		/*
		 *		Scanning forwards from the current top of the screen. Now
		 *		see if it would be faster to scan backwards from the end of
		 *		the event list.
		 */
		if ((EndSeq - seqnum) < (seqnum - TopSeq)) {
			for (topev = EndEvent; topev != TopEvent && topev->seqnum > seqnum;
								   topev = PrevNode(topev))
				;
		} else {
			for (topev = TopEvent; topev != EndEvent && topev->seqnum < seqnum;
								   topev = NextNode(topev))
				;
		}
	}
	
	/*
	 *		Now topev is the new top event to display in the window.
	 *		But wait! What if this topev is close to the bottom of our
	 *		list? We want to always show a full window of text if
	 *		possible. So, adjust it to ensure as many events as possible
	 *		can fit in the screen.
	 */
	while (topev != firstevent && seqnum > (EndSeq - BoxRows + 1)) {
		topev = PrevNode(topev);
		seqnum--;
	}

	/*
	 *		Okay! Now we finally have topev pointing to our new top of
	 *		screen node. Now update our display accordingly. We also
	 *		take this opportunity to refresh our scroll bar, since it's
	 *		relative position may well now be different.
	 */
	FirstSeq  = firstseq;
	TopEvent  = topev;
	TopSeq    = seqnum;

	if ((displaytype & DISPLAY_NONE) || !MainWindow)
		goto done_show_buffer;

	SetupBufferRastPort();
	rport = MainWindow->RPort;

	/*
	 *		We record the top line that was last drawn into the buffer. This
	 *		is useful when ShowBuffer(..., DISPLAY_NONE) is called since it
	 *		let's us detect when the info inside the window is no longer valid
	 *		due to having scrolled off the top of the buffer.
	 */
	if (seqnum != oldtopseq)
		LastDrawnTopSeq = TopSeq;

	if (displaytype & DISPLAY_ALL)
		goto redrawall;
	
	/*
	 *		Setup so we only render to bitplane 0 -- this greatly
	 *		speeds up text output and rendering speed, especially
	 *		on deep Workbenches.
	 */
	if (GfxBase->LibNode.lib_Version >= 39)
		SetWriteMask(rport, 1);		/* Speed up scrolling and rendering! */
	else
		rport->Mask = 1;

	/* 
	 *		Now setup our scroll threshold. If running on V37 (where
	 *		when we scroll, we get backfilled with colour 0 before
	 *		we can redraw), then our threshold is 1/2 the displayed
	 *		number of rows. If running on V39 or above, we increase
	 *		the threshold to 3/4 of the displayed rows.
	 */
	scrollthresh = BoxRows / 2;
	if (GfxBase->LibNode.lib_Version >= 39)
		scrollthresh = (BoxRows * 3) / 4;

	if (seqnum == oldtopseq) {
		/*
		 *		We're already at the correct position in the buffer, so
		 *		just scan looking for new or updated entries (i.e. any
		 *		which are not yet ES_ACCEPTED)
		 */
		while (row < BoxRows && seqnum <= EndSeq) {
			if (topev->status != ES_ACCEPTED)
				OutputBufLine(topev, row);
			topev = NextNode(topev);
			seqnum++;
			row++;
		}
	} else if (seqnum > oldtopseq && (seqnum - oldtopseq) <= scrollthresh) {
		/*
		 *		Scrolling forward, so move buffer up.
		 *
		 *		If running on V39 or above, we disable background fills
		 *		for our layer while we ScrollRaster() since we're going
		 *		to be redrawing the area anyway -- it speeds things up,
		 *		and is also less visually obtrusive.
		 */
		int dy = seqnum - oldtopseq;
		int scrollwidth = (RightCol - LeftCol + 1) * BoxCharWidth;

		DB("ScrollRaster start\n");
		if (GfxBase->LibNode.lib_Version >= 39) {
			struct Hook *oldhook;

			oldhook = InstallLayerHook(MainWindow->WLayer, LAYERS_NOBACKFILL);
			ScrollRasterBF(rport, 0, dy * BoxSpacing,
						   BoxInLeft, BoxInTop,
						   BoxInLeft + scrollwidth-1, BoxInTop + BoxInHeight-1);
			InstallLayerHook(MainWindow->WLayer, oldhook);
		} else {
			ScrollRaster(rport, 0, dy * BoxSpacing,
						 BoxInLeft, BoxInTop,
						 BoxInLeft + scrollwidth-1, BoxInTop + BoxInHeight-1);
		}
		DB("ScrollRaster end\n");
					 
		while (seqnum <= EndSeq && row < (BoxRows - dy)) {
			/*
			 *		Even though these lines are already present,
			 *		we still have to re-output any lines that are
			 *		not ES_ACCEPTED since they may have been
			 *		updated since the last time they were displayed.
			 */
			if (topev->status != ES_ACCEPTED)
				OutputBufLine(topev, row);
			topev = NextNode(topev);
			seqnum++;
			row++;
		}
		while (seqnum <= EndSeq && row < BoxRows) {
			OutputBufLine(topev, row);
			topev = NextNode(topev);
			seqnum++;
			row++;
		}
	} else if (oldtopseq > seqnum && (oldtopseq - seqnum) <= scrollthresh) {
		/*
		 *		Scrolling backward, so move buffer down.
		 */
		int dy = oldtopseq - seqnum;
		int scrollwidth = (RightCol - LeftCol + 1) * BoxCharWidth;

		DB("ScrollRaster start\n");
		if (GfxBase->LibNode.lib_Version >= 39) {
			struct Hook *oldhook;

			oldhook = InstallLayerHook(MainWindow->WLayer, LAYERS_NOBACKFILL);
			ScrollRasterBF(rport, 0, -dy * BoxSpacing,
						   BoxInLeft, BoxInTop,
						   BoxInLeft + scrollwidth-1, BoxInTop+BoxInHeight-1);
			InstallLayerHook(MainWindow->WLayer, oldhook);
		} else {
			ScrollRaster(rport, 0, -dy * BoxSpacing,
						 BoxInLeft, BoxInTop,
						 BoxInLeft + scrollwidth-1, BoxInTop + BoxInHeight-1);
		}
		DB("ScrollRaster end\n");
		/*
		 *		Update the new (blank) areas first
		 */
		while (seqnum <= oldtopseq) {
			OutputBufLine(topev, row);
			topev = NextNode(topev);
			seqnum++;
			row++;
		}
		/*
		 *		Now can the remaining lines in the buffer (which are
		 *		already on the screen) and re-output any which are
		 *		not ES_ACCEPTED, since they may have changed since the
		 *		last update.
		 */
		while (row < BoxRows && seqnum <= EndSeq) {
			if (topev->status != ES_ACCEPTED)
				OutputBufLine(topev, row);
			topev = NextNode(topev);
			seqnum++;
			row++;
		}
	} else {
		/*
		 *		The move is too big to handle by scrolling, so just
		 *		redraw the entire buffer instead.
		 */
redrawall:
		while (row < BoxRows && seqnum <= EndSeq) {
			OutputBufLine(topev, row);
			topev = NextNode(topev);
			seqnum++;
			row++;
		}
	}

	if ((displaytype & DISPLAY_ALL) == 0) {
		/*
		 *		Undo the bitplane optimisation we did earlier, but only
		 *		if we're doing an optimised refresh.
		 */
		if (GfxBase->LibNode.lib_Version >= 39)
			SetWriteMask(rport, 0xFF);
		else
			rport->Mask = 0xff;
	}
	BottomSeq = seqnum - 1;	/* Bottom most sequence number in window */

done_show_buffer:
	DB("ShowBuffer-Semaphore-done\n");
	ReleaseSemaphore(&BufSem);
	UNLOCK_LAYERS;
}

/*
 *		HandleNewEvents()
 *
 *		Called whenever we are signalled that a new event has arrived
 *		in the input queue.
 *
 *		We simply scan the queue from our last known position to our
 *		current position and, if necessary, update our output window
 *		to reflect any new events. We also output any new _complete_
 *		events to the logfile.
 */
void HandleNewEvents(void)
{
	Event *firstevent;
	Event *newev;
	Event *ev;
	int   curendcompleteseq = EndCompleteSeq;
	int   curendseq         = EndSeq;
	LONG  newseq;
	int   refresh = 0;

	DB("HandleNewEvent-Semaphore\n");

	if (IsListEmpty(&EventList) || AwaitingResize != RESIZE_DONE
								|| DraggingColumn || DraggingRow)
		return;
	
	/*
	 *		It is vital that for the duration of the screen update,
	 *		we retain complete access to our output rastport. Otherwise,
	 *		we can run into a deadlock where a higher priority task
	 *		(like input.device) locks the screen layer's while we
	 *		are in the middle of our refresh, and then calls one of
	 *		our patched functions (like OpenFont) -- instant hang
	 *		(because we own the buffer semaphore and the patched
	 *		function will sit waiting for us to free it).
	 *
	 *		By locking our layers, we ensure that nobody else can
	 *		ever get in once we start our refresh. The downside is
	 *		that this locks out menu rendering etc, but since we'll
	 *		be quick at refreshing, that's okay.
	 *
	 *		Note that we lock the layers _before_ getting the buffer
	 *		semaphore -- if we do it afterwards, there is a window of
	 *		opportunity where we task switch to a higher priority task
	 *		between the two calls which then proceeds to lock layers
	 *		and try and lock the buffer semaphore -- deadlock again!
	 *
	 *		News Flash! We don't lock layers any more, since it lead
	 *		to jerky performance when dragging the scrollbar etc.
	 *		Instead, our patch code checks to see if our layers are
	 *		locked by the calling task and if they are, bypasses
	 *		outputting the event completely. Since the only things
	 *		that ever call our patches when layers are locked are
	 *		low-level Intuition functions that are re-rendering
	 *		gadgets etc (and calling OpenFont()) this is actually
	 *		a win-win since we're typically not interested in those
	 *		events anyway.
	 */		
	if (MainWindow) {
		LOCK_LAYERS;		/* This is now a no-op */
	}
	ObtainSemaphore(&BufSem);

	if (EndSeq == BottomSeq) 			/* If at end of buf, show new o/p	*/
		refresh = 1;

	firstevent = HeadNode(&EventList);
	if (firstevent->seqnum > EndCompleteSeq)
		newev = firstevent;
	else if (EndCompleteEvent == TailNode(&EventList)) {
		newev = EndCompleteEvent;
	} else {
		newev = NextNode(EndCompleteEvent);
	}
	newseq = newev->seqnum;
	
	/*
	 *		First we scan all the new incoming entries to figure out
	 *		our new EndEvent/EndSeq (for ShowBuffer)
	 */
	for (ev = newev; NextNode(ev) != NULL && ev->status != ES_CREATING;
					 ev = NextNode(ev))
		;
	EndEvent = PrevNode(ev);
	EndSeq   = EndEvent->seqnum;

	if (MainWindow) {
		if (refresh) {
			/*
			 *		We were already displaying the end of the buffer and we
			 *		got new info in, so move the display to the new end of
			 *		buffer.
			 */
			ShowBuffer(EndSeq, DISPLAY_QUICK);
		} else if (BottomSeq > EndCompleteSeq) {
			/*
			 *		We weren't at the very end of the buffer, but there was an
			 *		unfinished event displayed somewhere on the screen, so
			 *		rescan the currently displayed bufferlines in case it
			 *		needs to be updated.
			 */
			ShowBuffer(TopSeq, DISPLAY_QUICK);		
		} else {
			/*
			 *		We're too far up in the buffer to show any of the
			 *		new data, but update our internal variables anyway
			 *		so we can keep the scroll bar up to date.
			 */
			ShowBuffer(TopSeq, DISPLAY_NONE);
		}
	} else {
		/*
		 *		Simply update TopSeq accordingly so it will be correct the
		 *		next time we open the window
		 */
		TopSeq	 = EndSeq;
		TopEvent = EndEvent;
	}

	/*
	 *		Now mark all new events which are finished as complete, so that
	 *		we won't rescan them
	 */
	for (ev = newev; NextNode(ev) != NULL && ev->status >= ES_READY;
	                 ev = NextNode(ev)) {
		EndCompleteEvent = ev;
		EndCompleteSeq   = ev->seqnum;
	}
	DB("HandleNewEvent-Semaphore-done\n");
	ReleaseSemaphore(&BufSem);

	if (MainWindow) {
		CheckForDirtyMainWindow();
		UNLOCK_LAYERS;
		UpdateMainVScroll();
	}

	if (LogActive && !Disabled) {
		/*
		 *		Now output the new events to our logfile. We have to be
		 *		very careful doing this since we must not keep the buffer
		 *		semaphore locked while doing file output (it could lead
		 *		to deadlocks if the file output causes a requester to
		 *		appear on the screen, for example). Thus, we must lock
		 *		and unlock the semaphore as we scan each event. Sigh.
		 */
		LONG latestready;		/* seqnum of highest event that's ES_READY */

		/*
		 *		Output the new events to the logfile. We only output
		 *		events that are complete, or events that are incomplete
		 *		but which have a complete event after them. Any complete
		 *		events that have been output earlier are not output
		 *		again, unless they were only partially output the first
		 *		time.
		 */
		ObtainSemaphore(&BufSem);
		ev = HeadNode(&EventList);
		if (ev->seqnum > newseq) {
			newev  = ev;
			newseq = ev->seqnum;
		} else if (newseq == curendcompleteseq) {
			/*
			 *		We couldn't advance past the endcompleteseq earlier on
			 *		because there was nothing beyond it, so step past now.
			 */
			newev = NextNode(newev);
			newseq++;
		}
		for (ev = TailNode(&EventList);
			ev->seqnum > newseq && ev->status < ES_READY; ev = PrevNode(ev))
			;
		latestready = ev->seqnum;
		if (ev->status < ES_READY)
			latestready--;		/* Just in case ev->seqnum == newseq */

		for (ev = newev; NextNode(ev) != NULL && ev->seqnum <= latestready;
						 ev = NextNode(ev)) {
			int firstchar = 0;

			/*
			 *		For each event that is either complete, or partially
			 *		complete but followed by a complete event, output it
			 *		to the logfile (unless already output).
			 */
			if (ev->status >= ES_READY) {
				if ((ev->flags & EFLG_LOGDONE) == 0) {
					if (ev->flags & EFLG_LOGPARTIAL)
						firstchar = '\\';
					else
						firstchar = ' ';
					ev->flags |= EFLG_LOGDONE;
				}
			} else if ((ev->flags & EFLG_LOGPARTIAL) == 0) {
				/*
				 *		Output partial event
				 */
				ev->flags |= EFLG_LOGPARTIAL;
				firstchar = '/';
			}

			if (firstchar) {
				/*
				 *		Output the line
				 */
				FormatEvent(LogEFormat, ev, BufferLine+1, 0, LogWidth - 1);
				BufferLine[0]			  = firstchar;
				BufferLine[LogWidth+1] = '\n';
				BufferLine[LogWidth+2] = 0;
				ReleaseSemaphore(&BufSem);
				WriteLog(BufferLine);
				ObtainSemaphore(&BufSem);
				/*
				 *		While we had the semaphore released, it's possible
				 *		that the event we're on may have scrolled off the
				 *		buffer. If it has, then give up right now (we'll
				 *		catch the new ones the next time around).
				 */
				if (((Event *)HeadNode(&EventList))->seqnum > latestready)
					break;
			}
		}
		ReleaseSemaphore(&BufSem);
		if (Paused)
			WriteLog(NULL);	/* Flush log if we're single-stepping */
	}
	if (AutoOpen && !MainWindow && curendseq < EndSeq)
		ShowSnoopDos();
}

/*
 *		CleanupMainWindow()
 *
 *		Frees any resources associated with this module
 */
void CleanupMainWindow(void)
{
	CloseMainWindow();
	if (MainGadList)	FreeGadgets(MainGadList),	MainGadList		= NULL;
	if (BufferFont)		CloseFont(BufferFont),		BufferFont		= NULL;
	if (LogActive)		CloseLog();
}
