Milán Major

netscape-revival

1 branch
Code

lib/mac/VText/V_PowerPlant_Mods/LTextView.cp

//	===========================================================================
//	LTextView.cp					©1994 Metrowerks Inc. All rights reserved.
//	===========================================================================

#ifdef PowerPlant_PCH
#include PowerPlant_PCH
#endif

//	#define		INCLUDE_TX
//	#define	DEBUGRECTS	1
//	#define	DEBUGRECTS	2

#include	"LTextView.h"
#include	<LTextEngine.h>
#include	<LTextModel.h>
#include	<LTextElemAEOM.h>
#include	<LTextSelection.h>
#include	<LTextEditHandler.h>
#include	<LAETypingAction.h>
#include	<LListIterator.h>
#include	<PP_Messages.h>
#include	<LStream.h>
#include	<UTextTraits.h>
#include	<UDrawingState.h>
#include	<UDrawingUtils.h>
#include	<UCursor.h>
#include	<UGWorld.h>
#include	<StClasses.h>

#ifndef		__RESOURCES__
#include	<Resources.h>
#endif

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

#pragma	warn_unusedarg off

const Int16		Unwrapped_Width	= 1000;

#define	IsActiveView()	(IsOnDuty() || mHasActiveTextView)

//	===========================================================================
//	Maintenance

/*-
LTextView*	LTextView::CreateTextViewStream(LStream	*inStream)
{
	return new LTextView(inStream);
}
*/

LTextView::LTextView()
{
	mTextModel = NULL;
	mTextEngine = NULL;
	mTextSelection = NULL;
	mHasActiveTextView = false;

	mOldSelectionIsCaret = false;
	mCaretOn = false;
	mCaretTime = TickCount();
	
	mPrintRange = LTextEngine::sTextAll;
	mImageWidthFollowsFrame = false;
}

LTextView::LTextView(LStream	*inStream)
:	LSelectHandlerView(inStream)
{
	mTextModel = NULL;
	mTextEngine = NULL;
	mTextSelection = NULL;
	mHasActiveTextView = false;
	
	mOldSelectionIsCaret = false;
	mCaretOn = false;
	mCaretTime = TickCount();

	mPrintRange = LTextEngine::sTextAll;

#ifndef	PP_No_ObjectStreaming
	SetTextModel(ReadStreamable<LTextModel *>()(*inStream));
	SetTextEngine(ReadStreamable<LTextEngine *>()(*inStream));
#else
	Int32	dummy;
	
	*inStream >> dummy;
	Assert_(dummy == typeNull);
	
	*inStream >> dummy;
	Assert_(dummy == typeNull);
#endif

//	*inStream >> mImageWidthFollowsFrame;
mImageWidthFollowsFrame = true;
}

LTextView::LTextView(LStream *inStream, Boolean inOldStyle)
:	LSelectHandlerView(inStream, inOldStyle)
//	TEMPORARY Constructor
{
	mTextModel = NULL;
	mTextEngine = NULL;
	mHasActiveTextView = false;

	mOldSelectionIsCaret = false;
	mCaretOn = false;
	mCaretTime = TickCount();
	
	mPrintRange = LTextEngine::sTextAll;
}

void	LTextView::FinishCreateSelf(void)
{
	inheritView::FinishCreateSelf();
	
	SetSelection(mSelection);	//	update mTextSelection (object type isn't
								//	known during stream constructor).
}

LTextView::~LTextView()
{
	PostAnAction(NULL);
	
	SetTextEngine(NULL);
	SetTextModel(NULL);
}

//	===========================================================================
//	Text object access

LTextEngine *	LTextView::GetTextEngine(void)
{
	return mTextEngine;
}

void	LTextView::SetTextEngine(LTextEngine *inTextEngine)
{
	if (mTextEngine != inTextEngine) {

		if (mTextEngine) {
			mTextEngine->RemoveListener(this);
			if (mTextEngine->GetHandlerView() == this) {
				mTextEngine->SetHandlerView(NULL);
				OutOfFocus(this);
			}
		}
	
		ReplaceSharable(mTextEngine, inTextEngine, this);
		
		if (mTextEngine)
			mTextEngine->AddListener(this);
	
		FixImage();
		FixView();
	}
}

LTextModel *	LTextView::GetTextModel(void)
{
	return mTextModel;
}

void	LTextView::SetTextModel(LTextModel *inTextModel)
{
	ReplaceSharable(mTextModel, inTextModel, this);
}

LTextSelection*	LTextView::GetTextSelection(void)
{
	return mTextSelection;
}

void	LTextView::SetSelection(LSelection *inSelection)
{
	inherited::SetSelection(inSelection);
	
	mTextSelection = dynamic_cast<LTextSelection *>(mSelection);
	
	ThrowIf_(mTextSelection != mSelection);
}

Boolean	LTextView::GetImageWidthFollowsFrame(void) const
{
	return mImageWidthFollowsFrame;
}

void	LTextView::SetImageWidthFollowsFrame(Boolean inFollows)
{
	mImageWidthFollowsFrame = inFollows;
	OutOfFocus(this);
}

//	===========================================================================
//	Implementation

void	LTextView::SpendTime(const EventRecord &inMacEvent)
{
	if (!mTextEngine)
		return;

	LHandlerView	*handlerView = mTextEngine->GetHandlerView();

	Try_ {	//	If the handler view gets changed (through a FocusDraw)... boom

		LSelectHandlerView::SpendTime(inMacEvent);
		
		if (IsOnDuty()) {
			FocusDraw();
			CaretMaintenance();
		}

		mTextEngine->SetHandlerView(handlerView);
		OutOfFocus(this);
	} Catch_(inErr) {
		mTextEngine->SetHandlerView(handlerView);
		OutOfFocus(this);
		Throw_(inErr);
	} EndCatch_;
}

void	LTextView::ListenToMessage(MessageT inMessage, void *ioParam)
{
	LHandlerView	*handlerView = NULL;
	
	if ((inMessage == msg_BroadcasterDied) && (ioParam == mTextEngine)) {
		mTextEngine = NULL;
		return;
	}

	if (mTextEngine)
		handlerView = mTextEngine->GetHandlerView();

	Try_ {	//	If the handler view gets changed (through a FocusDraw)... boom

		switch (inMessage) {
	
			case msg_ModelScopeChanged:
			{
				FocusDraw();
				if (mTextEngine) {
					SDimension32	size,
									oldSize;
					mTextEngine->GetImageSize(&size);
					GetImageSize(oldSize);
					inheritView::ResizeImageBy(size.width - oldSize.width, size.height - oldSize.height, true);
				}
				break;
			}
	
			case msg_ModelAboutToChange:
			{
				FocusDraw();
				CaretOff();
				mOldSelectionChanged = false;
				break;
			}
				
			case msg_ModelChanged:
			{
				FocusDraw();
				if (mOldSelectionChanged) {
					Assert_(mTextEngine && (mTextEngine->GetNestingLevel() == 1));
					if (mOldSelectionIsSubstantive != (mSelection && mSelection->IsSubstantive()))
						SetUpdateCommandStatus(true);
			
					FocusDraw();
					MutateSelection(IsOnDuty());
					CheckSelectionScroll();
				}
				CaretOn();
				break;
			}
	
			case msg_InvalidateImage:
			{
				FocusDraw();
				Refresh();
				break;
			}
			
			case msg_UpdateImage:
			{
				if (!mTextEngine)
					return;

				ThrowIfNULL_(ioParam);
				TextUpdateRecordT *update = (TextUpdateRecordT *)ioParam;
				UpdateTextImage(update);
				break;
			}
			
			//	for multiview...
			case msg_SelectionAboutToChange:
			{
				mOldSelectionIsSubstantive = mSelection && mSelection->IsSubstantive();
				
				FocusDraw();
				if (mTextEngine && (mTextEngine->GetNestingLevel() == 0)) {
					CaretOff();
					inherited::ListenToMessage(inMessage, ioParam);
				}
				break;
			}

			case msg_SelectionChanged:
			{
				FocusDraw();
				if (mTextEngine && (mTextEngine->GetNestingLevel() == 0)) {
					
					CheckSelectionScroll();

					//	inherited::ListenToMessage(inMessage, ioParam);
					//
					//	Not!!!
					//
					//	The inherited method would always call SetUpdateCommandStatus(true)
					//	which would result in UpdateMenus being called for every typing
					//	event.  This would slow down typing drastically.  Instead,
					//	SetUpdateCommandStatus is called only when mSelection->IsSubstantive
					//	changes.
					{
						if (mOldSelectionIsSubstantive != (mSelection && mSelection->IsSubstantive()))
							SetUpdateCommandStatus(true);
				
						FocusDraw();
						MutateSelection(IsOnDuty());
					}
					CaretOn();
				} else {
					mOldSelectionChanged = true;
				}
				break;
			}

			default:
			{
				inherited::ListenToMessage(inMessage, ioParam);
				break;
			}
		}

		if (mTextEngine)
			mTextEngine->SetHandlerView(handlerView);
		OutOfFocus(this);
		if (handlerView)
			handlerView->FocusDraw();
	} Catch_(inErr) {
		if (mTextEngine)
			mTextEngine->SetHandlerView(handlerView);
		OutOfFocus(this);
		if (handlerView)
			handlerView->FocusDraw();
		Throw_(inErr);
	} EndCatch_;
}

void	LTextView::CheckSelectionScroll(void)
{
	if ((mOnDuty == triState_On) || (mOnDuty == triState_Latent)) {
		if (mSelection && mTextEngine) {
			TextRangeT	range = ((LTextSelection*)mSelection)->GetSelectionRange();
			mTextEngine->ScrollToRange(range);
		}
	}
}

Boolean	LTextView::ObeyCommand(
	CommandT	inCommand,
	void*		ioParam)
{
	Boolean				cmdHandled = true;
	
	switch (inCommand) {
	
		case cmd_SelectAll:
		{
			if (mSelection) {
				LTextElemAEOM 	*newSelect = NULL;
				if (mTextModel) {
					newSelect = mTextModel->MakeNewElem(LTextEngine::sTextAll, cChar);
				}
				mSelection->SelectSimple(newSelect);
			}
			cmdHandled = true;
			break;
		}

		default:
			cmdHandled = LSelectHandlerView::ObeyCommand(inCommand, ioParam);
			break;
	}
	
	return cmdHandled;
}

void	LTextView::FindCommandStatus(
	CommandT	inCommand,
	Boolean		&outEnabled,
	Boolean		&outUsesMark,
	Char16		&outMark,
	Str255		outName)
{
	switch (inCommand) {
	
		case cmd_SelectAll:
			outUsesMark = false;
			if (mSelection)
				outEnabled = true;
			break;
			
		default:
			LSelectHandlerView::FindCommandStatus(inCommand, outEnabled,
									outUsesMark, outMark, outName);
			switch (inCommand) {
				case cmd_Clear:
				case cmd_Cut:
				case cmd_Paste:
					if (!mTextEngine || !(mTextEngine->GetAttributes() & textEngineAttr_Editable))
						outEnabled = false;
					break;
			}
			break;
	}
}

LSelectableItem *	LTextView::OverItem(Point inWhere)
{
	LSelectableItem	*rval = NULL;
	
	if (mTextEngine && mTextModel && mEventHandler) {
		FocusDraw();
		
		//	Find corresponding text offset
		TextRangeT		range;
		Boolean			leadingEdge;
		SPoint32		where;
		LocalToImagePoint(inWhere, where);
		mTextEngine->Image2Range(where, &leadingEdge, &range);
		if (range.Length()) {
			if (leadingEdge)
				range.Front();
			else
				range.Back();
		}
		
		//	Determine part type to find
		DescType		findType = cChar;
		LTextElemAEOM	*oldElem = NULL;
		rval = inherited::OverItem(inWhere);
		if (rval) {									//	inWhere is guaranteed to hit rval
//			member(rval, LTextElemAEOM);
			oldElem = (LTextElemAEOM *)rval;
			findType = oldElem->GetModelKind();	//	in case count > 4
		}
		switch (mEventHandler->GetClickCount()) {
			case 0:
			case 1:
				findType = cChar;
				break;
			case 2:
				findType = cWord;
				break;
			case 3:
				findType = cLine;
				break;
			case 4:
				findType = cParagraph;
				break;
		}
		
		//	Return old equivalent elem or make a new elem
		TextRangeT	foundRange = range;
		DescType	partFound = cChar;
		if (findType != cChar) {
			DescType type = mTextEngine->FindPart(LTextEngine::sTextAll, &foundRange, findType);
			if (type != typeNull)
				partFound = type;
		}
		if (!oldElem || (oldElem->GetRange() != foundRange))
			rval = mTextModel->MakeNewElem(foundRange, partFound);
	}
		
	return rval;
}

LSelectableItem *	LTextView::OverReceiver(Point inWhere)
{
	LSelectableItem	*rval = NULL;
	
	do {
		if (mEventHandler)
			rval = mEventHandler->OverReceiverSelf(inWhere);
		
		if (rval)
			break;
		
		if (!mTextEngine || !mTextModel || !mEventHandler)
			break;
			
		FocusDraw();
	
		//	Find corresponding text offset
		TextRangeT		range;
		Boolean			leadingEdge;
		SPoint32		where;
		LocalToImagePoint(inWhere, where);
		mTextEngine->Image2Range(where, &leadingEdge, &range);
		if (range.Length()) {
			if (leadingEdge)
				range.Front();
			else
				range.Back();
		}
		rval = mTextModel->MakeNewElem(range, cChar);
	} while (false);
	
	return rval;
}

void	LTextView::NoteOverNewThing(LManipulator *inThing)
{
	if (inThing) {
		switch(inThing->ItemType()) {

			case kManipulator:
				UCursor::Reset();	//	?
				break;

			case kSelection:
				if (mEventHandler && ((LDataDragEventHandler *)mEventHandler)->
						GetStartsDataDrags()) {
					LSelectHandlerView::NoteOverNewThing(inThing);	//	Ie. data drag "hand"
					break;
				} else {
					//	fall through
				}

			case kSelectableItem:
				UCursor::Tick(cu_IBeam);
				break;

		}
	} else {
		UCursor::Reset();
	}
}

