/*:ts=8 */
/*  >>      PopColours: The amazing colour-set control panel      << 
**  >> From The Transactor - THE Magazine for Commodore Computing <<
**  
**   November 1986, Version 1.0
**
** - uses proportional gadgets for setting R G B
**   and allows alteration of any colour register
** - operates on the TOPMOST OR SECOND SCREEN, so
**   lets you change the colours of any currently 
**   executing program with its own screen
** - only opens up when selected, so it doesn't eat up
**   screen space when you're not using it
**
**           >> THIS PROGRAM MAY BE FREELY DISTRIBUTED <<
**
** (c) 1986, AHA! (Acme Heuristic Applications!)
**
** Written by Chris Zamara and Nick Sullivan
*/

#include <exec/types.h>
#include <intuition/intuition.h>
#include <graphics/gfx.h>
#include <graphics/view.h>

/* defines for defaults, positioning, sizes, etc. */
#define SCREEN2INIT FALSE /* default to topmost screen			*/
#define GADGHEIGHT 10L	/* height of proportional RGB gadgets		*/
#define RTOP 15L	/* y position of top of Red gadget		*/
#define GTOP 28L	/* " " " Green gadget				*/
#define BTOP 41L	/* " " " Blue gadget				*/
#define CVAL 177L	/* x position of colour values			*/
#define CPOSX 195L	/* x position of colour register indicator	*/
#define CPOSY 63L	/* y position of " " ...			*/
#define RPOSX 115L	/* x position of Register text			*/
#define SCRPOSX 15L	/* x position of screen indicator		*/
#define SCRPOSY 63L	/* y position of screen indicator		*/
#define UPGADGX 197L	/* x position of up gadget			*/
#define UPGADGY 10L 	/* y position of up gadget			*/
#define DNGADGX 197L	/* x position of down gadget			*/
#define DNGADGY 32L 	/* y position of down gadget			*/
#define COLORGADGET  1	/* ID for RGB gadgets				*/
#define UPGADGET     2	/* ID for up gadget				*/
#define DOWNGADGET   3	/* ID for down gadget				*/
#define SCREENGADGET 4	/* ID for down gadget				*/
#define ARROWDELAY 5	/* # of ticks before repeating up/down		*/
#define DELAY 100000	/* pause for "no 2nd scrn" message 		*/
#define POTINC 0x1111	/* amount added to each colour pot per click	*/
#define RTEXT (RTOP+(GADGHEIGHT >> 1)+2) /* gadget text position	*/
#define GTEXT (GTOP+(GADGHEIGHT >> 1)+2)
#define BTEXT (BTOP+(GADGHEIGHT >> 1)+2)

/* global structure declarations */
struct IntuitionBase *IntuitionBase;
struct GfxBase *GfxBase;
struct ViewPort *vp;
struct RastPort *rp;
struct IntuiMessage *GetMsg(), *message;
struct Window *OpenWindow(), *gwind, *SmallWind;
struct Screen *TheScreen;

/* function declarations */
APTR   OpenLibrary();
USHORT GetRGB4();
USHORT GadgPressed();
BOOL   BigWindow();

/* gadget structures for colour controls */
struct Gadget gadgR, gadgG, gadgB;

/* gadget structures for up/down arrows and screen selector */
struct Gadget gadgUp, gadgDown, gadgScreen;

/* define NewWindow structure for our window */
struct NewWindow GadgWind = {
	400, 16, 233, 72,	/* left, top, width, height	*/
	-1, -1,			/* use screen colours		*/
	GADGETDOWN		/* IDCMP flags			*/
	  | GADGETUP
	  | MOUSEBUTTONS
	  | INTUITICKS
	  | INACTIVEWINDOW
	  | CLOSEWINDOW,
	WINDOWDEPTH		/* window flags			*/
	  | WINDOWCLOSE
	  | WINDOWDRAG
	  | INACTIVEWINDOW
	  | RMBTRAP
	  | ACTIVATE
	  | SMART_REFRESH,
	&gadgUp,		/* first gadget in list		*/
	NULL,
	(UBYTE *)"PopColours 1.0", /* window title		*/
	NULL,			/* ptr to screen 		*/
	NULL,
	0, 0, 0, 0,		/* sizing limits (non-resizable)*/
	WBENCHSCREEN
};

/* also make NewWindow structure for small window */
struct NewWindow SWind;

/* PropInfo structures for control gadgets */
struct PropInfo propinfR, propinfG, propinfB;

/* control knob Image structures */
struct Image knobR, knobG, knobB;

/* co-ordinate of points of up/down arrows */
SHORT UpXY[8]   = { 13,0, 25,17,  0,17, 13,0 };
SHORT DownXY[8] = {  0,0, 25, 0, 13,17,  0,0 };

/* Border structure for Up-Arrow gadget */
struct Border UpArrow = {
	3, 2,
	1, 0, JAM1,
	4,
	UpXY,
	NULL
};

/* Border structure for Down-Arrow gadget */
struct Border DownArrow = {
	3, 2,
	1, 0, JAM1,
	4,
	DownXY,
	NULL
};

/* this next array is for fast'n'easy integer to ASCII conversion */
char *cnum[32] = { "00", "01", "02", "03", "04", "05", "06", "07",
		   "08", "09", "10", "11", "12", "13", "14", "15",
		   "16", "17", "18", "19", "20", "21", "22", "23",
		   "24", "25", "26", "27", "28", "29", "30", "31",
		 };

USHORT colreg = 0;	/* current colour register 		*/
BOOL Screen2Flag;	/* false=top screen, true=second screen */
BOOL Old2Flag;		/* previous value of Screen2Flag        */


main (argc, argv)

int argc;
char *argv[];
{

	/* open intuition and graphics libraries	*/
	if ( (IntuitionBase = (struct IntuitionBase *)
	               OpenLibrary ("intuition.library", 0L) ) == NULL)
		exit (0L);

	if ( (GfxBase = (struct GfxBase *)
	                OpenLibrary ("graphics.library", 0L) ) == NULL) {
		CloseLibrary (IntuitionBase);
		exit (0L);
	}

	/* second arg specifies start-up colour register */
	if (argc == 2)
		colreg = atoi(argv[1]);

	/* switch between small and big windows until big is closed */
	do {
		SmallWindow();
	} while (BigWindow());

	/* close everything up before ending */
	CloseLibrary(GfxBase);
	CloseLibrary(IntuitionBase);

}


BOOL BigWindow ()
{

ULONG  msgclass;	/* message class from IDCMP			*/
USHORT msgcode;		/* message code from IDCMP			*/
APTR   IAddr;		/* pointer to gadget from IDCMP			*/
USHORT WhichGadg;	/* ID of selected gadget			*/
USHORT TickStart;	/* countdown for up/down arrow repeat delay	*/
BOOL   GadgSel = FALSE;	/* set when a colour gadget is selected		*/
BOOL   RegSel = FALSE;	/* set when up or down gadget is selected	*/
BOOL   window_still_open = TRUE;
BOOL   window_active = TRUE;

	GadgSetup();	/* initialize gadget structures */

	Screen2Flag = SCREEN2INIT;
	Old2Flag   = !SCREEN2INIT;

	/* now attempt to open the window containing the gadgets */
	if ((gwind = OpenWindow(&GadgWind)) == NULL) {
		CloseLibrary(GfxBase);
		CloseLibrary(IntuitionBase);
		exit(0L);
	}

	/* get ptr to rastport for graphics routines	*/
	rp = gwind->RPort;

	/* set RGB gadget positions to current colours	*/
	SetColrs();

	/* put up labels on window display		*/
	WinText();

	/* put up colour register number indicator	*/
	RegIndicate();

	/*** main event loop ***/
	while (window_still_open && window_active) {

		/* wait politely, except when sliding a colour gadget */
		if (! GadgSel)
			Wait (1L << gwind->UserPort->mp_SigBit);

		while (message = GetMsg(gwind->UserPort)) {
			/* get what we need from the message port */
			msgclass = message->Class;
			msgcode  = message->Code;
			IAddr    = message->IAddress;
			ReplyMsg(message); /* reply to message right away */

			/* now we can interpret the message */

			/* check for gadget selected */
			if (msgclass == GADGETDOWN)
				WhichGadg = GadgPressed(IAddr, &GadgSel,
						        &RegSel, &TickStart);

			/* check if gadget released, even if off of gadget */
			else if ( (msgclass == MOUSEBUTTONS
				   && msgcode == SELECTUP)
				  || msgclass == GADGETUP )
				GadgReleased(IAddr, &GadgSel, &RegSel,
						WhichGadg);

			/* auto-repeat arrow gadgets after delay */
			else if (msgclass == INTUITICKS && RegSel) {
				/* wait ARROWDELAY ticks before repeating */
				if (TickStart++ > ARROWDELAY)
					UpdateColReg(WhichGadg);
			}

			/* finish up if the close gadget is clicked */
			else if (msgclass == CLOSEWINDOW)
				window_still_open = FALSE;

			/* exit if window made inactive */
			else if (msgclass == INACTIVEWINDOW)
				window_active = FALSE;

		}
		if (GadgSel)
			/* set colour to pot values */
			ReadGadg();

	}

	/* update NewWindow struct in case window has been moved */
	GadgWind.LeftEdge = gwind->LeftEdge;
	GadgWind.TopEdge = gwind->TopEdge;

	CloseWindow(gwind);

	return(window_still_open);
}


