Set Toolbar style so button labels are displayed again (thanks Alan Fitton).
[techtalk-pse.git] / techtalk-pse.pl
1 #!/usr/bin/perl -w
2 # -*- perl -*-
3 # @configure_input@
4 #
5 # Tech Talk PSE
6 # Copyright (C) 2010 Red Hat Inc.
7 #
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21
22 use warnings;
23 use strict;
24 use utf8;
25
26 use POSIX qw(setsid);
27 use Pod::Usage;
28 use Getopt::Long;
29 use Cwd qw(getcwd abs_path);
30 use Glib qw(TRUE FALSE);
31 use Gtk2 -init;
32 use Gtk2::MozEmbed;
33
34 =encoding utf8
35
36 =head1 NAME
37
38 techtalk-pse - superior technical demonstration software
39
40 =head1 SYNOPSIS
41
42  cd /path/to/talk/; techtalk-pse
43
44  techtalk-pse /path/to/talk/
45
46 =head1 DESCRIPTION
47
48 Tech Talk "Platinum Supreme Edition" (PSE) is Linux Presentation
49 Software designed by technical people to give technical software
50 demonstrations to other technical people.  It is designed to be simple
51 to use (for people who know how to use an editor and the command line)
52 and powerful, so that you can create informative, technically accurate
53 and entertaining talks and demonstrations.
54
55 Tech Talk PSE is good at opening editors at the right place, opening
56 shell prompts with preloaded history, compiling and running things
57 during the demonstration, displaying text, photos, figures and video.
58
59 Tech Talk PSE is I<bad> at slide effects, chart junk and bullet
60 points.
61
62 This manual page covers all the documentation you will need to use
63 Tech Talk PSE.  The next section covers running the tool from the
64 command line.  After that there is a L</TUTORIAL> section to get you
65 started.  Then there is a detailed L</REFERENCE> section.  Finally
66 there is a discussion on L<WHAT MAKES A GOOD TALK>.
67
68 =head1 RUNNING THE TOOL FROM THE COMMAND LINE
69
70 A Tech Talk PSE talk is not a single file, but a directory full of
71 files.  (If you want to start a new talk, see the L</TUTORIAL> section
72 below).  To display or run the talk, change into the directory
73 containing all those files and run the C<techtalk-pse> command:
74
75  cd /path/to/talk/; techtalk-pse
76
77 You can also run C<techtalk-pse> without changing directory, instead
78 specifying the path to the talk:
79
80  techtalk-pse /path/to/talk/
81
82 =head2 OPTIONS
83
84 =over 4
85
86 =cut
87
88 my $help;
89
90 =item B<--help>
91
92 Display brief help and exit.
93
94 =cut
95
96 my $last;
97
98 =item B<--last>
99
100 Start at the last slide.
101
102 You cannot use this with the B<-n> / B<--start> option.
103
104 =cut
105
106 my $start;
107
108 =item B<-n SLIDE> | B<--start SLIDE>
109
110 Start at the named slide.  I<SLIDE> is the shortest unique prefix of
111 the slide name, so to start at a slide named
112 I<00010-introduction.html>, you could use I<-n 00010> or I<-n 00010-intro>,
113 or give the full filename I<-n 00010-introduction.html>.
114
115 The default is to start at the first slide in the talk.
116
117 =cut
118
119 my $splash = 1;
120
121 =item B<--no-splash>
122
123 Don't display the initial "splash" screen which advertises Tech Talk
124 PSE to your audience.  Just go straight into the talk.
125
126 =cut
127
128 my $verbose;
129
130 =item B<--verbose>
131
132 Display verbose messages, useful for debugging or tracing
133 what the program is doing.
134
135 =cut
136
137 my $version;
138
139 =item B<--version>
140
141 Display version number and exit.
142
143 =cut
144
145 my $mozembed;
146
147 GetOptions ("help|?" => \$help,
148             "last" => \$last,
149             "mozembed" => \$mozembed,
150             "n=s" => \$start,
151             "splash!" => \$splash,
152             "start=s" => \$start,
153             "verbose" => \$verbose,
154             "version" => \$version,
155     ) or pod2usage (2);
156
157 =back
158
159 =cut
160
161 pod2usage (1) if $help;
162 if ($version) {
163     print "@PACKAGE@ @VERSION@\n";
164     exit
165 }
166 die "techtalk-pse: cannot use --start and --last options together\n"
167     if defined $last && defined $start;
168
169 die "techtalk-pse: too many arguments\n" if !$mozembed && @ARGV >= 2;
170
171 # Get the true name of the program.
172 $0 = abs_path ($0);
173
174 # Locate the talk.
175 if (@ARGV > 0) {
176     my $d = $ARGV[0];
177     if (-d $d) {
178         chdir $d or die "techtalk-pse: chdir: $d: $!";
179     } else {
180         # XXX In future allow people to specify an archive and unpack
181         # it for them.
182         die "techtalk-pse: argument is not a directory"
183     }
184 }
185
186 # Get the talk directory and set environment variable $talkdir
187 # which is inherited by all the scripts.
188 my $talkdir = getcwd;
189 $ENV{talkdir} = $talkdir;
190
191 # Get the files.
192 my @files;
193 my %files;
194 sub reread_directory
195 {
196     @files = ();
197
198     my $i = 0;
199     foreach (glob ("*")) {
200         if (/^(\d+)(?:-.*)\.(html|sh)$/) {
201             print STDERR "reading $_\n" if $verbose;
202
203             my $seq = $1;
204             my $ext = $2;
205             warn "techtalk-pse: $_: command file is not executable (+x)\n"
206                 if $ext eq "sh" && ! -x $_;
207
208             my $h = { name => $_, seq => $1, ext => $2, i => $i };
209             push @files, $h;
210             $files{$_} = $h;
211             $i++;
212         } else {
213             print STDERR "ignoring $_\n" if $verbose;
214         }
215     }
216
217     if (@files > 0) {
218         $files[0]->{first} = 1;
219         $files[$#files]->{last} = 1;
220     }
221 }
222 reread_directory ();
223 print STDERR "read ", 0+@files, " files\n" if $verbose;
224 if (@files == 0) {
225     warn "techtalk-pse: no files found, continuing anyway ...\n"
226 }
227
228 # Run with --mozembed: see below.
229 run_mozembed () if $mozembed;
230
231 # Else, normal run of the program ...
232
233 # Work out what slide we're starting on.
234 my $current;
235 if (defined $current) {
236     die "start slide not implemented yet XXX"
237 }
238 elsif (@files) {
239     $current = $files[0];
240 }
241 # else $current is undefined
242
243 if ($splash) {
244     my $w = Gtk2::AboutDialog->new;
245     $w->set_authors ("Richard W.M. Jones");
246     $w->set_comments (
247         "Superior technical demonstration software\n"
248         );
249     $w->set_program_name ("Tech Talk Platinum Supreme Edition (PSE)");
250     $w->set_version ("@VERSION@");
251     $w->set_website ("http://people.redhat.com/~rjones");
252     $w->set_license ("GNU General Public License v2 or above");
253     $w->run;
254     print STDERR "calling \$w->destroy on about dialog\n" if $verbose;
255     $w->destroy;
256
257     # The dialog doesn't really get destroyed here.  We have
258     # to add this hack to really destroy it.
259     Glib::Idle->add (sub { Gtk2->main_quit; return FALSE; });
260     Gtk2->main;
261 }
262
263 MAIN: while (1) {
264     if (defined $current) {
265         my $go = show_slide ($current);
266         if (defined $go) {
267             print STDERR "go = $go\n" if $verbose;
268             last MAIN if $go eq "QUIT";
269
270             my $i = $current->{i};
271             print STDERR "i = $i\n" if $verbose;
272             $i-- if $go eq "PREV" && $i > 0;
273             $i++ if $go eq "NEXT" && $i+1 < @files;
274             $i = 0 if $go eq "FIRST";
275             $i = $#files if $go eq "LAST";
276             $i = $1 if $go =~ /^I_(\d+)$/;
277             $current = $files[$i];
278         }
279     } else {
280         print "No slides found.  Press any key to reload directory ...\n";
281         $_ = <STDIN>;
282     }
283
284     # Reread directory between slides.
285     reread_directory ();
286
287     if (defined $current && !exists $files{$current->{name}}) {
288         # Current slide was deleted.
289         undef $current;
290         $current = $files[0] if @files;
291     }
292 }
293
294 sub show_slide
295 {
296     my $slide = shift;
297
298     # Display an HTML page.
299     if ($slide->{ext} eq "html") {
300         # MozEmbed is incredibly crashy, so we run ourself as a
301         # subprocess, so when it segfaults we don't care.  If all goes
302         # well and it doesn't crash, it should print a line 'RESULT FOO'
303         # where 'FOO' is the instruction (eg. 'NEXT', 'PREV', 'QUIT' etc).
304         my @cmd = ($0, "--mozembed", $talkdir, $slide->{name});
305         print STDERR "running subcommand: ", join (" ", @cmd), "\n"
306             if $verbose;
307         open CMD, "-|", @cmd
308             or die "failed to execute subcommand: ", join(" ", @cmd), ": $!\n";
309         my $r;
310         while (<CMD>) {
311             if (/^RESULT ([A-Z]+.*)/) {
312                 $r = $1;
313                 print STDERR "subcommand result: $r\n" if $verbose;
314                 last;
315             }
316         }
317         # No RESULT line?  Subcommand probably segfaulted, just
318         # continue to next slide.
319         $r ||= "NEXT";
320         return $r;
321     }
322     # Run a shell command.
323     elsif ($slide->{ext} eq "sh") {
324         my $pid;
325         # http://docstore.mik.ua/orelly/perl/cookbook/ch10_17.htm
326         local *run_process = sub {
327             $pid = fork ();
328             die "fork: $!" unless defined $pid;
329             unless ($pid) {
330                 # Child.
331                 POSIX::setsid ();
332                 exec ("./".$slide->{name});
333                 die "failed to execute command: ", $slide->{name}, ": $!";
334             }
335             # Parent returns.
336         };
337         local *kill_process = sub {
338             print STDERR "sending TERM signal to process group $pid\n"
339                 if $verbose;
340             kill "TERM", -$pid;
341         };
342         run_process ();
343
344         my $r = "NEXT";
345
346         my $w = Gtk2::Window->new ();
347
348         my $s = $w->get_screen;
349         $w->set_default_size ($s->get_width, -1);
350         $w->move (0, 0);
351         $w->set_decorated (0);
352
353         my $bbox =
354             make_button_bar ((exists $slide->{first}),
355                              (exists $slide->{last}),
356                              sub { $r = $_[0]; $w->destroy },
357                              restart => sub {
358                                  kill_process ();
359                                  run_process ();
360                              },
361             );
362
363         $w->add ($bbox);
364
365         $w->signal_connect (destroy => sub {
366             Gtk2->main_quit;
367             return FALSE;
368         });
369         $w->show_all ();
370
371         Gtk2->main;
372
373         kill_process ();
374         print STDERR "returning r=$r\n" if $verbose;
375         return $r;
376     }
377 }
378
379 # If invoked with the --mozembed parameter then we just display a
380 # single page.  This is just to prevent crashes in MozEmbed from
381 # killing the whole program.
382 sub run_mozembed
383 {
384     my $w = Gtk2::Window->new ();
385     my $vbox = Gtk2::VBox->new ();
386     my $moz = Gtk2::MozEmbed->new ();
387
388     reread_directory ();
389
390     my $name = $ARGV[1];
391     $current = $files{$name};
392     my $url = "file://$talkdir/$name";
393
394     my $bbox =
395         make_button_bar ($current->{first}, $current->{last},
396                          sub { print "RESULT ", $_[0], "\n"; $w->destroy }
397         );
398
399     $vbox->pack_start ($bbox, 0, 0, 0);
400     $vbox->add ($moz);
401     $w->fullscreen ();
402     #$w->set_default_size (640, 480);
403     $w->add ($vbox);
404
405     $w->signal_connect (destroy => sub {
406         Gtk2->main_quit;
407         return FALSE;
408     });
409     $w->show_all ();
410
411     $moz->load_url ($url);
412
413     Gtk2->main;
414
415     exit 0;
416 }
417
418 # Make the standard button bar across the top of the page.
419 sub make_button_bar
420 {
421     my $first = shift;
422     my $last = shift;
423     my $cb = shift;
424     my %params = @_;
425
426     my $bbox = Gtk2::Toolbar->new ();
427     $bbox->set_style ("GTK_TOOLBAR_TEXT");
428
429     my $i = 0;
430
431     my $bnext = Gtk2::ToolButton->new (undef, "Next slide");
432     $bnext->signal_connect (clicked => sub { &$cb ("NEXT") });
433     $bnext->set_sensitive (!$last);
434     $bbox->insert ($bnext, $i++);
435
436     my $bback = Gtk2::ToolButton->new (undef, "Back");
437     $bback->signal_connect (clicked => sub { &$cb ("PREV") });
438     $bback->set_sensitive (!$first);
439     $bbox->insert ($bback, $i++);
440
441     if (exists $params{restart}) {
442         $bbox->insert (Gtk2::SeparatorToolItem->new (), $i++);
443
444         my $brestart = Gtk2::ToolButton->new (undef, "Kill & restart");
445         $brestart->signal_connect (clicked => $params{restart});
446         $bbox->insert ($brestart, $i++);
447     }
448
449     my $sep = Gtk2::SeparatorToolItem->new ();
450     $sep->set_expand (TRUE);
451     $sep->set_draw (FALSE);
452     $bbox->insert ($sep, $i++);
453
454     my $optsmenu = Gtk2::Menu->new ();
455
456     my $bfirst = Gtk2::MenuItem->new ("First slide");
457     $bfirst->signal_connect (activate => sub { \&$cb ("FIRST") });
458     $bfirst->show ();
459     $optsmenu->append ($bfirst);
460
461     my $blast = Gtk2::MenuItem->new ("Last slide");
462     $blast->signal_connect (activate => sub { \&$cb ("LAST") });
463     $blast->show ();
464     $optsmenu->append ($blast);
465
466     my $slidesmenu = Gtk2::Menu->new ();
467     foreach (@files) {
468         my $item = Gtk2::MenuItem->new ($_->{name});
469         my $index = $_->{i};
470         $item->signal_connect (activate => sub { \&$cb ("I_$index") });
471         $item->set_sensitive ($current->{i} != $index);
472         $item->show ();
473         $slidesmenu->append ($item);
474     }
475
476     my $bslides = Gtk2::MenuItem->new ("Slides");
477     $bslides->set_submenu ($slidesmenu);
478     $bslides->show ();
479     $optsmenu->append ($bslides);
480
481     my $sep2 = Gtk2::SeparatorMenuItem->new ();
482     $sep2->show ();
483     $optsmenu->append ($sep2);
484
485     my $bscreenshot = Gtk2::MenuItem->new ("Take a screenshot");
486     $bscreenshot->signal_connect (activate => sub { screenshot () });
487     $bscreenshot->show ();
488     $optsmenu->append ($bscreenshot);
489
490     my $sep3 = Gtk2::SeparatorMenuItem->new ();
491     $sep3->show ();
492     $optsmenu->append ($sep3);
493
494     my $bquit = Gtk2::MenuItem->new ("Quit");
495     $bquit->signal_connect (activate => sub { \&$cb ("QUIT") });
496     $bquit->show ();
497     $optsmenu->append ($bquit);
498
499     my $boptions = Gtk2::MenuToolButton->new (undef, "Options");
500     #$boptions->signal_connect (clicked =>
501     #  sub { $optsmenu->popup (undef, undef, undef, undef, ?, ?) } );
502     $bbox->insert ($boptions, $i++);
503     $boptions->set_menu ($optsmenu);
504
505     return $bbox;
506 }
507
508 # Try running the external "gnome-screenshot" program, if it's
509 # available, else take a screenshot using gdk routines.
510 sub screenshot
511 {
512     system ("gnome-screenshot");
513
514     if ($? == -1) {
515         # We are going to save the entire screen.
516         my $root = Gtk2::Gdk->get_default_root_window ();
517         my ($width, $height) = $root->get_size;
518
519         # Create blank pixbuf to hold the image.
520         my $gdkpixbuf = Gtk2::Gdk::Pixbuf->new ('rgb',
521                                                 0, 8, $width, $height);
522
523         $gdkpixbuf->get_from_drawable ($root, $root->get_colormap (),
524                                        0, 0, 0, 0, $width, $height);
525
526         my $i = 0;
527         $i++ while -f "screenshot$i.png";
528         $gdkpixbuf->save ("screenshot$i.png", 'png');
529     }
530
531     return FALSE;
532 }
533
534 1;
535
536 __END__
537
538 =head1 TUTORIAL
539
540 =head2 START WRITING A TALK
541
542 [Before you start writing your real talk, I urge you to read
543 L</WHAT MAKES A GOOD TALK> below].
544
545 To start your talk, all you have to do is to make a new directory
546 somewhere:
547
548  mkdir talk
549  cd talk
550
551 A tech talk consists of HTML files ("slides") and shell scripts.  The
552 filenames must start with a number, followed optionally by a
553 description, followed by the extension (C<.html> or C<.sh>).  So to
554 start our talk with two slides:
555
556  echo "This is the introduction" > 0010-introduction.html
557  echo "This is the second slide" > 0020-second.html
558
559 To run it, run the command from within the talk directory:
560
561  techtalk-pse
562
563 Any other file in the directory is ignored, so if you want to add
564 Makefiles, version control files etc, just go ahead.
565
566 =head2 TIPS FOR WRITING HTML
567
568 You may have your own techniques and tools for writing HTML, so
569 this section is just to share my ideas.  I start every
570 HTML file with a standard stylesheet and Javascript header:
571
572  <link rel="stylesheet" href="style.css" type="text/css"/>
573  <script src="code.js" type="text/javascript"></script>
574
575 That just ensures that I can put common styling instructions for all
576 my slides in a single file (C<style.css>), and I have one place where
577 I can add all Javascript, if I need to use any (C<code.js>).
578
579 =head3 BACKGROUNDS, FONTS AND LOGOS
580
581 To add a common background and font size to all slides, put this in
582 C<style.css>:
583
584  body {
585      font-size: 24pt;
586      background: url(background-image.jpg) no-repeat;
587  }
588
589 To add a logo in one corner:
590
591  body {
592      background: url(logo.jpg) top right no-repeat;
593  }
594
595 =head3 SCALING AND CENTERING
596
597 Scaling slide text and images so that they appear at the same
598 proportionate size for any screen resolution can be done using
599 Javascript.  (See
600 L<https://developer.mozilla.org/En/DOM/window.innerHeight>).
601
602 If you want to center text horizontally, use CSS, eg:
603
604  p.center {
605      text-align: center;
606  }
607
608 To center text vertically, CSS3 is supposed to offer a solution some
609 time, but while you're waiting for that try
610 L<http://www.w3.org/Style/Examples/007/center#vertical>.
611
612 =head3 PREVIEWING HTML
613
614 I find it helpful to have Firefox open to display the HTML files and
615 styles as I edit them.  Just start firefox in the talk directory:
616
617  firefox file://$(pwd) &
618
619 When you edit an HTML file, click the Firefox reload button to
620 immediately see your changes.
621
622 Tech Talk PSE uses Mozilla embedding to display HTML, which uses the
623 same Mozilla engine as Firefox, so what you should see in Firefox
624 should be identical to what Tech Talk PSE displays.
625
626 =head2 CREATING FIGURES
627
628 Use your favorite tool to draw the figure, convert it to an image (in
629 any format that the Mozilla engine can display) and include it using
630 an C<E<lt>imgE<gt>> tag, eg:
631
632  <img src="fig1.gif">
633
634 Suitable tools include: XFig, GnuPlot, GraphViz, and many TeX tools
635 such as PicTex and in particular TikZ.
636
637 =head2 EMBEDDING VIDEOS, ANIMATIONS, ETC.
638
639 Using HTML 5, embedding videos in the browser is easy.  See:
640 L<https://developer.mozilla.org/En/Using_audio_and_video_in_Firefox>
641
642 For animations, you could try L<Haxe|http://haxe.org/> which has a
643 Javascript back-end.  There are many other possibilities.
644
645 If you are B<sure> that the venue will have an internet connection,
646 why not embed a YouTube video.
647
648 =head2 DISPLAYING EXISTING WEB PAGES
649
650 Obviously you could just have an HTML file that contains a redirect to
651 the public web page:
652
653  <meta http-equiv="Refresh" content="0; url=http://www.example.com/">
654
655 However if you want your talk to work offline, then it's better to
656 download the web page in advance, eg. using Firefox's "Save Page As
657 -E<gt> Web Page, complete" feature, into the talk directory, then
658 either rename or make a symbolic link to the slide name:
659
660  ln -s "haXe - Welcome to haXe.html" 0010-haxe-homepage.html
661
662 =head2 TIPS FOR WRITING SHELL SCRIPTS
663
664 Make sure each C<*.sh> file you write is executable, otherwise Tech
665 Talk PSE won't be able to run it.  (The program gives a warning if you
666 forget this).
667
668 A good idea is to start each script by sourcing some common functions.
669 All my scripts start with:
670
671  #!/bin/bash -
672  source functions
673
674 where C<functions> is another file (ignored by Tech Talk PSE) which
675 contains common functions for setting shell history and starting a
676 terminal.
677
678 In C<functions>, I have:
679
680  # -*- shell-script -*-
681  
682  # Place any local environment variables required in 'local'.
683  if [ -f local ]; then source local; fi
684  
685  export PS1="$ "
686  
687  export HISTFILE=$talkdir/history
688  
689  rm -f $HISTFILE
690  touch $HISTFILE
691  
692  add_history ()
693  {
694      echo "$@" >> $HISTFILE
695  }
696  
697  terminal ()
698  {
699      # Make $HISTFILE unwritable so the shell won't update it
700      # when it exits.
701      chmod -w $HISTFILE
702  
703      # Run gnome-terminal.
704      exec \
705          gnome-terminal \
706          --window \
707          --geometry=+100+100 \
708          --hide-menubar \
709          --disable-factory \
710          -e '/bin/bash --norc' \
711          "$@"
712  }
713
714 By initializing the shell history, during your talk you can rapidly
715 recall commands to start parts of the demonstration just by hitting
716 the Up arrow.  A complete shell script from one of my talks would look
717 like this:
718
719  #!/bin/bash -
720  source functions
721  add_history guestfish -i debian.img
722  terminal --title="Examining a Debian guest image in guestfish"
723
724 This is just a starting point for your own scripts.  You may want to
725 use a different terminal, such as xterm, and you may want to adjust
726 terminal fonts.
727
728 =head1 REFERENCE
729
730 =head2 ORDER OF FILES
731
732 Tech Talk PSE displays the slides in the directory in lexicographic
733 order (the same order as C<LANG=C ls -1>).  Only files matching the
734 following regexp are considered:
735
736  ^(\d+)(?:-.*)\.(html|sh)$
737
738 For future compatibility, you should ensure that every slide has a
739 unique numeric part (ie. I<don't> have C<0010-aaa.html> and
740 C<0010-bbb.html>).  This is because in future we want to have the
741 ability to display multiple files side by side.
742
743 Also for future compatibility, I<don't> use file names that have an
744 uppercase letter immediately after the numeric part.  This is because
745 in future we want to allow placement hints using filenames like
746 C<0010L-on-the-left.html> and C<0010R-on-the-right.html>.
747
748 =head2 BASE URL AND CURRENT DIRECTORY
749
750 The base URL is set to the be the directory containing the talk files.
751 Thus you should use relative paths, eg:
752
753  <img src="fig1.gif">
754
755 You can also place assets into subdirectories, because subdirectories
756 are ignored by Tech Talk PSE, eg:
757
758  <img src="images/fig1.gif">
759
760 When running shell scripts, the current directory is also set to be
761 the directory containing the talk files, so the same rules about using
762 relative paths apply there too.
763
764 The environment variable C<$talkdir> is exported to scripts and it
765 contains the absolute path of the directory containing the talk files.
766 When a script is run, the current directory is the same as
767 C<$talkdir>, but if your script changes directory (eg. into a
768 subdirectory containing supporting files) then it can be useful to use
769 C<$talkdir> to refer back to the original directory.
770
771 =head1 WHAT MAKES A GOOD TALK
772
773 I like what Edward Tufte writes, for example his evisceration of
774 PowerPoint use at NASA here:
775 L<http://www.edwardtufte.com/bboard/q-and-a-fetch-msg?msg_id=0001yB>
776
777 However it is sometimes hard to translate his ideas into clear
778 presentations, and not all of that is the fault of the tools.  Here
779 are my thoughts and rules on how to deliver a good talk.
780
781 B<First, most important rule:> Before you start drawing any slides at
782 all, write your talk as a short essay.
783
784 This is the number one mistake that presenters make, and it is partly
785 a tool fault, because PowerPoint, OpenOffice, even Tech Talk PSE, all
786 open up on an initial blank slide, inviting you to write a title and
787 some bullet points.  If you start that way, you will end up using the
788 program as a kind of clumsy outlining tool, and then reading that
789 outline to your audience.  That's boring and a waste of time for you
790 and your audience.  (It would be quicker for them just to download the
791 talk and read it at home).
792
793 B<Secondly:> How long do you want to spend preparing the talk?  A good
794 talk, with a sound essay behind it, well thought out diagrams and
795 figures, and interesting demonstrations, takes many hours to prepare.
796 How many hours?  I would suggest thinking about how many hours of
797 effort your audience are putting in.  Even just 20 people sitting
798 there for half an hour is 10 man-hours of attention, and that is a
799 very small talk, and doesn't include all the extra time and hassle
800 that it took to get them all in one place.
801
802 I don't think you can get away with spending less than two full days
803 preparing a talk, if you want to master the topic and draw up accurate
804 slides.  Steve Jobs is reputed to spend weeks preparing his annual
805 sales talk to the Apple faithful.
806
807 B<Thirdly:> Now that you're going to write your talk as an essay, what
808 should go in the slides?  I would say that you should consider
809 delivering the essay, I<not> the slides, to people who don't make the
810 talk.  An essay can be turned into an article or blog posting, whereas
811 even "read-out-the-bullet-point" slides have a low information
812 density, large size, and end-user compatibility problems (*.pptx
813 anyone?).
814
815 What, then, goes on the slides?  Anything you cannot just say:
816 diagrams, graphs, videos, animations, and of course (only with Tech
817 Talk PSE!) demonstrations.
818
819 B<Lastly:> Once you've got your talk as an essay and slides, practice,
820 practice and practice again.  Deliver the talk to yourself in the
821 mirror, to your colleagues.  Practice going backwards and forwards
822 through the slides, using your actual laptop and the software so you
823 know what to click and what keys to press.  Partly memorize what you
824 are going to say (but use short notes written on paper if you need
825 to).
826
827 =head1 SEE ALSO
828
829 The Cognitive Style of PowerPoint, Tufte, Edward R.
830
831 =head1 AUTHOR
832
833 Richard W.M. Jones L<http://people.redhat.com/~rjones/>
834
835 =head1 COPYRIGHT
836
837 Copyright (C) 2010 Red Hat Inc.
838
839 This program is free software; you can redistribute it and/or modify
840 it under the terms of the GNU General Public License as published by
841 the Free Software Foundation; either version 2 of the License, or
842 (at your option) any later version.
843
844 This program is distributed in the hope that it will be useful,
845 but WITHOUT ANY WARRANTY; without even the implied warranty of
846 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
847 GNU General Public License for more details.
848
849 You should have received a copy of the GNU General Public License
850 along with this program; if not, write to the Free Software
851 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.