/****************************************************************
** CharMap.c : Display an ASCII table of a given Amiga font.   **
**             Written (mainly) by T.Pierron                   **
**             11-Dec-1999                                     **
**-------------------------------------------------------------**
** Special Requirements: WorkBench 2.0, version 36 or above    **
****************************************************************/

#include <libraries/gadtools.h>
#include <intuition/screens.h>
#include <intuition/intuitionbase.h>
#include <intuition/gadgetclass.h>
#include <graphics/gfxmacros.h>
#include <graphics/gfxbase.h>
#include <graphics/text.h>
#include <dos/dos.h>

#include "CharMap.h"
#include "Utility.h"
#define  CATCOMP_NUMBERS		/* We will need the string id */
#define  CATCOMP_STRINGS		/* and the english string corresponding to the id */
#include "CharMapStrs.h"

/* Shared libraries we'll need to open */
struct IntuitionBase *IntuitionBase = NULL;
struct GfxBase *      GfxBase       = NULL;
struct Library *      GadToolsBase  = NULL;
struct Library *      AslBase       = NULL;
struct Library *      DiskfontBase  = NULL;
struct Library *      IFFParseBase  = NULL;
struct Library *      KeymapBase    = NULL;
struct Library *      LocaleBase    = NULL;
struct Library *      UtilityBase   = NULL;

struct RastPort *RP, RPT;              /* Graphics areas */
struct TextFont *guifont  = NULL;      /* Font used for gui components */
struct TextFont *cmapfont = NULL;      /* The one used for Char table */

struct Menu   *menu   = NULL;          /* Menu attached to the window */
struct Screen *screen = NULL;          /* Screen where window will be opened */
struct Window *window = NULL;          /* Main window ptr */
struct Gadget *glist  = NULL;          /* Gadget list pointer */
void          *vi     = NULL;          /* VisualInfo pointer */
struct Gadget *gads[6];                /* Gadgets of main interface */

struct IBox ZoomData;                  /* Dimension of iconified window */
struct IBox Map;                       /* Dimension of char table */
UBYTE CharWid[256];                    /* Data of ASCII table */
UBYTE Layout   = LAYOUT_SMALL;         /* Types of layout */
UBYTE VertSort = TRUE;                 /* TRUE if chars are sorted vertically first */
UBYTE Fh;                              /* Font information */

/* For the "version" command */
const UBYTE VERSION[]= "$VER:CharMap v" CHARMAP_VERSION " " CHARMAP_DATE "\n";

/* Set the color of the menu to Newlook (only 3.0+) */
ULONG MenuTags[] = {
	GTMN_FrontPen, 1,
	TAG_DONE
};

ULONG BoxTags[] = {
	GT_VisualInfo, 0,
	GTBB_Recessed, FALSE,
	TAG_DONE
};

/* Our gadtools gadget text */
STRPTR GadTxt[] = {
	"<=",0,0,0,0,
	/* MSG_FONTTYPE_STR      **
	** MSG_MENUCHARSET_STR   **
	** MSG_COPY_STR          **
	** MSG_PASTE_STR         */
	MSG_STRGAD_STR,0,0,0,
	/* MSG_MENUASCII8_STR    **
	** MSG_MENUISOLATIN1_STR **
	** MSG_MENUSTDAMIGA_STR  */
	NULL,
	MSG_NEWFONT_STR,   /* GT **
	** MSG_MENUFONTSCREEN_ST **
	** MSG_MENUTEXTFONT_STR  **
	** MSG_MENUCUSTOMFONT_ST */
	0,0,0,
	NULL
};

WORD             GadWidth[6];   /* Width of various gadgets */
#define CBLen    GadWidth[0]    /* "Paste" */
#define ChrLen   GadWidth[1]    /* "Charset" */
#define FtLen    GadWidth[2]    /* "Font" */
#define VaLen    GadWidth[3]    /* Various */
#define CyCLen   GadWidth[4]    /* Charset cycle gadget */
#define CyFLen   GadWidth[5]    /* Font type cycle gadget */

