Child’s Play Day

December 6th, 2006

Mike Zornek had a brilliant idea for a way that independent software vendors could show a little holiday spirit this year. He’s put together a loose-knit collection of companies whose profits from sales tomorrow will be donated 100% to the Child’s Play Charity, which helps hospitals buy toys, books, and other entertainment for the kids under their care.

I love this charity idea because it gives small developers a chance to unite for a good cause, and because that cause is pretty universal and apolitical. Who doesn’t want sick kids to be as happy as possible?

I’m putting my products into the pool, so if you had any intention of buying them, do it tomorrow, December 7th, 2006. I appreciate that picking a 24 hour period and calling it “a day” can be tough to clarify internationally. So I’m interpreting it a little loosely and starting the “sale” right now. My online store includes a note explaining that the charity sales are in place, and I’ll take that notice down sometime after midnight tomorrow. While that notice is in effect you are guaranteed that 100% of your payments will go to the charity (i’m donating the difference in any PayPal or Kagi fees, so the total contribution will be exactly the cost of the product).

There are lots of other great companies participating, so if you’ve been considering any of the products on the list, tomorrow would be a great time to make the purchase!

iTunes Scripting Seizure

December 5th, 2006

I love iTunes, but it has a number of rough edges that leave me constantly praying that a major revision will come along one day soon. Most of my complaints have to do with it being simultaneously the “only way to manage music and podcasts on the Mac” and yet being frustratingly feature-incomplete in some important ways. For instance, it does a great job of downloading my podcasts, but neglects to put them on my iPod until I either unplug and replug it, or else manually select “Sync.” Inevitably, I end up thinking that since iTunes and my iPod have been making out all weekend, surely those new podcasts are on the iPod. It’s not until I’m at the gym with nothing to listen to that I realize I’ve been sync-jacked!

But that’s a complaint for another time. Today I’d like to highlight an infuriating behavior that iTunes has, of blocking the processing of all incoming AppleEvents while its preferences dialog is being displayed. What does this mean in most practical terms? iTunes can’t respond to AppleScript while the Preferences window is open. So if you run some script that asks for the name of the current track, for instance, it will hang and give the running app a spinning beachball while you wonder what the heck is going on. Finally you switch to iTunes and realize that you were fiddling with preferences yesterday when you got distracted and switched to another application. Close the dialog, and life goes on as usual.

This is bad enough when it’s inflicted on yourself as the script author and user, but it’s worse when your script is either shared with other users, or included as part of the functional infrastructure for an application. With iTunes integration popping up everywhere, it stands to reason that any developer who uses AppleScript to achieve the integration runs the risk of answering support call from users who have run into this iTunes freeze-out behavior.

The solution? Apple should fix iTunes so it doesn’t freeze-out AppleEvent processing while displaying the Preferences dialog.

The solution for people who live in the real world? We have a few techniques to choose from.

Time Out Gracefully

The first is to use AppleScript’s “with timeout” clause to seriously shorten the length of time it will hang out waiting for iTunes to wake up. We can pick a relatively short period of time in seconds that still constitutes an eternity in scripting time:

try
	with timeout of 2 seconds
		activate application "iTunes"
	end timeout
on error
	display dialog "iTunes is Frozen, try closing the Preferences window!"
end try

Go ahead, try it with the iTunes Preferences window open. This eliminates the outright freeze that users would experience otherwise.

But it feels risky to estimate with confidence the precise number of seconds that is “long enough, but not too long.” Maybe the user just has a really slow Mac, or something unusual happens to slow things down one out of ten times. For many iTunes scripting purposes, activating iTunes and bringing it to the front is part of the procedure. If iTunes is frontmost, then the user will see that the Preferences dialog is open and (hopefully) intuit that they must dismiss the dialog to let the scripted action carry on. The problem is, merely activating iTunes with AppleScript requires that it respond to AppleEvents! As the example script above shows, we can’t even bring it forward when it’s frozen. Or can we?

Blame It On iTunes

We know the user can bring it forward, so how do we simulate that from a script? If we keep iTunes itself out of the scripting loop, we can enlist the services of the system to change the frontmost application:

tell application "System Events"
	set frontmost of application process "iTunes" to true
end tell

Try this script with iTunes Preferences open and you’ll see that it zips to the fore, regardless of the frozen state. By putting this script command first in your iTunes-activating script, you can ensure that users will at least be confronted by the offending Preferences window, instead of staring at a spinning beachball in your application.

Not Give A Damn

Finally, we have the option of sort of throwing our scripting requests out to the world with little regard for their success or failure. For programmer types you can think of this as roughly equivalent to weak-linking a particular system API. If something neat can be done, fine we want it. Otherwise, we’ll keep running business as usual. The “ignoring application responses” clause tells AppleScript to send events but to disregard responses. For instance, the following script asks iTunes to skip to the next track, but shed no tears if that can’t be done right this second:

