/*****************************************************************************\
 * $VER: FontViewQ.c 1.2     DICE/LATTICE C/SAS C/AZTEC C + AmigaOS 2.04/2.1 *
 *                 _                                                         *
 *            _   // (c)1992 by "Quarky" Dieter Temme                        *
 *            \\ //                                                          *
 * :ts=4       \X/ --- Freeware --- ONLY AMIGA MAKES IT POSSIBLE             *
 *                                                                           *
 * commodity to show a character set table on the screen                     *
\*****************************************************************************/

#define PRGNAME "FontViewQ"
#define VERSION "1.2"
#define PRGDATE "22.9.92"

#define PRGNAMELEN 9 /* to avoid a 'strlen(const)' */

#include "amigacompq.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <exec/types.h>
#include <clib/alib_protos.h>
#include <clib/asl_protos.h>
#include <clib/commodities_protos.h>
#include <clib/diskfont_protos.h>
#include <clib/dos_protos.h>
#include <clib/exec_protos.h>
#include <clib/gadtools_protos.h>
#include <clib/graphics_protos.h>
#include <clib/icon_protos.h>
#include <clib/intuition_protos.h>
#include <clib/keymap_protos.h>
#include <clib/locale_protos.h>
#include <clib/utility_protos.h>
#include <devices/keymap.h>
#include <dos/dos.h>
#include <exec/libraries.h>
#include <exec/memory.h>
#include <graphics/text.h>
#include <intuition/gadgetclass.h>
#include <intuition/intuition.h>
#include <libraries/asl.h>
#include <libraries/commodities.h>
#include <libraries/gadtools.h>
#include <libraries/locale.h>

#ifdef PRAGMAS_
 #include <pragmas/asl_lib.h>
 #include <pragmas/exec_lib.h>
 #include <pragmas/commodities_lib.h>
 #include <pragmas/diskfont_lib.h>
 #include <pragmas/dos_lib.h>
 #include <pragmas/gadtools_lib.h>
 #include <pragmas/graphics_lib.h>
 #include <pragmas/icon_lib.h>
 #include <pragmas/intuition_lib.h>
 #include <pragmas/keymap_lib.h>
 #include <pragmas/locale_lib.h>
 #include <pragmas/utility_lib.h>
#endif

#ifdef LATTICE
 int CXBRK(void) { return 0; }  /* Disable Lattice CTRL-C handling */
 int chkabort(void) { return 0; }
#endif

#ifdef AZTEC_C
 void _wb_parse(void) {	extern long Enable_Abort; Enable_Abort= FALSE; }
 void _abort(void) {}
#endif

TEXT VersionString[]= "\0$VER: " PRGNAME " " VERSION " (" PRGDATE ")";

#define STRINGARRAY
#include "FontViewQ.h"

/*>> library bases and locale/catalog variables <<*/
extern struct Library *SysBase;
struct Library *AslBase, *CxBase, *DiskfontBase, *GadToolsBase, *GfxBase,
				*IconBase, *IntuitionBase, *KeymapBase, *LocaleBase,
				*UtilityBase;
struct Locale *locale;
struct Catalog *cat;

/*>> miscellaneous variables <<*/
struct NewBroker newbroker=		/* newbroker structure */
{	5, PRGNAME, "V" VERSION " (c)1992 by \"Quarky\" Dieter Temme", NULL,
	NBU_UNIQUE|NBU_NOTIFY, COF_SHOW_HIDE
};
CxObj *broker;					/* Commodities' broker structure */
TEXT *hotkey;					/* hotkey description */
struct MsgPort *brokport;		/* port for Commodities' messages */
struct FontRequester *freq;		/* ASL's font requester structure */
APTR vi;						/* Intuition's visual information */
struct Screen *pubscr;			/* pointer to public screen's structure */
struct Window *win;				/* pointer to FontViewQ's window structure */
struct Gadget *congad;			/* pointer to GadTools' anchor gadget struct */
struct RastPort *rp;			/* pointer to screen's rastport */
ULONG winsig;					/* signal flag of window's userport */
struct TextFont *tabfont;		/* actual font structures for table */
struct TextAttr tabtattr;
UWORD tabxsize;					/* number of keys in a line of the table */
UWORD tabnumx;					/* number of rows on the table page */
UWORD tabnumy;					/* number of lines on the table page */
UWORD tablen;					/* number of keys on one table page */
UWORD *tabmem;					/* pointer to memory for the key labels */
UWORD tablopage;				/* lowest key number on table page */
UWORD tabhipage;				/* highest key number on table page */
UWORD tablochar;				/* lowest key number in font */
UWORD tabhichar;				/* highest key number in font */
UBYTE tabpages;					/* number of pages of the table */
UBYTE tabpage;					/* actual page number (0..tabpages-1) */
UBYTE current;					/* current highlighted key */

/* miscellaneous definitions */
#define TAB_TOGGLE_		0		/* for CtrlTable_() */
#define TAB_CLOSE_		1
#define TAB_OPEN_		2
#define TAB_NEWPAGE_	3
#define TAB_NEWFONT_	4

#define SFON_INIT_		0		/* for SelectFont_() */
#define SFON_FILL_		1
#define SFON_SELECT_	2

#define GID_FONTSEL_	0		/* for struct NewGadget */
#define GID_FONT_		1
#define GID_CHAR_		2
#define GID_DECIMAL_	3
#define GID_STROKE_		4
#define GID_PAGE_		5
#define GID_HIDE_		6
#define GID_QUIT_		7
#define GID_ASCII_		8

#define MOUSE_SET_		0		/* for CtrlPointer_() */
#define MOUSE_CLR_		1
#define MOUSE_INIT_		2
#define MOUSE_REMOVE_	3