/* Tags associated with the charset cycle gadget */
ULONG CycleTags[]={
	GTCY_Labels, (ULONG) &GadTxt[ GTXT_CHARSET ],
	GTCY_Active, 1,
	TAG_DONE
};

/* The ones associated with the font type */
ULONG FontTags[]={
	GTCY_Labels, (ULONG) &GadTxt[ GTXT_FONTTYPE ],
	GTCY_Active, 0,
	TAG_DONE
};

ULONG sigwin = 0;

struct NewWindow new_window = {
	0, 0, 0, 0, 0, 0,     /* Fill later */
	IDCMP_CLOSEWINDOW | IDCMP_REFRESHWINDOW | IDCMP_MOUSEMOVE  | IDCMP_RAWKEY |
	IDCMP_MENUVERIFY  | IDCMP_MOUSEBUTTONS  | IDCMP_MENUPICK   | IDCMP_GADGETUP,
	WFLG_DEPTHGADGET  | WFLG_SMART_REFRESH  | WFLG_CLOSEGADGET | WFLG_ACTIVATE |
	WFLG_REPORTMOUSE  | WFLG_DRAGBAR,
	NULL, NULL, NULL, NULL, NULL,
	0, 0, 0, 0,
	CUSTOMSCREEN
};

/* NewMenu menu declaration */
struct NewMenu newmenu[] =
{
	{NM_TITLE, MSG_MENUTITLE_STR, 0, 0, 0, 0},
	{  NM_ITEM, MSG_FONTTYPE_STR, 0, 0, 0, 0},
	{   NM_SUB,   MSG_MENUFONTSCREEN_STR, "S", CHECKIT|CHECKED, ~1, (APTR) (NMUD_FONT)   },
	{   NM_SUB,   MSG_MENUTEXTFONT_STR,   "T", CHECKIT,         ~2, (APTR) (NMUD_FONT+1) },
	{   NM_SUB,   MSG_MENUCUSTOMFONT_STR, "F", CHECKIT,         ~4, (APTR) (NMUD_FONT+2) },
	{  NM_ITEM, MSG_MENUCHARSET_STR, 0, 0, 0, 0},
	{   NM_SUB,   MSG_MENUASCII8_STR,     "1", CHECKIT|CHECKED, ~1, (APTR)1021},
	{   NM_SUB,   MSG_MENUISOLATIN1_STR,  "2", CHECKIT,         ~2, (APTR)1022},
	{   NM_SUB,   MSG_MENUSTDAMIGA_STR,   "3", CHECKIT,         ~4, (APTR)1023},
	{  NM_ITEM, NM_BARLABEL, 0, 0, 0, 0},
	{  NM_ITEM, MSG_MENUABOUT_STR,        "?", 0, 0, (APTR)103},
	{  NM_ITEM, NM_BARLABEL, 0, 0, 0, 0},
	{  NM_ITEM, MSG_MENUICONOFY_STR,      "I", 0, 0, (APTR)104},
	{  NM_ITEM, MSG_MENUQUIT_STR,         "Q", 0, 0, (APTR)105},

	{NM_TITLE, MSG_EDITMENU_STR, 0, 0, 0, 0},
	{  NM_ITEM, MSG_CUT_STR,              "X", 0, 0, (APTR)201},
	{  NM_ITEM, MSG_COPY_STR,             "C", 0, 0, (APTR)202},
	{  NM_ITEM, MSG_PASTE_STR,            "V", 0, 0, (APTR)203},
	{  NM_ITEM, NM_BARLABEL, 0, 0, 0, 0},
	{  NM_ITEM, MSG_MENUCLEARTXT_STR,   "Del", NM_COMMANDSTRING, 0, (APTR)204},
	{  NM_ITEM, MSG_MENUSORT_STR,       "Tab", NM_COMMANDSTRING|CHECKIT|MENUTOGGLE, 0, (APTR)205},
	{  NM_ITEM, NM_BARLABEL, 0, 0, 0, 0},
	{  NM_ITEM, MSG_SAVESETTINGS_STR,     "W", 0,  0, (APTR)206},
	{NM_END, 0, 0, 0, 0, 0},
};

