Archive for October, 2006

Vienna release candidate

Sunday, October 29th, 2006

Vienna 2.1.0.2107, the first release candidate of Vienna 2.1, is now available. It will be decided in early November whether the incumbent, build 2107, or the challenger, build 2108, is elected to the position of Gold Master. (That’s slightly below Jedi Master, slightly above Thighmaster.) Your vote will not count, however. The election is rigged, and the outcome will be determined secretly, using computers, by a coterie of the powerful (me and Steve).

You can read about the changes since the last release in the release notes for build 2107. Bugs should be reported in the forum, not here. Run, don’t walk, to your local mirror and download Vienna 2.1.0.2107. You have your orders. Dismissed!

Mouse tracking by Lap Cat

Thursday, October 26th, 2006

How do you call among you the little mouse, the mouse that jumps? Oh, that’s right, we call that one Mighty Mouse. Actually, my cat prefers the keyboard. He must be a hacker at heart. (A hacker and a slicer.)

In the post Single-click renaming in NSTableView, I mentioned that Cocoa gives the appearance of being able to see into the future. Now I’ll describe another example of this phenomenon. Strange things are afoot at the Circle K! Our story begins with a Vienna bug: mouse-clicking in another browser tab didn’t always bring the other tab forward. The bug seemed to occur at random, and that, as you know, is the worst kind of bug. (Though yellow jackets are pretty bad too, I’ve discovered.) So how do we debug de, err, the bug?

Here I come to save the day! Perhaps I’m old school (the Academy), but I find old school methods to be useful, and this looks like the perfect situation for NSLog(). (It’s a little known fact, however, that NS stands for New School.) I’ve uploaded a simple demo application project, TrackingRects, that simulates mouse tracking for Vienna browser tabs with close buttons. The application presents a window containing two larger boxes, or ‘tabs’, each of which contains a smaller box, or ‘close button’. Every box has a mouse-tracking rectangle, or ‘cat’, within its borders, and all of the resulting mouseEntered: and mouseExited: messages are logged.

If you move the mouse in and out of the boxes slowly, then everything works as expected. Weirdness begins to happen, though, if you zip the mouse through the boxes. According to the logs, the mouse has entered the second box before it exited the first box. You can’t be serious! You cannot be serious! Yet logs don’t lie, folks. (Although they do sometimes commit sins of omission.) Apparently, Cocoa likes to send mouseEntered: messages before mouseExited: messages when they’re close enough together. It’s almost as if your mouse were approaching the speed of light, and events that appear in sequence from the mouse’s perspective appear simultaneous from the perspective of the stationary window, because of the principles of special relativity. Wow, you didn’t think you needed to be a rocket scientist to write Cocoa apps, did you?

Moral of the story: Do not rely on the runtime to send event messages to your code in a particular order. And most important, do not do your homework without wearing headphones.

The post is over, thought I’d something more to say.

More problems with WordPress feeds

Monday, October 23rd, 2006

Welcome to the second installment of WordPress Bug Friday. You may have noticed that it’s Monday, but … uh … weekends don’t count. Yeah, that’s the ticket! Furthermore, it’s almost the end of Daylight Saving Time, so I’m just getting a head start on rolling back the clocks.

Until I saw it in my web site logs, I didn’t even realize that my blog had an Atom feed. Incidentally, I’m a bit surprised that my logs are filled with requests from NetNewsWire. I am the developer of Vienna after all! Where are you, Vienna users? Anyway, the WordPress template for Atom is obsolete. We’re talking Edsel, Betamax, Eric Clapton obsolete here. The template, contained in the file wp-atom.php, is based on Atom 0.3, which has been deprecated.

The other issue I had was that I revised the post Build settings for Xcode projects on Sunday, but the post didn’t show up as revised in Vienna, which has a neat little feature I added that gives revised articles a green dot to distinguish them visually from blue-dotted new articles. (If you prefer, you can turn the feature off with defaults write uk.co.opencommunity.vienna2 CheckForUpdatedArticles -bool no.) Neither the Atom nor the RSS 2.0 feed showed the post as revised. Indeed, I was getting the dreaded HTTP 304 response (Leave me alone, I’m playing Doom!).

The bug with the (obsolete) Atom feed was that the template calls the same function, get_post_time, for both the (obsolete) issued and (obsolete) modified fields. The correct post creation and modification dates are listed in the blog’s database, so I could use get_post_time for one and get_post_modified_time for the other. With the RSS 2.0 feed, in contrast, it wasn’t clear that there was a bug, because RSS 2.0 has only one default field for item dates, pubDate. I wanted post revisions to show up in the feed, so I replaced get_post_time with get_post_modified_time for pubDate in the WordPress RSS 2.0 template file, wp-rss2.php.

