libraries/iosrv/client/text_formatter.cpp
author Tom Sutcliffe <thomas.sutcliffe@accenture.com>
Thu, 09 Sep 2010 15:47:34 +0100
changeset 57 683f4b1f08ce
parent 0 7f656887cf89
permissions -rw-r--r--
merge

// text_formatter.cpp
// 
// Copyright (c) 2006 - 2010 Accenture. All rights reserved.
// This component and the accompanying materials are made available
// under the terms of the "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:
// Accenture - Initial contribution
//

#include <e32base.h>
#include "ioutils.h"
#include "pod_lexer.h"
#include "pod_formatter.h"

using namespace IoUtils;


//
// Constants.
//

_LIT(KSpace, " ");
_LIT(KNewLine, "\r\n");


//
// CTextBuffer.
//

EXPORT_C CTextBuffer* CTextBuffer::NewL(TInt aExpandSize)
	{
	CTextBuffer* self = CTextBuffer::NewLC(aExpandSize);
	CleanupStack::Pop(self);
	return self;
	}

EXPORT_C CTextBuffer* CTextBuffer::NewLC(TInt aExpandSize)
	{
	CTextBuffer* self = new(ELeave) CTextBuffer();
	CleanupStack::PushL(self);
	self->ConstructL(aExpandSize);
	return self;
	}

EXPORT_C CTextBuffer::~CTextBuffer()
	{
	Cancel();
	delete iBuf;
	delete iScratchBuf;
	delete iScratchBuf8;
	iAttributes.Close();
	}

EXPORT_C void CTextBuffer::Zero()
	{
	iBuf->Delete(0, iBuf->Size());
	iAttributes.Reset();
	}

EXPORT_C void CTextBuffer::Reset()
	{
	iBuf->Reset();
	iAttributes.Reset();
	}

EXPORT_C void CTextBuffer::ResetText()
	{
	iBuf->Reset();
	}

EXPORT_C void CTextBuffer::SetAttributesL(TUint aAttributes, ConsoleAttributes::TColor aForegroundColor, ConsoleAttributes::TColor aBackgroundColor)
	{
	const TInt numAttributes = iAttributes.Count();
	if (numAttributes == 0)
		{
		if (Length() == 0)
			{
			TAttributes attributes(0, aAttributes, aForegroundColor, aBackgroundColor);
			iAttributes.AppendL(attributes);
			}
		else
			{
			TAttributes attributes(Length(), aAttributes, aForegroundColor, aBackgroundColor);
			if (!attributes.Matches(ConsoleAttributes::ENone, ConsoleAttributes::EUnchanged, ConsoleAttributes::EUnchanged))
				{
				TAttributes defaultAttributes(0, ConsoleAttributes::ENone, ConsoleAttributes::EUnchanged, ConsoleAttributes::EUnchanged);
				iAttributes.AppendL(defaultAttributes);
				TInt err = iAttributes.Append(attributes);
				if (err != KErrNone)
					{
					iAttributes.Reset();
					User::Leave(err);
					}
				}
			}
		}
	else if (iAttributes[numAttributes - 1].Matches(aAttributes, aForegroundColor, aBackgroundColor))
		{
		// Do nothing.
		}
	else if (iAttributes[numAttributes - 1].iPosition == Length())
		{
		TAttributes& att = iAttributes[numAttributes - 1];
		if (aAttributes & ConsoleAttributes::ENone)
			{
			att.iAttributes.iAttributes = aAttributes;
			att.iAttributes.iForegroundColor = aForegroundColor;
			att.iAttributes.iBackgroundColor = aBackgroundColor;
			}
		else
			{
			// Merge on top of existing attributes
			att.iAttributes.iAttributes |= aAttributes;
			if (aForegroundColor != ConsoleAttributes::EUnchanged) att.iAttributes.iForegroundColor = aForegroundColor;
			if (aBackgroundColor != ConsoleAttributes::EUnchanged) att.iAttributes.iBackgroundColor = aBackgroundColor;
			}
		}
	else
		{
		TAttributes attributes(Length(), aAttributes, aForegroundColor, aBackgroundColor);
		iAttributes.AppendL(attributes);
		}
	}

EXPORT_C void CTextBuffer::SetAttributesL(const ConsoleAttributes::TAttributes& aAttributes)
	{
	SetAttributesL(aAttributes.iAttributes, aAttributes.iForegroundColor, aAttributes.iBackgroundColor);
	}

EXPORT_C void CTextBuffer::GetCurrentAttributes(TUint& aAttributes, ConsoleAttributes::TColor& aForegroundColor, ConsoleAttributes::TColor& aBackgroundColor) const
	{
	if (iAttributes.Count() > 0)
		{
		const TAttributes& att = iAttributes[iAttributes.Count() - 1];
		aAttributes = att.iAttributes.iAttributes;
		aForegroundColor = att.iAttributes.iForegroundColor;
		aBackgroundColor = att.iAttributes.iBackgroundColor;
		}
	else
		{
		aAttributes = ConsoleAttributes::ENone;
		aForegroundColor = ConsoleAttributes::EUnchanged;
		aBackgroundColor = ConsoleAttributes::EUnchanged;
		}
	}

