Archive

Archive for the ‘Six Days of Cocoa’ Category

Day Four: Integrating Help in your application using VoodooPad

May 11th, 2009 3 comments

[Update September 12th, 2009: VoodooPad 4.2.2 fixes the one bug in this post. Thanks Gus!.]
[Update June 28, 2009: there is an issue with the Help Indexer script and Snow Leopard. Contact me for the details, which only matter if you have Snow Leopard.][Update August 30th, 2009: now that Snow Leopard has been released, I updated the Help Indexer script to run with hiutil.]

Documentation

I’m a big fan of using the right tool for the right job, and I also found myself suffering from “blank page syndrome” when it came time to write initial documentation for iChibi.

I really like VoodooPad for its ability to quickly transfer ideas from my head to some kind of structured document, which I am free to revise later.

VoodooPad even has a web export module, which can create a set of pages that are compatible with Apple Help. Excellent! I could now overcome the blank page and start writing some documentation.

The process

If you write all your documentation in VoodooPad, you must follow these steps to create a valid Help folder to integrate in your application:

  1. Export the document to your Help folder.
  2. Open the main page of your document and insert the appropriate AppleTitle and AppleIcon tags.
  3. Drag-and-drop your Help folder to Help Indexer (/Developer/Applications/Utilities/Help Indexer.app), to auto-generate the index.

That’s still a lot of clicking.

Integrating with Xcode

In order to make sure that when I release software, I ship the latest of everything I like to add build phases to Xcode and have it perform all these tasks automatically. I sometimes run these scripts in Release builds only, because I don’t want to waste any time in the compile-link-debug cycle of a Debug build.

Here is the script I use within Xcode. You can use it too, just add a new “Run Script” phase to your target:

Run Script build phase in Xcode

if [[ ${CONFIGURATION} == "Release" ]]
then
	# Set to whatever you have as CFBundleHelpBook in your Info.plist
	HELP_FOLDER="$TARGET_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH/English.lproj/Help/"
	HELP_DOC="iChibi Help.vpdoc"
	# Clean folder, so we don't have extra junk in there
	rm -r "$HELP_FOLDER"
	echo "Generating documentation..."
	open "$SRCROOT/doc/$HELP_DOC"
	osascript -e "tell application \"VoodooPad Pro\"" \
		-e "tell document \"$HELP_DOC\"" \
		-e "web export to \"$HELP_FOLDER\" with properties {fileExtension:\"html\"}" \
		-e "end tell" -e "end tell"
fi

(You will want to replace the name and path of your VoodooPad Help document and output folder, of course.)

What this does is delete the current Help folder, and re-generate it using AppleScript to tell VoodooPad to export the proper document to the Help folder.

But wait, there’s more!

In this Xcode build script, there is nothing about setting the AppleTitle or the AppleIcon, nor is there any Help Indexer. Those are all handled by VoodooPad using built-in scripts.

VoodooPad’s Web Export behavior can be overriden by specially-named pages:

  • WebExportPageTemplate
    This page overrides the Export Module selection in the Web Export function.
    This is your basic html page, with extra markup for the actual page content ($page$). The only notable addition I made was to add two comments in the <head> section:
    	<!-- AppleTitle -->
    <!-- AppleIcon -->

    These will be used postflight script below.

  • WebExportPostflightScript
    This page will be run as a shell script with a few interesting environment variables, notably $VPWebExportOutputDirectory. Here is the content of the page:
    #!/bin/sh
    
    # Replace AppleTitle comment in index.html with appropriate value
    perl -pi -e 's/<!-- AppleTitle -->/<meta name=\"AppleTitle\" content=\"iChibi Help\">/' "$VPWebExportOutputDirectory/index.html"
    
    # Replace AppleIcon comment in index.html with appropriate value
    perl -pi -e 's/<!-- AppleIcon -->/<meta name=\"AppleIcon\" content=\"appicon16.png\">/' "$VPWebExportOutputDirectory/index.html"
    
    # Index documentation
    if [ -a "/usr/bin/hiutil" ]; then
      # Using hiutil on Snow Leopard
      /usr/bin/hiutil --create "$VPWebExportOutputDirectory"Help/" --file "$VPWebExportOutputDirectory"Help/Help.helpindex"
    else
      # Using Help Indexer.app
      "/Developer/Applications/Utilities/Help Indexer.app/Contents/MacOS/Help Indexer" "$VPWebExportOutputDirectory"Help/"
    fi
    
    exit 0
    
    

Tip: in the Page Info, check “Skip on Export” for those two files since they are not needed by the Help Viewer.

You can download iChibi’s Help document (VoodooPad format) and use it as a starting point for your Help document.


Bugs to iron out


The build script activates VoodooPad (brings it forward) and leaves the Web Export dialog active. You have to manually dismiss this dialog, or use AppleScript to tell VoodooPad to quit. I don’t like the heavy-handed “quit” approach because I may be working in other VoodooPad documents, and don’t want them to disappear even if they are auto-saved (thanks, VoodooPad!).

There is probably a way to dismiss the dialog using AppleScript and accessibility (e.g. "tell button 3 of dialog 'Web Export' to perform action") but that strikes me as even more of a hack. I hope Gus can fix it in an upcoming release of VoodooPad :-) .

This is fixed in VoodooPad 4.2.2.

Categories: Six Days of Cocoa Tags:

Day Two: iChibi and ImageSoup

April 20th, 2009 Comments off

Six Days of Cocoa: Day Two

Day Two: code-name iChibi

