"Hello, World."

Dear subset of the LazyWeb who hack on OSX,

I've finally started on the OSX port of XDaliClock. (And there was much rejoicing.) Since this is my first native Mac program, I could use some help figuring out what I'm doing wrong so far.

  • I made a preferences panel, but I still have no clue how I'm supposed to actually hook it up, so that I can read the settings in it and act on them. One of the tutorials I saw seemed to imply that I needed to have both a "DaliClockView" and a "DaliClockController", but I still just follow how this crap works at all.

  • And once that stuff's hooked up, how do I cause the settings to be saved across sessions?

  • When I test the interface from Interface Builder, the color picker dialog has an "Opacity" slider, but in the real application, that slider's not there. Why?

  • I'm confused about the lifetimes of objects, and whether, when, or how I'm expected to increase or decrease the reference counts. Especially the "fg" and "bg" instance variables. Am I leaking stuff? I think I probably am...

  • Calling setMovableByWindowBackground from initWithFrame doesn't seem to do anything (it needs to be called later) so I'm calling it from acceptsFirstResponder, which is clearly bogus. What's the right place for this sort of thing?

  • How do I make the window always stay on top?

  • How do I make the app exit when the window close box is clicked?

  • Is there a more efficient way of taking a 1bpp bitmap and rendering it on a window with a given pair of foreground/background colors than the horrible way I'm doing it (scale the 1bpp bitmap up to a 24bpp pixmap and render that instead)?

The code is here: xdaliclock-2.21alpha.tar.gz. Please don't distribute this yet, but please do take a look at it and tell me what parts I did in a crazy ass-backwards way. The XCode project is in the "OSX" subdirectory.

Tags: , , , ,

14 Responses:

  1. rexpop says:

    Calling setMovableByWindowBackground from initWithFrame doesn't seem to do anything (it needs to be called later) so I'm calling it from acceptsFirstResponder, which is clearly bogus. What's the right place for this sort of thing?

    What you need to do is:

    1. In Interface Builder create a subclass of NSWindow. Call it DaliClockWindow or something like that.
    2. Get Interface Builder to create the .h and .m files.
    3. On the instances palette of select the Window instance. On the floating inspector select the ''Custom Class" Panel. Change the class of the instance to the one you created in (1).
    4. Open the .m file you created in (2) and add the following block:

    - (id)initWithContentRect:(NSRect)contentRect styleMask:(unsigned int)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag
    {
    self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
    [self setMovableByWindowBackground:YES];

    return self;
    }

    5. Remove the acceptsFirstResponder stuff. Compile and all should work.

    If the NSBorderlessWindowMask isn't to your liking you can change to one of the other options. Hope this helps.

  2. fdaapproved says:

    I made a preferences panel, but I still have no clue how I'm supposed to actually hook it up, so that I can read the settings in it and act on them. One of the tutorials I saw seemed to imply that I needed to have both a "DaliClockView" and a "DaliClockController", but I still just follow how this crap works at all.

    There's a built-in controller in the "Controllers" palette in Interface Builder. Use that and then call through the standard NSUserDefaults to get your preference information. It should be automatically written out to disk. (There might also be some transaction-type thing you can do, I don't recall).

    I'm confused about the lifetimes of objects, and whether, when, or how I'm expected to increase or decrease the reference counts. Especially the "fg" and "bg" instance variables. Am I leaking stuff? I think I probably am...

    In this case you're leaking, in theory, because you're not deallocating bg and fg in your dealloc method. That said, NSColor is probably giving you singleton objects anyway so it may be just ignoring the retain/release commands.

    How do I make the window always stay on top?

    Use setLevel: on the Windows. NSScreenSaverLevel + 1 or NSFloatingWindowLevel is probably what you want.

    How do I make the app exit when the window close box is clicked?

    Override windowDidClose to send terminate to NSApp.

    Is there a more efficient way of taking a 1bpp bitmap and rendering it on a window with a given pair of foreground/background colors than the horrible way I'm doing it (scale the 1bpp bitmap up to a 24bpp pixmap and render that instead)?

    Some variation CIImageMaskCreate is probably what you want.

    • q: How do I make the app exit when the window close box is clicked?
      a: Override windowDidClose to send terminate to NSApp.

      i've never written a screen-saver, but assuming it's similar enough to a standard app that you've got an NSApplication object to work with, there is a better way. NSApplication has a delegate method called applicationShouldTerminateAfterLastWindowClosed: that is tailor-made for the task.

    • rexpop says:

      In this case you're leaking, in theory, because you're not deallocating bg and fg in your dealloc method. That said, NSColor is probably giving you singleton objects anyway so it may be just ignoring the retain/release commands.

      I believe both bg and fg are populated with object that have been created via the framework and so its the frameworks job to manage these object. Before passing the objects back to the controller the system has probably already called release or autorelease on the objects, so when the autorelease pool is released those objects will be deallocated at that point.

      The most likely candidate for a memory leak would be:


      img = [[NSImage alloc] init];

      as I can't find a call to release for this object.

      The basic rule of thumb that I follow is anything that I create I'm responsible for deallocating. Anything the framework gives me is the frameworks responsibility unless (a) the documentation says otherwise (b) I call retain on the object in which case I'm now responsible for its release.

      How do I make the app exit when the window close box is clicked?

      Override windowDidClose to send terminate to NSApp.

      Nope. Attach a delegate to your NSApplication instance (File Owner in Interface builder). In this delegate implement:


      - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication {
      return YES;
      }

      When the last window in the application is closed. The application will close.

      • fdaapproved says:

        Yes there are more things that should be deallocated in dealloc { }. However, bg and fg are among those deallocated since they're retained in the setForeground:background: method.

        Yeah, I forgot about applicationShouldTerminateAfterLastWindowClosed:.

  3. I'll answer a few of the obvious questions. You may want to check out http://www.cocoadev.com as a repository for a lot of your questions and also check out http://developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/MemoryMgmt.html for details of Cocoa object lifetime management.

    The Aaron Hillegass book (http://www.amazon.com/gp/product/0321213149/qid=1135663462) is probably the best introduction to Cocoa development and the first edition was the one that got me started. I highly recommend it.

    "How do I save settings across sessions"

    You want to be looking at the NSUserDefaults stuff. That and NSNotificationCenter to allow you to post notifications when settings change will get you almost all the way there.

    "How do I hook up a Preference Pane"

    Check out the SimpleMultiWindow example that comes with the SDK. It should be somewhere in /Developer/Examples/InterfaceBuilder if you've installed XCode. That's a pretty simple example of how to launch a second window from the first.

    "How do I make the window always stay on top?"

    Something like:

    [ setLevel:NSFloatingWindowLevel];

    where is the NSWindow * of your window will do it. I'd add an IBOutlet NSWindow * appWindow in DaliClockController.h and hook that up to the window in Interface Builder. Then you can call the above statement from awakeFromNib in DaliClockController.m.

    "How do I make the app exit when the window close box is clicked".

    In DaliClockController, add a -(void)awakeFromNib method and within that, send [NSApp setDelegate:self] to make that class the delegate for applicaition events. Then in the same class, add a handler for "applicationShouldTerminateAfterLastWindowClosed" and return YES. See the Apple docs for the specific signatures of these functions.

    - Steve
    steve@opencommunity.co.uk
    http://www.opencommunity.co.uk

  4. chanson says:

    If you're willing to require Mac OS X 10.3 or later, you can just use Cocoa bindings to configure your preferences. Then your code can just ask NSUserDefaults for your preference values at any point they're needed. The preferences will be persisted automatically.

    Coincidentally - well, OK, not really - mmalcolm also has some very simple rules for memory management in Cocoa that will probably help with understanding how object lifetime and memory management work in Cocoa.

    The key to it all is that Cocoa uses reference counting, but with a twist: In addition to a decrement-the-refcount-now method (-release), there's also a decrement-the-refcount-at-some-point-in-the-future method (-autorelease). -autorelease adds an object in an "autorelease pool" collection every time it's called; at the end of the main event loop, the pool is cleaned up and each object in the collection is sent -release once for each time it was added.

    This lets Cocoa use an ownership model that lets most code be written as if there was garbage collection in place: If you get an object as the result of an +alloc, +new, or -copy/-mutableCopy method, or if you -retain it, you have to -release it at some point in the future using either -release or -autorelease. If you get an object in any other way, you must assume it's been sent -autorelease and could go away at the end of the current event loop. if you want to keep the object around, you need to -retain or -copy it.

    To make an app exit when the its last window is closed, you need to have some object acting as the application object's delegate. This object should respond YES when sent -applicationShouldTerminateAfterLastWindowClosed: by the application.

  5. rexpop says:

    When I test the interface from Interface Builder, the color picker dialog has an "Opacity" slider, but in the real application, that slider's not there. Why ?

    From the documentation to

    + (void)setIgnoresAlpha:(BOOL)flag

    in NSColor:

    "If flag is YES, the application won't support alpha. In this case, no opacity slider is displayed in the color panel, and colors dragged in or pasted have their alpha values set to 1.0. By default, applications ignore alpha. Applications that need to import alpha can invoke this method with flag set to NO and explicitly make colors opaque in cases where it matters to them. Note that calling this with a value of YES overrides any value set with the NSColorPanel method setShowsAlpha:."

    The default value is YES as mentioned in the documentation to '+ (BOOL)ignoresAlpha' in the same class.

    Basically somewhere in your initialization code you need to call this method with NO and the opacity slider should then appear.

  6. mayoff says:

    I've fixed some of your problems and uploaded the project to http://dqd.com/~mayoff/DaliClock-2.21alpha-mayoff.zip . I'm using a later version of Xcode than you, and Xcode's project file's aren't backward-compatible, so either upgrade or copy the files into a new project.

    I would recommend dumping the DaliClockController. However, you'll need an app delegate and a window delegate for some of the things you want. I created DaliClockAppDelegate and made it both the app delegate and the window delegate.

    For your prefs panel, use the shared NSUserDefaultsController and Cocoa Bindings. This takes care of saving/restoring the settings automatically. You can read up on this at http://developer.apple.com/cocoa/cocoabindings.html . I wired up some of the prefs to do this (the colors, the sliders, and the always-on-top checkbox). I also set the bundle identifier (in Info.plist) to org.jwz.DaliClock. Use the outline view in the IB MainMenu.nib window to quickly find bindings. You switch to outline view using the little control above the vertical scrollbar. To see how the code handles the prefs, for addObserver, updateFromPreferences, and initialize in DaliClockAppDelegate.m and DaliClockView.m.

    For the color picker, you need to send [[NSColorPanel sharedColorPanel] setShowsAlpha:YES]. I put this in -[DaliClockAppDelegate applicationDidFinishLaunching:].

    Regarding object lifetimes and reference counts: I seem to recall finding this article helpful when I was starting out. Unfortunately the site seems to be suffering serious bit-rot so you'll have to look at the Google cache version: http://64.233.161.104/search?q=cache:1GcKwdycPfUJ:cocoadevcentral.com/articles/000055.php .

    You might also want to check out some of the other articles linked from CocoaDev: http://cocoadev.com/index.pl?MemoryManagement . I didn't look for any reference-count problems in your code. Check out the MallocDebug and ObjectAlloc tools for help tracking down problems.

    You make the window always stay on top by sending -[NSWindow setLevel:] with a parameter other than NSNormalWindowLevel. I put this in -[DaliClockAppDelegate updateFromPreferences:].

    You make the app exit when the window close box is clicked by making the window's delegate's windowWillClose: method send [[NSApplication sharedApplication] terminate: self]. This is in -[DaliClockAppDelegate windowWillClose:].

    One other change: I set the window's autosave name to clockWindow in IB. This makes the app automatically save and restore the position of the window.

    • mayoff says:

      You'lle have to put the directory in my archive under your xdaliclock directory to make it compile. And regarding setMovableByWindowBackground, you can do that in -[DaliClockAppDelegate applicationDidFinishLaunching:].

    • jwz says:

      Thanks!

      I don't think I quite figured out how you created DaliClockAppDelegate in IB: I made it the delegate of both the window and the app, but when I have IB generate the file, it doesn't add "IBOutlet NSWindow *window" to the DaliClockAppDelegate.h file: where does that come from and how does it get filled in?

      I'm confused about why the "defaults" dictionary exists in "initialize" -- why don't those default values (slider positions, radio button states, etc) just come from whatever is selected in IB? (I guess I'd be surprised to find that the party line was "set your default prefs by typing their names into a .m file".)

      Is calling addObserver manually the only way to cause observeValueForKeyPaty to be called, or is there some more clickety-draggety way to do that from IB?

      • mayoff says:

        Here's how you tell IB that DaliClockAppDelegate has a "window" outlet: select the Classes tab of nib window. Find DaliClockAppDelegate. (You can also double-click your instance of DaliClockAppDelegate in the Instances tab to get the class selected.) Go to the Inspector panel. Press Command-1 or choose Attributes from the pop-up menu at the top of the Inspector panel. Choose the Outlets tab (as opposed to the Actions tab). Add a new outlet named "window" of type "NSWindow". Now control-click the DaliClockAppDelegate class in the nib window and choose Create Files. You'll probably have to go through the merge process using Apple's FileMerge program. It'll get launched automatically.

        Alternatively, you can add "IBOutlet NSWindow* window;" to DaliClockAppDelegate.h by hand. Then, in IB, control-click DaliClockAppDelegate in the Classes tab of the nib window and choose "Read DaliClockAppDelegate.h".

        Regarding the defaults dictionary in initialize: Surprise! That's the party line! The settings of your Preferences panel controls in IB don't have any effect on the final program. That would be pretty darn nice. There are probably ways to finesse this, say by creating a defaults database and distributing in the app, but I haven't done it.

        Regarding addObserver: you can do this a bit differently with more clicky-draggy in IB. Get rid of the calls to addObserver and get rid of the observeValueForKeyPath methods. Change updateFromPreferences in both DaliClockAppDelegate and DaliClockView to this signature:

        - (IBAction)updateFromPreferences:(id)sender;

        Change the places where you send [self updateFromPreferences] to [self updateFromPreferences:self]. Copy the signature of updateFromPreferences: into the .h files. In IB, read those .h files to pick up the actions. Then control-drag from the cycleSpeed slider to the DaliClockView instance and set the action to updateFromPreferences:. Repeat for the other prefs controls. Some of them should be wired to the app delegate instead of to the view.