/*
**	$Id: termGadTools.c,v 1.3 92/08/15 20:14:05 olsen Sta Locker: olsen $
**	$Revision: 1.3 $
**	$Date: 92/08/15 20:14:05 $
**
**	GadTools object management support routines
**
**	Copyright © 1990-1992 by Olaf `Olsen' Barthel & MXM
**		All Rights Reserved
*/

	/* Some includes for starters. */

#include <intuition/intuition.h>
#include <libraries/gadtools.h>
#include <intuition/sghooks.h>
#include <utility/tagitem.h>
#include <exec/memory.h>

#include <clib/intuition_protos.h>
#include <clib/utility_protos.h>
#include <clib/exec_protos.h>

#include <stdarg.h>

	/* User interface definitions. */

#define SZLV_Display	1		/* Listview includes display */
#define SZLV_String	2		/* Listview includes string */

#define SZ_Adjust	TAG_USER+1	/* Adjust object */
#define SZ_AutoWidth	TAG_USER+2	/* Use default width? */
#define SZ_AlignExtra	TAG_USER+3	/* Add extra space to top edge */
#define SZ_NewColumn	TAG_USER+4	/* Start new column */
#define SZ_Lines	TAG_USER+5	/* Number of lines in object */
#define SZ_Chars	TAG_USER+6	/* Number of chars (width) in object */
#define SZ_AlignLeft	TAG_USER+7	/* Align to left border */
#define SZ_AlignRight	TAG_USER+8	/* Align to right border */
#define SZ_AlignCentre	TAG_USER+9	/* Centre it */
#define SZ_AlignBottom	TAG_USER+10	/* Align to window bottom */
#define SZ_GroupCount	TAG_USER+11	/* Create a group with N objects */
#define SZ_GroupNext	TAG_USER+12	/* Skip to next position */
#define SZ_SetLeft	TAG_USER+13	/* Place left to current object, don't touch the left edge position, though. */
#define SZ_SetBelow	TAG_USER+14	/* Place below current object, don't touch the top edge position, though. */
#define SZ_UseLeft	TAG_USER+15	/* Use this coordinate as left edge. */
#define SZ_UseTop	TAG_USER+16	/* Use this coordinate as top edge. */
#define SZ_UseWidth	TAG_USER+17	/* Use this gadget width. */
#define SZ_UseHeight	TAG_USER+18	/* Use this gadget height. */
#define SZ_FullWidth	TAG_USER+19	/* Use full window width for gadget. */
#define SZ_FullHeight	TAG_USER+20	/* Use full window height for gadget. */

	/* Some #define values which may not yet be available in
	 * every include file set.
	 */

#ifndef GTMN_NewLookMenus
#define GTMN_NewLookMenus	(GT_TagBase+67)
#endif	/* GTMN_NewLookMenus */

#ifndef WA_NewLookMenus
#define WA_NewLookMenus		(WA_Dummy+0x30)
#endif	/* WA_NewLookMenus */

#ifndef GTCB_Scaled
#define GTCB_Scaled		GT_TagBase+68
#endif	/* GTCB_Scaled */

#ifndef GTMX_Scaled
#define GTMX_Scaled		GT_TagBase+69
#endif	/* GTMX_Scaled */

enum	{	COLOUR_AMIGA,COLOUR_EIGHT,COLOUR_SIXTEEN,COLOUR_MONO };

	/* term configuration. */

struct Configuration
{
	/* Serial Preferences. */

	ULONG	BaudRate;
	BYTE	BitsPerChar;
	BYTE	Parity;
	BYTE	StopBits;
	BYTE	Handshaking;
	BYTE	Duplex;
	BYTE	HighSpeed;
	ULONG	BreakLength;
	UBYTE	SerialDevice[40];
	LONG	UnitNumber;

	/* Modem Preferences. */

	UBYTE	ModemInit[80];
	UBYTE 	ModemExit[80];
	UBYTE 	ModemHangup[80];
	UBYTE	DialPrefix[80];
	LONG	RedialDelay;
	LONG	DialRetries;
	LONG	DialTimeout;
	BYTE	ConnectAutoCapture;
	BYTE	ConnectAutoBaud;
	BYTE	LogActions;
	UBYTE	NoCarrier[16];
	UBYTE	Connect[16];
	UBYTE	Voice[16];
	UBYTE	Ring[16];
	UBYTE	Busy[16];

	/* Transfer Preferences. */

	UBYTE	Protocol[40];

	/* Startup macro. */

	UBYTE	StartupMacro[256];

	/* Macro Preferences. */

	UBYTE	MacroFile[256];

	/* Screen Preferences. */

	ULONG	DisplayMode;
	UWORD	Colours[16];
	BYTE	MakeScreenPublic;
	BYTE	ShanghaiWindows;

	/* Terminal Preferences. */

	BYTE	CaptureFilter;
	BYTE	DestructiveBackspace;
	BYTE	AudibleBell;
	BYTE	VisibleBell;
	BYTE	EightyColumns;
	BYTE	DisableBlinking;
	BYTE	SendCR;
	BYTE	SendLF;
	BYTE	ColourMode;
	BYTE	Emulation;
	BYTE	Font;

	/* Emulation control. */

	BYTE	CursorApp;
	BYTE	FontScale;
	BYTE	JumpScroll;
	BYTE	AutoWrap;
	BYTE	CursorWrap;
	BYTE	NewLine;
	BYTE	InsertChar;
	BYTE	NumApp;

	/* Path Preferences. */

	UBYTE	DefaultStorage[256];

	UBYTE	TextUploadPath[256];
	UBYTE	TextDownloadPath[256];

	UBYTE	ASCIIUploadPath[256];
	UBYTE	ASCIIDownloadPath[256];

	UBYTE	BinaryUploadPath[256];
	UBYTE	BinaryDownloadPath[256];

	UBYTE	CapturePath[256];
	UBYTE	LogFile[256];
	UBYTE	Editor[256];
	UBYTE	BeepSound[256];

	/* Extension in version 1.9b */

	BYTE	SwapBSDelete;
	BYTE	StripBit8;

	/* Some more extensions... */

	BYTE	RasterEnabled;

	UBYTE	EmulationName[256];

	/* Up-/Download Macros. */

	UBYTE	UploadMacro[256];
	UBYTE	DownloadMacro[256];

	/* Default fast! macro file. */

	UBYTE	FastMacroFile[256];

	/* Program priority. */

	BYTE	Priority;

	/* Override transfer path. */

	BYTE	OverridePath;

	/* Backup config between switching to phone entries. */

	BYTE	BackupConfig;

	/* ZModem auto upload panel enabled? */

	BYTE	AutoUpload;

	/* No dialtone string. */

	UBYTE	NoDialTone[16];

	/* Check carrier upon `CARRIER LOST' message. */

	BYTE	CheckCarrier;

	/* Display the screen title bar? */

	BYTE	TitleBar;

	/* Display the status lines? */

	BYTE	StatusLine;

	BYTE	DropDTR;

	/* Custom keymap file name. */

	UBYTE	KeyMapName[40];

	/* Serial buffer size. */

	LONG	SerBuffSize;

	/* Maximum log book buffer size. */

	LONG	MaxLogBuffSize;

	/* Is the buffering enabled? */

	BYTE	BufferEnabled;

	/* Receive CR/LF as... */

	BYTE	ReceiveCR;
	BYTE	ReceiveLF;

	/* Set `archived' bit on uploaded files. */

	BYTE	SetArchivedBit;

	/* Open the fast! macro panel on startup? */

	BYTE	OpenFastMacroPanel;

	/* Pass xON/xOFF through to modem? */

	BYTE	PassThrough;

	/* Use system DisplayBeep()? */

	BYTE	SystemBeep;
	BYTE	Pad;

	/* Answerback message. */

	UBYTE	AnswerBack[41];
	BYTE	Pad2;

	/* Font settings. */

	UBYTE	FontName[32];
	WORD	FontHeight;
};

	/* External data. */

extern struct Library		*GadToolsBase,
				*DiskfontBase;
extern struct Gadget		*ActiveGadget;
extern struct Configuration	 Config;
extern UBYTE			 SharedBuffer[];
extern APTR			 VisualInfo;
extern LONG			 InterWidth,
				 InterHeight;

	/* This structure will help us to remember the current state of
	 * a GadTools object.
	 */

struct GadgetSupportInfo
{
	struct GadgetSupportInfo	*PointBack;	/* Points back to itself. */
	struct Gadget			*Gadget;	/* Points back to the host. */

	WORD				 Kind;		/* Item type (BUTTON_KIND, STRING_KIND, etc.) */
	UBYTE				 Key;		/* The key to press. */
	BYTE				 ReadOnly;	/* LISTVIEW_KIND only: is this a read-only object? */
	LONG				 Current,	/* Current state or counter. */
					 Min,		/* Minimum value for `Current'. */
					 Max;		/* Maximum value for `Current'. */
};

	/* A text box for several lines of text. */

