/*
**	CustomClasses.c
**
**	Copyright (C) 1995,96,97 Bernardo Innocenti
**
**	Special custom BOOPSI classes.
*/

#include <exec/memory.h>
#include <utility/tagitem.h>
#include <graphics/gfxbase.h>
#include <devices/inputevent.h>

#include <intuition/intuition.h>
#include <intuition/classes.h>
#include <intuition/classusr.h>
#include <intuition/gadgetclass.h>
#include <intuition/icclass.h>
#include <intuition/imageclass.h>

#include <proto/exec.h>
#include <proto/graphics.h>
#include <proto/intuition.h>
#include <proto/utility.h>

#include "CustomClasses.h"
#include "XModulePriv.h"
#include "Gui.h"

/* Per object instance data */
struct ScrollButtonData
{
	/* The number of ticks we still have to wait
	 * before sending any notification.
	 */
	ULONG TickCounter;
};



/* Function prototypes */

static LIBCALL ULONG ScrollButtonDispatcher (
	REG(a0, Class *cl),
	REG(a2, struct Gadget *g),
	REG(a1, struct gpInput *gpi));

static ULONG HOOKCALL VImageDispatcher (
	REG(a0, Class *cl),
	REG(a2, struct Image *im),
	REG(a1, struct opSet *ops));

static void NotifyAttrChanges	(Object *o, struct GadgetInfo *gi, ULONG flags, Tag attr1, ...);
static void DrawPlayImage		(struct RastPort *rp, UWORD width, UWORD heigth);
static void DrawStopImage		(struct RastPort *rp, UWORD width, UWORD heigth);
static void DrawForwardImage	(struct RastPort *rp, UWORD width, UWORD heigth);
static void DrawRewindImage		(struct RastPort *rp, UWORD width, UWORD heigth);
static void DrawPickImage		(struct RastPort *rp, UWORD width, UWORD height);
static void DrawVImage			(struct impDraw *imp, struct Image *im, struct BitMap *bm);



/* tagcall stub for OM_NOTIFY */
static void NotifyAttrChanges (Object *o, struct GadgetInfo *gi, ULONG flags, Tag attr1, ...)
{
	DoSuperMethod (OCLASS(o), o, OM_NOTIFY, &attr1, gi, flags);
}


/*
**	ScrollButtonClass
**
**	Parts of the code have been inspired by ScrollerWindow 0.3 demo
**	Copyright © 1994 Christoph Feck, TowerSystems.
**
**	Subclass of buttongclass.  The ROM class has two problems, which make
**	it not quite usable for scrollarrows.  The first problem is the missing
**	delay.  Once the next INTUITICK gets send by input.device, the ROM
**	class already sends a notification.  The other problem is that it also
**	notifies us, when the button finally gets released (which is necessary
**	for command buttons).
**
**	We define a new class with the GM_GOACTIVE and GM_HANDLEINPUT method
**	overloaded to work around these problems.
*/

static LIBCALL ULONG ScrollButtonDispatcher (
	REG(a0, Class *cl),
	REG(a2, struct Gadget *g),
	REG(a1, struct gpInput *gpi))