EXPORT_C void CTextBuffer::GetAttributes(TInt aPos, TUint& aAttributes, ConsoleAttributes::TColor& aForegroundColor, ConsoleAttributes::TColor& aBackgroundColor) const
	{
	ASSERT(aPos < Length());

	aAttributes = ConsoleAttributes::ENone;
	aForegroundColor = ConsoleAttributes::EUnchanged;
	aBackgroundColor = ConsoleAttributes::EUnchanged;

	const TInt numAttributes = iAttributes.Count();
	for (TInt i = 0; i < numAttributes; ++i)
		{
		const TAttributes& att = iAttributes[i];
		if (aPos < att.iPosition)
			{
			return;
			}
		else
			{
			aAttributes = att.iAttributes.iAttributes;
			aForegroundColor = att.iAttributes.iForegroundColor;
			aBackgroundColor = att.iAttributes.iBackgroundColor;
			}
		}
	}

EXPORT_C void CTextBuffer::AppendL(const TChar& aChar)
	{
	TUint16 ch = (TUint16)TUint(aChar); // We don't support surrogate pairs
	iBuf->InsertL(iBuf->Size(), &ch, 2);
	}

EXPORT_C void CTextBuffer::AppendL(const TDesC& aText)
	{
	iBuf->InsertL(iBuf->Size(), aText.Ptr(), aText.Size());
	}

EXPORT_C void CTextBuffer::AppendL(const TDesC8& aText)
	{
	if (!iScratchBuf)
		{
		iScratchBuf = HBufC::NewL(Max(aText.Length(), 256));
		}
	if (iScratchBuf->Des().MaxLength() < aText.Length())
		{
		iScratchBuf = iScratchBuf->ReAllocL(Max(iScratchBuf->Des().MaxLength()*2, aText.Length()));
		}
	iScratchBuf->Des().Copy(aText);	
	iBuf->InsertL(iBuf->Size(), iScratchBuf->Ptr(), iScratchBuf->Size());
	}

EXPORT_C void CTextBuffer::AppendL(const CTextBuffer& aText)
	{
	AppendL(aText, 0, aText.Length());
	}

EXPORT_C void CTextBuffer::AppendL(const CTextBuffer& aText, TInt aPosition)
	{
	AppendL(aText, aPosition, aText.Length() - aPosition);
	}

EXPORT_C void CTextBuffer::AppendL(const CTextBuffer& aText, TInt aPosition, TInt aLength)
	{
	const TInt numAttributes = aText.iAttributes.Count();
	if (numAttributes > 0)
		{
		TInt textPos = 0;
		TInt appendCount = 0;
		TInt blockIndex = 0;
		const TDesC* block = NULL;
		do
			{
			TUint attributes;
			ConsoleAttributes::TColor foregroundColor;
			ConsoleAttributes::TColor backgroundColor;
			aText.NextBlock(blockIndex, block, attributes, foregroundColor, backgroundColor);
			if (block != NULL)
				{
				if ((textPos + block->Length()) > aPosition)
					{
					SetAttributesL(attributes, foregroundColor, backgroundColor);
					TInt blockOffset = aPosition - textPos;
					if (blockOffset < 0)
						{
						blockOffset = 0;
						}
					TPtrC appendBlock(block->Mid(blockOffset, Min(aLength - appendCount, block->Length() - blockOffset)));
					AppendL(appendBlock);
					appendCount += appendBlock.Length();
					if (appendCount == aLength)
						{
						break;
						}
					}
				textPos += block->Length();
				}
			}
			while (block != NULL);
		}
	else
		{
		AppendL(aText.Descriptor().Mid(aPosition, aLength));
		}
	}

EXPORT_C void CTextBuffer::AppendFormatL(TRefByValue<const TDesC> aFmt, ...)
	{
	VA_LIST list;
	VA_START(list, aFmt);
	AppendFormatListL(aFmt, list);
	}

EXPORT_C void CTextBuffer::AppendFormatListL(const TDesC16& aFmt, VA_LIST& aList)
	{
	TOverflowLeave overflow;
	TInt err = KErrNone;
	do
		{
		TPtr ptr(iScratchBuf->Des());
		ptr.Zero();
		TRAP(err, ptr.AppendFormatList(aFmt, aList, &overflow));
		if (err == KErrOverflow)
			{
			iScratchBuf = iScratchBuf->ReAllocL(ptr.MaxLength() * 2);
			}
		}
		while (err == KErrOverflow);

	User::LeaveIfError(err);
	iBuf->InsertL(iBuf->Size(), iScratchBuf->Ptr(), iScratchBuf->Size());
	}
	
EXPORT_C void CTextBuffer::AppendFormatL(TRefByValue<const TDesC8> aFmt, ...)
	{
	VA_LIST list;
	VA_START(list, aFmt);
	AppendFormatListL(aFmt, list);
	}
	
EXPORT_C void CTextBuffer::AppendFormatListL(const TDesC8& aFmt, VA_LIST& aList)
	{
	TOverflowLeave8 overflow;
	TInt err = KErrNone;
	if (!iScratchBuf8)
		{
		iScratchBuf8 = HBufC8::NewL(0x100);
		}
		
	do
		{
		TPtr8 ptr(iScratchBuf8->Des());
		ptr.Zero();
		TRAP(err, ptr.AppendFormatList(aFmt, aList, &overflow));
		if (err == KErrOverflow)
			{
			iScratchBuf8 = iScratchBuf8->ReAllocL(ptr.MaxLength() * 2);
			}
		}
		while (err == KErrOverflow);

	User::LeaveIfError(err);
	
	TPtr8 ptr(iScratchBuf8->Des());
	if (iScratchBuf->Des().MaxLength() < ptr.Length())
		{
		iScratchBuf = iScratchBuf->ReAllocL(Max(iScratchBuf->Des().MaxLength()*2, ptr.Length()));
		}
	iScratchBuf->Des().Copy(*iScratchBuf8);	
	
	iBuf->InsertL(iBuf->Size(), iScratchBuf->Ptr(), iScratchBuf->Size());
	}

EXPORT_C void CTextBuffer::AppendHumanReadableSizeL(TInt aSize)
	{
	AppendHumanReadableSizeL(TInt64(aSize), EColumnAlignedRight);
	}

EXPORT_C void CTextBuffer::AppendHumanReadableSizeL(TInt64 aSize)
	{
	AppendHumanReadableSizeL(aSize, EColumnAlignedRight);
	}

EXPORT_C void CTextBuffer::AppendHumanReadableSizeL(TInt aSize, TAlignment aAlignment)
	{
	AppendHumanReadableSizeL(TInt64(aSize), aAlignment);
	}

EXPORT_C void CTextBuffer::AppendHumanReadableSizeL(TInt64 aSize, TAlignment aAlignment)
	{
	const TInt64 KB = 1024;
	const TInt64 MB = KB * KB;
	const TInt64 GB = MB * KB;
	const TInt64 TB = GB * KB;
	_LIT(KBytes, " B ");
	_LIT(KKilobytes, " KB");
	_LIT(KMegabytes, " MB");
	_LIT(KGigabytes, " GB");
	_LIT(KTerabytes, " TB");

	_LIT(KLeftWhole, "%- 7.0f");
	_LIT(KLeftFrac, "%- 7.2f");
	_LIT(KRightWhole, "%+ 7.0f");
	_LIT(KRightFrac, "%+ 7.2f");
	_LIT(KNormalWhole, "%.0f");
	_LIT(KNormalFrac, "%.2f");

	const TDesC* suff = &KBytes;
	TReal n = aSize;
	TInt64 factor = 1;

	TInt64 absSize = aSize;
	if (absSize < 0) absSize = -absSize;

	if (absSize >= TB)
		{
		suff = &KTerabytes;
		factor = TB;
		}
	else if (absSize >= GB)
		{
		suff = &KGigabytes;
		factor = GB;
		}
	else if (absSize >= MB)
		{
		suff = &KMegabytes;
		factor = MB;
		}
	else if (absSize >= KB)
		{
		suff = &KKilobytes;
		factor = KB;
		}

	n = n / (TReal)factor;
	TBool wholeNumUnits = (absSize & (factor-1)) == 0; // ie aSize % factor == 0

	const TDesC* fmt = NULL;
	if (aAlignment == EColumnAlignedLeft)
		{
		fmt = wholeNumUnits ? &KLeftWhole : &KLeftFrac;
		}
	else if (aAlignment == EColumnAlignedRight)
		{
		fmt = wholeNumUnits ? &KRightWhole : &KRightFrac;
		}
	else
		{
		fmt = wholeNumUnits ? &KNormalWhole : &KNormalFrac;
		}

	AppendFormatL(*fmt, n);
	AppendL(*suff);
	}

EXPORT_C void CTextBuffer::AppendSpacesL(TInt aCount)
	{
	ASSERT(aCount >= 0);
	while (aCount > 0)
		{
		AppendL(KSpace);
		--aCount;
		}
	}

EXPORT_C void CTextBuffer::Delete(TInt aPos, TInt aLength)
	{
	//for (TInt i = 0; i < iAttributes.Count(); i++)
	//	{
	//	RDebug::Printf("attribute %d: pos %d", i, iAttributes[i].iPosition);
	//	}

	// Find the first and last attributes that are inside the delete region.
	TInt firstAttribute = -1;
	TInt lastAttribute = -1;
	TInt numAttributes = iAttributes.Count();
	for (TInt i = (numAttributes - 1); i >= 0; --i)
		{
		const TAttributes& thisAttribute = iAttributes[i];
		if ((lastAttribute == -1) && (thisAttribute.iPosition >= aPos) && (thisAttribute.iPosition < aPos + aLength))
			{
			lastAttribute = i;
			}
		if (lastAttribute >= 0 && thisAttribute.iPosition >= aPos)
			{
			firstAttribute = i;
			}
		}
	//RDebug::Printf("firstAttribute=%d lastAttribute=%d", firstAttribute, lastAttribute);

	if ((lastAttribute > 0))
		{
		// Set the position of the last affected attribute to just after the deleted chunk.
		iAttributes[lastAttribute].iPosition = aPos + aLength;

		// Remove all but the last affected attribute. Need to keep the last affected attribute
		// because it has an impact on the formatting of text that appears after the deleted chunk.
		for (TInt i = (lastAttribute - 1); i >= firstAttribute; --i)
			{
			iAttributes.Remove(i);
			}
		}

	// Update the positions of attributes after the block being deleted.
	numAttributes = iAttributes.Count();
	for (TInt i = Max(0, firstAttribute); i < numAttributes; ++i)
		{
		TAttributes& thisAttribute = iAttributes[i];
		if (thisAttribute.iPosition >= aPos + aLength)
			{
			thisAttribute.iPosition -= aLength;
			}
		}
	iBuf->Delete(aPos * 2, aLength * 2);
	}

