diff -r 000000000000 -r 1f04cf54edd8 symhelp/helpmodel/src/HLPMODEL.CPP --- /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 +#include +#include + +// 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 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; iAt(i); + picture.HandleHelpModelDestruction(); + } + } + + +void CHlpModel::ConstructL() + { + iDatabases = new(ELeave) CHlpDatabases(2); + iPictures = new(ELeave) CArrayPtrFlat(KAverageNumberOfPicturesInHelpTopic); + iSearch = CHlpSQLSearch::NewL(*this); + + // There must always be 3 default zoom ratios present in the array... + iZoomFactors = new(ELeave) CArrayFixFlat(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; iAt(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* array=REINTERPRET_CAST(CArrayPtr*,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; iCount(); + 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; iAt(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; iAt(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; iAt(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; iAt(i); + if (database->HelpFileUid() == aHelpItem.HelpFileUid()) + { + // Run the SQL on this database. + iCurrentDb = i; + + TBuf 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 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; iAt(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; iAt(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; iAt(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(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; iAt(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; iAt(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(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; + }