struct
{	BYTE line;				 /* line diff to previous gadget, last is -1 */
	UBYTE textnum;			 /* number of localized text string */
	BYTE kind;				 /* parameter for CreateGadget (and ng_Flags),
							    actually BUTTON_KIND and TEXT_KIND,
								negative: PLACETEXT_ABOVE */
	UBYTE length;			 /* for TEXT_KIND: number of chars */
	UWORD width;			 /* 0, later gets width in pixels */
	UWORD fullwidth;		 /* 0, later gets full width in pixels */
	struct Gadget *gad;		 /* NULL, later gets result of CreateGadget */
} gads[]=
{	{	0, MSG_FONTSEL, BUTTON_KIND,	0	},
	{	1, 0,			TEXT_KIND,		31	},
	{	1, MSG_CHAR,	TEXT_KIND,		1	},
	{	1, MSG_DECIMAL, TEXT_KIND,		3	},
	{	2, MSG_STROKE,	TEXT_KIND,		31	},
	{	3, MSG_PAGE,	SLIDER_KIND,	15	},
	{	1, MSG_HIDE,	BUTTON_KIND,	0	},
	{	1, MSG_QUIT,	BUTTON_KIND,	0	},
	{  -1									}
};


/*==== get catalog string localized if locale.library is open ====*/
TEXT *GetCatalogStrQ_(UBYTE num)
{	if (LocaleBase) return GetCatalogStr(cat, num, AppStrings[num].as_Str);
	return AppStrings[num].as_Str;
}

/*==== get character for "keyable" gagdets ====*/
WORD GetGadgetChar_(const ULONG num)
{	TEXT *s= strchr(GetCatalogStrQ_(num), '_');
	return s? ToUpper(s[1]) : -1;
}

/*==== clean up all before exit ====*/
void CleanUp_(void)
{	void CtrlTable_(UBYTE action);
	void CtrlPointer_(UBYTE action);

	CtrlTable_(TAB_CLOSE_);
	CtrlPointer_(MOUSE_REMOVE_);
	if (pubscr) UnlockPubScreen(NULL, pubscr);
	if (tabfont) CloseFont(tabfont);
	if (freq) FreeAslRequest((APTR)freq);
	if (broker) DeleteCxObjAll(broker);
	if (brokport)
	{	struct Message *msg;
		while (msg= GetMsg(brokport)) ReplyMsg(msg);
		DeleteMsgPort(brokport);
	}
	ArgArrayDone();
	if (LocaleBase)
	{	CloseCatalog(cat);
		CloseLocale(locale);
		CloseLibrary(LocaleBase);
	}
	if (AslBase) CloseLibrary(AslBase);
	if (CxBase) CloseLibrary(CxBase);
	if (DiskfontBase) CloseLibrary(DiskfontBase);
	if (GadToolsBase) CloseLibrary(GadToolsBase);
	if (GfxBase) CloseLibrary(GfxBase);
	if (IconBase) CloseLibrary(IconBase);
	if (IntuitionBase) CloseLibrary(IntuitionBase);
	if (KeymapBase) CloseLibrary(KeymapBase);
	if (UtilityBase) CloseLibrary(UtilityBase);
}

/*==== bring error text to the user ====*/
void ExitError_(TEXT *format, ...)
{	void CtrlPointer_(UBYTE action);

	static struct EasyStruct easystruct=
	{	sizeof(struct EasyStruct), 0, PRGNAME, NULL, NULL
	};
	ULONG idcmpflags;
	va_list ap;

	if (IntuitionBase)
	{	DisplayBeep(pubscr);
		va_start(ap, format);
		CtrlPointer_(MOUSE_SET_);
		easystruct.es_TextFormat= format;
		easystruct.es_GadgetFormat= GetCatalogStrQ_(MSG_ABORT_GAD);
		EasyRequestArgs(win, &easystruct, NULL, &va_arg(ap, TEXT *));
		CtrlPointer_(MOUSE_CLR_);
	}

	exit(RETURN_FAIL);
}

void ExitMemError_(void)
{	ExitError_(GetCatalogStrQ_(MSG_NOMEMORY));
}

/*==== set or clear busy pointer ====*/
void CtrlPointer_(UBYTE action)
{	static ULONG idcmpflags;

	/*>> busy mouse pointer image <<*/
	static UWORD *pimage;			/* mouse imagedata in chip mem */
	static UWORD mouseimage[]=		/* a 'simple sprite' */
	{	0, 0,
		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,
		0, 0
	};

	switch (action)
	{	case MOUSE_INIT_:
			if (!(pimage= AllocMem(sizeof(mouseimage), MEMF_CHIP)))
				ExitMemError_();
			CopyMem(mouseimage, pimage, sizeof(mouseimage));
			break;
		case MOUSE_SET_:
			if (pimage && win)
			{	idcmpflags= win->IDCMPFlags;
				ModifyIDCMP(win, IDCMP_MENUPICK);
				SetPointer(win, pimage, 16, 16, -6, 0);
			}
			break;
		case MOUSE_CLR_:
			if (pimage && win)
			{	ClearPointer(win);
				if (idcmpflags) ModifyIDCMP(win, idcmpflags);
			}
			break;
		case MOUSE_REMOVE_:
			if (pimage) FreeMem(pimage, sizeof(mouseimage));
			break;
	}
}

/*==== open library ====*/
struct Library *OpenLibraryQ_(TEXT *str)
{	struct Library *base;

	if (!(base= OpenLibrary(str, 0)))
		ExitError_(GetCatalogStrQ_(MSG_NOTFOUND), str);
	return base;
}

