Milán Major

netscape-revival

1 branch
Code

lib/mac/VText/Core/User_Interface/VTSMProxy.cp

//	===========================================================================
//	VTSMProxy
//
//	Copyright 1996 ViviStar Consulting.
//	===========================================================================

#ifdef PowerPlant_PCH
#include PowerPlant_PCH
#endif

#include	"VTSMProxy.h"

#include	<VTextView.h>
#include	<LStyleSet.h>
#include	<LTextSelection.h>
#include	<LAETypingAction.h>
#include	<VTextHandler.h>
#include	<UFontsStyles.h>

#include	<UDrawingState.h>
#include	<UAppleEventsMgr.h>
#include	<UMemoryMgr.h>

#include	<Gestalt.h>

#pragma	warn_unusedarg off

//	===========================================================================
//	WTSMManager

Boolean				WTSMManager::sTSMPresent = false;
AEEventHandlerUPP	WTSMManager::sAEHandler = NewAEEventHandlerProc(AEHandlerTSM);
VTSMProxy*			WTSMManager::sCurrent = NULL;

void	WTSMManager::Initialize(void)
{
	OSErr	err = noErr;

	do {
		//	tsm present
		long	result;
		err = ::Gestalt(gestaltTSMgrVersion, &result);
		if (err)
			break;
		
		err = ::InitTSMAwareApplication();
		ThrowIfOSErr_(err);
		
		err = ::AEInstallEventHandler(kTextServiceClass, kUpdateActiveInputArea, sAEHandler, kUpdateActiveInputArea, false);
		ThrowIfOSErr_(err);
		err = ::AEInstallEventHandler(kTextServiceClass, kPos2Offset, sAEHandler, kPos2Offset, false);
		ThrowIfOSErr_(err);
		err = ::AEInstallEventHandler(kTextServiceClass, kOffset2Pos, sAEHandler, kOffset2Pos, false);
		ThrowIfOSErr_(err);
		
		sTSMPresent = true;
	} while (false);
}

void	WTSMManager::Quit(void)
{
	if (sTSMPresent) {
		OSErr	err = CloseTSMAwareApplication();
		Assert_(
			(err == noErr) ||
			(err == tsmDocumentOpenErr)	//	bug in system?
		);
	}
}

VInlineTSMProxy *	WTSMManager::MakeInlineTSMProxy(VTextView &inTextView)
{
	VInlineTSMProxy	*rval = NULL;

	if (sTSMPresent && inTextView.GetSelection() && inTextView.GetEventHandler()) {
		LTextEngine	*engine = inTextView.GetTextEngine();
		if (engine)
			rval = new VInlineTSMProxy(*engine, inTextView);
	}
	
	return rval;
}

VTSMTEProxy *	WTSMManager::MakeTSMTEProxy(TEHandle inTEH)
{
	VTSMTEProxy	*rval = NULL;
	
	Assert_(inTEH);
	if (inTEH) {
		ThrowIfNULL_(*inTEH);
		rval = new VTSMTEProxy(inTEH);
	}

	return rval;
}

pascal OSErr	WTSMManager::AEHandlerTSM(
	const AppleEvent	*inAppleEvent,
	AppleEvent			*outReply,
	Int32				inRefCon)
{
	OSErr	err = noErr;
	
	THz	oldZone = ::LMGetTheZone();	//	Apple bug #115424?
	::LMSetTheZone(LMGetApplZone());
	
	try {

		VInlineTSMProxy* proxy = dynamic_cast<VInlineTSMProxy*>(GetCurrent());
		ThrowIfNULL_(proxy);
		
		StHandleLocker	lock(inAppleEvent->dataHandle);
		LAESubDesc		appleEvent(*inAppleEvent);
		LAEStream		replyStream;
		
		ThrowIf_(((Int32)(void *)proxy) != appleEvent.KeyedItem(keyAETSMDocumentRefcon).ToInt32());
		
		replyStream.OpenRecord();
		
		switch(inRefCon) {

			case kUpdateActiveInputArea:
				proxy->AEUpdate(appleEvent, replyStream);
				break;

			case kPos2Offset:
				proxy->AEPos2Offset(appleEvent, replyStream);
				break;

			case kOffset2Pos:
				proxy->AEOffset2Pos(appleEvent, replyStream);
				break;
		}
		
		replyStream.CloseRecord();
		
		//	Transfer reply parameters to the real reply (hopefully MacOS 8 will have a way around this)
		//	ie, can simply say:
		//
		//		replyStream.Close(outReply);
		//
		StAEDescriptor	reply;
		replyStream.Close(reply);
		LAESubDesc		replySD(reply);
		AEKeyword		key;
		for (Int32 i = 1; i <= replySD.CountItems(); i++) {
			StAEDescriptor	parm;
			replySD.NthItem(i, &key).ToDesc(&parm.mDesc);
			err = ::AEPutParamDesc(outReply, key, &parm.mDesc);
			ThrowIfOSErr_(err);
		}		

	} catch (ExceptionCode inErr) {
		err = inErr;
	} catch (...) {
		err = paramErr;
	}

	::LMSetTheZone(oldZone);	//	Apple bug #115424?
	
	return err;
}

