Export NSDocument in Mac app - macos

How do you export a NSDocument in one format into another NSDocument in another format?
I would like to implement the typical Export option in my document-based app. I'm not sure where I should put the format conversion code, and what is already provided by Cocoa.

All the writing options in NSDocument get a string parameter to specify the type of file that should be written. So in your dataOfType:error: or fileWrapperOfType:error: methods you should implement the conversion code for each file type you want to support.
To start your export operation you can use the method saveToURL:ofType:forSaveOperation:completionHandler: with the desired type and a save operation of NSSaveToOperation.
For more information on the methods you can override to support loading and saving document data take a look at this programming guide.
You can get the available types from the class method writableTypes or the instance method writableTypesForSaveOperation:, again with NSSaveToOperation.
The file types you want to support need to be declared in your Info.plist file.

If your NSDocument subclass supports in-place autosaving, and all writable types are also readable (as they should be), I would recommend to use the already provided type-conversion workflow, where the user should use "Duplicate" followed by "Save".
In this workflow, when the user "Duplicate" the document, it's written/copied to a temporary file (where autosaved files are saved) as an untitled document. When the user closes the document window, the app suggests her to save the document or delete it. Since the document has no permanent URL yet, an NSSavePanel will appear with an accessory view that lets the user to select the document type.
In this solution everything is already provided by Cocoa and you don't have to do anything to support a special "Export" functionality as the user can use "Duplicate" followed by "Save".
You only have to be able to save your document to all writable types from dataOfType:error: or in fileWrapperOfType:error: according to the typeName argument (as Sven said).
The advantage here is that the user has to choose the URL only when she closes the file (and chooses not to delete it) - and is compatible with the new workflow in document-based apps where the "save as" operation has been replaced by "duplicate" followed by "save".
Note that you also have to make sure that you can duplicate documents of non-writable documents (you can achieve that by copying the original file instead of using writeSafelyToURL:ofType:forSaveOperation:error:).

Related

Dragging file with Alt+Cmd from Finder does not use NSDragOperationLink flag in draggingSourceOperationMask

I have a simple app that accepts dropping files from Finder.
I want to support 3 different dragging types:
move
copy
link (make alias)
When pressing Alt+Cmd while dragging, Finder usually creates an alias (link). However, in my app in draggingEntered: the flag NSDragOperationLink is not set in that case.
Below are the various flags for the different modifier key combinations:
move (no keys): Private, Delete, Copy, Generic, Link, Move, All_Obsolete, Every
copy (Alt) : Copy, All_Obsolete, Every
link (Alt+Cmd): Copy, Generic, All_Obsolete, Every
Note how in the last case the Link flag is not set. How could I tell in performDragOperation: that I need to create an alias?
Do I really have to check the modifier keys in the current event? I'd much rather have a clean solution via the source dragging operation mask...
Tested on 10.8.5 and 10.9.
You need to register first your dragged types in the code using
registerfordraggedtypes: api
Yes, I know it's a very old thread but it still comes up in search results.
For drag-and-drop operations that span applications you have to hold down Control to filter for the Link flag.
In the online documentation for NSDraggingInfo draggingSourceOperationMask there's a table showing how the modifier keys are handled but it's wrong (it seems to be describing how the Finder works). If you can dig up older documentation on the drag-and-drop API it shows a different filtering algorithm where Option filters for Copy, Command filters for Generic, and Control filters for Link. That's the filtering algorithm that the OS still applies to inter-application drag-and-drop.

How can I intercept the result of openDocument before NSDocument loads?

I am writing a program which will load and process XML data. If the XML file contains a single XML 'Dictionary' then it will need to open an NSDocument window (so far, so good - I can do this!), but if the XML file contains an array of Dictionaries then it should open up a list window, from which the individual Dictionaries can be opened into an NSDocument.
Because File->Open sends an action to First Responder->openDocument before the document window opens, I think that the openDocument function is not part of NSDocument. I'd like, therefore, to be able to intercept the open function before it hands off to NSDocument - just to check if the document is one that I want to be opening as a document rather than as my natty list view.
If, on the other hand, openDocument is an NSDocument function, how can I quietly close the NSDocument window and hand the XML list to my list window without raising an error in this one scenario? Of course, I don't want to suppress errors altogether - because there may be legitimate reasons to raise an error (unreadable file, bad syntax etc)
I realise that what I'm trying to do is a little unorthodox - but hopefully its possible. Any ideas?
openDocument: is an instance method of NSDocumentController.
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSDocumentController_Class/Reference/Reference.html#//apple_ref/doc/uid/20000030-openDocument_
You can subclass the NSDocumentController with your own. This will allow you to intercept openDocument:.
If you want to check the file before creating a document, you'll need to use NSOpenPanel for the open file dialog. Then call openDocumentWithContentsOfURL:display:completionHandler: when you want to create the document. If you don't want to create the document, you can trigger whatever you want to do instead.
Just add a function to handle openDocument to your app delegate
func openDocument(sender: AnyObject) {
print("openDocument got called")
}
run app and press cmd+o

NSApplication orderFrontStandardAboutPanel: Making my about panel slightly less standard

What are my options, if any, of adding additional, arbitrary data to the standard Cocoa about dialog that is displayed by an NSApplication when it receives a orderFrontStandardAboutPanel message.
If you add a file named Credits.rtf to Resources the contents will automatically be used in the expanded standard about panel and you can put whatever info you want in the file. It will still pull the standard copyright, version info, etc from the info.plist. It is the easiest way I know of to add arbitrary info, otherwise you pretty much will have to roll your own about panel.
-[NSApplication orderFrontStandardAboutPanelWithOptions:]
Expanding further on the answers from Darrell Root and theMikeSwan above, Apple's documentation for the credits property of NSApplication.AboutPanelOptionKey states:-
The value of this key is an NSAttributedString displayed in the info
area of the panel. If not specified, AppKit then looks for a file
named “Credits.html”, “Credits.rtf”, and “Credits.rtfd”, in that
order, in the bundle returned by the Bundle class method main. The
first file found is used. If none is found, the info area is left
blank.
Expanding on theMikeSwan's answer, by accident I found that if you add a file named Credits.html to the Resources folder, it's contents get used in the expanded standard about panel. In fact Credits.html appears to override a Credits.rtf.
So your choice whether to use html or rtf format, or wire up "About" to a completely different custom window controller.

Add to the "Open Recent" menu an item that doesn't point to a file

Is there a way to add an item that doesn't point to a file that exists on the file system to the "Open Recent" menu?
In an application not based on NSDocument, I can add an item to the "Open Recent" submenu with the following code:
[[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL:[NSURL URLWithString:stringToFilePath]];
It works as documented, as long as the URL points to a file that exists on the file system.
If the url doesn't point to a file on the system, such as a web url, or a custom url scheme, nothing happens.
For example, this code has no effect, and produce no log during execution, even if my app handles the scheme used in the URL:
[[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL:[NSURL URLWithString:#"http://www.stackoverflow.com"]];
Update: someone found (a long time ago) a way to tweak this menu to have it show files whether they exist or not: http://lists.apple.com/archives/cocoa-dev/2007/Apr/msg00651.html
I successfully managed to subclass NSDocumentController, but my override of the method - (NSArray *)recentDocumentURLs is never called.
It's not very surprising, as the doc says:
This method is not a good one to
override since the internals of
NSDocumentController do not generally
use it.
But the doc doesn't say what to use instead and the poster didn't give more detail. Any idea?
If there is no solution, on workaround would be to rewrite the entire menu from scratch.
If possible, I would prefer to avoid that, for all the stuff I get for free (like when you have two items with the same name, it displays the parent directory as well to help differentiate them).
It looks like you'll probably have to create your own menu and maintain your own separate list. This menu automatically excludes files that don't exist.
I believe this is also true of files on removable media that is absent (ie, if the media comes back, the I believe the file is once again available in the list if it hasn't been pushed off by more recent items).

NSSavePanel: Squelching the "confirm replace?" dialog

In the Nav Services world one could specify kNavDontConfirmReplacement as an option to create a NavDialogRef that would not ask the user to confirm the replacement of a file when saving with a file name that already exists. How do I specify an equivalent behavior with the Cocoa NSSavePanel?
Here's how it can be done:
Add a delegate to handle NSSavePanel callbacks
Override - (NSString*)panel:(id)sender userEnteredFilename:(NSString*)filename confirmed:(BOOL)okFlag in your delegate
In the delegate:
If okFlag is false, return filename
Otherwise, retain filename as an NSString* in your delegate
Return some unique string that is highly unlikely to be the name of an actual file
When NSSavePanel returns to your code, pull the value of filename from your delegate method, and discard whatever filename NSSavePanel told you (which should be your unique string).
Since userEnteredFilename: is called by the OS before the confirm-replace check is made it gives you a chance to get what the user specified without letting the OS in on the secret. The unique string will assure that the confirm-replace dialog is not popped accidentally.
Gross but efficacious.
No, there is no easy way to do this with NSSavePanel. In theory you could extend NSSavePanel with a category and override certain private methods. I took a quick look though and there is nothing simple about it.
Your customers is going to expect the exact confirmation alert when faced with a NSSavePanel, so don't customize it.
I'm not sure what kind of customized confirm-overwrite dialog you are planning, but might I suggest you use a NSOpenPanel instead, and customize this dialog box with a "Create New File" button? (I believe you can do this via setAccessoryView API.)
For example, if you are asking your customer to choose a file to append new data to, the NSOpenPanel will work quite well; and if the customer want to save the new data to a new file (instead of appending to an existing file), the "Create New File" button is just an additional click.

Resources