C Code to add a Cocoa Window to and Igor XOP

Average rating
(2 votes)

I have a window that displays a bunch of status (that I get from an external TCP connection), which I had been drawing with QuickDraw. Now that QuickDraw calls are not even provided with Xcode, it is time to modernize my XOP window. Here is the hard part of converting it to a Cocoa window.

There is no interaction whatsoever between Igor and the window, so I have high hopes that it will continue to work in Igor 7.

The hard part (for me) was finding and loading the correct nib file. For those of you familiar with Cocoa, here is the main structure of how I do it. This of course needs to be in an objective-C (.m) file.

Once you've done this, you can put stuff in the window either by making IBOutlets and connecting them to views in the window, or with bindings. I use a combination of the two. I put all the outlets in my window controller. You could put them in the window, but it's closer to the Model-View-Controller design pattern to put them in the controller.

The window seems to get all the usual events, and coexists very nicely with Igor. I have working buttons in my window, and I also handle keydown events (which I send to the Igor command line, since you're not supposed to be able to type in my window.)

// I subclassed NSWindow, mainly so I could handle keydown events.
// I subclassed NSWindowController to handle actions from controls in the window.
// I store the window and its controller in global variables.
MyWindowClass*		myCocoaWindow;
MyWindowController*	myWinController;
// Create the window and its controller.  I store the NSWindow and the NSWindowController
// in global variables, but you could return them.
// I also never release the window, so it just gets hidden when closed.  If you want to
// release it, you could do that (and remember to nil out the global variable!) in
// the delegate's WindowWillClose method, or in the window's Close method (assuming you
// subclass NSWindow).
static long ShowMyCocoaWindow()
	long		err = 0;
	NSString*	title;
	NSBundle*	xopBundle;		// CAUTION:  CFBundles are NOT toll-free bridged with NSBundles.
	NSArray*	nibObjects;
	// Don't forget to create your own autorelease pool, since we're not in a regular run loop.
	// If we don't do this, all autoreleased objects we create below will leak.
	NSAutoreleasePool*		pool = [[NSAutoreleasePool alloc] init];
	if( myCocoaWindow )
		// It already exists.  Just bring it to the front.
		[myCocoaWindow makeKeyAndOrderFront:nil];
		// First we have to find the bundle containing the XOP.
		// bundleWithIdentifier looks first in our own process, so it will always
		// find the one that was actually loaded.  Nice!
		xopBundle = [NSBundle bundleWithIdentifier:@"gov.nasa.gsfc.myxop"];
		if( xopBundle )
			// Now find the desired nib in this bundle.
			NSNib* winNib = [[NSNib alloc] initWithNibNamed:@"MyWindowName" bundle:xopBundle];
			if( winNib )
				// Found the nib! Create a window controller with no window.
				myWinController = [[XGXboxWinController alloc] initWithWindow:nil];
				// Instantiate the nib containing the window.
				[winNib instantiateNibWithOwner:myWinController topLevelObjects:&nibObjects];
				// Find the window amongst the top objects in the nib.  
				// There may be a more elegant way to do this, but this is guaranteed to work
				NSEnumerator*	enumerator = [nibObjects objectEnumerator];
				id				thisObject;
				while( (thisObject = [enumerator nextObject] ) )
					if( [thisObject isKindOfClass:[NSWindow class]] )
						myCocoaWindow = thisObject;
				// Now set the window controller to control this window, and 
				// copy the window to my global variable
				[myWinController setWindow:myCocoaWindow];
				// Title the window appropriately.  title is autoreleased; good thing we have an autorelease pool, eh?
				title = [NSString stringWithFormat:@"A Cocoa Window"];
				[myCocoaWindow setTitle:title];
	[pool release];
	return err;

That's it! The window and controller have no other code that deals with creating it or updating it. I have a bunch of IBOutlet NSTextFields in the controller, which I have connected to the text fields in Interface Builder, and when I have new data I just say [[myWinController someTextField] setFloatValue:x] and let the Cocoa runtime do the rest.

Oh, here is the code I use to make sure key events that go to this window are redirected to Igor's command window. This goes in MyWindowClass.m

- (void)keyDown:(NSEvent *)theEvent
	NSString*	chars;
	// Put the character(s) that we got onto the command line
	chars = [theEvent characters];
	PutCmdLine( [chars cStringUsingEncoding:NSASCIIStringEncoding], INSERTCMD );
	// Bring command window to the front
	XOPSilentCommand( "DoWindow/H" );

Left as an exercise for the reader, because I haven't done it yet, is handling command-whatever key combinations, which do not arrive as key events.

Back to top