/*==== allocate memory ====*/
void *MAllocQ_(ULONG size)
{	void *ptr;

	if (!(ptr= (void *)malloc(size))) ExitMemError_();
	return ptr;
}

/*==== fill in text display gadgets ====*/
/*>>> necessary because GadTools don't center
      the text or print more than one line!
      NOTE: string has to end with '\n'!      <<<*/
void SetGadgetDisplay_(struct Gadget *gad, TEXT *str)
{	UWORD x= gad->LeftEdge, y= gad->TopEdge, height= gad->Height;
	UBYTE lines= 0;
	TEXT *s, *index;

	SetFont(win->RPort, win->WScreen->RastPort.Font);
	SetDrMd(win->RPort, JAM1);
	SetAPen(win->RPort, 0);
	RectFill(win->RPort, x+2, y+1, x+gad->Width-4, y+height-2);

	s= str; while (s= strchr(s, '\n'))
	{	s++;
		lines++;
	}
	y+= ((height+1-lines*(win->RPort->Font->tf_YSize+1))>>1)
		+win->RPort->Font->tf_Baseline;
	SetDrMd(win->RPort, JAM2);
	SetAPen(win->RPort, 1);
	s= str; while (*s)
	{	index= strchr(s, '\n');
		Move(win->RPort,
			x+((gad->Width-TextLength(win->RPort, s, index-s))>>1), y);
		Text(win->RPort, s, index-s);
		s= index+1;
		y+= win->RPort->Font->tf_YSize+1;
	}
}

/*==== handle fonts ====*/
void SelectFont_(UBYTE action)
{	struct RastPort *rport;

	if (action == SFON_INIT_)
	{	rport= &pubscr->RastPort;
		goto setfont;
	} else if (action == SFON_SELECT_)
	{	CtrlPointer_(MOUSE_SET_);
		if (AslRequestTags(freq,
			ASLFO_TitleText, GetCatalogStrQ_(MSG_CHOOSEFONT),
			ASLFO_Window, win,
			ASLFO_InitialName, tabtattr.ta_Name,
			ASLFO_InitialSize, tabtattr.ta_YSize,
			TAG_DONE))
		{	CloseFont(tabfont);
			rport= win->RPort;
			rport->Font= tabfont= OpenDiskFont(&freq->fo_Attr);

setfont:	tabtattr.ta_Name=  rport->Font->tf_Message.mn_Node.ln_Name;
			tabtattr.ta_YSize= rport->Font->tf_YSize;
			tabtattr.ta_Style= rport->Font->tf_Style;
			tabtattr.ta_Flags= rport->Font->tf_Flags;
			if (!tabfont) tabfont= OpenDiskFont(&tabtattr);

			if (tabtattr.ta_Flags&FPF_PROPORTIONAL) /* find biggest xsize */
			{	UBYTE x= tabfont->tf_LoChar, n;
				tabxsize= 0;
				do
				{	n= TextLength(rport, &x, 1);
					if (n > tabxsize) tabxsize= n;
				} while (x++ != tabfont->tf_HiChar);
			} else
				tabxsize= tabfont->tf_XSize;
		}
		CtrlPointer_(MOUSE_CLR_);
	} else /* if (action == SFON_FILL_) */
	{	TEXT s[31]= { ' ' };
		strcpy(s, tabtattr.ta_Name);
		sprintf(strrchr(s, '.'), "/%lu\n", tabtattr.ta_YSize);
		SetGadgetDisplay_(gads[GID_FONT_].gad, s);
	}
}

