/* ======================================================================= */

/* SetFont 2.0	-

		by Dave Haynie (Hazy)
			CIS: 	76703,2047
			Usenet:	{allegra,caip,ihnp4}!cbmvax!daveh
			Drink:	Guiness

   BUSINESS (MUNDANE, BUT IMPORTANT):

	This program is written by me, Dave Haynie, as mentioned above.
   I have, however, placed it in the PUBLIC DOMAIN.  That, of course, means
   that you can do (almost) absolutely anything with it.  You may hack it
   to bits, include it with YOUR product, sell it, GIVE IT AWAY, or anything
   else you like.  Of course, since anyone else can also give it away, it
   would be foolish to stick your name on it and try to sell it.  I really
   would like everyone to give it away, nicely, without being mean about it.
   If you want to use it with your product, do that too, new products for
   the Amiga are generally a good thing for the entire Amiga community.  You
   can take bit and pieces and use them in your own programs, or the whole
   thing for that matter.  And if you really want to have fun, you can even
   IMPROVE this program, as its far from perfect, and give YOUR version 
   away too, if you're so inclined.  That's about it for the (mundane)
   business section, let's get on to the (interesting) notes section.

   ABOUT IT:

	This program is designed to allow you to set the font of a console
   window (or really, a general WorkBench window) to any font, ROM or disk
   based, that you have in mind.  This works much better than the first
   version, in that it allows a greater variety of things to be changed
   in the WorkBench.  What, you say, a VARIETY of things?  Well, lemme
   explain....

   	First off, something about fonts.  Amiga fonts all over the place
   default to one of two ROM fonts, as specified in Preferences.  A ROM font
   is just a font that happens to stored in with the KickStart code, instead
   of out on a disk.  Since anything ever running on any Amiga can get to 
   these fonts, they're used very heavily, perhaps too much.  If you've 
   ever used the NotePad, however, you will probably have used some other
   fonts.  These fonts are available on disk, and in fact, you can use
   FontEd to create a variety of disk fonts for your own use.

   Whether disk or ROM, most programs use the Amiga's font management
   utilities, which are part of the KickStart based graphics library.  But
   very few folks just look at the graphics library.  Most everyone looks 
   at Intuition.  Intuition manages things like screens and windows, and
   you can write things to both screens and windows.  And, perhaps not
   coincidentally, both screens and windows have some idea of what font they
   should use, providing they've been given no special directions toward
   using some other fonts.  The screen stores a pointer to a critter called
   a "TextAttr", which is essentially a DESCRIPTOR for a font.  A TextAttr
   contains the name of the font, the point size of the font, and a little
   more information related to the font.  Both screens and windows also
   contains a graphics structure, called a RastPort, which points to the
   memory used to store the displayed memory.  The RastPort sports a pointer
   to a different critter, the "TextFont".  The is the actual font, which is
   the result of passing a TextAttr to one of the font opening functions.

   	In this version I allow you to change the screen's font information,
   like before, but I also know a bit more about the console window now.
   When I open a window within a screen, such as the WorkBench screen, 
   it's RastPort gets its default font from the screen's TextAttr font 
   descriptor.  If this window is going to be a console window, it will
   built a ConUnit, which has its own idea of the current font, based on
   the window.  I can use the ROM Kernal SetFont() call to change the
   font of the Window's RastPort.  Then I send an "<ESC>c" to the console
   which will reinitialize the ConUnit from the window (and also clear
   the screen....oh well).

	I need pointers to the window and screen to do this.  Since the 
   Window structure contains a pointer to its screen, my goal is to find
   the window associated with the calling console.  The best way I've found
   for this is by setting up the current console as an I/O item, then
   calling the DOS Info() function, which will return a pointer to the
   console's window.  From this window I can get a the screen pointer and
   the window's RastPort, so the fonts can be put in place.  When the
   Screen's TextAttr is changed, I only change the values of that font
   descriptor that the user's asked me to change.  Occasionally, this can
   result in a few bytes of unreclaimed memory, as in font names:  I have no
   idea how much memory is reserved in the screen's TextAttr structure on
   startup.  I use the SetFont() function to set the window's or screen's
   RastPort fonts.

	There are a few problems with my scheme.  First of all, many 
   programs are written to support only the topaz 8 (80 column) font.  If 
   you're a 60 column user, you've probably experienced this before.  Its
   not a problem with the Amiga as a whole, since most of the system will
   adjust itself.  But it may be a problem with programs that have a fixed
   idea of what a font should look like.  Most 80 column fonts work with
   most applications, and an 80 column 8x8 font will work just about
   everywhere.  Some programs, like CLI for instance, have trouble with
   proportionally-spaced fonts.  The best thing to to is try out the font
   you like.  One final problem is that some applications ask the
   WorkBench screen to close when they start up.  It'll close if there's
   nothing else open on it, but when it re-opens, it'll restart with the
   Preferences-selected font, not the SetFont selected font.  Of course,
   preferences doesn't support arbitrary fonts (which is why this program is
   even necessary).  Oh well, maybe in 1.3?  The solution to this is to 
   always keep at least one window open, so that WorkBench will never be
   closed on you.
*/

