Stone Hill Invoicer 2

May 12th, 2010

E.B. White once said “The best writing is rewriting.” He wasn’t talking about Stone Hill Invoicer, but he could have been.

I’m pleased to announce the release of Stone Hill Invoicer 2. The entire application has been been rewritten, right down to it’s core. Of course, that’s not all. If you’ve used previous versions of Invoicer, you’ll see lots of new stuff: quotes, uninvoiced items, a decent address book, folders, more powerful smart folders, Stone Hill Time Card integration, better searching, multiple companies, and an even cleaner and simpler design.

If you’ve never used it before, here are a few key facts:

  1. If Stone Hill Invoicer had a MySpace page, it would say “Influences: iTunes, Mail, Brent Simmons and his Weblog, WWPD.”
  2. Stone Hill Invoicer uses 12 pieces of open source code, including one that was written just for it.
  3. Stone Hill Invoicer got popovers a couple weeks before the iPad was introduced, but it doesn’t expect you to believe that.
  4. The best way to get to know Stone Hill Invoicer better is to download it and give it a try.

If you’ve got any questions or comments, I’d be pleased to answer them.

In Defense of Online Help Pages

May 11th, 2010

In a couple days I’m going to be releasing version 2 of Stone Hill Invoicer. The last beta reports are trickling in, and like the superb testers they are, my beta group is working hard to find anything they can to comment on. Among other things, todays email contained a bit of discussion on the delay in the scrolling about box, a note about behaviour of the sourcelist when a certain sequence of clicks and keyboard arrows was applied, and two notes about the online help system.

I’ve done something a little different this time around. In previous versions of Stone Hill I shipped a standard Apple Help Book. This is an HTML help book with a few special tweaks that you can bundle up with your app so that it’s available through the Apple Help Viewer. In this version, the “Stone Hill 2.0 Help” menu item opens your default web browser and directs you to a blog that I set up specially for the app.

There’s only one disadvantage compared to a help book for my app: it’s not local.

So what about the guy on a fourteen hour flight to Paris? Will he be unable to work because he doesn’t have a local copy? For Google Docs, I buy that argument. For an invoicing app’s help system? Not so much. I’d like to imagine I write good enough apps that you don’t need the help system, but even if you do, a lack of access to it for a few hours is unlikely to kill your ability to be productive.

But what advantages does an online help system bring? Even if the man on the airplane is mythical, there have to be a few advantages to make it a good idea.

1. It’s a lot easier on me

There are no good tools to generate help books. They’re a pain to update and a pain to work with. Writing entries in MarsEdit is a thousand times better than building a static site with mildly specialized HTML.

2. I really like the idea of being able to leave comments on documentation

I get the occasional email that mentions some tip or way of doing something that a customer came up with, asking if I want to put it in the help documentation. Assuming it’s a great tip, and I have the time to dust off the help docs and repackage them and reupload the app, that’s still a huge turnaround time for something that’s as simple as a comment on a blog post.

With the online help system, I’ve left comments enabled. Sure, I’ll have to put in a minute or two a day to deal with spam, but a few good tips and tricks from users will more than make up for that.

3. Updating the help without updating the app

I can’t justify making the user download a new copy of the app just to get a new revision of the help book. Especially if I want to make lots of little revisions as I see the support mail come in. In previous versions I’ve had to separate help and the FAQ, when really they’re the same thing, I just feel able to update one and not the other.

4. The Leopard and Snow Leopard help viewer isn’t very good

I’m sure there’s a very good reason for it somewhere, but a help viewer that insists on sitting on top of all other windows means that I can’t keep it open and use the app I need help with. On my Powerbook this was a huge issue. On my development machine (20″ + 23″ screens) it’s less of a problem, but still annoys me.

Your browser is an excellent document viewer. We should use it for that.

5. Now I can link users directly to a piece of documentation

In the past, I’ve actually copied and pasted entire help book entries, because the help entry describes exactly what’s being asked, they just didn’t check for it. With the help entries online, I can now include a link in the email, saving me from having to reformat any pictures and text in the file.

Quirky Things in the Stone Hill Invoicer 2 Interface

May 9th, 2010

When it came time to update Stone Hill Invoicer’s design to be a little more modern, I couldn’t resist playing around a bit.   Some of this experimentation didn’t make it into the final app, but I think it’s fun to show it off anyhow.

Popovers

When the iPad announcement came out, I’ll admit I was thrilled about popovers. Those are those little bubbles that act like temporary panels when you press some button to summon them up.

A couple weeks earlier I’d used MAAttachedWindow to implement popovers in my app. In several places, the previous version of the app needed to use a sheet to present a more detailed view. That’s a lousy thing to do unless it’s really required. The user has to shift their focus, deal with a modal dialog, and then remember what they were doing.

Old Modal.png

An old modal sheet used to edit the billing address on an invoice

With a popover the user sees details immediately related to what they’re working on without changing their focus. Clicking away from the dialog immediately dismisses it. When their attention shifts to another part of the document, they don’t need to perform the initial manual step of dismissing what they were previously working on.

popover.png

A new popover, displaying the same information, but in a way that doesn’t break the users workflow.

All in all, these work great on the desktop. They let me get rid of at least a half dozen modal sheets.

That being said, during initial testing, I found that users were confused by the lack of ‘Ok’ and ‘Cancel’ buttons on the popovers. Clicking elsewhere to dismiss it wasn’t an intuitive action, though they didn’t expect to have to dismiss the popovers when switching focus to another task. I added ‘Done’ and ‘Cancel’ buttons to my popovers. This increased the size of the popover, but provided the user with an immediate cue for how to dismiss them.


Modal Popovers with Lightboxing

Flush with popover success, I decided to try applying the same style to modal sheets.  I’ve seen this in a few web apps, and on the iPad too if I’m not mistaken (I might be, we don’t get them in Canada for a while yet).

When I was experimenting, one of the big problems was that they weren’t very visible against the app.  There was no real way of indicating that they were modal.

Lightboxes are something you see a lot in the web world.  In order to highlight the modal popover, I dimmed the window they were modal to.  Though it helped draw attention to them, in my implementation it disabled interaction with the window below it.

Screen shot 2010-05-09 at 5.18.26 PM.png
A modal popover on a lightboxed background.

Feedback during beta testing was that they were just weird.  I got rid of these and replaced them with sheets.

That being said, I’m convinced that these help an application look more visually balanced, and it’s just a matter of getting the implementation right.  I plan on revisiting this at some point.


Nag Screens in the Sidebar

There’s a fine line between annoying your users with nag screens and not getting paid for your software.  In Stone Hill, I decided to try a bit of a different way of walking that line.

The nagsceen is unobtrusive.  In unregistered copies, there’s a couple of items in the source list: “Buy Stone Hill Invoicer” and “Register Stone Hill Invoicer”.  Clicking on the “Buy Stone Hill Invoicer” item shows a decent sized sales pitch.

Purchase Screen

The ‘upgrade nag screen’.  This only shows when you click the “Buy Stone Hill Invoicer” item in the sourcelist.

During beta testing, one of my users asked where the purchase button was.  He’d checked under the application menu and couldn’t find it.  Fair enough.  I ended up adding a menu item that shows the same screen under the application menu.

This strikes me as being in the same vein as Panic’s top–of-the-window nag counters.


 

Sidebar Search via the Global Search Box

One of the things that always drove me nuts about Stone Hill was that the search function only worked on some screens.  In version two, using the search from one of the document screens filters the customer and inventory lists.  Though I considered creating a special iPhone style search box at the top of each table that would initially be scrolled out of view, it seems like a better idea to keep the global search box as the only place to search from.

Feedback has been positive about this so far, but this method may have discoverability issues.

Search

A customer list filtered by search text

Code Snippet: Finding a Router’s Name and MAC Address

March 14th, 2010

Overview

A couple years ago I blogged about some code that I use in Locations to find a routers name and MAC address. A recent bug report brought that code back to my attention. I’d made a couple of assumptions, like assuming that en0 was always ethernet, and not being able to detect when the user was on a VPN. Skip down a bit to find the updated code.

