netscape-revival
lib/mac/VText/V_PowerPlant_Mods/LTextEngine.cp
// ===========================================================================
// LTextEngine.cp ©1994 Metrowerks Inc. All rights reserved.
// ===========================================================================
#ifdef PowerPlant_PCH
#include PowerPlant_PCH
#endif
#include "LTextEngine.h"
#include <LListIterator.h>
#include <UMemoryMgr.h>
#include <LHandlerView.h>
#include <LEventHandler.h>
#include <LStyleSet.h>
#include <LRulerSet.h>
#include <LStream.h>
#include <URegions.h>
#include <UFontsStyles.h>
#ifndef __AEREGISTRY__
#include <AERegistry.h>
#endif
#ifndef __ERRORS__
#include <Errors.h>
#endif
#ifndef __SCRAP__
#include <Scrap.h>
#endif
#ifndef __SCRIPT__
#include <Script.h>
#endif
#pragma warn_unusedarg off
// ===========================================================================
const TextRangeT LTextEngine::sTextAll(0, max_Int32);
const TextRangeT LTextEngine::sTextStart(0, 0, rangeCode_After);
const TextRangeT LTextEngine::sTextEnd(max_Int32);
const TextRangeT LTextEngine::sTextUndefined;
LTextEngine::LTextEngine(void)
{
mHandlerView = NULL;
mAttributes = textEngineAttr_Editable | textEngineAttr_Selectable;
if (UFontsStyles::MultiByteScriptPresent())
mAttributes |= textEngineAttr_MultiByte;
mMargins.top = 4;
mMargins.left = 4;
mMargins.right = 4;
mMargins.bottom = 4;
mDefaultStyleSet = NULL;
mDefaultRulerSet = NULL;
mImageSize.width = 1000;
mImageSize.height = 0;
mImageWidth = mImageSize.width;
mUpdateRange = sTextUndefined;
mUpdatePreLength = 0;
mTotalRange = sTextStart;
mFlags = 0;
mUpdateLevel = 0;
}
LTextEngine::~LTextEngine(void)
{
ReplaceSharable(mDefaultStyleSet, (LStyleSet *)NULL, this);
ReplaceSharable(mDefaultRulerSet, (LRulerSet *)NULL, this);
}
#ifndef PP_No_ObjectStreaming
void LTextEngine::WriteObject(LStream &inStream) const
{
inStream << mAttributes;
inStream << mMargins;
inStream << mImageSize.width;
inStream << mImageSize.height;
// inherited::WriteObject(inStream); // none!
inStream << mDefaultStyleSet;
}
void LTextEngine::ReadObject(LStream &inStream)
{
inStream >> mAttributes;
inStream >> mMargins;
inStream >> mImageSize.width;
inStream >> mImageSize.height;
mImageWidth = mImageSize.width;
// inherited::ReadObject(inStream); // none!
SetDefaultStyleSet(ReadStreamable<LStyleSet *>()(inStream));
SetDefaultRulerSet(ReadStreamable<LRulerSet *>()(inStream));
if (UFontsStyles::MultiByteScriptPresent())
mAttributes |= textEngineAttr_MultiByte;
else
mAttributes &= ~textEngineAttr_MultiByte;
}
#endif
// ===========================================================================
void LTextEngine::SetAttributes(Uint16 inAttributes)
{
if (mAttributes != inAttributes) {
mAttributes = inAttributes;
mAttributes &= ~textEngineAttr_MultiByte;
if (UFontsStyles::MultiByteScriptPresent())
mAttributes |= textEngineAttr_MultiByte;
Refresh(GetTotalRange());
}
}
Uint16 LTextEngine::GetAttributes(void) const
{
return mAttributes;
}
void LTextEngine::SetTextMargins(const Rect &inMargins)
{
if (!EqualRect(&mMargins, &inMargins)) {
mMargins = inMargins;
Refresh(recalc_AllWidth);
}
}
void LTextEngine::GetTextMargins(Rect *outMargins) const
{
*outMargins = mMargins;
}
LHandlerView * LTextEngine::GetHandlerView(void)
{
return mHandlerView;
}
void LTextEngine::SetHandlerView(LHandlerView *inHandlerView)
{
if (inHandlerView == mHandlerView)
return;
mHandlerView = inHandlerView;
}
GrafPtr LTextEngine::GetPort(void)
{
//- Assert_(false); // must override
return NULL;
}
void LTextEngine::SetPort(GrafPtr inPort)
{
//- Assert_(false); // must override
}
void LTextEngine::GetImageSize(
SDimension32 *outSize
) const
{
Assert_(false); // Must override
}
#if 0
//-
void LTextEngine::SetImageSize(
const SDimension32 &inSize
)
/*
SetImageSize will NOT:
change the view rect
or
broadcast msg_ViewParmsChanged
But may make an update event that will
broadcast msg_ModelScopeChanged
*/
{
Assert_(false);
}
#endif
void LTextEngine::SetImageWidth(
Int32 inWidth
)
{
if (mImageWidth != inWidth) {
mImageWidth = inWidth;
mImageSize.width = mImageWidth;
Refresh(recalc_AllWidth);
}
}
void LTextEngine::GetViewRect(
SPoint32 *outOrigin,
SDimension32 *outSize) const
{
Assert_(false); // Must override
}
void LTextEngine::SetViewRect(
const SPoint32 &inLocation,
const SDimension32 &inSize
)
/*
SetViewRect will not:
change the image size
or
broadcast msg_ViewParmsChanged, or msg_ModelScopeChanged
*/
{
Assert_(false); // Must override
}
void LTextEngine::ScrollView(
Int32 inLeftDelta,
Int32 inTopDelta,
Boolean inRefresh)
{
Assert_(false); // Must override
}
void LTextEngine::ScrollToRange(const TextRangeT &inRange)
{
SPoint32 scrollVector;
if (GetRangeScrollVector(inRange, &scrollVector)) {
mHandlerView->ScrollPinnedImageBy(scrollVector.h, scrollVector.v, true);
}
}
Boolean LTextEngine::GetRangeScrollVector(
const TextRangeT &inRange,
SPoint32 *outVector
) const
/*
Returns a scrolling vector to bring the selection into view. Vector is valid even if false
is returned.
Returns true iff the "critical" point of the current selection is outside the
current view rectangle.
*/
{
Boolean rval = false;
TextRangeT range = inRange;
// Find "where ranges" (frame coords to put critical pt inside of).
Range32T hWhere,
vWhere;
SPoint32 origin;
SDimension32 size,
imageSize;
GetViewRect(&origin, &size);
GetImageSize(&imageSize);
hWhere.SetStart((mScrollPrefs.hWhere.Start() * size.width) >> 8);
hWhere.SetEnd((mScrollPrefs.hWhere.End() * size.width) >> 8);
vWhere.SetStart((mScrollPrefs.vWhere.Start() * size.height) >> 8);
vWhere.SetEnd((mScrollPrefs.vWhere.End() * size.height) >> 8);
// Find critical points (image coords that should be in view)
SPoint32 criticalPt1, // Bottom of critical "caret"
criticalPt2; // Top of critical "caret"
if (mScrollPrefs.doEnd) {
range.Back();
Range2Image(range, true, &criticalPt1);
} else {
range.Front();
Range2Image(range, false, &criticalPt1);
}
criticalPt2 = criticalPt1;
criticalPt2.v = GetRangeTop(range);
// Find desired vertical scroll position
SPoint32 scrollPosition = origin; // image coord of top left of frame
if (criticalPt2.v - origin.v < vWhere.Start()) {
scrollPosition.v = criticalPt2.v - vWhere.Start();
} else if (criticalPt1.v - origin.v > vWhere.End()) {
scrollPosition.v = criticalPt1.v - vWhere.End();
}
scrollPosition.v = URange32::Min(scrollPosition.v, imageSize.height - size.height); // Dont scroll past max boundary
scrollPosition.v = URange32::Max(scrollPosition.v, 0); // Dont scroll past min boundary
// Find horizontal scroll position
if (criticalPt1.h - origin.h < hWhere.Start()) {
scrollPosition.h = criticalPt1.h - hWhere.Start();
} else if (criticalPt1.h - origin.h > hWhere.End()) {
scrollPosition.h = criticalPt1.h - hWhere.End() + 1; // +1 is for caret width of 1 pixel
}
if (criticalPt1.h < size.width)
scrollPosition.h = 0; // Hug left edge
scrollPosition.h = URange32::Min(scrollPosition.h, imageSize.width - size.width); // Dont scroll past max boundary
scrollPosition.h = URange32::Max(scrollPosition.h, 0); // Dont scroll past min boundary
// Determine if scrolling should occur
if (mScrollPrefs.doAlways) {
// if criticalPt isn't visible, scroll.
Boolean doH = (criticalPt1.h < origin.h) || (criticalPt1.h >= (origin.h + size.width)),
doV = (criticalPt2.v < origin.v) || (criticalPt1.v >= (origin.v + size.height));
rval = doH || doV;
// Only scroll each axis as necessary
if (!doH)
scrollPosition.h = origin.h;
if (!doV)
scrollPosition.v = origin.v;
} else {
// if any part of range is visible, don't scroll!
StRegion rangeRgn;
Rect r;
r.top = origin.v;
r.left = origin.h;
r.right = r.left + size.width;
r.bottom = r.top + size.height;
GetRangeRgn(inRange, &rangeRgn.mRgn);
if (EmptyRgn(rangeRgn) || !RectInRgn(&r, rangeRgn)) {
rval = true;
} else {
rval = false;
}
}
// Return
if (outVector) {
// These values are valid even if false is returned
outVector->v = scrollPosition.v - origin.v;
outVector->h = scrollPosition.h - origin.h;
}
return rval;
}
SScrollPrefs::SScrollPrefs()
{
hWhere = Range32T(0x0040, 0x00c0); // quarters
vWhere = Range32T(0x0000, 0x0100);
// hWhere = Range32T(0x0010, 0x00f0); // for testing
// hWhere = Range32T(0x0080, 0x0080); // for testing
// hWhere = Range32T(0x0000, 0x0100); // for testing
// vWhere = hWhere; // for testing
doEnd = true;
doAlways = false;
}
const SScrollPrefs & LTextEngine::ScrollPreferences(void) const
{
return mScrollPrefs;
}
SScrollPrefs & LTextEngine::ScrollPreferences(void)
{
return mScrollPrefs;
}
void LTextEngine::DrawArea(
const SPoint32 &inLocation,
const SDimension32 &inSize
)
{
Assert_(mHandlerView);
Recalc();
}
Boolean LTextEngine::GetTextChanged(void)
{
return mIsChanged;
}
void LTextEngine::SetTextChanged(Boolean inChanged)
{
if (inChanged && ((mAttributes & textEngineAttr_Editable) == 0))
Throw_(wrPermErr); // should read can't edit this file -- search string: paramErr
mIsChanged = inChanged;
}
const TextRangeT & LTextEngine::GetTotalRange(void) const
/*
Override to load mTotalRange with the implementation engine value.
*/
{
return mTotalRange;
}
// ===========================================================================
// Text get/set
void LTextEngine::TextMove(const TextRangeT &inRangeA, const TextRangeT &inRangeB)
/*
Range A is moved to range B.
*/
{
TextRangeT srcRange = inRangeA,
dstRange = inRangeB;
srcRange.Crop(GetTotalRange());
dstRange.Crop(GetTotalRange());
if (srcRange.Intersects(dstRange))
ThrowOSErr_(paramErr);
if (!SpaceForBytes(srcRange.Length() - dstRange.Length()))
ThrowOSErr_(teScrapSizeErr);
StHandleBlock temp(srcRange.Length(), true, true);
TextGetThruHandle(srcRange, temp);
TextDelete(srcRange);
if (srcRange.Start() < dstRange.Start())
dstRange.Translate(-srcRange.Length());
TextReplaceByHandle(dstRange, temp);
}
void LTextEngine::TextDelete(const TextRangeT &inRange)
{
TextReplaceByPtr(inRange, NULL, 0);
}
void LTextEngine::TextGetThruPtr(const TextRangeT &inRange, Ptr outBuffer)
/*
Notes:
This method is MAYBE only suitable for plain text. Consider using
one of the tube methods above unless really needing plain text
from a non tube source.
TextGetThruPtr or TextGetThruHandle must be overridden or infinite
recursion will occur.
*/
{
TextRangeT range = inRange;
range.Crop(GetTotalRange());
StHandleBlock text(range.Length(), true, true);
ThrowIfNULL_(outBuffer);
TextGetThruHandle(range, text.mHandle);
BlockMoveData(*(text.mHandle), outBuffer, range.Length());
}
void LTextEngine::TextGetThruHandle(const TextRangeT &inRange, Handle outTextH)
/*
Notes:
This method is MAYBE only suitable for plain text. Consider using
one of the tube methods above unless really needing plain text
from a non tube source.
TextGetThruPtr or TextGetThruHandle must be overridden or infinite
recursion will occur.
*/
{
ThrowIfNULL_(outTextH);
TextRangeT range = inRange;
range.Crop(GetTotalRange());
if (GetHandleSize(outTextH) != inRange.Length()) {
SetHandleSize(outTextH, inRange.Length());
ThrowIfMemError_();
}
StHandleLocker p(outTextH);
TextGetThruPtr(range, *outTextH);
}
void LTextEngine::TextReplaceByPtr(
const TextRangeT &inRange,
const Ptr inSrcText,
Int32 inSrcLength)
/*
Note:
This method is MAYBE only suitable for plain text. Consider using
one of the tube methods above unless really needing plain text
from a non tube source.
*/
{
/* Must be overridden w/ something like...
TextRangeT range = inRange;
range.Crop(GetTotalRange());
SetTextChanged();
TextFluxPre(range);
// change your data...
TextFluxPost(inSrcLength - range.Length());
*/
}
void LTextEngine::TextReplaceByHandle(
const TextRangeT &inRange,
const Handle inNewTextH)
/*
Note:
This method is MAYBE only suitable for plain text. Consider using
one of the tube methods above unless really needing plain text
from a non tube source.
*/
{
StHandleLocker p(inNewTextH);
Int32 length = GetHandleSize(inNewTextH);
TextReplaceByPtr(inRange, *inNewTextH, length);
}
// ===========================================================================
// Support
const Handle LTextEngine::GetTextHandle(void) const
/*
You can NOT GetTextHandle, change the data in the handle, and then hope
that a stylized text engine will correctly update the
styles for the text.
You should only use this routine for such things as writing
raw ascii to disk.
The handle returned may be larger than the text contained by the text
engine -- get the actual text length with GetTotalRange().Length() not
GetHandleSize!
*/
{
return NULL;
}
void LTextEngine::SetTextHandle(Handle inHandle)
/*
This routines causes the text engine to use the
text inHandle. Thereafter, the TextEngine owns the handle.
The initial style of the text in the Handle will be
the default styleset for the text engine.
The handle can not contain any padding (in contrast to GetTextHandle
whose handle may contain padding).
*/
{
Assert_(false);
}
// This member is protected -- you can't use it from client code
const void * LTextEngine::GetTextPtr(Int32 inOffset) const
{
char **q,
*p;
q = GetTextHandle();
ThrowIfNULL_(q);
p = *q;
ThrowIfNULL_(p);
return p + inOffset;
}
// ===========================================================================
void LTextEngine::DomainToLocalRect(const Domain32T &inDomain, Rect *outRect) const
{
SPoint32 p32;
ThrowIfNULL_(mHandlerView);
p32.h = inDomain.Width().Start();
p32.v = inDomain.Height().Start();
mHandlerView->ImageToLocalPoint(p32, topLeft(*outRect));
p32.h = inDomain.Width().End();
p32.v = inDomain.Height().End();
mHandlerView->ImageToLocalPoint(p32, botRight(*outRect));
}
void LTextEngine::GetRangeRgn(const TextRangeT &inRange, RgnHandle *ioRgn) const
/*
Returns the region of inRange.
IoRgn is clipped to the current handler view (GetHandlerView()) and the current
handler view defines the 16 bit coordinate system used for ioRgn.
If there is no current handler view or inRange lies outside the current handler
view, ioRgn is set to an empty rgn.
*/
{
mHandlerView->FocusDraw();
Boolean exists = false;
TextRangeT range = inRange;
ThrowIfNULL_(ioRgn);
ThrowIfNULL_(*ioRgn);
range.Crop(GetTotalRange());
do {
if (!mHandlerView)
break;
((LTextEngine *)this)->Recalc();
SPoint32 origin;
SDimension32 size;
GetViewRect(&origin, &size);
Domain32T vRect(
Range32T(origin.h, origin.h + size.width),
Range32T(origin.v, origin.v + size.height)
); // view rect in image coords
Range32T rHeight(GetRangeTop(range)); // top and bottom of range
rHeight.SetLength(GetRangeHeight(range));
rHeight.Crop(vRect.Height());
if (!rHeight.Length())
break;
Rect r; // scratch rect
if (range.Length()) {
SPoint32 p1, // anchor points of range start and end
p2;
Range2Image(range, true, &p1);
Range2Image(range, false, &p2);
p1.h--; // include one pixel to left of char
if (p1.v == p2.v) {
// one line
if (p1.h >= p2.h)
Throw_(paramErr);
Domain32T l1(Range32T(p1.h, p2.h), rHeight);
l1.Crop(vRect);
DomainToLocalRect(l1, &r);
RectRgn(*ioRgn, &r);
ThrowIfOSErr_(QDError());
} else {
// two lines
TextRangeT front = range,
back = range;
front.Front();
back.Back();
Int32 h1 = GetRangeHeight(front), // heights of first and last line of range
h2 = GetRangeHeight(back),
extremeLeft = 0 + mMargins.left,
extremeRight = mImageSize.width - mMargins.right;
extremeLeft--; // include one pixel to left of char
Domain32T l1(Range32T(p1.h, extremeRight), Range32T(p1.v - h1, p1.v)),
l2(Range32T(extremeLeft, p2.h), Range32T(p2.v - h2, p2.v));
l1.Crop(vRect);
DomainToLocalRect(l1, &r);
RectRgn(*ioRgn, &r);
ThrowIfOSErr_(QDError());
l2.Crop(vRect);
DomainToLocalRect(l2, &r);
StRegion rgn(r);
UnionRgn(*ioRgn, rgn, *ioRgn);
ThrowIfOSErr_(QDError());
if ((p2.v - h2) > p1.v) {
// region between the two lines
Domain32T mb(
Range32T(extremeLeft, extremeRight),
Range32T(p1.v, p2.v - h2)
);
mb.Crop(vRect);
DomainToLocalRect(mb, &r);
RectRgn(rgn, &r);
UnionRgn(*ioRgn, rgn, *ioRgn);
ThrowIfOSErr_(QDError());
}
}
} else {
// An insertion point
SPoint32 p1;
Rect r;
Range2Image(range, true, &p1);
Domain32T l1(Range32T(p1.h -1, p1.h), rHeight);
l1.Crop(vRect);
DomainToLocalRect(l1, &r);
RectRgn(*ioRgn, &r);
ThrowIfOSErr_(QDError());
}
exists = true;
} while (false);
if (!exists) {
SetEmptyRgn(*ioRgn);
ThrowIfOSErr_(QDError());
}
}
// ===========================================================================
// Presentation query
Boolean LTextEngine::RangeIsMultiLine(const TextRangeT &inRange) const
{
return true;
}
Int32 LTextEngine::GetRangeTop(const TextRangeT &inRange) const
{
TextRangeT range = inRange;
SPoint32 where;
Int32 rval;
range.Crop(GetTotalRange());
range.SetLength(0);
Range2Image(range, true, &where);
rval = where.v - GetRangeHeight(range);
return rval;
}
Int32 LTextEngine::GetRangeHeight(const TextRangeT &inRange) const
{
Assert_(false);
return 0;
}
Int32 LTextEngine::GetRangeWidth(const TextRangeT &inRange) const
{
SPoint32 anchor, end;
Int32 width;
TextRangeT range = inRange;
if (range.Length() == 0)
return 0;
range.Crop(GetTotalRange());
Range2Image(range, true, &anchor);
Range2Image(range, false, &end);
width = end.h - anchor.h;
if (end.v != anchor.v) {
SDimension32 size;
GetImageSize(&size);
return size.width;
}
if (width <= 0) {
SDimension32 size;
GetImageSize(&size);
if (width == 0)
return size.width;
width += size.width;
}
Assert_(width > 0);
return width;
}
void LTextEngine::Range2Image(
const TextRangeT &inRange,
Boolean inLeadingEdge,
SPoint32 *outWhere) const
{
}
void LTextEngine::Image2Range(
SPoint32 inWhere,
Boolean *outLeadingEdge,
TextRangeT *outRange) const
{
}
Boolean LTextEngine::PointInRange(SPoint32 inWhere, const TextRangeT &inRange) const
/*
Returns true iff:
inRange.Length() > 0: inWhere lies on top of any glyph in inRange
inRange.Length() = 0: inWhere lies on same "side" of indicated
insertion point.
*/
{
TextRangeT rangeFound,
range = inRange;
Boolean leadingEdge,
rval = false;
range.WeakCrop(GetTotalRange());
Image2Range(inWhere, &leadingEdge, &rangeFound);
if (range.Length()) {
rval = range.Contains(rangeFound);
} else {
if (rangeFound.Length()) {
if (leadingEdge)
rangeFound.Front();
else
rangeFound.Back();
}
rval = range == rangeFound;
}
// Unusual construction for breakpoint handles
if (rval)
return true;
else
return false;
}
// ===========================================================================
// Styles
LStyleSet * LTextEngine::GetDefaultStyleSet(void) const
{
return mDefaultStyleSet;
}
void LTextEngine::SetDefaultStyleSet(LStyleSet *inNewStyle)
{
ThrowIfNULL_(inNewStyle);
ReplaceSharable(mDefaultStyleSet, inNewStyle, this);
}
LStyleSet * LTextEngine::GetStyleSet(const TextRangeT &inRange, Int32 inIndex) const
{
return mDefaultStyleSet;
}
void LTextEngine::SetStyleSet(const TextRangeT &inRange, const LStyleSet *inNewStyle)
{
Assert_(false);
}
Int32 LTextEngine::CountStyleSets(const TextRangeT &inRange) const
{
return 1;
}
void LTextEngine::GetStyleSetRange(TextRangeT *ioRange) const
{
ThrowIfNULL_(ioRange);
*ioRange = GetTotalRange();
}
void LTextEngine::StyleSetChanged(LStyleSet *inChangedStyle)
{
}
// ===========================================================================
// Rulers
LRulerSet * LTextEngine::GetDefaultRulerSet(void) const
{
return mDefaultRulerSet;
}
void LTextEngine::SetDefaultRulerSet(LRulerSet *inNewRuler)
{
ThrowIfNULL_(inNewRuler);
ReplaceSharable(mDefaultRulerSet, inNewRuler, this);
}
LRulerSet * LTextEngine::GetRulerSet(const TextRangeT &inRange, Int32 inIndex) const
{
return mDefaultRulerSet;
}
void LTextEngine::SetRulerSet(const TextRangeT &inRange, const LRulerSet *inNewRuler)
{
Assert_(false);
}
Int32 LTextEngine::CountRulerSets(const TextRangeT &inRange) const
{
return 1;
}
void LTextEngine::GetRulerSetRange(TextRangeT *ioRange) const
{
ThrowIfNULL_(ioRange);
*ioRange = GetTotalRange();
}
void LTextEngine::RulerSetChanged(LRulerSet *inChangedRuler)
{
}
// ===========================================================================
// Implementation help:
void LTextEngine::Focus(void)
{
if (mHandlerView)
mHandlerView->FocusDraw();
}
void LTextEngine::CheckImageSize(void)
{
SDimension32 imageSize;
GetImageSize(&imageSize);
if ((mImageSize.width != imageSize.width) || (mImageSize.height != imageSize.height)) {
BroadcastMessage(msg_ModelScopeChanged);
Focus();
mImageSize = imageSize;
}
}
void LTextEngine::RecalcSelf(void)
{
if (mFlags & recalc_WindowUpdate) {
// views have presumably already been invalidated
mFlags &= ~recalc_WindowUpdate;
}
CheckImageSize();
LRecalcAccumulator::RecalcSelf();
}
void LTextEngine::Refresh(RecalcFlagsT inFlags)
{
RecalcFlagsT oldFlags = mFlags;
if (mUpdateLevel) {
Assert_(!(inFlags & recalc_WindowUpdate));
LRecalcAccumulator::Refresh(inFlags & (~recalc_WindowUpdate));
} else {
LRecalcAccumulator::Refresh(inFlags | recalc_WindowUpdate);
if (!(oldFlags & recalc_WindowUpdate)) {
BroadcastMessage(msg_InvalidateImage);
}
}
}
void LTextEngine::Refresh(const TextRangeT &inRange)
{
TextRangeT range = inRange;
range.Crop(GetTotalRange());
if (mUpdateRange.Undefined())
mUpdateRange = range;
else
mUpdateRange.Union(range);
Refresh(recalc_Range);
}
void LTextEngine::TextFluxPre(const TextRangeT &inRange)
{
//-. Assert_(mUpdateLevel > 0); // can only be used inside a StRecalculator
mPreFluxRange = inRange;
Refresh(inRange);
}
void LTextEngine::TextFluxPost(Int32 inLengthDelta)
{
//-. Assert_(mUpdateLevel > 0); // can only be used inside a StRecalculator
// Update total range
mTotalRange.SetLength(mTotalRange.Length() + inLengthDelta, rangeCode_After);
// Figure flux...
Assert_(mPreFluxRange.Length() >= (-inLengthDelta));
TextFluxRecordT flux;
flux.range = mPreFluxRange;
flux.lengthDelta = inLengthDelta;
// Adjust mUpdateRange
{
TextRangeT affectedRange = mPreFluxRange;
affectedRange.Adjust(flux.range, inLengthDelta, true);
if (mUpdateRange.Undefined()) {
mUpdateRange = affectedRange;
} else {
mUpdateRange.Adjust(flux.range, inLengthDelta, true);
mUpdateRange.Union(affectedRange);
}
mUpdateRange.Crop(GetTotalRange());
}
TextFluxPostSelf(mPreFluxRange, inLengthDelta);
// Broadcast flux
try {
BroadcastMessage(msg_TextFluxed, &flux);
} catch (...) {
Assert_(false); // you must not throw in a listen to msg_TextFluxed!
};
}
void LTextEngine::TextFluxPostSelf(
const TextRangeT &inRange,
Int32 inLengthDelta)
{
}
void LTextEngine::NestedUpdateIn(void)
{
Assert_(mUpdateLevel > 0);
if (mUpdateLevel == 1) {
SDimension32 size;
GetImageSize(&size);
mUpdatePreHeight = size.height;
mUpdateRange = sTextUndefined;
mUpdatePreLength = GetTotalRange().Length();
BroadcastMessage(msg_ModelAboutToChange);
}
}
void LTextEngine::NestedUpdateOut(void)
{
Assert_(mUpdateLevel > 0);
if (mUpdateLevel != 1)
return;
TextRangeT changedRange = mUpdateRange; // Shadow because the recalc & broadcast may change it again
Recalc();
if (!mUpdateRange.Undefined())
BroadcastMessage(msg_CumulativeRangeChanged, &changedRange);
if (!mUpdateRange.Undefined() && !(mFlags & recalc_WindowUpdate)) {
Recalc(); // Maybe the previous broadcast caused a change
TextUpdateRecordT update;
SDimension32 sizeNow;
TextRangeT range = mUpdateRange;
GetImageSize(&sizeNow);
range.Crop(GetTotalRange());
// expand range to include all of changed paragraphs -- their lines
// may have been reflowed
{
TextRangeT next = range;
next.After();
if (typeNull != FindPart(GetTotalRange(), &next, cParagraph))
range.SetEnd(URange32::Max(range.End(), next.End()));
}
// gap area (affected area)
update.gap = Range32T(GetRangeTop(range));
if (range.End() == GetTotalRange().End()) {
update.gap.SetLength(sizeNow.height - update.gap.Start());
} else {
update.gap.SetLength(GetRangeHeight(range));
}
Assert_(update.gap.Length() > 0);
// scolling parameters
update.scrollAmount = sizeNow.height - mUpdatePreHeight;
update.scrollStart = update.gap.End();
if (update.scrollAmount > 0)
update.scrollStart -= update.scrollAmount;
BroadcastMessage(msg_UpdateImage, &update);
}
mUpdatePreLength = GetTotalRange().Length();
BroadcastMessage(msg_ModelChanged);
}
Boolean LTextEngine::SpaceForBytes(Int32 inAdditionalBytes) const
{
return false;
}
// PREMULTIBYTE is for regression performance testing only
#ifndef PREMULTIBYTE
Int32 LTextEngine::ThisCharSize(Int32 inInsertionLocation) const
/*
Returns the size of the character at inInsertionLocation
offset. Usage is required for multi-byte text engines.
Will return 0 if inInsertionLocation is past an end of the text.
Will fail if inInsertionLocation is not at a character boundary.
*/
{
/*
Defaults to a single byte per character system.
*/
if (inInsertionLocation >= GetTotalRange().End()) {
return 0;
} else {
TextRangeT range(inInsertionLocation);
range.SetInsertion(rangeCode_After);
StHandleLocker lockText(GetTextHandle());
LStyleSet *ss = GetStyleSet(range);
ThrowIfNULL_(ss);
TextRangeT queryRange = range;
GetStyleSetRange(&queryRange);
return ss->FindCharSize(queryRange, *GetTextHandle(), range);
}
}
Int32 LTextEngine::PrevCharSize(Int32 inInsertionLocation) const
/*
Returns the size of the character immediately before the inInsertionLocation
offset. Usage is required for multi-byte text engines.
Will return 0 if inInsertionLocation is at the begginning of all text.
*/
{
if (inInsertionLocation <= GetTotalRange().Start()) {
return 0;
} else {
TextRangeT range(inInsertionLocation);
range.SetInsertion(rangeCode_Before);
StHandleLocker lockText(GetTextHandle());
LStyleSet *ss = GetStyleSet(range);
ThrowIfNULL_(ss);
TextRangeT queryRange = range;
GetStyleSetRange(&queryRange);
return ss->FindCharSize(queryRange, *GetTextHandle(), range);
}
#if 0
// dang! -- we have to make an inefficient search!
TextRangeT styleRange(inInsertionLocation);
styleRange.SetInsertion(rangeCode_Before);
TextRangeT paraRange = styleRange;
DescType partFound = FindPart(sTextAll, ¶Range, cParagraph);
//-? ThrowIf_(partFound == typeNull);
if ((paraRange.Start() == styleRange.Start()) || !paraRange.Length()) {
if (paraRange.Start() == GetTotalRange().Start())
return 0;
// backup a line
if (paraRange.Start() > GetTotalRange().End()) {
paraRange = TextRangeT(GetTotalRange().End());
}
paraRange.SetInsertion(rangeCode_Before);
partFound = FindPart(sTextAll, ¶Range, cParagraph);
ThrowIf_(partFound == typeNull);
}
GetStyleSetRange(&styleRange);
LStyleSet *ss = GetStyleSet(styleRange);
ThrowIfNULL_(ss);
styleRange.Crop(paraRange); // restrict search -- para start starts on char boundary
Int32 startPos = styleRange.Start();
Int32 offset = inInsertionLocation - startPos;
ScriptCode styleScript = ss->GetScript();
offset--; // back up to possible previous single byte
if (offset > max_Int16) {
// There is a line longer than the "short offset" in CharacterByteType
// can handle. This should really never happens except in a psycho test
// case. Rather than throw, we'll assume the character length was 1.
Assert_(false);
rval = 1;
} else {
short type = CharacterByteType((Ptr)GetTextPtr(startPos), offset, styleScript);
switch (type) {
case smSingleByte:
rval = 1;
break;
case smLastByte:
rval = 2;
break;
default:
Throw_(paramErr); // what?
break;
}
}
} else {
rval = 1;
}
return rval;
#endif
}
Int16 LTextEngine::ThisChar(Int32 inInsertionLocation) const
/*
Returns the character immediately following the character at
inInsertionLocation.
Note the return value is the 16 bit multi-byte value!
Will fail if attempting to return character past ends of text.
*/
{
Int32 size = ThisCharSize(inInsertionLocation);
Uint16 c = 0;
unsigned char *p,
**q = (unsigned char **)((LTextEngine *)this)->GetTextHandle();
ThrowIfNULL_(q);
p = *q;
ThrowIfNULL_(p);
p += inInsertionLocation;
switch (size) {
case 2:
c |= *p++;
c = c << 8;
// fall through...
case 1:
c |= *p;
break;
case 0: // Can't get value for a non-existent character
default: // Can't get value for a non-text "character"
break;
}
return c;
}
#else
// For regression performance testing only...
Int32 LTextEngine::ThisCharSize(Int32 inInsertionLocation) const
{
TextRangeT totalRange;
GetTotalRange(&totalRange);
if (inInsertionLocation >= (totalRange.start + totalRange.length))
return 0;
else
return 1;
}
Int32 LTextEngine::PrevCharSize(Int32 inInsertionLocation) const
{
TextRangeT totalRange;
GetTotalRange(&totalRange);
if (inInsertionLocation <= totalRange.start)
return 0;
else
return 1;
}
Int16 LTextEngine::ThisChar(Int32 inInsertionLocation) const
{
char *p, **q = ((LTextEngine *)this)->GetTextHandle();
ThrowIfNULL_(q);
p = *q;
ThrowIfNULL_(p);
return *(p + inInsertionLocation);
}
#endif