|
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 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; |
|
21 |
|
22 // FIXME: improve this list of filters. |
|
23 const IMAGE_FILTERS = ['image/gif', 'image/jpeg', 'image/pjpeg', |
|
24 'image/png', 'image/svg+xml', 'image/tiff', |
|
25 'image/vnd.microsoft.icon']; |
|
26 const VIDEO_FILTERS = ['video/mpeg', 'video/mp4', 'video/ogg', |
|
27 'video/quicktime', 'video/webm', 'video/x-matroska', |
|
28 'video/x-ms-wmv', 'video/x-flv']; |
|
29 const AUDIO_FILTERS = ['audio/basic', 'audio/L24', 'audio/mp4', |
|
30 'audio/mpeg', 'audio/ogg', 'audio/vorbis', |
|
31 'audio/vnd.rn-realaudio', 'audio/vnd.wave', |
|
32 'audio/webm']; |
|
33 |
|
34 Cu.import('resource://gre/modules/XPCOMUtils.jsm'); |
|
35 Cu.import("resource://gre/modules/osfile.jsm"); |
|
36 |
|
37 XPCOMUtils.defineLazyServiceGetter(this, 'cpmm', |
|
38 '@mozilla.org/childprocessmessagemanager;1', |
|
39 'nsIMessageSender'); |
|
40 |
|
41 function FilePicker() { |
|
42 } |
|
43 |
|
44 FilePicker.prototype = { |
|
45 classID: Components.ID('{436ff8f9-0acc-4b11-8ec7-e293efba3141}'), |
|
46 QueryInterface: XPCOMUtils.generateQI([Ci.nsIFilePicker]), |
|
47 |
|
48 /* members */ |
|
49 |
|
50 mParent: undefined, |
|
51 mExtraProps: undefined, |
|
52 mFilterTypes: undefined, |
|
53 mFileEnumerator: undefined, |
|
54 mFilePickerShownCallback: undefined, |
|
55 |
|
56 /* methods */ |
|
57 |
|
58 init: function(parent, title, mode) { |
|
59 this.mParent = parent; |
|
60 this.mExtraProps = {}; |
|
61 this.mFilterTypes = []; |
|
62 this.mMode = mode; |
|
63 |
|
64 if (mode != Ci.nsIFilePicker.modeOpen && |
|
65 mode != Ci.nsIFilePicker.modeOpenMultiple) { |
|
66 throw Cr.NS_ERROR_NOT_IMPLEMENTED; |
|
67 } |
|
68 }, |
|
69 |
|
70 /* readonly attribute nsILocalFile file - not implemented; */ |
|
71 /* readonly attribute nsISimpleEnumerator files - not implemented; */ |
|
72 /* readonly attribute nsIURI fileURL - not implemented; */ |
|
73 |
|
74 get domfiles() { |
|
75 return this.mFilesEnumerator; |
|
76 }, |
|
77 |
|
78 get domfile() { |
|
79 return this.mFilesEnumerator ? this.mFilesEnumerator.mFiles[0] : null; |
|
80 }, |
|
81 |
|
82 get mode() { |
|
83 return this.mMode; |
|
84 }, |
|
85 |
|
86 appendFilters: function(filterMask) { |
|
87 // Ci.nsIFilePicker.filterHTML is not supported |
|
88 // Ci.nsIFilePicker.filterText is not supported |
|
89 |
|
90 if (filterMask & Ci.nsIFilePicker.filterImages) { |
|
91 this.mFilterTypes = this.mFilterTypes.concat(IMAGE_FILTERS); |
|
92 // This property is needed for the gallery app pick activity. |
|
93 this.mExtraProps['nocrop'] = true; |
|
94 } |
|
95 |
|
96 // Ci.nsIFilePicker.filterXML is not supported |
|
97 // Ci.nsIFilePicker.filterXUL is not supported |
|
98 // Ci.nsIFilePicker.filterApps is not supported |
|
99 // Ci.nsIFilePicker.filterAllowURLs is not supported |
|
100 |
|
101 if (filterMask & Ci.nsIFilePicker.filterVideo) { |
|
102 this.mFilterTypes = this.mFilterTypes.concat(VIDEO_FILTERS); |
|
103 } |
|
104 |
|
105 if (filterMask & Ci.nsIFilePicker.filterAudio) { |
|
106 this.mFilterTypes = this.mFilterTypes.concat(AUDIO_FILTERS); |
|
107 } |
|
108 |
|
109 if (filterMask & Ci.nsIFilePicker.filterAll) { |
|
110 // This property is needed for the gallery app pick activity. |
|
111 this.mExtraProps['nocrop'] = true; |
|
112 } |
|
113 }, |
|
114 |
|
115 appendFilter: function(title, extensions) { |
|
116 // pick activity doesn't support extensions |
|
117 }, |
|
118 |
|
119 open: function(aFilePickerShownCallback) { |
|
120 this.mFilePickerShownCallback = aFilePickerShownCallback; |
|
121 |
|
122 cpmm.addMessageListener('file-picked', this); |
|
123 |
|
124 let detail = {}; |
|
125 if (this.mFilterTypes) { |
|
126 detail.type = this.mFilterTypes; |
|
127 } |
|
128 |
|
129 for (let prop in this.mExtraProps) { |
|
130 if (!(prop in detail)) { |
|
131 detail[prop] = this.mExtraProps[prop]; |
|
132 } |
|
133 } |
|
134 |
|
135 cpmm.sendAsyncMessage('file-picker', detail); |
|
136 }, |
|
137 |
|
138 fireSuccess: function(file) { |
|
139 this.mFilesEnumerator = { |
|
140 QueryInterface: XPCOMUtils.generateQI([Ci.nsISimpleEnumerator]), |
|
141 |
|
142 mFiles: [file], |
|
143 mIndex: 0, |
|
144 |
|
145 hasMoreElements: function() { |
|
146 return (this.mIndex < this.mFiles.length); |
|
147 }, |
|
148 |
|
149 getNext: function() { |
|
150 if (this.mIndex >= this.mFiles.length) { |
|
151 throw Components.results.NS_ERROR_FAILURE; |
|
152 } |
|
153 return this.mFiles[this.mIndex++]; |
|
154 } |
|
155 }; |
|
156 |
|
157 if (this.mFilePickerShownCallback) { |
|
158 this.mFilePickerShownCallback.done(Ci.nsIFilePicker.returnOK); |
|
159 this.mFilePickerShownCallback = null; |
|
160 } |
|
161 }, |
|
162 |
|
163 fireError: function() { |
|
164 if (this.mFilePickerShownCallback) { |
|
165 this.mFilePickerShownCallback.done(Ci.nsIFilePicker.returnCancel); |
|
166 this.mFilePickerShownCallback = null; |
|
167 } |
|
168 }, |
|
169 |
|
170 receiveMessage: function(message) { |
|
171 if (message.name !== 'file-picked') { |
|
172 return; |
|
173 } |
|
174 |
|
175 cpmm.removeMessageListener('file-picked', this); |
|
176 |
|
177 let data = message.data; |
|
178 if (!data.success || !data.result.blob) { |
|
179 this.fireError(); |
|
180 return; |
|
181 } |
|
182 |
|
183 // The name to be shown can be part of the message, or can be taken from |
|
184 // the DOMFile (if the blob is a DOMFile). |
|
185 let name = data.result.name; |
|
186 if (!name && |
|
187 (data.result.blob instanceof this.mParent.File) && |
|
188 data.result.blob.name) { |
|
189 name = data.result.blob.name; |
|
190 } |
|
191 |
|
192 // Let's try to remove the full path and take just the filename. |
|
193 if (name) { |
|
194 let names = OS.Path.split(name); |
|
195 name = names.components[names.components.length - 1]; |
|
196 } |
|
197 |
|
198 // the fallback is a filename composed by 'blob' + extension. |
|
199 if (!name) { |
|
200 name = 'blob'; |
|
201 if (data.result.blob.type) { |
|
202 let mimeSvc = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService); |
|
203 let mimeInfo = mimeSvc.getFromTypeAndExtension(data.result.blob.type, ''); |
|
204 if (mimeInfo) { |
|
205 name += '.' + mimeInfo.primaryExtension; |
|
206 } |
|
207 } |
|
208 } |
|
209 |
|
210 let file = new this.mParent.File(data.result.blob, |
|
211 { name: name, |
|
212 type: data.result.blob.type }); |
|
213 |
|
214 if (file) { |
|
215 this.fireSuccess(file); |
|
216 } else { |
|
217 this.fireError(); |
|
218 } |
|
219 } |
|
220 }; |
|
221 |
|
222 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([FilePicker]); |