Automatically localize your nibs when building

December 22nd, 2009 21 comments

This post applies to all Cocoa apps, whether on the desktop or on the iPhone.

When you want to localize your application, you can take several routes.

No nibs, only .strings files

The first one is to not use nibs (or xibs, in the new parlance). If you build everything programmatically, +alloc and -init-ing your controls and your views, you can store all the strings in .strings files. These files are simple key-value pairs of Unicode-encoded strings. Call the files “Localizable.strings”, drop them in the appropriate language folders (English.lproj, French.lproj, etc…).

	English.lproj/
		Localizable.strings
			[file contents]
			"Dawn" = "Dawn";
			"Sunrise" = "Sunrise";
			"Sunset" = "Sunset";
			"Dusk" = "Dusk";
	French.lproj/
		Localizable.strings
			[file contents]
			"Dawn" = "Aube";
			"Sunrise" = "Lever";
			"Sunset" = "Coucher";
			"Dusk" = "Crépuscule";

To use, simply call call:

	NSString *str = NSLocalizedString(@"Dawn", nil);

This technique is important even if you have nib files, because most of your strings are probably in .strings files already. Don’t use hard-coded strings for anything that is user-visible. Only use hard-coded keys.

Use nibs

This looks easy: duplicate your English nib, move it to the appropriate folder, open it in Interface Builder and translate all the strings directly in IB.

This is very bad. Don’t do it.

  • It’s unmaintainable: as soon as you change one thing in the original nib, you have to do it in one or more other nibs.
  • It’s error-prone: you can easily disconnect a binding without realizing it, and Undo support in IB is spotty at best (I don’t rely on it). By the time you realize your mistake, it may be too late and you have to revert everything.
  • It’s not versionable: although xib files are XML files, they are not meant to be user-editable. Think of them as binary files. You wouldn’t merge binary files now, would you?
  • It’s hard to do: there are many nooks and crannies where localizable strings are found in IB (tooltips?), you’re bound to forget some of them in some language.
  • It needs a developer: you would not hand off a nib file to a translator and expect him to know all of the above: there is some training involved. And even if you have the Fear of God when editing nib files (because you’ve been burnt before by a disconnected IBOutlet), chances are your translator has no such qualms.

Translators want strings files, not nib files

You could use Wil Shipley’s approach of translating everything at run-time. That approach has one great advantage: everything is driven by strings files, which only contain strings (duh!). Your translator(s) will do a great job with them, and you can easily diff the results in your own version control system when the translations come back.

There are, however, drawbacks to this approach. Since everything happens at run-time:

  • You have to ship and execute more code than necessary (this is a minor point, but still valid for the iPhone since it is very resource-limited).
  • You can only see the results by launching your app in the appropriate language, switching using the International Settings panel, which is tedious.

A compile-time approach

My approach expands on Brian Dunagan’s use of ibtools and genstrings.

Xcode has excellent build scriptability due to Run Script build phases. In this example, we will use a script to generate a localized French xib from an English xib, using a strings file as a template for translation.

First, create a French version of your xib file by adding a French localization to your English xib file.
Assuming it is called “MainWindow.xib”, select the xib file, choose File > Get Info and in the “General” pane click “Add Localization”. Type “French”.

This will create the “French.lproj” folder, which will contain a copy of the English MainWindow.xib.

Next, add a Run Script phase to your application target:

Run Script build phase in Xcode

Finally, enter this as your script:

# Extract English strings (use this to check if you added new strings to your nibs)
ibtool --generate-strings-file Resources/English.lproj/MainWindow.strings Resources/English.lproj/MainWindow.xib
# Generate French interface
ibtool --strings-file Resources/French.lproj/MainWindow.strings --write Resources/French.lproj/MainWindow.xib Resources/English.lproj/MainWindow.xib

Repeat each pair of lines for each xib you need to localize, and adjust the names accordingly.

The first command extracts all the localizable strings from your English xib, and stores them in English.lproj/MainWindow.strings. This is your reference file: add it to version control, but you do not need to add it to your application. You can add it to your project, but make sure it is not included in your app bundle (it is useless at runtime).

The second command takes a French-localized version of the same strings file (French.lproj/MainWindow.strings) and, using the English xib as a template, generates the French xib.

Wait a moment…

If you followed so far, build your app. The script should fail, because French.lproj/MainWindow.strings does not exist yet. Just make a copy of English.lproj/MainWindow.strings and put it in the French folder. Just like the English MainWindow.strings, you want to add this file to version control and your project, but not to your app’s resources.

If you build again, everything should go fine and your French MainWindow.xib should be created… in English.

Translation

Of course, you have to translate the French MainWindow.strings. Mine looks a bit like this:

/* Class = "IBUIBarButtonItem"; title = "Done"; ObjectID = "138"; */
"138.title" = "Terminé";
/* Class = "IBUIBarButtonItem"; title = "Today"; ObjectID = "140"; */
"140.title" = "Aujourd'hui";
/* etc... */

You can send this file to your translator, and instruct her to only translate the words in quotes on the right-side of the equal (“=”) sign. Don’t touch anything else.

But… the Fear of God?

Everything else in this file is necessary for ibtool to do its job properly, and should not be touched. There are two safeguards against you (or your translator) accidentally touching this information:

  1. You have the original English file. When the file comes back from your translator, you can diff it against the original (which is in English, remember?) and see that she has followed your instructions (or not). It should be pretty easy to spot the differences with FileMerge.
  2. Every build, the script re-creates the English MainWindow.strings file. Your version control system should flag any differences immediately. For instance, if you added a label, you would see it in your new English file and you could apply the exact same change to your French file, and make a note to send it for translation again.

I found that these two safeguards more than compensated for the fact that the generated strings file are really source code, since they contain essential information for ibtool to translate properly.

Summary

Since everything happens at compile-time, my solution has none of the drawbacks of Wil’s solution:

  • No extra code needed.
  • You can look at a generated xib file in Interface Builder and immediately see any layout issues you might have. In this case, change the English xib, rebuild and check again.

Remember, these files should be added to version control, but not to your app bundle:

  • English.lproj/MainWindow.strings
  • English.lproj/MainWindow.xib
  • French.lproj/MainWindow.strings

And this file should be added to your app bundle, but not to version control (since it is generated each time):

  • French.lproj/MainWindow.xib

Now, every time you build, you will generate your French interface and it will be added to your app. Simple and efficient.

Categories: Development, MacOSX Tags:

C4[3]: Each decision is an opportunity to fuck up

September 30th, 2009 8 comments

I was fortunate enough to attend the fourth edition of the C4 conference in Chicago, September 25th to the 27th.

Synopsis

C4 is a developer conference, oriented towards Mac and iPhone independent (“indie”) developers.

The talks are intended to get you out of your comfort zone, and to expose you to new ideas. To make you think about a new way to explore problems.

This year, several BlitzTalks were presented. These talks are five minutes long and have 20 slides, auto-advancing every 15 seconds. It is fair to say that the BlitzTalks stole the show and were very appreciated by the audience. It was obvious that a lot of craftmanship went into each of those bite-sized talks.

I gave a BlitzTalk at C4[3], which was titled “Friction-free documentation” and was very well-received. I have included it in this post for completeness’ sake, because I obviously did not take notes during my own talk.

Juxtaposition
One of the funnier, and probably coincidental, aspect of this year’s C4 was all the contradictory advice that was given. For instance, “you should have a web form for contact info” (Aspeslagh) vs “You should never have a web form” (Welch). Or “Don’t use private APIs” (Drance) to “Here’s how to load a plugin in 64 bits now that Input Managers are gone” (Morrisson). Or “JSTalk is the future. Applescript is dead” (Rentzsch) to “You should always have an AppleScript interface” (Correia).

After all, the conference was exciting, thought-provoking and insightful. The food was excellent, and a lot of beer was drunk.

Apologies in advance…

This is my account. It has my biases. I may have summarized things in my own words, this is not verbatim. For that, you will have to wait for the videos.
I took quite a few notes, but some talks just did not lend themselves to note-taking. For example, Mark Boszko’s excellent “Video and You” was, well, very visual, and I don’t draw that fast.
It was especially hard to take notes in the BlitzTalks, they were, as Mike Ash put it, “like a shotgun blast to the frontal lobes, in a good way“.

