/*
**	Gui.c
**
**	Copyright (C) 1993,94,95,96,97 Bernardo Innocenti
**
**	Graphic User Interface handling routines.
*/

#include <exec/nodes.h>
#include <exec/memory.h>
#include <intuition/intuition.h>
#include <intuition/intuitionbase.h>
#include <intuition/gadgetclass.h>
#include <intuition/imageclass.h>
#include <utility/tagitem.h>
#include <graphics/rpattr.h>

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/intuition.h>
#include <proto/gadtools.h>
#include <proto/graphics.h>
#include <proto/layers.h>
#include <proto/keymap.h>
#include <proto/utility.h>
#include <proto/commodities.h>
#include <proto/diskfont.h>

#include "XModulePriv.h"
#include "Gui.h"
#include "CustomClasses.h"



XDEF struct Screen		*Scr		= NULL;
XDEF struct ScrInfo		 ScrInfo	= {0};
XDEF APTR				 VisualInfo	= NULL;
XDEF struct DrawInfo	*DrawInfo	= NULL;


XDEF struct TextAttr
	TopazAttr	= { "topaz.font", 8, FS_NORMAL, FPF_ROMFONT },
	ScreenAttr	= { 0 },
	WindowAttr	= { 0 },
	ListAttr	= { 0 },
	EditorAttr	= { 0 };

XDEF struct TextFont
	*TopazFont	= NULL,
	*WindowFont	= NULL,
	*ListFont	= NULL;


/* Window borders layout information */
XDEF UWORD				 OffX, OffY;				/* X and Y offsets for window rendering	*/
XDEF WORD				 SizeWidth = 18,			/* Dimensions of the window size gadget	*/
						 SizeHeight = 10;

/* IDCMP windows support */
XDEF ULONG				 IDCMPSig = 0;				/* Signal for above mentioned port		*/
XDEF ULONG				 Signals = SIGBREAKFLAGS;	/* Signals for Wait() in main loop		*/
XDEF struct IntuiMessage IntuiMsg;					/* A copy of the last received IntuiMsg	*/
XDEF struct List		 WindowList;				/* Linked list of all open windows		*/

static UBYTE			 ActiveKey	= 0;			/* These three are used to handle		*/
static struct Gadget	*ActiveGad	= NULL;			/*		selection of button gadgets		*/
static struct Window	*ActiveWin	= NULL;			/*		with keyboard shortcuts.		*/
static struct Window *OldPrWindowPtr = (struct Window *)1L;

XDEF LONG	LastErr				= 0;
XDEF ULONG	UniqueID;						/* An ID got from GetUniqueID()				*/
XDEF UWORD	WinLockCount		= 0;		/* Allow nesting of window locking			*/
XDEF BOOL	Quit				= FALSE;
XDEF BOOL	DoNextSelect		= TRUE;		/* Menu selection, see menu handling code	*/
XDEF BOOL	ShowRequesters		= TRUE;
XDEF BOOL	OwnScreen			= FALSE;	/* Are we owners or visitors?				*/
XDEF BOOL	ScreenReopening		= FALSE;	/* Set to TRUE while reopening windows		*/
XDEF BOOL	ScreenShutdown		= FALSE;	/* Set to TRUE while shutting down screen	*/
XDEF Class *ScrollButtonClass	= NULL;

/* Shared IDCMP port for all windows */
static struct MsgPort	*WinPort			= NULL;
static Class			*VImageClass		= NULL;
static ULONG			 VImageUseCount		= 0;
static struct Image		*ButtonFrame		= NULL;
static WORD				 ButtonFrameWidth	= 0;
static WORD				 ButtonFrameHeight	= 0;


XDEF struct WDescr WDescr[WID_COUNT] =
{
	{ NULL, 0, 0, (struct TagItem *)ToolBoxWinTags		},
	{ NULL, 0, 0, (struct TagItem *)PrefsWinTags		},
	{ NULL, 0, 0, (struct TagItem *)SongInfoWinTags		},
	{ NULL, 0, 0, (struct TagItem *)InstrumentsWinTags	},
	{ NULL, 0, 0, (struct TagItem *)SequenceWinTags		},
	{ NULL, 0, 0, (struct TagItem *)PatternWinTags		},
	{ NULL, 0, 0, (struct TagItem *)PlayWinTags			},
	{ NULL, 0, 0, (struct TagItem *)SampleWinTags		},
	{ NULL, 0, 0, (struct TagItem *)OptimizationWinTags	},
	{ NULL, 0, 0, (struct TagItem *)SaversWinTags		},
	{ NULL, 0, 0, (struct TagItem *)PattPrefsWinTags	},
	{ NULL, 0, 0, (struct TagItem *)PattSizeWinTags		},
	{ NULL, 0, 0, (struct TagItem *)ClearWinTags		},
	{ NULL, 0, 0, (struct TagItem *)LogWinTags			},
	{ NULL, 0, 0, (struct TagItem *)ProgressWinTags		}
};



XDEF struct GuiSwitches GuiSwitches =
{
	TRUE,			/* SaveIcons		*/
	TRUE,			/* AskOverwrite		*/
	TRUE,			/* AskExit			*/
	TRUE,			/* ShowAppIcon		*/
	FALSE,			/* UseReqTools		*/
	TRUE,			/* SmartRefresh		*/
	TRUE,			/* UseDataTypes		*/
	TRUE,			/* InstrSaveIcons	*/
	TRUE,			/* AskAutosave		*/
	FALSE,			/* DoBackups		*/
	FALSE,			/* LogToFile		*/
	INST_8SVX,		/* InstrSaveMode	*/
	1,				/* SampDrawMode		*/
	XMDMF_NOTE+1,	/* LogLevel			*/
	0,				/* AutosaveTime		*/
	3,				/* BackupVersions	*/
	"*,#",			/* BackupTemplate	*/
	"CON:////XModule Log/AUTO/CLOSE/INACTIVE/SCREEN XMODULE"	/* LogFile */
};



#ifndef OS30_ONLY

/* Wait pointer image data */

static __chip UWORD WaitPointer[] =
{
	0x0000, 0x0000,

	0x0400, 0x07c0,
	0x0000, 0x07c0,
	0x0100, 0x0380,
	0x0000, 0x07e0,
	0x07c0, 0x1ff8,
	0x1ff0, 0x3fec,
	0x3ff8, 0x7fde,
	0x3ff8, 0x7fbe,
	0x7ffc, 0xff7f,
	0x7efc, 0xffff,
	0x7ffc, 0xffff,
	0x3ff8, 0x7ffe,
	0x3ff8, 0x7ffe,
	0x1ff0, 0x3ffc,
	0x07c0, 0x1ff8,
	0x0000, 0x07e0,

	0x0000, 0x0000
};

#endif /* OS30_ONLY */


/* Martin Taillefer's block pointer */
/*
static UWORD __chip BlockPointer[] =
{
	0x0000, 0x0000,

	0x0000, 0x0100,
	0x0100, 0x0280,
	0x0380, 0x0440,
	0x0100, 0x0280,
	0x0100, 0x0ee0,
	0x0000, 0x2828,
	0x2008, 0x5834,
	0x783c, 0x8002,
	0x2008, 0x5834,
	0x0000, 0x2828,
	0x0100, 0x0ee0,
	0x0100, 0x0280,
	0x0380, 0x0440,
	0x0100, 0x0280,
	0x0000, 0x0100,
	0x0000, 0x0000,

	0x0000, 0x0000
};
*/


/* Local function prototypes */

static void					 HandleKey			(void);
static void					 SelectButton		(struct Window *win, struct Gadget *gad);
static void					 DeselectButton		(void);
static void 				 RenderWindowBorders(struct WinUserData *wud);
static struct WindowBorder	*CreateWindowBorder (struct WinUserData *wud, UWORD left, UWORD top, UWORD width, UWORD height, ULONG bordertype);
static struct Gadget		*CreateVImageButton (struct TagItem *tags, struct NewGadget *ng);
static void					 DeleteVImageButton	(struct Gadget *g);
static struct Gadget		*CreateGadgets		(struct LayoutGadgetsArgs *lga, UWORD mode, UWORD left, UWORD top, UWORD width, UWORD height);
static void					 LayoutGadgets		(struct LayoutGadgetsArgs *lga, UWORD mode);
static void					 DeleteGadgets		(struct WinUserData *wud);

static void					 DeleteWUD			(struct WinUserData *wud);
static struct WinUserData	*CreateLayoutInfo	(struct WinUserData *wud);



GLOBALCALL LONG HandleGui (void)

/* Handle XModule GUI - Main event handling loop */
{
	ULONG recsig;	/* Received Signals	*/
	LONG rc = 0;	/* Return Code		*/


	/* This is the main event handling loop */

	while (!Quit)
	{
		recsig = Wait (Signals);

		if (recsig & IDCMPSig)
			HandleIDCMP();

		if (recsig & AudioSig)
			HandleAudio();

		if (recsig & PubPortSig)
			HandleRexxMsg();

		if (recsig & AppSig)
			HandleAppMessage();

		if (recsig & FileReqSig)
			HandleFileRequest();

		if (recsig & CxSig)
			HandleCx();

		if (recsig & AmigaGuideSig)
			HandleAmigaGuide();

		/* Check break signals */
		if (recsig & SIGBREAKFLAGS)
		{
			if (recsig & SIGBREAKF_CTRL_C)
			{
				Quit = TRUE;
				GuiSwitches.AskExit = FALSE;
				rc = ERROR_BREAK;
			}

			if (recsig & SIGBREAKF_CTRL_D)
				if (MyBroker) ActivateCxObj (MyBroker, FALSE);

			if (recsig & SIGBREAKF_CTRL_E)
				if (MyBroker) ActivateCxObj (MyBroker, TRUE);

			if (recsig & SIGBREAKF_CTRL_F)
				DeIconify();
		}


		if (LastErr)
		{
			switch (LastErr)
			{
				case ERROR_NO_FREE_STORE:
					ShowMessage (MSG_NO_FREE_STORE);
					break;

				case ERROR_BREAK:
					ShowMessage (MSG_BREAK);
					break;

				default:
					break;
			}

			DisplayBeep (Scr);
			LastErr = 0;
		}

		if (Quit && GuiSwitches.AskExit)
			if (!ShowRequestArgs (MSG_REALLY_QUIT_XMODULE, MSG_YES_OR_NO, NULL))
			{
				Quit = FALSE;
				rc = 0;
			}

	}	/* End main loop */

	return rc;
}



/* Intuition Event Handler.  Based on GadToolsBox's HandleIDCMP() */
GLOBALCALL void HandleIDCMP (void)
{
	struct IntuiMessage	*m;
	struct MenuItem		*n;
	struct Window		*win;
	struct WinUserData	*wud;

	while (m = GT_GetIMsg (WinPort))
	{
		IntuiMsg = *m;	/* Make a local copy and return message immediately */

		GT_ReplyIMsg (m);

		win = IntuiMsg.IDCMPWindow;
		wud = (struct WinUserData *)win->UserData;

		switch (IntuiMsg.Class)
		{
			case IDCMP_REFRESHWINDOW:

				/* TODO: Handle multiple IDCMP_REFRESHWINDOW
				 * messages sent at the same time.
				 */

				/* Lock Layer in sizeable windows so its size
				 * won't change until we are finished rendering
				 * on it.
				 *
				 * Newsflash: **DON'T!**  Layers BeginUpdate() will already
				 * lock the window layer for us, so doing it again we would
				 * risk a complete GUI deadlock in some particular conditions...
				 */
				/* if (win->Flags & WFLG_SIZEGADGET)
				 *	LockLayer (NULL, win->WLayer);
				 */

				if (wud->WUDFlags & WUDF_JUSTRESIZED)
				{
					// RefreshGadgets (wud->GList, win, NULL);
					GT_RefreshWindow (win, NULL);
					RefreshWindowFrame (win);

					if (wud->Borders)
						RenderWindowBorders (wud);

					wud->WUDFlags &= ~WUDF_JUSTRESIZED;
				}
				else
				{
					GT_BeginRefresh (win);

					if (wud->Borders)
						RenderWindowBorders (wud);

					GT_EndRefresh (win, TRUE);
				}

				/* if (win->Flags & WFLG_SIZEGADGET)
				 *	UnlockLayer (win->WLayer);
				 */
				break;

			case IDCMP_RAWKEY:
				HandleKey ();
				break;

			case IDCMP_NEWSIZE:
			{
				struct Gadget *g;
				struct LayoutGadgetsArgs lga;
				struct RastPort rp;
				ULONG SpecialTags[20];
				UWORD newwidth, newheight;

				/* TODO: Handle multiple IDCMP_NEWSIZE
				 * messages sent at the same time.
				 */

//				LockLayer (NULL, win->WLayer);

				newwidth	= win->Width - win->BorderLeft - win->BorderRight;
				newheight	= win->Height - win->BorderTop - win->BorderBottom;

				if ((wud->WindowSize.Width == newwidth) &&
					(wud->WindowSize.Height == newheight))
				{
//					UnlockLayer (win->WLayer);
					break;
				}

				if (!(wud->WUDFlags & WUDF_CUSTOMLAYOUT))
				{
					/* Detatch and free old gadgets */
					RemoveGList (win, wud->GList, -1);
					DeleteGadgets (wud);

					/* Clear old window region (or what remains of it) */
					SetAPen (win->RPort, 0);
					SetDrMd (win->RPort, JAM1);
					RectFill (win->RPort, win->BorderLeft, win->BorderTop,
						min (wud->WindowSize.Width, newwidth) + win->BorderLeft - 1,
						min (wud->WindowSize.Height, newheight) + win->BorderTop - 1);

					wud->WindowSize.Width = newwidth;
					wud->WindowSize.Height = newheight;


					/* Layout phase 2 */

					InitRastPort (&rp);
					SetFont (&rp, wud->Font);

					if (!CreateContext (&wud->GList))
					{
//						UnlockLayer (win->WLayer);
						DeleteWUD (wud);
						break;
					}

					lga.Args		= wud->LayoutArgs;
					lga.VInfo		= VisualInfo;
					lga.PrevGad		= wud->GList;
					lga.GInfo		= wud->GInfo;
					lga.Wud			= wud;
					lga.DummyRast	= &rp;
					lga.Count		= 0;
					lga.SpecialTags	= SpecialTags;

					SpecialTags[0] = GT_Underscore;
					SpecialTags[1] = (ULONG) '_';

					g = CreateGadgets (&lga, LAYOUTMODE_V, OffX + lga.GInfo[0].LabelWidth + HSPACING, OffY + VSPACING,
						wud->WindowSize.Width - lga.GInfo[0].LabelWidth - HSPACING * 2,
						wud->WindowSize.Height - VSPACING * 2);

					if (!g)
					{
//						UnlockLayer (win->WLayer);
						DeleteWUD (wud);
						break;
					}

					AddGList (win, wud->GList, -1, -1, NULL);
					wud->WUDFlags |= WUDF_JUSTRESIZED;
				}

				if (wud->IDCMPFunc) wud->IDCMPFunc (wud);

//				UnlockLayer (win->WLayer);
				break;
			}

			case IDCMP_CLOSEWINDOW:
				if (wud->WindowID == WID_TOOLBOX)
					Quit = 1;
				else
					MyCloseWindow (wud);

				break;

			case IDCMP_GADGETUP:
			case IDCMP_GADGETDOWN:
			{
				struct Gadget *gad = (struct Gadget *)IntuiMsg.IAddress;

				/* Toggle switch */
				if (wud->GInfo[gad->GadgetID].GKind == CHECKBOX_KIND)
				{
					UWORD *check;

					if (check = (WORD *)(wud->GInfo[gad->GadgetID].SpecialStorage))
						*check ^= 1;
				}

				/* Execute function */
				if (((struct Gadget *)IntuiMsg.IAddress)->UserData)
					((void (*)(struct WinUserData *)) gad->UserData) (wud);

				break;
			}

			case IDCMP_MENUPICK:
				while (IntuiMsg.Code != MENUNULL)
				{
					n = ItemAddress (win->MenuStrip, IntuiMsg.Code);
					((void (*)(struct WinUserData *))(GTMENUITEM_USERDATA(n))) (wud);

					/* Some window operations invalidate the menu
					 * we are working on.  For istance, Re-opening a
					 * window causes the old MenuStrip to be killed.
					 * The DoNextSelect flag provides a way to stop
					 * this loop and avoid a nasty crash.
					 */
					if (!DoNextSelect)
					{
						DoNextSelect = TRUE;
						break;
					}
					IntuiMsg.Code = n->NextSelect;
				}
				break;

			case IDCMP_INACTIVEWINDOW:
				DeselectButton();
				if (wud->IDCMPFunc) wud->IDCMPFunc (wud);
				break;

			case IDCMP_MENUHELP:
			case IDCMP_GADGETHELP:
				HandleHelp (&IntuiMsg);
				break;

			default:
				if (wud->IDCMPFunc) wud->IDCMPFunc (wud);
				break;

		}	/* End switch (IntuiMsg.Class) */

		if (!WinPort) break;

	}	/* End while (GT_GetIMsg ()) */
}



static void HandleKey (void)
{
	struct Window		*win = IntuiMsg.IDCMPWindow;
	struct WinUserData	*wud = (struct WinUserData *)win->UserData;
	UWORD i;
	UBYTE keycode;


	/* Handle key up for buttons */

	if (IntuiMsg.Code & IECODE_UP_PREFIX)
	{
		struct Gadget *gad = ActiveGad;

		DeselectButton();
		if (gad && (ActiveKey == (IntuiMsg.Code & ~IECODE_UP_PREFIX)))
			((void (*)(struct WinUserData *)) gad->UserData) (wud);

		return;
	}

	switch (IntuiMsg.Code)
	{
		case 0x5F:	/* HELP */
			HandleHelp (&IntuiMsg);
			return;

		case CURSORUP:
		case CURSORDOWN:

			if (wud->GInfo)
				for (i = 0; i < wud->GCount; i++)
					if (wud->GInfo[i].GKind == LISTVIEW_KIND)
					{
						struct Gadget *g = wud->Gadgets[i];
						LONG selected, oldselected, top = ~0;

#ifndef OS30_ONLY
						if (GadToolsBase->lib_Version < 39)
							selected = (LONG)(*(UWORD *)(((char *)g)+sizeof(struct Gadget)+48));
							/* top = *(short *)(((char *)gad) + sizeof(struct Gadget) + 6); +4 ? */
						else
#endif /* !OS30_ONLY */
							GT_GetGadgetAttrs (g, win, NULL,
								GTLV_Selected,	&selected,
								GTLV_Top,		&top,
								TAG_DONE);

						selected = (LONG)((WORD) selected);	/* Extend to long */
						oldselected = selected;	/* Make a backup of it */

						if (selected == ~0)
							selected = top;	/* Scroll Top */
						else
							top = ~0;

						if (IntuiMsg.Code == CURSORUP)
						{
							if (IntuiMsg.Qualifier & IEQUALIFIER_SHIFT)
								selected -= 5;
							else if (IntuiMsg.Qualifier & IEQUALIFIER_ALT)
								selected = 0;
							else
								selected--;
						}
						else /* CURSORDOWN */
						{
							if (IntuiMsg.Qualifier & IEQUALIFIER_SHIFT)
								selected += 5;
							else if (IntuiMsg.Qualifier & IEQUALIFIER_ALT)
								selected = 65535;
							else
								selected++;
						}

						if (selected < 0) selected = 0;

						GT_SetGadgetAttrs (g, win, NULL,
							(top == ~0) ? GTLV_Selected : GTLV_Top,			selected,
							(top == ~0) ? GTLV_MakeVisible : TAG_IGNORE,	selected,
							TAG_DONE);

#ifndef OS30_ONLY
						if (GadToolsBase->lib_Version < 39)
							selected = (LONG)(*(UWORD *)(((char *)g)+sizeof(struct Gadget)+48));
						else
#endif /* !OS30_ONLY */
							GT_GetGadgetAttrs (g, win, NULL,
								GTLV_Selected,	&selected,
								TAG_DONE);

						if (selected != oldselected)
						{
							IntuiMsg.Code = selected;
							if (g->UserData)
								((void (*)(struct WinUserData *)) g->UserData) (wud);
							break; /* Stop for() loop */
						}
					}	/* End for */
			return;

		case 0x42:	/* TAB */
			if (IntuiMsg.Qualifier & IEQUALIFIER_ALT)
			{
				struct WinUserData *nextwud;

				if (IntuiMsg.Qualifier & IEQUALIFIER_SHIFT)
				{
					/* ALT+SHIFT+TAB: Cycle windows backwards */

					nextwud = (struct WinUserData *)wud->Link.mln_Pred;

					if (!(nextwud->Link.mln_Pred))	/* List head? */
						nextwud = (struct WinUserData *)WindowList.lh_TailPred;
				}
				else
				{
					/* ALT+TAB: Cycle windows */

					nextwud = (struct WinUserData *)wud->Link.mln_Succ;

					if (!(nextwud->Link.mln_Succ))	/* List tail? */
						nextwud = (struct WinUserData *)WindowList.lh_Head;
				}

				RevealWindow (nextwud);
				return;
			}

		default:
			break;

	} /* End switch (IntuiMsg.Code) */


	/*	Convert the IDCMP_RAWKEY IntuiMessage to the single
	 *	character representation it corresponds to. If this isn't
	 *	possible (e.g. a HELP key or cursor key) then abort.
	 */
	{
		static struct InputEvent ie;

		ie.ie_NextEvent		= NULL;
		ie.ie_Class			= IECLASS_RAWKEY;
		ie.ie_SubClass		= 0;
		ie.ie_Code			= IntuiMsg.Code;
		ie.ie_Qualifier		= IntuiMsg.Qualifier & IEQUALIFIER_CONTROL;	/* Filter qualifiers. */
		ie.ie_EventAddress	= (APTR *) *((ULONG *)IntuiMsg.IAddress);
		if (MapRawKey (&ie, &keycode, 1, NULL) != 1)
			return;
	}


	/* Handle IDCMP_VANILLAKEY */

	/* Check special keys */
	switch (keycode)
	{
		case 0x03:	/* CTRL-C */
			Signal ((struct Task *)ThisTask, SIGBREAKF_CTRL_C);
			return;

		case 0x09:	/* TAB */
		case 0x0D:	/* RETURN */
			if (wud->GInfo)
				for (i = 0; i < wud->GCount; i++)
					if (wud->GInfo[i].GKind == STRING_KIND || wud->GInfo[i].GKind == INTEGER_KIND)
						ActivateGadget (wud->Gadgets[i],win, NULL);
			return;

		case 0x1B:	/* ESC */
			if (wud->WindowID != WID_TOOLBOX)
				MyCloseWindow (wud);
			return;

		default:
			break;
	}


	/* Look for gadget shortcuts */

	if (wud->GInfo)
		for (i = 0; i < wud->GCount; i++)
		{
			if (wud->Keys[i] == keycode)	/* Case insensitive compare */
			{
				struct Gadget *g = wud->Gadgets[i];
				LONG disabled = FALSE;

				/* Check disabled */

#ifndef OS30_ONLY
				if (GadToolsBase->lib_Version < 39)
					disabled = g->Flags & GFLG_DISABLED;
				else
#endif /* !OS30_ONLY */
					GT_GetGadgetAttrs (g, win, NULL,
						GA_Disabled, &disabled,
						TAG_DONE);

				if (disabled) break;	/* Stop for() loop */

				switch (wud->GInfo[i].GKind)
				{
					case BUTTON_KIND:
						if (!(IntuiMsg.Qualifier & IEQUALIFIER_REPEAT))
							SelectButton (win, g);
						break;

					case CHECKBOX_KIND:

						/* Toggle switch */
						if (wud->GInfo[i].SpecialStorage)
							*((UWORD *)wud->GInfo[i].SpecialStorage) ^= 1;

						GT_SetGadgetAttrs (g, win, NULL,
							GTCB_Checked, !(g->Flags & GFLG_SELECTED),
							TAG_DONE);

						if (g->UserData)
							((void (*)(struct WinUserData *)) g->UserData) (wud);
						break;

					case INTEGER_KIND:
					case STRING_KIND:
						ActivateGadget (g, win, NULL);
						break;

					case CYCLE_KIND:
#ifndef OS30_ONLY
						if (GadToolsBase->lib_Version >= 39)
#endif /* !OS30_ONLY */
						{
							LONG act, max;
							UBYTE **lab;

							/* ON V37: active = *(short *)(((char *)gad) + sizeof(struct Gadget) + 6); */

							GT_GetGadgetAttrs (g, win, NULL,
								GTCY_Active, &act,
								GTCY_Labels, &lab,
								TAG_DONE);

							act = (LONG)((UWORD)act);	/* Extend to LONG */

							if (IntuiMsg.Qualifier & IEQUALIFIER_SHIFT)
								act--;
							else
								act++;

							for (max = 0; lab[max]; max++);	/* Count labels */

							if (act >= max) act = 0;
							else if (act < 0) act = max - 1;

							GT_SetGadgetAttrs (g, win, NULL,
								GTCY_Active, act,
								TAG_DONE);

							if (g->UserData)
							{
								IntuiMsg.Code = act;
								((void (*)(struct WinUserData *)) g->UserData) (wud);
							}
						}
						break;

					case MX_KIND:
					{
						LONG act;

#ifndef OS30_ONLY
						if (GadToolsBase->lib_Version < 39)
							act = (LONG)(*(UWORD *)(((char *)g)+sizeof(struct Gadget)+24));	/* 38? */
						else
#endif /* !OS30_ONLY */
						{
							GT_GetGadgetAttrs (g, win, NULL,
								GTMX_Active, &act,
								TAG_DONE);
							act = (LONG)((UWORD)act);	/* Extend to LONG */
						}

						if (IntuiMsg.Qualifier & IEQUALIFIER_SHIFT)
							act--;
						else
							act++;

						GT_SetGadgetAttrs (g, win, NULL,
							GTMX_Active, act,
							TAG_DONE);

#ifndef OS30_ONLY
						if (GadToolsBase->lib_Version < 39)
							act = (LONG)(*(UWORD *)(((char *)g)+sizeof(struct Gadget)+24));
						else
#endif /* !OS30_ONLY */
						{
							GT_GetGadgetAttrs (g, win, NULL,
								GTMX_Active, &act,
								TAG_DONE);
							act = (LONG)((UWORD)act);	/* Extend to LONG */
						}

						if (g->UserData)
						{
							IntuiMsg.Code = act;
							((void (*)(struct WinUserData *)) g->UserData) (wud);
						}

						break;
					}

					case SLIDER_KIND:
#ifndef OS30_ONLY
						if (GadToolsBase->lib_Version >= 39)
#endif /* !OS30_ONLY */
						{
							LONG min, max, level;

							GT_GetGadgetAttrs (g, win, NULL,
								GTSL_Min, &min,
								GTSL_Max, &max,
								GTSL_Level, &level,
								TAG_DONE);

							/* Extend to LONG */
							min = (LONG)((WORD)min);
							max = (LONG)((WORD)max);
							level = (LONG)((WORD)level);

							if (IntuiMsg.Qualifier & IEQUALIFIER_SHIFT)
							{
								if (IntuiMsg.Qualifier & IEQUALIFIER_ALT)
									level = min;
								else level--;
							}
							else
							{
								if (IntuiMsg.Qualifier & IEQUALIFIER_ALT)
									level = max;
								else level++;
							}

							if (level > max) level = max;
							if (level < min) level = min;

							GT_SetGadgetAttrs (g, win, NULL,
								GTSL_Level, level,
								TAG_DONE);

							if (g->UserData)
							{
								IntuiMsg.Code = level;
								((void (*)(struct WinUserData *)) g->UserData) (wud);
							}
						}
						break;

					default:
						break;
				}

				return; /* Stop for() loop */
			}
		}	/* End for() */


	/* There is no apparent use for this key event,
	 * let's pass the IntuiMessage to user's IDCMPFunc()...
	 */
	if (wud->IDCMPFunc) ((void (*)(void)) wud->IDCMPFunc) ();
}



static void SelectButton (struct Window *win, struct Gadget *gad)

/* Selects the button gadget <gad>.  This operation is illegal with
 * GadTools gadgets, but many programs do it anyway, so this trick
 * will probably be supported in future OS releases :-).
 */
{
	UWORD gadpos;

	if (ActiveGad) DeselectButton();

	gadpos = RemoveGadget (win, gad);

	gad->Flags |= GFLG_SELECTED;
	AddGadget (win, gad, gadpos);
	RefreshGList (gad, win, NULL, 1);

	ActiveKey = IntuiMsg.Code;
	ActiveGad = gad;
	ActiveWin = win;
}



static void DeselectButton (void)

/* Deselects the button previously selected with SelectButton() */
{
	if	(ActiveGad)
	{
		UWORD gadpos = RemoveGadget (ActiveWin, ActiveGad);

		ActiveGad->Flags &= ~GFLG_SELECTED;
		AddGadget (ActiveWin, ActiveGad, gadpos);
		RefreshGList (ActiveGad, ActiveWin, NULL, 1);

		ActiveGad = NULL;
	}
}



GLOBALCALL void LockWindows (void)

/* Disable user input in all windows */
{
	struct WinUserData	*wud;
	struct Window		*win;
	struct WindowLock	*lock;


	/* Are the windows already locked? */
	WinLockCount++;
	if (WinLockCount > 1) return;

	for (wud = (struct WinUserData *) WindowList.lh_Head;
		wud->Link.mln_Succ;
		wud = (struct WinUserData *)wud->Link.mln_Succ)
	{
		if (!(win = wud->Win)) continue;

		/* Set wait pointer */
#ifndef OS30_ONLY
		if (IntuitionBase->LibNode.lib_Version < 39)
			SetPointer (win, WaitPointer, 16, 16, -6, 0);
		else
#endif /* !OS30_ONLY */
			SetWindowPointer (win, WA_BusyPointer, TRUE, TAG_DONE);

		/* Do not block input in Progress window */
		if (wud->WindowID == WID_PROGRESS) continue;

		/* Set an invisible Requester in window to block user input.
		 * We allocate 4 more bytes after the requester structure to store
		 * the IDCMP flags before modifying them.  MEMF_PUBLIC is used
		 * because intuition is going to process the Requester structure.
		 */

		if (!(lock = AllocPooled (Pool, sizeof (struct WindowLock))))
			continue;

		InitRequester (&lock->Req);
		lock->Req.Flags = SIMPLEREQ | NOREQBACKFILL;

		/* Disable window resizing */
		if (win->Flags & WFLG_SIZEGADGET)
		{
			lock->OldMinWidth	= win->MinWidth;
			lock->OldMinHeight	= win->MinHeight;
			lock->OldMaxWidth	= win->MaxWidth;
			lock->OldMaxHeight	= win->MaxHeight;
			WindowLimits (win, win->Width, win->Height,
				win->Width, win->Height);
		}

		/* Disable IDCMP messages except IDCMP_REFRESHWINDOW events.
		 * WARNING: ModifyIDCMP (win, 0) would free the shared port!!
		 */
		lock->OldIDCMPFlags = win->IDCMPFlags;
		ModifyIDCMP (win, IDCMP_REFRESHWINDOW);

		Request (&lock->Req, win);
	}
}



GLOBALCALL void UnlockWindows (void)

/* Restore user input in all windows. */
{
	struct WinUserData	*wud;
	struct Window		*win;
	struct WindowLock	*lock;

	/* Make sure windows arn't unlocked already */
	if (WinLockCount) return;

	/* Check lock nesting */
	WinLockCount--;
	if (WinLockCount) return;

	for (wud = (struct WinUserData *) WindowList.lh_Head;
		wud->Link.mln_Succ;
		wud = (struct WinUserData *)wud->Link.mln_Succ)
	{
		if (!(win = wud->Win)) continue;

		if (lock = (struct WindowLock *) win->FirstRequest)
		{
			/* Restore old window IDCMP */
			ModifyIDCMP (win, lock->OldIDCMPFlags);

			/* Re-enable window sizing and restore old window limits */
			if (win->Flags & WFLG_SIZEGADGET)
				WindowLimits (win, lock->OldMinWidth, lock->OldMinHeight,
					lock->OldMaxWidth, lock->OldMaxHeight);

			EndRequest (&lock->Req, wud->Win);
			FreePooled (Pool, lock, sizeof (struct WindowLock));
		}

		/* Restore standard pointer */
#ifndef OS30_ONLY
		if (IntuitionBase->LibNode.lib_Version < 39)
			ClearPointer (win);
		else
#endif /* !OS30_ONLY */
			SetWindowPointer (win, TAG_DONE);
	}
}



GLOBALCALL void RevealWindow (struct WinUserData *wud)
{
	WindowToFront (wud->Win);
	ActivateWindow (wud->Win);

	/* Make the window visible on the screen */

#ifndef OS30_ONLY
	if (IntuitionBase->LibNode.lib_Version >= 39)
#endif /* OS30_ONLY */
		ScreenPosition (Scr, SPOS_MAKEVISIBLE,
			wud->Win->LeftEdge, wud->Win->TopEdge,
			wud->Win->LeftEdge + wud->Win->Width - 1,
			wud->Win->TopEdge + wud->Win->Height - 1);
}



GLOBALCALL void SetGadgets (struct WinUserData *wud, LONG arg, ...)

/* Update status of gadgets in the window associated to <wud>.
 * <arg> is the first of a -1 terminated array of commands.
 * Each command is represented by a pair of LONGs, where the
 * first LONG is the gadget number, and the second is the value
 * to set for that gadget, depending on the gadget type.
 */
{
	LONG *cmd = &arg;

	static ULONG actions[] =
	{
		TAG_IGNORE,		/* GENERIC_KIND		*/
		TAG_IGNORE,		/* BUTTON_KIND		*/
		GTCB_Checked,	/* CHECKBOX_KIND	*/
		GTIN_Number,	/* INTEGER_KIND		*/
		GTLV_Selected,	/* LISTVIEW_KIND	*/
		GTMX_Active,	/* MX_KIND			*/
		GTNM_Number,	/* NUMBER_KIND		*/
		GTCY_Active,	/* CYCLE_KIND		*/
		GTPA_Color,		/* PALETTE_KIND		*/
		TAG_IGNORE,		/* SCROLLER_KIND	*/
		TAG_IGNORE,		/* -- reserved --	*/
		GTSL_Level,		/* SLIDER_KIND		*/
		GTST_String,	/* STRING_KIND		*/
		GTTX_Text		/* TEXT_KIND		*/
	};

	while (*cmd != -1)
	{
		GT_SetGadgetAttrs (wud->Gadgets[*cmd], wud->Win, NULL,
			actions[wud->GInfo[*cmd].GKind], *(cmd+1),
			TAG_DONE);

		cmd += 2;
	}
}



GLOBALCALL LONG AddListViewNode (struct List *lv, CONST_STRPTR label, ...)

/* Var-args stub for AddListViewNodeA */
{
	return AddListViewNodeA (lv, label, (LONG *) (&label)+1);
}



GLOBALCALL LONG AddListViewNodeA (struct List *lv, CONST_STRPTR label, LONG *args)

/* Allocate and add a new node to a ListView list.  The label
 * is printf()-formatted and copied to a buffer just after the
 * node structure.  Call RemListViewNode() to deallocate the node.
 *
 * RETURNS
 *   0 for failure (no memory), any other value for success.
 */
{
	struct Node *n;
	UBYTE buf[256];

	if (args)
	{
		VSPrintf (buf, label, args);
		label = buf;
	}

	if (!(n = AllocVecPooled (Pool, sizeof (struct Node) + strlen (label) + 1)))
		return FALSE;

	n->ln_Name = ((UBYTE *)n) + sizeof (struct Node);

	strcpy (n->ln_Name, label);
	n->ln_Pri = 0;	/* Selected */

	ADDTAIL (lv, n);

	return TRUE;
}



GLOBALCALL void RemListViewNode (struct Node *n)
{
	REMOVE (n);
	FreeVecPooled (Pool, n);
}




GLOBALCALL struct Image *NewImageObject (ULONG which)

/* Creates a sysiclass object. */
{
	return ((struct Image *)NewObject (NULL, SYSICLASS,
		SYSIA_DrawInfo,	DrawInfo,
		SYSIA_Which,	which,
		SYSIA_Size,		Scr->Flags & SCREENHIRES ? SYSISIZE_MEDRES : SYSISIZE_LOWRES,
		TAG_DONE));

	/* NB: SYSISIZE_HIRES not yet supported. */
}



static struct Gadget *CreateVImageButton (struct TagItem *tags, struct NewGadget *ng)

/* This routine is called by the layout engine to create an IMAGEBUTTON_KIND gadget. */
{
	struct Gadget	*VButton;
	struct Image	*VImage;

	if (!VImageClass)
	{
		if (VImageClass = InitVImageClass ())
		{
			if (ButtonFrame = NewObject (NULL, FRAMEICLASS,
				IA_FrameType,	FRAME_BUTTON,
				IA_EdgesOnly,	TRUE,
				TAG_DONE))
			{
				struct IBox FrameBox, ContentsBox = { 0, 0, 0, 0 };

				DoMethod ((Object *)ButtonFrame, IM_FRAMEBOX, &ContentsBox, &FrameBox, DrawInfo, 0);

				ButtonFrameWidth = FrameBox.Width;
				ButtonFrameHeight = FrameBox.Height;
			}
			else
			{
				FreeVImageClass (VImageClass);
				VImageClass = NULL;
				return NULL;
			}
		}
		else return NULL;
	}

	if (!(VImage = (struct Image *)NewObject (VImageClass, NULL,
		IA_Width,		ng->ng_Width - ButtonFrameWidth,
		IA_Height,		ng->ng_Height - ButtonFrameHeight,
		SYSIA_Which,	ng->ng_Flags,
		TAG_DONE)))
		return NULL;

	if (!(VButton = (struct Gadget *)NewObject (NULL, FRBUTTONCLASS,
		GA_ID,			ng->ng_GadgetID,
		GA_UserData,	ng->ng_UserData,
		GA_Left,		ng->ng_LeftEdge,
		GA_Top,			ng->ng_TopEdge,
		GA_Image,		ButtonFrame,
		GA_LabelImage,	VImage,
		GA_RelVerify,	TRUE,
		TAG_DONE)))
		DisposeObject (VImage);
	else
		VImageUseCount++;

	return VButton;
}



static void DeleteVImageButton (struct Gadget *g)
{
	if (g)
	{
		DisposeObject (g->GadgetText);
		DisposeObject (g);

		VImageUseCount--;

		if (!VImageUseCount)
		{
			if (ButtonFrame)
			{
				DisposeObject (ButtonFrame);
				ButtonFrame = NULL;
			}

			if (VImageClass)
			{
				FreeVImageClass (VImageClass);
				VImageClass = NULL;
			}
		}
	}
}



/* Gadget definition format:
 *
 * VGROUP_KIND,			BorderType,
 * HGROUP_KIND,			BorderType,
 * IMAGEBUTTON_KIND,	ClickedFunction, ImageType, Tag1, ...,
 * CHECKBOX_KIND,		ClickedFunction, Label, Storage (UWORD *), Tag1, ...,
 * BUTTON_KIND,			ClickedFunction, Label, Tag1, ...,
 * INTEGER_KIND,		ClickedFunction, Label, MaxDigits, Tag1, ...,
 * STRING_KIND,			ClickedFunction, Label, MaxChars, Tag1, ...,
 * TEXT_KIND,			Label, NumChars, Tag1, ...,
 * NUMBER_KIND,			Label, MaxDigits, Tag1, ...,
 * LISTVIEW_KIND,		ClickedFunction, Label, Labels (struct List *), Tag1, ...,
 * MX_KIND,				ClickedFunction, Label, Labels (ULONG *), Tag1, ...,
 * CYCLE_KIND,			ClickedFunction, Label, Labels (ULONG *), Tag1, ...,
 * SLIDER_KIND,			ClickedFunction, Label, Min, Max, LevelFormat (UBYTE *), MaxLevelLen (ULONG), Tag1, ...,
 * PALETTE_KIND,		ClickedFunction, Label, Tags,
 * ENDGROUP_KIND,
 */


static void LayoutGadgets (struct LayoutGadgetsArgs *lga, UWORD mode)
{
	struct GInfo		*ginfo,
						*myginfo = &lga->GInfo[lga->Count];
	STRPTR label;


	/* Skip this group */
	lga->Count++;

	if (mode == LAYOUTMODE_V)
		myginfo->Flags |= GIF_FIXEDWIDTH;
	else
		myginfo->Flags |= GIF_FIXEDHEIGHT;

	while (*lga->Args != ENDGROUP_KIND)
	{
		ginfo = &lga->GInfo[lga->Count];
		label = NULL;

		switch (ginfo->GKind = *lga->Args++)
		{
			case HGROUP_KIND:
			case VGROUP_KIND:
				if (*lga->Args++) /* Border? */
				{
					LayoutGadgets (lga, (ginfo->GKind == HGROUP_KIND) ? LAYOUTMODE_H : LAYOUTMODE_V);

					/* Add border dimensions to group size */
					ginfo->MinWidth += HSPACING * 8;
					ginfo->Fixed.Width += HSPACING * 8;
					ginfo->MinHeight += VSPACING * 8;
					ginfo->Fixed.Height += VSPACING * 8;
					ginfo->Flags |= GIF_HASBORDER;
				}
				else LayoutGadgets (lga, (ginfo->GKind == HGROUP_KIND) ? LAYOUTMODE_H : LAYOUTMODE_V);
				break;

			case IMAGEBUTTON_KIND:
				lga->Args++;	/* Skip Clicked function */
				lga->Args++;	/* Skip Image */
				ginfo->MinWidth		= lga->Wud->Font->tf_XSize * 3 + 2;
				ginfo->MinHeight	= lga->Wud->Font->tf_YSize + 4;
				ginfo->Flags |= GIF_FIXEDWIDTH | GIF_FIXEDHEIGHT;
				break;

			case CHECKBOX_KIND:
				lga->Args++;	/* Skip Clicked function */
				label = STR(*lga->Args++);
				ginfo->SpecialStorage = (void *)*lga->Args++;	/* Record storage */
				ginfo->MinWidth		= lga->Wud->Font->tf_XSize * 2 + 8;
				ginfo->MinHeight	= lga->Wud->Font->tf_YSize + 4;
				ginfo->LabelWidth	= TextLength (lga->DummyRast, label, strlen (label)) + LABELSPACING;
				ginfo->Flags		= GIF_FIXEDWIDTH | GIF_FIXEDHEIGHT;
				break;

			case BUTTON_KIND:
				lga->Args++;	/* Skip Clicked function */
				label = STR(*lga->Args++);
				ginfo->MinWidth		= TextLength (lga->DummyRast, label, strlen (label)) + LABELSPACING;
				ginfo->MinHeight	= lga->Wud->Font->tf_YSize + 4;
				ginfo->Flags		= GIF_FIXEDHEIGHT;
				break;

			case INTEGER_KIND:
			case STRING_KIND:
			{
				UWORD maxchars;

				lga->Args++;	/* Skip Clicked function */
				if (label = STR (*lga->Args++))
					ginfo->LabelWidth	= TextLength (lga->DummyRast, label, strlen (label)) + LABELSPACING;

				maxchars = *lga->Args++;

				ginfo->MinWidth		= lga->Wud->Font->tf_XSize * min (maxchars + 1, 8) + 12;
				ginfo->MinHeight	= lga->Wud->Font->tf_YSize + 6;
				ginfo->Flags		= GIF_FIXEDHEIGHT;

				break;
			}

			case LISTVIEW_KIND:
				lga->Args++;	/* Skip Clicked function */
				ginfo->MinWidth		= lga->Wud->Font->tf_XSize * 16 + 16;
				ginfo->MinHeight	= lga->Wud->Font->tf_YSize * 4 + 4;
				if (label = STR (*lga->Args++))
					ginfo->LabelWidth	= TextLength (lga->DummyRast, label, strlen (label)) + LABELSPACING;
				ginfo->SpecialStorage = (void *)*lga->Args++;	/* Record List pointer */
				break;

			case MX_KIND:
			case CYCLE_KIND:
			{
				ULONG	*labels, cnt = 0;
				STRPTR	str;

				lga->Args++;	/* Skip Clicked function */

				if (label = STR(*lga->Args++))
					ginfo->LabelWidth	= TextLength (lga->DummyRast, label, strlen (label)) + LABELSPACING;

				ginfo->MinWidth	= ginfo->MinHeight = 0;


				/* Count labels */

				labels = (ULONG *) *lga->Args++;
				while (labels[cnt]) cnt++;


				/* Allocate and fill-in MX/Cycle labels array */

				if (!ginfo->SpecialStorage)
					 ginfo->SpecialStorage = AllocVecPooled (Pool, (cnt + 1) * sizeof (STRPTR));

				if (ginfo->SpecialStorage)
				{
					ULONG i;

					for (i = 0; i < cnt; i++)
					{
						/* Store localized label */
						((STRPTR *)(ginfo->SpecialStorage))[i] = str = STR (labels[i]);

						/* Calculate maximum width */
						ginfo->MinWidth = max (ginfo->MinWidth, TextLength (lga->DummyRast, str, strlen (str)) + LABELSPACING);

						if (ginfo->GKind == MX_KIND)
							ginfo->MinHeight += lga->Wud->Font->tf_YSize + 2;
					}

					/* Terminate labels array */
					((STRPTR *)(ginfo->SpecialStorage))[i] = NULL;
				}

				/* add width of radio button (or cycle image) */
				ginfo->MinWidth += lga->Wud->Font->tf_XSize * 2 + 4;

				if (ginfo->GKind == MX_KIND)
					ginfo->Flags = GIF_FIXEDWIDTH | GIF_FIXEDHEIGHT;
				else
				{
					ginfo->Flags = GIF_FIXEDHEIGHT;
					ginfo->MinHeight += lga->Wud->Font->tf_YSize + 4;
				}

				break;
			}

			case NUMBER_KIND:
			case TEXT_KIND:

				if (label = STR (*lga->Args++))
					ginfo->LabelWidth	= TextLength (lga->DummyRast, label, strlen (label)) + LABELSPACING;

				ginfo->MinWidth		= lga->Wud->Font->tf_XSize * (*lga->Args++) + 8;
				ginfo->MinHeight	= lga->Wud->Font->tf_YSize + 4;
				ginfo->Flags		= GIF_FIXEDHEIGHT;
				break;

			case SLIDER_KIND:
				lga->Args++;	/* Skip Clicked function */

				if (label = STR (*lga->Args++))
					ginfo->LabelWidth	= TextLength (lga->DummyRast, label, strlen (label)) + LABELSPACING;

				lga->Args += 3;	/* Skip Min, Max and LevelFormat */

				if (ginfo->SpecialStorage = (APTR)((*lga->Args++) * lga->Wud->Font->tf_XSize))
					ginfo->SpecialStorage = (APTR) ((ULONG)ginfo->SpecialStorage + 4);

				ginfo->MinWidth		= lga->Wud->Font->tf_XSize * 8 + 8 + (ULONG)ginfo->SpecialStorage;
				ginfo->MinHeight	= lga->Wud->Font->tf_YSize;
				ginfo->Flags		= GIF_FIXEDHEIGHT;
				break;

			case PALETTE_KIND:
				lga->Args++;	/* Skip Clicked function */

				if (label = STR (*lga->Args++))
					ginfo->LabelWidth	= TextLength (lga->DummyRast, label, strlen (label)) + LABELSPACING;

				ginfo->MinWidth		= lga->Wud->Font->tf_XSize * 6 + 8;
				ginfo->MinHeight	= lga->Wud->Font->tf_YSize * 3 + 4;
				break;
		}

		if ((ginfo->GKind != HGROUP_KIND) && (ginfo->GKind != VGROUP_KIND))
		{
			UBYTE *c;

			/* Go to next gadget (ie: skip tags until TAG_END) */
			while (*lga->Args) lga->Args += 2;
			lga->Args++;

			/* Look for the key equivalent of this gadget. */
			if (c = label)
				for ( ; *c ; c++)
					if (*c == '_')
					{
						/* Found! Now store in the key array */
						lga->Wud->Keys[lga->Count] = *(++c) | (1<<5);	/* Lower case */
						break;
					}
		}

		if (mode == LAYOUTMODE_V)
		{
			myginfo->MinHeight += ginfo->MinHeight + VSPACING;
			if (ginfo->Flags & GIF_HASBORDER)
				myginfo->MinWidth	= max (myginfo->MinWidth, ginfo->MinWidth + ginfo->LabelWidth);
			else
			{
				myginfo->MinWidth	= max (myginfo->MinWidth, ginfo->MinWidth);
				myginfo->LabelWidth	= max (myginfo->LabelWidth, ginfo->LabelWidth);
			}

			myginfo->Fixed.Height += VSPACING;
			if (ginfo->Flags & GIF_FIXEDHEIGHT)
				myginfo->Fixed.Height += ginfo->MinHeight;
			if (!(ginfo->Flags & GIF_FIXEDWIDTH))
				myginfo->Flags &= ~GIF_FIXEDWIDTH;
		}
		else	/* LAYOUTMODE_H */
		{
			myginfo->MinWidth	+= ginfo->MinWidth + ginfo->LabelWidth + HSPACING;
			myginfo->MinHeight	= max (myginfo->MinHeight, ginfo->MinHeight);
			myginfo->Fixed.Width += ginfo->LabelWidth + HSPACING;
			if (ginfo->Flags & GIF_FIXEDWIDTH)
				myginfo->Fixed.Width += ginfo->MinWidth;
			if (!(ginfo->Flags & GIF_FIXEDHEIGHT))
				myginfo->Flags &= ~GIF_FIXEDHEIGHT;
		}

		lga->Count++;

	}	/* End while (*lga->Args != ENDGROUP_KIND) */


	if (mode == LAYOUTMODE_H)
	{
		/* Remove extra HSPACING after last child */
		myginfo->MinWidth		-= HSPACING;
		myginfo->Fixed.Width	-= HSPACING;

		if (!(myginfo->Flags & GIF_HASBORDER))
		{
			/* Align first child of HGROUP_KIND with other
			 * gadgets in our parent group
			 */
			myginfo->LabelWidth		= (myginfo+1)->LabelWidth;
			(myginfo+1)->LabelWidth = 0;
			myginfo->MinWidth		-= myginfo->LabelWidth;
			myginfo->Fixed.Width	-= myginfo->LabelWidth;
		}
	}
	else /* LAYOUTMODE_V */
	{
		/* Remove extra VSPACING after last child */
		myginfo->MinHeight		-= VSPACING;
		myginfo->Fixed.Height	-= VSPACING;
	}

	if (myginfo->MinWidth <= myginfo->Fixed.Width)
		myginfo->Flags |= GIF_FIXEDWIDTH;
	if (myginfo->MinHeight <= myginfo->Fixed.Height)
		myginfo->Flags |= GIF_FIXEDHEIGHT;

	lga->Args++;	/* Skip ENDGROUP_KIND		*/
	lga->Count--;	/* Take back one position	*/
}



static struct Gadget *CreateGadgets (struct LayoutGadgetsArgs *lga, UWORD mode, UWORD left, UWORD top, UWORD width, UWORD height)
{
	struct GInfo		*ginfo,
						*myginfo	= &lga->GInfo[lga->Count];
	struct TagItem		*Tags, *tmp;
	ULONG				 stc;
	struct NewGadget	 ng;
	BOOL				 boopsi;


	/* Skip this group */
	lga->Count++;


	while (*lga->Args != ENDGROUP_KIND)
	{
		ginfo = &lga->GInfo[lga->Count];
		Tags = (struct TagItem *)lga->SpecialTags;
		stc = 2;
		boopsi = FALSE;

		ng.ng_TextAttr		= lga->Wud->Attr;
		ng.ng_GadgetID		= lga->Count;
		ng.ng_VisualInfo	= lga->VInfo;
		ng.ng_Flags			= PLACETEXT_LEFT;

		if (mode == LAYOUTMODE_V)
		{
			if (ginfo->Flags & GIF_FIXEDWIDTH)
				ng.ng_Width = ginfo->MinWidth;
			else
				ng.ng_Width = width - ((ginfo->Flags & GIF_HASBORDER) ? ginfo->LabelWidth : 0);

			if (ginfo->Flags & GIF_FIXEDHEIGHT)
				ng.ng_Height = ginfo->MinHeight;
			else
				ng.ng_Height = (((ULONG)(height - myginfo->Fixed.Height)) * ((ULONG)ginfo->MinHeight)) / ((ULONG)(myginfo->MinHeight - myginfo->Fixed.Height));

			ng.ng_LeftEdge	= left + ((ginfo->Flags & GIF_HASBORDER) ? ginfo->LabelWidth : 0);
			ng.ng_TopEdge	= top;
			top = ng.ng_TopEdge + ng.ng_Height + VSPACING;
		}
		else	/* mode == LAYOUTMODE_H */
		{
			if (ginfo->Flags & GIF_FIXEDWIDTH)
				ng.ng_Width = ginfo->MinWidth;
			else
				ng.ng_Width = (((ULONG)(width - myginfo->Fixed.Width)) * ((ULONG)ginfo->MinWidth)) / ((ULONG)(myginfo->MinWidth - myginfo->Fixed.Width));

			if (ginfo->Flags & GIF_FIXEDHEIGHT)
			{
				/* Vertical center */
				ng.ng_Height	= ginfo->MinHeight;
				ng.ng_TopEdge	= top + (height - ng.ng_Height) / 2;
			}
			else
			{
				/* Vertical stretch */
				ng.ng_Height	= height;
				ng.ng_TopEdge	= top;
			}

			ng.ng_LeftEdge	= left + ginfo->LabelWidth;

			left = ng.ng_LeftEdge + ng.ng_Width + HSPACING;
		}

		switch (ginfo->GKind = *lga->Args++)
		{
			case BOOPSI_KIND:
				ng.ng_UserData = (void *)(*lga->Args++);
				lga->SpecialTags[stc++] = XMGAD_SetupFunc;
				lga->SpecialTags[stc++] = (*lga->Args++);
				boopsi = TRUE;
				break;

			case IMAGEBUTTON_KIND:
				ng.ng_UserData		= (void *)(*lga->Args++);
				ng.ng_Flags			= (*lga->Args++);
				ng.ng_GadgetText	= NULL;
				lga->SpecialTags[stc++] = XMGAD_SetupFunc;
				lga->SpecialTags[stc++] = (ULONG)CreateVImageButton;
				boopsi = TRUE;
				break;

			case VGROUP_KIND:
			case HGROUP_KIND:
			{
				ULONG bordertype;

				if (bordertype = *lga->Args++)
				{
					if (!CreateWindowBorder (lga->Wud, ng.ng_LeftEdge - ginfo->LabelWidth, ng.ng_TopEdge,
						ng.ng_Width + ginfo->LabelWidth, ng.ng_Height, bordertype))
						return NULL;
					if (!CreateGadgets (lga, (ginfo->GKind == VGROUP_KIND) ? LAYOUTMODE_V : LAYOUTMODE_H,
						ng.ng_LeftEdge + HSPACING*4, ng.ng_TopEdge + VSPACING*4,
						ng.ng_Width - HSPACING*8, ng.ng_Height - VSPACING*8))
						return NULL;
				}
				else if (!CreateGadgets (lga, (ginfo->GKind == VGROUP_KIND) ? LAYOUTMODE_V : LAYOUTMODE_H,
					ng.ng_LeftEdge, ng.ng_TopEdge, ng.ng_Width, ng.ng_Height))
					return NULL;


				break;
			}

			case BUTTON_KIND:
				ng.ng_UserData		= (void *)(*lga->Args++);
				ng.ng_GadgetText	= STR(*lga->Args++);
				ng.ng_Flags			= PLACETEXT_IN;
				break;

			case CHECKBOX_KIND:
				ng.ng_UserData = (void *)(*lga->Args++);
				ng.ng_GadgetText = STR(*lga->Args++);
				lga->Args++;	/* Skip storage */

				lga->SpecialTags[stc++] = GTCB_Checked;
				lga->SpecialTags[stc++] = (ginfo->SpecialStorage ? (*((UWORD *)ginfo->SpecialStorage)) : NULL);
				lga->SpecialTags[stc++] = GTCB_Scaled;
				lga->SpecialTags[stc++] = TRUE;
				break;

			case INTEGER_KIND:
				ng.ng_UserData = (void *)(*lga->Args++);
				ng.ng_GadgetText = STR(*lga->Args++);

				/* Editing in right-justified integer gadgets
				 * is very uncomfortable!!
				 *
				 * lga->SpecialTags[stc++] = STRINGA_Justification;
				 * lga->SpecialTags[stc++] = GACT_STRINGRIGHT;
				 */
				lga->SpecialTags[stc++] = GTIN_MaxChars;
				lga->SpecialTags[stc++] = *lga->Args++;
				lga->SpecialTags[stc++] = STRINGA_ExitHelp;
				lga->SpecialTags[stc++] = TRUE;
				break;

			case LISTVIEW_KIND:
			{
				struct List *l;

				ng.ng_UserData		= (void *)*lga->Args++;
				ng.ng_GadgetText	= STR(*lga->Args++);

				lga->SpecialTags[stc++] = GTLV_ShowSelected;
				lga->SpecialTags[stc++] = NULL;

				if (l = (struct List *)*lga->Args++)
				{
					lga->SpecialTags[stc++] = GTLV_Labels;
					lga->SpecialTags[stc++] = (ULONG)l;

					// l->lh_Type = 0;	/* Selected Item	*/
					// l->l_pad = 0;	/* Item Count		*/
				}

#ifndef OS30_ONLY
				if (GadToolsBase->lib_Version < 39)
					ng.ng_Height -= 4;
#endif /* !OS30_ONLY */

				break;
			}

			case MX_KIND:
			case CYCLE_KIND:
			{
				ng.ng_UserData		= (void *)(*lga->Args++);
				ng.ng_GadgetText	= STR(*lga->Args++);

				/* Skip labels */
				lga->Args++;

				if (ginfo->GKind == MX_KIND)
				{
					/* Special kludge: MX gadgets width and height refer to
					 * one button instead of the whole gadget.
					 */
					ng.ng_Width = lga->Wud->Font->tf_XSize * 2;
					ng.ng_Height = lga->Wud->Font->tf_YSize;

					ng.ng_Flags = PLACETEXT_RIGHT;

					lga->SpecialTags[stc++] = GTMX_Labels;
					lga->SpecialTags[stc++] = (ULONG)ginfo->SpecialStorage;
					lga->SpecialTags[stc++] = GTMX_Scaled;
					lga->SpecialTags[stc++] = TRUE;
					lga->SpecialTags[stc++] = GTMX_Spacing;
					lga->SpecialTags[stc++] = 2;
				}
				else
				{
					lga->SpecialTags[stc++] = GTCY_Labels;
					lga->SpecialTags[stc++] = (ULONG)ginfo->SpecialStorage;
				}

				break;
			}

			case NUMBER_KIND:
				ng.ng_UserData = NULL;
				ng.ng_GadgetText = STR(*lga->Args++);

				lga->Args++;	/* Skip NumDigits */

				lga->SpecialTags[stc++] = GTNM_Border;
				lga->SpecialTags[stc++] = TRUE;

				/* Under V39 and below, GTJ_RIGHT does not work properly. */
				if (GadToolsBase->lib_Version > 39)
				{
					lga->SpecialTags[stc++] = GTNM_Justification;
					lga->SpecialTags[stc++] = GTJ_RIGHT;
				}
				break;

			case TEXT_KIND:
				ng.ng_UserData = NULL;
				ng.ng_GadgetText = STR(*lga->Args++);

				lga->Args++;	/* Skip NumDigits */
				break;

			case PALETTE_KIND:
				ng.ng_UserData = (void *)(*lga->Args++);
				ng.ng_GadgetText = STR(*lga->Args++);

				lga->SpecialTags[stc++] = GTPA_IndicatorWidth;
				lga->SpecialTags[stc++] = ng.ng_Width / 8;
				break;

			case SCROLLER_KIND:
				ng.ng_UserData = (void *)(*lga->Args++);
				ng.ng_GadgetText = STR(*lga->Args++);

				lga->SpecialTags[stc++] = GTSC_Arrows;
				lga->SpecialTags[stc++] = lga->Wud->Font->tf_XSize + 4;
				break;

			case STRING_KIND:
/*				ClassId = STRGCLASS;
				lga->SpecialTags[stc++] = GA_UserData;
				lga->SpecialTags[stc++] = *lga->Args++;
				lga->SpecialTags[stc++] = GA_LabelImage;
				lga->SpecialTags[stc++] = *lga->Args++;
				lga->SpecialTags[stc++] = STRINGA_MaxChars;
				lga->SpecialTags[stc++] = *lga->Args++;
				lga->SpecialTags[stc++] = GA_Border;
				lga->SpecialTags[stc++] = StringFrame;
				lga->SpecialTags[stc++] = STRINGA_ExitHelp;
				lga->SpecialTags[stc++] = TRUE;
*/
				ng.ng_UserData = (void *)(*lga->Args++);
				ng.ng_GadgetText = STR(*lga->Args++);

				lga->SpecialTags[stc++] = GTST_MaxChars;
				lga->SpecialTags[stc++] = *lga->Args++;
				lga->SpecialTags[stc++] = STRINGA_ExitHelp;
				lga->SpecialTags[stc++] = TRUE;
				break;

			case SLIDER_KIND:
				ng.ng_UserData = (void *)(*lga->Args++);
				ng.ng_GadgetText = STR(*lga->Args++);

				lga->SpecialTags[stc++] = GTSL_Min;
				lga->SpecialTags[stc++] = *lga->Args++;
				lga->SpecialTags[stc++] = GTSL_Max;
				lga->SpecialTags[stc++] = *lga->Args++;
				lga->SpecialTags[stc++] = GTSL_LevelFormat;
				lga->SpecialTags[stc++] = *lga->Args++;
				lga->SpecialTags[stc++] = GTSL_MaxLevelLen;
				lga->SpecialTags[stc++] = *lga->Args++;
				lga->SpecialTags[stc++] = GTSL_LevelPlace;
				lga->SpecialTags[stc++] = PLACETEXT_RIGHT;
				lga->SpecialTags[stc++] = GTSL_Justification;
				lga->SpecialTags[stc++] = GTJ_RIGHT;
				lga->SpecialTags[stc++] = GA_RelVerify;
				lga->SpecialTags[stc++] = TRUE;

				ng.ng_Width -= (ULONG)ginfo->SpecialStorage;
				break;
		}

		if ((ginfo->GKind != HGROUP_KIND) && (ginfo->GKind != VGROUP_KIND))
		{
			if (*lga->Args != TAG_DONE)
			{
				/* Add user Tags */
				lga->SpecialTags[stc++] = TAG_MORE;
				lga->SpecialTags[stc] = (ULONG) lga->Args;
			}
			else lga->SpecialTags[stc] = TAG_DONE;


			/* Go to next gadget (ie: skip tags until TAG_END) */
			while (*lga->Args) lga->Args += 2;
			lga->Args++;

			if (boopsi)
			{
				/* BOOPSI Gadget. Let SetupFunc() allocate the gadget for us */

				if (tmp = FindTagItem (XMGAD_SetupFunc, Tags))
				{
					if (lga->PrevGad->NextGadget = ((struct Gadget * (*)(struct TagItem *, struct NewGadget *)) (tmp->ti_Data)) (Tags, &ng))
						/* Record it into the list */
						lga->Wud->Gadgets[lga->Count] = lga->PrevGad->NextGadget;
					else
						return NULL;
				}
				else
					return NULL;
			}
			else
			{
				/* Normal GadTools gadget */

				if (!(lga->Wud->Gadgets[lga->Count] = CreateGadgetA (ginfo->GKind, lga->PrevGad, &ng, Tags)))
					return NULL;
			}

			lga->PrevGad = lga->Wud->Gadgets[lga->Count];

		} /* End if (GKind != ?GROUP_KIND */

		lga->Count++;	/* Go to next gadget	*/
	}

	lga->Args++;	/* Skip ENDGROUP_KIND		*/
	lga->Count--;	/* Take back one position	*/

	return (lga->Wud->GList);
}



static void RenderWindowBorders (struct WinUserData *wud)
{
	struct WindowBorder *border = wud->Borders;

	while (border)
	{
		DrawBevelBox (wud->Win->RPort,
			border->Size.Left, border->Size.Top,
			border->Size.Width, border->Size.Height,
			GT_VisualInfo,	VisualInfo,
			GTBB_Recessed,	TRUE,
			GTBB_FrameType,	border->Type,
			TAG_DONE);

		border = border->NextBorder;
	}
}



static struct WindowBorder *CreateWindowBorder (struct WinUserData *wud,
	UWORD left, UWORD top, UWORD width, UWORD height, ULONG bordertype)

/* Creates new WindowBorder structure and links it to the WUD passed.
 * Will return NULL in case of failure.
 */
{
	struct WindowBorder *border;

	if (!(border = AllocPooled (Pool, sizeof (struct WindowBorder))))
		return NULL;

	/* Link this border struct in WUD */
	border->NextBorder = wud->Borders;
	wud->Borders = border;

	/* Fill in WindowBorder structure */
	border->Type		= bordertype;
	border->Size.Left	= left;
	border->Size.Top	= top;
	border->Size.Width	= width;
	border->Size.Height	= height;

	return border;
}



static void DeleteGadgets (struct WinUserData *wud)
{
	FreeGadgets (wud->GList);	wud->GList = NULL;

	if (wud->GInfo)
	{
		ULONG i;

		for (i = 0; i < wud->GCount; i++)
		{
			if (wud->Gadgets[i])
			{
				switch (wud->GInfo[i].GKind)
				{
					case IMAGEBUTTON_KIND:
						DeleteVImageButton (wud->Gadgets[i]);
						break;

					default:
						break;
				}
			}
		}
	}

	if (wud->Borders)
	{
		struct WindowBorder *border = wud->Borders, *nextborder;

		do
		{
			nextborder = border->NextBorder;
			FreePooled (Pool, border, sizeof (struct WindowBorder));
		}
		while (border = nextborder);

		wud->Borders = NULL;
	}
}



struct WinUserData *CreateWUD (ULONG id)
{
	struct WinUserData *wud;
	struct WDescr *wdescr = &WDescr[id];


	/* Create a new WUD structure for this window */
	if (wud = CAllocPooled (Pool, sizeof (struct WinUserData)))
	{
		wdescr->Wud = wud;
		wdescr->UseCnt++;
		wud->WindowID = id;
		wud->IDCMPFlags =	IDCMP_RAWKEY | IDCMP_MENUHELP |
							IDCMP_GADGETHELP | IDCMP_INACTIVEWINDOW;
		wud->Flags = WFLG_DRAGBAR | WFLG_DEPTHGADGET;
		wud->Title = PrgName;

		/* Link WUD to windows list */
		ADDHEAD (&WindowList, (struct Node *)wud);

		if (wdescr->CreationTags)
		{
			struct TagItem	*tstate = wdescr->CreationTags,
							*tag;

			/* Read taglist arguments */

			while (tag = NextTagItem (&tstate))
				switch (tag->ti_Tag)
				{
					case XMWIN_NewMenu:
						wud->NewMenu = (struct NewMenu *)tag->ti_Data;
						break;

					case XMWIN_LayoutArgs:
						wud->LayoutArgs = (ULONG *)tag->ti_Data;
						break;

					case XMWIN_GCount:
						wud->GCount = tag->ti_Data;
						break;

					case XMWIN_Title:
						wud->Title = STR(tag->ti_Data);
						break;

					case XMWIN_WindowFlags:
						wud->Flags |= tag->ti_Data;
						break;

					case XMWIN_IDCMPFlags:
						wud->IDCMPFlags |= tag->ti_Data;
						break;

					case XMWIN_IDCMPFunc:
						wud->IDCMPFunc = (void (*)()) tag->ti_Data;
						break;

					case XMWIN_DropIconFunc:
						wud->DropIcon = (void (*)()) tag->ti_Data;
						break;

					case XMWIN_LayoutFunc:
						wud->LayoutArgs = (ULONG *)tag->ti_Data;
						wud->WUDFlags |= WUDF_CUSTOMLAYOUT;
						break;

					case XMWIN_PreOpenFunc:
						wud->PreOpenFunc = (LONG (*)()) tag->ti_Data;
						break;

					case XMWIN_PostOpenFunc:
						wud->PostOpenFunc = (void (*)()) tag->ti_Data;
						break;

					case XMWIN_PreCloseFunc:
						wud->PreCloseFunc = (LONG (*)()) tag->ti_Data;
						break;

					case XMWIN_PostCloseFunc:
						wud->PostCloseFunc = (void (*)()) tag->ti_Data;
						break;

					case XMWIN_HelpNode:
						wud->HelpNode = (STRPTR) tag->ti_Data;
						break;

					case XMWIN_UserData:
						wud->UserData = (APTR) tag->ti_Data;
						break;

					case XMWIN_LayoutCleanup:
						wud->LayoutCleanupFunc = (void (*)()) tag->ti_Data;
						break;

					default:
						break;
				}
		}

		if (!CreateLayoutInfo (wud))
		{
			DeleteWUD (wud);
			wud = NULL;
		}
	}

	return wud;
}



static void DeleteWUD (struct WinUserData *wud)
{
	if (wud)
	{
		MyCloseWindow (wud);

		REMOVE ((struct Node *) wud);

		{
			struct WDescr *wdescr = &WDescr[wud->WindowID];

			wdescr->Wud = NULL;

			if (--wdescr->UseCnt)
			{
				struct WinUserData *otherwud;

				/* Search for another wud with same ID and set it in WDescr... */

				for (otherwud = (struct WinUserData *)WindowList.lh_Head;
						otherwud->Link.mln_Succ;
						otherwud = (struct WinUserData *)otherwud->Link.mln_Succ)
					if (otherwud->WindowID == wud->WindowID)
					{
						wdescr->Wud = otherwud;
						break;
					}
			}
		}

		DeleteLayoutInfo (wud);

		FreePooled (Pool, wud, sizeof (struct WinUserData));
	}
}



static struct WinUserData *CreateLayoutInfo (struct WinUserData *wud)
{
	struct WDescr *wdescr = &WDescr[wud->WindowID];

	/* Compute font */

	if (wud->Font = WindowFont)
		wud->Attr = &WindowAttr;
	else
	{
		wud->Font = TopazFont;
		wud->Attr = &TopazAttr;
	}


	if (wud->LayoutArgs)
	{
		if (!wud->Gadgets)
		{
			/* Allocate Gadgets array */
			if (!(wud->Gadgets = CAllocPooled (Pool,
				wud->GCount * sizeof (struct Gadget *))))
				return NULL;
		}

		if (wud->WUDFlags & WUDF_CUSTOMLAYOUT)
		{
			if (!(wud->GList = ((struct Gadget * (*)(struct WinUserData *))
				wud->LayoutArgs) (wud) ))
					return NULL;
		}
		else	/* Use standard layout engine */
		{
			struct LayoutGadgetsArgs lga;

			lga.Wud = wud;

			/* Layout Phase 1 */

			if (!wud->GInfo)
			{
				struct RastPort rp;
				InitRastPort (&rp);
				SetFont (&rp, wud->Font);

				lga.DummyRast	= &rp;
				lga.Args		= wud->LayoutArgs;
				lga.Count		= 0;


				/* Allocate Key shortcuts array */
				if (!(wud->Keys = CAllocPooled (Pool, wud->GCount)))
					return NULL;

				/* Allocate GInfo array */
				if (!(wud->GInfo = lga.GInfo = CAllocPooled (Pool, sizeof (struct GInfo) * wud->GCount)))
					return NULL;

				LayoutGadgets (&lga, LAYOUTMODE_V);

				if ((lga.GInfo[0].Flags & (GIF_FIXEDWIDTH | GIF_FIXEDHEIGHT)) != (GIF_FIXEDWIDTH | GIF_FIXEDHEIGHT))
				{
					wud->Flags |= (WFLG_SIZEGADGET | WFLG_SIZEBBOTTOM);
					wud->IDCMPFlags |= IDCMP_NEWSIZE;
				}

				/* Force minimum size */
				wud->WindowSize.Width = max (wud->WindowSize.Width, lga.GInfo[0].MinWidth + lga.GInfo[0].LabelWidth + HSPACING * 2);
				wud->WindowSize.Height = max (wud->WindowSize.Height, lga.GInfo[0].MinHeight + VSPACING * 2);
			}
			else
				/* Reuse previous GInfo array */
				lga.GInfo = wud->GInfo;


			/* Layout Phase 2 */
			{
				ULONG SpecialTags[20];

				SpecialTags[0] = GT_Underscore;
				SpecialTags[1] = (ULONG) '_';

				if (!CreateContext (&wud->GList))
					return NULL;

				lga.Args		= wud->LayoutArgs;
				lga.VInfo		= VisualInfo;
				lga.PrevGad		= wud->GList;
				lga.Count		= 0;
				lga.SpecialTags	= SpecialTags;

				if (!CreateGadgets (&lga, LAYOUTMODE_V, OffX + lga.GInfo[0].LabelWidth + HSPACING, OffY + VSPACING,
					wud->WindowSize.Width - lga.GInfo[0].LabelWidth - HSPACING * 2,
					wud->WindowSize.Height - VSPACING * 2))
					return NULL;
			}
		}
	}

	if (!(wdescr->Flags & XMWINF_LOCALEDONE))
	{
		if (wud->NewMenu)
		{
			/* Localize the MenuStrip */
			struct NewMenu *nm = wud->NewMenu;

			while (nm->nm_Type != NM_END)
			{
				if (nm->nm_Label != (STRPTR)NM_BARLABEL)
					nm->nm_Label = STR ((ULONG)nm->nm_Label);

				nm++;
			}
		}

		wdescr->Flags |= XMWINF_LOCALEDONE;
	}


	if (wud->NewMenu && (!wud->MenuStrip))
	{
		if (!(wud->MenuStrip = CreateMenusA (wud->NewMenu, NULL)))
			return NULL;

		if (!(LayoutMenus (wud->MenuStrip, VisualInfo,
			GTMN_NewLookMenus, TRUE,
			TAG_DONE)))
			return NULL;
	}


	/* Setup zoom size */

	if (wud->Flags & WFLG_SIZEGADGET)
	{
		wud->WindowZoom.Left = 0;
		wud->WindowZoom.Top = 1;
		wud->WindowZoom.Width = Scr->Width;
		wud->WindowZoom.Height = Scr->Height;
	}
	else
	{
		wud->WindowZoom.Left = wud->WindowSize.Left;
		wud->WindowZoom.Top = wud->WindowSize.Top;
		if (wud->Title)
			wud->WindowZoom.Width = TextLength (&Scr->RastPort, wud->Title,
				strlen (wud->Title)) + 80;
		else
			wud->WindowZoom.Width = 80;
		wud->WindowZoom.Height = Scr->WBorTop + Scr->RastPort.TxHeight + 1;

		/* TODO: Under V39 specifying ~0,~0 as Zoom X,Y, intuition
		 * will only size-zoom the window.  Consider implementing it...
		 */
	}

	return wud;
}



GLOBALCALL void DeleteLayoutInfo (struct WinUserData *wud)
{
	if (!wud) return;

	DeleteGadgets (wud);

	if (wud->LayoutCleanupFunc)
		wud->LayoutCleanupFunc (wud);

	if (wud->Keys)
	{
		FreePooled (Pool, wud->Keys, wud->GCount);
		wud->Keys = NULL;
	}

	if (wud->Gadgets)
	{
		if (wud->GInfo)
		{
			ULONG i;

			/* Free gadget associated resources */

			for (i = 0; i < wud->GCount; i++)
			{
				if (wud->Gadgets[i])
				{
					switch (wud->GInfo[i].GKind)
					{
						case MX_KIND:
						case CYCLE_KIND:
							FreeVecPooled (Pool, wud->GInfo[i].SpecialStorage);

						default:
							break;
					}
				}
			}
		}

		FreePooled (Pool, wud->Gadgets, wud->GCount * sizeof (struct Gadget *));
		wud->Gadgets = NULL;
	}

	if (wud->GInfo)
	{
		FreePooled (Pool, wud->GInfo, wud->GCount * sizeof (struct GInfo));
		wud->GInfo = NULL;
	}

	if (wud->MenuStrip)
	{
		FreeMenus (wud->MenuStrip);
		wud->MenuStrip = NULL;
	}
}



GLOBALCALL struct Window *NewWindow (ULONG id)

/* Create a new window using the given Window ID. */
{
	struct WDescr		*wdescr		= &WDescr[id];
	struct WinUserData	*wud;

	if (!Scr) return NULL;

	/* Some windows can not be opened multiple times */

	if (!(wud = wdescr->Wud) || (wdescr->Flags & XMWINF_OPENMULTI))
	{
		if (!(wud = CreateWUD (id)))
			return NULL;
	}

	return MyOpenWindow (wud);
}



GLOBALCALL struct Window *MyOpenWindow (struct WinUserData *wud)

/* Open & setup a window using the given WinUserData structure. */
/* This function is now OBSOLETE and should be merged with NewWindow() */
{
	if (!Scr) return NULL;

	if (wud->PreOpenFunc)
		if (wud->PreOpenFunc (wud))
		{
			if (wud->PreCloseFunc) wud->PreCloseFunc (wud);
			return NULL;
		}

	if (wud->Win)
	{
		RevealWindow (wud);
		return wud->Win;
	}

	if (!wud->Gadgets && wud->LayoutArgs)
		if (!CreateLayoutInfo (wud))
			return NULL;


	/* Open the Window */

	if (wud->Win = OpenWindowTags (NULL,
		WA_Left,			wud->WindowSize.Left,
		WA_Top,				wud->WindowSize.Top,
		WA_InnerWidth,		wud->WindowSize.Width,
		WA_InnerHeight,		wud->WindowSize.Height,
		WA_Gadgets,			wud->GList,
		WA_Title,			wud->Title,
		WA_Flags,			wud->Flags,
		WA_Zoom,			&(wud->WindowZoom),
		WA_PubScreen,		Scr,
		WA_HelpGroup,		UniqueID,
		/* Set user preferred refresh method unless this window
		 * requests simple refresh explicitly.
		 */
		GuiSwitches.SmartRefresh ? WA_SmartRefresh : WA_SimpleRefresh, TRUE,
		WA_Activate,		!ScreenReopening,

		/* Set minimum and maximum size according to the main gadget group
		 * properties.
		 */
		(wud->Flags & WFLG_SIZEGADGET) ? WA_MinWidth : TAG_IGNORE,
			wud->GInfo ? (wud->GInfo[0].MinWidth + Scr->WBorLeft + Scr->WBorRight) : wud->WindowSize.Width,
		(wud->Flags & WFLG_SIZEGADGET) ? WA_MinHeight : TAG_IGNORE,
			wud->GInfo ? (wud->GInfo[0].MinHeight + Scr->WBorTop + Scr->WBorBottom + Scr->Font->ta_YSize + SizeHeight + 1) : wud->WindowSize.Height,
/**/	(wud->Flags & WFLG_SIZEGADGET) ? WA_MaxWidth : TAG_IGNORE,
			wud->GInfo ? (wud->GInfo[0].Flags & GIF_FIXEDWIDTH ? 0 : -1) : -1,
		(wud->Flags & WFLG_SIZEGADGET) ? WA_MaxHeight : TAG_IGNORE,
			wud->GInfo ? (wud->GInfo[0].Flags & GIF_FIXEDHEIGHT ? wud->GInfo[0].MinHeight : -1) : -1,

		/* Constant tags */
		WA_ScreenTitle,		(ULONG)Version+6,
		WA_AutoAdjust,		TRUE,
		WA_MenuHelp,		TRUE,
	//	WA_MouseQueue,		2,
	//	WA_RptQueue,		2,
		WA_NewLookMenus,	TRUE,

		/* Add user tags */
		WDescr[wud->WindowID].CreationTags ? TAG_MORE : TAG_DONE, WDescr[wud->WindowID].CreationTags
		))
	{
		wud->Win->UserData = (BYTE *) wud;
		wud->Win->UserPort = WinPort;

		if (ModifyIDCMP (wud->Win, wud->IDCMPFlags))
		{
			/* Set default font for this window */
			if (wud->Font) SetFont (wud->Win->RPort, wud->Font);

			if (wud->MenuStrip) SetMenuStrip (wud->Win, wud->MenuStrip);

			/* Do initial refresh */
			GT_RefreshWindow (wud->Win, NULL);
			RenderWindowBorders (wud);

			/* Make the window visible on the screen */
			if (!ScreenReopening
#ifndef OS30_ONLY
				&& (IntuitionBase->LibNode.lib_Version >= 39)
#endif /* !OS30_ONLY */
				)
			    ScreenPosition (Scr, SPOS_MAKEVISIBLE,
					wud->Win->LeftEdge, wud->Win->TopEdge,
					wud->Win->LeftEdge + wud->Win->Width - 1,
					wud->Win->TopEdge + wud->Win->Height - 1);

			/* Make it an AppWindow if it is requested */
			if (wud->DropIcon) AddAppWin (wud);

			if (wud->PostOpenFunc)
				wud->PostOpenFunc (wud);

			wud->WUDFlags &= ~WUDF_REOPENME;

			return wud->Win;
		}

		/* ClearMenuStrip() -- no need to call it... */
		CloseWindow (wud->Win);	wud->Win = NULL;
	}

	return NULL;
}



/* Close a window and all related resources */
GLOBALCALL void MyCloseWindow (struct WinUserData *wud)
{
	struct Window *win = wud->Win;
	struct Requester *req;

	if (win)
	{
		DeselectButton();

		if (wud->PreCloseFunc)
			if (wud->PreCloseFunc (wud))
				return;

		/* Cleanup locked window */
		if (req = win->FirstRequest)
		{
			EndRequest (req, win);
			FreePooled (Pool, req, sizeof (struct WindowLock));
		}

		/* Remove AppWindow */
		if (wud->AppWin) RemAppWin (wud);


		/* Free MenuStrip */
		if (win->MenuStrip)
		{
			ClearMenuStrip (win);
			DoNextSelect = 0;	/* Do not loop any more on this window's MenuStrip */
		}

		/* Now remove any pending message from the shared IDCMP port */

		Forbid();
		{
			struct Node *succ;
			struct Message *msg = (struct Message *) win->UserPort->mp_MsgList.lh_Head;

			while (succ = msg->mn_Node.ln_Succ)
			{
				if (((struct IntuiMessage *)msg)->IDCMPWindow ==  win)
				{
					REMOVE ((struct Node *)msg);
					ReplyMsg (msg);
				}
				msg = (struct Message *) succ;
			}

			win->UserPort = NULL;	/* Keep intuition from freeing our port... */
			ModifyIDCMP (win, 0L);	/* ...and from sending us any more messages. */
		}
		Permit();


		/* Save Window position and clear window pointer */

		wud->WindowSize.Left	= win->LeftEdge;
		wud->WindowSize.Top		= win->TopEdge;

		if (win->Flags & WFLG_SIZEGADGET)
		{
			wud->WindowSize.Width	= win->Width - win->BorderLeft - win->BorderRight;
			wud->WindowSize.Height	= win->Height - win->BorderTop - win->BorderBottom;
		}

		CloseWindow (win);	wud->Win = NULL;

		if (wud->PostCloseFunc)
			wud->PostCloseFunc (wud);
	}

	if (wud->Font)
		{ CloseFont (wud->Font); wud->Font = NULL; }

	if ((WDescr[wud->WindowID].Flags & XMWINF_OPENMULTI)
		&& (WDescr[wud->WindowID].UseCnt > 1))
		DeleteWUD (wud);
}



GLOBALCALL void ReopenWindows (void)

/* Reopen windows that were previously open */
{
	struct WinUserData *wud;

	ScreenReopening = TRUE;

	/* Find window */
	for (wud = (struct WinUserData *)WindowList.lh_Head; wud->Link.mln_Succ;
		wud = (struct WinUserData *)wud->Link.mln_Succ)
	{
		if (wud->WUDFlags & WUDF_REOPENME)
			MyOpenWindow (wud);
	}

	ScreenReopening = FALSE;
}



GLOBALCALL LONG SetupScreen (void)
{
	struct Screen *DefScr;
	struct DrawInfo *DefDri;

	static LONG ExtraScreenTags[] =
	{
		SA_SysFont,			1,
		SA_FullPalette,		TRUE,
		SA_SharePens,		TRUE,	/* These three Tags are valid only under
		SA_LikeWorkbench,	TRUE,	 * V39 and are properly ignored by V37.
		SA_MinimizeISG,		TRUE,	 */
		SA_Interleaved,		TRUE,
		TAG_DONE
	};

	if (Scr)
	{
		/* If screen is already open, pop it to front and activate
		 * the main window.
		 */
		ScreenToFront (Scr);
		if (ThisTask->pr_WindowPtr)
			RevealWindow ((struct WinUserData *)(((struct Window *)(ThisTask->pr_WindowPtr))->UserData));

		return RETURN_OK;
	}


	if (!(ScrollButtonClass = InitScrollButtonClass()))
		return ERROR_NO_FREE_STORE;


	/* Try the user selected Public Screen */

	if (!(Scr = LockPubScreen (ScrInfo.PubScreenName[0] ? ScrInfo.PubScreenName : NULL)))
	{
		/* Try to open own screen */

		if (ScrInfo.DisplayID)
		{
			static UWORD		 PensArray[1] = {(UWORD)~0};
			ULONG				*ColorTable	= NULL;
#ifndef OS30_ONLY
			struct ColorSpec	*ColorSpec	= NULL;
#endif /* !OS30_ONLY */
			ULONG				 i;

			/* Color map translation */

			if (ScrInfo.OwnPalette)
			{
#ifndef OS30_ONLY
				if (IntuitionBase->LibNode.lib_Version >= 39)
				{
#endif /* !OS30_ONLY */
					if (ColorTable = AllocPooled (Pool, ((32 * 3) + 2) * sizeof (LONG)))
					{
						ULONG	*col = ColorTable,
								 tmp;

						*col++ = 32 << 16;

						for (i = 0; i < 32; i++)
						{
							tmp = (ScrInfo.Colors[i] >> 16);			/* Red */
							tmp |= tmp << 8 | tmp << 16 | tmp << 24;
							*col++ = tmp;

							tmp = (ScrInfo.Colors[i] >> 8) & 0xFF;		/* Green */
							tmp |= tmp << 8 | tmp << 16 | tmp << 24;
							*col++ = tmp;

							tmp = ScrInfo.Colors[i] & 0xFF;				/* Blue */
							tmp |= tmp << 8 | tmp << 16 | tmp << 24;
							*col++ = tmp;
						}

						*col = 0;
					}
				}
#ifndef OS30_ONLY
				else	/* V37 */
				{
					if (ColorSpec = AllocPooled (Pool, 33 * sizeof (struct ColorSpec)))
					{
						for (i = 0; i < 32; i++)
						{
							ColorSpec[i].ColorIndex = i;
							ColorSpec[i].Red	= ScrInfo.Colors[i] >> 20;
							ColorSpec[i].Green	= ScrInfo.Colors[i] >> 12 & 0xF;
							ColorSpec[i].Blue	= ScrInfo.Colors[i] >>  4 & 0xF;
						}

						ColorSpec[i].ColorIndex = -1;
					}
				}
			}
#endif /* !OS30_ONLY */


			/* Use user requested attributes for screen */

			Scr = OpenScreenTags (NULL,
				SA_Width,			ScrInfo.Width,
				SA_Height,			ScrInfo.Height,
				SA_Depth,			ScrInfo.Depth,
				SA_DisplayID,		ScrInfo.DisplayID,
				SA_Overscan,		ScrInfo.OverscanType,
				SA_AutoScroll,		ScrInfo.AutoScroll,
				SA_Title,			ScrInfo.PubScreenName,
				SA_PubName,			ScrInfo.PubScreenName,
				SA_Font,			ScreenAttr.ta_Name ? &ScreenAttr : NULL,
				SA_Pens,			PensArray,
				ScrInfo.OwnPalette ?
#ifndef OS30_ONLY
					((IntuitionBase->LibNode.lib_Version >= 39) ?
						SA_Colors32 : SA_Colors) : TAG_IGNORE,
					(IntuitionBase->LibNode.lib_Version >= 39) ?
						(ULONG)ColorTable : (ULONG)ColorSpec,
#else
					SA_Colors32 : TAG_IGNORE,	ColorTable,
#endif /* !OS30_ONLY */

				TAG_MORE,			ExtraScreenTags);

			if (ColorTable)	FreePooled (Pool, ColorTable, ((32 * 3) + 2) * sizeof (LONG));
#ifndef OS30_ONLY
			if (ColorSpec)	FreePooled (Pool, ColorSpec, 33 * sizeof (struct ColorSpec));
#endif /* !OS30_ONLY */
			if (Scr) OwnScreen = TRUE;
		}

		if (!Scr)
		{
			/* Try to clone the Default (Workbench) Screen */

			if (!(DefScr = LockPubScreen (NULL)))
			{
				CloseDownScreen();
				return ERROR_OBJECT_NOT_FOUND;
			}

			DefDri = GetScreenDrawInfo (DefScr);

			if (Scr = OpenScreenTags (NULL,
				SA_Depth,			DefDri->dri_Depth,
				SA_DisplayID,		GetVPModeID (&DefScr->ViewPort),
				SA_Overscan,		OSCAN_TEXT,
				SA_AutoScroll,		TRUE,
				SA_Title,			ScrInfo.PubScreenName,
				SA_PubName,			ScrInfo.PubScreenName,
				SA_Pens,			DefDri->dri_Pens,
				TAG_MORE,			ExtraScreenTags))
			{
				UnlockPubScreen (NULL, DefScr);
				OwnScreen = TRUE;
			}
			else
				Scr = DefScr;

			FreeScreenDrawInfo (DefScr, DefDri);
		}

		/* Make our screen really public */
		if (OwnScreen) PubScreenStatus (Scr, 0);
	}

	if (!(VisualInfo = GetVisualInfoA (Scr, NULL)))
	{
		CloseDownScreen();
		return ERROR_NO_FREE_STORE;
	}

	DrawInfo = GetScreenDrawInfo (Scr);

	OffX = Scr->WBorLeft;
	OffY = Scr->RastPort.TxHeight + Scr->WBorTop + 1;

	/* Setup fonts */
	if (!WindowAttr.ta_Name)	CopyTextAttrPooled (Pool, Scr->Font, &WindowAttr);
	if (!ListAttr.ta_Name)		CopyTextAttrPooled (Pool, Scr->Font, &ListAttr);
	if (!EditorAttr.ta_Name)	CopyTextAttrPooled (Pool, &TopazAttr, &EditorAttr);

	if (!(TopazFont = OpenFont (&TopazAttr)))
	{
		CantOpenLib (TopazAttr.ta_Name, 0);
		CloseDownScreen();
		return ERROR_NO_FREE_STORE;
	}

	if (DiskfontBase)
	{
		if (!(WindowFont = OpenDiskFont (&WindowAttr)))
			CantOpenLib (WindowAttr.ta_Name, 0);
		if (!(ListFont = OpenDiskFont (&WindowAttr)))
			CantOpenLib (ListAttr.ta_Name, 0);
	}

	/* Setup windows shared Message Port */
	if (!(WinPort = CreateMsgPort()))
	{
		CloseDownScreen();
		return ERROR_NO_FREE_STORE;
	}
	IDCMPSig = 1 << WinPort->mp_SigBit;
	Signals |= IDCMPSig;


	/* Create a SIZEIMAGE to get the correct size
	 * and position for the slider and arrow buttons.
	 */
	{
		struct Image *sizeimage;

		if (sizeimage = NewImageObject (SIZEIMAGE))
		{
			SizeWidth	= sizeimage->Width;
			SizeHeight	= sizeimage->Height;
			DisposeObject (sizeimage);
		}
	}


	if (!NewWindow (WID_TOOLBOX))
	{
		CloseDownScreen();
		return ERROR_NO_FREE_STORE;
	}

	/* Set Process Window pointer for DOS requesters */
	OldPrWindowPtr = ThisTask->pr_WindowPtr;
	ThisTask->pr_WindowPtr = WDescr[WID_TOOLBOX].Wud->Win;

	ReopenWindows();

	/* Bring screen to front in case it was hidden */
	ScreenToFront (Scr);

	return RETURN_OK;
}



GLOBALCALL void CloseDownScreen (void)

/* Free screen and all associated resources */
{
	struct WinUserData *wud;

	if (!Scr) return;

	ScreenShutdown = TRUE;

	/* Close AmigaGuide help window */
	CleanupHelp();


	if (Quit)
	{
		/* Destroy all windows */
		while (!IsListEmpty (&WindowList))
			DeleteWUD ((struct WinUserData *)WindowList.lh_Head);
	}
	else
	{
		/* Close all windows */
		for (wud = (struct WinUserData *)WindowList.lh_Head; wud->Link.mln_Succ;
				wud = (struct WinUserData *)wud->Link.mln_Succ)
		{
			MyCloseWindow (wud);
			DeleteLayoutInfo (wud);
			wud->WUDFlags |= WUDF_REOPENME;
		}
	}


	if (WinPort)
	{
		Signals &= ~IDCMPSig;
		IDCMPSig = 0;
		DeleteMsgPort (WinPort); WinPort = NULL;
	}

	FreeScreenDrawInfo (Scr, DrawInfo);	DrawInfo = NULL;
	FreeVisualInfo (VisualInfo);		VisualInfo = NULL;

	if (OwnScreen)
	{
		/* TODO: Use BuildEasyRequest()/SysReqHandler() with SA_PubSig like IPrefs */

		while (!CloseScreen (Scr))
			ShowRequestArgs (MSG_CLOSE_ALL_WINDOWS, MSG_CONTINUE, NULL);
	}
	else UnlockPubScreen (NULL, Scr);

	if (OldPrWindowPtr != (struct Window *)1L)
	{
		ThisTask->pr_WindowPtr = OldPrWindowPtr;
		OldPrWindowPtr = (struct Window *)1L;
	}

	if (WindowFont)
	{
		CloseFont (WindowFont);
		WindowFont = NULL;
	}

	if (ListFont)
	{
		CloseFont (ListFont);
		ListFont = NULL;
	}

	if (TopazFont)
	{
		CloseFont (TopazFont);
		TopazFont = NULL;
	}

	if (ScrollButtonClass)
	{
		FreeScrollButtonClass (ScrollButtonClass);
		ScrollButtonClass = NULL;
	}

	Scr = NULL;
	OwnScreen = FALSE;
	ScreenShutdown = FALSE;
}

