Set Toolbar style so button labels are displayed again (thanks Alan Fitton).
[techtalk-pse.git] / techtalk-pse.pl
index ae8a9c9..843f6c7 100755 (executable)
@@ -143,14 +143,10 @@ 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,
             "splash!" => \$splash,
             "start=s" => \$start,
             "n=s" => \$start,
             "splash!" => \$splash,
             "start=s" => \$start,
@@ -170,11 +166,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 +183,11 @@ 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;
+
 # Get the files.
 my @files;
 my %files;
 # Get the files.
 my @files;
 my %files;
@@ -228,6 +225,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 +244,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 +253,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 +271,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 +298,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 +350,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 +381,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 +402,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
@@ -580,9 +678,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 ()
  {
@@ -591,6 +696,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 \
@@ -651,6 +761,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