//	===========================================================================

VTSMDoEvent::VTSMDoEvent(
	MessageT		inMessage,
	Boolean			inExecuteHost)
:	LAttachment(inMessage, inExecuteHost)
{
}

void	VTSMDoEvent::ExecuteSelf(
	MessageT		inMessage,
	void			*ioParam)
{
	Boolean		executeHost = true;
	VTSMProxy	*proxy = WTSMManager::GetCurrent();
	
	if (proxy) {
		ThrowIfNULL_(ioParam);
		EventRecord	*event = (EventRecord *)ioParam;
		
		if (::TSMEvent(event)) {
			executeHost = false;
		}
	}
	
	SetExecuteHost(executeHost);
}

//	===========================================================================
//	VTSMProxy (base class)

VTSMProxy::VTSMProxy()
{
	mTSMDocID = 0;
	mDocIsActive = false;
}

VTSMProxy::~VTSMProxy()
{
	Assert_(!mDocIsActive);
	Assert_(mTSMDocID == 0);	//	derived class must dispose of document since the derived class created the doc
	if (WTSMManager::GetCurrent() == this)
		WTSMManager::SetCurrent(NULL);
}
	
void	VTSMProxy::Activate(void)
{
	if (!mDocIsActive) {
		OSErr	err;

		#if DEBUG
			//	check to see if a bug in TSM will be encountered
			ProcessSerialNumber	psn,
								csn;
			err = GetCurrentProcess(&psn);
			ThrowIfOSErr_(err);
			err = GetFrontProcess(&csn);
			ThrowIfOSErr_(err);
			Assert_((psn.highLongOfPSN == csn.highLongOfPSN) && (psn.lowLongOfPSN == csn.lowLongOfPSN));
		#endif

		if (mTSMDocID)
			err = ::ActivateTSMDocument(mTSMDocID);
		else
			err = ::UseInputWindow(NULL, true);
		ThrowIfOSErr_(err);
		
		mDocIsActive = true;
	}
		
	WTSMManager::SetCurrent(this);
}

void	VTSMProxy::Deactivate(void)
{
	FlushInput();

	if (mDocIsActive) {
		WTSMManager::SetCurrent(NULL);
		mDocIsActive = false;

		OSErr	err = ::DeactivateTSMDocument(mTSMDocID);
		if (err == tsmDocNotActiveErr) {
			//	Assert_(false);	//	this just seems to happen too much -- it is okay if it happens
		} else {
			ThrowIfOSErr_(err);
		}
	}
}

void	VTSMProxy::FlushInput(void)
{
	OSErr	err = ::FixTSMDocument(mTSMDocID);
	ThrowIfOSErr_(err);
}

//	===========================================================================
//	VInlineTSMProxy

VInlineTSMProxy::VInlineTSMProxy(
	LTextEngine			&inTextEngine,
	VTextView			&inTextView)
:	mTextEngine(inTextEngine)
,	mTextView(inTextView)
{
	OSType	supportedType = kTextService;
	OSErr	err = ::NewTSMDocument(1, &supportedType, &mTSMDocID, (long)(void *)this);
	ThrowIfOSErr_(err);
}

VInlineTSMProxy::~VInlineTSMProxy()
{
	Deactivate();	//	for a bug in TSM.  See TE27

	OSErr	err = noErr;
	if (mTSMDocID)
		err = ::DeleteTSMDocument(mTSMDocID);
	mTSMDocID = 0;
//	Assert_(err == noErr);
}
	
void	VInlineTSMProxy::FlushInput(void)
{
	if (mActiveRange.Length()) {
		inherited::FlushInput();
	}
}