SmallWindow ()
{

ULONG  msgclass;	/* message class from IDCMP			*/
BOOL   activated = FALSE;

	SWind = GadgWind; /* almost like big window, */
	/* except for ... */
	SWind.Height = 11;
	SWind.IDCMPFlags = ACTIVEWINDOW;
	SWind.FirstGadget = NULL;
	SWind.Flags = WINDOWDEPTH | ACTIVEWINDOW;

	/* attempt to open the small window */
	if ((SmallWind = OpenWindow(&SWind)) == NULL) {
		CloseLibrary(GfxBase);
		CloseLibrary(IntuitionBase);
		exit(0L);
	}

	Wait (1L << SmallWind->UserPort->mp_SigBit);
	while (message = GetMsg(SmallWind->UserPort)) {
		msgclass = message->Class;
		ReplyMsg(message); /* reply to message right away */

		if (msgclass == ACTIVEWINDOW)
			activated = TRUE;
	}

	/* update NewWindow struct in case window has been moved */
	GadgWind.LeftEdge = SmallWind->LeftEdge;
	GadgWind.TopEdge = SmallWind->TopEdge;

	CloseWindow(SmallWind);
}


USHORT GadgPressed (IAddr, GadgSel, RegSel, TickStart)
/* set flags and call appropriate routines when a gadget is clicked */

struct Gadget *IAddr;
BOOL   *GadgSel, *RegSel;
USHORT *TickStart;
{
USHORT WhichGadg;

	/* get gadget type */
	WhichGadg = IAddr->GadgetID;

	/* rgb gadget? */
	if (WhichGadg == COLORGADGET) {
		*GadgSel = TRUE;
		/* update settings if 2nd screen gone */
		if (Screen2Flag	
		    && IntuitionBase->FirstScreen->NextScreen == NULL)
			UpdateColReg(0);
	}

	/* up/down gadget? */
	else if (WhichGadg == UPGADGET || WhichGadg == DOWNGADGET) {
		*RegSel = TRUE;
		*TickStart = 0; /* start delay timer */
		UpdateColReg(WhichGadg);
	}

	/* Screen select gadget? */
	else if (WhichGadg == SCREENGADGET) {
		Screen2Flag = !Screen2Flag;
		UpdateColReg(0);
	}

	return (WhichGadg);
}


GadgReleased (IAddr, GadgSel, RegSel, WhichGadg)
/* a gadget has been released - set flags and call appropriate routines */

struct Gadget *IAddr;
BOOL   *GadgSel, *RegSel;
USHORT WhichGadg;
{

	if (WhichGadg == COLORGADGET) {
		ReadGadg();
		if ( ( ((struct PropInfo *)(IAddr->SpecialInfo))->Flags )
		     & KNOBHIT )
			SetColrs(); /* re-position knob if it was moved */

		*GadgSel = FALSE;   /* colour gadget no longer selected */
	}
	else
		*RegSel = FALSE;    /* ..or other gadget no longer selected */
}


ReadGadg ()
/* read gadget pot values and set colour register 'colreg' accordingly */

{
USHORT RedVal, GrnVal, BluVal;

	RedVal = propinfR.HorizPot / POTINC;
	GrnVal = propinfG.HorizPot / POTINC;
	BluVal = propinfB.HorizPot / POTINC;

	/* get viewport of topmost or second screen */
	vp = &(TheScreen->ViewPort);

	/* change colour register */
	SetRGB4(vp, (ULONG)colreg, (ULONG)RedVal,
		    (ULONG)GrnVal, (ULONG)BluVal
	       );

	PrintValues(RedVal, GrnVal, BluVal); /* numbers to right */
}


