macOS Save and Open Dialog Classes for accessing more properties

OK, that’s odd. It now seems to be the same in the standard open file dialog box. I’m sure it didn’t used to be.

Well yes. So you’ve declared that this type should include everything that conforms to “public.plain-text” which is any file type that contains text. You’ve also declared a mime type of text/plain which also declares that.

So… UTIs declare two different conformance types. There’s type conformance and functional conformance. You may need to define the functional one as well.

As far as why it works in the built in version, we would need to ask Xojo if they’ve implemented that callback to do the filtering. I am surprised that it’s slow for you though. It should only be asking for the files that are currently visible, so I’d expect that to be relatively fast.

Perhaps @William_Yu can shed some light on that.

No “public.plain-text” is the type I’ve asked for, not a conforms to.

I can’t see that adding things to Conforms too should include items in the dialog. I mean I’ve included Public.item, which includes just about ever file on the system. That doesn’t cause them all to be available.

Right, but public.plain-text could be in the “conforms to” list for any of the types you are seeing. By defining that as the type you want, the system is showing all files with that type and which conform to that type.

Apple’s docs are very clear that UTIs are designed to make it easier to include files that are of similar internal structure regardless of what the file extension is since it’s so easy for a user to change.

I found an article that says that the setAllowedContentTypes: selector takes both UTIs and file extensions, but I can’t tell how old the article is. Apple’s docs clearly state that it is meant to take an array of UTType which are UTIs.

NOTE: I suspect that Xojo is using the now deprecated property allowedFileTypes which takes a string, but is marked as deprecated as of macOS 12. It does work in macOS 13, but there’s no telling when it will disappear. I’ve added it to the Github project.

Usage would be

dialog.filter = Array("txt", "text")

Keep in mind that this array is treated as a bunch of “OR” statements, so if you also supply “public.text” you’ll still get the items that you don’t want.

As I said Xojo now seems to match the behaviour of your system. Which I’m sure didn’t happen previously. The problem is that mapping the file back to a type relies on the extension being listed against the type. Which puts you in an almost impossible position, It shows you any extension it feels like but only maps to the type if you list the extensions in the filetype.

For example it shows .sql and .php files but when you get the FolderItem back from the dialog it has a blank Type, because .sql and .php isn’t listed in the Filetype that caused it to show. When you then perform a select case on the file type it doesn’t match anything.

You can never win as anyone could install an application that defines another extension that conforms to the public.text, let’s say .PurpleFrog as an example. The Open file dialog will then show that file and Xojo will not map it to a Filetype as the new (.PurpleFrog) extension isn’t listed on the Filetype.

A long standing bug report of mine, complains about this point.
FolderItem.Type property is defined solely by the file extension

Unfortunately UTIs came after Xojo’s implementation of FileTypes. The problem is that Xojo is trying to support everything that has ever existed for file types while Apple is just pushing ahead with whatever their latest idea is. Apple clearly wants you to use UTIs, but there’s a lot of legacy Xojo/Real code out there that would just suddenly stop being able to open or save files if they adopt the new system. It’s a real problem and I don’t envy them having to solve it at some point.

Not to mention that file extensions and mime-types are the way files are identified on Windows & Linux. Xojo really cannot win here.

To be fair, Apple is very clear on this. UTIs define categories of files. If you specify public.text-plain, you are saying that your app accepts anything that declares itself as plain text, and you wouldn’t want and app like BBEdit to have to know about every possible file extension of plain text file to be able to open it. That’s why this is the way it is, so defining that your app accepts public.text-plain means that the OS is doing exactly what it’s supposed to be doing. If your app creates its own proprietary .txt or .text files then it should be declaring them with your own specific Identifier (and possibly its own file extension), and the “File type is unique to this app” checkbox should be checked. If you still want your files to be opened in apps like BBEdit, you would then put public.plain-text in the “Conforms To” field.

As far as the Folderitem.Type property goes, I don’t think anyone should be relying on that property on macOS anyway. What it’s showing you there is the “Display Name” of the type, which could just as easily be a blank string (which causes dialog problems anyway). My guess is that FolderItem is doing some magic behind the scenes to look up to see if there are any FileTypes in the application whose extensions match the extension of the item. That approach is obviously fragile in this case and it probably should be also looking at the Type Identifiers on macOS.

Yes, but by putting public.plain-text in the conforms to you are adding another extension that is going to be visible to the open file dialog and your app is going to be exposed to it. You are then not going to know what type it is as Xojo is not going to tell you, as it only maps extensions to types. It is designed to fail.

If Xojo mapped the extension to the type I wouldn’t complain in the slightest, which extensions showed I would know from the type what the Filetype was and be able to act accordingly.

