/*
**	FracBlank - AmigaDOS 2.04 commodities utility screenblanker
**
**	Copyright © 1991-1995 by Olaf `Olsen' Barthel
**		All Rights Reserved
**
**	Cosmic flame fractal code derived from xlock source code
**
**	Copyright © 1988-1991 by Patrick J. Naughton.
*/

#include <intuition/intuitionbase.h>
#include <intuition/pointerclass.h>
#include <intuition/sghooks.h>

#include <workbench/workbench.h>
#include <workbench/startup.h>

#include <libraries/commodities.h>
#include <libraries/asl.h>

#include <datatypes/pictureclass.h>

#include <graphics/gfxbase.h>

#include <hardware/custom.h>

#include <exec/interrupts.h>
#include <exec/execbase.h>
#include <exec/devices.h>
#include <exec/memory.h>

#include <dos/dosextens.h>
#include <dos/dostags.h>
#include <dos/rdargs.h>

#include <clib/commodities_protos.h>
#include <clib/intuition_protos.h>
#include <clib/datatypes_protos.h>
#include <clib/graphics_protos.h>
#include <clib/utility_protos.h>
#include <clib/keymap_protos.h>
#include <clib/icon_protos.h>
#include <clib/alib_protos.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <clib/asl_protos.h>

#include <pragmas/datatypes_pragmas.h>
#include <pragmas/utility_pragmas.h>
#include <pragmas/keymap_pragmas.h>
#include <pragmas/icon_pragmas.h>
#include <pragmas/exec_pragmas.h>
#include <pragmas/asl_pragmas.h>

#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <dos.h>

#include "gtlayout.h"

	// Use a simple address trick instead of the predefined
	// address in amiga.lib

#ifndef custom
#define custom (*(struct Custom *)0xDFF000)
#endif	// custom

	// Command line arguments

enum	{	ARG_PRIORITY,
		ARG_POPKEY,
		ARG_POPUP,
		ARG_BLANKSCREEN,
		ARG_SAVESCREEN,
		ARG_FRACTAL,
		ARG_COLOUR,
		ARG_DISPLAYMODE,
		ARG_DEPTH,
		ARG_SCREENTIMEOUT,
		ARG_PATTERNTIMEOUT,
		ARG_MAXRECURSION,
		ARG_MAXDOTS,

		ARGCOUNT
	};

	// Hotkey IDs

enum	{	POP_WINDOW,
		BLANK_SCREEN,
		SAVE_SCREEN
	};

	// Gadget IDs

enum	{	GAD_HOTKEY=1000,
		GAD_BLANKSCREEN,
		GAD_SAVESCREEN,
		GAD_MODE,
		GAD_HIDE,
		GAD_SAVE,
		GAD_QUIT
	};

	// Error codes

enum	{	ERR_SystemError	= 30000,	// CBERR_SYSERR
		ERR_Duplicate,			// CBERR_DUP,
		ERR_Version,			// CBERR_VERSION,

		ERR_BadFilter = 31000,		// COERR_BADFILTER,
		ERR_BadType,			// COERR_BADTYPE

		ERR_NoIntuition = 32000,	// intuition.library didn't open
		ERR_NoGfx,			// graphics.library didn't open
		ERR_NoAsl,			// asl.library didn't open
		ERR_NoCommodities,		// commodities.library didn't open
		ERR_NoUtility,			// utility.library didn't open
		ERR_NoIcon,			// icon.library didn't open
		ERR_ScreenModeRequest,		// Couldn't allocate ScreenModeRequester
		ERR_NoColours,			// Couldn't allocate colour table
		ERR_NoPointer,			// Couldn't create transparent pointer
		ERR_NoControlProcess,		// Couldn't create control process
		ERR_NoMsgPort,			// Couldn't create msgport
		ERR_NoWindow,			// Couldn't open window
		ERR_NoKeymap,			// Couldn't open keymap.library
		ERR_NoGTLayout,			// Couldn't open gtlayout.library
	};

	// All the rawkey codes we know about

#define RAWKEY_CURSOR_UP	76
#define RAWKEY_CURSOR_DOWN	77
#define RAWKEY_CURSOR_RIGHT	78
#define RAWKEY_CURSOR_LEFT	79

#define RAWKEY_F1		80
#define RAWKEY_F2		81
#define RAWKEY_F3		82
#define RAWKEY_F4		83
#define RAWKEY_F5		84
#define RAWKEY_F6		85
#define RAWKEY_F7		86
#define RAWKEY_F8		87
#define RAWKEY_F9		88
#define RAWKEY_F10		89

#define RAWKEY_HELP		95

	// Some useful signals

#define SIG_FINISH	SIGBREAKF_CTRL_E
#define SIG_BREAK	SIGBREAKF_CTRL_C
#define SIG_CHANGE	SIGBREAKF_CTRL_D
#define SIG_CYCLE	SIGBREAKF_CTRL_E
#define SIG_START	SIGBREAKF_CTRL_F
#define SIG_REFRESH	SIG_START
#define SIG_WAKEUP	SIGBREAKF_CTRL_F
#define SIG_HANDSHAKE	SIGF_SINGLE
#define SIG_CX		(1L << CxPort -> mp_SigBit)
#define SIG_WINDOW	(1L << Window -> UserPort -> mp_SigBit)

	// Fractal types

enum	{	FRACTAL_REAL_PLANE,
		FRACTAL_COSMIC_FLAME,
		FRACTAL_RANDOM
	};

	// Colour modes

enum	{	COLOUR_CYCLE,
		COLOUR_STATIC
	};

	// A 96 bit colour entry

typedef struct ColourEntry
{
	ULONG			Red,
				Green,
				Blue;
} ColourEntry;

	// A table of colour entries, ready for LoadRGB32()

typedef struct ColourTable
{
	WORD		NumColours,
			FirstColour;

	ColourEntry	Entry[1];

	ULONG		Terminator;
} ColourTable;

	// Spread a byte across a long word

#define SPREAD(v)	((ULONG)(v) << 24 | (ULONG)(v) << 16 | (ULONG)(v) << 8 | (v))

	// Program revision tag

UBYTE			 VersTag[] = "\0$VER: FracBlank 2.3 (9.3.95)";

	// Shared library identifiers

extern struct ExecBase	*SysBase;

struct IntuitionBase	*IntuitionBase;
struct GfxBase		*GfxBase;
struct Library		*CxBase,
			*IconBase,
			*AslBase,
			*UtilityBase,
			*DataTypesBase,
			*KeymapBase,
			*GTLayoutBase;

	// The main program

struct Process		*MainProcess;
BOOL			 PopUp;

	// BlankerEntry data

struct SignalSemaphore	 BlankSemaphore;
struct Hook		 ScreenHook,
			 WindowHook;
struct Task		*BlankTask;
ULONG			 CycleMask;
LONG			 CycleBit;
struct Screen		*BlankScreen;
struct Window		*BlankWindow;
WORD			 Width,
			 Height;
struct RastPort		*RPort;
struct ViewPort		*VPort;
ULONG			 Pen;

	// A blank pointer

APTR			 Pointer;
UWORD			 Nothing;
struct BitMap		 PointerBitMap;

	// String editing hook

struct Hook		 EditHook;

	// BlankerControl data

struct Process		*BlankerControlProcess;

	// Commodities interface data

struct MsgPort		*CxPort;
CxObj			*Broker;
ULONG			 SpecialQualifier;

	// Declarations for cosmic flame blanker code

double			 Flame[2][3][2];
WORD			 FlameLevel,
			 FlameAlternateForm;
WORD			 FlameWheel,
			 FlameColour;
ULONG			 FlamePoints;
ULONG			 MaxRecursionLevel	= 40,
			 MaxFlamePoints		= 200;

	// Key sequence buffers

UBYTE			 HotkeyBuffer[256],
			 BlankScreenBuffer[256],
			 SaveScreenBuffer[256];

	// Screen and pattern change timeout

ULONG			 ScreenCount	= 0,
			 PatternCount	= 0,
			 ScreenTimeout	= 60,
			 PatternTimeout	= 60;

	// Some kind of a semaphore to tell the blank task to stop drawing

BYTE			 StopDrawing	= FALSE;

	// sin -45° = cos -45° (saves precious calculation time)

double			 deg45;

	// In case we need to rescale things

double			 VerticalScale;

	// The current colour mode

UBYTE			 ColourMode = COLOUR_CYCLE;

	// The current fractal type

UBYTE			 FractalType = FRACTAL_RANDOM;

	// Colour table data

UBYTE			 Red[255],
			 Green[255],
			 Blue[255];
WORD			 MaxColour,
			 Wheel;
ColourTable		*Colours;

	// User interface data

struct Window		*Window;
LayoutHandle		*Handle;

	// Screen mode requester data

struct ScreenModeRequester	*ScreenModeRequest;
BOOL			 ResetRequest = FALSE;
ULONG			 DisplayID;
WORD			 DisplayDepth;
WORD			 Depth;
UBYTE			 DisplayModeBuffer[DISPLAYNAMELEN + 40];

	// The process responsible for saving the screen data to the clipboard

struct Process		*Saver;

	// A new broker definition, Commodities needs this

struct NewBroker NewBroker =
{
	NB_VERSION,
	"FracBlank",
	"Fractal screen blanker v2.3",
	"Screen blanker",
	NBU_NOTIFY | NBU_UNIQUE,
	COF_SHOW_HIDE,
	0,NULL,0
};

	// Prototypes for this module

BOOL			GetModeName(ULONG Mode,STRPTR Buffer);
struct Library *	SafeOpenLibrary(STRPTR Name,LONG Version);
CxObj *			CustomHotKey(STRPTR Code,struct MsgPort *Port,LONG ID);
void			hsv2rgb(double H,double S,double V,UBYTE *r,UBYTE *g,UBYTE *b);
void			hsvramp(double h1,double s1,double b1,double h2,double s2,double b2,int count,UBYTE *red,UBYTE *green,UBYTE *blue);
VOID			StuffChar(VOID);
VOID __stdargs		SPrintf(STRPTR Buffer,STRPTR FormatString,...);
LONG			Atol(STRPTR Buffer);
STRPTR			ToolString(STRPTR *Array,STRPTR Match,STRPTR Default);
LONG			ToolValue(STRPTR *Array,STRPTR Match,LONG Default);
ULONG			BackfillDummy(VOID);
VOID			RotatePalette(VOID);
VOID			BlackScreen(VOID);
VOID __saveds		BlankerControlEntry(VOID);
ULONG			Random(ULONG MaxValue);
VOID			MonoPlot(WORD Left,WORD Top);
VOID			MultiPlot(ULONG Colour,WORD Left,WORD Top);
VOID			RealPlane(VOID);
BOOL			RecurseMono(double x,double y,WORD Level);
BOOL			RecurseColour(double x,double y,WORD Level);
VOID			CosmicFlame(VOID);
VOID __saveds		BlankerEntry(VOID);
VOID __saveds __stdargs	BlankerAction(CxMsg *CxMessage,CxObj *CxObject);
VOID			ShutdownCx(VOID);
WORD			GetSeconds(STRPTR String);
LONG			SetupCx(VOID);
VOID			HandleCxMsg(CxMsg *Message);
LONG __saveds __stdargs	ShowTime(struct Gadget *SomeGadget,WORD Level);
VOID			ShutdownWindow(VOID);
BOOL			SetupWindow(VOID);
VOID			CloseAll(LONG ReturnCode);
VOID			OpenAll(int argc,char **argv);
VOID			UpdateModeString(VOID);
VOID			SaveOptions(char **argv);
VOID __saveds		SaverEntry(VOID);
ULONG			BuildMask(STRPTR Code);
ULONG			AddQualifier(STRPTR Code,ULONG Qualifier);
ULONG			SubQualifier(STRPTR Code,ULONG Qualifier);
WORD __stdargs		ShowRequest(struct Window *Window,STRPTR Text,STRPTR Gadgets,...);
VOID			ReportError(struct Window *Window,STRPTR Template,LONG Error);
ULONG __saveds __asm	EditRoutine(register __a0 struct Hook *Hook,register __a2 struct SGWork *Work,register __a1 ULONG *Msg);

	/* EditRoutine():
	 *
	 *	A special string gadget editing routine for key
	 *	combination input.
	 */