Boolean	LTextView::FocusDraw(void)
{
	Boolean			rval;
	
	rval = inheritView::FocusDraw();
	
	if (mTextEngine == NULL)
		return rval;
	
	mTextEngine->SetHandlerView(this);
	mTextEngine->SetPort(UQDGlobals::GetCurrentPort());
	FixView();

	return rval;
}

void	LTextView::BeTarget(void)
{
	Assert_(!mHasActiveTextView);
	
	FocusDraw();
	
	LSelectHandlerView::BeTarget();
	
	mHasActiveTextView = true;
	
	CaretOn();
	
	if (mTextModel)
		LModelObject::DoAESwitchTellTarget(mTextModel);
}

void	LTextView::DontBeTarget(void)
{
	Assert_(mHasActiveTextView);
	
	FocusDraw();

	mHasActiveTextView = false;

	CaretOff();
	
	LSelectHandlerView::DontBeTarget();	
}

void	LTextView::SubtractSelfErasingAreas(RgnHandle inSourceRgn)
{
	StRegion	myRgn;
	Rect		r;
	
	if (CalcPortFrameRect(r)) {
		RectRgn(myRgn, &r);
		DiffRgn(inSourceRgn, myRgn, inSourceRgn);
	}

//	NO!	This pane's entire region was already subtracted above!
//
//	inherited::SubtractSelfErasingAreas(inSourceRgn);
//
//	NO!
}

void	LTextView::DrawSelf(void)
{
StProfile	profileMe;

	if (mTextEngine) {
		SPoint32		ul,
						lr;
		SDimension32	size;
		
		GrafPtr			port = UQDGlobals::GetCurrentPort();
		ThrowIfNULL_(port);
		
		Rect			r;
		SectRect( &(*(port->visRgn))->rgnBBox, &(*(port->clipRgn))->rgnBBox, &r);
		LocalToImagePoint(topLeft(r), ul);
		LocalToImagePoint(botRight(r), lr);
		size.width = lr.h - ul.h;
		size.height = lr.v - ul.v;
		
		mTextEngine->DrawArea(ul, size);
	} else {
		Rect	r;
	
		if (CalcLocalFrameRect(r))
			EraseRect(&r);	
	}

	#ifdef	DEBUGRECTS
		DrawDebugRects();
	#endif
	
	LSelectHandlerView::DrawSelf();
}

void	LTextView::DrawSelfSelection(void)
{
	if (mOldSelectionIsCaret) {
		if (mCaretOn)
			CaretUpdate();
	} else {
		inherited::DrawSelfSelection();
	}
}

void	LTextView::CaretUpdate(void)
{
	if (mOldSelectionIsCaret) {
		if (IsActiveView()) {
			StColorPenState	savePen;
		
			PenPat(&UQDGlobals::GetQDGlobals()->black);
			PenMode(srcXor);
			InvertRgn(mOldSelectionRgn.mRgn);
		}
	}
}

void	LTextView::CaretMaintenance(void)
{
	if (mOldSelectionIsCaret) {
		if ((TickCount() - mCaretTime) > GetCaretTime()) {
			if (mCaretOn)
				CaretOff();
			else
				CaretOn();
		}
	}
}

void	LTextView::CaretOn(void)
{
	if (mOldSelectionIsCaret) {
		if (!mCaretOn)
			CaretUpdate();
		
		mCaretOn = IsActiveView();
		mCaretTime = TickCount();
	}
}

void	LTextView::CaretOff(void)
{
	if (mOldSelectionIsCaret) {
		if (mCaretOn)
			CaretUpdate();
	
		mCaretOn = false;
		mCaretTime = TickCount();
	}
}

#define	SelectionIsCaret(isActive)	(								\
									mSelection						\
								&& 	isActive						\
								&&	!mSelection->IsSubstantive()	\
								&&	(mSelection->ListCount() == 1)	\
)																	\

void	LTextView::GetPresentHiliteRgn(
	Boolean		inAsActive,
	RgnHandle	ioRegion,
	Int16		inOriginH,
	Int16		inOriginV
)
{
	if (mSelection) {
		if (mSelection->IsSubstantive()) {
			inherited::GetPresentHiliteRgn(inAsActive, ioRegion, inOriginH, inOriginV);
		} else {
			Point	origin;
			origin.h = inOriginH;
			origin.v = inOriginV;
			mSelection->MakeRegion(origin, &ioRegion);	//	don't make an outline
														//	because that would just be
														//	strange looking.
	
			//	Clip to visible area...
			Rect		r;
			if (CalcLocalFrameRect(r)) {
				StRegion	tempRgn(r);
	
				SectRgn(ioRegion, tempRgn, ioRegion);
				ThrowIfOSErr_(QDError());
			}
		}
	} else {
		URegions::MakeEmpty(ioRegion);
	}
	
	mOldSelectionIsCaret = SelectionIsCaret(inAsActive);
}

void	LTextView::MutateSelectionSelf(
	Boolean			inOldIsCaret,
	const RgnHandle	inOldRegion,
	Boolean			inNewIsCaret,
	const RgnHandle	inNewRegion) const
{
	if (!inOldIsCaret && !inNewIsCaret) {

		StRegion	tempRgn(inOldRegion);
		
		XorRgn(tempRgn, inNewRegion, tempRgn);
		ThrowIfOSErr_(QDError());
		
		URegions::Hilite(tempRgn);
	
	} else if (!inOldIsCaret && inNewIsCaret) {
	
		URegions::Hilite(inOldRegion);
		
	} else if (inOldIsCaret && !inNewIsCaret) {
		
		URegions::Hilite(inNewRegion);

	} else {
	
		//	nada
	
	}
}

void	LTextView::MutateSelection(Boolean inAsActive)
{
	Boolean		oldIsCaret = mOldSelectionIsCaret;
	StRegion	oldRegion(mOldSelectionRgn);
	
	GetPresentHiliteRgn(inAsActive, mOldSelectionRgn);
	mOldSelectionInvalid = false;
	
	MutateSelectionSelf(oldIsCaret, oldRegion, mOldSelectionIsCaret, mOldSelectionRgn);
}

void	LTextView::UpdateTextImage(TextUpdateRecordT *inUpdate)
{
	FocusDraw();

	Range32T frameInterval;			//	in image y-pixels
	frameInterval = TextRangeT(mFrameLocation.v - mImageLocation.v);
	frameInterval.SetLength(mFrameSize.height);				

	if (!frameInterval.Intersects(inUpdate->gap)) {
	
		//	¥	Then lets not so trivially handle this...
		if (inUpdate->gap.Start() > frameInterval.Start()) {
			//	change is completely below this frame
		} else if (inUpdate->scrollAmount < 0) {
			Range32T	zappedBit(inUpdate->scrollStart, inUpdate->scrollStart - inUpdate->scrollAmount);
			
			if (zappedBit.Contains(frameInterval)) {
				inherited::ScrollImageBy(0, mImageLocation.v - mFrameLocation.v + inUpdate->gap.Start(), false);
				FocusDraw();
				DrawSelf();
			} else if (frameInterval.Contains(
						Range32T(inUpdate->scrollStart - inUpdate->scrollAmount)
			) ) {
				inherited::ScrollImageBy(0, mImageLocation.v - mFrameLocation.v + inUpdate->gap.End(), false);
				FocusDraw();
				DrawSelf();
			} else {
				//	view is below changes.
				inherited::ScrollImageBy(0, inUpdate->scrollAmount, false);
			}
		} else {
			//	view is below changes.
			inherited::ScrollImageBy(0, inUpdate->scrollAmount, false);
		}
		MutateSelection(IsActiveView());
		
	} else {

		//	in local coords...
		Rect		rFrame,
					rGap,
					rAbove,		//	region in frame above gap
					rScroll;	//	scrolled rect pre-scroll location

								//	rGap in 32bit! local coords
		Int32		rgTop = inUpdate->gap.Start() + mPortOrigin.v + mImageLocation.v,
					rgLeft = 0 + mFrameLocation.h + mPortOrigin.h,
					rgRight = rgLeft + mFrameSize.width, 
					rgBottom = inUpdate->gap.End() + mPortOrigin.v + mImageLocation.v;

		if (CalcLocalFrameRect(rFrame)) {
			rGap.top = URange32::Max(rFrame.top, rgTop);
			rGap.left = URange32::Max(rFrame.left, rgLeft);
			rGap.right = URange32::Min(rFrame.right, rgRight);
			rGap.bottom = URange32::Min(rFrame.bottom, rgBottom);
		} else {
			Assert_(false);	//	huh?
		}
		rAbove = rFrame;
		rAbove.bottom = rGap.top;
		
		Boolean		oldSelectionIsCaret = mOldSelectionIsCaret;
		StRegion	rOld(mOldSelectionRgn.mRgn),
					rScreen,
					rTemp,
					rMask;
		StRegion	rgn;
		
		GetPresentHiliteRgn(IsActiveView(), mOldSelectionRgn);
	
		//	¥	Scroll
		if (inUpdate->scrollAmount &&
			frameInterval.Contains(Range32T(inUpdate->scrollStart))
		) {
			Rect	r;
	
			RectRgn(rMask, &rAbove);
			SectRgn(rOld, rMask, rScreen);

			r.top = inUpdate->scrollStart + mPortOrigin.v + mImageLocation.v;
			r.left = mFrameLocation.h + mPortOrigin.h;
			r.right = r.left + mFrameSize.width;
			r.bottom = mFrameLocation.v + mFrameSize.height + mPortOrigin.v;
			
			if (inUpdate->scrollAmount < (r.bottom - r.top)) {
				ScrollRect(&r, 0, inUpdate->scrollAmount, rgn);
				
				//	old selection...
				rScroll = r;
				if (inUpdate->scrollAmount > 0)
					rScroll.bottom -= inUpdate->scrollAmount;
				else
					rScroll.top -= inUpdate->scrollAmount;
				RectRgn(rMask, &rScroll);
				SectRgn(rOld, rMask, rTemp);
				OffsetRgn(rTemp, 0, inUpdate->scrollAmount);
				UnionRgn(rScreen, rTemp, rScreen);
			}
			if (inUpdate->scrollAmount < 0) {
				//	(offscreening this section would be a waste)
//				RGBColor	green = {0, 0xffff, 0};	RGBForeColor(&green);	PaintRgn(rgn);
				StClipRgnState	clipMe(rgn);
				DrawSelf();

				//	old selection...
				SectRgn(mOldSelectionRgn, rgn, rTemp);
				UnionRgn(rScreen, rTemp, rScreen);
			}
		} else {
			CopyRgn(rOld, rScreen);
		}
	
		//	¥	Gap
		Rect rReveal;			
		GetRevealedRect(rReveal);
		PortToLocalPoint(topLeft(rReveal));
		PortToLocalPoint(botRight(rReveal));

//-		if (!EmptyRect(&rGap))	{
		if (!EmptyRect(&rGap) && IsVisible() && ::SectRect(&rGap, &rReveal, &rGap))	{
			RectRgn(rgn, &rGap);
//			RGBColor	green = {0, 0xffff, 0};	RGBForeColor(&green);	PaintRgn(rgn);
			Boolean	didit = false;
#if	1
			Try_ {
				StOffscreenGWorld	smoothit(rGap, 0, useTempMem);
//?				StClipRgnState		clipMe(rgn);
				mTextEngine->SetPort(UQDGlobals::GetCurrentPort());
				DrawSelf();
				didit = true;
			} Catch_(inErr) {
				// 	failure okay -- just draw ugly
			} EndCatch_;
			mTextEngine->SetPort(UQDGlobals::GetCurrentPort());
#endif
			if (!didit) {
				StClipRgnState		clipMe(rgn);
				DrawSelf();
			}

			//	knock out gap area and update selection
			DiffRgn(rScreen, rgn, rScreen);
			DiffRgn(mOldSelectionRgn, rgn, rTemp);
			MutateSelectionSelf(oldSelectionIsCaret, rScreen, mOldSelectionIsCaret, rTemp);
		} else {
			MutateSelectionSelf(oldSelectionIsCaret, rScreen, mOldSelectionIsCaret, mOldSelectionRgn);
		}
	}
}


