NSSavePanel: Squelching the "confirm replace?" dialog - cocoa

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.

Related

Make "Close All" (cmd+option+W) apply only to one type of document windows

I have an application that manages different types of NSDocument subclasses (along with matching NSWindow subclasses).
For instance, it's possible that the app has one window of type A open, and two windows of type B.
Now, if a window of type B is active, and the user chooses "Close All" or hits cmd+option+W, all my app's windows are sent the close message.
But I only want all of the active window type's windows closed instead, i.e. only the two type B, not the type A window. How do I accomplish this?
I currently have no explicit menu entry for "Close All". Instead, macOS provides that automagically. If there perhaps a way to intercept a "closeAll" message? Can't find one, though.
AppKit will add the Close All menu item if there isn't one. Add an alternate menu item item with key equivalent cmd+option+W below the Close menu and connect it to your own action method.
You might succeed with overriding your document's canClose(withDelegate:,shouldClose:,contextInfo:) to return whether a document should be closed.
If this doesn't behave the way you want, you can create a subclass of NSDocumentController (if you don't have one already). Details on how to do that vary, but usually you have main (menu) XIB or main Storyboard, which has a "Document Controller" object: set its class to your custom class.
Then override closeAllDocuments(withDelegate:,didCloseAllSelector:,contextInfo:) and implement your custom logic.
Note that you should detect whether your app is about to quit and then really do close all you documents (unless you really want to prevent the app quit, e.g. because a document is dirty).
After some digging I figured out where the auto-generated "Close All" menu item sends its action to: To the closeAll: selector of the application target:
Thus my solution is to subclass NSApplication and implement the handler there, which then simply closes all windows that are of the same type (this assumes that I use specific subclasses for my different types of windows):
- (IBAction)closeAll:(id)sender {
Class windowClass = self.keyWindow.class;
for (NSWindow *w in self.windows) {
if (windowClass == w.class) {
[w performClose:sender];
}
}
}
Caution: If you adopt this pattern be aware that:
The closeAll: selector is not documented nor mentioned in the header files, meaning that Apple might feel free to change in a future SDK, though I find that unlikely. It will probably not break anything if that happens, but instead your custom handler won't be called any more.
The code simply tells all windows to close, ignoring the fact that one might reject to be closed, e.g. by user interaction. In that case you may want to stop the loop instead of continuing to close more windows (though I know of no easy way to accomplish that).

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

Export NSDocument in Mac app

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:).

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).

Setting value of AXTextField programmatically (OS X Cocoa Accessibility API)

I'm using the Cocoa Accessibility API to try and modify the value of a text field (AXTextField) in another application, but I've run into a problem: my code correctly identifies and modifies the contents of the text field in question, and the text of the field visibly changes, but the changes aren't registered by the program I'm trying to control. Is there a way to do this in with the API without having to generate keyboard events?
Sample code:
AXUIElementCopyElementAtPosition(appRef,
clickPoint.x,
clickPoint.y,
&boxRef);
NSString *valueToSet = [NSString stringWithFormat:#"%f",amount];
AXUIElementSetAttributeValue(boxRef,kAXValueAttribute,valueToSet);
And the text field changes to the value specified in "amount" but the other program doesn't recognize the change - I have to go type the number in myself to get it to pick up the change (I can tell the difference, because the program responds when a new value is typed in the box). Can anyone point me in the right direction?
For posterity: Informed sources tell me that this is actually a bug in the application I'm trying to control. You can tell the difference by using UI Browser (http://prefabsoftware.com/uibrowser/) to try and set the value of the textfield; if UI Browser can't make the change stick, then the matter is out of your control.
Try telling the text field:
perform action "AXConfirm"
This is Applescript, but whatever the Cocoa equivalent is, it may make the change stick even if UI Browser can not (I've used it before).

Resources