struct TextBox
{
	struct TextBox	*NextBox;		/* Next box in chain. */

	LONG		 Left,Top,		/* Position and size. */
			 Width,Height;

	LONG		 LineWidth,		/* Line width in pixels. */
			 LineHeight;		/* Line height in pixels. */

	LONG		 NumChars,		/* Number of chars per line. */
			 NumLines;		/* Number of lines. */

	STRPTR		*Title,			/* Line titles. */
			*Text;			/* Line texts. */

	WORD		 TextPen;
};

	/* Sizing data. */

STATIC struct RastPort	*SZ_RPort;
STATIC struct TextFont	*SZ_TextFont;
STATIC struct Screen	*SZ_Screen;

STATIC LONG		 SZ_Left,
			 SZ_Top,
			 SZ_WindowWidth,
			 SZ_WindowHeight;
			 SZ_CurrentLeft,
			 SZ_CurrentTop,
			 SZ_CurrentWidth,
			 SZ_MaxWidth,
			 SZ_GroupDelta,
			 SZ_GroupX,
			 SZ_WidthMax,
			 SZ_TextPen;

	/* Local routines. */

struct Gadget *			CreateGadget(ULONG Kind,struct Gadget *Previous,struct NewGadget *New,...);
VOID				FreeGadgets(struct Gadget *GadgetList);
VOID				GT_SetGadgetAttrs(struct Gadget *Gadget,struct Window *Window,struct Requester *Requester,...);
VOID				GT_ReplyIMsg(struct IntuiMessage *Message);

VOID				KeySelect(struct Gadget **GadgetList,WORD Last,UBYTE Key,struct Window *Window,struct Gadget **Gadget,ULONG *Class,LONG *Code);

	/* Sizing routines. */

LONG				SZ_GetWindowWidth(VOID);
LONG				SZ_GetWindowHeight(VOID);
LONG				SZ_GetLeftEdge(VOID);
LONG				SZ_GetTopEdge(VOID);
VOID __regargs			SZ_SetTopEdge(LONG Top);
VOID __regargs			SZ_SetLeftEdge(LONG Left);
VOID __regargs			SZ_SetAbsoluteTop(LONG Top);
VOID __regargs			SZ_SetAbsoluteLeft(LONG Left);
VOID __regargs			SZ_SetWidth(LONG Width);
LONG				SZ_ResetMaxWidth(VOID);
VOID __regargs			SZ_UpdateMaxWidth(UBYTE Type,STRPTR Title,LONG Chars,STRPTR *Choices);

VOID __regargs			SZ_GetDimension(LONG *Width,LONG *Height);
LONG __regargs			SZ_TextWidth(STRPTR String);
VOID __regargs			SZ_AddLeftOffset(LONG Offset);
LONG __stdargs			SZ_LeftOffsetN(LONG DataArray,...);
LONG __regargs			SZ_LeftOffsetDelta(LONG From,LONG To);
VOID				SZ_SizeCleanup();
BYTE __regargs			SZ_SizeSetup(struct Screen *Screen,struct TextAttr *TextAttr,BYTE Title);
LONG __regargs			SZ_Height(UBYTE Type,LONG Lines,BYTE Display);
LONG __regargs			SZ_Width(UBYTE Type,STRPTR Title,LONG Chars,STRPTR *Choices);

	/* Text box routines. */

VOID __regargs			SZ_FreeBox(struct TextBox *Box);
VOID __regargs			SZ_FreeBoxes(struct TextBox *FirstBox);
LONG __regargs			SZ_BoxWidth(LONG Chars);
LONG __regargs			SZ_BoxHeight(LONG Lines);
struct TextBox * __stdargs	SZ_CreateTextBox(struct TextBox **FirstBox,...);
VOID __stdargs			SZ_SetBoxTitles(struct TextBox *Box,STRPTR Array,...);
VOID __stdargs			SZ_PrintLine(struct RastPort *RPort,struct TextBox *Box,LONG Line,STRPTR String,...);
VOID __regargs			SZ_DrawBox(struct RastPort *RPort,struct TextBox *Box);
VOID __regargs			SZ_DrawBoxes(struct RastPort *RPort,struct TextBox *FirstBox);

	/* The original gadtools.library routines. */

struct Gadget *			OldCreateGadgetA(ULONG,struct Gadget *,struct NewGadget *,struct TagItem *);
VOID				OldFreeGadget(struct Gadget *);
VOID				OldGT_SetGadgetAttrsA(struct Gadget *,struct Window *,struct Requester *,struct TagItem *);
VOID				OldGT_ReplyIMsg(struct IntuiMessage *);

	/* Pragma calls: the easy way. */

#pragma libcall GadToolsBase OldCreateGadgetA 1E A98004
#pragma libcall GadToolsBase OldFreeGadgets 24 801
#pragma libcall GadToolsBase OldGT_SetGadgetAttrsA 2A BA9804
#pragma libcall GadToolsBase OldGT_ReplyIMsg 4E 901

	/* CreateGadget():
	 *
	 *	A frontend to the original routine.
	 */