For now I’ve implemented the extension filter in the NSSavePanel_ShouldEnableItem method. When I say it is slow it is because the files show as disabled and the enable a moment or two later, which is visually off-putting. Needs must until UTI is better mapped to Filetypes.

You’re thinking about this too hard.

What appears in the Xojo Conforms To field is all about how the system treats the files that are written by your app. If your app writes a file with the .txt or .text extension, the items listed in the Conforms To field affect that file.

When it comes to the file dialogs, they only accept what’s in the file type’s identifier field.

But I think the disconnect that you are having is that public.text-plain itself conforms to other things. if you run

mdls path/to/file.txt

you’ll see there’s a section called kMDItemContentTypeTree, which in this case says:

"public.plain-text",
"public.text",
"public.data",
"public.item",
"public.content"

When an open dialog is shown where the type that you’ve specified is public.plain-text, this file would appear. The trick is that if you’ve specified any of the types in the conforms to list, this file will also appear because the type conforms to the requirements of those types as well.

Instead of using text (which is way too generic for explanation purposes), let’s try using pictures.

Let’s say you want to create an open dialog that can accept pictures. You know about PNG and JPG files and you create a file type for PNG and one for JPG using the types public.png and public.jpeg respectively. That would work just fine except that your app is missing out on being able to open other types that macOS can open. If we look at the kMDItemContentTypeTree for a PNG file, we find that the next item in the list is public.image. If you made a File Type that specified that as the identifier, your app suddenly gets access to anything that declares itself as conforming to the public.image identifier.

If you’re interested in knowing what types are built-in, you can look here:

in addition to just text, there are definitions for UTF8 and UTF16 text as well as delimited text.

Just as a side note, that page defines a UTType called UTTypePHPScript with the following hierarchy:

Type Identifier
UTTypePHPScript public.php-script
UTTypeShellScript public.shell-script
UTTypeScript public.script
UTTypeSourceCode public.source-code
UTTypeText public.text

But that only works if the PHP file that you have was created using the public.php-script UTI. An app could declare a different hierarchy which includes public.plain-text and suddenly it appears in your app.

Personally I find UTIs to be a very elegant solution to a very complex issue, but it does take a little time to wrap your head around them and how they behave. For instance, each type doesn’t necessarily have only one “parent” type. public.image is a subtype of both public.content and public.data. Additionally, there are both form and function types. Form being “this file is plain text” whereas Function being “this file is a database” (see public.database)

IMHO, Apple made a huge mistake in not bringing forward their old documentation on this subject, but it’s thankfully still available.

3 Likes

As far as Xojo goes the massive issue with this is when you are provided with a Folderitem from the system the type only maps to a Xojo Filetype if the extension is listed on the Filetype. At which point the system falls apart as you don’t know what type of file you are trying to open, because you haven’t registered the extension. It is impossible to register every extension because an app can come along and register a new type that conforms to a type you support and you have a new extension that you cannot in any way deal with.

I suppose when given a Folderitem that has no type I could try and query macOS to find out what types it conforms to myself.

I’ve just made a slight optimisation to my filter which helps a little. I was extracting the extension and then comparing it with a list of all valid extensions I had built at startup. I realised I could just check if the FolderItem.Type was blank.

I wish that was better explained in the Xojo docs.

1 Like

Hmm, the lazy evaluation of the NSSavePanel_ShouldEnableItem method creates a problem with the dialog. If you click on a file in the list the dialog would normally jump to the first file that starts with that letter. When the filter system is in place files that are not on screen are all disabled, leading to it failing to jump to the given location, even though if you scroll to it it gets enabled.

In the end I’ve dropped the ShouldEnableItem attempts as it has two many issues (slight delays and lack of keyboard response). Instead I’ve just treated the lack of FolderItem.Type as if it was “public.plain-text”. It is the only type I’m using that could produce additional extensions, so for now I’ll stick with that.

Or just use the change to Filter that I made today which allows you to limit based on file extensions instead of file types.

To be honest, it’s not very well explained on Apple’s docs either. You have to go look at those older pages that I linked to to get any meaningful info about it and I was only able to find one short paragraph on the main Uniform Type Identifier page about it. Most of what I know about UTIs is from experience.

2 Likes

I worried that using a deprecated API would be an issue for the App store?

Depending on how far back you intend to support, you may need to try because the UTType class (which is used for the undeprecated methods) only became available in the macOS 11 SDK.

There are methods on UTType for creating them using extensions and I can certainly add those, but it won’t change the fact that the app still needs references to the old functions for use with 10.14 and 10.15.

I can no longer get my VMs to start (VirtualBox 7) to try earlier versions (over on my Intel box).

Just pushed out some changes that will fix file type functionality on 10.14-10.15 and extension functionality on macOS 11+.

1 Like