How to set up OSX screen saver configuration sheet? - objective-c

I'm working on a screen saver for OSX (Mountain Lion) and I'm having trouble setting up the configuration sheet (so when the user clicks "Screen Saver Options..." within System Preferences, my options appear). There seem to be only two or three tutorials on writing OSX screen savers anywhere on the Internet, and they're all several years old so the material doesn't quite translate to OSX 10.8 and Xcode 4.
First of all, in my ScreenSaverView.m file, I have:
- (BOOL)hasConfigureSheet
{
return NO;
}
- (NSWindow*)configureSheet
{
return nil;
}
...and yet, in System Preferences, the "Screen Saver Options..." button is still clickable (nothing happens when it's clicked) rather than disabled as in the "Arabesque" screen saver.
What are the steps to having a configuration sheet appear when the button is clicked, and why is the button not currently disabled?
Edit:
I realized why the "Screen Saver Options..." button wasn't disabled. I had forgotten to include -(BOOL)hasConfigureSheet; in the ScreenSaverView.h file. My question about how to get the configuration sheet to appear, however, remains.

First things first: Be sure to include ScreenSaver.framework in your project; it provides the necessary classes ScreenSaverView and ScreenSaverDefaults.
You say you already have a file called ScreenSaverView.m. Couple that with you also saying that you have to export your -hasConfigureSheet method in order to make the "Screen Saver Options..." button disable. This leads me to wonder if you have subclassed ScreenSaverView as you should have done. (Did you subclass NSObject instead?) ScreenSaverView exports methods such as -hasConfigureSheet for you. You should be subclassing it, and overriding the appropriate methods in it.
A couple more things:
You should have included a xib file in your project that contains the UI for your configuration sheet, and you should have provided IBOutlets in your subclass' interface to reference the panel and the UI elements it contains (those for which you actually need an outlet, that is).
Finally, your -configureSheet method should fetch the configuration sheet in a manner similar to this (in this example, configSheet would be one of your IBOutlets):
if (configSheet == nil)
{
if ([NSBundle loadNibNamed:#"myConfigSheet" owner:self] == NO)
{
NSLog(#"load config sheet failed");
}
}
// then retrieve your defaults and set up your sheet; you should
// be working with ScreenSaverDefaults, a subclass of NSUserDefaults.
// then return 'configSheet'
Edit:
Apologies in advance if I'm about to tell you things you already know, but you did say you were having difficulties in configSheet, and so I'm simply covering all the bases.
In My_ScreensaverView.h, declare an outlet for your panel:
IBOutlet id configSheet;
Note that I used id instead of NSWindow * or NSPanel *, simply because I don't know what class you actually are using for the sheet. (Ideally, a NSPanel should be used for a sheet.)
In your nib file, make sure that the File's Owner is an instance of My_ScreensaverView. You can determine this by selecting the icon for this object and then using the Identity inspector to specify the class.
Make the connection between the configSheet outlet and the panel. One way of doing this is to hold down the Control key while dragging from the File's Owner object to the window or panel icon, then selecting configSheet from the pop-up that appears.
As always, good luck to you in your endeavors.

Related

How to get an NSDocument subclass to print custom views

What needs to be hooked up for an NSDocument subclass to call its print methods when File->Print... is chosen?
How does the File->Print menu get hooked up? (Right now the selector/action is hooked up to first responder's print method. That's in a storyboard, while my NSDocument subclass has its own xib.)
I've tried implementing all of:
-(void)print:(id)sender;
-(void)printDocument:(id)sender;
-(void)printDocumentWithSettings:(NSDictionary *)printSettings showPrintPanel:(BOOL)showPrintPanel delegate:(id)delegate didPrintSelector:(SEL)didPrintSelector contextInfo:(void *)contextInfo;
-(NSPrintOperation*)printOperationWithSettings:(NSDictionary *)printSettings error:(NSError *__autoreleasing *)outError;
But none of them are ever called when I choose Print. Where are these methods supposed to go/who calls them? (I also tried a basic app with a custom view and didn't have luck there either.)
Okay. Looks like the problem is due to a bug in Xcode: When creating a Document based app using storyboards the file menu is by default hooked to print: and printDocument: is not available.
The strange part is that my print: call is getting hijacked somewhere along the line but I can't figure out where (at the application level, not the document, because the print dialog is a window not a sheet). printDocument: works as expected, but must be defined manually in order to hook it up.
This is for a document-based app, targeting 10.10, and using storyboards.
In the storyboard with Main Menu, add a User Defined Action for printDocument: (This is where storyboard based differs, and I feel is a bug. Xib based do not require this User Defined Action.)
Hook up the selector for File -> Print to First Responder and choose printDocument: instead of print:
Don't define printDocument: in your NSDocument subclass. If you want to, then be sure to call super or perhaps one of the methods below.
From NSDocument.h
/* The action of the File menu's Print... item in a document-based application.
The default implementation of this method merely invokes
[self printDocumentWithSettings:[NSDictionary dictionary]
showPrintPanel:YES
delegate:nil
didPrintSelector:NULL
contextInfo:NULL].
*/
- (IBAction)printDocument:(id)sender;
The default implementation of printDocumentWithSettings in turn calls printOperationWithSettings, so you can use either of those methods to draw custom information prior to the print sheet appearing.
The accepted solution of setting the menu item to -printDocument: is correct, but not (technically) because of an Xcode bug. (It is very a poor default setting, though.)
The menu item is calling the -print: of the whatever is the first-responder. NSView implements -print:, so if anything has been set as first-responder you will print with NSView's -print:, not your document's -print:. If editing your document requires text editing, the control you are using to implement editing will be set to first-responder, and that control will get the -print:.
Take a look at the TextEdit sample code from Apple (https://developer.apple.com/library/mac/samplecode/TextEdit/Introduction/Intro.html)
In my non-document based app I have set a custom action for the print menu item. In that method I notified my controller about the print operation via NSNotificationCenter. Maybe that works for you too :)

open new window from xib in cocoa

My first project is a tool which changes the wallpaper according to the time of the day.
Therefore, I want to create a little window to select the wallpapers.
I have an alertView with three Buttons (kill changer/cancel/set wallpapers).
Pressing "set wallpapers" will run the method wall setter:
//method to set / copy / rename the wallpapers
- (void)wallsetter {
//copy chosen wallpaper to specified folder and rename it according to the time
printf("wallsetter starts...\n");
NSWindowController * wc=[[NSWindowController alloc] initWithWindowNibName:#"setter"];
[wc showWindow:self];
}
I also created a window called setter.xib with the Interface I like it to have which is now located in "supporting files".
By pressing "set wallpapers" that window should show up, but it does not. Can someone tell me how to do it? All I find are examples with .nib files but they have been replaced with the .xib.
if it helps here is a link to the complete code: wallchange#pastebin

Xcode: How should I organize my multi-window user-interface project?

Note: Using Objective-C, Cocoa, and Xcode.
At first, I did all my programming in the AppDelegate and had all user-interface elements such as windows in the same '.xib' (nib file). This worked great at first, but then as my application became more advanced with more "features", everything became extremely cluttered and the file too long for my liking.
I'm now trying to progress. I'm wondering how I should (properly and efficiently) go about having a multi-window project? My plan was to have a .xib file for every window, and put only necessary things in the AppDelegate. I would then have a core file for all necessary calculations and such to be used in my application and a Main Controller file to control outlets and actions from all windows in my app. However, I'm quite new to Objective-C and have been running into SO many issues and problems trying to set up Window Controllers and stuff.
Am I even on the right path? Am I doing it wrong? How should I manage a multi-window user-interface application in Xcode?
Thanks in advance.
For multiple windows, I think it's best to use an NSWindowController for each, with it's associated xib file for the window. I'm not sure what you mean by "a Main Controller file to control outlets and actions from all windows" -- each window controller will have outlets to its own window. You can't connect outlets across multiple xib files. You can have one window in the MainMenu.xib file that you get by default when you create a project, and use that to launch other windows perhaps, but it's hard to offer any more specific advice without knowing how all your windows relate to each other.
After Edit:
If you want to open another window, have a menu item's action method be something like this:
self.controller = [[WindowController alloc] initWithWindowNibName:#"WindowController"];
[self.controller showWindow:nil];
Here, I have a property called controller to keep a strong reference to the new window controller. If you don't do that, the controller will be deallocated, so if you have multiple windows, that you want to show at the same time, you'll need properties to hold on to them.

How to activate a custom screensaver preview in Cocoa/Obj-C?

I have created a fairly simple screensaver that runs on Mac OS 10.6.5 without issue.
The configuration screen has accumulated quite a few different options and I'm trying to implement my own preview on the configureSheet window so the user (just me, currently) can immediately see the effect of a change without having to OK and Test each change.
I've added an NSView to the configureSheet and set the custom class in Interface Builder to my ScreenSaverView subclass. I know that drawRect: is firing, because I can remove the condition for clearing the view to black, and my custom preview no longer appears with the black background.
Here is that function (based on several fine tutorials on the Internet):
- (void)drawRect:(NSRect)rect
{
if ( shouldDrawBackground )
{
[super drawRect:rect];
shouldDrawBackground = NO;
}
if (pausing == NO)
[spiroForm drawForm];
}
The spiroForm class simply draws itself into the ScreenSaverView frame using NSBezierPath and, as mentioned, is not problematical for the actual screensaver or the built-in System Preferences preview. The custom preview (configureView) frame is passed into the init method for, um, itself (since its custom class is my ScreenSaverView subclass.) The -initWithFrame method is called in configureSheet before returning the configureSheet object to the OS:
[configureView initWithFrame:[configureView bounds] isPreview:YES];
Maybe I don't have to do that? It was just something I tried to see if it was required for drawing.
I eventually added a delegate to the configureSheet to try triggering the startAnimation and stopAnimation functions of my preview via windowWillBeginSheet and windowWillEndSheet notifications, but those don't appear to be getting called for some reason. The delegate is declared as NSObject <NSWindowDelegate> and I set the delegate in the configureSheet method before returning the configureSheet object.
I've been working on this for days, but haven't been able to find anything about how the OS manages the ScreenSaverView objects (which I think is what I'm trying to emulate by running my own copy.)
Does anybody have any suggestions on how to manage this or if Apple documents it somewhere that I haven't found? This isn't really required for the screensaver to work, I just think it would be fun (I also looked for a way to use the OS preview, but it's blocked while the configureSheet is activated.)
OK, there are a couple of 'duh' moments involved with the solution:
First of all, I was setting the delegate for the sheet notifications to the sheet itself. The window that the sheet belongs to gets the notifications.
Secondly, that very window that the sheet belongs to is owned by System Preferences, I don't see any way to set my delegate class as a delegate to that window, so the whole delegate thing doesn't appear to be a viable solution.
I ended up subclassing NSWindow for the configureSheet so that I could start and stop animation on my preview by over-riding the makeKeyWindow and close methods.
- (void) makeKeyWindow
{
if (myPreview != nil)
if ( ! [myPreview isAnimating])
{
[myPreview startAnimation];
}
[super makeKeyWindow];
}
I also had to add an IBOutlet for my preview object itself and connect it in Interface Builder.
Still working out a couple of issues, but now when I click on my screensaver Options button, my configureSheet drops down and displays its own preview while you set options. Sheesh. The hoops I jump through for these little niceties. Anyway, I like it. Onward and upward.

Programmatically creating new windows and accessing window objects in Cocoa

I'm having an issue with creating new windows in Cocoa.
Hypothetically speaking, let's say I have "WindowA" and has a button called "myButton".
When you click on "myButton", it runs this code in the following class file:
-(void)openFile2:(id)sender
{
myNextWindow = [[TestWindowController alloc] initWithWindowNibName:#"MainMenu"];
NSString *testString = #"foo";
[myNextWindow showWindow:self];
[myNextWindow setButtonText:testString];
}
The code in a nutshell makes a duplicate "WindowA" and shows it. As you can see, this code also runs a method called 'setButtonText', which is this:
- (void)setButtonText:(NSString *)passedText
{
[myButton setTitle:passedText];
}
The problem is that when I call this method locally, in the original window - the button text changes (e.g., [self setButtonText:testString]) it works. However, it does not work in the newly created window (e.g., [myNextWindow setButtonText:testString];)
When I debug the newly created window, step by step, the 'myButton' value it gives is 0x0. Do I have to manually assign controllers/delegates to the new window? I think the 'myButton' in the code isn't associated to the 'myButton' in the newly created window.
How would I fix this problem?
The first problem is that you are loading the MainMenu NIB/XIB repeatedly. That will do Very Bad Things -- the MainMenu should only be loaded once at application startup.
You want to break out any UI that needs to be loaded repeatedly into a separate NIB/XIB file (the same way a document based application has a MainMenu.xib and Document.xib files).
To properly do this, you need to understand the concept of "File's Owner" and how to leverage it properly. Note that there is also overlap with window controllers and understanding those, if you want to use them, will be helpful.