view probation-slide/slidy.js @ 13:12cb508ee15d after-organizing

add slides for probation.
author kent <kent@cr.ie.u-ryukyu.ac.jp>
date Tue, 16 Feb 2010 14:48:06 +0900
parents
children
line wrap: on
line source

/* slidy.js

   Copyright (c) 2005-2009 W3C (MIT, ERCIM, Keio), All Rights Reserved.
   W3C liability, trademark, document use and software licensing
   rules apply, see:

   http://www.w3.org/Consortium/Legal/copyright-documents
   http://www.w3.org/Consortium/Legal/copyright-software
*/

var ns_pos = (typeof window.pageYOffset!='undefined');
var khtml = ((navigator.userAgent).indexOf("KHTML") >= 0 ? true : false);
var opera = ((navigator.userAgent).indexOf("Opera") >= 0 ? true : false);
var ie = (typeof document.all != "undefined" && !opera);
var ie7 = (!ns_pos && navigator.userAgent.indexOf("MSIE 7") != -1);
var ie8 = (!ns_pos && navigator.userAgent.indexOf("MSIE 8") != -1);
var slidy_started = false;

// added by kent.
var showspeak = false;

if (ie && !ie8)
  document.write("<iframe id='historyFrame' src='javascript:\"<html"+"></"+"html>\"' height='1' width='1' style='position:absolute;left:-800px'></iframe>");

// IE only event handlers to ensure all slides are printed
// I don't yet know how to emulate these for other browsers
if (typeof beforePrint != 'undefined')
{
  window.onbeforeprint = beforePrint;
  window.onafterprint = afterPrint;
}

// to avoid a clash with other scripts or onload attribute on <body>
// we use something smarter than window.onload
//window.onload = startup;


if (ie)
  setTimeout(ieSlidyInit, 100);
else if (document.addEventListener)
  document.addEventListener("DOMContentLoaded", startup, false);

function ieSlidyInit()
{
  if (//document.readyState == "interactive" ||
      document.readyState == "complete" ||
      document.readyState == "loaded")
  {
    startup();
  }
  else
  {
    setTimeout(ieSlidyInit, 100);
  }
}

setTimeout(hideSlides, 50);

function hideSlides()
{
  if (document.body)
    document.body.style.visibility = "hidden";
  else
    setTimeout(hideSlides, 50);
}

var slidenum = 0;     // integer slide count: 0, 1, 2, ...
var slides;           // set to array of slide div's
var slideNumElement;  // element containing slide number
var notes;            // set to array of handout div's
var backgrounds;      // set to array of background div's
var toolbar;          // element containing toolbar
var title;            // document title
var lastShown = null; // last incrementally shown item
var eos = null;       // span element for end of slide indicator
var toc = null;       // table of contents
var outline = null;   // outline element with the focus
var selectedTextLen;  // length of drag selection on document

var viewAll = 0;      // 1 to view all slides + handouts
var wantToolbar = 1;   // 0 if toolbar isn't wanted
var mouseClickEnabled = true;  // enables left click for next slide
var scrollhack = 0;   // IE work around for position: fixed

var helpAnchor;  // used for keyboard focus hack in showToolbar()
var helpPage = "http://www.w3.org/Talks/Tools/Slidy/help.html";
var helpText = "Navigate with mouse click, space bar, Cursor Left/Right, " +
               "or Pg Up and Pg Dn. Use S and B to change font size.";

var sizeIndex = 0;
var sizeAdjustment = 0;
var sizes = new Array("10pt", "12pt", "14pt", "16pt", "18pt", "20pt",
                      "22pt", "24pt", "26pt", "28pt", "30pt", "32pt");
var okayForIncremental = incrementalElementList();

// needed for efficient resizing
var lastWidth = 0;
var lastHeight = 0;

// Needed for cross browser support for relative width/height on
// object elements. The work around is to save width/height attributes
// and then to recompute absolute width/height dimensions on resizing
var objects;

// updated to language specified by html file
var lang = "en";

//var localize = {};

// for each language there is an associative array
var strings_es = {
  "slide":"pág.",
  "help?":"Ayuda",
  "contents?":"Índice",
  "table of contents":"tabla de contenidos",
  "Table of Contents":"Tabla de Contenidos",
  "restart presentation":"Reiniciar presentación",
  "restart?":"Inicio"
   };

strings_es[helpText] =
    "Utilice el ratón, barra espaciadora, teclas Izda/Dcha, " +
    "o Re pág y Av pág. Use S y B para cambiar el tamaño de fuente.";

var strings_ca = {
 "slide":"pàg..",
 "help?":"Ajuda",
 "contents?":"Índex",
 "table of contents":"taula de continguts",
 "Table of Contents":"Taula de Continguts",
 "restart presentation":"Reiniciar presentació",
 "restart?":"Inici"
  };

strings_ca[helpText] =
   "Utilitzi el ratolí, barra espaiadora, tecles Esq./Dta. " +
   "o Re pàg y Av pàg. Usi S i B per canviar grandària de font.";

var strings_nl = {
  "slide":"pagina",
  "help?":"Help?",
  "contents?":"Inhoud?",
  "table of contents":"inhoudsopgave",
  "Table of Contents":"Inhoudsopgave",
  "restart presentation":"herstart presentatie",
  "restart?":"Herstart?"
   };

strings_nl[helpText] =
    "Navigeer d.m.v. het muis, spatiebar, Links/Rechts toetsen, " +
    "of PgUp en PgDn. Gebruik S en B om de karaktergrootte te veranderen.";

var strings_de = {
  "slide":"Seite",
  "help?":"Hilfe",
  "contents?":"Übersicht",
  "table of contents":"Inhaltsverzeichnis",
  "Table of Contents":"Inhaltsverzeichnis",
  "restart presentation":"Präsentation neu starten",
  "restart?":"Neustart"
   };

strings_de[helpText] =
    "Benutzen Sie die Maus, Leerschlag, die Cursortasten links/rechts oder " +
    "Page up/Page Down zum Wechseln der Seiten und S und B für die Schriftgrösse.";

var strings_pl = {
  "slide":"slajd",
  "help?":"pomoc?",
  "contents?":"spis treści?",
  "table of contents":"spis treści",
  "Table of Contents":"Spis Treści",
  "restart presentation":"Restartuj prezentację",
  "restart?":"restart?"
   };

strings_pl[helpText] =
    "Zmieniaj slajdy klikając myszą, naciskając spację, strzałki lewo/prawo" +
    "lub PgUp / PgDn. Użyj klawiszy S i B, aby zmienić rozmiar czczionki.";

var strings_fr = {
  "slide":"page",
  "help?":"Aide",
  "contents?":"Index",
  "table of contents":"table des matières",
  "Table of Contents":"Table des matières",
  "restart presentation":"Recommencer l'exposé",
  "restart?":"Début"
  };

strings_fr[helpText] =
    "Naviguez avec la souris, la barre d'espace, les flèches " +
    "gauche/droite ou les touches Pg Up, Pg Dn. Utilisez " +
    "les touches S et B pour modifier la taille de la police.";

var strings_hu = {
  "slide":"oldal",
  "help?":"segítség",
  "contents?":"tartalom",
  "table of contents":"tartalomjegyzék",
  "Table of Contents":"Tartalomjegyzék",
  "restart presentation":"bemutató újraindítása",
  "restart?":"újraindítás"
   };

strings_hu[helpText] =
    "Az oldalak közti lépkedéshez kattintson az egérrel, vagy " +
    "használja a szóköz, a bal, vagy a jobb nyíl, illetve a Page Down, " +
    "Page Up billentyűket. Az S és a B billentyűkkel változtathatja " +
    "a szöveg méretét.";

var strings_it = {
  "slide":"pag.",
  "help?":"Aiuto",
  "contents?":"Indice",
  "table of contents":"indice",
  "Table of Contents":"Indice",
  "restart presentation":"Ricominciare la presentazione",
  "restart?":"Inizio"
   };

strings_it[helpText] =
    "Navigare con mouse, barra spazio, frecce sinistra/destra o " +
    "PgUp e PgDn. Usare S e B per cambiare la dimensione dei caratteri.";

var strings_el = {
  "slide":"σελίδα",
  "help?":"βοήθεια;",
  "contents?":"περιεχόμενα;",
  "table of contents":"πίνακας περιεχομένων",
  "Table of Contents":"Πίνακας Περιεχομένων",
  "restart presentation":"επανεκκίνηση παρουσίασης",
  "restart?":"επανεκκίνηση;"
   };

