A Table View For The Ages

July 26th, 2006

Cocoa contains an awesome but sort of half-baked infrastructure for “autosaving” UI configurations for the user. Many of the common UI elements, such as windows, table views, and toolbars possess the ability to write out their configuration to the app’s preferences so they can be automatically restored the next time the app is launched.

When this works, it works. And it is, as I said, awesome. But for such a cool idea there is a great lack of consistency in its implementation. NSWindow lets you autosave the frame by setting a unique name under which it will be saved. The API is rather extensive:

+ removeFrameUsingName:  
– saveFrameUsingName:  
– setFrameUsingName:  
– setFrameUsingName:force:  
– setFrameAutosaveName:  
– frameAutosaveName  
– setFrameFromString:  
– stringWithSavedFrame 

Notice that there is no BOOL “autosavesFrame” method – it just figures if you set a name, you want it to be saved. This name can even be set from Interface Builder. The number of methods is a bit large for what seems like a simple operation, but what’s really nice is it exposes the “magic format” to developers so we can override the default behavior or make different use of the autosave information.

For example, let’s say you wanted to implement a funky little “Exposé” feature for just your application, something that tiles all the windows so that they fit neatly into the available screen real estate. That’s fine, but you should be prepared to put all the windows back the way the user had them before. Using “stringWithSavedFrame,” this is trivial. Just iterate over your open windows and collect the screen position information in a format that NSWindow itself guarantees to be usable for resetting it. It’s not like we couldn’t figure it out ourselves, but AppKit already figuerd it out! Thanks for sharing.

NSToolbar takes a different approach. Instead of allowing developers to participate in the complicated question of “naming” the autosaved information, it offers a simpler API, implying it will come up with a way of saving the information itself:

– autosavesConfiguration  
– setAutosavesConfiguration:  
– configurationDictionary  
– setConfigurationFromDictionary: 

This is a great compromise. We’ve lost the ability to influence naming conventions, but who cares? They still give us access to the magic format! So if we don’t like the way it’s handling autosave, we just turn it off and implement our own with the help of “configurationDictionary.”

But NSTableView, oh NSTableView. You had to go your own way:

– autosaveName  
– setAutosaveName:  
– autosaveTableColumns  
– setAutosaveTableColumns:  

NSTableView lets us alter the autosave name, but unlike NSWindow, merely setting the name doesn’t imply that the feature is active. We have to set that BOOL separately. Notice how NSToolbar’s technique uses the same number of methods, but provides a lot more flexibility. The worst part about NSTableView’s approach is it hides the magic data so we can’t even override the default mechanism.

If only NSTableView exposed its magic data!

I really wanted this functionality, because in developing FlexTime, I decided to offer a feature to “save window layout” to the document itself. The other autosaving mechanisms work well with this approach – just grab the magic data and archive it. NSTableView stymies me, though! Perhaps I could set an autosaveName, then do something to provoke it being saved, then look it up manually. Nah! It’s all too fragile.

NSTableView+RSAutosaving is my solution. Yours too, under MIT License. It’s a category on NSTableView that adds the missing (IMHO) methods:

- dictionaryForAutosavingLayout
- adjustLayoutForAutosavedDictionary:

The “magic data” in this case consists of the widths of every column and their ordering in the table. There are a few subtle gotchas to doing this right, and I think I did it right. So you’ll be glad if you end up not writing it yourself.

A Gentle Touch

July 24th, 2006

Anybody who packages software (or other files) for distribution on the Mac is probably familiar with the tedious process of setting up the icons “just right” in the Finder so that when your customer opens the folder for your app, they see things neatly organized the way you left them.

I don’t know how many times I’ve longed for a “nudge” keyboard shortcut in the Finder. If I could just move that icon one pixel to the left, it would be perfect. Inevitably I click and drag just a little bit too far. It’s hard to get right on the first try!

But today my worries came to an end. My NudgeFinderSelection scripts simply ask the Finder to move all the selected icons in a given direction. Since the behavior is is 99% identical for whichever direction the nudge should be, I just wrote a single script and copied it 3 times. The script examines its own name and determines its functionality accordingly.

Note: Because of this “self-determination,” you can customize your own version to go above and beyond the current behavior. Try making a copy and naming it “NudgeDownRight” for instance, and you’ve got yourself a nice little diagonal nudger.

I hooked up all four of these as Finder-specific shortcuts with FastScripts. Now when I’m tweaking icon layout in the Finder, a nudge is just a short cmd-opt-arrow-key away.

Note: my “clever hack” here begs the question, why doesn’t FastScripts respect symbolic links? It would be much cleaner to have a single copy of the file with multiple references to it. Yes, it’s a grievous shortcoming, and hopefully situations like this will inspire me to remedy it soon.

