Plugin troubles

Tags ( )

Hi, just two quick (I hope) questions:

1. With a menu item plugin, what would cause the menu item to be greyed out? (It's getting added to the right menu, but it isn't active.

2. With a pref pane plugin, everything works (i.e. I'm getting the correctly labeled icon in the pref pane bar) except for loading the view. I have the nib in a resources folder in but nothing is being loaded when the pref pane icon is chosen. Any ideas where I'm missing a linkage?

Thanks,
Jeff

Ok, I solved the problem

Ok, I solved the problem with #2. In the nib file, I hadn't set the file's owner to be the custom controller class, so I didn't have a view outlet.

There are a number of

There are a number of possibilities for a grayed out menu item, but the most likely is that the item can't find a target with a suitable action method. Show me the code that you are using the create the menu item, and where is the action method that the menu item is supposed to target?

In NCMenuExtension.m:

In NCMenuExtension.m: (adapted from Mori Math)

- (void)declareMenuItems:(id )menuController
{
NSMenuItem *openNoteChan = [[[NSMenuItem alloc] initWithTitle: @"Open Note-chan Window" action: @selector(openNoteChan:) keyEquivalent: @""] autorelease];
[menuController insertMenuItem: openNoteChan insertPath: @"/View/TopGroup"];
[menuController declareMenuItem: openNoteChan menuItemPath: @"/View/OpenNoteChan"];

}

NCMenuExtension.h imports NCController which declares:

- (IBAction) openNoteChan:(id)sender;

- (IBAction) openNoteChan:(id)sender{
NSLog(@"entered open note chan");
}

This setup will only work if

This setup will only work if NCMenuExtension is in the responder chain since the menu target is nil...in most cases that means it must be a NSView subclass and inserted into the user interface. That's how the menu item would find the action openNoteChan:. You should be able to fix the problem by setting the menu target directly like this:

[openNoteChan setTarget:self];

Here's more documentation on the responder chain and how it works:

http://developer.apple.com/documentation/Cocoa/Conceptual/BasicEventHandling/Concepts/AboutRespChain.html

Hi Jesse, Thanks for the

Hi Jesse,

Thanks for the quick reply.

I assume that the "[openNoteChan setTarget:self];" message goes in the "declareMenuItems" method.

Thanks for the link to the developer docs. They should help.

I got it working. Thanks

I got it working.

Thanks

Xcode magic

Just to follow up with a question:

One of the reasons that the menu item was greyed out, was because I think that there was no instance of NCController generated when the plugin was loaded.
When I made note-chan as an AS Studio app and then rewrote it as a Cocoa app, on start up it would generate a window instance from the nib file and then it also made a NCController object (NSObject subclass, and not NSWindowController) automatically. I hadn't written any explicit initilizers, or explicitly generated any instances of the windows or controller objects.
So my question is: what bit of xcode/IB magic is responsible for making sure that the controller and windows get automatically generated on startup? I'm asking so I'll know what to duplicate in a plugin setting.

Thanks

There's no real magic. As I

There's no real magic. As I understand the process, there's a main nib file that gets loaded when an application starts (usually something like MainMenu.nib). This creates the class instances included in the nib file. If you want to initialize classes outside of the context of the nib file (say for a non-GUI application) you should do that in main().

I recently added a preference pane to Mori Math (release 2 is almost out the door). Here's the bit of code the loads the nib file:


- (id)init
{
[super init];

[NSBundle loadNibNamed: @"MoriMathPrefPane.nib" owner: self];

return self;
}

If you want to display windows when it is loaded and stuff, you may need to do that manually (I'm not sure unchecking deferred works for nibs loaded after startup).

You should keep in mind, though, that if you use the same class as the controller of your nib file to tell Mori is your preference pane extension, the instance created by the nib file is the one that will be doing work, not the one instantiated by Mori.

You should keep in mind,


You should keep in mind, though, that if you use the same class as the controller of your nib file to tell Mori is your preference pane extension, the instance created by the nib file is the one that will be doing work, not the one instantiated by Mori.

Is this true even if the controller is accessed in the nib file as "file's owner," such that you don't have a separate controller class object in the nib (such as MoriMathController) but you reference and bind to it as the file's owner (which is a custom class: MoriMathController)?

Also, in your example above of loading the nib, what object was being initialized?

Thanks

Yes. In fact, that's how I

Yes. In fact, that's how I have the class set up.

The object being initialized is of the class MoriMathPreferencesExtension : NSObject . In the plugin.xml this class is given as the extension point of com.hogbaysoftware.preferences.preferencepane. I have assumed (I didn't actually dig up the code) that Mori (through the Blocks framework) creates an instance of this class. I'm responsible for doing everything else, so you could separate the extension point class from the nib controller if you wanted to (it might save some memory if your controller is large).

Here's some good

Here's some good documentation on the magic behind loading nib files.

http://developer.apple.com/documentation/Cocoa/Conceptual/LoadingResources/index.html#//apple_ref/doc/uid/10000051i

In the case of Note-Chan here are some design thoughts. If I were you I would copy the basic design of the BKCrashReporter plugin that comes with the Blocks framework source code. Using that pattern you should have a class called NCWindowController that follows the same design of the BKCrashReportController class. The most important feature of your NCWindowController is that it extends from NSWindowController... that will make it a bit easier to load your nib file. Note that in the BKCrashReporterWindow.nib the File's Owner is declared to be a BKCrashReportController... and the BKCrashReportController's window outlet is connected to the window in the nib. You should do the same thing in your NCWindow.nib file, set the owner to be a NCWindowController and make sure to connect the NCWindowController's window outlet (you'll only see that outline once NCWindowController is a subclass of NSWindowController.

I hope that description isn't to confusing. But the basic idea is to build your Note-Chan window just as you would if you did not need to worry about it getting loaded by the Blocks framework. So create your window nib, and create a window controller that will load the nib and contain the Note-Chan application logic.

The last step step is to get this loaded by blocks. The best way to do this is to create a small utility "Extension" class that will hock into Blocks. For instance the BKCrashReporter plugin declares a BKCrashReporterLifecycleExtension class. This is the class declared in the plugin.xml file. And if you look at the implementation you'll see that it's very simple, it just tells the BKCrashReporterController to check for crashes:

[[BKCrashReporterController sharedInstance] check:self];

A good start for you might be to follow this patter exactly, make an extension class that extends from the lifecycle extension point and just show Note-Chan when Mori launches. The code to do this would look like this once your NCWindowController is extending from NSWindowController:

- (void)applicationDidFinishLaunching {
[[NCWindowController sharedInstance] showWindow:nil];
}

Latter you can create other extension classes, for instance you could create an extension class that adds a menu item to Mori to show Note-Chan.

I hope that helps. And of course let me know if you need more help, I can probably setup this basic design pretty quickly if you send me your sources.

Jesse

In a Bind (with bindings)

I've been struggling to absorb Bindings now for the last couple of days. My only consolation is that many people on the various developer sites seem to have difficulty "getting" bindings on the first pass.

Here's my problem:
I can bind a preference object (like a slider) to the shared defaults and the value will be stored and restored on the next launch of the plugin. great.

or

I can bind the same slider to the appController object so that the value gets is reflected in the one of the objects instance variables and actually gets some work done.

However, I haven't figured out how to accomplish both simultaneously. I want the preference to get saved to the plist and at the same time I want the appController object to have its instance variable updated.

Up till now, I've been working with setting the bindings in Interface Builder. Should I set programmically the appController object as an observer of the preference?

Also, I noticed that some bindings tutorials have their controller object encapsulate all instance variables in a 'parameters' NSdictionary. Is that preferable to having "naked" variables?

At least the good news is that thanks to the advice from Jessen and Gordon my plugin is working to display new windows from within Mori. :)

Yes, make appController an

Yes, make appController an observer of the preference.

It doesn't actually matter which way you store the variables. If you do it with an NSDictionary you don't have to implement as many methods on your class, but if you do it with "naked" variables you might gain some speed (I'm not sure how much, though). At least, I think that's the case; I've only used bindings with core data objects.

I don't mind writing the

I don't mind writing the code to register the controller object as an observer, but it just seems that with all the touted power of bindings it's silly that they can't bind a value to two objects (e.g. the appController and the Shared User Defaults).

In looking at Mori code, it seems that the prefs are bound to the Shared user defaults and then the appController accessors call the NSUserDefaults when they need the value.

So it seems the three ways to update both the appController and the defaults:

1. register (programmically) as an observer of the value
2. bind the value to the defaults and then use the target/action mechanism to update the appController.
3. bind to the defaults, and then use NSUserDefaults to access the value when needed. (of course it will be needed whenever it's been updated.)

Any comments on the best/easiest?

thanks

As you've noticed I often do

As you've noticed I often do #3, but I'm not really sure that it's the best way. I think I have correct bindings solutions in Most of Mori's code, but I got lazy in the preferences.

Probably the "correct" way to do it would be to bind both your slider and app controller to the user defaults controller. The benefit over #3 is that your app controller should get notified in this case when a defaults value changes.

Maybe. I'm not expert on bindings.

Like a lot of things in

Like a lot of things in Objective-C, bindings require a lot of infrastructure to make hard tasks seem easy once you build the infrastructure. It reminds me of how I felt when I started working with it in Mac OS X Preview and got frustrated with delegates because I had to do, what felt to me, like all this extra programming to make things work. But once I got used to delegates, I realized how they could solve some problems better than other solutions every could.

This issue just came up on

This issue just came up on cocoadev. It looks like my guess was right, just bind everything to user defaults.

http://www.cocoabuilder.com/archive/message/cocoa/2006/2/24/157420

Thanks for the link. When

Thanks for the link.

When one binds an ivar to shared user defaults is that always done programmically or can IB do it through a NSObjectController and the content of the object with the relevant ivar?

And if I were to do it progammically, would that usually be done when the object is initialized?
Thanks

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.