MacDevCenter    
 Published on MacDevCenter (http://www.macdevcenter.com/)
 See this if you're having trouble printing code examples


O'Reilly Book Excerpts: Mac OS X Panther Hacks

Hacking Mac OS X Panther

Editor's note: Rael Dornfest, coauthor of Mac OS X Panther Hacks, has selected these three hacks from the book for your sampling pleasure. The first two detail how to find anyone in your Address Book who has an Amazon Wish List, and how to build a GUI to your Unix scripts with a bit of Perl or Python; the third is just for fun. Enjoy.

Moderate Hack #15

Fulfill Wishes with Address Book


This hack will find anyone in your Address Book that has an Amazon Wish List, but it's up to you buy them something!

The Macintosh operating system has always been a rather scriptable environment. Many applications support scriptable events that let you automate tasks or integrate separate programs. With Mac OS X, there are even more opportunities for scripting, now that the ultimate scriptable environment, Unix, is under the hood. But why limit yourself to the Desktop? The Mac's script-friendly environment can integrate your Desktop application with web applications such as Amazon.

This hack is a quick example that loosely integrates the Mac Address Book with Amazon. Using AppleScript and an underlying Unix tool called curl, this script finds people in your Address Book that also have an Amazon Wish List. This script might help you find a gift for someone you know, or it might just give you a different perspective on someone by finding what they're interested in. Most importantly, though, it shows how Desktop applications can become smarter by integrating with web applications.

Mac OS X Panther Hacks

Related Reading

Mac OS X Panther Hacks
100 Industrial Strength Tips & Tools
By Rael Dornfest, James Duncan Davidson

The Code

Open the Script Editor (Applications→AppleScript), enter the following code, and save the script with a suitably snappy name, like Get Wish Lists:

(*
Find Wish Lists in Address Book

The script loops through people in your Address Book, checking Amazon
to see if they have a Wish List. If their Wish List is found, you
have the option to view it in your default browser.

by Paul Bausch
*)

-- set some variables for the curl command

set userAgent to "Mozilla/5.0 (Macintosh; U; PPC Mac OSX; en-us)"
set curlCommand to "curl -i -b -A -L \"" & userAgent & "\" "

-- open Address Book and loop through people

tell application "Address Book"
  repeat with thisPerson in the people
    set thisName to name of thisPerson
    repeat with thisAddress in email of thisPerson
      set thisEmail to value of thisAddress

      -- build the URL that will search for the Wish List

      set baseURL to "http://www.amazon.com/gp/registry/search.html"
      set thisURL to baseURL & "/?type=wishlist\\&field-name=" & thisEmail

      -- use curl to fetch the search page

      set thisWishPage to do shell script curlCommand & thisURL

      -- if the search returns what appears to be a match, prompt the user

      if thisWishPage contains "&id=" then
        set theAction to display dialog thisName & ¬
" has an Amazon wishlist." buttons {"View", "Ignore"}
        if button returned of theAction is "View" then

          -- Find the ID on the page

          set beginID to (offset of "&id=" in thisWishPage) + 4
          set endID to (offset of "'s Wish List" in thisWishPage) - 1
          set thisWishID to get text beginID thru endID of thisWishPage

          if thisWishPage contains "&id=" thenset beginID to 1
            set endID to (offset of "\">" in thisWishID) - 1
          set thisWishID to get text beginID thru endID of thisWishID

          -- Open the default browser to the Wishlist page

          tell me to open location "http://www.amazon.com/o/registry/" ¬
& thisWishID
        end if
      end if
    end repeat
  end repeat
end tell

The script starts by setting some variables for curl that will be used later.

TIP: If you'd like learn more about curl and what these settings mean, open a Terminal window (Applications→Utilities→Terminal) and type man curl. You'll get the complete documentation that explains what all of these command switches do. Or, you can read "Downloading Files from the Command Line" [Mac OS X Hacks, Hack #61].

Then, the script loops through all of the entries in the Address Book, looking for email addresses. It uses each individual email address to build a URL that queries Amazon for a Wish List associated with that address. This is where the script needs to step out of its AppleScript confines to the larger world of Unix commands. With the do shell script command, curl contacts Amazon to see if this person has a Wish List.

If a Wish List is found, the display dialog command brings up a window like the one in Figure 2-6, with two options: View or Ignore.


Figure 2-6. Script prompt

Clicking View tells the script to open the Wish List with the default browser. Because this command takes place within the tell application "AddressBook" block, the context-switching tell me command needs to precede AppleScript's open location function.

Running the Code

You can run the code directly from the Script Editor by clicking the Run button.

To always have it a click away from your Address Book, move the script to the Library/Scripts/Address Book Scripts folder. This way, it'll be available from your Address Book's Scripts menu. Run the script at any time by selecting Script Menu→Get Wish Lists from the Address Book application's menu bar.

Paul Bausch

Expert Hack #25

Add a Dab of GUI to Unix Scripts


Build a graphical dialog front-end to your Unix scripts with just a snippet of Perl or Python glue code.

When it comes to combining Unix scripting and GUI, more often than not, a little dab will do. Such was the case with the Mac OS X Installer I was building for Blosxom (http://www.blosxom.com), my designed-for-Mac-yet-runs-anywhere weblog [Hack #30] application.

I'd built a nice Mac OS X package (http://developer.apple.com/documentation/DeveloperTools/Conceptual/SoftwareDistribution), which turned my otherwise rather Unix-y application into a double-clickable install. However, I still needed to set a configuration setting or three and didn't want to require my users to resort to editing Perl scripts by hand. So, I set about divining an elaborate—and not a little bit grotty—scheme involving one part shell scripting, two parts Perl, and a pinch of AppleScript.

The Grotty Way

As part of its standard procedure, the Mac OS X Installer runs any postflight shell script it finds in a package at the end of the installation process—thus the name postflight (http://developer.apple.com/documentation/DeveloperTools /Conceptual/SoftwareDistribution/Concepts/sd_pre_post_processing.html #//apple_ref/doc/uid/20001945/TPXREF19). This gives the package maker a chance to perform any last-minute reshuffling, fix permissions, run additional scripts, and generally do anything you can do on and from the command line. I slipped a call to a Perl script, configure.pl, into Blosxom's postflight script like so:

echo Running perl/applescript configuration scripts...

sudo /usr/bin/perl "$PACKAGE_PATH/Contents/Resources/configure.pl"

TIP: The output generated by echo in the preceding shell snippet and print in the following Perl snippet are never seen by the user; they're silently recorded in the installer's log. To watch the log as a package is installed, select Show Log from the Installer's File menu.

One of the things this configure.pl Perl script does is call an AppleScript script, configure.scpt, to prompt the user through a series of dialog boxes for the answers to various configuration questions and retrieve the responses as a string of key/value pairs (stored in $options), later to be applied automagically to the Blosxom application itself. Calling the AppleScript script itself is just a matter of passing the script to the osascript command-line utility that takes care of actually running it for me:

print "... prompting for answers to configuration questions\n";

my $options = `/usr/bin/osascript 
               $PACKAGE_PATH/Contents/Resources/configure.scpt 2>&1`;

...

# Do something magical with the contents of $options

...

Finally we come to the GUI bit itself. The configure.scpt AppleScript consists of nothing more than a series of display dialog statements, each prompting for a single configuration setting and saving the user's responses:

tell application "Finder"

activate

display dialog "You're mere moments away from your very own blog." & return 

& return & "Before letting you get on with it, however, let's get a few last 
settings taken care of for you." & return & return & "To skip this step, 
click Cancel." & return with icon 1
display dialog "What would you like to call your blog?" default answer 
"blosxom"
set blog_title to (text returned of result)
display dialog "How would you describe your blog (keep it short)?" default 
answer "yet another blosxom blog"
set blog_description to (text returned of result) 
display dialog "What language code would you like to associate with 
your blog (e.g. en=English, fr=French, de=German)?" default answer "en"
set blog_language to (text returned of result) 
display dialog "How many entries would you like displayed on your 
blog's home page?" default answer "40"
set blog_num_entries to (text returned of result) 
display dialog "What URL should be used for your blog (leave blank 
to have Blosxom figure this out for itself)?" default answer ""
set blog_url to (text returned of result)
set result to ("$$$blog_title=" & blog_title & "$$$blog_description=" & 
blog_description & "$$$blog_language=" & blog_language & "$$$num_entries=" 
& blog_num_entries & "$$$url=" & blog_url)
end tell

For instance, line 1 displays prompts for a blog name, as shown in Figure 2-24, while line 2 saves the result to the AppleScript variable blog_title.


Figure 2-24. One of a series of AppleScript dialogs

Line 3 strings all the various responses together to be returned to the calling configure.pl Perl script.

While this actually does work remarkably well, it is overly involved (albeit not particularly complex) for the rather simple requirements it has to meet. Also, it has quite a staccato feel to it from the user's point of view, with dialog boxes bouncing up and down one after another. Unfortunately, there was just no way, without doing quite a bit more programming, that I could get around sending the user (very possibly a newbie) to the command line—a no-no in my book.

The Nifty Way

Thankfully, I stumbled upon Carsten Blüm's Pashua (http://www.bluem.net/downloads/pashua_en; donateware), a rather nifty tool for creating simple, native Aqua GUIs from Perl, Python, PHP, Tcl, Rexx, and AppleScript. Pashua's capabilities are limited to dialog windows, but it does provide a nice collection of widgets: labels, tool tips, text fields, password fields, checkboxes, radio buttons, pop-up and combo boxes, file selectors, and buttons.

I can now replace that staccato series of dialog boxes with a single dialog box that sports all the configuration questions I need—at least one window's worth.

Installation

Installing Pashua is just a matter of downloading the disk image and copying the contents across to your hard drive. To have the Pashua.app itself readily accessible from anywhere you care to use it, you might consider dropping it into your Applications folder. You'll also want to make sure that the associated Perl or Python module (Pashua.pm and Pashua.py, respectively) you'll be using in your code is somewhere Perl or Python will expect to find it.

My preference is to keep all of the Pashua bits together, simply creating a Pashuafolder in my Documents/Code directory (where I do all of my coding).

You just need to be sure that the Pashua.app application and appropriate Pashua.pm or Pashua.py module is available to your script. The simplest way to do this is to copy these into the same folder as your script and distribute the entire folder to anyone who'll be using your software.

The Basics

Pasha scripts are simply wrappers around a configuration string that's fed to the Pashua application. This string defines the dialog window itself and the various GUI widgets you'd like Pashua to display.

While I'll not delve into every widget in turn, each definition looks a little something like this:

title_type=textfield
title_label=What do you want to call your blog?
title_width=200
title_default=My Blosxom
title_tooltip=Keep it simple.

title is the name I've given to this particular widget; I could have used any string of letters or numbers (e.g., title1, thingy, or wotsit22). Each widget is described by using attributes, prefixed with an underscore (_) and appended to the widget's name. Most widgets take four attributes: type (textfield, button, checkbox, etc.), label (what appears alongside the checkbox or on the button), width (in pixels), and a default (which radio button is initially selected, the initial value of a textfield, etc.). So, this widget is a 200-pixel-wide textfield named title, labeled What do you want to callyour blog?, and with an initial value of My Blosxom.

TIP: It's all much like designing an HTML form, except that attributes are listed as name_attribute, one per line, rather than in an HTML tag. The just-described textfield might look something like this in an HTML form:

What do you want to call your blog?
<input type="text" name="title" size="200" 
value="My blosxom" />

Here's another:

future_type=radiobutton
future_label=Should I show entries from the future?
future_option=No
future_option=Yes
future_default=No

This one is a radiobutton group named future, labeled Should I showentries from the future?, with No and Yes options, the former selected by default.

As for the dialog window itself, there's not much to fiddle with:

# Set window title
windowtitle=Blosxom Configuration Wizard
# Set transparency: 0 is transparent, 1 is opaque
transparency=0.95
# Set the window to brushed metal; the default is regular Aqua
appearance=metal

This window is titled Blosxom Configuration Wizard and sports a 95% opaque (5% transparent) brushed-metal look.

You'll find complete documentation for these directives in the Pasha package itself (Pashua/Documentation/documentation.html) and various hints in the code and comments of the examples used in this hack.

Simple Example

Let's start out with a simple example, one that displays a single dialog window with some number of GUI widgets representing the minimal configuration settings Blosxom requires.

The code

While I could have chosen any of the supported languages, I thought I'd stick to Perl, because that's what Blosxom is written in and I can write Perl in my sleep (although, admittedly, I'm more of a Python devotee at heart).

To a folder named Simple (Documents/Code/Pashua/Simple), I copied Pashua.app and Pashua.pm. I then wrote the following script, borrowing the basic framework from the example code (Pashua/Examples/example.pl) found in the Pashua distribution. I saved the file as Simple.pl:

# !/usr/bin/perl -w

# Simple.pl
# A simple Pashua example

# Add the local directory to the queue of places to look for Pashua.pm
BEGIN { 
        use File::Basename;
        unshift @INC, dirname($0);
}
use strict;
use Pashua;

# Define what the dialog should be like
# Take a look at Pashua's Readme file for more info on the syntax
my $conf = <<EOCONF;
# Lines starting with a hash character are
# comments, empty lines are ignored

# Set transparency: 0 is transparent, 1 is opaque
transparency=0.95

# Set window title
windowtitle=Blosxom Configuration Wizard

txt_type=text
txt_text=Welcome to Blosxom.[return][return]You're mere moments 
 away from your very own blog.[return][return]This wizard will 
 take you through some last minute configuration settings.  If 
 you're in need of some details, take a gander at the gory 
 details on the Blosxom Configuration page at 
 http://www.blosxom.com/documentation/users/configure/.

title_type=textfield
title_label=What do you want to call your blog?
title_width=200
title_default=My Blosxom

description_type=textfield
description_label=How would you describe your blog?
description_width=400
description_default=Yet another blosxom blog.

language_type=textfield
language_label=What will be your primary written language? (e.g. en=English)
language_width=25
language_default=en

future_type=radiobutton
future_label=Should I show entries from the future (i.e. post-dated entries)?
future_option=No
future_option=Yes
future_default=No

# Add a cancel button - if you like, you can set the
# button label by uncommenting the second line below
cncl_type=cancelbutton
#cncl_label=If you click here, no values will be returned

# A default button is added automatically - if you want to
# change the button title, you should uncomment the next
# two lines to override the "built-in" default button
#default_type=defaultbutton
#default_label=Click here to return the values
EOCONF

# Pass the configuration string to the Pashua module
my %result = Pashua::run($conf);

if (%result) {
        print "  Pashua returned the following hash keys and values:\n";
        while (my($k, $v) = each(%result)) {
                print "    $k = $v\n";
        }
}
else {
        print " No result returned. Looks like 
               the 'Cancel' button has been pressed.\n";
}

Lines 7-12 make the Pashua.pm Perl module in the current folder available to the script.

The heart of the script is a long configuration string that defines the various aspects of the dialog window itself (lines 20-24) and component GUI widgets (lines 26-59) stashed in a $conf variable.

Line 63 is where the actual work gets done: the $conf configuration string is passed to the Pashua::run() method of the Pashua.pm Perl module, which, in turn, asks the Pashua.app application to display a dialog box that contains the various described widgets.

Lines 65-73 print the results to the Terminal window, although you'll most likely want to do something far more useful with them in any application you're building with Pashua.

Running the code

Run the Perl script on the command line, like so:

$ perl Simple.pl

You'll see the Pashua application bounce into being in your Dock and your dialog pop up on the screen, as shown in Figure 2-25.


Figure 2-25. A simple Pashua-generated dialog

Click the OK button or just hit the Return key on your keyboard to send the values of the various dialog widgets to the Terminal:

$ perl Simple.pl
  Pashua returned the following hash keys and values:
    cncl = 0
    title = My Blosxom
    future = No
    description = Yet another blosxom blog.
    language = en

Hacking the hack

Since the lion's share of your script is Pashua configuration, the process is just about identical regardless of the language you choose. Here's the same thing (the configuration is abbreviated) in Python:

#!/usr/bin/env pythonimport Pashuaconf = """
# Lines starting with a hash character are
# comments, empty lines are ignored

# Set transparency: 0 is transparent, 1 is opaque
transparency=0.95

# Set window title
windowtitle=Blosxom Configuration Wizard

...

# two lines to override the "built-in" default button
# default_type=defaultbutton
# default_label=Click here to return the values
"""

if Result['cncl'] is '0':
  print "  Pashua returned the following dictionary keys and values:"
  for Key in Result.keys( ):
    print "    %s = %s" % (Key, Result[Key])
else:
  print "  No result returned. Looks like the 'Cancel' button has been pressed.";

print

Wizard Example

I created a new folder named Wizard(Documents/Code/Pashua/Wizard) and again copied Pashua.app and Pashua.pm into it.

The code

The following script, Wizard.pl, defines two separate dialog window configurations, $general and $configurestatic, each to be fed serially to the Pashua engine (additions and alterations from the code in the preceding "Simple Example" section—mostly the addition of a second screen configuration—are called out in bold):

#!/usr/bin/perl -w

# Wizard.pl
# A multi-screen Pashua Wizard example

BEGIN { 
        use File::Basename;
        unshift @INC, dirname($0);
}
use strict;
use Pashua;

# Define the Wizard's first screen
my $general = <<GENERAL;
# Lines starting with a hash character are
# comments, empty lines are ignored

# Set transparency: 0 is transparent, 1 is opaque
transparency=0.95

# Set window title
windowtitle=Blosxom Configuration Wizard: General

txt_type=text
txt_text=Welcome to Blosxom.[return][return]You're mere 
moments away from your very own blog.[return][return]This 
wizard will take you through some last minute configuration 
settings.  If you're in need of some details, take a gander 
at the gory details on the Blosxom Configuration page at 
http://www.blosxom.com/documentation/users/configure/.

title_type=textfield
title_label=What do you want to call your blog?
title_width=200
title_default=My Blosxom

description_type=textfield
description_label=How would you describe your blog?
description_width=400
description_default=Yet another blosxom blog.

language_type=textfield
language_label=What will be your primary written language? (e.g. en=English)
language_width=25
language_default=en

future_type=radiobutton
future_label=Should I show entries from the future (i.e. post-dated entries)?
future_option=No
future_option=Yes
future_default=No

cncl_type=cancelbutton

# A default button is added automatically - if you want to
# change the button title, you should uncomment the next
# two lines to override the "built-in" default button
default_type=defaultbutton
default_label=Next
GENERAL

# Define the Wizard's second screen
my $configurestatic = <<CONFIGURESTATIC;
windowtitle=Blosxom Configuration Wizard: Static Settings

staticdir_type=openbrowser
staticdir_label=Where would you like the static version of you blog to live?
staticdir_width=400
staticdir_default=/Library/WebServer/Documents

staticpwd_label=What would you like to use as your static rendering  password?
staticpwd_type=password
staticpwd_width=200
staticpwd_default=

staticentries_type=radiobutton
staticentries_label=Would you like to statically render individual entries?
staticentries_option=No
staticentries_option=Yes
staticentries_default=No

editor_type=popup
editor_label=When would you like static rendering to run?
editor_width=200
editor_option=Manually
editor_option=Every 1/2 Hour
editor_option=Every 1 Hour
editor_option=Every 2 Hours
editor_option=Every 3 Hours
editor_option=Every 4 Hours
editor_option=Every 5 Hours
editor_option=Every 6 Hours
editor_option=Every 12 Hours
editor_option=Every 24 Hours
editor_default=Manually

cncl_type=cancelbutton
cncl_label=Cancel
default_type=defaultbutton
default_label=Next
CONFIGURESTATIC

# Define the rest of the wizard's screens
# ...

# Pass each configuration string in turn to the Pashua module to create
# a Wizard-like screen-by-screen interface and gather the results along
# the way
my %result = Pashua::run($general));
%result = (%result, Pashua::run($configurestatic));
# All the rest of the screens go here in the same manner
# %result = (%result, Pashua::run($shareware));

if (%result) {
        print "  Pashua returned the following hash keys and values:\n";
        while (my($k, $v) = each(%result)) {
                print "    $k = $v\n";
        }
}
else {
        print "  No result returned. Looks like the 
               'Cancel' button has been pressed.\n";
}

Line 104 runs Pashua::run(), feeding it the first screen configuration held in the $general variable. Line 181 calls Pashua::run() again, this time feeding it the second screen, as described in $configurestatic.

Running the code

Run the Perl script on the command line, like so:

$ perl Wizard.pl

Up comes the first screen of your wizard. Click the Next button to move on to the next screen, shown in Figure 2-26.


Figure 2-26. The second screen of a Pashua-generated wizard

Click the Next button again (in your final wizard, a Finish button is probably more appropriate for the last screen) or just hit the Return key on your keyboard to send the values of the various dialog widgets across all the wizard's screens to the Terminal:

$ perl Wizard.pl
Pashua returned the following hash keys and values:
    title = My Blosxom
    default = 1
    description = Yet another blosxom blog.
    future = No
    staticdir = /Library/WebServer/Documents
    staticentries = No
    staticpwd = s33kr1t
    cncl = 0
    editor = Manually
    language = en

WARNING Be sure to choose different names for your various variables across wizard screens, because all the key/value pairs are stored in a single hash (at least in this example) and a second widget of the same name on another screen will overwrite the value of the first.

And this gets me to right where I wanted to be: a multiscreen configuration wizard that's capable of being called as a script by the Mac OS X Installer.

But if you try this yourself, you'll notice something slightly irritating: for each screen, the Pashua app is brought to life and killed off, over and over. While not quite as staccato as the grotty way I started out with and far more feature-filled and flexible, it still feels a mite bit rough around the edges. And what if the user wanted to rerun it? She'd have to visit the command line and invoke it from there—still a no-no.

Double-Clickable Example

Wouldn't it be nice to make a self-contained, double-clickable application of this, turning your simple Unix script into a GUI wizard that's virtually indistinguishable (except for the more limited form-driven-only functionality) from a first-class application?

The Pashua distribution includes just such a thing in the form of Doubleclickable Example (Pashua/Examples/Doubleclickable Example.app).

Option-drag a copy of Doubleclickable Example somewhere for editing (leaving the original as it is, in case you need it again as a starting point); my copy is Documents/Code/Pashua/Doubleclickable Example.

Control- or right-click the application and select Show Package Contents from the context menu, as shown in Figure 2-27. Browse through the app until you get to Doubleclickable Example/Contents/MacOS. Notice that both the Pashua app and Pashua.pm Perl module are baked right into the application's contents so that they're readily available to the core script. The script itself has the same name as the outer application, Doubleclickable Example.


Figure 2-27. Show Package Contents

Otherwise, open the script in your favorite plain text editor [Hack #78] and edit to your heart's content.

The code

Here's what is essentially the same code as appeared in the preceding "Wizard Example" section. I've added in a few more screens and put in some basic logic for which screens to show, given the input provided on the previous screen and so forth:

#!/usr/bin/perl -w

# Doubleclickable Example
# A double-clickable Pashua Wizard example

BEGIN { 
    use File::Basename;
    unshift @INC, dirname($0);
}
use strict;
use Pashua;
#!/usr/bin/perl -w

use strict;
use Pashua;

my $general = <<GENERAL;
windowtitle=Blosxom Configuration Wizard: General Settings

txt_type=text
txt_text=Welcome to Blosxom.[return][return]You're mere 
moments away from your very own blog.[return][return]This 
wizard will take you through some last minute configuration 
settings.  If you're in need of some details, take a gander 
at the gory details on the Blosxom Configuration page at 
http://www.blosxom.com/documentation/users/configure/.

title_type=textfield
title_label=What do you want to call your blog?
title_width=200
title_default=My Blosxom

description_type=textfield
description_label=How would you describe your blog?
description_width=400
description_default=Yet another blosxom blog.

language_type=textfield
language_label=What will be your primary written language? (e.g. en=English)
language_width=25
language_default=en

future_type=radiobutton
future_label=Should I show entries from the future (i.e. post-dated entries)?
future_option=No
future_option=Yes
future_default=No

cncl_type=cancelbutton
cncl_label=Cancel
default_type=defaultbutton
default_label=Next
GENERAL

my $staticordynamic = <<STATICORDYNAMIC;
windowtitle=Blosxom Configuration Wizard: Dynamic or Static

txt_type=text
txt_text=Blosxom runs either dynamically (on-the-fly) or 
statically, rendering the main index, category indexes, 
date indexes, and (optionally) individual entries as regular 
files on your filesystem. Static rendering is useful for 
those who either prefer to or are only able to serve up 
files from their Web server. You can render your blog from 
locally and then move them to a mounted WebDAV drive, .Mac 
iDisk, or FTP/rsync them up to your ISP's server.[return][return]
For more details on static rendering, visit the Blosxom Static 
page at http://www.blosxom.com/documentation/users/configure/static.html.
staticdynamic_type=radiobutton
staticdynamic_label=Would you prefer to run your blog in static or dynamic mode?
staticdynamic_option=Dynamic
staticdynamic_option=Static
staticdynamic_default=Dynamic

cncl_type=cancelbutton
cncl_label=Cancel
default_type=defaultbutton
default_label=Next
STATICORDYNAMIC

my $configurestatic = <<CONFIGURESTATIC;
windowtitle=Blosxom Configuration Wizard: Static Settings

staticdir_type=openbrowser
staticdir_label=Where would you like the static version of you blog to live?
staticdir_width=400
staticdir_default=/Library/WebServer/Documents

staticpwd_label=What would you like to use as your static rendering  password?
staticpwd_type=password
staticpwd_width=200
staticpwd_default=

staticentries_type=radiobutton
staticentries_label=Would you like to statically render individual entries?
staticentries_option=No
staticentries_option=Yes
staticentries_default=No

editor_type=popup
editor_label=When would you like static rendering to run?
editor_width=200
editor_option=Manually
editor_option=Every 1/2 Hour
editor_option=Every 1 Hour
editor_option=Every 2 Hours
editor_option=Every 3 Hours
editor_option=Every 4 Hours
editor_option=Every 5 Hours
editor_option=Every 6 Hours
editor_option=Every 12 Hours
editor_option=Every 24 Hours
editor_default=Manually

cncl_type=cancelbutton
cncl_label=Cancel
default_type=defaultbutton
default_label=Next
CONFIGURESTATIC
my $shareware = <<SHAREWARE;
windowtitle=Blosxom Configuration Wizard: Shareware

txt_type=text
txt_text=Blosxom is free for the taking and sharing.  That said, 
it does take a considerable amount of not-so-free time and loving 
care.  This Blosxom Installer for Mac OS X is shareware; when you've 
yourself situated and have a moment, please do pay the one-time $15 
shareware fee at:[return][return]http://www.amazon.com/paypage/P13LC7VUIVY0N .

cncl_type=cancelbutton
cncl_label=Cancel
default_type=defaultbutton
default_label=Next
SHAREWARE

my $finish = <<FINISH;
windowtitle=Blosxom Configuration Wizard: Finishing Up

txt_type=text
txt_text=That's all there is to it.  Click the Finish button, let the 
installer finish up, and you'll be whisked away to your brand new blog.

cncl_type=cancelbutton
cncl_label=Cancel
default_type=defaultbutton
default_label=Finish
FINISH
my %result;
%result = Pashua::run($general);
$result{cncl} and exit;
%result = (%result, Pashua::run($staticordynamic));
$result{cncl} and exit;
$result{staticdynamic} eq 'Static' and %result = 
  (%result, Pashua::run($configurestatic));
$result{cncl} and exit;
%result = (%result, Pashua::run($shareware));
$result{cncl} and exit;
%result = (%result, Pashua::run($finish));
# Do something useful with the results (or just save them
# somewhere for now)
open OUT, "> /tmp/pashua.out";
print OUT "  Pashua returned the following hash keys and values:\n";
while (my($k, $v) = each(%result)) {
    print OUT "    $k = $v\n";
}
close OUT;

There are some slight changes—aside from the whole thing running as a first-class, double-clickable application, that is—worth pointing out here.

Line 2 is a cosmetic change, replacing the Next button of the previous screens with a more appropriate Finish button.

Lines 3 through 12 call the Pashua::run() method for each screen and store the results in a %result hash. I have added some logic to check after each screen that the user isn't trying to get out of the wizard by hitting the Cancel button (Lines 5, 7, 9, and 11). If so, we terminate the script on the spot.

Now, of course, we could simply have dropped all the screen names into a loop and iterated over them one by one, like so:

my %result;
foreach my $screen ( ($general, $staticordynamic, $configurestatic,
  $shareware, $finish) ) {
    %result = (%result, Pashua::run($screen));
    $result{cncl} and exit;
}

Line 8 (requiring an exception) is why I chose to do things manually. We offer the user a choice of configuring Blosxom for dynamic or static rendering. Since only the latter requires any kind of additional configuration, the $configurestatic screen is called only if the user selects the Static radio button in Line 1.

The last few lines of the script do something useful with the results—or, in this example, simply stash them somewhere for now. You can't just print the values out to the Terminal (as in the case in the Simple or Wizard examples), because this script runs as a double-clickable app and doesn't involve a visit to the Terminal at all.

Running the code

Double-click the Doubleclickable Example application icon in the Finder to open your dialog window. Click the Next button to jump from screen to screen. Be sure to select Static mode on the Dynamic or Static screen; otherwise, you'll never get to see the Static Settings (as well you shouldn't) shown in Figure 2-28. At the end, click the Finish button to finish up. You can quit the app at any time by clicking the Cancel button or hitting the Esc key on your keyboard.


Figure 2-28. The Static Settings screen of a Pashua-powered, double-clickable application

Notice that the Pashua app no longer ebbs and flows between each screen, making for a nice, smooth experience for the end user.

If you're interested in what shows up in the temporary output file, open a Terminal window and type the following command:

$ less /tmp/pashua.out

  Pashua returned the following hash keys and values:
    description = Yet another blosxom blog.
    editor = Every 1 Hour
    staticpwd = 
    language = en
    cncl = 0
    title = My Blosxom
    staticdir = /Library/WebServer/Documents
    future = No
    staticentries = No
    staticdynamic = Static
    default = 1
/tmp/pashua.out (END)

Hacking the hack

While the documentation says that to change the name of the app you need to change both the name of the outer application, Doubleclickable Example.app, and inner Doubleclickable Example Perl script, I found that changing the name of the outer without touching the inner worked like a charm; indeed, changing the inner script's name caused the app not to run at all.

Moderate Hack #60

iOscillate


Get in enough face time with your fans by means of an iSight, an oscillating fan, a little ingenuity, and a well-developed sense of play.

If you've ever actually tried to do any video-conferencing using iChat and an iSight (or equivalent camera [Hack #34] ), you've no doubt found that it works surprisingly well. Sure, there are sound hiccups and video burps, but most of these can be ameliorated. Add a tad more bandwidth (e.g., DSL instead of 56K modem dialup). Don't download large MP3 files during the call. Shutting off email stems the tide of those large attachments washing in from the office. Or simply use an actual telephone (gasp!) for audio.

But try it with a roomful of people spread unevenly around a conference table and you're sure to find yourself staring at a stray notepad, box of tissues, hopelessly out-of-date organizational chart, or the one person in the room not saying a thing or moving a muscle. Now, you'd think some kind-hearted soul would move the camera every so often, pointing it at least at another unmoving, unblinking participant or different notepad; they probably won't. You'd hope someone would be nominated to or just take charge of pointing the camera at whoever is speaking; it doesn't usually happen. Even when talking directly to the poor schlub on the far end of the call, people will actually stare at the side of the camera, as if doing so somehow provided more presence.

So, what's a telecommuter with poor iSight to do? Why, oscillate, of course.

An iSight mounted to the top of debladed oscillating fan, as shown in Figure 5-50, sweeps out up to a 180-degree field of view. While this doesn't mean you're necessarily going to be looking at the person speaking for more than a split second or so, it does provide more of a sense of actually being there—albeit in an admittedly nauseating fashion.


Figure 5-50. iSight + oscillating fan = iOscillate

Intrigued? I was too when the idea first struck, so I set about building one.

Building an iOscillate

Throwing together an iOscillate of your very own is trivial, eating up a scant 15 minutes or so. It requires little in the way of parts, and no tools are necessary.

Prepare the fan

Appropriate a disused or otherwise available oscillating fan—probably not best done on the hottest day of the summer.

Strip it of its blade and metal or plastic cage. Your average Walmart unit ships with these parts preremoved for your convenience. If it's already assembled, disassembly usually entails only two or three steps and requires no tools. Unclip the cage edges to separate the front portion. Unscrew the nose (usually clockwise in the U.S.) that holds the blade in place and remove the blade. Unscrew the washer that holds the back portion of the cage in place and remove it. If possible, replace the nose so that the spinning metal shaft doesn't hurt anyone.

Mount the camera

Attach one of the various plastic connectoids that came with your iSight or other webcam to the top of the fan. I found that the flat, sticky-based iMac mount worked nicely with my iSight.

You can even just use Scotch or duct tape if all else fails. This, however, does mean that it'll be difficult to impossible to point the camera up or down as needed to catch the faces rather than ties or toupees of the participants.

Try to keep the camera itself away from the fan, to avoid vibration and cut down on the noise of the motor (if your webcam has a built-in microphone).

Do make sure that the camera is upright. And whatever you do, don't even think of strapping it to the soon-to-be-spinning metal shaft.

Run the USB or FireWire cable

Drape or stick down the webcam's USB or FireWire cable in such a way that it has more than enough play yet is well clear of the metal shaft that is used to turn the blade, the mechanics involved in oscillation of the fan head, and anything else electro-mechanical on the fan.

That's all there is to building this wondrous Rube Goldberg device (http://www.rube-goldberg.com). Let's give it a whirl, shall we?

Oscillating

Place this contraption on a conference table, such that it is most likely to provide a sweeping view of all participants—not to mention the occasional glimpse of that gorgeous oak tree outside the window. This generally works best with all participants arranged in an arc slightly shorter than 180 degrees, close enough together so that the camera doesn't try to focus on the wall behind when there's a wide enough gap between two people.

Hook the iOscillate up to a Mac (or PC if compatible), orienting the screen so that most of the participants can see the person on the other end of the line.

Start your engines!—or sufficiently quiet, steady, and well-geared motor. While the speed setting you choose should have no bearing on oscillation, I did find that my fan's High setting made for a smoother ride.

Fire up iChat or the equivalent and ring your remote peer.

You might suggest he pop some Dramamine or wear those oddly effective seasickness wristbands. These things do whiz along at quite a clip and the camera can sometimes get confused while trying to maintain focus.

Hacking the Hack

Picking your oscillating fan is key. While any old fan will do, if you're going to go out and buy one—really you shouldn't, not unless you're hot, that is—you might see if you can find one with an adjustable oscillation speed. Also, pay attention to the vibration-to-dollar ratio of some of the cheapest models.

A friend suggested actually leaving the fan blade and cage assembly intact, so as to actually cool the participants and make for a nice, wind-blown supermodel effect. If you mount the webcam behind the cage, know that the blades will confuse the iSight's autofocus to no end. If you mount it to the top, you'll find it vibrates considerably and there's a risk of catching some part of the USB or FireWire cable in the blade.

For a decidedly manual version of this hack, try placing your webcam on a lazy Susan: that revolving tray one finds in the center of large round dining tables. You'll still have to remember to aim the camera at whoever is talking, but it then becomes a group endeavor (and makes for a smoother ride than the usual jiggly reorientation). Place the laptop that's hosting the session next to the camera so that participants can see to whom they are speaking. Put the speakerphone on the tray too for greater sound quality on the listener's end. Or, if it's a lunch meeting, use it as intended: to pass the Kung Pao and rice.


Return to the Mac DevCenter

Copyright © 2009 O'Reilly Media, Inc.