ULONG __saveds __asm
EditRoutine(register __a0 struct Hook *Hook,register __a2 struct SGWork *Work,register __a1 ULONG *Msg)
{
	struct InputEvent	Event;
	ULONG			Qualifier;
	UWORD			Code;
	UBYTE			Key[10];
	WORD			KeyLen;
	STRPTR			KeyName;

		// If the gadget just got activated or the caps lock key
		// is active, don't change anything

	if(*Msg == SGH_CLICK || (*Msg == SGH_KEY && (Work -> IEvent -> ie_Qualifier & IEQUALIFIER_CAPSLOCK)))
		return(TRUE);
	else
	{
			// Complain if this is not what we expect to be fun

		if(*Msg != SGH_KEY)
			return(FALSE);
	}

		// Ditch the qualifier keys

	if(Work -> IEvent -> ie_Code >= 96 && Work -> IEvent -> ie_Code <= 103)
	{
		Work -> Actions	&= ~(SGA_USE | SGA_BEEP);

		return(TRUE);
	}

		// Strip all the qualifiers we don't want

	Qualifier = Work -> IEvent -> ie_Qualifier & ~(IEQUALIFIER_REPEAT | IEQUALIFIER_INTERRUPT | IEQUALIFIER_MULTIBROADCAST | IEQUALIFIER_RELATIVEMOUSE);

		// Check for raw keys

	switch(Work -> IEvent -> ie_Code)
	{
		case RAWKEY_CURSOR_UP:
		case RAWKEY_CURSOR_DOWN:
		case RAWKEY_CURSOR_RIGHT:
		case RAWKEY_CURSOR_LEFT:

				// A cursor key was pressed, check if there
				// is probably some special feature involved

			if(!(Qualifier & ~(IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT)))
				return(TRUE);

			// FALLS THROUGH TO:

		case RAWKEY_F1:
		case RAWKEY_F2:
		case RAWKEY_F3:
		case RAWKEY_F4:
		case RAWKEY_F5:
		case RAWKEY_F6:
		case RAWKEY_F7:
		case RAWKEY_F8:
		case RAWKEY_F9:
		case RAWKEY_F10:

		case RAWKEY_HELP:

				// Ok, so this is a raw key event

			Code	= Work -> IEvent -> ie_Code;
			KeyLen	= 0;

			break;

		default:

				// Make a copy of the inputevent and
				// clear the qualifier bits

			CopyMem(Work -> IEvent,&Event,sizeof(struct InputEvent));

			Event . ie_Qualifier = NULL;

			Code = 0;

				// Translate the event

			if((KeyLen = MapRawKey(&Event,Key,10,NULL)) < 0)
				KeyLen = 10;

			break;
	}

		// Is the user holding down a single Amiga key?

	if(KeyLen && (Qualifier & IEQUALIFIER_RCOMMAND) && !(Qualifier & ~IEQUALIFIER_RCOMMAND))
	{
		UBYTE Char = ToUpper(Key[0]);

			// Undo and clear are supported

		if(Char == 'Q' || Char == 'X')
			return(TRUE);
	}

		// Can we safely continue?

	if((!Code && !KeyLen) || KeyLen > 1 || (!Code && KeyLen && !Key[0]))
	{
		Work -> Actions	= (Work -> Actions & ~SGA_USE) | SGA_BEEP;

		return(TRUE);
	}

		// Take care of special characters

	if(KeyLen)
	{
		STATIC struct { UBYTE Code; STRPTR Name; } KeyTable[] =
		{
			'\r',	"Return",
			'\b',	"Backspace",
			'\033',	"Escape",
			' ',	"Spacebar",
			',',	"Comma",
			'\177',	"Delete",
			'\t',	"Tab",

			0
		};

		BOOL GotIt = FALSE;
		WORD i;

			// Carriage return was pressed

		if(Key[0] == '\r')
		{
				// This probably ends input

			if(!Qualifier)
			{
				Work -> Actions	= (Work -> Actions & ~SGA_BEEP) | SGA_USE | SGA_END;

				return(TRUE);
			}
			else
			{
					// Is this the enter key?

				if(Qualifier & IEQUALIFIER_NUMERICPAD)
				{
					KeyName = "Enter";

					Qualifier &= ~IEQUALIFIER_NUMERICPAD;

					GotIt = TRUE;
				}
			}
		}

			// If this is just the tab key, pass it through cleanly

		if(Key[0] == '\t' && !(Qualifier & ~(IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT)))
			return(TRUE);

			// Now check for special characters

		Key[0] = ToUpper(Key[0]);

		if(!GotIt)
		{
			for(i = 0 ; KeyTable[i] . Code ; i++)
			{
				if(KeyTable[i] . Code == Key[0])
				{
					KeyName = KeyTable[i] . Name;

					GotIt = TRUE;

					break;
				}
			}
		}

			// If no special character is involved,
			// use the vanilla character code

		if(!GotIt)
		{
			if((Key[0] > ' ' && Key[0] < 127) || Key[0] >= 160)
			{
				Key[1] = 0;

				KeyName = Key;
			}
			else
			{
				Work -> Actions	= (Work -> Actions & ~SGA_USE) | SGA_BEEP;

				return(TRUE);
			}
		}
	}
	else
	{
			// Special raw key code table

		STATIC struct { UWORD Code; STRPTR Name; } RawTable[] =
		{
			RAWKEY_CURSOR_UP,	"Cursor_Up",
			RAWKEY_CURSOR_DOWN,	"Cursor_Down",
			RAWKEY_CURSOR_RIGHT,	"Cursor_Right",
			RAWKEY_CURSOR_LEFT,	"Cursor_Left",

			RAWKEY_F1,		"F1",
			RAWKEY_F2,		"F2",
			RAWKEY_F3,		"F3",
			RAWKEY_F4,		"F4",
			RAWKEY_F5,		"F5",
			RAWKEY_F6,		"F6",
			RAWKEY_F7,		"F7",
			RAWKEY_F8,		"F8",
			RAWKEY_F9,		"F9",
			RAWKEY_F10,		"F10",

			RAWKEY_HELP,		"Help",

			0
		};

		WORD i;

			// One eventually must match

		for(i = 0 ; RawTable[i] . Code ; i++)
		{
			if(Code == RawTable[i] . Code)
			{
				KeyName = RawTable[i] . Name;

				break;
			}
		}
	}

		// Take care of the qualifiers. Note that we do not distinguish
		// between the left and right shift/alt keys

	if(Qualifier)
	{
		STATIC struct { ULONG Qualifier; STRPTR Name; } QualifierTable[] =
		{
			IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT,	"Shift ",
			IEQUALIFIER_LALT | IEQUALIFIER_RALT,		"Alt ",
			IEQUALIFIER_LCOMMAND,				"LAmiga ",
			IEQUALIFIER_RCOMMAND,				"RAmiga ",
			IEQUALIFIER_LEFTBUTTON,				"Left_Button ",
			IEQUALIFIER_MIDBUTTON,				"Middle_Button ",
			IEQUALIFIER_RBUTTON,				"Right_Button ",
			IEQUALIFIER_NUMERICPAD,				"Numeric_Pad ",

			0
		};

		WORD i;

			// Ok, now start building the string

		Work -> WorkBuffer[0] = 0;

		for(i = 0 ; QualifierTable[i] . Qualifier ; i++)
		{
			if(Qualifier & QualifierTable[i] . Qualifier)
				strcat(Work -> WorkBuffer,QualifierTable[i] . Name);
		}

			// Add the key itself

		strcat(Work -> WorkBuffer,KeyName);

			// Update the work data

		Work -> NumChars	= strlen(Work -> WorkBuffer);
		Work -> BufferPos	= Work -> NumChars;

			// Finished...

		Work -> Actions	= (Work -> Actions & ~(SGA_BEEP | SGA_PREVACTIVE | SGA_NEXTACTIVE | SGA_END)) | SGA_REDISPLAY | SGA_USE;
	}
	else
		Work -> Actions	&= ~(SGA_USE | SGA_BEEP);

	return(TRUE);
}

	/* ReportError(struct Window *Window,STRPTR Template,LONG Error):
	 *
	 *	Show an error message.
	 */

VOID
ReportError(struct Window *Window,STRPTR Template,LONG Error)
{
	STATIC struct { LONG Error; STRPTR Code; } Table[] =
	{
		ERR_SystemError,		"Commodities system error; out of memory",
		ERR_Duplicate,			"Commodities broker already exists",
		ERR_Version,			"Commodities broker version not supported",

		ERR_BadFilter,			"Bad filter descriptor",
		ERR_BadType,			"Bad type descriptor",

		ERR_NoIntuition,		"Could not open intuition.library v37",
		ERR_NoGfx,			"Could not open graphics.library v39",
		ERR_NoAsl,			"Could not open asl.library v38",
		ERR_NoCommodities,		"Could not open commodities.library v37",
		ERR_NoUtility,			"Could not open utility.library v37",
		ERR_NoIcon,			"Could not open icon.library v37",
		ERR_ScreenModeRequest,		"Could not allocate screen mode requester",
		ERR_NoColours,			"Could not allocate colour table; out of memory",
		ERR_NoPointer,			"Could not allocate mouse pointer; out of memory",
		ERR_NoControlProcess,		"Could not create mouse blanker process",
		ERR_NoMsgPort,			"Could not create message port",
		ERR_NoWindow,			"Could not open window",
		ERR_NoKeymap,			"Could not open keymap.library",
		ERR_NoGTLayout,			"Could not open gtlayout.library v9",

		0,			NULL
	};

	UBYTE	LocalBuffer[256];
	STRPTR	Buffer;
	BOOL	GotIt = FALSE;
	LONG	i;

		// Look for the corresponding error message.

	for(i = 0 ; Table[i] . Error ; i++)
	{
		if(Error == Table[i] . Error)
		{
			Buffer = Table[i] . Code;

			GotIt = TRUE;

			break;
		}
	}

	if(!GotIt)
	{
		if(!Fault(Error,"",LocalBuffer,256))
			return;

		Buffer = &LocalBuffer[2];
	}

	if(!Template)
		Template = Buffer;

	if(MainProcess -> pr_CLI && !Window)
	{
		Printf("FracBlank: ");
		Printf(Template,Buffer);
		Printf("\n");
	}
	else
		ShowRequest(Window,Template,"Continue",Buffer);
}

	/* ShowRequest(struct Window *Window,STRPTR Text,STRPTR Gadgets,...):
	 *
	 *	This routine shows an easy requester. If there is just one
	 *	button to be pressed, the user will be able to answer it
	 *	by pressing any key.
	 */

WORD __stdargs
ShowRequest(struct Window *Window,STRPTR Text,STRPTR Gadgets,...)
{
	if(IntuitionBase)
	{
		struct EasyStruct	Easy;
		va_list			VarArgs;
		LONG			i,GadgetCount;

		for(i = GadgetCount = 0 ; i < strlen(Gadgets) ; i++)
		{
			if(Gadgets[i] == '|')
				GadgetCount++;
		}

		Easy . es_StructSize	= sizeof(struct EasyStruct);
		Easy . es_Flags		= NULL;
		Easy . es_Title		= "Fractal screen blanker";
		Easy . es_TextFormat	= Text;
		Easy . es_GadgetFormat	= Gadgets;

		if(GadgetCount)
		{
			WORD Result;

			if(GTLayoutBase)
				LT_LockWindow(Window);

			va_start(VarArgs,Gadgets);
			Result = EasyRequestArgs(Window,&Easy,NULL,VarArgs);
			va_end(VarArgs);

			if(GTLayoutBase)
				LT_UnlockWindow(Window);

			return(Result);
		}
		else
		{
			struct Window *ReqWindow;

			if(GTLayoutBase)
				LT_LockWindow(Window);

			va_start(VarArgs,Gadgets);

			if(ReqWindow = BuildEasyRequestArgs(Window,&Easy,IDCMP_RAWKEY,VarArgs))
			{
				ULONG	IDCMP;
				LONG	Result;

				FOREVER
				{
					WaitPort(ReqWindow -> UserPort);

					IDCMP = NULL;

					Result = SysReqHandler(ReqWindow,&IDCMP,FALSE);

					if(!Result || (Result == -2 && !(IDCMP & IDCMP_RAWKEY)))
						break;
				}

				FreeSysRequest(ReqWindow);
			}

			va_end(VarArgs);

			if(GTLayoutBase)
				LT_UnlockWindow(Window);
		}
	}

	return(0);
}

	/* BuildMask(STRPTR Code):
	 *
	 *	Build a mask of qualifiers covered by the given
	 *	code.
	 */

ULONG
BuildMask(STRPTR Code)
{
	IX Expression;

	if(!ParseIX(Code,&Expression))
	{
		ULONG Mask = NULL,Bits = Expression . ix_Qualifier & Expression . ix_QualMask;

			// Are shift/caps lock keys involved?

		if(Bits & IXSYM_CAPSMASK)
		{
				// Are both shift keys equivalent?

			if(Expression . ix_QualSame & IXSYM_SHIFT)
				Mask |= IXSYM_SHIFTMASK | (Bits & IEQUALIFIER_CAPSLOCK);
			else
			{
					// Are all shift/caps lock keys equivalent?

				if(Expression . ix_QualSame & IXSYM_CAPS)
					Mask |= IXSYM_CAPSMASK;
				else
					Mask |= Bits & IXSYM_CAPSMASK;
			}
		}

			// Are alt keys involved?

		if(Bits & IXSYM_ALTMASK)
		{
				// Are both alt keys equivalent?

			if(Expression . ix_QualSame & IXSYM_ALT)
				Mask |= IXSYM_ALTMASK;
			else
				Mask |= Bits & IXSYM_ALTMASK;
		}

			// Take care of the remaining qualifiers

		Mask |= Bits & ~(IXSYM_ALTMASK | IXSYM_CAPSMASK);

		return(Mask);
	}
	else
		return(NULL);
}

	/* AddQualifier(STRPTR Code,ULONG Qualifier):
	 *
	 *	Add to the qualifier mask.
	 */