/* NewMenus currently checked */
struct NewMenu * nm_charset  = &newmenu[ NM_CHARSET  ];
struct NewMenu * nm_fonttype = &newmenu[ NM_FONTTYPE ];

/* Different width of box for each charset */
UBYTE MaxWid, NumColumns, CharsetNum;

/* Colors pens */
UWORD pens[] = {3, 1, 3, 2, 2, 1};

/* Position of normal and minimized window */
WORD Offset[]={0x7fff,0x7fff,0x7fff,0x7fff};


int main(int argc, char *argv[])
{
	/* If CharMap is already running, notify it */
	if( find_charmap() ) cleanup(NULL, RETURN_OK);

	/* If no locale, use english as default */
	Translate_strings();

	/* These libraries are located in ROM. If it failed to open, it's   **
	** because we are attempting to run this programme on an old system */
	if( !(GfxBase       = (APTR) OpenLibrary("graphics.library", MIN_VERSION)) ||
	    !(IntuitionBase = (APTR) OpenLibrary("intuition.library",MIN_VERSION)) ||
	    !(GadToolsBase  = (APTR) OpenLibrary("gadtools.library", MIN_VERSION)) ||
	    !(UtilityBase   = (APTR) OpenLibrary("utility.library",  MIN_VERSION)) ||
	    !(KeymapBase    = (APTR) OpenLibrary("keymap.library",   37)) )
		cleanup(ErrMsg(ERR_BADOS),30);

	/* No ASL or diskfont library, no font requester */
	if( !(DiskfontBase  = (APTR) OpenLibrary("diskfont.library", 0))           ||
	    !(AslBase       = (APTR) OpenLibrary("asl.library",      MIN_VERSION)) )
	{
		newmenu[NM_FONTTYPE+2].nm_Flags |= NM_ITEMDISABLED;
		GadTxt[GTXT_FONTTYPE+2] = 0;
	}

	/* Get command line arguments */
	ParseCmdLine( argc, argv );

	/* For temporary font measurement */
	InitRastPort( &RPT );

	/* Get access to the frontmost screen */
	screen = IntuitionBase->ActiveScreen;

	/* Register fonts */
	Add_pref_font(screen->RastPort.Font, PTYP_SCREENFONT, CHARSET_NOCARE);
	Add_pref_font(GfxBase->DefaultFont,  PTYP_DEFAULT,    CHARSET_NOCARE);

	Read_pref();

	/* If setup failed at start (font too big) */
	if(Setup() == FALSE) Handle_menu(113L);

	Handle_input();
}

