/*
 * handler.c
 * 
 * Routines to set up handler.
 * Part of Yak.
 * 
 * Martin W. Scott, 9/92.
 */
#include <exec/types.h>
#include <exec/exec.h>
#include <hardware/custom.h>
#include <hardware/dmabits.h>
#include <devices/input.h>
#include <devices/inputevent.h>
#include <libraries/commodities.h>
#include <graphics/gfxmacros.h>
#include <intuition/intuition.h>
#include <intuition/intuitionbase.h>
#include <clib/alib_protos.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/layers.h>
#include <proto/intuition.h>

#include "yak.h"

#define TIMERCOUNT	2	/* how long mouse must stop before autopoint */

CxObj *clickobj;
ULONG clicksigflag;
static BYTE clicksigbit;
static struct Task *thistask;
static mouseoff = FALSE;

#define INTERRUPT void __interrupt __saveds

/* modified from DMouse */
struct Window *
WindowUnderMouse()
{
	struct Layer *layer;
	struct Screen *scr = IntuitionBase->FirstScreen;
	WORD mousex, mousey;

	/* these wont change since this routine called inside Forbid()/Permit() */
	mousey = IntuitionBase->MouseY;
	mousex = IntuitionBase->MouseX;

	for (; scr; scr = scr->NextScreen)
	{
		if (!(scr->ViewPort.Modes & LACE))
			mousey >>= 1;
		if (!(scr->ViewPort.Modes & HIRES))
			mousex >>= 1;

		if (layer = WhichLayer(&scr->LayerInfo, mousex - scr->ViewPort.DxOffset, mousey - scr->ViewPort.DyOffset))
			break;
		if (mousey >= scr->ViewPort.DyOffset)
			break;
	}

	return (layer ? layer->Window : NULL);
}

/* does active window have an active string gadget? */
BOOL
StrGadgetActive(struct Window *w)
{
	struct Gadget *g = w->FirstGadget;

	for (; g; g = g->NextGadget)
		if ((g->GadgetType & STRGADGET) && (g->Flags & GFLG_SELECTED))
			return TRUE;
	return FALSE;
}

/* activate window under mouse */
/* only if: no menus, no string gadgets active */
/* that last one is a bit difficult, since IntuitionBase went REALLY private... */
/* But hey! Works with SiliconMenus Iris option! (Unlike AutoPoint, DMouse) */
void
ActivateMouseWindow()
{
	struct Window *win;

	Forbid();

	if ((win = WindowUnderMouse()) &&
	    !(win->Flags & WFLG_WINDOWACTIVE) &&
	    !(IntuitionBase->ActiveWindow->Flags & (WFLG_INREQUEST|WFLG_MENUSTATE)) &&
	    !StrGadgetActive(IntuitionBase->ActiveWindow))
	{
		ActivateWindow(win);
	}
	Permit();
}

#ifdef USE_SETPOINTER

/*
 * Problem with these routines is that (e.g.) reqtools changes pointer to
 * 'busy' clock when opening a file-requester. When it's finished, it restores
 * blank pointer, but these routines have lost track of that window.
 */

static UWORD __chip BlankPointer[] = {
   0x0000, 0x0000,	/* position control */
			/* zilcho data */
   0x0000, 0x0000	/* next sprite field */
};

static struct Window *blankedwin;
static USHORT *Pointer;
static BYTE PtrHeight;
static BYTE PtrWidth;
static BYTE XOffset, YOffset;

void
TurnMouseOff()
{
	mouseoff = TRUE;

	Forbid();

	blankedwin = IntuitionBase->ActiveWindow;
	Pointer = blankedwin->Pointer;
	PtrHeight = blankedwin->PtrHeight;
	PtrWidth = blankedwin->PtrWidth;
	XOffset = blankedwin->XOffset;
	YOffset = blankedwin->YOffset;

	SetPointer(blankedwin, BlankPointer, 0,0,0,0);

	Permit();
}

void
TurnMouseOn()
{
	struct Screen *scr = IntuitionBase->FirstScreen;
	struct Window *win;

	mouseoff = FALSE;

	Forbid();
	/* check that window still exists */
	for (; scr; scr = scr->NextScreen)
		for (win = scr->FirstWindow; win; win = win->NextWindow)
			if (win && win == blankedwin)
			{
				SetPointer(win, Pointer,
						PtrHeight, PtrWidth,
						XOffset, YOffset);
				Permit();
				return;
			}
	Permit();
	return;
}

#else	/* turn of sprites to blank mouse */

extern struct Custom __far custom;

/* the mouse-blank time is unadjustable at the moment. It's less crucial
 * anyway -- you either want it or you don't.
 */ 

#define TurnMouseOn() {WaitTOF(); ON_SPRITE; mouseoff = FALSE;}
#define TurnMouseOff() {WaitTOF(); OFF_SPRITE; custom.spr[0].dataa = custom.spr[0].datab=0; mouseoff = TRUE;}

#endif /* USE_SETPOINTER */

static BOOL blanked;
static struct Screen *blankscr;
/* blank display, by putting up a black screen */
void
BlankScreen()
{
	if (blankscr)
		ScreenToFront(blankscr);
	else if (blankscr = OpenScreenTags(NULL,SA_Depth, 1,
						SA_Quiet, TRUE,
						TAG_DONE))
	{
		SetRGB4(&blankscr->ViewPort, 0, 0, 0, 0);
		blanked = TRUE;
	}
	TurnMouseOff();
}