void	VInlineTSMProxy::DrawHilite(void)
{
	//	owning text view is already focused
	TextRangeT	lineRange;
	DescType	partFound;
	
	vector<RangeHiliteRec>::iterator	ip = mHilites.begin(),
										ie = mHilites.end();
	while (ip != ie) {
		
		TextRangeT	hiliteRange = (*ip).range,
					range = hiliteRange;

		if (lineRange.Undefined()) {
			Assert_(ip == mHilites.begin());
			lineRange = hiliteRange;
			lineRange.Front();
		}
		if (!lineRange.Length()) {
			partFound = mTextEngine.FindPart(LTextEngine::sTextAll, &lineRange, cLine);
			ThrowIf_(partFound == typeNull);
			if (!lineRange.Length()) {
				Assert_(false);
				break;
			}
		}
		
		range.Crop(lineRange);
		if (hiliteRange.End() > lineRange.End()) {
			DrawRangeHilite(range, (*ip).hilite);
			lineRange = range;
			lineRange.After();
			continue;
		}
		
		DrawRangeHilite(range, (*ip).hilite);

		ip++;
	}
}

void	VInlineTSMProxy::DrawRangeHilite(
	const TextRangeT	&inRange,
	Int16				inHiliteStyle)
/*
	inRange is assumed not to cross line boundaries
*/
{
	if (inRange.Length()) {
		SPoint32	i1, i2;
		Point		p1, p2;
		
		mTextEngine.Range2Image(inRange, true, &i1);
		mTextEngine.Range2Image(inRange, false, &i2);
		mTextView.ImageToLocalPoint(i1, p1);
		mTextView.ImageToLocalPoint(i2, p2);
		Range32T	span;
		span.SetRange(p1.h, p2.h);
		
		StColorPenState	saveQDState;
		
		if ((inHiliteStyle == kRawText) || (inHiliteStyle == kSelectedRawText)) 
			::PenPat(&UQDGlobals::GetQDGlobals()->gray);
		else
			::PenPat(&UQDGlobals::GetQDGlobals()->black);
		
		p1.h += 1;
		p2.h -= 2;
		p1.v -= 2;
		p2.v -= 2;
		if ((inHiliteStyle == kSelectedRawText) || (inHiliteStyle == kSelectedConvertedText)) {
			PenSize(1, 2);
		} else {
			PenSize(1, 1);
		}
		MoveTo(p1.h, p1.v);
		LineTo(p2.h, p2.v);
	}
}

void	VInlineTSMProxy::AEUpdate(
	const LAESubDesc	&inAppleEvent,
	LAEStream			&inStream)
{
	LTextView::OutOfFocus(&mTextView);

	//	input
	LAESubDesc	textSD(inAppleEvent.KeyedItem(keyAETheData), typeChar);

	ScriptLanguageRecord	writingCode;
	inAppleEvent.KeyedItem(keyAETSMScriptTag).ToPtr(typeIntlWritingCode, &writingCode, sizeof(writingCode));

	Int32	fixLength = inAppleEvent.KeyedItem(keyAEFixLength).ToInt32();
	if (fixLength < 0)
		fixLength = textSD.GetDataLength();

	//	process
	LTextSelection&	selection(dynamic_cast<LTextSelection&>(*mTextView.GetSelection()));
	VTextHandler&	handler(dynamic_cast<VTextHandler&>(*(mTextView.GetEventHandler())));
	
	if (selection.GetCurrentStyle()->GetScript() != writingCode.fScript)
		Throw_(paramErr);
		
//-?	handler.FollowKeyScript();	//	if key script was changed
	LAETypingAction	*typing = selection.GetTypingAction();
	if (!typing || typing->NewTypingLocation())	{
		typing = handler.NewTypingAction();
		handler.PostAction(typing);
		mActiveRange = selection.GetSelectionRange();
	} else {
		if (!mActiveRange.Length())
			mActiveRange = selection.GetSelectionRange();
	}

	StRecalculator	change(&mTextEngine);
	TextRangeT		oldRange = mActiveRange,
					newRange = mActiveRange;

	typing->KeyStreamRememberPrepend(oldRange);	
	newRange.SetLength(textSD.GetDataLength());
	mTextEngine.TextReplaceByPtr(mActiveRange, (const Ptr)textSD.GetDataPtr(), newRange.Length());
	mTextEngine.SetStyleSet(newRange, selection.GetCurrentStyle());
	typing->KeyStreamRememberNew(newRange);
	
	mActiveRange = TextRangeT(oldRange.Start() + fixLength, newRange.End());
	TextRangeT	newSelRange = mActiveRange;
	newSelRange.Back();
	
	//	input (optional)
	mHilites.erase(mHilites.begin(), mHilites.end());
	if (inAppleEvent.KeyExists(keyAEHiliteRange)) {
		LAESubDesc	hiliteSD(inAppleEvent.KeyedItem(keyAEHiliteRange), typeTextRangeArray);
		TextRangeArrayPtr	p = (TextRangeArrayPtr)hiliteSD.GetDataPtr();
		mHilites.reserve(p->fNumOfRanges);
		for (Int32 i = 0; i < p->fNumOfRanges; i++) {
			RangeHiliteRec	record;
			record.range = TextRangeT(p->fRange[i].fStart, p->fRange[i].fEnd);
			record.hilite = p->fRange[i].fHiliteStyle;
			if (record.hilite < 0)
				record.hilite = -record.hilite;
			if (!record.range.Length()) {
				newSelRange = record.range;
				newSelRange.SetStart(mActiveRange.Start() + newSelRange.Start());
				Assert_(mActiveRange.WeakContains(newSelRange));
				continue;
			}
			record.range.SetStart(record.range.Start() + oldRange.Start());
			mHilites.push_back(record);
		}
	}
	selection.SetSelectionRange(newSelRange);
	
	typing->UpdateTypingLocation();
	
/*?
	inAppleEvent.KeyedItem(keyAEUpdateRange);
	typeTextRangeArray
*/	

	if (inAppleEvent.KeyExists(keyAEPinRange)) {
		TextRange	pinRange;
		inAppleEvent.KeyedItem(keyAEPinRange).ToPtr(typeTextRange, &pinRange, sizeof(pinRange));
		TextRangeT	scrollRange(pinRange.fStart + oldRange.Start(), pinRange.fEnd + oldRange.Start());
		mTextEngine.Recalc();	//	to get coordinates correct for ScrollToRange
		mTextEngine.ScrollToRange(scrollRange);
	}

/*?
	inAppleEvent.KeyedItem(keyAEClauseOffsets);
	typeOffsetArray
*/
	
	//	output
	LTextView::OutOfFocus(&mTextView);
}

