diff --git a/mail/base/content/commandglue.js b/mail/base/content/commandglue.js index ae89c91c8a79..913fef549ef4 100644 --- a/mail/base/content/commandglue.js +++ b/mail/base/content/commandglue.js @@ -537,8 +537,23 @@ function ConvertColumnIDToSortType(columnID) sortKey = nsMsgViewSortType.byAttachments; break; default: - dump("unsupported sort column: " + columnID + "\n"); - sortKey = 0; + + //no predefined column handler - lets check if there is a custom column handler + try { + //try to grab the columnHandler (an error is thrown if it does not exist) + columnHandler = gDBView.getColumnHandler(columnID); + + //it exists - save this column ID in the customSortCol property of dbFolderInfo + //for later use (see nsIMsgDBView.cpp) + gDBView.db.dBFolderInfo.setProperty('customSortCol', columnID); + + sortKey = nsMsgViewSortType.byCustom; + } + catch(err) + { + dump("unsupported sort column: " + columnID + " - no custom handler installed. (Error was: " + err + ")\n"); + sortKey = 0; + } break; } return sortKey; @@ -601,6 +616,20 @@ function ConvertSortTypeToColumnID(sortKey) case nsMsgViewSortType.byAttachments: columnID = "attachmentCol"; break; + case nsMsgViewSortType.byCustom: + + //TODO: either change try() catch to if (property exists) or restore the getColumnHandler() check + try //getColumnHandler throws an errror when the ID is not handled + { + columnID = gDBView.db.dBFolderInfo.getProperty('customSortCol'); + } + + catch (err) { //error - means no handler + dump("ConvertSortTypeToColumnID: custom sort key but no handler for column '" + columnID + "'\n"); + columnID = "dateCol"; + } + + break; default: dump("unsupported sort key: " + sortKey + "\n"); columnID = "dateCol"; diff --git a/mailnews/base/public/Makefile.in b/mailnews/base/public/Makefile.in index 536ba8acf31e..a5bf52730168 100644 --- a/mailnews/base/public/Makefile.in +++ b/mailnews/base/public/Makefile.in @@ -98,6 +98,7 @@ XPIDLSRCS = \ nsIMsgMdnGenerator.idl \ nsISpamSettings.idl \ nsIMapiRegistry.idl \ + nsIMsgCustomColumnHandler.idl \ $(NULL) include $(topsrcdir)/config/rules.mk diff --git a/mailnews/base/public/nsIMsgCustomColumnHandler.idl b/mailnews/base/public/nsIMsgCustomColumnHandler.idl new file mode 100644 index 000000000000..c800b20efbb3 --- /dev/null +++ b/mailnews/base/public/nsIMsgCustomColumnHandler.idl @@ -0,0 +1,65 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * The Mozilla Corporation. + * Portions created by the Initial Developer are Copyright (C) 2006 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Oren Nachmore + * David Bienvenu + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsITreeView.idl" + +interface nsIMsgDBHdr; + + /* //TODO JavaDoc + When implementing a custom column handler (of type nsITreeView) you must implement the following + functions: + 1. isEditable + 2. GetCellProperties + 3. GetImageSrc + 4. GetCellText + 5. CycleCell + 6. GetSortStringForRow + 7. GetSortLongForRow + 8. isString + */ + + +[scriptable, uuid(33ef10af-54fb-42ce-95d0-52d8f6f1e681)] +interface nsIMsgCustomColumnHandler : nsITreeView +{ + AString getSortStringForRow(in nsIMsgDBHdr aHdr); + unsigned long getSortLongForRow(in nsIMsgDBHdr aHdr); + boolean isString(); +}; + diff --git a/mailnews/base/public/nsIMsgDBView.idl b/mailnews/base/public/nsIMsgDBView.idl index bdedcf8ffafe..cc65a234ae3a 100644 --- a/mailnews/base/public/nsIMsgDBView.idl +++ b/mailnews/base/public/nsIMsgDBView.idl @@ -46,6 +46,8 @@ interface nsIMsgDBViewCommandUpdater; interface nsIMsgDatabase; interface nsIMsgSearchSession; interface nsISimpleEnumerator; +interface nsITreeView; +interface nsIMsgCustomColumnHandler; typedef long nsMsgViewNotificationCodeValue; typedef long nsMsgViewCommandCheckStateValue; @@ -104,6 +106,7 @@ interface nsMsgViewSortType const nsMsgViewSortTypeValue byJunkStatus = 0x1f; const nsMsgViewSortTypeValue byAttachments = 0x20; const nsMsgViewSortTypeValue byAccount = 0x21; + const nsMsgViewSortTypeValue byCustom = 0x22; }; [scriptable, uuid(255d1c1e-fde7-11d4-a5be-0060b0fc04b7)] @@ -250,7 +253,7 @@ interface nsMsgNavigationType }; -[scriptable, uuid(704c7d28-fd1a-11d4-a5be-0060b0fc04b7)] +[scriptable, uuid(7a49bcaa-5367-476d-a3da-7f305b7afb90)] interface nsIMsgDBView : nsISupports { void open(in nsIMsgFolder folder, in nsMsgViewSortTypeValue sortType, in nsMsgViewSortOrderValue sortOrder, in nsMsgViewFlagsTypeValue viewFlags, out long count); @@ -333,6 +336,17 @@ interface nsIMsgDBView : nsISupports // use lines or kB for size? readonly attribute boolean usingLines; + + // Custom Column Implementation note: see nsIMsgCustomColumnHandler + + // attaches a custom column handler to a specific column (can be a new column or a built in) + void addColumnHandler(in AString aColumn, in nsIMsgCustomColumnHandler aHandler); + + // removes a custom column handler leaving the column to be handled by the system + void removeColumnHandler(in AString aColumn); + + // returns the custom column handler attached to a specific column - if any + nsIMsgCustomColumnHandler getColumnHandler(in AString aColumn); }; /* this interface is rapidly morphing from a command updater interface into a more generic diff --git a/mailnews/base/src/nsMsgDBView.cpp b/mailnews/base/src/nsMsgDBView.cpp index f3602b8cd83e..ac11ed880794 100644 --- a/mailnews/base/src/nsMsgDBView.cpp +++ b/mailnews/base/src/nsMsgDBView.cpp @@ -39,6 +39,7 @@ #include "msgCore.h" #include "nsReadableUtils.h" +#include "nsIMsgCustomColumnHandler.h" #include "nsMsgDBView.h" #include "nsISupports.h" #include "nsIMsgFolder.h" @@ -910,6 +911,18 @@ nsresult nsMsgDBView::CycleThreadedColumn(nsIDOMElement * aElement) NS_IMETHODIMP nsMsgDBView::IsEditable(PRInt32 row, nsITreeColumn* col, PRBool* _retval) { + //attempt to retreive a custom column handler. If it exists call it and return + const PRUnichar* colID; + col->GetIdConst(&colID); + + nsIMsgCustomColumnHandler* colHandler = GetColumnHandler(colID); + + if (colHandler) + { + colHandler->IsEditable(row, col, _retval); + return NS_OK; + } + *_retval = PR_FALSE; return NS_OK; } @@ -1350,6 +1363,16 @@ NS_IMETHODIMP nsMsgDBView::GetCellProperties(PRInt32 aRow, nsITreeColumn *col, n } } + //custom column handlers are called at the end of getCellProperties + //to make life easier for extension writers + nsIMsgCustomColumnHandler* colHandler = GetColumnHandler(colID); + + if (colHandler != nsnull) + { + colHandler->GetCellProperties(aRow, col, properties); + return NS_OK; + } + return NS_OK; } @@ -1505,6 +1528,18 @@ nsresult nsMsgDBView::GetDBForViewIndex(nsMsgViewIndex index, nsIMsgDatabase **d NS_IMETHODIMP nsMsgDBView::GetImageSrc(PRInt32 aRow, nsITreeColumn* aCol, nsAString& aValue) { + //attempt to retreive a custom column handler. If it exists call it and return + const PRUnichar* colID; + aCol->GetIdConst(&colID); + + nsIMsgCustomColumnHandler* colHandler = GetColumnHandler(colID); + + if (colHandler) + { + colHandler->GetImageSrc(aRow, aCol, aValue); + return NS_OK; + } + return NS_OK; } @@ -1519,6 +1554,82 @@ NS_IMETHODIMP nsMsgDBView::GetCellValue(PRInt32 aRow, nsITreeColumn* aCol, nsASt return NS_OK; } +//add a custom column handler +NS_IMETHODIMP nsMsgDBView::AddColumnHandler(const nsAString& column, nsIMsgCustomColumnHandler* handler) +{ + + PRInt32 index = m_customColumnHandlerIDs.IndexOf(column); + + nsAutoString strColID(column); + + //does not exist + if (index == -1) + { + m_customColumnHandlerIDs.AppendString(strColID); + m_customColumnHandlers.AppendObject(handler); + } + else + { + //insert new handler into the appropriate place in the COMPtr array + //no need to replace the column ID (it's the same) + m_customColumnHandlers.ReplaceObjectAt(handler, index); + + } + + return NS_OK; +} + +//remove a custom column handler +NS_IMETHODIMP nsMsgDBView::RemoveColumnHandler(const nsAString& aColID) +{ + + PRInt32 index = m_customColumnHandlerIDs.IndexOf(aColID); + + if (index != -1) + { + m_customColumnHandlerIDs.RemoveStringAt(index); + m_customColumnHandlers.RemoveObjectAt(index); + + return NS_OK; + } + + return NS_ERROR_FAILURE; //can't remove a column that isn't currently custom handled +} + +//TODO: NS_ENSURE_SUCCESS +nsIMsgCustomColumnHandler* nsMsgDBView::GetCurColumnHandlerFromDBInfo() +{ + nsresult rv; + + nsCOMPtr dbInfo; + m_db->GetDBFolderInfo(getter_AddRefs(dbInfo)); + + nsAutoString colID; + rv = dbInfo->GetProperty("customSortCol", colID); + + return GetColumnHandler(colID.get()); +} + +nsIMsgCustomColumnHandler* nsMsgDBView::GetColumnHandler(const PRUnichar *colID) +{ + nsIMsgCustomColumnHandler* columnHandler = nsnull; + + PRInt32 index = m_customColumnHandlerIDs.IndexOf(nsDependentString(colID)); + + if (index > -1) + columnHandler = m_customColumnHandlers[index]; + + return columnHandler; +} + +NS_IMETHODIMP nsMsgDBView::GetColumnHandler(const nsAString& aColID, nsIMsgCustomColumnHandler** aHandler) +{ + NS_ENSURE_ARG_POINTER(aHandler); + nsAutoString column(aColID); + NS_IF_ADDREF(*aHandler = GetColumnHandler(column.get())); + return (*aHandler) ? NS_OK : NS_ERROR_FAILURE; +} + NS_IMETHODIMP nsMsgDBView::GetCellText(PRInt32 aRow, nsITreeColumn* aCol, nsAString& aValue) { nsresult rv = NS_OK; @@ -1529,7 +1640,8 @@ NS_IMETHODIMP nsMsgDBView::GetCellText(PRInt32 aRow, nsITreeColumn* aCol, nsAStr nsCOMPtr msgHdr; rv = GetMsgHdrForViewIndex(aRow, getter_AddRefs(msgHdr)); - if (NS_FAILED(rv) || !msgHdr) { + if (NS_FAILED(rv) || !msgHdr) + { ClearHdrCache(); return NS_MSG_INVALID_DBVIEW_INDEX; } @@ -1541,6 +1653,16 @@ NS_IMETHODIMP nsMsgDBView::GetCellText(PRInt32 aRow, nsITreeColumn* aCol, nsAStr const PRUnichar* colID; aCol->GetIdConst(&colID); + + //attempt to retreive a custom column handler. If it exists call it and return + nsIMsgCustomColumnHandler* colHandler = GetColumnHandler(colID); + + if (colHandler) + { + colHandler->GetCellText(aRow, aCol, aValue); + return NS_OK; + } + switch (colID[0]) { case 's': @@ -1673,6 +1795,16 @@ NS_IMETHODIMP nsMsgDBView::CycleCell(PRInt32 row, nsITreeColumn* col) const PRUnichar* colID; col->GetIdConst(&colID); + + //attempt to retreive a custom column handler. If it exists call it and return + nsIMsgCustomColumnHandler* colHandler = GetColumnHandler(colID); + + if (colHandler) + { + colHandler->CycleCell(row, col); + return NS_OK; + } + switch (colID[0]) { case 'u': // unreadButtonColHeader @@ -3150,7 +3282,8 @@ nsresult nsMsgDBView::GetFieldTypeAndLenForSort(nsMsgViewSortTypeValue sortType, NS_ENSURE_ARG_POINTER(pMaxLen); NS_ENSURE_ARG_POINTER(pFieldType); - switch (sortType) { + switch (sortType) + { case nsMsgViewSortType::bySubject: *pFieldType = kCollationKey; *pMaxLen = kMaxSubjectKey; @@ -3182,6 +3315,28 @@ nsresult nsMsgDBView::GetFieldTypeAndLenForSort(nsMsgViewSortTypeValue sortType, *pFieldType = kU32; *pMaxLen = 0; break; + case nsMsgViewSortType::byCustom: + { + nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandlerFromDBInfo(); + + if (colHandler != nsnull) + { + PRBool isString; + colHandler->IsString(&isString); + + if (isString) + { + *pFieldType = kCollationKey; + *pMaxLen = kMaxRecipientKey; //80 - do we need a seperate k? + } + else + { + *pFieldType = kU32; + *pMaxLen = 0; + } + } + break; + } default: return NS_ERROR_UNEXPECTED; } @@ -3226,7 +3381,7 @@ nsresult nsMsgDBView::GetStatusSortValue(nsIMsgDBHdr *msgHdr, PRUint32 *result) return NS_OK; } -nsresult nsMsgDBView::GetLongField(nsIMsgDBHdr *msgHdr, nsMsgViewSortTypeValue sortType, PRUint32 *result) +nsresult nsMsgDBView::GetLongField(nsIMsgDBHdr *msgHdr, nsMsgViewSortTypeValue sortType, PRUint32 *result, nsIMsgCustomColumnHandler* colHandler) { nsresult rv; NS_ENSURE_ARG_POINTER(msgHdr); @@ -3294,6 +3449,18 @@ nsresult nsMsgDBView::GetLongField(nsIMsgDBHdr *msgHdr, nsMsgViewSortTypeValue s else rv = msgHdr->GetDateInSeconds(result); break; + case nsMsgViewSortType::byCustom: + if (colHandler != nsnull) + { + colHandler->GetSortLongForRow(msgHdr, result); + rv = NS_OK; + } + else + { + NS_ASSERTION(PR_FALSE, "should not be here (Sort Type: byCustom (Long), but no custom handler)"); + rv = NS_ERROR_UNEXPECTED; + } + break; case nsMsgViewSortType::byId: // handled by caller, since caller knows the key default: @@ -3306,9 +3473,8 @@ nsresult nsMsgDBView::GetLongField(nsIMsgDBHdr *msgHdr, nsMsgViewSortTypeValue s return NS_OK; } - nsresult -nsMsgDBView::GetCollationKey(nsIMsgDBHdr *msgHdr, nsMsgViewSortTypeValue sortType, PRUint8 **result, PRUint32 *len) +nsMsgDBView::GetCollationKey(nsIMsgDBHdr *msgHdr, nsMsgViewSortTypeValue sortType, PRUint8 **result, PRUint32 *len, nsIMsgCustomColumnHandler* colHandler) { nsresult rv; NS_ENSURE_ARG_POINTER(msgHdr); @@ -3345,6 +3511,22 @@ nsMsgDBView::GetCollationKey(nsIMsgDBHdr *msgHdr, nsMsgViewSortTypeValue sortTyp rv = dbToUse->CreateCollationKey(str, result, len); } break; + case nsMsgViewSortType::byCustom: + if (colHandler != nsnull) + { + nsAutoString strKey; + rv = colHandler->GetSortStringForRow(msgHdr, strKey); + NS_ASSERTION(NS_SUCCEEDED(rv),"failed to get sort string for custom row"); + nsAutoString strTemp(strKey); + + rv = m_db->CreateCollationKey(strKey, result, len); + } + else + { + NS_ASSERTION(PR_FALSE,"should not be here (Sort Type: byCustom (String), but no custom handler)"); + //rv = NS_ERROR_UNEXPECTED; + } + break; default: rv = NS_ERROR_UNEXPECTED; break; @@ -3502,11 +3684,15 @@ NS_IMETHODIMP nsMsgDBView::Sort(nsMsgViewSortTypeValue sortType, nsMsgViewSortOr msgHdr = nsnull; } + //check if a custom column handler exists. If it does then grab it and pass it in + //to either GetCollationKey or GetLongField + nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandlerFromDBInfo(); + // could be a problem here if the ones that appear here are different than the ones already in the array PRUint32 actualFieldLen = 0; if (fieldType == kCollationKey) { - rv = GetCollationKey(msgHdr, sortType, &keyValue, &actualFieldLen); + rv = GetCollationKey(msgHdr, sortType, &keyValue, &actualFieldLen, colHandler); NS_ENSURE_SUCCESS(rv,rv); longValue = actualFieldLen; @@ -3519,7 +3705,7 @@ NS_IMETHODIMP nsMsgDBView::Sort(nsMsgViewSortTypeValue sortType, nsMsgViewSortOr } else { - rv = GetLongField(msgHdr, sortType, &longValue); + rv = GetLongField(msgHdr, sortType, &longValue, colHandler); NS_ENSURE_SUCCESS(rv,rv); } } @@ -4166,10 +4352,15 @@ nsMsgViewIndex nsMsgDBView::GetInsertIndexHelper(nsIMsgDBHdr *msgHdr, nsMsgKeyAr int (* PR_CALLBACK comparisonFun) (const void *pItem1, const void *pItem2, void *privateData)=nsnull; int retStatus = 0; msgHdr->GetMessageKey(&EntryInfo1.id); + + //check if a custom column handler exists. If it does then grab it and pass it in + //to either GetCollationKey or GetLongField + nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandlerFromDBInfo(); + switch (fieldType) { case kCollationKey: - rv = GetCollationKey(msgHdr, sortType, &EntryInfo1.key, &EntryInfo1.dword); + rv = GetCollationKey(msgHdr, sortType, &EntryInfo1.key, &EntryInfo1.dword, colHandler); NS_ASSERTION(NS_SUCCEEDED(rv),"failed to create collation key"); comparisonFun = FnSortIdKeyPtr; comparisonContext = m_db.get(); @@ -4178,7 +4369,7 @@ nsMsgViewIndex nsMsgDBView::GetInsertIndexHelper(nsIMsgDBHdr *msgHdr, nsMsgKeyAr if (sortType == nsMsgViewSortType::byId) EntryInfo1.dword = EntryInfo1.id; else - GetLongField(msgHdr, sortType, &EntryInfo1.dword); + GetLongField(msgHdr, sortType, &EntryInfo1.dword, colHandler); comparisonFun = FnSortIdDWord; break; default: @@ -4198,7 +4389,7 @@ nsMsgViewIndex nsMsgDBView::GetInsertIndexHelper(nsIMsgDBHdr *msgHdr, nsMsgKeyAr if (fieldType == kCollationKey) { PR_FREEIF(EntryInfo2.key); - rv = GetCollationKey(tryHdr, sortType, &EntryInfo2.key, &EntryInfo2.dword); + rv = GetCollationKey(tryHdr, sortType, &EntryInfo2.key, &EntryInfo2.dword, colHandler); NS_ASSERTION(NS_SUCCEEDED(rv),"failed to create collation key"); } else if (fieldType == kU32) @@ -4207,7 +4398,7 @@ nsMsgViewIndex nsMsgDBView::GetInsertIndexHelper(nsIMsgDBHdr *msgHdr, nsMsgKeyAr EntryInfo2.dword = EntryInfo2.id; } else { - GetLongField(tryHdr, sortType, &EntryInfo2.dword); + GetLongField(tryHdr, sortType, &EntryInfo2.dword, colHandler); } } retStatus = (*comparisonFun)(&pValue1, &pValue2, comparisonContext); diff --git a/mailnews/base/src/nsMsgDBView.h b/mailnews/base/src/nsMsgDBView.h index ef3a5e69a22e..c781e32827ed 100644 --- a/mailnews/base/src/nsMsgDBView.h +++ b/mailnews/base/src/nsMsgDBView.h @@ -65,6 +65,7 @@ #include "nsIMsgFilterPlugin.h" #include "nsIStringBundle.h" #include "nsMsgTagService.h" +#include "nsCOMArray.h" #define MESSENGER_STRING_URL "chrome://messenger/locale/messenger.properties" @@ -290,8 +291,10 @@ protected: // for sorting nsresult GetFieldTypeAndLenForSort(nsMsgViewSortTypeValue sortType, PRUint16 *pMaxLen, eFieldType *pFieldType); - nsresult GetCollationKey(nsIMsgDBHdr *msgHdr, nsMsgViewSortTypeValue sortType, PRUint8 **result, PRUint32 *len); - nsresult GetLongField(nsIMsgDBHdr *msgHdr, nsMsgViewSortTypeValue sortType, PRUint32 *result); + nsresult GetCollationKey(nsIMsgDBHdr *msgHdr, nsMsgViewSortTypeValue sortType, PRUint8 **result, + PRUint32 *len, nsIMsgCustomColumnHandler* colHandler = nsnull); + nsresult GetLongField(nsIMsgDBHdr *msgHdr, nsMsgViewSortTypeValue sortType, PRUint32 *result, + nsIMsgCustomColumnHandler* colHandler = nsnull); nsresult GetStatusSortValue(nsIMsgDBHdr *msgHdr, PRUint32 *result); nsresult GetLocationCollationKey(nsIMsgDBHdr *msgHdr, PRUint8 **result, PRUint32 *len); @@ -387,6 +390,12 @@ protected: nsUInt32Array mIndicesToNoteChange; + //these hold pointers (and IDs) for the nsIMsgCustomColumnHandler object that constitutes the custom column handler + nsCOMArray m_customColumnHandlers; + nsStringArray m_customColumnHandlerIDs; + + nsIMsgCustomColumnHandler* GetColumnHandler(const PRUnichar*); + nsIMsgCustomColumnHandler* GetCurColumnHandlerFromDBInfo(); protected: static nsresult InitDisplayFormats();