ULONG
AddQualifier(STRPTR Code,ULONG Qualifier)
{
	return(Qualifier | BuildMask(Code));
}

	/* SubQualifier(STRPTR Code,ULONG Qualifier):
	 *
	 *	Remove keys from the qualifier mask.
	 */

ULONG
SubQualifier(STRPTR Code,ULONG Qualifier)
{
	return(Qualifier & ~BuildMask(Code));
}

	/* GetModeName(ULONG Mode,STRPTR Buffer):
	 *
	 *	This routine supplies the name for a DisplayID.
	 */

BOOL
GetModeName(ULONG Mode,STRPTR Buffer)
{
	struct NameInfo NameInfo;

	if(GetDisplayInfoData(NULL,(APTR)&NameInfo,sizeof(struct NameInfo),DTAG_NAME,Mode))
		strcpy(Buffer,NameInfo . Name);
	else
	{
		struct DisplayInfo DisplayInfo;

		if(GetDisplayInfoData(NULL,(APTR)&DisplayInfo,sizeof(struct DisplayInfo),DTAG_DISP,Mode))
		{
			struct DimensionInfo DimensionInfo;

			if(GetDisplayInfoData(NULL,(APTR)&DimensionInfo,sizeof(struct DimensionInfo),DTAG_DIMS,Mode))
			{
				STRPTR MonitorName;

				switch(Mode & MONITOR_ID_MASK)
				{
					case NTSC_MONITOR_ID:

						MonitorName = "NTSC:";
						break;

					case PAL_MONITOR_ID:

						MonitorName = "PAL:";
						break;

					case VGA_MONITOR_ID:

						MonitorName = "VGA:";
						break;

					case A2024_MONITOR_ID:

						MonitorName = "A2024:";
						break;

					default:

						MonitorName = "";
						break;
				}

				if(DisplayInfo . PropertyFlags & DIPF_IS_HAM)
					SPrintf(Buffer,"%s%ld × %ld HAM",MonitorName,DimensionInfo . TxtOScan . MaxX - DimensionInfo . TxtOScan . MinX + 1,DimensionInfo . TxtOScan . MaxY - DimensionInfo . TxtOScan . MinY + 1);
				else
					SPrintf(Buffer,"%s%ld × %ld",MonitorName,DimensionInfo . TxtOScan . MaxX - DimensionInfo . TxtOScan . MinX + 1,DimensionInfo . TxtOScan . MaxY - DimensionInfo . TxtOScan . MinY + 1);
			}
			else
				return(FALSE);
		}
		else
			return(FALSE);
	}

	return(TRUE);
}

	/* SafeOpenLibrary(STRPTR Name,LONG Version):
	 *
	 *	Just like OpenLibrary, but makes sure that old
	 *	library versions get flushed from memory.
	 */

struct Library *
SafeOpenLibrary(STRPTR Name,LONG Version)
{
	struct Library *Base;

	Forbid();

	if(Base = (struct Library *)FindName(&SysBase -> LibList,FilePart(Name)))
	{
		if(Base -> lib_Version < Version)
			RemLibrary(Base);
	}

	Permit();

	return(OpenLibrary(Name,Version));
}

	/* CustomHotKey(STRPTR Code,struct MsgPort *Port,LONG ID):
	 *
	 *	A replacement for the HotKey() routine found in
	 *	amiga.lib.
	 */

CxObj *
CustomHotKey(STRPTR Code,struct MsgPort *Port,LONG ID)
{
	CxObj *Filter;

	if(Filter = CxFilter(Code))
	{
		CxObj *Sender;

		if(Sender = CxSender(Port,ID))
		{
			CxObj *Translator;

			AttachCxObj(Filter,Sender);

			if(Translator = CxTranslate(NULL))
			{
				AttachCxObj(Filter,Translator);

				if(!CxObjError(Filter))
					return(Filter);
			}
		}

		DeleteCxObjAll(Filter);
	}

	return(NULL);
}

	/* hsv2rgb(double H,double S,double V,UBYTE *r,UBYTE *g,UBYTE *b):
	 *
	 *	Convert HSV (hue, saturation, value) colour index into
	 *	plain RGB (red, green, blue) colour space.
	 */

void
hsv2rgb(double H,double S,double V,UBYTE *r,UBYTE *g,UBYTE *b)
{
	int i;
	double f,bb;
	UBYTE p,q,t;

	H -= floor(H);
	H *= 6.0;
	i = floor(H);
	f = H - (double)i;
	bb = 255.0 * V;
	p = (UBYTE)(bb * (1.0 - S));
	q = (UBYTE)(bb * (1.0 - (S * f)));
	t = (UBYTE)(bb * (1.0 - (S * (1.0 - f))));

	switch(i)
	{
		case 0:	*r = (UBYTE)bb;
			*g = t;
			*b = p;
			break;

		case 1:	*r = q;
			*g = (UBYTE)bb;
			*b = p;
			break;

		case 2:	*r = p;
			*g = (UBYTE)bb;
			*b = t;
			break;

		case 3:	*r = p;
			*g = q;
			*b = (UBYTE)bb;
			break;

		case 4:	*r = t;
			*g = p;
			*b = (UBYTE)bb;
			break;

		case 5:	*r = (UBYTE)bb;
			*g = p;
			*b = q;
			break;
	}
}

	/* hsvramp():
	 *
	 *	Creates a ramp within HSV colour space, showing all
	 *	colours of the HSV circle.
	 */

void
hsvramp(double h1,double s1,double b1,double h2,double s2,double b2,int count,UBYTE *red,UBYTE *green,UBYTE *blue)
{
	double dh,ds,db;

	dh = (h2 - h1) / count;
	ds = (s2 - s1) / count;
	db = (b2 - b1) / count;

	while(count--)
	{
		hsv2rgb(h1,s1,b1,red++,green++,blue++);

		h1 += dh;
		s1 += ds;
		b1 += db;
	}
}

VOID
StuffChar()
{
	__emit(0x16C0);	// MOVE D0,(A3)+
}

	/* SPrintf(STRPTR Buffer,STRPTR FormatString,...):
	 *
	 *	String formatting routine, similar to sprintf().
	 */

VOID __stdargs
SPrintf(STRPTR Buffer,STRPTR FormatString,...)
{
	va_list VarArgs;

	va_start(VarArgs,FormatString);
	RawDoFmt(FormatString,VarArgs,(VOID (*)())StuffChar,Buffer);
	va_end(VarArgs);
}

	/* Atol(STRPTR Buffer):
	 *
	 *	Convert a string into a long word.
	 */

LONG
Atol(STRPTR Buffer)
{
	LONG Result;

	StrToLong(Buffer,&Result);

	return(Result);
}

	/* ToolString(STRPTR *Array,STRPTR Match,STRPTR Default):
	 *
	 *	Return the matching tooltype value, if available.
	 *	Otherwise do with the default value.
	 */

STRPTR
ToolString(STRPTR *Array,STRPTR Match,STRPTR Default)
{
	if(Array)
	{
		STRPTR String;

		if(String = FindToolType(Array,Match))
			return(String);
	}

	return(Default);
}

	/* ToolValue(STRPTR *Array,STRPTR Match,LONG Default):
	 *
	 *	Return the matching tooltype value, if available.
	 *	Otherwise do with the default value.
	 */

LONG
ToolValue(STRPTR *Array,STRPTR Match,LONG Default)
{
	if(Array)
	{
		STRPTR String;

		if(String = FindToolType(Array,Match))
		{
			LONG Result;

			if(StrToLong(String,&Result) > 0)
				return(Result);
		}
	}

	return(Default);
}

	/* BackfillDummy():
	 *
	 *	A no-op backfill routine, we'll clear the screen manually.
	 */

ULONG
BackfillDummy()
{
	return(TRUE);
}

	/* RotatePalette():
	 *
	 *	Fill in the screen palette with new data.
	 */

VOID
RotatePalette()
{
	WORD i,Index = Wheel;

	for(i = 1 ; i <= MaxColour ; i++)
	{
		Colours -> Entry[i] . Red	= SPREAD(Red[Index]);
		Colours -> Entry[i] . Green	= SPREAD(Green[Index]);
		Colours -> Entry[i] . Blue	= SPREAD(Blue[Index]);

		Index = (Index + 1) % MaxColour;
	}

		// Terminate the table, just in case...

	Colours -> Entry[Colours -> NumColours] . Red = 0;
}

	/* BlackScreen():
	 *
	 *	Clear the screen, this is accomplished by first setting the
	 *	screen colours to black, clearing the screen and finally
	 *	restoring the palette.
	 */

VOID
BlackScreen()
{
	WORD Count;

	Count = Colours -> NumColours;

	Colours -> NumColours = MaxColour + 1;

	memset(&Colours -> Entry[0],0,256 * sizeof(ColourEntry));

	LoadRGB32(VPort,(ULONG *)Colours);

	SetRast(RPort,0);

	Colours -> NumColours = Count;

	RotatePalette();

	LoadRGB32(VPort,(ULONG *)Colours);
}

	/* BlankerControlEntry():
	 *
	 *	The screen blanker control task.
	 */

