diff --git a/mailnews/compose/resources/content/MsgComposeCommands.js b/mailnews/compose/resources/content/MsgComposeCommands.js index 5d41ebe29a4d..6ec3137106a1 100644 --- a/mailnews/compose/resources/content/MsgComposeCommands.js +++ b/mailnews/compose/resources/content/MsgComposeCommands.js @@ -178,8 +178,8 @@ function enableEditableFields() var gComposeRecyclingListener = { onClose: function() { //Reset recipients and attachments - awResetAllRows(); - RemoveAllAttachments(); + awResetAllRows(); + RemoveAllAttachments(); //We need to clear the identity popup menu in case the user will change them. It will be rebuilded later in ComposeStartup ClearIdentityListPopup(document.getElementById("msgIdentityPopup")); @@ -2191,12 +2191,12 @@ function SetLastAttachDirectory(attachedLocalFile) function AttachFile() { - var currentAttachment = ""; + var attachments; //Get file using nsIFilePicker and convert to URL try { var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker); - fp.init(window, sComposeMsgsBundle.getString("chooseFileToAttach"), nsIFilePicker.modeOpen); + fp.init(window, sComposeMsgsBundle.getString("chooseFileToAttach"), nsIFilePicker.modeOpenMultiple); var lastDirectory = GetLastAttachDirectory(); if (lastDirectory) @@ -2204,28 +2204,36 @@ function AttachFile() fp.appendFilters(nsIFilePicker.filterAll); if (fp.show() == nsIFilePicker.returnOK) { - currentAttachment = fp.fileURL.spec; - SetLastAttachDirectory(fp.file) + attachments = fp.files; } } catch (ex) { - dump("failed to get the local file to attach\n"); + dump("failed to get attachments: " + ex + "\n"); } - - if (currentAttachment == "") + + if (!attachments || !attachments.hasMoreElements()) return; - if (DuplicateFileCheck(currentAttachment)) - { - dump("Error, attaching the same item twice\n"); - } - else - { - var attachment = Components.classes["@mozilla.org/messengercompose/attachment;1"] - .createInstance(Components.interfaces.nsIMsgAttachment); - attachment.url = currentAttachment; - AddAttachment(attachment); - gContentChanged = true; + var haveSetAttachDirectory = false; + + while (attachments.hasMoreElements()) { + var currentFile = attachments.getNext().QueryInterface(Components.interfaces.nsILocalFile); + + if (!haveSetAttachDirectory) { + SetLastAttachDirectory(currentFile); + haveSetAttachDirectory = true; + } + + var ioService = Components.classes["@mozilla.org/network/io-service;1"] + ioService = ioService.getService(Components.interfaces.nsIIOService); + var currentAttachment = ioService.getURLSpecFromFile(currentFile); + + if (!DuplicateFileCheck(currentAttachment)) { + var attachment = Components.classes["@mozilla.org/messengercompose/attachment;1"].createInstance(Components.interfaces.nsIMsgAttachment); + attachment.url = currentAttachment; + AddAttachment(attachment); + gContentChanged = true; + } } } diff --git a/widget/public/nsIFilePicker.idl b/widget/public/nsIFilePicker.idl index 3acb35257a35..f52434e4b78d 100644 --- a/widget/public/nsIFilePicker.idl +++ b/widget/public/nsIFilePicker.idl @@ -26,6 +26,7 @@ interface nsILocalFile; interface nsIFileURL; interface nsIDOMWindowInternal; +interface nsISimpleEnumerator; [scriptable, uuid(c47de916-1dd1-11b2-8141-82507fa02b21)] interface nsIFilePicker : nsISupports @@ -33,6 +34,7 @@ interface nsIFilePicker : nsISupports const short modeOpen = 0; // Load a file or directory const short modeSave = 1; // Save a file or directory const short modeGetFolder = 2; // Select a folder/directory + const short modeOpenMultiple= 3; // Load multiple files const short returnOK = 0; // User hit Ok, process selection const short returnCancel = 1; // User hit cancel, ignore selection @@ -118,6 +120,14 @@ interface nsIFilePicker : nsISupports */ readonly attribute nsIFileURL fileURL; + /** + * Get the enumerator for the selected files + * only works in the modeOpenMultiple mode + * + * @return Returns the files currently selected + */ + readonly attribute nsISimpleEnumerator files; + /** * Show File Dialog. The dialog is displayed modally. * diff --git a/widget/src/beos/nsFilePicker.cpp b/widget/src/beos/nsFilePicker.cpp index 561694b17dd9..b63b5258f698 100644 --- a/widget/src/beos/nsFilePicker.cpp +++ b/widget/src/beos/nsFilePicker.cpp @@ -80,7 +80,7 @@ NS_IMETHODIMP nsFilePicker::Show(PRInt16 *retval) node_flavors = B_DIRECTORY_NODE; panel_mode = B_OPEN_PANEL; } - else if (mMode == modeOpen) { + else if (mMode == modeOpen || mMode == modeOpenMultiple) { node_flavors = B_FILE_NODE; panel_mode = B_OPEN_PANEL; } @@ -136,7 +136,7 @@ NS_IMETHODIMP nsFilePicker::Show(PRInt16 *retval) result = PR_FALSE; } - if (mMode == modeOpen && ppanel->IsOpenSelected()) { + if ((mMode == modeOpen || mMode == modeOpenMultiple) && ppanel->IsOpenSelected()) { BList *list = ppanel->OpenRefs(); if ((list) && list->CountItems() >= 1) { entry_ref *ref = (entry_ref *)list->ItemAt(0); diff --git a/widget/src/cocoa/nsFilePicker.cpp b/widget/src/cocoa/nsFilePicker.cpp index 21753fe82bde..08061819aa46 100644 --- a/widget/src/cocoa/nsFilePicker.cpp +++ b/widget/src/cocoa/nsFilePicker.cpp @@ -208,7 +208,7 @@ NS_IMETHODIMP nsFilePicker::Show(PRInt16 *retval) // XXX Ignore the filter list for now.... - if (mMode == modeOpen) + if (mMode == modeOpen || mMode == modeOpenMultiple) userClicksOK = GetLocalFile(title, &theFile); else if (mMode == modeSave) userClicksOK = PutLocalFile(title, defaultName, &theFile); diff --git a/widget/src/mac/nsFilePicker.cpp b/widget/src/mac/nsFilePicker.cpp index b360386a57d7..e7ef2e399b31 100644 --- a/widget/src/mac/nsFilePicker.cpp +++ b/widget/src/mac/nsFilePicker.cpp @@ -160,7 +160,7 @@ NS_IMETHODIMP nsFilePicker::Show(PRInt16 *retval) // XXX Ignore the filter list for now.... - if (mMode == modeOpen) + if (mMode == modeOpen || mMode == modeOpenMultiple) userClicksOK = GetLocalFile(mTitle, &theFile); else if (mMode == modeSave) userClicksOK = PutLocalFile(mTitle, mDefault, &theFile); diff --git a/widget/src/windows/nsFilePicker.cpp b/widget/src/windows/nsFilePicker.cpp index 110ca1dce1f4..295ac9e21f8c 100644 --- a/widget/src/windows/nsFilePicker.cpp +++ b/widget/src/windows/nsFilePicker.cpp @@ -19,6 +19,7 @@ * * Contributor(s): * Stuart Parmenter + * Seth Spitzer */ // Define so header files for openfilename are included @@ -36,6 +37,7 @@ #include "nsIURL.h" #include "nsIFileURL.h" #include "nsIStringBundle.h" +#include "nsEnumeratorUtils.h" #include "nsCRT.h" #include #include @@ -404,6 +406,10 @@ NS_IMETHODIMP nsFilePicker::Show(PRInt16 *aReturnVal) ofn.Flags |= OFN_FILEMUSTEXIST; result = ::GetOpenFileName(&ofn); } + else if (mMode == modeOpenMultiple) { + ofn.Flags |= OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT | OFN_EXPLORER; + result = ::GetOpenFileName(&ofn); + } else if (mMode == modeSave) { ofn.Flags |= OFN_NOREADONLYRETURN; result = ::GetSaveFileName(&ofn); @@ -433,9 +439,53 @@ NS_IMETHODIMP nsFilePicker::Show(PRInt16 *aReturnVal) if (result == PR_TRUE) { // I think it also needs a conversion here (to unicode since appending to nsString) // but doing that generates garbage file name, weird. + if (mMode == modeOpenMultiple) { + nsresult rv = NS_NewISupportsArray(getter_AddRefs(mFiles)); + NS_ENSURE_SUCCESS(rv,rv); + + // from msdn.microsoft.com, "Open and Save As Dialog Boxes" section: + // If you specify OFN_EXPLORER, + // The directory and file name strings are NULL separated, + // with an extra NULL character after the last file name. + // This format enables the Explorer-style dialog boxes + // to return long file names that include spaces. + char *current = fileBuffer; + const char *dirName = current; + + while (current && *current && *(current + strlen(current) + 1)) { + current = current + strlen(current) + 1; + + nsCOMPtr file = do_CreateInstance("@mozilla.org/file/local;1", &rv); + NS_ENSURE_SUCCESS(rv,rv); + + // dirName contains a trailing slash + rv = file->InitWithNativePath(nsDependentCString(dirName) + nsDependentCString(current)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = mFiles->AppendElement(file); + NS_ENSURE_SUCCESS(rv,rv); + } + + // handle the case where the user selected just one + // file. according to msdn.microsoft.com: + // If you specify OFN_ALLOWMULTISELECT and the user selects + // only one file, the lpstrFile string does not have + // a separator between the path and file name. + if (current && *current && (current == fileBuffer)) { + nsCOMPtr file = do_CreateInstance("@mozilla.org/file/local;1", &rv); + NS_ENSURE_SUCCESS(rv,rv); + + rv = file->InitWithNativePath(nsDependentCString(current)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = mFiles->AppendElement(file); + NS_ENSURE_SUCCESS(rv,rv); + } + } + else { mFile.Append(fileBuffer); } - + } } if (title) @@ -528,6 +578,12 @@ NS_IMETHODIMP nsFilePicker::GetFileURL(nsIFileURL **aFileURL) return NS_OK; } +NS_IMETHODIMP nsFilePicker::GetFiles(nsISimpleEnumerator **aFiles) +{ + NS_ENSURE_ARG_POINTER(aFiles); + return NS_NewArrayEnumerator(aFiles, mFiles); +} + //------------------------------------------------------------------------- // // Get the file + path diff --git a/widget/src/windows/nsFilePicker.h b/widget/src/windows/nsFilePicker.h index aa21dfdd8630..d5d4c08545cb 100644 --- a/widget/src/windows/nsFilePicker.h +++ b/widget/src/windows/nsFilePicker.h @@ -19,12 +19,15 @@ * * Contributor(s): * Stuart Parmenter + * Seth Spitzer */ #ifndef nsFilePicker_h__ #define nsFilePicker_h__ #include "nsILocalFile.h" +#include "nsISimpleEnumerator.h" +#include "nsISupportsArray.h" #include "nsICharsetConverterManager.h" #include "nsBaseFilePicker.h" @@ -54,6 +57,7 @@ public: NS_IMETHOD SetFilterIndex(PRInt32 aFilterIndex); NS_IMETHOD GetFile(nsILocalFile * *aFile); NS_IMETHOD GetFileURL(nsIFileURL * *aFileURL); + NS_IMETHOD GetFiles(nsISimpleEnumerator **aFiles); NS_IMETHOD Show(PRInt16 *aReturnVal); #ifdef MOZ_UNICODE NS_IMETHOD ShowW(PRInt16 *aReturnVal); @@ -83,7 +87,7 @@ protected: nsIUnicodeDecoder* mUnicodeDecoder; nsCOMPtr mDisplayDirectory; PRInt16 mSelectedType; - + nsCOMPtr mFiles; static char mLastUsedDirectory[]; #ifdef MOZ_UNICODE diff --git a/widget/src/xpwidgets/nsBaseFilePicker.cpp b/widget/src/xpwidgets/nsBaseFilePicker.cpp index d141304144ad..521325d440c4 100644 --- a/widget/src/xpwidgets/nsBaseFilePicker.cpp +++ b/widget/src/xpwidgets/nsBaseFilePicker.cpp @@ -36,6 +36,9 @@ #include "nsIStringBundle.h" #include "nsXPIDLString.h" #include "nsIServiceManager.h" +#include "nsISupportsArray.h" +#include "nsILocalFile.h" +#include "nsEnumeratorUtils.h" #include "nsBaseFilePicker.h" @@ -183,3 +186,22 @@ NS_IMETHODIMP nsBaseFilePicker::SetFilterIndex(PRInt32 aFilterIndex) return NS_OK; } +NS_IMETHODIMP nsBaseFilePicker::GetFiles(nsISimpleEnumerator **aFiles) +{ + NS_ENSURE_ARG_POINTER(aFiles); + nsCOMPtr files; + nsresult rv = NS_NewISupportsArray(getter_AddRefs(files)); + NS_ENSURE_SUCCESS(rv,rv); + + // if we get into the base class, the platform + // doesn't implement GetFiles() yet. + // so we fake it. + nsCOMPtr file; + rv = GetFile(getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = files->AppendElement(file); + NS_ENSURE_SUCCESS(rv,rv); + + return NS_NewArrayEnumerator(aFiles, files); +} diff --git a/widget/src/xpwidgets/nsBaseFilePicker.h b/widget/src/xpwidgets/nsBaseFilePicker.h index c714440d35ea..8e1dce6cde7f 100644 --- a/widget/src/xpwidgets/nsBaseFilePicker.h +++ b/widget/src/xpwidgets/nsBaseFilePicker.h @@ -27,6 +27,7 @@ #include "nsIFilePicker.h" #include "nsIWidget.h" +#include "nsISimpleEnumerator.h" class nsBaseFilePicker : public nsIFilePicker { @@ -41,6 +42,7 @@ public: NS_IMETHOD AppendFilters(PRInt32 filterMask); NS_IMETHOD GetFilterIndex(PRInt32 *aFilterIndex); NS_IMETHOD SetFilterIndex(PRInt32 aFilterIndex); + NS_IMETHOD GetFiles(nsISimpleEnumerator **aFiles); protected: diff --git a/xpfe/components/filepicker/res/content/filepicker.js b/xpfe/components/filepicker/res/content/filepicker.js index eda2e7d01188..139abb6a4eae 100644 --- a/xpfe/components/filepicker/res/content/filepicker.js +++ b/xpfe/components/filepicker/res/content/filepicker.js @@ -78,12 +78,13 @@ function filepickerLoad() { } } - if (filePickerMode != nsIFilePicker.modeOpen) { + if (filePickerMode != nsIFilePicker.modeOpen && filePickerMode != nsIFilePicker.modeOpenMultiple) { var newDirButton = document.getElementById("newDirButton"); newDirButton.removeAttribute("hidden"); } if ((filePickerMode == nsIFilePicker.modeOpen) || + (filePickerMode == nsIFilePicker.modeOpenMultiple) || (filePickerMode == nsIFilePicker.modeSave)) { treeView.setFilter(filterTypes[0]); @@ -199,7 +200,9 @@ function openOnOK() if (dir) gotoDirectory(dir); + retvals.file = dir; + retvals.buttonStatus = nsIFilePicker.returnCancel; var filterMenuList = document.getElementById("filterMenuList"); @@ -248,6 +251,7 @@ function selectOnOK() switch(filePickerMode) { case nsIFilePicker.modeOpen: + case nsIFilePicker.modeOpenMultiple: if (isFile) { if (file.isReadable()) { retvals.directory = file.parent.path; @@ -339,6 +343,11 @@ function selectOnOK() } retvals.file = file; + + gFilesEnumerator.mFile = file; + gFilesEnumerator.mHasMore = true; + + retvals.files = gFilesEnumerator; retvals.buttonStatus = ret; var filterMenuList = document.getElementById("filterMenuList"); @@ -347,11 +356,27 @@ function selectOnOK() return (ret != nsIFilePicker.returnCancel); } +var gFilesEnumerator = { + mHasMore: false, + mFile: null, + + hasMoreElements: function() + { + return this.mHasMore; + }, + getNext: function() + { + this.mHasMore = false; + return this.mFile; + } +}; + function onCancel() { // Close the window. retvals.buttonStatus = nsIFilePicker.returnCancel; retvals.file = null; + retvals.files = null; return true; } @@ -465,6 +490,7 @@ function getOKAction(file) { buttonLabel = gFilePickerBundle.getString("selectFolderButtonLabel"); break; case nsIFilePicker.modeOpen: + case nsIFilePicker.modeOpenMultiple: buttonLabel = gFilePickerBundle.getString("openButtonLabel"); break; case nsIFilePicker.modeSave: diff --git a/xpfe/components/filepicker/src/nsFilePicker.js b/xpfe/components/filepicker/src/nsFilePicker.js index bcaf82b359b1..a46120e1c858 100644 --- a/xpfe/components/filepicker/src/nsFilePicker.js +++ b/xpfe/components/filepicker/src/nsFilePicker.js @@ -84,6 +84,10 @@ nsFilePicker.prototype = { set file(a) { throw "readonly property"; }, get file() { return this.mFile; }, + /* readonly attribute nsISimpleEnumerator files; */ + set files(a) { throw "readonly property"; }, + get files() { return this.mFilesEnumerator; }, + /* readonly attribute nsIFileURL fileURL; */ set fileURL(a) { throw "readonly property"; }, get fileURL() { @@ -110,6 +114,7 @@ nsFilePicker.prototype = { /* members */ mFile: undefined, + mFilesEnumerator: undefined, mParentWindow: null, /* methods */ @@ -197,6 +202,7 @@ nsFilePicker.prototype = { o); this.mFile = o.retvals.file; this.mFilterIndex = o.retvals.filterIndex; + this.mFilesEnumerator = o.retvals.files; lastDirectory = o.retvals.directory; return o.retvals.buttonStatus; } catch(ex) { dump("unable to open file picker\n" + ex + "\n"); } diff --git a/xpfe/components/filepicker/src/nsFilePicker.js.in b/xpfe/components/filepicker/src/nsFilePicker.js.in index bcaf82b359b1..a46120e1c858 100644 --- a/xpfe/components/filepicker/src/nsFilePicker.js.in +++ b/xpfe/components/filepicker/src/nsFilePicker.js.in @@ -84,6 +84,10 @@ nsFilePicker.prototype = { set file(a) { throw "readonly property"; }, get file() { return this.mFile; }, + /* readonly attribute nsISimpleEnumerator files; */ + set files(a) { throw "readonly property"; }, + get files() { return this.mFilesEnumerator; }, + /* readonly attribute nsIFileURL fileURL; */ set fileURL(a) { throw "readonly property"; }, get fileURL() { @@ -110,6 +114,7 @@ nsFilePicker.prototype = { /* members */ mFile: undefined, + mFilesEnumerator: undefined, mParentWindow: null, /* methods */ @@ -197,6 +202,7 @@ nsFilePicker.prototype = { o); this.mFile = o.retvals.file; this.mFilterIndex = o.retvals.filterIndex; + this.mFilesEnumerator = o.retvals.files; lastDirectory = o.retvals.directory; return o.retvals.buttonStatus; } catch(ex) { dump("unable to open file picker\n" + ex + "\n"); }