If your WordPress posts have date-based permalinks, do not, I repeat, DO NOT edit the timestamp of an already published post in order to change the date in the feed. (And don’t sit with your back to any doors.) Editing the timestamp will not only change the date in the feed but also change the URL of the post! You’re a very bad man, Jerry, for suggesting this. Fortunately, I was able to repair the damage quickly. Even if your posts don’t have date-based permalinks, editing the timestamp could mess up your archives. To change the date in the feed, use my method of calling get_post_modified_time in the feed template.

I’ve updated my blog’s wp-atom.php file to conform to the Atom 1.0 specifications and made it available for download at wp-atom.txt (the extension has been changed from php to txt). You might want to consider using the attribute type="xhtml" rather than type="html" for the content and summary fields, depending on how your posts are formatted. One element that’s missing from the template is the recommended rel=self link to the feed itself, because I didn’t know how to request the feed link. I’d appreciate help from WordPress experts out there.

Tune in next week, same bat time, same bat channel, for the next exciting installment of WordPress bug Friday! Or maybe some other bat time.

From the days of yore

Friday, October 20th, 2006

When you work in an office, or more precisely a cubicle, you often receive an inheritance from the previous occupants. In my last office I inherited a number of books, which I didn’t pay much attention to until I moved out. Among the chaff, I recently discovered some wheat. Well, not really wheat: more like wood pulp, quite inedible wood pulp, but interesting inedible wood pulp nonetheless.

  1. Macintosh Pascal by Robert Moll and Rachel Folsom, 1985.
  2. Macintosh Revealed: Unlocking the Toolbox by Stephen Chernicoff, 1987.
  3. Macintosh Revealed: Programming with the Toolbox by Stephen Chernicoff, 1987.
  4. Macintosh 512K enhanced by Carol Kaehler, Apple Computer, 1986.

The last book is particularly interesting, because it’s a manual. That’s right, folks, believe it or not, Macs used to come with a manual! Actually, Macs still come with a manual, but it’s mostly a hardware manual, whereas the old manual gives extensive instructions on how to use the operating system and software. I could tell that it was from the 80s without even looking at the publication date, because the people in the photos are all wearing official 80s uniforms, hairstyle included. I imagine that a-ha is playing in the background.

A few things struck me in looking through these books. First, a cloud of dust in my face. Second, it struck me how much has remained the same over the years. The Finder is still there, along with the Desktop and the majority of menu items, keyboard shortcuts, and mouse behaviors. I was intrigued by something called the MiniFinder, which seems to be a cross between the Dock and the application switcher. (Really, it wasn’t so mini, because it took up the entire screen.)

Another thing that struck me was the status of Pascal as the primary language for the Mac at that time. I wrote Pascal programs a lot as an undergraduate. I think I last saw Pascal on one of those VH-1 Where are they now? specials.

The final thing that struck me was the vast number of years Chris Espinosa has spent at Apple. The acknowledgments of Stephen Chernicoff’s books thank Chris Espinosa as one of his managers at Apple. Chris still posts frequently as a representative of Apple on the Xcode-users mailing list. Of course, I’m just assuming that it’s the same person rather than, say, the second generation of Espinosas, and that Chris has been at Apple the whole time.

Sorry, this post doesn’t offer any useful information. If you didn’t like it, you can send me feedback, but you have to use a #2 pencil.

A matter of style

Tuesday, October 17th, 2006

I was dreaming when I wrote this. Forgive me if it goes astray. Like many programmers, I’ve experimented with several coding styles. (But I didn’t inhale.) The point of this experimentation was not simply aesthetic, although I do believe that eye-appeal is important when you’re staring at something for hours a day. Good coding style makes it easier for you and others to understand, debug, and modify your code. It can also prevent bugs from occurring the first place.

In essence, coding styles offer alternative presentations of code that the compiler treats as the same. Compilers, it turns out, have no taste. A compiler wouldn’t care if your entire application appeared all smushed together on one line. That, by the way, is one of the reasons we prefer not to talk to compilers directly; the preprocessor is a much more civilized conversational partner.

In zoological order below, I express my current opinions on coding style, for whatever they’re worth. (Make me an offer.) I of course encourage a diversity of viewpoints, so in the comments to this post I look forward to hearing from both sides on these issues: those who agree with me and those who vehemently agree with me.

Making it explicit