VOID __saveds
BlankerControlEntry()
{
	ULONG SignalSet;

	Forbid();

		// Ring back...

	Signal(MainProcess,SIG_HANDSHAKE);

	FOREVER
	{
			// Wait for something to happen

		SignalSet = Wait(SIG_BREAK | SIG_START | SIG_CHANGE | SIG_FINISH);

			// Remove both the screen and the blanker task?

		if(SignalSet & (SIG_BREAK | SIG_FINISH))
		{
			ObtainSemaphore(&BlankSemaphore);

			if(BlankScreen)
				ScreenToBack(BlankScreen);

				// Remove the blanker task

			if(BlankTask)
			{
				SetSignal(0,SIG_HANDSHAKE);

					// Tell the blanker task to shut down

				Signal(BlankTask,SIG_BREAK);

					// Move the blanker task priority up,
					// we don't want the low priority task
					// to wait until some high priority task
					// yields CPU time

				SetTaskPri(BlankTask,127);

					// Wait for handshake signal...

				Wait(SIG_HANDSHAKE);

					// He's dead, Jim

				BlankTask = NULL;
			}

				// Close the blanker screen

			if(BlankScreen)
			{
				ScreenToBack(BlankScreen);

				CloseWindow(BlankWindow);

				BlankWindow = NULL;

				CloseScreen(BlankScreen);

				BlankScreen = NULL;
			}

			ReleaseSemaphore(&BlankSemaphore);

			SignalSet &= ~(SIG_CHANGE | SIG_START);
		}

			// Leave the town?

		if(SignalSet & SIG_FINISH)
			break;

			// Start the screen blanker?

		if(SignalSet & SIG_START)
		{
				// Is the screen already open?

			if(!BlankScreen)
			{
				ULONG LocalID;

					// Use the default public screen display ID
					// if necessary

				if(DisplayID == INVALID_ID || (ModeNotAvailable(DisplayID) & ~DI_AVAIL_NOTWITHGENLOCK))
				{
					struct Screen *PubScreen = LockPubScreen(NULL);

					if(PubScreen)
					{
						LocalID = GetVPModeID(&PubScreen -> ViewPort);

						UnlockPubScreen(NULL,PubScreen);
					}
					else
						LocalID = INVALID_ID;
				}
				else
					LocalID = DisplayID;

					// Did we get a valid ID?

				if(LocalID != INVALID_ID)
				{
					WORD			LocalDepth;
					struct DimensionInfo	DimensionInfo;
					struct DisplayInfo	DisplayInfo;

						// Choose the right display depth

					LocalDepth = Depth;

					if(GetDisplayInfoData(NULL,(APTR)&DimensionInfo,sizeof(DimensionInfo),DTAG_DIMS,LocalID))
					{
						if(!LocalDepth)
						{
							if((LocalDepth = DimensionInfo . MaxDepth) > 8)
								LocalDepth = 8;
						}
						else
						{
							if(LocalDepth > DimensionInfo . MaxDepth)
								LocalDepth = DimensionInfo . MaxDepth;
						}
					}
					else
					{
						if(!LocalDepth)
							LocalDepth = 2;
					}

						// Compensate for the display aspect ratio

					if(GetDisplayInfoData(NULL,(APTR)&DisplayInfo,sizeof(DisplayInfo),DTAG_DISP,LocalID))
					{
						if(DisplayInfo . Resolution . y)
						{
							double x,y;

							x = (double)DisplayInfo . Resolution . x;
							y = (double)DisplayInfo . Resolution . y;

							VerticalScale = x / y;
						}
						else
							VerticalScale = 1.0;
					}
					else
						VerticalScale = 1.0;

						// Set the colour table to black

					MaxColour = (1 << LocalDepth) - 1;

					Colours -> NumColours = MaxColour + 1;

					memset(&Colours -> Entry[0],0,256 * sizeof(ColourEntry));

						// Now open the screen and the window

					ObtainSemaphore(&BlankSemaphore);

					if(BlankScreen = OpenScreenTags(NULL,
						SA_Behind,	TRUE,
						SA_Quiet,	TRUE,
						SA_DisplayID,	LocalID,
						SA_Overscan,	OSCAN_MAX,
						SA_Depth,	LocalDepth,
						SA_ShowTitle,	FALSE,
						SA_BackFill,	&ScreenHook,
						SA_Colors32,	Colours,
						SA_Draggable,	FALSE,
						SA_Exclusive,	TRUE,
					TAG_DONE))
					{
						VPort	= &BlankScreen -> ViewPort;
						Width	= BlankScreen -> Width;
						Height	= BlankScreen -> Height;

							// To keep the ball rolling...

						if((DisplayDepth = LocalDepth) == 1)
						{
							MaxColour = 255;

							Colours -> NumColours = 2;
						}

							// Create a nice colour table

						hsvramp(0.0,1.0,1.0,1.0,1.0,1.0,MaxColour,Red,Green,Blue);

						Wheel = Random(MaxColour);

						RotatePalette();

							// Open the window

						if(BlankWindow = OpenWindowTags(NULL,
							WA_Left,		0,
							WA_Top,			0,
							WA_Width,		Width,
							WA_Height,		Height,
							WA_Backdrop,		TRUE,
							WA_Borderless,		TRUE,
							WA_Pointer,		Pointer,
							WA_CustomScreen,	BlankScreen,
							WA_Activate,		TRUE,
							WA_RMBTrap,		TRUE,
							WA_BackFill,		&WindowHook,
						TAG_DONE))
						{
							RPort = BlankWindow -> RPort;

							SetABPenDrMd(RPort,Pen = 1,0,JAM1);

								// Clear the screen

							SetRast(RPort,0);

							LoadRGB32(VPort,(ULONG *)Colours);

								// Display the screen

							ScreenToFront(BlankScreen);

							PatternCount = 0;

								// Wait for something to happen

							Forbid();

							BlankTask = (struct Task *)CreateTask("« FracBlank BlankerEntry Task »",127,BlankerEntry,4096 + 400 * MaxRecursionLevel);

							SetSignal(0,SIG_HANDSHAKE);

							Wait(SIG_HANDSHAKE);

							Permit();
						}
						else
						{
							CloseScreen(BlankScreen);

							BlankScreen = NULL;
						}
					}
				}

				ReleaseSemaphore(&BlankSemaphore);
			}
			else
			{
					// Push the blanker screen to the front if necessary

				if(BlankScreen -> TopEdge > 0)
					MoveScreen(BlankScreen,0,-BlankScreen -> TopEdge);

				if(IntuitionBase -> FirstScreen != BlankScreen)
					ScreenToFront(BlankScreen);

				if(IntuitionBase -> ActiveWindow != BlankWindow)
					ActivateWindow(BlankWindow);
			}

			SignalSet &= ~SIG_CHANGE;
		}

			// Change the patterns?

		if(SignalSet & SIG_CHANGE)
		{
			if(BlankTask)
			{
					// Stop drawing patterns

				StopDrawing = TRUE;

				Signal(BlankTask,SIG_CHANGE);
			}
		}
	}

	BlankerControlProcess = NULL;

	Signal(MainProcess,SIG_HANDSHAKE);
}

	/* Random(ULONG MaxValue):
	 *
	 *	Simple random number generation routine.
	 */

ULONG
Random(ULONG MaxValue)
{
	if(MaxValue)
	{
		STATIC ULONG RandomSeed = 3735879991;

		RandomSeed = RandomSeed * custom . vhposr + 3780343439;

		return(RandomSeed % MaxValue);
	}
	else
		return(0);
}

	/* MonoPlot(WORD Left,WORD Top):
	 *
	 *	Set a pixel somewhere on the screen.
	 */

VOID
MonoPlot(WORD Left,WORD Top)
{
	if(Left >= 0 && Left < Width && Top >= 0 && Top < Height)
		WritePixel(RPort,Left,Top);
}

	/* MultiPlot(ULONG Colour,WORD Left,WORD Top):
	 *
	 *	Set a coloured pixel somewhere on the screen.
	 */

VOID
MultiPlot(ULONG Colour,WORD Left,WORD Top)
{
	if(Left >= 0 && Left < Width && Top >= 0 && Top < Height)
	{
		if(Pen != Colour)
			SetAPen(RPort,Pen = Colour);

		WritePixel(RPort,Left,Top);
	}
}

	/* RealPlane():
	 *
	 *	Draw real plane fractals.
	 */

VOID
RealPlane()
{
	UWORD	OffsetX = Width / 2,OffsetY = Height / 2;
	ULONG	Signals;
	double	x = 0,y = 0,yy,a,b,c,sx,sy,mag;

		// Are we running in monochrome mode?

	if(DisplayDepth == 1)
	{
			// Provide starting numbers for the fractal
			// parameters

		a = (double)(Random(700) + 5) / 100;
		b = (double)(Random(190) + 5) / 100;
		c = (double)(Random( 90) + 5) / 100;

		mag = (double)(1 << (Random(6) + 2)) * deg45;

			// Go into fractal generation loop

		FOREVER
		{
			Signals = SetSignal(0,SIG_BREAK | SIG_CHANGE | CycleMask);

				// Are we to shut down?

			if(Signals & SIG_BREAK)
			{
				Forbid();

				Signal(BlankerControlProcess,SIG_HANDSHAKE);

				FreeSignal(CycleBit);

				RemTask(NULL);
			}

				/* The original formula looks like
				 * this:
				 *                                    ½
				 *    x = y - SIGN(x) × ABS(b × x - c)
				 *    y = a - x
				 *
				 * I have split the calculation into
				 * several steps to save time and
				 * variables.
				 */

			yy = a - x;

			if(x < 0)
				x = y + sqrt(fabs(b * x - c));
			else
				x = y - sqrt(fabs(b * x - c));

			y = yy;

				/* The resulting image appears to have
				 * been rotated by 45°, so we'll
				 * rotate the pixel coordinates by -45°
				 *
				 *    x =  x × cos(alpha) + y × sin(alpha)
				 *    y = -x × sin(alpha) + y × cos(alpha)
				 *
				 * We also magnify the image (i.e. the
				 * distribution of pixels) in the following
				 * lines.
				 */

			sx = mag *                 ( x + y);
			sy = mag * VerticalScale * (-x + y);

				// If the pixel happens to reside within
				// the boundaries of the screen, draw it.

			MonoPlot((WORD)(sx) + OffsetX,(WORD)(sy) + OffsetY);

				// Change the pattern?

			if(Signals & SIG_CHANGE)
			{
				BlackScreen();

				StopDrawing = FALSE;

				x = y = 0;

				a = (double)(Random(700) + 5) / 100;
				b = (double)(Random(190) + 5) / 100;
				c = (double)(Random( 90) + 5) / 100;

				mag = (double)(1 << (Random(6) + 2)) * deg45;

				Signals &= ~CycleMask;
			}

				// Cycle the colours?

			if(Signals & CycleMask)
			{
				LoadRGB32(VPort,(ULONG *)Colours);

				Wheel = (Wheel + 1) % MaxColour;

				RotatePalette();
			}
		}
	}
	else
	{
		UWORD		 Count = 0;
		WORD		 Colour;

		a = (double)(Random(700) + 5) / 100;
		b = (double)(Random(190) + 5) / 100;
		c = (double)(Random( 90) + 5) / 100;

		mag = (double)(1 << (Random(6) + 2)) * deg45;

		Colour = Random(MaxColour - 1) + 1;

		FOREVER
		{
			Signals = SetSignal(0,SIG_BREAK | SIG_CHANGE | CycleMask);

			if(Signals & SIG_BREAK)
			{
				Forbid();

				Signal(BlankerControlProcess,SIG_HANDSHAKE);

				FreeSignal(CycleBit);

				RemTask(NULL);
			}

			yy = a - x;

			if(x < 0)
				x = y + sqrt(fabs(b * x - c));
			else
				x = y - sqrt(fabs(b * x - c));

			y = yy;

			sx = mag *                 ( x + y);
			sy = mag * VerticalScale * (-x + y);

			MultiPlot(Colour,(WORD)(sx) + OffsetX,(WORD)(sy) + OffsetY);

				/* Oh well, it's not that easy to
				 * produce decent colour values for
				 * the pixels to be rendered.
				 *
				 * The following statement will change
				 * the current drawing pen after exactly
				 * 1200 pixels have been rendered and will
				 * pick a new colour between 1 and 31.
				 */

			if(Count++ >= 1200)
			{
				WORD NewColour;

				Count = 0;

				do
					NewColour = Random(MaxColour - 1) + 1;
				while(NewColour == Colour);

				Colour = NewColour;
			}

			if(Signals & SIG_CHANGE)
			{
				BlackScreen();

				StopDrawing = FALSE;

				x = y = 0;

				a = (double)(Random(700) + 5) / 100;
				b = (double)(Random(190) + 5) / 100;
				c = (double)(Random( 90) + 5) / 100;

				mag = (double)(1 << (Random(6) + 2)) * deg45;

				Signals &= ~CycleMask;
			}

			if(Signals & CycleMask)
			{
				LoadRGB32(VPort,(ULONG *)Colours);

				Wheel = (Wheel + 1) % MaxColour;

				RotatePalette();
			}
		}
	}
}

	/* RecurseMono(double x,double y,WORD Level):
	 *
	 *	Cosmic flame calculation routine (monochrome).
	 */

BOOL
RecurseMono(double x,double y,WORD Level)
{
	ULONG Signals = SetSignal(0,SIG_BREAK | SIG_CHANGE | CycleMask);

		// Are we to shut down?

	if(Signals & SIG_BREAK)
	{
		Forbid();

		Signal(BlankerControlProcess,SIG_HANDSHAKE);

		FreeSignal(CycleBit);

		RemTask(NULL);
	}

		// Return to top level?

	if(StopDrawing)
	{
		FlameLevel	= 0;
		StopDrawing	= FALSE;

		return(FALSE);
	}

		// Change the pattern?

	if(Signals & SIG_CHANGE)
	{
		FlameLevel	= 0;
		StopDrawing	= FALSE;

		return(FALSE);
	}

		// Cycle the colours?

	if(Signals & CycleMask)
	{
		LoadRGB32(VPort,(ULONG *)Colours);

		Wheel = (Wheel + 1) % MaxColour;

		RotatePalette();
	}

	if(Level >= MaxRecursionLevel)
	{
		if((FlamePoints++) > MaxFlamePoints * 100)
			return(FALSE);
		else
			MonoPlot((WORD)((Width / 2) * (x + 1.0)),(WORD)((Height / 2) * VerticalScale * (y + 1.0)));
	}
	else
	{
		double	nx,ny;
		WORD	i;

		for(i = 0 ; i < 2 ; i++)
		{
			nx = Flame[0][0][i] * x + Flame[0][1][i] * y + Flame[0][2][i];
			ny = Flame[1][0][i] * x + Flame[1][1][i] * y + Flame[1][2][i];

			if(i < FlameAlternateForm)
			{
				nx = sin(nx);
				ny = sin(ny);
			}

			if(!StopDrawing)
			{
				if(!RecurseMono(nx,ny,Level + 1))
					return(FALSE);
			}
			else
				return(FALSE);
		}
	}

	return(TRUE);
}

	/* RecurseColour(double x,double y,WORD Level):
	 *
	 *	Cosmic flame calculation routine (colour).
	 */