/* ScrollButton Class Dispatcher entrypoint.
 * Handle BOOPSI messages.
 */
{
	struct ScrollButtonData *bd = (struct ScrollButtonData *) INST_DATA(cl, g);

	switch (gpi->MethodID)
	{
		case GM_GOACTIVE:
			/* May define an attribute to make delay configurable */
			bd->TickCounter = 3;

			/* Notify our target that we have initially hit. */
			NotifyAttrChanges ((Object *)g, gpi->gpi_GInfo, 0,
				GA_ID, g->GadgetID,
				TAG_DONE);

			/* Send more input */
			return GMR_MEACTIVE;

		case GM_HANDLEINPUT:
		{
			struct RastPort *rp;
			ULONG retval = GMR_MEACTIVE;
			UWORD selected = 0;

			/* This also works with classic (non-boopsi) images. */
			if (PointInImage ((gpi->gpi_Mouse.X << 16) + (gpi->gpi_Mouse.Y), g->GadgetRender))
			{
				/* We are hit */
				selected = GFLG_SELECTED;
			}

			if (gpi->gpi_IEvent->ie_Class == IECLASS_RAWMOUSE && gpi->gpi_IEvent->ie_Code == SELECTUP)
			{
				/* Gadgetup, time to go */
				retval = GMR_NOREUSE;
				/* Unselect the gadget on our way out... */
				selected = 0;
			}
			else if (gpi->gpi_IEvent->ie_Class == IECLASS_TIMER)
			{
				/* We got a tick.  Decrement counter, and if 0, send notify. */

				if (bd->TickCounter) bd->TickCounter--;
				else if (selected)
				{
					NotifyAttrChanges ((Object *) g, gpi->gpi_GInfo, 0,
						GA_ID, g->GadgetID,
						TAG_DONE);
				}
			}

			if ((g->Flags & GFLG_SELECTED) != selected)
			{
				/* Update changes in gadget render */
				g->Flags ^= GFLG_SELECTED;
				if (rp = ObtainGIRPort (gpi->gpi_GInfo))
				{
					DoMethod ((Object *) g, GM_RENDER, gpi->gpi_GInfo, rp, GREDRAW_UPDATE);
					ReleaseGIRPort (rp);
				}
			}
			return retval;
		}

		default:
			/* Super class handles everything else */
			return (DoSuperMethodA (cl, (Object *)g, (Msg) gpi));
	}
}



GLOBALCALL Class *InitScrollButtonClass (void)
{
	Class *class;

	if (class = MakeClass (NULL, BUTTONGCLASS, NULL, sizeof(struct ScrollButtonData), 0))
		class->cl_Dispatcher.h_Entry = (ULONG (*)()) ScrollButtonDispatcher;

	return class;
}



GLOBALCALL BOOL FreeScrollButtonClass (Class *cl)
{
	return (FreeClass (cl));
}



static ULONG HOOKCALL VImageDispatcher (
	REG(a0, Class *cl),
	REG(a2, struct Image *im),
	REG(a1, struct opSet *ops))

/* VImage Class Dispatcher entrypoint.
 * Handle BOOPSI messages.
 */
{
	switch (ops->MethodID)
	{
		case OM_NEW:

			/* Create the image structure */
			if (im = (struct Image *)DoSuperMethodA (cl, (Object *)im, (Msg) ops))
			{
				ULONG			 which;
				struct RastPort	 rp;
			/*	struct DrawInfo *dri = (struct DrawInfo *)GetTagData (GA_DrawInfo, NULL, ops->ops_AttrList);
			*/
				which = GetTagData (SYSIA_Which, 0, ops->ops_AttrList);

				InitRastPort (&rp);

#ifndef OS30_ONLY
				if (GfxBase->LibNode.lib_Version >= 39)
#endif /* !OS30_ONLY */
					rp.BitMap = AllocBitMap (im->Width, im->Height, 1, BMF_CLEAR, NULL);
#ifndef OS30_ONLY
				else
				{
					if (rp.BitMap = AllocMem (sizeof (struct BitMap), MEMF_PUBLIC))
					{
						InitBitMap (rp.BitMap, 1, im->Width, im->Height);
						if (!(rp.BitMap->Planes[0] = AllocMem (RASSIZE(im->Width, im->Height), MEMF_CHIP | MEMF_CLEAR)))
						{
							FreeMem (rp.BitMap, sizeof (struct BitMap));
							rp.BitMap = NULL;
						}
					}
				}
#endif /* !OS30_ONLY */

				if (rp.BitMap)
				{
					PLANEPTR		planeptr;
					struct TmpRas	tmpras;
					struct AreaInfo	areainfo;
					WORD			areabuffer[(5 * 10 + 1) / 2];

					if (planeptr = AllocRaster (im->Width, im->Height))
					{
						InitTmpRas (&tmpras, planeptr, RASSIZE(im->Width, im->Height));
						InitArea (&areainfo, areabuffer, 10);
						SetAPen (&rp, 1);
						rp.TmpRas = &tmpras;
						rp.AreaInfo = &areainfo;

						switch (which)
						{
							case IM_PLAY:
								DrawPlayImage (&rp, im->Width, im->Height);
								break;

							case IM_STOP:
								DrawStopImage (&rp, im->Width, im->Height);
								break;

							case IM_FWD:
								DrawForwardImage (&rp, im->Width, im->Height);
								break;

							case IM_REW:
								DrawRewindImage (&rp, im->Width, im->Height);
								break;

							case IM_PICK:
								DrawPickImage (&rp, im->Width, im->Height);
								break;
						}

						/* Just to be sure... */
						rp.TmpRas = NULL;
						rp.AreaInfo = NULL;

						FreeRaster (planeptr, im->Width, im->Height);
					}

					/* This way our image will complement better */
					rp.BitMap->Planes[1] = (UBYTE *)0;
					rp.BitMap->Depth = 2;


					/* Failing to allocate the TmpRas will cause the
					 * image to be blank, but no error will be
					 * reported.
					 */

					/* Store the BitMap pointer here for later usage */
					im->ImageData = (UWORD *)rp.BitMap;

					return (ULONG)im;	/* Return new image object */
				}

				DisposeObject (im);
			}

			return NULL;

		case IM_DRAW:
		case IM_DRAWFRAME:
			DrawVImage ((struct impDraw *)ops, im, (struct BitMap *)im->ImageData);
			break;

		case OM_DISPOSE:

			/* Restore original depth! */
			((struct BitMap *)im->ImageData)->Depth = 1;

#ifndef OS30_ONLY
			if (GfxBase->LibNode.lib_Version >= 39)
#endif /* !OS30_ONLY */
				FreeBitMap ((struct BitMap *)im->ImageData);
#ifndef OS30_ONLY
			else
			{
				FreeMem (((struct BitMap *)im->ImageData)->Planes[0], RASSIZE(im->Width, im->Height));
				FreeMem (((struct BitMap *)im->ImageData), sizeof (struct BitMap));
			}
#endif /* !OS30_ONLY */

			/* Now let our superclass free it's istance */
			/* Note: I'm falling through here! */

		default:
			/* Our Super class handles everything else */
			return (DoSuperMethodA (cl, (Object *)im, (Msg) ops));
	}
}