/*** Setup the window, return FALSE if init failed ***/
BOOL Setup( void )
{
	struct DrawInfo *di;

	/* Get the screen's visual information data */
	if( !(vi = (APTR) GetVisualInfoA( screen, NULL )) )
		cleanup(ErrMsg(ERR_NOINTERFACE), RETURN_FAIL);

	BoxTags[1] = (ULONG) vi;

	/* Adjust the pen numbers to what the screen uses */
	if( di = (void *)GetScreenDrawInfo(screen) )
	{
		/* Get a copy of the correct pens for the screen */
		PEN_BACKFILL = di->dri_Pens[FILLPEN];
		PEN_TEXT     = di->dri_Pens[TEXTPEN];
		PEN_TEXTFILL = di->dri_Pens[FILLTEXTPEN];
		PEN_SHINE    = di->dri_Pens[SHINEPEN];
		PEN_SHADE    = di->dri_Pens[SHADOWPEN];
		if(PEN_TEXT == PEN_INFOWND) PEN_INFOWND = PEN_BACKFILL;

		/* This one is only available on system V39+ */
		if(di->dri_Version>=2) MenuTags[1] = di->dri_Pens[BARDETAILPEN];
		FreeScreenDrawInfo(screen,di);
	}
	/* Set flags for menu entry */
	if(VertSort) newmenu[NM_VERTSORT].nm_Flags |= CHECKED;
	else         newmenu[NM_VERTSORT].nm_Flags &= ~CHECKED;

	/* Build the menu-strip and compute menu items size */
	if( (menu = (APTR) CreateMenusA(newmenu, MenuTags)) && LayoutMenusA(menu, vi, NULL) )
	{
		UWORD i, gad_wid;
		SetFont(&RPT, cmapfont);
		/* Read the size of the 256 characters of the current font, **
		** and compute the maximal width of the box chars           */
		NumColumns = (CharsetNum == CHARSET_8BITS  ? 32 :
		             (CharsetNum == CHARSET_LATIN1 ? 28 : 24));
		{
			register UBYTE * W;
			for(i=0, W=CharWid, MaxWid=0; i<256; i++,W++)
			{
				UBYTE Char = i;
				*W = TextLength(&RPT, &Char, 1);

				/* Compute the width of each box */
				switch( CharsetNum )
				{
					case CHARSET_AMIGA:  if(i>=127 && i<160) break;
					case CHARSET_LATIN1: if(i<32) break;
					case CHARSET_8BITS:  if(MaxWid < *W+6) MaxWid=*W+6;
				}
			}
		}
		/* Some important font size-depending values */
		Fh = cmapfont->tf_YSize+4;
		/* Use current screen's font */
		SetFont(&RPT,screen->RastPort.Font);

		/* Measure length of strings */
		GadTxt[ GTXT_CYCLECHAR ] = newmenu[ NM_CHARSET-1 ].nm_Label;
		for(i=0; i<4; i++)
		{
			static UBYTE Len[] = {15,10,10,10}, offs[] = {
				GTXT_PASTE, GTXT_CYCLECHAR, GTXT_CYCLEFONT, GTXT_STRTEST
			};
			register STRPTR str  = GadTxt[ offs[i] ];
			GadWidth[ i ] = TextLength(&RPT,str,strlen(str)) + Len[i];
		}
		/* Choose which fonts will appeared in the font list gadget */
		Font_list_to_table();

		CyCLen = Meas_table( CycleTags[1] ) + 30;
		CyFLen = Meas_table(  FontTags[1] ) + 30;
		/* If "Font" text length is shorter than "String", right aligns text */
		if(FtLen < VaLen) FtLen = VaLen;

		for(i=1;;) {
			/* Computes the width of our window */
			new_window.Width = MaxWid * NumColumns + 20;

			/* Does the width of the window fit in the screen? */
			if(new_window.Width > screen->Width)
			{
				/* No! force the window width */
				MaxWid = (screen->Width-20) / NumColumns;
				continue;
			}

			if( i == 0 ) break;
			/* Is there place for gadgets? */
			i = FtLen + CyCLen + CyFLen;
			if( i + 30 > new_window.Width )
				MaxWid=(i + 10) / NumColumns + 1, i = 0;
			else break;
		}

		/* Try to optimize rendering of layout */
		i = new_window.Width-ChrLen-FtLen-40; gad_wid = i/3;
		Layout = LAYOUT_LARGE;
		if(CBLen < i - CyCLen - CyFLen)
			if(CyCLen <= gad_wid && CyFLen <= gad_wid)
				CBLen = CyCLen = CyFLen = gad_wid;
			else /* One of cycle gadget is too wide, adjust just copy/paste gad */
				CBLen = i - CyCLen - CyFLen;
		else /* Expand just cycle gadgets */
			if(i + 10 >= CyCLen + CyFLen)
				CyCLen=CyFLen= (i + 10) / 2, Layout = LAYOUT_MIDDLE;
			else /* Not enough place for both gadget's caption & gadget */
				CyCLen=CyFLen=(i+10+ChrLen)/2, ChrLen=0,
				GadTxt[ GTXT_CYCLECHAR ]=NULL, Layout = LAYOUT_SMALL;

		/* Setup normal and minimized position of main window */
		new_window.TopEdge  = Adjust_pos(Offset[1],screen->Height,
		new_window.Height   = Fh*8+4*screen->Font->ta_YSize+41 );
		new_window.LeftEdge = Adjust_pos(Offset[0],screen->Width,
		new_window.Width);

		/* Title of the window is the name of our font */
		new_window.Title    = cmapfont->tf_Message.mn_Node.ln_Name;
		new_window.Screen   = screen;

		/* Init the position and the size of the minimized window */
		ZoomData.Left   = Adjust_pos(Offset[2],screen->Width,
		ZoomData.Width  = TextLength(&screen->RastPort,new_window.Title,strlen(new_window.Title))+100);
		ZoomData.Top    = Adjust_pos(Offset[3],screen->Height,
		ZoomData.Height = screen->BarHeight+1);

		/* If window won't open, font is really too big */
		if( window = (APTR) OpenWindowTags(&new_window,
				WA_Zoom,         &ZoomData,
				WA_NewLookMenus, TRUE,
				TAG_DONE) )
		{
			RP = window->RPort;
			sigwin = 1 << window->UserPort->mp_SigBit;
			SetMenuStrip(window, menu);
			Init_helpwin(window);
			Create_gadgets();
			return TRUE;
		}
		else /* If ASL is available, show a requester to change the font */
		{
			if( avert(ErrMsg(ERR_FONTREALLYBIG)) )
				return FALSE;
		}
	}
	/* Painful to continue */
	cleanup( ErrMsg(ERR_NOINTERFACE), RETURN_FAIL );
}

