Code Snippet: Better Table Support in NSTextView

May 31st, 2009

Overview

I’m working on Stone Hill Invoicer, adding some new functionality to the .rtfd editor where users can customize how their invoices print. One of the support emails that pops up once in a while is asking why it’s so hard to add and delete columns to tables. Until now, I’ve just been using a standard NSTextView, so I’ve been apologetic and put it on my list of things to take care of for 2.0.

This post presents some code for a subclass which add in additional contextual menu items to the standard NSTextView menu when over a table. Here are the old and new menus for comparison:

Picture 2.png
Picture 5.png

Download

If you’d like to use this subclass, download the header and implementation files and integrate them into your project. Remember to select your NSTextView in interface builder and change it to a CHTextView.

Attribution
All source is released free of charge. You may incorporate it into any of your applications. I assume no liability for any action that may come from you using this source. If you do use this in your application, I’d like a mention in your about box, but I don’t require it. Don’t forget to drop me an email at warwick@codehackers.net so I know when to stroke my ego.

Links For Adding Applescript to Your Cocoa App

June 28th, 2008

Looking to implement some basic Applescript support on my application, I was somewhat at a loss where to start. Here are some links I found useful. If you know of any other Applescript resources that helped you out, don’t be shy about dropping a link in the comments!

Cocoa Scripting Guide

An in-depth look at how scripting works from a conceptual level. A long read, but worth it.

Simple Scripting Properties

A simple example for XCode 3 and Leopard which shows a small Applescript implementation that actually works. Excellent to tear apart and learn from.

Applescript Terminology and Apple Event Codes

Lists Applescript terminology and associated codes.

Code Snippet: Get Playlist Names From iTunes

June 2nd, 2008

I’m working on Locations Pro and adding some new actions. One action I’m adding is ‘Play an iTunes Playlist’. Obviously I need to know what the playlists are so that I can present the user with a nice list to choose from.

If you need to retrieve library information from iTunes there are a couple ways to do it. You can make a request via Applescript but that requires iTunes to be open. Alternately you can use the “iTunes Music Library.xml” file. Below is a method I put together this morning to do that.

While I was putting this together, I came across a link from Jay Tuley. He pointed out that the the library xml file has two possible locations that you have to account for.

He also make a quick reference to the occasional user doing fun things with Finder aliases and moving that XML file. Why you’d want to move that file, I’ve got no idea. I do know that this morning I had no idea how to resolve an alias or do anything with FSRefs. Now I know a little more. The iTunes related code calls a second little function I put together which returns an NSURL with the aliases resolved given a POSIX path.

Enough talking. Here’s the code:

- (NSArray *) iTunesPlaylistNames {

    NSMutableArray *playlistNames = [NSMutableArray array];

    NSURL *libraryURL;
    
    NSString *normalLibraryPath = [@"~/Music/iTunes/iTunes Music Library.xml" stringByExpandingTildeInPath];
    NSString *olderLibraryPath = [@"~/Documents/iTunes/iTunes Music Library.xml" stringByExpandingTildeInPath];
        
    if ([[NSFileManager defaultManager] fileExistsAtPath:normalLibraryPath]) {
        libraryURL = [self resolvedFileURLWithPath:normalLibraryPath];
    } else if ([[NSFileManager defaultManager] fileExistsAtPath:olderLibraryPath]) {
        libraryURL = [self resolvedFileURLWithPath:olderLibraryPath];
    } else {
        libraryURL = nil;
    }

    if (libraryURL != nil) { // If an iTunes Library was found

        NSDictionary *libraryDictionary = [NSDictionary dictionaryWithContentsOfURL:libraryURL];
        NSArray *playlists = [libraryDictionary objectForKey:@"Playlists"];
        
        int i=0;
        for (i=0; i<[playlists count]; i++) {
            if (![[playlists objectAtIndex:i] objectForKey:@"Visible"]) {
                if ([[playlists objectAtIndex:i] objectForKey:@"Name"]) {
                    [playlistNames addObject:[[playlists objectAtIndex:i] objectForKey:@"Name"]];
                }
            }
        }
        
    }

    return playlistNames;
    
}

- (NSURL *) resolvedFileURLWithPath:(NSString *)path {

    CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, (CFStringRef)path, kCFURLPOSIXPathStyle, NO);
    NSString *resolvedPath;
    
    if (url != NULL) {
        FSRef fsRef;
        if (CFURLGetFSRef(url, &fsRef)) {
            Boolean targetIsFolder, wasAliased;
            FSResolveAliasFile (&fsRef, true, &targetIsFolder, &wasAliased);
            CFURLRef resolvedUrl = CFURLCreateFromFSRef(kCFAllocatorDefault, &fsRef);
            if (resolvedUrl != NULL) {
                resolvedPath = [NSString stringWithString:
                    (NSString *)CFURLCopyFileSystemPath(resolvedUrl, kCFURLPOSIXPathStyle)];
                CFRelease(resolvedUrl);
            }
        }
        CFRelease(url);
    }
    
    if (resolvedPath != nil) {
        NSURL *resolvedURL = [NSURL fileURLWithPath:resolvedPath];
        return resolvedURL;
    }
    
    return nil;
    
}

Edit: As always, go ahead and use the code however you want, commercially or otherwise. I don’t mind a mention in your about box (and an email if I get one), but I don’t require it.

Prototype Mode

May 31st, 2008

If you’re a developer you’ve probably fallen prey at some point. You start on a new project and it flies together faster than you can say “good coding practice”. Do you keep running while the idea is fresh? Do you stop and make your code maintainable? I usually keep running.

I just finished a large update to one of my apps. It’s only 8000 lines of code, but quick coding and late night enthusiasm had compressed it into a few poorly thought out classes. Adding features was criminal. When I decided to streamline the UI, I started with a clean project and brought code over. Sometimes the only way to clean house is to move.

Prototype mode is a sweet place to be. My favourite features are born here. I can’t rid myself of it, but I have come up with a few strategies to help mitigate the damage.

Be Inspired Somewhere Else

I prototype in Interface Builder, sprinkling code as needed. If I add to my current project I take shortcuts to see results quicker. Building in another project means that I’ll integrate it later. By that time, I’m ready to listen to the better angels of my nature.

Use Plugins When Applicable

When I started writing Locations Pro I decided to make every action it’s own plugin. Using bundles forced me to think my API through, giving each bundle a common structure. It also kept my code isolated in smaller blocks which were less daunting to clean up.

Get Rolling With Cleanup

My biggest problem with getting started in the morning is the part where I actually get started. If I don’t slip right into it I find my day devolves into staring at an empty NetNewsWire. Starting with little cleanup tasks gets me rolling. The side effect is better code.

The Appeal of Twitter

April 11th, 2008

Twitter is like getting instant messages from the people you wish you knew.