struct Gadget *
CreateGadget(ULONG Kind,struct Gadget *Previous,struct NewGadget *New,...)
{
	STATIC struct NewGadget		 Backup;

	va_list				 VarArgs;
	struct TagItem			*TagList,
					*ThisTag,
					 MoreTags[3];
	struct GadgetSupportInfo	*Info = NULL;
	WORD				 i;
	UBYTE				 Underscore = 0;
	struct Gadget			*Result;

		/* Get the tag item array. */

	va_start(VarArgs,New);

		/* Make a backup copy of the original NewGadget structure,
		 * we may want to modify it.
		 */

	CopyMem(New,&Backup,sizeof(struct NewGadget));

	New = &Backup;

		/* Get the tag item list. */

	TagList = (struct TagItem *)VarArgs;

		/* Try to find the SZ_Adjust tag item. If found, we are to
		 * apply our formatting functions to it.
		 */

	if(ThisTag = FindTagItem(SZ_Adjust,TagList))
	{
		if(ThisTag -> ti_Data)
		{
			STRPTR	*Labels = NULL;
			LONG	 Width,
				 Height,
				 Left = SZ_CurrentLeft;
			BYTE	 AutoWidth	= FALSE,
				 MoveDown	= TRUE,
				 SaveWidth	= TRUE,
				 SetLeft	= FALSE,
				 SetBelow	= FALSE;

				/* Are we to leave the left edge untouched? */

			if(ThisTag = FindTagItem(SZ_SetLeft,TagList))
				SetLeft = ThisTag -> ti_Data;

				/* Are we to leave the top edge untouched? */

			if(ThisTag = FindTagItem(SZ_SetBelow,TagList))
				SetBelow = ThisTag -> ti_Data;

				/* Are we to align the object to the
				 * window bottom?
				 */

			if(ThisTag = FindTagItem(SZ_AlignBottom,TagList))
			{
				if(ThisTag -> ti_Data)
					SZ_CurrentTop = SZ_WindowHeight;
			}

				/* Are we to add extra space to the
				 * object top edge?
				 */

			if(ThisTag = FindTagItem(SZ_AlignExtra,TagList))
			{
				if(ThisTag -> ti_Data)
					SZ_CurrentTop += InterHeight;
			}

				/* Are we to start a new column? */

			if(ThisTag = FindTagItem(SZ_NewColumn,TagList))
			{
				if(ThisTag -> ti_Data)
				{
					SZ_CurrentTop	= SZ_Top;
					Left		= Left + SZ_MaxWidth + InterWidth;

					SZ_MaxWidth	= 0;
				}
			}

				/* Is the object to contain a certain
				 * number of lines?
				 */

			if(ThisTag = FindTagItem(SZ_Lines,TagList))
			{
				LONG Lines = (LONG)ThisTag -> ti_Data;

					/* Is it a listview object? */

				if(Kind == LISTVIEW_KIND)
				{
						/* Determine height of object. */

					if(ThisTag = FindTagItem(GTLV_ShowSelected,TagList))
					{
						if(ThisTag -> ti_Data)
							Height = SZ_Height(Kind,Lines,SZLV_String);
						else
							Height = SZ_Height(Kind,Lines,SZLV_Display);
					}
					else
						Height = SZ_Height(Kind,Lines,NULL);
				}
				else
					Height = SZ_Height(Kind,Lines,NULL);
			}
			else
			{
					/* If this is a MX kind object,
					 * determine the number of lines
					 * by looking at the labels.
					 */

				if(ThisTag = FindTagItem(GTMX_Labels,TagList))
				{
					STRPTR	*Choices;
					LONG	 Lines = 0;

					Labels = Choices = (STRPTR *)ThisTag -> ti_Data;

					while(*Choices++)
						Lines++;

					Height = SZ_Height(Kind,Lines,NULL);
				}
				else
					Height = SZ_Height(Kind,0,NULL);
			}

				/* Is the object width derived from
				 * certain magical calculations or
				 * is it as wide as the default width?
				 */

			if(ThisTag = FindTagItem(SZ_AutoWidth,TagList))
				AutoWidth = ThisTag -> ti_Data;

				/* Determine the width of the object. */

			if(!AutoWidth)
			{
				STRPTR	*Choices	= NULL,
					 Title		= NULL;
				LONG	 Chars		= 0;

					/* Determine number of characters in
					 * this object.
					 */

				if(ThisTag = FindTagItem(SZ_Chars,TagList))
					Chars = ThisTag -> ti_Data;

					/* If this is a button object,
					 * calculate the width by looking
					 * at the title.
					 */

				if(Kind == BUTTON_KIND && New -> ng_GadgetText)
					Title = New -> ng_GadgetText;

					/* If this is a cycle object,
					 * take a look at the labels.
					 */

				if(ThisTag = FindTagItem(GTCY_Labels,TagList))
					Choices = (STRPTR *)ThisTag -> ti_Data;

					/* If this is a listview object,
					 * take a look at the list.
					 */

				if(ThisTag = FindTagItem(GTLV_Labels,TagList))
				{
					if(ThisTag -> ti_Data != ~0)
					{
						struct List	*List = (struct List *)ThisTag -> ti_Data;
						struct Node	*Node;
						LONG		 Len;

						Node = List -> lh_Head;

						while(Node -> ln_Succ)
						{
							if((Len = strlen(Node -> ln_Name)) > Chars)
								Chars = Len;

							Node = Node -> ln_Succ;
						}
					}
				}

					/* Determine object width. */

				Width = SZ_Width(Kind,Title,Chars,Choices);
			}
			else
			{
					/* Use the default width. */

				Width = SZ_CurrentWidth;

					/* A checkbox object has a
					 * fixed width. We don't want
					 * any other objects to share
					 * it.
					 */

				if(Kind == CHECKBOX_KIND)
				{
					Width = SZ_Width(Kind,NULL,0,NULL);

					SaveWidth = FALSE;
				}
			}

				/* Are we to align this object to
				 * the left window edge?
				 */

			if(ThisTag = FindTagItem(SZ_AlignLeft,TagList))
			{
				if(ThisTag -> ti_Data)
				{
					Left = SZ_Left;

					MoveDown = FALSE;
				}
			}

				/* Are we to use a specific object width? */

			if(ThisTag = FindTagItem(SZ_UseWidth,TagList))
				Width = ThisTag -> ti_Data;

				/* Are we to use a specific object height? */

			if(ThisTag = FindTagItem(SZ_UseHeight,TagList))
				Height = ThisTag -> ti_Data;

				/* Are we to align this object to the
				 * right window edge?
				 */

			if(ThisTag = FindTagItem(SZ_AlignRight,TagList))
			{
				if(ThisTag -> ti_Data)
				{
					Left = SZ_WindowWidth - INTERWIDTH - Width;

					MoveDown = FALSE;
				}
			}

				/* Are we to centre the object in
				 * the window?
				 */

			if(ThisTag = FindTagItem(SZ_AlignCentre,TagList))
			{
				if(ThisTag -> ti_Data)
				{
					Left = (SZ_WindowWidth - Width) / 2;

					MoveDown = FALSE;
				}
			}

				/* Are we to build a horizontal group
				 * using the following objects?
				 */

			if(ThisTag = FindTagItem(SZ_GroupCount,TagList))
			{
				LONG	Count = ThisTag -> ti_Data,
					Width = SZ_WindowWidth;

					/* Use a specific left edge? */

				if(ThisTag = FindTagItem(SZ_UseLeft,TagList))
				{
					Width -= ThisTag -> ti_Data - InterWidth;

					SZ_GroupDelta	= SZ_CurrentWidth + ((Width - 2 * InterWidth) - (Count * SZ_CurrentWidth)) / (Count - 1);
					SZ_GroupX	= Left = ThisTag -> ti_Data;
				}
				else
				{
					SZ_GroupDelta	= SZ_CurrentWidth + ((Width - 2 * InterWidth) - (Count * SZ_CurrentWidth)) / (Count - 1);
					SZ_GroupX	= Left = SZ_Left;
				}

				MoveDown = FALSE;
			}
			else
			{
				if(ThisTag = FindTagItem(SZ_UseLeft,TagList))
					Left = ThisTag -> ti_Data;
			}

				/* Skip to the next group item position? */

			if(ThisTag = FindTagItem(SZ_GroupNext,TagList))
			{
				if(ThisTag -> ti_Data)
				{
					SZ_GroupX += SZ_GroupDelta;

					Left = SZ_GroupX;

					MoveDown = FALSE;
				}
			}

				/* Determine default label placement. */

			switch(Kind)
			{
				case MX_KIND:
				case CHECKBOX_KIND:	New -> ng_Flags	= (New -> ng_Flags & ~(PLACETEXT_LEFT|PLACETEXT_RIGHT|PLACETEXT_ABOVE|PLACETEXT_BELOW|PLACETEXT_IN)) | PLACETEXT_LEFT;

							SaveWidth = FALSE;

							break;

				default:		if(New -> ng_Flags & PLACETEXT_ABOVE)
								SZ_CurrentTop += INTERHEIGHT + SZ_TextFont -> tf_YSize;

							break;
			}

				/* Gadget is to be as wide as the entire window? */

			if(ThisTag = FindTagItem(SZ_FullWidth,TagList))
			{
				if(ThisTag -> ti_Data)
					Width = SZ_WindowWidth - 2 * INTERWIDTH;
			}

				/* Gadget is to be as high as the entire window? */

			if(ThisTag = FindTagItem(SZ_FullHeight,TagList))
			{
				if(ThisTag -> ti_Data)
					Height = SZ_WindowHeight - InterHeight - SZ_Top;
			}

				/* Use a specific top edge? */

			if(ThisTag = FindTagItem(SZ_UseTop,TagList))
				SZ_CurrentTop = ThisTag -> ti_Data;

				/* Fill in the NewGadget. */

			New -> ng_LeftEdge	= Left;
			New -> ng_TopEdge	= SZ_CurrentTop;
			New -> ng_Width		= Width;
			New -> ng_Height	= Height;

				/* Add the default tags. */

			switch(Kind)
			{
				case PALETTE_KIND:	MoreTags[0] . ti_Tag	= GTPA_IndicatorWidth;
							MoreTags[0] . ti_Data	= Width / 4;

							MoreTags[1] . ti_Tag	= GTPA_IndicatorHeight;
							MoreTags[1] . ti_Data	= Height;

							MoreTags[2] . ti_Tag	= TAG_MORE;
							MoreTags[2] . ti_Data	= (ULONG)TagList;

							TagList = MoreTags;

							break;

				case LISTVIEW_KIND:	MoreTags[0] . ti_Tag	= GTLV_ScrollWidth;
							MoreTags[0] . ti_Data	= 2 + 2 * SZ_TextFont -> tf_XSize + 2;

							MoreTags[1] . ti_Tag	= TAG_MORE;
							MoreTags[1] . ti_Data	= (ULONG)TagList;

							TagList = MoreTags;

							break;

				case CHECKBOX_KIND:	MoreTags[0] . ti_Tag	= GTCB_Scaled;
							MoreTags[0] . ti_Data	= TRUE;

							MoreTags[1] . ti_Tag	= TAG_MORE;
							MoreTags[1] . ti_Data	= (ULONG)TagList;

							TagList = MoreTags;

							break;

				case MX_KIND:		MoreTags[0] . ti_Tag	= GTMX_Scaled;
							MoreTags[0] . ti_Data	= TRUE;

							MoreTags[1] . ti_Tag	= TAG_MORE;
							MoreTags[1] . ti_Data	= (ULONG)TagList;

							TagList = MoreTags;

							break;

				default:		break;
			}

				/* Restore default object width if necessary. */

			if(SaveWidth)
				SZ_CurrentWidth	= Width;

				/* Are we to leave the top edge untouched? */

			if(SetBelow)
				MoveDown = FALSE;

			if(MoveDown)
			{
					/* Move down to next object. */

				SZ_CurrentTop = SZ_CurrentTop + Height + InterHeight;

					/* Extend window height if necessary. */

				if(SZ_CurrentTop > SZ_WindowHeight)
					SZ_WindowHeight = SZ_CurrentTop;
			}
			else
			{
					/* Extend window height if necessary. */

				if(SZ_CurrentTop + Height + InterHeight > SZ_WindowHeight)
					SZ_WindowHeight = SZ_CurrentTop + Height + InterHeight;

				SZ_MaxWidth = 0;
			}

				/* Update maximum object width. */

			if(Width > SZ_MaxWidth)
				SZ_MaxWidth = Width;

				/* Extend window width if necessary. */

			if(Left + SZ_MaxWidth + INTERWIDTH > SZ_WindowWidth)
				SZ_WindowWidth = Left + SZ_MaxWidth + INTERWIDTH;

				/* Are we to leave the left edge untouched? */

			if(!SetLeft)
				SZ_CurrentLeft = Left;
		}
	}

		/* Now for keystroke activation... */

	switch(Kind)
	{
		case STRING_KIND:
		case INTEGER_KIND:
		case BUTTON_KIND:
		case CHECKBOX_KIND:
		case CYCLE_KIND:
		case LISTVIEW_KIND:
		case MX_KIND:
		case PALETTE_KIND:
		case SCROLLER_KIND:
		case SLIDER_KIND:

				/* Do we have an underscore character to
				 * play with?
				 */

			if(ThisTag = FindTagItem(GT_Underscore,TagList))
				Underscore = ThisTag -> ti_Data;

			if(Underscore)
			{
					/* Do we have a gadget label? */

				if(New -> ng_GadgetText)
				{
						/* Look for the underscore
						 * character...
						 */

					for(i = 0 ; i < strlen(New -> ng_GadgetText) ; i++)
					{
						if(New -> ng_GadgetText[i] == Underscore)
						{
								/* Is it a printable character? */

							if(New -> ng_GadgetText[i + 1] > ' ')
							{
									/* Create information buffer. */

								if(Info = (struct GadgetSupportInfo *)AllocMem(sizeof(struct GadgetSupportInfo),MEMF_ANY|MEMF_CLEAR))
								{
										/* Set up the default data. */

									Info -> PointBack	= Info;
									Info -> Kind		= Kind;
									Info -> Key		= New -> ng_GadgetText[i + 1];

										/* Add object-specific information. */

									if(Info -> Kind == PALETTE_KIND)
									{
										Info -> Current	= 1;
										Info -> Max	= 1;
									}

									if(Info -> Kind == LISTVIEW_KIND)
										Info -> Current	= ~0;

									break;
								}
							}
						}
					}
				}
			}

			break;

		default:

			break;
	}

		/* Fill in object info if available. */

	New -> ng_UserData = Info;

		/* Did we create any object info? */

	if(Info)
	{
		struct Node	*Node;
		struct List	*List;
		LONG		 Count;
		STRPTR		*Strings;

			/* The following code takes care of object-specific
			 * characteristics, such as currently selected items,
			 * current value, minimum value, maximum value, etc.
			 * and places them in the gadget info.
			 */

		switch(Kind)
		{
			case CHECKBOX_KIND:	if(ThisTag = FindTagItem(GTCB_Checked,TagList))
							Info -> Current = ThisTag -> ti_Data;

						break;

			case CYCLE_KIND:	if(ThisTag = FindTagItem(GTCY_Active,TagList))
							Info -> Current = ThisTag -> ti_Data;

						if(ThisTag = FindTagItem(GTCY_Labels,TagList))
						{
							Count = 0;

							if(Strings = (STRPTR *)ThisTag -> ti_Data)
							{
								while(Strings[Count])
									Count++;
							}

							if(Count)
								Info -> Max = Count - 1;
						}

						break;

			case LISTVIEW_KIND:	if(ThisTag = FindTagItem(GTLV_Selected,TagList))
							Info -> Current = ThisTag -> ti_Data;

						if(ThisTag = FindTagItem(GTLV_ReadOnly,TagList))
						{
							if(ThisTag -> ti_Data)
								Info -> ReadOnly = TRUE;
						}

						if(ThisTag = FindTagItem(GTLV_ShowSelected,TagList))
						{
							if(ThisTag -> ti_Data)
								Info -> ReadOnly = FALSE;
						}

						if(ThisTag = FindTagItem(GTLV_Labels,TagList))
						{
							Count = 0;

							if(ThisTag -> ti_Data && ThisTag -> ti_Data != ~0)
							{
								List = (struct List *)ThisTag -> ti_Data;

								Node = List -> lh_Head;

								while(Node -> ln_Succ)
								{
									Count++;

									Node = Node -> ln_Succ;
								}
							}

							if(Count)
								Info -> Max = Count - 1;
						}

						break;

			case MX_KIND:		if(ThisTag = FindTagItem(GTMX_Active,TagList))
							Info -> Current = ThisTag -> ti_Data;

						if(ThisTag = FindTagItem(GTMX_Labels,TagList))
						{
							Count = 0;

							if(Strings = (STRPTR *)ThisTag -> ti_Data)
							{
								while(Strings[Count])
									Count++;
							}

							if(Count)
								Info -> Max = Count - 1;
						}

						break;

			case SCROLLER_KIND:	if(ThisTag = FindTagItem(GTSC_Top,TagList))
							Info -> Current = ThisTag -> ti_Data;

						if(ThisTag = FindTagItem(GTSC_Total,TagList))
							Info -> Max = ThisTag -> ti_Data;

						break;

			case SLIDER_KIND:	if(ThisTag = FindTagItem(GTSL_Level,TagList))
							Info -> Current = ThisTag -> ti_Data;

						if(ThisTag = FindTagItem(GTSL_Min,TagList))
							Info -> Min = ThisTag -> ti_Data;

						if(ThisTag = FindTagItem(GTSL_Max,TagList))
							Info -> Max = ThisTag -> ti_Data;

						break;

			case PALETTE_KIND:	if(ThisTag = FindTagItem(GTPA_Color,TagList))
							Info -> Current = ThisTag -> ti_Data;

						if(ThisTag = FindTagItem(GTPA_Depth,TagList))
							Info -> Max = (1 << ((LONG)ThisTag -> ti_Data)) - 1;

						break;

			default:		break;
		}

			/* Create the object and attach the info to it. */

		if(Result = OldCreateGadgetA(Kind,Previous,New,TagList))
			Info -> Gadget = Result;
		else
			FreeMem(Info,sizeof(struct GadgetSupportInfo));
	}
	else
		Result = OldCreateGadgetA(Kind,Previous,New,TagList);

	va_end(VarArgs);

	return(Result);
}

	/* GT_SetGadgetAttrs():
	 *
	 *	A frontend to the original routine.
	 */

VOID
GT_SetGadgetAttrs(struct Gadget *Gadget,struct Window *Window,struct Requester *Requester,...)
{
	va_list				 VarArgs;
	struct TagItem			*TagList,
					*ThisTag;
	struct GadgetSupportInfo	*Info;

		/* Get the tag item list. */

	va_start(VarArgs,Requester);

	TagList = (struct TagItem *)VarArgs;

		/* Does the object in question have an info structure attached? */

	if(Info = (struct GadgetSupportInfo *)Gadget -> UserData)
	{
			/* Does the data look sensible? */

		if(Info -> PointBack == Info && Info -> Gadget == Gadget)
		{
			struct Node	*Node;
			struct List	*List;
			LONG		 Count;
			STRPTR		*Strings;

				/* Update object-specific information. */

			switch(Info -> Kind)
			{
				case CHECKBOX_KIND:	if(ThisTag = FindTagItem(GTCB_Checked,TagList))
								Info -> Current = ThisTag -> ti_Data;

							break;

				case CYCLE_KIND:	if(ThisTag = FindTagItem(GTCY_Active,TagList))
								Info -> Current = ThisTag -> ti_Data;

							if(ThisTag = FindTagItem(GTCY_Labels,TagList))
							{
								Count = 0;

								if(Strings = (STRPTR *)ThisTag -> ti_Data)
								{
									while(Strings[Count])
										Count++;
								}

								if(Count)
									Info -> Max = Count - 1;
								else
									Info -> Max = 0;
							}

							break;

				case LISTVIEW_KIND:	if(ThisTag = FindTagItem(GTLV_Selected,TagList))
								Info -> Current = ThisTag -> ti_Data;

							if(ThisTag = FindTagItem(GTLV_Labels,TagList))
							{
								Count = 0;

								if(ThisTag -> ti_Data && ThisTag -> ti_Data != ~0)
								{
									List = (struct List *)ThisTag -> ti_Data;

									Node = List -> lh_Head;

									while(Node -> ln_Succ)
									{
										Count++;

										Node = Node -> ln_Succ;
									}
								}

								if(Count)
									Info -> Max = Count - 1;
								else
									Info -> Max = 0;
							}

							break;

				case PALETTE_KIND:	if(ThisTag = FindTagItem(GTPA_Color,TagList))
								Info -> Current = ThisTag -> ti_Data;

							break;

				case MX_KIND:		if(ThisTag = FindTagItem(GTMX_Active,TagList))
								Info -> Current = ThisTag -> ti_Data;

							break;

				case SCROLLER_KIND:	if(ThisTag = FindTagItem(GTSC_Top,TagList))
								Info -> Current = ThisTag -> ti_Data;

							if(ThisTag = FindTagItem(GTSC_Total,TagList))
								Info -> Max = ThisTag -> ti_Data;

							break;

				case SLIDER_KIND:	if(ThisTag = FindTagItem(GTSL_Level,TagList))
								Info -> Current = ThisTag -> ti_Data;

							if(ThisTag = FindTagItem(GTSL_Min,TagList))
								Info -> Min = ThisTag -> ti_Data;

							break;

				default:		break;
			}
		}
	}

		/* Call the original routine. */

	OldGT_SetGadgetAttrsA(Gadget,Window,Requester,TagList);

	va_end(VarArgs);
}

	/* FreeGadgets(struct Gadget *GadgetList):
	 *
	 *	A frontend to the original routine.
	 */