BOOL
RecurseColour(double x,double y,WORD Level)
{
	ULONG Signals = SetSignal(0,SIG_BREAK | SIG_CHANGE | CycleMask);

	if(Signals & SIG_BREAK)
	{
		Forbid();

		Signal(BlankerControlProcess,SIG_HANDSHAKE);

		FreeSignal(CycleBit);

		RemTask(NULL);
	}

	if(StopDrawing)
	{
		FlameLevel	= 0;
		StopDrawing	= FALSE;

		return(FALSE);
	}

		/* Change the pattern? */

	if(Signals & SIG_CHANGE)
	{
		FlameLevel	= 0;
		StopDrawing	= FALSE;

		return(FALSE);
	}

		// Cycle the colours?

	if(Signals & CycleMask)
	{
		LoadRGB32(VPort,(ULONG *)Colours);

		Wheel = (Wheel + 1) % MaxColour;

		RotatePalette();
	}

	if(Level >= MaxRecursionLevel)
	{
		if((FlamePoints++) > MaxFlamePoints * 100)
			return(FALSE);
		else
			MultiPlot(FlameColour,(WORD)((Width / 2) * (x + 1.0)),(WORD)((Height / 2) * VerticalScale * (y + 1.0)));
	}
	else
	{
		double	nx,ny;
		WORD	i;

		for(i = 0 ; i < 2 ; i++)
		{
			nx = Flame[0][0][i] * x + Flame[0][1][i] * y + Flame[0][2][i];
			ny = Flame[1][0][i] * x + Flame[1][1][i] * y + Flame[1][2][i];

			if(i < FlameAlternateForm)
			{
				nx = sin(nx);
				ny = sin(ny);
			}

			if(!StopDrawing)
			{
				if(!RecurseColour(nx,ny,Level + 1))
					return(FALSE);
			}
			else
				return(FALSE);
		}
	}

	return(TRUE);
}

	/* CosmicFlame():
	 *
	 *	The cosmic flame screen blanker.
	 *
	 *	xlock.c - X11 client to lock a display and show a screen saver.
	 *
	 *	Copyright (c) 1988-91 by Patrick J. Naughton.
	 *
	 *	Permission to use, copy, modify, and distribute this software and its
	 *	documentation for any purpose and without fee is hereby granted,
	 *	provided that the above copyright notice appear in all copies and that
	 *	both that copyright notice and this permission notice appear in
	 *	supporting documentation.
	 */

VOID
CosmicFlame()
{
	WORD i,j,k;
	BOOL Alternate = FALSE;

	FlameLevel = 0;

		// Monochrome mode?

	if(DisplayDepth == 1)
	{
			// Go into fractal generation loop

		FOREVER
		{
			if(!((FlameLevel++) % MaxRecursionLevel))
			{
				BlackScreen();

				Alternate = !Alternate;
			}

			if(Alternate)
				FlameAlternateForm = 0;
			else
				FlameAlternateForm = Random(2) + 2;

			for(k = 0 ; k < 2 ; k++)
			{
				for(i = 0 ; i < 2 ; i++)
				{
					for(j = 0; j < 3; j++)
						Flame[i][j][k] = ((double)Random(1024)) / 512.0 - 1.0;
				}
			}

			FlamePoints = 0;

			RecurseMono(0.0,0.0,0);
		}
	}
	else
	{
		FlameColour = Random(MaxColour - 1) + 1;

		FOREVER
		{
			if(!((FlameLevel++) % MaxRecursionLevel))
			{
				BlackScreen();

				Alternate = !Alternate;
			}
			else
			{
				WORD NewColour;

				do
					NewColour = Random(MaxColour - 1) + 1;
				while(NewColour == FlameColour);

				FlameColour = NewColour;
			}

			if(Alternate)
				FlameAlternateForm = 0;
			else
				FlameAlternateForm = Random(2) + 2;

			for(k = 0 ; k < 2 ; k++)
			{
				for(i = 0 ; i < 2 ; i++)
				{
					for(j = 0; j < 3; j++)
						Flame[i][j][k] = ((double)Random(1024)) / 512.0 - 1.0;
				}
			}

			FlamePoints = 0;

			RecurseColour(0.0,0.0,0);
		}
	}
}

	/* BlankerEntry():
	 *
	 *	The screen blanker itself.
	 */

VOID __saveds
BlankerEntry()
{
	LONG CycleBit;

	Forbid();

		// Shouldn't go wrong right now, this is the second
		// action this task takes, about 16 signal bits
		// should still be vacant

	if((CycleBit = AllocSignal(-1)) != -1)
	{
		CycleMask = (1L << CycleBit);

			// Tell the control process we're running

		Signal(BlankerControlProcess,SIG_HANDSHAKE);

			// Now step back

		SetTaskPri(SysBase -> ThisTask,-20);

		Permit();

			// Determine the fractal type

		if(FractalType == FRACTAL_COSMIC_FLAME)
			CosmicFlame();
		else
		{
			if(FractalType == FRACTAL_REAL_PLANE)
				RealPlane();
			else
			{
				if(Random(42) >= 21)
					CosmicFlame();
				else
					RealPlane();
			}
		}
	}
	else
	{
			// Wave goodbye

		BlankTask = NULL;

		Signal(BlankerControlProcess,SIG_HANDSHAKE);
	}
}

	/* BlankerAction(CxMsg *CxMessage,CxObj *CxObject):
	 *
	 *	Commodities support routine, handles the Commodities
	 *	custom actions (in this case: filter the InputEvents
	 *	coming in and enable/disable the screen blanker).
	 */

VOID __saveds __stdargs
BlankerAction(CxMsg *CxMessage,CxObj *CxObject)
{
	STATIC BYTE Count = 0;

	struct InputEvent *Event = (struct InputEvent *)CxMsgData(CxMessage);

		// Push the blanker screen to the front if necessary

	ObtainSemaphoreShared(&BlankSemaphore);

	if(BlankScreen)
		Signal(BlankerControlProcess,SIG_REFRESH);

	ReleaseSemaphore(&BlankSemaphore);

		// This looks like a timer event

	if(Event -> ie_Class == IECLASS_TIMER)
	{
		if(!Window)
		{
				// Screen blanker still inactive?

			ObtainSemaphoreShared(&BlankSemaphore);

			if(!BlankTask)
			{
					// Is there a timeout to take care of?

				if(ScreenTimeout && !Window)
				{
						// Are we ready to create the
						// screenblanker?

					if(ScreenCount++ >= ScreenTimeout * 10)
						Signal(BlankerControlProcess,SIG_START);
				}
			}
			else
			{
					// Every 5/10 second we signal the blanker
					// task to rotate the palette.

				if(ColourMode == COLOUR_CYCLE)
				{
					if(Count++ >= 2)
					{
						Signal(BlankTask,CycleMask);

						Count = 0;
					}
				}

					// Is it time to change the pattern?

				if(PatternTimeout)
				{
					if(PatternCount++ >= PatternTimeout * 10)
					{
						Signal(BlankerControlProcess,SIG_CHANGE);

						PatternCount = 0;
					}
				}
			}

			ReleaseSemaphore(&BlankSemaphore);
		}
	}
	else
	{
			// The following lines determine whether
			// the blanker is to be removed or to
			// be left running.

		switch(Event -> ie_Class)
		{
			case IECLASS_RAWKEY:

				if(!(Event -> ie_Code & IECODE_UP_PREFIX) && !(Event -> ie_Qualifier & SpecialQualifier))
					Signal(BlankerControlProcess,SIG_BREAK);

				break;

			case IECLASS_NEWPOINTERPOS:
			case IECLASS_POINTERPOS:
			case IECLASS_RAWMOUSE:

				Signal(BlankerControlProcess,SIG_BREAK);

				break;
		}

		ScreenCount = 0;
	}
}

	/* ShutdownCx():
	 *
	 *	Close the Commodities interface.
	 */

VOID
ShutdownCx()
{
	if(CxPort)
	{
		struct Message *Message;

			// Remove the broker

		if(Broker)
			DeleteCxObjAll(Broker);

			// Remove the MsgPort from the public list

		RemPort(CxPort);

			// Remove all pending messages

		while(Message = GetMsg(CxPort))
			ReplyMsg(Message);

			// Delete the MsgPort

		DeleteMsgPort(CxPort);

		CxPort = NULL;
		Broker = NULL;
	}
}

	/* GetSeconds(STRPTR String):
	 *
	 *	Calculates the number of seconds corresponding to
	 *	expressions such as `11:25' or `10'.
	 */

WORD
GetSeconds(STRPTR String)
{
	UBYTE	Buffer[10];
	WORD	i,Seconds;

	CopyMem(String,Buffer,9);

	Buffer[9] = 0;

	String = Buffer;

	for(i = strlen(String) - 1 ; i >= 0 ; i--)
	{
		if(String[i] == ':')
		{
			Seconds = Atol(&String[i + 1]);

			String[i] = 0;

			Seconds += 60 * Atol(String);

			if(Seconds > 30 * 60)
				Seconds = 30 * 60;

			return(Seconds);
		}
	}

	if((Seconds = Atol(String)) > 30 * 60)
		Seconds = 30 * 60;

	return(Seconds);
}

	/* SetupCx():
	 *
	 *	Set up the Commodities interface.
	 */

LONG
SetupCx()
{
	LONG Error;

		// Cancel any previously made assignments

	ShutdownCx();

		// Create a reply port

	if(CxPort = CreateMsgPort())
	{
			// Fill in a unique name

		CxPort -> mp_Node . ln_Name = NewBroker . nb_Name;

			// Add the reply port to the public list

		AddPort(CxPort);

			// Install the replyport

		NewBroker . nb_Port = CxPort;

			// Create the broker

		if(Broker = CxBroker(&NewBroker,&Error))
		{
			CxObj *ObjectList;

				// Link the hotkeys

			AttachCxObj(Broker,CustomHotKey(HotkeyBuffer,		CxPort,POP_WINDOW));
			AttachCxObj(Broker,CustomHotKey(BlankScreenBuffer,	CxPort,BLANK_SCREEN));
			AttachCxObj(Broker,CustomHotKey(SaveScreenBuffer,	CxPort,SAVE_SCREEN));

				// Install the plain InputEvent handler

			ObjectList = CxCustom(BlankerAction,NULL);

				// Any accumulated errors?

			if(!(Error = CxObjError(ObjectList)))
			{
					// Add the custom object

				AttachCxObj(Broker,ObjectList);

					// Any errors?

				if(!(Error = CxObjError(Broker)))
				{
						// Activate the broker

					ActivateCxObj(Broker,TRUE);

					return(0);
				}
			}

			if(Error & COERR_BADFILTER)
				Error = ERR_BadFilter;
			else
			{
				if(Error & COERR_BADTYPE)
					Error = ERR_BadType;
				else
					Error = ERROR_NO_FREE_STORE;
			}
		}
		else
			Error = Error - CBERR_SYSERR + ERR_SystemError;
	}
	else
		Error = ERR_NoMsgPort;

	ShutdownCx();

	return(Error);
}

	/* SaverEntry():
	 *
	 *	This background process handles the job of storing the
	 *	contents of the screen in the clipboard.
	 */

VOID __saveds
SaverEntry()
{
	struct BitMap *BitMap;

		// That's me

	Saver = (struct Process *)FindTask(NULL);

	ObtainSemaphoreShared(&BlankSemaphore);

	Forbid();

		// Create a bitmap to hold a copy of the screen

	if(BitMap = AllocBitMap(Width,Height,DisplayDepth,NULL,RPort -> BitMap))
	{
		STATIC ULONG Count = 1;

		UBYTE	 LocalBuffer[256];
		Object	*Image;

			// Copy the contents

		BltBitMap(RPort -> BitMap,0,0,BitMap,0,0,Width,Height,0xC0,(1L << DisplayDepth) - 1,NULL);

			// Wait until they have arrived

		WaitBlit();

		Permit();

		ReleaseSemaphore(&BlankSemaphore);

			// Build a unique name, in case someone needs it

		SPrintf(LocalBuffer,"FracBlank_%ld×%ld×%ld_%ld",Width,Height,DisplayDepth,Count++);

			// Wrap the bitmap into an image object

		if(Image = NewDTObject(LocalBuffer,
			DTA_SourceType,	DTST_RAM,
			DTA_GroupID,	GID_PICTURE,
			PDTA_NumColors,	1L << DisplayDepth,
			PDTA_BitMap,	BitMap,
			PDTA_ModeID,	GetVPModeID(&BlankScreen -> ViewPort),
		TAG_DONE))
		{
			struct ColorRegister	*ColourMap;
			struct BitMapHeader	*BitMapHeader;
			ULONG			*ColourTab;

				// Get the internal data storage pointers

			if(GetDTAttrs(Image,
				PDTA_BitMapHeader,	&BitMapHeader,
				PDTA_ColorRegisters,	&ColourMap,
				PDTA_CRegs,		&ColourTab,
			TAG_DONE) == 3)
			{
				LONG i;

					// Fill in the header

				BitMapHeader -> bmh_Left	= 0;
				BitMapHeader -> bmh_Top		= 0;
				BitMapHeader -> bmh_Width	= Width;
				BitMapHeader -> bmh_Height	= Height;
				BitMapHeader -> bmh_Depth	= DisplayDepth;
				BitMapHeader -> bmh_PageWidth	= Width;
				BitMapHeader -> bmh_PageHeight	= Height;

					// Fill in the colour tables

				for(i = 0 ; i < 1L << DisplayDepth ; i++)
				{
					ColourTab[i * 3 + 0]	= Colours -> Entry[i] . Red;
					ColourTab[i * 3 + 1]	= Colours -> Entry[i] . Green;
					ColourTab[i * 3 + 2]	= Colours -> Entry[i] . Blue;

					ColourMap[i] . red	= Colours -> Entry[i] . Red	>> 24;
					ColourMap[i] . green	= Colours -> Entry[i] . Green	>> 24;
					ColourMap[i] . blue	= Colours -> Entry[i] . Blue	>> 24;
				}

					// Put the image into the clipboard

				DoMethod(Image,DTM_COPY,NULL);
			}

			DisposeDTObject(Image);
		}
		else
			FreeBitMap(BitMap);
	}
	else
	{
		Permit();

		ReleaseSemaphore(&BlankSemaphore);
	}

	Forbid();

	Saver = NULL;
}

	/* HandleCxMsg(CxMsg *Message):
	 *
	 *	Handle incoming Commodities messages.
	 */