/*** Desallocate all ressources: ***/
void cleanup( STRPTR msg, WORD err )
{
	extern APTR FR, catalog;

	if (window)
	{
		/* Save the positions of the window before to close it */
		CopyMem(&window->LeftEdge, Offset,   2*sizeof(WORD));
		CopyMem(&ZoomData.Left,    Offset+2, 2*sizeof(WORD));
		/* Now we can restore it's place if it will be reopen */
		ClearMenuStrip (window); sigwin = 0;
		CloseWindow    (window); window = NULL;
	}

	if (glist) FreeGadgets(glist);   glist = NULL;
	if (vi)    FreeVisualInfo(vi);   vi    = NULL;
	if (menu)  FreeMenus(menu);      menu  = NULL;

	if (err < 0) return;

	close_port();
	FreePrefFonts();
	if (FR)              FreeAslRequest(FR);
	if (cmapfont)        CloseFont(cmapfont);
	if (catalog)         CloseCatalog(catalog);
	if (IFFParseBase)    CloseLibrary(IFFParseBase);
	if (AslBase)         CloseLibrary(AslBase);
	if (KeymapBase)      CloseLibrary(KeymapBase);
	if (LocaleBase)      CloseLibrary(LocaleBase);
	if (DiskfontBase)    CloseLibrary(DiskfontBase);
	if (GadToolsBase)    CloseLibrary(GadToolsBase);
	if (IntuitionBase)   CloseLibrary(IntuitionBase);
	if (GfxBase)         CloseLibrary(GfxBase);
	if (msg)             puts(msg);
	exit(err);
}

/*** Draw the table of characters ***/
void Draw_ASCIIChart( void )
{
	register UWORD i,x,y;

	/* Initial bevel box, which will be copied up to 256 times */
	SetDrMd(RP, JAM1); SetFont(RP, cmapfont);
	SetAPen(RP, 0);    BoxTags[2] = TAG_DONE;
	RectFill      (RP, Map.Left, Map.Top, MaxWid+Map.Left, Fh+Map.Top+Fh);
	DrawBevelBoxA (RP, Map.Left, Map.Top, MaxWid,          Fh, BoxTags);

	/* Exponential copy (horizontal) */
	for(x=MaxWid,i=Map.Left+x,y=window->Width-10; i<Map.Width; ) {
		ClipBlit(RP, Map.Left, Map.Top, RP, i, Map.Top, x, Fh, 0xC0);
		i+=x; x<<=1; if(i+x>y) x=y-i;
	}
	/* Exponential copy (vertical) */
	for(x=Fh, i=Map.Top+x; i<Map.Height; i+=x, x<<=1)
		ClipBlit(RP, Map.Left, Map.Top, RP, Map.Left, i, y, x, 0xC0);

	SetAPen(RP, PEN_TEXT);
	/* Character in each box */
	for(i = (CharsetNum == CHARSET_8BITS ? 0 : 32), x = Map.Left,
	    y = Map.Top + cmapfont->tf_Baseline + 2;
	    i < 256; i++, VertSort ? (y+=Fh):(x+=MaxWid) )
	{
		UBYTE Char;
		if( x>=Map.Width  ) x =Map.Left, y+=Fh;
		if( y>=Map.Height ) x+=MaxWid,   y-=Fh<<3;
		/* 127th char for Amiga charset isn't displayed */
		if(CharsetNum == CHARSET_AMIGA && i==127) { i=159; continue; }
		Char = i;
		Move(RP, x + (MaxWid-CharWid[i]>>1), y);
		Text(RP, &Char, 1);
	}
}