VOID
FreeGadgets(struct Gadget *GadgetList)
{
	struct Gadget			*Gadget = GadgetList;
	struct GadgetSupportInfo	*Info;

		/* Run down the gadget list. */

	while(Gadget)
	{
			/* Does it have an info structure attached?*/

		if(Info = (struct GadgetSupportInfo *)Gadget -> UserData)
		{
				/* Does the data make sense? */

			if(Info -> PointBack == Info && Info -> Gadget == Gadget)
				FreeMem(Info,sizeof(struct GadgetSupportInfo));
		}

			/* Skip to next object. */

		Gadget = Gadget -> NextGadget;
	}

	OldFreeGadgets(GadgetList);
}

	/* GT_ReplyIMsg(struct IntuiMessage *Message):
	 *
	 *	A frontend to the original routine.
	 */

VOID
GT_ReplyIMsg(struct IntuiMessage *Message)
{
		/* Is this really a message? */

	if(Message)
	{
			/* The following input event classes are likely to
			 * affect a gadtools object.
			 */

		if(Message -> Class == IDCMP_GADGETUP || Message -> Class == IDCMP_GADGETDOWN || Message -> Class == IDCMP_MOUSEMOVE)
		{
			struct Gadget *Gadget;

				/* Take a look at the item attached. */

			if(Gadget = (struct Gadget *)Message -> IAddress)
			{
				struct GadgetSupportInfo *Info = Gadget -> UserData;

					/* If it's really a gadget, does it
					 * have an info structure attached?
					 */

				if(Info)
				{
						/* Does the data make sense? */

					if(Info -> PointBack == Info && Info -> Gadget == Gadget)
					{
						LONG Value = (WORD)Message -> Code;

							/* Update the object information. */

						switch(Info -> Kind)
						{
							case CHECKBOX_KIND:

								if(Gadget -> Flags & GFLG_SELECTED)
									Info -> Current = TRUE;
								else
									Info -> Current = FALSE;

								break;

							case CYCLE_KIND:

								Info -> Current = Message -> Code;

								break;

							case LISTVIEW_KIND:

								Info -> Current = Message -> Code;

								break;

							case MX_KIND:

								Info -> Current = Message -> Code;

								break;

							case SCROLLER_KIND:

								Info -> Current = Message -> Code;

								break;

							case SLIDER_KIND:

								if(Info -> Min >= 0 && Value < 0)
									Info -> Current = Message -> Code;
								else
									Info -> Current = Value;

								break;

							case PALETTE_KIND:

								Info -> Current = Message -> Code;

								break;

							default:

								break;
						}
					}
				}
			}
		}
	}

		/* Reply the message correctly. */

	OldGT_ReplyIMsg(Message);
}

	/* KeySelect():
	 *
	 *	Handles gadget keystroke activation.
	 */

VOID
KeySelect(struct Gadget **GadgetList,WORD Last,UBYTE Key,struct Window *Window,struct Gadget **Gadget,ULONG *Class,LONG *Code)
{
		/* If an escape code comes in and the window does in
		 * fact have a close gadget, return a close window
		 * event.
		 */

	if(Key == '\033' && (Window -> Flags & WFLG_CLOSEGADGET))
		*Class = IDCMP_CLOSEWINDOW;
	else
	{
		struct GadgetSupportInfo	*Info;
		WORD				 i;
		BYTE				 Forward,
						 GotIt = FALSE;

			/* Run down all the gadgets. */

		for(i = 0 ; !GotIt && i <= Last ; i++)
		{
				/* Is this gadget enabled? */

			if(!(GadgetList[i] -> Flags & GFLG_DISABLED))
			{
					/* Does it have an info structure attached? */

				if(Info = GadgetList[i] -> UserData)
				{
						/* Does it make sense? */

					if(Info -> PointBack == Info && Info -> Gadget == GadgetList[i])
					{
							/* Do the keys match? */

						if(ToUpper(Info -> Key) == ToUpper(Key))
						{
								/* Are we cycle forward
								 * or backwards?
								 */

							if(ToUpper(Info -> Key) == Key)
								Forward = FALSE;
							else
								Forward = TRUE;

								/* Update the object information. */

							switch(Info -> Kind)
							{
								case STRING_KIND:
								case INTEGER_KIND:	ActivateGadget(ActiveGadget = GadgetList[i],Window,NULL);

											*Class	= IDCMP_GADGETDOWN;
											*Code	= 0;
											*Gadget	= GadgetList[i];

											GotIt = TRUE;

											break;

								case BUTTON_KIND:	*Class	= IDCMP_GADGETUP;
											*Code	= 0;
											*Gadget	= GadgetList[i];

											GotIt = TRUE;

											break;

								case CHECKBOX_KIND:	GT_SetGadgetAttrs(GadgetList[i],Window,NULL,
												GTCB_Checked,	Info -> Current ? FALSE : TRUE,
											TAG_DONE);

											*Class	= IDCMP_GADGETUP;
											*Code	= Info -> Current;
											*Gadget	= GadgetList[i];

											GotIt = TRUE;

											break;

								case CYCLE_KIND:	if(Forward)
											{
												GT_SetGadgetAttrs(GadgetList[i],Window,NULL,
													GTCY_Active,	(Info -> Current == Info -> Max) ? 0 : Info -> Current + 1,
												TAG_DONE);
											}
											else
											{
												GT_SetGadgetAttrs(GadgetList[i],Window,NULL,
													GTCY_Active,	Info -> Current ? Info -> Current - 1 : Info -> Max,
												TAG_DONE);
											}

											*Class	= IDCMP_GADGETUP;
											*Code	= Info -> Current;
											*Gadget	= GadgetList[i];

											GotIt = TRUE;

											break;

								case LISTVIEW_KIND:	if(Forward)
											{
												if(Info -> Current == ~0)
													Info -> Current = -1;

												if(Info -> Current < Info -> Max)
												{
													if(Info -> ReadOnly)
													{
														GT_SetGadgetAttrs(GadgetList[i],Window,NULL,
															GTLV_Top,	Info -> Current + 1,
														TAG_DONE);
													}
													else
													{
														GT_SetGadgetAttrs(GadgetList[i],Window,NULL,
															GTLV_Selected,	Info -> Current + 1,
															GTLV_Top,	Info -> Current + 1,
														TAG_DONE);
													}

													*Class	= IDCMP_GADGETUP;
													*Code	= Info -> Current;
													*Gadget	= GadgetList[i];
												}
											}
											else
											{
												if(Info -> Current == ~0)
													Info -> Current = Info -> Max + 1;

												if(Info -> Current > Info -> Min)
												{
													if(Info -> ReadOnly)
													{
														GT_SetGadgetAttrs(GadgetList[i],Window,NULL,
															GTLV_Top,	Info -> Current - 1,
														TAG_DONE);
													}
													else
													{
														GT_SetGadgetAttrs(GadgetList[i],Window,NULL,
															GTLV_Selected,	Info -> Current - 1,
															GTLV_Top,	Info -> Current - 1,
														TAG_DONE);
													}

													*Class	= IDCMP_GADGETUP;
													*Code	= Info -> Current;
													*Gadget	= GadgetList[i];
												}
											}

											GotIt = TRUE;

											break;

								case MX_KIND:		if(Forward)
											{
												if(Info -> Current < Info -> Max)
												{
													GT_SetGadgetAttrs(GadgetList[i],Window,NULL,
														GTMX_Active,	*Code = Info -> Current + 1,
													TAG_DONE);

													*Class	= IDCMP_GADGETUP;
													*Code	= Info -> Current;
													*Gadget	= GadgetList[i];
												}
											}
											else
											{
												if(Info -> Current > Info -> Min)
												{
													GT_SetGadgetAttrs(GadgetList[i],Window,NULL,
														GTMX_Active,	*Code = Info -> Current - 1,
													TAG_DONE);

													*Class	= IDCMP_GADGETUP;
													*Code	= Info -> Current;
													*Gadget	= GadgetList[i];
												}
											}

											GotIt = TRUE;

											break;

								case PALETTE_KIND:	if(Forward)
											{
												GT_SetGadgetAttrs(GadgetList[i],Window,NULL,
													GTPA_Color,	(Info -> Current == Info -> Max) ? 0 : Info -> Current + 1,
												TAG_DONE);
											}
											else
											{
												GT_SetGadgetAttrs(GadgetList[i],Window,NULL,
													GTPA_Color,	Info -> Current ? Info -> Current - 1 : Info -> Max,
												TAG_DONE);
											}

											*Class	= IDCMP_GADGETUP;
											*Code	= Info -> Current;
											*Gadget	= GadgetList[i];

											GotIt = TRUE;

											break;

								case SCROLLER_KIND:	if(Forward)
											{
												if(Info -> Current < Info -> Max)
												{
													GT_SetGadgetAttrs(GadgetList[i],Window,NULL,
														GTSC_Top,	Info -> Current + 1,
													TAG_DONE);

													*Class	= IDCMP_MOUSEMOVE;
													*Code	= Info -> Current;
													*Gadget	= GadgetList[i];
												}
											}
											else
											{
												if(Info -> Current > Info -> Min)
												{
													GT_SetGadgetAttrs(GadgetList[i],Window,NULL,
														GTSC_Top,	Info -> Current - 1,
													TAG_DONE);

													*Class	= IDCMP_MOUSEMOVE;
													*Code	= Info -> Current;
													*Gadget	= GadgetList[i];
												}
											}

											GotIt = TRUE;

											break;

								case SLIDER_KIND:	if(Forward)
											{
												if(Info -> Current < Info -> Max)
												{
													GT_SetGadgetAttrs(GadgetList[i],Window,NULL,
														GTSL_Level,	Info -> Current + 1,
													TAG_DONE);

													*Class	= IDCMP_MOUSEMOVE;
													*Code	= Info -> Current;
													*Gadget	= GadgetList[i];
												}
											}
											else
											{
												if(Info -> Current > Info -> Min)
												{
													GT_SetGadgetAttrs(GadgetList[i],Window,NULL,
														GTSL_Level,	Info -> Current - 1,
													TAG_DONE);

													*Class	= IDCMP_MOUSEMOVE;
													*Code	= Info -> Current;
													*Gadget	= GadgetList[i];
												}
											}

											GotIt = TRUE;

											break;

								default:		break;
							}
						}
					}
				}
			}
		}

			/* If no matching object was found, take a closer look at the types. */

		if(!GotIt)
		{
			for(i = 0 ; i <= Last ; i++)
			{
					/* If it's a string object, activat it. */

				if(!(GadgetList[i] -> Flags & GFLG_DISABLED) && (GadgetList[i] -> GadgetType & GTYP_GTYPEMASK) == GTYP_STRGADGET)
				{
					ActivateGadget(ActiveGadget = GadgetList[i],Window,NULL);

					*Class	= IDCMP_GADGETDOWN;
					*Code	= 0;
					*Gadget	= GadgetList[i];

					break;
				}
			}
		}
	}
}

	/* SZ_ResetMaxWidth():
	 *
	 *	Reset maximum object width and return current maximum width.
	 */