VOID
HandleCxMsg(CxMsg *Message)
{
	ULONG MessageID = CxMsgID(Message),MessageType = CxMsgType(Message);

	ReplyMsg((struct Message *)Message);

		// Take a look at the message type

	switch(MessageType)
	{
			// It's a hotkey

		case CXM_IEVENT:

			switch(MessageID)
			{
					// Create the control panel

				case POP_WINDOW:

						// Make sure the blanker has terminated

					Signal(BlankerControlProcess,SIG_BREAK);

					SetupWindow();

					break;

					// Blank the screen

				case BLANK_SCREEN:

					if(!Window)
					{
						ObtainSemaphoreShared(&BlankSemaphore);

						if(!BlankTask)
							Signal(BlankerControlProcess,SIG_START);
						else
						{
							Signal(BlankerControlProcess,SIG_CHANGE);

							PatternCount = 0;
						}

						ReleaseSemaphore(&BlankSemaphore);
					}

					break;

					// Save the screen in the clipboard

				case SAVE_SCREEN:

					if(DataTypesBase)
					{
						ObtainSemaphoreShared(&BlankSemaphore);

							// Let the background process handle the job

						if(BlankScreen && !Saver)
						{
							CreateNewProcTags(
								NP_Name,	"« FracBlank Screen Saver »",
								NP_Entry,	SaverEntry,
								NP_StackSize,	4096,
								NP_WindowPtr,	-1,
							TAG_DONE);
						}

						ReleaseSemaphore(&BlankSemaphore);
					}

					break;
			}

			break;

			// It's an internal Commodities command

		case CXM_COMMAND:

			switch(MessageID)
			{
					// Disable the Commodity

				case CXCMD_DISABLE:

					ActivateCxObj(Broker,FALSE);
					break;

					// Enable the Commodity

				case CXCMD_ENABLE:

					ActivateCxObj(Broker,TRUE);
					break;

					// Create the control panel

				case CXCMD_APPEAR:
				case CXCMD_UNIQUE:

					SetupWindow();
					break;

					// Close the control panel

				case CXCMD_DISAPPEAR:

					ShutdownWindow();
					break;

					// Remove this Commodity

				case CXCMD_KILL:

					CloseAll(RETURN_OK);
					break;
			}

			break;
	}
}

	/* ShowTime(struct Gadget *SomeGadget,WORD Level):
	 *
	 *	Gadtools support routine, displays the timeouts.
	 */

LONG __saveds __stdargs
ShowTime(struct Gadget *SomeGadget,WORD Level)
{
	STATIC UBYTE Buffer[30];

	if(Level)
		SPrintf(Buffer,"%2ld:%02ld min.",Level / 60,Level % 60);
	else
		SPrintf(Buffer,"«Off»");

	return((LONG)Buffer);
}

	/* ShowDots(struct Gadget *SomeGadget,WORD Level):
	 *
	 *	Cheap trick; multiply the number of dots by 100.
	 */

LONG __saveds __stdargs
ShowDots(struct Gadget *SomeGadget,WORD Level)
{
	return((LONG)Level * 100);
}

	/* ShutdownWindow():
	 *
	 *	Closes the control panel.
	 */

VOID
ShutdownWindow()
{
	if(Handle)
	{
		LT_DeleteHandle(Handle);

		Handle = NULL;

		Window = NULL;

		CloseLibrary(GTLayoutBase);

		GTLayoutBase = NULL;
	}
}

	/* SetupWindow():
	 *
	 *	Creates the control panel and disables the screen
	 *	blanker.
	 */

BOOL
SetupWindow()
{
	if(Handle)
	{
		LT_ShowWindow(Handle,TRUE);

		return(TRUE);
	}
	else
	{
		if(!(GTLayoutBase = SafeOpenLibrary("PROGDIR:gtlayout.library",9)))
			GTLayoutBase = SafeOpenLibrary("gtlayout.library",9);

		if(GTLayoutBase)
		{
			if(Handle = LT_CreateHandleTagList(NULL,NULL))
			{
				LT_New(Handle,
					LA_Type,	VERTICAL_KIND,
				TAG_DONE);
				{
					LT_New(Handle,
						LA_LabelText,	"Fractal screen blanker",
						LA_Type,	VERTICAL_KIND,
					TAG_DONE);
					{
						STATIC STRPTR Copyright[] =
						{
							"Copyright © 1991-1995",
							"by Olaf `Olsen' Barthel",
							"All Rights Reserved",
							NULL
						};

						LT_New(Handle,
							LA_Type,	BOX_KIND,
							LABX_Lines,	Copyright,
							LABX_AlignText,	ALIGNTEXT_CENTERED,
							LA_DrawBox,	FALSE,
						TAG_DONE);

						LT_EndGroup(Handle);
					}

					LT_New(Handle,
						LA_LabelText,	"Controls",
						LA_Type,	VERTICAL_KIND,
					TAG_DONE);
					{
						STATIC STRPTR FractalTypes[] =
						{
							"Real plane",
							"Cosmic flames",
							"« Random »",
							NULL
						};

						STATIC STRPTR ColourModes[] =
						{
							"Cycling",
							"Static",
							NULL
						};

						LT_New(Handle,
							LA_Type,		SLIDER_KIND,
							LA_LabelText,		"Screen timeout",
							GTSL_Min,		0,
							GTSL_Max,		30 * 60,
							GTSL_LevelFormat,	"%s",
							GTSL_DispFunc,		ShowTime,
							LA_ULONG,		&ScreenTimeout,
							LA_Chars,		20,
						TAG_DONE);

						LT_New(Handle,
							LA_Type,		SLIDER_KIND,
							LA_LabelText,		"Pattern timeout",
							GTSL_Min,		0,
							GTSL_Max,		30 * 60,
							GTSL_LevelFormat,	"%s",
							GTSL_DispFunc,		ShowTime,
							LA_ULONG,		&PatternTimeout,
						TAG_DONE);

						LT_New(Handle,
							LA_Type,		XBAR_KIND,
						TAG_DONE);

						EditHook . h_Entry = (HOOKFUNC)EditRoutine;

						LT_New(Handle,
							LA_Type,		STRING_KIND,
							LA_LabelText,		"Hot key",
							GTST_MaxChars,		255,
							GTST_String,		HotkeyBuffer,
							GTST_EditHook,		&EditHook,
							LA_ID,			GAD_HOTKEY,
						TAG_DONE);

						LT_New(Handle,
							LA_Type,		STRING_KIND,
							LA_LabelText,		"Blank screen",
							GTST_MaxChars,		255,
							GTST_String,		BlankScreenBuffer,
							GTST_EditHook,		&EditHook,
							LA_ID,			GAD_BLANKSCREEN,
						TAG_DONE);

						if(DataTypesBase)
						{
							LT_New(Handle,
								LA_Type,	STRING_KIND,
								LA_LabelText,	"Save screen to clipboard",
								GTST_MaxChars,	255,
								GTST_String,	SaveScreenBuffer,
								GTST_EditHook,	&EditHook,
								LA_ID,		GAD_SAVESCREEN,
							TAG_DONE);
						}

						LT_New(Handle,
							LA_Type,		XBAR_KIND,
						TAG_DONE);

						UpdateModeString();

						LT_New(Handle,
							LA_Type,		TEXT_KIND,
							LA_LabelText,		"Display mode",
							LA_ID,			GAD_MODE,
							GTTX_Text,		DisplayModeBuffer,
							GTTX_Border,		TRUE,
							LATX_Picker,		TRUE,
						TAG_DONE);

						LT_New(Handle,
							LA_Type,		XBAR_KIND,
						TAG_DONE);

						LT_New(Handle,
							LA_Type,		CYCLE_KIND,
							LA_LabelText,		"Colour mode",
							GTCY_Labels,		ColourModes,
							LA_UBYTE,		&ColourMode,
						TAG_DONE);

						LT_New(Handle,
							LA_Type,		CYCLE_KIND,
							LA_LabelText,		"Fractal type",
							GTCY_Labels,		FractalTypes,
							LA_UBYTE,		&FractalType,
						TAG_DONE);

						LT_New(Handle,
							LA_Type,		SLIDER_KIND,
							LA_LabelText,		"Maximum recursion level",
							GTSL_Min,		10,
							GTSL_Max,		100,
							GTSL_LevelFormat,	"%ld",
							LA_ULONG,		&MaxRecursionLevel,
						TAG_DONE);

						LT_New(Handle,
							LA_Type,		SLIDER_KIND,
							LA_LabelText,		"Maximum dots",
							GTSL_Min,		10,
							GTSL_Max,		1000,
							GTSL_LevelFormat,	"%lD",
							GTSL_DispFunc,		ShowDots,
							LA_ULONG,		&MaxFlamePoints,
						TAG_DONE);

						LT_EndGroup(Handle);
					}

					LT_New(Handle,
						LA_Type,	VERTICAL_KIND,
					TAG_DONE);
					{
						LT_New(Handle,
							LA_Type,		XBAR_KIND,
							LAXB_FullSize,		TRUE,
						TAG_DONE);

						LT_EndGroup(Handle);
					}

					LT_New(Handle,
						LA_Type,	HORIZONTAL_KIND,
						LAGR_Spread,	TRUE,
						LAGR_SameSize,	TRUE,
					TAG_DONE);
					{
						if(!MainProcess -> pr_CLI)
						{
							LT_New(Handle,
								LA_Type,	BUTTON_KIND,
								LA_LabelText,	"Save",
								LABT_ExtraFat,	TRUE,
								LA_ID,		GAD_SAVE,
								LA_Chars,	8,
							TAG_DONE);
						}

						LT_New(Handle,
							LA_Type,	BUTTON_KIND,
							LA_LabelText,	"Hide",
							LABT_ReturnKey,	TRUE,
							LABT_ExtraFat,	TRUE,
							LA_ID,		GAD_HIDE,
							LA_Chars,	8,
						TAG_DONE);

						LT_New(Handle,
							LA_Type,	BUTTON_KIND,
							LA_LabelText,	"Quit",
							LA_ID,		GAD_QUIT,
						TAG_DONE);

						LT_EndGroup(Handle);
					}

					LT_EndGroup(Handle);
				}

				if(Window = LT_Build(Handle,
					LAWN_IDCMP,		IDCMP_CLOSEWINDOW,
					LAWN_BelowMouse,	TRUE,
					LAWN_SmartZoom,		TRUE,
					LAWN_TitleText,		&VersTag[7],
					WA_DepthGadget,		TRUE,
					WA_CloseGadget,		TRUE,
					WA_DragBar,		TRUE,
					WA_RMBTrap,		TRUE,
					WA_Activate,		TRUE,
				TAG_DONE))
				{
					ResetRequest = TRUE;

					return(TRUE);
				}

				LT_DeleteHandle(Handle);

				Handle = NULL;
			}

			ReportError(NULL,NULL,ERR_NoWindow);

			CloseLibrary(GTLayoutBase);

			GTLayoutBase = NULL;
		}
		else
			ReportError(NULL,NULL,ERR_NoGTLayout);

		return(FALSE);
	}
}

	/* CloseAll(LONG ReturnCode):
	 *
	 *	Free all resources and exit the program.
	 */

VOID
CloseAll(LONG ReturnCode)
{
		// Wait until the screen is saved

	while(Saver)
		Delay(TICKS_PER_SECOND);

	if(CxBase)
		ShutdownCx();

	if(BlankerControlProcess)
	{
		Forbid();

		SetSignal(0,SIG_HANDSHAKE);

		Signal(BlankerControlProcess,SIG_FINISH);

		Wait(SIG_HANDSHAKE);

		Permit();
	}

	if(Pointer)
		DisposeObject(Pointer);

	ShutdownWindow();

	if(Colours)
		FreeVec(Colours);

	if(ScreenModeRequest)
		FreeAslRequest(ScreenModeRequest);

	if(KeymapBase)
		CloseLibrary(KeymapBase);

	if(IconBase)
		CloseLibrary(IconBase);

	if(UtilityBase)
		CloseLibrary(UtilityBase);

	if(CxBase)
		CloseLibrary(CxBase);

	if(AslBase)
		CloseLibrary(AslBase);

	if(DataTypesBase)
		CloseLibrary(DataTypesBase);

	if(GfxBase)
		CloseLibrary(GfxBase);

	if(IntuitionBase)
		CloseLibrary(IntuitionBase);

	exit(ReturnCode);
}

	/* OpenAll(int argc,char **argv):
	 *
	 *	Open all resources, initialize the colour table and
	 *	create the Commodities interface.
	 */

VOID
OpenAll(int argc,char **argv)
{
	STRPTR			*ToolTypes,
				 String,
				 Array[ARGCOUNT];
	struct RDArgs		*Args;
	struct DiskObject	*Icon;
	IX			 Expression;
	LONG			 Error;

		// Initialize this, the rotation code needs it

	deg45 = sin(-45.0);

	ScreenHook . h_Entry = (HOOKFUNC)BackfillDummy;
	WindowHook . h_Entry = (HOOKFUNC)BackfillDummy;

	InitSemaphore(&BlankSemaphore);

	MainProcess = (struct Process *)FindTask(NULL);

		// Open the libraries we need

	if(!(IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library",37)))
		CloseAll(RETURN_FAIL);

	if(!(CxBase = OpenLibrary("commodities.library",37)))
	{
		ReportError(NULL,NULL,ERR_NoCommodities);

		CloseAll(RETURN_FAIL);
	}

	if(!(UtilityBase = OpenLibrary("utility.library",37)))
	{
		ReportError(NULL,NULL,ERR_NoUtility);

		CloseAll(RETURN_FAIL);
	}

	if(!(IconBase = OpenLibrary("icon.library",0)))
	{
		ReportError(NULL,NULL,ERR_NoIcon);

		CloseAll(RETURN_FAIL);
	}

	if(!(AslBase = OpenLibrary("asl.library",38)))
	{
		ReportError(NULL,NULL,ERR_NoAsl);

		CloseAll(RETURN_FAIL);
	}

	if(!(GfxBase = (struct GfxBase *)OpenLibrary("graphics.library",39)))
	{
		ReportError(NULL,NULL,ERR_NoGfx);

		CloseAll(RETURN_FAIL);
	}

	if(!(KeymapBase = OpenLibrary("keymap.library",37)))
	{
		ReportError(NULL,NULL,ERR_NoKeymap);

		CloseAll(RETURN_FAIL);
	}

	DataTypesBase = OpenLibrary("datatypes.library",39);

	if(!(ScreenModeRequest = AllocAslRequestTags(ASL_ScreenModeRequest,
		ASLSM_DoDepth,		TRUE,
		ASLSM_PropertyFlags,	NULL,
		ASLSM_PropertyMask,	DIPF_IS_DUALPF | DIPF_IS_PF2PRI | DIPF_IS_HAM | DIPF_IS_EXTRAHALFBRITE,
		ASLSM_MinDepth,		1,
		ASLSM_MaxDepth,		8,
		ASLSM_UserData,		FALSE,
	TAG_DONE)))
	{
		ReportError(NULL,NULL,ERR_ScreenModeRequest);

		CloseAll(RETURN_FAIL);
	}

		// Set up a transparent window pointer

	InitBitMap(&PointerBitMap,2,16,1);

	PointerBitMap . Planes[0] = PointerBitMap . Planes[1] = (PLANEPTR)&Nothing;

	if(!(Pointer = NewObject(NULL,POINTERCLASS,
		POINTERA_BitMap,	&PointerBitMap,
		POINTERA_XOffset,	0,
		POINTERA_YOffset,	0,
		POINTERA_WordWidth,	2,
	TAG_DONE)))
	{
		ReportError(NULL,NULL,ERR_NoPointer);

		CloseAll(RETURN_FAIL);
	}

	if(!(Colours = (ColourTable *)AllocVec(sizeof(ColourTable) + 255 * sizeof(ColourEntry),MEMF_ANY | MEMF_PUBLIC | MEMF_CLEAR)))
	{
		ReportError(NULL,NULL,ERR_NoColours);

		CloseAll(RETURN_FAIL);
	}

	Forbid();

	if(BlankerControlProcess = CreateNewProcTags(
		NP_Name,	"« FracBlank Control Process »",
		NP_Priority,	100,
		NP_Entry,	BlankerControlEntry,
		NP_StackSize,	4096,
		NP_WindowPtr,	-1,
	TAG_DONE))
	{
		SetSignal(0,SIG_HANDSHAKE);

			// Wait for handshake signal

		Wait(SIG_HANDSHAKE);
	}

	Permit();

	if(!BlankerControlProcess)
	{
		ReportError(NULL,NULL,ERR_NoControlProcess);

		CloseAll(RETURN_FAIL);
	}

	memset(Array,0,sizeof(Array));

	if(argc)
	{
		if(!(Args = ReadArgs("CX_PRIORITY/N/K,CX_POPKEY/K,CX_POPUP/K,BLANKSCREEN/K,SAVESCREEN/K,FRACTAL/K,COLOUR/K,DISPLAYMODE/K,DEPTH/N/K,SCREENTIMEOUT/K,PATTERNTIMEOUT/K,MAXRECURSION/N/K,MAXDOTS/N/K",(LONG *)Array,NULL)))
		{
			PrintFault(IoErr(),"FracBlank");

			CloseAll(RETURN_FAIL);
		}

		ToolTypes	= NULL;
		Icon		= NULL;
	}
	else
	{
		struct WBStartup	*Startup = (struct WBStartup *)argv;
		BPTR			 OldDir;

		Args = NULL;

		OldDir = CurrentDir(MainProcess -> pr_HomeDir);

		if(Icon = GetDiskObject(Startup -> sm_ArgList -> wa_Name))
			ToolTypes = (STRPTR *)Icon -> do_ToolTypes;
		else
			ToolTypes = NULL;

		CurrentDir(OldDir);
	}

	NewBroker . nb_Pri = ToolValue(ToolTypes,"CX_PRIORITY",Array[ARG_PRIORITY] ? *(LONG *)Array[ARG_PRIORITY] : 0);

	if(NewBroker . nb_Pri < -128)
		NewBroker . nb_Pri = -128;
	else
	{
		if(NewBroker . nb_Pri > 127)
			NewBroker . nb_Pri = 127;
	}

		// We'll put up a mask of qualifiers we don't want the blanker
		// to stop at

	SpecialQualifier = IEQUALIFIER_REPEAT;

		// Set the Commodity popup hotkey if possible

	strcpy(HotkeyBuffer,ToolString(ToolTypes,"CX_POPKEY",Array[ARG_POPKEY] ? Array[ARG_POPKEY] : (STRPTR)"shift f1"));

	SpecialQualifier = AddQualifier(HotkeyBuffer,SpecialQualifier);

	if(Error = ParseIX(HotkeyBuffer,&Expression))
	{
		ReportError(NULL,"Invalid CX_POPKEY specification\n%s",ERR_BadFilter);

		CloseAll(RETURN_FAIL);
	}

		/* Determine the screen blanker hotkey. */

	strcpy(BlankScreenBuffer,ToolString(ToolTypes,"BLANKSCREEN",Array[ARG_BLANKSCREEN] ? Array[ARG_BLANKSCREEN] : (STRPTR)"shift f2"));

	SpecialQualifier = AddQualifier(BlankScreenBuffer,SpecialQualifier);

	if(Error = ParseIX(BlankScreenBuffer,&Expression))
	{
		ReportError(NULL,"Invalid BLANKSCREEN specification\n%s",ERR_BadFilter);

		CloseAll(RETURN_FAIL);
	}

		// Set the screen save hotkey

	strcpy(SaveScreenBuffer,ToolString(ToolTypes,"SAVESCREEN",Array[ARG_SAVESCREEN] ? Array[ARG_SAVESCREEN] : (STRPTR)"shift f3"));

	SpecialQualifier = AddQualifier(SaveScreenBuffer,SpecialQualifier);

	if(Error = ParseIX(SaveScreenBuffer,&Expression))
	{
		ReportError(NULL,"Invalid SAVESCREEN specification\n%s",ERR_BadFilter);

		CloseAll(RETURN_FAIL);
	}

		// Which fractal type?

	String = ToolString(ToolTypes,"FRACTAL",Array[ARG_FRACTAL] ? Array[ARG_FRACTAL] : (STRPTR)"RANDOM");

	if(MatchToolValue(String,"REALPLANE") || MatchToolValue(String,"REAL"))
		FractalType = FRACTAL_REAL_PLANE;
	else
	{
		if(MatchToolValue(String,"COSMICFLAME") || MatchToolValue(String,"COSMIC") || MatchToolValue(String,"FLAME"))
			FractalType = FRACTAL_COSMIC_FLAME;
		else
			FractalType = FRACTAL_RANDOM;
	}

		// Which colour mode?

	String = ToolString(ToolTypes,"COLOUR",Array[ARG_COLOUR] ? Array[ARG_COLOUR] : (STRPTR)"CYCLE");

	if(MatchToolValue(String,"STATIC"))
		ColourMode = COLOUR_STATIC;
	else
		ColourMode = COLOUR_CYCLE;

		// Which display mode?

	DisplayID = INVALID_ID;

	if(String = ToolString(ToolTypes,"DISPLAYMODE",Array[ARG_DISPLAYMODE]))
	{
		UBYTE LocalBuffer[40];
		ULONG ID = INVALID_ID;

		while((ID = NextDisplayInfo(ID)) != INVALID_ID)
		{
			if(GetModeName(ID,LocalBuffer))
			{
				if(!Stricmp(LocalBuffer,String))
				{
					struct DisplayInfo DisplayInfo;

					if(GetDisplayInfoData(NULL,(APTR)&DisplayInfo,sizeof(struct DisplayInfo),DTAG_DISP,ID))
					{
						if(!(DisplayInfo . NotAvailable & ~DI_AVAIL_NOTWITHGENLOCK))
						{
							if(!(DisplayInfo . PropertyFlags & (DIPF_IS_DUALPF | DIPF_IS_PF2PRI | DIPF_IS_HAM | DIPF_IS_EXTRAHALFBRITE)))
							{
								DisplayID = ID;

								break;
							}
						}
					}
				}
			}
		}
	}

		// Which depth?

	Depth = ToolValue(ToolTypes,"DEPTH",Array[ARG_DEPTH] ? *(LONG *)Array[ARG_DEPTH] : 0);

	if(Depth > 8)
		Depth = 8;
	else
	{
		if(Depth < 0)
			Depth = 0;
	}

		// Adjust the screen timeout if possible

	ScreenTimeout = GetSeconds(ToolString(ToolTypes,"SCREENTIMEOUT",Array[ARG_SCREENTIMEOUT] ? Array[ARG_SCREENTIMEOUT] : (STRPTR)"1:00"));

		// Adjust the pattern change timeout

	PatternTimeout = GetSeconds(ToolString(ToolTypes,"PATTERNTIMEOUT",Array[ARG_PATTERNTIMEOUT] ? Array[ARG_PATTERNTIMEOUT] : (STRPTR)"1:00"));

		// Take care of the recursion level

	MaxRecursionLevel = ToolValue(ToolTypes,"MAXRECURSION",Array[ARG_MAXRECURSION] ? *(LONG *)Array[ARG_MAXRECURSION] : 40);

	if(MaxRecursionLevel < 10)
		MaxRecursionLevel = 10;
	else
	{
		if(MaxRecursionLevel > 100)
			MaxRecursionLevel = 100;
	}

		// Now for the cosmic flame dots

	MaxFlamePoints = ToolValue(ToolTypes,"MAXDOTS",Array[ARG_MAXDOTS] ? *(LONG *)Array[ARG_MAXDOTS] : 20000);

	MaxFlamePoints /= 100;

	if(MaxFlamePoints < 10)
		MaxFlamePoints = 10;
	else
	{
		if(MaxFlamePoints > 1000)
			MaxFlamePoints = 1000;
	}

		// Create the commodities interface

	if(Error = SetupCx())
	{
		if(Error != ERR_Duplicate)
			ReportError(NULL,NULL,Error);

		if(Args)
			FreeArgs(Args);

		if(Icon)
			FreeDiskObject(Icon);

		CloseAll(RETURN_FAIL);
	}

		// Pop up the control panel if necessary

	if(MatchToolValue(ToolString(ToolTypes,"CX_POPUP",Array[ARG_POPUP] ? Array[ARG_POPUP] : (STRPTR)"no"),"yes"))
	{
		PopUp = TRUE;

		SetupWindow();
	}

		// Clean up

	if(Args)
		FreeArgs(Args);

	if(Icon)
		FreeDiskObject(Icon);
}

	/* UpdateModeString():
	 *
	 *	This routine builds the screen mode and screen depth string
	 *	for the user interface.
	 */

VOID
UpdateModeString()
{
	WORD Len;

	if(!GetModeName(DisplayID,DisplayModeBuffer))
		strcpy(DisplayModeBuffer,"«Default screen mode»");

	Len = strlen(DisplayModeBuffer);

	if(Depth)
		SPrintf(&DisplayModeBuffer[Len],", %ld colours",1L << Depth);
	else
		strcpy(&DisplayModeBuffer[Len],", «Maximum depth»");
}

	/* SaveOptions(char **argv):
	 *
	 *	Here we store the current options within the program icon.
	 */

VOID
SaveOptions(char **argv)
{
	STATIC STRPTR KnownTypes[] =
	{
		"CX_PRIORITY",
		"CX_POPKEY",
		"CX_POPUP",
		"BLANKSCREEN",
		"SAVESCREEN",
		"FRACTAL",
		"COLOUR",
		"DISPLAYMODE",
		"DEPTH",
		"SCREENTIMEOUT",
		"PATTERNTIMEOUT",
		"MAXRECURSION",
		"MAXDOTS"
	};

	struct WBStartup	*Startup = (struct WBStartup *)argv;
	BPTR			 OldDir;
	struct DiskObject	*Icon;

	OldDir = CurrentDir(MainProcess -> pr_HomeDir);

		// Get the program icon

	if(Icon = GetDiskObject(Startup -> sm_ArgList -> wa_Name))
	{
		STRPTR	*Types,
			*NewTypes,
			 Data;
		LONG	 Count = 0,Known = sizeof(KnownTypes) / sizeof(STRPTR),Len = 0,i;

			// Count the number of tooltypes present

		if(Types = (STRPTR *)Icon -> do_ToolTypes)
		{
			while(Types[Count])
				Count++;
		}

			// Count the room required for the tooltypes to add

		for(i = 0 ; i < Known ; i++)
			Len += strlen(KnownTypes[i]) + 2;

			// Add room for the keys

		Len += (10) + (256) + (4) + (256) + (256) + (8) + (8) + (40) + (10) + (10) + (10) + (10) + (10);

			// All the data will go here

		if(NewTypes = (STRPTR *)AllocVec(Len + (Count + 1 + Known) * sizeof(STRPTR),MEMF_ANY | MEMF_CLEAR))
		{
			STRPTR	*Tool = NewTypes,String;
			UBYTE	LocalBuffer[DISPLAYNAMELEN];

			Data = (STRPTR)&NewTypes[Count + 1 + Known];

				// CX_PRIORITY=[..]

			SPrintf(Data,"CX_PRIORITY=%ld",NewBroker . nb_Pri);

			*Tool++ = Data;
			Data += strlen(Data) + 1;

				// CX_POPKEY=[..]

			SPrintf(Data,"CX_POPKEY=%s",HotkeyBuffer);

			*Tool++ = Data;
			Data += strlen(Data) + 1;

				// CX_POPUP=[..]

			SPrintf(Data,"CX_POPUP=%s",PopUp ? "yes" : "no");

			*Tool++ = Data;
			Data += strlen(Data) + 1;

				// BLANKSCREEN=[..]

			SPrintf(Data,"BLANKSCREEN=%s",BlankScreenBuffer);

			*Tool++ = Data;
			Data += strlen(Data) + 1;

				// SAVESCREEN=[..]

			SPrintf(Data,"SAVESCREEN=%s",SaveScreenBuffer);

			*Tool++ = Data;
			Data += strlen(Data) + 1;

				// FRACTAL=[..]

			switch(FractalType)
			{
				case FRACTAL_REAL_PLANE:

					String = "REALPLANE";
					break;

				case FRACTAL_COSMIC_FLAME:

					String = "COSMICFLAME";
					break;

				case FRACTAL_RANDOM:

					String = "RANDOM";
					break;
			}

			SPrintf(Data,"FRACTAL=%s",String);

			*Tool++ = Data;
			Data += strlen(Data) + 1;

				// COLOUR=[..]

			switch(ColourMode)
			{
				case COLOUR_CYCLE:

					String = "CYCLE";
					break;

				case COLOUR_STATIC:

					String = "STATIC";
					break;
			}

			SPrintf(Data,"COLOUR=%s",String);

			*Tool++ = Data;
			Data += strlen(Data) + 1;

				// DISPLAYMODE=[..]

			if(GetModeName(DisplayID,LocalBuffer))
			{
				SPrintf(Data,"DISPLAYMODE=%s",LocalBuffer);

				*Tool++ = Data;
				Data += strlen(Data) + 1;
			}

				// DEPTH=[..]

			SPrintf(Data,"DEPTH=%ld",Depth);

			*Tool++ = Data;
			Data += strlen(Data) + 1;

				// SCREENTIMEOUT=[..]

			SPrintf(Data,"SCREENTIMEOUT=%ld:%02ld",ScreenTimeout / 60,ScreenTimeout % 60);

			*Tool++ = Data;
			Data += strlen(Data) + 1;

				// PATTERNTIMEOUT=[..]

			SPrintf(Data,"PATTERNTIMEOUT=%ld:%02ld",PatternTimeout / 60,PatternTimeout % 60);

			*Tool++ = Data;
			Data += strlen(Data) + 1;

				// MAXRECURSION=[..]

			SPrintf(Data,"MAXRECURSION=%ld",MaxRecursionLevel);

			*Tool++ = Data;
			Data += strlen(Data) + 1;

				// MAXDOTS=[..]

			SPrintf(Data,"MAXDOTS=%ld",MaxFlamePoints * 100);

			*Tool++ = Data;

				// Now add the remaining old tooltypes,
				// unless we already took care of them

			if(Types = (STRPTR *)Icon -> do_ToolTypes)
			{
				BOOL GotIt;

				while(*Types)
				{
					GotIt = FALSE;

					for(i = 0 ; i < Known ; i++)
					{
						if(!Strnicmp(*Types,KnownTypes[i],strlen(KnownTypes[i])))
						{
							GotIt = TRUE;

							break;
						}
					}

					if(!GotIt)
						*Tool++ = *Types;

					Types++;
				}
			}

				// Install the new tooltypes

			Icon -> do_ToolTypes = (char **)NewTypes;

				// Write the icon

			if(!PutDiskObject(Startup -> sm_ArgList -> wa_Name,Icon))
				ReportError(Window,"Error writing icon\n%s",IoErr());

				// Clean up

			FreeVec(NewTypes);
		}
		else
			ReportError(Window,NULL,ERROR_NO_FREE_STORE);

		FreeDiskObject(Icon);
	}
	else
		ReportError(Window,"Error reading icon\n%s",IoErr());

	CurrentDir(OldDir);
}

	/* main(int argc,char **argv):
	 *
	 *	That's where all the trouble starts.
	 */

void __stdargs
main(int argc,char **argv)
{
	ULONG	SignalSet;
	BOOL	Done = FALSE;

		// Open everything we need

	OpenAll(argc,argv);

		// Go into loop waiting for messages

	do
	{
			// Wait for a signal...

		SignalSet = SIG_CX | SIG_BREAK | SIG_WAKEUP;

		if(Window)
			SignalSet |= SIG_WINDOW;

		SignalSet = Wait(SignalSet);

			// Check the commodities toolkit reply port

		if(SignalSet & SIG_CX)
		{
			CxMsg *Message;

			while(Message = (CxMsg *)GetMsg(CxPort))
				HandleCxMsg(Message);
		}

			// ^C tells the program to quit

		if(SignalSet & SIG_BREAK)
			Done = TRUE;

			// ^F tells the program to open its control panel

		if(SignalSet & SIG_WAKEUP)
			SetupWindow();

			// If the control panel is still open,
			// check for new messages.

		if(Window)
		{
			if(SignalSet & SIG_WINDOW)
			{
				struct IntuiMessage	*IMsg;
				BOOL			 Hide = FALSE;
				ULONG			 MsgClass;
				struct Gadget		*MsgGadget;
				ULONG			 LocalID,LocalDepth;
				IX			 Expression;
				BOOL			 NewKeys;
				STRPTR			 String;
				LONG			 Error;
				struct IBox		 Box;

				while(IMsg = LT_GetIMsg(Handle))
				{
					MsgClass	= IMsg -> Class;
					MsgGadget	= (struct Gadget *)IMsg -> IAddress;

					LT_ReplyIMsg(IMsg);

					switch(MsgClass)
					{
						case IDCMP_CLOSEWINDOW:

							Hide = TRUE;
							break;

						case IDCMP_GADGETUP:

							switch(MsgGadget -> GadgetID)
							{
								case GAD_QUIT:

									Done = TRUE;
									break;

									// Make sure that the key
									// codes are correct

								case GAD_BLANKSCREEN:
								case GAD_SAVESCREEN:
								case GAD_HOTKEY:

									if(ParseIX(LT_GetString(Handle,MsgGadget -> GadgetID),&Expression))
									{
										DisplayBeep(Window -> WScreen);

										LT_Activate(Handle,MsgGadget -> GadgetID);
									}

									break;

								case GAD_HIDE:
								case GAD_SAVE:

									LT_LockWindow(Window);

										// Update the Commodities
										// interface only if the
										// key assignments have
										// changed

									NewKeys = FALSE;

									String = LT_GetString(Handle,GAD_HOTKEY);

									if(!ParseIX(String,&Expression))
									{
										IX OldExpression;

										ParseIX(HotkeyBuffer,&OldExpression);

										if(memcmp(&Expression,&OldExpression,sizeof(IX)))
										{
											SpecialQualifier = SubQualifier(HotkeyBuffer,SpecialQualifier);

											strcpy(HotkeyBuffer,String);

											SpecialQualifier = AddQualifier(HotkeyBuffer,SpecialQualifier);

											NewKeys = TRUE;
										}
									}

									String = LT_GetString(Handle,GAD_BLANKSCREEN);

									if(!ParseIX(String,&Expression))
									{
										IX OldExpression;

										ParseIX(BlankScreenBuffer,&OldExpression);

										if(memcmp(&Expression,&OldExpression,sizeof(IX)))
										{
											SpecialQualifier = SubQualifier(BlankScreenBuffer,SpecialQualifier);

											strcpy(BlankScreenBuffer,String);

											SpecialQualifier = AddQualifier(BlankScreenBuffer,SpecialQualifier);

											NewKeys = TRUE;
										}
									}

									String = LT_GetString(Handle,GAD_SAVESCREEN);

									if(!ParseIX(String,&Expression))
									{
										IX OldExpression;

										ParseIX(SaveScreenBuffer,&OldExpression);

										if(memcmp(&Expression,&OldExpression,sizeof(IX)))
										{
											SpecialQualifier = SubQualifier(SaveScreenBuffer,SpecialQualifier);

											strcpy(SaveScreenBuffer,String);

											SpecialQualifier = AddQualifier(SaveScreenBuffer,SpecialQualifier);

											NewKeys = TRUE;
										}
									}

									LT_UnlockWindow(Window);

									if(NewKeys)
									{
										if(Error = SetupCx())
										{
											if(Error != ERR_Duplicate)
												ReportError(Window,NULL,Error);

											CloseAll(RETURN_FAIL);
										}
									}

									if(MsgGadget -> GadgetID == GAD_HIDE)
										Hide = TRUE;
									else
										SaveOptions(argv);

									break;
							}

							break;

							// Pick a new display mode

						case IDCMP_IDCMPUPDATE:

							if(DisplayID == INVALID_ID)
								LocalID = GetVPModeID(&Window -> WScreen -> ViewPort);
							else
								LocalID = DisplayID;

							if(Depth)
								LocalDepth = Depth;
							else
							{
								struct DimensionInfo DimensionInfo;

								if(GetDisplayInfoData(NULL,(APTR)&DimensionInfo,sizeof(DimensionInfo),DTAG_DIMS,LocalID))
									LocalDepth = DimensionInfo . MaxDepth;
								else
									LocalDepth = 2;
							}

							if(ScreenModeRequest -> sm_UserData && !ResetRequest)
								CopyMem(&ScreenModeRequest -> sm_LeftEdge,&Box,sizeof(struct IBox));
							else
							{
								CopyMem(&Window -> LeftEdge,&Box,sizeof(struct IBox));

								Box . Top	+= Window -> BorderTop;
								Box . Height	-= Window -> BorderTop;
							}

							if(AslRequestTags(ScreenModeRequest,
								ASLSM_Window,			Window,
								ASLSM_SleepWindow,		TRUE,
								ASLSM_InitialDisplayID,		LocalID,
								ASLSM_InitialDisplayDepth,	LocalDepth,
								ASLSM_UserData,			TRUE,
								ASLSM_InitialLeftEdge,		Box . Left,
								ASLSM_InitialTopEdge,		Box . Top,
								ASLSM_InitialWidth,		Box . Width,
								ASLSM_InitialHeight,		Box . Height,
							TAG_DONE))
							{
								ResetRequest = FALSE;

								DisplayID	= ScreenModeRequest -> sm_DisplayID;
								Depth		= ScreenModeRequest -> sm_DisplayDepth;

								UpdateModeString();

								LT_SetAttributes(Handle,GAD_MODE,
									GTTX_Text,	DisplayModeBuffer,
								TAG_DONE);
							}
							else
								ResetRequest = TRUE;

							break;
					}
				}

				if(Hide)
					ShutdownWindow();
			}
		}
	}
	while(!Done);

	CloseAll(RETURN_OK);
}