void	LTextView::DrawDebugRects(void)
{
#ifdef	DEBUGRECTS
	StColorState	savePen;
	RGBColor		red = {0xffff, 0, 0},			//	Image
					green = {0, 0xffff, 0},			//	Frame
					blue = {0, 0, 0xffff},			//	text frame
					purple = {0xffff, 0, 0xffff};	//	text image
	Rect			r,
					cr;
	SPoint32		o;
	SDimension32	s;

	StClipRgnState	clipSaver(GetMacPort()->portRect);

	RGBForeColor(&green);
	CalcLocalFrameRect(r);
	FrameRect(&r);

	if (mTextEngine) {
		mTextEngine->GetViewRect(&o, &s);
		r.top = o.v;
		r.left = o.h;
		r.right = r.left + s.width;
		r.bottom = r.top + s.height;
		RGBForeColor(&blue);
		FrameRect(&r);
	}
	
	//	image rects
	#if	DEBUGRECTS > 1
		GetScrollPosition(o);
		GetImageSize(s);
		r.top = 0;
		r.left = 0;
		r.right = r.left + s.width;
		r.bottom = r.top + s.height;
		RGBForeColor(&red);
		FrameRect(&r);
	
		if (mTextEngine) {
			mTextEngine->GetImageSize(&s);
			r.top = 0;
			r.left = 0;
			r.right = r.left + s.width;
			r.bottom = r.top + s.height;
			RGBForeColor(&purple);
			FrameRect(&r);
		}
	#endif
	
#endif
}

void	LTextView::FixView(void)
{
	SPoint32		location;
	SDimension32	size;
	
	if (mTextEngine) {
		GetViewArea(&location, &size);
		mTextEngine->SetViewRect(location, size);
	}
}

void	LTextView::FixImage(void)
{
	SDimension32	size = {0, 0},
					oldSize;

	//	change the image width
	FocusDraw();
		
	//	Wrap to frame size...
	if (mTextEngine  && mImageWidthFollowsFrame) {
		SPoint32		location;
		Rect			r;

		GetScrollPosition(location);

		if (!CalcLocalFrameRect(r))
			Throw_(paramErr);
	
		size.width = r.right - r.left + location.h;
		size.height = 0;

		mTextEngine->GetImageSize(&oldSize);
		if (oldSize.width != size.width) {
			mTextEngine->SetImageWidth(size.width);
			//	Will likely cause an update that
			//	will fix the size of the scrollbars
		}
	}
	
	if (mTextEngine) {
		mTextEngine->GetImageSize(&size);
	} else {
		size.width = mFrameSize.width;
		size.height = mFrameSize.height;
	}

	GetImageSize(oldSize);
	inheritView::ResizeImageBy(size.width - oldSize.width, size.height - oldSize.height, true);
}

void	LTextView::GetViewArea(SPoint32 *outOrigin, SDimension32 *outSize)
{
	Rect			r;

	if (!CalcLocalFrameRect(r))
		Throw_(paramErr);

	GetScrollPosition(*outOrigin);
	outSize->width = r.right - r.left;
	outSize->height = URange32::Min(r.bottom - r.top, mImageSize.height - outOrigin->v);
	outSize->height = URange32::Max(0, outSize->height);
}

void	LTextView::MoveBy(
	Int32	inHorizDelta,
	Int32	inVertDelta,
	Boolean	inRefresh)
{
	inheritView::MoveBy(inHorizDelta, inVertDelta, inRefresh);
	FixView();
}

void	LTextView::ResizeFrameBy(
	Int16		inWidthDelta,
	Int16		inHeightDelta,
	Boolean		inRefresh)
{
	inheritView::ResizeFrameBy(inWidthDelta, inHeightDelta, inRefresh);

	FixImage();
}

void	LTextView::ResizeImageBy(
	Int32		inWidthDelta,
	Int32		inHeightDelta,
	Boolean		inRefresh)
{
	SDimension32	size;

	GetImageSize(size);
	size.width += inWidthDelta;

	FocusDraw();	//	for good measure
	
	if (mTextEngine)
		mTextEngine->SetImageWidth(size.width);	//	will do a broadcast back (in
											//	an update event
											//	of ModelScopeChanged that
											//	will in turn call the inherited
											//	method.
}

void	LTextView::ScrollImageBy(
	Int32		inLeftDelta,
	Int32		inTopDelta,
	Boolean		inRefresh)
{
FocusDraw();
	SectRgn(mOldSelectionRgn, UQDGlobals::GetCurrentPort()->visRgn, mOldSelectionRgn);
	ThrowIfOSErr_(QDError());

	SPoint32	oldPortOrigin;	//	Type is important to avoid truncation
								//	errors below.

	oldPortOrigin.h = mPortOrigin.h;
	oldPortOrigin.v = mPortOrigin.v;
	
	FocusDraw();
	CaretOff();
	
	{
		StChange<Boolean>	tempChange(&mScrollingUpdate, true);

FocusDraw();
		if (mTextEngine)
			mTextEngine->ScrollView(inLeftDelta, inTopDelta, inRefresh);
FocusDraw();

	}

	if (	(mPortOrigin.h != (oldPortOrigin.h + inLeftDelta)) ||
			(mPortOrigin.v != (oldPortOrigin.v + inTopDelta))
	) {
		//	The Quickdraw coordinate system just got wrapped so...
		//
		//	Convert:	the old hilite rgn in the old coordinate system to...
		//				the old hilite rgn in the NEW coordinate system.
		SPoint32	delta32;
		Point		delta;
		
		delta32.h = mPortOrigin.h - (oldPortOrigin.h + inLeftDelta);
		delta32.v = mPortOrigin.v - (oldPortOrigin.v + inTopDelta);
		Assert_((min_Int16 <= delta.h) && (delta.h <= max_Int16));
		Assert_((min_Int16 <= delta.v) && (delta.v <= max_Int16));
		delta.h = delta32.h;
		delta.v = delta32.v;
		URegions::Translate(mOldSelectionRgn, delta);
	}
	
	if (inRefresh && (inLeftDelta || inTopDelta)) {
		MutateSelection(IsActiveView());
	}
	
	CaretOn();

	#ifdef	DEBUGRECTS
		FocusDraw();
		DrawDebugRects();
	#endif
}

void	LTextView::SavePlace(
	LStream		*outPlace)
{
	SPoint32		location = {0, 0};
	SDimension32	size = {0, 0};

	if (mTextEngine)
		mTextEngine->GetViewRect(&location, &size);

	LView::SavePlace(outPlace);
	
	outPlace->WriteData(&location, sizeof(location));
	outPlace->WriteData(&size, sizeof(size));
}


void	LTextView::RestorePlace(
	LStream		*inPlace)
{
	SPoint32		location;
	SDimension32	size;

	LView::RestorePlace(inPlace);

	inPlace->ReadData(&location, sizeof(location));
	inPlace->ReadData(&size, sizeof(size));

	if (mTextEngine)
		mTextEngine->SetViewRect(location, size);
}

void	LTextView::SetPrintRange(const TextRangeT &inRange)
{
	mPrintRange = inRange;
	if (mTextEngine)
		mPrintRange.Crop(mTextEngine->GetTotalRange());
}

const TextRangeT &	LTextView::GetPrintRange(void) const
{
	return mPrintRange;
}

Boolean	LTextView::NextPanelTop(Int32 *ioPanelTop) const
/*
	ioPanelTop is assumed correct on entry
*/
{
	Int32	oldTop = *ioPanelTop;
	Boolean	anotherExists = false;
	
	do {
		if (!mTextEngine)
			break;
		
		//	trivial reject if ioPanelTop is already past mPrintRange bottom
		Range32T	printV(mTextEngine->GetRangeTop(mPrintRange));
		printV.SetLength(mTextEngine->GetRangeHeight(mPrintRange));
		if (*ioPanelTop >= printV.End())
			break;
		
		//	get frame lower left image point
		SDimension16	frameSize;
		GetFrameSize(frameSize);
		SPoint32	llPt;
		llPt.h = 0;
		llPt.v = *ioPanelTop + frameSize.height;
		
		//	get vertical range of last partially(?) visible line
		TextRangeT	llRange;
		Boolean		leadingEdge;
		mTextEngine->Image2Range(llPt, &leadingEdge, &llRange);
		Range32T	llV(mTextEngine->GetRangeTop(llRange));
		llV.SetLength(mTextEngine->GetRangeHeight(llRange));

		if (printV.End() <= llPt.v) {
			*ioPanelTop = printV.End();
		} else {
			*ioPanelTop = llV.Start();
		}
		
		if (*ioPanelTop <= oldTop) {
			//	Then a line is so tall it couldn't fit in a single panel so go ahead and split it
			*ioPanelTop = oldTop + frameSize.height;
		}
		
		if (*ioPanelTop >= printV.End())
			break;
			
		anotherExists = true;

		if (oldTop >= *ioPanelTop) {
			Assert_(false);
			anotherExists = false;
		}
	} while (false);
	
	return anotherExists;
}

void	LTextView::CountPanels(
	Uint32	&outHorizPanels,
	Uint32	&outVertPanels)
{
	SDimension32	imageSize;
	SDimension16	frameSize;

	GetImageSize(imageSize);
	GetFrameSize(frameSize);
	
	outHorizPanels = 1;
	if (frameSize.width > 0  &&  imageSize.width > 0) {
		outHorizPanels = ((imageSize.width - 1) / frameSize.width) + 1;
	}
	
	outVertPanels = 1;

	if (mTextEngine) {
		//	Find the real number of vertical panels
		Int32		panelTop = mTextEngine->GetRangeTop(mPrintRange);
		while (NextPanelTop(&panelTop))
			outVertPanels++;
	}
}

Boolean	LTextView::ScrollToPanel(const PanelSpec &inPanel)
{
	Boolean		panelInImage = false;
	
	if (mTextEngine) {
		Int32		panelTop = mTextEngine->GetRangeTop(mPrintRange),
					panelBottom,
					panelLeft;
		Int32		vertPanel;
		
		vertPanel = 0;
		
		do {
			vertPanel++;
		} while ((vertPanel < inPanel.vertIndex) && NextPanelTop(&panelTop));
		
		if (vertPanel == inPanel.vertIndex) {
			SDimension16	frameSize;
			GetFrameSize(frameSize);
			
			panelBottom = panelTop;
			NextPanelTop(&panelBottom);
			panelLeft = frameSize.width * (inPanel.horizIndex -1);
			
			OutOfFocus(this);
			FocusDraw();
			ScrollImageTo(panelLeft, panelTop, false);
			Rect	clipRect;
			if (CalcLocalFrameRect(clipRect)) {
				mRevealedRect.bottom = mRevealedRect.top + panelBottom - panelTop;
				clipRect.bottom = clipRect.top + panelBottom - panelTop;
				::ClipRect(&clipRect);
			}
			panelInImage = true;
		}
	}
	
	return panelInImage;
}