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#import <Cocoa/Cocoa.h>
7
8#include "nsFilePicker.h"
9#include "nsCOMPtr.h"
10#include "nsReadableUtils.h"
11#include "nsNetUtil.h"
12#include "nsIComponentManager.h"
13#include "nsIFile.h"
14#include "nsILocalFileMac.h"
15#include "nsIURL.h"
16#include "nsArrayEnumerator.h"
17#include "nsIStringBundle.h"
18#include "nsCocoaUtils.h"
19#include "mozilla/Preferences.h"
20
21// This must be included last:
22#include "nsObjCExceptions.h"
23
24using namespace mozilla;
25
26const float kAccessoryViewPadding = 5;
27const int   kSaveTypeControlTag = 1;
28
29static bool gCallSecretHiddenFileAPI = false;
30const char kShowHiddenFilesPref[] = "filepicker.showHiddenFiles";
31
32/**
33 * This class is an observer of NSPopUpButton selection change.
34 */
35@interface NSPopUpButtonObserver : NSObject
36{
37  NSPopUpButton* mPopUpButton;
38  NSOpenPanel*   mOpenPanel;
39  nsFilePicker*  mFilePicker;
40}
41- (void) setPopUpButton:(NSPopUpButton*)aPopUpButton;
42- (void) setOpenPanel:(NSOpenPanel*)aOpenPanel;
43- (void) setFilePicker:(nsFilePicker*)aFilePicker;
44- (void) menuChangedItem:(NSNotification*)aSender;
45@end
46
47NS_IMPL_ISUPPORTS(nsFilePicker, nsIFilePicker)
48
49// We never want to call the secret show hidden files API unless the pref
50// has been set. Once the pref has been set we always need to call it even
51// if it disappears so that we stop showing hidden files if a user deletes
52// the pref. If the secret API was used once and things worked out it should
53// continue working for subsequent calls so the user is at no more risk.
54static void SetShowHiddenFileState(NSSavePanel* panel)
55{
56  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
57
58  bool show = false;
59  if (NS_SUCCEEDED(Preferences::GetBool(kShowHiddenFilesPref, &show))) {
60    gCallSecretHiddenFileAPI = true;
61  }
62
63  if (gCallSecretHiddenFileAPI) {
64    // invoke a method to get a Cocoa-internal nav view
65    SEL navViewSelector = @selector(_navView);
66    NSMethodSignature* navViewSignature = [panel methodSignatureForSelector:navViewSelector];
67    if (!navViewSignature)
68      return;
69    NSInvocation* navViewInvocation = [NSInvocation invocationWithMethodSignature:navViewSignature];
70    [navViewInvocation setSelector:navViewSelector];
71    [navViewInvocation setTarget:panel];
72    [navViewInvocation invoke];
73
74    // get the returned nav view
75    id navView = nil;
76    [navViewInvocation getReturnValue:&navView];
77
78    // invoke the secret show hidden file state method on the nav view
79    SEL showHiddenFilesSelector = @selector(setShowsHiddenFiles:);
80    NSMethodSignature* showHiddenFilesSignature = [navView methodSignatureForSelector:showHiddenFilesSelector];
81    if (!showHiddenFilesSignature)
82      return;
83    NSInvocation* showHiddenFilesInvocation = [NSInvocation invocationWithMethodSignature:showHiddenFilesSignature];
84    [showHiddenFilesInvocation setSelector:showHiddenFilesSelector];
85    [showHiddenFilesInvocation setTarget:navView];
86    [showHiddenFilesInvocation setArgument:&show atIndex:2];
87    [showHiddenFilesInvocation invoke];
88  }
89
90  NS_OBJC_END_TRY_ABORT_BLOCK;
91}
92
93nsFilePicker::nsFilePicker()
94: mSelectedTypeIndex(0)
95{
96}
97
98nsFilePicker::~nsFilePicker()
99{
100}
101
102void
103nsFilePicker::InitNative(nsIWidget *aParent, const nsAString& aTitle)
104{
105  mTitle = aTitle;
106}
107
108NSView* nsFilePicker::GetAccessoryView()
109{
110  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
111
112  NSView* accessoryView = [[[NSView alloc] initWithFrame:NSMakeRect(0, 0, 0, 0)] autorelease];
113
114  // Set a label's default value.
115  NSString* label = @"Format:";
116
117  // Try to get the localized string.
118  nsCOMPtr<nsIStringBundleService> sbs = do_GetService(NS_STRINGBUNDLE_CONTRACTID);
119  nsCOMPtr<nsIStringBundle> bundle;
120  nsresult rv = sbs->CreateBundle("chrome://global/locale/filepicker.properties", getter_AddRefs(bundle));
121  if (NS_SUCCEEDED(rv)) {
122    nsXPIDLString locaLabel;
123    bundle->GetStringFromName(u"formatLabel", getter_Copies(locaLabel));
124    if (locaLabel) {
125      label = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(locaLabel.get())
126                                      length:locaLabel.Length()];
127    }
128  }
129
130  // set up label text field
131  NSTextField* textField = [[[NSTextField alloc] init] autorelease];
132  [textField setEditable:NO];
133  [textField setSelectable:NO];
134  [textField setDrawsBackground:NO];
135  [textField setBezeled:NO];
136  [textField setBordered:NO];
137  [textField setFont:[NSFont labelFontOfSize:13.0]];
138  [textField setStringValue:label];
139  [textField setTag:0];
140  [textField sizeToFit];
141
142  // set up popup button
143  NSPopUpButton* popupButton = [[[NSPopUpButton alloc] initWithFrame:NSMakeRect(0, 0, 0, 0) pullsDown:NO] autorelease];
144  uint32_t numMenuItems = mTitles.Length();
145  for (uint32_t i = 0; i < numMenuItems; i++) {
146    const nsString& currentTitle = mTitles[i];
147    NSString *titleString;
148    if (currentTitle.IsEmpty()) {
149      const nsString& currentFilter = mFilters[i];
150      titleString = [[NSString alloc] initWithCharacters:reinterpret_cast<const unichar*>(currentFilter.get())
151                                                  length:currentFilter.Length()];
152    }
153    else {
154      titleString = [[NSString alloc] initWithCharacters:reinterpret_cast<const unichar*>(currentTitle.get())
155                                                  length:currentTitle.Length()];
156    }
157    [popupButton addItemWithTitle:titleString];
158    [titleString release];
159  }
160  if (mSelectedTypeIndex >= 0 && (uint32_t)mSelectedTypeIndex < numMenuItems)
161    [popupButton selectItemAtIndex:mSelectedTypeIndex];
162  [popupButton setTag:kSaveTypeControlTag];
163  [popupButton sizeToFit]; // we have to do sizeToFit to get the height calculated for us
164  // This is just a default width that works well, doesn't truncate the vast majority of
165  // things that might end up in the menu.
166  [popupButton setFrameSize:NSMakeSize(180, [popupButton frame].size.height)];
167
168  // position everything based on control sizes with kAccessoryViewPadding pix padding
169  // on each side kAccessoryViewPadding pix horizontal padding between controls
170  float greatestHeight = [textField frame].size.height;
171  if ([popupButton frame].size.height > greatestHeight)
172    greatestHeight = [popupButton frame].size.height;
173  float totalViewHeight = greatestHeight + kAccessoryViewPadding * 2;
174  float totalViewWidth  = [textField frame].size.width + [popupButton frame].size.width + kAccessoryViewPadding * 3;
175  [accessoryView setFrameSize:NSMakeSize(totalViewWidth, totalViewHeight)];
176
177  float textFieldOriginY = ((greatestHeight - [textField frame].size.height) / 2 + 1) + kAccessoryViewPadding;
178  [textField setFrameOrigin:NSMakePoint(kAccessoryViewPadding, textFieldOriginY)];
179
180  float popupOriginX = [textField frame].size.width + kAccessoryViewPadding * 2;
181  float popupOriginY = ((greatestHeight - [popupButton frame].size.height) / 2) + kAccessoryViewPadding;
182  [popupButton setFrameOrigin:NSMakePoint(popupOriginX, popupOriginY)];
183
184  [accessoryView addSubview:textField];
185  [accessoryView addSubview:popupButton];
186  return accessoryView;
187
188  NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
189}
190
191// Display the file dialog
192NS_IMETHODIMP nsFilePicker::Show(int16_t *retval)
193{
194  NS_ENSURE_ARG_POINTER(retval);
195
196  *retval = returnCancel;
197
198  int16_t userClicksOK = returnCancel;
199
200// Random questions from DHH:
201//
202// Why do we pass mTitle, mDefault to the functions?  Can GetLocalFile. PutLocalFile,
203// and GetLocalFolder get called someplace else?  It generates a bunch of warnings
204// as it is right now.
205//
206// I think we could easily combine GetLocalFile and GetLocalFolder together, just
207// setting panel pick options based on mMode.  I didn't do it here b/c I wanted to
208// make this look as much like Carbon nsFilePicker as possible.
209
210  mFiles.Clear();
211  nsCOMPtr<nsIFile> theFile;
212
213  switch (mMode)
214  {
215    case modeOpen:
216      userClicksOK = GetLocalFiles(mTitle, false, mFiles);
217      break;
218
219    case modeOpenMultiple:
220      userClicksOK = GetLocalFiles(mTitle, true, mFiles);
221      break;
222
223    case modeSave:
224      userClicksOK = PutLocalFile(mTitle, mDefault, getter_AddRefs(theFile));
225      break;
226
227    case modeGetFolder:
228      userClicksOK = GetLocalFolder(mTitle, getter_AddRefs(theFile));
229      break;
230
231    default:
232      NS_ERROR("Unknown file picker mode");
233      break;
234  }
235
236  if (theFile)
237    mFiles.AppendObject(theFile);
238
239  *retval = userClicksOK;
240  return NS_OK;
241}
242
243static
244void UpdatePanelFileTypes(NSOpenPanel* aPanel, NSArray* aFilters)
245{
246  // If we show all file types, also "expose" bundles' contents.
247  [aPanel setTreatsFilePackagesAsDirectories:!aFilters];
248
249  [aPanel setAllowedFileTypes:aFilters];
250}
251
252@implementation NSPopUpButtonObserver
253- (void) setPopUpButton:(NSPopUpButton*)aPopUpButton
254{
255  mPopUpButton = aPopUpButton;
256}
257
258- (void) setOpenPanel:(NSOpenPanel*)aOpenPanel
259{
260  mOpenPanel = aOpenPanel;
261}
262
263- (void) setFilePicker:(nsFilePicker*)aFilePicker
264{
265  mFilePicker = aFilePicker;
266}
267
268- (void) menuChangedItem:(NSNotification *)aSender
269{
270  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
271  int32_t selectedItem = [mPopUpButton indexOfSelectedItem];
272  if (selectedItem < 0) {
273    return;
274  }
275
276  mFilePicker->SetFilterIndex(selectedItem);
277  UpdatePanelFileTypes(mOpenPanel, mFilePicker->GetFilterList());
278
279  NS_OBJC_END_TRY_ABORT_BLOCK_RETURN();
280}
281@end
282
283// Use OpenPanel to do a GetFile. Returns |returnOK| if the user presses OK in the dialog.
284int16_t
285nsFilePicker::GetLocalFiles(const nsString& inTitle, bool inAllowMultiple, nsCOMArray<nsIFile>& outFiles)
286{
287  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
288
289  int16_t retVal = (int16_t)returnCancel;
290  NSOpenPanel *thePanel = [NSOpenPanel openPanel];
291
292  SetShowHiddenFileState(thePanel);
293
294  // Set the options for how the get file dialog will appear
295  SetDialogTitle(inTitle, thePanel);
296  [thePanel setAllowsMultipleSelection:inAllowMultiple];
297  [thePanel setCanSelectHiddenExtension:YES];
298  [thePanel setCanChooseDirectories:NO];
299  [thePanel setCanChooseFiles:YES];
300  [thePanel setResolvesAliases:YES]; //this is default - probably doesn't need to be set
301
302  // Get filters
303  // filters may be null, if we should allow all file types.
304  NSArray *filters = GetFilterList();
305
306  // set up default directory
307  NSString *theDir = PanelDefaultDirectory();
308
309  // if this is the "Choose application..." dialog, and no other start
310  // dir has been set, then use the Applications folder.
311  if (!theDir) {
312    if (filters && [filters count] == 1 &&
313        [(NSString *)[filters objectAtIndex:0] isEqualToString:@"app"])
314      theDir = @"/Applications/";
315    else
316      theDir = @"";
317  }
318
319  if (theDir) {
320    [thePanel setDirectoryURL:[NSURL fileURLWithPath:theDir isDirectory:YES]];
321  }
322
323  int result;
324  nsCocoaUtils::PrepareForNativeAppModalDialog();
325  if (mFilters.Length() > 1) {
326    // [NSURL initWithString:] (below) throws an exception if URLString is nil.
327
328    NSPopUpButtonObserver* observer = [[NSPopUpButtonObserver alloc] init];
329
330    NSView* accessoryView = GetAccessoryView();
331    [thePanel setAccessoryView:accessoryView];
332
333    [observer setPopUpButton:[accessoryView viewWithTag:kSaveTypeControlTag]];
334    [observer setOpenPanel:thePanel];
335    [observer setFilePicker:this];
336
337    [[NSNotificationCenter defaultCenter]
338      addObserver:observer
339      selector:@selector(menuChangedItem:)
340      name:NSMenuWillSendActionNotification object:nil];
341
342    UpdatePanelFileTypes(thePanel, filters);
343    result = [thePanel runModal];
344
345    [[NSNotificationCenter defaultCenter] removeObserver:observer];
346    [observer release];
347  } else {
348    // If we show all file types, also "expose" bundles' contents.
349    if (!filters) {
350      [thePanel setTreatsFilePackagesAsDirectories:YES];
351    }
352    [thePanel setAllowedFileTypes:filters];
353    result = [thePanel runModal];
354  }
355  nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
356
357  if (result == NSFileHandlingPanelCancelButton)
358    return retVal;
359
360  // Converts data from a NSArray of NSURL to the returned format.
361  // We should be careful to not call [thePanel URLs] more than once given that
362  // it creates a new array each time.
363  // We are using Fast Enumeration, thus the NSURL array is created once then
364  // iterated.
365  for (NSURL* url in [thePanel URLs]) {
366    if (!url) {
367      continue;
368    }
369
370    nsCOMPtr<nsIFile> localFile;
371    NS_NewLocalFile(EmptyString(), true, getter_AddRefs(localFile));
372    nsCOMPtr<nsILocalFileMac> macLocalFile = do_QueryInterface(localFile);
373    if (macLocalFile && NS_SUCCEEDED(macLocalFile->InitWithCFURL((CFURLRef)url))) {
374      outFiles.AppendObject(localFile);
375    }
376  }
377
378  if (outFiles.Count() > 0)
379    retVal = returnOK;
380
381  return retVal;
382
383  NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
384}
385
386// Use OpenPanel to do a GetFolder. Returns |returnOK| if the user presses OK in the dialog.
387int16_t
388nsFilePicker::GetLocalFolder(const nsString& inTitle, nsIFile** outFile)
389{
390  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
391  NS_ASSERTION(outFile, "this protected member function expects a null initialized out pointer");
392
393  int16_t retVal = (int16_t)returnCancel;
394  NSOpenPanel *thePanel = [NSOpenPanel openPanel];
395
396  SetShowHiddenFileState(thePanel);
397
398  // Set the options for how the get file dialog will appear
399  SetDialogTitle(inTitle, thePanel);
400  [thePanel setAllowsMultipleSelection:NO];   //this is default -probably doesn't need to be set
401  [thePanel setCanSelectHiddenExtension:YES];
402  [thePanel setCanChooseDirectories:YES];
403  [thePanel setCanChooseFiles:NO];
404  [thePanel setResolvesAliases:YES];          //this is default - probably doesn't need to be set
405  [thePanel setCanCreateDirectories:YES];
406
407  // packages != folders
408  [thePanel setTreatsFilePackagesAsDirectories:NO];
409
410  // set up default directory
411  NSString *theDir = PanelDefaultDirectory();
412  if (theDir) {
413    [thePanel setDirectoryURL:[NSURL fileURLWithPath:theDir isDirectory:YES]];
414  }
415  nsCocoaUtils::PrepareForNativeAppModalDialog();
416  int result = [thePanel runModal];
417  nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
418
419  if (result == NSFileHandlingPanelCancelButton)
420    return retVal;
421
422  // get the path for the folder (we allow just 1, so that's all we get)
423  NSURL *theURL = [[thePanel URLs] objectAtIndex:0];
424  if (theURL) {
425    nsCOMPtr<nsIFile> localFile;
426    NS_NewLocalFile(EmptyString(), true, getter_AddRefs(localFile));
427    nsCOMPtr<nsILocalFileMac> macLocalFile = do_QueryInterface(localFile);
428    if (macLocalFile && NS_SUCCEEDED(macLocalFile->InitWithCFURL((CFURLRef)theURL))) {
429      *outFile = localFile;
430      NS_ADDREF(*outFile);
431      retVal = returnOK;
432    }
433  }
434
435  return retVal;
436
437  NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
438}
439
440// Returns |returnOK| if the user presses OK in the dialog.
441int16_t
442nsFilePicker::PutLocalFile(const nsString& inTitle, const nsString& inDefaultName, nsIFile** outFile)
443{
444  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
445  NS_ASSERTION(outFile, "this protected member function expects a null initialized out pointer");
446
447  int16_t retVal = returnCancel;
448  NSSavePanel *thePanel = [NSSavePanel savePanel];
449
450  SetShowHiddenFileState(thePanel);
451
452  SetDialogTitle(inTitle, thePanel);
453
454  // set up accessory view for file format options
455  NSView* accessoryView = GetAccessoryView();
456  [thePanel setAccessoryView:accessoryView];
457
458  // set up default file name
459  NSString* defaultFilename = [NSString stringWithCharacters:(const unichar*)inDefaultName.get() length:inDefaultName.Length()];
460
461  // set up allowed types; this prevents the extension from being selected
462  // use the UTI for the file type to allow alternate extensions (e.g., jpg vs. jpeg)
463  NSString* extension = defaultFilename.pathExtension;
464  if (extension.length != 0) {
465    CFStringRef type = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (CFStringRef)extension, NULL);
466
467    if (type) {
468      thePanel.allowedFileTypes = @[(NSString*)type];
469      CFRelease(type);
470    } else {
471      // if there's no UTI for the file extension, use the extension itself.
472      thePanel.allowedFileTypes = @[extension];
473    }
474  }
475  // Allow users to change the extension.
476  thePanel.allowsOtherFileTypes = YES;
477
478  // set up default directory
479  NSString *theDir = PanelDefaultDirectory();
480  if (theDir) {
481    [thePanel setDirectoryURL:[NSURL fileURLWithPath:theDir isDirectory:YES]];
482  }
483
484  // load the panel
485  nsCocoaUtils::PrepareForNativeAppModalDialog();
486  [thePanel setNameFieldStringValue:defaultFilename];
487  int result = [thePanel runModal];
488  nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
489  if (result == NSFileHandlingPanelCancelButton)
490    return retVal;
491
492  // get the save type
493  NSPopUpButton* popupButton = [accessoryView viewWithTag:kSaveTypeControlTag];
494  if (popupButton) {
495    mSelectedTypeIndex = [popupButton indexOfSelectedItem];
496  }
497
498  NSURL* fileURL = [thePanel URL];
499  if (fileURL) {
500    nsCOMPtr<nsIFile> localFile;
501    NS_NewLocalFile(EmptyString(), true, getter_AddRefs(localFile));
502    nsCOMPtr<nsILocalFileMac> macLocalFile = do_QueryInterface(localFile);
503    if (macLocalFile && NS_SUCCEEDED(macLocalFile->InitWithCFURL((CFURLRef)fileURL))) {
504      *outFile = localFile;
505      NS_ADDREF(*outFile);
506      // We tell if we are replacing or not by just looking to see if the file exists.
507      // The user could not have hit OK and not meant to replace the file.
508      if ([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]])
509        retVal = returnReplace;
510      else
511        retVal = returnOK;
512    }
513  }
514
515  return retVal;
516
517  NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
518}
519
520NSArray *
521nsFilePicker::GetFilterList()
522{
523  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
524
525  if (!mFilters.Length()) {
526    return nil;
527  }
528
529  if (mFilters.Length() <= (uint32_t)mSelectedTypeIndex) {
530    NS_WARNING("An out of range index has been selected. Using the first index instead.");
531    mSelectedTypeIndex = 0;
532  }
533
534  const nsString& filterWide = mFilters[mSelectedTypeIndex];
535  if (!filterWide.Length()) {
536    return nil;
537  }
538
539  if (filterWide.Equals(NS_LITERAL_STRING("*"))) {
540    return nil;
541  }
542
543  // The extensions in filterWide are in the format "*.ext" but are expected
544  // in the format "ext" by NSOpenPanel. So we need to filter some characters.
545  NSMutableString* filterString = [[[NSMutableString alloc] initWithString:
546                                    [NSString stringWithCharacters:reinterpret_cast<const unichar*>(filterWide.get())
547                                                            length:filterWide.Length()]] autorelease];
548  NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:@". *"];
549  NSRange range = [filterString rangeOfCharacterFromSet:set];
550  while (range.length) {
551    [filterString replaceCharactersInRange:range withString:@""];
552    range = [filterString rangeOfCharacterFromSet:set];
553  }
554
555  return [[[NSArray alloc] initWithArray:
556           [filterString componentsSeparatedByString:@";"]] autorelease];
557
558  NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
559}
560
561// Sets the dialog title to whatever it should be.  If it fails, eh,
562// the OS will provide a sensible default.
563void
564nsFilePicker::SetDialogTitle(const nsString& inTitle, id aPanel)
565{
566  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
567
568  [aPanel setTitle:[NSString stringWithCharacters:(const unichar*)inTitle.get() length:inTitle.Length()]];
569
570  if (!mOkButtonLabel.IsEmpty()) {
571    [aPanel setPrompt:[NSString stringWithCharacters:(const unichar*)mOkButtonLabel.get() length:mOkButtonLabel.Length()]];
572  }
573
574  NS_OBJC_END_TRY_ABORT_BLOCK;
575}
576
577// Converts path from an nsIFile into a NSString path
578// If it fails, returns an empty string.
579NSString *
580nsFilePicker::PanelDefaultDirectory()
581{
582  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
583
584  NSString *directory = nil;
585  if (mDisplayDirectory) {
586    nsAutoString pathStr;
587    mDisplayDirectory->GetPath(pathStr);
588    directory = [[[NSString alloc] initWithCharacters:reinterpret_cast<const unichar*>(pathStr.get())
589                                               length:pathStr.Length()] autorelease];
590  }
591  return directory;
592
593  NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
594}
595
596NS_IMETHODIMP nsFilePicker::GetFile(nsIFile **aFile)
597{
598  NS_ENSURE_ARG_POINTER(aFile);
599  *aFile = nullptr;
600
601  // just return the first file
602  if (mFiles.Count() > 0) {
603    *aFile = mFiles.ObjectAt(0);
604    NS_IF_ADDREF(*aFile);
605  }
606
607  return NS_OK;
608}
609
610NS_IMETHODIMP nsFilePicker::GetFileURL(nsIURI **aFileURL)
611{
612  NS_ENSURE_ARG_POINTER(aFileURL);
613  *aFileURL = nullptr;
614
615  if (mFiles.Count() == 0)
616    return NS_OK;
617
618  return NS_NewFileURI(aFileURL, mFiles.ObjectAt(0));
619}
620
621NS_IMETHODIMP nsFilePicker::GetFiles(nsISimpleEnumerator **aFiles)
622{
623  return NS_NewArrayEnumerator(aFiles, mFiles);
624}
625
626NS_IMETHODIMP nsFilePicker::SetDefaultString(const nsAString& aString)
627{
628  mDefault = aString;
629  return NS_OK;
630}
631
632NS_IMETHODIMP nsFilePicker::GetDefaultString(nsAString& aString)
633{
634  return NS_ERROR_FAILURE;
635}
636
637// The default extension to use for files
638NS_IMETHODIMP nsFilePicker::GetDefaultExtension(nsAString& aExtension)
639{
640  aExtension.Truncate();
641  return NS_OK;
642}
643
644NS_IMETHODIMP nsFilePicker::SetDefaultExtension(const nsAString& aExtension)
645{
646  return NS_OK;
647}
648
649// Append an entry to the filters array
650NS_IMETHODIMP
651nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter)
652{
653  // "..apps" has to be translated with native executable extensions.
654  if (aFilter.EqualsLiteral("..apps")) {
655    mFilters.AppendElement(NS_LITERAL_STRING("*.app"));
656  } else {
657    mFilters.AppendElement(aFilter);
658  }
659  mTitles.AppendElement(aTitle);
660
661  return NS_OK;
662}
663
664// Get the filter index - do we still need this?
665NS_IMETHODIMP nsFilePicker::GetFilterIndex(int32_t *aFilterIndex)
666{
667  *aFilterIndex = mSelectedTypeIndex;
668  return NS_OK;
669}
670
671// Set the filter index - do we still need this?
672NS_IMETHODIMP nsFilePicker::SetFilterIndex(int32_t aFilterIndex)
673{
674  mSelectedTypeIndex = aFilterIndex;
675  return NS_OK;
676}
677