/* unblank display, i.e. close our screen */
void
UnBlankScreen()
{
	if (blankscr)
		CloseScreen(blankscr);
	blankscr = NULL;
	blanked = FALSE;
	TurnMouseOn();		/* ooer! */
}


#define ALL_BUTTONS	(IEQUALIFIER_LEFTBUTTON|IEQUALIFIER_RBUTTON|IEQUALIFIER_MIDBUTTON)
#define KEY_QUAL	(IEQUALIFIER_LSHIFT|IEQUALIFIER_RSHIFT \
			|IEQUALIFIER_CONTROL \
			|IEQUALIFIER_LALT|IEQUALIFIER_RALT \
			|IEQUALIFIER_LCOMMAND|IEQUALIFIER_RCOMMAND)

/* the input handler itself */
INTERRUPT Handler(CxMsg *CxMsg, CxObj *CO)
{
	static struct timeval lastclick;/* last left-button click */
	static UBYTE apcount;		/* timer events since last mousemove */
	struct InputEvent *ev;


	ev = (struct InputEvent *)CxMsgData(CxMsg);

	if (ev->ie_Class == IECLASS_TIMER)
	{
		if (autopoint && apcount && !--apcount)	/* auto-activate? */
			ActivateMouseWindow();

		if (blanktimeout && !--blankcount)	/* blank screen? */
		{
			BlankScreen();
			blankcount = blanktimeout;	/* reset counter */
		}					/* in case sceen opens on top */

		if (mouseblank && !mouseoff && !--mblankcount)	/* blank mouse? */
			TurnMouseOff();
	}
	else if ((ev->ie_Class == IECLASS_RAWKEY) && !(ev->ie_Code & IECODE_UP_PREFIX))
	{
		blankcount = blanktimeout;	/* reset blanking countdown */
		if (blanked)			/* turn off screen-blanking */
			 UnBlankScreen();
		if (mouseblank && !mouseoff)	/* blank the mouse */
			TurnMouseOff();
		if (click_volume)		/* perform keyclick */
			Signal(thistask, clicksigflag);
		if (keyactivate)		/* perform key-activate */
			ActivateMouseWindow();
	}
	else if (ev->ie_Class == IECLASS_RAWMOUSE)
	{
	    /* restore screen/mouse pointer */
		blankcount = blanktimeout;	/* reset blanking countdowns */
		mblankcount = mblanktimeout;
		if (blanked)			/* turn off screen-blanking */
			 UnBlankScreen();
		if (mouseoff)			/* restore mouse pointer */
			TurnMouseOn();

	    /* window/screen cycling... */
	    Forbid();
		if (clicktofront && ev->ie_Code == IECODE_LBUTTON && !(ev->ie_Qualifier & KEY_QUAL))
		{
			if (DoubleClick(lastclick.tv_secs,
					lastclick.tv_micro,
					ev->ie_TimeStamp.tv_secs,
					ev->ie_TimeStamp.tv_micro))
			{
				struct Window *win = WindowUnderMouse();

				if (win && !(win->Flags & WFLG_BACKDROP))
					WindowToFront(win);
				lastclick.tv_secs = 0;
				lastclick.tv_micro = 0;
			}
			else
			{
				lastclick.tv_secs = ev->ie_TimeStamp.tv_secs;
				lastclick.tv_micro = ev->ie_TimeStamp.tv_micro;
			}
		}
		else if ((ev->ie_Qualifier & IEQUALIFIER_LEFTBUTTON) && ev->ie_Code == IECODE_RBUTTON)
		{
			struct Window *win = WindowUnderMouse();
			if (win && !(win->Flags & WFLG_BACKDROP) &&
			   (win->NextWindow || win->WScreen->FirstWindow != win))
			{
				if (clicktoback)
				{
					WindowToBack(win);
				}
			}
			else if (screencycle && IntuitionBase->FirstScreen->MouseY > IntuitionBase->FirstScreen->BarHeight)
			{
				ScreenToBack(IntuitionBase->FirstScreen);
				ActivateMouseWindow();
			}
		}
		else if (ev->ie_Code == IECODE_NOBUTTON && !(ev->ie_Qualifier & ALL_BUTTONS))
			apcount = TIMERCOUNT;	/* mouse-move */
	    Permit();
	}
}

/* close resources allocated for handler */
void
EndHandler()
{
	if (clickobj) DeleteCxObj(clickobj);
	FreeAudio();
	if (clicksigbit != -1) FreeSignal(clicksigbit);
	UnBlankScreen();	/* restores mouse too */
}

/* open resources needed for handler */
BOOL
InitHandler()
{
	if (((clicksigbit = AllocSignal(-1)) != -1) &&
	    (AllocAudio() == TRUE))
	{
		thistask = FindTask(NULL);      /* initialize liason structure */
		clicksigflag = 1 << clicksigbit;

		clickobj = CxCustom(Handler, 0L);
		AttachCxObj(broker, clickobj);
		return TRUE;
	}
	EndHandler();
	return FALSE;
}