EXPORT_C TInt CTextBuffer::Length() const
	{
	return iBuf->Size() / 2;
	}

EXPORT_C const TDesC& CTextBuffer::Descriptor() const
	{
	TPtr8 narrowPtr(iBuf->Ptr(0));
	iPtr.Set((const TUint16 *)narrowPtr.Ptr(), narrowPtr.Length() / 2);
	return iPtr;
	}

EXPORT_C const TDesC& CTextBuffer::Descriptor(TInt aPos, TInt aLength) const
	{
	TPtr8 narrowPtr(iBuf->Ptr(aPos * 2));
	iPtr.Set((const TUint16 *)narrowPtr.Ptr(), aLength);
	return iPtr;
	}
	
EXPORT_C TPtrC8 CTextBuffer::Collapse()
	{
	TPtr8 narrowPtr(iBuf->Ptr(0));
	TPtr ptr((TUint16 *)narrowPtr.Ptr(), narrowPtr.Length() / 2, narrowPtr.Length() / 2);
	return ptr.Collapse();
	}

EXPORT_C TInt CTextBuffer::Write(RIoWriteHandle& aWriteHandle) const
	{
	return Write(aWriteHandle, 0, Length());
	}

EXPORT_C void CTextBuffer::Write(RIoWriteHandle& aWriteHandle, TRequestStatus& aStatus) const
	{
	Write(aWriteHandle, 0, Length(), aStatus);
	}

EXPORT_C TInt CTextBuffer::Write(RIoWriteHandle& aWriteHandle, TInt aPosition, TInt aLength) const
	{
	const TInt numAttributes = iAttributes.Count();
	if ((aWriteHandle.AttachedToConsole() > 0) && (numAttributes > 0))
		{
		// Write the text buffer to the console a block at a time, updating the console's attributes at the beginning of each block.
		TInt blockIndex = 0;
		TInt textPos = 0;
		TInt writeCount = 0;
		const TDesC* block = NULL;
		do
			{
			TUint attributes;
			ConsoleAttributes::TColor foregroundColor;
			ConsoleAttributes::TColor backgroundColor;
			NextBlock(blockIndex, block, attributes, foregroundColor, backgroundColor);
			if (block != NULL)
				{
				if ((textPos + block->Length()) > aPosition)
					{
					RIoConsoleWriteHandle(aWriteHandle).SetAttributes(attributes, foregroundColor, backgroundColor); // Ignore error - may not be supported.
					TInt blockOffset = aPosition - textPos;
					if (blockOffset < 0)
						{
						blockOffset = 0;
						}
					TPtrC toWrite(block->Mid(blockOffset, Min(aLength - writeCount, block->Length() - blockOffset)));
					TInt err = aWriteHandle.Write(toWrite);
					if (err)
						{
						return err;
						}
					writeCount += toWrite.Length();
					if (writeCount == aLength)
						{
						break;
						}
					}
				textPos += block->Length();
				}
			}
			while (block != NULL);
		}
	else
		{
		return aWriteHandle.Write(Descriptor().Mid(aPosition, aLength));
		}

	return KErrNone;
	}

EXPORT_C void CTextBuffer::Write(RIoWriteHandle& aWriteHandle, TInt aPosition, TInt aLength, TRequestStatus& aStatus) const
	{
	const TInt numAttributes = iAttributes.Count();
	if ((aWriteHandle.AttachedToConsole() > 0) && (numAttributes > 0))
		{
		// Write the text buffer asynchronously to the console a block at a time, updating the console's attributes at the beginning of each block.
		if (!IsAdded())
			{
			CActiveScheduler::Add(const_cast<CTextBuffer*>(this));
			}
		aStatus = KRequestPending;
		iWriteStatus = &aStatus;
		iAsyncBlockIndex = 0;
		iAsyncWritePos = 0;
		iAsyncWriteStartPos = aPosition;
		iAsyncWriteLength = aLength;
		iConsoleWriteHandle = aWriteHandle;
		AsyncWriteNextBlock();
		}
	else
		{
		aWriteHandle.Write(Descriptor().Mid(aPosition, aLength), aStatus);
		}
	}

CTextBuffer::CTextBuffer()
	: CActive(CActive::EPriorityStandard), iPtr(NULL, 0)
	{
	}

void CTextBuffer::ConstructL(TInt aExpandSize)
	{
	iBuf = CBufFlat::NewL(aExpandSize);
	iScratchBuf = HBufC::NewL(aExpandSize);
	}

