Milán Major

netscape-revival

1 branch
Code

lib/mac/VText/V_PowerPlant_Mods/LEventMachine.engine.cp

//	===========================================================================
//	LEventMachine.cp				©1995 Metrowerks Inc. All rights reserved.
//	===========================================================================

#ifdef PowerPlant_PCH
#include PowerPlant_PCH
#endif

#include	"LEventMachine.h"
#include	<UEventUtils.h>
#include	<UCursor.h>
#include	<PP_Messages.h>

#ifndef __ERRORS_
#include <Errors.h>
#endif

//	===========================================================================
/*
	NOTICE	!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
	
	If you try to understand this code make certain you read the separate 
	documentation. The documentation includes a state diagram for the state
	machine implemented by this event handler.  Knowledge of that state
	diagram makes this code much more understandable.
*/
//	===========================================================================

Boolean	LEventMachine::DoEvent(const EventRecord &inEvent)
/*
	Return value indicates whether event was completely
	handled and caller need take no further action.
	
	Note:
		The following should be appropriately set or
		prepared before this member is called:
		
			QuickDraw port
			pane or view Prepare'd or FocusDraw'ed.
	
	IGNORE return value for the time being.
*/
{
/*
	The complicating factor for an event dispatching routine
	(DoLiteralEvent) is that Mac event fields are only valid
	for given event types.  This routine stuffs the invalid fields
	with the correct information -- this way the event handler has real 
	information in its state instance variables.
	
*/
	EventRecord	theEvent = inEvent;
	
	UEventUtils::FleshOutEvent(
		(mEvtState != evs_Cold) && (mEvtRecord.modifiers & btnState),
		&theEvent);
	
	return DoLiteralEvent(theEvent);
}

Boolean	LEventMachine::DoLiteralEvent(const EventRecord &inEvent)
/*
	DoLiteralEvent does the real work of DoEvent.  DoLiteralEvent
	is provided as an additional entry point in the unlikely event
	you want to send an exact event without the event handler
	filling in the blanks that are normally present.
	
	You will likely never need to call DoLiteralEvent directly.
*/
{
/*
	Another complicating factor for an event dispatching routine
	is that a Mac event can actually be multiple events -- a mouse
	move and a mouse down.
*/
	EventRecord	theEvent = inEvent;
	Boolean		rval = false;
	Point		localMouse;
	Boolean		doMove = false;
	Boolean		doDown = false;
	Boolean		doUp = false;
	
	switch (theEvent.what) {

		case keyDown:
		case keyUp:
		case autoKey:
		case mouseDown:
		case mouseUp:
		case nullEvent:
			break;
		
		default:
			return false;
	}
	
	localMouse = theEvent.where;
	GlobalToLocal(&localMouse);
	
	//	Check startup state info...
	if (mEvtState == evs_Cold) {
		theEvent.modifiers = 0;
		mLastEvtRecord = mEvtRecord = theEvent;
		mEvtMouse = mLastEvtMouse = localMouse;
		mEvtState = mLastEvtState = evs_Idle;
		mEvtClickCount = 0;
		CheckCursor();
		return true;
	}
	
	Try_ {
	
		if (sActiveDragger && (sActiveDragger != this))
			return false;

		
		//	Boundary checking...
		switch (GetEvtState()) {
			case evs_Maybe:
			case evs_Drag:

				//	PowerPlant has an annoying habit of changing the cursor
				//	when over a pane that hasn't overridden AdjustCursorSelf.
				//	This isn't what we want for auto scrolling but...
				//
				//	This technique will at least flash the cursor to what
				//	it ought to be.
				UCursor::Restore();
				break;
			
			default:
				if (!PointInHandlerRgn(localMouse)) {
					if (mInBounds)
						OutOfBounds(localMouse);
				} else {
					if (!mInBounds) {
						InBounds(localMouse);
						doMove = true;
					}
				}
				break;
		}
	
		//	Find out what to do before it's done and the state info
		//	gets messed up.
		if (!EqualPt(localMouse, mEvtMouse))
			doMove = true;
		if ((theEvent.modifiers & btnState) && !(mEvtRecord.modifiers & btnState))
			doDown = true;
		if (!(theEvent.modifiers & btnState) && (mEvtRecord.modifiers & btnState))
			doUp = true;
				
		//	Always update mouse first...
		if (doMove) {
			UpdateLastEvt(theEvent, localMouse);
			MouseMove();
			rval = true;
		}

		//	Now a prioritized list of other event types...
		if (doDown && mInBounds) {							//	note mInBounds check
		
			//	mouseDown
			UpdateLastEvt(theEvent, localMouse);
			MouseDown();
			rval = true;
		
		} else if (doUp) {
	
			//	mouseUp
			UpdateLastEvt(theEvent, localMouse);
			MouseUp();
			rval = true;
		
		} else if (theEvent.what == keyDown) {
	
			//	KeyDown
			UpdateLastEvt(theEvent, localMouse);
			KeyDown();
			rval = true;
		
		} else if (theEvent.what == autoKey) {
	
			//	AutoKey (KeyDown)
			UpdateLastEvt(theEvent, localMouse);
			KeyDown();
			rval = true;
		
		} else if (theEvent.what == keyUp) {
	
			//	KeyUp
			UpdateLastEvt(theEvent, localMouse);
			KeyUp();
			rval = true;
		
		} else if (!doMove) {

			//	Idle
			UpdateLastEvt(theEvent, localMouse);
			Idle();
			rval = true;
		
		}

	} Catch_(err) {
		//	Make sure exceptions leave handler in a safe state
		Reset();
		Throw_(err);
	} EndCatch_;

	return rval;
}