/*** Create the gadgets associated with the window ***/
void Create_gadgets( void )
{
	static UBYTE goff[]={3,0,0,5,4}, toff[] = {
		GTXT_DEL, GTXT_COPY, GTXT_PASTE, GTXT_CYCLEFONT, GTXT_CYCLECHAR
	};
	struct NewGadget ng;
	UWORD  top,I;

	/* Init some variables */
	top     = window->BorderTop + 1;
	gads[0] = (APTR) CreateContext(&glist);
	I       = (Layout == LAYOUT_LARGE ? CBLen : (CBLen<<1)+10);

	ng.ng_TextAttr   = screen->Font;
	ng.ng_VisualInfo = vi;

	ng.ng_Flags      = PLACETEXT_LEFT | NG_HIGHLABEL;
	ng.ng_GadgetText = GadTxt[ GTXT_STRTEST ];
	ng.ng_LeftEdge   = VaLen+10;
	if(ng.ng_LeftEdge+10 > FtLen) ng.ng_LeftEdge = FtLen+10;
 	ng.ng_Width      = window->Width - 20 - I - ng.ng_LeftEdge - (
	ng.ng_Height     = VaLen = screen->Font->ta_YSize+6);
	ng.ng_TopEdge    = top+5;
	ng.ng_GadgetID   = GAD_STRTEST;

	/** String gadget **/
	gads[0] = (APTR) CreateGadget(STRING_KIND, gads[0], &ng,
	          GTST_MaxChars, MAX_CHARACTERS, TAG_DONE);

	/** Name of the charset displayed in the cycle gad **/
	CycleTags[3] = CharsetNum;

	/** Creates the 5 shortcut gadgets (Sorry for this very crap code...) **/
	ng.ng_LeftEdge -= 10;
	ng.ng_Flags     = PLACETEXT_IN;
	for(I=0; I<5; I++)
	{
		/* Sorry for this crap code */
		ng.ng_GadgetID    = I+1;
		ng.ng_GadgetText  = GadTxt[ toff[I] ];
		if(I!=2 || Layout != LAYOUT_LARGE) ng.ng_LeftEdge += ng.ng_Width+10;
		else goto next_row;
		ng.ng_Width       = GadWidth[ goff[I] ];

		if(I==3) { ng.ng_Flags = PLACETEXT_LEFT | NG_HIGHLABEL; ng.ng_LeftEdge = FtLen+10;
		           if( Layout != LAYOUT_LARGE )
		next_row:  ng.ng_Height -= 2, top = (ng.ng_TopEdge += ng.ng_Height+6); }

		if(I==4) ng.ng_LeftEdge += ChrLen;

		gads[I+1] = (APTR) CreateGadget(I<=2 ? BUTTON_KIND:CYCLE_KIND, gads[I], &ng,
					TAG_MORE,    (I==4 ? CycleTags : (I==3 ? FontTags : NULL)));
	}

/*	if (gads[5] != NULL) */
	{
		/** Refresh everything **/
		AddGList         (window, glist, (UWORD)-1, (UWORD)-1, NULL);
		RefreshGList     (glist, window, NULL,      (UWORD)-1);
		GT_RefreshWindow (window, NULL);

		/** Draw the ASCII table **/
		Map.Left  = 10;               Map.Top    = top + ng.ng_Height + 5;
		Map.Width = window->Width-10; Map.Height = Map.Top + Fh*8;
		Draw_ASCIIChart();
	}
/*	else cleanup(ErrMsg(ERR_NOINTERFACE), RETURN_FAIL); */
}
