!import
1 //@line 44 "/home/visbrero/mnt/roisin/rev_control/hg/mozilla/toolkit/components/filepicker/content/filepicker.js"
2
3 const nsIFilePicker = Components.interfaces.nsIFilePicker;
4 const nsIProperties = Components.interfaces.nsIProperties;
5 const NS_DIRECTORYSERVICE_CONTRACTID = "@mozilla.org/file/directory_service;1";
6 const NS_IOSERVICE_CONTRACTID = "@mozilla.org/network/io-service;1";
7 const nsITreeBoxObject = Components.interfaces.nsITreeBoxObject;
8 const nsIFileView = Components.interfaces.nsIFileView;
9 const NS_FILEVIEW_CONTRACTID = "@mozilla.org/filepicker/fileview;1";
10 const nsITreeView = Components.interfaces.nsITreeView;
11 const nsILocalFile = Components.interfaces.nsILocalFile;
12 const nsIFile = Components.interfaces.nsIFile;
13 const NS_LOCAL_FILE_CONTRACTID = "@mozilla.org/file/local;1";
14 const NS_PROMPTSERVICE_CONTRACTID = "@mozilla.org/embedcomp/prompt-service;1";
15
16 var sfile = Components.classes[NS_LOCAL_FILE_CONTRACTID].createInstance(nsILocalFile);
17 var retvals;
18 var filePickerMode;
19 var homeDir;
20 var treeView;
21 var allowURLs;
22
23 var textInput;
24 var okButton;
25
26 var gFilePickerBundle;
27
28 // name of new directory entered by the user to be remembered
29 // for next call of newDir() in case something goes wrong with creation
30 var gNewDirName = { value: "" };
31
filepickerLoad
32 function filepickerLoad() {
33 gFilePickerBundle = document.getElementById("bundle_filepicker");
34
35 textInput = document.getElementById("textInput");
36 okButton = document.documentElement.getButton("accept");
37 treeView = Components.classes[NS_FILEVIEW_CONTRACTID].createInstance(nsIFileView);
38
39 if (window.arguments) {
40 var o = window.arguments[0];
41 retvals = o.retvals; /* set this to a global var so we can set return values */
42 const title = o.title;
43 filePickerMode = o.mode;
44 if (o.displayDirectory) {
45 const directory = o.displayDirectory.path;
46 }
47
48 const initialText = o.defaultString;
49 const filterTitles = o.filters.titles;
50 const filterTypes = o.filters.types;
51 const numFilters = filterTitles.length;
52
53 document.title = title;
54 allowURLs = o.allowURLs;
55
56 if (initialText) {
57 textInput.value = initialText;
58 }
59 }
60
61 if (filePickerMode != nsIFilePicker.modeOpen && filePickerMode != nsIFilePicker.modeOpenMultiple) {
62 var newDirButton = document.getElementById("newDirButton");
63 newDirButton.removeAttribute("hidden");
64 }
65
66 if (filePickerMode == nsIFilePicker.modeGetFolder) {
67 var textInputLabel = document.getElementById("textInputLabel");
68 textInputLabel.value = gFilePickerBundle.getString("dirTextInputLabel");
69 textInputLabel.accessKey = gFilePickerBundle.getString("dirTextInputAccesskey");
70 }
71
72 if ((filePickerMode == nsIFilePicker.modeOpen) ||
73 (filePickerMode == nsIFilePicker.modeOpenMultiple) ||
74 (filePickerMode == nsIFilePicker.modeSave)) {
75
76 /* build filter popup */
77 var filterPopup = document.createElement("menupopup");
78
79 for (var i = 0; i < numFilters; i++) {
80 var menuItem = document.createElement("menuitem");
81 if (filterTypes[i] == "..apps")
82 menuItem.setAttribute("label", filterTitles[i]);
83 else
84 menuItem.setAttribute("label", filterTitles[i] + " (" + filterTypes[i] + ")");
85 menuItem.setAttribute("filters", filterTypes[i]);
86 filterPopup.appendChild(menuItem);
87 }
88
89 var filterMenuList = document.getElementById("filterMenuList");
90 filterMenuList.appendChild(filterPopup);
91 if (numFilters > 0)
92 filterMenuList.selectedIndex = 0;
93 var filterBox = document.getElementById("filterBox");
94 filterBox.removeAttribute("hidden");
95
96 filterMenuList.selectedIndex = o.filterIndex;
97
98 treeView.setFilter(filterTypes[o.filterIndex]);
99
100 } else if (filePickerMode == nsIFilePicker.modeGetFolder) {
101 treeView.showOnlyDirectories = true;
102 }
103
104 // The dialog defaults to an "open" icon, change it to "save" if applicable
105 if (filePickerMode == nsIFilePicker.modeSave)
106 okButton.setAttribute("icon", "save");
107
108 // start out with a filename sort
109 handleColumnClick("FilenameColumn");
110
111 try {
112 setOKAction();
113 } catch (exception) {
114 // keep it set to "OK"
115 }
116
117 // setup the dialogOverlay.xul button handlers
118 retvals.buttonStatus = nsIFilePicker.returnCancel;
119
120 var tree = document.getElementById("directoryTree");
121 if (filePickerMode == nsIFilePicker.modeOpenMultiple)
122 tree.removeAttribute("seltype");
123
124 tree.treeBoxObject.view = treeView;
125
126 // Start out with the ok button disabled since nothing will be
127 // selected and nothing will be in the text field.
128 okButton.disabled = filePickerMode != nsIFilePicker.modeGetFolder;
129
130 // This allows the window to show onscreen before we begin
131 // loading the file list
132
133 setTimeout(setInitialDirectory, 0, directory);
134 }
135
setInitialDirectory
136 function setInitialDirectory(directory)
137 {
138 // Start in the user's home directory
139 var dirService = Components.classes[NS_DIRECTORYSERVICE_CONTRACTID]
140 .getService(nsIProperties);
141 homeDir = dirService.get("Home", Components.interfaces.nsIFile);
142
143 if (directory) {
144 sfile.initWithPath(directory);
145 if (!sfile.exists() || !sfile.isDirectory())
146 directory = false;
147 }
148 if (!directory) {
149 sfile.initWithPath(homeDir.path);
150 }
151
152 gotoDirectory(sfile);
153 }
154
onFilterChanged
155 function onFilterChanged(target)
156 {
157 // Do this on a timeout callback so the filter list can roll up
158 // and we don't keep the mouse grabbed while we are refiltering.
159
160 setTimeout(changeFilter, 0, target.getAttribute("filters"));
161 }
162
changeFilter
163 function changeFilter(filterTypes)
164 {
165 window.setCursor("wait");
166 treeView.setFilter(filterTypes);
167 window.setCursor("auto");
168 }
169
showErrorDialog
170 function showErrorDialog(titleStrName, messageStrName, file)
171 {
172 var errorTitle =
173 gFilePickerBundle.getFormattedString(titleStrName, [file.path]);
174 var errorMessage =
175 gFilePickerBundle.getFormattedString(messageStrName, [file.path]);
176 var promptService =
177 Components.classes[NS_PROMPTSERVICE_CONTRACTID].getService(Components.interfaces.nsIPromptService);
178
179 promptService.alert(window, errorTitle, errorMessage);
180 }
181
openOnOK
182 function openOnOK()
183 {
184 var dir = treeView.selectedFiles.queryElementAt(0, nsIFile);
185 if (dir)
186 gotoDirectory(dir);
187
188 return false;
189 }
190
selectOnOK
191 function selectOnOK()
192 {
193 var errorTitle, errorMessage, promptService;
194 var ret = nsIFilePicker.returnOK;
195
196 var isDir = false;
197 var isFile = false;
198
199 retvals.filterIndex = document.getElementById("filterMenuList").selectedIndex;
200 retvals.fileURL = null;
201
202 if (allowURLs) {
203 try {
204 var ios = Components.classes[NS_IOSERVICE_CONTRACTID].getService(Components.interfaces.nsIIOService);
205 retvals.fileURL = ios.newURI(textInput.value, null, null);
206 var fileList = [];
207 if (retvals.fileURL instanceof Components.interfaces.nsIFileURL)
208 fileList.push(retvals.fileURL.file);
209 gFilesEnumerator.mFiles = fileList;
210 retvals.files = gFilesEnumerator;
211 retvals.buttonStatus = ret;
212
213 return true;
214 } catch (e) {
215 }
216 }
217
218 var fileList = processPath(textInput.value);
219 if (!fileList) {
220 // generic error message, should probably never happen
221 showErrorDialog("errorPathProblemTitle",
222 "errorPathProblemMessage",
223 textInput.value);
224 return false;
225 }
226
227 var curFileIndex;
228 for (curFileIndex = 0; curFileIndex < fileList.length &&
229 ret != nsIFilePicker.returnCancel; ++curFileIndex) {
230 var file = fileList[curFileIndex].QueryInterface(nsIFile);
231
232 // try to normalize - if this fails we will ignore the error
233 // because we will notice the
234 // error later and show a fitting error alert.
235 try{
236 file.normalize();
237 } catch(e) {
238 //promptService.alert(window, "Problem", "normalize failed, continuing");
239 }
240
241 var fileExists = file.exists();
242
243 if (!fileExists && (filePickerMode == nsIFilePicker.modeOpen ||
244 filePickerMode == nsIFilePicker.modeOpenMultiple)) {
245 showErrorDialog("errorOpenFileDoesntExistTitle",
246 "errorOpenFileDoesntExistMessage",
247 file);
248 return false;
249 }
250
251 if (!fileExists && filePickerMode == nsIFilePicker.modeGetFolder) {
252 showErrorDialog("errorDirDoesntExistTitle",
253 "errorDirDoesntExistMessage",
254 file);
255 return false;
256 }
257
258 if (fileExists) {
259 isDir = file.isDirectory();
260 isFile = file.isFile();
261 }
262
263 switch(filePickerMode) {
264 case nsIFilePicker.modeOpen:
265 case nsIFilePicker.modeOpenMultiple:
266 if (isFile) {
267 if (file.isReadable()) {
268 retvals.directory = file.parent.path;
269 } else {
270 showErrorDialog("errorOpeningFileTitle",
271 "openWithoutPermissionMessage_file",
272 file);
273 ret = nsIFilePicker.returnCancel;
274 }
275 } else if (isDir) {
276 if (!sfile.equals(file)) {
277 gotoDirectory(file);
278 }
279 textInput.value = "";
280 doEnabling();
281 ret = nsIFilePicker.returnCancel;
282 }
283 break;
284 case nsIFilePicker.modeSave:
285 if (isFile) { // can only be true if file.exists()
286 if (!file.isWritable()) {
287 showErrorDialog("errorSavingFileTitle",
288 "saveWithoutPermissionMessage_file",
289 file);
290 ret = nsIFilePicker.returnCancel;
291 } else {
292 // we need to pop up a dialog asking if you want to save
293 var confirmTitle = gFilePickerBundle.getString("confirmTitle");
294 var message =
295 gFilePickerBundle.getFormattedString("confirmFileReplacing",
296 [file.path]);
297
298 promptService = Components.classes[NS_PROMPTSERVICE_CONTRACTID].getService(Components.interfaces.nsIPromptService);
299 var rv = promptService.confirm(window, confirmTitle, message);
300 if (rv) {
301 ret = nsIFilePicker.returnReplace;
302 retvals.directory = file.parent.path;
303 } else {
304 ret = nsIFilePicker.returnCancel;
305 }
306 }
307 } else if (isDir) {
308 if (!sfile.equals(file)) {
309 gotoDirectory(file);
310 }
311 textInput.value = "";
312 doEnabling();
313 ret = nsIFilePicker.returnCancel;
314 } else {
315 var parent = file.parent;
316 if (parent.exists() && parent.isDirectory() && parent.isWritable()) {
317 retvals.directory = parent.path;
318 } else {
319 var oldParent = parent;
320 while (!parent.exists()) {
321 oldParent = parent;
322 parent = parent.parent;
323 }
324 errorTitle =
325 gFilePickerBundle.getFormattedString("errorSavingFileTitle",
326 [file.path]);
327 if (parent.isFile()) {
328 errorMessage =
329 gFilePickerBundle.getFormattedString("saveParentIsFileMessage",
330 [parent.path, file.path]);
331 } else {
332 errorMessage =
333 gFilePickerBundle.getFormattedString("saveParentDoesntExistMessage",
334 [oldParent.path, file.path]);
335 }
336 if (!parent.isWritable()) {
337 errorMessage =
338 gFilePickerBundle.getFormattedString("saveWithoutPermissionMessage_dir", [parent.path]);
339 }
340 promptService = Components.classes[NS_PROMPTSERVICE_CONTRACTID].getService(Components.interfaces.nsIPromptService);
341 promptService.alert(window, errorTitle, errorMessage);
342 ret = nsIFilePicker.returnCancel;
343 }
344 }
345 break;
346 case nsIFilePicker.modeGetFolder:
347 if (isDir) {
348 retvals.directory = file.parent.path;
349 } else { // if nothing selected, the current directory will be fine
350 retvals.directory = sfile.path;
351 }
352 break;
353 }
354 }
355
356 gFilesEnumerator.mFiles = fileList;
357
358 retvals.files = gFilesEnumerator;
359 retvals.buttonStatus = ret;
360
361 return (ret != nsIFilePicker.returnCancel);
362 }
363
364 var gFilesEnumerator = {
365 mFiles: null,
366 mIndex: 0,
367
hasMoreElements
368 hasMoreElements: function()
369 {
370 return (this.mIndex < this.mFiles.length);
371 },
getNext
372 getNext: function()
373 {
374 if (this.mIndex >= this.mFiles.length)
375 throw Components.results.NS_ERROR_FAILURE;
376 return this.mFiles[this.mIndex++];
377 }
378 };
379
onCancel
380 function onCancel()
381 {
382 // Close the window.
383 retvals.buttonStatus = nsIFilePicker.returnCancel;
384 retvals.file = null;
385 retvals.files = null;
386 return true;
387 }
388
onDblClick
389 function onDblClick(e) {
390 // we only care about button 0 (left click) events
391 if (e.button != 0) return;
392
393 var t = e.originalTarget;
394 if (t.localName != "treechildren")
395 return;
396
397 openSelectedFile();
398 }
399
openSelectedFile
400 function openSelectedFile() {
401 var fileList = treeView.selectedFiles;
402 if (fileList.length == 0)
403 return;
404
405 var file = fileList.queryElementAt(0, nsIFile);
406 if (file.isDirectory())
407 gotoDirectory(file);
408 else if (file.isFile())
409 document.documentElement.acceptDialog();
410 }
411
onClick
412 function onClick(e) {
413 var t = e.originalTarget;
414 if (t.localName == "treecol")
415 handleColumnClick(t.id);
416 }
417
convertColumnIDtoSortType
418 function convertColumnIDtoSortType(columnID) {
419 var sortKey;
420
421 switch (columnID) {
422 case "FilenameColumn":
423 sortKey = nsIFileView.sortName;
424 break;
425 case "FileSizeColumn":
426 sortKey = nsIFileView.sortSize;
427 break;
428 case "LastModifiedColumn":
429 sortKey = nsIFileView.sortDate;
430 break;
431 default:
432 dump("unsupported sort column: " + columnID + "\n");
433 sortKey = 0;
434 break;
435 }
436
437 return sortKey;
438 }
439
handleColumnClick
440 function handleColumnClick(columnID) {
441 var sortType = convertColumnIDtoSortType(columnID);
442 var sortOrder = (treeView.sortType == sortType) ? !treeView.reverseSort : false;
443 treeView.sort(sortType, sortOrder);
444
445 // set the sort indicator on the column we are sorted by
446 var sortedColumn = document.getElementById(columnID);
447 if (treeView.reverseSort) {
448 sortedColumn.setAttribute("sortDirection", "descending");
449 } else {
450 sortedColumn.setAttribute("sortDirection", "ascending");
451 }
452
453 // remove the sort indicator from the rest of the columns
454 var currCol = sortedColumn.parentNode.firstChild;
455 while (currCol) {
456 if (currCol != sortedColumn && currCol.localName == "treecol")
457 currCol.removeAttribute("sortDirection");
458 currCol = currCol.nextSibling;
459 }
460 }
461
onKeypress
462 function onKeypress(e) {
463 if (e.keyCode == 8) /* backspace */
464 goUp();
465
466 /* enter is handled by the ondialogaccept handler */
467 }
468
doEnabling
469 function doEnabling() {
470 if (filePickerMode != nsIFilePicker.modeGetFolder)
471 // Maybe add check if textInput.value would resolve to an existing
472 // file or directory in .modeOpen. Too costly I think.
473 okButton.disabled = (textInput.value == "")
474 }
475
onTreeFocus
476 function onTreeFocus(event) {
477 // Reset the button label and enabled/disabled state.
478 onFileSelected(treeView.selectedFiles);
479 }
480
setOKAction
481 function setOKAction(file) {
482 var buttonLabel;
483 var buttonIcon = "open"; // used in all but one case
484
485 if (file && file.isDirectory()) {
486 document.documentElement.setAttribute("ondialogaccept", "return openOnOK();");
487 buttonLabel = gFilePickerBundle.getString("openButtonLabel");
488 }
489 else {
490 document.documentElement.setAttribute("ondialogaccept", "return selectOnOK();");
491 switch(filePickerMode) {
492 case nsIFilePicker.modeGetFolder:
493 buttonLabel = gFilePickerBundle.getString("selectFolderButtonLabel");
494 break;
495 case nsIFilePicker.modeOpen:
496 case nsIFilePicker.modeOpenMultiple:
497 buttonLabel = gFilePickerBundle.getString("openButtonLabel");
498 break;
499 case nsIFilePicker.modeSave:
500 buttonLabel = gFilePickerBundle.getString("saveButtonLabel");
501 buttonIcon = "save";
502 break;
503 }
504 }
505 okButton.setAttribute("label", buttonLabel);
506 okButton.setAttribute("icon", buttonIcon);
507 }
508
onSelect
509 function onSelect(event) {
510 onFileSelected(treeView.selectedFiles);
511 }
512
onFileSelected
513 function onFileSelected(/* nsIArray */ selectedFileList) {
514 var validFileSelected = false;
515 var invalidSelection = false;
516 var file;
517 var fileCount = selectedFileList.length;
518
519 for (var index = 0; index < fileCount; ++index) {
520 file = selectedFileList.queryElementAt(index, nsIFile);
521 if (file) {
522 var path = file.leafName;
523
524 if (path) {
525 var isDir = file.isDirectory();
526 if ((filePickerMode == nsIFilePicker.modeGetFolder) || !isDir) {
527 if (!validFileSelected)
528 textInput.value = "";
529 addToTextFieldValue(path);
530 }
531
532 if (isDir && fileCount > 1) {
533 // The user has selected multiple items, and one of them is
534 // a directory. This is not a valid state, so we'll disable
535 // the ok button.
536 invalidSelection = true;
537 }
538
539 validFileSelected = true;
540 }
541 }
542 }
543
544 if (validFileSelected) {
545 setOKAction(file);
546 okButton.disabled = invalidSelection;
547 } else if (filePickerMode != nsIFilePicker.modeGetFolder)
548 okButton.disabled = (textInput.value == "");
549 }
550
addToTextFieldValue
551 function addToTextFieldValue(path)
552 {
553 var newValue = "";
554
555 if (textInput.value == "")
556 newValue = path.replace(/\"/g, "\\\"");
557 else {
558 // Quote the existing text if needed,
559 // then append the new filename (quoted and escaped)
560 if (textInput.value[0] != '"')
561 newValue = '"' + textInput.value.replace(/\"/g, "\\\"") + '"';
562 else
563 newValue = textInput.value;
564
565 newValue = newValue + ' "' + path.replace(/\"/g, "\\\"") + '"';
566 }
567
568 textInput.value = newValue;
569 }
570
onTextFieldFocus
571 function onTextFieldFocus() {
572 setOKAction(null);
573 doEnabling();
574 }
575
onDirectoryChanged
576 function onDirectoryChanged(target)
577 {
578 var path = target.getAttribute("label");
579
580 var file = Components.classes[NS_LOCAL_FILE_CONTRACTID].createInstance(nsILocalFile);
581 file.initWithPath(path);
582
583 if (!sfile.equals(file)) {
584 // Do this on a timeout callback so the directory list can roll up
585 // and we don't keep the mouse grabbed while we are loading.
586
587 setTimeout(gotoDirectory, 0, file);
588 }
589 }
590
populateAncestorList
591 function populateAncestorList(directory) {
592 var menu = document.getElementById("lookInMenu");
593
594 while (menu.hasChildNodes()) {
595 menu.removeChild(menu.firstChild);
596 }
597
598 var menuItem = document.createElement("menuitem");
599 menuItem.setAttribute("label", directory.path);
600 menuItem.setAttribute("crop", "start");
601 menu.appendChild(menuItem);
602
603 // .parent is _sometimes_ null, see bug 121489. Do a dance around that.
604 var parent = directory.parent;
605 while (parent && !parent.equals(directory)) {
606 menuItem = document.createElement("menuitem");
607 menuItem.setAttribute("label", parent.path);
608 menuItem.setAttribute("crop", "start");
609 menu.appendChild(menuItem);
610 directory = parent;
611 parent = directory.parent;
612 }
613
614 var menuList = document.getElementById("lookInMenuList");
615 menuList.selectedIndex = 0;
616 }
617
goUp
618 function goUp() {
619 try {
620 var parent = sfile.parent;
621 } catch(ex) { dump("can't get parent directory\n"); }
622
623 if (parent) {
624 gotoDirectory(parent);
625 }
626 }
627
goHome
628 function goHome() {
629 gotoDirectory(homeDir);
630 }
631
newDir
632 function newDir() {
633 var file;
634 var promptService =
635 Components.classes[NS_PROMPTSERVICE_CONTRACTID].getService(Components.interfaces.nsIPromptService);
636 var dialogTitle =
637 gFilePickerBundle.getString("promptNewDirTitle");
638 var dialogMsg =
639 gFilePickerBundle.getString("promptNewDirMessage");
640 var ret = promptService.prompt(window, dialogTitle, dialogMsg, gNewDirName, null, {value:0});
641
642 if (ret) {
643 file = processPath(gNewDirName.value);
644 if (!file) {
645 showErrorDialog("errorCreateNewDirTitle",
646 "errorCreateNewDirMessage",
647 file);
648 return false;
649 }
650
651 file = file[0].QueryInterface(nsIFile);
652 if (file.exists()) {
653 showErrorDialog("errorNewDirDoesExistTitle",
654 "errorNewDirDoesExistMessage",
655 file);
656 return false;
657 }
658
659 var parent = file.parent;
660 if (!(parent.exists() && parent.isDirectory() && parent.isWritable())) {
661 var oldParent = parent;
662 while (!parent.exists()) {
663 oldParent = parent;
664 parent = parent.parent;
665 }
666 if (parent.isFile()) {
667 showErrorDialog("errorCreateNewDirTitle",
668 "errorCreateNewDirIsFileMessage",
669 parent);
670 return false;
671 }
672 if (!parent.isWritable()) {
673 showErrorDialog("errorCreateNewDirTitle",
674 "errorCreateNewDirPermissionMessage",
675 parent);
676 return false;
677 }
678 }
679
680 try {
681 file.create(nsIFile.DIRECTORY_TYPE, 0755);
682 } catch (e) {
683 showErrorDialog("errorCreateNewDirTitle",
684 "errorCreateNewDirMessage",
685 file);
686 return false;
687 }
688 file.normalize(); // ... in case ".." was used in the path
689 gotoDirectory(file);
690 // we remember and reshow a dirname if something goes wrong
691 // so that errors can be corrected more easily. If all went well,
692 // reset the default value to blank
693 gNewDirName = { value: "" };
694 }
695 return true;
696 }
697
gotoDirectory
698 function gotoDirectory(directory) {
699 window.setCursor("wait");
700 try {
701 populateAncestorList(directory);
702 treeView.setDirectory(directory);
703 document.getElementById("errorShower").selectedIndex = 0;
704 } catch(ex) {
705 document.getElementById("errorShower").selectedIndex = 1;
706 }
707
708 window.setCursor("auto");
709
710 if (filePickerMode == nsIFilePicker.modeGetFolder) {
711 textInput.value = "";
712 }
713 textInput.focus();
714 textInput.setAttribute("autocompletesearchparam", directory.path);
715 sfile = directory;
716 }
717
toggleShowHidden
718 function toggleShowHidden(event) {
719 treeView.showHiddenFiles = !treeView.showHiddenFiles;
720 }
721
722 // from the current directory and whatever was entered
723 // in the entry field, try to make a new path. This
724 // uses "/" as the directory separator, "~" as a shortcut
725 // for the home directory (but only when seen at the start
726 // of a path), and ".." to denote the parent directory.
727 // returns an array of the files listed,
728 // or false if an error occurred.
processPath
729 function processPath(path)
730 {
731 var fileArray = new Array();
732 var strLength = path.length;
733
734 if (path[0] == '"' && filePickerMode == nsIFilePicker.modeOpenMultiple &&
735 strLength > 1) {
736 // we have a quoted list of filenames, separated by spaces.
737 // iterate the list and process each file.
738
739 var curFileStart = 1;
740
741 while (1) {
742 var nextQuote;
743
744 // Look for an unescaped quote
745 var quoteSearchStart = curFileStart + 1;
746 do {
747 nextQuote = path.indexOf('"', quoteSearchStart);
748 quoteSearchStart = nextQuote + 1;
749 } while (nextQuote != -1 && path[nextQuote - 1] == '\\');
750
751 if (nextQuote == -1) {
752 // we have a filename with no trailing quote.
753 // just assume that the filename ends at the end of the string.
754
755 if (!processPathEntry(path.substring(curFileStart), fileArray))
756 return false;
757 break;
758 }
759
760 if (!processPathEntry(path.substring(curFileStart, nextQuote), fileArray))
761 return false;
762
763 curFileStart = path.indexOf('"', nextQuote + 1);
764 if (curFileStart == -1) {
765 // no more quotes, but if we're not at the end of the string,
766 // go ahead and process the remaining text.
767
768 if (nextQuote < strLength - 1)
769 if (!processPathEntry(path.substring(nextQuote + 1), fileArray))
770 return false;
771 break;
772 }
773 ++curFileStart;
774 }
775 } else {
776 // If we didn't start with a quote, assume we just have a single file.
777 if (!processPathEntry(path, fileArray))
778 return false;
779 }
780
781 return fileArray;
782 }
783
processPathEntry
784 function processPathEntry(path, fileArray)
785 {
786 var filePath;
787 var file;
788
789 try {
790 file = sfile.clone().QueryInterface(nsILocalFile);
791 } catch(e) {
792 dump("Couldn't clone\n"+e);
793 return false;
794 }
795
796 var tilde_file = file.clone();
797 tilde_file.append("~");
798 if (path[0] == '~' && // Expand ~ to $HOME, except:
799 !(path == "~" && tilde_file.exists()) && // If ~ was entered and such a file exists, don't expand
800 (path.length == 1 || path[1] == "/")) // We don't want to expand ~file to ${HOME}file
801 filePath = homeDir.path + path.substring(1);
802 else
803 filePath = path;
804
805 // Unescape quotes
806 filePath = filePath.replace(/\\\"/g, "\"");
807
808 if (filePath[0] == '/') /* an absolute path was entered */
809 file.initWithPath(filePath);
810 else if ((filePath.indexOf("/../") > 0) ||
811 (filePath.substr(-3) == "/..") ||
812 (filePath.substr(0,3) == "../") ||
813 (filePath == "..")) {
814 /* appendRelativePath doesn't allow .. */
815 try{
816 file.initWithPath(file.path + "/" + filePath);
817 } catch (e) {
818 dump("Couldn't init path\n"+e);
819 return false;
820 }
821 }
822 else {
823 try {
824 file.appendRelativePath(filePath);
825 } catch (e) {
826 dump("Couldn't append path\n"+e);
827 return false;
828 }
829 }
830
831 fileArray[fileArray.length] = file;
832 return true;
833 }