/*==== put description of keystroke for character into buffer ====*/
/*>>> actually function is guaranteed only for AmigaOS 2.04/2.1! <<<*/
void MapANSIChar_(UBYTE chr, TEXT buffer[256])
{	/* >> these definitions are hopefully ok in all national keymaps! << */
	#define NUMDEADKEYS 5				/* a maximum of 16 is possible */
	static TEXT deadkeys[NUMDEADKEYS]=	/* rawkey codes of deadkeys */
	{	0x23, 0x24, 0x25, 0x26, 0x27
	};

	struct KeyMap *km= AskKeyMapDefault(); /* pointer to default keymap */
	UBYTE qualflags;					/* qualifiers SHIFT, ALT, CTRL */
	UBYTE key;							/* raw key number 0x00 .. 0x7f */
	UBYTE dead= 0;						/* number of deadkey from table */

	const static BYTE mapoffsets[8][8]= /* [LoKeyMapTypes[lokey]][qualflags] */
	{	/*  none,  S  ,   A ,  SA ,    C,  S C,   AC,  SAC */
		{	0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff	},	/* none */
		{	0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff	},	/* S    */
		{	0x00, 0xff, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff	},	/*  A   */
		{	0x00, 0x01, 0x02, 0x03, 0xff, 0xff, 0xff, 0xff	},	/* SA   */
		{	0x00, 0xff, 0xff, 0xff, 0x01, 0xff, 0xff, 0xff	},	/*   C  */
		{	0x00, 0x01, 0xff, 0xff, 0x02, 0x03, 0xff, 0xff	},	/* S C  */
		{	0x00, 0xff, 0x01, 0xff, 0x02, 0xff, 0x03, 0xff	},	/*  AC  */
		{	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07	}	/* SAC  */
	};

	buffer[0]= '\0';

	/*- get control characters using the common way -*/
	if ((chr&0x7f) < ' ')
	{	union
		{	UWORD word;
			struct
			{	UBYTE code, qual;
			} bytes;
		} buf;

		if (MapANSI(&chr, 1, &buf, 1, NULL) > 0)
		{	key= buf.bytes.code;
			qualflags= 0;
			if (buf.bytes.qual&(IEQUALIFIER_LSHIFT|IEQUALIFIER_RSHIFT))
				qualflags|= KCF_SHIFT;
			if (buf.bytes.qual&(IEQUALIFIER_LALT|IEQUALIFIER_RALT))
				qualflags|= KCF_ALT;
			if (buf.bytes.qual&(IEQUALIFIER_CONTROL))
				qualflags|= KCF_CONTROL;
			goto found;
		}
	}

	/*- find rawkey qualifier and code in keymap -*/
	{	BYTE offset;				/* byte offset into LoKeyMap[lokey] */
		BOOL deadflag;				/* FALSE: search non-dead, TRUE: dead */
		UBYTE *kmtypes;				/* pointer into km_??KeyMapTypes */
		ULONG *kmmap;				/* pointer into km_??KeyMap */

		for (deadflag= FALSE; deadflag <= TRUE; deadflag++)
		{	for (qualflags= KC_NOQUAL; qualflags <= KC_VANILLA; qualflags++)
			{	for (key= 0; key <= 0x78; key++)
				{	if (!(key&0x3f))
					{	kmtypes= key&0x40?
							km->km_HiKeyMapTypes : km->km_LoKeyMapTypes;
						kmmap= key&0x40? km->km_HiKeyMap: km->km_LoKeyMap;
					}
					offset= mapoffsets[*kmtypes&KC_VANILLA][qualflags];
					if (!(*kmtypes&KCF_NOP) && (offset >= 0))
					{	if (*kmtypes&KCF_DEAD)
						{	UBYTE *addr= (UBYTE *)*kmmap;
							if (addr[offset<<1]&DPF_MOD)
							{	addr+= addr[(offset<<1)|1]+deadflag;
								dead= deadflag;
								do
								{	if (*addr == chr) goto found;
									addr++;
									dead++;
								} while (deadflag && (dead != NUMDEADKEYS+1));
								dead= 0;
							} else if (((UWORD *)addr)[offset] == (UWORD)chr)
								goto found;
						} else if (!deadflag && !(offset&4))
						{	offset= (offset&3)^3;
							if (*kmtypes&KCF_STRING)
							{	UBYTE *addr= (UBYTE *)*kmmap;
								if ((addr[offset<<1] == 1)
									&& (addr[addr[(offset<<1)|1]] == chr))
									goto found;
							} else if (((UBYTE *)kmmap)[offset] == chr)
								goto found;
						}
					}
					kmtypes++;
					kmmap++;
				}
			}
		}
	}
	return; /* not found */

found:
	/*- put description into buffer -*/
	{	struct InputEvent ie;			/* structure for MapRawKey() */
		TEXT *index;					/* temporary index for highkeys */

		static UBYTE highkeycodes[]=
		{	0x40,			0x48,			0x50,	0x58,
			0x41,			0x49,			0x51,	0x59,
			0x42,							0x52,	0x5f,
			0x43,			0x4b,			0x53,
			0x44,			0x4c,			0x54,	0x6e,
			0x45,			0x4d,			0x55,	0x6f,
			0x46,			0x4e,			0x56,	0x70,
			0x47,			0x4f,			0x57,	0x71,		0
		};
		static TEXT *highkeytexts[]=
		{	"space",		"page_up",		"f1",	"f9",
			"backspace",	"page_down",	"f2",	"f10",
			"tab",							"f3",	"help",
			"enter",		"f11",			"f4",
			"return",		"up",			"f5",	"pause",
			"esc",			"down",			"f6",	"f12",
			"del",			"right",		"f7",	"home",
			"insert",		"left",			"f8",	"end"
		};
		static TEXT numkeycodes[]=
		{	0x0f, 0x1d, 0x1e, 0x1f, 0x2d, 0x2e, 0x2f, 0x3c,
			0x3d, 0x3e, 0x3f, 0x4a, 0x5a, 0x5b, 0x5c, 0x5d,
			0x5e,                                           0
		};

		ie.ie_Class= IECLASS_RAWKEY;
		ie.ie_Qualifier= 0;
		ie.ie_EventAddress= 0;

		if (dead)
		{	strcpy(buffer, "alt  ,\n");
			ie.ie_Code= deadkeys[dead-1];
			MapRawKey(&ie, buffer+4, 1, km);
		}

		if (qualflags&KCF_CONTROL)	strcat(buffer, "ctrl ");
		if (qualflags&KCF_SHIFT)	strcat(buffer, "shift ");
		if (qualflags&KCF_ALT)		strcat(buffer, "alt ");

		if (key && (index= strchr(highkeycodes, key)))
		{	strcat(buffer, highkeytexts[index-highkeycodes]);
			strcat(buffer, "\n");
		} else
		{	if (key && strchr(numkeycodes, key)) strcat(buffer, "numericpad ");
			strcat(buffer, " \n");
			ie.ie_Code= key;
			MapRawKey(&ie, strrchr(buffer, ' '), 1, km);
		}
	}
}

/*==== color gadget of current key ====*/
void ColorCurrentKey_(UBYTE which, UBYTE color)
{	UWORD x, y;

	SetFont(win->RPort, tabfont);
	SetDrMd(win->RPort, JAM1);
	SetAPen(win->RPort, color);
	x= INTERWIDTH+((which-tablopage)%tabnumx)*(tabxsize+INTERWIDTH);
	y= INTERHEIGHT+1+((which-tablopage)/tabnumx)*(tabtattr.ta_YSize+6);
	RectFill(win->RPort, x+2, y,
		x+2+tabxsize+INTERWIDTH-5, y+tabtattr.ta_YSize+6-3);
	SetAPen(win->RPort, 1);
	Move(win->RPort,
		x+((1+INTERWIDTH+tabxsize-TextLength(win->RPort, &which, 1))>>1),
		y+3-1+tabfont->tf_Baseline);
	Text(win->RPort, &which, 1);
}

/*==== show info of selected character ====*/
void OutputCharInfo_(UBYTE num)
{	TEXT s[256];

	if ((current >= tablopage) && (current < tabhipage))
		/* de-color old actual key gadget */
		ColorCurrentKey_(current, 0);

	if ((num >= tablopage) && (num < tabhipage))
	{	/* set character gadget */
		if ((num&0x7f) >= 0x20)
		{	s[0]= num; s[1]= '\n'; s[2]= '\0';
		} else
			s[0]= '\0';
		SetGadgetDisplay_(gads[GID_CHAR_].gad, s);
		/* set decimal value gadget */
		sprintf(s, "%lu\n", num);
		SetGadgetDisplay_(gads[GID_DECIMAL_].gad, s);
		/* set stroke gadget */
		MapANSIChar_(num, s);
		SetGadgetDisplay_(gads[GID_STROKE_].gad, s);

		/* color new actual key gadget */
		ColorCurrentKey_(current= num, 3);
	} else
	{	SetGadgetDisplay_(gads[GID_CHAR_].gad, "\n");
		SetGadgetDisplay_(gads[GID_DECIMAL_].gad, "\n");
		SetGadgetDisplay_(gads[GID_STROKE_].gad, "\n");
	}
}

/*==== open or close character table ====*/
void CtrlTable_(UBYTE action)
{	static struct NewWindow newwin=
	{	0, 0, 0, 0, 0, 1,
		SLIDERIDCMP|BUTTONIDCMP|IDCMP_CLOSEWINDOW
			|IDCMP_VANILLAKEY|IDCMP_RAWKEY,
		WFLG_DRAGBAR|WFLG_DEPTHGADGET|WFLG_CLOSEGADGET|WFLG_ACTIVATE
			|WFLG_SMART_REFRESH|WFLG_NOCAREREFRESH|WFLG_GIMMEZEROZERO,
		NULL, NULL, NULL, NULL, NULL, 0, 0, 0, 0, PUBLICSCREEN
	};

	static struct TagItem texttags[]=
	{	GTTX_Border, TRUE
	}, moretags[]=
	{	STRINGA_ExitHelp, TRUE,
		GT_Underscore, (ULONG)'_',
		TAG_DONE
	}, pagetags[]=
	{	GA_RelVerify, TRUE,
		PGA_Freedom, LORIENT_HORIZ,
		TAG_DONE
	};

	static BOOL tableopen;				/* TRUE if window is open */
	BYTE i;								/* loop variable */
	UWORD m, n;							/* temporary text lengths */
	TEXT *str;							/* temporary string pointer */
	struct NewGadget newgad;			/* structure for CreateGadget() */
	struct Gadget *keygad= NULL;		/* pointer to first key gadget */
	static UWORD fieldwidth;			/* width of choice gadgets */
	static UWORD fieldheight;			/* height of choice gadgets */
	static xsize, ysize;				/* font size */
	static UWORD winwidth, winheight;	/* window dimensions */

	if (action == TAB_TOGGLE_) action= TAB_OPEN_-tableopen;

	/*- bring window to front if FontViewQ loaded again -*/
	if ((action == TAB_OPEN_) && tableopen)
	{	WindowToFront(win);
		return;
	}

	/*- close window -*/
	if (action == TAB_CLOSE_)
	{	if (win)
		{	TEXT *s= win->Title;
			CloseWindow(win); win= NULL;
			free(s);
		}
		if (tabmem)
		{	free(tabmem); tabmem= NULL;
		}
		if (congad)
		{	FreeGadgets(congad); congad= NULL; 
		}
		if (vi)
		{	FreeVisualInfo(vi); vi= NULL;
		}
		tableopen= FALSE;
		return;
	}

	/*- get choice gadgets' dimensions or remove gadgets from window -*/
	if (!tableopen)
	{	UWORD u;						/* temporary underscore width */

		/* lock default public screen and get visual information */
		pubscr= LockPubScreen(NULL);
		if (!tabfont) SelectFont_(SFON_INIT_);
		rp= &pubscr->RastPort;
		if (!(vi= GetVisualInfo(pubscr, TAG_DONE)))
exitnowin:	ExitError_(GetCatalogStrQ_(MSG_NOWINDOW));

		/* find biggest xsize of font, set xsize and ysize */
		if (rp->Font->tf_Flags&FPF_PROPORTIONAL)
		{	UBYTE n, x= rp->Font->tf_LoChar;
			if (32 > x) x= 32;
			x--;
			xsize= 0;
			do
			{	x++;
				n= TextLength(rp, &x, 1);
				if (n > xsize) xsize= n;
			} while ((x < 126) && (x != rp->Font->tf_HiChar));
			xsize= (xsize<<1)/3;
		} else
			xsize= rp->Font->tf_XSize;
		ysize= rp->Font->tf_YSize;

		/* handle all widths correctly */
		u= TextLength(rp, "_", 1);
		fieldwidth= 0;
		fieldheight= 2*INTERHEIGHT+6+ysize;
		i= -1; while (gads[++i].line != -1)
		{	fieldheight+= gads[i].line*(INTERHEIGHT+6+ysize);
			str= gads[i].textnum? GetCatalogStrQ_(gads[i].textnum) : "";
			m= TextLength(rp, str, strlen(str));
			switch (gads[i].kind)
			{	case SLIDER_KIND:
					n= xsize*gads[i].length;
					m+= INTERWIDTH+n;
					break;

				case TEXT_KIND:
					n= xsize*(gads[i].length+1)+3*INTERWIDTH;
					if (i == GID_STROKE_)
					{	if (n > m) m= n;
					} else
					{	if (m) m+= INTERWIDTH;
						m+= n;
					}
					break;

				case BUTTON_KIND:
					m= n= 3*INTERWIDTH+m;
					if (strchr(str, '_')) n-= u;
			}
			gads[i].width= n;
			gads[i].fullwidth= m;
			if (m > fieldwidth) fieldwidth= m;
		}
	} else
	{	CtrlPointer_(MOUSE_SET_);
		if (action == TAB_NEWFONT_)
		{	RemoveGList(win, congad, -1);
			FreeGadgets(congad); congad= NULL;
			SetDrMd(win->RPort, JAM1);
			SetAPen(win->RPort, 0);
			RectFill(win->RPort, 0, 0, win->GZZWidth-1, win->GZZHeight-1);
		} else /* if (action == TAB_NEWPAGE_) */
		{	RemoveGList(win, gads[GID_QUIT_].gad->NextGadget, -1);
			FreeGadgets(gads[GID_QUIT_].gad->NextGadget);
			gads[GID_QUIT_].gad->NextGadget= NULL;
			SetDrMd(win->RPort, JAM1);
			SetAPen(win->RPort, 0);
			RectFill(win->RPort, INTERWIDTH, 0,
				INTERWIDTH+tabnumx*(INTERWIDTH+tabxsize)-1, win->GZZHeight-1);
		}
	}

	/*- calculate new window dimensions -*/
	winwidth= fieldwidth+3*INTERWIDTH;

	/*- calculate dimensions of table and create choice gadgets -*/
	if (action != TAB_NEWPAGE_)
	{	/* calculate dimensions of table */
		{	UWORD scrheight;
			if (action == TAB_OPEN_)
			{	win= OpenWindowTags(NULL,
					WA_Width, 1,
					WA_Height, 1);
			}
			scrheight= win->WScreen->Height-win->BorderTop+win->BorderBottom
				-2*INTERHEIGHT;
			if (action == TAB_OPEN_)
			{	CloseWindow(win);
				win= NULL;
			}
			tabnumy= scrheight/(tabtattr.ta_YSize+INTERHEIGHT);
		}
		tabnumx= 1; m= ~0;
		n= ((pubscr? pubscr : win->WScreen)->Width-winwidth-2*INTERWIDTH)
			/(tabxsize+INTERWIDTH);
		while (n > (tabnumx<<1)) { tabnumx<<= 1; m<<= 1; }
		tablochar= tabfont->tf_LoChar&m;
		tabhichar= (tabfont->tf_HiChar+tabnumx-1)&m;
		m= tabnumx*tabnumy; n= tabhichar-tablochar;
		tabpages= (m-n < 0)? (n+m-1)/m : 1;
		n= (n+tabpages-1)/tabpages;
		while ((((tabnumx*tabnumy)>>1) > n) && (tabnumx > 1)) tabnumx>>= 1;
		tabnumy= ((tabhichar-tablochar+tabpages-1)
			/tabpages+tabnumx-1)/tabnumx;
		tablen= tabnumx*tabnumy;
		if (tabmem) free(tabmem);
		tabpage= 0;
		tabmem= (UWORD *)MAllocQ_((tabhichar-tablochar)<<1);
		if (!tabmem) ExitMemError_();
		{	UWORD j;
			for (j= tablochar; j != tabhichar; j++)
				tabmem[j-tablochar]= j<<8;
		}
		winwidth+= tabnumx*(INTERWIDTH+tabxsize);

		/* bring gadgets to screen */
		CreateContext(&congad);
		newgad.ng_TextAttr= (tableopen? win->WScreen : pubscr)->Font;
		newgad.ng_VisualInfo= vi;
		newgad.ng_TopEdge= INTERHEIGHT;
		newgad.ng_GadgetID= 0;
		i= -1; while ((n= gads[++i].line) != -1)
		{	while (n-- > 0)
				newgad.ng_TopEdge+= ysize+6+INTERHEIGHT;
			newgad.ng_Flags=
				(gads[i].kind == BUTTON_KIND)? PLACETEXT_IN :
				(i == GID_STROKE_)? PLACETEXT_ABOVE : PLACETEXT_LEFT;
			newgad.ng_Height= (i == GID_PAGE_)? ysize : 6+ysize;
			if (i == GID_STROKE_) newgad.ng_Height<<= 1;
			newgad.ng_Width= gads[i].width;
			newgad.ng_LeftEdge= winwidth-INTERWIDTH
					-((fieldwidth-gads[i].fullwidth)>>1)-gads[i].width;
			newgad.ng_GadgetText= gads[i].textnum?

			GetCatalogStrQ_(gads[i].textnum) : NULL;
				gads[i].gad= CreateGadgetA((ULONG)gads[i].kind,
				i? gads[i-1].gad : congad, &newgad,
				(gads[i].kind == BUTTON_KIND)? moretags :
				(gads[i].kind == SLIDER_KIND)? pagetags : texttags);
			newgad.ng_GadgetID++;
		}
		if (!gads[GID_QUIT_].gad) goto exitnowin;
	}

	/*- create character set gadgets -*/
	{	UWORD j;
			newgad.ng_VisualInfo= vi;
		newgad.ng_TextAttr= &tabtattr;
		newgad.ng_Width= tabxsize+INTERWIDTH;
		newgad.ng_Height= tabtattr.ta_YSize+6;
		newgad.ng_TopEdge= INTERHEIGHT-newgad.ng_Height;
		newgad.ng_GadgetID= GID_ASCII_+tabpage*tablen;

		tablopage= tablochar+tablen*tabpage;
		tabhipage= tablochar+tablen*(tabpage+1);
		if (tabhipage > tabhichar) tabhipage= tabhichar;

		{	struct Gadget *kgad= CreateContext(&keygad);
			UWORD *tmptr= tabmem+tablopage-tablochar;

				for (j= tablopage; j != tabhipage; j++)
			{	if (!(j%tabnumx))
				{	newgad.ng_LeftEdge= INTERWIDTH;
					newgad.ng_TopEdge+= newgad.ng_Height;
				} else
					newgad.ng_LeftEdge+= newgad.ng_Width;
				newgad.ng_GadgetText= (TEXT *)tmptr++;
				kgad= CreateGadgetA(BUTTON_KIND, kgad, &newgad, NULL);
				newgad.ng_GadgetID++;
			}

			if (!kgad) goto goexitnw;
		}

		winheight= fieldheight;
		if ((j= newgad.ng_TopEdge+newgad.ng_Height+INTERHEIGHT)
			> winheight) winheight= j;
	}

	/*- open or resize window -*/
	if (!tableopen)
	{	TEXT *l, *s, q[]= "%s: Hot Key = <%s>";
		struct Catalog *ccat;

		if (LocaleBase)
			ccat= OpenCatalogA(locale, "sys/commodities.catalog", NULL);
		l= LocaleBase? GetCatalogStr(ccat, 1, q) : q;
		s= MAllocQ_(PRGNAMELEN+1+strlen(l)+strlen(hotkey));
		sprintf(s, l, PRGNAME, hotkey);
		if (LocaleBase) CloseCatalog(ccat);

		if (!(win= OpenWindowTags(&newwin,
			WA_Title, s,
			WA_InnerWidth, winwidth,
			WA_InnerHeight, winheight,
			TAG_DONE)))
goexitnw:
		{	FreeGadgets(keygad);
			goto exitnowin;
		}

		winsig= 1<<win->UserPort->mp_SigBit;
	} else if (action == TAB_NEWFONT_)
	{	WORD y= win->WScreen->Height-win->TopEdge-winheight
				-win->BorderTop-win->BorderBottom;
		WORD x= win->WScreen->Width-win->LeftEdge-winwidth
				-win->BorderLeft-win->BorderRight;
		MoveWindow(win, (x > 0)? 0 : x, (y > 0)? 0 : y);
		SizeWindow(win,
			winwidth-(win->GZZWidth), winheight-(win->GZZHeight));
	}

	/*- unlock public default screen -*/
	if (pubscr)
	{	UnlockPubScreen(NULL, pubscr); pubscr= NULL;
	}

	/*- add gadget list to window -*/
	if (action != TAB_NEWPAGE_) AddGList(win, congad, ~0, -1, NULL);
	AddGList(win, keygad, ~0, -1, NULL);
	RefreshGList(congad, win, NULL, -1);
	GT_RefreshWindow(win, NULL);
	GT_SetGadgetAttrs(gads[GID_PAGE_].gad, win, NULL,
		GTSL_Max, tabpages-1,
		TAG_DONE);

	tableopen= TRUE;
	OutputCharInfo_(current);
	SelectFont_(SFON_FILL_);
	CtrlPointer_(MOUSE_CLR_);
}

int main(int argc, TEXT *argv[])
{	ULONG cxsig;					/* signal flag of commodities' port */
	ULONG sigrcvd;					/* signal flags received from Wait() */
	BOOL end= FALSE;				/* TRUE if program is to exit */
	BYTE i;							/* temporary variable */

	atexit(CleanUp_);

	/*- only allow AmigaOS >= 2.04 -*/
	if (SysBase->lib_Version < 37)
	{	BPTR file;
		file= argc? Output() : Open("CON:0/0/350/30/" PRGNAME, MODE_NEWFILE);
		Write(file, "Sorry, you'll need at least AmigaOS 2.04!\n", 42);
		Delay(100);
		Close(file);
		return RETURN_FAIL;
	}

	/*- open libraries -*/
	IntuitionBase= OpenLibraryQ_("intuition.library");
	if (LocaleBase= OpenLibrary("locale.library", 0))
	{	locale= OpenLocale(NULL);
		cat= OpenCatalogA(locale, PRGNAME ".catalog", NULL);
	}
	AslBase= OpenLibraryQ_("asl.library");
	CxBase= OpenLibraryQ_("commodities.library");
	DiskfontBase= OpenLibraryQ_("diskfont.library");
	GadToolsBase= OpenLibraryQ_("gadtools.library");
	GfxBase= OpenLibraryQ_("graphics.library");
	IconBase= OpenLibraryQ_("icon.library");
	KeymapBase= OpenLibraryQ_("keymap.library");
	UtilityBase= OpenLibraryQ_("utility.library");

	/*- process arguments or tool types -*/
	argv= ArgArrayInit(argc, argv);
	newbroker.nb_Pri= (BYTE)ArgInt(argv, "CX_PRIORITY", 0);
	i= !Stricmp(ArgString(argv, "CX_POPUP", "TRUE"), "TRUE");
	hotkey= ArgString(argv, "CX_POPKEY", "ctrl alt f");

	/*- initialize as commodity with hotkey -*/
	newbroker.nb_Descr= GetCatalogStrQ_(MSG_CXDESCR);
	if (!(brokport= newbroker.nb_Port= CreateMsgPort())) ExitMemError_();
	cxsig= 1<<brokport->mp_SigBit;
	{	CxObj *filter;
		LONG error;

		if (!(broker= CxBroker(&newbroker, &error)) || error
			|| !(filter= HotKey(hotkey, brokport, 0))
			|| (AttachCxObj(broker, filter), CxObjError(filter)))
			if (error != CBERR_DUP)
				ExitError_(GetCatalogStrQ_(MSG_INITFAIL));
			else
				return RETURN_OK;
	}
	ActivateCxObj(broker, TRUE);

	/*- allocate font requester structure -*/
	if (!(freq= (struct FontRequest *)AllocAslRequestTags(ASL_FontRequest,
		TAG_DONE))) ExitMemError_();

	/*- allocate chip mem for busy mouse pointer -*/
	CtrlPointer_(MOUSE_INIT_);

	/*- eventually open window now -*/
	current= 'Q';
	if (i) CtrlTable_(TAB_OPEN_);

	/*- big message loop -*/
	while (!end)
	{	sigrcvd= Wait(SIGBREAKF_CTRL_C|cxsig|winsig);
	
	    if (sigrcvd&SIGBREAKF_CTRL_C) end= TRUE;

		if (sigrcvd&winsig) /* table loop */
		{	struct IntuiMessage *imsg;
			BOOL pageflag= FALSE;

			while (pageflag || win)
			{	BOOL closeflag= FALSE;
				BYTE i;

				/* get new message from GadTools */
				imsg= GT_GetIMsg(win->UserPort);
				if (pageflag && (!imsg || ((imsg->Class != IDCMP_GADGETUP)
					|| (imsg->Code != GID_PAGE_))))
				{	CtrlTable_(TAB_NEWPAGE_);
					pageflag= FALSE;
				}
				if (!imsg) break;

				/* process Intuition message */
				switch (imsg->Class)
				{	case IDCMP_GADGETUP:
						i= ((struct Gadget *)imsg->IAddress)->GadgetID;
select:					switch (i)
						{	case GID_FONTSEL_:
								SelectFont_(SFON_SELECT_);
								CtrlTable_(TAB_NEWFONT_);
								break;

							case GID_HIDE_:
								goto close;

							case GID_QUIT_:
								end= TRUE;
								break;

							case GID_PAGE_:
								if (imsg->Code != tabpage)
								{	tabpage= imsg->Code;
									pageflag= TRUE;
								}
								break;

							default:
								OutputCharInfo_((UBYTE)i-GID_ASCII_+tablochar);
								break;
						}
						break;

					case IDCMP_VANILLAKEY:
					{	WORD c= ToUpper(imsg->Code);
						while (gads[++i].line != -1)
							if (c == GetGadgetChar_(gads[i].textnum))
								goto select;
						c= imsg->Qualifier;
						if ((c&(IEQUALIFIER_LSHIFT|IEQUALIFIER_RSHIFT))
							&& (c&IEQUALIFIER_NUMERICPAD))
							switch (imsg->Code)
							{	case '1':	goto raw_end; /* end */
								case '3':	goto raw_down; /* page down */
								case '7':	goto raw_home; /* home */
								case '9':	goto raw_up; /* page up */
							}
						break;
					}

					case IDCMP_RAWKEY:
						switch (imsg->Code) /*>> from OS 2.1 and up! <<*/
						{	case 0x48: /* page up */
raw_up:							if (!tabpage) break;
								tabpage--;
								goto newpage;
							case 0x49: /* page down */
raw_down:						if (tabpage == tabpages-1) break;
								tabpage++;
								goto newpage;
							case 0x70: /* home */
raw_home:						if (!tabpage) break;
								tabpage= 0;
								goto newpage;
							case 0x71: /* end */
raw_end:						if (tabpage == tabpages-1) break;
								tabpage= tabpages-1;
newpage:						GT_SetGadgetAttrs(gads[GID_PAGE_].gad,
									win, NULL,
									GTSL_Level, tabpage,
									TAG_DONE);
						CtrlTable_(TAB_NEWPAGE_);
								break;
						}
						break;

					case IDCMP_CLOSEWINDOW:
close:					closeflag= TRUE;
						break;
				}

				/* reply message and eventually close window */
				GT_ReplyIMsg(imsg);
				if (closeflag)
				{	while (imsg= GT_GetIMsg(win->UserPort)) GT_ReplyIMsg(imsg);
					CtrlTable_(TAB_CLOSE_);
				}
			}
		}

		if (sigrcvd&cxsig) /* commodity loop */
		{	CxMsg *cxmsg;

			while (cxmsg= (CxMsg *)GetMsg(brokport))
			{	switch (CxMsgType(cxmsg))
				{	case CXM_IEVENT:
						CtrlTable_(TAB_TOGGLE_);
    	    	        break;
        	    	case CXM_COMMAND:
						switch (i= (BYTE)CxMsgID(cxmsg))
						{	case CXCMD_DISABLE:
							case CXCMD_ENABLE:
								ActivateCxObj(broker, i == CXCMD_ENABLE);
								break;
							case CXCMD_KILL:
								end= TRUE;
								break;
							case CXCMD_APPEAR:
							case CXCMD_UNIQUE:
							case CXCMD_DISAPPEAR:
								CtrlTable_((i == CXCMD_DISAPPEAR)?
									TAB_CLOSE_ : TAB_OPEN_);
								break;
	            	    }
		        }
				ReplyMsg((struct Message *)cxmsg);
		    }
		}
	}

	return RETURN_OK;
}

#ifdef _DCC
int wbmain(struct WBStartup *wbstart)
{	return main(0, (TEXT **)wbstart);
}
#endif