//	so which is it?
#define	keyAELeadingEdge	keyAELeftSide

void	VInlineTSMProxy::AEPos2Offset(
	const LAESubDesc	&inAppleEvent,
	LAEStream			&inStream) const
{
	OSErr	err = noErr;
	
	//	input
	Point	where;
	Boolean	dragging = false;

	inAppleEvent.KeyedItem(keyAECurrentPoint).ToPtr(typeQDPoint, &where, sizeof(where));

	LAESubDesc	sd = inAppleEvent.KeyedItem(keyAEDragging);
	if (sd.GetType() != typeNull)	//	keyAEdragging is optional
		dragging = sd.ToBoolean();
	
	//	process
	LTextView::OutOfFocus(&mTextView);
	mTextView.FocusDraw();	//	for GlobalToLocal
	::GlobalToLocal(&where);
	LTextView::OutOfFocus(&mTextView);
	SPoint32	where32;
	mTextView.LocalToImagePoint(where, where32);
	Boolean		leadingEdge;
	TextRangeT	range;
	mTextEngine.Image2Range(where32, &leadingEdge, &range);
	if (dragging)
		range.Crop(mActiveRange);
		
	//	output
	inStream.WriteKey(keyAEOffset);
	Int32	offset = range.Start();
	offset -= mActiveRange.Start();
	inStream.WriteDesc(typeLongInteger, &offset, sizeof(offset));
	
	inStream.WriteKey(keyAERegionClass);
	short	aShort = kTSMOutsideOfBody;
	
	SDimension32 size;
	mTextEngine.GetImageSize(&size);
	
	if ((0 <= where32.h) && (where32.h < size.width) && (0 <= where32.v) && (where.v < size.height))
		{
		if (mActiveRange.Contains(range))
			aShort = kTSMInsideOfActiveInputArea;
		else
			aShort = kTSMInsideOfBody;
		}

	inStream.WriteDesc(typeShortInteger, &aShort, sizeof(aShort));

	//	output (optional)
	inStream.WriteKey(keyAELeadingEdge);
	char	aChar = leadingEdge;
	inStream.WriteDesc(typeBoolean, &aChar, sizeof(aChar));
}