/* ======================================================================= */

/*	Modifications -

		by Keith Young,  DAS Member (Devil's Advocate Software)
			"We do Neat/Dangerous things... so you don't have to"
			CIS: 73170,307

	 First off, thanks to Dave Haynie (Hazy) for supplying this little
	dittie.  In my never-ending quest to learn something new every day,
	I've discovered a few things about how the system handles fonts and
	made a few changes to SetFont that reflect these findings.

	 Most of the changes I've made have a /* ky /* in front of the change
	as well as comments about them, so I won't repeat myself up here :).
	The other changes that aren't marked are mostly casts to get the code
	to compile cleanly with Manx and 16 bit ints. To compile/link with
	Manx:	1> cc setfont
			1> ln setfont -lc

	 There is still one place (font pointer) in the system that I have
	not looked into completely... and that is GfxBase->DefaultFont.
	According to the DevCon notes, this is updated anytime SetPrefs()
	is called, but I'm not yet sure of it's significance.  Whoops...
	make that 2 places... IntuitionBase->Preferences->FontHeight is
	the other... not sure what significance that has either (in the
	grand scheme of things)

		Cheers,
				Keith

	p.s.  Keep in mind that this program pokes at INTUITIONPRIVATE
		pointers, and so is not guarranteed to work with future
		versions of the OS.

*/

#include <exec/types.h>
#include <exec/io.h>
#include <exec/ports.h>
#include <exec/memory.h>
#include <graphics/gfxbase.h>
#include <graphics/text.h>
#include <graphics/rastport.h>
#include <libraries/diskfont.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <libraries/diskfont.h>
#include <intuition/intuitionbase.h>
#include <stdio.h>

#include <functions.h>  /* ky */

/* ========================================================================= */

/* External things */

/* ky */  /* these are in Manx's "functions.h"  header file
extern struct Library *OpenLibrary();
extern struct TextFont *OpenFont();
extern struct TextFont *OpenDiskFont();
extern struct Window *OpenWindow();
*/

extern char *strcat();
extern char *strcpy();
extern long strlen();

/* ========================================================================= */

/* Global variables */

struct GfxBase *GfxBase = NULL;
struct Library *DiskfontBase = NULL;
struct IntuitionBase *IntuitionBase = NULL;

struct MsgPort portblk = { 
	{ 0, 0, NT_MSGPORT, 0, 0}, 0, -1, 0, 
	{(struct Node *)&portblk.mp_MsgList.lh_Tail, 0, (struct Node *)&portblk.mp_MsgList.lh_Head, 0, 0} 
};

struct MsgPort *port = &portblk;

struct IOStdReq reqblk = { 
	{
	{0, 0, 0, 0, 0},
	&portblk, 0	}
	, 0
}; 
struct IOStdReq *request = &reqblk;

struct Window *window = NULL;
struct Screen *screen = NULL;

/* ========================================================================= */ 

/* This section contains startup and shutdown functions. */

