macOS Save and Open Dialog Classes for accessing more properties

Here’s a project that will let you access Apple’s NSSavePanel and NSOpenPanel through two classes that are completely declare driven.

MacFileDialogTest

I’ve matched the property names to what Xojo uses where I could, everything else uses the Apple names.

Note: In these classes, Filter takes an array of FileType objects, not strings, because I used the Uniform Type Identifiers to look up the underlying classes.

8 Likes

Thank you for this share.

This item has been moved to Github.

5 Likes

@Greg_O Sorry for the necro, but…

Wow, that looks very useful. I’ve just downloaded it and taken a quick look. One thing I don’t quite understand. If you add an accessory view to the save dialog it seems to remove the popup menu that lists the file types you are able to save in. Typically a standard dialog with a filter added will look like this:

But if you add your own accessory view that goes away:

I’m assuming that I would have to put them into the view myself. However, I would then have to let the save dialog know that I had changed the type to filter on… any pointers welcome.

I believe the answer is that you need to add it yourself. I see no indication on the NSSavePanel docs that say that this menu is automagically created for you so it’s probably something that the Xojo framework does.

That said, it’s just a container control and everything you need to duplicate that exists in the FileType class. The Name property is what you would show the user and you could make the row tags be the file types themselves. When the pop up menu changes, you could change the extension on the current file name to match.

I am looking into an issue where the options panel isn’t the correct height though. The docs say it should expand to the correct height, but I’m guessing that relies on constraints now which are not there.

OK, I kinda thought that would be the case. I’m not sure how I can effect the file name in real time though. I’ll look into the API and the code.

As for the hight of the panel it does change in response to changes in the height of the container control. It doesn’t seem to honour the Xojo control lock conditions. (top, left, bottom, right).

So looking at it I can keep a reference to the NSSavePanelGTO (dlg) in the container control and then use SuggestedFileName method to change the filename.

“There can be only one” accessory view per NSSavePanel, AFAIK the Xojo format selector is an accessory view.

Somewhere, sometime ago, I filed a feature request with Xojo to allow us to specify our own accessory panel, as Xojo have all the pieces in place.

Maybe you can. I’ve been looking into this and it doesn’t always change. There are several StackOverflow articles about it.

The other issue is that Apple changed the way this is done recently. I’ll see if I can figure this out tomorrow.

Nope, it didn’t work.

This, would suggest that you need to use:

- (IBAction) selectFileType:(id)sender
{
    [savePanel setAllowedFileTypes:@[ [[sender selectedCell] title] ] ];
    // this will set the right extension
}

looking into that.

Wow that was painful, but I figured this out. Working on cleaning up the changes for the repo.

For anyone that’s curious, if you want to get the extension to automagically change, you need to reset the filter on the panel when the user changes the type.

Now, the thing that tripped me up was that I had an imported text filetype where the Type Identifier was set to public.text. for some reason, that one doesn’t work and just changing the UTI to public.txt and moving public.text up to the “conforms to” field made it work. Thanks Apple.

1 Like

Ok, I’ve updated the repo with the new features.

2 Likes

OK, that does work. Now to see if it can cope with my export options :slight_smile:

Great thank you.

Sorry, another problem. The AccessoryView on NSOpenPanel needs to be notified when the user selects a file in the main dialog area. So it can configure its self for the appropriate options. I thought for a moment that the ItemsSelected event was going to do the trick, but that only fires when you press the OK button.

Sorry to be so persistent, but it is nearly perfect.

I don’t really have time to work on this right now, but if you want to fork the project and make a pull request, i’d be happy to look at it next week.

Sorry, no problem at all. Not at all urgent, thanks.

I’m already looking at the docs and it seems to required a “NSOpenSavePanelDelegate” and for that to implement a function

func panelSelectionDidChange(Any?)

I’ll look further. If I get anywhere I will obviously push the changes back to the repo.

OK, I think I’ve got a handle on this, I’ll take a look further on Monday.

I’ve got this working now. Will check something in shortly.

Ok, it’s almost working. Several of the events are triggering more than once so what I’m going to do is check this stuff in on a branch and hopefully get some feedback from others that might have an idea about what’s going on.

It’s only the three methods where the return type is void:

  • panelSelectionDidChange
  • panel:didChangeToDirectoryURL
  • panel:willExpand (this one fires three times, False, True, False or True, False, True)

The changes are located in “dev/set_up_callback_delegate”

That’s very good of you. It certainly seems to do the job. The main thing I was concerned about was if the file would be openable in the selection event (prior to the user clicking open (or what ever the go button is). The good news is that seems to be the case, so things like my preview panel, auto delimiter chooser etc will work.

I can see you’ve done some work to prevent the duplicates from firing at the Xojo level, which all seems to work well.

One thing I can see is that the mDelegateCache dictionary never seems to get tidied up when the dialog is dismissed. Looking at the code it appears that this dictionary holds a collection of open dialogs and deals with sending the events to the correct instances of the dialog. So if you have two open dialogs on different windows each would get the correct events for the right instance. However, the dictionary never seems to remove old instances when they are done with, resulting in a tiny member creep over time. This is also a problem with macOSlib and the way it copes with the same dispatch cache. Is there a reason behind this or is it an oversight?