LONG
SZ_ResetMaxWidth()
{
	LONG Width = SZ_WidthMax;

	SZ_WidthMax = 0;

	return(Width);
}

	/* SZ_UpdateMaxWidth():
	 *
	 *	Update current maximum object width.
	 */

VOID __regargs
SZ_UpdateMaxWidth(UBYTE Type,STRPTR Title,LONG Chars,STRPTR *Choices)
{
	LONG Width = SZ_Width(Type,Title,Chars,Choices);

	if(Width > SZ_WidthMax)
		SZ_WidthMax = Width;
}

	/* SZ_GetWindowWidth():
	 *
	 *	Get the current window width.
	 */

LONG
SZ_GetWindowWidth()
{
	return(SZ_WindowWidth);
}

	/* SZ_GetWindowHeight():
	 *
	 *	Get the current window height.
	 */

LONG
SZ_GetWindowHeight()
{
	return(SZ_WindowHeight + SZ_Screen -> WBorBottom);
}

	/* SZ_GetLeftEdge():
	 *
	 *	Get the current object left edge.
	 */

LONG
SZ_GetLeftEdge()
{
	return(SZ_CurrentLeft);
}

	/* SZ_GetTopEdge():
	 *
	 *	Get the current object top edge.
	 */

LONG
SZ_GetTopEdge()
{
	return(SZ_CurrentTop);
}

	/* SZ_SetTopEdge(LONG Top):
	 *
	 *	Set the current object top edge.
	 */

VOID __regargs
SZ_SetTopEdge(LONG Top)
{
	SZ_CurrentTop = Top;
}

	/* SZ_SetLeftEdge(LONG Left):
	 *
	 *	Set the current object left edge.
	 */

VOID __regargs
SZ_SetLeftEdge(LONG Left)
{
	SZ_CurrentLeft = Left;
}

	/* SZ_SetAbsoluteTop(LONG Top):
	 *
	 *	Set new inner window top edge.
	 */

VOID __regargs
SZ_SetAbsoluteTop(LONG Top)
{
	SZ_Top = Top;
}

	/* SZ_SetAbsoluteLeft(LONG Left):
	 *
	 *	Set new inner window left edge.
	 */

VOID __regargs
SZ_SetAbsoluteLeft(LONG Left)
{
	SZ_Left = Left;
}

	/* SZ_SetWidth(LONG Width):
	 *
	 *	Set current object width.
	 */

VOID __regargs
SZ_SetWidth(LONG Width)
{
	SZ_CurrentWidth = Width;
}

	/* SZ_GetDimension(LONG *Width,LONG *Height):
	 *
	 *	Get current window dimensions.
	 */

VOID __regargs
SZ_GetDimension(LONG *Width,LONG *Height)
{
	*Width	= SZ_WindowWidth;
	*Height	= SZ_WindowHeight + SZ_Screen -> WBorBottom;
}

	/* SZ_TextWidth(STRPTR String):
	 *
	 *	Determine string width in pixels.
	 */

LONG __regargs
SZ_TextWidth(STRPTR String)
{
	UBYTE	TempString[256];
	WORD	i,j;

	for(i = j = 0 ; String[i] ; i++)
	{
		if(String[i] != '_')
			TempString[j++] = String[i];
	}

	return(TextLength(SZ_RPort,TempString,j));
}

	/* SZ_AddLeftOffset(LONG Offset):
	 *
	 *	Update current object left offset.
	 */

VOID __regargs
SZ_AddLeftOffset(LONG Offset)
{
	SZ_CurrentLeft += Offset;
}

	/* SZ_LeftOffsetN(LONG DataArray,...):
	 *
	 *	Determine the maximum length of a number of
	 *	gadget labels (first, second, third item, -1 terminates
	 *	the array).
	 */

LONG __stdargs
SZ_LeftOffsetN(LONG DataArray,...)
{
	extern STRPTR __regargs	LocaleString(LONG ID);

	LONG	*Data = &DataArray,
		 Len,
		 Max = 0;

	while(*Data != -1)
	{
		if((Len = SZ_TextWidth(LocaleString(*Data++))) > Max)
			Max = Len;
	}

	return(Max + INTERWIDTH);
}

	/* SZ_LeftOffsetDelta(LONG From,LONG To):
	 *
	 *	Determine the maximum length of a number of
	 *	gadget labels (given as first and last gadget).
	 */

LONG __regargs
SZ_LeftOffsetDelta(LONG From,LONG To)
{
	extern STRPTR __regargs	LocaleString(LONG ID);

	LONG	i,
		Len,
		Max = 0;

	for(i = From ; i <= To ; i++)
	{
		if((Len = SZ_TextWidth(LocaleString(i))) > Max)
			Max = Len;
	}

	return(Max + INTERWIDTH);
}

	/* SZ_SizeCleanup():
	 *
	 *	Free data allocated by SZ_SizeSetup().
	 */

VOID
SZ_SizeCleanup()
{
	if(SZ_TextFont)
	{
		CloseFont(SZ_TextFont);

		SZ_TextFont = NULL;
	}

	if(SZ_RPort)
	{
		FreeVec(SZ_RPort);

		SZ_RPort = NULL;
	}
}

	/* SZ_SizeSetup(struct Screen *Screen,struct TextAttr *TextAttr,BYTE Title):
	 *
	 *	Perform setups for gadget creation.
	 */