My whole family is very much into all things japanese (including, of course, manga and anime). The other day, they found a little “sound-playing ghost” called Flele (I have no idea what the name means).

As far as I can tell, Flele is a small application that you drop one or more MP3s on, and it starts to “sing” when you click on the character’s hair. I have no idea how it actually works, since it requires Windows and the Japanese language pack. But my daughter and I quickly hashed out that what we could do would be a globally-floating iTunes controller, who would respond to click and animate when music was playing (we’ll figure out the “singing” part later).

iChibi

My daughter drew lots of PNGs this weekend (using Painter, of course), and we assembled them in sequences to make animations. The iChibi can blink, and lights turn on/off on its headphones whenever music is playing.

iChibi can be dragged anywhere on screen, and will float on top of all windows.

Clicking on its left ear starts playing in iTunes, on the right ear stops playing. The left headphone goes to the previous track, the right headphone to the next track.

Clicking on the zipper gives a Settings panel, where you can control the its size, opacity and which playlist the songs are coming from.

The higher the song rating, the happier it will look.

iTunes support is done through the excellent Eyetunes Framework.

iChibi works on MacOSX 10.5 and later.

If you are interested in following the development of iChibi, follow @ichibiapp on Twitter.


ImageSoup

Since iChibi uses lots of images for animations, and I didn’t want to keep multiple copies of the same image in memory (for example, if an image is re-used in multiple animation loops), I figured I should make a small class to hold all the images, loading them as necessary. I named this class ImageSoup, referring to the Newton’s filesystem of course…

ImageSoup is very simple, and does not need to be its own class (after all, it’s a dictionary). But it is nice to be able to abstract that implementation detail out of your code, as well as the image-loading code. What if your source image was an Acorn image or a Painter RIFF? You could hide the image-loading code in ImageSoup, and your calling code need not be aware of this.

To use ImageSoup, just create an ImageSoup* instance and always ask it to load your images (using the full path to the image):

#import "ImageSoup.h"

[...]

ImageSoup* allImages = [[ImageSoup alloc] init];

[...]

NSImage* myImage = [allImages getImageAtPath: @"Full/path/to/image"];

If the image exists and has been loaded previously, ImageSoup will return it right away. If the image does not exist, ImageSoup will load it and store it for future reference.

When you release your ImageSoup*, all its images will be released.

ImageSoup.h

//
//  ImageSoup.h
//  iChibi
//
//  Created by Philippe on 09-04-19.
//  Copyright 2009 Philippe Casgrain. All rights reserved.
//

#import <Cocoa/Cocoa.h>

@interface ImageSoup : NSObject
{
  NSMutableDictionary* _images;
}

- (NSImage*) getImageAtPath: (NSString*) path;

@end

ImageSoup.m

//
//  ImageSoup.m
//  iChibi
//
//  Created by Philippe on 09-04-19.
//  Copyright 2009 Philippe Casgrain. All rights reserved.
//

#import "ImageSoup.h"

@implementation ImageSoup

- (id) init
{
  self = [super init];
  if (self != nil) 
  {
    _images = [[NSMutableDictionary dictionaryWithCapacity: 0] retain];
  }
  return self;
}

- (void) dealloc
{
  [_images removeAllObjects];
  [_images release];
  [super dealloc];
}


- (NSImage*) getImageAtPath: (NSString*) path
{
  NSImage* retrievedImage = [_images valueForKey: path];
  if (retrievedImage == nil)
  {
    NSImage* image = [[NSImage alloc] initWithContentsOfFile: path];
    if (image)
      [_images setObject: image forKey: path];
    [image release];
    retrievedImage = image;
  }
  return retrievedImage;
}

@end
Categories: Graphics, Leopard, Six Days of Cocoa Tags:

Day One: Daylight

April 6th, 2009 3 comments

[Update 04/08/2009 – Daylight is available on the App Store. Have a look, it’s free!]
[Update 19/07/2009 - Daylight has been submitted to the App Store!]
[Update 19/07/2009 - Twilight is now called Daylight.]

Six Days of Cocoa: Day One

I found myself with six unexpected days off, so I decided to take them on six consecutive Mondays, when the kids are in school and most of the housework is done, to concentrate on my independent Cocoa projects. These are projects that I started but put on the back burner for lack of “quality time”.

Day One: Daylight

Daylight is an iPhone application that I wrote to scratch an itch: when does the sun rise or set every day? It’s important to me because I bike to work year-round, and cars can see me much better at dusk than they can at night.

It’s also useful for photographers and filmmakers. One hour before sunset is the so-called “golden hour“, where the shadows are long and the scenery is tinted with an amber glow. Dusk and dawn also form the “blue hour“, much more important at higher latitudes, where there is no direct sunlight; everything is diffused through the atmosphere. No shadows, no glare, no overexposure…

Daylight is extremely simple. It uses Core Location to determine where you are in the world, and uses the internal clock to figure the current time, and offset from GMT.

There are only a few settings in Daylight . You can choose between Civil, Nautical and Astronomical twilight, set the date (defaults to Today) and reset your location (which is cached for 30 days by default).

Daylight is perhaps the very definition of a one-shot app: you launch it, it does what it says, and you’re done. It encourages discoverability by having large buttons and a little bit of animation.


Today, I found the one bug that was preventing me from going forward, so I am looking for beta-testers for Daylight . If you are interested, please send me a direct message on Twitter (d daylightapp) or send an email to daylight-beta@casgrain.com with your device’s identifier.

Categories: Six Days of Cocoa Tags: