How do I dismiss an NSPanel when creating or opening a new document? - cocoa

I am working on a document-based Cocoa application. At startup, the user is presented with a "welcome panel" (of type NSPanel) with buttons for common actions like "Create New Document" and "Open Existing Document". These actions are linked to the first responder's newDocument: and openDocument: actions, respectively, just like the matching items in the File menu.
Everything works as expected...with three caveats:
The welcome panel is not dismissed when creating or opening a new document.
Document windows do not have focus when they are created.
Open document windows do not have the open file represented in the window title bar; likewise, new document windows do not get created with titles like "Untitled", "Untitled 2", "Untitled 3", etc., as expected. (I'm mentioning this not only because it's annoying, but because it may yield some insight into what's going wrong.)
I have partially solved #1 by making my application controller a delegate of the welcome panel. When clicking the "Open Existing Document" button, the panel resigns its key status (since a file browser dialog is being opened), so I can close the panel in the delegate's windowDidResignKey: method. However, I can't figure out how to close the panel when creating a new document, since I can't find a notification that is posted, or a delegate method that is called, when creating a new document. And ultimately, #2 is still a problem, since the document windows don't gain focus when they're created.
I have only subclassed NSDocument -- I'm not using a custom document or window controller at all. I've also tried changing the panel to an NSWindow, thinking that an NSWindow may behave differently, but the same problems are occurring.

Make a custom document controller, and have it know about your Starting Points panel's controller, and hide the window in addDocument: and show it again (if no other documents remain) in removeDocument:.
This is what we did in Adium Xtras Creator. That code is under a BSD license (unlike Adium proper), so you can borrow it if you want.

Instead of linking to the first responder's default actions, just create custom action method in your window controller and set your buttons to trigger those actions. In your method, you need to close the welcome window and then create a new document.
Something like this:
- (IBAction)createNewDocument:(id)sender
{
//this will close the window if you're using NSWindowController
[self close];
[[NSDocumentController sharedDocumentController] newDocument:sender];
}
Or if you're not using an NSWindowController for your welcome window you can just message the window directly:
- (IBAction)createNewDocument:(id)sender
{
//assume you have a "window" outlet connected to your welcome window
[window orderOut:sender];
[[NSDocumentController sharedDocumentController] newDocument:sender];
}

Related

how to implement "show recents" item on OS 10.9 and later

I notice on Mac, every app on docker, when it's not opened and ctrl click it, there will be a pop-up menu, inside has a menu item called "show recents", when open the app this item will become "show all windows". I want to know how to make it work cuz right now, when I click it on my own app, it has nothing to show.
I have tested using doc controller and use noteNewRecentDocumentURL: method, but still, both "open recent" item in "file" and "show recents" in docker, it doesn't show my url I just added.
I do not know, where you click on the app to show the menu. The recent documents list is shown
in the app's File menu in the submenu Open Recent
in the dock menu for the app as a list with one item per document
likely at some other places related to the app
However where it is shown, the list is automatically managed by [NSDocumentController sharedDocumentController], which is added to the project template for document-based applications. If you do not have a document-based application you should re-check, whether this would be the better choice. But you can use [NSDocumentController sharedDocumentController] in a non-document-based application as well:
In some situations, it is worthwhile to subclass NSDocumentController in non-NSDocument-based applications to get some of its features. For example, the NSDocumentController management of the Open Recent menu is useful in applications that don’t use subclasses of NSDocument.
Documentation
There is a section Managing the Open Recent Menu containing the description of the method -noteNewRecentDocumentURL: in the documentation of NSDocumentController. You have to send this message to the shared instance,
whenever you want to add an item to the recent docs list:
[[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL:theURLOfTheDocYourAppOpens];
Please note:
Applications not based on NSDocument must also implement the application:openFile: method in the application delegate to handle requests from the Open Recent menu command.
If you do not want to use the document controller, you have to maintain the list yourself and add it to the different locations manually. You can start here. I do not recommend that.
Here's code for use with Xamarin.Mac.
// Add the file to the menu. Note creation of file url.
void AddFileToOpenRecentMenu(string filePath)
{
var fileUrl = NSUrl.FromFilename(filePath);
NSDocumentController.SharedDocumentController.NoteNewRecentDocumentURL(fileUrl);
}
// Open the file selected from the menu (in AppDelegate.cs)
[Export("application:openFile:")]
public override bool OpenFile(NSApplication sender, string filename)
{
var keepFileInMenu = ProcessFile(filename);
return keepFileInMenu;
}

XCode 4.6 - Document.xib + MainMenu.xib action from MainMenu shall set outlet in Document.xib

I have a MainMenu which should call an action in an AppController which then should send a message to an outlet of the MainDocument.
When I instantiate an object in Interface Builder in MainMenu.xib this cannot send a message to an outlet in a different xib, as far I understand.
Is there a solution to this?
Most of the default items in the menu are hooked up to that strange "First Responder" placeholder that you see in Interface Builder. Any action message you send to it will get sent through the responder chain which is probably what you want. Read that linked document for more information.
(It's rare that you'd need to hook up an outlet across multiple .xib files.)
If I understand you correctly, you are looking to notify the MainDocument object when you click on a menu item in the MainMenu? If that is the case, one way is to use NSNotification to post the message. You can review the Apple Docs on how to do this here: https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Notifications/Introduction/introNotifications.html#//apple_ref/doc/uid/10000043i

How can I make my (non-document-based) app respond to openFile:withApplication:?

I have an app, which is a single-window, non-document-based app.
I want to make it respond to NSWorkspace-openFile:withApplication:, but only when the path is to a folder, and also implement the File->Open menu. I'm having trouble tracking down how to do this (without becoming a document-based application).
You have to configure your NSOpenPanel to accept directories:
[myOpenPanel setCanChooseDirectories:YES];
Just check what action the Open menu item is connected to in Interface Builder. If I remember correctly, it would be connected to the "First Responder" object and the method open:. Is that right?
In this case, just implement the open: method in your AppDelegate class. (To understand why the method goes to the delegate, read about "nil-targeted actions" in Hillegass' book, or here: http://www.cocoadev.com/index.pl?NilTargetedAction. The thing to remember is that a control connected to "First Responder" in IB is actually IB's way of denoting that the target is nil.)
Note that you will have to implement the open panel yourself using NSOpenPanel -- see some code for example here: NSOpenPanel setAllowedFileTypes
If this is the same thing as that you're doing in openFile:withApplication:, you will probably want to create a common private method and call that method from both openFile:withApplication: and open:.

Preventing the "Save on Exit" dialogue on exit of a Cocoa Document Application

Hi
I have a Cocoa document based application that I have been building, that allows you to open seperate views and interact with a webview component. However, when ever you have interacted with it, and then go to close the application, a message comes down saying:
"Do you want to save the changes you made in the document “Untitled”?
"Your changes will be lost if you don’t save them."
I wish to make it that when my application is closed, this message is not shown. I do not need any auto-saves or saving options. How do I prevent this message been shown and disable it?
Thanks in advance for any help and support.
Sam
Recently I had the exact same wish to prevent the save dialog from showing up. What I did is put the following method in my custom document class that inherits NSDocument
-(BOOL)isDocumentEdited {
return NO;
}

How do you display a dialog from a hidden window application?

I have developed a COM component (dll) that implements an Edit() method displaying a WTL modal dialog.
The complete interface to this COM component corresponds to a software standard used in the chemical process industry (CAPE-OPEN) and as a result this COM component is supposed to be usable by a range of 3rd party executables that are out of my control.
My component works as expected in many of these EXEs, but for one in particular the Edit() method just hangs without the dialog appearing.
However, if I make a call to ::MessageBox() immediately before DoModal() the dialog displays and behaves correctly after first showing the MessageBox.
I have a suspicion that the problem may be something to do with this particular EXE running as a 'hidden window application'.
I have tried using both NULL and the return value from ::GetConsoleWindow() as the dialog's parent, neither have worked.
The dialog itself is an ATL/WTL CPropertySheetImpl.
The parent application (EXE) in question is out of my control as it is developed by a (mildly hostile) 3rd party.
I do know that I can successfully call ::MessageBox() or display the standard Windows File Dialog from my COM component, and that after doing so I am then able to display my custom dialog. I'm just unable to display my custom dialog without first displaying a 'standard' dialog.
Can anyone suggest how I might get it to display the dialog without first showing an unnecessary MessageBox? I know it is possible because I've seen this EXE display the dialogs from other COM components corresponding to the same interface.
Are you using a parent for the Dialog? e.g.
MyDialog dialog(pParent);
dialog.DoModal();
If you are, try removing the parent. Especially if the parent is the desktop window.
Depending on how the "hidden window" application works, it might not be able to display a window. For example, services don't have a "main message loop", and thus are not able to process messages sent to windows in the process. i.e, the application displaying the window should have something like this:
while(GetMessage(&msg, NULL, 0, 0))
{
if(!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
in WinMain.
This isn't supposed to be reliable - but try ::GetDesktopWindow() as the parent (it returns a HWND).
Be warned - if your app crashes, it will bring down the desktop with it. But i'd be interested to see if it works.
It turns out I was mistaken:
If I create my dialog with a NULL parent then it is not displayed, and hangs the parent application
However if I create my dialog with ::GetConsoleWindow() as the parent then the dialog is displayed; it just fooled me because it was displayed behind the window of the application that launched the parent application
So now I just have to find out how to bring my dialog to the front.
Thanks for the answers ;-)
Whatever you do, do not use the desktop window as the parent for your modal dialog box.
See here for explanation: http://blogs.msdn.com/b/oldnewthing/archive/2004/02/24/79212.aspx
To quote the rationale:
Put this together: If the owner of a
modal dialog is the desktop, then the
desktop becomes disabled, which
disables all of its descendants. In
other words, it disables every window
in the system. Even the one you're
trying to display!

Resources