GLOBALCALL Class *InitVImageClass (void)
{
	Class *class;

	if (class = MakeClass (NULL, IMAGECLASS, NULL, 0, 0))
		class->cl_Dispatcher.h_Entry = (ULONG (*)()) VImageDispatcher;

	return class;
}



GLOBALCALL BOOL FreeVImageClass (Class *cl)
{
	return (FreeClass (cl));
}



static void DrawPlayImage (struct RastPort *rp, UWORD width, UWORD height)
{
	UWORD	ymin = height / 4,
			ymax = (height * 3) / 4,
			ymid;

	ymin -= (ymax - ymin) & 1;	/* Force odd heigth for better arrow aspect */
	ymid = (ymin + ymax) / 2;

	RectFill (rp, 1, ymin, (width / 4) - 1, ymax);

	AreaMove (rp, width / 3, ymin);
	AreaDraw (rp, width - 2, ymid);
	AreaDraw (rp, width / 3, ymax);

	AreaEnd (rp);
}



static void DrawStopImage (struct RastPort *rp, UWORD width, UWORD height)
{
	RectFill (rp, width / 4, height / 4, (width * 3) / 4, (height * 3) / 4);
}



static void DrawForwardImage (struct RastPort *rp, UWORD width, UWORD height)
{
	UWORD	ymin = height / 4,
			ymax = (height * 3) / 4,
			ymid;

	ymin -= (ymax - ymin) & 1;	/* Force odd heigth for better arrow aspect */
	ymid = (ymin + ymax) / 2;

	AreaMove (rp, 1, ymin);
	AreaDraw (rp, width / 2, ymid);
	AreaDraw (rp, 1, ymax);

	AreaMove (rp, width / 2, ymin);
	AreaDraw (rp, width - 2, ymid);
	AreaDraw (rp, width / 2, ymax);

	AreaEnd (rp);
}



static void DrawRewindImage (struct RastPort *rp, UWORD width, UWORD height)
{
	UWORD	ymin = height / 4,
			ymax = (height * 3) / 4,
			ymid;

	ymin -= (ymax - ymin) & 1;	/* Force odd heigth for better arrow aspect */
	ymid = (ymin + ymax) / 2;

	AreaMove (rp, width - 2, ymin);
	AreaDraw (rp, width / 2, ymid);
	AreaDraw (rp, width - 2, ymax);

	AreaMove (rp, width / 2 - 1, ymin);
	AreaDraw (rp, 1, ymid);
	AreaDraw (rp, width / 2 - 1, ymax);

	AreaEnd (rp);
}


static void DrawPickImage (struct RastPort *rp, UWORD width, UWORD height)
/*
 *      arrowxmin
 *      | tailxmin
 *      | | tailxmax
 *      | | |
 *      | v v
 *      | ###<----tailymin
 *      v ###
 *      #######<--arrowymin
 *       #####
 *        ###
 *         #<-----arrowymax
 *      #######<--arrowymax+1
 */
{
	UWORD	tailymin	= height / 6,
			tailxmin	= (width * 2) / 5,
			tailxmax	= (width * 3) / 5,
			arrowymin	= (height * 2) / 5,
			arrowymax	= (height * 4) / 5,
			arrowxmin	= width / 5,
			arrowxmax	= (width * 4) / 5;

	AreaMove (rp, tailxmin, tailymin);
	AreaDraw (rp, tailxmax, tailymin);
	AreaDraw (rp, tailxmax, arrowymin);
	AreaDraw (rp, arrowxmax, arrowymin);
	AreaDraw (rp, (arrowxmin + arrowxmax) / 2, arrowymax);
	AreaDraw (rp, arrowxmin, arrowymin);
	AreaDraw (rp, tailxmin, arrowymin);
	AreaEnd (rp);

	if (arrowymax < height - 1) arrowymax++;

	Move (rp, arrowxmin, arrowymax);
	Draw (rp, arrowxmax, arrowymax);
}



static void DrawVImage (struct impDraw *imp, struct Image *im, struct BitMap *bm)
{
	if (bm)
		BltBitMapRastPort (bm, 0, 0, imp->imp_RPort,
			imp->imp_Offset.X, imp->imp_Offset.Y, im->Width, im->Height,
			(imp->imp_State == IDS_SELECTED) ? 0x030 : 0x0C0);
}



GLOBALCALL struct Gadget *CreateUpButton (ULONG id, struct Gadget *target, LONG *map, LONG Place)
{
	struct Gadget	*UpButton;
	struct Image	*UpImage;

	if (!(UpImage = NewImageObject (UPIMAGE)))
		return NULL;

	if (!(UpButton = (struct Gadget *)NewObject (ScrollButtonClass, NULL,
		GA_ID,				id,
		GA_RelBottom,		- (UpImage->Height * 2) - SizeHeight + 1,
		(Place == SCROLLERPLACE_LEFT) ? GA_Left : GA_RelRight,
			(Place == SCROLLERPLACE_LEFT) ? 0 : (- SizeWidth + 1),
		/* ^--- perhaps using UpImage->Width would be better... */

		(Place == SCROLLERPLACE_LEFT) ? GA_LeftBorder : GA_RightBorder,	TRUE,

		/* No need for GA_Width/Height.  buttongclass is smart :) */
		GA_Image,			UpImage,
		ICA_TARGET,			target,
		ICA_MAP,			map,
		TAG_DONE)))
		DisposeObject (UpImage);

	return UpButton;
}



GLOBALCALL struct Gadget *CreateDnButton (ULONG id, struct Gadget *target, LONG *map, LONG Place)
{
	struct Gadget	*DnButton;
	struct Image	*DnImage;


	if (!(DnImage = NewImageObject (DOWNIMAGE)))
		return NULL;

	if (!(DnButton = (struct Gadget *)NewObject (ScrollButtonClass, NULL,
		GA_ID,				id,
		GA_RelBottom,		- DnImage->Height - SizeHeight + 1,
		(Place == SCROLLERPLACE_LEFT) ? GA_Left : GA_RelRight,
			(Place == SCROLLERPLACE_LEFT) ? 0 : (- SizeWidth + 1),
		(Place == SCROLLERPLACE_LEFT) ? GA_LeftBorder : GA_RightBorder,	TRUE,
		/* No need for GA_Width/Height.  buttongclass is smart :) */
		GA_Image,			DnImage,
		ICA_TARGET,			target,
		ICA_MAP,			map,
		TAG_DONE)))
		DisposeObject (DnImage);

	return DnButton;
}