ignoring application responses
	tell application "iTunes" to next track
end ignoring

The big negative here of course is the script gives no feedback to the user about the success or failure of the intended action. It also silently queues up an action that might disturb the user later. For instance, when she switches to iTunes and closes the Preferences dialog, suddenly her favorite song gets skipped over.

In Closing

As I said, the solution here is for iTunes to grow up and be a real 21st Century application. At least in terms of modelessness. But for anybody trying to work around the failings of iTunes or other event-blocking applications, I hope these tips will come in handy.

Stay Responsive

November 30th, 2006

In general, a user’s changes to a Cocoa NSTextField are saved when they finish editing (by tabbing or clicking to another field) or press return. This is fine, and it works 95% of the time. But on occasion we may find good reason to saved a user’s changes regardless of whether they’ve performed one of these completing actions.

For example, in the “Send to iTunes” feature I’m working on for FlexTime, I decided it was quite possible that a user would fine-tune an activity’s details, say change the text of a “Speak Text” cue, and then immediately select “Send to iTunes.” What should happen in this case? Certainly the user’s changes should be committed and included in the exported audio. The user hasn’t “finished editing” in a conventional way, but by choosing to export the routine, they’ve let me know they are expecting to hear what they’ve just typed.

The cleanest way to end editing for a particular window was explained to me a few years ago by Erik Buck, who kindly explained on a mailing list that it’s best to first ask the window to become firstResponder (thus taking firstReponder away from the focused field), and if that failed, to resort to the crude “endEditingFor:” method on NSWindow.

This works! But it has the side-effect of causing there to no longer be a reasonable first responder for the window. In the scenario I’m dealing with, I want to:

  1. Commit Edits.
  2. Send to iTunes.
  3. Put things back how they were.

I figured if I could be responsible for taking first responder status away from some field in my window, I could just give it back after I was done. But things don’t work out so easily in a world where the mysterious field editor is always being injected into the view hierarchy. My first naive solution resulted in my essentially saving and restoring the field editor as the first responder for the window. But after the editing has ended, the field editor isn’t even on the screen anymore. So setting it to first responder just causes a weird focus-free situation that you can’t even tab out of.

The solution, I realized, was to detect the field editor being the first responder and, in that situation, treat its delegate as the responder that needs to be restored after editing. Now, I’m pretty sure that the delegate of a field editor will always be an NSResponder, but just to be sure, I do some runtime checking. The resulting code for “commit edits while saving and restoring the first responder” looks something like this:


// Save the current first responder, respecting the fact
// that it might conceptually be the delegate of the 
// field editor that is "first responder."
id oldFirstResponder = [oMainDocumentWindow firstResponder];
if ((oldFirstResponder != nil) &&
     [oldFirstResponder isKindOfClass:[NSTextView class]] &&
     [(NSTextView*)oldFirstResponder isFieldEditor])
{   
   // A field editor's delegate is the view we're editing
   oldFirstResponder = [oldFirstResponder delegate];
   if ([oldFirstResponder isKindOfClass:[NSResponder class]] == NO)
   {
      // Eh ... we'd better back off if 
      // this thing isn't a responder at all
      oldFirstResponder = nil;
   }
} 

// Gracefully end all editing in our window (from Erik Buck).
// This will cause the user's changes to be committed.
if([oMainDocumentWindow makeFirstResponder:oMainDocumentWindow])
{
   // All editing is now ended and delegate messages sent etc.
}
else
{
   // For some reason the text object being edited will
   // not resign first responder status so force an 
   /// end to editing anyway
   [oMainDocumentWindow endEditingFor:nil];
}

// If we had a first responder before, restore it
if (oldFirstResponder != nil)
{
   [oMainDocumentWindow makeFirstResponder:oldFirstResponder];
}

This works perfectly for my needs and hopefully it will at least put you on the right path towards making your app work perfectly, too.

Learn Quartz With Scott

November 29th, 2006

Scott Stevenson has posted the second part of his Intro to Quartz tutorial at Cocoa Dev Central.

The visual production of his tutorials is always really impressive, because he identifies (by instinct, I assume) the best graphical illustrations for any given point. This second Quartz tutorial is especially notable for the scope of techniques it covers, from simple paths to complex cutouts, transparent overlays, and drawing directly to an image. These concepts are covered by Apple’s documentation, but never with so much lush imagery as on Scott’s site.

Most of us just blog on a whim, with little attention to overall production value, but Scott’s articles on Cocoa Dev Central are clearly well-planned works of art. He’s accumulated quite a collection of articles that cover many of the basics, and some of the more advanced techniques involved in programming a Mac. Check them out!