/* This function opens the required Amiga libraries, devices, and other
   good stuff.  It returns TRUE if successful.  Graphics, Intuition, and
   DiskFont libraries are opened. The console device is opened and the 
   I/O request message port is initialized.  I also do a check to make sure
   that the associated Task is really a Process.  This is absolutely 
   necessary, as some of the functions that I'll be using later are DOS
   functions, which require Processes, not Tasks.  */

BOOL OpenUp()
{
	/* The Libraries */

	if ((GfxBase = (struct GfxBase *)OpenLibrary("graphics.library", 0L)) == NULL)
		return FALSE;
	if ((DiskfontBase = OpenLibrary("diskfont.library",0L)) == NULL)
		return FALSE;
	if ((IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", 0L)) == NULL)
		return FALSE;

	/* The Devices */

	if ((OpenDevice("console.device", -1L, request, 0L)) != 0L)
		return FALSE;
	if ((port->mp_SigBit = AllocSignal(-1L)) < 0)
		return FALSE;
	port->mp_SigTask = (struct Task *) FindTask((char *) NULL); 
	if (port->mp_SigTask->tc_Node.ln_Type != NT_PROCESS)
		return FALSE;
	return TRUE;
}

/* This function frees up dynamically opened things, like the devices
   and the libraries, opened above. */

void ShutDown()
{ 
	if (request->io_Device != NULL) { 
		if (port->mp_SigBit != -1) FreeSignal((long)port->mp_SigBit); 
		CloseDevice(request); 
	} 
	if (IntuitionBase != NULL) CloseLibrary(IntuitionBase); 
	if (DiskfontBase != NULL) CloseLibrary(DiskfontBase);
	if (GfxBase != NULL) CloseLibrary(GfxBase);
} 

/* ========================================================================= */

/* Attribute based functions. */

/* This function allocates space for the given font descriptor, converting
   the point size into a numeric form at the same time. */

struct TextAttr *AllocAttr(fontname, pointname)
char *fontname, *pointname;
{
	short point;
	char *str;
	struct TextAttr *attr = NULL;

	attr = (struct TextAttr *) AllocMem((long)sizeof(*attr),0L);

	if (pointname == NULL)
		point = 8;
	else
		point = atoi(pointname);

	str = (char *) AllocMem((long)(strlen(fontname)+6),0L);
	strcpy(str,fontname);
	strcat(str,".font");
	attr->ta_Name = (STRPTR) str;
	attr->ta_YSize = point;
	attr->ta_Style = 0;
	attr->ta_Flags = 0;
	return attr;
}

/* This function frees a TextAttr allocated with AllocAttr(). */

void FreeAttr(attr)
struct TextAttr *attr;
{
	if (attr == NULL)
		return;
	FreeMem(attr->ta_Name, (long)strlen(attr->ta_Name));
	FreeMem(attr,(long)sizeof(*attr));
}
/* ========================================================================= */

/* This function allocates a packet and associated info block.  It returns
   a pointer to this packet if successful, NULL otherwise. */

struct StandardPacket *AllocInfoPacket()
{
	struct StandardPacket *pac = NULL;
	struct InfoData *inf = NULL;

	pac = (struct StandardPacket *) AllocMem((long)sizeof(*pac), MEMF_CLEAR);
	if (pac == NULL)
		return NULL;
	inf = (struct InfoData *) AllocMem((long)sizeof(*inf), MEMF_CLEAR);
	if (inf == NULL) {
		FreeMem(pac,(long)sizeof(*pac));
		return NULL;
	}
	pac->sp_Msg.mn_Node.ln_Name = (char *) &(pac->sp_Pkt); 
	pac->sp_Pkt.dp_Link = &(pac->sp_Msg); 
	pac->sp_Pkt.dp_BufAddr = ((ULONG) inf) >> 2; 
	pac->sp_Pkt.dp_Type = ACTION_DISK_INFO; 

	return pac;
}

/* ========================================================================= */

/* This function gets the Window pointer for the current console.  It 
   takes the message port for the console.  It uses the fact that the I/O
   device's message port points to the process associated with this CLI.  
   This process needs a console window, and it should have a pointer to a
   message port for this purpose.  We use DOS packets to request an
   InfoData block on this console window.  The window pointer is passed 
   back as the volume name of the console.  We're getting into DOS, so look
   out for BCPL pointers and longword alignment! */

struct Window *GetConsoleWindow(port)
struct MsgPort *port;
{
	struct MsgPort *cp; 			/* Console port */
	struct InfoData *inf;		/* Packet data block */
	struct StandardPacket *pac = NULL;   /* Packet for DOS request */
	struct Window *w = NULL;		/* Resulting Window */

	cp = (struct MsgPort *)((struct Process *)port->mp_SigTask)->pr_ConsoleTask; 
	if (cp == 0)
		return NULL;

	if ((pac = AllocInfoPacket()) == NULL)
		return NULL;
	pac->sp_Pkt.dp_Port = port; 
	inf = (struct InfoData *) (((ULONG) pac->sp_Pkt.dp_BufAddr) << 2);
	PutMsg(cp, pac); 
	WaitPort(port); 
	w = (struct Window *) inf->id_VolumeNode;
	FreeMem(inf, (long)sizeof(*inf));
	FreeMem(pac, (long)sizeof(*pac));

	return w;
}

/* ========================================================================= */

/* This function tries to get a requested font from an attribute descriptor;
   it returns FALSE if the font doesn't exist. */

struct TextFont *GetFont(attr)
struct TextAttr *attr;
{
	struct TextFont *font;

	if ((font = OpenDiskFont(attr)) != NULL)
		return font;

/* ky */
/* OpenDiskFont() will use an in-memory font if it exists... */
/* so the following call should not be needed */
/*	if ((font = OpenFont(attr)) != NULL)
		return font; */

	return NULL;
}

/* ========================================================================= */

/* The main function */

main(argc, argv) 
int argc; 
char *argv[]; 
{ 
	short i;
	BOOL printmode = FALSE;
	BOOL mode_screen = FALSE;
	BOOL mode_title = FALSE;
	BOOL mode_window = FALSE;
	struct TextAttr *attr = NULL;
	struct TextFont *newfont, *oldfont;

	/* Automatic help command */

	if (argc == 1)
		printmode = TRUE;
	else if (argv[1][0] == '?') {
		printf("SetFont 2.0 by Dave Haynie; mods by Keith Young\n\n");
		printf("Usage: SetFont [fontname [point [place]]], where\n\n");
		printf("  \2331mfontname\2330m  is the font's name (e.g. \"topaz\")\n");
		printf("  \2331mpoint\2330m     is the point size (default is 8)\n");
		printf("  \2331mplace\2330m     pick the place, one or more of:\n");
		printf("    \2331mSCREEN\2330m    set the screen font only\n");
		printf("    \2331mTITLES\2330m    set the screen titles only\n");
		printf("    \2331mWINDOW\2330m    set the window's font only\n\n");
		printf("If no \2331mplace\2330m switch is given, everything is set.\n");
		exit(10);
	}

	/* Process the command-line arguments, AmigaDOS style. */

	for (i=3; i<argc; i++) {
		if (argv[i][0] == 'T' || argv[i][0] == 't')
			mode_title = TRUE;
		else if (argv[i][0] == 'S' || argv[i][0] == 's')
			mode_screen = TRUE;
		else if (argv[i][0] == 'W' || argv[i][0] == 'w')
			mode_window = TRUE;
	}
	if (!mode_title && !mode_screen && !mode_window)
		mode_title = mode_screen = mode_window = TRUE;

	/* First all, we've got to open all the good stuff. */

	if (!OpenUp() || (window=GetConsoleWindow(port)) == NULL) {
		ShutDown();
		exit(10);
	}

	/* Process the informational print mode, real simple */

	if (printmode) {
		printf("SetFont 2.0 by Dave Haynie; mods by Keith Young\n\n");
		attr = window->WScreen->Font;
		printf("Screen Font: %s (%ld) ", attr->ta_Name, attr->ta_YSize);
		printf("Style %ld, Flags %ld\n", attr->ta_Style, attr->ta_Flags);
		ShutDown();
		exit(0);
	}

	/* Get the attribute and font, where available. */

	attr = AllocAttr(argv[1], argv[2]);
	if ((newfont = GetFont(attr)) == NULL) {
		printf("Error: Font not found\n");
		FreeAttr(attr);
		ShutDown(); 
		exit(10);
	}

	/* Here's where the various options are processed individually. */

	/* The screen titling  font is changed via a SetFont() call to the 
	      screen's RastPort.  */

	if (mode_title) {
		oldfont = window->WScreen->RastPort.Font;
		if( SetFont(&(window->WScreen->RastPort),newfont) ) {

/* ky */ /* the screen's BarLayer->rp also needs to be SetFont'd so
			that the title-bar is displayed correctly... note that
			Intuition (currently) also uses this font pointer for string
			gadgets (and most other gadget text), menu titles, and
			a few other places. We also need to update the screen's
			BarHeight (this fixes the display of the title-bar, but
			doesn't adjust the drag-gadget attached to it... that
			might make a nice addition to this routine, I'll leave
			that as "an excersize left for the programmer..." :-) */

/* ky */	if( SetFont(window->WScreen->BarLayer->rp, newfont) ) {
				window->WScreen->BarHeight = newfont->tf_YSize + 2;
				window->WScreen->BarLayer->bounds.MaxY = newfont->tf_YSize + 2;
			}
			CloseFont(oldfont);
		}
	}

	/* If the current font is the same font, we can just change the point
	      size attribute value.  If its a completely new font, the entire
	      attribute pointer is reassigned.  This may not be necessary, but it
	      works this way, and I've had trouble with other schemes.  */

/* ky */ /*	each Screen structure has a struct TextAttr *Font in it...
			after a fair ammount of investigating, I've found out that
			(for screens that don't specify a font to use in their
			NewScreen struct) this pointer (currently, under 1.3) points
			to... IntuitionBase->SysFont (an INTUITIONPRIVATE field btw :)
			So... it appears that when a new screen is opened, that doesn't
			ask for a specific font in it's NewScreen struct, it uses this
			TextAttr.
				Dave's code reassigned the entire attribute pointer if it
			was a new font name, this caused the screen to no longer
			point at IntuitionBase->SysFont. What I do is just (effectively)
			change IntuitionBase->SysFont.ta_Name to point to the new font
			name and adjust the ta_YSize.
				And now for the caveat/feature... any new screens opened
			that don't specify a font will use this new font.  Good if that's
			what you want... Bad if the program doesn't handle it well :) */

	if (mode_screen) {
		if (strcmp(window->WScreen->Font->ta_Name,attr->ta_Name) == 0) {
			window->WScreen->Font->ta_YSize = attr->ta_YSize;
			FreeAttr(attr);
		}
		else {
/* ky */	window->WScreen->Font->ta_Name = attr->ta_Name;
/* ky */	window->WScreen->Font->ta_YSize = attr->ta_YSize;
		}
	}

	/* The window font is changed via a SetFont() call to the window's
	      RastPort, much in the same way that the screen's titling fonts are
	      changed.  Additionally, the console device must be reset to force
	      the ConUnit to be rebuit, which draws the new font from the
	      window.  */

	if (mode_window) {
		oldfont = window->RPort->Font;
		if( SetFont(window->RPort,newfont) ) {

/* ky */ /* again, we need to adjust the title-bar... */
			window->BorderTop = newfont->tf_YSize + 2 + 1;
			CloseFont(oldfont);
		}

/* ky */ /* now comes the ConUnit rebuild (via the printf("\033c"))...
			this (unfortunately) clears the screen, but it also doesn't
			'quite' clean up the top of the window (title-bar area)
			if we are using a new height font.  Since the window will be
			cleared anyway, I've added a RectFill() of my own, to clean
			up the window. */

		SetAPen(window->RPort, 0L);
		RectFill( window->RPort, 2L, (long)(window->BorderTop-2),
								(long)(window->Width-3),
								(long)(window->Height - window->BorderBottom));
		printf("\033c");
		RefreshWindowFrame(window);
	}

/* ky */ /* there used to be a 'CloseFont( newfont )' here... but
			since we need it open (cause we are using it) it should
			not be closed */

	ShutDown();
	exit(0);
}