GLOBALCALL struct Gadget *CreateSxButton (ULONG id, struct Gadget *target, LONG *map)
{
	struct Gadget	*SxButton;
	struct Image	*SxImage;


	if (!(SxImage = NewImageObject (LEFTIMAGE)))
		return NULL;

	if (!(SxButton = (struct Gadget *)NewObject (ScrollButtonClass, NULL,
		GA_ID,				id,
		GA_RelBottom,		- SxImage->Height + 1,
		GA_RelRight,		- SizeWidth - (SxImage->Width * 2) + 1,
		GA_BottomBorder,	TRUE,
		/* No need for GA_Width/Height.  buttongclass is smart :) */
		GA_Image,			SxImage,
		ICA_TARGET,			target,
		ICA_MAP,			map,
		TAG_DONE)))
		DisposeObject (SxImage);

	return SxButton;
}



GLOBALCALL struct Gadget *CreateDxButton (ULONG id, struct Gadget *target, LONG *map)
{
	struct Gadget	*DxButton;
	struct Image	*DxImage;


	if (!(DxImage = NewImageObject (RIGHTIMAGE)))
		return NULL;

	if (!(DxButton = (struct Gadget *)NewObject (ScrollButtonClass, NULL,
		GA_ID,				id,
		GA_RelBottom,		- DxImage->Height + 1,
		GA_RelRight,		- SizeWidth - DxImage->Width + 1,
		GA_BottomBorder,	TRUE,
		/* No need for GA_Width/Height.  buttongclass is smart :) */
		GA_Image,			DxImage,
		ICA_TARGET,			target,
		ICA_MAP,			map,
		TAG_DONE)))
		DisposeObject (DxImage);

	return DxButton;
}



GLOBALCALL struct Gadget *CreateVSlider (ULONG id, struct Gadget *target, LONG *map, LONG ButtonsSpacing, LONG Place)
{
	UWORD horizpos;

	horizpos = (Place == SCROLLERPLACE_LEFT) ? Scr->WBorLeft : (- SizeWidth + 5);

	return ((struct Gadget *)NewObject (NULL, PROPGCLASS,
		GA_ID,				id,
		GA_Top,				OffY + 1,
		(Place == SCROLLERPLACE_LEFT) ? GA_Left : GA_RelRight, horizpos,
		GA_Width,			SizeWidth - 8,
		GA_RelHeight,		- OffY - Scr->WBorBottom - SizeHeight
							- ButtonsSpacing,
		(Place == SCROLLERPLACE_LEFT) ? GA_LeftBorder : GA_RightBorder,	TRUE,

		PGA_NewLook,		TRUE,

		/* Borderless sliders do only look good with newlook screens */
		PGA_Borderless,		((DrawInfo->dri_Flags & DRIF_NEWLOOK) && DrawInfo->dri_Depth != 1),

		ICA_TARGET,			target,
		ICA_MAP,			map,

		TAG_DONE));
}



GLOBALCALL struct Gadget *CreateHSlider (ULONG id, struct Gadget *target, LONG *map, LONG ButtonsSpacing)
{
	struct Gadget	*HSlider;

	if (!(HSlider = (struct Gadget *)NewObject (NULL, PROPGCLASS,
		GA_ID,				id,
		GA_Left,			OffX - 1,
		GA_RelBottom,		- SizeHeight + ((SizeHeight > 15) ? 4 : 3),
		GA_RelWidth,		- OffX - SizeWidth
							- ButtonsSpacing,
		GA_Height,			SizeHeight - ((SizeHeight > 15)  ? 6 : 4),
		GA_BottomBorder,	TRUE,

		PGA_NewLook,		TRUE,
		PGA_Borderless,		((DrawInfo->dri_Flags & DRIF_NEWLOOK) && DrawInfo->dri_Depth != 1),
		PGA_Freedom,		FREEHORIZ,

		ICA_TARGET,			target,
		ICA_MAP,			map,

		TAG_DONE)))
		return NULL;
	return HSlider;
}