void CTextBuffer::NextBlock(TInt& aBlockIndex, const TDesC*& aText, TUint& aAttributes, ConsoleAttributes::TColor& aForegroundColor, ConsoleAttributes::TColor& aBackgroundColor) const
	{
	if (iAttributes.Count() > 0)
		{
		if (aBlockIndex < iAttributes.Count())
			{
			const TAttributes& thisAttribute = iAttributes[aBlockIndex];
			aAttributes = thisAttribute.iAttributes.iAttributes;
			aForegroundColor = thisAttribute.iAttributes.iForegroundColor;
			aBackgroundColor = thisAttribute.iAttributes.iBackgroundColor;
			TInt length;
			if (aBlockIndex < (iAttributes.Count() - 1))
				{
				length = iAttributes[aBlockIndex + 1].iPosition - thisAttribute.iPosition;
				}
			else
				{
				length = Length() - thisAttribute.iPosition;
				}
			aText = &Descriptor(thisAttribute.iPosition, length);
			++aBlockIndex;
			}
		else
			{
			aText = NULL;
			}
		}
	else
		{
		// There are no attributes, so block zero is the whole buffer.
		ASSERT((aBlockIndex == 0) || (aBlockIndex == 1));
		if (aBlockIndex == 0)
			{
			aAttributes = ConsoleAttributes::ENone;
			aForegroundColor = ConsoleAttributes::EUnchanged;
			aBackgroundColor = ConsoleAttributes::EUnchanged;
			aText = &Descriptor(0, Length());
			++aBlockIndex;
			}
		else
			{
			aText = NULL;
			}
		}
	}

void CTextBuffer::AsyncWriteNextBlock() const
	{
	const TDesC* block = NULL;
	TUint attributes;
	ConsoleAttributes::TColor foregroundColor;
	ConsoleAttributes::TColor backgroundColor;
	NextBlock(iAsyncBlockIndex, block, attributes, foregroundColor, backgroundColor);
	if (block != NULL)
		{
		if ((iAsyncWritePos + block->Length()) > iAsyncWriteStartPos)
			{
			iConsoleWriteHandle.SetAttributes(attributes, foregroundColor, backgroundColor); // Ignore error - may not be supported.
			TInt blockOffset = iAsyncWriteStartPos - iAsyncWritePos;
			if (blockOffset < 0)
				{
				blockOffset = 0;
				}
			iAsyncWritePtr.Set(block->Mid(blockOffset, Min(iAsyncWriteLength, block->Length() - blockOffset)));
			iAsyncWriteLength -= iAsyncWritePtr.Length();
			iConsoleWriteHandle.Write(iAsyncWritePtr, const_cast<TRequestStatus&>(iStatus));
			const_cast<CTextBuffer*>(this)->SetActive();
			}
		iAsyncWritePos += block->Length();
		}
	else
		{
		User::RequestComplete(iWriteStatus, KErrNone);
		}
	}

void CTextBuffer::RunL()
	{
	if (iStatus.Int() || (iAsyncWriteLength <= 0))
		{
		User::RequestComplete(iWriteStatus, iStatus.Int());
		}
	else
		{
		AsyncWriteNextBlock();
		}
	}

void CTextBuffer::DoCancel()
	{
	iConsoleWriteHandle.WriteCancel();
	}

CTextBuffer::TAttributes::TAttributes(TInt aPosition, TUint aAttributes, ConsoleAttributes::TColor aForegroundColor, ConsoleAttributes::TColor aBackgroundColor)
	: iPosition(aPosition), iAttributes(aAttributes, aForegroundColor, aBackgroundColor)
	{
	}

TBool CTextBuffer::TAttributes::Matches(TUint aAttributes, ConsoleAttributes::TColor aForegroundColor, ConsoleAttributes::TColor aBackgroundColor) const
	{
	return ((aAttributes == iAttributes.iAttributes) && (aForegroundColor == iAttributes.iForegroundColor) && (aBackgroundColor == iAttributes.iBackgroundColor));
	}


//
// CTextFormatter.
//

EXPORT_C CTextFormatter* CTextFormatter::NewL(TInt aAvailableWidth)
	{
	CTextFormatter* self = CTextFormatter::NewLC(aAvailableWidth);
	CleanupStack::Pop(self);
	return self;
	}

EXPORT_C CTextFormatter* CTextFormatter::NewLC(TInt aAvailableWidth)
	{
	CTextFormatter* self = new(ELeave) CTextFormatter(aAvailableWidth);
	CleanupStack::PushL(self);
	self->ConstructL();
	return self;
	}

EXPORT_C CTextFormatter* CTextFormatter::NewL(RIoConsoleWriteHandle& aConsoleWriteHandle)
	{
	CTextFormatter* self = CTextFormatter::NewLC(aConsoleWriteHandle);
	CleanupStack::Pop(self);
	return self;
	}

EXPORT_C CTextFormatter* CTextFormatter::NewLC(RIoConsoleWriteHandle& aConsoleWriteHandle)
	{
	TSize size;
	if (aConsoleWriteHandle.AttachedToConsole())
		{
		User::LeaveIfError(aConsoleWriteHandle.GetScreenSize(size));
		}
	else
		{
		size.iWidth = 80;
		}
	CTextFormatter* self = CTextFormatter::NewLC(size.iWidth);
	self->iWriteHandle = &aConsoleWriteHandle;
	self->iAttributesSupported = (aConsoleWriteHandle.SetAttributes(0) == KErrNone);
	return self;
	}

EXPORT_C CTextFormatter::~CTextFormatter()
	{
	}

CTextFormatter::CTextFormatter(TInt aAvailableWidth)
	: iAvailableWidth(aAvailableWidth - 1)
	{
	}

void CTextFormatter::ConstructL()
	{
	CTextBuffer::ConstructL(0x100);
	}

EXPORT_C void CTextFormatter::WrapL(TInt aIndent, const TDesC& aText)
	{
	AppendSpacesL(aIndent);
	WrapL(aIndent, aIndent, aText);
	}

