|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 /* |
|
7 * No magic constructor behaviour, as is de rigeur for XPCOM. |
|
8 * If you must perform some initialization, and it could possibly fail (even |
|
9 * due to an out-of-memory condition), you should use an Init method, which |
|
10 * can convey failure appropriately (thrown exception in JS, |
|
11 * NS_FAILED(nsresult) return in C++). |
|
12 * |
|
13 * In JS, you can actually cheat, because a thrown exception will cause the |
|
14 * CreateInstance call to fail in turn, but not all languages are so lucky. |
|
15 * (Though ANSI C++ provides exceptions, they are verboten in Mozilla code |
|
16 * for portability reasons -- and even when you're building completely |
|
17 * platform-specific code, you can't throw across an XPCOM method boundary.) |
|
18 */ |
|
19 |
|
20 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
21 |
|
22 const DEBUG = false; /* set to true to enable debug messages */ |
|
23 |
|
24 const LOCAL_FILE_CONTRACTID = "@mozilla.org/file/local;1"; |
|
25 const APPSHELL_SERV_CONTRACTID = "@mozilla.org/appshell/appShellService;1"; |
|
26 const STRBUNDLE_SERV_CONTRACTID = "@mozilla.org/intl/stringbundle;1"; |
|
27 |
|
28 const nsIAppShellService = Components.interfaces.nsIAppShellService; |
|
29 const nsILocalFile = Components.interfaces.nsILocalFile; |
|
30 const nsIFileURL = Components.interfaces.nsIFileURL; |
|
31 const nsISupports = Components.interfaces.nsISupports; |
|
32 const nsIFactory = Components.interfaces.nsIFactory; |
|
33 const nsIFilePicker = Components.interfaces.nsIFilePicker; |
|
34 const nsIInterfaceRequestor = Components.interfaces.nsIInterfaceRequestor; |
|
35 const nsIDOMWindow = Components.interfaces.nsIDOMWindow; |
|
36 const nsIStringBundleService = Components.interfaces.nsIStringBundleService; |
|
37 const nsIWebNavigation = Components.interfaces.nsIWebNavigation; |
|
38 const nsIDocShellTreeItem = Components.interfaces.nsIDocShellTreeItem; |
|
39 const nsIBaseWindow = Components.interfaces.nsIBaseWindow; |
|
40 |
|
41 var titleBundle = null; |
|
42 var filterBundle = null; |
|
43 var lastDirectory = null; |
|
44 |
|
45 function nsFilePicker() |
|
46 { |
|
47 if (!titleBundle) |
|
48 titleBundle = srGetStrBundle("chrome://global/locale/filepicker.properties"); |
|
49 if (!filterBundle) |
|
50 filterBundle = srGetStrBundle("chrome://global/content/filepicker.properties"); |
|
51 |
|
52 /* attributes */ |
|
53 this.mDefaultString = ""; |
|
54 this.mFilterIndex = 0; |
|
55 this.mFilterTitles = new Array(); |
|
56 this.mFilters = new Array(); |
|
57 this.mDisplayDirectory = null; |
|
58 if (lastDirectory) { |
|
59 try { |
|
60 var dir = Components.classes[LOCAL_FILE_CONTRACTID].createInstance(nsILocalFile); |
|
61 dir.initWithPath(lastDirectory); |
|
62 this.mDisplayDirectory = dir; |
|
63 } catch (e) {} |
|
64 } |
|
65 } |
|
66 |
|
67 nsFilePicker.prototype = { |
|
68 classID: Components.ID("{54ae32f8-1dd2-11b2-a209-df7c505370f8}"), |
|
69 |
|
70 QueryInterface: function(iid) { |
|
71 if (iid.equals(nsIFilePicker) || |
|
72 iid.equals(nsISupports)) |
|
73 return this; |
|
74 |
|
75 throw Components.results.NS_ERROR_NO_INTERFACE; |
|
76 }, |
|
77 |
|
78 |
|
79 /* attribute nsILocalFile displayDirectory; */ |
|
80 set displayDirectory(a) { |
|
81 this.mDisplayDirectory = a && |
|
82 a.clone().QueryInterface(nsILocalFile); |
|
83 }, |
|
84 get displayDirectory() { |
|
85 return this.mDisplayDirectory && |
|
86 this.mDisplayDirectory.clone() |
|
87 .QueryInterface(nsILocalFile); |
|
88 }, |
|
89 |
|
90 /* readonly attribute nsILocalFile file; */ |
|
91 get file() { return this.mFilesEnumerator.mFiles[0]; }, |
|
92 |
|
93 /* readonly attribute nsISimpleEnumerator files; */ |
|
94 get files() { return this.mFilesEnumerator; }, |
|
95 |
|
96 /* readonly attribute nsIDOMFile domfile; */ |
|
97 get domfile() { |
|
98 let enumerator = this.domfiles; |
|
99 return enumerator ? enumerator.mFiles[0] : null; |
|
100 }, |
|
101 |
|
102 /* readonly attribute nsISimpleEnumerator domfiles; */ |
|
103 get domfiles() { |
|
104 if (!this.mFilesEnumerator) { |
|
105 return null; |
|
106 } |
|
107 |
|
108 if (!this.mDOMFilesEnumerator) { |
|
109 this.mDOMFilesEnumerator = { |
|
110 QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISimpleEnumerator]), |
|
111 |
|
112 mFiles: [], |
|
113 mIndex: 0, |
|
114 |
|
115 hasMoreElements: function() { |
|
116 return (this.mIndex < this.mFiles.length); |
|
117 }, |
|
118 |
|
119 getNext: function() { |
|
120 if (this.mIndex >= this.mFiles.length) { |
|
121 throw Components.results.NS_ERROR_FAILURE; |
|
122 } |
|
123 return this.mFiles[this.mIndex++]; |
|
124 } |
|
125 }; |
|
126 |
|
127 var utils = this.mParentWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor) |
|
128 .getInterface(Components.interfaces.nsIDOMWindowUtils); |
|
129 |
|
130 for (var i = 0; i < this.mFilesEnumerator.mFiles.length; ++i) { |
|
131 var file = utils.wrapDOMFile(this.mFilesEnumerator.mFiles[i]); |
|
132 this.mDOMFilesEnumerator.mFiles.push(file); |
|
133 } |
|
134 } |
|
135 |
|
136 return this.mDOMFilesEnumerator; |
|
137 }, |
|
138 |
|
139 /* readonly attribute nsIURI fileURL; */ |
|
140 get fileURL() { |
|
141 if (this.mFileURL) |
|
142 return this.mFileURL; |
|
143 |
|
144 if (!this.mFilesEnumerator) |
|
145 return null; |
|
146 |
|
147 var ioService = Components.classes["@mozilla.org/network/io-service;1"] |
|
148 .getService(Components.interfaces.nsIIOService); |
|
149 |
|
150 return this.mFileURL = ioService.newFileURI(this.file); |
|
151 }, |
|
152 |
|
153 /* attribute wstring defaultString; */ |
|
154 set defaultString(a) { this.mDefaultString = a; }, |
|
155 get defaultString() { return this.mDefaultString; }, |
|
156 |
|
157 /* attribute wstring defaultExtension */ |
|
158 set defaultExtension(ext) { }, |
|
159 get defaultExtension() { return ""; }, |
|
160 |
|
161 /* attribute long filterIndex; */ |
|
162 set filterIndex(a) { this.mFilterIndex = a; }, |
|
163 get filterIndex() { return this.mFilterIndex; }, |
|
164 |
|
165 /* attribute boolean addToRecentDocs; */ |
|
166 set addToRecentDocs(a) {}, |
|
167 get addToRecentDocs() { return false; }, |
|
168 |
|
169 /* readonly attribute short mode; */ |
|
170 get mode() { return this.mMode; }, |
|
171 |
|
172 /* members */ |
|
173 mFilesEnumerator: undefined, |
|
174 mDOMFilesEnumerator: undefined, |
|
175 mParentWindow: null, |
|
176 |
|
177 /* methods */ |
|
178 init: function(parent, title, mode) { |
|
179 this.mParentWindow = parent; |
|
180 this.mTitle = title; |
|
181 this.mMode = mode; |
|
182 }, |
|
183 |
|
184 appendFilters: function(filterMask) { |
|
185 if (filterMask & nsIFilePicker.filterHTML) { |
|
186 this.appendFilter(titleBundle.GetStringFromName("htmlTitle"), |
|
187 filterBundle.GetStringFromName("htmlFilter")); |
|
188 } |
|
189 if (filterMask & nsIFilePicker.filterText) { |
|
190 this.appendFilter(titleBundle.GetStringFromName("textTitle"), |
|
191 filterBundle.GetStringFromName("textFilter")); |
|
192 } |
|
193 if (filterMask & nsIFilePicker.filterImages) { |
|
194 this.appendFilter(titleBundle.GetStringFromName("imageTitle"), |
|
195 filterBundle.GetStringFromName("imageFilter")); |
|
196 } |
|
197 if (filterMask & nsIFilePicker.filterXML) { |
|
198 this.appendFilter(titleBundle.GetStringFromName("xmlTitle"), |
|
199 filterBundle.GetStringFromName("xmlFilter")); |
|
200 } |
|
201 if (filterMask & nsIFilePicker.filterXUL) { |
|
202 this.appendFilter(titleBundle.GetStringFromName("xulTitle"), |
|
203 filterBundle.GetStringFromName("xulFilter")); |
|
204 } |
|
205 this.mAllowURLs = !!(filterMask & nsIFilePicker.filterAllowURLs); |
|
206 if (filterMask & nsIFilePicker.filterApps) { |
|
207 // We use "..apps" as a special filter for executable files |
|
208 this.appendFilter(titleBundle.GetStringFromName("appsTitle"), |
|
209 "..apps"); |
|
210 } |
|
211 if (filterMask & nsIFilePicker.filterAudio) { |
|
212 this.appendFilter(titleBundle.GetStringFromName("audioTitle"), |
|
213 filterBundle.GetStringFromName("audioFilter")); |
|
214 } |
|
215 if (filterMask & nsIFilePicker.filterVideo) { |
|
216 this.appendFilter(titleBundle.GetStringFromName("videoTitle"), |
|
217 filterBundle.GetStringFromName("videoFilter")); |
|
218 } |
|
219 if (filterMask & nsIFilePicker.filterAll) { |
|
220 this.appendFilter(titleBundle.GetStringFromName("allTitle"), |
|
221 filterBundle.GetStringFromName("allFilter")); |
|
222 } |
|
223 }, |
|
224 |
|
225 appendFilter: function(title, extensions) { |
|
226 this.mFilterTitles.push(title); |
|
227 this.mFilters.push(extensions); |
|
228 }, |
|
229 |
|
230 open: function(aFilePickerShownCallback) { |
|
231 var tm = Components.classes["@mozilla.org/thread-manager;1"] |
|
232 .getService(Components.interfaces.nsIThreadManager); |
|
233 tm.mainThread.dispatch(function() { |
|
234 let result = Components.interfaces.nsIFilePicker.returnCancel; |
|
235 try { |
|
236 result = this.show(); |
|
237 } catch(ex) { |
|
238 } |
|
239 if (aFilePickerShownCallback) { |
|
240 aFilePickerShownCallback.done(result); |
|
241 } |
|
242 }.bind(this), Components.interfaces.nsIThread.DISPATCH_NORMAL); |
|
243 }, |
|
244 |
|
245 show: function() { |
|
246 var o = new Object(); |
|
247 o.title = this.mTitle; |
|
248 o.mode = this.mMode; |
|
249 o.displayDirectory = this.mDisplayDirectory; |
|
250 o.defaultString = this.mDefaultString; |
|
251 o.filterIndex = this.mFilterIndex; |
|
252 o.filters = new Object(); |
|
253 o.filters.titles = this.mFilterTitles; |
|
254 o.filters.types = this.mFilters; |
|
255 o.allowURLs = this.mAllowURLs; |
|
256 o.retvals = new Object(); |
|
257 |
|
258 var parent; |
|
259 if (this.mParentWindow) { |
|
260 parent = this.mParentWindow; |
|
261 } else if (typeof(window) == "object" && window != null) { |
|
262 parent = window; |
|
263 } else { |
|
264 try { |
|
265 var appShellService = Components.classes[APPSHELL_SERV_CONTRACTID].getService(nsIAppShellService); |
|
266 parent = appShellService.hiddenDOMWindow; |
|
267 } catch(ex) { |
|
268 debug("Can't get parent. xpconnect hates me so we can't get one from the appShellService.\n"); |
|
269 debug(ex + "\n"); |
|
270 } |
|
271 } |
|
272 |
|
273 var parentWin = null; |
|
274 try { |
|
275 parentWin = parent.QueryInterface(nsIInterfaceRequestor) |
|
276 .getInterface(nsIWebNavigation) |
|
277 .QueryInterface(nsIDocShellTreeItem) |
|
278 .treeOwner |
|
279 .QueryInterface(nsIInterfaceRequestor) |
|
280 .getInterface(nsIBaseWindow); |
|
281 } catch(ex) { |
|
282 dump("file picker couldn't get base window\n"+ex+"\n"); |
|
283 } |
|
284 try { |
|
285 parent.openDialog("chrome://global/content/filepicker.xul", |
|
286 "", |
|
287 "chrome,modal,titlebar,resizable=yes,dependent=yes", |
|
288 o); |
|
289 |
|
290 this.mFilterIndex = o.retvals.filterIndex; |
|
291 this.mFilesEnumerator = o.retvals.files; |
|
292 this.mFileURL = o.retvals.fileURL; |
|
293 lastDirectory = o.retvals.directory; |
|
294 return o.retvals.buttonStatus; |
|
295 } catch(ex) { dump("unable to open file picker\n" + ex + "\n"); } |
|
296 |
|
297 return null; |
|
298 } |
|
299 } |
|
300 |
|
301 if (DEBUG) |
|
302 debug = function (s) { dump("-*- filepicker: " + s + "\n"); }; |
|
303 else |
|
304 debug = function (s) {}; |
|
305 |
|
306 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsFilePicker]); |
|
307 |
|
308 /* crap from strres.js that I want to use for string bundles since I can't include another .js file.... */ |
|
309 |
|
310 var strBundleService = null; |
|
311 |
|
312 function srGetStrBundle(path) |
|
313 { |
|
314 var strBundle = null; |
|
315 |
|
316 if (!strBundleService) { |
|
317 try { |
|
318 strBundleService = Components.classes[STRBUNDLE_SERV_CONTRACTID].getService(nsIStringBundleService); |
|
319 } catch (ex) { |
|
320 dump("\n--** strBundleService createInstance failed **--\n"); |
|
321 return null; |
|
322 } |
|
323 } |
|
324 |
|
325 strBundle = strBundleService.createBundle(path); |
|
326 if (!strBundle) { |
|
327 dump("\n--** strBundle createInstance failed **--\n"); |
|
328 } |
|
329 return strBundle; |
|
330 } |
|
331 |