strings_el[helpText] =
  "Πλοηγηθείτε με το κλίκ του ποντικιού, το space, τα βέλη αριστερά/δεξιά, " +
  "ή Page Up και Page Down. Χρησιμοποιήστε τα πλήκτρα S και B για να αλλάξετε " +
  "το μέγεθος της γραμματοσειράς.";

var strings_ja = {
  "slide":"スライド",
  "help?":"ヘルプ",
  "contents?":"目次",
  "table of contents":"目次を表示",
  "Table of Contents":"目次",
  "restart presentation":"最初から再生",
  "restart?":"最初から"
};

strings_ja[helpText] =
    "マウス左クリック ・ スペース ・ 左右キー " +
    "または Page Up ・ Page Downで操作, S ・ Bでフォントサイズ変更";

var strings_zh = {
  "slide":"幻灯片",
  "help?":"帮助?",
  "contents?":"内容?",
  "table of contents":"目录",
  "Table of Contents":"目录",
  "restart presentation":"重新启动展示",
  "restart?":"重新启动?"
};

strings_zh[helpText] =
  "用鼠标点击, 空格条, 左右箭头, Pg Up 和 Pg Dn 导航. " +
  "用 S, B 改变字体大小.";

var strings_ru = {
  "slide":"слайд",
  "help?":"помощь?",
  "contents?":"содержание?",
  "table of contents":"оглавление",
  "Table of Contents":"Оглавление",
  "restart presentation":"перезапустить презентацию",
  "restart?":"перезапуск?"
   };

strings_ru[helpText] =
    "Перемещайтесь кликая мышкой, используя клавишу пробел, стрелки" +
    "влево/вправо или Pg Up и Pg Dn. Клавиши S и B меняют размер шрифта.";


// each such language array is declared in the localize array
// used indirectly as in help.innerHTML = "help".localize();
var localize = {
     "es":strings_es,
     "ca":strings_ca,
     "nl":strings_nl,
     "de":strings_de,
     "pl":strings_pl,
     "fr":strings_fr,
     "hu":strings_hu,
     "it":strings_it,
     "el":strings_el,
     "jp":strings_ja,
     "zh":strings_zh,
     "ru":strings_ru
   };

/* general initialization */
function startup()
{
   if (slidy_started)
   {
      alert("already started");
      return;
   }
   slidy_started = true;

   // find human language from html element
   // for use in localizing strings
   lang = document.body.parentNode.getAttribute("lang");

   if (!lang)
     lang = document.body.parentNode.getAttribute("xml:lang");

   if (!lang)
     lang = "en";

   document.body.style.visibility = "visible";
   title = document.title;
   toolbar = addToolbar();
   wrapImplicitSlides();
   slides = collectSlides();
   notes = collectNotes();
   objects = document.body.getElementsByTagName("object");
   backgrounds = collectBackgrounds();
   patchAnchors();

   slidenum = findSlideNumber(location.href);
   window.offscreenbuffering = true;
   sizeAdjustment = findSizeAdjust();
   hideImageToolbar();  // suppress IE image toolbar popup
   initOutliner();  // activate fold/unfold support

   if (slides.length > 0)
   {
      var slide = slides[slidenum];
      slide.style.position = "absolute";
   
      if (slidenum > 0)
      {
         setVisibilityAllIncremental("visible");
         lastShown = previousIncrementalItem(null);
         setEosStatus(true);
      }
      else
      {
         lastShown = null;
         setVisibilityAllIncremental("hidden");
         setEosStatus(!nextIncrementalItem(lastShown));
      }

      setLocation();
   }

   toc = tableOfContents();
   hideTableOfContents();

   // bind event handlers
   document.onclick = mouseButtonClick;
   document.onmouseup = mouseButtonUp;
   document.onkeydown = keyDown;
   window.onresize  = resized;
   window.onscroll = scrolled;
   window.onunload = unloaded;
   singleSlideView();


   setLocation();
   resized();

   if (ie7)
     setTimeout("ieHack()", 100);

   showToolbar();
   setInterval("checkLocation()", 200); // for back button detection
}

// add localize method to all strings for use
// as in help.innerHTML = "help".localize();
String.prototype.localize = function()
{
  if (this == "")
    return this;

  // try full language code, e.g. en-US
  var s, lookup = localize[lang];

  if (lookup)
  {
    s = lookup[this];

    if (s)
      return s;
  }

  // try en if undefined for en-US
  var lg = lang.split("-");

  if (lg.length > 1)
  {
    lookup = localize[lg[0]];

    if (lookup)
    {
      s = lookup[this];

      if (s)
        return s;
    }
  }

  // otherwise string as is
  return this;
}

// suppress IE's image toolbar pop up
function hideImageToolbar()
{
  if (!ns_pos)
  {
    var images = document.getElementsByTagName("IMG");

    for (var i = 0; i < images.length; ++i)
      images[i].setAttribute("galleryimg", "no");
  }
}

// hack to persuade IE to compute correct document height
// as needed for simulating fixed positioning of toolbar
function ieHack()
{
   window.resizeBy(0,-1);
   window.resizeBy(0, 1);
}

function unloaded(e)
{
  //alert("unloaded");
}

// Firefox reload SVG bug work around
function reload(e)
{
   if (!e)
      var e = window.event;

   hideBackgrounds();
   setTimeout("document.reload();", 100);

   stopPropagation(e);
   e.cancel = true;
   e.returnValue = false;

   return false;
}

// Safari and Konqueror don't yet support getComputedStyle()
// and they always reload page when location.href is updated
function isKHTML()
{
   var agent = navigator.userAgent;
   return (agent.indexOf("KHTML") >= 0 ? true : false);
}

function resized()
{
   var width = 0;

   if ( typeof( window.innerWidth ) == 'number' )
      width = window.innerWidth;  // Non IE browser
   else if (document.documentElement && document.documentElement.clientWidth)
      width = document.documentElement.clientWidth;  // IE6
   else if (document.body && document.body.clientWidth)
      width = document.body.clientWidth; // IE4

   var height = 0;

   if ( typeof( window.innerHeight ) == 'number' )
      height = window.innerHeight;  // Non IE browser
   else if (document.documentElement && document.documentElement.clientHeight)
      height = document.documentElement.clientHeight;  // IE6
   else if (document.body && document.body.clientHeight)
      height = document.body.clientHeight; // IE4

   if (height && (width/height > 1.05*1024/768))
   {
     width = height * 1024.0/768;
   }

   // IE fires onresize even when only font size is changed!
   // so we do a check to avoid blocking < and > actions
   if (width != lastWidth || height != lastHeight)
   {
      if (width >= 1100)
         sizeIndex = 5;    // 4
      else if (width >= 1000)
         sizeIndex = 4;    // 3
      else if (width >= 800)
         sizeIndex = 3;    // 2
      else if (width >= 600)
         sizeIndex = 2;    // 1
      else if (width)
         sizeIndex = 0;

      // add in font size adjustment from meta element e.g.
      // <meta name="font-size-adjustment" content="-2" />
      // useful when slides have too much content ;-)

      if (0 <= sizeIndex + sizeAdjustment &&
             sizeIndex + sizeAdjustment < sizes.length)
        sizeIndex = sizeIndex + sizeAdjustment;

      // enables cross browser use of relative width/height
      // on object elements for use with SVG and Flash media
      adjustObjectDimensions(width, height);

      document.body.style.fontSize = sizes[sizeIndex];

      lastWidth = width;
      lastHeight = height;

      // force reflow to work around Mozilla bug
      //if (ns_pos)
      {
         var slide = slides[slidenum];
         hideSlide(slide);
         showSlide(slide);
      }

      // force correct positioning of toolbar
      refreshToolbar(200);
   }
}

function scrolled()
{
   if (toolbar && !ns_pos && !ie7)
   {
      hackoffset = scrollXOffset();
      // hide toolbar
      toolbar.style.display = "none";

      // make it reappear later
      if (scrollhack == 0 && !viewAll)
      {
         setTimeout(showToolbar, 1000);
         scrollhack = 1;
      }
   }
}

// used to ensure IE refreshes toolbar in correct position
function refreshToolbar(interval)
{
   if (!ns_pos && !ie7)
   {
     hideToolbar();
     setTimeout(showToolbar, interval);
   }
}

// restores toolbar after short delay
function showToolbar()
{
   if (wantToolbar)
   {
      if (!ns_pos)
      {
         // adjust position to allow for scrolling
         var xoffset = scrollXOffset();
         toolbar.style.left = xoffset;
         toolbar.style.right = xoffset;

         // determine vertical scroll offset
         //var yoffset = scrollYOffset();

         // bottom is doc height - window height - scroll offset
         //var bottom = documentHeight() - lastHeight - yoffset

         //if (yoffset > 0 || documentHeight() > lastHeight)
         //   bottom += 16;  // allow for height of scrollbar

         toolbar.style.bottom = 0; //bottom;
      }

      toolbar.style.display = "block";
      toolbar.style.visibility = "visible";
   }

   scrollhack = 0;


   // set the keyboard focus to the help link on the
   // toolbar to ensure that document has the focus
   // IE doesn't always work with window.focus()
   // and this hack has benefit of Enter for help

   try
   {
     if (!opera)
       helpAnchor.focus();
   }
   catch (e)
   {
   }
}

function hideToolbar()
{
   toolbar.style.display = "none";
   toolbar.style.visibility = "hidden";
   window.focus();
}

// invoked via F key
function toggleToolbar()
{
   if (!viewAll)
   {
      if (toolbar.style.display == "none")
      {
         toolbar.style.display = "block";
         toolbar.style.visibility = "visible";
         wantToolbar = 1;
      }
      else
      {
         toolbar.style.display = "none";
         toolbar.style.visibility = "hidden";
         wantToolbar = 0;
      }
   }
}

function scrollXOffset()
{
   if (window.pageXOffset)
      return self.pageXOffset;

   if (document.documentElement && 
             document.documentElement.scrollLeft)
      return document.documentElement.scrollLeft;

   if (document.body)
      return document.body.scrollLeft;

    return 0;
}


function scrollYOffset()
{
   if (window.pageYOffset)
      return self.pageYOffset;

   if (document.documentElement && 
             document.documentElement.scrollTop)
      return document.documentElement.scrollTop;

   if (document.body)
      return document.body.scrollTop;

    return 0;
}

// looking for a way to determine height of slide content
// the slide itself is set to the height of the window
function optimizeFontSize()
{
   var slide = slides[slidenum];

   //var dh = documentHeight(); //getDocHeight(document);
   var dh = slide.scrollHeight;
   var wh = getWindowHeight();
   var u = 100 * dh / wh;

   alert("window utilization = " + u + "% (doc "
      + dh + " win " + wh + ")");
}

function getDocHeight(doc) // from document object
{
  if (!doc)
    doc = document;

  if (doc && doc.body && doc.body.offsetHeight)
    return doc.body.offsetHeight;  // ns/gecko syntax

  if (doc && doc.body && doc.body.scrollHeight)
    return doc.body.scrollHeight;

  alert("couldn't determine document height");
}

function getWindowHeight()
{
  if ( typeof( window.innerHeight ) == 'number' )
    return window.innerHeight;  // Non IE browser

  if (document.documentElement && document.documentElement.clientHeight)
    return document.documentElement.clientHeight;  // IE6

  if (document.body && document.body.clientHeight)
    return document.body.clientHeight; // IE4
}



function documentHeight()
{
   var sh, oh;

   sh = document.body.scrollHeight;
   oh = document.body.offsetHeight;

   if (sh && oh)
   {
      return (sh > oh ? sh : oh);
   }

   // no idea!
   return 0;
}

function smaller()
{
   if (sizeIndex > 0)
   {
      --sizeIndex;
   }

   toolbar.style.display = "none";
   document.body.style.fontSize = sizes[sizeIndex];
   var slide = slides[slidenum];
   hideSlide(slide);
   showSlide(slide);
   setTimeout(showToolbar, 300);
}

function bigger()
{
   if (sizeIndex < sizes.length - 1)
   {
      ++sizeIndex;
   }

   toolbar.style.display = "none";
   document.body.style.fontSize = sizes[sizeIndex];
   var slide = slides[slidenum];
   hideSlide(slide);
   showSlide(slide);
   setTimeout(showToolbar, 300);
}

// enables cross browser use of relative width/height
// on object elements for use with SVG and Flash media
// with thanks to Ivan Herman for the suggestion
function adjustObjectDimensions(width, height)
{
   for( var i = 0; i < objects.length; i++ )
   {
      var obj = objects[i];
      var mimeType = obj.getAttribute("type");

      if (mimeType == "image/svg+xml" || mimeType == "application/x-shockwave-flash")
      {
         if ( !obj.initialWidth ) 
            obj.initialWidth = obj.getAttribute("width");

         if ( !obj.initialHeight ) 
            obj.initialHeight = obj.getAttribute("height");

         if ( obj.initialWidth && obj.initialWidth.charAt(obj.initialWidth.length-1) == "%" )
         {
            var w = parseInt(obj.initialWidth.slice(0, obj.initialWidth.length-1));
            var newW = width * (w/100.0);
            obj.setAttribute("width",newW);
         }

         if ( obj.initialHeight && obj.initialHeight.charAt(obj.initialHeight.length-1) == "%" )
         {
            var h = parseInt(obj.initialHeight.slice(0, obj.initialHeight.length-1));
            var newH = height * (h/100.0);
            obj.setAttribute("height", newH);
         }
      }
   }
}

function cancel(event)
{
  if (event)
  {
     event.cancel = true;
     event.returnValue = false;

    if (event.preventDefault)
      event.preventDefault();
  }

  return false;
}

//  See e.g. http://www.quirksmode.org/js/events/keys.html for keycodes
function keyDown(event)
{
    var key;

    if (!event)
      var event = window.event;

    // kludge around NS/IE differences 
    if (window.event)
       key = window.event.keyCode;
    else if (event.which)
       key = event.which;
    else
       return true; // Yikes! unknown browser

    // ignore event if key value is zero
    // as for alt on Opera and Konqueror
    if (!key)
       return true;

    // check for concurrent control/command/alt key
    // but are these only present on mouse events?

    if (event.ctrlKey || event.altKey || event.metaKey)
       return true;

    // dismiss table of contents if visible
    if (isShownToc() && key != 9 && key != 16 && key != 38 && key != 40)
    {
      hideTableOfContents();

      if (key == 27 || key == 84 || key == 67)
        return cancel(event);
    }

    if (key == 34) // Page Down
    {
       if (viewAll)
         return true;

       nextSlide(false);
       return cancel(event);
    }
    else if (key == 33) // Page Up
    {
       if (viewAll)
         return true;

       previousSlide(false);
       return cancel(event);
    }
    else if (key == 32) // space bar
    {
       nextSlide(true);
       return cancel(event);
    }
    else if (key == 37) // Left arrow
    {
       previousSlide(!event.shiftKey);
       return cancel(event);
    }
    else if (key == 36) // Home
    {
       firstSlide();
       return cancel(event);
    }
    else if (key == 35) // End
    {
       lastSlide();
       return cancel(event);
    }
    else if (key == 39) // Right arrow
    {
       nextSlide(!event.shiftKey);
       return cancel(event);
    }
    else if (key == 13) // Enter
    {
       if (outline)
       {
          if (outline.visible)
            fold(outline);
          else
            unfold(outline);
          
         return cancel(event);
       }
    }
    else if (key == 188)  // < for smaller fonts
    {
       smaller();
       return cancel(event);
    }
    else if (key == 190)  // > for larger fonts
    {
       bigger();
       return cancel(event);
    }
    else if (key == 189 || key == 109)  // - for smaller fonts
    {
       smaller();
       return cancel(event);
    }
    else if (key == 187 || key == 191 || key == 107)  // = +  for larger fonts
    {
       bigger();
       return cancel(event);
    }
    else if (key == 83)  // S for smaller fonts
    {
       smaller();
       return cancel(event);
    }
    else if (key == 66)  // B for larger fonts
    {
       bigger();
       return cancel(event);
    }
    else if (key == 90)  // Z for last slide
    {
       lastSlide();
       return cancel(event);
    }
    else if (key == 70)  // F for toggle toolbar
    {
       toggleToolbar();
       return cancel(event);
    }
    else if (key == 65)  // A for toggle view single/all slides
    {
       toggleView();
       return cancel(event);
    }
    else if (key == 75)  // toggle action of left click for next page
    {
       // commented out by kent 20100210.
       /*
       mouseClickEnabled = !mouseClickEnabled;
       alert((mouseClickEnabled ? "enabled" : "disabled") +  " mouse click advance");
       return cancel(event);
       */
    }
    else if (key == 84 || key == 67)  // T or C for table of contents
    {
       if (toc)
         showTableOfContents();

       return cancel(event);
    }
    else if (key == 72) // H for help
    {
       window.location = helpPage;
       return cancel(event);
    }

    // added by kent 20100210
    else if (key == 77) {
      showspeak = !showspeak;
      var value = (showspeak? "visible":"hidden");
      var elems = document.getElementsByClassName("speak");

      for (var i=0; i<elems.length; i++) {
        elems[i].style.visibility = value;
      }
      return cancel(event);
    }

    //else if (key == 93) // Windows menu key
      //alert("lastShown is " + lastShown);
    //else alert("key code is "+ key);


    return true;
}

// make note of length of selected text
// as this evaluates to zero in click event
function mouseButtonUp(e)
{
  selectedTextLen = getSelectedText().length;
}

// right mouse button click is reserved for context menus
// it is more reliable to detect rightclick than leftclick
function mouseButtonClick(e)
{
   var rightclick = false;
   var leftclick = false;
   var middleclick = false;
   var target;

   if (!e)
      var e = window.event;

   if (e.target)
      target = e.target;
   else if (e.srcElement)
      target = e.srcElement;

   // work around Safari bug
   if (target.nodeType == 3)
      target = target.parentNode;

   if (e.which) // all browsers except IE
   {
      leftclick = (e.which == 1);
      middleclick = (e.which == 2);
      rightclick = (e.which == 3);
   }
   else if (e.button)
   {
      // Konqueror gives 1 for left, 4 for middle
      // IE6 gives 0 for left and not 1 as I expected

      if (e.button == 4)
        middleclick = true;

      // all browsers agree on 2 for right button
      rightclick = (e.button == 2);
   }
   else leftclick = true;

   //alert("selected text length = "+selectedTextLen);

   if (selectedTextLen > 0)
   {
      stopPropagation(e);
      e.cancel = true;
      e.returnValue = false;
      return false;
   }

   // dismiss table of contents
   hideTableOfContents();

   // check if target is something that probably want's clicks
   // e.g. embed, object, input, textarea, select, option

   if (mouseClickEnabled && leftclick &&
        target.nodeName != "EMBED" &&
        target.nodeName != "OBJECT" &&
        target.nodeName != "VIDEO" &&
        target.nodeName != "INPUT" &&
        target.nodeName != "TEXTAREA" &&
        target.nodeName != "SELECT" &&
        target.nodeName != "OPTION")
   {
      nextSlide(true);
      stopPropagation(e);
      e.cancel = true;
      e.returnValue = false;
   }
}

function previousSlide(incremental)
{
   if (!viewAll)
   {
      var slide;

      if ((incremental || slidenum == 0) && lastShown != null)
      {
         lastShown = hidePreviousItem(lastShown);
         setEosStatus(false);
      }
      else if (slidenum > 0)
      {
         slide = slides[slidenum];
         hideSlide(slide);

         slidenum = slidenum - 1;
         slide = slides[slidenum];
         setVisibilityAllIncremental("visible");
         lastShown = previousIncrementalItem(null);
         setEosStatus(true);
         showSlide(slide);
      }

      setLocation();

      if (!ns_pos)
         refreshToolbar(200);
   }
}

function nextSlide(incremental)
{
   if (!viewAll)
   {
      var slide, last = lastShown;

      if (incremental || slidenum == slides.length - 1)
         lastShown = revealNextItem(lastShown);

      if ((!incremental || lastShown == null) && slidenum < slides.length - 1)
      {
         slide = slides[slidenum];
         hideSlide(slide);

         slidenum = slidenum + 1;
         slide = slides[slidenum];
         lastShown = null;
         setVisibilityAllIncremental("hidden");
         showSlide(slide);
      }
      else if (!lastShown)
      {
         if (last && incremental)
           lastShown = last;
      }

      setLocation();

      setEosStatus(!nextIncrementalItem(lastShown));

      if (!ns_pos)
         refreshToolbar(200);
   }
}

// to first slide with nothing revealed
// i.e. state at start of presentation
function firstSlide()
{
   if (!viewAll)
   {
      var slide;

      if (slidenum != 0)
      {
         slide = slides[slidenum];
         hideSlide(slide);

         slidenum = 0;
         slide = slides[slidenum];
         lastShown = null;
         setVisibilityAllIncremental("hidden");
         showSlide(slide);
      }

      setEosStatus(!nextIncrementalItem(lastShown));
      setLocation();
   }
}


// to last slide with everything revealed
// i.e. state at end of presentation
function lastSlide()
{
   if (!viewAll)
   {
      var slide;

      lastShown = null; //revealNextItem(lastShown);

      if (lastShown == null && slidenum < slides.length - 1)
      {
         slide = slides[slidenum];
         hideSlide(slide);
         slidenum = slides.length - 1;
         slide = slides[slidenum];
         setVisibilityAllIncremental("visible");
         lastShown = previousIncrementalItem(null);

         showSlide(slide);
      }
      else
      {
         setVisibilityAllIncremental("visible");
         lastShown = previousIncrementalItem(null);
      }

      setEosStatus(true);
      setLocation();
   }
}

// first slide is 0
function gotoSlide(num)
{
  //alert("going to slide " + (num+1));
  var slide = slides[slidenum];
  hideSlide(slide);
  slidenum = num;
  slide = slides[slidenum];
  lastShown = null;
  setVisibilityAllIncremental("hidden");
  setEosStatus(!nextIncrementalItem(lastShown));
  document.title = title + " (" + (slidenum+1) + ")";
  showSlide(slide);
  showSlideNumber();
}

function setEosStatus(state)
{
   if (eos)
      eos.style.color = (state ? "rgb(240,240,240)" : "red");
}

function showSlide(slide)
{
   syncBackground(slide);
   window.scrollTo(0,0);
   slide.style.visibility = "visible";
   slide.style.display = "block";
}

function hideSlide(slide)
{
   slide.style.visibility = "hidden";
   slide.style.display = "none";
}

function beforePrint()
{
   showAllSlides();
   hideToolbar();
}

function afterPrint()
{
   if (!viewAll)
   {
      singleSlideView();
      showToolbar();
   }
}

function printSlides()
{
  beforePrint();
  window.print();
  afterPrint();
}

function toggleView()
{
   if (viewAll)
   {
      singleSlideView();
      showToolbar();
      viewAll = 0;
   }
   else
   {
      showAllSlides();
      hideToolbar();
      viewAll = 1;
   }
}

// prepare for printing
function showAllSlides()
{
   var slide;

   for (var i = 0; i < slides.length; ++i)
   {
      slide = slides[i];

      slide.style.position = "relative";
      slide.style.borderTopStyle = "solid";
      slide.style.borderTopWidth = "thin";
      slide.style.borderTopColor = "black";

      try {
        if (i == 0)
          slide.style.pageBreakBefore = "avoid";
        else
          slide.style.pageBreakBefore = "always";
      }
      catch (e)
      {
        //do nothing
      }

      setVisibilityAllIncremental("visible");
      showSlide(slide);
   }

   var note;

   for (var i = 0; i < notes.length; ++i)
   {
      showSlide(notes[i]);
   }

   // no easy way to render background under each slide
   // without duplicating the background divs for each slide
   // therefore hide backgrounds to avoid messing up slides
   hideBackgrounds();
}

// restore after printing
function singleSlideView()
{
   var slide;

   for (var i = 0; i < slides.length; ++i)
   {
      slide = slides[i];

      slide.style.position = "absolute";

      if (i == slidenum)
      {
         slide.style.borderStyle = "none";
         showSlide(slide);
      }
      else
      {
         slide.style.borderStyle = "none";
         hideSlide(slide);
      }
   }

   setVisibilityAllIncremental("visible");
   lastShown = previousIncrementalItem(null);

   var note;

   for (var i = 0; i < notes.length; ++i)
   {
      hideSlide(notes[i]);
   }
}

// the string str is a whitespace separated list of tokens
// test if str contains a particular token, e.g. "slide"
function hasToken(str, token)
{
   if (str)
   {
      // define pattern as regular expression
      var pattern = /\w+/g;

      // check for matches
      // place result in array
      var result = str.match(pattern);

      // now check if desired token is present
      for (var i = 0; i < result.length; i++)
      {
         if (result[i] == token)
            return true;
      }
   }

   return false;
}

function getClassList(element)
{
  if (typeof element.className != 'undefined')
    return element.className;

  var clsname = (ns_pos||ie8) ? "class" : "className";
  return element.getAttribute(clsname);
}

function hasClass(element, name)
{
  var regexp = new RegExp("(^| )" + name + "\W*");

  if (typeof element.className != 'undefined')
    return regexp.test(element.className);

  var clsname = (ns_pos||ie8) ? "class" : "className";
  return regexp.test(element.getAttribute(clsname));
}

function removeClass(element, name)
{
  var regexp = new RegExp("(^| )" + name + "\W*");
  var clsval = "";

  if (typeof element.className != 'undefined')
  {
     clsval = element.className;

     if (clsval)
     {
       clsval = clsval.replace(regexp, "");
       element.className = clsval;
     }
  }
  else
  {
    var clsname = (ns_pos||ie8) ? "class" : "className";
    clsval = element.getAttribute(clsname);

    if (clsval)
    {
      clsval = clsval.replace(regexp, "");
      element.setAttribute(clsname, clsval);
    }
  }
}

function addClass(element, name)
{
  if (!hasClass(element, name))
  {
    if (typeof element.className != 'undefined')
      element.className += " " + name;
    else
    {
      var clsname = (ns_pos||ie8) ? "class" : "className";
      var clsval = element.getAttribute(clsname);
      clsval = clsval ? clsval + " " + name : name;
      element.setAttribute(clsname, clsval);
    }
  }
}

// wysiwyg editors make it hard to use div elements
// e.g. amaya loses the div when you copy and paste
// this function wraps div elements around implicit
// slides which start with an h1 element and continue
// up to the next heading or div element
function wrapImplicitSlides()
{
   var i, heading, node, next, div;
   var headings = document.getElementsByTagName("h1");

   if (!headings)
     return;

   for (i = 0; i < headings.length; ++i)
   {
      heading = headings[i];

      if (heading.parentNode != document.body)
         continue;

      node = heading.nextSibling;

      div = document.createElement("div");
      addClass(div, "slide");
      document.body.replaceChild(div, heading);
      div.appendChild(heading);

      while (node)
      {
         if (node.nodeType == 1 &&    // an element
                  (node.nodeName == "H1" ||
                   node.nodeName == "h1" ||
                   node.nodeName == "DIV" ||
                   node.nodeName == "div"))
            break;

         next = node.nextSibling;
         node = document.body.removeChild(node);
         div.appendChild(node);
         node = next;
      } 
   }
}

// return new array of all slides
function collectSlides()
{
   var slides = new Array();
   var divs = document.body.getElementsByTagName("div");

   for (var i = 0; i < divs.length; ++i)
   {
      div = divs.item(i);

      if (hasClass(div, "slide"))
      {
         // add slide to collection
         slides[slides.length] = div;

         // hide each slide as it is found
         div.style.display = "none";
         div.style.visibility = "hidden";

         // add dummy <br/> at end for scrolling hack
         var node1 = document.createElement("br");
         div.appendChild(node1);
         var node2 = document.createElement("br");
         div.appendChild(node2);
      }
      else if (hasClass(div, "background"))
      {  // work around for Firefox SVG reload bug
         // which otherwise replaces 1st SVG graphic with 2nd
         div.style.display = "block";
      }
   }

   return slides;
}

// return new array of all <div class="handout">
function collectNotes()
{
   var notes = new Array();
   var divs = document.body.getElementsByTagName("div");

   for (var i = 0; i < divs.length; ++i)
   {
      div = divs.item(i);

      if (hasClass(div, "handout"))
      {
         // add slide to collection
         notes[notes.length] = div;

         // hide handout notes as they are found
         div.style.display = "none";
         div.style.visibility = "hidden";
      }
   }

   return notes;
}

// return new array of all <div class="background">
// including named backgrounds e.g. class="background titlepage"
function collectBackgrounds()
{
   var backgrounds = new Array();
   var divs = document.body.getElementsByTagName("div");

   for (var i = 0; i < divs.length; ++i)
   {
      div = divs.item(i);

      if (hasClass(div, "background"))
      {
         // add slide to collection
         backgrounds[backgrounds.length] = div;

         // hide named backgrounds as they are found
         // e.g. class="background epilog"
         if (getClassList(div) != "background")
         {
            div.style.display = "none";
            div.style.visibility = "hidden";
         }
      }
   }

   return backgrounds;
}

// show just the backgrounds pertinent to this slide
function syncBackground(slide)
{
   var background;
   var bgColor;

   if (slide.currentStyle)
      bgColor = slide.currentStyle["backgroundColor"];
   else if (document.defaultView)
   {
      var styles = document.defaultView.getComputedStyle(slide,null);

      if (styles)
          bgColor = styles.getPropertyValue("background-color");
      else // broken implementation probably due Safari or Konqueror
      {
          //alert("defective implementation of getComputedStyle()");
          bgColor = "transparent";
      }
   }
   else
      bgColor == "transparent";

   if (bgColor == "transparent")
   {
      var slideClass = getClassList(slide);

      for (var i = 0; i < backgrounds.length; i++)
      {
         background = backgrounds[i];

         var bgClass = getClassList(background);

         if (matchingBackground(slideClass, bgClass))
         {
            background.style.display = "block";
            background.style.visibility = "visible";
         }
         else
         {
            background.style.display = "none";
            background.style.visibility = "hidden";
         }
      }
   }
   else // forcibly hide all backgrounds
      hideBackgrounds();
}

function hideBackgrounds()
{
   for (var i = 0; i < backgrounds.length; i++)
   {
      background = backgrounds[i];
      background.style.display = "none";
      background.style.visibility = "hidden";
   }
}

// compare classes for slide and background
function matchingBackground(slideClass, bgClass)
{
   if (bgClass == "background")
      return true;

   // define pattern as regular expression
   var pattern = /\w+/g;

   // check for matches and place result in array
   var result = slideClass.match(pattern);

   // now check if desired name is present for background
   for (var i = 0; i < result.length; i++)
   {
      if (hasToken(bgClass, result[i]))
         return true;
   }

   return false;
}

// left to right traversal of root's content
function nextNode(root, node)
{
   if (node == null)
      return root.firstChild;

   if (node.firstChild)
      return node.firstChild;

   if (node.nextSibling)
      return node.nextSibling;

   for (;;)
   {
      node = node.parentNode;

      if (!node || node == root)
         break;

      if (node && node.nextSibling)
         return node.nextSibling;
   }

   return null;
}

// right to left traversal of root's content
function previousNode(root, node)
{
   if (node == null)
   {
      node = root.lastChild;

      if (node)
      {
         while (node.lastChild)
            node = node.lastChild;
      }

      return node;
   }

   if (node.previousSibling)
   {
      node = node.previousSibling;

      while (node.lastChild)
         node = node.lastChild;

      return node;
   }

   if (node.parentNode != root)
      return node.parentNode;

   return null;
}

// HTML elements that can be used with class="incremental"
// note that you can also put the class on containers like
// up, ol, dl, and div to make their contents appear
// incrementally. Upper case is used since this is what
// browsers report for HTML node names (text/html).
function incrementalElementList()
{
   var inclist = new Array();
   inclist["P"] = true;
   inclist["PRE"] = true;
   inclist["LI"] = true;
   inclist["BLOCKQUOTE"] = true;
   inclist["DT"] = true;
   inclist["DD"] = true;
   inclist["H2"] = true;
   inclist["H3"] = true;
   inclist["H4"] = true;
   inclist["H5"] = true;
   inclist["H6"] = true;
   inclist["SPAN"] = true;
   inclist["ADDRESS"] = true;
   inclist["TABLE"] = true;
   inclist["TR"] = true;
   inclist["TH"] = true;
   inclist["TD"] = true;
   inclist["IMG"] = true;
   inclist["OBJECT"] = true;
   return inclist;
}

function nextIncrementalItem(node)
{
   var slide = slides[slidenum];

   for (;;)
   {
      node = nextNode(slide, node);

      if (node == null || node.parentNode == null)
         break;

      if (node.nodeType == 1)  // ELEMENT
      {
         if (node.nodeName == "BR")
           continue;

         if (hasClass(node, "incremental")
             && okayForIncremental[node.nodeName])
            return node;

         if (hasClass(node.parentNode, "incremental")
             && !hasClass(node, "non-incremental"))
            return node;
      }
   }

   return node;
}

function previousIncrementalItem(node)
{
   var slide = slides[slidenum];

   for (;;)
   {
      node = previousNode(slide, node);

      if (node == null || node.parentNode == null)
         break;

      if (node.nodeType == 1)
      {
         if (node.nodeName == "BR")
           continue;

         if (hasClass(node, "incremental")
             && okayForIncremental[node.nodeName])
            return node;

         if (hasClass(node.parentNode, "incremental")
             && !hasClass(node, "non-incremental"))
            return node;
      }
   }

   return node;
}

// set visibility for all elements on current slide with
// a parent element with attribute class="incremental"
function setVisibilityAllIncremental(value)
{
   var node = nextIncrementalItem(null);

   while (node)
   {
      node.style.visibility = value;
      node = nextIncrementalItem(node);
   }
}

// reveal the next hidden item on the slide
// node is null or the node that was last revealed
function revealNextItem(node)
{
   node = nextIncrementalItem(node);

   if (node && node.nodeType == 1)  // an element
      node.style.visibility = "visible";

   return node;
}


// exact inverse of revealNextItem(node)
function hidePreviousItem(node)
{
   if (node && node.nodeType == 1)  // an element
      node.style.visibility = "hidden";

   return previousIncrementalItem(node);
}


/* set click handlers on all anchors */
function patchAnchors()
{
   var anchors = document.body.getElementsByTagName("a");

   for (var i = 0; i < anchors.length; ++i)
   {
      anchors[i].onclick = clickedAnchor;
   }
}

function clickedAnchor(e)
{
   if (!e)
      var e = window.event;

   // compare this.href with location.href
   // for link to another slide in this doc

   if (pageAddress(this.href) == pageAddress(location.href))
   {
      // yes, so find new slide number
      var newslidenum = findSlideNumber(this.href);

      if (newslidenum != slidenum)
      {
         slide = slides[slidenum];
         hideSlide(slide);
         slidenum = newslidenum;
         slide = slides[slidenum];
         showSlide(slide);
         setLocation();
      }
   }
   else if (this.target == null)
      location.href = this.href;

   this.blur();
   stopPropagation(e);
}

function pageAddress(uri)
{
   var i = uri.indexOf("#");

   if (i < 0)
     i = uri.indexOf("%23");

   // check if anchor is entire page

   if (i < 0)
      return uri;  // yes

   return uri.substr(0, i);
}

function showSlideNumber()
{
   slideNumElement.innerHTML = "slide".localize() + " " +
           (slidenum + 1) + "/" + slides.length;
}

// every 200mS check if the location has been changed as a
// result of the user activating the Back button/menu item
// doesn't work for Opera < 9.5
function checkLocation()
{
  var hash = location.hash;

  if (slidenum > 0 && (hash == "" || hash == "#"))
    gotoSlide(0);
  else if (hash.length > 2 && hash != "#("+(slidenum+1)+")")
  {
    var num = parseInt(location.hash.substr(2));

    if (!isNaN(num))
      gotoSlide(num-1);
  }
}

// this doesn't push location onto history stack for IE
// for which a hidden iframe hack is needed: load page into
// the iframe with script that set's parent's location.hash
// but that won't work for standalone use unless we can
// create the page dynamically via a javascript: URL
function setLocation()
{
   var uri = pageAddress(location.href);
   var hash = "#(" + (slidenum+1) + ")";

   if (slidenum >= 0)
      uri = uri + hash;

   if (ie && !ie8)
      pushHash(hash);

   if (uri != location.href /*&& !khtml */)
      location.href = uri;

   if (khtml)
      hash = "(" + (slidenum+1) + ")";

   if (!ie && location.hash != hash && location.hash != "")
     location.hash = hash;

   document.title = title + " (" + (slidenum+1) + ")";
   showSlideNumber();
}

// only used for IE6 and IE7
function onFrameLoaded(hash)
{
  location.hash = hash;
  var uri = pageAddress(location.href);
  location.href = uri + hash;
}

// history hack with thanks to Bertrand Le Roy
function pushHash(hash)
{
  if (hash == "") hash = "#(1)";
  window.location.hash = hash;
  var doc = document.getElementById("historyFrame").contentWindow.document;
  doc.open("javascript:'<html></html>'");
  doc.write("<html><head><script type=\"text/javascript\">parent.onFrameLoaded('"+
    (hash) + "');</script></head><body>hello mum</body></html>");
  doc.close();
}

// find current slide based upon location
// first find target anchor and then look
// for associated div element enclosing it
// finally map that to slide number
function findSlideNumber(uri)
{
   // first get anchor from page location

   var i = uri.indexOf("#");

   // check if anchor is entire page

   if (i < 0)
      return 0;  // yes

   var anchor = unescape(uri.substr(i+1));

   // now use anchor as XML ID to find target
   var target = document.getElementById(anchor);

   if (!target)
   {
      // does anchor look like "(2)" for slide 2 ??
      // where first slide is (1)
      var re = /\((\d)+\)/;

      if (anchor.match(re))
      {
         var num = parseInt(anchor.substring(1, anchor.length-1));

         if (num > slides.length)
            num = 1;

         if (--num < 0)
            num = 0;

         return num;
      }

      // accept [2] for backwards compatibility
      re = /\[(\d)+\]/;

      if (anchor.match(re))
      {
         var num = parseInt(anchor.substring(1, anchor.length-1));

         if (num > slides.length)
            num = 1;

         if (--num < 0)
            num = 0;

         return num;
      }

      // oh dear unknown anchor
      return 0;
   }

   // search for enclosing slide

   while (true)
   {
      // browser coerces html elements to uppercase!
      if (target.nodeName.toLowerCase() == "div" &&
            hasClass(target, "slide"))
      {
         // found the slide element
         break;
      }

      // otherwise try parent element if any

      target = target.parentNode;

      if (!target)
      {
         return 0;   // no luck!
      }
   };

   for (i = 0; i < slides.length; ++i)
   {
      if (slides[i] == target)
         return i;  // success
   }

   // oh dear still no luck
   return 0;
}

// find slide name from first h1 element
// default to document title + slide number
function slideName(index)
{
   var name = null;
   var slide = slides[index];

   var heading = findHeading(slide);

   if (heading)
     name = extractText(heading);

   if (!name)
     name = title + "(" + (index + 1) + ")";

   name.replace(/\&/g, "&amp;");
   name.replace(/\</g, "&lt;");
   name.replace(/\>/g, "&gt;");

   return name;
}

// find first h1 element in DOM tree
function findHeading(node)
{  if (!node || node.nodeType != 1)
    return null;

  if (node.nodeName == "H1" || node.nodeName == "h1")
    return node;

  var child = node.firstChild;

  while (child)
  {
    node = findHeading(child);

    if (node)
      return node;

    child = child.nextSibling;
  }

  return null;
}

// recursively extract text from DOM tree
function extractText(node)
{
  if (!node)
    return "";

  // text nodes
  if (node.nodeType == 3)
    return node.nodeValue;

  // elements
  if (node.nodeType == 1)
  {
    node = node.firstChild;
    var text = "";

    while (node)
    {
      text = text + extractText(node);
      node = node.nextSibling;
    }

    return text;
  }

  return "";
}


// find copyright text from meta element
function findCopyright()
{
   var name, content;
   var meta = document.getElementsByTagName("meta");

   for (var i = 0; i < meta.length; ++i)
   {
      name = meta[i].getAttribute("name");
      content = meta[i].getAttribute("content");

      if (name == "copyright")
         return content;
   }

   return null;
}

function findSizeAdjust()
{
   var name, content, offset;
   var meta = document.getElementsByTagName("meta");

   for (var i = 0; i < meta.length; ++i)
   {
      name = meta[i].getAttribute("name");
      content = meta[i].getAttribute("content");

      if (name == "font-size-adjustment")
         return 1 * content;
   }

   return 1;
}

function addToolbar()
{
   var slideCounter, page;

   var toolbar = createElement("div");
   toolbar.setAttribute("class", "toolbar");

   if (ns_pos) // a reasonably behaved browser
   {
      var right = document.createElement("div");
      right.setAttribute("style", "float: right; text-align: right");

      slideCounter = document.createElement("div")
      slideCounter.innerHTML = "slide".localize() + " n/m";
      right.appendChild(slideCounter);
      toolbar.appendChild(right);

      var left = document.createElement("div");
      left.setAttribute("style", "text-align: left");

      // global end of slide indicator
      eos = document.createElement("span");
      eos.innerHTML = "* ";
      left.appendChild(eos);

      var help = document.createElement("a");
      help.setAttribute("href", helpPage);
      help.setAttribute("title", helpText.localize());
      help.innerHTML = "help?".localize();
      left.appendChild(help);
      helpAnchor = help;  // save for focus hack

      var gap1 = document.createTextNode(" ");
      left.appendChild(gap1);

      var contents = document.createElement("a");
      contents.setAttribute("href", "javascript:toggleTableOfContents()");
      contents.setAttribute("title", "table of contents".localize());
      contents.innerHTML = "contents?".localize();
      left.appendChild(contents);

      var gap2 = document.createTextNode(" ");
      left.appendChild(gap2);

      var start = document.createElement("a");
      start.setAttribute("href", "javascript:firstSlide()");
      start.setAttribute("title", "restart presentation".localize());
      start.innerHTML = "restart?".localize();
//    start.setAttribute("href", "javascript:printSlides()");
//    start.setAttribute("title", "print all slides".localize());
//    start.innerHTML = "print!".localize();
      left.appendChild(start);

      var copyright = findCopyright();

      if (copyright)
      {
         var span = document.createElement("span");
         span.innerHTML = copyright;
         span.style.color = "black";
         span.style.marginLeft = "4em";
         left.appendChild(span);
      }

      toolbar.appendChild(left);
   }
   else // IE so need to work around its poor CSS support
   {
      toolbar.style.position = (ie7 ? "fixed" : "absolute");
      toolbar.style.zIndex = "200";
      toolbar.style.width = "99.9%";
      toolbar.style.height = "1.2em";
      toolbar.style.top = "auto";
      toolbar.style.bottom = "0";
      toolbar.style.left = "0";
      toolbar.style.right = "0";
      toolbar.style.textAlign = "left";
      toolbar.style.fontSize = "60%";
      toolbar.style.color = "red";
      toolbar.borderWidth = 0;
      toolbar.style.background = "rgb(240,240,240)";

      // would like to have help text left aligned
      // and page counter right aligned, floating
      // div's don't work, so instead use nested
      // absolutely positioned div's.

      var sp = document.createElement("span");
      sp.innerHTML = "&nbsp;&nbsp;*&nbsp;";
      toolbar.appendChild(sp);
      eos = sp;  // end of slide indicator

      var help = document.createElement("a");
      help.setAttribute("href", helpPage);
      help.setAttribute("title", helpText.localize());
      help.innerHTML = "help?".localize();
      toolbar.appendChild(help);
      helpAnchor = help;  // save for focus hack

      var gap1 = document.createTextNode(" ");
      toolbar.appendChild(gap1);

      var contents = document.createElement("a");
      contents.setAttribute("href", "javascript:toggleTableOfContents()");
      contents.setAttribute("title", "table of contents".localize());
      contents.innerHTML = "contents?".localize();
      toolbar.appendChild(contents);

      var gap2 = document.createTextNode(" ");
      toolbar.appendChild(gap2);

      var start = document.createElement("a");
      start.setAttribute("href", "javascript:firstSlide()");
      start.setAttribute("title", "restart presentation".localize());
      start.innerHTML = "restart?".localize();
//    start.setAttribute("href", "javascript:printSlides()");
//    start.setAttribute("title", "print all slides".localize());
//    start.innerHTML = "print!".localize();
      toolbar.appendChild(start);

      var copyright = findCopyright();

      if (copyright)
      {
         var span = document.createElement("span");
         span.innerHTML = copyright;
         span.style.color = "black";
         span.style.marginLeft = "2em";
         toolbar.appendChild(span);
      }

      slideCounter = document.createElement("div")
      slideCounter.style.position = "absolute";
      slideCounter.style.width = "auto"; //"20%";
      slideCounter.style.height = "1.2em";
      slideCounter.style.top = "auto";
      slideCounter.style.bottom = 0;
      slideCounter.style.right = "0";
      slideCounter.style.textAlign = "right";
      slideCounter.style.color = "red";
      slideCounter.style.background = "rgb(240,240,240)";

      slideCounter.innerHTML = "slide".localize() + " n/m";
      toolbar.appendChild(slideCounter);
   }

   // ensure that click isn't passed through to the page
   toolbar.onclick = stopPropagation;
   document.body.appendChild(toolbar);
   slideNumElement = slideCounter;
   setEosStatus(false);

   return toolbar;
}

function isShownToc()
{
  if (toc && toc.style.visible == "visible")
    return true;

  return false;
}

function showTableOfContents()
{
  if (toc)
  {
    if (toc.style.visibility != "visible")
    {
      toc.style.visibility = "visible";
      toc.style.display = "block";
      toc.focus();

      if (ie7 && slidenum == 0)
        setTimeout("ieHack()", 100);
    }
    else
      hideTableOfContents();
  }
}

function hideTableOfContents()
{
  if (toc && toc.style.visibility != "hidden")
  {
    toc.style.visibility = "hidden";
    toc.style.display = "none";

    try
    {
       if (!opera)
         helpAnchor.focus();
    }
    catch (e)
    {
    }
  }
}

function toggleTableOfContents()
{
  if (toc)
  {
     if (toc.style.visible != "visible")
       showTableOfContents();
     else
       hideTableOfContents();
  }
}

// called on clicking toc entry
function gotoEntry(e)
{
   var target;

   if (!e)
      var e = window.event;

   if (e.target)
      target = e.target;
   else if (e.srcElement)
      target = e.srcElement;

   // work around Safari bug
   if (target.nodeType == 3)
      target = target.parentNode;

   if (target && target.nodeType == 1)
   {
     var uri = target.getAttribute("href");

     if (uri)
     {
        //alert("going to " + uri);
        var slide = slides[slidenum];
        hideSlide(slide);
        slidenum = findSlideNumber(uri);
        slide = slides[slidenum];
        lastShown = null;
        setLocation();
        setVisibilityAllIncremental("hidden");
        setEosStatus(!nextIncrementalItem(lastShown));
        showSlide(slide);
        //target.focus();

        try
        {
           if (!opera)
             helpAnchor.focus();
        }
        catch (e)
        {
        }
     }
   }

   hideTableOfContents(e);
   if (ie7) ieHack();
   stopPropagation(e);
   return cancel(e);
}

// called onkeydown for toc entry
function gotoTocEntry(event)
{
  var key;

  if (!event)
    var event = window.event;

  // kludge around NS/IE differences 
  if (window.event)
    key = window.event.keyCode;
  else if (event.which)
    key = event.which;
  else
    return true; // Yikes! unknown browser

  // ignore event if key value is zero
  // as for alt on Opera and Konqueror
  if (!key)
     return true;

  // check for concurrent control/command/alt key
  // but are these only present on mouse events?

  if (event.ctrlKey || event.altKey)
     return true;

  if (key == 13)
  {
    var uri = this.getAttribute("href");

    if (uri)
    {
      //alert("going to " + uri);
      var slide = slides[slidenum];
      hideSlide(slide);
      slidenum = findSlideNumber(uri);
      slide = slides[slidenum];
      lastShown = null;
      setLocation();
      setVisibilityAllIncremental("hidden");
      setEosStatus(!nextIncrementalItem(lastShown));
      showSlide(slide);
      //target.focus();

      try
      {
         if (!opera)
           helpAnchor.focus();
      }
      catch (e)
      {
      }
    }

    hideTableOfContents();
    if (ie7) ieHack();
    return cancel(event);
  }

  if (key == 40 && this.next)
  {
    this.next.focus();
    return cancel(event);
  }

  if (key == 38 && this.previous)
  {
    this.previous.focus();
    return cancel(event);
  }

  return true;
}

function isTitleSlide(slide)
{
   return hasClass(slide, "title");
}

// create div element with links to each slide
function tableOfContents()
{
  var toc = document.createElement("div");
  addClass(toc, "toc");
  //toc.setAttribute("tabindex", "0");

  var heading = document.createElement("div");
  addClass(heading, "toc-heading");
  heading.innerHTML = "Table of Contents".localize();

  heading.style.textAlign = "center";
  heading.style.width = "100%";
  heading.style.margin = "0";
  heading.style.marginBottom = "1em";
  heading.style.borderBottomStyle = "solid";
  heading.style.borderBottomColor = "rgb(180,180,180)";
  heading.style.borderBottomWidth = "1px";

  toc.appendChild(heading);
  var previous = null;

  for (var i = 0; i < slides.length; ++i)
  {
    var title = hasClass(slides[i], "title");
    var num = document.createTextNode((i + 1) + ". ");

    toc.appendChild(num);

    var a = document.createElement("a");
    a.setAttribute("href", "#(" + (i+1) + ")");

    if (title)
      addClass(a, "titleslide");

    var name = document.createTextNode(slideName(i));
    a.appendChild(name);
    a.onclick = gotoEntry;
    a.onkeydown = gotoTocEntry;
    a.previous = previous;

    if (previous)
      previous.next = a;

    toc.appendChild(a);

    if (i == 0)
      toc.first = a;

    if (i < slides.length - 1)
    {
      var br = document.createElement("br");
      toc.appendChild(br);
    }

    previous = a;
  }

  toc.focus = function () {
    if (this.first)
      this.first.focus();
  }

  toc.onmouseup = mouseButtonUp;

  toc.onclick = function (e) {
    e||(e=window.event);

    if (selectedTextLen <= 0)
       hideTableOfContents();

    stopPropagation(e);
    
    if (e.cancel != undefined)
      e.cancel = true;
      
    if (e.returnValue != undefined)
      e.returnValue = false;
      
    return false;
  };

  toc.style.position = "absolute";
  toc.style.zIndex = "300";
  toc.style.width = "60%";
  toc.style.maxWidth = "30em";
  toc.style.height = "30em";
  toc.style.overflow = "auto";
  toc.style.top = "auto";
  toc.style.right = "auto";
  toc.style.left = "4em";
  toc.style.bottom = "4em";
  toc.style.padding = "1em";
  toc.style.background = "rgb(240,240,240)";
  toc.style.borderStyle = "solid";
  toc.style.borderWidth = "2px";
  toc.style.fontSize = "60%";

  document.body.insertBefore(toc, document.body.firstChild);
  return toc;
}

function replaceByNonBreakingSpace(str)
{
   for (var i = 0; i < str.length; ++i)
      str[i] = 160;
}


function initOutliner()
{
  var items = document.getElementsByTagName("LI");

  for (var i = 0; i < items.length; ++i)
  {
     var target = items[i];

     if (!hasClass(target.parentNode, "outline"))
        continue;

     target.onclick = outlineClick;

     if (!ns_pos)
     {
        target.onmouseover = hoverOutline;
        target.onmouseout = unhoverOutline;
     }

     if (foldable(target))
     {
       target.foldable = true;
       target.onfocus = function () {outline = this;};
       target.onblur = function () {outline = null;};

       if (!target.getAttribute("tabindex"))
         target.setAttribute("tabindex", "0");

       if (hasClass(target, "expand"))
         unfold(target);
       else
         fold(target);
     }
     else
     {
       addClass(target, "nofold");
       target.visible = true;
       target.foldable = false;
     }
  }
}

function foldable(item)
{
   if (!item || item.nodeType != 1)
      return false;

   var node = item.firstChild;

   while (node)
   {
     if (node.nodeType == 1 && isBlock(node))
       return true;

      node = node.nextSibling;
   }

   return false;
}

function fold(item)
{
  if (item)
  {
    removeClass(item, "unfolded");
    addClass(item, "folded");
  }

  var node = item ? item.firstChild : null;

  while (node)
  {
    if (node.nodeType == 1 && isBlock(node)) // element
    {
      // note that getElementStyle won't work for Safari 1.3
      node.display = getElementStyle(node, "display", "display");
      node.style.display = "none";
      node.style.visibility = "hidden";
    }

    node = node.nextSibling;
  }

  item.visible = false;
}

function unfold(item)
{
   if (item)
   {
     addClass(item, "unfolded");
     removeClass(item, "folded");
   }

  var node = item ? item.firstChild : null;

  while (node)
  {
    if (node.nodeType == 1 && isBlock(node)) // element
    {
      // with fallback for Safari, see above
      node.style.display = (node.display ? node.display : "block");
      node.style.visibility = "visible";
    }

    node = node.nextSibling;
  }

  item.visible = true;
}

function outlineClick(e)
{
   var rightclick = false;
   var target;

   if (!e)
      var e = window.event;

   if (e.target)
      target = e.target;
   else if (e.srcElement)
      target = e.srcElement;

   // work around Safari bug
   if (target.nodeType == 3)
      target = target.parentNode;

   while (target && target.visible == undefined)
      target = target.parentNode;

   if (!target)
      return true;

   if (e.which)
      rightclick = (e.which == 3);
   else if (e.button)
      rightclick = (e.button == 2);

   if (!rightclick && target.visible != undefined)
   {
      if (target.foldable)
      {
         if (target.visible)
           fold(target);
         else
           unfold(target);
      }

      stopPropagation(e);
      e.cancel = true;
      e.returnValue = false;
   }

   return false;
}

function hoverOutline(e)
{
   var target;

   if (!e)
      var e = window.event;

   if (e.target)
      target = e.target;
   else if (e.srcElement)
      target = e.srcElement;

   // work around Safari bug
   if (target.nodeType == 3)
      target = target.parentNode;

   while (target && target.visible == undefined)
      target = target.parentNode;

   if (target && target.foldable)
      target.style.cursor = "pointer";

   return true;
}

function unhoverOutline(e)
{
   var target;

   if (!e)
      var e = window.event;

   if (e.target)
      target = e.target;
   else if (e.srcElement)
      target = e.srcElement;

   // work around Safari bug
   if (target.nodeType == 3)
      target = target.parentNode;

   while (target && target.visible == undefined)
      target = target.parentNode;

   if (target)
     target.style.cursor = "default";

   return true;
}


function stopPropagation(e)
{
   if (window.event)
   {
      window.event.cancelBubble = true;
      //window.event.returnValue = false;
   }
   else if (e)
   {
      e.cancelBubble = true;
      e.stopPropagation();
      //e.preventDefault();
   }
}

/* can't rely on display since we set that to none to hide things */
function isBlock(elem)
{
   var tag = elem.nodeName;

   return tag == "OL" || tag == "UL" || tag == "P" ||
          tag == "LI" || tag == "TABLE" || tag == "PRE" ||
          tag == "H1" || tag == "H2" || tag == "H3" ||
          tag == "H4" || tag == "H5" || tag == "H6" ||
          tag == "BLOCKQUOTE" || tag == "ADDRESS"; 
}

function getElementStyle(elem, IEStyleProp, CSSStyleProp)
{
   if (elem.currentStyle)
   {
      return elem.currentStyle[IEStyleProp];
   }
   else if (window.getComputedStyle)
   {
      var compStyle = window.getComputedStyle(elem, "");
      return compStyle.getPropertyValue(CSSStyleProp);
   }
   return "";
}

// works with text/html and text/xhtml+xml with thanks to Simon Willison
function createElement(element)
{
   if (typeof document.createElementNS != 'undefined')
   {
      return document.createElementNS('http://www.w3.org/1999/xhtml', element);
   }

   if (typeof document.createElement != 'undefined')
   {
      return document.createElement(element);
   }

   return false;
}

// designed to work with both text/html and text/xhtml+xml
function getElementsByTagName(name)
{
   if (typeof document.getElementsByTagNameNS != 'undefined')
   {
      return document.getElementsByTagNameNS('http://www.w3.org/1999/xhtml', name);
   }

   if (typeof document.getElementsByTagName != 'undefined')
   {
      return document.getElementsByTagName(name);
   }

   return null;
}

/*
// clean alternative to innerHTML method, but on IE6
// it doesn't work with named entities like &nbsp;
// which need to be replaced by numeric entities
function insertText(element, text)
{
   try
   {
     element.textContent = text;  // DOM3 only
   }
   catch (e)
   {
      if (element.firstChild)
      {
         // remove current children
         while (element.firstChild)
            element.removeChild(element.firstChild);
      }

      element.appendChild(document.createTextNode(text));
   }
}

// as above, but as method of all element nodes
// doesn't work in IE6 which doesn't allow you to
// add methods to the HTMLElement prototype
if (HTMLElement != undefined)
{
  HTMLElement.prototype.insertText = function(text) {
    var element = this;

    try
    {
      element.textContent = text;  // DOM3 only
    }
    catch (e)
    {
      if (element.firstChild)
      {
         // remove current children
         while (element.firstChild)
           element.removeChild(element.firstChild);
      }

      element.appendChild(document.createTextNode(text));
    }
  };
}
*/

function getSelectedText()
{
  try
  {
    if (window.getSelection)
      return window.getSelection().toString();

    if (document.getSelection)
      return document.getSelection().toString();

    if (document.selection)
      return document.selection.createRange().text;
  }
  catch (e)
  {
    return "";
  }
  return "";
}