Center Windows in Interface Builder

December 6th, 2005

How many times have you been bitten by the problem of moving a window in Interface Builder, for easier editing, and then neglecting to put it back before saving it? Sometimes this can pop up as an embarrassing bug when you realize that your preferences have been saving an appropriate location for the window, but now new users are seeing it pop-up in the lower left corner of the window. Where you left it the last time you edited it in Interface Builder!

One of the ingredients leading to this disaster is the relative difficulty of getting the window back to precisely where it belongs. Many times, a window is saved “centered” with the window autoposition springs set so that it will be centered on whatever screen the user has. But the tedium of getting it “back to center” can make you hesitant to move it to a more convenient position for editing. Worse, it can make it easy to “postpone” the act of putting it back where it belongs until you forget about it and ship that way.

Unfortunately, the amazingly useful alignment and centering tools provided in Interface Builder only work on view-level interface elements. They don’t work on windows. Wouldn’t it be nice if you could easily and quickly snap a window back to a centered position?

My Center Window script lets you do just that. By adding this to the Interface Builder scripts menu, you can easily bounce a window back to horizontal center at any time by selecting the script from the Apple Script Menu, or (better!) by pressing a FastScripts keyboard shortcut. I’m using Cmd-Opt-C to center windows in the blink of an eye.

Note: This script requires the Jon’s Commands scripting addition. If you don’t want to install that, you can modify the script to define the width and height of your main screen manually.

It should be pretty easy to modify the script to also position the Y-axis as you see fit.

Throttling NSSlider

December 5th, 2005

Suppose you want to throttle the rate at which a continuous NSSlider sends its action. To save you from the same fate I met, I will talk you through some of the avenues you might pursue, and ultimately describe the simple solution I adopted which is both easiest and is the only solution I know of that actually gets the job done.

Searching the documentation, you might discover the very intriguing “getPeriodicDelay:interval:” method of NSCell. The description of this method in the Apple documentation is as follows:

Returns initial delay and repeat values for continuous sending of action messages to target objects.

Sounds like a winner! Sigh. It’s not.

The problem, as I’ve deduced after several hours of beating my head against a wall, wondering whether I am in fact not cut out for a programming career after all, is that “continuous” means two distinct things, depending on the type of NSCell you are dealing with. The documentation rarely addresses this, and even misleads us with documentation like this where outright promises are made about the relationship between “continuous actions” and the periodic delay attribute.

The two categories of continuous controls in Cocoa are “periodic” and “send on drag”. Hints at this distinction in the documentation are few and far between, but if you look in the description of NSCell’s “sendActionOn:” method, you’ll see this line (emphasis mine):

You can use setContinuous: to turn on the flag corresponding to NSPeriodicMask or NSLeftMouseDraggedMask, whichever is appropriate to the given subclass of NSCell.

What does it mean by this? It means that controls whose cells are continuous in a user-driven nature, like a user tweaking a knob or pushing a slider, shouldn’t send their value continuously when the user is not … uh, tweaking. Controls like NSButton, which are simply pressed as the user tracks them, need some stimulation for the periodic send: a periodic timer. Controls like NSSlider can rely on the user to move the mouse when the action needs to be sent.

Unfortunately, what I’ve discovered is that the timing of “send on drag” controls is completely un-throttled. The control’s cell appears to send out actions as fast as the user can generate drag events. If drag events were only generated every pixel or whatever, that would be fine (* See End of Entry), but drag events come pouring through your application as the user makes the slightest nudge of their pointing device. Depending on what you’re doing in your slider response, this might be highly undesirable.

Ideally, I would expect that “send on drag” cells should also limit their sending to the interval identified by the getPeriodicDelay:interval: method. “Send on drag” cells should be an optimization of “periodic” cells, not a completely new implementation. This would make all of Apple’s documentation correct. As it is, most of Apple’s documentation having to do with “continuous controls and periodic events” is pretty misleading if not downright incorrect.

Working Around the Problem

It turns out that this problem can be worked around in a pretty simple way that doesn’t even require subclassing the associated control cell. It’s too bad that the documentation was confusing and nothing worked like it said it would(*), but now that I’ve come to this solution, I’m happy it’s the simplest of them all.

Easy Solution: Throttle continuous “send on drag” controls by overriding “sendAction:to:”.

Instead of limiting the frequency of the “send” action, just nip it in the bud. Whenever the cell for your control ultimately decides it’s ready to send an action, it goes through the sendAction:to: method. By subclassing the control and placing a date throttle on the method, you can achieve whatever throttling you feel is appropriate.

Note: Several readers noticed a shortcoming in the original version: the final value was often not sent because the throttling prevented it from being sent when the “expiration date” had not yet arrived. Special thanks to Jack Nutting for observing that a quick check of the current event type would solve the problem nicely. The only reservation I have about this is whether there are automated ways of manipulating a slider that might not involve mouse ups and downs. Does anybody know if this is possible?

The code below has been updated to include a check for this “final value” condition. Let me know if anybody spots other problems that should be remedied!

	static NSTimeInterval kTimeBetweenSends = 0.2;	// 1/5 second

	- (BOOL)sendAction:(SEL)theAction to:(id)theTarget
	{
		BOOL handleEvent = NO;
		
		// Always send action when the mouse comes up. This means that the final 
		// send is probably coming through, which we don't want to miss!
		if ([[NSApp currentEvent] type] == NSLeftMouseUp)
		{
			handleEvent = YES;
			
			// We also want to clear the "next time" so 
			// we start fresh if the user quickly clicks again
			[mNextSendTime release];
			mNextSendTime = nil;
		}
		else 
		{	
			// Only pass actions on if we're not waiting for a throttling date to pass
			NSDate* nowDate = [NSDate date];
			if ((mNextSendTime == nil) || ([mNextSendTime compare:nowDate] == NSOrderedAscending))
			{		
				handleEvent = YES;
			}
		}
		
		if (handleEvent == YES)
		{
			handleEvent = [super sendAction:theAction to:theTarget];

			[mNextSendTime release];
			mNextSendTime = [[NSDate dateWithTimeIntervalSinceNow:kTimeBetweenSends] retain];
		}
		
		return handleEvent;
	}

So if you find yourself in my position someday, my advice is this: ignore Apple’s documentation on this issue and implement something like the above instead. Easier to subclass, easier to inject, and it actually works.

Update: If you’re inside Apple and are wondering whether I wrote a bug or not, here it is: 4365583.

* Update 2: When I said it would be “OK” if it only sent the value when the pixel changes, I was wrong. It’s not OK. In fact, that’s what it does. Kudos to Apple! But… it’s still not OK. I don’t want the changes as fast as the user can move the mouse. That’s too fast! My need for this subclass still stands, though the argument about the pixel-based notifications is null.

Add a Favorites Section to Xcode File Templates

December 4th, 2005

Scenario

If you’re developing software with Xcode, you’re familiar with the typical process of adding a new source file to your project. You hit Cmd-N, and then either navigate or typeselect to the desired file type. Usually, if you’re a Cocoa programmer, this means getting past all the AppleScript, BSD, and Carbon templates you don’t give a rat’s ass about. Even if you use the disclosure triangle to “close” those sections, they’ll reopen the next time you open the dialog.

Wouldn’t it be great if all the file types *YOU* care about were at the top of the list, beckoning you to instantly select them and get on with your job, hobby, or ascetic pursuit of nirvana?

Here’s a trick I discovered that allows just that: place a custom category of templates at the top of the list, with symbolic links pointing to all the desired items in their proper home folders. The best news? Xcode chooses the name of the template for the dialog by the name of the file, so you also rename the link to accurately describe the template in question. For instance, there are templates called “C File” in both BSD and Carbon sections. By linking to the items but renaming the link, I end up with a clear and useful Favorites section that works best for my current workflow:

Note that because the templates in the “Design” section are not true templates, there is no way that I know of to include them in the Favorites section. These items must by dynamically injected by Xcode, because they are not represented by “.pbfiletemplate” files, and include a custom UI in the New File assistant dialog.

Step by Step…

Just so it’s exceedingly clear how this is done, I’ll describe the process as a series of simple steps:

  1. Create a Custom Favorites Folder
    To get your folder to show up above all the other categories, you’ll want to name it so that it sorts alphanumerically early. A trick I’ve used is to insert a space before the word, to make sure it sorts early. The following example shows how you might create the required folder from the Terminal:
    % cd ~
    % mkdir -p "Library/Application Support/Apple/Developer Tools/File Templates/ Favorites"
  2. Find Existing Templates to Link To
    Unless you have existing custom templates in your home directory, you’ll find that all the templates worth linking to are located in Xcode’s File Templates folder. Navigate in the Finder to this directory (Cmd-Shift-G): “/Library/Application Support/Apple/Developer Tools/File Templates/”. You should be looking at a a list of folders in the Finder, each containing a number template items. Items ending in “.pbfiletemplate” are templates folders. These folders are what you want to link directly to.

  3. Add Links to Favorites
    The easiest way to add links to the Favorite folder is to open up a Terminal window, change to the Favorites directory, and use Drag-and-Drop to easily drop in the full paths of templates you want to include. Type “ln -s “, then drag in the pbfiletemplate folder you wish to add, hitting backspace a few times to remove the trailing slash. After your link has been created, you can rename the link to avoid naming collisions with other templates, or to increase the clarity as you see fit. For example, here are the commands I used to add the “BSD C File” template to my list of favorites:

    % cd ~/Library/Application\ Support/Apple/Developer\ Tools/File\ Templates/\ Favorites
    % ln -s /Library/Application\ Support/Apple/Developer\ Tools/File\ Templates/BSD/C\ File.pbfiletemplate
    % mv C\ File.pbfiletemplate BSD\ C\ File.pbfiletemplate
    

    Note: Unfortunately, Mac OS Aliases do not work. You must use a link. I recommend symbolic links because a hard link would not allow you to rename the link as described above (without also renaming the original). (Update: 12/20/2005: Kyle Dean points out in the comments that this is not true. See the comments for discussion.End update.)

Summary

Adding a list of favorites to the Xcode file templates will only save you a few seconds here and there, but the clarity of focus and satisfaction that your tools are working for you and not against you can improve your productivity and increase your desire to work. Enjoy!

A Home for Wayward Podcasts

December 1st, 2005

Anybody who uses iTunes to acquire “podcast” audio files knows that there are some major perks to using iTunes for this purpose as opposed to simply downloading the mp3 file with NetNewsWire or your web browser. The biggest advantages to me are:

  1. New iTunes podcasts are automatically transferred to your iPod
  2. Old (already listened to) iTunes podcasts are automatically removed from your iPod.
  3. “Legitimate” podcasts show up in a special “Podcasts” section on your iPod.
  4. Your iPod and iTunes “remember where you were” when you stop listening midway. (Update: See bottom of entry.)

These relatively modest improvements to the regular “mp3 listening” process don’t exactly make managing and listening to podcasts pain free, but they help. When you are forced to abandon them because your “looks and talks like a podcast” mp3 file doesn’t happen to satisfy iTunes’ idea of what makes a podcast, the absence of these features becomes extremely annoying!

I often come across a “podcast episode” that is packaged as a simple mp3 link on somebody’s web page. Or I decide that a particular episode from a regular feed is intriguing, but I don’t want to subscribe to the whole feed just to get that one episode. Maybe I don’t *want* to listen to all the other episodes in that feed! In these situations, a highly annoying fact of life is that iTunes will not be coerced into treating a plain mp3 file as a podcast episode.