BYTE __regargs
SZ_SizeSetup(struct Screen *Screen,struct TextAttr *TextAttr,BYTE Title)
{
	struct DrawInfo *DrawInfo;

	SZ_SizeCleanup();

	if(DrawInfo = GetScreenDrawInfo(Screen))
	{
		SZ_TextPen = DrawInfo -> dri_Pens[TEXTPEN];

		FreeScreenDrawInfo(Screen,DrawInfo);
	}

	SZ_Screen = Screen;

	if(SZ_RPort = (struct RastPort *)AllocVec(sizeof(struct RastPort),MEMF_ANY | MEMF_CLEAR))
	{
		InitRastPort(SZ_RPort);

		if(!TextAttr)
			TextAttr = Screen -> Font;

		if(DiskfontBase)
			SZ_TextFont = (struct TextFont *)OpenDiskFont(TextAttr);
		else
			SZ_TextFont = (struct TextFont *)OpenFont(TextAttr);

		if(SZ_TextFont)
		{
			InterWidth = INTERWIDTH;

			if(SZ_TextFont -> tf_YSize <= 8)
				InterHeight = 1;
			else
				InterHeight = SZ_TextFont -> tf_YSize / 4;
				
			SetFont(SZ_RPort,SZ_TextFont);

			if(Title)
				SZ_Top	= Screen -> WBorTop + Screen -> Font -> ta_YSize + 1 + InterHeight;
			else
				SZ_Top	= Screen -> WBorTop + InterHeight;

			SZ_Left		= InterWidth;

			SZ_CurrentLeft	= InterWidth;
			SZ_CurrentTop	= SZ_Top;

			SZ_WindowWidth	= 0;
			SZ_WindowHeight	= 0;

			SZ_CurrentWidth	= 0;
			SZ_MaxWidth	= 0;

			return(TRUE);
		}
	}

	SZ_SizeCleanup();

	return(FALSE);
}

	/* SZ_Height(UBYTE Type,LONG Lines,BYTE Display):
	 *
	 *	Determine the height of an object.
	 */

LONG __regargs
SZ_Height(UBYTE Type,LONG Lines,BYTE Display)
{
	LONG Height = 0;

	switch(Type)
	{
		case LISTVIEW_KIND:	Height = 4 + Lines * SZ_TextFont -> tf_YSize + 4;

					if(Display == SZLV_Display)
						Height += 2 + SZ_TextFont -> tf_YSize + 2;

					if(Display == SZLV_String)
						Height += 3 + SZ_TextFont -> tf_YSize + 3;

					break;

		case INTEGER_KIND:
		case STRING_KIND:	Height = 3 + SZ_TextFont -> tf_YSize + 3;
					break;

		case CYCLE_KIND:
		case TEXT_KIND:
		case BUTTON_KIND:	Height = 2 + SZ_TextFont -> tf_YSize + 2;
					break;

		case CHECKBOX_KIND:	if(SZ_TextFont -> tf_YSize < 11)
						Height = 11;
					else
						Height = SZ_TextFont -> tf_YSize;

					break;

		case SLIDER_KIND:	Height = 1 + SZ_TextFont -> tf_YSize + 1;
					break;

		case MX_KIND:		Height = Lines * (SZ_TextFont -> tf_YSize + 1) - 1;
					break;

		case PALETTE_KIND:	Height = 2 * SZ_TextFont -> tf_YSize;
					break;
	}

	return(Height);
}

	/* SZ_Width(UBYTE Type,STRPTR Title,LONG Chars,STRPTR *Choices):
	 *
	 *	Determine the width of an object.
	 */

LONG __regargs
SZ_Width(UBYTE Type,STRPTR Title,LONG Chars,STRPTR *Choices)
{
	LONG	Width = 0,
		Len,Max = 0,i;

	switch(Type)
	{
		case LISTVIEW_KIND:	Width = (4 + Chars * SZ_TextFont -> tf_XSize + 4) + (2 + 2 * SZ_TextFont -> tf_XSize + 2) + 4;	/* KLUDGE + 4 */
					break;

		case TEXT_KIND:
		case BUTTON_KIND:	if(Title)
						Width = 6 + SZ_TextWidth(Title) + 6;
					else
						Width = 6 + Chars * SZ_TextFont -> tf_XSize + 6;

					break;

		case SLIDER_KIND:
		case STRING_KIND:
		case INTEGER_KIND:	Width = 6 + Chars * SZ_TextFont -> tf_XSize + 6;
					break;

		case CHECKBOX_KIND:	if(SZ_TextFont -> tf_XSize < 13)
						Width = 26;
					else
						Width = 2 * SZ_TextFont -> tf_XSize;

					break;

		case CYCLE_KIND:	if(Choices)
					{
						for(i = 0 ; Choices[i] ; i++)
						{
							if((Len = TextLength(SZ_RPort,Choices[i],strlen(Choices[i]))) > Max)
								Max = Len;
						}

						Width = 24 + Max + 4;
					}
					else
						Width = 24 + Chars * SZ_TextFont -> tf_XSize + 4;

					break;

		case MX_KIND:		if(SZ_TextFont -> tf_XSize < 8)
						Width = 17;
					else
						Width = 2 * SZ_TextFont -> tf_XSize;

					break;
	}

	return(Width);
}

	/* SZ_FreeBox(struct TextBox *Box):
	 *
	 *	Free a text box.
	 */

VOID __regargs
SZ_FreeBox(struct TextBox *Box)
{
	if(Box)
	{
		if(Box -> Text)
		{
			LONG i;

			for(i = 0 ; i < Box -> NumLines ; i++)
			{
				if(Box -> Text[i])
					FreeVec(Box -> Text[i]);
			}

			FreeVec(Box -> Text);
		}

		if(Box -> Title)
			FreeVec(Box -> Title);

		FreeVec(Box);
	}
}

	/* SZ_FreeBoxes(struct TextBox *FirstBox):
	 *
	 *	Free a number of text boxes.
	 */

VOID __regargs
SZ_FreeBoxes(struct TextBox *FirstBox)
{
	if(FirstBox)
	{
		struct TextBox *NextBox;

		do
		{
			NextBox = FirstBox -> NextBox;

			SZ_FreeBox(FirstBox);

			FirstBox = NextBox;
		}
		while(FirstBox);
	}
}

	/* SZ_BoxWidth(LONG Chars):
	 *
	 *	Determine the width of a text box.
	 */

LONG __regargs
SZ_BoxWidth(LONG Chars)
{
	return(4 + SZ_TextFont -> tf_XSize * Chars + 4);
}

	/* SZ_BoxHeight(LONG Lines):
	 *
	 *	Determine the height of a text box.
	 */

LONG __regargs
SZ_BoxHeight(LONG Lines)
{
	return(2 + SZ_TextFont -> tf_YSize * Lines + 2);
}

	/* SZ_CreateTextBox(struct TextBox **FirstBox,...):
	 *
	 *	Create a text box, this routine works similar
	 *	to the CreateGadget() frontend.
	 */

