forked from mirrors/gecko-dev
		
	 355baf5b39
			
		
	
	
		355baf5b39
		
	
	
	
	
		
			
			This patch was generated automatically, using the following commands: ``` rg -l 'NS_OBJC_BEGIN_TRY_ABORT_BLOCK' . | xargs sed -i '' -e 's/NS_OBJC_BEGIN_TRY_ABORT_BLOCK/NS_OBJC_BEGIN_TRY_IGNORE_BLOCK/g' rg -l 'NS_OBJC_END_TRY_ABORT_BLOCK' . | xargs sed -i '' -e 's/NS_OBJC_END_TRY_ABORT_BLOCK/NS_OBJC_END_TRY_IGNORE_BLOCK/g' ``` Differential Revision: https://phabricator.services.mozilla.com/D104960
		
			
				
	
	
		
			638 lines
		
	
	
	
		
			22 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
			
		
		
	
	
			638 lines
		
	
	
	
		
			22 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | ||
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | ||
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | ||
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | ||
| 
 | ||
| #import <Cocoa/Cocoa.h>
 | ||
| 
 | ||
| #include "nsFilePicker.h"
 | ||
| #include "nsCOMPtr.h"
 | ||
| #include "nsReadableUtils.h"
 | ||
| #include "nsNetUtil.h"
 | ||
| #include "nsIFile.h"
 | ||
| #include "nsILocalFileMac.h"
 | ||
| #include "nsArrayEnumerator.h"
 | ||
| #include "nsIStringBundle.h"
 | ||
| #include "nsCocoaUtils.h"
 | ||
| #include "mozilla/Preferences.h"
 | ||
| 
 | ||
| // This must be included last:
 | ||
| #include "nsObjCExceptions.h"
 | ||
| 
 | ||
| using namespace mozilla;
 | ||
| 
 | ||
| const float kAccessoryViewPadding = 5;
 | ||
| const int kSaveTypeControlTag = 1;
 | ||
| 
 | ||
| static bool gCallSecretHiddenFileAPI = false;
 | ||
| const char kShowHiddenFilesPref[] = "filepicker.showHiddenFiles";
 | ||
| 
 | ||
| /**
 | ||
|  * This class is an observer of NSPopUpButton selection change.
 | ||
|  */
 | ||
| @interface NSPopUpButtonObserver : NSObject {
 | ||
|   NSPopUpButton* mPopUpButton;
 | ||
|   NSOpenPanel* mOpenPanel;
 | ||
|   nsFilePicker* mFilePicker;
 | ||
| }
 | ||
| - (void)setPopUpButton:(NSPopUpButton*)aPopUpButton;
 | ||
| - (void)setOpenPanel:(NSOpenPanel*)aOpenPanel;
 | ||
| - (void)setFilePicker:(nsFilePicker*)aFilePicker;
 | ||
| - (void)menuChangedItem:(NSNotification*)aSender;
 | ||
| @end
 | ||
| 
 | ||
| NS_IMPL_ISUPPORTS(nsFilePicker, nsIFilePicker)
 | ||
| 
 | ||
| // We never want to call the secret show hidden files API unless the pref
 | ||
| // has been set. Once the pref has been set we always need to call it even
 | ||
| // if it disappears so that we stop showing hidden files if a user deletes
 | ||
| // the pref. If the secret API was used once and things worked out it should
 | ||
| // continue working for subsequent calls so the user is at no more risk.
 | ||
| static void SetShowHiddenFileState(NSSavePanel* panel) {
 | ||
|   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
 | ||
| 
 | ||
|   bool show = false;
 | ||
|   if (NS_SUCCEEDED(Preferences::GetBool(kShowHiddenFilesPref, &show))) {
 | ||
|     gCallSecretHiddenFileAPI = true;
 | ||
|   }
 | ||
| 
 | ||
|   if (gCallSecretHiddenFileAPI) {
 | ||
|     // invoke a method to get a Cocoa-internal nav view
 | ||
|     SEL navViewSelector = @selector(_navView);
 | ||
|     NSMethodSignature* navViewSignature = [panel methodSignatureForSelector:navViewSelector];
 | ||
|     if (!navViewSignature) return;
 | ||
|     NSInvocation* navViewInvocation = [NSInvocation invocationWithMethodSignature:navViewSignature];
 | ||
|     [navViewInvocation setSelector:navViewSelector];
 | ||
|     [navViewInvocation setTarget:panel];
 | ||
|     [navViewInvocation invoke];
 | ||
| 
 | ||
|     // get the returned nav view
 | ||
|     id navView = nil;
 | ||
|     [navViewInvocation getReturnValue:&navView];
 | ||
| 
 | ||
|     // invoke the secret show hidden file state method on the nav view
 | ||
|     SEL showHiddenFilesSelector = @selector(setShowsHiddenFiles:);
 | ||
|     NSMethodSignature* showHiddenFilesSignature =
 | ||
|         [navView methodSignatureForSelector:showHiddenFilesSelector];
 | ||
|     if (!showHiddenFilesSignature) return;
 | ||
|     NSInvocation* showHiddenFilesInvocation =
 | ||
|         [NSInvocation invocationWithMethodSignature:showHiddenFilesSignature];
 | ||
|     [showHiddenFilesInvocation setSelector:showHiddenFilesSelector];
 | ||
|     [showHiddenFilesInvocation setTarget:navView];
 | ||
|     [showHiddenFilesInvocation setArgument:&show atIndex:2];
 | ||
|     [showHiddenFilesInvocation invoke];
 | ||
|   }
 | ||
| 
 | ||
|   NS_OBJC_END_TRY_IGNORE_BLOCK;
 | ||
| }
 | ||
| 
 | ||
| nsFilePicker::nsFilePicker() : mSelectedTypeIndex(0) {}
 | ||
| 
 | ||
| nsFilePicker::~nsFilePicker() {}
 | ||
| 
 | ||
| void nsFilePicker::InitNative(nsIWidget* aParent, const nsAString& aTitle) { mTitle = aTitle; }
 | ||
| 
 | ||
| NSView* nsFilePicker::GetAccessoryView() {
 | ||
|   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
 | ||
| 
 | ||
|   NSView* accessoryView = [[[NSView alloc] initWithFrame:NSMakeRect(0, 0, 0, 0)] autorelease];
 | ||
| 
 | ||
|   // Set a label's default value.
 | ||
|   NSString* label = @"Format:";
 | ||
| 
 | ||
|   // Try to get the localized string.
 | ||
|   nsCOMPtr<nsIStringBundleService> sbs = do_GetService(NS_STRINGBUNDLE_CONTRACTID);
 | ||
|   nsCOMPtr<nsIStringBundle> bundle;
 | ||
|   nsresult rv =
 | ||
|       sbs->CreateBundle("chrome://global/locale/filepicker.properties", getter_AddRefs(bundle));
 | ||
|   if (NS_SUCCEEDED(rv)) {
 | ||
|     nsAutoString locaLabel;
 | ||
|     rv = bundle->GetStringFromName("formatLabel", locaLabel);
 | ||
|     if (NS_SUCCEEDED(rv)) {
 | ||
|       label = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(locaLabel.get())
 | ||
|                                       length:locaLabel.Length()];
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   // set up label text field
 | ||
|   NSTextField* textField = [[[NSTextField alloc] init] autorelease];
 | ||
|   [textField setEditable:NO];
 | ||
|   [textField setSelectable:NO];
 | ||
|   [textField setDrawsBackground:NO];
 | ||
|   [textField setBezeled:NO];
 | ||
|   [textField setBordered:NO];
 | ||
|   [textField setFont:[NSFont labelFontOfSize:13.0]];
 | ||
|   [textField setStringValue:label];
 | ||
|   [textField setTag:0];
 | ||
|   [textField sizeToFit];
 | ||
| 
 | ||
|   // set up popup button
 | ||
|   NSPopUpButton* popupButton = [[[NSPopUpButton alloc] initWithFrame:NSMakeRect(0, 0, 0, 0)
 | ||
|                                                            pullsDown:NO] autorelease];
 | ||
|   uint32_t numMenuItems = mTitles.Length();
 | ||
|   for (uint32_t i = 0; i < numMenuItems; i++) {
 | ||
|     const nsString& currentTitle = mTitles[i];
 | ||
|     NSString* titleString;
 | ||
|     if (currentTitle.IsEmpty()) {
 | ||
|       const nsString& currentFilter = mFilters[i];
 | ||
|       titleString =
 | ||
|           [[NSString alloc] initWithCharacters:reinterpret_cast<const unichar*>(currentFilter.get())
 | ||
|                                         length:currentFilter.Length()];
 | ||
|     } else {
 | ||
|       titleString =
 | ||
|           [[NSString alloc] initWithCharacters:reinterpret_cast<const unichar*>(currentTitle.get())
 | ||
|                                         length:currentTitle.Length()];
 | ||
|     }
 | ||
|     [popupButton addItemWithTitle:titleString];
 | ||
|     [titleString release];
 | ||
|   }
 | ||
|   if (mSelectedTypeIndex >= 0 && (uint32_t)mSelectedTypeIndex < numMenuItems)
 | ||
|     [popupButton selectItemAtIndex:mSelectedTypeIndex];
 | ||
|   [popupButton setTag:kSaveTypeControlTag];
 | ||
|   [popupButton sizeToFit];  // we have to do sizeToFit to get the height calculated for us
 | ||
|   // This is just a default width that works well, doesn't truncate the vast majority of
 | ||
|   // things that might end up in the menu.
 | ||
|   [popupButton setFrameSize:NSMakeSize(180, [popupButton frame].size.height)];
 | ||
| 
 | ||
|   // position everything based on control sizes with kAccessoryViewPadding pix padding
 | ||
|   // on each side kAccessoryViewPadding pix horizontal padding between controls
 | ||
|   float greatestHeight = [textField frame].size.height;
 | ||
|   if ([popupButton frame].size.height > greatestHeight)
 | ||
|     greatestHeight = [popupButton frame].size.height;
 | ||
|   float totalViewHeight = greatestHeight + kAccessoryViewPadding * 2;
 | ||
|   float totalViewWidth =
 | ||
|       [textField frame].size.width + [popupButton frame].size.width + kAccessoryViewPadding * 3;
 | ||
|   [accessoryView setFrameSize:NSMakeSize(totalViewWidth, totalViewHeight)];
 | ||
| 
 | ||
|   float textFieldOriginY =
 | ||
|       ((greatestHeight - [textField frame].size.height) / 2 + 1) + kAccessoryViewPadding;
 | ||
|   [textField setFrameOrigin:NSMakePoint(kAccessoryViewPadding, textFieldOriginY)];
 | ||
| 
 | ||
|   float popupOriginX = [textField frame].size.width + kAccessoryViewPadding * 2;
 | ||
|   float popupOriginY =
 | ||
|       ((greatestHeight - [popupButton frame].size.height) / 2) + kAccessoryViewPadding;
 | ||
|   [popupButton setFrameOrigin:NSMakePoint(popupOriginX, popupOriginY)];
 | ||
| 
 | ||
|   [accessoryView addSubview:textField];
 | ||
|   [accessoryView addSubview:popupButton];
 | ||
|   return accessoryView;
 | ||
| 
 | ||
|   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
 | ||
| }
 | ||
| 
 | ||
| // Display the file dialog
 | ||
| nsresult nsFilePicker::Show(int16_t* retval) {
 | ||
|   NS_ENSURE_ARG_POINTER(retval);
 | ||
| 
 | ||
|   *retval = returnCancel;
 | ||
| 
 | ||
|   int16_t userClicksOK = returnCancel;
 | ||
| 
 | ||
|   mFiles.Clear();
 | ||
|   nsCOMPtr<nsIFile> theFile;
 | ||
| 
 | ||
|   // Note that GetLocalFolder shares a lot of code with GetLocalFiles.
 | ||
|   // Could combine the functions and just pass the mode in.
 | ||
|   switch (mMode) {
 | ||
|     case modeOpen:
 | ||
|       userClicksOK = GetLocalFiles(false, mFiles);
 | ||
|       break;
 | ||
| 
 | ||
|     case modeOpenMultiple:
 | ||
|       userClicksOK = GetLocalFiles(true, mFiles);
 | ||
|       break;
 | ||
| 
 | ||
|     case modeSave:
 | ||
|       userClicksOK = PutLocalFile(getter_AddRefs(theFile));
 | ||
|       break;
 | ||
| 
 | ||
|     case modeGetFolder:
 | ||
|       userClicksOK = GetLocalFolder(getter_AddRefs(theFile));
 | ||
|       break;
 | ||
| 
 | ||
|     default:
 | ||
|       NS_ERROR("Unknown file picker mode");
 | ||
|       break;
 | ||
|   }
 | ||
| 
 | ||
|   if (theFile) mFiles.AppendObject(theFile);
 | ||
| 
 | ||
|   *retval = userClicksOK;
 | ||
|   return NS_OK;
 | ||
| }
 | ||
| 
 | ||
| static void UpdatePanelFileTypes(NSOpenPanel* aPanel, NSArray* aFilters) {
 | ||
|   // If we show all file types, also "expose" bundles' contents.
 | ||
|   [aPanel setTreatsFilePackagesAsDirectories:!aFilters];
 | ||
| 
 | ||
|   [aPanel setAllowedFileTypes:aFilters];
 | ||
| }
 | ||
| 
 | ||
| @implementation NSPopUpButtonObserver
 | ||
| - (void)setPopUpButton:(NSPopUpButton*)aPopUpButton {
 | ||
|   mPopUpButton = aPopUpButton;
 | ||
| }
 | ||
| 
 | ||
| - (void)setOpenPanel:(NSOpenPanel*)aOpenPanel {
 | ||
|   mOpenPanel = aOpenPanel;
 | ||
| }
 | ||
| 
 | ||
| - (void)setFilePicker:(nsFilePicker*)aFilePicker {
 | ||
|   mFilePicker = aFilePicker;
 | ||
| }
 | ||
| 
 | ||
| - (void)menuChangedItem:(NSNotification*)aSender {
 | ||
|   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
 | ||
|   int32_t selectedItem = [mPopUpButton indexOfSelectedItem];
 | ||
|   if (selectedItem < 0) {
 | ||
|     return;
 | ||
|   }
 | ||
| 
 | ||
|   mFilePicker->SetFilterIndex(selectedItem);
 | ||
|   UpdatePanelFileTypes(mOpenPanel, mFilePicker->GetFilterList());
 | ||
| 
 | ||
|   NS_OBJC_END_TRY_BLOCK_RETURN();
 | ||
| }
 | ||
| @end
 | ||
| 
 | ||
| // Use OpenPanel to do a GetFile. Returns |returnOK| if the user presses OK in the dialog.
 | ||
| int16_t nsFilePicker::GetLocalFiles(bool inAllowMultiple, nsCOMArray<nsIFile>& outFiles) {
 | ||
|   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
 | ||
| 
 | ||
|   int16_t retVal = (int16_t)returnCancel;
 | ||
|   NSOpenPanel* thePanel = [NSOpenPanel openPanel];
 | ||
| 
 | ||
|   SetShowHiddenFileState(thePanel);
 | ||
| 
 | ||
|   // Set the options for how the get file dialog will appear
 | ||
|   SetDialogTitle(mTitle, thePanel);
 | ||
|   [thePanel setAllowsMultipleSelection:inAllowMultiple];
 | ||
|   [thePanel setCanSelectHiddenExtension:YES];
 | ||
|   [thePanel setCanChooseDirectories:NO];
 | ||
|   [thePanel setCanChooseFiles:YES];
 | ||
|   [thePanel setResolvesAliases:YES];
 | ||
| 
 | ||
|   // Get filters
 | ||
|   // filters may be null, if we should allow all file types.
 | ||
|   NSArray* filters = GetFilterList();
 | ||
| 
 | ||
|   // set up default directory
 | ||
|   NSString* theDir = PanelDefaultDirectory();
 | ||
| 
 | ||
|   // if this is the "Choose application..." dialog, and no other start
 | ||
|   // dir has been set, then use the Applications folder.
 | ||
|   if (!theDir) {
 | ||
|     if (filters && [filters count] == 1 &&
 | ||
|         [(NSString*)[filters objectAtIndex:0] isEqualToString:@"app"])
 | ||
|       theDir = @"/Applications/";
 | ||
|     else
 | ||
|       theDir = @"";
 | ||
|   }
 | ||
| 
 | ||
|   if (theDir) {
 | ||
|     [thePanel setDirectoryURL:[NSURL fileURLWithPath:theDir isDirectory:YES]];
 | ||
|   }
 | ||
| 
 | ||
|   int result;
 | ||
|   nsCocoaUtils::PrepareForNativeAppModalDialog();
 | ||
|   if (mFilters.Length() > 1) {
 | ||
|     // [NSURL initWithString:] (below) throws an exception if URLString is nil.
 | ||
| 
 | ||
|     NSPopUpButtonObserver* observer = [[NSPopUpButtonObserver alloc] init];
 | ||
| 
 | ||
|     NSView* accessoryView = GetAccessoryView();
 | ||
|     [thePanel setAccessoryView:accessoryView];
 | ||
| 
 | ||
|     [observer setPopUpButton:[accessoryView viewWithTag:kSaveTypeControlTag]];
 | ||
|     [observer setOpenPanel:thePanel];
 | ||
|     [observer setFilePicker:this];
 | ||
| 
 | ||
|     [[NSNotificationCenter defaultCenter] addObserver:observer
 | ||
|                                              selector:@selector(menuChangedItem:)
 | ||
|                                                  name:NSMenuWillSendActionNotification
 | ||
|                                                object:nil];
 | ||
| 
 | ||
|     UpdatePanelFileTypes(thePanel, filters);
 | ||
|     result = [thePanel runModal];
 | ||
| 
 | ||
|     [[NSNotificationCenter defaultCenter] removeObserver:observer];
 | ||
|     [observer release];
 | ||
|   } else {
 | ||
|     // If we show all file types, also "expose" bundles' contents.
 | ||
|     if (!filters) {
 | ||
|       [thePanel setTreatsFilePackagesAsDirectories:YES];
 | ||
|     }
 | ||
|     [thePanel setAllowedFileTypes:filters];
 | ||
|     result = [thePanel runModal];
 | ||
|   }
 | ||
|   nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
 | ||
| 
 | ||
|   if (result == NSFileHandlingPanelCancelButton) return retVal;
 | ||
| 
 | ||
|   // Converts data from a NSArray of NSURL to the returned format.
 | ||
|   // We should be careful to not call [thePanel URLs] more than once given that
 | ||
|   // it creates a new array each time.
 | ||
|   // We are using Fast Enumeration, thus the NSURL array is created once then
 | ||
|   // iterated.
 | ||
|   for (NSURL* url in [thePanel URLs]) {
 | ||
|     if (!url) {
 | ||
|       continue;
 | ||
|     }
 | ||
| 
 | ||
|     nsCOMPtr<nsIFile> localFile;
 | ||
|     NS_NewLocalFile(u""_ns, true, getter_AddRefs(localFile));
 | ||
|     nsCOMPtr<nsILocalFileMac> macLocalFile = do_QueryInterface(localFile);
 | ||
|     if (macLocalFile && NS_SUCCEEDED(macLocalFile->InitWithCFURL((CFURLRef)url))) {
 | ||
|       outFiles.AppendObject(localFile);
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   if (outFiles.Count() > 0) retVal = returnOK;
 | ||
| 
 | ||
|   return retVal;
 | ||
| 
 | ||
|   NS_OBJC_END_TRY_BLOCK_RETURN(0);
 | ||
| }
 | ||
| 
 | ||
| // Use OpenPanel to do a GetFolder. Returns |returnOK| if the user presses OK in the dialog.
 | ||
| int16_t nsFilePicker::GetLocalFolder(nsIFile** outFile) {
 | ||
|   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
 | ||
|   NS_ASSERTION(outFile, "this protected member function expects a null initialized out pointer");
 | ||
| 
 | ||
|   int16_t retVal = (int16_t)returnCancel;
 | ||
|   NSOpenPanel* thePanel = [NSOpenPanel openPanel];
 | ||
| 
 | ||
|   SetShowHiddenFileState(thePanel);
 | ||
| 
 | ||
|   // Set the options for how the get file dialog will appear
 | ||
|   SetDialogTitle(mTitle, thePanel);
 | ||
|   [thePanel setAllowsMultipleSelection:NO];
 | ||
|   [thePanel setCanSelectHiddenExtension:YES];
 | ||
|   [thePanel setCanChooseDirectories:YES];
 | ||
|   [thePanel setCanChooseFiles:NO];
 | ||
|   [thePanel setResolvesAliases:YES];
 | ||
|   [thePanel setCanCreateDirectories:YES];
 | ||
| 
 | ||
|   // packages != folders
 | ||
|   [thePanel setTreatsFilePackagesAsDirectories:NO];
 | ||
| 
 | ||
|   // set up default directory
 | ||
|   NSString* theDir = PanelDefaultDirectory();
 | ||
|   if (theDir) {
 | ||
|     [thePanel setDirectoryURL:[NSURL fileURLWithPath:theDir isDirectory:YES]];
 | ||
|   }
 | ||
|   nsCocoaUtils::PrepareForNativeAppModalDialog();
 | ||
|   int result = [thePanel runModal];
 | ||
|   nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
 | ||
| 
 | ||
|   if (result == NSFileHandlingPanelCancelButton) return retVal;
 | ||
| 
 | ||
|   // get the path for the folder (we allow just 1, so that's all we get)
 | ||
|   NSURL* theURL = [[thePanel URLs] objectAtIndex:0];
 | ||
|   if (theURL) {
 | ||
|     nsCOMPtr<nsIFile> localFile;
 | ||
|     NS_NewLocalFile(u""_ns, true, getter_AddRefs(localFile));
 | ||
|     nsCOMPtr<nsILocalFileMac> macLocalFile = do_QueryInterface(localFile);
 | ||
|     if (macLocalFile && NS_SUCCEEDED(macLocalFile->InitWithCFURL((CFURLRef)theURL))) {
 | ||
|       *outFile = localFile;
 | ||
|       NS_ADDREF(*outFile);
 | ||
|       retVal = returnOK;
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   return retVal;
 | ||
| 
 | ||
|   NS_OBJC_END_TRY_BLOCK_RETURN(0);
 | ||
| }
 | ||
| 
 | ||
| // Returns |returnOK| if the user presses OK in the dialog.
 | ||
| int16_t nsFilePicker::PutLocalFile(nsIFile** outFile) {
 | ||
|   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
 | ||
|   NS_ASSERTION(outFile, "this protected member function expects a null initialized out pointer");
 | ||
| 
 | ||
|   int16_t retVal = returnCancel;
 | ||
|   NSSavePanel* thePanel = [NSSavePanel savePanel];
 | ||
| 
 | ||
|   SetShowHiddenFileState(thePanel);
 | ||
| 
 | ||
|   SetDialogTitle(mTitle, thePanel);
 | ||
| 
 | ||
|   // set up accessory view for file format options
 | ||
|   NSView* accessoryView = GetAccessoryView();
 | ||
|   [thePanel setAccessoryView:accessoryView];
 | ||
| 
 | ||
|   // set up default file name
 | ||
|   NSString* defaultFilename = [NSString stringWithCharacters:(const unichar*)mDefaultFilename.get()
 | ||
|                                                       length:mDefaultFilename.Length()];
 | ||
| 
 | ||
|   // Set up the allowed type. This prevents the extension from being selected.
 | ||
|   NSString* extension = defaultFilename.pathExtension;
 | ||
|   if (extension.length != 0) {
 | ||
|     thePanel.allowedFileTypes = @[ extension ];
 | ||
|   }
 | ||
|   // Allow users to change the extension.
 | ||
|   thePanel.allowsOtherFileTypes = YES;
 | ||
| 
 | ||
|   // If extensions are hidden and we’re saving a file with multiple extensions,
 | ||
|   // only the last extension will be hidden in the panel (".tar.gz" will become
 | ||
|   // ".tar"). If the remaining extension is known, the OS will think that we're
 | ||
|   // trying to add a non-default extension. To avoid the confusion, we ensure
 | ||
|   // that all extensions are shown in the panel if the remaining extension is
 | ||
|   // known by the OS.
 | ||
|   NSString* fileName = [[defaultFilename lastPathComponent] stringByDeletingPathExtension];
 | ||
|   NSString* otherExtension = fileName.pathExtension;
 | ||
|   if (otherExtension.length != 0) {
 | ||
|     // There's another extension here. Get the UTI.
 | ||
|     CFStringRef type = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension,
 | ||
|                                                              (CFStringRef)otherExtension, NULL);
 | ||
|     if (type) {
 | ||
|       if (!CFStringHasPrefix(type, CFSTR("dyn."))) {
 | ||
|         // We have a UTI, otherwise the type would have a "dyn." prefix. Ensure
 | ||
|         // extensions are shown in the panel.
 | ||
|         [thePanel setExtensionHidden:NO];
 | ||
|       }
 | ||
|       CFRelease(type);
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   // set up default directory
 | ||
|   NSString* theDir = PanelDefaultDirectory();
 | ||
|   if (theDir) {
 | ||
|     [thePanel setDirectoryURL:[NSURL fileURLWithPath:theDir isDirectory:YES]];
 | ||
|   }
 | ||
| 
 | ||
|   // load the panel
 | ||
|   nsCocoaUtils::PrepareForNativeAppModalDialog();
 | ||
|   [thePanel setNameFieldStringValue:defaultFilename];
 | ||
|   int result = [thePanel runModal];
 | ||
|   nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
 | ||
|   if (result == NSFileHandlingPanelCancelButton) return retVal;
 | ||
| 
 | ||
|   // get the save type
 | ||
|   NSPopUpButton* popupButton = [accessoryView viewWithTag:kSaveTypeControlTag];
 | ||
|   if (popupButton) {
 | ||
|     mSelectedTypeIndex = [popupButton indexOfSelectedItem];
 | ||
|   }
 | ||
| 
 | ||
|   NSURL* fileURL = [thePanel URL];
 | ||
|   if (fileURL) {
 | ||
|     nsCOMPtr<nsIFile> localFile;
 | ||
|     NS_NewLocalFile(u""_ns, true, getter_AddRefs(localFile));
 | ||
|     nsCOMPtr<nsILocalFileMac> macLocalFile = do_QueryInterface(localFile);
 | ||
|     if (macLocalFile && NS_SUCCEEDED(macLocalFile->InitWithCFURL((CFURLRef)fileURL))) {
 | ||
|       *outFile = localFile;
 | ||
|       NS_ADDREF(*outFile);
 | ||
|       // We tell if we are replacing or not by just looking to see if the file exists.
 | ||
|       // The user could not have hit OK and not meant to replace the file.
 | ||
|       if ([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]])
 | ||
|         retVal = returnReplace;
 | ||
|       else
 | ||
|         retVal = returnOK;
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   return retVal;
 | ||
| 
 | ||
|   NS_OBJC_END_TRY_BLOCK_RETURN(0);
 | ||
| }
 | ||
| 
 | ||
| NSArray* nsFilePicker::GetFilterList() {
 | ||
|   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
 | ||
| 
 | ||
|   if (!mFilters.Length()) {
 | ||
|     return nil;
 | ||
|   }
 | ||
| 
 | ||
|   if (mFilters.Length() <= (uint32_t)mSelectedTypeIndex) {
 | ||
|     NS_WARNING("An out of range index has been selected. Using the first index instead.");
 | ||
|     mSelectedTypeIndex = 0;
 | ||
|   }
 | ||
| 
 | ||
|   const nsString& filterWide = mFilters[mSelectedTypeIndex];
 | ||
|   if (!filterWide.Length()) {
 | ||
|     return nil;
 | ||
|   }
 | ||
| 
 | ||
|   if (filterWide.Equals(u"*"_ns)) {
 | ||
|     return nil;
 | ||
|   }
 | ||
| 
 | ||
|   // The extensions in filterWide are in the format "*.ext" but are expected
 | ||
|   // in the format "ext" by NSOpenPanel. So we need to filter some characters.
 | ||
|   NSMutableString* filterString = [[[NSMutableString alloc]
 | ||
|       initWithString:[NSString
 | ||
|                          stringWithCharacters:reinterpret_cast<const unichar*>(filterWide.get())
 | ||
|                                        length:filterWide.Length()]] autorelease];
 | ||
|   NSCharacterSet* set = [NSCharacterSet characterSetWithCharactersInString:@". *"];
 | ||
|   NSRange range = [filterString rangeOfCharacterFromSet:set];
 | ||
|   while (range.length) {
 | ||
|     [filterString replaceCharactersInRange:range withString:@""];
 | ||
|     range = [filterString rangeOfCharacterFromSet:set];
 | ||
|   }
 | ||
| 
 | ||
|   return
 | ||
|       [[[NSArray alloc] initWithArray:[filterString componentsSeparatedByString:@";"]] autorelease];
 | ||
| 
 | ||
|   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
 | ||
| }
 | ||
| 
 | ||
| // Sets the dialog title to whatever it should be.  If it fails, eh,
 | ||
| // the OS will provide a sensible default.
 | ||
| void nsFilePicker::SetDialogTitle(const nsString& inTitle, id aPanel) {
 | ||
|   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
 | ||
| 
 | ||
|   [aPanel setTitle:[NSString stringWithCharacters:(const unichar*)inTitle.get()
 | ||
|                                            length:inTitle.Length()]];
 | ||
| 
 | ||
|   if (!mOkButtonLabel.IsEmpty()) {
 | ||
|     [aPanel setPrompt:[NSString stringWithCharacters:(const unichar*)mOkButtonLabel.get()
 | ||
|                                               length:mOkButtonLabel.Length()]];
 | ||
|   }
 | ||
| 
 | ||
|   NS_OBJC_END_TRY_IGNORE_BLOCK;
 | ||
| }
 | ||
| 
 | ||
| // Converts path from an nsIFile into a NSString path
 | ||
| // If it fails, returns an empty string.
 | ||
| NSString* nsFilePicker::PanelDefaultDirectory() {
 | ||
|   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
 | ||
| 
 | ||
|   NSString* directory = nil;
 | ||
|   if (mDisplayDirectory) {
 | ||
|     nsAutoString pathStr;
 | ||
|     mDisplayDirectory->GetPath(pathStr);
 | ||
|     directory =
 | ||
|         [[[NSString alloc] initWithCharacters:reinterpret_cast<const unichar*>(pathStr.get())
 | ||
|                                        length:pathStr.Length()] autorelease];
 | ||
|   }
 | ||
|   return directory;
 | ||
| 
 | ||
|   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
 | ||
| }
 | ||
| 
 | ||
| NS_IMETHODIMP nsFilePicker::GetFile(nsIFile** aFile) {
 | ||
|   NS_ENSURE_ARG_POINTER(aFile);
 | ||
|   *aFile = nullptr;
 | ||
| 
 | ||
|   // just return the first file
 | ||
|   if (mFiles.Count() > 0) {
 | ||
|     *aFile = mFiles.ObjectAt(0);
 | ||
|     NS_IF_ADDREF(*aFile);
 | ||
|   }
 | ||
| 
 | ||
|   return NS_OK;
 | ||
| }
 | ||
| 
 | ||
| NS_IMETHODIMP nsFilePicker::GetFileURL(nsIURI** aFileURL) {
 | ||
|   NS_ENSURE_ARG_POINTER(aFileURL);
 | ||
|   *aFileURL = nullptr;
 | ||
| 
 | ||
|   if (mFiles.Count() == 0) return NS_OK;
 | ||
| 
 | ||
|   return NS_NewFileURI(aFileURL, mFiles.ObjectAt(0));
 | ||
| }
 | ||
| 
 | ||
| NS_IMETHODIMP nsFilePicker::GetFiles(nsISimpleEnumerator** aFiles) {
 | ||
|   return NS_NewArrayEnumerator(aFiles, mFiles, NS_GET_IID(nsIFile));
 | ||
| }
 | ||
| 
 | ||
| NS_IMETHODIMP nsFilePicker::SetDefaultString(const nsAString& aString) {
 | ||
|   mDefaultFilename = aString;
 | ||
|   return NS_OK;
 | ||
| }
 | ||
| 
 | ||
| NS_IMETHODIMP nsFilePicker::GetDefaultString(nsAString& aString) { return NS_ERROR_FAILURE; }
 | ||
| 
 | ||
| // The default extension to use for files
 | ||
| NS_IMETHODIMP nsFilePicker::GetDefaultExtension(nsAString& aExtension) {
 | ||
|   aExtension.Truncate();
 | ||
|   return NS_OK;
 | ||
| }
 | ||
| 
 | ||
| NS_IMETHODIMP nsFilePicker::SetDefaultExtension(const nsAString& aExtension) { return NS_OK; }
 | ||
| 
 | ||
| // Append an entry to the filters array
 | ||
| NS_IMETHODIMP
 | ||
| nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter) {
 | ||
|   // "..apps" has to be translated with native executable extensions.
 | ||
|   if (aFilter.EqualsLiteral("..apps")) {
 | ||
|     mFilters.AppendElement(u"*.app"_ns);
 | ||
|   } else {
 | ||
|     mFilters.AppendElement(aFilter);
 | ||
|   }
 | ||
|   mTitles.AppendElement(aTitle);
 | ||
| 
 | ||
|   return NS_OK;
 | ||
| }
 | ||
| 
 | ||
| // Get the filter index - do we still need this?
 | ||
| NS_IMETHODIMP nsFilePicker::GetFilterIndex(int32_t* aFilterIndex) {
 | ||
|   *aFilterIndex = mSelectedTypeIndex;
 | ||
|   return NS_OK;
 | ||
| }
 | ||
| 
 | ||
| // Set the filter index - do we still need this?
 | ||
| NS_IMETHODIMP nsFilePicker::SetFilterIndex(int32_t aFilterIndex) {
 | ||
|   mSelectedTypeIndex = aFilterIndex;
 | ||
|   return NS_OK;
 | ||
| }
 |