void	LEventMachine::Track(void)
/*
	Track mouse action until the mouse button is up.
	This routine only returns when the mouse button is up.
	
	THIS METHOD IS NEEDED EXTREMELY RARELY...
	
	Avoid the traditional urge to code "track" type operations and
	just lest the event handler state machine take care of
	everything.  Letting the event handler state machine process
	events allows better "cooperative" multitasking.  You also wont
	have to worry if your doing the "right thing" immediately
	before and after your tracking code.
	
	But, for the few cases where a track operation is needed
	and for the paranoid... 
*/
{
	EventRecord		event;

	FocusDraw();

	while (true) {

		//	Stuff an event.
		GetMouse(&event.where);
		LocalToGlobal(&event.where);
		event.when = TickCount();
		event.modifiers = (Button()) ? btnState : 0;
		if (event.modifiers & btnState)
			event.what = nullEvent;	//	MouseMoved
		else
			event.what = mouseUp;
	
		//	The event isn't stuffed exactly correctly right now
		//	but DoEvent will fill in the rest...
		DoEvent(event);
		
		if ((event.modifiers & btnState) == 0)
			break;
		
		SystemTask();	//	Be cooperative		
	}
}

void	LEventMachine::Reset(void)
/*
	Subclasses may find it necessary to override and inherit this
	method.
	
	This method is called when an exception occurs during DoEvent
	and allows the handler to "reset" itself to a known or usable
	state.
	
	Code executed during Reset must not throw another exception
	UNLESS it is caught and handled by subclass::Reset.
*/
{
	mEvtState = evs_Cold;
	mMaybeDragDragged = false;
	mBDHasEntered = false;
	UCursor::Reset();
	
	if (sActiveDragger == this)
		sActiveDragger = NULL;
}

//	===========================================================================
//	State machine tables
#pragma mark --- State machine tables

EventStateT	LEventMachine::sNextState[] = {
//	evv_Idle		evv_Move		evv_Down		evv_Up			evv_KeyDown		evv_KeyUp
	evs_BIdle,		evs_BIdle,		evs_Maybe,		evs_Err,		evs_,			evs_,			//	evs_Idle
	evs_Idle,		evs_Idle,		evs_Maybe,		evs_Err,		evs_,			evs_,			//	evs_Count
	evs_Delayed,	evs_Drag,		evs_Err,		evs_Count,		evs_,			evs_,			//	evs_Maybe
	evs_,			evs_Drag,		evs_Err,		evs_Count,		evs_,			evs_,			//	evs_Delayed
	evs_,			evs_,			evs_Err,		evs_Idle,		evs_,			evs_,			//	evs_Drag
	evs_Idle,		evs_Idle,		evs_BDown,		evs_Err,		evs_,			evs_,			//	evs_BIdle
	evs_,			evs_BUp,		evs_Err,		evs_Idle,		evs_,			evs_,			//	evs_BDown
	evs_,			evs_BDown,		evs_Err,		evs_Idle,		evs_,			evs_			//	evs_BUp
};