The resulting code takes less resources, is more robust, and is shorter. This is definitely a case of looking back at my code and wondering “What moron wrote this?” It’s a wonderful feeling.

Code – Finding the Router’s MAC Address

+ (NSString *) primaryRoutersMACAddress {
    
    NSMutableString *MACAddress = [NSMutableString string];
    
    // Figure out which interface we’re using
    SCDynamicStoreRef theDynamicStore = SCDynamicStoreCreate(nil, CFSTR("FindCurrentInterfaceAndIP"), nil, nil);
    CFDictionaryRef returnedPList = SCDynamicStoreCopyValue(theDynamicStore, CFSTR("com.apple.network.identification"));
    CFRelease(theDynamicStore);
    
    if ([(NSDictionary *)returnedPList valueForKey:@"ActiveIdentifiers"] != nil) { // We have a network
        NSString *activeIdentifier = [[(NSDictionary *)returnedPList valueForKey:@"ActiveIdentifiers"] lastObject];
        NSArray *identifierParts = [activeIdentifier componentsSeparatedByString:@"="];
        [MACAddress setString:[identifierParts lastObject]];
    }
    
    return MACAddress;
    
}

Code – Finding the Router’s SSID

+ (NSString *) primaryRoutersSSID {
    
    NSMutableString *interfaceName = [NSMutableString string];
    
    // Figure out if we’ve only got the one network identifier, or if there’s several
    // If there’s several, we’re on a VPN or something
    // In that case, we’ll just use the servername as the interface name
    
    SCDynamicStoreRef theDynamicStore = SCDynamicStoreCreate(nil, CFSTR("FindCurrentInterfaceAndIP"), nil, nil);
    CFDictionaryRef networkIdentification = SCDynamicStoreCopyValue(theDynamicStore, CFSTR("com.apple.network.identification"));
    CFRelease(theDynamicStore);
    
    if ([[(NSDictionary *)networkIdentification valueForKey:@"ActiveIdentifiers"] count] > 1) {
        NSString *activeIdentifier = [[(NSDictionary *)networkIdentification valueForKey:@"ActiveIdentifiers"] lastObject];
        NSArray *identifierParts = [activeIdentifier componentsSeparatedByString:@"="];
        [interfaceName setString:[identifierParts lastObject]];
    } else {
        
        // Check the primary interface name
        SCDynamicStoreRef theDynamicStore = SCDynamicStoreCreate(nil, CFSTR("FindCurrentInterfaceAndIP"), nil, nil);
        CFDictionaryRef interfacePlist = SCDynamicStoreCopyValue(theDynamicStore, CFSTR("State:/Network/Global/IPv4"));
        NSString *enX = [(NSDictionary *)interfacePlist valueForKey:@"PrimaryInterface"];
        
        // Find if there’s an airport entry for that interface
        NSString *airportPath = [NSString stringWithFormat:@"State:/Network/Interface/%@/AirPort", enX];
        CFDictionaryRef airportPlist = SCDynamicStoreCopyValue(theDynamicStore, (CFStringRef)airportPath);
        
        if (airportPlist != nil) { // If there is, grab the SSID
            if ([(NSDictionary *)airportPlist valueForKey:@"SSID_STR"] != nil) {
                [interfaceName setString:[(NSDictionary *)airportPlist valueForKey:@"SSID_STR"]];
            } else if ([(NSDictionary *)airportPlist valueForKey:@"SSID"] != nil) {
                [interfaceName setString:[(NSDictionary *)airportPlist valueForKey:@"SSID"]];
            } else {
                [interfaceName setString:@"Wireless Network"];
            }
        } else { // Otherwise, return ‘Wired Network’
            [interfaceName setString:@"Wired Network"];
        }
        
    }
    
    // Figure out which interface we’re using
    return interfaceName;
    
}

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.

“Feel the Beat”

December 18th, 2009

It’s been a busy semester but luckily, one of the (weirdest) things about being a student is that my projects actually have an ending. Earlier this week I helped finish up a project for my music information retrieval class. Here’s what we’ve been working on.

When I was a kid, I wanted to be either a toy inventor or a writer. These days I’m lucky enough to be someone who writes toys.