Document Based Application, preinitialize window (enter serial, buy, trial) - cocoa

I need to create several windows before NSDocument is loaded, or create a window that blocks NSDocument window and top menu.
I tried several solutions - but they didn't work right.
modal window, one after another. there were some problems with Async URLConnection, and some other problems with my NSDocument content.
I created custom MainMenu.xib with no menu, that opens my preinitialize windows.
here i found some other problems, when a file(associated with my application) is opened - the Document Window initializes. Here i tried to subclass NSDocumentController, but i found no way to pause the "open document". (i want the document to be opened anyway, but only after the preinitalize windows would be closed).
So what is the right way to do this?

Implement applicationShouldOpenUntitledFile: in your app delegate to return NO if the user has to go through the not-registered-yet dialog first.
In the action methods for your “Trial” and “Confirm Registration” buttons, create the untitled document yourself (by sending the necessary message to the document controller).

So the right answer is to implement:
* application:openFiles:
* applicationShouldOpenUntitledFile:
and implement your own document creation. this is the way it worked for me.
MyDocument* document = [[MyDocument alloc]
initWithContentsOfURL:fileURL
ofType:[fileName pathExtension]
error:nil
];
if(document)
{
[[NSDocumentController sharedDocumentController] addDocument:document];
[document makeWindowControllers];
[document showWindows];
}
of course you need to write error handling code.

Related

Showing a non-modal template chooser synchronously

I'm writing a Cocoa app using the document architecture. Whenever an untitled document is created in this app, the user should be shown a window that lets them pick a template and prompts for other information. Only one of these windows should show up at a time, and preferably it should be possible to interact with the rest of the application while the template chooser is visible. (This is how Pages behaves.)
I've got most of this working by overriding -[NSDocumentController openUntitledDocumentAndDisplay:error:]:
- (id)openUntitledDocumentAndDisplay:(BOOL)displayDocument
error:(NSError *__autoreleasing *)outError {
TsDocument * doc = [self makeUntitledDocumentOfType:self.defaultType
error:outError];
if(!doc) {
return nil;
}
TsNewWindowController * newController = [TsNewWindowController new];
newController.document = doc;
if([NSApp runModalForWindow:newController.window] == NSRunAbortedResponse) {
if(outError) {
*outError = [NSError errorWithDomain:NSCocoaErrorDomain
code:NSUserCancelledError
userInfo:nil];
}
return nil;
}
[self addDocument:doc];
if(displayDocument) {
[doc makeWindowControllers];
[doc showWindows];
}
return doc;
}
However, as you can see, the window is displayed modally, blocking access to the rest of the app. Is there a simple way to achieve what I want without making the template picker modal?
To explain a couple things more clearly:
I do of course know that -runModalForWindow: will run the window modally—it's right there in the name! I'm looking for another way to display the window that will still block -openUntitledDocumentAndDisplay:error:, or that doesn't require me to block the method at all.
I don't believe I can simply create the document, show the newController's window, and call the document's makeWindowControllers and showWindows later because, if the app quits, Restore will not show the template chooser—it shows the normal editing interface.
You do need to create and use a NSWindowController, but you need to do so before openUntitledDocument…:: is called.
In the unreleased Adium Xtras Creator, I tapped in at several points:
In the application's delegate, in applicationOpenUntitledFile:, I show the template-chooser window and return YES.
In the document controller, in removeDocument:, I pass the message on to super, then check whether there are still any documents open. If not, I show the template-chooser window.
In the document controller, in addDocument:, I hide the template-chooser window, then pass to super.
Thus:
If the user tries to create a new document (of no particular type) by any means, the template-chooser will be shown instead.
If the user creates a new document (of an explicit type) by any means, the template-chooser will be hidden. (The application I did this in had its “New” menu item set up as a submenu containing specific types.)
If the user opens any document by any means, the template-chooser will be hidden.
If the user closes the last open document, the template-chooser will be shown.
If the user or another application tries to reopen the application by any means, the template-chooser will be shown.
You are calling runModalForWindow: so of course it's running the window as a modal window.
Why not just show the window instead? Use an NSWindowController and call showWindow: to display the template window. In your window controller, implement actions that react to the user's selection and then create the appropriate document (or cancel).
I don't think you need to actually create a document in openUntitledDocumentAndDisplay:error:.

Cocoa screen saver config panel floating freely

I'm writing a screen saver using Cocoa's ScreenSaver API. It's compiled for 64-bit arch and I'm running it on Lion.
In order to enable configuration, I have added the following to the main view:
- (BOOL)hasConfigureSheet
{
return YES;
}
- (NSWindow*)configureSheet
{
if (configureSheet == nil) {
if (![NSBundle loadNibNamed: #"WTConfigureSheet" owner: self]) {
NSLog(#"Failed to load config sheet");
return nil;
}
}
ScreenSaverDefaults *defaults =
[ScreenSaverDefaults defaultsForModuleWithName: WTModuleName];
backgroundColorWell.color = [defaults objectForKey: #"BackgroundColor"];
lightLetterColorWell.color = [defaults objectForKey: #"LightLetterColor"];
darkLetterColorWell.color = [defaults objectForKey: #"DarkLetterColor"];
return configureSheet;
}
After installing the saver freshly, clicking "Options" makes the config sheet appear not as a sheet, but floating freely on the screen, without a border. Otherwise, it works correctly and disappears after being dismissed.
When I click "Options" a second time, the config sheet appears again, this time correctly as a sheet of the preferences window. It then immediately freezes, so that I can't click any of its controls.
Does anyone have an idea what causes this behavior?
I had the same problem as you today and it took me quite some time to figure this one out, so here's my solution:
I discovered that the NSWindow appears as soon as you call loadNibNamed:owner:. So there had to be some sort of mechanism to automatically open windows from nibs.
So I re-checked the nib and saw that there is an option called "Visible At Launch" on the attribute inspector pane which is checked by default.
The solution is very simple: just uncheck that checkbox and it works as expected.
I find it easy to overlook since you expect the window to open, but it actually opens twice (once automatically and a second time because System Preferences.app shows it as a sheet) which leads to the glitches.
Another problem that could happen, depending on how you defined the ivar / property on your class is that after the first close and re-open of the window it just freezes.
This is because per default the window releases itself when closed.
So be sure to also uncheck "Release When Closed" in interface builder.
For this code to work as written, you need to create an IBOutlet of type NSWindow* named configureSheet in your main view's header file, save that file so Interface Builder can see the change, then load WTConfigureSheet.xib in Interface Builder and connect up the toplevel window component to Files Owner -> configureSheet.

MacRuby + Interface Builder: How to display, then close, then display a window again

I'm a complete n00b with MacRuby and Cocoa, so keep that in mind when answering - I need lots of details and explanation. :)
I've set up a simple project that has 2 windows in it, both of which are built with Interface Builder. The first window is a simple list of accounts using a table view. It has a "+" button below the table. When I click the + button, I want to show an "Add New Account" window.
I also have an AccountsController < NSWindowController and a AddNewAccountController < NSWindowController class, set up as the delegates for these windows, with the appropriate button click methods wired up, and outlets to reference the needed windows.
When I click the "+" button in the Accounts window, I have this code fire:
#add_account.center
#add_account.display
#add_account.makeKeyAndOrderFront(nil)
#add_account.orderFrontRegardless
this works great the first time I click the + button. Everything shows up, I'm able to enter my data and have it bind to my model. however, when I close the add new account form, things start going bad.
if I set the add new account window to release on close, then the second time I click the + button, the window will still pop up but it's frozen. i can't click any buttons, enter any data, or even close the form. i assume this is because the form's code has been released, so there is no message loop processing the form... but i'm not entirely sure about this.
if i set the add new account window to not release on close, then the second time i click the + button, the window shows up fine and it is usable - but it still has all the data that i had previously entered... it's still bound to my previous Account class instance.
what am I doing wrong? what's the correct way to create a new instance of the Add New Account form, create a new Account model, bind that model to the form and show the form, when I click the + button on the Accounts form?
... this is all being done on OSX 10.6.6, 64bit, with XCode 3.2.4
The issue is that it doesn't create the window each time. Release on close is a bit of an annoying option and generally is only used if you know the window controller is also being released when the window closes. (Note I've never used MacRuby so I'll be giving code in Obj-C as I know that it is correct, hopefully you can convert it. I'll be assuming GC is on as it should be with MacRuby).
Now there are two ways to do this. I'm not entirely sure how your NIB/classes are set up as it could be one of two ways.
--
The first way to solve it is to use the outlets you use to reference the form elements to blank them out when you display the window again eg [myTextField setStringValue:#""]. If you're using cocoa bindings then it's a little trickier, but basically you have to make sure the bound object is blanked out. I would recommend against bindings though if you are new to Cocoa.
--
The second way is to make the AddNewAccountController class a subclass of NSWindowController. When you press the + button you would then create a new instance of it and display it (remember to store it in an ivar). The best way to do it would be as so:
if (!addAccountController) {
addAccountController = [[AddNewAccountController alloc] initWithWindowNibName:#"AddNewAccountController"];
[[addAccountController window] setDelegate:self];
}
[addAccountController showWindow:self];
This prevents a new instance being made if the window is already visible. You then need to implement the delegate:
- (void)windowWillClose:(NSNotification *)notification {
//If you don't create the account in the AddNewAccountController then do it here
addAccountController = nil;
}
Obviously you would need to move the window to a separate NIB called "AddNewAccountController". In this NIB make sure to set the class of the File's Owner to AddNewAccountController and then to connect the File's Owner's window outlet to the window.
When all this is set up, you will get a fresh controller/window each time. It also has the benefit of splitting up nibs and controllers into more focused units.
--
One last thing. While it is fine to do something like this in a window, you may want to eventually look at doing this via a sheet, as it would then prevent the possibility of the add account window getting hidden behind other windows.

load nibs in cocoa

How can I load a nib inside of another window?
I tried initWithWindowName,
if (mmController == NULL)
mmController = [[mainMenu alloc] initWithWindowNibName:#"mainMenu"];
[mmController showWindow:self];
but it opens a new window.
I also tried loadNibNamed
[NSBundle loadNibNamed:#"mainGame" owner:self];
and it succeeded, but when I try to use the same method to get back to the main menu,
[NSBundle loadNibNamed:#"mainMenu" owner:self];
it doesn't work. It does nothing at all...
Any ideas?
I tried initWithWindowName,
You mean initWithWindow¹Nib²Name³:, which takes the name (3) of a nib (2) containing a window (1).
if (mmController == NULL)
This should be nil, not NULL, since you are comparing an Objective-C object pointer.
mmController = [[mainMenu alloc] initWithWindowNibName:#"mainMenu"];
What is mainMenu here? It must be a class, but what is it a subclass of?
[mmController showWindow:self];
From this message and the previous message, I'm guessing mainMenu is a subclass of NSWindowController.
Guessing should not be required. You should name your classes specifically, so that anybody can tell what the class is and its instances are merely by the class name.
Brevity is a virtue, but if you need to go long, go long. We've got modern tools with name completion. The tab key can eliminate the sole advantage of an abbreviated name.
but it opens a new window.
Yes. You created a window by loading it from a nib, and then you told the window controller to show that window. Showing a new window is the expected result.
I also tried loadNibNamed
[NSBundle loadNibNamed:#"mainGame" owner:self];
and it succeeded, but when I try to use the same method to get back to the main menu,
There is no “get back”. Loading a nib is simply creating objects by loading them from an archive. You can load the same nib multiple times, and loading a nib does not somehow undo the results of loading a previous nib.
You may want to read the Resource Programming Guide, which covers nibs as well as image and sound files, and the Bundle Programming Guide.
If you want to hide the window you loaded from the mainGame nib, do that. The term for this in AppKit is “ordering out” (as opposed to “ordering in”, which “ordering front” and “ordering back” are specific ways of doing).
[NSBundle loadNibNamed:#"mainMenu" owner:self];
it doesn't work. It does nothing at all...
Are you trying to load the MainMenu nib that came with your project? If so, make sure you get the case right—you don't want your app to be broken for people who run it from a case-sensitive volume, nor do you want it to be broken for people who use the default case-insensitive file-system.
If that's not what you're trying to do, then it isn't clear what you are trying to do. MainMenu is normally the nib containing the main menu (the contents of the menu bar); naming any other nib “mainMenu” or anything like that is going to cause confusion at best and problems at worst. If this is supposed to be some other nib, you should give it a different name.
Either way, this is not what you need to do. If you want to hide the window you loaded from mainGame, then you need to hide that window, not load a different nib.
Moreover, once the window is loaded, do not load it again (unless you close and release it). Once you have loaded it, you can simply order it back in. Most probably, you will want to both make it key and order it front.
On the Mac, you are not limited to one window at a time; indeed, your app has multiple windows (at least three), no matter what you do. The APIs are built around your ability to show multiple windows.
See the Window Programming Guide for more information.
How can I load a nib inside of another window?
As Justin Meiners already told you, you may want NSViewController for that, although you can go without and just load the nib containing the view directly using loadNibNamed:.
Be warned that NSViewController is not nearly as powerful/featureful as Cocoa Touch's UIViewController.
You'll want to read the View Programming Guide for this.

Making a Cocoa App Pop Up a Widget instead of a Window

I have never made an app using XCode and Cocoa but I think following these instruction:
http://developer.apple.com/Mac/library/documentation/GraphicsImaging/Reference/IKImagePicker_Class/IKImagePicker_Reference.html
I could easily make an app that pops up a window that allows you to push a button to bring up the IKPictureTaker, but this is not quite what I want. I want my app to just automatically bring up the PictureTaker. I assume to do this I would have to abandon the interface builder altogether and do the whole thing programatically but I can't figure out what call will tell the Cocoa app to use this class or method at start up. Where would this be done programatically? I am trying to do all this in a Cocoa app that will be run in OSX.
alt text http://www.freeimagehosting.net/image.php?38f459584c.png][img=http://www.freeimagehosting.net/uploads/th.38f459584c.png
You'll want to use the beginPictureTakerWithDelegate:didEndSelector:contextInfo: method, which will give you a stand-alone pictureTaker window.
IKPictureTaker *sharedPictureTaker = [IKPictureTaker pictureTaker];
[sharedPictureTaker setValue:[NSNumber numberWithBool:YES] forKey:IKPictureTakerShowEffectsKey];
[sharedPictureTaker beginPictureTakerWithDelegate:self didEndSelector:#selector(pictureTakerDidEnd:returnCode:contextInfo:) contextInfo:nil];
If you put that somewhere, like in your Application Delegate's applicationDidFinishLaunching: method, you'll get the picture taker window # startup.
If you want a more custom solution you can look into the QuicktimeKit. It's not as easy as the three liner posted above but it's relatively pain-free. You'll have much more flexibility in the look of your picture taker window, be able to select from any number of inputs, be able to add your own filters, etc. Could be worth a look.
I'm not totally familiar with the IKPictureTaker. If it does something I'm not crediting it then let me know.
The way to automatically instantiate a class in Xcode /Cocoa is, strangely enough, through Interface Builder (IB). Open your MainMenu.xib in IB and make sure you can see the Document window (menu Window >> Document). Now, in the Library expand Cocoa >> Objects & Controllers >> Controllers. You'll see a number of controller, among them a blue cube. Drag this blue cube to your MainMenu.xib document window. You'll see that File's Owner and Font Manager sport the same blue cubes as their logo. Now, select your blue cube and in the Inspector choose the Identitiy panel (letter i on blue circle). Set the Class to the class you created before in Xcode, and save MainMenu.xib.
When you run your program, your class will automatically be instantiated. Your starting point from where you can start calling other methods or instantiating other objects is
- (void)awakeFromNib
{
NSLog(#"%# I'm alive!", [self class]);
}

Resources