EventConditionT LEventMachine::sConditions[] = {
//	evv_Idle		evv_Move		evv_Down		evv_Up			evv_KeyDown		evv_KeyUp
	evc_Button,		evc_Button,		evc_,			evc_,			evc_,			evc_,			//	evs_Idle
	evc_Tardy,		evc_Move,		evc_,			evc_,			evc_,			evc_,			//	evs_Count
	evc_Tardy,		evc_Move,		evc_,			evc_,			evc_,			evc_,			//	evs_Maybe
	evc_,			evc_Move,		evc_,			evc_,			evc_,			evc_,			//	evs_Delayed
	evc_,			evc_,			evc_,			evc_,			evc_,			evc_,			//	evs_Drag
	evc_NButton,	evc_NButton,	evc_,			evc_,			evc_,			evc_,			//	evs_BIdle
	evc_,			evc_Out,		evc_,			evc_,			evc_,			evc_,			//	evs_BDown
	evc_,			evc_In,			evc_,			evc_,			evc_,			evc_			//	evs_BUp
};

EventConditionT	LEventMachine::sActions[] = {
//	evv_Idle		evv_Move		evv_Down		evv_Up			evv_KeyDown		evv_KeyUp
	eva_CC,			eva_CC,			eva_Down,		eva_,			eva_,			eva_,			//	evs_Idle
	eva_Count_Zero,	eva_Count_Zero,	eva_Down,		eva_,			eva_,			eva_,			//	evs_Count
	eva_,			eva_DStart,		eva_,			eva_Up,			eva_,			eva_,			//	evs_Maybe
	eva_,			eva_DStart,		eva_,			eva_Up,			eva_,			eva_,			//	evs_Delayed
	eva_CBD,		eva_DMove,		eva_,			eva_DStop,		eva_,			eva_,			//	evs_Drag
	eva_CC,			eva_CC,			eva_BSet,		eva_,			eva_,			eva_,			//	evs_BIdle
	eva_,			eva_,			eva_,			eva_BClear,		eva_,			eva_,			//	evs_BDown
	eva_,			eva_,			eva_,			eva_BClear,		eva_,			eva_			//	evs_BUp
};

EventConditionT	LEventMachine::sNotes[] = {
//	evv_Idle		evv_Move		evv_Down		evv_Up			evv_KeyDown		evv_KeyUp
	evn_,			evn_,			evn_Down,		evn_,			evn_,			evn_,			//	evs_Idle
	evn_,			evn_,			evn_Down,		evn_,			evn_,			evn_,			//	evs_Count
	evn_Delayed,	evn_DStart,		evn_,			evn_Up,			evn_,			evn_,			//	evs_Maybe
	evn_,			evn_DStart,		evn_,			evn_Up,			evn_,			evn_,			//	evs_Delayed
	evn_,			evn_,			evn_,			evn_DStop,		evn_,			evn_,			//	evs_Drag
	evn_,			evn_,			evn_Down,		evn_,			evn_,			evn_,			//	evs_BIdle
	evn_,			evn_,			evn_,			evn_BUp,		evn_,			evn_,			//	evs_BDown
	evn_,			evn_,			evn_,			evn_,			evn_,			evn_			//	evs_BUp
};

EventConditionT	LEventMachine::sFallbacks[] = {
//	evv_Idle		evv_Move		evv_Down		evv_Up			evv_KeyDown		evv_KeyUp
	evf_,			evf_,			evf_,			evf_,			evf_,			evf_,			//	evs_Idle
	evf_,			evf_,			evf_,			evf_,			evf_,			evf_,			//	evs_Count
	evf_,			evf_,			evf_,			evf_,			evf_,			evf_,			//	evs_Maybe
	evf_,			evf_,			evf_,			evf_,			evf_,			evf_,			//	evs_Delayed
	evf_,			evf_,			evf_,			evf_,			evf_,			evf_,			//	evs_Drag
	evf_,			evf_,			evf_,			evf_,			evf_,			evf_,			//	evs_BIdle
	evf_,			evf_,			evf_,			evf_,			evf_,			evf_,			//	evs_BDown
	evf_,			evf_,			evf_,			evf_,			evf_,			evf_			//	evs_BUp
};

