michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * No magic constructor behaviour, as is de rigeur for XPCOM. michael@0: * If you must perform some initialization, and it could possibly fail (even michael@0: * due to an out-of-memory condition), you should use an Init method, which michael@0: * can convey failure appropriately (thrown exception in JS, michael@0: * NS_FAILED(nsresult) return in C++). michael@0: * michael@0: * In JS, you can actually cheat, because a thrown exception will cause the michael@0: * CreateInstance call to fail in turn, but not all languages are so lucky. michael@0: * (Though ANSI C++ provides exceptions, they are verboten in Mozilla code michael@0: * for portability reasons -- and even when you're building completely michael@0: * platform-specific code, you can't throw across an XPCOM method boundary.) michael@0: */ michael@0: michael@0: Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: michael@0: const DEBUG = false; /* set to true to enable debug messages */ michael@0: michael@0: const LOCAL_FILE_CONTRACTID = "@mozilla.org/file/local;1"; michael@0: const APPSHELL_SERV_CONTRACTID = "@mozilla.org/appshell/appShellService;1"; michael@0: const STRBUNDLE_SERV_CONTRACTID = "@mozilla.org/intl/stringbundle;1"; michael@0: michael@0: const nsIAppShellService = Components.interfaces.nsIAppShellService; michael@0: const nsILocalFile = Components.interfaces.nsILocalFile; michael@0: const nsIFileURL = Components.interfaces.nsIFileURL; michael@0: const nsISupports = Components.interfaces.nsISupports; michael@0: const nsIFactory = Components.interfaces.nsIFactory; michael@0: const nsIFilePicker = Components.interfaces.nsIFilePicker; michael@0: const nsIInterfaceRequestor = Components.interfaces.nsIInterfaceRequestor; michael@0: const nsIDOMWindow = Components.interfaces.nsIDOMWindow; michael@0: const nsIStringBundleService = Components.interfaces.nsIStringBundleService; michael@0: const nsIWebNavigation = Components.interfaces.nsIWebNavigation; michael@0: const nsIDocShellTreeItem = Components.interfaces.nsIDocShellTreeItem; michael@0: const nsIBaseWindow = Components.interfaces.nsIBaseWindow; michael@0: michael@0: var titleBundle = null; michael@0: var filterBundle = null; michael@0: var lastDirectory = null; michael@0: michael@0: function nsFilePicker() michael@0: { michael@0: if (!titleBundle) michael@0: titleBundle = srGetStrBundle("chrome://global/locale/filepicker.properties"); michael@0: if (!filterBundle) michael@0: filterBundle = srGetStrBundle("chrome://global/content/filepicker.properties"); michael@0: michael@0: /* attributes */ michael@0: this.mDefaultString = ""; michael@0: this.mFilterIndex = 0; michael@0: this.mFilterTitles = new Array(); michael@0: this.mFilters = new Array(); michael@0: this.mDisplayDirectory = null; michael@0: if (lastDirectory) { michael@0: try { michael@0: var dir = Components.classes[LOCAL_FILE_CONTRACTID].createInstance(nsILocalFile); michael@0: dir.initWithPath(lastDirectory); michael@0: this.mDisplayDirectory = dir; michael@0: } catch (e) {} michael@0: } michael@0: } michael@0: michael@0: nsFilePicker.prototype = { michael@0: classID: Components.ID("{54ae32f8-1dd2-11b2-a209-df7c505370f8}"), michael@0: michael@0: QueryInterface: function(iid) { michael@0: if (iid.equals(nsIFilePicker) || michael@0: iid.equals(nsISupports)) michael@0: return this; michael@0: michael@0: throw Components.results.NS_ERROR_NO_INTERFACE; michael@0: }, michael@0: michael@0: michael@0: /* attribute nsILocalFile displayDirectory; */ michael@0: set displayDirectory(a) { michael@0: this.mDisplayDirectory = a && michael@0: a.clone().QueryInterface(nsILocalFile); michael@0: }, michael@0: get displayDirectory() { michael@0: return this.mDisplayDirectory && michael@0: this.mDisplayDirectory.clone() michael@0: .QueryInterface(nsILocalFile); michael@0: }, michael@0: michael@0: /* readonly attribute nsILocalFile file; */ michael@0: get file() { return this.mFilesEnumerator.mFiles[0]; }, michael@0: michael@0: /* readonly attribute nsISimpleEnumerator files; */ michael@0: get files() { return this.mFilesEnumerator; }, michael@0: michael@0: /* readonly attribute nsIDOMFile domfile; */ michael@0: get domfile() { michael@0: let enumerator = this.domfiles; michael@0: return enumerator ? enumerator.mFiles[0] : null; michael@0: }, michael@0: michael@0: /* readonly attribute nsISimpleEnumerator domfiles; */ michael@0: get domfiles() { michael@0: if (!this.mFilesEnumerator) { michael@0: return null; michael@0: } michael@0: michael@0: if (!this.mDOMFilesEnumerator) { michael@0: this.mDOMFilesEnumerator = { michael@0: QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISimpleEnumerator]), michael@0: michael@0: mFiles: [], michael@0: mIndex: 0, michael@0: michael@0: hasMoreElements: function() { michael@0: return (this.mIndex < this.mFiles.length); michael@0: }, michael@0: michael@0: getNext: function() { michael@0: if (this.mIndex >= this.mFiles.length) { michael@0: throw Components.results.NS_ERROR_FAILURE; michael@0: } michael@0: return this.mFiles[this.mIndex++]; michael@0: } michael@0: }; michael@0: michael@0: var utils = this.mParentWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor) michael@0: .getInterface(Components.interfaces.nsIDOMWindowUtils); michael@0: michael@0: for (var i = 0; i < this.mFilesEnumerator.mFiles.length; ++i) { michael@0: var file = utils.wrapDOMFile(this.mFilesEnumerator.mFiles[i]); michael@0: this.mDOMFilesEnumerator.mFiles.push(file); michael@0: } michael@0: } michael@0: michael@0: return this.mDOMFilesEnumerator; michael@0: }, michael@0: michael@0: /* readonly attribute nsIURI fileURL; */ michael@0: get fileURL() { michael@0: if (this.mFileURL) michael@0: return this.mFileURL; michael@0: michael@0: if (!this.mFilesEnumerator) michael@0: return null; michael@0: michael@0: var ioService = Components.classes["@mozilla.org/network/io-service;1"] michael@0: .getService(Components.interfaces.nsIIOService); michael@0: michael@0: return this.mFileURL = ioService.newFileURI(this.file); michael@0: }, michael@0: michael@0: /* attribute wstring defaultString; */ michael@0: set defaultString(a) { this.mDefaultString = a; }, michael@0: get defaultString() { return this.mDefaultString; }, michael@0: michael@0: /* attribute wstring defaultExtension */ michael@0: set defaultExtension(ext) { }, michael@0: get defaultExtension() { return ""; }, michael@0: michael@0: /* attribute long filterIndex; */ michael@0: set filterIndex(a) { this.mFilterIndex = a; }, michael@0: get filterIndex() { return this.mFilterIndex; }, michael@0: michael@0: /* attribute boolean addToRecentDocs; */ michael@0: set addToRecentDocs(a) {}, michael@0: get addToRecentDocs() { return false; }, michael@0: michael@0: /* readonly attribute short mode; */ michael@0: get mode() { return this.mMode; }, michael@0: michael@0: /* members */ michael@0: mFilesEnumerator: undefined, michael@0: mDOMFilesEnumerator: undefined, michael@0: mParentWindow: null, michael@0: michael@0: /* methods */ michael@0: init: function(parent, title, mode) { michael@0: this.mParentWindow = parent; michael@0: this.mTitle = title; michael@0: this.mMode = mode; michael@0: }, michael@0: michael@0: appendFilters: function(filterMask) { michael@0: if (filterMask & nsIFilePicker.filterHTML) { michael@0: this.appendFilter(titleBundle.GetStringFromName("htmlTitle"), michael@0: filterBundle.GetStringFromName("htmlFilter")); michael@0: } michael@0: if (filterMask & nsIFilePicker.filterText) { michael@0: this.appendFilter(titleBundle.GetStringFromName("textTitle"), michael@0: filterBundle.GetStringFromName("textFilter")); michael@0: } michael@0: if (filterMask & nsIFilePicker.filterImages) { michael@0: this.appendFilter(titleBundle.GetStringFromName("imageTitle"), michael@0: filterBundle.GetStringFromName("imageFilter")); michael@0: } michael@0: if (filterMask & nsIFilePicker.filterXML) { michael@0: this.appendFilter(titleBundle.GetStringFromName("xmlTitle"), michael@0: filterBundle.GetStringFromName("xmlFilter")); michael@0: } michael@0: if (filterMask & nsIFilePicker.filterXUL) { michael@0: this.appendFilter(titleBundle.GetStringFromName("xulTitle"), michael@0: filterBundle.GetStringFromName("xulFilter")); michael@0: } michael@0: this.mAllowURLs = !!(filterMask & nsIFilePicker.filterAllowURLs); michael@0: if (filterMask & nsIFilePicker.filterApps) { michael@0: // We use "..apps" as a special filter for executable files michael@0: this.appendFilter(titleBundle.GetStringFromName("appsTitle"), michael@0: "..apps"); michael@0: } michael@0: if (filterMask & nsIFilePicker.filterAudio) { michael@0: this.appendFilter(titleBundle.GetStringFromName("audioTitle"), michael@0: filterBundle.GetStringFromName("audioFilter")); michael@0: } michael@0: if (filterMask & nsIFilePicker.filterVideo) { michael@0: this.appendFilter(titleBundle.GetStringFromName("videoTitle"), michael@0: filterBundle.GetStringFromName("videoFilter")); michael@0: } michael@0: if (filterMask & nsIFilePicker.filterAll) { michael@0: this.appendFilter(titleBundle.GetStringFromName("allTitle"), michael@0: filterBundle.GetStringFromName("allFilter")); michael@0: } michael@0: }, michael@0: michael@0: appendFilter: function(title, extensions) { michael@0: this.mFilterTitles.push(title); michael@0: this.mFilters.push(extensions); michael@0: }, michael@0: michael@0: open: function(aFilePickerShownCallback) { michael@0: var tm = Components.classes["@mozilla.org/thread-manager;1"] michael@0: .getService(Components.interfaces.nsIThreadManager); michael@0: tm.mainThread.dispatch(function() { michael@0: let result = Components.interfaces.nsIFilePicker.returnCancel; michael@0: try { michael@0: result = this.show(); michael@0: } catch(ex) { michael@0: } michael@0: if (aFilePickerShownCallback) { michael@0: aFilePickerShownCallback.done(result); michael@0: } michael@0: }.bind(this), Components.interfaces.nsIThread.DISPATCH_NORMAL); michael@0: }, michael@0: michael@0: show: function() { michael@0: var o = new Object(); michael@0: o.title = this.mTitle; michael@0: o.mode = this.mMode; michael@0: o.displayDirectory = this.mDisplayDirectory; michael@0: o.defaultString = this.mDefaultString; michael@0: o.filterIndex = this.mFilterIndex; michael@0: o.filters = new Object(); michael@0: o.filters.titles = this.mFilterTitles; michael@0: o.filters.types = this.mFilters; michael@0: o.allowURLs = this.mAllowURLs; michael@0: o.retvals = new Object(); michael@0: michael@0: var parent; michael@0: if (this.mParentWindow) { michael@0: parent = this.mParentWindow; michael@0: } else if (typeof(window) == "object" && window != null) { michael@0: parent = window; michael@0: } else { michael@0: try { michael@0: var appShellService = Components.classes[APPSHELL_SERV_CONTRACTID].getService(nsIAppShellService); michael@0: parent = appShellService.hiddenDOMWindow; michael@0: } catch(ex) { michael@0: debug("Can't get parent. xpconnect hates me so we can't get one from the appShellService.\n"); michael@0: debug(ex + "\n"); michael@0: } michael@0: } michael@0: michael@0: var parentWin = null; michael@0: try { michael@0: parentWin = parent.QueryInterface(nsIInterfaceRequestor) michael@0: .getInterface(nsIWebNavigation) michael@0: .QueryInterface(nsIDocShellTreeItem) michael@0: .treeOwner michael@0: .QueryInterface(nsIInterfaceRequestor) michael@0: .getInterface(nsIBaseWindow); michael@0: } catch(ex) { michael@0: dump("file picker couldn't get base window\n"+ex+"\n"); michael@0: } michael@0: try { michael@0: parent.openDialog("chrome://global/content/filepicker.xul", michael@0: "", michael@0: "chrome,modal,titlebar,resizable=yes,dependent=yes", michael@0: o); michael@0: michael@0: this.mFilterIndex = o.retvals.filterIndex; michael@0: this.mFilesEnumerator = o.retvals.files; michael@0: this.mFileURL = o.retvals.fileURL; michael@0: lastDirectory = o.retvals.directory; michael@0: return o.retvals.buttonStatus; michael@0: } catch(ex) { dump("unable to open file picker\n" + ex + "\n"); } michael@0: michael@0: return null; michael@0: } michael@0: } michael@0: michael@0: if (DEBUG) michael@0: debug = function (s) { dump("-*- filepicker: " + s + "\n"); }; michael@0: else michael@0: debug = function (s) {}; michael@0: michael@0: this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsFilePicker]); michael@0: michael@0: /* crap from strres.js that I want to use for string bundles since I can't include another .js file.... */ michael@0: michael@0: var strBundleService = null; michael@0: michael@0: function srGetStrBundle(path) michael@0: { michael@0: var strBundle = null; michael@0: michael@0: if (!strBundleService) { michael@0: try { michael@0: strBundleService = Components.classes[STRBUNDLE_SERV_CONTRACTID].getService(nsIStringBundleService); michael@0: } catch (ex) { michael@0: dump("\n--** strBundleService createInstance failed **--\n"); michael@0: return null; michael@0: } michael@0: } michael@0: michael@0: strBundle = strBundleService.createBundle(path); michael@0: if (!strBundle) { michael@0: dump("\n--** strBundle createInstance failed **--\n"); michael@0: } michael@0: return strBundle; michael@0: } michael@0: