Archive

Author Archive

Open any page or location in your Help Book

June 29th, 2009 Comments off

Anchors

So you wrote your documentation using VoodooPad and you want to open a page of your choice, for instance, the “Quick Start” page.
But you don’t want this page to be the index page.

You know you can open your Help Book with this code:

[[NSApplication sharedApplication] showHelp: nil];

What you want to use is this:

NSString* bookName = [[NSBundle mainBundle] 
                              objectForInfoDictionaryKey: @"CFBundleHelpBookName"];
[[NSHelpManager sharedHelpManager] openHelpAnchor: @"__ANCHOR__" inBook: bookName];

(where __ANCHOR__ is an actual HTML anchor in your Help Book)

Creating the anchors

There are two parts to this. First, create page aliases in VoodooPad and second, update your WebExportPageTemplate to output those aliases.

VoodooPad Page Aliases

Any page in VoodooPad can have one or more aliases. They can be added by clicking the Info button on the Toolbar and clicking on the “+” button at the bottom of the Info panel. You can add as many as you want.

Tip: If you have multiple languages for your Help file, make sure that the aliases match the openHelpAnchor: above. Since this string is not user-visible, it makes sense that it be the same across all Help files.

Modifying the page template

In your VoodooPad document, open the WebExportPageTemplate page.
On that page, insert the following code at a proper location (for instance, just after the <body>tag):

<a name = "$alias$"></a>

$alias$ is a special VoodooPad variable that is replaced by the first alias in the list you defined above. It can also be called $alias[0]$.

If you have more than one alias, and you want to have multiple anchors in your page (for instance, a header and a footer), you can use $alias[1]$, $alias[2]$, etc. But in general, your help pages will be small enough that one anchor/alias per page should be sufficient to open a given page in your Help book, directly from your application.

Indexing

Make sure you actually run the Help Indexer application on your HTML Help folder.

#!/bin/sh
"/Developer/Applications/Utilities/Help Indexer.app/Contents/MacOS/Help Indexer" \ 
               "$VPWebExportOutputDirectory"

This step is necessary for the Help System to find your anchors by name.

Categories: Development, MacOSX Tags:

Sending Mercurial commit messages to Twitter

May 25th, 2009 Comments off

[Update 16/02/2011 - This no longer works now that Twitter has disabled basic_auth. Oh well....]
[Update 21/07/2009 - Twilight is now called Daylight.]

If you follow what I do on this blog and on my podcast with Philippe Guitard, you know that I like Mercurial (hg), one of the newer distributed version control systems.

In particular, I really like Murky as a GUI front-end to Mercurial. In fact, I’m working on the French localization with Olivier Kaluzny.

I want to create a tweet from @ichibiapp or @daylightapp for every commit. I do this with other apps and subversion, and find it pretty handy to catch new commits to production. Plus, if you follow these products on Twitter, you too can get on the inside track of the software releases.