//	===========================================================================
//	Table driven state machine

void	LEventMachine::ProcessVerb(EventVerbT inVerb)
{
	EventStateT			oldState = GetEvtState(),
						newState = oldState;
	EventConditionT		condition = FindCondition(oldState, inVerb);
	EventConditionT		action;
	EventConditionT		note;
	EventConditionT		fallback;
	
	if (DoCondition(condition)) {

		newState = FindNextState(oldState, inVerb);
		SetEvtState(newState);

		action = FindAction(oldState, inVerb);
		DoAction(action);

		note = FindNote(oldState, inVerb);
		DoNote(note);

	}
	
	if (newState == oldState) {
	
		fallback = FindFallback(oldState, inVerb);
		DoFallback(fallback);
	
	}
		
}


Boolean	LEventMachine::DoCondition(EventConditionT inCondition)
{
	Boolean	rval = false;
	
	switch (inCondition) {

		//	Conditions
		case evc_:
			rval = true;
			break;

		case evc_Move:
			if (!PointIsClose(mEvtMouse, mEvtLastButtonWhere) || (!mInBounds))
				rval = true;
			break;
		
		case evc_Tardy:
			if (IsTardy())
				rval = true;
			break;
			
		
		//	Actions
		case eva_Count_Zero:
			mEvtClickCount = 0;
			CheckThings();
			CheckCursor();	//	note absent if
			break;
			
		case eva_Down:
			mEvtClickCount++;
			mMaybeDragDragged = false;
			CheckCursor();
			break;
		
		case eva_CC:
			if (CheckThings())
				CheckCursor();
			break;
		
		case eva_CBD:
			CheckBoundaryDrag();
			break;
			
		default:
			break;
	}
	
	return rval;
}

void	LEventMachine::DoAction(EventConditionT inAction)
{
	DoCondition(inAction);
}

void	LEventMachine::DoNote(EventConditionT inNote)
{
	switch (inNote) {
		case evn_:
			//	don't broadcast anything!
			break;
		
		default:
		{
			SEventHandlerNote	rec;
			
			rec.handler = this;
			rec.note = inNote;
			
			BroadcastMessage(msg_EventHandlerNote, &rec);
			break;
		}
	};
}

void	LEventMachine::DoFallback(EventConditionT inFallback)
{
	DoCondition(inFallback);
}

EventStateT	LEventMachine::GetEvtState(void)
{
	return mEvtState;
}

void	LEventMachine::SetEvtState(EventStateT inState)
{
	switch (inState) {

		case evs_Err:
			//	Impossible state transition.
			//
			//	Were you tracking a mouse down when the
			//	debugger switched in?  Then, when you
			//	continued in this app, was your mouse up?
			//	If so, that's why the state machine got
			//	confused.
			Throw_(paramErr);
			break;

		case evs_:
			//	no change -- leave as is
			break;
		
		default:
			if (inState < 0) {
				//	Once started, you can't set the state to an exceptional state.
				Throw_(paramErr);
			}
			mEvtState = inState;
			break;
	}
}

//	===========================================================================
//	Verbs

void	LEventMachine::Idle(void)
{
	ProcessVerb(evv_Idle);
}

void	LEventMachine::MouseMove(void)
{
	ProcessVerb(evv_Move);
}

void	LEventMachine::MouseDown(void)
{
	Assert_(sActiveDragger == NULL);
	sActiveDragger = this;
	
	ProcessVerb(evv_Down);
	
	mEvtLastButtonWhere = mEvtMouse;	
	mEvtLastButtonTime = mEvtRecord.when;
}

void	LEventMachine::MouseUp(void)
{
	sActiveDragger = NULL;
	
	ProcessVerb(evv_Up);
		
	mEvtLastButtonWhere = mEvtMouse;	
	mEvtLastButtonTime = mEvtRecord.when;
}

void	LEventMachine::KeyDown(void)
{
	ProcessVerb(evv_KeyDown);
}

void	LEventMachine::KeyUp(void)
{
	ProcessVerb(evv_KeyUp);
}