And since you’ve read this far, I think you deserve a little treat. Ever thought that the icons in a window would look best arranged in sine wave formation? Well, you were probably wrong, but SinusInfection will guide you to forming a permanent opinion. Unfortunately, there’s no undo, so don’t run this with an important Finder window front-most!

It’s not a very polished script, and may take some tweaking to work right on your system. C’est la vie! In particular make sure you have an open window with a substantial number of items in it. And turn off any automatic “keep organized” settings in the Finder view options. I’ve also noticed that Finder can sort of freak out while you’re doing this, apparently because so much is changing out from under it. The sine wave tends to get broken and re-originates at a slightly different window offset.

FlexTime 1.0b6

July 24th, 2006

The feedback I’ve gotten on FlexTime since releasing the last beta almost two weeks ago has been simply amazing. I really appreciate all the thoughtful comments people have made. While there has been some scrutinizing the limited feature set of this initial version, most of the feedback has focused on eliminating annoying bugs and making minor tweaks to improve the usability of the product.

I’m really happy with how things are progressing.

FlexTime 1.0b6 contains a number of fixes directly motivated and inspired by beta testers. Please note before downloading that the document format has changed in this release. To ease the transition, I’m including a “Document Upgrader” droplet that should take care of any old documents you have created with previous beta releases. BUT you should update all of your documents and never look back at older FlexTime releases. Please, do it for the children.

Update: Andy Lee found a heinous UI problem in 1.0b6 that prevented repeating cues from being set on new activities. Ouch! I’ve made a quick fix and upped the download to 1.0b7.

Click Here to download FlexTime 1.0b7.
Reminder: Be sure to drag your old documents to the upgrader droplet!

So now that we have the big scary document change out of the way, what else is in store with this release? Here are my notes:

- Revised About Box 
- Custom document icon
- Add "Repeat when finished" checkbox to main document window
- Add a "Remember Layout" menu item to save info in document:
	- Window frame
	- Table column order and sizes
- Scriptability
	- Added support for a "reset" command
	- Added scriptable access to the "running" attribute
		of a routine document
			- Caveat: cannot stop/resume from within a cue
			handler script at this time.
	- Added a "Display Message" command to give script access
		 to the text message mechanism
	- Early support for a special handler in AppleScript cues.
		HandleFlexTimeCue(theDocument, theActivity) gets called
		if exists within the cue script
- Fix a bug that prevented scripts from being chosen for Run Script
- Fix crashing bugs with "Speak Text" cues
- Fix a crash that could occur when a script alias was stale
- New documents now start out with a single default activity
- Open documents are now preserved across launches

Executive summary? Lots of bug fixes, a slightly cleaned up UI, and a controversial “Remember Layout” feature. Let me say now that this item is probably going to disappear before 1.0 final, so don’t get too used to it. The idea is that some FlexTime routines are suited to a particular UI layout and that layout should be remembered by the application. The question is whether the remembering should be automatic or explicit. I think I’ve been convinced that it should be automatic, and be saved in the user’s preferences instead of the document itself. If you’ve got strong feelings about this, now is the time to make your case.

So what’s left before it goes final? Mostly just documentation and finishing up the AppleScript support. Keep your eyes peeled for a final release within the next few weeks.

Iron Coder Three

July 23rd, 2006

The third Iron Coder concluded tonight. Mark Dalrymple emerged victorius! I’m really happy for Mark’s win because his entry was fun, and because it was clear he latched on to the opportunity to use the contest as an opportunity to really learn more about the Mac OS X APIs. I also have a lot of faith that Mark will be a good “chairman” for the next contest.

I came in 3rd or 4th, depending on how you do the math. My entry this time is called What Have You Done For Me Lately. It’s as a relatively snazzy (IMHO) application that gives the user a visual impression of how much time they’re spending in each application over a given period of time. Since the API for this contest was “CoreGraphics,” I used it as an excuse to play with patterns, which I use to fill the bar-graphs.

Download all the entries for the latest contest in a single DMG! Click here. (Thanks to Jon Wight for putting this together).

I take a different approach to Iron Coder than many of the contestants. There is a tendency to make “something cool” at the expense of refinement. These submissions are awesome! They make me laugh and stun me with their creativity. But I stubbornly cling to a different ideal. I use the event as an opportunity to play “what if a product’s entire cycle was 48 hours.” Now, I won’t go so far as to say my submission is bug-free, but I try to make it “release-ready.” That means I hold off adding features if the time limit doesn’t support it. I like the constraints that a 48 hour deadline gives me for producing a “relatively shippable product.” Instead of asking myself how much I can possibly cram into the submission, I ask myself how much can I make “user-ready” and try to accomplish that.

At the end of 48 hours (with time out for sleep, family, and social commitments), what I submit is not exactly product material, but something I can proudly attach the Red Sweater brand to.