So this is a transcription of my notes. There are probably some errors in there; if you see some please let me know and I will gladly fix them.

And if I did not capture your talk fully, or at all, that can also mean that I was so absorbed by it that I could not take notes.

I do have a special apology for Marshall Culpepper. I do not have any notes from his talk about Appcelerator, but the only reason is that I was physically drained from giving my BlitzTalk, which felt like giving a 30-minute talk in 5 minutes. That’s a lame excuse, but it’s the only one I have.

Full-length talks

Rentzsch – JSTalk

JSTalk is a language with optional bracket syntax, so it is easily accessible to both Obj-C and Javascript developers

One will note that JSTalk is intended for Desktop applications, while Objective-J (see 280North.com) is intended for Web apps.

JSTalk contains a framework, an optional editor and a command-line tool. You can add JSTalk support to your app with one line of code: [JSTalk listen]. This exposes your classes and object model in much the same way that FScriptAnywhere does. No need to write an object model wrapper like you do for AppleScript.

Benefits: fast and easy to do
Drawbacks: everything is exposed, nothing is documented unless you have sample code.

Note that you can save a JSTalk script as a truly stand-alone applet, as the script bundle can contain the JSTalk framework, which is pretty small.

Finally, JSTalk isn’t really about Javascript, it’s about scripting over Distributed Objects. Because that’s really what’s going on behind the scenes, as opposed to AppleEvents.

http://github.com/ccgus/jstalk

Drance – How to be a Good Developer

Matt Drance was a developer at Apple, moving from DTS to System Software. He became indie a few weeks ago and would like to share, from the inside, how to be a Good Developer™.

First, you must have and cultivate your contacts at Apple. To do this, it helps a lot if you make great stuff.
Find your focus, and polish that application. Don’t be shy, do some marketing and it will attract Apple’s attention. Get listed on MacOSX downloads: it’s free!.
Adopt Apple technologies early and often. Submit to the ADA: you may not win, but some smart and influential people will look at your application.
Go to Apple Events as much as you can. WWDC is not mandatory every year, but don’t avoid it either. If there are TechTalks or kitchens in your area (or relatively close), try to attend as much as possible.
When you have contacts at Apple, stay in touch. Sending promo codes to Apple employees is a great way to do this.

Information, Organization and Diplomacy

Information

Log bugs! If it’s not in Radar, it does not exist. Never assume that Apple knows.
Try to log good bugs. For the title, think keywords: ER (enhancement), REGR (regression), 10A342 (build number).
Note: bug titles are editable, so make sure that this information is also in the description.
More info is better. Shark samples are very useful.
If you can, attach the smallest sample project that reproduces the issue. A screen recording is also a very good way to explain a problem: more often than not, an engineer will look at a screencast and think: “I know exactly why this is happening”. This is good.
Think big! There may be a larger perspective here. You don’t know who else can be affected by this, might be thousands.
Your Enhancement Request can make Apple products better. For instance, the overlay controls in the iPhone camera were originally an ER for putting mustaches in front of people’s faces. Now they are used for Augmented Reality apps on the iPhone.
Duplicates happen, but they are important in weighing a bug. A good bug might also contain more info and help pinpoint the problem.
Answer your bugs. You may disagree politely (for instance “as designed”). Be civil, but you can also ask that it may be converted to a feature request. Make sure that documentation bugs are marked as such, as they are on a different track / release schedule.
If the Next Major OS release is not soon enough, you can request a Clone for Software Update. No guarantees, of course.

Get organized

Your elevator pitch for your bugs should always be ready. You should know your bug numbers: keep a prioritized hitlist / wishlist, complete with bug numbers. If it is a clear, brief and forwardable document, and you send it to a contact at Apple, it has a good chance of being passed around.

Diplomacy and Trust

It is very important to establish trust. Assume things are under NDA. If unsure, ask. Keep things quiet: don’t blab about next releases, and the possible fixes in them.
If you see one of your Apple contacts listed in the press, on a blog, something, tip them off.
Don’t name names: poorguy@apple.com may not have been supposed to help you!
Stick to the public API. Private APIs will break the trust you spent years building up.
Control yourself. Be fair in public, and firm in private. Be nice in Radar, and think before speaking.

Overall, stay positive and friendly. Apple is made of humans too!

Dribin – Unit Tests

System Tests

These tests are difficult to automate. They are usually slow. This is often your UI.
Several solutions exist for this (e.g. Eggplant) but they tend to be very high-maintenance.
You are better off having good QA people, and beta testers.
System tests often don’t do well with edge cases, because they are so rare.

Functional Tests

These tests can be fully automated, or launched on-demand (e.g. nightly). They tend to test several areas in-depth (ex: file I/O).
They can be relatively slow, their time is counted in minutes. 20 minutes is not unusual.

Unit Tests

These tests are very, very fast. So fast that you could run them at each compile and not notice. His app has over 200 unit tests and they run in less than a second.
Unit Tests are always automated, repeatable and self-checking (meaning that they report failures in a standard manner, be it a build break or an exit code).
Unit tests should not talk to a database, the network, the file system, or require a special setup of your environment.
UTs rely a lot on fake (mock) objects. For example. Core Data has the option of an in-memory store which is blazingly fast because it is not serialized to disk on every transaction. That has limited uses in the real world, but for Unit Tests it is essential.

Benefits include regression testing (did I break something that worked 5 minutes ago?), the ability to refactor, code that is cleaner and easier to maintain. It also allows testing hard-to-run conditions (what if that counter was 40,000?) and helps pinpoint the locality (scope) of errors.
Tests are often used as sample code, and so promote faster development.

Refactoring is improving your design after it has been written, in order to make your software easiter to understand. It will help you find bugs, and program faster, but to do that you have to have good unit tests.

Note that the MVC pattern in general makes code easier to test (see the Delegate pattern), but watch out for huge, all-encompassing controller classes.

Wayer – Translucent databases

What if you had a database of truly anonymous data?

Note that most americans can be uniquely identified by age, sex and zip code. Scary, huh?

If you remove the root user, you push the security to the edge, to your users: they have to remember their passwords since what is stored in the database is a hash combining their name and password. That way, if the database is compromised, no private information is divulged.

You may run the names through a simplification function first (e.g. ALLCAPS, no spaces) to help with entry name variation.

If you do not have a password-recovery system (and you may not have one, it is acceptable) you may have to throw away data, make a new account. This might be acceptable (e.g. throwaway email accounts) or not (bank / health records).

Welch – Carrot and the Stick

What to do when your users are angry at you.

Listen to your users: they have something to say. As long as they are talking to you, they are still passionate about your product. This is good.
Even if they use profanity, they still are professional.
Communication is a two-way street. Engage your users with modern means of communication. You can even call them: they will listen. Don’t have a simple web form as the only way to reach you.

Don’t tell them what to think. “Not a bug, but a feature” is not the correct response.

Your users are human, too. And if they can feel that you are human, that will go a long way.

Don’t try to cater to every user. If you honestly can’t do a certain thing, because you either can’t or there is no business case for it, tell them. And maybe recommend another product. You will make two people happy: the user who wanted to buy your product (but won’t) and the other company whose product you recommend. And you have not lost anything: it’s not money you would have had anyway. But this kind of honesty tends to come back in terms of trust, especially for future products.

Finally, there are two classes of users whom you may ignore, because the insignificant amount of money you will get from them pales in comparison to the time that they will cost you.

The New Media Douchebag can be totally ignored. The Scobles, Winers and Arringtons pretend to wield a lot of power but in fact they do not. Don’t be intimidated by them. They are a fickle bunch, and if you start to kiss their ass they will love you until the next shiny thing comes around, which may be tomorrow. And then you will have kissed ass for nothing.

The blogloon can also be ignored. But make sure that beneath the bile that they are trying to spread there is not a valid issue. Because blogloons typically don’t express their ideas clearly, good things can be lost in the noise. But once you have identified their issue (if there is one), you can ignore them.

Fackler – DVCS internals

Augie Fackler wrote and maintains hg-subversion, the premier hg interface to svn. He had the following tidbits of information regarding the lesser-known DVCSs.