void	VInlineTSMProxy::AEOffset2Pos(
	const LAESubDesc	&inAppleEvent,
	LAEStream			&inStream) const
{
	OSErr	err = noErr;
	
	//	input
	Int32	offset = inAppleEvent.KeyedItem(keyAEOffset).ToInt32();
	offset += mActiveRange.Start();
	
	TextRangeT	range(offset);
	range.SetInsertion(rangeCode_After);
	SPoint32	where32;
	mTextEngine.Range2Image(range, true, &where32);
	Point		where;
	mTextView.ImageToLocalPoint(where32, where);
	LTextView::OutOfFocus(&mTextView);
	mTextView.FocusDraw();	//	for LocalToGlobal
	::LocalToGlobal(&where);
	
	//	output
	inStream.WriteKey(keyAEPoint);
	inStream.WriteDesc(typeQDPoint, &where, sizeof(where));
	
	//	output (optional)
	LStyleSet	*style = mTextEngine.GetStyleSet(range);
	Int32		aLong;
	Fixed		aFixed;
	Int16		aShort = style->GetHeight();
	{
		static Str255	fName;
		StAEDescriptor	sizeD,
						fontD;
		style->GetAEProperty(pPointSize, UAppleEventsMgr::sAnyType, sizeD);
		style->GetAEProperty(pFont, UAppleEventsMgr::sAnyType, fontD);
		LAESubDesc	sizeSD(sizeD),
					fontSD(fontD);
		aFixed = WFixed::Long2FixedT(sizeSD.ToInt32()).ToFixed();
		fontSD.ToPString(fName);
		aLong = UFontsStyles::GetFontId(fName);
	}
	LTextView::OutOfFocus(&mTextView);
	
	inStream.WriteKey(keyAETSMTextFont);
	inStream.WriteDesc(typeLongInteger, &aLong, sizeof(aLong));

	inStream.WriteKey(keyAETSMTextPointSize);
	inStream.WriteDesc(typeFixed, &aFixed, sizeof(aFixed));

	inStream.WriteKey(keyAETextLineHeight);
	inStream.WriteDesc(typeShortInteger, &aShort, sizeof(aShort));

	aShort = style->GetAscent();
	inStream.WriteKey(keyAETextLineAscent);
	inStream.WriteDesc(typeShortInteger, &aShort, sizeof(aShort));

/*?
	inStream.WriteKey(keyAEAngle)
	typeFixed
	...
*/
}

//	===========================================================================
//	VTSMTEProxy

TSMTEPreUpdateUPP	VTSMTEProxy::sPreUpdateUPP = NewTSMTEPreUpdateProc(VTSMTEProxy::PreUpdate);

VTSMTEProxy::VTSMTEProxy(TEHandle inTEHandle)
{
	ThrowIfNULL_(sPreUpdateUPP);

	mTSMTEHandle = NULL;

	OSErr theErr;
	theErr = ::Gestalt(gestaltTSMTEVersion, &mVersion);
    ThrowIfOSErr_(theErr);		

	OSType theType = kTSMTEInterfaceType;
	theErr = ::NewTSMDocument(1, &theType, &mTSMDocID, (long) &mTSMTEHandle);
	ThrowIfOSErr_(theErr);

	if (!mTSMTEHandle && mTSMDocID)
		{
		::DeleteTSMDocument(mTSMDocID);
		Throw_(paramErr);
		}
	
	(*mTSMTEHandle)->textH = inTEHandle;
	(*mTSMTEHandle)->preUpdateProc = sPreUpdateUPP;
	(*mTSMTEHandle)->postUpdateProc = NULL;
	(*mTSMTEHandle)->updateFlag = kTSMTEAutoScroll;
	(*mTSMTEHandle)->refCon = (Int32)this;
}

VTSMTEProxy::~VTSMTEProxy()
{
//? FIX ME!!! do we need to dispose of mTSMTEHandle????

	// Deactivate can throw, but we dont care at this point
	try
		{
		Deactivate();	//	for a bug in TSM.  See TE27

		OSErr	err = noErr;
		if (mTSMDocID)
			err = ::DeleteTSMDocument(mTSMDocID);
		mTSMDocID = 0;
	//	Assert_(err == noErr);
		}
	catch (ExceptionCode inErr)
		{
		// no need to propogate the error here	
		}
}

pascal void VTSMTEProxy::PreUpdate(
	TEHandle 	inTEHandle,
	Int32		inRefCon)
{
	VTSMTEProxy* theProxy = (VTSMTEProxy*)inRefCon;
	Assert_(theProxy != NULL);
	
	if (theProxy->mVersion == gestaltTSMTE1)
		{
		ScriptCode keyboardScript = ::GetScriptManagerVariable(smKeyScript);
		TEHandle theTEH = theProxy->mTEH;
		
		TextStyle theStyle;
		Int16 theMode = doFont;
		if (!(::TEContinuousStyle(&theMode, &theStyle, theTEH) &&
					::FontToScript(theStyle.tsFont) == keyboardScript))
			{
			theStyle.tsFont = ::GetScriptVariable(keyboardScript, smScriptAppFond);
			::TESetStyle(doFont, &theStyle, false, theTEH);
			}
		}
}