5. Replacing the CustomView class

There’s only one class left to convert, CustomView, which handles drawing in the application’s window. The Objective-C source is below, again with most of the comments stripped out for brevity (read them in the original Apple sources):

Original CustomView source code [Objective-C]
@interface CustomView : NSView
{
    NSImage* circleImage, *pentaImage;
}
@end

@implementation CustomView

-(void)awakeFromNib
{
    circleImage = [NSImage imageNamed:@"circle"];
    pentaImage = [NSImage imageNamed:@"pentagram"];
    [self setNeedsDisplay:YES];
}

-(void)drawRect:(NSRect)rect
{
    [[NSColor clearColor] set];
    NSRectFill([self frame]);
    if ([[self window] alphaValue]>0.7)
        [circleImage compositeToPoint:NSZeroPoint operation:NSCompositeSourceOver];
    else
        [pentaImage compositeToPoint:NSZeroPoint operation:NSCompositeSourceOver];
    // the next line resets the CoreGraphics window shadow (calculated around our custom window shape content)
    // so it's recalculated for the new shape, etc.  The API to do this was introduced in 10.2.
    if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_1)
    {
        [[self window] setHasShadow:NO];
        [[self window] setHasShadow:YES];
    }
    else
        [[self window] invalidateShadow];
}
@end

It has two methods, awakeFromNib and drawRect:. The first is easy to convert, and there are only a few small problems with drawRect:. We’ll discuss them in the next section. Try it for yourself, or use my converted code below:

CustomView [ruby]
class CustomView < OSX::NSView
    def awakeFromNib
        @circleImage = OSX::NSImage.imageNamed "circle"
        @pentaImage = OSX::NSImage.imageNamed "pentagram"
        setNeedsDisplay(true)
    end
    
    ns_overrides :drawRect_
    def drawRect(rect)
        OSX::NSColor.clearColor.set
        OSX::NSRectFill(frame)   
        if window.alphaValue > 0.7
            @circleImage.compositeToPoint_operation_([0,0], OSX::NSCompositeSourceOver)
        else
            @pentaImage.compositeToPoint_operation_([0,0], OSX::NSCompositeSourceOver)
        end
        window.invalidateShadow
    end
end

If you tried to write the conversion yourself, you probably encountered a few problems. Here are the ones that I had:

  • The NSZeroPoint constant is not defined in RubyCocoa, so a reference to OSX::NSZeroPoint triggers an error message. But after checking the RubyCocoa sources, I discovered that NSZeroPoint is defined as a function, so you can get it as OSX.NSZeroPoint. But we don’t actually need it; guessing that NSZeroPoint meant a point at the origin (0,0), I simply replaced the references to it with [0,0], which RubyCocoa accepts as a synonym for OSX::NSPoint.new(0,0).
  • Similarly, the NSAppKitVersionNumber constant is defined by RubyCocoa as a function and not a constant. But NSAppKitVersionNumber10_1 is not available at all. Fortunately for us, this doesn’t matter either: recent versions of RubyCocoa only work on OS X 10.2 and later. So we can safely use the later API and call window.invalidateShadow.
  • The Xcode console might show a warning message that says: “NSView not correctly initialized. Did you forget to call super?” This message was a known issue with RubyCocoa that has recently been fixed. It was annoying but benign, and now it should not arise if you are running with a current RubyCocoa release.

If you’ve made it this far, congratulations! You now have a fully-rubified Cocoa application. It’s not really any smaller than the Objective-C version, but this little example doesn’t do much for us to optimize with Ruby. But what we can do now, and very easily, is make some changes. Let’s get into that in the next chapter.

Did you find an error? Is something missing? Post your comment or suggestion below!

Comments (4) post
  1. pig_farmer@earthlink.net Wed Sep 27 01:33:06 +0000 2006

    I could only get CustomView to build and run if I moved the instance variables @circleImage and @pentaImage into the drawRect method:

    class CustomView < OSX::NSView def awakefromNib
    1. @circleImage = OSX::NSImage.imageNamed “circle”
    2. @pentaImage = OSX::NSImage.imageNamed “pentagram” setNeedsDisplay(true) end end
    def drawRect(rect)
      @circleImage = OSX::NSImage.imageNamed "circle" 
      @pentaImage = OSX::NSImage.imageNamed "pentagram" 
      OSX::NSColor.clearColor.set
      OSX::NSRectFill(frame)
      if window.alphaValue > 0.7
        @circleImage.compositeToPoint_operation_([0,0], OSX::NSCompositeSourceOver)
      else
        @pentaImage.compositeToPoint_operation_([0,0], OSX::NSCompositeSourceOver)
      end
      window.invalidateShadow
    end

    Building using your example resulted in the following error: [Session started at 2006-09-27 01:17:27 -0700.] /Volumes/WDC_250/Ruby_Area/XCode-Ruby/RoundTransparentWindow/build/Development/RoundTransparentWindow.app/Contents/Resources/RTW.rb:67:in `NSApplicationMain’: NSApplicationMain – RBException_NoMethodError – undefined method `compositeToPoint_operation_’ for nil:NilClass (OSX::OCException) from /Volumes/WDC_250/Ruby_Area/XCode-Ruby/RoundTransparentWindow/build/Development/RoundTransparentWindow.app/Contents/Resources/rb_main.rb:22

    P.S.: I noticed when previewing my submission that the pound sign (#) used to comment out a statement begets a number (1.)(2.).

  2. Tim Wed Sep 27 09:09:42 +0000 2006

    Did you confirm that your awakeFromNib is getting called? I’d use OSX::NSLog to print a message to be sure. Was the method name spelled correctly? This is one of the challenges of working with dynamic languages, you have to check things that a compiler would otherwise check for you.

  3. pig_farmer@earthlink.net Wed Sep 27 11:11:44 +0000 2006

    You’re right, I had misspelled awakeFromNib as awakefromNib. To me the problem looked like a compile error rather than a runtime error.

    Thanks!

  4. grimm@grimmwerks.com Sat Nov 25 22:00:48 +0000 2006

    huh – I’m doing puts as traces testing both in the if and the window.alphaValue—it actually works but doesn’t do the NSCompositeSourceOver for the pentacle – stays as circle…hmm.