PrintValues (R, G, B)
/* print colour values to right of gadgets */

USHORT R, G, B;
{
	SetAPen(rp, 1L);
	Move(rp, CVAL, RTEXT);
	Text(rp, cnum[R], 2L);
	Move(rp, CVAL, GTEXT);
	Text(rp, cnum[G], 2L);
	Move(rp, CVAL, BTEXT);
	Text(rp, cnum[B], 2L);
}


SetColrs ()
/* set gadget pot values according to rgb in register 'colreg' */

{
USHORT C0, R, G, B;
ULONG hpR, hpG, hpB;

	/* get viewport of topmost screen */
	ScreenPick(); /* select screen one or two */
	vp = &(TheScreen->ViewPort);

	/* get rgb of specified colour register */
	C0 = GetRGB4(vp->ColorMap, (ULONG)(colreg) );

	/* convert rgb to HorizPot values */
	R = (C0 >> 8) & 0xF;
	G = (C0 >> 4) & 0xF;
	B = C0 & 0xF;
	hpR = R * POTINC;
	hpG = G * POTINC;
	hpB = B * POTINC;

	/* change gadgets to reflect new colours */
	ModifyProp(&gadgR, gwind, NULL,
		   (ULONG)propinfR.Flags, hpR, 0L,
		   (ULONG)propinfR.HorizBody, 0L
		  );
	ModifyProp(&gadgG, gwind, NULL,
		   (ULONG)propinfG.Flags, hpG, 0L,
		   (ULONG)propinfG.HorizBody, 0L
		  );
	ModifyProp(&gadgB, gwind, NULL,
		   (ULONG)propinfB.Flags, hpB, 0L,
		   (ULONG)propinfB.HorizBody, 0L
		  );

	PrintValues(R, G, B); /* colour numbers */
}


RegIndicate ()
/* show colour register number */

{
ULONG c;

	/* first print register number */
	Move(rp, CPOSX, CPOSY);
	SetAPen(rp, 1L);
	Text(rp, cnum[colreg], 2L);

	/* now display a block in 'colreg' colour if changing this screen */
	/* or background colour if changing another screen                */
	c = (IntuitionBase->ActiveScreen == TheScreen) ? colreg : 0;
	Move(rp, CPOSX+24, CPOSY);	/* move past text	*/
	SetDrMd(rp, INVERSVID);		/* inverse mode		*/
	SetAPen(rp, c);			/* set colour 		*/
	Text(rp, " ", 1L);		/* print a space	*/
	SetDrMd(rp, JAM2);		/* set mode back to normal */
	SetAPen(rp, 1L);
}


WinText ()
/* put up various labels on window */

{
	SetAPen(rp, 1L);

	Move(rp, 5L, RTEXT);
	Text(rp, "R", 1L);

	Move(rp, 5L, GTEXT);
	Text(rp, "G", 1L);

	Move(rp, 5L, BTEXT);
	Text(rp, "B", 1L);

	Move(rp, RPOSX, CPOSY);
	Text(rp, " Register", 9L);

}


UpdateColReg (WhichGadg)
/* increment or decrement colour register	    */
/* if WhichGadg is 0, just display current settings */

USHORT WhichGadg;

{
USHORT numregs;

	/* get number of bitplanes of screen */
	ScreenPick(); /* set 'TheScreen' */
	numregs = 1 << ((TheScreen->ViewPort).RasInfo->BitMap->Depth);
	/* (whew!) */

	/* increment or decrement colour register */
	if (WhichGadg == UPGADGET)
		colreg++;
	else if (WhichGadg == DOWNGADGET)
		colreg--;

	colreg = colreg % numregs;

	RegIndicate();
	SetColrs();
}


ScreenPick ()
/* select top screen if Screen2Flag false  **
** or second screen if true                **
** and update display if necesary          **
*/