I know what you’re thinking, but get your mind out of the gutter! The headline refers to giving clear indications in your code of things that would otherwise be handled automatically according to the specifications of the language. For example, in expressions with multiple operators, I put parentheses around the sub-expressions, 1 + (2 * 3), instead of relying on the implicit order of operations, although I do make an exception when the main operator is an assignment, sum = 1 + 2. When checking for 0 values, I put the 0 values in my code: if (pointer != NULL), if (pointer == nil), if ([array count] > 0), as opposed to if (pointer), if (!pointer), if ([array count]). I use casts, (int)count, and constant suffixes, 0u, instead of allowing implicit type conversions. This applies to function and method arguments and variable assignments too!

Even if you know the operator precedence and type conversion rules like the back of your hand — actually, I can’t say that I give much thought to the back of my hand — others who read your code might not. When these are left implicit, bugs are destined to arise. In fact, I recently fixed a bug in Vienna where some rows were too short because the methods took floats but the calculations were done as integers, thus truncating the fractional values. Besides, implicit rules can vary from language to language, so if you’re concerned about code portability, or perhaps the sheer effort of mastering multiple languages, why not just free your code from reliance on the linguistic eccentricities?

By the way, I really like the way Java handles logical expressions, using only boolean values. This is definitely a bias due to my background in logic. I want boolean expressions to be real booleans, not integers! My instinctive reaction when encountering if (expression) is to treat expression as boolean, so I don’t want to do a double-take every time just in case I should really be thinking non-boolean.

Goto jail. Do not pass Go. Do not collect $200.

On the one hand, it’s painful to read function and method implementations that consist entirely of nested if clauses. On the other hand, a single return at the end makes an implementation easier to understand and debug. I’ve come to the conclusion, then, that goto is the way to go. I admit that it seems very BASIC, but give it a chance.


-(BOOL)doSomethingWithArgument:(id)argument {
    BOOL success = NO;

    if (argument == nil) {
        goto end;
    }
    statements
    if (condition) {
        goto end;
    statements

    success = YES;

    end:
    return success;
}

Space: The final frontier

I put a space before before and after binary operators, 1 + 2. The only exceptions would be the operators for structure or union membership, a.b and a->b, which I don’t put space around, but you could argue that they are actually binary postfix operators, while other binary operators are infix.

I don’t have a strong opinion about whether to put space after an opening parenthesis and before a closing parenthesis, such as with expressions or function arguments, (1 + 2) vs. ( 1 + 2 ). I tend not to use space in these cases. However, I do have a strong opinion about whether to put space after the asterisk in a declaration: YES! In some cases, leaving out the space is highly misleading. For example, int *array[50] does not declare a pointer to an array of integers but rather an array of pointers to integers, so it’s better to use int * array[50]. The * character already has too many functions — pointer declaration, pointer dereferencing, multiplication — which is why I think it’s best to distinguish them as much as possible.

In the past, I preferred that the opening brace of a code block appear on its own line.


while (condition)
{
    statements
}

I suppose that the logician in me enjoyed the symmetry of opening and closing braces at the same level. However, I’ve come to appreciate the virtues of the other standard.


while (condition) {
    statements
}

Using this style, it’s just as easy to determine the beginning and ending of the code block, and over the course of many blocks you fill a lot less vertical space, which means that you can see more of your function or method at once (which is a good thing). Moreover, there are instances where you want to put something after the closing brace, such as in a do while loop.


do {
    statements
} while (condition);

Therefore, if you want to be consistent, you shouldn’t be opposed to similar constructs elsewhere.


if (condition) {
    statements
} else {
    statements
}

Speaking of consistency, I like to use the ‘shorter’ style for function or method implementations (and even for Objective-C @interface ivar declarations).


type function(arguments) {
    statements
}

It may be true that functions and methods are ‘special’, but I believe that the same considerations apply to them as to other blocks. I don’t understand why developers would deviate here from the coding style used everywhere else.

Party’s over

Oops, out of time.

Build settings for Xcode projects

Sunday, October 15th, 2006

Here’s an iFAQ: Jeff, how do you choose the topics for your blog posts? Answer: I don’t. All of my topics are actually chosen by manatees. For some reason, those crazy manatees have indicated that today I should talk about build settings for Xcode projects. They’re inscrutable! The manatees, that is, not the build settings…at least not for the most part.

Some developers never dare to change the default build settings. Why? Because the Xcode build settings pane is scary — perhaps not as scary as the Oompa-Loompas, but scary nonetheless. And like the Oompa-Loompas, there is more than one pane. Besides the project build settings, each target in the project has its own build settings too. It’s enough to drive you mad! (That’s what happened to this friend of mine, so he had a lobotomy. Now he’s well again.) Still, you might want to change the build settings in your project, because the default settings were designed for backwards compatibility, not for the latest-and-greatest, state-of-the-art, up-to-the-minute, cutting-and-bleeding-edge technologies (such as Mac OS X 10.2). Even Apple recommends changing the value of ALWAYS_SEARCH_USER_PATHS, for example.

The default build settings for a project, indeed many of the elements of a project, come from a template in the folder Project Templates. When you create a new project in Xcode, it uses the template corresponding to the project type. The template for a Cocoa application project is in the Cocoa Application folder. Like other items in the /Library/ folder, you can override the default template in a particular user account, say, yours, by putting a parallel item in the user’s ~/Library/ folder. To change the template for a Cocoa application, create the folder ~/Library/Application Support/Apple/Developer Tools/Project Templates/Application/, copy the Cocoa Application/ template to that folder, and modify your copy of the template. The build settings are stored in the file CocoaApp.xcodeproj/project.pbxproj. If you’re adventurous, you could edit that file directly. On the other hand, you could just open the CocoaApp project in Xcode and change the build settings there, but afterward you’d want to delete the Xcode-generated files such as build/ in the project folder as well as the .mode1 and .pbxuser files in xcodeproj/. You might also want to delete those pesky Finder-generated .DS_Store files. (Please, please get rid of them in Leopard!) Xcode will use your custom template rather than the factory template whenever you create a new Cocoa application project.

There are many different types of project template, even for a Cocoa application — Cocoa Application, Cocoa Document-based Application, Core Data Application, etc. — so it could become tedious to change every template you need. If you only want to change the default build settings, it would be much easier to create a build settings configuration file that you can use in all of your projects.

Which default build settings should you change? As I mentioned, you should uncheck Always Search User Paths. You should check Enable Objective-C Exceptions (GCC_ENABLE_OBJC_EXCEPTIONS) to use @try, @catch, and @throw exception handling. You’ll probably also want to set the value of Mac OS X Deployment Target (MACOSX_DEPLOYMENT_TARGET) to the oldest version that your application will support. If you want to be cruel, set it to 10.5.

The most confusing collection of build settings, in my opinion, are the compiler warnings. Apple has an interesting article about this, and you can find definitions of the warnings in the GCC manual. If you hate to RTFM, you’re in luck, because I already did. I recommend using the warnings Missing Function Prototypes (GCC_WARN_ABOUT_MISSING_PROTOTYPES), Missing Newline At End Of File (GCC_WARN_ABOUT_MISSING_NEWLINE), Sign Comparison (GCC_WARN_SIGN_COMPARE), and for Other Warning Flags (WARNING_CFLAGS), -Wall. You would think that -Wall would cover all warnings, but noooooooooooo! However, the -Wall option does enable the default warnings in a Cocoa application project, Mismatched Return Type (GCC_WARN_ABOUT_RETURN_TYPE) and Unused Variables (GCC_WARN_UNUSED_VARIABLE), so those settings can be deleted when you use this flag.

If you would like to use my settings, you can copy the text below into an .xcconfig file. The values of SDKROOT and PREBINDING are preserved from the default Cocoa application template. For information on prebinding, see the documentation.

P.S. The manatees have asked me to remind you to use precompiled prefix headers!

// Jeff's project build settings
SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk
ALWAYS_SEARCH_USER_PATHS = NO
PREBINDING = NO
MACOSX_DEPLOYMENT_TARGET = 10.4
GCC_ENABLE_OBJC_EXCEPTIONS = YES
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES
GCC_WARN_ABOUT_MISSING_NEWLINE = YES
GCC_WARN_SIGN_COMPARE = YES
WARNING_CFLAGS = -Wall

Update: I’ve revised the build settings to remove some warnings. It turns out that the flags -Wall -Wextra already enable GCC_WARN_UNUSED_PARAMETER, and that warning can get annoying anyway, so I removed it and the -Wextra flag. I removed the flag -Wconversion too, because it does far more than I wanted it to. The warning GCC_WARN_SHADOW was ditched because it doesn’t like you to use the variable index. I added GCC_WARN_SIGN_COMPARE, which was enabled by -Wextra.

WordPress bug with post comment feeds

Friday, October 13th, 2006

In the tradition of Apple Bug Friday (RSS), this post inaugurates WordPress Bug Friday. Actually, it’s just a coincidence that I fixed the bug on a Friday. Anyway, the bug is that the comments feeds for posts were returning HTTP code 304 (Not Modified) in Vienna, even though the posts had new comments that did appear in the main comments feed for the blog. Vienna, being a good net citizen, reads the Last-Modified date from a feed’s HTTP headers and sends the date back as If-Modified-Since in its request headers to the feed.

The cause of the bug in WordPress 2.0.4—as well as earlier versions of 2.0, I believe—appears to be that the comments feeds for posts were checking the last modified date for posts rather than the last modified date for comments, because unlike the main comments feed for the blog, http://lapcatsoftware.com/blog/comments/feed/, the comments feeds for posts, e.g., http://lapcatsoftware.com/blog/2006/10/09/stand-and-unfold-yourself/feed/, do not contain /comments/ in their URLs.

I discovered a ticket at the WordPress Trac discussing the bug, along with a patch. The patch was just two lines of code (or one, depending on how you count), so the change in the file /wp-includes/classes.php was easy to make, and it seems to be working fine now. Please let me know if you experience any problems.

While I’m on the subject of WordPress bugs, I’ll mention another one that has bit me a few times. If your post includes escaped HTML characters such as less-than and greater-than symbols, then when you edit your post again after saving or publishing, WordPress unescapes the characters in the editing view. Consequently, if you don’t re-escape those characters before saving or publishing, your post will be messed up. The technical term to describe this situation is lame. In some circles, they use sucky. My workaround is to compose a post entirely in a text editor such as Smultron and ensure that everything is perfect before I enter the post once and only once in WordPress.

Thus our first edition of WordPress Bug Friday comes to a close. I would be remiss, perhaps, if I did not also throw in an Apple bug. Since this post is lacking somewhat by my usual standards of (attempted) humor, I’ll mention an amusing and harmless little bug I came across. As a member of the Apple Developer Connection, I receive a monthly mailing containing a DVD with documentation and software updates. On the mailing label a few months ago, between my name and address, there was a line with the text NULL. Obviously the mailing label software was printing an additional field that in my case was empty. Given my programming background, I found this to be hilarious. (Admittedly, no one else did.)

By the way, I also find it amusing that ADC keeps track of us by PersonID. I am not a number, I am a person! Oh wait, I’m both.

Single-click renaming in NSTableView

Thursday, October 12th, 2006

Greetings, programs! Welcome to another installment of…um…this blog. I’m going to talk about the very important topic of renaming, because nobody is happy with their given name. Can you imagine Eleanor Gow wearing a Ralph Lifschitz dress? When I was born, I was destined to become Kid Dyn-O-Mite!

In earlier versions of Vienna, double-clicking a feed in the folders list started editing the feed’s name. The folders list is an NSOutlineView, which is a subclass of NSTableView and inherits much of the superclass behavior, including the default behavior of editing when a table cell is double-clicked. Daniel Jalkut suggested to me that double-clicking should open a feed’s home page, because the user will probably want to do that much more often than rename the feed. I agreed, but in order to follow his suggestion, I had to override the default behavior of NSOutlineView. It took me six revisions to finally get this right. (svn commit -m "D'oh!") The same technique works for both NSOutlineView and NSTableView, so I’ll examine the more general case.

According to the class reference for NSTableView, you need to do four things to override the default double-clicking behavior.

  1. Make the table cell uneditable. One way of doing this would be with the delegate method tableView:shouldEditTableColumn:row:.
  2. Set the double action for the table (setDoubleAction:).
  3. Set the action for the table (setAction:).
  4. Set the target for the table (setTarget:).

However, getting the single-click and double-click behaviors you desire is not as simple as setting the action and double action. The catch is that the table’s action method gets called for many different reasons. For example, the action method gets called as a result of the first click of a double-click. (Ted: No way! Bill: Yes way!) The action method also gets called when you click to make the table the first responder or to change the selection in the table. The irony is—isn’t it ironic, don’t you think?—that the change in table selection or first responder status is a result of your click, but the action method gets called after tableViewSelectionDidChange: or becomeFirstResponder. It’s like the runtime is prescient! There must be some spice in that Cocoa.

What you want is for a single-click to trigger editing only when the table is already the first responder and not when changing the cell selection or double-clicking the cell, but you need to take into account that the selection can change via mouse or keyboard, and the table can become first responder in any number of ways:

  1. As the initialFirstResponder of the window
  2. As the recipient of a mouse click
  3. As the nextKeyView of the first responder
  4. As the recipient of a makeFirstResponder: message

Are you exhausted yet? Never fear, because I’ve done the rest of the work for you. The trick to weeding out those ‘retrospective’ single-clicks is to set a short timer that prevents single-click renaming after your non-renaming events.

The following code is adapted from Vienna’s FoldersTree and FoldersView classes. I know it’s bad form to combine your model, view, and controller in one class, but JJTableView does have the virtues of brevity, which is appreciated on the web, and full functionality, when applied to a table in Interface Builder. This code is released under the SHAG license: if you use it in your app and become rich and famous while I linger in poverty and obscurity, I will Silently Hold A Grudge.

#import <Cocoa/Cocoa.h>

@interface JJTableView : NSTableView {
    NSMutableArray * array;
    BOOL canRename;
    IBOutlet NSTextField * textField;
    NSTimer * timer;
}
-(void)doClick:(id)sender;
-(void)doDoubleClick:(id)sender;
-(void)enableClickToRenameAfterDelay;
-(void)enableClickToRenameByTimer:(id)sender;
-(void)renameByTimer:(id)sender;
-(void)startTimerWithTimeInterval:(NSTimeInterval)seconds selector:(SEL)selector;
-(void)stopTimer;
@end

@implementation JJTableView

// NSNibAwaking
-(void)awakeFromNib {
    NSLog(@"awakeFromNib");
    array = [[NSMutableArray alloc] initWithObjects:
        @"1", @"2", @"3", @"4", @"5", nil];
    canRename = NO;
    timer = nil;
    [self setDataSource:self];
    [self setDelegate:self];
    [self setAction:@selector(doClick:)];
    [self setDoubleAction:@selector(doDoubleClick:)];
    [self setTarget:self];
    [self setNextKeyView:textField];
    [textField setNextKeyView:self];
}

// NSResponder (super)
-(BOOL)becomeFirstResponder {
    NSLog(@"becomeFirstResponder");
    BOOL flag = [super becomeFirstResponder];
    if (flag) {
        [self enableClickToRenameAfterDelay];
    }
    return flag;
}

// NSObject (super)
-(void)dealloc {
    [self stopTimer];
    [array release];
    [super dealloc];
}

// Action
-(void)doClick:(id)sender {
    NSLog(@"doClick:");
    if (canRename) {
        int row = [self clickedRow];
        if (row >= 0) {
            [self startTimerWithTimeInterval:0.5 selector:@selector(renameByTimer:)];
        }
    }
}

// DoubleAction
-(void)doDoubleClick:(id)sender {
    NSLog(@"doDoubleClick:");
    [self enableClickToRenameAfterDelay];
}

-(void)enableClickToRenameAfterDelay {
    canRename = NO;
    [self startTimerWithTimeInterval:0.2
        selector:@selector(enableClickToRenameByTimer:)];
}

-(void)enableClickToRenameByTimer:(id)sender {
    NSLog(@"enableClickToRenameByTimer:");
    canRename = YES;
}

-(void)renameByTimer:(id)sender {
    if (canRename) {
        int row = [self selectedRow];
        if (row != -1) {
            [self editColumn:0 row:row withEvent:nil select:YES];
        }
    }
}

-(void)startTimerWithTimeInterval:(NSTimeInterval)seconds selector:(SEL)selector {
    [self stopTimer];
    timer = [[NSTimer scheduledTimerWithTimeInterval:seconds
        target:self
        selector:selector
        userInfo:nil
        repeats:NO] retain];
}

-(void)stopTimer {
    if (timer != nil) {
        if ([timer isValid]) {
            [timer invalidate];
        }
        [timer release];
    }
}

// NSTableDataSource
-(int)numberOfRowsInTableView:(NSTableView *)tableView {
    return (int)[array count];
}

// NSTableDataSource
-(id)tableView:(NSTableView *)tableView
        objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)rowIndex {
    id value = nil;
    if ((rowIndex >= 0) && ((unsigned int)rowIndex < [array count])) {
        value = [array objectAtIndex:(unsigned int)rowIndex];
    }
    return value;
}

// NSTableDataSource
-(void)tableView:(NSTableView *)tableView
        setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn
        row:(int)rowIndex {
    if ((object != nil) && (rowIndex >= 0) && ((unsigned int)rowIndex < [array count])) {
        [array replaceObjectAtIndex:(unsigned int)rowIndex withObject:object];
    }
}

// NSTableView delegate
-(BOOL)tableView:(NSTableView *)tableView
        shouldEditTableColumn:(NSTableColumn *)tableColumn row:(int)rowIndex {
    return NO;
}

// NSTableView delegate
-(void)tableViewSelectionDidChange:(NSNotification *)notification {
    NSLog(@"tableViewSelectionDidChange:");
    [self enableClickToRenameAfterDelay];
}

@end

Filling an NSMutableArray

Tuesday, October 10th, 2006

It is estimated that there are over 6 billion people living on Earth. This staggering number raises many issues. For the Cocoa programmer, one of the issues is, can they all fit in an NSArray?

I’ve always wondered how many objects can fit in an NSArray. My first guess is one fewer than the number of grains in a heap of sand. According to the class reference for NSMutableArray, the method +[NSMutableArray arrayWithCapacity:] takes an unsigned int argument, so what’s the maximum value of an unsigned int? This should be defined in the standard headers by the macro UINT_MAX. We could easily learn the value of UINT_MAX by calling NSLog(@"UINT_MAX: %u", UINT_MAX); but instead let’s go on a wild goose chase! That would be much more fun. The natural place to start would be /usr/include/limits.h.

Nope, no dice. Ok, time to give up.

Wait! Near the top of the file we find #include <machine/limits.h> and #include <sys/syslimits.h>. The next stop on our goose chase is /usr/include/machine/limits.h. This file just tells you where to look depending on your architecture. If you have a PowerPC machine, it’s /usr/include/ppc/limits.h; I have an Intel machine, so I’m going to try /usr/include/i386/limits.h. Bingo!

#define UINT_MAX 0xffffffff /* max value for an unsigned int */

For those of you who don’t count in hexadecimal, that’s decimal 4,294,967,295. For those of you who don’t count in decimal, that’s 4 billion, give or take (actually, give). Unfortunately, an array with capacity UINT_MAX is not big enough to hold every person, or every Person, as the sample code usually goes. On the other hand, who pays attention to the stated capacity? Certainly not elevator riders. Maybe we can just keep stuffing an array with objects if we push really hard. After all, an NSMutableArray is supposed to be able to expand beyond its Capacity: argument.

Before we send our array to the all-you-can-eat object buffet, we should carefully consider the consequences. Will it have to go on an NSDiet afterward? Will we be able to find an object after it has been added to the array? The method -[NSArray indexOfObject:] returns an unsigned int. What index will the (UINT_MAX + 1)th object return? Another worry is that this method returns NSNotFound when the array does not contain the object. The file NSObjCRuntime.h defines NSNotFound:

enum {NSNotFound = 0x7fffffff};

That’s 2,147,483,647 for the hex-impaired. It turns out, then, that NSNotFound < UINT_MAX. (Note to self: link to the sound of a car slamming on its brakes and screeching to a halt.) If we add more than NSNotFound objects to an array, how will we know whether -[NSArray indexOfObject:] has found an object or not?

We’ve accumulated plenty of unanswered questions. It’s time to put up or shut up. (Note to self: stop talking to yourself.) Let’s test what actually happens when we add a large number of objects to an NSMutableArray. I decided to begin with NSNotFound before moving up to UINT_MAX. In the end, it didn’t make a difference.

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
	NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

	unsigned int size = NSNotFound;
	NSMutableArray * array = [NSMutableArray arrayWithCapacity:size];
	NSNumber * number;
	unsigned int index;
	for (index = 0u; index < size; ++index) {
		number = [[NSNumber alloc] initWithUnsignedInt:index];
		[array addObject:number];
		[number release];
		if ((index % 1000000u) == 0u) {
			NSLog(@"Current index: %u", index);
		}
	}

	NSString * string = [[NSString alloc] initWithString:@"MAD_MAX"];
	[array addObject:string];
	NSLog(@"MAD_MAX index: %u", [array indexOfObject:string]);
	[string release];

    [pool release];
    return 0;
}

Can you say “SIGBUS”, children? Good! I knew you could! Yes, my test program crashed hard at somewhere between 115 and 116 million objects.

ArraySize(3780) malloc: *** vm_allocate(size=1069056) failed (error code=3)
ArraySize(3780) malloc: *** error: can't allocate region

Moral of the story: What’s the capacity of an NSMutableArray? I don’t know. Before it reaches any kind of limit, your computer will run out of memory. My iMac has 1 GB RAM; a Mac Pro has a maximum of 16 GB. Therefore, if you’re adding a completely unknown quantity of objects to an array, you might want to place your own limits. Or in other words, be excellent to each other, and party on dudes!

Drag and drop multiple URLs

Monday, October 9th, 2006

As a reminder, in case you forgot from the last post, I am Jeff Johnson of the clan Johnson. I was born in the flatlands of America, and I cannot die unless you take my head…or shoot me, stab me, burn me, force feed me tainted spinach, etc. I am not immortal, and neither are you, so let’s get on with it.

In the Vienna support forum, we got a bug report from a user who was trying to drag multiple RSS feeds from Safari’s bookmarks page to Vienna’s subscription list. Instead of creating a subscription for each feed URL, Vienna created only one subscription with all the feed URLs strung together. Bummer. So much for migrating from Safari RSS to Vienna!

To fix the bug, I needed to know what exactly was in the pasteboard. Thankfully, Apple provides a little tool for just this purpose: Clipboard Viewer. I knew that Vienna had to be getting the data from NSStringPboardType, because that was the only non-custom type it accepted. In Clipboard Viewer, I could see that NSStringPboardType contained the feed URLs separated by line feeds (hexadecimal code 0a), which Clipboard Viewer displays as dots (.) for some reason. One solution, then, would be to extract the URLs from the string, perhaps using -[NSString componentsSeparatedByString:] with the argument @"\n". That might be ok if your app only accepts dragged URLs, but what if it accepts other data? The app would have to be able to distinguish between URLs and other kinds of dragged strings. Is there an easier way?

Clipboard Viewer gives us a list of available pasteboard types. There is Apple URL pasteboard type, which turns out to be just NSURLPboardType. Unfortunately, you’ll find in the docs for NSPasteboard that NSURLPboardType carries NSURL data for one file or resource. There can be only one, flatlander! The one URL for NSURLPboardType is the first of the selected Safari bookmarks. Other available pasteboard types include 'url ' and 'urln', AKA CorePasteboardFlavorType 0x75726C20 and CorePasteboardFlavorType 0x75726C6E, AKA Sammy the Snitch and Jimmy Two-Fingers. You can see in the Uniform Type Identifiers Overview that 'url ' and 'urln' are tags for, no surprise, a URL and a URL name. These pasteboard types also contain the data for only one URL, so they don’t help us here. (Incidentally, I love that pasteboard types have flavors. This suggests to me that in the future, Apple will release a lickable interface, perhaps a wireless lollipop.) Retro programmers will enjoy NeXT plain ascii pasteboard type, but its content is basically the same as NSStringPboardType.

BookmarkDictionaryListPboardType looks very interesting, because it seems to contain everything that we want, and more. However, a search for the term at ADC returns no results. My guess is that BookmarkDictionaryListPboardType and its apparent sidekick BookmarkStatisticsPBoardType (note the different capitalization, though) are private Safari-defined types. I’ll let Safari hackers figure that out. (Actually, I don’t recall these pasteboard types existing when I first investigated URL dragging. A No-Prize goes to the person who can tell us when they were introduced.)

The last available pasteboard type, but certainly not least—you knew the story was going to have a happy ending!—is WebURLsWithTitlesPboardType. Although an ADC search again turns up nothing, the name of the pasteboard type begins with Web, which should sound familiar: WebFrame, WebView, WebFrameView (when WebFrame and WebView get married), WebUndefined (huh?). In other words, WebURLsWithTitlesPboardType is probably defined in WebKit, the framework used by Safari to handle web content. WebKit is open source, so we can check it out for ourselves. Yay! A project search reveals that WebKit does indeed define WebURLsWithTitlesPboardType, in the file WebURLsWithTitles.h of all places. Go figure. The file is short, so I’ll reproduce it below for your reading pleasure.

/*
 * Copyright (C) 2005 Apple Computer, Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#import <Cocoa/Cocoa.h>

#define WebURLsWithTitlesPboardType     @"WebURLsWithTitlesPboardType"

// Convenience class for getting URLs and associated titles on and off an NSPasteboard

@interface WebURLsWithTitles : NSObject

// Writes parallel arrays of URLs and titles to the pasteboard. These items can be retrieved by
// calling URLsFromPasteboard and titlesFromPasteboard. URLs must consist of NSURL objects.
// titles must consist of NSStrings, or be nil. If titles is nil, or if titles is a different
// length than URLs, empty strings will be used for all titles. If URLs is nil, this method
// returns without doing anything. You must declare an WebURLsWithTitlesPboardType data type
// for pasteboard before invoking this method, or it will return without doing anything.
+ (void)writeURLs:(NSArray *)URLs andTitles:(NSArray *)titles toPasteboard:(NSPasteboard *)pasteboard;

// Reads an array of NSURLs off the pasteboard. Returns nil if pasteboard does not contain
// data of type WebURLsWithTitlesPboardType. This array consists of the URLs that correspond to
// the titles returned from titlesFromPasteboard.
+ (NSArray *)URLsFromPasteboard:(NSPasteboard *)pasteboard;

// Reads an array of NSStrings off the pasteboard. Returns nil if pasteboard does not contain
// data of type WebURLsWithTitlesPboardType. This array consists of the titles that correspond to
// the URLs returned from URLsFromPasteboard.
+ (NSArray *)titlesFromPasteboard:(NSPasteboard *)pasteboard;

@end

P.S. It’s clear from the code in the file WebNSPasteboardExtras.m that 'url ' and 'urln' have an additional set of aliases: WebURLPboardType and WebURLNamePboardType.