Add --new flag which creates a skeleton talk.
[techtalk-pse.git] / techtalk-pse.pl
index 3955c7e..5f5b688 100755 (executable)
@@ -67,10 +67,38 @@ there is a discussion on L<WHAT MAKES A GOOD TALK>.
 
 =head1 RUNNING THE TOOL FROM THE COMMAND LINE
 
 
 =head1 RUNNING THE TOOL FROM THE COMMAND LINE
 
-A Tech Talk PSE talk is not a single file, but a directory full of
-files.  (If you want to start a new talk, see the L</TUTORIAL> section
-below).  To display or run the talk, change into the directory
-containing all those files and run the C<techtalk-pse> command:
+=head2 CREATING A NEW TALK
+
+Tech Talk PSE talks are just directories containing C<*.html> and
+C<*.sh> (shell script) files:
+
+ 0010-introduction.html
+ 0500-demonstration.sh
+ 9900-conclusion.html
+
+The filenames that Tech Talk PSE considers to be slides have to match
+the regular expression:
+
+ ^(\d+)(?:-.*)\.(html|sh)$
+
+(any other file or subdirectory is ignored).  Shell scripts I<must>
+be executable.
+
+You can create a new talk just by creating an empty directory and
+adding files as above, but you can also create a useful skeleton talk
+like this:
+
+ mkdir talk
+ cd talk
+ techtalk-pse --new
+
+The C<--new> flag will refuse to overwrite any existing files, so you
+should run it in an empty directory.
+
+=head2 DISPLAYING AN EXISTING TALK
+
+To display or run a talk, change into the directory containing all
+those files and run the C<techtalk-pse> command:
 
  cd /path/to/talk/; techtalk-pse
 
 
  cd /path/to/talk/; techtalk-pse
 
@@ -103,6 +131,16 @@ You cannot use this with the B<-n> / B<--start> option.
 
 =cut
 
 
 =cut
 
+my $new;
+
+=item B<--new>
+
+Create a new outline talk in an empty directory.
+
+This refuses to overwrite existing files.
+
+=cut
+
 my $start;
 
 =item B<-n SLIDE> | B<--start SLIDE>
 my $start;
 
 =item B<-n SLIDE> | B<--start SLIDE>
@@ -143,15 +181,12 @@ Display version number and exit.
 =cut
 
 my $mozembed;
 =cut
 
 my $mozembed;
-my $mozembed_first;
-my $mozembed_last;
 
 GetOptions ("help|?" => \$help,
             "last" => \$last,
             "mozembed" => \$mozembed,
 
 GetOptions ("help|?" => \$help,
             "last" => \$last,
             "mozembed" => \$mozembed,
-            "mozembed-first" => \$mozembed_first,
-            "mozembed-last" => \$mozembed_last,
             "n=s" => \$start,
             "n=s" => \$start,
+            "new" => \$new,
             "splash!" => \$splash,
             "start=s" => \$start,
             "verbose" => \$verbose,
             "splash!" => \$splash,
             "start=s" => \$start,
             "verbose" => \$verbose,
@@ -170,11 +205,7 @@ if ($version) {
 die "techtalk-pse: cannot use --start and --last options together\n"
     if defined $last && defined $start;
 
 die "techtalk-pse: cannot use --start and --last options together\n"
     if defined $last && defined $start;
 
-# Run with --mozembed: see below.
-run_mozembed () if $mozembed;
-
-# Normal run of the program.
-die "techtalk-pse: too many arguments\n" if @ARGV >= 2;
+die "techtalk-pse: too many arguments\n" if !$mozembed && @ARGV >= 2;
 
 # Get the true name of the program.
 $0 = abs_path ($0);
 
 # Get the true name of the program.
 $0 = abs_path ($0);
@@ -191,6 +222,149 @@ if (@ARGV > 0) {
     }
 }
 
     }
 }
 
+# Get the talk directory and set environment variable $talkdir
+# which is inherited by all the scripts.
+my $talkdir = getcwd;
+$ENV{talkdir} = $talkdir;
+
+# Create a new talk (--new flag).
+if ($new) {
+    my %files = (
+        "essay.txt" => {
+            mode => 0644,
+            desc => "essay and background notes",
+            c => 'Start by writing your thoughts in this file as an essay.
+
+You can then provide this as extra background reading material
+for your audience after the talk.'
+        },
+
+        "0010-introduction.html" => {
+            mode => 0644,
+            desc => "title slide",
+            c => '<link rel="stylesheet" href="style.css" type="text/css"/>
+<script src="code.js" type="text/javascript"></script>
+
+<div class="titlepage">
+<p>A Technical Talk</p>
+<author>by John Smith (jsmith@example.com)</author>
+</div>'
+        },
+
+        "0500-demonstration.html" => {
+            mode => 0644,
+            desc => "intro to demonstration slide",
+            c => '<link rel="stylesheet" href="style.css" type="text/css"/>
+<script src="code.js" type="text/javascript"></script>
+
+<h1>Demonstration</h1>
+
+<p>The next slide demonstrates a gnome terminal.</p>
+
+<p>Hit the UP arrow to access the preloaded history.</p>'
+        },
+
+        "0510-demonstration.sh" => {
+            mode => 0755,
+            desc => "shell demonstration slide",
+            c => '#!/bin/bash -
+source functions
+add_history ls -l
+add_history virt-df -a
+terminal --title="Demonstration terminal"'
+        },
+
+        "9900-conclusion.html" => {
+            mode => 0644,
+            desc => "last slide",
+            c => '<link rel="stylesheet" href="style.css" type="text/css"/>
+<script src="code.js" type="text/javascript"></script>
+
+<h1>Conclusions</h1>
+
+<p>The conclusion page</p>'
+        },
+
+        "functions" => {
+            mode => 0644,
+            desc => "shell script helper functions",
+            c => '# -*- shell-script -*-
+
+# Place any local environment variables and settings in "local".
+if [ -f local ]; then source local; fi
+
+export PS1=\'\\$ \'
+
+export HISTFILE=$talkdir/history
+
+rm -f $HISTFILE
+touch $HISTFILE
+
+add_history ()
+{
+    echo "$@" >> $HISTFILE
+}
+
+# Note: If you hand-configure gnome-terminal by adding a
+# new profile (eg. with larger fonts) then you can use that
+# profile here by replacing the --window flag with
+# --window-with-profile=ProfileName
+
+terminal ()
+{
+    chmod -w $HISTFILE
+    exec \\
+        gnome-terminal \\
+        --window \\
+        --geometry=+140+64 \\
+        --hide-menubar \\
+        --disable-factory \\
+        -e \'/bin/bash --norc\' \\
+        "$@"
+}
+'
+        },
+
+        "style.css" => {
+            mode => 0644,
+            desc => "HTML stylesheet",
+            c => 'body {
+    font-size: 28pt;
+    font-family: liberation, helvetica;
+}
+
+h1 {
+    font-size: 48px;
+    top: 8;
+    left: 0;
+    border-bottom: 2px solid #ccc;
+}
+
+div.titlepage {
+    margin-top: 100px;
+    text-align: center;
+}
+'
+        },
+    );
+
+    # Refuse to overwrite existing files.
+    foreach (sort keys %files) {
+        die "techtalk-pse: refusing to overwrite '$_'\n" if -f $_;
+    }
+
+    # Write the files.
+    foreach (sort keys %files) {
+        print "writing $_ ($files{$_}{desc}) ...\n";
+        open FILE, ">$_" or die "$_: open: $!";
+        print FILE $files{$_}{c} or die "$_: print: $!";
+        close FILE or die "$_: close: $!";
+        chmod $files{$_}{mode}, $_ or die "$_: chmod: $!";
+    }
+
+    exit 0;
+}
+
 # Get the files.
 my @files;
 my %files;
 # Get the files.
 my @files;
 my %files;
@@ -228,6 +402,11 @@ if (@files == 0) {
     warn "techtalk-pse: no files found, continuing anyway ...\n"
 }
 
     warn "techtalk-pse: no files found, continuing anyway ...\n"
 }
 
+# Run with --mozembed: see below.
+run_mozembed () if $mozembed;
+
+# Else, normal run of the program ...
+
 # Work out what slide we're starting on.
 my $current;
 if (defined $current) {
 # Work out what slide we're starting on.
 my $current;
 if (defined $current) {
@@ -242,11 +421,7 @@ if ($splash) {
     my $w = Gtk2::AboutDialog->new;
     $w->set_authors ("Richard W.M. Jones");
     $w->set_comments (
     my $w = Gtk2::AboutDialog->new;
     $w->set_authors ("Richard W.M. Jones");
     $w->set_comments (
-        "Superior technical demonstration software\n".
-        "\n".
-        "Keys\n".
-        "↑ — Go back one slide\n".
-        "↓ — Go forward one slide\n"
+        "Superior technical demonstration software\n"
         );
     $w->set_program_name ("Tech Talk Platinum Supreme Edition (PSE)");
     $w->set_version ("@VERSION@");
         );
     $w->set_program_name ("Tech Talk Platinum Supreme Edition (PSE)");
     $w->set_version ("@VERSION@");
@@ -255,6 +430,11 @@ if ($splash) {
     $w->run;
     print STDERR "calling \$w->destroy on about dialog\n" if $verbose;
     $w->destroy;
     $w->run;
     print STDERR "calling \$w->destroy on about dialog\n" if $verbose;
     $w->destroy;
+
+    # The dialog doesn't really get destroyed here.  We have
+    # to add this hack to really destroy it.
+    Glib::Idle->add (sub { Gtk2->main_quit; return FALSE; });
+    Gtk2->main;
 }
 
 MAIN: while (1) {
 }
 
 MAIN: while (1) {
@@ -268,6 +448,9 @@ MAIN: while (1) {
             print STDERR "i = $i\n" if $verbose;
             $i-- if $go eq "PREV" && $i > 0;
             $i++ if $go eq "NEXT" && $i+1 < @files;
             print STDERR "i = $i\n" if $verbose;
             $i-- if $go eq "PREV" && $i > 0;
             $i++ if $go eq "NEXT" && $i+1 < @files;
+            $i = 0 if $go eq "FIRST";
+            $i = $#files if $go eq "LAST";
+            $i = $1 if $go =~ /^I_(\d+)$/;
             $current = $files[$i];
         }
     } else {
             $current = $files[$i];
         }
     } else {
@@ -292,29 +475,26 @@ sub show_slide
     # Display an HTML page.
     if ($slide->{ext} eq "html") {
         # MozEmbed is incredibly crashy, so we run ourself as a
     # Display an HTML page.
     if ($slide->{ext} eq "html") {
         # MozEmbed is incredibly crashy, so we run ourself as a
-        # subprocess, so when it segfaults we don't care.
-        my @cmd = ($0, "--mozembed");
-        push @cmd, "--mozembed-first" if exists $slide->{first};
-        push @cmd, "--mozembed-last" if exists $slide->{last};
-        my $cwd = getcwd;
-        my $url = "file://" . $cwd . "/" . $slide->{name};
-        push @cmd, $url;
-        system (@cmd);
-        die "failed to execute subcommand: ", join(" ", @cmd), ": $!\n"
-            if $? == -1;
-        if ($? & 127) {
-            # Subcommand probably segfaulted, just continue to next slide.
-            return "NEXT";
-        } else {
-            my $r = $? >> 8;
-            if ($r == 0) {
-                return "NEXT";
-            } elsif ($r == 1) {
-                return "PREV";
-            } elsif ($r == 2) {
-                return "QUIT";
+        # subprocess, so when it segfaults we don't care.  If all goes
+        # well and it doesn't crash, it should print a line 'RESULT FOO'
+        # where 'FOO' is the instruction (eg. 'NEXT', 'PREV', 'QUIT' etc).
+        my @cmd = ($0, "--mozembed", $talkdir, $slide->{name});
+       print STDERR "running subcommand: ", join (" ", @cmd), "\n"
+           if $verbose;
+        open CMD, "-|", @cmd
+            or die "failed to execute subcommand: ", join(" ", @cmd), ": $!\n";
+        my $r;
+        while (<CMD>) {
+            if (/^RESULT ([A-Z]+.*)/) {
+                $r = $1;
+                print STDERR "subcommand result: $r\n" if $verbose;
+                last;
             }
         }
             }
         }
+        # No RESULT line?  Subcommand probably segfaulted, just
+        # continue to next slide.
+        $r ||= "NEXT";
+        return $r;
     }
     # Run a shell command.
     elsif ($slide->{ext} eq "sh") {
     }
     # Run a shell command.
     elsif ($slide->{ext} eq "sh") {
@@ -347,30 +527,15 @@ sub show_slide
         $w->move (0, 0);
         $w->set_decorated (0);
 
         $w->move (0, 0);
         $w->set_decorated (0);
 
-        my $bbox = Gtk2::HButtonBox->new ();
-        $bbox->set_layout ('start');
-
-        my $bnext = Gtk2::Button->new ("Next slide");
-        $bnext->signal_connect (clicked => sub { $r = "NEXT"; $w->destroy });
-        $bnext->set_sensitive (!(exists $slide->{last}));
-        $bbox->add ($bnext);
-
-        my $bback = Gtk2::Button->new ("Back");
-        $bback->signal_connect (clicked => sub { $r = "PREV"; $w->destroy });
-        $bback->set_sensitive (!(exists $slide->{first}));
-        $bbox->add ($bback);
-
-        my $brestart = Gtk2::Button->new ("Kill & restart");
-        $brestart->signal_connect (clicked => sub {
-            kill_process ();
-            run_process ();
-        });
-        $bbox->add ($brestart);
-
-        my $bquit = Gtk2::Button->new ("Quit");
-        $bquit->signal_connect (clicked => sub { $r = "QUIT"; $w->destroy });
-        $bbox->add ($bquit);
-        $bbox->set_child_secondary ($bquit, 1);
+        my $bbox =
+            make_button_bar ((exists $slide->{first}),
+                             (exists $slide->{last}),
+                             sub { $r = $_[0]; $w->destroy },
+                             restart => sub {
+                                 kill_process ();
+                                 run_process ();
+                             },
+            );
 
         $w->add ($bbox);
 
 
         $w->add ($bbox);
 
@@ -393,14 +558,20 @@ sub show_slide
 # killing the whole program.
 sub run_mozembed
 {
 # killing the whole program.
 sub run_mozembed
 {
-    my $r = 0;
-
     my $w = Gtk2::Window->new ();
     my $vbox = Gtk2::VBox->new ();
     my $moz = Gtk2::MozEmbed->new ();
 
     my $w = Gtk2::Window->new ();
     my $vbox = Gtk2::VBox->new ();
     my $moz = Gtk2::MozEmbed->new ();
 
-    my $bbox = Gtk2::HButtonBox->new ();
-    $bbox->set_layout ('start');
+    reread_directory ();
+
+    my $name = $ARGV[1];
+    $current = $files{$name};
+    my $url = "file://$talkdir/$name";
+
+    my $bbox =
+        make_button_bar ($current->{first}, $current->{last},
+                         sub { print "RESULT ", $_[0], "\n"; $w->destroy }
+        );
 
     $vbox->pack_start ($bbox, 0, 0, 0);
     $vbox->add ($moz);
 
     $vbox->pack_start ($bbox, 0, 0, 0);
     $vbox->add ($moz);
@@ -408,35 +579,139 @@ sub run_mozembed
     #$w->set_default_size (640, 480);
     $w->add ($vbox);
 
     #$w->set_default_size (640, 480);
     $w->add ($vbox);
 
-    my $bnext = Gtk2::Button->new ("Next slide");
-    $bnext->signal_connect (clicked => sub { $r = 0; $w->destroy });
-    $bnext->set_sensitive (!$mozembed_last);
-    $bbox->add ($bnext);
-
-    my $bback = Gtk2::Button->new ("Back");
-    $bback->signal_connect (clicked => sub { $r = 1; $w->destroy });
-    $bback->set_sensitive (!$mozembed_first);
-    $bbox->add ($bback);
-
-    my $bquit = Gtk2::Button->new ("Quit");
-    $bquit->signal_connect (clicked => sub { $r = 2; $w->destroy });
-    $bbox->add ($bquit);
-    $bbox->set_child_secondary ($bquit, 1);
-
     $w->signal_connect (destroy => sub {
         Gtk2->main_quit;
         return FALSE;
     });
     $w->show_all ();
 
     $w->signal_connect (destroy => sub {
         Gtk2->main_quit;
         return FALSE;
     });
     $w->show_all ();
 
-    $moz->load_url ($ARGV[0]);
+    $moz->load_url ($url);
+
     Gtk2->main;
 
     Gtk2->main;
 
-    exit $r;
+    exit 0;
+}
+
+# Make the standard button bar across the top of the page.
+sub make_button_bar
+{
+    my $first = shift;
+    my $last = shift;
+    my $cb = shift;
+    my %params = @_;
+
+    my $bbox = Gtk2::Toolbar->new ();
+    $bbox->set_style ("GTK_TOOLBAR_TEXT");
+
+    my $i = 0;
+
+    my $bnext = Gtk2::ToolButton->new (undef, "Next slide");
+    $bnext->signal_connect (clicked => sub { &$cb ("NEXT") });
+    $bnext->set_sensitive (!$last);
+    $bbox->insert ($bnext, $i++);
+
+    my $bback = Gtk2::ToolButton->new (undef, "Back");
+    $bback->signal_connect (clicked => sub { &$cb ("PREV") });
+    $bback->set_sensitive (!$first);
+    $bbox->insert ($bback, $i++);
+
+    if (exists $params{restart}) {
+        $bbox->insert (Gtk2::SeparatorToolItem->new (), $i++);
+
+        my $brestart = Gtk2::ToolButton->new (undef, "Kill & restart");
+        $brestart->signal_connect (clicked => $params{restart});
+        $bbox->insert ($brestart, $i++);
+    }
+
+    my $sep = Gtk2::SeparatorToolItem->new ();
+    $sep->set_expand (TRUE);
+    $sep->set_draw (FALSE);
+    $bbox->insert ($sep, $i++);
+
+    my $optsmenu = Gtk2::Menu->new ();
+
+    my $bfirst = Gtk2::MenuItem->new ("First slide");
+    $bfirst->signal_connect (activate => sub { \&$cb ("FIRST") });
+    $bfirst->show ();
+    $optsmenu->append ($bfirst);
+
+    my $blast = Gtk2::MenuItem->new ("Last slide");
+    $blast->signal_connect (activate => sub { \&$cb ("LAST") });
+    $blast->show ();
+    $optsmenu->append ($blast);
+
+    my $slidesmenu = Gtk2::Menu->new ();
+    foreach (@files) {
+        my $item = Gtk2::MenuItem->new ($_->{name});
+        my $index = $_->{i};
+        $item->signal_connect (activate => sub { \&$cb ("I_$index") });
+        $item->set_sensitive ($current->{i} != $index);
+        $item->show ();
+        $slidesmenu->append ($item);
+    }
+
+    my $bslides = Gtk2::MenuItem->new ("Slides");
+    $bslides->set_submenu ($slidesmenu);
+    $bslides->show ();
+    $optsmenu->append ($bslides);
+
+    my $sep2 = Gtk2::SeparatorMenuItem->new ();
+    $sep2->show ();
+    $optsmenu->append ($sep2);
+
+    my $bscreenshot = Gtk2::MenuItem->new ("Take a screenshot");
+    $bscreenshot->signal_connect (activate => sub { screenshot () });
+    $bscreenshot->show ();
+    $optsmenu->append ($bscreenshot);
+
+    my $sep3 = Gtk2::SeparatorMenuItem->new ();
+    $sep3->show ();
+    $optsmenu->append ($sep3);
+
+    my $bquit = Gtk2::MenuItem->new ("Quit");
+    $bquit->signal_connect (activate => sub { \&$cb ("QUIT") });
+    $bquit->show ();
+    $optsmenu->append ($bquit);
+
+    my $boptions = Gtk2::MenuToolButton->new (undef, "Options");
+    #$boptions->signal_connect (clicked =>
+    #  sub { $optsmenu->popup (undef, undef, undef, undef, ?, ?) } );
+    $bbox->insert ($boptions, $i++);
+    $boptions->set_menu ($optsmenu);
+
+    return $bbox;
+}
+
+# Try running the external "gnome-screenshot" program, if it's
+# available, else take a screenshot using gdk routines.
+sub screenshot
+{
+    system ("gnome-screenshot");
+
+    if ($? == -1) {
+        # We are going to save the entire screen.
+        my $root = Gtk2::Gdk->get_default_root_window ();
+        my ($width, $height) = $root->get_size;
+
+        # Create blank pixbuf to hold the image.
+        my $gdkpixbuf = Gtk2::Gdk::Pixbuf->new ('rgb',
+                                                0, 8, $width, $height);
+
+        $gdkpixbuf->get_from_drawable ($root, $root->get_colormap (),
+                                       0, 0, 0, 0, $width, $height);
+
+        my $i = 0;
+        $i++ while -f "screenshot$i.png";
+        $gdkpixbuf->save ("screenshot$i.png", 'png');
+    }
+
+    return FALSE;
 }
 
 1;
 
 }
 
 1;
 
+__END__
+
 =head1 TUTORIAL
 
 =head2 START WRITING A TALK
 =head1 TUTORIAL
 
 =head2 START WRITING A TALK
@@ -445,18 +720,16 @@ sub run_mozembed
 L</WHAT MAKES A GOOD TALK> below].
 
 To start your talk, all you have to do is to make a new directory
 L</WHAT MAKES A GOOD TALK> below].
 
 To start your talk, all you have to do is to make a new directory
-somewhere:
+somewhere and run Tech Talk PSE with the C<--new> flag to create an
+outline:
 
  mkdir talk
  cd talk
 
  mkdir talk
  cd talk
+ techtalk-pse --new
 
 A tech talk consists of HTML files ("slides") and shell scripts.  The
 filenames must start with a number, followed optionally by a
 
 A tech talk consists of HTML files ("slides") and shell scripts.  The
 filenames must start with a number, followed optionally by a
-description, followed by the extension (C<.html> or C<.sh>).  So to
-start our talk with two slides:
-
- echo "This is the introduction" > 0010-introduction.html
- echo "This is the second slide" > 0020-second.html
+description, followed by the extension (C<.html> or C<.sh>).
 
 To run it, run the command from within the talk directory:
 
 
 To run it, run the command from within the talk directory:
 
@@ -478,6 +751,8 @@ That just ensures that I can put common styling instructions for all
 my slides in a single file (C<style.css>), and I have one place where
 I can add all Javascript, if I need to use any (C<code.js>).
 
 my slides in a single file (C<style.css>), and I have one place where
 I can add all Javascript, if I need to use any (C<code.js>).
 
+=head3 BACKGROUNDS, FONTS AND LOGOS
+
 To add a common background and font size to all slides, put this in
 C<style.css>:
 
 To add a common background and font size to all slides, put this in
 C<style.css>:
 
@@ -486,11 +761,43 @@ C<style.css>:
      background: url(background-image.jpg) no-repeat;
  }
 
      background: url(background-image.jpg) no-repeat;
  }
 
+To add a logo in one corner:
+
+ body {
+     background: url(logo.jpg) top right no-repeat;
+ }
+
+=head3 SCALING AND CENTERING
+
 Scaling slide text and images so that they appear at the same
 proportionate size for any screen resolution can be done using
 Javascript.  (See
 L<https://developer.mozilla.org/En/DOM/window.innerHeight>).
 
 Scaling slide text and images so that they appear at the same
 proportionate size for any screen resolution can be done using
 Javascript.  (See
 L<https://developer.mozilla.org/En/DOM/window.innerHeight>).
 
+If you want to center text horizontally, use CSS, eg:
+
+ p.center {
+     text-align: center;
+ }
+
+To center text vertically, CSS3 is supposed to offer a solution some
+time, but while you're waiting for that try
+L<http://www.w3.org/Style/Examples/007/center#vertical>.
+
+=head3 PREVIEWING HTML
+
+I find it helpful to have Firefox open to display the HTML files and
+styles as I edit them.  Just start firefox in the talk directory:
+
+ firefox file://$(pwd) &
+
+When you edit an HTML file, click the Firefox reload button to
+immediately see your changes.
+
+Tech Talk PSE uses Mozilla embedding to display HTML, which uses the
+same Mozilla engine as Firefox, so what you should see in Firefox
+should be identical to what Tech Talk PSE displays.
+
 =head2 CREATING FIGURES
 
 Use your favorite tool to draw the figure, convert it to an image (in
 =head2 CREATING FIGURES
 
 Use your favorite tool to draw the figure, convert it to an image (in
@@ -546,9 +853,16 @@ terminal.
 In C<functions>, I have:
 
  # -*- shell-script -*-
 In C<functions>, I have:
 
  # -*- shell-script -*-
+ # Place any local environment variables required in 'local'.
+ if [ -f local ]; then source local; fi
  export PS1="$ "
  export PS1="$ "
- export HISTFILE=/tmp/history
+ export HISTFILE=$talkdir/history
  rm -f $HISTFILE
  rm -f $HISTFILE
+ touch $HISTFILE
  
  add_history ()
  {
  
  add_history ()
  {
@@ -557,6 +871,11 @@ In C<functions>, I have:
  
  terminal ()
  {
  
  terminal ()
  {
+     # Make $HISTFILE unwritable so the shell won't update it
+     # when it exits.
+     chmod -w $HISTFILE
+     # Run gnome-terminal.
      exec \
          gnome-terminal \
          --window \
      exec \
          gnome-terminal \
          --window \
@@ -617,6 +936,13 @@ When running shell scripts, the current directory is also set to be
 the directory containing the talk files, so the same rules about using
 relative paths apply there too.
 
 the directory containing the talk files, so the same rules about using
 relative paths apply there too.
 
+The environment variable C<$talkdir> is exported to scripts and it
+contains the absolute path of the directory containing the talk files.
+When a script is run, the current directory is the same as
+C<$talkdir>, but if your script changes directory (eg. into a
+subdirectory containing supporting files) then it can be useful to use
+C<$talkdir> to refer back to the original directory.
+
 =head1 WHAT MAKES A GOOD TALK
 
 I like what Edward Tufte writes, for example his evisceration of
 =head1 WHAT MAKES A GOOD TALK
 
 I like what Edward Tufte writes, for example his evisceration of