Back to index

lightning-sunbird  0.9+nobinonly
nsFilePicker.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
00002  *
00003  * ***** BEGIN LICENSE BLOCK *****
00004  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00005  *
00006  * The contents of this file are subject to the Mozilla Public License Version
00007  * 1.1 (the "License"); you may not use this file except in compliance with
00008  * the License. You may obtain a copy of the License at
00009  * http://www.mozilla.org/MPL/
00010  *
00011  * Software distributed under the License is distributed on an "AS IS" basis,
00012  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00013  * for the specific language governing rights and limitations under the
00014  * License.
00015  *
00016  * The Original Code is the Mozilla browser.
00017  *
00018  * The Initial Developer of the Original Code is
00019  * Netscape Communications Corporation.
00020  * Portions created by the Initial Developer are Copyright (C) 2000
00021  * the Initial Developer. All Rights Reserved.
00022  *
00023  * Contributor(s):
00024  *   Stuart Parmenter <pavlov@netscape.com>
00025  *   Seth Spitzer <sspitzer@netscape.com>
00026  *
00027  * Alternatively, the contents of this file may be used under the terms of
00028  * either the GNU General Public License Version 2 or later (the "GPL"), or
00029  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00030  * in which case the provisions of the GPL or the LGPL are applicable instead
00031  * of those above. If you wish to allow use of your version of this file only
00032  * under the terms of either the GPL or the LGPL, and not to allow others to
00033  * use your version of this file under the terms of the MPL, indicate your
00034  * decision by deleting the provisions above and replace them with the notice
00035  * and other provisions required by the GPL or the LGPL. If you do not delete
00036  * the provisions above, a recipient may use your version of this file under
00037  * the terms of any one of the MPL, the GPL or the LGPL.
00038  *
00039  * ***** END LICENSE BLOCK ***** */
00040 
00041 #include "nsCOMPtr.h"
00042 #include "nsGUIEvent.h"
00043 #include "nsReadableUtils.h"
00044 #include "nsNetUtil.h"
00045 #include "nsWindow.h"
00046 #include "nsIServiceManager.h"
00047 #include "nsIPlatformCharset.h"
00048 #include "nsICharsetConverterManager.h"
00049 #include "nsFilePicker.h"
00050 #include "nsILocalFile.h"
00051 #include "nsIURL.h"
00052 #include "nsIFileURL.h"
00053 #include "nsIStringBundle.h"
00054 #include "nsNativeCharsetUtils.h"
00055 #include "nsEnumeratorUtils.h"
00056 #include "nsCRT.h"
00057 #include <windows.h>
00058 #include <shlobj.h>
00059 
00060 // commdlg.h and cderr.h are needed to build with WIN32_LEAN_AND_MEAN
00061 #include <commdlg.h>
00062 #ifndef WINCE
00063 #include <cderr.h>
00064 #endif
00065 
00066 #include "nsString.h"
00067 #include "nsToolkit.h"
00068 
00069 static NS_DEFINE_CID(kCharsetConverterManagerCID, NS_ICHARSETCONVERTERMANAGER_CID);
00070 
00071 NS_IMPL_ISUPPORTS1(nsFilePicker, nsIFilePicker)
00072 
00073 nsString nsFilePicker::mLastUsedUnicodeDirectory;
00074 char nsFilePicker::mLastUsedDirectory[MAX_PATH+1] = { 0 };
00075 
00076 #define MAX_EXTENSION_LENGTH 10
00077 
00078 #ifndef BIF_USENEWUI
00079 // BIF_USENEWUI isn't defined in the platform SDK that comes with
00080 // MSVC6.0. 
00081 #define BIF_USENEWUI 0x50
00082 #endif
00083 
00084 //-------------------------------------------------------------------------
00085 //
00086 // nsFilePicker constructor
00087 //
00088 //-------------------------------------------------------------------------
00089 nsFilePicker::nsFilePicker()
00090 {
00091   mSelectedType   = 1;
00092 }
00093 
00094 //-------------------------------------------------------------------------
00095 //
00096 // nsFilePicker destructor
00097 //
00098 //-------------------------------------------------------------------------
00099 nsFilePicker::~nsFilePicker()
00100 {
00101 }
00102 
00103 //-------------------------------------------------------------------------
00104 //
00105 // Show - Display the file dialog
00106 //
00107 //-------------------------------------------------------------------------
00108 
00109 #ifndef WINCE
00110 int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
00111 {
00112   if (uMsg == BFFM_INITIALIZED)
00113   {
00114     char * filePath = (char *) lpData;
00115     if (filePath)
00116     {
00117       ::SendMessage(hwnd, BFFM_SETSELECTION, TRUE /* true because lpData is a path string */, lpData);
00118       nsCRT::free(filePath);
00119     }
00120   }
00121   return 0;
00122 }
00123 #endif
00124 
00125 NS_IMETHODIMP nsFilePicker::ShowW(PRInt16 *aReturnVal)
00126 {
00127   NS_ENSURE_ARG_POINTER(aReturnVal);
00128 
00129   // suppress blur events
00130   if (mParentWidget) {
00131     nsIWidget *tmp = mParentWidget;
00132     nsWindow *parent = NS_STATIC_CAST(nsWindow *, tmp);
00133     parent->SuppressBlurEvents(PR_TRUE);
00134   }
00135 
00136   PRBool result = PR_FALSE;
00137   PRUnichar fileBuffer[FILE_BUFFER_SIZE+1];
00138   wcsncpy(fileBuffer,  mDefault.get(), FILE_BUFFER_SIZE);
00139   fileBuffer[FILE_BUFFER_SIZE] = '\0'; // null terminate in case copy truncated
00140 
00141   NS_NAMED_LITERAL_STRING(htmExt, "html");
00142   nsAutoString initialDir;
00143   if (mDisplayDirectory)
00144     mDisplayDirectory->GetPath(initialDir);
00145 
00146   // If no display directory, re-use the last one.
00147   if(initialDir.IsEmpty()) {
00148     // Allocate copy of last used dir.
00149     initialDir = mLastUsedUnicodeDirectory;
00150   }
00151 
00152   mUnicodeFile.Truncate();
00153 
00154 #ifndef WINCE
00155 
00156   if (mMode == modeGetFolder) {
00157     PRUnichar dirBuffer[MAX_PATH+1];
00158     wcsncpy(dirBuffer, initialDir.get(), MAX_PATH);
00159 
00160     BROWSEINFOW browserInfo;
00161     browserInfo.hwndOwner      = (HWND)
00162       (mParentWidget.get() ? mParentWidget->GetNativeData(NS_NATIVE_WINDOW) : 0); 
00163     browserInfo.pidlRoot       = nsnull;
00164     browserInfo.pszDisplayName = (LPWSTR)dirBuffer;
00165     browserInfo.lpszTitle      = mTitle.get();
00166     browserInfo.ulFlags        = BIF_USENEWUI | BIF_RETURNONLYFSDIRS;
00167     if (initialDir.Length()) // convert folder path to native, the strdup copy will be released in BrowseCallbackProc
00168     {
00169       nsCAutoString nativeFolderPath;
00170       NS_CopyUnicodeToNative(initialDir, nativeFolderPath);
00171       browserInfo.lParam       = (LPARAM) nsCRT::strdup(nativeFolderPath.get()); 
00172       browserInfo.lpfn         = &BrowseCallbackProc;
00173     }
00174     else
00175     {
00176     browserInfo.lParam         = nsnull;
00177       browserInfo.lpfn         = nsnull;
00178     }
00179     browserInfo.iImage         = nsnull;
00180 
00181     // XXX UNICODE support is needed here --> DONE
00182     LPITEMIDLIST list = nsToolkit::mSHBrowseForFolder(&browserInfo);
00183     if (list != NULL) {
00184       result = nsToolkit::mSHGetPathFromIDList(list, (LPWSTR)fileBuffer);
00185       if (result) {
00186           mUnicodeFile.Assign(fileBuffer);
00187       }
00188   
00189       // free PIDL
00190       CoTaskMemFree(list);
00191     }
00192   }
00193   else 
00194 #endif // WINCE
00195   {
00196 
00197     OPENFILENAMEW ofn;
00198     memset(&ofn, 0, sizeof(ofn));
00199 
00200     ofn.lStructSize = sizeof(ofn);
00201 
00202     nsString filterBuffer = mFilterList;
00203                                   
00204     if (!initialDir.IsEmpty()) {
00205       ofn.lpstrInitialDir = initialDir.get();
00206     }
00207     
00208     ofn.lpstrTitle   = (LPCWSTR)mTitle.get();
00209     ofn.lpstrFilter  = (LPCWSTR)filterBuffer.get();
00210     ofn.nFilterIndex = mSelectedType;
00211     ofn.hwndOwner    = (HWND)
00212       (mParentWidget.get() ? mParentWidget->GetNativeData(NS_NATIVE_WINDOW) : 0); 
00213     ofn.lpstrFile    = fileBuffer;
00214     ofn.nMaxFile     = FILE_BUFFER_SIZE;
00215 
00216     ofn.Flags = OFN_NOCHANGEDIR | OFN_SHAREAWARE | OFN_LONGNAMES | OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY | OFN_PATHMUSTEXIST;
00217 
00218     if (!mDefaultExtension.IsEmpty()) {
00219       ofn.lpstrDefExt = mDefaultExtension.get();
00220     }
00221     else {
00222       // Get file extension from suggested filename
00223       //  to detect if we are saving an html file
00224       //XXX: nsIFile SHOULD HAVE A GetExtension() METHOD!
00225       PRInt32 extIndex = mDefault.RFind(".");
00226       if ( extIndex >= 0) {
00227         nsAutoString ext;
00228         mDefault.Right(ext, mDefault.Length() - extIndex);
00229         // Should we test for ".cgi", ".asp", ".jsp" and other
00230         // "generated" html pages?
00231 
00232         if ( ext.LowerCaseEqualsLiteral(".htm")  ||
00233              ext.LowerCaseEqualsLiteral(".html") ||
00234              ext.LowerCaseEqualsLiteral(".shtml") ) {
00235           // This is supposed to append ".htm" if user doesn't supply an extension
00236           //XXX Actually, behavior is sort of weird:
00237           //    often appends ".html" even if you have an extension
00238           //    It obeys your extension if you put quotes around name
00239           ofn.lpstrDefExt = htmExt.get();
00240         }
00241       }
00242     }
00243 
00244 #ifndef WINCE
00245     try {
00246 #endif
00247       if (mMode == modeOpen) {
00248         // FILE MUST EXIST!
00249         ofn.Flags |= OFN_FILEMUSTEXIST;
00250         result = nsToolkit::mGetOpenFileName(&ofn);
00251       }
00252       else if (mMode == modeOpenMultiple) {
00253         ofn.Flags |= OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT | OFN_EXPLORER;
00254         result = nsToolkit::mGetOpenFileName(&ofn);
00255       }
00256       else if (mMode == modeSave) {
00257         ofn.Flags |= OFN_NOREADONLYRETURN;
00258 
00259         // Don't follow shortcuts when saving a shortcut, this can be used
00260         // to trick users (bug 271732)
00261         NS_ConvertUTF16toUTF8 ext(mDefault);
00262         ext.Trim(" .", PR_FALSE, PR_TRUE); // watch out for trailing space and dots
00263         ToLowerCase(ext);
00264         if (StringEndsWith(ext, NS_LITERAL_CSTRING(".lnk")) ||
00265             StringEndsWith(ext, NS_LITERAL_CSTRING(".pif")) ||
00266             StringEndsWith(ext, NS_LITERAL_CSTRING(".url")))
00267           ofn.Flags |= OFN_NODEREFERENCELINKS;
00268 
00269         result = nsToolkit::mGetSaveFileName(&ofn);
00270         if (!result) {
00271           // Error, find out what kind.
00272           if (::GetLastError() == ERROR_INVALID_PARAMETER ||
00273               ::CommDlgExtendedError() == FNERR_INVALIDFILENAME) {
00274             // probably the default file name is too long or contains illegal characters!
00275             // Try again, without a starting file name.
00276             ofn.lpstrFile[0] = 0;
00277             result = nsToolkit::mGetSaveFileName(&ofn);
00278           }
00279         }
00280       }
00281       else {
00282         NS_ASSERTION(0, "unsupported mode"); 
00283       }
00284 #ifndef WINCE
00285     }
00286     catch(...) {
00287       MessageBox(ofn.hwndOwner,
00288                  0,
00289                  "The filepicker was unexpectedly closed by Windows.",
00290                  MB_ICONERROR);
00291       result = PR_FALSE;
00292     }
00293 #endif
00294   
00295     if (result == PR_TRUE) {
00296       // Remember what filter type the user selected
00297       mSelectedType = (PRInt16)ofn.nFilterIndex;
00298 
00299       // Set user-selected location of file or directory
00300       if (mMode == modeOpenMultiple) {
00301         nsresult rv = NS_NewISupportsArray(getter_AddRefs(mFiles));
00302         NS_ENSURE_SUCCESS(rv,rv);
00303         
00304         // from msdn.microsoft.com, "Open and Save As Dialog Boxes" section:
00305         // If you specify OFN_EXPLORER,
00306         // The directory and file name strings are NULL separated, 
00307         // with an extra NULL character after the last file name. 
00308         // This format enables the Explorer-style dialog boxes
00309         // to return long file names that include spaces. 
00310         PRUnichar *current = fileBuffer;
00311         
00312         nsAutoString dirName(current);
00313         // sometimes dirName contains a trailing slash
00314         // and sometimes it doesn't.
00315         if (current[dirName.Length() - 1] != '\\')
00316           dirName.Append((PRUnichar)'\\');
00317         
00318         while (current && *current && *(current + nsCRT::strlen(current) + 1)) {
00319           current = current + nsCRT::strlen(current) + 1;
00320           
00321           nsCOMPtr<nsILocalFile> file = do_CreateInstance("@mozilla.org/file/local;1", &rv);
00322           NS_ENSURE_SUCCESS(rv,rv);
00323           
00324           rv = file->InitWithPath(dirName + nsDependentString(current));
00325           NS_ENSURE_SUCCESS(rv,rv);
00326           
00327           rv = mFiles->AppendElement(file);
00328           NS_ENSURE_SUCCESS(rv,rv);
00329         }
00330         
00331         // handle the case where the user selected just one
00332         // file.  according to msdn.microsoft.com:
00333         // If you specify OFN_ALLOWMULTISELECT and the user selects 
00334         // only one file, the lpstrFile string does not have 
00335         // a separator between the path and file name.
00336         if (current && *current && (current == fileBuffer)) {
00337           nsCOMPtr<nsILocalFile> file = do_CreateInstance("@mozilla.org/file/local;1", &rv);
00338           NS_ENSURE_SUCCESS(rv,rv);
00339           
00340           rv = file->InitWithPath(nsDependentString(current));
00341           NS_ENSURE_SUCCESS(rv,rv);
00342           
00343           rv = mFiles->AppendElement(file);
00344           NS_ENSURE_SUCCESS(rv,rv);
00345         }
00346       }
00347       else {
00348         // I think it also needs a conversion here (to unicode since appending to nsString) 
00349         // but doing that generates garbage file name, weird.
00350         mUnicodeFile.Assign(fileBuffer);
00351       }
00352     }
00353 
00354   }
00355 
00356   if (result) {
00357     PRInt16 returnOKorReplace = returnOK;
00358 
00359     // Remember last used directory.
00360     nsCOMPtr<nsILocalFile> file(do_CreateInstance("@mozilla.org/file/local;1"));
00361     NS_ENSURE_TRUE(file, NS_ERROR_FAILURE);
00362 
00363     // XXX  InitWithPath() will convert UCS2 to FS path !!!  corrupts unicode 
00364     file->InitWithPath(mUnicodeFile);
00365     nsCOMPtr<nsIFile> dir;
00366     if (NS_SUCCEEDED(file->GetParent(getter_AddRefs(dir)))) {
00367       mDisplayDirectory = do_QueryInterface(dir);
00368       if (mDisplayDirectory) {
00369         nsAutoString newDir;
00370         mDisplayDirectory->GetPath(newDir);
00371         if(!newDir.IsEmpty())
00372           mLastUsedUnicodeDirectory.Assign(newDir);
00373       }
00374     }
00375 
00376     if (mMode == modeSave) {
00377       // Windows does not return resultReplace,
00378       //   we must check if file already exists
00379       PRBool exists = PR_FALSE;
00380       file->Exists(&exists);
00381 
00382       if (exists)
00383         returnOKorReplace = returnReplace;
00384     }
00385     *aReturnVal = returnOKorReplace;
00386   }
00387   else {
00388     *aReturnVal = returnCancel;
00389   }
00390   if (mParentWidget) {
00391     nsIWidget *tmp = mParentWidget;
00392     nsWindow *parent = NS_STATIC_CAST(nsWindow *, tmp);
00393     parent->SuppressBlurEvents(PR_FALSE);
00394   }
00395 
00396   return NS_OK;
00397 }
00398 
00399 NS_IMETHODIMP nsFilePicker::Show(PRInt16 *aReturnVal)
00400 {
00401   return ShowW(aReturnVal);
00402 }
00403 
00404 NS_IMETHODIMP nsFilePicker::GetFile(nsILocalFile **aFile)
00405 {
00406   NS_ENSURE_ARG_POINTER(aFile);
00407 
00408   if (mUnicodeFile.IsEmpty())
00409       return NS_OK;
00410 
00411   nsCOMPtr<nsILocalFile> file(do_CreateInstance("@mozilla.org/file/local;1"));
00412     
00413   NS_ENSURE_TRUE(file, NS_ERROR_FAILURE);
00414 
00415   file->InitWithPath(mUnicodeFile);
00416 
00417   NS_ADDREF(*aFile = file);
00418 
00419   return NS_OK;
00420 }
00421 
00422 //-------------------------------------------------------------------------
00423 NS_IMETHODIMP nsFilePicker::GetFileURL(nsIFileURL **aFileURL)
00424 {
00425   nsCOMPtr<nsILocalFile> file(do_CreateInstance("@mozilla.org/file/local;1"));
00426   NS_ENSURE_TRUE(file, NS_ERROR_FAILURE);
00427   file->InitWithPath(mUnicodeFile);
00428 
00429   nsCOMPtr<nsIURI> uri;
00430   NS_NewFileURI(getter_AddRefs(uri), file);
00431   nsCOMPtr<nsIFileURL> fileURL(do_QueryInterface(uri));
00432   NS_ENSURE_TRUE(fileURL, NS_ERROR_FAILURE);
00433 
00434   NS_ADDREF(*aFileURL = fileURL);
00435 
00436   return NS_OK;
00437 }
00438 
00439 NS_IMETHODIMP nsFilePicker::GetFiles(nsISimpleEnumerator **aFiles)
00440 {
00441   NS_ENSURE_ARG_POINTER(aFiles);
00442   return NS_NewArrayEnumerator(aFiles, mFiles);
00443 }
00444 
00445 //-------------------------------------------------------------------------
00446 //
00447 // Get the file + path
00448 //
00449 //-------------------------------------------------------------------------
00450 NS_IMETHODIMP nsFilePicker::SetDefaultString(const nsAString& aString)
00451 {
00452   mDefault = aString;
00453 
00454   //First, make sure the file name is not too long!
00455   PRInt32 nameLength;
00456   PRInt32 nameIndex = mDefault.RFind("\\");
00457   if (nameIndex == kNotFound)
00458     nameIndex = 0;
00459   else
00460     nameIndex ++;
00461   nameLength = mDefault.Length() - nameIndex;
00462   
00463   if (nameLength > _MAX_FNAME) {
00464     PRInt32 extIndex = mDefault.RFind(".");
00465     if (extIndex == kNotFound)
00466       extIndex = mDefault.Length();
00467 
00468     //Let's try to shave the needed characters from the name part
00469     PRInt32 charsToRemove = nameLength - _MAX_FNAME;
00470     if (extIndex - nameIndex >= charsToRemove) {
00471       mDefault.Cut(extIndex - charsToRemove, charsToRemove);
00472     }
00473   }
00474 
00475   //Then, we need to replace illegal characters.
00476   //At this stage, we cannot replace the backslash as the string might represent a file path.
00477   mDefault.ReplaceChar(FILE_ILLEGAL_CHARACTERS, '-');
00478 
00479   return NS_OK;
00480 }
00481 
00482 NS_IMETHODIMP nsFilePicker::GetDefaultString(nsAString& aString)
00483 {
00484   return NS_ERROR_FAILURE;
00485 }
00486 
00487 //-------------------------------------------------------------------------
00488 //
00489 // The default extension to use for files
00490 //
00491 //-------------------------------------------------------------------------
00492 NS_IMETHODIMP nsFilePicker::GetDefaultExtension(nsAString& aExtension)
00493 {
00494   aExtension = mDefaultExtension;
00495   return NS_OK;
00496 }
00497 
00498 NS_IMETHODIMP nsFilePicker::SetDefaultExtension(const nsAString& aExtension)
00499 {
00500   mDefaultExtension = aExtension;
00501   return NS_OK;
00502 }
00503 
00504 //-------------------------------------------------------------------------
00505 //
00506 // Set the filter index
00507 //
00508 //-------------------------------------------------------------------------
00509 NS_IMETHODIMP nsFilePicker::GetFilterIndex(PRInt32 *aFilterIndex)
00510 {
00511   // Windows' filter index is 1-based, we use a 0-based system.
00512   *aFilterIndex = mSelectedType - 1;
00513   return NS_OK;
00514 }
00515 
00516 NS_IMETHODIMP nsFilePicker::SetFilterIndex(PRInt32 aFilterIndex)
00517 {
00518   // Windows' filter index is 1-based, we use a 0-based system.
00519   mSelectedType = aFilterIndex + 1;
00520   return NS_OK;
00521 }
00522 
00523 //-------------------------------------------------------------------------
00524 void nsFilePicker::InitNative(nsIWidget *aParent,
00525                               const nsAString& aTitle,
00526                               PRInt16 aMode)
00527 {
00528   mParentWidget = aParent;
00529   mTitle.Assign(aTitle);
00530   mMode = aMode;
00531 }
00532 
00533 
00534 NS_IMETHODIMP
00535 nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter)
00536 {
00537   mFilterList.Append(aTitle);
00538   mFilterList.Append(PRUnichar('\0'));
00539 
00540   if (aFilter.EqualsLiteral("..apps"))
00541     mFilterList.AppendLiteral("*.exe;*.com");
00542   else
00543   {
00544     nsAutoString filter(aFilter);
00545     filter.StripWhitespace();
00546     if (filter.EqualsLiteral("*"))
00547       filter.AppendLiteral(".*");
00548     mFilterList.Append(filter);
00549   }
00550 
00551   mFilterList.Append(PRUnichar('\0'));
00552 
00553   return NS_OK;
00554 }