struct TextBox * __stdargs
SZ_CreateTextBox(struct TextBox **FirstBox,...)
{
	va_list				 VarArgs;
	struct TagItem			*TagList,
					*ThisTag;
	LONG				 Chars,Lines,
					 Width,
					 Height,
					 Left = SZ_CurrentLeft;
	BYTE				 AutoWidth	= FALSE,
					 MoveDown	= TRUE,
					 SetLeft	= FALSE,
					 SetBelow	= FALSE;

	struct TextBox			*Box;
	LONG				 i;

	va_start(VarArgs,FirstBox);

	TagList = (struct TagItem *)VarArgs;

	if(ThisTag = FindTagItem(SZ_Lines,TagList))
		Lines = (LONG)ThisTag -> ti_Data;
	else
		return(NULL);

	Height = 2 + SZ_TextFont -> tf_YSize * Lines + 2;

	if(ThisTag = FindTagItem(SZ_Chars,TagList))
		Chars = (LONG)ThisTag -> ti_Data;
	else
	{
		if(ThisTag = FindTagItem(SZ_AutoWidth,TagList))
			AutoWidth = ThisTag -> ti_Data;

		if(!AutoWidth)
			return(NULL);
		else
			Chars = (SZ_CurrentWidth - 8) / SZ_TextFont -> tf_XSize;
	}

	if(!(Box = (struct TextBox *)AllocVec(sizeof(struct TextBox),MEMF_ANY | MEMF_CLEAR)))
		return(NULL);

	if(ThisTag = FindTagItem(SZ_SetLeft,TagList))
		SetLeft = ThisTag -> ti_Data;

	if(ThisTag = FindTagItem(SZ_SetBelow,TagList))
		SetBelow = ThisTag -> ti_Data;

	if(ThisTag = FindTagItem(SZ_AlignBottom,TagList))
	{
		if(ThisTag -> ti_Data)
			SZ_CurrentTop = SZ_WindowHeight;
	}

	if(ThisTag = FindTagItem(SZ_AlignExtra,TagList))
	{
		if(ThisTag -> ti_Data)
			SZ_CurrentTop += InterHeight;
	}

	if(ThisTag = FindTagItem(SZ_NewColumn,TagList))
	{
		if(ThisTag -> ti_Data)
		{
			SZ_CurrentTop	= SZ_Top;
			Left		= Left + SZ_MaxWidth + InterWidth;

			SZ_MaxWidth	= 0;
		}
	}

	if(ThisTag = FindTagItem(SZ_AutoWidth,TagList))
		AutoWidth = ThisTag -> ti_Data;

	if(!AutoWidth)
		Width = SZ_BoxWidth(Chars);
	else
		Width = SZ_CurrentWidth;

	if(ThisTag = FindTagItem(SZ_AlignLeft,TagList))
	{
		if(ThisTag -> ti_Data)
		{
			Left = SZ_Left;

			MoveDown = FALSE;
		}
	}

	if(ThisTag = FindTagItem(SZ_AlignRight,TagList))
	{
		if(ThisTag -> ti_Data)
		{
			Left = SZ_WindowWidth - INTERWIDTH - Width;

			MoveDown = FALSE;
		}
	}

	if(ThisTag = FindTagItem(SZ_AlignCentre,TagList))
	{
		if(ThisTag -> ti_Data)
		{
			Left = (SZ_WindowWidth - Width) / 2;

			MoveDown = FALSE;
		}
	}

	if(ThisTag = FindTagItem(SZ_GroupCount,TagList))
	{
		LONG Count = ThisTag -> ti_Data,Width = SZ_WindowWidth;

		if(ThisTag = FindTagItem(SZ_UseLeft,TagList))
		{
			Width -= ThisTag -> ti_Data - InterWidth;

			SZ_GroupDelta	= SZ_CurrentWidth + ((Width - 2 * InterWidth) - (Count * SZ_CurrentWidth)) / (Count - 1);
			SZ_GroupX	= Left = ThisTag -> ti_Data;
		}
		else
		{
			SZ_GroupDelta	= SZ_CurrentWidth + ((Width - 2 * InterWidth) - (Count * SZ_CurrentWidth)) / (Count - 1);
			SZ_GroupX	= Left = SZ_Left;
		}

		MoveDown = FALSE;
	}
	else
	{
		if(ThisTag = FindTagItem(SZ_UseLeft,TagList))
			Left = ThisTag -> ti_Data;
	}

	if(ThisTag = FindTagItem(SZ_GroupNext,TagList))
	{
		if(ThisTag -> ti_Data)
		{
			SZ_GroupX += SZ_GroupDelta;

			Left = SZ_GroupX;

			MoveDown = FALSE;
		}
	}

	if(ThisTag = FindTagItem(SZ_UseTop,TagList))
		SZ_CurrentTop = ThisTag -> ti_Data;

	Box -> Left		= Left;
	Box -> Top		= SZ_CurrentTop;
	Box -> Width		= Width;
	Box -> Height		= Height;

	Box -> LineWidth	= Chars * SZ_TextFont -> tf_XSize;
	Box -> LineHeight	= SZ_TextFont -> tf_YSize;

	Box -> NumChars		= Chars;
	Box -> NumLines		= Lines;

	Box -> TextPen		= SZ_TextPen;

	if(!(Box -> Title = (STRPTR *)AllocVec(sizeof(STRPTR) * Lines,MEMF_ANY | MEMF_CLEAR)))
	{
		SZ_FreeBox(Box);

		return(NULL);
	}

	if(!(Box -> Text = (STRPTR *)AllocVec(sizeof(STRPTR) * Lines,MEMF_ANY | MEMF_CLEAR)))
	{
		SZ_FreeBox(Box);

		return(NULL);
	}

	for(i = 0 ; i < Lines ; i++)
	{
		if(!(Box -> Text[i] = (STRPTR)AllocVec(Chars + 1,MEMF_ANY | MEMF_CLEAR)))
		{
			SZ_FreeBox(Box);

			return(NULL);
		}
	}

	if(SetBelow)
		MoveDown = FALSE;

	if(MoveDown)
	{
		SZ_CurrentTop	= SZ_CurrentTop + Height + InterHeight;

		if(SZ_CurrentTop > SZ_WindowHeight)
			SZ_WindowHeight = SZ_CurrentTop;
	}
	else
	{
		if(SZ_CurrentTop + Height + InterHeight > SZ_WindowHeight)
			SZ_WindowHeight = SZ_CurrentTop + Height + InterHeight;

		SZ_MaxWidth = 0;
	}

	if(Width > SZ_MaxWidth)
		SZ_MaxWidth = Width;

	if(Left + SZ_MaxWidth + INTERWIDTH > SZ_WindowWidth)
		SZ_WindowWidth = Left + SZ_MaxWidth + INTERWIDTH;

	if(!SetLeft)
		SZ_CurrentLeft = Left;

	if(!(*FirstBox))
		*FirstBox = Box;
	else
	{
		struct TextBox *CurrentBox = *FirstBox;

		while(CurrentBox -> NextBox)
			CurrentBox = CurrentBox -> NextBox;

		CurrentBox -> NextBox = Box;
	}

	return(Box);
}

	/* SZ_SetBoxTitles(struct TextBox *Box,STRPTR Array,...):
	 *
	 *	Set the titles displayed in a text box.
	 */

VOID __stdargs
SZ_SetBoxTitles(struct TextBox *Box,STRPTR Array,...)
{
	if(Box)
	{
		STRPTR	*Data = &Array;
		LONG	 i = 0;

		while(*Data != NULL)
		{
			if(i < Box -> NumLines)
				Box -> Title[i++] = *Data;

			Data++;
		}
	}
}

	/* SZ_PrintLine():
	 *
	 *	Print a string into a text box.
	 */

VOID __stdargs
SZ_PrintLine(struct RastPort *RPort,struct TextBox *Box,LONG Line,STRPTR String,...)
{
	if(Box)
	{
		if(AttemptLockLayerRom(RPort -> Layer))
		{
			BYTE	FgPen = RPort -> FgPen,DrMd = RPort -> DrawMode;
			LONG	Width,Len,Left = Box -> Left + 4,Top = Box -> Top + 2 + Line * Box -> LineHeight;
			va_list	VarArgs;
			UBYTE	Buffer[256];

			va_start(VarArgs,String);
			VSPrintf(Buffer,String,VarArgs);
			va_end(VarArgs);

			if(DrMd != JAM2)
				SetDrMd(RPort,JAM2);

			SetAPen(RPort,Box -> TextPen);

			Len = strlen(Buffer);

			if(Len > Box -> NumChars)
				Len = Box -> NumChars;

			Width = TextLength(RPort,Buffer,Len);

			Move(RPort,Left,Top + RPort -> Font -> tf_Baseline);
			Text(RPort,Buffer,Len);

			if(Width != Box -> LineWidth)
			{
				if(FgPen != 0)
					SetAPen(RPort,0);

				RectFill(RPort,Left + Width,Top,Left + Box -> LineWidth - 1,Top + Box -> LineHeight - 1);
			}

			SetAPen(RPort,FgPen);

			if(DrMd != JAM2)
				SetDrMd(RPort,DrMd);

			if(String != Box -> Text[Line])
				strcpy(Box -> Text[Line],Buffer);

			UnlockLayerRom(RPort -> Layer);
		}
	}
}

	/* SZ_DrawBox(struct RastPort *RPort,struct TextBox *Box):
	 *
	 *	(Re-)Draw a text box.
	 */

VOID __regargs
SZ_DrawBox(struct RastPort *RPort,struct TextBox *Box)
{
	if(Box)
	{
		LONG LineY,i,Len,FgPen = RPort -> FgPen;

		DrawBevelBox(RPort,Box -> Left,Box -> Top,Box -> Width,Box -> Height,
			GT_VisualInfo,	VisualInfo,
			GTBB_Recessed,	TRUE,
		TAG_DONE);

		LineY = Box -> Top + 2 + RPort -> Font -> tf_Baseline;

		SetAPen(RPort,Box -> TextPen);

		for(i = 0 ; i < Box -> NumLines ; i++)
		{
			Len = strlen(Box -> Title[i]);

			Move(RPort,Box -> Left - INTERWIDTH - TextLength(RPort,Box -> Title[i],Len),LineY);
			Text(RPort,Box -> Title[i],Len);

			LineY += Box -> LineHeight;
		}

		SetAPen(RPort,FgPen);

		for(i = 0 ; i < Box -> NumLines ; i++)
			SZ_PrintLine(RPort,Box,i,Box -> Text[i]);
	}
}

	/* SZ_DrawBoxes(struct RastPort *RPort,struct TextBox *FirstBox):
	 *
	 *	(Re-)Draw a number of text boxes.
	 */

VOID __regargs
SZ_DrawBoxes(struct RastPort *RPort,struct TextBox *FirstBox)
{
	do
	{
		SZ_DrawBox(RPort,FirstBox);

		FirstBox = FirstBox -> NextBox;
	}
	while(FirstBox);
}