EXPORT_C void CTextFormatter::WrapL(TInt aStartPosition, TInt aIndent, const TDesC& aText)
	{
	TUint originalAttributes = ConsoleAttributes::ENone;
	ConsoleAttributes::TColor foregroundColor = ConsoleAttributes::EUnchanged;
	ConsoleAttributes::TColor backgroundColor = ConsoleAttributes::EUnchanged;
	if (iAttributesSupported)
		{
		GetCurrentAttributes(originalAttributes, foregroundColor, backgroundColor);
		}

	CTextBuffer* buffer = CTextBuffer::NewLC(0x100);
	DecodeInteriorPodSequencesL(aText, *buffer);
	TLex lex(buffer->Descriptor());
	TInt linePos = aStartPosition;
	while (!lex.Eos())
		{
		lex.Mark();
		lex.SkipSpace();
		TPtrC whiteSpace(lex.MarkedToken());
		if (whiteSpace.Length() > 0)
			{
			TInt newLinePos;
			do
				{
				newLinePos = whiteSpace.Find(KNewLine);
				if (newLinePos >= 0)
					{
					AppendSpacesL(iAvailableWidth - linePos - 1);
					AppendL(KNewLine);
					AppendSpacesL(aIndent);
					linePos = aIndent;
					whiteSpace.Set(whiteSpace.Mid(newLinePos + KNewLine().Length()));
					}
				}
				while (newLinePos >= 0);

			if (iAvailableWidth < whiteSpace.Length())
				{
				AppendSpacesL(iAvailableWidth - linePos - 1);
				AppendL(KNewLine);
				AppendSpacesL(aIndent);
				linePos = aIndent;
				}
			else
				{
				AppendL(whiteSpace);
				linePos += whiteSpace.Length();
				}
			}

		lex.Mark();
		lex.SkipCharacters();
		TPtrC word(lex.MarkedToken());
		const TInt wordLength = word.Length();
		if (wordLength > 0)
			{
			if ((linePos + wordLength + KNewLine().Length()) > iAvailableWidth)
				{
				AppendL(KNewLine);
				AppendSpacesL(aIndent);
				linePos = aIndent;
				}

			if (iAttributesSupported)
				{
				for (TInt i = 0; i < wordLength; ++i)
					{
					TUint attributes;
					buffer->GetAttributes(lex.Offset() - wordLength + i, attributes, foregroundColor, backgroundColor);
					SetAttributesL(originalAttributes | attributes, foregroundColor, backgroundColor);
					AppendL(word[i]);
					}
				}
			else
				{
				AppendL(word);
				}
			linePos += wordLength;
			}
		}

	CleanupStack::PopAndDestroy(buffer);
	SetAttributesL(originalAttributes, foregroundColor, backgroundColor);
	}

EXPORT_C void CTextFormatter::TabulateL(TInt aIndent, TInt aGap, const TDesC& aText)
	{
	TabulateL(aIndent, aGap, aText, EWrapLastColumn);
	}

EXPORT_C void CTextFormatter::TabulateL(TInt aIndent, TInt aGap, const TDesC& aText, TTabMode aMode)
	{
	CTextBuffer* scratchBuffer = NULL;
	RArray<TInt> columnWidths;
	CleanupClosePushL(columnWidths);
	TLex lineLexer(aText);

	// Find column widths.
	TPtrC line;
	while (NextLine(lineLexer, line))
		{
		TLex columnLexer(line);
		TInt col = 0;
		TPtrC column;
		while (NextColumn(columnLexer, column))
			{
			if (col >= columnWidths.Count())
				{
				User::LeaveIfError(columnWidths.Append(aGap));
				}
			TInt thisColumnWidth = ActualLength(column);
			if (columnWidths[col] < (thisColumnWidth + aGap))
				{
				columnWidths[col] = (thisColumnWidth + aGap);
				}
			++col;
			}
		}

	const TInt numCols = columnWidths.Count();

	if (numCols == 0)
		{
		// No columns found, so just copy aText directly.
		AppendL(aText);
		CleanupStack::PopAndDestroy(&columnWidths);
		return;
		}

	if (aMode == EWrapLastColumn)
		{
		// Find the widest of all but the last column.
		TInt sum = aIndent;
		for (TInt i = 0; i < (numCols - 1); ++i)
			{
			sum += columnWidths[i];
			}
		TInt remainingSpace = iAvailableWidth - sum;
		if (remainingSpace < 0)
			{
			// If you hit this, it means that the text cannot be columnised because the columns preceding the last one take up more space than the overall.
			User::Leave(KErrTooBig); // Let's go for something marginally more descriptive than KErrGeneral
			}
		// Assign the remaining space to the last column (which will be wrapped).
		columnWidths[numCols - 1] = remainingSpace; 
		}
	else if (aMode == ETruncateLongestColumn)
		{
		// Find the total width of all the columns.
		TInt sum = aIndent;
		for (TInt i = 0; i < numCols; ++i)
			{
			sum += columnWidths[i];
			}
		TInt excess = iAvailableWidth - sum;
		if (excess < 0)
			{
			// Not enough space, so steal from the widest column until there is.
			while (excess < 0)
				{
				TInt widestColumn = -1;
				TInt widestColumnWidth = 0;
				for (TInt i = 0; i < numCols; ++i)
					{
					if (columnWidths[i] > widestColumnWidth)
						{
						widestColumnWidth = columnWidths[i];
						widestColumn = i;
						}
					}
				if (widestColumn < 0)
					{
					User::Leave(KErrGeneral);
					}
				--columnWidths[widestColumn];
				++excess;
				}
			}
		else
			{
			// Assign execess to the last column.
			columnWidths[numCols - 1] += excess; 
			}
		}
	else if (aMode == EIgnoreAvailableWidth)
		{
		// just use the column widths we've already worked out, no matter if they're too wide.
		}
	else
		{
		ASSERT(EFalse);
		}

	// Write formatted text to the buffer.
	lineLexer = TLex(aText);
	TInt linePos = 0;
	while (NextLine(lineLexer, line))
		{
		AppendSpacesL(aIndent);
		linePos += aIndent;
		TLex columnLexer(line);
		TInt col = 0;
		TPtrC column;
		while (NextColumn(columnLexer, column))
			{
			TBool isLastColumn(col == (numCols - 1));
			const TInt columnWidth = columnWidths[col];
			if ((aMode == ETruncateLongestColumn) || !isLastColumn)
				{
				if (scratchBuffer == NULL)
					{
					scratchBuffer = CTextBuffer::NewLC(0x100);
					}
				else
					{
					scratchBuffer->Zero();
					}
				DecodeInteriorPodSequencesL(column, *scratchBuffer);
				TInt availableWidth = columnWidth;
				if (!isLastColumn)
					{
					availableWidth -= aGap;
					}
				if (scratchBuffer->Descriptor().Length() > availableWidth)
					{
					scratchBuffer->Delete(availableWidth, scratchBuffer->Descriptor().Length() - availableWidth);
					}
				AppendL(*scratchBuffer);
				AppendSpacesL(columnWidth - scratchBuffer->Descriptor().Length());
				linePos += columnWidth;
				}
			else if (aMode == EIgnoreAvailableWidth)
				{
				AppendL(column);
				if (!isLastColumn)
					{
					AppendSpacesL(columnWidth - column.Length());
					}
				linePos += columnWidth;
				}
			else
				{
				WrapL(linePos, linePos, column);
				}
			++col;
			}
		AppendL(KNewLine);
		linePos = 0;
		}

	if (scratchBuffer)
		{
		CleanupStack::PopAndDestroy(scratchBuffer);
		}
	CleanupStack::PopAndDestroy(&columnWidths);
	}

EXPORT_C void CTextFormatter::ColumnizeL(TInt aIndent, TInt aGap, const TDesC& aText)
	{
	// Store the tab delimited text as an array of TPtrCs.
	RArray<TPtrC> items;
	CleanupClosePushL(items);
	TLex lex(aText);
	TPtrC column;
	while (NextColumn(lex, column))
		{
		User::LeaveIfError(items.Append(column));
		}
	ColumnizeL(aIndent, aGap, items.Array());
	CleanupStack::PopAndDestroy(1, &items);
	}

EXPORT_C void CTextFormatter::ColumnizeL(TInt aIndent, TInt aGap, const TArray<TPtrC>& aItems)
	{
	// Lays out tab delimited data as a set of columns that read like a news paper
	// (i.e. starting with the left most column, you read down to the bottom of it
	// and then start reading the next column from the top, etc.).

	// Determine the smallest number of rows that allows all the data to fit
	// the available horizontal space. The algorithm works by iteratively increasing
	// the number of rows from one until a fit is found.
	const TInt numItems = aItems.Count();
	RArray<TInt> columnWidths;
	CleanupClosePushL(columnWidths);
	TInt numRows = 0;
	TBool fits(EFalse);
	while (!fits)
		{
		++numRows;
		columnWidths.Reset();
		TInt requiredWidth = aIndent;
		TInt col = 0;
		TInt i = 0;
		while (i < numItems)
			{
			for (TInt row = 0; row < numRows; ++row)
				{
				if (columnWidths.Count() <= col)
					{
					User::LeaveIfError(columnWidths.Append(0));
					}
				TInt thisItemWidth = ActualLength(aItems[i]);
				if (thisItemWidth > columnWidths[col])
					{
					columnWidths[col] = thisItemWidth;
					}
				if (++i >= numItems)
					{
					break;
					}
				}
			requiredWidth += columnWidths[col];
			if ((requiredWidth + aGap) > iAvailableWidth)
				{
				if ((requiredWidth + KNewLine().Length()) > iAvailableWidth)
					{
					if (col > 0)
						{
						// Not enough space with this number of rows, so try another row.
						i = 0;
						break;
						}
					else
						{
						// Not enough space even with just one column. Lay out as a single column anyway and accept ugly wrapping by the console.
						CleanupStack::PopAndDestroy(1, &columnWidths);
						for (TInt i = 0; i < numItems; ++i)
							{
							AppendSpacesL(aIndent);
							AppendL(aItems[i]);
							AppendL(KNewLine);
							}
						return;
						}
					}
				else
					{
					requiredWidth += KNewLine().Length();
					}
				}
			else
				{
				requiredWidth += aGap;
				}
			++col;
			}
		if (i >= numItems)
			{
			fits = ETrue;
			}
		}

	// Layout the columns.
	const TInt numCols = columnWidths.Count();
	for (TInt row = 0; row < numRows; ++row)
		{
		AppendSpacesL(aIndent);
		for (TInt col = 0; col < numCols; ++col)
			{
			TInt index = (numRows * col) + row;
			if (index < numItems)
				{
				TPtrC item(aItems[index]);
				DoAppendPodL(item);
				TInt gap = (col == (numCols - 1)) ? 0 : aGap;
				AppendSpacesL((columnWidths[col] + gap) - ActualLength(item));
				}
			}
		AppendL(KNewLine);
		}

	CleanupStack::PopAndDestroy(1, &columnWidths);
	}

EXPORT_C void CTextFormatter::AppendPodL(const TDesC& aPod)
	{
	TPodFormatter podFormatter(*this);
	podFormatter.FormatL(aPod);
	}

EXPORT_C TInt CTextFormatter::Write()
	{
	return CTextBuffer::Write(*iWriteHandle);
	}

void CTextFormatter::DoAppendPodL(const TDesC& aPod)
	{
	DecodeInteriorPodSequencesL(aPod, *this);
	}

void CTextFormatter::DecodeInteriorPodSequencesL(const TDesC& aPod, CTextBuffer& aBuffer) const
	{
	TPodLexer lexer(aPod);
	TBool eop(EFalse);
	TBool eos(EFalse);

	TUint originalAttributes = ConsoleAttributes::ENone;
	ConsoleAttributes::TColor foregroundColor = ConsoleAttributes::EUnchanged;
	ConsoleAttributes::TColor backgroundColor = ConsoleAttributes::EUnchanged;
	if (iAttributesSupported)
		{
		aBuffer.GetCurrentAttributes(originalAttributes, foregroundColor, backgroundColor);
		}
	TUint currentAttributes = originalAttributes;

	while (!eos)
		{
		TPtrC token;
		TPodLexer::TTokenType tokenType;
		TUint attributes;
		lexer.NextTokenL(token, tokenType, attributes, eop, eos);

		switch (tokenType)
			{
			case TPodLexer::EAttributePush:
			case TPodLexer::EAttributePop:
				if (!iAttributesSupported && (attributes & TPodLexer::EBold))
					{
					aBuffer.AppendL('*');
					}
				else if (!iAttributesSupported && (attributes & TPodLexer::EItalic))
					{
					aBuffer.AppendL('\'');
					}
				else if ((!iAttributesSupported && (attributes & TPodLexer::EFileName)) || (attributes & TPodLexer::TPodLexer::ECode))
					{
					aBuffer.AppendL('"');
					}
				break;
			case TPodLexer::ELink:
				{
				TInt pos = token.Locate('|');
				if (pos >= 0)
					{
					token.Set(token.Left(pos));
					}
				}
				// Deliberate fall through.
			case TPodLexer::ETextBlock:
			case TPodLexer::ECodeBlock:
			case TPodLexer::EIndexEntry:
				if (iAttributesSupported)
					{
					if ((attributes == 0) || (attributes & (TPodLexer::ENull | TPodLexer::ECode)))
						{
						currentAttributes = originalAttributes;
						}
					if (attributes & (TPodLexer::EBold | TPodLexer::EItalic | TPodLexer::EFileName))
						{
						currentAttributes |= ConsoleAttributes::EBold;
						}
					aBuffer.SetAttributesL(currentAttributes);
					}
				aBuffer.AppendL(token);
				break;
			default:
				ASSERT(EFalse);
			}
		}

	if (iAttributesSupported && (currentAttributes != originalAttributes))
		{
		aBuffer.SetAttributesL(originalAttributes);
		}
	}

EXPORT_C void CTextFormatter::Zero()
	{
	CTextBuffer::Zero();
	}

EXPORT_C void CTextFormatter::Reset()
	{
	CTextBuffer::Reset();
	}

EXPORT_C void CTextFormatter::ResetText()
	{
	CTextBuffer::ResetText();
	}

TBool CTextFormatter::NextColumn(TLex& aLex, TPtrC& aPtr) const
	{
	aLex.Mark();
	while (!aLex.Eos())
		{
		if (aLex.Get() == '\t')
			{
			aLex.UnGet();
			aPtr.Set(aLex.MarkedToken());
			aLex.Get();
			return ETrue;
			}
		}
	if (aLex.TokenLength() > 0)
		{
		aPtr.Set(aLex.MarkedToken());
		return ETrue;
		}
	return EFalse;
	}

TBool CTextFormatter::NextLine(TLex& aLex, TPtrC& aPtr) const
	{
	aLex.Mark();
	while (!aLex.Eos())
		{
		TChar ch = aLex.Get();
		if (ch == '\r')
			{
			if (aLex.Get() == '\n')
				{
				aLex.UnGet();
				aLex.UnGet();
				aPtr.Set(aLex.MarkedToken());
				aLex.Get();
				aLex.Get();
				return ETrue;
				}
			else
				{
				aLex.UnGet();
				}
			}
		}
	if (aLex.TokenLength() > 0)
		{
		aPtr.Set(aLex.MarkedToken());
		return ETrue;
		}
	return EFalse;
	}

TInt CTextFormatter::ActualLength(const TDesC& aPod) const
	{
	CTextBuffer* buffer = CTextBuffer::NewLC(0x100);
	DecodeInteriorPodSequencesL(aPod, *buffer);
	TInt length = buffer->Descriptor().Length();
	CleanupStack::PopAndDestroy(buffer);
	return length;
	}