{
ULONG d;

	TheScreen = IntuitionBase->FirstScreen;

	Move(rp, SCRPOSX, SCRPOSY);
	SetAPen(rp, 1L);
	SetBPen(rp, 3L); /* print in different background colour */

	if (Screen2Flag) { 
		if (TheScreen->NextScreen == NULL) {
			/* no second screen, put up temp. message */
			Screen2Flag = FALSE;
			Text(rp, "(no 2nd scrn)", 13L);
			/* delay the easy way (ok, so we hog a second) */
			for(d=0; d<DELAY; d++)
				;
			Move(rp, SCRPOSX, SCRPOSY);
			Text(rp, " TOP  SCREEN ", 13L);

		}
		else {
			/* set pointer to second screen */
			TheScreen = TheScreen->NextScreen;
			if (Old2Flag != Screen2Flag)
				Text(rp, "SECOND SCREEN", 13L);
		}
	}
	if (!Screen2Flag && Old2Flag)
		Text(rp, " TOP  SCREEN ", 13L);

	SetBPen(rp, 0L);
	Old2Flag = Screen2Flag;
}


GadgSetup ()
{
	/* proportional gadget for red */
	gadgR.NextGadget = NULL;
	gadgR.LeftEdge = 16;
	gadgR.TopEdge = RTOP;
	gadgR.Width = 155;
	gadgR.Height = GADGHEIGHT;
	gadgR.Flags = GADGHNONE | GADGIMAGE;
	gadgR.Activation = GADGIMMEDIATE | RELVERIFY;
	gadgR.GadgetType = PROPGADGET;
	gadgR.GadgetRender = (APTR)&knobR;
	gadgR.SelectRender = NULL;
	gadgR.GadgetText = NULL;
	gadgR.MutualExclude = NULL;
	gadgR.SpecialInfo = (APTR)&propinfR;
	gadgR.GadgetID = COLORGADGET;
	gadgR.UserData = NULL;

	/* up-arrow for changing colour register */
	gadgUp.NextGadget = NULL;
	gadgUp.LeftEdge = UPGADGX;
	gadgUp.TopEdge = UPGADGY;
	gadgUp.Width = 31;
	gadgUp.Height = 21;
	gadgUp.Flags = GADGHCOMP;
	gadgUp.Activation = GADGIMMEDIATE | RELVERIFY;
	gadgUp.GadgetType = BOOLGADGET;
	gadgUp.GadgetRender = (APTR)&UpArrow;
	gadgUp.SelectRender = NULL;
	gadgUp.GadgetText = NULL;
	gadgUp.MutualExclude = NULL;
	gadgUp.SpecialInfo = NULL;
	gadgUp.GadgetID = UPGADGET;
	gadgUp.UserData = NULL;

	/* top/second screen indicator/selector */
	gadgScreen = gadgUp;
	gadgScreen.LeftEdge = SCRPOSX;
	gadgScreen.TopEdge = SCRPOSY-5;
	gadgScreen.Width = 104;
	gadgScreen.Height = 11;
	gadgScreen.Activation = GADGIMMEDIATE;
	gadgScreen.GadgetRender = NULL;
	gadgScreen.GadgetID = SCREENGADGET;

	/* values needed for propinfo structure */
	propinfR.Flags = FREEHORIZ | AUTOKNOB;
	propinfR.HorizBody = 1 << 12;	/* body increment (1/16) */
	/* propinf structures for R G B gadgets all alike */
	propinfG = propinfR;
	propinfB = propinfR;

	/* G and B gadgets same as R */
	gadgG = gadgR;
	gadgB = gadgR;
	/* except for... */
	gadgG.TopEdge = GTOP;
	gadgB.TopEdge = BTOP;
	gadgG.GadgetRender = (APTR)&knobG;
	gadgB.GadgetRender = (APTR)&knobB;
	gadgG.SpecialInfo = (APTR)&propinfG;
	gadgB.SpecialInfo = (APTR)&propinfB;

	/* define down gadget in terms of up gadget */
	gadgDown = gadgUp;
	gadgDown.GadgetRender = (APTR)&DownArrow;
	gadgDown.LeftEdge = DNGADGX;
	gadgDown.TopEdge  = DNGADGY;
	gadgDown.GadgetID = DOWNGADGET;

	/* link all gadgets by pointers */
	gadgUp.NextGadget = &gadgDown;
	gadgDown.NextGadget = &gadgR;
	gadgR.NextGadget = &gadgG;
	gadgG.NextGadget = &gadgB;
	gadgB.NextGadget = &gadgScreen;

}