If you want to do the same for your Mercurial repositories, here’s how:

  1. Put this shell script, called commit-twitter, in your $PATH and mark it as executable (chmod a+x commit-twitter):
    #!/bin/sh
    # Simple script to send the first line of the commit message as a tweet
    if [ $# -ne 2 ]
    then
      echo "Usage: `basename $0` twitter-username twitter-password"
      exit 1
    fi
    TWITTER_USER=$1
    TWITTER_PASS=$2
    TWEET=$(hg log -r $HG_NODE --template '{desc|firstline}')
    curl -s -u "$TWITTER_USER:$TWITTER_PASS" -d "status=$TWEET" http://twitter.com/statuses/update.xml & > /dev/null
    


    The script takes two arguments, username and password, and checks that you have those two arguments. Then it extracts the tweet from the log using the HG_ environment variables set by Mercurial.
    Finally, it uses curl to generate the tweet, using silent mode (-s), running in the background with & (to return immediately) and sending the output to /dev/null.

  2. Add the following lines to your repository’s .hg/hgrc file, creating it if it does not exist:
    [hooks]
    commit.tweet = commithook-twitter <twitter-username> <twitter-password>
    
  3. There is no Step 3!

VoilĂ ! Every time you commit to Mercurial, the first line of your commit message will be posted as a tweet to the specified twitter account.

Categories: Quickie Tags:

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:

Finding your iPhone or iPod Touch Device ID

April 6th, 2009 Comments off

If you are asked to provide your Device ID to someone, for example if you are beta-testing, here is an easy way to do it:

  1. Connect the device to your Mac or PC.
  2. On the “Summary” tab in iTunes, click the “Serial Number” label. You will notice that “Serial Number” changes to “Identifier”.
  3. Click on the “Edit” menu and select “Copy”. This works even though you can’t select the

Now you can paste the device ID into an email or twitter direct message. It will look something like 3fb3e69f58e0412b962bcdf0293f0c524191b447, but will obviously be different for your device.

Categories: Quickie Tags:

Quickie: Remote Desktop IP address

February 24th, 2009 Comments off

Microsoft’s Remote Desktop for Mac is a great piece of software (Remote Desktop on Windows is way ahead of the Mac, which is slow and based on VNC).

Because VNC is so slow, I often ssh into my work system when I work remotely. But I want to use Remote Desktop for my Windows work as well.

My Windows system has a dynamically-attributed IP address and no fixed name (it’s not “on the domain”). I also have a Remote Desktop session running on my work Mac.

lsof

lsof (list open files) not only lists open files by all processes, it also lists open ports. Once you know that the Windows Remote Desktop port is called ms-wbt-server, all you need to do is:

    % lsof | grep ms-wbt-server
    -> Remote      220 philippec    9u    IPv4 0x8977e64       0t0       TCP 120.151.251.57:49161->120.151.248.213:ms-wbt-server (ESTABLISHED)

Voilà! The address of the remote Windows system is 120.151.248.213 .

Parsing this through awk to extract the IP address for scripting purposes is left as an exercise to the reader…

Categories: Quickie Tags:

AvatarManager is Open Source

February 18th, 2009 Comments off

What is it?

Around the time of c4[2] I was futzing around with making a proper UI for my Twitter Avatar Acorn script.

I also wanted to learn about Core Data and Image Kit.

So I wrote AvatarManager, which uses Core Data to store images in png, tiff or jpg format and displays them in an ImageKit view. People suggested that it should not be limited to Twitter (which is my main Social Network app), so I made a pop-up menu populated by scripts that are run as an NSTask by the app. The scripts provide the interface to the image-upload interface of your on-line service.

Things change

Unfortunately, the Twitter “API” got modified and my script broke, so I lost interest. I was using Ruby’s ‘mechanize’ module to essentially script my way in the web page, which is an interesting way of doing it.

It’s still a good project!

I think the app is a good starting point: it’s simple, robust and extensible. The source code is easy to understand, and it shows how to use the Keychain (to store your passwords) and NSTasks.

You can help!

Grab a copy of the code and hack away! Patches welcome. Especially if you have new and updated upload scripts.

hg clone http://bitbucket.org/philippec/avatarmanager

Requirements

  • MacOSX 10.5 (Leopard)
  • Xcode 3
Categories: Development, Leopard, MacOSX Tags:

Professional code aesthetics

September 15th, 2008 3 comments

At the c4[2] conference, Buzz Andersen said in his talk Apple to Indie:

Professional code is ugly

If I understood him correctly, as he started to work at Apple, he was expecting the code there to be beautiful. Paraphrasing: These guys are the keepers of the APIs. The code they write has to be beautiful!

If you are reading this, you probably believe that you, too, can write beautiful (or at least, aesthetically pleasing) code. And you are probably correct.

Your code probably starts out as beautiful, or as beautiful as you can make it. You are the only one working on it. It is your design, your vision. You are proud of it. This is good.

If you are part of a team, your code probably gets in a common source code repository. Then your colleagues start to work on it, because a of an edge case you had not anticipated. They are motivated by the best intentions, but unfortunately, smallest common denominator sets in. “Monkey-see, monkey-do”-type code cannot lead to beautiful code: it has to be designed.

Design is hard

Design is hard. It’s also hard to maintain as you are fighting barbarians at the gate: deadlines, colleagues, new features, even bugs.

Good design is obvious, especially in hindsight. It can be enforced by the design patterns, the unit tests, the compiler macros or the C++ templates.

Interfaces

Your code also has to interface with other code, be it the operating system, or the Perl script that talks to your printer-plotter over a RS-232 connection.

Maybe you control the interface, and can keep the design. Usually, you don’t. And then you’re stuck interfacing with ugly code, which tends to infect your code by imposing its own constraints. Lowest common denominator again.

Changing requirements

No code can handle everything. There are some extreme cases where requirements don’t change, and the person/team in charge of those requirement know everything in advance. If that is the case, congratulations, you can use the waterfall development model. The rest of us have to deal with change.

When a change in requirements happens, maybe your design can accomodate it, maybe not. Usually, you have to refactor.

Refactoring

You may have some budget for refactoring? Well-planned. But remember, the ultimate goal of refactoring is to ensure that your code performs in the same way as before. This is a win for you, and future versions of your product, but does nothing for the current customer.

The “business case” for refactoring tends to diminish with time. As deadlines loom, those two months that you had planned to rewrite the print engine will fly by, and if you are not 80% done in 20% of the time, you should seriously think about how important this is. Because if you are committing your whole team for two months, that’s a lot of (wo)manpower. And if you take a subset of the team, they might feel like they are left behind, working on something that has no user-visible impact while someone else works on a whiz-bang shiny feature.

Refactoring is not undertaken lightly. There is a very distinct possibility that your refactoring will break the app. One way to alleviate those concerns is to use unit tests. When automated (and run as often as possible, for instance on every check-in), they can increase your confidence that you did not break anything important by re-writing a critical subsection of your code.

Test-driven development (TDD)

I love TDD. I love writing a test for a bug, exposing it and fixing the code to make the test pass. I also love to develop “framework-level” features, for example File I/O, using tests to create, open, parse, and save files in the new disk or network format.

However, test-driven development leads to utilitarian code. Literally, “write enough code to cover the unit tests, and then no more.”. While some interesting designs may arise from this, more often than not the code is textbook-like, driven by the requirements imposed by the tests.

Summary

Any of these features can lead to ugly code:

  • Several people working on it
  • Changing requirements
  • Interfacing to non-beautiful code
  • Test-driven code

I would be hard-pressed to find some code that does not have at least one of these characteristics.

So I agree with Buzz. Professional code is ugly. When you see beautiful code (and you will know when you see it), treat it like a endangered species. Protect it, nourish it, make it regain some strength. Educate others about it. Because one day you will look at it again and think proudly: this is beautiful code.

Categories: Development Tags:

Desktop Core Location

September 8th, 2008 20 comments

Update 18/Feb/09 @ 19:45 EST: Source code is on BitBucket.org:

hg clone http://bitbucket.org/philippec/desktopcorelocation

Update 5/Feb/09 @ 12:43 EST: It appears that Apple may implement CoreLocation in their next generation desktop OS. Excellent!.

Update 8/Sep/08 @ 22:40 EST: The license is MIT. Share and enjoy!.

Update 8/Sep/08 @ 21:45 EST: This is an IronCoder entry, and is essentially a hack. I’m working on figuring out a license.

My IronCoder entry for c4[2]: a clean-room implementation of Apple’s CoreLocation.framework, complete with sample application.

http://developer.casgrain.com/files/DesktopCoreLocation.zip

Description

This is a clean-room implementation of Apple’s CoreLocation framework that is part of the iPhone SDK.

It uses Apple’s own headers, which are installed when you install the iPhone SDK, as the interface, and implements all the functionality of CoreLocation in an embeddable framework.

It can be made a system-wide framework buy changing its executable path from @loader_path/../Frameworks/ to /Library/Frameworks.

The Desktop and Phone sample applications are very similar: they both demonstrate using CoreLocation.framework.

How it works

The framework figures out your current, internet-facing IP address using whatismyip.com. It then uses basic IP Geolocation web services to extract latitude and longitude. Results are cached 30 days for each IP address.

There are certainly other IP geolocation services (for instance, SkyHook Wireless) but they required a paid license.

Paranoïa

In keeping with the theme, you can drop a file called unauthorizedApps in /Library/Documents/WebServer/clbl/ and start your webserver. You can then edit the file at will to deny a particular app the use of CoreLocation.

Contents

PhoneLocation/PhoneLocation.xcodeproj
Sample application that demonstrates using CoreLocation on the iPhone/iPod Touch.
Build and go, it will find your current location (which, in the Simulator, is 1, Infinite Loop, Cupertino, CA). Press the “Show Me” button to go to these coordinates in Google Maps.

DesktopLocation/DesktopLocation.xcodeproj
Sample application that demonstrates using CoreLocation on the Desktop. It uses the Desktop CoreLocation framework in exactly the same manner as PhoneLocation, but the results are pulled from my version of the framework.
Press the “Show Me” button to go to these coordinates in Google Maps.

CoreLocation/CoreLocation.xcodeproj
Stand-alone, embeddable framework

Requirements

  • MacOSX 10.5
  • Latest iPhone SDK. Does not contain any Apple proprietary information.
  • Internet connexion
Categories: Development, MacOSX Tags: