Archive for the ‘Vienna’ Category

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.

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.

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

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.

Stand and unfold yourself

Monday, October 9th, 2006

Hi, I’m Jeff Johnson. You may remember me from such projects as Vienna, an open source feed reader for Mac OS X. Software development will be the principal subject of this blog, so if you’re not a software developer, you’ll probably be bored by it. Even if you are a software developer, I can’t make any guarantees, but I’ll try to be as entertaining as possible. Bear in mind that I’m no ridiculous fish.

I started this blog for two reasons. First, and least important, I’ve wanted to share the coding tips and tricks I’ve learned through trial and error. By trial I mean a person, thing, or situation that tests a person’s endurance or forbearance. By error I mean a misplay by a fielder that allows a batter to reach base or a runner to advance. Although I love the powerful features and tools that the Cocoa development environment provides ‘for free’ (I also love the name—who wouldn’t want a warm, delicious cup of application frameworks?), the API documentation seems to be written according to the Strunk and White dictum, Omit needless words. In my opinion, Cocoa is to approached like a genie: it can do magic, but only if you can figure out what to wish for.

The other reason I started this blog is for blatant self-promotion. I recently switched fields from academic philosophy, so I need to do some networking. That’s what the net is for, right? (Uh, right?) As I said in my résumé, I’m looking for permanent or temporary work in computer programming or computer-related consulting, support, training, or writing. I would also accept a position in Quality Assurance, either computer-related or chocolate-related. Please contact me if you’re looking for someone in these areas, you know of someone who’s looking for someone in these areas, or you know of someone or are someone who could throw some money at someone in these areas or near my area. Thanks!

Disclaimer: No animals were harmed in the making of this blog. In fact, the maker of this blog was harmed (superficially) during the making of the blog by one particular animal with particularly long claws and a slight politeness deficit.