Grr!

A Solution: Typecast 1.0 Beta

These are the types of nuisances that regularly cause my “project ideas” list to expand to such length that few items have any hope of ever being implemented. Usually I just grind my teeth and work around the problem the way everybody else does: with sweat and tears! In this case, however, I found the limitations of iTunes to be a threat to my way of life. I need to have those plain mp3 files treated as Podcast episodes. I will have my way with iTunes.

I am happy to announce Typecast 1.0 Beta a FREE utility for performing one particular feat of daring: slamming arbitrary audio files into a “faked-to-order” Podcast subscription for iTunes. This is surprisingly hard to do, because iTunes doesn’t provide any mechanism (that I could find!) for coercing arbitrary audio files into podcasts. So what’s my secret? Believe it or not, I had to implement a custom HTTP server that, from within Typecast, listens on a high-numbered port. I then compose an RSS-format feed for the user-specified files and URLs, before sending the subscription request for this “fake” podcast directly to iTunes. The number of nasty workarounds this product’s implementation required is really depressing. Fortunately for you, they’re all hidden behind the exceedingly clean and friendly interface of Typecast.

Still don’t get what this is good for? Looking for an example page to try it out on? Check out CIO’s interview with Joel Spolsky. See the link that says “Listen to the Podcast” (replicated here for your convenience)? Now, how do you get that into iTunes as a Podcast episode? You can’t drag anything to the Podcasts playlist. You can’t AppleScript it. You can’t subscribe directly to an mp3 file. You can’t invoke some secret iTunes URL scheme. You can’t do it! Sucks to be you!

Now, download and launch Typecast. Left-Click and drag the mp3 link directly from Safari to Typecast. Tweak the titles and descriptions to your liking (or don’t), and “Send to iTunes.”

Suddenly it’s real easy. And I can cross an item off of my “fix the nuisances” project list.

Quirks and Caveats

This project is sort of on the borderline between “clever hack” and “usable product.” I’d like to think of it as a “clever hack with unusually smoothed-down edges.” Nonetheless, there are some disclaimers to be made.

One feature of this application that may annoy you is that every time you “Send to iTunes” it produces a new subscription in iTunes, instead of updating an existing one. This limitation is the fall out of relatively poor scripting support in iTunes for podcast-specific control. The very limitations that led me to produce this solution also limit my ability to do it perfectly. I have chosen to opt for a philosophy that allows me to at least “do what I do well.” Since updating the “fake podcast” once it is absorbed into iTunes is “difficult to impossible,” I’ve adopted the policy that every Send is a unique “fake feed.”

Although this has the unfortunate effect of causing multiple “subscriptions” to show up in iTunes, they will all be merged into a single “show” on your iPod. For instance, I publish wayward podcasts to iTunes under the podcast name “My Rescued Podcasts.” When I’m listening through my iPod, all the various podcasts I might have added with that fake subscription get unified into one choice on the iPod.

Typecast is the server! This is important, because it means that iTunes has to finish downloading everything specified in your pseudo-podcast before you quit Typecast. It also means that every future attempt to “update” a podcast injected in this way will quietly fail. No big deal, but you should know about it.

The Future of Typecast

Do you like this solution? If so, let me know. The future of Typecast is largely dependent on your expression of interest. Possibilities for future enhancements could include AppleScript support, multiple podcast documents, and my delving deeper into the possibility of convincing iTunes to “update” a previously sent Podcast. Heck, someday Typecast might even get an icon! (Like Typecast and have a knack for graphic design? How about contributing an icon? Leave a comment or contact me directly if interested).

Update: One of the cited benefits of having iTunes treat an arbitrary audio file as a podcast, that the iPod and iTunes share a memory of when you stopped listening last, is actually easy to apply to any iTunes file track. Just use AppleScript to set the “bookmarkable” attribute of any tracks you want to behave in this way. Good to know!