symhelp/helpmodel/src/HLPMODEL.CPP
changeset 0 1f04cf54edd8
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/symhelp/helpmodel/src/HLPMODEL.CPP	Tue Jan 26 15:15:23 2010 +0200
@@ -0,0 +1,1714 @@
+// 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 "HLPMODEL.H"
+
+// System includes
+#include <coehelp.h>
+#include <txtrich.h>
+#include <bautils.h>
+
+// User includes
+#include "hlppict.h"
+#include "HLPSRCH.H"
+#include "hlppanic.h"
+#include "Hlpsqlconsts.h"
+#ifdef SYMBIAN_ENABLE_SPLIT_HEADERS
+#include "hlpmodel_internal.h"
+#endif
+// For searching for help files
+_LIT(KHlpFileSpec,			"*.h*");
+_LIT(KDefaultHelpExtension, ".hlp");
+
+_LIT(KHlpFileSearchPath,	"\\Resource\\Help\\");
+
+#ifdef __SHOW_LOADING_INFO__
+_LIT(KFormat1, "\n%S %d: %c:%S%S");
+_LIT(KFormat2, "\n%S %d: %c:%S%S");
+_LIT(KCompareNewListWithMasterList, "compare master list");
+_LIT(KDeleteEntry, "deleting");
+_LIT(KGetNearestLanguageFile, "getting nearest languuage file for");
+_LIT(KAddNewEntryToMasterList, "adding to master list");
+_LIT(KLoadMasterList, "Loading");
+_LIT(KFoundEntry, "Found entry during dir scan");
+_LIT(KDuplicateEntry, "entry discarded as duplicate");
+_LIT(KAddNewEntry, "entry added to list");
+#endif
+
+
+// Typedefs
+typedef CArrayPtrFlat<CHlpFileEntry> CHlpFileList;
+
+// Constants
+const TInt KHlpModelNumericValueSize = 32;
+
+// Average number of pictures per Help Topic
+const TInt KAverageNumberOfPicturesInHelpTopic = 3;
+
+#define UNUSED_VAR(a) a = a
+//
+// ----> CHlpFileEntry (header)
+//
+
+class CHlpFileEntry : public CBase
+	{
+
+public:
+	static CHlpFileEntry* NewLC(TDriveUnit aDrive, const TDesC& aFile);
+
+	inline TDriveUnit Drive() const { return iDrive; }
+	inline const TDesC& FileName() const { return iFile; }
+	inline const TDesC& Name() const { return iName; }
+	void GetFullNameAndPath(TDes& aName) const;
+
+private:
+	CHlpFileEntry(TDriveUnit aDrive, const TDesC& aFile);
+
+private:
+	TName iName;
+	TName iFile;
+	TDriveUnit iDrive;
+	};
+
+
+
+//
+// ----> Static utility function (source)
+//
+
+//#define __SHOW_LOADING_INFO__
+
+#ifdef __SHOW_LOADING_INFO__
+static void PrintEntryL(const TDesC& aPrompt, const CHlpFileEntry& aEntry, TInt aNumber = -1)
+	{
+	TFileName pFileName(aEntry.FileName());
+	TChar driveLetter = '?';
+	RFs::DriveToChar(aEntry.Drive(), driveLetter);
+
+	HBufC* buf = HBufC::NewLC(aPrompt.Length() + pFileName.Length() + KHlpFileSearchPath().Length() + 40);
+	TDes pBuf(buf->Des());
+
+
+	if	(aNumber >= KErrNone)
+		pBuf.Format(KFormat1, &aPrompt, aNumber, driveLetter, &KHlpFileSearchPath(), &pFileName);
+	else
+		pBuf.Format(KFormat2, &aPrompt, driveLetter, &KHlpFileSearchPath(), &pFileName);
+
+	RDebug::Print(pBuf);
+
+	CleanupStack::PopAndDestroy();
+	}
+#define __PRINT_FILE(aPrompt, aEntry)				(PrintEntryL(aPrompt, aEntry))
+#define __PRINT_FILE_NO(aPrompt, aEntry, aNumber)	(PrintEntryL(aPrompt, aEntry, aNumber))
+#else
+#define __PRINT_FILE(aPrompt, aEntry)
+#define __PRINT_FILE_NO(aPrompt, aEntry, aNumber)
+#endif
+
+
+
+
+//
+// ----> CHlpFileEntry (source)
+//
+CHlpFileEntry::CHlpFileEntry(TDriveUnit aDrive, const TDesC& aFile)
+:iFile(aFile)
+,iDrive(aDrive)
+	{
+	TParsePtrC parser(iFile);
+	iName.Copy(parser.Name());
+	}
+
+CHlpFileEntry* CHlpFileEntry::NewLC(TDriveUnit aDrive, const TDesC& aFile)
+	{
+	CHlpFileEntry* self = new(ELeave) CHlpFileEntry(aDrive, aFile);
+	CleanupStack::PushL(self);
+	return self;
+	}
+
+
+//
+//
+//
+void CHlpFileEntry::GetFullNameAndPath(TDes& aName) const
+	{
+	TChar driveLetter = '?';
+	RFs::DriveToChar(Drive(), driveLetter);
+	aName.Zero();
+	aName.Append(driveLetter);
+	aName.Append(':');
+	aName.Append(KHlpFileSearchPath);
+	aName.Append(FileName());
+	}
+
+
+//
+// ----> CHlpModel
+//
+
+CHlpModel::CHlpModel(RFs& aFs, MHlpModelObserver& aObserver)
+:	iFsSession(aFs), iObserver(&aObserver)
+	{
+	}
+
+EXPORT_C CHlpModel::~CHlpModel()
+/** Destructor. */
+	{
+	if	(iDatabases)
+		iDatabases->ResetAndDestroy();
+
+	NotifyHelpModelDestructionToPictures();
+	if	(iPictures)
+		iPictures->Reset();
+
+	delete iDatabases;
+	delete iPictures;
+	delete iSearch;
+	delete iCriterion;
+	delete iZoomFactors;
+	}
+
+void CHlpModel::NotifyHelpModelDestructionToPictures()
+	{
+	if	(!iPictures)
+		return;
+	const TInt count = iPictures->Count();
+	for (TInt i=0; i<count; i++)
+		{
+		CHlpPicture& picture = *iPictures->At(i);
+		picture.HandleHelpModelDestruction();
+		}
+	}
+
+
+void CHlpModel::ConstructL()
+	{
+	iDatabases = new(ELeave) CHlpDatabases(2);
+	iPictures = new(ELeave) CArrayPtrFlat<CHlpPicture>(KAverageNumberOfPicturesInHelpTopic);
+	iSearch = CHlpSQLSearch::NewL(*this);
+
+	// There must always be 3 default zoom ratios present in the array...
+	iZoomFactors = new(ELeave) CArrayFixFlat<TInt>(3);
+	iZoomFactors->AppendL(KHlpModelZoomFactorSmall);
+	iZoomFactors->AppendL(KHlpModelZoomFactorMedium);
+	iZoomFactors->AppendL(KHlpModelZoomFactorLarge);
+	}
+
+EXPORT_C CHlpModel* CHlpModel::NewL(RFs& aFs, MHlpModelObserver* aObserver)
+/** Allocates and creates a help model object.
+
+@param aFs Open file server handle
+@param aObserver Client callback interface to handle messages from the help
+model
+@return New help model object */
+	{
+	CHlpModel* self = CHlpModel::NewLC(aFs, aObserver);
+	CleanupStack::Pop();
+	return self;
+	}
+
+EXPORT_C CHlpModel* CHlpModel::NewLC(RFs& aFs, MHlpModelObserver* aObserver)
+/** Allocates and creates a help model object, leaving the object on the cleanup
+stack.
+
+@param aFs Open file server handle
+@param aObserver Client callback interface to handle messages from the help
+model
+@return New help model object */
+	{
+	CHlpModel* self = new(ELeave) CHlpModel(aFs, *aObserver);
+	CleanupStack::PushL(self);
+	self->ConstructL();
+	return self;
+	}
+
+
+//
+//
+//
+
+EXPORT_C void CHlpModel::OpenL()
+/** Opens all the help files in \\Resource\\Help. */
+	{
+	CHlpFileList* masterList = new(ELeave) CHlpFileList(5);
+	CleanupStack::PushL(TCleanupItem(ResetAndDestroyArrayOfCHlpFileEntry,masterList));
+
+	TInt i = EDriveY;
+	FOREVER
+		{
+		// Make sure we go from Y -> A, then do Z last
+		if	(i < EDriveA)
+			i = EDriveZ;
+
+		if	(DiskPresent(i))
+			{
+			// This generates a list for a specific directory on the specified drive.
+			CHlpFileList* list = BuildListForDriveLC(TDriveUnit(i), iFsSession);
+
+			TInt newListCount = list->Count();
+			if(newListCount)
+				{
+				// compare the new list with our master list to check for duplicates
+				TInt masterCount = masterList->Count();
+				for(TInt j=masterCount-1; j>=0; j--)
+					{
+					CHlpFileEntry* entry = masterList->At(j);
+					__PRINT_FILE_NO(KCompareNewListWithMasterList, *entry, j);
+
+					newListCount = list->Count();
+					for(TInt k=newListCount-1; k>=0; k--)
+						{
+						CHlpFileEntry* newEntry = list->At(k);
+
+						if	(entry->FileName().CompareF(newEntry->FileName()) == 0)
+							{
+							// Names same, so nearest language file already in master list
+							__PRINT_FILE_NO(KDeleteEntry, *newEntry, k);
+							delete newEntry;
+							list->Delete(k);
+							}
+						}
+					}
+				}
+
+			// At this point, anything that is left in the new list should
+			// have it's nearest language file added to the master list...
+			newListCount = list->Count();
+			for(TInt k=newListCount-1; k>=0; k--)
+				{
+				//get nearest language file
+				__PRINT_FILE_NO(KGetNearestLanguageFile, *list->At(k), k);
+
+				TChar driveLetter = '?';
+				RFs::DriveToChar(list->At(k)->Drive(), driveLetter);
+
+				TFileName fileName;
+				fileName.Append(driveLetter);
+				fileName.Append(':');
+				fileName.Append(KHlpFileSearchPath);
+				fileName.Append(list->At(k)->Name());
+				fileName.Append(KDefaultHelpExtension);
+
+				BaflUtils::NearestLanguageFile(iFsSession, fileName);
+
+				//get drive and filename+extension of nearest language file
+				TParsePtrC parser(fileName);
+				if	(!parser.ExtPresent())
+					User::Leave(KErrCorrupt);
+				TPtrC drive=parser.Drive();
+				TPtrC name=parser.NameAndExt();
+
+				//create new CHlpFileEntry
+				CHlpFileEntry* entry = CHlpFileEntry::NewLC(drive, name);
+				__PRINT_FILE(KAddNewEntryToMasterList, *entry);
+
+				//add to master list
+				masterList->AppendL(entry);
+				CleanupStack::Pop(entry);
+				}
+
+			list->ResetAndDestroy();
+			CleanupStack::PopAndDestroy(list);
+			}
+
+		if	(i-- == EDriveZ)
+			break;
+		}
+	// The code below validates the masterList of help files and filters out unnecessary files based on the criteria specified below -
+	// i)   In case of two files - abc.hlp and abc.h01 - present in the master list,since abc.h01 is the latest version, it
+	//      will be picked. In other words abc.hlp will be removed from the list.
+	// ii)  if two files for different language code exist, both should remain in masterList
+	// iii) if the system language is changed, the file as per the new language should be retained and
+	//      default file with .hlp extension should be removed from the masterList
+	TLanguage language;
+	language = User::Language(); //gets the default language
+	TInt languageCode;
+	languageCode = language; // integer equivalent of the language code. For example, ELangEnglish is 01
+	for(TInt i=masterList->Count()-1; i >= 0; i--)
+		{
+		CHlpFileEntry* newEntry = masterList->At(i);
+		TPtrC ptrFile (newEntry->FileName());
+		TBufC<8> fileExtension(ptrFile.Right(2));
+		TLex lex(fileExtension);
+		TInt lexIntValue;
+		lex.Val(lexIntValue);
+		TParse parser;
+		TBool hlpFlag = ETrue;
+		User::LeaveIfError(parser.Set(ptrFile, NULL, NULL));
+
+		if(lexIntValue != language)
+			{
+			// Get the filename with language code in file extension
+			TBuf<256> fileName(newEntry->Name());
+			TBuf<10> name;
+			name.Format(_L(".h%02d"),languageCode);
+			fileName.Append(name);
+
+			// Get the filename with the .hlp extension
+			TBuf<256> defaultFileName(newEntry->Name());
+			TBuf<10> defaultName;
+			defaultName.Format(_L(".hlp"));
+			defaultFileName.Append(defaultName);
+
+			//Check the file against all the files in the list
+			for(TInt j = masterList->Count()-1; j >=0 ; j--)
+				{
+				CHlpFileEntry* entry = masterList->At(j);
+				// If two files with same name exist, delete one from the masterList
+				// and reset hlpFlag to EFalse
+				if(entry->FileName().CompareF(fileName)==0)
+					{
+					masterList->Delete(i);
+					hlpFlag = EFalse;
+					break;
+					}
+				}
+				if( (hlpFlag) && (parser.Ext().CompareF(KDefaultHelpExtension)!=0))
+					{
+					for(TInt k = masterList->Count()-1; k >=0 ; k--)
+						{
+						CHlpFileEntry* entry = masterList->At(k);
+						if(entry->FileName().CompareF(defaultFileName)==0)
+							{
+							masterList->Delete(i);
+							break;
+							}
+						}
+					}
+				}
+			}
+
+	// Load the master list
+	TFileName file;
+	TInt count = masterList->Count();
+
+	for (i=0; i<count; i++)
+		{
+		masterList->At(i)->GetFullNameAndPath(file);
+		__PRINT_FILE_NO(KLoadMasterList, *masterList->At(i), i);
+
+		// This will leave with KErrArgument if it's a bad file
+		TRAPD(error, OpenFileL(file));
+		UNUSED_VAR(error); // used to suppress build warnings
+		}
+
+	CleanupStack::PopAndDestroy(masterList);
+	}
+
+TBool CHlpModel::DiskPresent(TInt aDrive) const
+	{
+	TDriveList list;
+	if	(iFsSession.DriveList(list) < KErrNone)
+		return EFalse;
+
+	TDriveInfo info;
+	TInt error = iFsSession.Drive(info, aDrive);
+	if	(error < KErrNone)
+		return EFalse;
+
+	return (list[aDrive] && info.iType != EMediaNotPresent);
+	}
+
+void CHlpModel::ResetAndDestroyArrayOfCHlpFileEntry(TAny* aObject)
+	{
+	CArrayPtr<CHlpFileEntry>* array=REINTERPRET_CAST(CArrayPtr<CHlpFileEntry>*,aObject);
+	if (array)
+		array->ResetAndDestroy();
+	delete array;
+	}
+
+CHlpFileList* CHlpModel::BuildListForDriveLC(TDriveUnit aDrive, RFs& aFsSession) const
+//
+//	Generate a list of help files for the specified drive
+//
+	{
+	CHlpFileList* list = new(ELeave) CHlpFileList(5);
+	CleanupStack::PushL(TCleanupItem(ResetAndDestroyArrayOfCHlpFileEntry,list));
+
+	// Generate the folder spec to search in
+	TFileName searchSpec;
+	TChar driveLetter;
+	User::LeaveIfError(RFs::DriveToChar(aDrive, driveLetter));
+	searchSpec.Append(driveLetter);
+	searchSpec.Append(':');
+	searchSpec.Append(KHlpFileSearchPath);
+	searchSpec.Append(KHlpFileSpec);
+
+	CDir* dirList;
+
+	TFindFile finder(aFsSession);
+	TInt ret = finder.FindWildByPath(searchSpec, NULL, dirList);
+	if	(ret < KErrNone)
+		{
+		if	(ret == KErrNotFound)
+			return list;
+		else
+			User::Leave(ret);
+		}
+	CleanupStack::PushL(dirList);
+
+	// Add files to help file list
+	TInt dirCount = dirList->Count();
+	for(TInt i=0; i<dirCount; i++)
+		{
+		CHlpFileEntry* newEntry = CHlpFileEntry::NewLC(aDrive, (*dirList)[i].iName);
+		__PRINT_FILE_NO(KFoundEntry, *newEntry, i);
+
+		//check if new file already in the list
+		TBool foundMatch=EFalse;
+		TInt existingEntryCount=list->Count();
+		for(TInt j=existingEntryCount-1; j>=0; j--)
+			{
+			if(list->At(j)->Name().CompareF(newEntry->Name())==0)
+				{
+				__PRINT_FILE_NO(KDuplicateEntry, *newEntry, i);
+				foundMatch=ETrue;
+				}
+			}
+
+		if(!foundMatch)
+			{
+			__PRINT_FILE_NO(KAddNewEntry, *newEntry, i);
+			list->AppendL(newEntry);
+			CleanupStack::Pop(newEntry);
+			}
+		else
+			CleanupStack::PopAndDestroy(newEntry);
+		}
+	CleanupStack::PopAndDestroy(dirList);
+
+	return list;
+	}
+
+EXPORT_C void CHlpModel::CloseL()
+/** Closes all open help files. */
+	{
+	const TInt KDatabaseCount = DatabaseCount();
+	for (TInt i=0; i<KDatabaseCount; i++)
+		iDatabases->At(i)->Close();
+	iDatabases->ResetAndDestroy();
+	}
+
+
+//
+//
+//
+
+/**
+Opens a specified help file.
+
+@param aFileName Help file to open
+*/
+EXPORT_C void CHlpModel::OpenFileL(const TDesC& aFileName)
+	{
+	TEntry entry;
+	User::LeaveIfError(iFsSession.Entry(aFileName, entry));
+	if	(entry.IsDir())
+		{
+		return; // don't try and open directories as help files ;)
+		}
+
+	TParsePtrC parser(aFileName);
+	if (parser.Path() != KHlpFileSearchPath)
+		{ // given file is outside \\Resource\\Help\\ directory.
+		User::Leave(KErrArgument);
+		}
+
+	if	(!(entry[0] == KPermanentFileStoreLayoutUid && entry[1] == KUidHlpApp))
+		{
+		User::Leave(KErrArgument);
+		}
+
+	CHlpDatabase* newDatabase = CHlpDatabase::NewLC(iFsSession, aFileName);
+ 	iDatabases->AppendL(newDatabase);
+	CleanupStack::Pop(newDatabase);
+	}
+
+EXPORT_C void CHlpModel::CloseFileL(const TDesC& aFileName)
+/** Closes a specified help file.
+
+@param aFileName Help file to close */
+	{
+	const TInt KDatabaseCount = DatabaseCount();
+	for (TInt i=0; i<KDatabaseCount; i++)
+		{
+		CHlpDatabase* database = iDatabases->At(i);
+		if	(database->FileName() == aFileName)
+			{
+			delete database;
+			iDatabases->Delete(i);
+			return;
+			}
+		}
+	User::Leave(KErrNotFound);
+	}
+
+//
+//
+//
+
+EXPORT_C void CHlpModel::ContextSearchL(TCoeHelpContext& aContext)
+/** Searches for a topic with the specified help context.
+
+A successful search generates an ETopicAvailable event. The topic can then
+be retrieved using LoadTopicL().
+
+An unsuccessful search generates an ETopicNotFound event.
+
+@param aContext Help context to search for */
+	{
+	// Set up ready for search
+	ResetReadyForSearch();
+	SetSearchType(EContextSearch);
+
+	// See if we can find the specified context in any of the meta
+	// data that each database contains. This reduces the time taken to
+	// perform a search, as only the database which actually contains the
+	// correct information actually has the SQL executed on it (major time saver).
+	TInt db = MatchUidL(aContext.iMajor);
+	if	(db >= KErrNone)
+		{
+		// Search the specified database for the context. If this context is
+		// found then the view will be prepared ready for the caller to extract
+		// the topic.
+		iCurrentDb = db;
+		TInt searchResult = CurrentDatabase()->ContextSearchL(aContext.iContext);
+		ReportEventToObserverL(searchResult);
+		}
+	else
+		{
+		ReportEventToObserverL(ETopicNotFound);
+		}
+	}
+
+EXPORT_C void CHlpModel::CategoryUIDSearchL(TUid aCategoryUID)
+/** Searches for topics with the specified help category UID.
+
+A successful search generates an ETopicListAvailable event. The list can then
+be retrieved using LoadListL().
+
+An unsuccessful search generates an ETopicListNoneFound event.
+
+@param aCategoryUID Category UID to search for */
+	{
+	// Set up ready for search
+	ResetReadyForSearch();
+	SetSearchType(ETopicListForCategoryUID);
+
+	// See if we can find the specified category Uid in any of the meta
+	// data that each database contains. This reduces the time taken to
+	// perform a search, as only the database which actually contains the
+	// correct information actually has the SQL executed on it (major time saver).
+	TInt db=MatchUidL(aCategoryUID);
+	if	(db >= KErrNone)
+		{
+		iTransientCategoryUid = aCategoryUID;
+		iCurrentDb=db;
+
+		TBuf<32> buf;
+		buf.Num(STATIC_CAST(TUint, aCategoryUID.iUid));
+		DoSearchL(ETopicListForCategoryUID, buf);
+
+		// Reset the category uid after we've done the search (used in
+		// debug invariant state check)
+		iTransientCategoryUid = KNullUid;
+		}
+	else
+		{
+		ReportEventToObserverL(ETopicNotFound);
+		}
+	}
+
+EXPORT_C void CHlpModel::TopicSearchL(const CHlpItem& aHelpItem)
+/** Searches for a topic for the specified help item.
+
+A successful search generates an ETopicAvailable event. The topic can then
+be retrieved using LoadTopicL().
+
+An unsuccessful search generates an ETopicNotFound event.
+
+@param aHelpItem Help item to search for */
+	{
+	// aHelpItem contains the information required for a speedy retrieval
+	// of the specified topic from the help model. Using it's topic Id (iId)
+	// category uid (CategoryUid()) and help file uid (HelpFileUid()) we
+	// can run the search SQL on exactly the right database file, for exactly
+	// the right category, and exactly the right topic.
+
+	// if already searching, dont start another search
+	if(iFound && CurrentSearchType()==ETopicIdSearch)
+		return;
+
+	// Set up ready for search
+	ResetReadyForSearch();
+	SetSearchType(ETopicIdSearch);
+
+	TInt count = DatabaseCount();
+	for(TInt i=0; i<count; i++)
+		{
+		CHlpDatabase* database = iDatabases->At(i);
+		if	(database->HelpFileUid() == aHelpItem.HelpFileUid())
+			{
+			// Run the SQL on this database.
+			iCurrentDb = i;
+			TInt result = database->TopicIdSearchL(aHelpItem.CategoryUid(), STATIC_CAST(TUint, aHelpItem.iId));
+			iFound = (result == ETopicAvailable);
+			ReportEventToObserverL(result);
+			return;
+			}
+		}
+	ReportEventToObserverL(ETopicNotFound);
+	}
+
+EXPORT_C void CHlpModel::IndexSearchL(const CHlpItem& aHelpItem)
+/** Searches for index entries for the specified help item.
+
+A successful search generates an ETopicListAvailable event. The list can then
+be retrieved using LoadListL().
+
+An unsuccessful search generates an ETopicListNoneFound event.
+
+@param aHelpItem Help item to search for */
+	{
+	// Set up ready for search
+	ResetReadyForSearch();
+	SetSearchType(ETopicIdSearch);
+
+	TInt count = DatabaseCount();
+	for(TInt i=0; i<count; i++)
+		{
+		CHlpDatabase* database = iDatabases->At(i);
+		if	(database->HelpFileUid() == aHelpItem.HelpFileUid())
+			{
+			// Run the SQL on this database.
+			iCurrentDb = i;
+
+			TBuf<KHlpModelNumericValueSize> buf;
+			buf.Num(STATIC_CAST(TUint, aHelpItem.iId));
+			DoSearchL(EIndexSearch, buf);
+
+			return;
+			}
+		}
+	ReportEventToObserverL(EIndexSearchListNoneFound);
+	}
+
+//
+//
+//
+
+EXPORT_C void CHlpModel::SearchL(TInt aType, TUint32 aId)
+/** Searches using a specified type of search with a numeric criterion.
+
+@param aType Type of search. This is a search type (EIndexList etc.) enum
+value.
+@param aId Numeric search criterion */
+	{
+	if	(aType == ETopicListForCategoryUID)
+		iTransientCategoryUid = TUid::Uid(aId);
+
+	TBuf<KHlpModelNumericValueSize> buf;
+	buf.Num(STATIC_CAST(TUint, aId));
+	SearchL(aType, buf);
+	}
+
+EXPORT_C void CHlpModel::SearchL(TInt aType, HBufC* aCriterion)
+/** Searches using a specified type of search, allowing a NULL seach criterion.
+
+@param aType Type of search. This is a search type (EIndexList etc.) enum
+value.
+@param aCriterion String search criterion. This can be NULL if the search type
+requires no search criterion. */
+	{
+	// Kludge to remain source compatible in App Engines 5.2
+	// This function should be removed.
+	if	(aCriterion)
+		SearchL(aType, *aCriterion);
+	else
+		SearchL(aType, KNullDesC);
+	}
+
+EXPORT_C void CHlpModel::SearchL(TInt aType, const TDesC& aCriterion)
+//
+//	This function is the base of all the SearchL(...) methods, i.e. it is callled
+//	by SearchL(TInt, HBufC*) and also SearchL(TInt, TUint32)
+//
+/** Searches using a specified type of search with a string search criterion.
+
+Note that the full text search, EFullTextSearch, is asynchronous.
+
+@param aType Type of search. This is a search type (EIndexList etc.) enum
+value.
+@param aCriterion String search criterion */
+	{
+	// Set up ready for search
+	ResetReadyForSearch();
+
+	// Do the actual search itself.
+	DoSearchL(aType, aCriterion);
+	}
+
+EXPORT_C TInt CHlpModel::CancelSearch()
+/** Cancels a full text search.
+
+@return KErrNone if successful, KErrArgument if a search is not in progress */
+	{
+	iSearch->CancelEvaluator();
+
+	if	(CurrentSearchType() != EFullTextSearch)
+		return KErrArgument;
+
+	return KErrNone;
+	}
+
+//
+//
+//
+
+EXPORT_C void CHlpModel::LoadTopicL(CRichText& aRichText, TDes& aTitle)
+/** Gets the current help topic text and title.
+
+The function assumes that a successful search has already been performed.
+
+@param aRichText On return, the help topic text
+@param aTitle On return, the help topic title */
+	{
+	// Fetch the rich text
+	LoadTopicL(aRichText);
+
+	// Next get the title
+	RDbView& view		= CurrentDatabase()->View();
+	__ASSERT_ALWAYS(view.AtRow(), Panic(EHlpNoRowAtCursor));
+	CDbColSet* colset	= view.ColSetL();
+	TDbColNo topicTitle	= colset->ColNo(KSQLTopicTitleColumn);
+	delete colset;
+
+	view.FirstL();
+	view.GetL();
+	aTitle = view.ColDes(topicTitle);
+	}
+
+EXPORT_C void CHlpModel::LoadTopicL(CRichText& aRichText)
+/** Gets the current help topic text.
+
+The function assumes that a successful search has already been performed.
+
+@param aRichText On return, the help topic text */
+	{
+	RDbView& view		= CurrentDatabase()->View();
+	__ASSERT_ALWAYS(view.CountL(), Panic(EHlpTopicNoRowsInView));
+
+	CDbColSet* colset	= view.ColSetL();
+	TDbColNo topicCol	= colset->ColNo(KSQLTopicTextColumn);
+	TDbColNo markupCol	= colset->ColNo(KSQLTopicMarkupColumn);
+	delete colset;
+	view.FirstL();
+	view.GetL();
+
+	aRichText.Reset();
+	TInt len = view.ColLength(topicCol);
+	HBufC* buf = HBufC::NewLC(len);
+	TPtr pBuf(buf->Des());
+
+	RDbColReadStream stream;
+	stream.OpenLC(view, topicCol);
+	stream.ReadL(pBuf, len);
+	aRichText.InsertL(0, pBuf);
+	CleanupStack::PopAndDestroy(2); // stream & buf
+
+	if (!view.IsColNull(markupCol))
+		{
+		RDbColReadStream blob;
+		blob.OpenL(view, markupCol);
+
+		aRichText.SetPictureFactory(this, this);
+		CEmbeddedStore* embeddedStore = CEmbeddedStore::FromLC(blob);
+		iCurrentRichTextStore = embeddedStore;
+
+		RStoreReadStream readStream;
+		readStream.OpenLC(*embeddedStore, embeddedStore->Root());
+		aRichText.InternalizeMarkupDataL(readStream);
+		aRichText.DetachFromStoreL(CPicture::EDetachFull);
+		CleanupStack::PopAndDestroy(2); // embeddedStore, readStream
+		aRichText.SetPictureFactory(NULL, NULL);
+		iCurrentRichTextStore = NULL;
+		}
+	}
+
+EXPORT_C void CHlpModel::LoadTopicL(CHlpTopic* aTopic)
+/** Gets the current help topic.
+
+The function assumes that a successful search has already been performed.
+
+@param aTopic On return, the help topic. This is caller allocated. */
+	{
+	__ASSERT_ALWAYS(aTopic, Panic(EHlpNoTopic));
+
+	LoadTopicL(*(aTopic->TopicText()));
+
+	RDbView& view		= CurrentDatabase()->View();
+	CDbColSet* colset	= view.ColSetL();
+	TDbColNo topicTitle	= colset->ColNo(KSQLTopicTitleColumn);
+	TDbColNo topicId	= colset->ColNo(KSQLTopicIdColumn);
+	TDbColNo category	= colset->ColNo(KSQLCategoryColumn);
+	delete colset;
+
+	// Populate the topic's
+	aTopic->iTopicTitle = view.ColDes(topicTitle);
+	aTopic->iTopicId	= view.ColUint32(topicId);
+	aTopic->iCategory	= view.ColDes(category);
+	}
+
+EXPORT_C void CHlpModel::LoadListL(CHlpList* aList)
+/** Gets the current help list.
+
+The function assumes that a successful search has already been performed.
+
+@param aList On return, the help list. This is caller allocated. */
+	{
+	__ASSERT_ALWAYS(aList, Panic(EHlpNoHelpList));
+
+	// Clear all entries in the list
+	aList->Reset();
+
+	// This loop provides the mechanism by which we iterate through every database
+	// in the help model. We query each database in turn to find out a) if it has
+	// been searched in the first place (not every search results in *every* help
+	// file's database view being initialised) and b) if it actually contains any
+	// results.
+	const TInt KDatabaseCount = DatabaseCount();
+	for (TInt i=0; i < KDatabaseCount; i++)
+		{
+		// Get a help database pointer and it's view.
+		CHlpDatabase* currentDatabase = iDatabases->At(i);
+		RDbView& view = currentDatabase->View();
+
+		// Does this database meet the criteria described above (points a & b)?
+		if	(!currentDatabase->ViewReady() || !view.CountL())
+			continue; // Doesn't meet criteria, so skip this database
+
+		// Criteria satisifed, so look up the column id's we're interested in.
+		CDbColSet* colset		= view.ColSetL();
+		TDbColNo colTitle		= colset->ColNo(KSQLTopicTitleColumn);
+		TDbColNo colTopicId		= colset->ColNo(KSQLTopicIdColumn);
+		TDbColNo colCategoryId	= colset->ColNo(KSQLCategoryUidColumn);
+		TDbColNo colIndex		= colset->ColNo(KSQLIndexColumn);
+		TDbColNo colIndexId		= colset->ColNo(KSQLIndexIdColumn);
+		delete colset;
+
+		// Each topic is associated with a particular category. This information
+		// is cached with the help item so that we can ensure we restore the
+		// topic relating to the category searched (topic id's are not unique, they
+		// are assigned in increasing numerical order by the help compiler, on a
+		// category basis. Category ids, however, are unique, hence we need to store
+		// both in order to be able to restore the correct topic text).
+		view.FirstL();
+		view.GetL();
+
+		// Iterate through each row in this database's view, and store each entry
+		// as a help item in the list passed as a parameter to this function.
+		FOREVER
+			{
+			CHlpItem* item = NULL;
+
+			// This bit loads different content into the help item depending on the last performed
+			// search type.
+			switch (iSearchType)
+				{
+			case ECategoryList:
+			case ETopicListForCategory:
+			case ETopicListForCategoryUID:
+				item = CHlpItem::NewLC(view.ColDes(colTitle), view.ColUint32(colTopicId), TUid::Uid(view.ColUint(colCategoryId)), currentDatabase->HelpFileUid());
+				break;
+			case EIndexList:
+				item = CHlpItem::NewLC(view.ColDes(colIndex), view.ColUint32(colIndexId), currentDatabase->HelpFileUid());
+				break;
+			case EQuickSearch:
+			case EFullTextSearch:
+			case EIndexSearch:
+				item = CHlpItem::NewLC(view.ColDes(colTitle), view.ColUint32(colTopicId), TUid::Uid(view.ColUint(colCategoryId)), currentDatabase->HelpFileUid());
+				break;
+			default:
+				User::Leave(KErrNotSupported);
+				}
+
+			aList->AppendL(item);
+			CleanupStack::Pop(item);
+			view.NextL();
+			if	(view.AtEnd())
+				break;
+			view.GetL();
+			}
+		}
+	}
+
+EXPORT_C void CHlpModel::CategoryListL(CDesCArray* aList)
+/** Populates a list of available help categories.
+
+This can be called without needing to perform a prior search.
+
+@param aList On return, the list of available help categories. This is caller
+allocated. */
+	{
+	__ASSERT_ALWAYS(aList, Panic(EHlpNoCategoryList));
+
+	const TInt count = DatabaseCount();
+	for(TInt i=0; i<count; i++)
+		iDatabases->At(i)->AppendCategoryListL(*aList);
+	aList->Sort();
+	}
+
+//
+//
+//
+
+EXPORT_C void CHlpModel::SetZoomSizeL(THlpZoomState aState)
+/** Sets the zoom size to use for help text and pictures.
+
+@param aState Zoom size */
+	{
+	iZoomSize = aState;
+	const TInt count = iPictures->Count();
+	for (TInt i=0; i<count; i++)
+		{
+		// Fetch a picture from the array
+		CHlpPicture* picture = iPictures->At(i);
+		picture->HandleZoomChangedL(iZoomSize);
+		}
+	}
+
+EXPORT_C THlpZoomState CHlpModel::ZoomSize() const
+/** Gets the zoom size used for help text and pictures.
+
+@return Zoom size */
+	{
+	return iZoomSize;
+	}
+
+/* Sets the iZoomFactors array to the appropriate
+zoom factor value, depending on the current zoom state
+as dictated by the aZoomState argument variable.
+
+The hlpmodel's THlpZoomState enumerated type is related to
+the AppUI's TZoomStates enumerated type. This relation is required
+so that the correct pixels to twips ratio can be used in the
+CHlpPicture::GetOriginalSizeInTwips() method.
+
+Error Condition	: None.
+@param aZoomState A zoom size.
+@param aFactor The zoom factor that corresponds to the current zoom size.
+@pre None.
+@post The iZoomFactors data member has been populated with the appropriate
+zoom factor values. */
+EXPORT_C void CHlpModel::SetZoomFactors(THlpZoomState aZoomState, TInt aFactor)
+/** Sets a zoom factor for a logical zoom size.
+
+@param aZoomState Logical zoom size for which to set the factor.
+@param aFactor Zoom factor. For example, 2 specifies double size. */
+	{
+	__ASSERT_ALWAYS(iZoomFactors->Count() == 3, Panic(EHlpNotEnoughZoomRatios));
+	//
+	switch(aZoomState)
+		{
+	/*
+	 * If zoom state is large, assign zoom factor 750
+	 * to index 0 of 'iZoomFactors'
+	 */
+	case EHlpZoomStateSmall:
+		iZoomFactors->At(EHlpZoomStateSmall) = aFactor;
+		break;
+
+	default:
+	/*
+	 * If zoom state is large, assign zoom factor 1000
+	 * to index 1 of 'iZoomFactors'
+	 */
+	case EHlpZoomStateMedium:
+		iZoomFactors->At(EHlpZoomStateMedium) = aFactor;
+		break;
+	/*
+	 * If zoom state is large, assign zoom factor 1250
+	 * to index 2 of 'iZoomFactors'
+	 */
+	case EHlpZoomStateLarge:
+		iZoomFactors->At(EHlpZoomStateLarge) = aFactor;
+		break;
+		}
+	}
+
+/*
+Accesses the iZoomFactors data member, and returns
+the current zoom factor, depending on the value of
+the current zoom state, which is to be used for the
+pixel scaling of a picture in CHlpPicture::GetOriginalSizeInTwips().
+Error Condition	: None
+
+@return The zoom factor, corresponding to the current zoom size.
+@pre None.
+@post Current zoom factor is returned. */
+TInt CHlpModel::CurrentZoomFactor() const
+	{
+	__ASSERT_ALWAYS(iZoomFactors->Count() == 3, Panic(EHlpNotEnoughZoomRatios));
+	//
+	switch (iZoomSize)
+		{
+	case EHlpZoomStateSmall:
+		return iZoomFactors->At(EHlpZoomStateSmall);
+	default:
+	case EHlpZoomStateMedium:
+		return iZoomFactors->At(EHlpZoomStateMedium);
+	case EHlpZoomStateLarge:
+		return iZoomFactors->At(EHlpZoomStateLarge);
+		}
+	}
+
+/* Removes a CHlpPicture from the iPictures array.
+
+Error Condition	: None
+
+@param aHelpPicture A pointer to a CHlpPicture object.
+@pre A valid CHlpPicture is passed to the function.
+@post CHlpPicture's that are no longer displayed have been removed
+from the iPictures data member. */
+void CHlpModel::RemoveHelpPicture(CHlpPicture* aHelpPicture)
+	{
+	if (iPictures != 0)
+	{
+	const TInt count = iPictures->Count();
+	__ASSERT_DEBUG(count > 0, User::Invariant());
+	//
+	for(TInt i=0; i<count; i++)
+		{
+		if	(aHelpPicture == iPictures->At(i))
+			{
+			iPictures->Delete(i);
+			return;
+			}
+		}
+	__ASSERT_DEBUG(EFalse, Panic(EHlpUnlocatedHelpPicture));
+	}
+	}
+
+
+//
+//
+//
+
+/* This is a mixin callback required to restore pictures
+from the help model database into the topic rich text.
+Because pictures are stored in their own picture table within the
+help database (this means that the same picture may be used many
+times within the rich text, but will only actually appear once
+in the database - a major space saver) the restoration function
+must have a primed view so that it knows which database to use
+as the source picture table.
+
+@param aHdr A reference to a picture header.
+@param aDeferredPictureStore A stream store where the pictures are kept.
+@pre A valid picture database must exist, so that the stream store can relate to.
+@post A TInt pointer (index) to the picture table is inserted
+in the rich text to indicate which picture needs restoring.
+The CHlpPicture looks up this index in the picture table. */
+void CHlpModel::NewPictureL(TPictureHeader& aHeader, const CStreamStore& aDeferredPictureStore) const
+	{
+	if	(aHeader.iPictureType != KUidHelpImage)
+		User::Leave(KErrNotSupported);
+	if	(!aHeader.iPicture.IsId())
+		User::Leave(KErrBadHandle);
+
+	TStreamId id = aHeader.iPicture.AsId();
+	CHlpPicture* picture = CHlpPicture::NewLC(aDeferredPictureStore, id, *CurrentDatabase(), *const_cast<CHlpModel*>(this));
+	aHeader.iPicture = picture;
+
+	// Add picture to the picture array. We need to do this so that we can update the picture
+	// when the zoom size is changed by the UI.
+	iPictures->AppendL(picture);
+
+	CleanupStack::Pop(picture);
+	}
+
+EXPORT_C const CStreamStore& CHlpModel::StreamStoreL(TInt /*aPos*/) const
+/** Gets the current rich text store.
+
+@param aPos Unused
+@return Current rich text store */
+	{
+	__ASSERT_ALWAYS(iCurrentRichTextStore, Panic(EHlpNoPictureStore));
+	return *iCurrentRichTextStore;
+	}
+
+//
+//
+//
+
+EXPORT_C TInt CHlpModel::MatchUidL(TUid aUid)
+/** Searches the open help databases for the specified topic UID.
+
+@param aUid Topic UID to search for
+@return Index of the database if the item was found, or KErrNotFound if not */
+	{
+	const TInt KDatabaseCount = DatabaseCount();
+	for (TInt i=0; i<KDatabaseCount; i++)
+		{
+		if (iDatabases->At(i)->MatchUidL(aUid))
+			return i;
+		}
+	return KErrNotFound;
+	}
+
+EXPORT_C void CHlpModel::SetObserver(MHlpModelObserver* aObserver)
+/** Sets the client callback interface.
+
+@param aObserver Client callback interface */
+	{
+	iObserver = aObserver;
+	}
+
+//
+//
+//
+
+void CHlpModel::DoSearchL(TInt aType, const TDesC& aCriterion)
+	{
+	// Initialise the member data with the type of search we are about to perform,
+	// and also setup the search criteria which is needed to peform multiple searches
+	// across databases.
+	SetSearchType(aType);
+	SetCriterionL(aCriterion);
+
+	if	(!DatabaseCount())
+		{
+		ReportEventToObserverL(ETopicNotFound);
+		return;
+		}
+
+	// Prepare the searcher with the database to search
+	iSearch->SetDatabase(*CurrentDatabase());
+
+	// Indicate that this database is being searched, and therefore it is guaranteed not
+	// to have a null view
+	CurrentDatabase()->SetViewReady(ETrue);
+
+	// Get the searcher to actually do the search, which includes
+	// building the necessary SQL statement and then running the SQL on the
+	// correct table.
+	iSearch->SearchL(aType, *iCriterion);
+	}
+
+void CHlpModel::DoNextSearchL()
+//
+//	This function is used to perform an incremental search (mirroring an inner join which
+//	EPOC DBMS doesn't support) across multiple databases.
+//
+	{
+	if	(iCurrentDb < DatabaseCount()-1)
+		{
+		// If we're peforming a category Uid-based search, then
+		// we only run the sql seach on the databases that actually
+		// hold meta data on the specified category.
+		iCurrentDb++;
+		if	(CurrentSearchType() == ETopicListForCategoryUID)
+			{
+			__ASSERT_DEBUG(iTransientCategoryUid != KNullUid, Panic(EHlpFault));
+			TInt numberOfMatches = 0;
+			do
+				{
+				if	(CurrentDatabase()->MatchUidL(iTransientCategoryUid))
+					{
+					// This database does have meta data on the specified category,
+					// so it's worth running the check
+					DoSearchL(CurrentSearchType(), *iCriterion);
+					++numberOfMatches;
+					}
+				}
+			while (++iCurrentDb < DatabaseCount());
+			if	(numberOfMatches == 0)
+				ReportEventToObserverL(ESearchComplete);
+			}
+		else
+			{
+			// Doesn't matter what type of search it is, we still
+			// run the SQL :(
+			DoSearchL(CurrentSearchType(), *iCriterion);
+			}
+		}
+	else
+		{
+		ReportEventToObserverL(ESearchComplete);
+		}
+	}
+
+void CHlpModel::ResetReadyForSearch()
+//
+//	This function is called regardless of search type - it prepares all the necessary
+//	variables ready for a search.
+//
+	{
+	// Set to the first database
+	iCurrentDb=0;
+
+	// Indicate that we've currently not found any results
+	iFound=EFalse;
+
+	// Reset the database views to 'not yet ready'
+	ResetViews();
+	}
+
+void CHlpModel::ResetViews()
+//
+//	Go through each database and reset the "view ready" flag to EFalse
+//	to indicate that this particular database's view has not yet been primed.
+//
+	{
+	const TInt count = DatabaseCount();
+	for(TInt i=0; i<count; i++)
+		iDatabases->At(i)->SetViewReady(EFalse); // not ready
+	}
+
+void CHlpModel::SetCriterionL(const TDesC& aCriterion)
+//
+//	Updates the internal iCriterion pointer to contain the new
+//	criteria for searching.
+//
+	{
+	HBufC* newCriteria = aCriterion.AllocL();
+	delete iCriterion;
+	iCriterion = newCriteria;
+	}
+
+RDbView* CHlpModel::CurrentView() const
+	{
+	return &(CurrentDatabase()->View());
+	}
+
+//
+//
+//
+
+void CHlpModel::HandleDbEventL(TInt aEvent)
+//
+//	Called by the SQL searcher. This function routes responses from the searcher
+//	to the model observer, and performs and recursive searching that is required.
+//
+	{
+	switch(aEvent)
+		{
+	case ENoRecordsFound:
+		// No records were found for this search. If a context search was requested,
+		// we indicate that nothing was found and let the observer of the database
+		// perform any action. Otherwise, we search the next database in turn.
+		if	(CurrentSearchType() != ETopicIdSearch &&
+			 CurrentSearchType() != EContextSearch &&
+			 CurrentSearchType() != EIndexSearch
+			)
+			DoNextSearchL();
+		else
+			ReportEventToObserverL(ENoRecordsFound);
+		break;
+	case ESearchComplete:
+		// Indicate that at least some matching critera was found and then
+		// either end the search, or search the next database.
+		iFound=ETrue;
+		if	(CurrentSearchType() != ETopicIdSearch &&
+			 CurrentSearchType() != EContextSearch &&
+			 CurrentSearchType() != EIndexSearch
+			)
+			DoNextSearchL();
+		else
+			ReportEventToObserverL(ESearchComplete);
+		break;
+	case ESearchInProgress:
+		// Indicate to the observer that a search is in progress
+		ReportEventToObserverL(ESearchInProgress);
+		break;
+	case EHlpSearchCancelled:
+		// Indicate to the observer that a search was cancelled
+		ReportEventToObserverL(EHlpSearchCancelled);
+		break;
+	default:
+		__ASSERT_DEBUG(EFalse, Panic(EHlpFault));
+		break;
+		}
+	}
+
+void CHlpModel::ReportEventToObserverL(TInt aEvent)
+	{
+	if	(!iObserver)
+		return; // can't do anything without an observer
+
+	switch(aEvent)
+		{
+	case ESearchInProgress:
+		iObserver->HandleModelEventL(EModelSearchInProgress);
+		return;
+	case EHlpSearchCancelled:
+		iObserver->HandleModelEventL(EHlpSearchCancelled);
+		return;
+	default: // Keep GCC happy
+		break;
+		}
+
+	switch(CurrentSearchType())
+		{
+	case EIndexList:
+		iObserver->HandleModelEventL((iFound?EIndexListAvailable:EIndexListNoneFound));
+		break;
+	case ECategoryList:
+		iObserver->HandleModelEventL((iFound?ECategoryListAvailable:ECategoryListNoneFound));
+		break;
+	case ETopicListForCategory:
+	case ETopicListForCategoryUID:
+		iObserver->HandleModelEventL((iFound?ETopicListAvailable:ETopicListNoneFound));
+		break;
+	case EContextSearch:
+		iObserver->HandleModelEventL(aEvent);
+		break;
+	case EIndexSearch:
+		iObserver->HandleModelEventL((iFound?EIndexSearchListAvailable:EIndexSearchListNoneFound));
+		break;
+	case EQuickSearch:
+	case EFullTextSearch:
+		iObserver->HandleModelEventL((iFound?ESearchListAvailable:ESearchListNoneFound));
+		break;
+	case ETopicIdSearch:
+		iObserver->HandleModelEventL((iFound?ETopicAvailable:ETopicNotFound));
+		// event has been reported, reset now.
+		iFound=EFalse;
+		break;
+	default:
+		__ASSERT_DEBUG(EFalse, Panic(EHlpFault));
+		break;
+		}
+	}
+
+
+
+
+
+
+
+
+//
+// ----> CHlpList
+//
+
+EXPORT_C CHlpList::~CHlpList()
+/** Destructor. */
+	{
+	if(iList)
+		iList->ResetAndDestroy();
+	delete iList;
+	}
+
+void CHlpList::ConstructL()
+	{
+	iList = new(ELeave) CArrayPtrFlat<CHlpItem>(2);
+	}
+
+EXPORT_C CHlpList* CHlpList::NewL()
+/** Allocates and creates a new help list object.
+
+@return New help list object */
+	{
+	CHlpList* self = CHlpList::NewLC();
+	CleanupStack::Pop(self);
+	return self;
+	}
+
+EXPORT_C CHlpList* CHlpList::NewLC()
+/** Allocates and creates a new help list object, leaving the object on the cleanup
+stack.
+
+@return New help list object */
+	{
+	CHlpList* self = new(ELeave) CHlpList;
+	CleanupStack::PushL(self);
+	self->ConstructL();
+	return self;
+	}
+
+EXPORT_C TInt CHlpList::MdcaCount() const
+/** Gets the number of items in the list.
+
+@return Number of items in the list */
+	{
+	return iList->Count();
+	}
+
+EXPORT_C TPtrC CHlpList::MdcaPoint(TInt aIndex) const
+/** Gets the title of the item at the specified index.
+
+@param aIndex Item index
+@return Title of the item */
+	{
+	return TPtrC(iList->At(aIndex)->iTitle->Des());
+	}
+
+EXPORT_C TUint32 CHlpList::At(TInt aIndex) const
+/** Gets the topic ID of the item at the specified index.
+
+@param aIndex Item index
+@return Topic ID */
+	{
+	return iList->At(aIndex)->iId;
+	}
+
+EXPORT_C void CHlpList::Reset()
+/** Resets the list. */
+	{
+	iList->ResetAndDestroy();
+	}
+
+EXPORT_C CHlpItem* CHlpList::Item(TInt aIndex) const
+/** Gets the item at the specified index.
+
+@param aIndex Item index
+@return Item */
+	{
+	return iList->At(aIndex);
+	}
+
+EXPORT_C TInt CHlpList::Find(TUint32 aId)
+/** Searches the list for a specified item ID.
+
+@param aId Item ID
+@return Item index, or KErrNotFound if not found */
+	{
+	CHlpItem* item = new CHlpItem(aId);
+	if	(!item)
+		return KErrNoMemory;
+
+	TKeyArrayFix key(_FOFF(CHlpItem, iId), ECmpTUint32);
+	TInt pos;
+	TInt result = iList->Find(item, key, pos);
+	delete item;
+
+	if (!result)
+		return pos;
+	else
+		return KErrNotFound;
+	}
+
+EXPORT_C void CHlpList::AppendL(CHlpItem* aItem)
+/** Appends an item to the list.
+
+@param aItem Item to add */
+	{
+	__ASSERT_ALWAYS(aItem, Panic(EHlpNoItem));
+	iList->AppendL(aItem);
+	}
+
+
+
+
+
+//
+// ----> CHlpItem - representing an individual item in the help file
+//
+
+CHlpItem::CHlpItem(TUint32 aId)
+:	iId(aId), iCategoryUid(KNullUid), iHelpFileUid(KNullUid)
+	{
+	}
+
+CHlpItem::CHlpItem(TUint32 aId, TUid aHelpFileUid)
+:	iId(aId), iCategoryUid(KNullUid), iHelpFileUid(aHelpFileUid)
+	{
+	}
+
+CHlpItem::CHlpItem(TUint32 aId, TUid aCategoryId, TUid aHelpFileUid)
+:	iId(aId), iCategoryUid(aCategoryId), iHelpFileUid(aHelpFileUid)
+	{
+	}
+
+EXPORT_C CHlpItem::~CHlpItem()
+/** Destructor. */
+	{
+	delete iTitle;
+	}
+
+void CHlpItem::ConstructL(const TDesC& aTitle)
+	{
+	iTitle = aTitle.AllocL();
+	}
+
+CHlpItem* CHlpItem::NewL(const TDesC& aTitle, TUint32 aId, TUid aCategoryId, TUid aHelpFileUid)
+/** Allocates and creates a new help item object.
+
+@param aTitle Item title
+@param aId Item ID
+@param aCategoryId Category ID
+@param aHelpFileUid Help file UID
+@return New help item object */
+	{
+	CHlpItem* self = NewLC(aTitle, aId, aCategoryId, aHelpFileUid);
+	CleanupStack::Pop(self);
+	return self;
+	}
+
+CHlpItem* CHlpItem::NewLC(const TDesC& aTitle, TUint32 aId, TUid aCategoryId, TUid aHelpFileUid)
+/** Allocates and creates a new help item object, leaving the object on the cleanup
+stack.
+
+@param aTitle Item title
+@param aId Item ID
+@param aCategoryId Category ID
+@param aHelpFileUid Help file UID
+@return New help item object */
+	{
+	CHlpItem* self = new(ELeave) CHlpItem(aId, aCategoryId, aHelpFileUid);
+	CleanupStack::PushL(self);
+	self->ConstructL(aTitle);
+	return self;
+	}
+
+CHlpItem* CHlpItem::NewLC(const TDesC& aTitle, TUint32 aId, TUid aHelpFileUid)
+/** Allocates and creates a new help item object, leaving the object on the cleanup
+stack.
+
+This overload does not specify a category UID.
+
+@param aTitle Item title
+@param aId Item ID
+@param aHelpFileUid Help file UID
+@return New help item object */
+	{
+	CHlpItem* self = new(ELeave) CHlpItem(aId, aHelpFileUid);
+	CleanupStack::PushL(self);
+	self->ConstructL(aTitle);
+	return self;
+	}
+
+
+
+
+
+
+//
+// ----> CHlpTopic
+//
+
+EXPORT_C CHlpTopic::~CHlpTopic()
+	{
+	delete iTopicText;
+	delete iGlobalCharFormatLayer;
+	delete iGlobalParaFormatLayer;
+	}
+
+void CHlpTopic::ConstructL()
+	{
+	// Create the necessary formatting layers for the rich text object.
+	iGlobalParaFormatLayer	= CParaFormatLayer::NewL();
+	iGlobalCharFormatLayer	= CCharFormatLayer::NewL();
+	iTopicText				= CRichText::NewL(iGlobalParaFormatLayer, iGlobalCharFormatLayer);
+	}
+
+EXPORT_C CHlpTopic* CHlpTopic::NewL()
+/** Allocates and creates a new help topic object.
+
+@return New help topic object */
+	{
+	CHlpTopic* self = CHlpTopic::NewLC();
+	CleanupStack::Pop(self);
+	return self;
+	}
+
+EXPORT_C CHlpTopic* CHlpTopic::NewLC()
+/** Allocates and creates a new help topic object, leaving the object on the cleanup
+stack.
+
+@return New help topic object */
+	{
+	CHlpTopic* self = new(ELeave) CHlpTopic();
+	CleanupStack::PushL(self);
+	self->ConstructL();
+	return self;
+	}
+
+EXPORT_C void CHlpTopic::RestoreL(RDbView* aView)
+/** Restores the object from a database view.
+
+This only restores text and markup, not pictures. Help application authors
+should use CHlpModel::LoadTopicL() instead.
+
+@param aView Database view */
+	{
+	// NOTE: This function does not restore pictures because it does not know
+	// which help model database to use as the source for the picture table.
+	// Use CHlpModel::LoadTopicL(...) instead
+	__ASSERT_ALWAYS(aView, Panic(EHlpNoView));
+	__ASSERT_ALWAYS(aView->AtRow(), Panic(EHlpNoRowAtCursor));
+
+	aView->FirstL();
+	CDbColSet* colset	= aView->ColSetL();
+	TDbColNo topicCol	= colset->ColNo(KSQLTopicTextColumn);
+	TDbColNo markupCol	= colset->ColNo(KSQLTopicMarkupColumn);
+	TDbColNo titleCol	= colset->ColNo(KSQLTopicTitleColumn);
+	TDbColNo catCol		= colset->ColNo(KSQLCategoryColumn);
+	TDbColNo idCol		= colset->ColNo(KSQLTopicIdColumn);
+	delete colset;
+
+	aView->GetL();
+
+	TInt len = aView->ColLength(topicCol);
+	HBufC* buf=HBufC::NewLC(len);
+	TPtr pBuf(buf->Des());
+
+	RDbColReadStream stream;
+	stream.OpenLC(*aView, topicCol);
+	stream.ReadL(pBuf, len);
+	iTopicText->Reset();
+	iTopicText->InsertL(0, *buf);
+	CleanupStack::PopAndDestroy(2); // stream, buf
+
+	if	(!aView->IsColNull(markupCol))
+		{
+		RDbColReadStream blob;
+		blob.OpenL(*aView, markupCol);
+		CEmbeddedStore* embeddedStore = CEmbeddedStore::FromLC(blob);
+		RStoreReadStream readStream;
+		readStream.OpenLC(*embeddedStore, embeddedStore->Root());
+		iTopicText->InternalizeMarkupDataL(readStream);
+		CleanupStack::PopAndDestroy(2); // embeddedStore, readStream
+		}
+
+	iCategory.Append(aView->ColDes(catCol));
+	iTopicTitle.Append(aView->ColDes(titleCol));
+	iTopicId=aView->ColUint32(idCol);
+	}
+
+EXPORT_C CRichText* CHlpTopic::TopicText()
+/** Gets the topic text.
+
+@return Topic text */
+	{
+	return iTopicText;
+	}
+
+EXPORT_C TDesC& CHlpTopic::TopicTitle()
+/** Gets the topic title.
+
+@return Topic title */
+	{
+	return iTopicTitle;
+	}
+
+EXPORT_C TDesC& CHlpTopic::Category()
+/** Gets the topic category.
+
+@return Topic category */
+	{
+	return iCategory;
+	}