Darcs was written by a physicist, in Haskell, over the principle of commutable patches. With those patches, you are essentially transforming your source tree from one version to another using a transform function, which is reversible. While seductive in theory, it turns out to be extremely slow in practice (on non-trivial source trees). Plus did I mention that you have to install a Haskell compiler?

Monotone is so security-conscious that every patch is signed, to the point that if you remove a patch in the middle of a chain, every subsequent patch has to be re-signed, in order, by the original signers. This is a big deal for most organizations, as people come and go all the time and key management becomes a nightmate.

BitKeeper was the premier DVCS system, until the point where a smart kernel hacker realized he could telnet into the database, run a few commands and get a full dump with history. BK were so irate that they pulled their Open Source licenses for kernel development, and Linus had to write git (but that’s another story all in itself).

Modern DVCS are fast and work offline as well as online. They are very efficient for storage, and make revisionist history obvious. It is easy in CVS and possible in SVN to go back and change files & history in an undetectable manner. Not at all with DVCSs, which store md5 hashes of every change, making it easy to detect reporsitory corruption and tampering.

The two main modern DVCSs are Git and Mercurial.

http://progit.org/book/ and http://hgbook.red-bean.com/read/ are the two books that are online. Both are good content.

Lloyd – Cocotron

Cocotron is an open-source, liberally-licensed project to port Cocoa to Windows. It is similar in intent to GnuStep, but has two big advantages.
First, it runs natively on Windows. No need for a “Yellow Box” or a reboot.
Second, it does not use the GPL LGPL license.

Cocotron started at about the same time as GNUStep, right after the publication of the OpenStep spec in 1994. It was not open-sourced until 2006, when it became possible to do so in part because of the Xcode developer tools.

Cocotron does not have native controls. Everything is custom-drawn.
Implementation is surprisingly efficient: Foundation relies on ~160 native OS calls, while AppKit has ~200 OS calls.
In graphic terms, CoreGraphics is a wrapper for the native drawing functionality. A project called Onyx 2D plans to implement the complete, native CoreGraphics suite in the near future.

You can build Cocotron using cross-compilation in Xcode. There are modified versions of binutils, for example -F / -framework options are added to ld, gcc and gdb are also modified. Xcode plugins are available.
Cocotron looks like a compiler to Xcode, accessible through build rules. It requires MinGW and the System headers/libs for the target system.

Remote debugging of the Windows executable is possible from gdb on the Mac, and should be possible with a VM though Christopher had no first-hand experience in this.

Lopp (aka Rands) – Talking shit, delegating and knowning what you want

When you do a presentation, have an arc, and three points.

For this presentation, the arc is that software development is a series of small decisions. There are of course large decisions, but these are more milestones than the small, day-to-day decisions that we continually make: “what will I name this variable? this class?” etc.

The milestones: Holy Shit, We could do this, We’re doing it, I’m screwed, We’re screwed, Glimmer of hope, Actual hope, Done.

Three points

Skills: talking shit (see Jerk City. Or don’t. Rands disavows any knowledge of this.)

You have to develop your comedy skills. It’s the art of improvisation. With time, this will become less about the funny and more about the “quickly parse everything you know and find the best answer.” Rands believes that all geeks are extremely good at this, even though they may not show it.

Delegate:

We are all control freaks. We like to detect the system to figure out its rules so that we can win. And winning is good. Only problem is we’re all very bad at something, which we may or may not be aware of. It may be graphic design. Maybe finance. Or managing a group of people to get them to do what you want. What do we do then?
The simple answer is to improve. We’re so bad at this skill that if we work at it, we can improve it and it will be better. And that’s often a good idea.
The better idea is to delegate: find someone who’s a rock star at that skill you suck so badly at. But to do that, you have to trust that others an help you. Which is difficult since you’re a control freak, remember?

Each decision is an opportunity to fuck up.

Knowing what you want.

This takes a shit ton of work. It goes from “Holy shit!” to knowing every. single. fact. ever. about something, and building your confidence about this subject. You may be a font nerd who cringes when he sees Arial, or a graphic design nerd, or a color nerd. But Knowing What You Want allows you to push all the decisions as early as possible

[Note: I’m not sure I agree that Big Design Up Front, which was touted by Rands, is the most appropriate method, but it is certain that the earlier you make the decisions, the better your product will be. Kind of like doing the riskiest things first.]

Using your new skills

This will help you make decisions in a crisis, and will give you leverage when negociation comes around. Because who wants to make compromises? Knowing what you want gives you measure, structure, direction.

Thomson – MacRuby

Objective-C is great, but it is not modern. It lacks several features of newer languages.

Code reuse could be much better. Single inheritance is often a barrier. For instance, the Singleton design pattern implies lots of boilerplate code. Who wants to write boilerplate code? Modern languages use either multiple inheritance (ugh!) or mixin classes.

Safety is limited. C is powerful at the price of safety. Raw pointers are often a major source of problems. Garbage collection means one less thing to worry about when writing the code you really care about.

Syntactic abstraction. Why is it necessary to declare an NSArray? Why aren’t hash tables part of the language? Things are getting better, slowly (e.g. foreach loops).

MacRuby is not a bridge to Cocoa. It is a first-class citizen in Cocoa, with complete access to all the frameworks. It is as fast, sometimes faster than Obj-C when dispatching methods. It has IB and debugging support.

When you use MacRuby, you get all the power and flexibility of a modern language. MacRuby boasts the first Ruby pre-compiler, which explains why it is so fast.

However, like all duck-typed languages, you sacrifice static type safety when you use MacRuby. That may be an issue for you, but years of Objective-C have shown that programs are fairly resilient to those dynamic issues.

Blitz Talks

Rhyne – Briefs

Briefs is a small application framework that allows you to create simple mockups for the iPhone, directly on the iPhone. It is also possible to create a fairly complex application that way. Perfect for elevator pitches. Check it out at http://giveabrief.com.

Aspeslagh – Full Time

Going indie full-time can be a lot of work. You have to automate your workflow as much as possible. Don’t manually authorize every sale, for instance.
When you build your store, assume you have more than one product. This will help tremendously when growing,
Have a support form, and (if possible) a support person. Get a virtual phone number.
Diversify your portfolio: people who buy one of your products are already predisposed to buying another one, especially if the experience was positive.

Bobtiki – Filming and photographing

Check out HowTube.com, and http://github.com/atebits/ for SimFinger, screencasting for the iPhone.

Speirs – One-man band

http://ecorner.stanford.edu is a great learning resource for things, even non-programming-related. Fraser learned all he knows about business and marketing from these free courses.

Czerniak – Security tools

CrashWrangler is a great tool from Apple to help you analyze crashes.
Dtrace for analysis. Good paper to read is Beauchamp & Weston.
RE:Trace
PyDbg is a Python debugger.

Vazquez – App Categories

Apps on the iTunes store can be categorized in multiple ways. Here are three of them, along with the observation that you can charge a fair price for some of those apps without too much difficulty.

Quick Hit – Twitter, email
Immersive – games
Accessory – subordinate to main activity. For instance, a guitar tuner is subordinate to playing guitar.
You are marketing to a group of enthusiasts that care about quality. There is no race to the bottom for 0.99$: you can charge a good price if your application is well done and polished.
Example: people that pay 10,000$ for a saxophone will not mind paying 800$ for a case if it is the best, toughest, lightest case out there.

Correia – AppleScript Matters

Your application’s scripting interface matters, because there are a lot more scripters out there than you think. And they will think up ways of using your app that you never thought of.
Your scripting interface is your UI, for scripters. It does not have to be pretty, but it has to be consistent.
Especially with AppleScript. Learn AS and its idioms, and adopt them in your scripting interface. For example, don’t do “do new window” (one commanr) where “create new window” (verb, qualifier, noun) is the accepted idiom.

You should design your scripting interface along with your object model. Cocoa makes this easier than you think.

Benefits include connections with thousands of other apps out there, and Automator, the Scripts menu, etc.

Casgrain – Friction-free documentation

The Blank Page syndrome

“Where to start?” can be paralyzing. Coders’ “blank pages” usually have some scaffolding

Help reduces your Tech Support. Time spent on Tech Support is time not spent on developing your app. Having the user help herself is always faster than email. Writing Help will pay for itself many times over.

Tech Support staff can double as Technical Writers. Definitely worth it on a larger app, but not if you’re just getting started. Hiring someone leads to management/synchronization issues, so friction.

Keep It Simple

Help is just HTML, so you could hire a web designer for your css instead of making one yourself.

Help is just a localizable folder. Users expect to find Help in the Help menu (Spotlight-searchable). Do you like writing html? Go nuts! It’s essentially Safari in a top-window. Don’t overcomplicate things for now
It’s easy if you’ve never done it. Don’t worry about structure: all the html files could be in the same folder.

Resist the urge to write a pdf. It won’t be searchable, and in most cases will open in a separate app (Preview) unless you do something clever. Don’t do it, it’s a time sink and you will tend to sweat the details.

Writing Help should be Friction-Free

What tool do you currently use to capture text? Text Editor? Snippet editor? Wiki?
Outliner?

Avoiding the Blank Page

It should be easy to lay down new thoughts with your tool. No need to create a new document per page. Auto-save is great. You should have minimal structure: titles, lists, maybe bold / italics. Automatic linking between sections if you can get it. I use VoodooPad.

Integrating Help in your Dev Tasks

Think of Help as a Unit Test: document the feature, how it should work, then write the code for it. Don’t sweat the details just yet (no screen grabs!); it’s all “code” for now. Spell-check is your friend.
One page per feature is a good rule of thumb. Spell-check is invaluable, as this “code” is not compiled but interpreted by humans. IT IS NOT A FEATURE SPEC.

Just like code, you will throw some of it away, but less than you might think. Also why it’s important to avoid putting images in your documentation when you start: those can be expensive to re- create.

Integrate Help in Version Control

Sometimes simpler is better (we like to diff) Pure text, Markdown, HTML, XML, RTF, VoodooPad, OmniOutliner…

Help should be a first-class resource in your app, just like the app icon and MainMenu.xib. Make sure it lives where your resources live.
Integrate Help in your build using Shell script build phases. Automate, automate, automate… For instance, you could automatically convert Markdown to HTML.

On-line help is seductive

Just a couple of stub html files, and you’re ready to go (_target=“blank”, anyone?). You can change the content without updating the whole app

Don’t do it unless your app depends on being on-line, since Help becomes dissociated from your app (you broke your version control). Plus your server infrastructure must be kept alive. It’s a great way to procrastinate: “I’ll do it after I ship the app…”
Version Control is broken, unless you do something clever in which case you’re just procrastinating

Example: iChibi http://apps.casgrain.com/iChibi

Godwin-Jones – Opacity

Opacity is a Quartz-based, vector image editor that specializes in icons. It can save to a variety of formats using factories, most interestingly as NSView subclasses and HTML5 <canvas> objects for quick prototyping. Highly recommended.

Hess – Cocoa Boutique

Cocoa Boutique is an open-source, in-app purchase for your app. It uses Aquatic Prime for the licensing, and php/mysql/paypal for the back-end processing.
It has not been written because Wil Shipley is greedy (Golden-Braeburn). The Potion Store is also good but runs on Rails.

http://github.com/fraserhess/boutique

Mitchell – PyObjC

It is a scripting bridge from Python to Objective-C. You can import most Cocoa frameworks directly into python.
For developers, the language is faster / easier than Applescript.
You can debug it with pdb.
ipython gets you an interactive AppKit shell.

Morisson – Loading plugins in 64 bit applications

[I have no notes for this because I was too absorbed by the presentation.]

Wood – Marketing

The “Google Keyword Tool” helps you fine-tune your title and meta-data to rank better in Google. In general, limit your to 65 characters and your <meta> to 156 characters.<br /> Try to link yourself to your products (from a blog or your main page or something) using descriptive text, because Google indexes the link, as well as the text that generated the link. Don’t use “Click Here” as your hyperlink text.</p> <p> Look for the Google Website Optimizer, it is full of good tools to help you make the most of your website by tracking where your users go and what helps them to buy your product.</p> <p> Have an email list with an incentive to join (eg freebie app, more templates for your app). Send good content to that mailing list so your users stay subscribed. When the time comes to announce a new product, do it on the mailing list and your users will help you with your marketing, because most of the time you give them valuable content for free, so they don’t mind (and in fact appreciate) the occasional marketing message.</p> <p> These users are very good: work to keep them. They are predisposed to liking your product, and maybe recommending it to friends. That kind of marketing cannot be bought, it has to be earned.</p> <p><h4>Gerbarg – Compiler engineering</h4> <p> So you found a bug, and you think the compiler is generating bad code. There are good news and bad news.<br /> Bad news: gcc’s code is old and scary. It is very hard to go in there and find, much less fix issues. Few mortals can do this.<br /> Good news: 90% of the issues are in the front-end parser, and Apple has just released (Open Source) a new tag-team: clang/llvm.<br /> <s>Clang is the static analyzer and llvm is a gcc front-end.</s> clang is a compiler front-end, while llvm is a low-level virtual machine which <a href="http://llvm.org/docs/CommandGuide/html/llvmgcc.html">can also generate native code</a>. They are written in easy-to-read, easy-to-maintain C++.</p> <p> You can subscribe and submit patches to the cfe-dev mailing list.<br /> You can check out the code from svn for clang-llvm and build it with current tools: congratulations, you have built your own compiler!</p> <p> Louis said he was not a compiler engineer, having worked on the front-end only, and he was able in a few hours to create a patch to llvm that allows pushing and popping a stack of pragma warnings. In those few hours he also submitted the patch, got it approved and committed to the clang-llvm source tree. His contribution should appear in the next release of Xcode.</p> <div class="fixed"></div> </div> <div class="under"> <span class="categories">Categories: </span><span><a href="http://developer.casgrain.com/?cat=1" rel="category">Development</a></span> <span class="tags">Tags: </span><span></span> </div> </div> <div class="post" id="post-43"> <h2><a class="title" href="http://developer.casgrain.com/?p=43" rel="bookmark">Display std::wstring in Xcode using a Data Formatter</a></h2> <div class="info"> <span class="date">July 21st, 2009</span> <span class="author"><a href="http://developer.casgrain.com/?author=2" title="Posts by Philippe" rel="author">Philippe</a></span> <span class="comments"><a href="http://developer.casgrain.com/?p=43#comments">10 comments</a></span> <div class="fixed"></div> </div> <div class="content"> <h2><code>std::wstring</code>: <a href="http://www.losingfight.com/blog/2006/07/28/wchar_t-unsafe-at-any-size/">unsafe</a>, but sometimes necessary</h2> <p>When dealing with cross-platform code, especially Windows, it is not uncommon to encounter <code>wchar_t</code> and its companion string class, <code>std::wstring</code>.</p> <p> Unfortunately, Xcode does not display these types natively. Yes, you can <a href="http://www.yolinux.com/TUTORIALS/src/dbinit_stl_views-1.01.txt">add this file to your <code>.gdbinit</code></a> file and then you will be able to type:</p> <p><pre> pwstring mystring </pre> <p> but what you really want is for the values to show up in Xcode’s variable display, and in tooltips.<br /> <br /> What you need is called a <a href="http://developer.apple.com/documentation/developertools/Conceptual/XcodeDebugging/600-Viewing_Variables_and_Memory/variables_and_memory.html#//apple_ref/doc/uid/TP40007057-CH9-SW24">Custom Data Formatter</a>.</p> <p> In this post, I will adapt Apple’s sample <code>wchar_t</code> data formatter and modify it to display <code>std::wstring</code> variables. I will also explain where you should put this formatter in Xcode 3.1.<br /> <br /> Complete source code is available on <a href="http://bitbucket.org/philippec/wchardataformatter/">bitbucket.org</a>, with a MIT license.</p> <h2>Initial Data Formatter</h2> <p>Xcode Data Formatters are simple <a href="http://developer.apple.com/documentation/CoreFoundation/Conceptual/CFBundles/CFBundles.html">bundles</a> that are loaded by Xcode at startup. They are essentially plug-ins to extend Xcode.</p> <p> Starting with Apple’s own <a href="http://developer.apple.com/samplecode/WcharDataFormatter/">wchar_t Data Formatter sample</a>, we immediately notice that:</p> <ul> <li>This sample is quite outdated, almost 4 years old (the Release build actually builds only PPC!) <li>It won’t work as-is with <code>std::wstring</code> since it only contains .c files. </ul> <p>We will update this sample to work with <code>std::wstring</code> and build it as a 4-way universal binary.</p> <h2>Modernizing the Data Formatter</h2> <h3>Switching to C++</h3> <p>Since the data formatter will use <code>std::wstring</code>, we need to build it using C++. Just rename all the .c files to .cpp and voilà!</p> <h3>Modify CustomDataViews.plist</h3> <p>If you open CustomDataViews.plist, you will see that it has one entry per variable type that the formatter can handle. That entry contains a Data Formatter, much like the one you would type in Xcode itself (for example, to display the name of a notification—of type NSNotification—you can use <code>{(NSString *)[$VAR name]}</code>). <br /> What is different here is the formatter contains a string that calls back in our custom Data Formatter: for example, <code>wchar_t</code> corresponds to <code>{(char *)myWCharDataFormatter((wchar_t) $VAR, (int) $ID)}:s</code>. <br /> Add two entries to that plist, for types <code>wstring</code> and <code>wstring*</code>, and replace the callback strings with a new unique string, for instance <code>myWStringDataFormatter</code> and <code>myWStringPointerDataFormatter</code>. Watch for capitalization, case matters.</p> <h3>Add new callback functions</h3> <p>Now add the two callback functions you referenced in the plist in <code>myCustomFormatter.cpp</code>:</p> <pre> #include <string> char * myWStringDataFormatter(std::wstring wstr, int identifier) { size_t bufLen = wstr.size() + 1; return dataformatter_char_for_wchar(wstr.data(), identifier, bufLen); } char * myWStringPointerDataFormatter(std::wstring *wstr, int identifier) { size_t bufLen = wstr->size() + 1; return dataformatter_char_for_wchar(wstr->data(), identifier, bufLen); } </pre> <p>As you can see, we are re-using the preexisting <code>wchar_t</code> code with our <code>std::wstring</code>.</p> <h3>Build a 4-way Universal Binary</h3> <p>Double-click the wchardataformatter project icon to bring up the Project Inspector. Select the Release configuration, and click the “Architectures” pop-up menu. Select 32/64 bit universal, as shown below.</p> <p> <img src="/images/wchardataformatter_universal.png" hspace="4" vspace="8" alt="Universal Architecture in Xcode"></p> <h3>Modify the Test Code</h3> <p>Finally, update <code>wcharTest_main.cpp</code> to add new <code>std::wstring</code> variables to display in the debugger:</p> <pre> #include <stdlib.h> #include <wchar.h> #include <string> int main(int argc, char *argv[]) { wchar_t c = 'A'; wchar_t b[255]; wchar_t *d = &b[0]; char *a = "a b c d e f g"; mbstowcs(d, a , strlen(a)); std::wstring s = L"wide string"; std::wstring *ps = &s; return 0; } </pre> <p>Switch the target to <em>wCharTest</em> in Xcode, set a breakpoint on the last line, and hit “Build and Go”. Xcode should build the test application and stop in the Debugger. Since you have not installed the formatter, you should not see anything special in the Variable Display window.</p> <h2>Installing the Data Formatter</h2> <p>Build the Data Formatter (Debug or Release). Locate the bundle by control-clicking on the “Products / wcharDataFormatter.bundle” in the Xcode project.<br /> Drag the bundle to this folder: <code>~/Library/Application\ Support/Developer/Shared/Xcode/CustomDataViews</code>, creating intermediate folders along the way if they do not exist.<br /> You can remove the ~ to install for all users (not just you), and you can also replace “Shared” in the path with “3.0” or “3.1” if you only wanted to install the formatter for Xcode 3.0 or 3.1.</p> <h2>Testing the Formatter</h2> <p>Build the <em>wcharTest</em> target, and set a breakpoint in the main() function of wcharTest_main.cpp. You can step over each line, and you should see your data formatter being called and displaying in Xcode’s Variable Display, as well as in tooltips (Xcode 3.1 and later).</p> <p><img align="center" src="/images/wchardataformatter_display.png" hspace="4" vspace="8" alt="Formatter Display in Xcode"></p> <div class="fixed"></div> </div> <div class="under"> <span class="categories">Categories: </span><span><a href="http://developer.casgrain.com/?cat=1" rel="category">Development</a>, <a href="http://developer.casgrain.com/?cat=5" rel="category">MacOSX</a></span> <span class="tags">Tags: </span><span></span> </div> </div> <div class="post" id="post-37"> <h2><a class="title" href="http://developer.casgrain.com/?p=37" rel="bookmark">Open any page or location in your Help Book</a></h2> <div class="info"> <span class="date">June 29th, 2009</span> <span class="author"><a href="http://developer.casgrain.com/?author=2" title="Posts by Philippe" rel="author">Philippe</a></span> <span class="comments"><span>Comments off</span></span> <div class="fixed"></div> </div> <div class="content"> <h3>Anchors</h3> <p> So you <a href="http://developer.casgrain.com/?p=16">wrote your documentation using VoodooPad</a> and you want to open a page of your choice, for instance, the “Quick Start” page. <br />But you don’t want this page to be the index page.</p> <p> You know you can open your Help Book with this code:</p> <pre> [[NSApplication sharedApplication] showHelp: nil]; </pre> <p> What you want to use is this:</p> <pre> NSString* bookName = [[NSBundle mainBundle] objectForInfoDictionaryKey: @"CFBundleHelpBookName"]; [[NSHelpManager sharedHelpManager] openHelpAnchor: @"__ANCHOR__" inBook: bookName]; </pre> <p>(where <code>__ANCHOR__</code> is an actual HTML anchor in your Help Book)</p> <h3>Creating the anchors</h3> <p>There are two parts to this. First, create page aliases in VoodooPad and second, update your <code>WebExportPageTemplate</code> to output those aliases.</p> <h4>VoodooPad Page Aliases</h4> <p>Any page in VoodooPad can have one or more aliases. They can be added by clicking the <strong>Info</strong> button on the Toolbar and clicking on the “+” button at the bottom of the Info panel. You can add as many as you want.</p> <p> <em>Tip:</em> If you have multiple languages for your Help file, make sure that the aliases match the <code>openHelpAnchor:</code> above. Since this string is not user-visible, it makes sense that it be the same across all Help files.</p> <h4>Modifying the page template</h4> <p>In your VoodooPad document, open the <code>WebExportPageTemplate</code> page. <br /> On that page, insert the following code at a proper location (for instance, just after the <code><body></code>tag):</p> <p><pre> <a name = "$alias$"></a> </pre> <p> <code>$alias$</code> is a special <a href="http://www.flyingmeat.com/fs/flystashweb.cgi/33e3c704-3124-01da-160e-c0cbe4c4be5d">VoodooPad variable</a> that is replaced by the first alias in the list you defined above. It can also be called <code>$alias[0]$</code>.</p> <p> 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 <code>$alias[1]$</code>, <code>$alias[2]$</code>, 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.</p> <p><h4>Indexing</h4> <p>Make sure you actually run the Help Indexer application on your HTML Help folder. </p> <p><pre> #!/bin/sh "/Developer/Applications/Utilities/Help Indexer.app/Contents/MacOS/Help Indexer" \ "$VPWebExportOutputDirectory" </pre> <p> This step is necessary for the Help System to find your anchors by name.</p> <div class="fixed"></div> </div> <div class="under"> <span class="categories">Categories: </span><span><a href="http://developer.casgrain.com/?cat=1" rel="category">Development</a>, <a href="http://developer.casgrain.com/?cat=5" rel="category">MacOSX</a></span> <span class="tags">Tags: </span><span></span> </div> </div> <div class="post" id="post-31"> <h2><a class="title" href="http://developer.casgrain.com/?p=31" rel="bookmark">Sending Mercurial commit messages to Twitter</a></h2> <div class="info"> <span class="date">May 25th, 2009</span> <span class="author"><a href="http://developer.casgrain.com/?author=2" title="Posts by Philippe" rel="author">Philippe</a></span> <span class="comments"><span>Comments off</span></span> <div class="fixed"></div> </div> <div class="content"> <p>[Update 16/02/2011 – This no longer works now that Twitter has disabled basic_auth. Oh well….]<br /> [Update 21/07/2009 – Twilight is now called Daylight.]</p> <p>If you follow what I do on this blog and on my <a href="http://www.cocoacast.com/?q=blog/189">podcast</a> with <a href="http://twitter.com/philippeguitard">Philippe Guitard</a>, you know that I like <a href="http://www.selenic.com/mercurial/">Mercurial (<code>hg</code>)</a>, one of the newer <a href="http://en.wikipedia.org/wiki/Distributed_revision_control">distributed version control systems</a>.</p> <p> In particular, I really like <a href="http://bitbucket.org/snej/murky/wiki/Home">Murky</a> as a GUI front-end to Mercurial. In fact, I’m working on the <a href="http://bitbucket.org/kaluznyo/murky_i18n/">French localization</a> with <a href="http://bitbucket.org/kaluznyo/">Olivier Kaluzny</a>.</p> <p> I want to create a tweet from <a href="http://twitter.com/ichibiapp">@ichibiapp</a> or <a href="http://twitter.com/daylightapp">@daylightapp</a> 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.</p> <p> If you want to do the same for your Mercurial repositories, here’s how:</p> <ol> <li>Put this shell script, called <code>commit-twitter</code>, in your <code>$PATH</code> and mark it as executable (<code>chmod a+x commit-twitter</code>): <pre> #!/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 </pre> <p> <font size="-1"><br /> 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 <code>HG_</code> environment variables set by Mercurial. <br />Finally, it uses <code>curl</code> to generate the tweet, using silent mode (<code>-s</code>), running in the background with <code>&</code> (to return immediately) and sending the output to <code>/dev/null</code>.<br /> </font> </li> <li>Add the following lines to your repository’s <code>.hg/hgrc</code> file, creating it if it does not exist: <pre> [hooks] commit.tweet = commithook-twitter <twitter-username> <twitter-password> </pre> </li> <p><li>There is no Step 3! </li> </ol> <p>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.</p> <div class="fixed"></div> </div> <div class="under"> <span class="categories">Categories: </span><span><a href="http://developer.casgrain.com/?cat=7" rel="category">Quickie</a></span> <span class="tags">Tags: </span><span></span> </div> </div> <div class="post" id="post-16"> <h2><a class="title" href="http://developer.casgrain.com/?p=16" rel="bookmark">Day Four: Integrating Help in your application using VoodooPad</a></h2> <div class="info"> <span class="date">May 11th, 2009</span> <span class="author"><a href="http://developer.casgrain.com/?author=2" title="Posts by Philippe" rel="author">Philippe</a></span> <span class="comments"><a href="http://developer.casgrain.com/?p=16#comments">3 comments</a></span> <div class="fixed"></div> </div> <div class="content"> <p><em>[Update September 12th, 2009: <a href="http://flyingmeat.com/download/latest/voodoopadreleasenotes.html">VoodooPad 4.2.2</a> fixes the one bug in this post. Thanks Gus!.]</em><br /> <s><em>[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.]</em></s><em>[Update August 30th, 2009: now that Snow Leopard has been released, I updated the Help Indexer script to run with <code>hiutil</code>.]</em></p> <h3>Documentation</h3> <p> 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 <a href="http://apps.casgrain.com/iChibi/">iChibi</a>.</p> <p> I really like <a href="http://flyingmeat.com/voodoopad/">VoodooPad</a> for its ability to quickly transfer ideas from my head to some kind of structured document, which I am free to revise later.</p> <p> 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.</p> <h3>The process</h3> <p>If you write all your documentation in VoodooPad, you must follow these steps to create a valid Help folder to integrate in your application:</p> <ol> <li>Export the document to your Help folder. </li> <p><li>Open the main page of your document and insert the <a href="http://developer.apple.com/documentation/Carbon/Conceptual/ProvidingUserAssitAppleHelp/user_help_concepts/apple_help_concepts.html">appropriate <code>AppleTitle</code> and <code>AppleIcon</code> tags</a>. </li> <p><li>Drag-and-drop your Help folder to Help Indexer (<code>/Developer/Applications/Utilities/Help Indexer.app</code>), to auto-generate the index. </li> </ol> <p>That’s still a lot of clicking.</p> <h3>Integrating with Xcode</h3> <p>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.</p> <p> Here is the script I use within Xcode. You can use it too, just add a new “Run Script” phase to your target:</p> <p> <img src="/images/run_script_build_phase.gif" width="878" height="302" hspace="4" vspace="8" alt="Run Script build phase in Xcode"></p> <pre> 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</pre> <p> (You will want to replace the name and path of your VoodooPad Help document and output folder, of course.)</p> <p> 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.</p> <h3>But wait, there’s more!</h3> <p>In this Xcode build script, there is nothing about setting the <code>AppleTitle</code> or the <code>AppleIcon</code>, nor is there any Help Indexer. Those are all handled by VoodooPad using built-in scripts.</p> <p> VoodooPad’s Web Export behavior can be overriden by <a href="http://flyingmeat.com/wikka/VoodooPadAdvancedWebExport">specially-named pages</a>:</p> <ul> <li>WebExportPageTemplate<br /> This page overrides the Export Module selection in the Web Export function.<br /> This is your basic html page, with extra markup for the actual page content (<code>$page$</code>). The only notable addition I made was to add two comments in the <code><head></code> section:</p> <p><pre> <!-- AppleTitle --><br> <!-- AppleIcon --> </pre> <p>These will be used postflight script below. </li> <p><li>WebExportPostflightScript<br /> This page will be run as a shell script with a few interesting environment variables, notably <code>$VPWebExportOutputDirectory</code>. Here is the content of the page:</p> <p><pre> #!/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 </pre> </li> </ul> <p><em>Tip: in the Page Info, check “Skip on Export” for those two files since they are not needed by the Help Viewer.</em></p> <p> You can <a href="/files/ichibi_help.zip">download iChibi’s Help document</a> (VoodooPad format) and use it as a starting point for your Help document.</p> <p><a name="#Bugs"></p> <h3>Bugs to iron out</h3> <p></a></p> <p><s><br /> 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!).</p> <p> There is probably a way to dismiss the dialog using AppleScript and accessibility (e.g. <code>"tell button 3 of dialog 'Web Export' to perform action"</code>) but that strikes me as even more of a hack. I hope Gus can fix it in an upcoming release of VoodooPad :-).<br /> </s></p> <p> <em>This is fixed in <a href="http://flyingmeat.com/download/latest/voodoopadreleasenotes.html">VoodooPad 4.2.2</a>.</em></p> <div class="fixed"></div> </div> <div class="under"> <span class="categories">Categories: </span><span><a href="http://developer.casgrain.com/?cat=8" rel="category">Six Days of Cocoa</a></span> <span class="tags">Tags: </span><span></span> </div> </div> <div class="post" id="post-15"> <h2><a class="title" href="http://developer.casgrain.com/?p=15" rel="bookmark">Day Two: iChibi and ImageSoup</a></h2> <div class="info"> <span class="date">April 20th, 2009</span> <span class="author"><a href="http://developer.casgrain.com/?author=2" title="Posts by Philippe" rel="author">Philippe</a></span> <span class="comments"><span>Comments off</span></span> <div class="fixed"></div> </div> <div class="content"> <h2>Six Days of Cocoa: Day Two</h2> <h3>Day Two: <i>code-name iChibi</i></h3> <p><img src="http://developer.casgrain.com/images/Flele_playing_4-5_img1.png" width="260" height="420" alt="" align="right" hspace="12"/></p> <p>My whole family is very much into all things japanese (including, of course, <em>manga</em> and <em>anime</em>). The other day, they found a little “sound-playing ghost” called <a href="http://navy.nm.land.to/ukgk/flele/">Flele</a> (I have no idea what the name means).</p> <p> 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).</p> <h4>iChibi</h4> <p>My <a href="http://cookieschao.deviantart.com/">daughter</a> drew lots of PNGs this weekend (using <a href="http://www.corel.com/painter/">Painter</a>, 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.</p> <p> iChibi can be dragged anywhere on screen, and will float on top of all windows.</p> <p> 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.<br /> <br /> Clicking on the zipper gives a Settings panel, where you can control the its size, opacity and which playlist the songs are coming from.</p> <p> The higher the song rating, the happier it will look.</p> <p> iTunes support is done through the excellent <a href="http://code.google.com/p/eyetunes/">Eyetunes Framework</a>.</p> <p> iChibi works on MacOSX 10.5 and later.</p> <p> If you are interested in following the development of iChibi, follow <code><a href="http://twitter.com/ichibiapp">@ichibiapp</a></code> on Twitter.</p> <p><br clear="all"/></p> <h4>ImageSoup</h4> <p>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 <code>ImageSoup</code>, referring to the Newton’s filesystem of course…</p> <p> <code>ImageSoup</code> 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 <a href="http://flyingmeat.com/acorn/">Acorn</a> image or a Painter RIFF? You could hide the image-loading code in ImageSoup, and your calling code need not be aware of this.</p> <p> To use <code>ImageSoup</code>, just create an <code>ImageSoup*</code> instance and always ask it to load your images (using the full path to the image):</p> <pre> #import "ImageSoup.h" [...] ImageSoup* allImages = [[ImageSoup alloc] init]; [...] NSImage* myImage = [allImages getImageAtPath: @"Full/path/to/image"]; </pre> <p>If the image exists and has been loaded previously, <code>ImageSoup</code> will return it right away. If the image does not exist, <code>ImageSoup</code> will load it and store it for future reference.<br /> <br /> When you release your <code>ImageSoup*</code>, all its images will be released.</p> <p> <strong>ImageSoup.h</strong></p> <pre> // // 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 </pre> <p> <strong>ImageSoup.m</strong></p> <pre> // // 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 </pre> <div class="fixed"></div> </div> <div class="under"> <span class="categories">Categories: </span><span><a href="http://developer.casgrain.com/?cat=3" rel="category">Graphics</a>, <a href="http://developer.casgrain.com/?cat=6" rel="category">Leopard</a>, <a href="http://developer.casgrain.com/?cat=8" rel="category">Six Days of Cocoa</a></span> <span class="tags">Tags: </span><span></span> </div> </div> <div class="post" id="post-14"> <h2><a class="title" href="http://developer.casgrain.com/?p=14" rel="bookmark">Day One: Daylight</a></h2> <div class="info"> <span class="date">April 6th, 2009</span> <span class="author"><a href="http://developer.casgrain.com/?author=2" title="Posts by Philippe" rel="author">Philippe</a></span> <span class="comments"><a href="http://developer.casgrain.com/?p=14#comments">3 comments</a></span> <div class="fixed"></div> </div> <div class="content"> <p>[Update 04/08/2009 – <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=324528814&mt=8">Daylight is available on the App Store. Have a look, it’s free!</a>]<br /> [Update 19/07/2009 – Daylight has been submitted to the App Store!]<br /> [Update 19/07/2009 – Twilight is now called Daylight.]</p> <h2>Six Days of Cocoa: Day One</h2> <p>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”.</p> <h3>Day One: Daylight </h3> <p><img src="http://developer.casgrain.com/images/twilight.png" width="320" height="460" alt="" align="right" hspace="12"/></p> <p><em> Daylight </em> 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.</p> <p> It’s also useful for photographers and filmmakers. One hour before sunset is the so-called “<a href="http://en.wikipedia.org/wiki/Golden_hour_(photography)">golden hour</a>“, where the shadows are long and the scenery is tinted with an amber glow. Dusk and dawn also form the “<a href="http://en.wikipedia.org/wiki/Blue_hour">blue hour</a>“, much more important at higher latitudes, where there is no direct sunlight; everything is diffused through the atmosphere. No shadows, no glare, no overexposure…</p> <p> <em> Daylight </em> 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.<br /> <br /> There are only a few settings in <em> Daylight </em>. You can choose between <a href="http://en.wikipedia.org/wiki/Twilight#Civil_twilight">Civil</a>, <a href="http://en.wikipedia.org/wiki/Twilight#Nautical_twilight">Nautical</a> and <a href="http://en.wikipedia.org/wiki/Twilight#Astronomical_twilight">Astronomical</a> twilight, set the date (defaults to Today) and reset your location (which is cached for 30 days by default).</p> <p> <em> Daylight </em> 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.</p> <p><br clear="all"/></p> <p><del datetime="2009-09-02T12:52:59+00:00">Today, I found the one bug that was preventing me from going forward, so I am looking for beta-testers for <em> Daylight </em>. If you are interested, please send me a direct message on Twitter (<code>d daylightapp</code>) or send an email to daylight-beta@casgrain.com with your <a href="http://developer.casgrain.com/?p=13">device’s identifier</a>.</del></p> <div class="fixed"></div> </div> <div class="under"> <span class="categories">Categories: </span><span><a href="http://developer.casgrain.com/?cat=8" rel="category">Six Days of Cocoa</a></span> <span class="tags">Tags: </span><span></span> </div> </div> <div class="post" id="post-13"> <h2><a class="title" href="http://developer.casgrain.com/?p=13" rel="bookmark">Finding your iPhone or iPod Touch Device ID</a></h2> <div class="info"> <span class="date">April 6th, 2009</span> <span class="author"><a href="http://developer.casgrain.com/?author=2" title="Posts by Philippe" rel="author">Philippe</a></span> <span class="comments"><span>Comments off</span></span> <div class="fixed"></div> </div> <div class="content"> <p>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:</p> <ol> <li>Connect the device to your Mac or PC. <li>On the “Summary” tab in iTunes, click the “Serial Number” label. You will notice that “Serial Number” changes to “Identifier”. <li>Click on the “Edit” menu and select “Copy”. This works even though you can’t select the </ol> <p>Now you can paste the device ID into an email or twitter direct message. It will look something like <code>3fb3e69f58e0412b962bcdf0293f0c524191b447</code>, but will obviously be different for your device.</p> <div class="fixed"></div> </div> <div class="under"> <span class="categories">Categories: </span><span><a href="http://developer.casgrain.com/?cat=7" rel="category">Quickie</a></span> <span class="tags">Tags: </span><span></span> </div> </div> <div class="post" id="post-12"> <h2><a class="title" href="http://developer.casgrain.com/?p=12" rel="bookmark">Quickie: Remote Desktop IP address</a></h2> <div class="info"> <span class="date">February 24th, 2009</span> <span class="author"><a href="http://developer.casgrain.com/?author=2" title="Posts by Philippe" rel="author">Philippe</a></span> <span class="comments"><span>Comments off</span></span> <div class="fixed"></div> </div> <div class="content"> <p>Microsoft’s <a href="http://www.microsoft.com/mac/products/remote-desktop/">Remote Desktop for Mac</a> is a great piece of software (Remote Desktop on Windows is way ahead of the Mac, which is slow and based on <a href="http://en.wikipedia.org/wiki/Vnc">VNC</a>).</p> <p> 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.</p> <p> 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.</p> <p><h3>lsof</h3> <p><code>lsof</code> (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 <code> ms-wbt-server</code>, all you need to do is:</p> <pre> % lsof | grep ms-wbt-server <i>-> Remote 220 philippec 9u IPv4 0x8977e64 0t0 TCP 120.151.251.57:49161->120.151.248.213:ms-wbt-server (ESTABLISHED)</i> </pre> <p>Voilà! The address of the remote Windows system is <code> 120.151.248.213 </code>.</p> <p> <i>Parsing this through awk to extract the IP address for scripting purposes is left as an exercise to the reader…</i></p> <div class="fixed"></div> </div> <div class="under"> <span class="categories">Categories: </span><span><a href="http://developer.casgrain.com/?cat=7" rel="category">Quickie</a></span> <span class="tags">Tags: </span><span></span> </div> </div> <div id="pagenavi"> <span class="newer"><a href="http://developer.casgrain.com/" >Newer Entries</a></span> <span class="older"><a href="http://developer.casgrain.com/?paged=3" >Older Entries</a></span> <div class="fixed"></div> </div> </div> <!-- main END --> <!-- sidebar START --> <div id="sidebar"> <!-- sidebar north START --> <div id="northsidebar" class="sidebar"> <!-- feeds --> <div class="widget widget_feeds"> <div class="content"> <div id="subscribe"> <a rel="external nofollow" id="feedrss" title="Subscribe to this blog..." href="http://developer.casgrain.com/?feed=rss2"><abbr title="Really Simple Syndication">RSS</abbr></a> <ul id="feed_readers"> <li id="google_reader"><a rel="external nofollow" class="reader" title="Subscribe with Google" href="http://fusion.google.com/add?feedurl=http://developer.casgrain.com/?feed=rss2"><span>Google</span></a></li> <li id="youdao_reader"><a rel="external nofollow" class="reader" title="Subscribe with Youdao" href="http://reader.youdao.com/#url=http://developer.casgrain.com/?feed=rss2"><span>Youdao</span></a></li> <li id="xianguo_reader"><a rel="external nofollow" class="reader" title="Subscribe with Xian Guo" href="http://www.xianguo.com/subscribe.php?url=http://developer.casgrain.com/?feed=rss2"><span>Xian Guo</span></a></li> <li id="zhuaxia_reader"><a rel="external nofollow" class="reader" title="Subscribe with Zhua Xia" href="http://www.zhuaxia.com/add_channel.php?url=http://developer.casgrain.com/?feed=rss2"><span>Zhua Xia</span></a></li> <li id="yahoo_reader"><a rel="external nofollow" class="reader" title="Subscribe with My Yahoo!" href="http://add.my.yahoo.com/rss?url=http://developer.casgrain.com/?feed=rss2"><span>My Yahoo!</span></a></li> <li id="newsgator_reader"><a rel="external nofollow" class="reader" title="Subscribe with newsgator" href="http://www.newsgator.com/ngs/subscriber/subfext.aspx?url=http://developer.casgrain.com/?feed=rss2"><span>newsgator</span></a></li> <li id="bloglines_reader"><a rel="external nofollow" class="reader" title="Subscribe with Bloglines" href="http://www.bloglines.com/sub/http://developer.casgrain.com/?feed=rss2"><span>Bloglines</span></a></li> <li id="inezha_reader"><a rel="external nofollow" class="reader" title="Subscribe with iNezha" href="http://inezha.com/add?url=http://developer.casgrain.com/?feed=rss2"><span>iNezha</span></a></li> </ul> </div> <div class="fixed"></div> </div> </div> <!-- showcase --> <!-- posts --> <div class="widget"> <h3>Random Posts</h3> <ul> <li><a href="http://developer.casgrain.com/?p=4">Running a Quartz composition in your application</a></li><li><a href="http://developer.casgrain.com/?p=6">Pimp my IKImageBrowserView: Slide Show</a></li><li><a href="http://developer.casgrain.com/?p=135"></a></li><li><a href="http://developer.casgrain.com/?p=131">Why I cancelled my WATCH order</a></li><li><a href="http://developer.casgrain.com/?p=102">Multiple developers, one iPhone app</a></li> </ul> </div> <!-- recent comments --> <!-- tag cloud --> <div id="tag_cloud" class="widget"> <h3>Tag Cloud</h3> </div> </div> <!-- sidebar north END --> <div id="centersidebar"> <!-- sidebar east START --> <div id="eastsidebar" class="sidebar"> <!-- categories --> <div class="widget widget_categories"> <h3>Categories</h3> <ul> <li class="cat-item cat-item-1"><a href="http://developer.casgrain.com/?cat=1" >Development</a> </li> <li class="cat-item cat-item-3"><a href="http://developer.casgrain.com/?cat=3" >Graphics</a> </li> <li class="cat-item cat-item-9"><a href="http://developer.casgrain.com/?cat=9" >iOS</a> </li> <li class="cat-item cat-item-5"><a href="http://developer.casgrain.com/?cat=5" >MacOSX</a> </li> <li class="cat-item cat-item-11"><a href="http://developer.casgrain.com/?cat=11" title="Posts from https://micro.blog/phil">Micro Blog</a> </li> <li class="cat-item cat-item-7"><a href="http://developer.casgrain.com/?cat=7" title="Quick Tips">Quickie</a> </li> <li class="cat-item cat-item-10"><a href="http://developer.casgrain.com/?cat=10" >Xcode</a> </li> </ul> </div> </div> <!-- sidebar east END --> <!-- sidebar west START --> <div id="westsidebar" class="sidebar"> <!-- blogroll --> <div class="widget widget_links"> <h3>Blogroll</h3> <ul> <li><a href="http://borkwarellc.wordpress.com">Borkware Miniblog</a></li> <li><a href="http://wilshipley.com/blog/">Call Me Fishmeal.</a></li> <li><a href="http://chanson.livejournal.com/">Chris Hanson</a></li> <li><a href="http://gusmueller.com/blog/">Gus’s blog, adventures in Flying Meat.</a></li> <li><a href="http://www.latenightcocoa.com/" title="Mac Development Podcast">Late Night Cocoa</a></li> <li><a href="http://www.red-sweater.com/blog">Red Sweater Blog</a></li> <li><a href="http://rentzsch.com">rentzsch.com: Tales from the Red Shed</a></li> <li><a href="http://theocacao.com/">Theobroma Cacao</a></li> </ul> </div> </div> <!-- sidebar west END --> <div class="fixed"></div> </div> <!-- sidebar south START --> <div id="southsidebar" class="sidebar"> <!-- archives --> <div class="widget"> <h3>Archives</h3> <ul> <li><a href='http://developer.casgrain.com/?m=201711'>November 2017</a></li> <li><a href='http://developer.casgrain.com/?m=201505'>May 2015</a></li> <li><a href='http://developer.casgrain.com/?m=201202'>February 2012</a></li> <li><a href='http://developer.casgrain.com/?m=201112'>December 2011</a></li> <li><a href='http://developer.casgrain.com/?m=201009'>September 2010</a></li> <li><a href='http://developer.casgrain.com/?m=201008'>August 2010</a></li> <li><a href='http://developer.casgrain.com/?m=201002'>February 2010</a></li> <li><a href='http://developer.casgrain.com/?m=201001'>January 2010</a></li> <li><a href='http://developer.casgrain.com/?m=200912'>December 2009</a></li> <li><a href='http://developer.casgrain.com/?m=200909'>September 2009</a></li> <li><a href='http://developer.casgrain.com/?m=200907'>July 2009</a></li> <li><a href='http://developer.casgrain.com/?m=200906'>June 2009</a></li> <li><a href='http://developer.casgrain.com/?m=200905'>May 2009</a></li> <li><a href='http://developer.casgrain.com/?m=200904'>April 2009</a></li> <li><a href='http://developer.casgrain.com/?m=200902'>February 2009</a></li> <li><a href='http://developer.casgrain.com/?m=200809'>September 2008</a></li> <li><a href='http://developer.casgrain.com/?m=200807'>July 2008</a></li> <li><a href='http://developer.casgrain.com/?m=200805'>May 2008</a></li> <li><a href='http://developer.casgrain.com/?m=200803'>March 2008</a></li> <li><a href='http://developer.casgrain.com/?m=200802'>February 2008</a></li> </ul> </div> <!-- meta --> <div class="widget"> <h3>Meta</h3> <ul> <li><a href="http://developer.casgrain.com/wp-login.php">Log in</a></li> </ul> </div> </div> <!-- sidebar south END --> </div> <!-- sidebar END --> <div class="fixed"></div> </div> <!-- content END --> <!-- footer START --> <div id="footer"> <a id="gotop" href="#" onclick="MGJS.goTop();return false;">Top</a> <a id="powered" href="http://wordpress.org/">WordPress</a> <div id="copyright"> Copyright © 2008-2017 developer.casgrain.com </div> <div id="themeinfo"> Theme by <a href="http://www.neoease.com/">NeoEase</a>. Valid <a href="http://validator.w3.org/check?uri=referer">XHTML 1.1</a> and <a href="http://jigsaw.w3.org/css-validator/check/referer?profile=css3">CSS 3</a>. </div> </div> <!-- footer END --> </div> <!-- container END --> </div> <!-- wrap END --> <!-- Powered by WPtouch: 4.3.21 --><script type='text/javascript' src='http://developer.casgrain.com/wp-includes/js/wp-embed.min.js?ver=4.8.4'></script> </body> </html>