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;
-my $mozembed_first;
-my $mozembed_last;
 
 GetOptions ("help|?" => \$help,
             "last" => \$last,
             "mozembed" => \$mozembed,
-            "mozembed-first" => \$mozembed_first,
-            "mozembed-last" => \$mozembed_last,
             "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;
 
-# 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);
@@ -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;
@@ -228,6 +225,11 @@ if (@files == 0) {
     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) {
@@ -242,11 +244,7 @@ if ($splash) {
     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@");
@@ -255,6 +253,11 @@ if ($splash) {
     $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) {
@@ -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;
+            $i = 0 if $go eq "FIRST";
+            $i = $#files if $go eq "LAST";
+            $i = $1 if $go =~ /^I_(\d+)$/;
             $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
-        # 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") {
@@ -347,30 +350,15 @@ sub show_slide
         $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);
 
@@ -393,14 +381,20 @@ sub show_slide
 # 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 $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);
@@ -408,35 +402,139 @@ sub run_mozembed
     #$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 ();
 
-    $moz->load_url ($ARGV[0]);
+    $moz->load_url ($url);
+
     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;
 
+__END__
+
 =head1 TUTORIAL
 
 =head2 START WRITING A TALK
@@ -580,9 +678,16 @@ terminal.
 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 HISTFILE=/tmp/history
+ export HISTFILE=$talkdir/history
  rm -f $HISTFILE
+ touch $HISTFILE
  
  add_history ()
  {
@@ -591,6 +696,11 @@ In C<functions>, I have:
  
  terminal ()
  {
+     # Make $HISTFILE unwritable so the shell won't update it
+     # when it exits.
+     chmod -w $HISTFILE
+     # Run gnome-terminal.
      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 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