/*
* Copyright (c) 1999-2009 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:
*
*/
#include "TmText.h"
#include <txtfrmat.h>
#include <txtetext.h>
#include "TMSTD.H"
/**
* Creates a CTmText object using the specified device map and formatting parameters.
*/
EXPORT_C CTmText* CTmText::NewL(MGraphicsDeviceMap& aDevice,const TTmFormatParamBase& aFormatParam)
{
CTmText* t = new(ELeave) CTmText;
CleanupStack::PushL(t);
t->iImp = new(ELeave) CTmTextImp(aDevice,aFormatParam);
CleanupStack::Pop();
return t;
}
/**
* Creates a CTmText object using the specified device map and formatting
* parameters that have default values (as in the default constructor for
* TTmFormatParamBase) apart from the wrap width and flags. The values of the
* flags are those used in for TTmFormatParamBase::iFlags.
*/
EXPORT_C CTmText* CTmText::NewL(MGraphicsDeviceMap& aDevice,TInt aWrapWidth,TInt aFlags)
{
TTmFormatParamBase param;
param.iWrapWidth = aWrapWidth;
param.iFlags = aFlags;
return NewL(aDevice,param);
}
CTmText::CTmText()
{
}
/**
* Destroys a CTmText object.
*/
EXPORT_C CTmText::~CTmText()
{
delete iImp;
}
/**
* Inserts the text aText at the document position aPos. The other arguments
* are all pointers which have default values of null. The first two,
* aCharFormat and aParFormat, specify the format for the inserted text. If
* either is null the format immediately before the insertion point is used.
*
* Put the coordinates of the area that has been reformatted and needs to be
* redrawn, relative to the origin of the CText object, in aRedrawRect. Put the
* amount by which the text after the redrawn text needs to be scrolled up or
* down in aScroll: this is the amount by which the reformatted text has
* changed in height, and thus is positive if the text after the redrawn text
* has increased in height, which is usually, but not always, the case.
*/
EXPORT_C void CTmText::InsertL(TInt aPos,const TDesC& aText,
const TTmCharFormat* aCharFormat,const RTmParFormat* aParFormat,
TRect* aRedrawRect,TInt* aScroll)
{
iImp->InsertL(aPos,aText,aCharFormat,aParFormat,aRedrawRect,aScroll);
}
/**
* Retrieves the current formatting parameters.
*/
EXPORT_C void CTmText::GetFormat(TTmFormatParamBase& aFormatParam) const
{
iImp->GetFormat(aFormatParam);
}
/**
* Sets the wrap width and reformat the text.
*/
EXPORT_C void CTmText::SetWrapWidthL(TInt aWrapWidth)
{
iImp->SetWrapWidthL(aWrapWidth);
}
/**
* Set the formatting parameters and reformat the text.
*/
EXPORT_C void CTmText::ChangeFormatL(const TTmFormatParamBase& aFormatParam)
{
iImp->ChangeFormatL(aFormatParam);
}
/**
* Deletes all the text.
*/
EXPORT_C void CTmText::Clear()
{
iImp->Clear();
}
/**
* Specifies custom drawing or layout for the object, or if aCustom is NULL,
* removes any customization. Reformats the object according to the new
* customized layout. See the MTmCustom class for details. Custom drawing
* allows background graphics to be drawn, and custom layout allows, among
* other things, control over the way line height is calculated and special
* characters are displayed.
*/
EXPORT_C void CTmText::CustomizeL(const MTmCustom* aCustom)
{
iImp->CustomizeL(aCustom);
}
/**
* Returns the number of bytes of memory used by a CTmText object, allowing 8
* bytes overhead per heap allocation.
*/
EXPORT_C TInt CTmText::MemoryUsed() const
{
return sizeof(*this) + iImp->MemoryUsed() + 8;
}
/**
* Returns a const reference to the CTmTextLayout object owned by this object.
* This function implements MTmTextLayoutForwarder::TextLayout.
*/
const CTmTextLayout& CTmText::TextLayout() const
{
return iImp->TextLayout();
}
template<class T> TInt RRefCountedArray<T>::Insert(const T& aEntry,TInt& aIndex)
{
//+ replace this linear search with a binary search
int n = iArray.Count();
for (int i = 0; i < n; i++)
if (aEntry == iArray[i].iEntry)
{
iArray[i].iRefCount++;
aIndex = i;
return KErrNone;
}
aIndex = n;
TCountedEntry new_entry;
new_entry.iRefCount = 1;
int error = iArray.Append(new_entry);
if (!error)
error = iArray[iArray.Count() - 1].iEntry.Copy(aEntry);
return error;
}
template<class T> void RRefCountedArray<T>::Remove(TInt aIndex)
{
if (--iArray[aIndex].iRefCount == 0)
iArray.Remove(aIndex);
}
template<class T> TInt RRefCountedArray<T>::MemoryUsed() const
{
TInt bytes = sizeof(*this);
if (iArray.Count())
bytes += User::AllocLen(&iArray[0]) + 8;
return bytes;
}
CTmTextImp::~CTmTextImp()
{
iCharFormatRun.Close();
iParFormatRun.Close();
iCharFormat.Close();
for (int i = 0; i < iParFormat.Count(); i++)
iParFormat[i].Close();
iParFormat.Close();
}
void CTmTextImp::InsertL(TInt aPos,const TDesC& aText,
const TTmCharFormat* aCharFormat,const RTmParFormat* aParFormat,
TRect* aRedrawRect,TInt* aScroll)
{
__ASSERT_DEBUG(0 <= aPos, TmPanic(EBadArg));
// Don't insert zero-length text.
if (aText.Length() == 0)
return;
// Clamp aPos to the end of the document.
if (iTextLayout.EndChar() <= aPos)
aPos = Max(0,iTextLayout.EndChar() - 1);
// If the document is empty and character or paragraph format is unspecified, use a default format.
TTmCharFormat default_char_format;
default_char_format.iFontSpec.SetHeight(12 * 20); // set height, which must be in twips, to 12pt
RTmParFormat default_par_format;
if (iText.Length() == 0)
{
if (aCharFormat == NULL)
aCharFormat = &default_char_format;
if (aParFormat == NULL)
aParFormat = &default_par_format;
}
// Insert the character format.
int error = 0;
int char_format_index = -1;
if (aCharFormat)
error = iCharFormat.Insert((TMyCharFormat&)*aCharFormat,char_format_index);
// Insert the paragraph format.
int par_format_index = -1;
if (!error && aParFormat)
error = iParFormat.Insert((RMyParFormat&)*aParFormat,par_format_index);
// Insert the character format run.
if (!error)
error = iCharFormatRun.Insert(aPos,aText.Length(),char_format_index);
// If the paragraph format has changed extend it to the paragraph bounds.
int old_par_format_index = -1;
if (!error && iText.Length() > 0)
{
if (par_format_index != -1)
old_par_format_index = iParFormatRun.Index(aPos);
if (par_format_index != old_par_format_index)
{
int par_start = ParagraphStart(aPos);
int par_end = ParagraphEnd(aPos);
if (iText.Length() == par_end - 1)
--par_end;
error = iParFormatRun.Set(par_start, par_end - par_start,
par_format_index);
}
}
// Insert the paragraph format run.
if (!error)
error = iParFormatRun.Insert(aPos,aText.Length(),par_format_index);
// Insert the text.
if (!error)
{
TRAP(error,iText.InsertL(aPos,aText));
}
default_par_format.Close();
User::LeaveIfError(error);
if (iTextLayout.EndChar() > 0)
{
TTmReformatParam reformat_param;
reformat_param.iStartChar = aPos;
reformat_param.iOldLength = 0;
reformat_param.iNewLength = aText.Length();
reformat_param.iParFormatChanged = par_format_index != old_par_format_index;
TTmReformatResult result;
iTextLayout.FormatL(iFormatParam,reformat_param,result);
if (aRedrawRect)
*aRedrawRect = result.iRedrawRect;
if (aScroll)
*aScroll = result.iHeightChange;
}
else
{
iTextLayout.SetTextL(*this,iFormatParam);
if (aRedrawRect)
*aRedrawRect = TRect(0,0,iTextLayout.LayoutWidth(),iTextLayout.LayoutHeight());
if (aScroll)
*aScroll = 0;
}
}
void CTmTextImp::ChangeFormatL(const TTmFormatParamBase& aFormatParam)
{
iFormatParam = aFormatParam;
ReformatL();
}
void CTmTextImp::ReformatL()
{
if (iText.Length() > 0)
iTextLayout.SetTextL(*this,iFormatParam);
}
void CTmTextImp::Clear()
{
iText.Reset();
iCharFormatRun.Reset();
iParFormatRun.Reset();
iCharFormat.Reset();
for (int i = 0; i < iParFormat.Count(); i++)
iParFormat[i].Close();
iParFormat.Reset();
iTextLayout.Clear();
}
void CTmTextImp::CustomizeL(const MTmCustom* aCustom)
{
iCustom = aCustom;
ReformatL();
}
TInt CTmTextImp::MemoryUsed() const
{
return sizeof(*this) +
iText.MemoryUsed() - sizeof(iText) +
iCharFormatRun.MemoryUsed() - sizeof(iCharFormatRun) +
iParFormatRun.MemoryUsed() - sizeof(iParFormatRun) +
iCharFormat.MemoryUsed() - sizeof(iCharFormat) +
iParFormat.MemoryUsed() - sizeof(iParFormat) +
iTextLayout.MemoryUsed() - sizeof(iTextLayout);
}
MGraphicsDeviceMap& CTmTextImp::FormatDevice() const
{
return iDevice;
}
MGraphicsDeviceMap& CTmTextImp::InterpretDevice() const
{
return iDevice;
}
TInt CTmTextImp::DocumentLength() const
{
return iText.Length();
}
void CTmTextImp::GetText(TInt aPos,TPtrC& aText,TTmCharFormat& aFormat) const
{
static const TText end_par = CEditableText::EParagraphDelimiter;
TInt index = 0;
TInt textLength = iText.Length();
__ASSERT_DEBUG(aPos <= textLength + 1, TmPanic(EIndexOutOfRange));
if (aPos < textLength)
{
TPtrC p = iText.PtrC(aPos);
TInt pLength = p.Length();
TRun charRun;
TInt charRunStart = iCharFormatRun.RunAndStartPos(aPos, charRun);
TInt remainingLength = charRunStart + charRun.iLength - aPos;
aText.Set(p.Ptr(), remainingLength < pLength? remainingLength : pLength);
index = charRun.iIndex;
}
else if (aPos == textLength)
{
aText.Set(&end_par,1);
if (0 < aPos)
index = iCharFormatRun.Index(aPos - 1);
else
{
TTmCharFormat d;
aFormat = d;
return;
}
}
else
{
aText.Set(0, 0);
return;
}
aFormat = iCharFormat[index];
}
void CTmTextImp::GetParagraphFormatL(TInt aPos,RTmParFormat& aFormat) const
{
__ASSERT_DEBUG(0 <= aPos && aPos <= iText.Length(), TmPanic(EBadArg));
TInt textLength = iText.Length();
if (0 == textLength)
{
RTmParFormat d;
CleanupClosePushL(d);
aFormat.CopyL(d);
CleanupStack::PopAndDestroy(&d);
return;
}
if (aPos != textLength)
++aPos;
aFormat.CopyL(iParFormat[iParFormatRun.Index(aPos)]);
}
TInt CTmTextImp::ParagraphStart(TInt aPos) const
{
while (aPos > 0)
{
TPtrC text = iText.BackPtrC(aPos);
const TText *p = text.Ptr();
const TText *q = p + text.Length();
while (p < q)
{
q--;
aPos--;
if (*q == CEditableText::EParagraphDelimiter)
return aPos + 1;
}
}
return 0;
}
TRgb CTmTextImp::SystemColor(TUint aColorIndex,TRgb aDefaultColor) const
{
if (iCustom)
return iCustom->SystemColor(aColorIndex,aDefaultColor);
else
return MTmSource::SystemColor(aColorIndex,aDefaultColor);
}
TInt CTmTextImp::Stretch(TUint aChar) const
{
if (iCustom)
return iCustom->Stretch(aChar);
else
return MTmSource::Stretch(aChar);
}
TUint CTmTextImp::Map(TUint aChar) const
{
if (iCustom)
return iCustom->Map(aChar);
else
return MTmSource::Map(aChar);
}
void CTmTextImp::SetLineHeight(const TLineHeightParam& aParam,TInt& aAscent,TInt& aDescent) const
{
if (iCustom)
iCustom->SetLineHeight(aParam,aAscent,aDescent);
else
MTmSource::SetLineHeight(aParam,aAscent,aDescent);
}
void CTmTextImp::DrawBackground(CGraphicsContext& aGc,const TPoint& aTextLayoutTopLeft,const TRect& aRect,
const TLogicalRgb& aBackground,TRect& aRectDrawn) const
{
if (iCustom)
iCustom->DrawBackground(aGc,aTextLayoutTopLeft,aRect,aBackground,aRectDrawn);
else
MTmSource::DrawBackground(aGc,aTextLayoutTopLeft,aRect,aBackground,aRectDrawn);
}
void CTmTextImp::DrawLineGraphics(CGraphicsContext& aGc,const TPoint& aTextLayoutTopLeft,const TRect& aRect,
const TTmLineInfo& aLineInfo) const
{
if (iCustom)
iCustom->DrawLineGraphics(aGc,aTextLayoutTopLeft,aRect,aLineInfo);
else
MTmSource::DrawLineGraphics(aGc,aTextLayoutTopLeft,aRect,aLineInfo);
}
void CTmTextImp::DrawText(CGraphicsContext& aGc,const TPoint& aTextLayoutTopLeft,const TRect& aRect,
const TTmLineInfo& aLineInfo,const TTmCharFormat& aFormat,
const TDesC& aText,const TPoint& aTextOrigin,TInt aExtraPixels) const
{
if (iCustom)
iCustom->DrawText(aGc,aTextLayoutTopLeft,aRect,aLineInfo,aFormat,aText,aTextOrigin,aExtraPixels);
else
MTmSource::DrawText(aGc,aTextLayoutTopLeft,aRect,aLineInfo,aFormat,aText,aTextOrigin,aExtraPixels);
}
TInt CTmTextImp::RRunArray::SplitRun(TInt aRunIndex,TInt aOffset)
{
TRun& run = iRun[aRunIndex];
TRun new_run;
new_run.iIndex = run.iIndex;
new_run.iLength = run.iLength - aOffset;
int error = iRun.Insert(new_run, aRunIndex + 1);
if (!error)
run.iLength = aOffset;
return error;
}
/*
Find the run containing aPos. The position at the end of a run is in the run.
The position at the start of a run is NOT in the run, but in the preceding run, if any.
*/
void CTmTextImp::RRunArray::FindRun(TInt aPos,TInt& aRunIndex,TInt& aOffset) const
{
#ifdef _DEBUG
TInt error =
#endif
FindRunNonstrict(aPos, aRunIndex, aOffset);
__ASSERT_DEBUG(!error, TmPanic(ETextRunNotFound));
}
TInt CTmTextImp::RRunArray::FindRunNonstrict(TInt aPos,TInt& aRunIndex,TInt& aOffset) const
{
int n = iRun.Count();
int start = 0;
int end = 0;
for (int i = 0; i < n; i++)
{
__ASSERT_DEBUG(0 <= iRun[i].iLength, TmPanic(EInvalidTextRunLength));
__ASSERT_DEBUG(0 <= iRun[i].iIndex, TmPanic(EInvalidTextRunIndex));
end += iRun[i].iLength;
if (end >= aPos)
{
aRunIndex = i;
aOffset = aPos - start;
return KErrNone;
}
start = end;
}
aRunIndex = aOffset = -1;
return KErrNotFound;
}
/* Find the latest run that starts less than or equal to aPos. Return it
* in aRunOut, and return its start position.
*/
TInt CTmTextImp::RRunArray::RunAndStartPos(TInt aPos, TRun& aRunOut) const
{
TInt runs = iRun.Count();
__ASSERT_DEBUG(0 < runs, TmPanic(ETextRunNotFound));
TInt start = 0;
TInt end = 0;
for (TInt i = 0; i != runs && end <= aPos; ++i)
{
aRunOut = iRun[i];
start = end;
end = start + aRunOut.iLength;
}
return start;
}
// Insert a run of length aLength and attribute aIndex at aPos; if aIndex is < 0, use the index at aPos.
TInt CTmTextImp::RRunArray::Insert(TInt aPos,TInt aLength,TInt aIndex)
{
__ASSERT_DEBUG(0 < aLength, TmPanic(EInvalidTextRunLength));
__ASSERT_DEBUG(0 <= aIndex || 0 < iRun.Count(), TmPanic(EParagraphFormatRequired));
int error = 0;
TRun new_run;
if (iRun.Count() == 0)
{
new_run.iLength = 0;
new_run.iIndex = aIndex;
error = iRun.Append(new_run);
if (error)
return error;
}
// Find the run; see if the index matches; if on a join between runs, check both.
int run_index, offset;
FindRun(aPos,run_index,offset);
if (offset == iRun[run_index].iLength && iRun[run_index].iIndex != aIndex && run_index < iRun.Count() - 1)
{
run_index++;
offset = 0;
}
TRun& run = iRun[run_index];
__ASSERT_DEBUG(0 <= offset && offset <= run.iLength, TmPanic(EInvariant));
// If aIndex is negative, it means don't change the attribute.
if (aIndex < 0)
aIndex = run.iIndex;
// No need to create a new run; just extend the existing one
if (run.iIndex == aIndex)
run.iLength += aLength;
// Split the run if necessary, then add a new run.
else
{
if (run.iLength == offset)
++run_index;
else if (offset != 0)
{
error = SplitRun(run_index,offset);
run_index++;
}
if (!error)
{
new_run.iIndex = aIndex;
new_run.iLength = aLength;
error = iRun.Insert(new_run, run_index);
}
}
return error;
}
TInt CTmTextImp::RRunArray::Set(TInt aPos,TInt aLength,TInt aIndex)
{
__ASSERT_DEBUG(0 <= aLength, TmPanic(EInvalidTextRunLength));
__ASSERT_DEBUG(0 <= aIndex, TmPanic(EInvalidTextRunIndex));
if (aLength == 0)
return KErrNone;
// Find the run start.
int start_run_index, start_run_offset;
FindRun(aPos,start_run_index,start_run_offset);
// Adjust the run start if there is no change to the attribute; return if this eliminates the range to be set.
if (iRun[start_run_index].iIndex == aIndex)
{
int n = iRun[start_run_index].iLength - start_run_offset;
aPos += n;
aLength -= n;
if (aLength <= 0)
return KErrNone;
}
// Find the run end.
int end_run_index, end_run_offset;
FindRun(aPos + aLength,end_run_index,end_run_offset);
// Adjust the run end if there is no change to the attribute; return if this eliminates the range to be set.
if (iRun[end_run_index].iIndex == aIndex)
{
aLength -= end_run_offset;
if (aLength <= 0)
return KErrNone;
}
// Determine the change in the number of runs. The maximum increase is 2.
int runs_increase = start_run_index - end_run_index + 2;
// Insert extra runs if needed.
int error = KErrNone;
if (runs_increase > 0)
{
error = SplitRun(start_run_index,start_run_offset);
runs_increase--;
if (start_run_index == end_run_index)
end_run_offset -= start_run_offset;
end_run_index++;
}
if (!error && runs_increase > 0)
{
error = SplitRun(end_run_index,end_run_offset);
end_run_index++;
end_run_offset = 0;
runs_increase--;
}
if (!error)
{
// Delete unneeded runs.
while (runs_increase < 0)
{
iRun.Remove(start_run_index + 2);
end_run_index--;
++runs_increase;
}
// Adjust the size of the start run.
iRun[start_run_index].iLength = start_run_offset;
// Set the run after the start run to the new attribute.
TRun& run = iRun[start_run_index + 1];
run.iLength = aLength;
run.iIndex = aIndex;
// Adjust the size of the end run.
iRun[end_run_index].iLength -= end_run_offset;
}
return error;
}
void CTmTextImp::RRunArray::Delete(TInt aPos,TInt aLength)
{
__ASSERT_DEBUG(0 <= aLength, TmPanic(EInvalidTextRunLength));
if (aLength == 0)
return;
// Find the start.
int start_run_index, start_run_offset;
FindRun(aPos,start_run_index,start_run_offset);
// Find the end.
int end_run_index, end_run_offset;
FindRun(aPos + aLength,end_run_index,end_run_offset);
// If the runs are the same it's trivial.
if (start_run_index == end_run_index)
{
iRun[start_run_index].iLength -= (end_run_offset - start_run_offset);
return;
}
// Shorten the start and end runs and determine the range of runs to delete.
iRun[start_run_index].iLength = start_run_offset;
int first_deleted_run = start_run_index + 1;
if (start_run_offset == 0)
first_deleted_run--;
iRun[end_run_index].iLength -= end_run_offset;
int last_deleted_run = end_run_index - 1;
if (iRun[end_run_index].iLength == 0)
last_deleted_run++;
// Delete runs.
for (int i = first_deleted_run; i <= last_deleted_run; i++)
iRun.Remove(first_deleted_run);
}
TInt CTmTextImp::RRunArray::Index(TInt aPos) const
{
int run_index, offset;
TInt error = FindRunNonstrict(aPos,run_index,offset);
return error? error : iRun[run_index].iIndex;
}
TInt CTmTextImp::RRunArray::MemoryUsed() const
{
TInt bytes = sizeof(*this);
if (iRun.Count())
bytes += User::AllocLen(&iRun[0]) + 8;
return bytes;
}
EXPORT_C void CTmText::Spare1()
{
TmPanic(EUnimplemented);
}