/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public License * Version 1.0 (the "NPL"); you may not use this file except in * compliance with the NPL. You may obtain a copy of the NPL at * http://www.mozilla.org/NPL/ * * Software distributed under the NPL is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL * for the specific language governing rights and limitations under the * NPL. * * The Initial Developer of this code under the NPL is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All Rights * Reserved. */ #include "msg.h" #include "msgdb.h" #include "msgdbvw.h" #include "thrdbvw.h" #include "dberror.h" #include "vwerror.h" #include "newsdb.h" #include "maildb.h" #include "thrhead.h" #include "grpinfo.h" #include "chngntfy.h" #include "msgmast.h" #include "thrnewvw.h" #include "xpgetstr.h" #include "addrbook.h" #include "dirprefs.h" #include "msglpane.h" #include "xp_qsort.h" #include "intl_csi.h" #include "msgimap.h" extern "C" { extern int MK_MSG_FIRST_MSG; extern int MK_MSG_NEXT_MSG; extern int MK_MSG_PREV_MSG; extern int MK_MSG_LAST_MSG; extern int MK_MSG_FIRST_UNREAD; extern int MK_MSG_NEXT_UNREAD; extern int MK_MSG_PREV_UNREAD; extern int MK_MSG_LAST_UNREAD; extern int MK_MSG_READ_MORE; extern int MK_MSG_NEXTUNREAD_THREAD; extern int MK_MSG_NEXTUNREAD_GROUP; extern int MK_MSG_FIRST_FLAGGED; extern int MK_MSG_NEXT_FLAGGED; extern int MK_MSG_PREV_FLAGGED; } #ifdef WINDOWS #include "windowsx.h" #else #define GlobalAllocPtr(a,b) XP_ALLOC(b) #define GlobalFreePtr(p) XP_FREE(p) #endif ViewChangeListener::ViewChangeListener(MessageDBView *view) { m_dbView = view; } ViewChangeListener::~ViewChangeListener() { } void ViewChangeListener::OnViewChange(MSG_ViewIndex startIndex, int32 numChanged, MSG_NOTIFY_CODE changeType, ChangeListener * instigator) { // propogate change to views' listeners // if we're not the instigator, update flags if this key is in our view if (instigator != &m_dbView->m_changeListener) { m_dbView->NotifyViewChangeAll(startIndex, numChanged, changeType, instigator); } } void ViewChangeListener::OnViewStartChange(MSG_ViewIndex startIndex, int32 numChanged, MSG_NOTIFY_CODE changeType, ChangeListener * instigator) { // propogate change to views' listeners // if we're not the instigator, update flags if this key is in our view if (instigator != &m_dbView->m_changeListener) { m_dbView->NotifyViewStartChangeAll(startIndex, numChanged, changeType, instigator); } } void ViewChangeListener::OnViewEndChange(MSG_ViewIndex startIndex, int32 numChanged, MSG_NOTIFY_CODE changeType, ChangeListener * instigator) { // propogate change to views' listeners // if we're not the instigator, update flags if this key is in our view if (instigator != &m_dbView->m_changeListener) { m_dbView->NotifyViewEndChangeAll(startIndex, numChanged, changeType, instigator); } } void ViewChangeListener::OnKeyChange(MessageKey keyChanged, int32 flags, ChangeListener *instigator) { // if we're not the instigator, update flags if this key is in our view if (instigator != &m_dbView->m_changeListener) { if (flags & kAdded) // message just downloaded { m_dbView->OnNewHeader(keyChanged, FALSE); } else { MSG_ViewIndex index = m_dbView->FindViewIndex(keyChanged); if (index != kViewIndexNone) { char extraFlag; m_dbView->SetExtraFlagsFromDBFlags(flags, index); // tell the view the extra flag changed, so it can // update the previous view, if any. if (m_dbView->GetExtraFlag(index, &extraFlag) == eSUCCESS) m_dbView->OnExtraFlagChanged(index, extraFlag); if (flags & (kExpired|kExpunged)) m_dbView->DeleteMsgByIndex(index, FALSE); else m_dbView->NoteChange(index, 1, MSG_NotifyChanged); } else { MSG_ViewIndex threadIndex = m_dbView->ThreadIndexOfMsg(keyChanged); // may need to fix thread counts if (threadIndex != MSG_VIEWINDEXNONE) m_dbView->NoteChange(threadIndex, 1, MSG_NotifyChanged); } } } // propogate change to views' listeners, even if we're not instigator m_dbView->NotifyKeyChangeAll(keyChanged, flags, instigator); } void ViewChangeListener::OnAnnouncerGoingAway (ChangeAnnouncer * instigator) { m_dbView->NotifyAnnouncerGoingAway(instigator); // shout it to the world! m_dbView->m_messageDB->RemoveListener(this); m_dbView->m_messageDB = NULL; } /*static*/ uint32 MessageDBView::m_publicEquivOfExtraFlags; MessageDBView::MessageDBView() : m_changeListener(this) { m_messageDB = NULL; m_refCount = 1; m_sortValid = TRUE; m_sortOrder = SortTypeNone; m_viewFlags = (ViewFlags) 0; if (m_publicEquivOfExtraFlags == 0) { // first, find out what the 32 bit public equivalent of the extra flags is. CopyExtraFlagsToDBFlags((char) 0xFF, &m_publicEquivOfExtraFlags); MessageDB::ConvertDBFlagsToPublicFlags(&m_publicEquivOfExtraFlags); } } MessageDBView::~MessageDBView() { NotifyAnnouncerGoingAway(this); CacheRemove (); XP_ASSERT(m_messageDB == NULL); // should be NULL if no errors closing the DB } // static method which given a URL string returns a view on that URL. // For example, if url = "news:secnews-alt.music.alternative", and viewType = ViewOnlyThreadsWithNew // We will return a threaded NewsDBView on the newsgroup alt.music.alternative on // the host secnews with only threads with new. MsgERR MessageDBView::OpenURL(const char * url, MSG_Master* master, ViewType viewType, MessageDBView **view, XP_Bool openInForeground) { MailDB *mailDB = NULL; NewsGroupDB *newsDB = NULL; MessageDBView *retView = NULL; MsgERR err = eSUCCESS; *view = NULL; const char *startFolder; char *endFolder = NULL; char *justFolder = NULL; int urlType = NET_URL_Type(url); switch (urlType) { case NEWS_TYPE_URL: // news:alt.music.alternative - news group // news:4agiou%24g7n@news.utdallas.edu - message-id err = NewsGroupDB::Open(url, master, &newsDB); if (newsDB != NULL && err == eSUCCESS) { if (viewType == ViewAny) viewType = newsDB->GetViewType(); // use last opened view type. // allow this view opening to run in the background. We should do this // for mail and imap too, but that involves reworking msgtpane.cpp // to deal with eBuildViewInBackground. int32 numHeadersInDB = newsDB->GetDBFolderInfo()->GetNumMessages(); MSG_FolderInfoNews *newsFolder = newsDB->GetFolderInfoNews(); if (!openInForeground) // if caller doesn't care openInForeground = (numHeadersInDB < 1000); // always open category containers in background, so we can check for new categories. err = OpenViewOnDB(newsDB, viewType, &retView, openInForeground); newsDB->SetViewType(viewType); // remember this view type. newsDB->Close(); // always close - above will addref. *view = retView; if (newsDB == NULL || retView == NULL) return err; } break; case IMAP_TYPE_URL: { switch (viewType) { case ViewWatchedThreadsWithNew: case ViewOnlyThreadsWithNew: case ViewAllThreads: case ViewOnlyNewHeaders: case ViewAny: case ViewCacheless: { MailDB *mailDB = NULL; // strip off id info to get just folder path startFolder = url + strlen("IMAP:"); char *host = NET_ParseURL (url, GET_HOST_PART); char *owner = NET_ParseURL (url, GET_USERNAME_PART); char *path = NET_ParseURL (url, GET_PATH_PART); if (!host || !path || !owner) return eOUT_OF_MEMORY; MSG_IMAPFolderInfoMail *folder = master->FindImapMailFolder(host, path + 1, owner, FALSE); FREEIF(host); FREEIF(owner); FREEIF(path); if (!folder) return eOUT_OF_MEMORY; // ### dmb - what kind of error is this? XP_Bool dbWasCreated=FALSE; err = ImapMailDB::Open(folder->GetPathname(), TRUE , &mailDB, master, &dbWasCreated); if (mailDB != NULL && err == eSUCCESS) { if (viewType == ViewAny) viewType = mailDB->GetViewType(); // use last opened view type. err = OpenViewOnDB(mailDB, viewType, &retView, TRUE); mailDB->SetViewType(viewType); // remember this view type. mailDB->Close(); // always close - above will addref. if (err != eSUCCESS) return err; *view = retView; if (mailDB == NULL || retView == NULL) return err; } if (justFolder) XP_FREE (justFolder); } break; case ViewCustom: { *view = new ThreadDBView(viewType); if (!*view) err = eOUT_OF_MEMORY; else err = eSUCCESS; } break; } } break; case MAILBOX_TYPE_URL: // strip off id info to get just folder path startFolder = url + strlen("mailbox:"); justFolder = XP_STRDUP (startFolder); if (!justFolder) return eOUT_OF_MEMORY; endFolder = XP_STRSTR (justFolder, "?id="); if (endFolder) endFolder[0] = '\0'; err = MailDB::Open(justFolder, FALSE /* create? */, &mailDB); if (mailDB != NULL && err == eSUCCESS) { retView = CacheLookup (mailDB, viewType); if (retView) { retView->AddReference (); mailDB->Close(); // adjust ref count down. } else { if (viewType == ViewAny) viewType = mailDB->GetViewType(); // use last opened view type. int32 numHeadersInDB = mailDB->GetDBFolderInfo()->GetNumMessages(); if (!openInForeground) // if caller doesn't care openInForeground = (numHeadersInDB < 1000); // try opening large mail dbs in background err = OpenViewOnDB(mailDB, viewType, &retView, openInForeground); mailDB->SetViewType(viewType); // remember this view type. mailDB->Close(); // always close - above will addref. if (retView == NULL) { mailDB->Close(); err = eOUT_OF_MEMORY; } } *view = retView; if (mailDB == NULL || retView == NULL) return err; } if (justFolder) XP_FREE (justFolder); break; default: return eBAD_URL; } return err; } // static method which creates and opens a view of the passed type on the already open db MsgERR MessageDBView::OpenViewOnDB(MessageDB *msgDB, ViewType viewType, MessageDBView ** pRetView, XP_Bool runInForeground /* = TRUE */) { MsgERR err = eSUCCESS; *pRetView = CacheLookup (msgDB, viewType); if (*pRetView) (*pRetView)->AddReference (); else { switch (viewType) { case ViewWatchedThreadsWithNew: // this can just filter out non-watched threads... case ViewOnlyThreadsWithNew: *pRetView = new ThreadsWithNewView(viewType); break; case ViewOnlyNewHeaders: case ViewAny: case ViewAllThreads: *pRetView = new ThreadDBView(viewType); break; case ViewCustom: *pRetView = new ThreadDBView(viewType); break; case ViewCacheless: *pRetView = new CachelessView(viewType); default: err = eInvalidViewType; break; } if (*pRetView != NULL) { err = (*pRetView)->Open(msgDB, viewType, NULL, runInForeground); msgDB->AddUseCount(); // add view as user of db. } } return err; } MsgERR MessageDBView::Open(MessageDB *messageDB, ViewType viewType, uint32* /*pCount*/, XP_Bool /* runInForeground = TRUE */) { m_messageDB = messageDB; m_messageDB->AddListener(&m_changeListener); if (viewType == ViewAny) viewType = ViewAllThreads; m_viewType = viewType; if (messageDB && messageDB->GetDBFolderInfo()->GetFlags() & MSG_FOLDER_PREF_SHOWIGNORED) m_viewFlags = (ViewFlags) ((int32) kShowIgnored | (int32) m_viewFlags); if (messageDB && messageDB->GetDBFolderInfo() && (viewType == ViewOnlyNewHeaders)) m_viewFlags = (ViewFlags) ((int32) kUnreadOnly | (int32) m_viewFlags); CacheAdd (); return eSUCCESS; } MsgERR MessageDBView::Close() { //if it's zero or negative, we should already have been deleted XP_ASSERT(m_refCount > 0); MsgERR err = eSUCCESS; // opening a view, even from cache, always adds a ref count to db, // so we should always close it. if (!--m_refCount) { if (m_messageDB != NULL) { m_messageDB->RemoveListener(&m_changeListener); err = m_messageDB->Close(); m_messageDB = NULL; } delete this; } else if (m_messageDB != NULL) { err = m_messageDB->Close(); } return err; } MsgERR MessageDBView::Init(uint32 * /*pCount*/, XP_Bool /*runInForeground = TRUE*/) { // MessageDBView is pure virtual in spirit. Subclasses need to override this. XP_ASSERT(FALSE); return eSUCCESS; } MsgERR MessageDBView::InitSort(SortType /*sortType*/, SortOrder /*sortOrder*/) { return eSUCCESS; } int32 MessageDBView::AddKeys(MessageKey * /*pOutput*/, int32 * /*pFlags*/, char * /*pLevels*/, SortType /*sortType*/, int /*numListed*/) { XP_ASSERT(FALSE); return 0; } XP_Bool MessageDBView::GetShowingIgnored() { return m_viewFlags & kShowIgnored; } void MessageDBView::SetShowingIgnored(XP_Bool bShowIgnored) { if (bShowIgnored) m_viewFlags |= kShowIgnored; else m_viewFlags &= ~kShowIgnored; } MsgERR MessageDBView::AddHdr(DBMessageHdr *msgHdr) { char flags = 0; #ifdef DEBUG_bienvenu XP_ASSERT((int) m_idArray.GetSize() == m_flags.GetSize() && (int) m_idArray.GetSize() == m_levels.GetSize()); #endif if (msgHdr->GetFlags() & kIgnored && !GetShowingIgnored()) return eSUCCESS; CopyDBFlagsToExtraFlags(msgHdr->GetFlags(), &flags); if (msgHdr->GetArticleNum() == msgHdr->GetThreadId()) flags |= kIsThread; MSG_ViewIndex insertIndex = GetInsertIndex(msgHdr); if (insertIndex == MSG_VIEWINDEXNONE) { // if unreadonly, level is 0 because we must be the only msg in the thread. char levelToAdd = (m_viewFlags & kUnreadOnly) ? 0 : msgHdr->GetLevel(); if (m_sortOrder == SortTypeAscending) { m_idArray.Add(msgHdr->GetMessageKey()); m_flags.Add(flags); m_levels.Add(levelToAdd); NoteChange(m_idArray.GetSize() - 1, 1, MSG_NotifyInsertOrDelete); } else { m_idArray.InsertAt(0, msgHdr->GetMessageKey()); m_flags.InsertAt(0, flags); m_levels.InsertAt(0, levelToAdd); NoteChange(0, 1, MSG_NotifyInsertOrDelete); } m_sortValid = FALSE; } else { m_idArray.InsertAt(insertIndex, msgHdr->GetMessageKey()); m_flags.InsertAt(insertIndex, flags); m_levels.InsertAt(insertIndex, (m_sortType == SortByThread) ? 0 : msgHdr->GetLevel()); NoteChange(insertIndex, 1, MSG_NotifyInsertOrDelete); } OnHeaderAddedOrDeleted(); return eSUCCESS; } MsgERR MessageDBView::InsertHdrAt(DBMessageHdr *msgHdr, MSG_ViewIndex insertIndex) { char flags = 0; CopyDBFlagsToExtraFlags(msgHdr->GetFlags(), &flags); NoteStartChange(insertIndex, 1, MSG_NotifyChanged); m_idArray.SetAt(insertIndex, msgHdr->GetMessageKey()); m_flags.SetAt(insertIndex, flags); m_levels.SetAt(insertIndex, (m_sortType == SortByThread) ? 0 : msgHdr->GetLevel()); NoteEndChange(insertIndex, 1, MSG_NotifyChanged); OnHeaderAddedOrDeleted(); return eSUCCESS; } MsgERR MessageDBView::OnNewHeader(MessageKey newKey, XP_Bool /*ensureListed*/) { MsgERR err = eID_NOT_FOUND;; // views can override this behaviour, which is to append to view. // This is the mail behaviour, but threaded views might want // to insert in order... DBMessageHdr *msgHdr = m_messageDB->GetDBHdrForKey(newKey); if (msgHdr != NULL) { err = AddHdr(msgHdr); delete msgHdr; } return err; } XP_Bool MessageDBView::WantsThisThread(DBThreadMessageHdr * /*threadHdr*/) { return TRUE; } MsgERR MessageDBView::FinishedAddingHeaders() { return eSUCCESS; } // make sure the passed key is "in" the view (e.g., for a threaded sort, this // may just mean the parent thread is in the view). void MessageDBView::EnsureListed(MessageKey key) { if (key != MSG_MESSAGEKEYNONE) { // find the key, expanding if neccessary. MSG_ViewIndex index = FindKey(key, TRUE); if (index == MSG_VIEWINDEXNONE) // tell the view about it. OnNewHeader(key, TRUE); } } MessageKey MessageDBView::GetAt(MSG_ViewIndex index) { if (index >= m_idArray.GetSize() || index == MSG_VIEWINDEXNONE) return kIdNone; else return(m_idArray.GetAt(index)); } MSG_ViewIndex MessageDBView::FindKey(MessageKey key, XP_Bool expand) { MSG_ViewIndex retIndex = MSG_VIEWINDEXNONE; retIndex = (MSG_ViewIndex) (m_idArray.FindIndex(key)); if (key != MSG_MESSAGEKEYNONE && retIndex == MSG_VIEWINDEXNONE && expand && m_messageDB) { MessageKey threadKey = m_messageDB->GetKeyOfFirstMsgInThread(key); if (threadKey != kIdNone) { MSG_ViewIndex threadIndex = FindKey(threadKey, FALSE); if (threadIndex != MSG_VIEWINDEXNONE) { char flags = m_flags[threadIndex]; if ((flags & kElided) && ExpandByIndex(threadIndex, NULL) == eSUCCESS) retIndex = FindKey(key, FALSE); } } } return retIndex; } typedef struct entryInfo { MessageKey id; char bits; } EntryInfo; typedef struct tagIdStr{ EntryInfo info; char str[1]; } IdStr; typedef struct tagIdStrPtr{ EntryInfo info; const char *strPtr; } IdStrPtr; static int /* __cdecl */ FnSortIdStr(const void* pItem1, const void* pItem2) { IdStr** p1 = (IdStr**)pItem1; IdStr** p2 = (IdStr**)pItem2; int retVal = XP_STRCMP((*p1)->str, (*p2)->str); // used to be strcasecmp, but INTL sorting routine lower cases it. if (retVal != 0) return(retVal); if ((*p1)->info.id >= (*p2)->info.id) return(1); else return(-1); } static int /* __cdecl */ FnSortIdStrPtr(const void* pItem1, const void* pItem2) { IdStrPtr** p1 = (IdStrPtr**)pItem1; IdStrPtr** p2 = (IdStrPtr**)pItem2; int retVal = XP_STRCMP((*p1)->strPtr, (*p2)->strPtr); // used to be strcasecmp, but INTL sorting routine lower cases it. if (retVal != 0) return(retVal); if ((*p1)->info.id >= (*p2)->info.id) return(1); else return(-1); } typedef struct tagIdWord{ EntryInfo info; uint16 word; } IdWord; static int /* __cdecl */ FnSortIdWord(const void* pItem1, const void* pItem2) { IdWord** p1 = (IdWord**)pItem1; IdWord** p2 = (IdWord**)pItem2; long retVal = (*p1)->word - (*p2)->word; if (retVal > 0) return(1); else if (retVal < 0) return(-1); else if ((*p1)->info.id >= (*p2)->info.id) return(1); else return(-1); } typedef struct tagIdDWord{ EntryInfo info; uint32 dword; } IdDWord; static int /* __cdecl */ FnSortIdDWord(const void* pItem1, const void* pItem2) { IdDWord** p1 = (IdDWord**)pItem1; IdDWord** p2 = (IdDWord**)pItem2; if ((*p1)->dword > (*p2)->dword) return(1); else if ((*p1)->dword < (*p2)->dword) return(-1); else if ((*p1)->info.id >= (*p2)->info.id) return(1); else return(-1); } MSG_ViewIndex MessageDBView::GetInsertIndex(DBMessageHdr *msgHdr) { XP_Bool done = FALSE; XP_Bool withinOne = FALSE; MSG_ViewIndex retIndex = MSG_VIEWINDEXNONE; MSG_ViewIndex tryIndex = GetSize() / 2; MSG_ViewIndex newTryIndex; MSG_ViewIndex lowIndex = 0; MSG_ViewIndex highIndex = GetSize() - 1; IdDWord dWordEntryInfo1, dWordEntryInfo2; IdStrPtr strPtrInfo1, strPtrInfo2; if (GetSize() == 0) return 0; uint16 maxLen; int16 csid = (GetDB()->GetDBFolderInfo()->GetCSID() & ~CS_AUTO); XPStringObj field1Str; XPStringObj field2Str; eFieldType fieldType = GetFieldTypeAndLenForSort(m_sortType, &maxLen); const void *pValue1, *pValue2; char *intlString1 = NULL; if (m_sortType == SortByThread) // punt on threaded view for now. return retIndex; int (*comparisonFun) (const void *pItem1, const void *pItem2)=NULL; int retStatus = 0; switch (fieldType) { case kString: comparisonFun = FnSortIdStrPtr; strPtrInfo1.strPtr = intlString1 = GetStringField(msgHdr, m_sortType, csid, field1Str); strPtrInfo1.info.id = msgHdr->GetMessageKey(); pValue1 = &strPtrInfo1; break; case kU16: case kU32: pValue1 = &dWordEntryInfo1; dWordEntryInfo1.dword = GetLongField(msgHdr, m_sortType); dWordEntryInfo1.info.id = msgHdr->GetMessageKey(); comparisonFun = FnSortIdDWord; break; default: done = TRUE; } while (!done) { if (highIndex == lowIndex) break; MessageKey messageKey = GetAt(tryIndex); DBMessageHdr *tryHdr = m_messageDB->GetDBHdrForKey(messageKey); char *intlString2 = NULL; if (!tryHdr) break; if (fieldType == kString) { strPtrInfo2.strPtr = intlString2 = GetStringField(tryHdr, m_sortType, csid, field2Str); strPtrInfo2.info.id = messageKey; pValue2 = &strPtrInfo2; } else { dWordEntryInfo2.dword = GetLongField(tryHdr, m_sortType); dWordEntryInfo2.info.id = messageKey; pValue2 = &dWordEntryInfo2; } delete tryHdr; retStatus = (*comparisonFun)(&pValue1, &pValue2); FREEIF(intlString2); if (retStatus == 0) break; if (m_sortOrder == SortTypeDescending) //switch retStatus based on sort order retStatus = (retStatus > 0) ? -1 : 1; if (retStatus < 0) { newTryIndex = tryIndex - (tryIndex - lowIndex) / 2; if (newTryIndex == tryIndex) { if (!withinOne && newTryIndex > lowIndex) { newTryIndex--; withinOne = TRUE; } } highIndex = tryIndex; } else { newTryIndex = tryIndex + (highIndex - tryIndex) / 2; if (newTryIndex == tryIndex) { if (!withinOne && newTryIndex < highIndex) { withinOne = TRUE; newTryIndex++; } lowIndex = tryIndex; } } if (tryIndex == newTryIndex) break; else tryIndex = newTryIndex; } if (retStatus >= 0) retIndex = tryIndex + 1; else if (retStatus < 0) retIndex = tryIndex; FREEIF(intlString1); return retIndex; } MsgERR MessageDBView::ExternalSort(SortType sortType, XP_Bool sort_forward_p) { SortOrder sortOrder = (sort_forward_p) ? SortTypeAscending : SortTypeDescending; return Sort(sortType, sortOrder); } MsgERR MessageDBView::Sort(SortType sortType, SortOrder sortOrder) { return SortInternal(sortType, sortOrder); } MsgERR MessageDBView::SortInternal(SortType sortType, SortOrder sortOrder) { int arraySize; MsgERR err = eSUCCESS; XPByteArray *pBits = GetFlagsArray(); XPPtrArray ptrs; XPStringObj fieldStr; int16 csid = (GetDB()->GetDBFolderInfo()->GetCSID() & ~CS_AUTO); arraySize = GetSize(); if (sortType == m_sortType && m_sortValid) { if (sortOrder == m_sortOrder) { return eSUCCESS; } else { if (sortType != SortByThread) { // reverse the order ReverseSort(); err = eSUCCESS; } else { err = ReverseThreads(); } m_sortOrder = sortOrder; m_messageDB->SetSortInfo(sortType, sortOrder); return err; } } if (sortType == SortByThread) return eSUCCESS; uint16 maxLen; eFieldType fieldType = GetFieldTypeAndLenForSort(sortType, &maxLen); int i; // This function uses GlobalAlloc because I don't want to fragment up out heap with these potentially large memory blocks // Also, freeing a heap block does not return the block to the system, but freeing a globalAlloc block does. IdStr** pPtrBase = (IdStr**)GlobalAllocPtr(GMEM_MOVEABLE, arraySize * sizeof(IdStr*)); if (pPtrBase) { int numSoFar = 0; // calc max possible size needed for all the rest uint32 maxSize = (uint32)(maxLen + sizeof(EntryInfo) + 1) * (uint32)(arraySize - numSoFar); uint32 maxBlockSize = (uint32) 0xf000L; uint32 allocSize = MIN(maxBlockSize, maxSize); char * pTemp = (char *)GlobalAllocPtr(GMEM_MOVEABLE, allocSize); char * pBase = pTemp; if (pTemp) { ptrs.Add(pTemp); // keep track of this so we can free them all XP_Bool more = TRUE; DBMessageHdr *msgHdr = NULL; uint32 longValue; while (more && numSoFar < arraySize) { MessageKey thisKey = m_idArray.GetAt(numSoFar); if (sortType != SortById) { msgHdr = m_messageDB->GetDBHdrForKey(thisKey); if (msgHdr == NULL) { err = eID_NOT_FOUND; break; } } else msgHdr = NULL; // could be a problem here if the ones that appear here are different than the ones already in the array const char* pField; char *intlString = NULL; int paddedFieldLen; int actualFieldLen; if (fieldType == kString) { pField = intlString = GetStringField(msgHdr, sortType, csid, fieldStr); actualFieldLen = (pField) ? strlen(pField) + 1 : 1; paddedFieldLen = actualFieldLen; int mod4 = actualFieldLen % 4; if (mod4 > 0) paddedFieldLen += 4 - mod4; } else { longValue = (sortType == SortById) ? thisKey : GetLongField(msgHdr, sortType); pField = (const char *) &longValue; actualFieldLen = paddedFieldLen = maxLen; } // check to see if this entry fits into the block we have allocated so far // pTemp - pBase = the space we have used so far // sizeof(EntryInfo) + fieldLen = space we need for this entry // allocSize = size of the current block if ((uint32)(pTemp - pBase) + (uint32)sizeof(EntryInfo) + (uint32)paddedFieldLen >= allocSize) { maxSize = (uint32)(maxLen + sizeof(EntryInfo) + 1) * (uint32)(arraySize - numSoFar); maxBlockSize = (uint32) 0xf000L; allocSize = MIN(maxBlockSize, maxSize); pTemp = (char*)GlobalAllocPtr(GMEM_MOVEABLE, allocSize); if (!pTemp) { err = eOUT_OF_MEMORY; break; } pBase = pTemp; ptrs.Add(pTemp); // remember this pointer so we can free it later } // make sure there aren't more IDs than we allocated space for if (numSoFar >= arraySize) { err = eOUT_OF_MEMORY; break; } // now store this entry away in the allocated memory pPtrBase[numSoFar] = (IdStr*)pTemp; EntryInfo *info = (EntryInfo *) pTemp; info->id = thisKey; char bits = 0; bits = m_flags[numSoFar]; info->bits = bits; pTemp += sizeof(EntryInfo); int32 bytesLeft = allocSize - (int32)(pTemp - pBase); int32 bytesToCopy = MIN(bytesLeft, actualFieldLen); if (pField) { memcpy((char *)pTemp, pField, bytesToCopy); if (bytesToCopy < actualFieldLen) { #ifdef DEBUG_bienvenu XP_ASSERT(FALSE); // wow, big block #endif *(pTemp + bytesToCopy) = '\0'; } FREEIF(intlString); // free intl'ized string } else *pTemp = 0; pTemp += paddedFieldLen; if (msgHdr) delete msgHdr; ++numSoFar; } if (err == eSUCCESS) { // now sort the array based on the appropriate type of comparison switch(fieldType) { case kString: XP_QSORT(pPtrBase, numSoFar, sizeof(IdStr*), FnSortIdStr); break; case kU16: XP_QSORT(pPtrBase, numSoFar, sizeof(IdWord*), FnSortIdWord); break; case kU32: XP_QSORT(pPtrBase, numSoFar, sizeof(IdDWord*), FnSortIdDWord); break; default: XP_ASSERT(FALSE); // not supposed to get here break; } // now puts the IDs into the array in proper order for (i = 0; i < numSoFar; i++) { m_idArray.SetAt(i, pPtrBase[i]->info.id); if (pBits != NULL) pBits->SetAt(i, pPtrBase[i]->info.bits); } m_sortType = sortType; m_sortOrder = sortOrder; if (sortOrder == SortTypeDescending) { ReverseSort(); } } } } // free all the memory we allocated for (i = 0; i < ptrs.GetSize(); i++) { GlobalFreePtr(ptrs[i]); } if (pPtrBase) GlobalFreePtr(pPtrBase); if (err == eSUCCESS) { m_sortValid = TRUE; m_messageDB->SetSortInfo(sortType, sortOrder); } return err; } MessageDBView::eFieldType MessageDBView::GetFieldTypeAndLenForSort(SortType sortType, uint16 *pMaxLen) { eFieldType fieldType; uint16 maxLen; switch (sortType) { case SortBySubject: fieldType = kString; maxLen = kMaxSubject; break; case SortByRecipient: fieldType = kString; maxLen = kMaxRecipient; break; case SortByAuthor: fieldType = kString; maxLen = kMaxAuthor; break; case SortByDate: fieldType = kU32; maxLen = sizeof(time_t); break; case SortByPriority: fieldType = kU32; maxLen = sizeof(uint32); break; case SortByThread: case SortById: case SortBySize: case SortByFlagged: case SortByUnread: case SortByStatus: fieldType = kU32; maxLen = sizeof(uint32); break; default: XP_ASSERT(FALSE); return kString; } *pMaxLen = maxLen; return fieldType; } // helper routines for internal sort. XPStringObj is only currently used by // recipients but we should change the rest so we aren't returning pointers // to hash strings. This routine allocates a string, so the caller has to free it. char *MessageDBView::GetStringField(DBMessageHdr *msgHdr, SortType sortType, int16 csid, XPStringObj &string) { const char *pField; switch (sortType) { case SortBySubject: if (msgHdr->GetSubject(string, FALSE, m_messageDB->GetDB())) pField = string; else pField = ""; break; case SortByRecipient: msgHdr->GetNameOfRecipient(string, 0, m_messageDB->GetDB()); pField = string ; if (!pField) pField = ""; break; case SortByAuthor: msgHdr->GetRFC822Author(string, m_messageDB->GetDB()); pField = string ; if (!pField) pField = ""; break; default: // XP_ASSERT(FALSE); return(0); } return INTL_DecodeMimePartIIAndCreateCollationKey(pField, csid, 0); } uint32 MessageDBView::GetStatusSortValue(DBMessageHdr *msgHdr) { uint32 sortValue = 5; uint32 messageFlags = m_messageDB->GetStatusFlags(msgHdr); if (messageFlags & MSG_FLAG_NEW) // happily, new by definition stands alone return 0; #define MSG_STATUS_MASK (MSG_FLAG_REPLIED | MSG_FLAG_FORWARDED) switch (messageFlags & MSG_STATUS_MASK) { case MSG_FLAG_REPLIED: sortValue = 2; break; case MSG_FLAG_FORWARDED|MSG_FLAG_REPLIED: sortValue = 1; break; case MSG_FLAG_FORWARDED: sortValue = 3; break; } if (sortValue == 5) // none of the above flags set { if (messageFlags & MSG_FLAG_READ) // make read a visible status in winfe. sortValue = 4; } return sortValue; } uint32 MessageDBView::GetLongField(DBMessageHdr *msgHdr, SortType sortType) { switch (sortType) { case SortByDate: return msgHdr->GetDate(); case SortBySize: return msgHdr->GetMessageSize(); case SortById: return msgHdr->GetMessageKey(); case SortByPriority: // want highest priority to have lowest value // so ascending sort will have highest priority first. return MSG_HighestPriority - msgHdr->GetPriority(); case SortByStatus: return GetStatusSortValue(msgHdr); case SortByFlagged: return !(msgHdr->GetFlags() & kMsgMarked); //make flagged come out on top. case SortByUnread: { XP_Bool isRead = FALSE; GetDB()->IsRead(msgHdr->GetMessageKey(), &isRead); return !isRead; // make unread show up at top } default: XP_ASSERT(FALSE); return 0; } } void MessageDBView::ReverseSort(void) { XPByteArray *pBits = GetFlagsArray(); int num = GetSize(); for (int j = 0; j < (num / 2); j++) { // go up half the array swapping values int end = num - j - 1; char bits; if (pBits != NULL) { bits = pBits->GetAt(j); pBits->SetAt(j, pBits->GetAt(end)); pBits->SetAt(end, bits); } MessageKey tempID = m_idArray.GetAt(j); m_idArray.SetAt(j, m_idArray.GetAt(end)); m_idArray.SetAt(end, tempID); } } // reversing threads involves reversing the threads but leaving the // expanded messages ordered relative to the thread, so we // make a copy of each array and copy them over. MsgERR MessageDBView::ReverseThreads() { MsgERR err = eSUCCESS; XPByteArray *newFlagArray = new XPByteArray; IDArray *newIdArray = new IDArray; XPByteArray *newLevelArray = new XPByteArray; int sourceIndex, destIndex; int viewSize = GetSize(); if (newIdArray == NULL || newFlagArray == NULL) { err = eOUT_OF_MEMORY; goto CLEANUP; } newIdArray->SetSize(m_idArray.GetSize()); newFlagArray->SetSize(m_flags.GetSize()); newLevelArray->SetSize(m_levels.GetSize()); for (sourceIndex = 0, destIndex = viewSize - 1; sourceIndex < viewSize;) { int endThread; // find end of current thread. XP_Bool inExpandedThread = FALSE; for (endThread = sourceIndex; endThread < viewSize; endThread++) { char flags = m_flags[endThread]; if (!inExpandedThread && (flags & (kIsThread|kHasChildren)) && !(flags & kElided)) inExpandedThread = TRUE; else if (flags & kIsThread) { if (inExpandedThread) endThread--; break; } } if (endThread == viewSize) endThread--; int saveEndThread = endThread; while (endThread >= sourceIndex) { newIdArray->SetAt(destIndex, m_idArray.GetAt(endThread)); newFlagArray->SetAt(destIndex, m_flags.GetAt(endThread)); newLevelArray->SetAt(destIndex, m_levels.GetAt(endThread)); endThread--; destIndex--; } sourceIndex = saveEndThread + 1; } // this copies the contents of both arrays - it would be cheaper to // just assign the new data ptrs to the old arrays and "forget" the new // arrays' data ptrs, so they won't be freed when the arrays are deleted. m_idArray.RemoveAll(); m_flags.RemoveAll(); m_levels.RemoveAll(); m_idArray.InsertAt(0, newIdArray); m_flags.InsertAt(0, newFlagArray); m_levels.InsertAt(0, newLevelArray); CLEANUP: // if we swizzle data pointers for these arrays, this won't be right. if (newFlagArray != NULL) delete newFlagArray; if (newIdArray != NULL) delete newIdArray; if (newLevelArray != NULL) delete newLevelArray; return err; } MsgERR MessageDBView::ListThreads(MessageKey * /*pMessageNums*/, int /*numToList*/, MessageHdrStruct * /*pOutput*/, int * /*pNumListed*/) { return eNYI; } MsgERR MessageDBView::ListThreadsShort(MessageKey * pMessageNums, int numToList, MSG_MessageLine * pOutput, int * pNumListed) { MsgERR err = eSUCCESS; XP_BZERO(pOutput, sizeof(*pOutput) * numToList); int i; for (i = 0; i < numToList && err == eSUCCESS; i++) { { err = m_messageDB->GetShortMessageHdr(pMessageNums[i], pOutput + i); if (err == eSUCCESS) { // force non-threaded view to be flat. if (m_sortType != SortByThread) { (pOutput + i)->level = 0; // (pOutput + i)->flags &= ~(kHasChildren); // Not used by FE. (pOutput + i)->numChildren = 0; (pOutput + i)->numNewChildren = 0; } } } } if (pNumListed != NULL) *pNumListed = i; return err; } // list the headers of the top-level thread ids MsgERR MessageDBView::ListThreadIds(MessageKey * /*startMsg*/, MessageKey * /*pOutput*/, int /*numToList*/, int * /*numListed*/) { return eNYI; } MsgERR MessageDBView::ListThreadIds(ListContext * /*context*/, MessageKey * /*pOutput*/, int /*numToList*/, int * /*numListed*/) { return eNYI; } // return the list header information for the documents in a thread. MsgERR MessageDBView::ListThread(MessageKey /*threadId*/, MessageKey /*startMsg*/, int /*numToList*/, MessageHdrStruct * /*pOutput*/, int * /*pNumListed*/) { return eNYI; } MsgERR MessageDBView::ListThreadShort(MessageKey /*threadId*/, MessageKey /*startMsg*/, int /*numToList*/, MSG_MessageLine * /*pOutput*/, int * /*pNumListed*/) { return eNYI; } MsgERR MessageDBView::ListShortMsgHdrByIndex(MSG_ViewIndex startIndex, int numToList, MSG_MessageLine *pOutput, int *pNumListed) { MsgERR err = eSUCCESS; XP_BZERO(pOutput, sizeof(*pOutput) * numToList); int i; for (i = 0; i < numToList && err == eSUCCESS; i++) { if (i + startIndex < m_idArray.GetSize()) { err = m_messageDB->GetShortMessageHdr(m_idArray[i + startIndex], pOutput + i); if (err == eSUCCESS) { char extraFlag = m_flags[i + startIndex]; CopyExtraFlagsToPublicFlags(extraFlag, &((pOutput + i)->flags)); // force non-threaded view to be flat. Wish FE's would do this if (m_sortType != SortByThread) { (pOutput + i)->level = 0; } else { (pOutput + i)->level = m_levels[i + startIndex]; } // m_levels is only valid in the thread sort - otherwise, use kIsThread flag, // Ideally, we'd always use kIsThread, but this is a safer fix for now. // It's probably not worth keeping level array valid for sorted views. if (m_levels[i + startIndex] == 0 && m_sortType == SortByThread || (m_sortType != SortByThread && extraFlag & kIsThread)) { DBThreadMessageHdr *thread = m_messageDB->GetDBThreadHdrForThreadID((pOutput + i)->threadId); if (thread != NULL) { if (m_sortType == SortByThread) // don't set child counts if not threaded { if (m_viewFlags & kUnreadOnly) { if (extraFlag & kElided) { (pOutput + i)->numChildren = thread->GetNumNewChildren() - 1; if (extraFlag & kIsRead) // count top message if it's read. (pOutput + i)->numChildren++; } else (pOutput + i)->numChildren = CountExpandedThread(i + startIndex) -1; } else (pOutput + i)->numChildren = thread->GetNumChildren() - 1; if ((int16) ((pOutput + i)->numChildren) < 0) (pOutput + i)->numChildren = 0; (pOutput + i)->numNewChildren = thread->GetNumNewChildren(); } if (thread->GetFlags() & kIgnored) (pOutput + i)->flags |= MSG_FLAG_IGNORED; else (pOutput + i)->flags &= ~MSG_FLAG_IGNORED; if (thread->GetFlags() & kWatched) (pOutput + i)->flags |= MSG_FLAG_WATCHED; else (pOutput + i)->flags &= ~MSG_FLAG_WATCHED; delete thread; } } } } else { err = eID_NOT_FOUND; } } if (pNumListed != NULL) *pNumListed = i; return err; } MsgERR MessageDBView::ListMsgHdrByIndex(MSG_ViewIndex startIndex, int numToList, MessageHdrStruct *pOutput, int *pNumListed) { MsgERR err = eSUCCESS; XP_BZERO(pOutput, sizeof(*pOutput) * numToList); int i; for (i = 0; i < numToList && err == eSUCCESS; i++) { if (i + startIndex < m_idArray.GetSize()) { err = m_messageDB->GetMessageHdr(m_idArray[i + startIndex], pOutput + i); if (err == eSUCCESS) { CopyExtraFlagsToPublicFlags(m_flags[i + startIndex], &((pOutput + i)->m_flags)); // force non-threaded view to be flat. if (m_sortType != SortByThread) { (pOutput + i)->m_level = 0; (pOutput + i)->m_flags &= ~(kHasChildren); } else { (pOutput + i)->m_level = m_levels[i + startIndex]; if ((pOutput + i)->m_level == 0) { DBThreadMessageHdr *thread = m_messageDB->GetDBThreadHdrForThreadID((pOutput + i)->m_threadId); if (thread != NULL) { (pOutput + i)->m_numChildren = thread->GetNumChildren() - 1; (pOutput + i)->m_numNewChildren = thread->GetNumNewChildren(); delete thread; } } } } } else { err = eID_NOT_FOUND; } } if (pNumListed != NULL) *pNumListed = i; return err; } MsgERR MessageDBView::GetMsgLevelByIndex(MSG_ViewIndex index, int &level) { MsgERR err = eSUCCESS; level = 0; if ((int) index < m_levels.GetSize() ) { level = m_sortType == SortByThread ? m_levels[ index ] : 0; } else { err = eID_NOT_FOUND; } return err; } // This counts the number of messages in an expanded thread, given the // index of the first message in the thread. int32 MessageDBView::CountExpandedThread(MSG_ViewIndex index) { int32 numInThread = 0; MSG_ViewIndex startOfThread = index; while ((int32) startOfThread >= 0 && m_levels[startOfThread] != 0) startOfThread--; MSG_ViewIndex threadIndex = startOfThread; do { threadIndex++; numInThread++; } while ((int32) threadIndex < m_levels.GetSize() && m_levels[threadIndex] != 0); return numInThread; } // returns the number of lines that would be added (> 0) or removed (< 0) // if we were to try to expand/collapse the passed index. MsgERR MessageDBView::ExpansionDelta(MSG_ViewIndex index, int32 *expansionDelta) { int32 numChildren; MsgERR err; *expansionDelta = 0; if ((int) index > m_idArray.GetSize()) return eID_NOT_FOUND; char flags = m_flags[index]; if (m_sortType != SortByThread) return eSUCCESS; // The client can pass in the key of any message // in a thread and get the expansion delta for the thread. if (!(m_viewFlags & kUnreadOnly)) { err = m_messageDB->GetThreadCount(m_idArray[index], &numChildren); if (err != eSUCCESS) return err; } else { numChildren = CountExpandedThread(index); } if (flags & kElided) *expansionDelta = numChildren - 1; else *expansionDelta = - (numChildren - 1); return eSUCCESS; } MsgERR MessageDBView::ToggleExpansion(MSG_ViewIndex index, uint32 *numChanged) { MSG_ViewIndex threadIndex = ThreadIndexOfMsg(GetAt(index), index); if (threadIndex == MSG_VIEWINDEXNONE) { XP_ASSERT(FALSE); return eNotThread; } char flags = m_flags[threadIndex]; // if not a thread, or doesn't have children, no expand/collapse // If we add sub-thread expand collapse, this will need to be relaxed if (!(flags & kIsThread) || !(flags && kHasChildren)) return eNotThread; if (flags & kElided) return ExpandByIndex(threadIndex, numChanged); else return CollapseByIndex(threadIndex, numChanged); } MsgERR MessageDBView::ExpandAll() { for (int i = GetSize() - 1; i >= 0; i--) { uint32 numExpanded; char flags = m_flags[i]; if (flags & kElided) ExpandByIndex(i, &numExpanded); } return eSUCCESS; } MsgERR MessageDBView::CollapseAll() { for (int i = 0; i < GetSize(); i++) { uint32 numCollapsed; char flags = m_flags[i]; if (!(flags & kElided)) CollapseByIndex(i, &numCollapsed); } return eSUCCESS; } MsgERR MessageDBView::ExpandByIndex(MSG_ViewIndex index, uint32 *pNumExpanded) { int numListed; char flags = m_flags[index]; MessageKey firstIdInThread, startMsg = kIdNone; MsgERR err; MSG_ViewIndex firstInsertIndex = index + 1; MSG_ViewIndex insertIndex = firstInsertIndex; uint32 numExpanded = 0; IDArray tempIDArray; XPByteArray tempFlagArray; XPByteArray tempLevelArray; XPByteArray unreadLevelArray; XP_ASSERT(flags & kElided); flags &= ~kElided; if ((int) index > m_idArray.GetSize()) return eID_NOT_FOUND; firstIdInThread = m_idArray[index]; DBMessageHdr *msgHdr = m_messageDB->GetDBHdrForKey(firstIdInThread); if (msgHdr == NULL) { XP_ASSERT(FALSE); return eID_NOT_FOUND; } m_flags[index] = flags; NoteChange(index, 1, MSG_NotifyChanged); do { const int listChunk = 200; MessageKey listIDs[listChunk]; char listFlags[listChunk]; char listLevels[listChunk]; if (m_viewFlags & kUnreadOnly) { if (flags & kIsRead) unreadLevelArray.Add(0); // keep top level hdr in thread, even though read. err = m_messageDB->ListUnreadIdsInThread(msgHdr->GetThreadId(), &startMsg, unreadLevelArray, listChunk, listIDs, listFlags, listLevels, &numListed); } else err = m_messageDB->ListIdsInThread(msgHdr, &startMsg, listChunk, listIDs, listFlags, listLevels, &numListed); // Don't add thread to view, it's already in. for (int i = 0; i < numListed; i++) { if (listIDs[i] != firstIdInThread) { tempIDArray.Add(listIDs[i]); tempFlagArray.Add(listFlags[i]); tempLevelArray.Add(listLevels[i]); insertIndex++; } } if (numListed < listChunk || startMsg == kIdNone) break; } while (err == eSUCCESS); numExpanded = (insertIndex - firstInsertIndex); NoteStartChange(firstInsertIndex, numExpanded, MSG_NotifyInsertOrDelete); m_idArray.InsertAt(firstInsertIndex, &tempIDArray); m_flags.InsertAt(firstInsertIndex, &tempFlagArray); m_levels.InsertAt(firstInsertIndex, &tempLevelArray); NoteEndChange(firstInsertIndex, numExpanded, MSG_NotifyInsertOrDelete); delete msgHdr; if (pNumExpanded != NULL) *pNumExpanded = numExpanded; return err; } MsgERR MessageDBView::CollapseByIndex(MSG_ViewIndex index, uint32 *pNumCollapsed) { MessageKey firstIdInThread; MsgERR err; char flags = m_flags[index]; int32 threadCount = 0; if (flags & kElided || m_sortType != SortByThread) return eSUCCESS; flags |= kElided; if (index > m_idArray.GetSize()) return eID_NOT_FOUND; firstIdInThread = m_idArray[index]; DBMessageHdr *msgHdr = m_messageDB->GetDBHdrForKey(firstIdInThread); if (msgHdr == NULL) { XP_ASSERT(FALSE); return eID_NOT_FOUND; } m_flags[index] = flags; NoteChange(index, 1, MSG_NotifyChanged); err = ExpansionDelta(index, &threadCount); if (err == eSUCCESS) { int32 numRemoved = threadCount; // don't count first header in thread NoteStartChange(index + 1, -numRemoved, MSG_NotifyInsertOrDelete); // start at first id after thread. for (int i = 1; i <= threadCount && index + 1 < m_idArray.GetSize(); i++) { m_idArray.RemoveAt(index + 1); m_flags.RemoveAt(index + 1); m_levels.RemoveAt(index + 1); } if (pNumCollapsed != NULL) *pNumCollapsed = numRemoved; NoteEndChange(index + 1, -numRemoved, MSG_NotifyInsertOrDelete); } delete msgHdr; return err; } MsgERR MessageDBView::FindPrevUnread(MessageKey startKey, MessageKey *pResultKey, MessageKey *resultThreadId) { MSG_ViewIndex startIndex = FindViewIndex(startKey); MSG_ViewIndex curIndex = startIndex; MSG_ViewIndex lastIndex = (uint32) GetSize() - 1; MsgERR err = eID_NOT_FOUND; if (startIndex == kViewIndexNone) return eID_NOT_FOUND; *pResultKey = kIdNone; if (resultThreadId) *resultThreadId = kIdNone; for (; (int) curIndex >= 0 && (*pResultKey == kIdNone); curIndex--) { char flags = m_flags[curIndex]; if (curIndex != startIndex && flags & kIsThread && flags & kElided) { MessageKey threadId = m_idArray[curIndex]; err = m_messageDB->GetUnreadKeyInThread(threadId, pResultKey, resultThreadId); if (err == eSUCCESS && (*pResultKey != kIdNone)) break; } if (!(flags & kIsRead) && (curIndex != startIndex)) { *pResultKey = m_idArray[curIndex]; err = eSUCCESS; break; } } // found unread message but we don't know the thread if (*pResultKey != kIdNone && resultThreadId && *resultThreadId == kIdNone) { *resultThreadId = m_messageDB->GetThreadIdForMsgId(*pResultKey); } return err; } // Note that these routines do NOT expand collapsed threads! This mimics the old behaviour, // but it's also because we don't remember whether a thread contains a flagged message the // same way we remember if a thread contains new messages. It would be painful to dive down // into each collapsed thread to update navigate status. // We could cache this info, but it would still be expensive the first time this status needs // to get updated. MsgERR MessageDBView::FindFirstFlagged(MSG_ViewIndex * pResultIndex) { return FindNextFlagged(0, pResultIndex); } MsgERR MessageDBView::FindPrevFlagged(MSG_ViewIndex startIndex, MSG_ViewIndex *pResultIndex) { MSG_ViewIndex lastIndex = (uint32) GetSize() - 1; MSG_ViewIndex curIndex; *pResultIndex = MSG_VIEWINDEXNONE; if (GetSize() > 0 && IsValidIndex(startIndex)) { curIndex = startIndex; do { if (curIndex != 0) curIndex--; char flags = m_flags[curIndex]; if (flags & kMsgMarked) { *pResultIndex = curIndex; break; } } while (curIndex != 0); } return eSUCCESS; } MsgERR MessageDBView::FindNextFlagged(MSG_ViewIndex startIndex, MSG_ViewIndex *pResultIndex) { MSG_ViewIndex lastIndex = (uint32) GetSize() - 1; MSG_ViewIndex curIndex; *pResultIndex = MSG_VIEWINDEXNONE; if (GetSize() > 0) { for (curIndex = startIndex; curIndex <= lastIndex; curIndex++) { char flags = m_flags[curIndex]; if (flags & kMsgMarked) { *pResultIndex = curIndex; break; } } } return eSUCCESS; } MsgERR MessageDBView::FindFirstNew(MSG_ViewIndex * pResultIndex) { MessageKey firstNewKey = m_messageDB->GetFirstNew(); if (pResultIndex) *pResultIndex = FindKey(firstNewKey, TRUE); return eSUCCESS; } // Generic routine to find next unread id. It doesn't do an expand of a // thread with new messages, so it can't return a view index. MsgERR MessageDBView::FindNextUnread(MessageKey startId, MessageKey *pResultKey, MessageKey *resultThreadId) { MSG_ViewIndex startIndex = FindViewIndex(startId); MSG_ViewIndex curIndex = startIndex; MSG_ViewIndex lastIndex = (uint32) GetSize() - 1; MsgERR err = eSUCCESS; if (startIndex == kViewIndexNone) return eID_NOT_FOUND; *pResultKey = kIdNone; if (resultThreadId) *resultThreadId = kIdNone; for (; curIndex <= lastIndex && (*pResultKey == kIdNone); curIndex++) { char flags = m_flags[curIndex]; if (!(flags & kIsRead) && (curIndex != startIndex)) { *pResultKey = m_idArray[curIndex]; break; } // check for collapsed thread with new children if (m_sortType == SortByThread && flags & kIsThread && flags & kElided) { MessageKey threadId = m_idArray[curIndex]; err = m_messageDB->GetUnreadKeyInThread(threadId, pResultKey, resultThreadId); if (err == eSUCCESS && (*pResultKey != kIdNone)) break; } } // found unread message but we don't know the thread if (*pResultKey != kIdNone && resultThreadId && *resultThreadId == kIdNone) { *resultThreadId = m_messageDB->GetThreadIdForMsgId(*pResultKey); } return err; } MsgERR MessageDBView::DataNavigate( MessageKey startKey, MSG_MotionType motion, MessageKey *pResultKey, MessageKey *pThreadKey) { MsgERR err = eSUCCESS; MSG_ViewIndex resultIndex; MSG_ViewIndex lastIndex = (uint32) GetSize() - 1; MSG_ViewIndex startIndex = FindViewIndex(startKey); if (startIndex == kViewIndexNone) return eID_NOT_FOUND; XP_ASSERT(pResultKey != NULL ); if (pResultKey == NULL) return eBAD_PARAMETER; switch (motion) { case MSG_FirstMessage: *pResultKey = m_idArray[0]; break; case MSG_NextMessage: // return same index and id on next on last message resultIndex = MIN(startIndex + 1, lastIndex); *pResultKey = m_idArray[resultIndex]; break; case MSG_PreviousMessage: *pResultKey = m_idArray[(startIndex > 0) ? startIndex : 0]; break; case MSG_LastMessage: *pResultKey = m_idArray[lastIndex]; break; case MSG_FirstUnreadMessage: // note fall thru - is this motion ever used? startKey = m_idArray[0]; case MSG_NextUnreadMessage: // It might be worthwhile to not actually find the next unread, // but just determine if there is one. Or, it might be worth // remembering the next unread. FindNextUnread(startKey, pResultKey, pThreadKey); break; case MSG_PreviousUnreadMessage: // will do an expand err = FindPrevUnread(startKey, pResultKey, pThreadKey); break; case MSG_LastUnreadMessage: break; default: XP_ASSERT(FALSE); // unsupported motion. break; } return err; } typedef struct CommandStrLookup { int command; int mkStringNum; } CommandStrLookup; // Because some compilers can't initialize static data with ints, we have the following pain CommandStrLookup navigateCommands[] = { { MSG_FirstMessage, 0, /*MK_MSG_FIRST_MSG */}, { MSG_NextMessage, 0, /*MK_MSG_NEXT_MSG */}, { MSG_PreviousMessage, 0, /*MK_MSG_PREV_MSG */}, { MSG_LastMessage, 0, /*MK_MSG_LAST_MSG */}, { MSG_FirstUnreadMessage, 0, /*MK_MSG_FIRST_UNREAD */}, { MSG_NextUnreadMessage, 0, /*MK_MSG_NEXT_UNREAD */}, { MSG_PreviousUnreadMessage, 0, /*MK_MSG_PREV_UNREAD */}, { MSG_LastUnreadMessage, 0, /*MK_MSG_LAST_UNREAD */}, { MSG_ReadMore, 0, /*MK_MSG_READ_MORE */}, { MSG_NextUnreadThread, 0, /*MK_MSG_NEXTUNREAD_THREAD */}, { MSG_NextUnreadGroup, 0, /*MK_MSG_NEXTUNREAD_GROUP */}, { MSG_FirstFlagged, 0, /*MK_MSG_FIRST_FLAGGED */}, { MSG_NextFlagged, 0, /*MK_MSG_NEXT_FLAGGED */}, { MSG_PreviousFlagged, 0, /*MK_MSG_PREVIOUS_FLAGGED */}, }; /*static*/ void MessageDBView::InitNavigateCommands() { if (navigateCommands[0].mkStringNum == 0) { navigateCommands[0].mkStringNum = MK_MSG_FIRST_MSG; navigateCommands[1].mkStringNum = MK_MSG_NEXT_MSG; navigateCommands[2].mkStringNum = MK_MSG_PREV_MSG; navigateCommands[3].mkStringNum = MK_MSG_LAST_MSG; navigateCommands[4].mkStringNum = MK_MSG_FIRST_UNREAD; navigateCommands[5].mkStringNum = MK_MSG_NEXT_UNREAD; navigateCommands[6].mkStringNum = MK_MSG_PREV_UNREAD; navigateCommands[7].mkStringNum = MK_MSG_LAST_UNREAD; navigateCommands[8].mkStringNum = MK_MSG_READ_MORE; navigateCommands[9].mkStringNum = MK_MSG_NEXTUNREAD_THREAD; navigateCommands[10].mkStringNum = MK_MSG_NEXTUNREAD_GROUP; navigateCommands[11].mkStringNum = MK_MSG_FIRST_FLAGGED; navigateCommands[12].mkStringNum = MK_MSG_NEXT_FLAGGED; navigateCommands[13].mkStringNum = MK_MSG_PREV_FLAGGED; } } MsgERR MessageDBView::GetNavigateStatus(MSG_MotionType motion, MSG_ViewIndex index, XP_Bool *selectable_p, const char **display_string) { XP_Bool enable = FALSE; MsgERR err = eFAILURE; MessageKey resultKey; MSG_ViewIndex resultIndex = MSG_VIEWINDEXNONE; InitNavigateCommands(); // warning - we no longer validate index up front because fe passes in -1 for no // selection, so if you use index, be sure to validate it before using it // as an array index. switch (motion) { case MSG_FirstMessage: case MSG_LastMessage: if (GetSize() > 0) enable = TRUE; break; case MSG_NextMessage: if (IsValidIndex(index) && index < (uint32) GetSize() - 1) enable = TRUE; break; case MSG_PreviousMessage: if (IsValidIndex(index) && index != 0 && GetSize() > 1) enable = TRUE; break; case MSG_FirstFlagged: err = FindFirstFlagged(&resultIndex); enable = (err == eSUCCESS && resultIndex != MSG_VIEWINDEXNONE); break; case MSG_NextFlagged: err = FindNextFlagged(index + 1, &resultIndex); enable = (err == eSUCCESS && resultIndex != MSG_VIEWINDEXNONE); break; case MSG_PreviousFlagged: if (IsValidIndex(index) && index != 0) err = FindPrevFlagged(index, &resultIndex); enable = (err == eSUCCESS && resultIndex != MSG_VIEWINDEXNONE); break; case MSG_FirstNew: err = FindFirstNew(&resultIndex); enable = (err == eSUCCESS && resultIndex != MSG_VIEWINDEXNONE); break; case MSG_LaterMessage: enable = GetSize() > 0; break; case MSG_ReadMore: enable = TRUE; // for now, always true. break; case MSG_NextFolder: case MSG_NextUnreadGroup: case MSG_NextUnreadThread: case MSG_NextUnreadMessage: case (MSG_MotionType) MSG_ToggleThreadKilled: enable = TRUE; // always enabled in nwo break; case MSG_PreviousUnreadMessage: if (IsValidIndex(index)) { err = FindPrevUnread(m_idArray[index], &resultKey); enable = (resultKey != kIdNone); } break; default: XP_ASSERT(FALSE); } if (selectable_p) *selectable_p = enable; // look up motion code in CommandStrLookup. for (int i = 0; display_string && i < (sizeof(navigateCommands) / sizeof(navigateCommands[0])); i++) { if (navigateCommands[i].command == motion) { *display_string = XP_GetString(navigateCommands[i].mkStringNum); break; } } return eSUCCESS; } // Starting from startId, performs the passed in navigation, including // any marking read needed, and returns the resultKey and index of the // destination of the navigation. If there are no more unread in the view, // it returns a resultId of kIdNone and an index of kViewIndexNone. MsgERR MessageDBView::Navigate(MSG_ViewIndex startIndex, MSG_MotionType motion, MessageKey * pResultKey, MSG_ViewIndex * pResultIndex, MSG_ViewIndex *pThreadIndex, XP_Bool wrap /* = TRUE */) { MsgERR err = eSUCCESS; MessageKey resultThreadKey; MSG_ViewIndex curIndex; MSG_ViewIndex lastIndex = (GetSize() > 0) ? (uint32) GetSize() - 1 : kViewIndexNone; MSG_ViewIndex threadIndex = kViewIndexNone; XP_ASSERT(pResultKey != NULL && pResultIndex != NULL ); if (pResultKey == NULL || pResultIndex == NULL) return eBAD_PARAMETER; switch (motion) { case MSG_FirstMessage: if (GetSize() > 0) { *pResultIndex = 0; *pResultKey = m_idArray[0]; } else { *pResultIndex = kViewIndexNone; *pResultKey = MSG_MESSAGEKEYNONE; } break; case MSG_NextMessage: // return same index and id on next on last message *pResultIndex = MIN(startIndex + 1, lastIndex); *pResultKey = m_idArray[*pResultIndex]; break; case MSG_PreviousMessage: *pResultIndex = (startIndex > 0) ? startIndex - 1 : 0; *pResultKey = m_idArray[*pResultIndex]; break; case MSG_LastMessage: *pResultIndex = lastIndex; *pResultKey = m_idArray[*pResultIndex]; break; case MSG_FirstFlagged: err = FindFirstFlagged(pResultIndex); if (IsValidIndex(*pResultIndex)) *pResultKey = m_idArray[*pResultIndex]; break; case MSG_NextFlagged: err = FindNextFlagged(startIndex + 1, pResultIndex); if (IsValidIndex(*pResultIndex)) *pResultKey = m_idArray[*pResultIndex]; break; case MSG_PreviousFlagged: err = FindPrevFlagged(startIndex, pResultIndex); if (IsValidIndex(*pResultIndex)) *pResultKey = m_idArray[*pResultIndex]; break; case MSG_FirstNew: err = FindFirstNew(pResultIndex); if (IsValidIndex(*pResultIndex)) *pResultKey = m_idArray[*pResultIndex]; break; case MSG_FirstUnreadMessage: startIndex = kViewIndexNone; // note fall thru - is this motion ever used? case MSG_NextUnreadMessage: for (curIndex = (startIndex == kViewIndexNone) ? 0 : startIndex; curIndex <= lastIndex && lastIndex != kViewIndexNone; curIndex++) { char flags = m_flags[curIndex]; // don't return start index since navigate should move if (!(flags & kIsRead) && (curIndex != startIndex)) { *pResultIndex = curIndex; *pResultKey = m_idArray[*pResultIndex]; break; } // check for collapsed thread with new children if (m_sortType == SortByThread && flags & kIsThread && flags & kElided) { DBThreadMessageHdr *threadHdr = m_messageDB->GetDBThreadHdrForMsgID(m_idArray[curIndex]); if (threadHdr == NULL) { XP_ASSERT(FALSE); continue; } if (threadHdr->GetNumNewChildren() > 0) { uint32 numExpanded; ExpandByIndex(curIndex, &numExpanded); lastIndex += numExpanded; if (pThreadIndex != NULL) *pThreadIndex = curIndex; } delete threadHdr; } } if (curIndex > lastIndex) { // wrap around by starting at index 0. if (wrap) { MessageKey startKey = GetAt(startIndex); err = Navigate(kViewIndexNone, MSG_NextUnreadMessage, pResultKey, pResultIndex, pThreadIndex, FALSE); if (*pResultKey == startKey) // wrapped around and found start message! { *pResultIndex = kViewIndexNone; *pResultKey = kIdNone; } } else { *pResultIndex = kViewIndexNone; *pResultKey = kIdNone; } } break; case MSG_PreviousUnreadMessage: err = FindPrevUnread(m_idArray[startIndex], pResultKey, &resultThreadKey); if (err == eSUCCESS) { *pResultIndex = FindViewIndex(*pResultKey); if (*pResultKey != resultThreadKey && m_sortType == SortByThread) { threadIndex = ThreadIndexOfMsg(*pResultKey, kViewIndexNone); if (*pResultIndex == kViewIndexNone) { DBThreadMessageHdr *threadHdr = m_messageDB->GetDBThreadHdrForMsgID(*pResultKey); if (threadHdr == NULL) { XP_ASSERT(FALSE); break; } if (threadHdr->GetNumNewChildren() > 0) { uint32 numExpanded; ExpandByIndex(threadIndex, &numExpanded); } delete threadHdr; *pResultIndex = FindViewIndex(*pResultKey); } } if (pThreadIndex != NULL) *pThreadIndex = threadIndex; } break; case MSG_LastUnreadMessage: break; case MSG_NextUnreadThread: if (startIndex == kViewIndexNone) { XP_ASSERT(FALSE); break; } err = MarkThreadOfMsgRead(m_idArray[startIndex], startIndex, TRUE); if (err == eSUCCESS) return Navigate(startIndex, MSG_NextUnreadMessage, pResultKey, pResultIndex, pThreadIndex, TRUE); break; case MSG_ToggleThreadKilled: { XP_Bool resultKilled; if (startIndex == kViewIndexNone) { XP_ASSERT(FALSE); break; } threadIndex = ThreadIndexOfMsg(GetAt(startIndex), startIndex); ToggleIgnored( &startIndex, 1, &resultKilled); if (resultKilled) { if (threadIndex != MSG_VIEWINDEXNONE) CollapseByIndex(threadIndex, NULL); return Navigate(threadIndex, MSG_NextUnreadThread, pResultKey, pResultIndex, pThreadIndex); } else { *pResultIndex = startIndex; *pResultKey = m_idArray[*pResultIndex]; return eSUCCESS; } } case MSG_LaterMessage: if (startIndex == kViewIndexNone) { XP_ASSERT(FALSE); break; } m_messageDB->MarkLater(m_idArray[startIndex], 0); return Navigate(startIndex, MSG_NextUnreadMessage, pResultKey, pResultIndex, pThreadIndex); default: XP_ASSERT(FALSE); // unsupported motion. break; } return err; } MSG_ViewIndex MessageDBView::GetIndexOfFirstDisplayedKeyInThread(DBThreadMessageHdr *threadHdr) { MSG_ViewIndex retIndex = MSG_VIEWINDEXNONE; int childIndex = 0; // We could speed up the unreadOnly view by starting our search with the first // unread message in the thread. Sometimes, that will be wrong, however, so // let's skip it until we're sure it's neccessary. // (m_viewFlags & kUnreadOnly) // ? threadHdr->GetFirstUnreadKey(m_messageDB) : threadHdr->GetChildAt(0); while (retIndex == MSG_VIEWINDEXNONE && childIndex < threadHdr->GetNumChildren()) { MessageKey childKey = threadHdr->GetChildAt(childIndex++); retIndex = FindViewIndex(childKey); } return retIndex; } DBMessageHdr *MessageDBView::GetFirstMessageHdrToDisplayInThread(DBThreadMessageHdr *threadHdr) { DBMessageHdr *msgHdr; if (m_viewFlags & kUnreadOnly) msgHdr = threadHdr->GetFirstUnreadChild(GetDB()); else msgHdr = threadHdr->GetChildHdrAt(0); return msgHdr; } // caller must referTo hdr if they want to hold it or change it! DBMessageHdr *MessageDBView::GetFirstDisplayedHdrInThread(DBThreadMessageHdr *threadHdr) { MSG_ViewIndex viewIndex = GetIndexOfFirstDisplayedKeyInThread(threadHdr); MessageKey msgKey = GetAt(viewIndex); return (msgKey == MSG_MESSAGEKEYNONE) ? 0 : m_messageDB->GetDBHdrForKey(msgKey); } // Find the view index of the thread containing the passed msgKey, if // the thread is in the view. MsgIndex is passed in as a shortcut if // it turns out the msgKey is the first message in the thread, // then we can avoid looking for the msgKey. MSG_ViewIndex MessageDBView::ThreadIndexOfMsg(MessageKey msgKey, MSG_ViewIndex msgIndex /* = kViewIndexNone */, int32 *pThreadCount /* = NULL */, uint32 *pFlags /* = NULL */) { if (m_sortType != SortByThread) return kViewIndexNone; DBThreadMessageHdr *threadHdr = m_messageDB->GetDBThreadHdrForMsgID(msgKey); MSG_ViewIndex retIndex = kViewIndexNone; if (threadHdr != NULL) { if (msgIndex == kViewIndexNone) msgIndex = FindViewIndex(msgKey); if (msgIndex == kViewIndexNone) // key is not in view, need to find by thread { msgIndex = GetIndexOfFirstDisplayedKeyInThread(threadHdr); MessageKey threadKey = (msgIndex == kViewIndexNone) ? kIdNone : GetAt(msgIndex); if (pFlags) *pFlags = threadHdr->GetFlags(); } MSG_ViewIndex startOfThread = msgIndex; while ((int32) startOfThread >= 0 && m_levels[startOfThread] != 0) startOfThread--; retIndex = startOfThread; if (pThreadCount) { int32 numChildren = 0; MSG_ViewIndex threadIndex = startOfThread; do { threadIndex++; numChildren++; } while ((int32) threadIndex < m_levels.GetSize() && m_levels[threadIndex] != 0); *pThreadCount = numChildren; } delete threadHdr; } return retIndex; } MsgERR MessageDBView::MarkThreadOfMsgRead(MessageKey msgId, MSG_ViewIndex msgIndex, XP_Bool bRead) { DBThreadMessageHdr *threadHdr = m_messageDB->GetDBThreadHdrForMsgID(msgId); MSG_ViewIndex threadIndex; MsgERR err; if (threadHdr == NULL) { XP_ASSERT(FALSE); return eID_NOT_FOUND; } if (msgId != threadHdr->GetChildAt(0)) threadIndex = GetIndexOfFirstDisplayedKeyInThread(threadHdr); else threadIndex = msgIndex; err = MarkThreadRead(threadHdr, threadIndex, bRead); delete threadHdr; return err; } MsgERR MessageDBView::MarkThreadRead(DBThreadMessageHdr *threadHdr, MSG_ViewIndex threadIndex, XP_Bool bRead) { XP_Bool threadElided = TRUE; if (threadIndex != kViewIndexNone) threadElided = (m_flags[threadIndex] & kElided); for (int childIndex = 0; childIndex < threadHdr->GetNumChildren(); childIndex++) { DBMessageHdr *msgHdr = threadHdr->GetChildHdrAt(childIndex); if (msgHdr == NULL) { #ifdef DEBUG_bienvenu XP_ASSERT(FALSE); #endif continue; } // don't pass in listener so we can get change notification! // We used to mark read through the view, but we weren't checking // if the key actually was in the view. m_messageDB->MarkHdrRead(msgHdr, bRead, NULL /* &m_changeListener */); delete msgHdr; } if (bRead) threadHdr->SetNumNewChildren(0); else threadHdr->SetNumNewChildren(threadHdr->GetNumChildren()); return eSUCCESS; } // view modifications methods by index // read/unread handling. MsgERR MessageDBView::ToggleReadByIndex(MSG_ViewIndex index) { if (!IsValidIndex(index)) return eInvalidIndex; return SetReadByIndex(index, !(m_flags[index] & kIsRead)); } MsgERR MessageDBView::SetReadByIndex(MSG_ViewIndex index, XP_Bool read) { MsgERR err; if (!IsValidIndex(index)) return eInvalidIndex; if (read) OrExtraFlag(index, kIsRead); else AndExtraFlag(index, ~kIsRead); err = m_messageDB->MarkRead(m_idArray[index], read, &m_changeListener); NoteChange(index, 1, MSG_NotifyChanged); if (m_sortType == SortByThread) { MSG_ViewIndex threadIndex = ThreadIndexOfMsg(m_idArray[index], index, NULL, NULL); if (threadIndex != index) NoteChange(threadIndex, 1, MSG_NotifyChanged); } return err; } MsgERR MessageDBView::SetThreadOfMsgReadByIndex(MSG_ViewIndex index, XP_Bool /*read*/) { MsgERR err; if (!IsValidIndex(index)) return eInvalidIndex; err = MarkThreadOfMsgRead(m_idArray[index], index, TRUE); return err; } static MsgERR msg_AddNameAndAddressToAB (MWContext* context, const char *name, const char *address, XP_Bool lastOneToAdd) { MsgERR err = eSUCCESS; PersonEntry person; char * tempname = NULL; DIR_Server* pab = NULL; INTL_CharSetInfo c = LO_GetDocumentCharacterSetInfo(context); person.Initialize(); if (XP_STRLEN (name)) tempname = XP_STRDUP (name); else { int len = 0; char * at = NULL; if (address) { // if the mail address doesn't contain @ // then the name is the whole email address if ((at = XP_STRCHR(address, '@')) == NULL) tempname = MSG_ExtractRFC822AddressNames (address); else { // otherwise extract everything up to the @ // for the name len = XP_STRLEN (address) - XP_STRLEN (at); tempname = (char *) XP_ALLOC (len + 1); XP_STRNCPY_SAFE (tempname, address, len + 1); } } } person.pGivenName = tempname; person.pEmailAddress = XP_STRDUP (address); person.WinCSID = INTL_GetCSIWinCSID(c); AB_BreakApartFirstName (FE_GetAddressBook(NULL), &person); DIR_GetPersonalAddressBook (FE_GetDirServers(), &pab); err = AB_AddUserWithUI (context, &person, pab, lastOneToAdd); person.CleanUp(); return err; } /* new address book APIs require a destination container */ MsgERR MessageDBView::AddSenderToABByIndex(MSG_Pane * pane, MWContext* context, MSG_ViewIndex index, XP_Bool lastOneToAdd, XP_Bool displayRecip, AB_ContainerInfo * destAB) { /* taken verbatim from the old address book version except we call the new API AB_AddNameAndAddress */ MsgERR err = eSUCCESS; char author[512]; char authorname [256]; int num; char * name = NULL; char * address = NULL; if (!IsValidIndex(index)) return eInvalidIndex; if (!context) return eUNKNOWN; DBMessageHdr *dbHdr = m_messageDB->GetDBHdrForKey(m_idArray[index]); if (displayRecip) { int32 numRecips = dbHdr->GetNumRecipients(); for (int32 i = 0; i < numRecips && err == eSUCCESS; i++) { XPStringObj recipName; dbHdr->GetNameOfRecipient (recipName, (int) i, m_messageDB->GetDB()); XPStringObj recipAddress; dbHdr->GetMailboxOfRecipient (recipAddress, (int) i, m_messageDB->GetDB()); err = AB_AddNameAndAddress(pane, destAB, recipName, recipAddress, lastOneToAdd); } } else { dbHdr->GetRFC822Author(authorname, sizeof (authorname), m_messageDB->GetDB()); dbHdr->GetAuthor(author, sizeof (author), m_messageDB->GetDB()); num = MSG_ParseRFC822Addresses(author, &name, &address); if (num < 0) return eOUT_OF_MEMORY; err = AB_AddNameAndAddress (pane, destAB, authorname, address, lastOneToAdd); } delete dbHdr; XP_FREEIF (name); XP_FREEIF (address); return err; } MsgERR MessageDBView::AddAllToABByIndex(MSG_Pane * pane, MWContext* context, MSG_ViewIndex index, XP_Bool lastOneToAdd, AB_ContainerInfo * destAB) { /* copied pretty much verbatim from the old address book version except we call the new APIs. */ MsgERR err = eSUCCESS; XPStringObj prettyname; XPStringObj mailbox; int32 numRecipients = 0; int32 numCCList = 0; char name [kMaxFullNameLength]; char address [kMaxEmailAddressLength]; if (!IsValidIndex(index)) return eInvalidIndex; DBMessageHdr *dbHdr = m_messageDB->GetDBHdrForKey(m_idArray[index]); numRecipients = dbHdr->GetNumRecipients(); numCCList = dbHdr->GetNumCCRecipients(); err = AddSenderToABByIndex (pane, context, index, (lastOneToAdd && numRecipients == 0 && numCCList == 0), FALSE, destAB); if (err != eSUCCESS) { delete dbHdr; return eUNKNOWN; } for (int32 i = 0; i < numRecipients && err == eSUCCESS; i++) { dbHdr->GetNameOfRecipient(prettyname, i, m_messageDB->GetDB()); XP_STRNCPY_SAFE(name, prettyname, kMaxFullNameLength); dbHdr->GetMailboxOfRecipient(mailbox, i, m_messageDB->GetDB()); XP_STRNCPY_SAFE(address, mailbox, kMaxEmailAddressLength); err = AB_AddNameAndAddress (pane, destAB,name, address, (lastOneToAdd && ( i == numRecipients - 1) && (numCCList == 0))); } for (int32 j = 0; j < numCCList && err == eSUCCESS; j++) { dbHdr->GetNameOfCCRecipient(prettyname, j, m_messageDB->GetDB()); XP_STRNCPY_SAFE(name, prettyname, kMaxFullNameLength); dbHdr->GetMailboxOfCCRecipient(mailbox, j, m_messageDB->GetDB()); XP_STRNCPY_SAFE(address, mailbox, kMaxEmailAddressLength); err = AB_AddNameAndAddress (pane, destAB, name, address, (lastOneToAdd && (j == numCCList - 1))); } delete dbHdr; return err; } /* mscott: DELETE THESE AS SOON AS THE NEW ADDRESS BOOK IS TURNED ON FOR EVERYONE */ MsgERR MessageDBView::AddSenderToABByIndex(MWContext * context, MSG_ViewIndex index, XP_Bool lastOneToAdd, XP_Bool displayRecip) { MsgERR err = eSUCCESS; char author[512]; char authorname [256]; int num; char * name = NULL; char * address = NULL; if (!IsValidIndex(index)) return eInvalidIndex; if (!context) return eUNKNOWN; DBMessageHdr *dbHdr = m_messageDB->GetDBHdrForKey(m_idArray[index]); if (displayRecip) { int32 numRecips = dbHdr->GetNumRecipients(); for (int32 i = 0; i < numRecips && err == eSUCCESS; i++) { XPStringObj recipName; dbHdr->GetNameOfRecipient (recipName, (int) i, m_messageDB->GetDB()); XPStringObj recipAddress; dbHdr->GetMailboxOfRecipient (recipAddress, (int) i, m_messageDB->GetDB()); err = msg_AddNameAndAddressToAB (context, recipName, recipAddress, lastOneToAdd); } } else { dbHdr->GetRFC822Author(authorname, sizeof (authorname), m_messageDB->GetDB()); dbHdr->GetAuthor(author, sizeof (author), m_messageDB->GetDB()); num = MSG_ParseRFC822Addresses(author, &name, &address); if (num < 0) return eOUT_OF_MEMORY; err = msg_AddNameAndAddressToAB (context, authorname, address, lastOneToAdd); } delete dbHdr; XP_FREEIF (name); XP_FREEIF (address); return err; } MsgERR MessageDBView::AddAllToABByIndex(MWContext* context, MSG_ViewIndex index, XP_Bool lastOneToAdd) { MsgERR err = eSUCCESS; XPStringObj prettyname; XPStringObj mailbox; int32 numRecipients = 0; int32 numCCList = 0; char name [kMaxFullNameLength]; char address [kMaxEmailAddressLength]; if (!IsValidIndex(index)) return eInvalidIndex; DBMessageHdr *dbHdr = m_messageDB->GetDBHdrForKey(m_idArray[index]); numRecipients = dbHdr->GetNumRecipients(); numCCList = dbHdr->GetNumCCRecipients(); err = AddSenderToABByIndex (context, index, (lastOneToAdd && numRecipients == 0 && numCCList == 0), FALSE); if (err != eSUCCESS) { delete dbHdr; return eUNKNOWN; } for (int32 i = 0; i < numRecipients && err == eSUCCESS; i++) { dbHdr->GetNameOfRecipient(prettyname, i, m_messageDB->GetDB()); XP_STRNCPY_SAFE(name, prettyname, kMaxFullNameLength); dbHdr->GetMailboxOfRecipient(mailbox, i, m_messageDB->GetDB()); XP_STRNCPY_SAFE(address, mailbox, kMaxEmailAddressLength); err = msg_AddNameAndAddressToAB (context, name, address, (lastOneToAdd && ( i == numRecipients - 1) && (numCCList == 0))); } for (int32 j = 0; j < numCCList && err == eSUCCESS; j++) { dbHdr->GetNameOfCCRecipient(prettyname, j, m_messageDB->GetDB()); XP_STRNCPY_SAFE(name, prettyname, kMaxFullNameLength); dbHdr->GetMailboxOfCCRecipient(mailbox, j, m_messageDB->GetDB()); XP_STRNCPY_SAFE(address, mailbox, kMaxEmailAddressLength); err = msg_AddNameAndAddressToAB (context, name, address, (lastOneToAdd && (j == numCCList - 1))); } delete dbHdr; return err; } MsgERR MessageDBView::MarkMarkedByIndex(MSG_ViewIndex index, XP_Bool mark) { MsgERR err; if (!IsValidIndex(index)) return eInvalidIndex; if (mark) OrExtraFlag(index, kMsgMarked); else AndExtraFlag(index, ~kMsgMarked); err = m_messageDB->MarkMarked(m_idArray[index], mark, &m_changeListener); NoteChange(index, 1, MSG_NotifyChanged); return err; } // caller must RemoveReference thread! MSG_ViewIndex MessageDBView::GetThreadFromMsgIndex(MSG_ViewIndex index, DBThreadMessageHdr **threadHdr) { MessageKey msgKey = GetAt(index); MsgViewIndex threadIndex; XP_ASSERT(threadHdr); *threadHdr = m_messageDB->GetDBThreadHdrForMsgID(msgKey); if (*threadHdr == NULL) { // XP_ASSERT(FALSE); return MSG_VIEWINDEXNONE; } if (msgKey != (*threadHdr)->m_threadKey) threadIndex = GetIndexOfFirstDisplayedKeyInThread(*threadHdr); else threadIndex = index; return threadIndex; } // ignore handling. MsgERR MessageDBView::ToggleThreadIgnored(DBThreadMessageHdr *thread, MSG_ViewIndex threadIndex) { MsgERR err; if (!IsValidIndex(threadIndex)) return eInvalidIndex; err = SetThreadIgnored(thread, threadIndex, !((thread->GetFlags() & kIgnored) != 0)); return err; } MsgERR MessageDBView::ToggleThreadWatched(DBThreadMessageHdr *thread, MSG_ViewIndex index) { MsgERR err; if (!IsValidIndex(index)) return eInvalidIndex; err = SetThreadWatched(thread, index, !((thread->GetFlags() & kWatched) != 0)); return err; } MsgERR MessageDBView::ToggleIgnored( MsgViewIndex* indices, int32 numIndices, XP_Bool *resultToggleState) { MsgERR err; DBThreadMessageHdr *thread = NULL; if (numIndices == 1) { MsgViewIndex threadIndex = GetThreadFromMsgIndex(*indices, &thread); if (thread) { err = ToggleThreadIgnored(thread, threadIndex); if (resultToggleState) *resultToggleState = (thread->GetFlags() & kIgnored) ? TRUE : FALSE; delete thread; } } else { if (numIndices > 1) XP_QSORT (indices, numIndices, sizeof(MSG_ViewIndex), MSG_Pane::CompareViewIndices); for (int curIndex = numIndices - 1; curIndex >= 0; curIndex--) { MsgViewIndex threadIndex = GetThreadFromMsgIndex(*indices, &thread); } } return eSUCCESS; } MsgERR MessageDBView::ToggleWatched( MsgViewIndex* indices, int32 numIndices) { MsgERR err; DBThreadMessageHdr *thread = NULL; if (numIndices == 1) { MsgViewIndex threadIndex = GetThreadFromMsgIndex(*indices, &thread); if (threadIndex != MSG_VIEWINDEXNONE) { err = ToggleThreadWatched(thread, threadIndex); delete thread; } } else { if (numIndices > 1) XP_QSORT (indices, numIndices, sizeof(MSG_ViewIndex), MSG_Pane::CompareViewIndices); for (int curIndex = numIndices - 1; curIndex >= 0; curIndex--) { MsgViewIndex threadIndex = GetThreadFromMsgIndex(*indices, &thread); } } return eSUCCESS; } MsgERR MessageDBView::SetThreadIgnored(DBThreadMessageHdr *thread, MSG_ViewIndex threadIndex, XP_Bool ignored) { MsgERR err; if (!IsValidIndex(threadIndex)) return eInvalidIndex; err = m_messageDB->MarkThreadIgnored(thread, m_idArray[threadIndex], ignored, &m_changeListener); NoteChange(threadIndex, 1, MSG_NotifyChanged); if (ignored) { MarkThreadRead(thread, threadIndex, TRUE); } return err; } MsgERR MessageDBView::SetThreadWatched(DBThreadMessageHdr *thread, MSG_ViewIndex index, XP_Bool watched) { MsgERR err; if (!IsValidIndex(index)) return eInvalidIndex; err = m_messageDB->MarkThreadWatched(thread, m_idArray[index], watched, &m_changeListener); NoteChange(index, 1, MSG_NotifyChanged); return err; } MsgERR MessageDBView::SetKeyByIndex(MSG_ViewIndex index, MessageKey id) { m_idArray.SetAt(index, id); return eSUCCESS; } // This method just removes the specified line from the view. It does // NOT delete it from the database. MsgERR MessageDBView::RemoveByIndex(MSG_ViewIndex index) { if (!IsValidIndex(index)) return eInvalidIndex; m_idArray.RemoveAt(index); m_flags.RemoveAt(index); m_levels.RemoveAt(index); NoteChange(index, -1, MSG_NotifyInsertOrDelete); return eSUCCESS; } MsgERR MessageDBView::DeleteMessagesByIndex(MSG_ViewIndex *indices, int32 numIndices, XP_Bool removeFromDB) { MsgERR err = eSUCCESS; IDArray messageKeys; MSG_ViewIndex *tmpIndices = new MSG_ViewIndex [numIndices]; if (!tmpIndices) return eOUT_OF_MEMORY; memcpy (tmpIndices, indices, numIndices * sizeof(MSG_ViewIndex)); XP_QSORT (tmpIndices, numIndices, sizeof(MSG_ViewIndex), MSG_Pane::CompareViewIndices); if (removeFromDB) messageKeys.SetSize(numIndices); for (int32 i = numIndices - 1; i >= 0 && err == eSUCCESS; i--) { MSG_ViewIndex viewIndex = tmpIndices[i]; if (IsValidIndex(viewIndex)) { if (removeFromDB) messageKeys.SetAt(i, m_idArray[viewIndex]); err = RemoveByIndex(viewIndex); OnHeaderAddedOrDeleted(); } } if (removeFromDB) { if (err == eSUCCESS) err = m_messageDB->DeleteMessages(messageKeys, &m_changeListener); } delete [] tmpIndices; return err; } // Delete the message from the database, and remove it from the view. // If there's an error deleting the message from the db, it will // not be removed from the view. We could return an error and still // have deleted the message from the db, if there's an error // deleting it from the view (this better not happen...) MsgERR MessageDBView::DeleteMsgByIndex(MSG_ViewIndex index, XP_Bool removeFromDB) { MsgERR err = eSUCCESS; if (!IsValidIndex(index)) return eInvalidIndex; // cache the key before it is removed from the m_idArray MessageKey keyOfDeletedMessage = m_idArray[index]; if (err == eSUCCESS) { err = RemoveByIndex(index); OnHeaderAddedOrDeleted(); } if (removeFromDB) err = m_messageDB->DeleteMessage(keyOfDeletedMessage, &m_changeListener); return err; } MsgERR MessageDBView::InsertByIndex(MSG_ViewIndex index, MessageKey id) { if (!IsValidIndex(index)) return eInvalidIndex; m_idArray.InsertAt(index, id, 1); m_flags.InsertAt(index, 0, 1); m_levels.InsertAt(index, 0, 1); return eSUCCESS; } // view modifications methods by ID MsgERR MessageDBView::SetReadByID(MessageKey id, XP_Bool read, MSG_ViewIndex* pIndex) { MSG_ViewIndex viewIndex = (MSG_ViewIndex) FindViewIndex(id); if (pIndex != NULL) *pIndex = viewIndex; return SetReadByIndex(viewIndex, read); } // This should insert a new id after the passed in id, and returns the index // at which it inserted it. Only write if needed. MsgERR MessageDBView::InsertByID(MessageKey /*id*/, MessageKey /*newId*/, MSG_ViewIndex* /*pIndex*/) { return eNYI; } void MessageDBView::NoteChange(MSG_ViewIndex firstlineChanged, int numChanged, MSG_NOTIFY_CODE changeType) { NotifyViewChangeAll(firstlineChanged, numChanged, changeType, &m_changeListener); } void MessageDBView::NoteStartChange(MSG_ViewIndex firstlineChanged, int numChanged, MSG_NOTIFY_CODE changeType) { NotifyViewStartChangeAll(firstlineChanged, numChanged, changeType, &m_changeListener); } void MessageDBView::NoteEndChange(MSG_ViewIndex firstlineChanged, int numChanged, MSG_NOTIFY_CODE changeType) { NotifyViewEndChangeAll(firstlineChanged, numChanged, changeType, &m_changeListener); } XP_Bool MessageDBView::IsValidIndex(MSG_ViewIndex index) { return (index < (MSG_ViewIndex) m_idArray.GetSize()); } MsgERR MessageDBView::OrExtraFlag(MSG_ViewIndex index, char orflag) { char flag; if (!IsValidIndex(index)) return eInvalidIndex; flag = m_flags[index]; flag |= orflag; m_flags[index] = flag; OnExtraFlagChanged(index, flag); return eSUCCESS; } MsgERR MessageDBView::AndExtraFlag(MSG_ViewIndex index, char andflag) { char flag; if (!IsValidIndex(index)) return eInvalidIndex; flag = m_flags[index]; flag &= andflag; m_flags[index] = flag; OnExtraFlagChanged(index, flag); return eSUCCESS; } MsgERR MessageDBView::SetExtraFlag(MSG_ViewIndex index, char extraflag) { if (!IsValidIndex(index)) return eInvalidIndex; m_flags[index] = extraflag; OnExtraFlagChanged(index, extraflag); return eSUCCESS; } MsgERR MessageDBView::GetExtraFlag(MSG_ViewIndex index, char *extraFlag) { if (!IsValidIndex(index)) return eInvalidIndex; *extraFlag = m_flags[index]; return eSUCCESS; } void MessageDBView::SetExtraFlagsFromDBFlags(uint32 messageFlags, MSG_ViewIndex index) { XP_ASSERT(IsValidIndex(index)); char flags = m_flags[index]; char origFlags = flags; char saveFlags; // save extra flags not stored in db, and restore them below. // make a mask of the state of the extra flags saveFlags = flags & (kHasChildren | kIsThread | kElided); CopyDBFlagsToExtraFlags(messageFlags, &flags); // no matter what CopyDBFlagsToExtraFlags did // clear the three extra flags flags &= ~(kHasChildren | kIsThread | kElided); // use the mask to restore the state of the three flags flags |= saveFlags; m_flags.SetAt(index, flags); if (m_sortType == SortByThread && (flags & kIsRead != origFlags & kIsRead)) // to update unread counts on thread headers. { MSG_ViewIndex threadIndex = ThreadIndexOfMsg(m_idArray[index], index, NULL, NULL); if (threadIndex != index) NoteChange(threadIndex, 1, MSG_NotifyChanged); } } // Copy flags from extra array byte into DBMessageHdr flags. These flags may not have // the same bit positions, although they do now. The message flags are currently 32 bits, and // the extra flags just 8. Also, this routine takes a pointer to messageFlags so that it // can just change the bits it knows about. // These are the DB flags, not the MSG_FLAG flags. void MessageDBView::CopyExtraFlagsToDBFlags(char flags, uint32 *messageFlags) { if (flags & kElided) *messageFlags |= kElided; else *messageFlags &= ~kElided; if (flags & kExpired) *messageFlags |= kExpired; else *messageFlags &= ~kExpired; if (flags & kIsRead) *messageFlags |= kIsRead; else *messageFlags &= ~kIsRead; if (flags & kHasChildren) *messageFlags |= kHasChildren; else *messageFlags &= ~kHasChildren; if (flags & kIsThread) *messageFlags |= kIsThread; else *messageFlags &= ~kIsThread; if (flags & kMsgMarked) *messageFlags |= kMsgMarked; else *messageFlags &= ~kMsgMarked; } void MessageDBView::CopyExtraFlagsToPublicFlags(char flags, uint32 *messageFlags) { uint32 extraFlags = 0; // Then turn off the possible bits in the result. *messageFlags &= ~m_publicEquivOfExtraFlags; // Now, convert the flags that are on, and turn the 32 bit public // equivalent on in the result. CopyExtraFlagsToDBFlags(flags, &extraFlags); MessageDB::ConvertDBFlagsToPublicFlags(&extraFlags); *messageFlags |= extraFlags; } void MessageDBView::CopyDBFlagsToExtraFlags(uint32 messageFlags, char *extraFlags) { if (messageFlags & kIsRead) *extraFlags |= kIsRead; else *extraFlags &= ~kIsRead; if (messageFlags & kExpired) *extraFlags |= kExpired; else *extraFlags &= ~kExpired; if (messageFlags & kHasChildren) *extraFlags |= kHasChildren; else *extraFlags &= ~kHasChildren; if (messageFlags & kElided) *extraFlags |= kElided; else *extraFlags &= ~kElided; if (messageFlags & kIsThread) *extraFlags |= kIsThread; else *extraFlags &= ~kIsThread; if (messageFlags & kMsgMarked) *extraFlags |= kMsgMarked; else *extraFlags &= ~kMsgMarked; if (messageFlags & kOffline) *extraFlags |= kOffline; else *extraFlags &= ~kOffline; } /* Cache of open view objects allows us to enforce one open view per DB */ XP_List *MessageDBView::m_viewCache = NULL; MessageDBView *MessageDBView::CacheLookup (MessageDB *pDB, ViewType type) { if (m_viewCache) { XP_List *walkCache = m_viewCache; MessageDBView *walkView = 0; do { walkView = (MessageDBView*) XP_ListNextObject(walkCache); if (walkView && walkView->GetDB() == pDB) { // dbShowingIgnored doesn't matter if pDB is NULL, so FALSE is OK. XP_Bool dbShowingIgnored = (pDB) ? (pDB->GetDBFolderInfo()->GetFlags() & MSG_FOLDER_PREF_SHOWIGNORED) : FALSE; if (( walkView->m_viewType == type && !!walkView->GetShowingIgnored() == !!dbShowingIgnored) || type == ViewAny || !pDB) return walkView; } } while (walkView); } return NULL; } void MessageDBView::CacheAdd () { // allocate list when needed if (!m_viewCache) m_viewCache = XP_ListNew(); // can't already exist in cache XP_ASSERT (!CacheLookup(m_messageDB, m_viewType)); XP_ListAddObject (m_viewCache, this); } void MessageDBView::CacheRemove () { // must already exist in cache XP_ASSERT (CacheLookup (m_messageDB, m_viewType)); XP_ListRemoveObject (m_viewCache, this); // delete list when empty if (XP_ListCount (m_viewCache) == 0) { XP_ListDestroy (m_viewCache); m_viewCache = NULL; // isn't this required? dmb } } int32 MessageDBView::AddReference () { m_messageDB->AddUseCount (); return ++m_refCount; }