Code

Extensions. Shebangs branch merge.
[inkscape.git] / share / extensions / jessyInk.js
1 // Copyright 2008, 2009 Hannes Hochreiner
2 // This program is free software: you can redistribute it and/or modify
3 // it under the terms of the GNU General Public License as published by
4 // the Free Software Foundation, either version 3 of the License, or
5 // (at your option) any later version.
6 //
7 // This program is distributed in the hope that it will be useful,
8 // but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10 // GNU General Public License for more details.
11 //
12 // You should have received a copy of the GNU General Public License
13 // along with this program.  If not, see http://www.gnu.org/licenses/.
15 // Set onload event handler.
16 window.onload = jessyInkInit;
18 // Creating a namespace dictionary. The standard Inkscape namespaces are taken from inkex.py.
19 var NSS = new Object();
20 NSS['sodipodi']='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd';
21 NSS['cc']='http://web.resource.org/cc/';
22 NSS['svg']='http://www.w3.org/2000/svg';
23 NSS['dc']='http://purl.org/dc/elements/1.1/';
24 NSS['rdf']='http://www.w3.org/1999/02/22-rdf-syntax-ns#';
25 NSS['inkscape']='http://www.inkscape.org/namespaces/inkscape';
26 NSS['xlink']='http://www.w3.org/1999/xlink';
27 NSS['xml']='http://www.w3.org/XML/1998/namespace';
28 NSS['jessyink']='https://launchpad.net/jessyink';
30 // Keycodes.
31 var LEFT_KEY = 37; // cursor left keycode
32 var UP_KEY = 38; // cursor up keycode
33 var RIGHT_KEY = 39; // cursor right keycode
34 var DOWN_KEY = 40; // cursor down keycode
35 var PAGE_UP_KEY = 33; // page up keycode
36 var PAGE_DOWN_KEY = 34; // page down keycode
37 var HOME_KEY = 36; // home keycode
38 var END_KEY = 35; // end keycode
39 var ENTER_KEY = 13; // next slide
40 var SPACE_KEY = 32;
41 var ESCAPE_KEY = 27;
43 // Presentation modes.
44 var SLIDE_MODE = 1;
45 var INDEX_MODE = 2;
46 var DRAWING_MODE = 3;
48 // Mouse handler actions.
49 var MOUSE_UP = 1;
50 var MOUSE_DOWN = 2;
51 var MOUSE_MOVE = 3;
52 var MOUSE_WHEEL = 4;
54 // Parameters.
55 var ROOT_NODE = document.getElementsByTagNameNS(NSS["svg"], "svg")[0];
56 var HEIGHT = 0;
57 var WIDTH = 0;
58 var INDEX_COLUMNS_DEFAULT = 4;
59 var INDEX_COLUMNS = INDEX_COLUMNS_DEFAULT;
60 var INDEX_OFFSET = 0;
61 var STATE_START = -1;
62 var STATE_END = -2;
63 var BACKGROUND_COLOR = null;
64 var slides = new Array();
66 // Initialisation.
67 var currentMode = SLIDE_MODE;
68 var masterSlide = null;
69 var activeSlide = 0;
70 var activeEffect = 0;
71 var timeStep = 30; // 40 ms equal 25 frames per second.
72 var lastFrameTime = null;
73 var processingEffect = false;
74 var transCounter = 0;
75 var effectArray = 0;
76 var defaultTransitionInDict = new Object();
77 defaultTransitionInDict["name"] = "appear";
78 var defaultTransitionOutDict = new Object();
79 defaultTransitionOutDict["name"] = "appear";
80 var jessyInkInitialised = false;
82 // Initialise char and key code dictionaries.
83 var charCodeDictionary = getDefaultCharCodeDictionary();
84 var keyCodeDictionary = getDefaultKeyCodeDictionary();
86 // Initialise mouse handler dictionary.
87 var mouseHandlerDictionary = getDefaultMouseHandlerDictionary();
89 var progress_bar_visible = false;
90 var timer_elapsed = 0;
91 var timer_start = timer_elapsed;
92 var timer_duration = 15; // 15 minutes
94 var history_counter = 0;
95 var history_original_elements = new Array();
96 var history_presentation_elements = new Array();
98 var mouse_original_path = null;
99 var mouse_presentation_path = null;
100 var mouse_last_x = -1;
101 var mouse_last_y = -1;
102 var mouse_min_dist_sqr = 3 * 3;
103 var path_colour = "red";
104 var path_width_default = 3;
105 var path_width = path_width_default;
106 var path_paint_width = path_width;
108 var number_of_added_slides = 0;
110 /** Initialisation function.
111  *  The whole presentation is set-up in this function.
112  */
113 function jessyInkInit()
115         // Make sure we only execute this code once. Double execution can occur if the onload event handler is set
116         // in the main svg tag as well (as was recommended in earlier versions). Executing this function twice does
117         // not lead to any problems, but it takes more time.
118         if (jessyInkInitialised)
119                 return;
121         // Making the presentation scaleable.
122         var VIEWBOX = ROOT_NODE.getAttribute("viewBox");
124         if (VIEWBOX)
125         {
126                 WIDTH = ROOT_NODE.viewBox.animVal.width;
127                 HEIGHT = ROOT_NODE.viewBox.animVal.height;
128         }
129         else
130         {
131                 HEIGHT = parseFloat(ROOT_NODE.getAttribute("height"));
132                 WIDTH = parseFloat(ROOT_NODE.getAttribute("width"));
133                 ROOT_NODE.setAttribute("viewBox", "0 0 " + WIDTH + " " + HEIGHT);
134         }
136         ROOT_NODE.setAttribute("width", "100%");
137         ROOT_NODE.setAttribute("height", "100%");
139         // Setting the background color.
140         var namedViews = document.getElementsByTagNameNS(NSS["sodipodi"], "namedview");
142         for (var counter = 0; counter < namedViews.length; counter++)
143         {
144                 if (namedViews[counter].hasAttribute("id") && namedViews[counter].hasAttribute("pagecolor"))
145                 {
146                         if (namedViews[counter].getAttribute("id") == "base")
147                         {
148                                 BACKGROUND_COLOR = namedViews[counter].getAttribute("pagecolor");
149                                 var newAttribute = "background-color:" + BACKGROUND_COLOR + ";";
151                                 if (ROOT_NODE.hasAttribute("style"))
152                                         newAttribute += ROOT_NODE.getAttribute("style");
154                                 ROOT_NODE.setAttribute("style", newAttribute);
155                         }
156                 }
157         }
159         // Defining clip-path.
160         var defsNodes = document.getElementsByTagNameNS(NSS["svg"], "defs");
162         if (defsNodes.length > 0)
163         {
164                 var existingClipPath = document.getElementById("jessyInkSlideClipPath");
166                 if (!existingClipPath)
167                 {
168                         var rectNode = document.createElementNS(NSS["svg"], "rect");
169                         var clipPath = document.createElementNS(NSS["svg"], "clipPath");
171                         rectNode.setAttribute("x", 0);
172                         rectNode.setAttribute("y", 0);
173                         rectNode.setAttribute("width", WIDTH);
174                         rectNode.setAttribute("height", HEIGHT);
176                         clipPath.setAttribute("id", "jessyInkSlideClipPath");
177                         clipPath.setAttribute("clipPathUnits", "userSpaceOnUse");
179                         clipPath.appendChild(rectNode);
180                         defsNodes[0].appendChild(clipPath);
181                 }
182         }
184         // Making a list of the slide and finding the master slide.
185         var nodes = document.getElementsByTagNameNS(NSS["svg"], "g");
186         var tempSlides = new Array();
187         var existingJessyInkPresentationLayer = null;
189         for (var counter = 0; counter < nodes.length; counter++)
190         {
191                 if (nodes[counter].getAttributeNS(NSS["inkscape"], "groupmode") && (nodes[counter].getAttributeNS(NSS["inkscape"], "groupmode") == "layer"))
192                 {
193                         if (nodes[counter].getAttributeNS(NSS["inkscape"], "label") && nodes[counter].getAttributeNS(NSS["jessyink"], "masterSlide") == "masterSlide")
194                                 masterSlide = nodes[counter];
195                         else if (nodes[counter].getAttributeNS(NSS["inkscape"], "label") && nodes[counter].getAttributeNS(NSS["jessyink"], "presentationLayer") == "presentationLayer")
196                                 existingJessyInkPresentationLayer = nodes[counter];
197                         else
198                                 tempSlides.push(nodes[counter].getAttribute("id"));
199                 }
200                 else if (nodes[counter].getAttributeNS(NSS['jessyink'], 'element'))
201                 {
202                         handleElement(nodes[counter]);
203                 }
204         }
206         // Hide master slide set default transitions.
207         if (masterSlide)
208         {
209                 masterSlide.style.display = "none";
211                 if (masterSlide.hasAttributeNS(NSS["jessyink"], "transitionIn"))
212                         defaultTransitionInDict = propStrToDict(masterSlide.getAttributeNS(NSS["jessyink"], "transitionIn"));
214                 if (masterSlide.hasAttributeNS(NSS["jessyink"], "transitionOut"))
215                         defaultTransitionOutDict = propStrToDict(masterSlide.getAttributeNS(NSS["jessyink"], "transitionOut"));
216         }
218         if (existingJessyInkPresentationLayer != null)
219         {
220                 existingJessyInkPresentationLayer.parentNode.removeChild(existingJessyInkPresentationLayer);
221         }
223         // Set start slide.
224         var hashObj = new LocationHash(window.location.hash);
226         activeSlide = hashObj.slideNumber;
227         activeEffect = hashObj.effectNumber;
229         if (activeSlide < 0)
230                 activeSlide = 0;
231         else if (activeSlide >= tempSlides.length)
232                 activeSlide = tempSlides.length - 1;
234         var originalNode = document.getElementById(tempSlides[counter]);
236         var JessyInkPresentationLayer = document.createElementNS(NSS["svg"], "g");
237         JessyInkPresentationLayer.setAttributeNS(NSS["inkscape"], "groupmode", "layer");
238         JessyInkPresentationLayer.setAttributeNS(NSS["inkscape"], "label", "JessyInk Presentation Layer");
239         JessyInkPresentationLayer.setAttributeNS(NSS["jessyink"], "presentationLayer", "presentationLayer");
240         JessyInkPresentationLayer.setAttribute("id", "jessyink_presentation_layer");
241         JessyInkPresentationLayer.style.display = "inherit";
242         ROOT_NODE.appendChild(JessyInkPresentationLayer);
244         // Gathering all the information about the transitions and effects of the slides, set the background
245         // from the master slide and substitute the auto-texts.
246         for (var counter = 0; counter < tempSlides.length; counter++)
247         {
248                 var originalNode = document.getElementById(tempSlides[counter]);
249                 originalNode.style.display = "none";
250                 var node = suffixNodeIds(originalNode.cloneNode(true), "_" + counter);
251                 JessyInkPresentationLayer.appendChild(node);
252                 slides[counter] = new Object();
253                 slides[counter]["original_element"] = originalNode;
254                 slides[counter]["element"] = node;
256                 // Set build in transition.
257                 slides[counter]["transitionIn"] = new Object();
259                 var dict;
261                 if (node.hasAttributeNS(NSS["jessyink"], "transitionIn"))
262                         dict = propStrToDict(node.getAttributeNS(NSS["jessyink"], "transitionIn"));
263                 else
264                         dict = defaultTransitionInDict;
266                 slides[counter]["transitionIn"]["name"] = dict["name"];
267                 slides[counter]["transitionIn"]["options"] = new Object();
269                 for (key in dict)
270                         if (key != "name")
271                                 slides[counter]["transitionIn"]["options"][key] = dict[key];
273                 // Set build out transition.
274                 slides[counter]["transitionOut"] = new Object();
276                 if (node.hasAttributeNS(NSS["jessyink"], "transitionOut"))
277                         dict = propStrToDict(node.getAttributeNS(NSS["jessyink"], "transitionOut"));
278                 else
279                         dict = defaultTransitionOutDict;
281                 slides[counter]["transitionOut"]["name"] = dict["name"];
282                 slides[counter]["transitionOut"]["options"] = new Object();
284                 for (key in dict)
285                         if (key != "name")
286                                 slides[counter]["transitionOut"]["options"][key] = dict[key];
288                 // Copy master slide content.
289                 if (masterSlide)
290                 {
291                         var clonedNode = suffixNodeIds(masterSlide.cloneNode(true), "_" + counter);
292                         clonedNode.removeAttributeNS(NSS["inkscape"], "groupmode");
293                         clonedNode.removeAttributeNS(NSS["inkscape"], "label");
294                         clonedNode.style.display = "inherit";
296                         node.insertBefore(clonedNode, node.firstChild);
297                 }
299                 // Setting clip path.
300                 node.setAttribute("clip-path", "url(#jessyInkSlideClipPath)");
302                 // Substitute auto texts.
303                 substituteAutoTexts(node, node.getAttributeNS(NSS["inkscape"], "label"), counter + 1, tempSlides.length);
305                 node.removeAttributeNS(NSS["inkscape"], "groupmode");
306                 node.removeAttributeNS(NSS["inkscape"], "label");
308                 // Set effects.
309                 var tempEffects = new Array();
310                 var groups = new Object();
312                 for (var IOCounter = 0; IOCounter <= 1; IOCounter++)
313                 {
314                         var propName = "";
315                         var dir = 0;
317                         if (IOCounter == 0)
318                         {
319                                 propName = "effectIn";
320                                 dir = 1;
321                         }
322                         else if (IOCounter == 1)
323                         {
324                                 propName = "effectOut";
325                                 dir = -1;
326                         }
328                         var effects = getElementsByPropertyNS(node, NSS["jessyink"], propName);
330                         for (var effectCounter = 0; effectCounter < effects.length; effectCounter++)
331                         {
332                                 var element = document.getElementById(effects[effectCounter]);
333                                 var dict = propStrToDict(element.getAttributeNS(NSS["jessyink"], propName));
335                                 // Put every element that has an effect associated with it, into its own group.
336                                 // Unless of course, we already put it into its own group.
337                                 if (!(groups[element.id]))
338                                 {
339                                         var newGroup = document.createElementNS(NSS["svg"], "g");
341                                         element.parentNode.insertBefore(newGroup, element);
342                                         newGroup.appendChild(element.parentNode.removeChild(element));
343                                         groups[element.id] = newGroup;
344                                 }
346                                 var effectDict = new Object();
348                                 effectDict["effect"] = dict["name"];
349                                 effectDict["dir"] = dir;
350                                 effectDict["element"] = groups[element.id];
352                                 for (var option in dict)
353                                 {
354                                         if ((option != "name") && (option != "order"))
355                                         {
356                                                 if (!effectDict["options"])
357                                                         effectDict["options"] = new Object();
359                                                 effectDict["options"][option] = dict[option];
360                                         }
361                                 }
363                                 if (!tempEffects[dict["order"]])
364                                         tempEffects[dict["order"]] = new Array();
366                                 tempEffects[dict["order"]][tempEffects[dict["order"]].length] = effectDict;
367                         }
368                 }
370                 // Make invisible, but keep in rendering tree to ensure that bounding box can be calculated.
371                 node.setAttribute("opacity",0);
372                 node.style.display = "inherit";
374                 // Create a transform group.
375                 var transformGroup = document.createElementNS(NSS["svg"], "g");
377                 // Add content to transform group.
378                 while (node.firstChild)
379                         transformGroup.appendChild(node.firstChild);
381                 // Transfer the transform attribute from the node to the transform group.
382                 if (node.getAttribute("transform"))
383                 {
384                         transformGroup.setAttribute("transform", node.getAttribute("transform"));
385                         node.removeAttribute("transform");
386                 }
388                 // Create a view group.
389                 var viewGroup = document.createElementNS(NSS["svg"], "g");
391                 viewGroup.appendChild(transformGroup);
392                 slides[counter]["viewGroup"] = node.appendChild(viewGroup);
394                 // Insert background.
395                 if (BACKGROUND_COLOR != null)
396                 {
397                         var rectNode = document.createElementNS(NSS["svg"], "rect");
399                         rectNode.setAttribute("x", 0);
400                         rectNode.setAttribute("y", 0);
401                         rectNode.setAttribute("width", WIDTH);
402                         rectNode.setAttribute("height", HEIGHT);
403                         rectNode.setAttribute("id", "jessyInkBackground" + counter);
404                         rectNode.setAttribute("fill", BACKGROUND_COLOR);
406                         slides[counter]["viewGroup"].insertBefore(rectNode, slides[counter]["viewGroup"].firstChild);
407                 }
409                 // Set views.
410                 var tempViews = new Array();
411                 var views = getElementsByPropertyNS(node, NSS["jessyink"], "view");
412                 var matrixOld = (new matrixSVG()).fromElements(1, 0, 0, 0, 1, 0, 0, 0, 1);
414                 // Set initial view even if there are no other views.
415                 slides[counter]["viewGroup"].setAttribute("transform", matrixOld.toAttribute());
416                 slides[counter].initialView = matrixOld.toAttribute();
418                 for (var viewCounter = 0; viewCounter < views.length; viewCounter++)
419                 {
420                         var element = document.getElementById(views[viewCounter]);
421                         var dict = propStrToDict(element.getAttributeNS(NSS["jessyink"], "view"));
423                         if (dict["order"] == 0)
424                         {
425                                 matrixOld = pointMatrixToTransformation(rectToMatrix(element)).mult((new matrixSVG()).fromSVGMatrix(slides[counter].viewGroup.getScreenCTM()).inv().mult((new matrixSVG()).fromSVGMatrix(element.parentNode.getScreenCTM())).inv());
426                                 slides[counter].initialView = matrixOld.toAttribute();
427                         }
428                         else
429                         {
430                                 var effectDict = new Object();
432                                 effectDict["effect"] = dict["name"];
433                                 effectDict["dir"] = 1;
434                                 effectDict["element"] = slides[counter]["viewGroup"];
435                                 effectDict["order"] = dict["order"];
437                                 for (var option in dict)
438                                 {
439                                         if ((option != "name") && (option != "order"))
440                                         {
441                                                 if (!effectDict["options"])
442                                                         effectDict["options"] = new Object();
444                                                 effectDict["options"][option] = dict[option];
445                                         }
446                                 }
448                                 effectDict["options"]["matrixNew"] = pointMatrixToTransformation(rectToMatrix(element)).mult((new matrixSVG()).fromSVGMatrix(slides[counter].viewGroup.getScreenCTM()).inv().mult((new matrixSVG()).fromSVGMatrix(element.parentNode.getScreenCTM())).inv());
450                                 tempViews[dict["order"]] = effectDict;
451                         }
453                         // Remove element.
454                         element.parentNode.removeChild(element);
455                 }
457                 // Consolidate view array and append it to the effect array.
458                 if (tempViews.length > 0)
459                 {
460                         for (var viewCounter = 0; viewCounter < tempViews.length; viewCounter++)
461                         {
462                                 if (tempViews[viewCounter])
463                                 {
464                                         tempViews[viewCounter]["options"]["matrixOld"] = matrixOld;
465                                         matrixOld = tempViews[viewCounter]["options"]["matrixNew"];
467                                         if (!tempEffects[tempViews[viewCounter]["order"]])
468                                                 tempEffects[tempViews[viewCounter]["order"]] = new Array();
470                                         tempEffects[tempViews[viewCounter]["order"]][tempEffects[tempViews[viewCounter]["order"]].length] = tempViews[viewCounter];
471                                 }
472                         }
473                 }
475                 // Set consolidated effect array.
476                 if (tempEffects.length > 0)
477                 {
478                         slides[counter]["effects"] = new Array();
480                         for (var effectCounter = 0; effectCounter < tempEffects.length; effectCounter++)
481                         {
482                                 if (tempEffects[effectCounter])
483                                         slides[counter]["effects"][slides[counter]["effects"].length] = tempEffects[effectCounter];
484                         }
485                 }
487                 node.setAttribute("onmouseover", "if ((currentMode == INDEX_MODE) && ( activeSlide != " + counter + ")) { indexSetActiveSlide(" + counter + "); };");
489                 // Set visibility for initial state.
490                 if (counter == activeSlide)
491                 {
492                         node.style.display = "inherit";
493                         node.setAttribute("opacity",1);
494                 }
495                 else
496                 {
497                         node.style.display = "none";
498                         node.setAttribute("opacity",0);
499                 }
500         }
502         // Set key handler.
503         var jessyInkObjects = document.getElementsByTagNameNS(NSS["svg"], "g");
505         for (var counter = 0; counter < jessyInkObjects.length; counter++)
506         {
507                 var elem = jessyInkObjects[counter];
509                 if (elem.getAttributeNS(NSS["jessyink"], "customKeyBindings"))
510                 {
511                         if (elem.getCustomKeyBindings != undefined)
512                                 keyCodeDictionary = elem.getCustomKeyBindings();
514                         if (elem.getCustomCharBindings != undefined)
515                                 charCodeDictionary = elem.getCustomCharBindings();
516                 }
517         }
519         // Set mouse handler.
520         var jessyInkMouseHandler = document.getElementsByTagNameNS(NSS["jessyink"], "mousehandler");
522         for (var counter = 0; counter < jessyInkMouseHandler.length; counter++)
523         {
524                 var elem = jessyInkMouseHandler[counter];
526                 if (elem.getMouseHandler != undefined)
527                 {
528                         var tempDict = elem.getMouseHandler();
530                         for (mode in tempDict)
531                         {
532                                 if (!mouseHandlerDictionary[mode])
533                                         mouseHandlerDictionary[mode] = new Object();
535                                 for (handler in tempDict[mode])
536                                         mouseHandlerDictionary[mode][handler] = tempDict[mode][handler];
537                         }
538                 }
539         }
541         // Check effect number.
542         if ((activeEffect < 0) || (!slides[activeSlide].effects))
543         {
544                 activeEffect = 0;
545         }
546         else if (activeEffect > slides[activeSlide].effects.length)
547         {
548                 activeEffect = slides[activeSlide].effects.length;
549         }
551         createProgressBar(JessyInkPresentationLayer);
552         hideProgressBar();
553         setProgressBarValue(activeSlide);
554         setTimeIndicatorValue(0);
555         setInterval("updateTimer()", 1000);
556         setSlideToState(activeSlide, activeEffect);
557         jessyInkInitialised = true;
560 /** Function to subtitute the auto-texts.
561  *
562  *  @param node the node
563  *  @param slideName name of the slide the node is on
564  *  @param slideNumber number of the slide the node is on
565  *  @param numberOfSlides number of slides in the presentation
566  */
567 function substituteAutoTexts(node, slideName, slideNumber, numberOfSlides)
569         var texts = node.getElementsByTagNameNS(NSS["svg"], "tspan");
571         for (var textCounter = 0; textCounter < texts.length; textCounter++)
572         {
573                 if (texts[textCounter].getAttributeNS(NSS["jessyink"], "autoText") == "slideNumber")
574                         texts[textCounter].firstChild.nodeValue = slideNumber;
575                 else if (texts[textCounter].getAttributeNS(NSS["jessyink"], "autoText") == "numberOfSlides")
576                         texts[textCounter].firstChild.nodeValue = numberOfSlides;
577                 else if (texts[textCounter].getAttributeNS(NSS["jessyink"], "autoText") == "slideTitle")
578                         texts[textCounter].firstChild.nodeValue = slideName;
579         }
582 /** Convenience function to get an element depending on whether it has a property with a particular name.
583  *      This function emulates some dearly missed XPath functionality.
584  *
585  *  @param node the node
586  *  @param namespace namespace of the attribute
587  *  @param name attribute name
588  */
589 function getElementsByPropertyNS(node, namespace, name)
591         var elems = new Array();
593         if (node.getAttributeNS(namespace, name))
594                 elems.push(node.getAttribute("id"));
596         for (var counter = 0; counter < node.childNodes.length; counter++)
597         {
598                 if (node.childNodes[counter].nodeType == 1)
599                         elems = elems.concat(getElementsByPropertyNS(node.childNodes[counter], namespace, name));
600         }
602         return elems;
605 /** Function to dispatch the next effect, if there is none left, change the slide.
606  *
607  *  @param dir direction of the change (1 = forwards, -1 = backwards)
608  */
609 function dispatchEffects(dir)
611         if (slides[activeSlide]["effects"] && (((dir == 1) && (activeEffect < slides[activeSlide]["effects"].length)) || ((dir == -1) && (activeEffect > 0))))
612         {
613                 processingEffect = true;
615                 if (dir == 1)
616                 {
617                         effectArray = slides[activeSlide]["effects"][activeEffect];
618                         activeEffect += dir;
619                 }
620                 else if (dir == -1)
621                 {
622                         activeEffect += dir;
623                         effectArray = slides[activeSlide]["effects"][activeEffect];
624                 }
626                 transCounter = 0;
627                 startTime = (new Date()).getTime();
628                 lastFrameTime = null;
629                 effect(dir);
630         }
631         else if (((dir == 1) && (activeSlide < (slides.length - 1))) || (((dir == -1) && (activeSlide > 0))))
632         {
633                 changeSlide(dir);
634         }
637 /** Function to skip effects and directly either put the slide into start or end state or change slides.
638  *
639  *  @param dir direction of the change (1 = forwards, -1 = backwards)
640  */
641 function skipEffects(dir)
643         if (slides[activeSlide]["effects"] && (((dir == 1) && (activeEffect < slides[activeSlide]["effects"].length)) || ((dir == -1) && (activeEffect > 0))))
644         {
645                 processingEffect = true;
647                 if (slides[activeSlide]["effects"] && (dir == 1))
648                         activeEffect = slides[activeSlide]["effects"].length;
649                 else
650                         activeEffect = 0;
652                 if (dir == 1)
653                         setSlideToState(activeSlide, STATE_END);
654                 else
655                         setSlideToState(activeSlide, STATE_START);
657                 processingEffect = false;
658         }
659         else if (((dir == 1) && (activeSlide < (slides.length - 1))) || (((dir == -1) && (activeSlide > 0))))
660         {
661                 changeSlide(dir);
662         }
665 /** Function to change between slides.
666  *
667  *  @param dir direction (1 = forwards, -1 = backwards)
668  */
669 function changeSlide(dir)
671         processingEffect = true;
672         effectArray = new Array();
674         effectArray[0] = new Object();
675         if (dir == 1)
676         {
677                 effectArray[0]["effect"] = slides[activeSlide]["transitionOut"]["name"];
678                 effectArray[0]["options"] = slides[activeSlide]["transitionOut"]["options"];
679                 effectArray[0]["dir"] = -1;
680         }
681         else if (dir == -1)
682         {
683                 effectArray[0]["effect"] = slides[activeSlide]["transitionIn"]["name"];
684                 effectArray[0]["options"] = slides[activeSlide]["transitionIn"]["options"];
685                 effectArray[0]["dir"] = 1;
686         }
687         effectArray[0]["element"] = slides[activeSlide]["element"];
689         activeSlide += dir;
690         setProgressBarValue(activeSlide);
692         effectArray[1] = new Object();
694         if (dir == 1)
695         {
696                 effectArray[1]["effect"] = slides[activeSlide]["transitionIn"]["name"];
697                 effectArray[1]["options"] = slides[activeSlide]["transitionIn"]["options"];
698                 effectArray[1]["dir"] = 1;
699         }
700         else if (dir == -1)
701         {
702                 effectArray[1]["effect"] = slides[activeSlide]["transitionOut"]["name"];
703                 effectArray[1]["options"] = slides[activeSlide]["transitionOut"]["options"];
704                 effectArray[1]["dir"] = -1;
705         }
707         effectArray[1]["element"] = slides[activeSlide]["element"];
709         if (slides[activeSlide]["effects"] && (dir == -1))
710                 activeEffect = slides[activeSlide]["effects"].length;
711         else
712                 activeEffect = 0;
714         if (dir == -1)
715                 setSlideToState(activeSlide, STATE_END);
716         else
717                 setSlideToState(activeSlide, STATE_START);
719         transCounter = 0;
720         startTime = (new Date()).getTime();
721         lastFrameTime = null;
722         effect(dir);
725 /** Function to toggle between index and slide mode.
726 */
727 function toggleSlideIndex()
729         var suspendHandle = ROOT_NODE.suspendRedraw(500);
731         if (currentMode == SLIDE_MODE)
732         {
733                 hideProgressBar();              
734                 INDEX_OFFSET = -1;
735                 indexSetPageSlide(activeSlide);
736                 currentMode = INDEX_MODE;
737         }
738         else if (currentMode == INDEX_MODE)
739         {
740                 for (var counter = 0; counter < slides.length; counter++)
741                 {
742                         slides[counter]["element"].setAttribute("transform","scale(1)");
744                         if (counter == activeSlide)
745                         {
746                                 slides[counter]["element"].style.display = "inherit";
747                                 slides[counter]["element"].setAttribute("opacity",1);
748                                 activeEffect = 0;
749                         }
750                         else
751                         {
752                                 slides[counter]["element"].setAttribute("opacity",0);
753                                 slides[counter]["element"].style.display = "none";
754                         }
755                 }
756                 currentMode = SLIDE_MODE;
757                 setSlideToState(activeSlide, STATE_START);
758                 setProgressBarValue(activeSlide);
760                 if (progress_bar_visible)
761                 {
762                         showProgressBar();
763                 }
764         }
766         ROOT_NODE.unsuspendRedraw(suspendHandle);
767         ROOT_NODE.forceRedraw();
770 /** Function to run an effect.
771  *
772  *  @param dir direction in which to play the effect (1 = forwards, -1 = backwards)
773  */
774 function effect(dir)
776         var done = true;
778         var suspendHandle = ROOT_NODE.suspendRedraw(200);
780         for (var counter = 0; counter < effectArray.length; counter++)
781         {
782                 if (effectArray[counter]["effect"] == "fade")
783                         done &= fade(parseInt(effectArray[counter]["dir"]) * dir, effectArray[counter]["element"], transCounter, effectArray[counter]["options"]);
784                 else if (effectArray[counter]["effect"] == "appear")
785                         done &= appear(parseInt(effectArray[counter]["dir"]) * dir, effectArray[counter]["element"], transCounter, effectArray[counter]["options"]);
786                 else if (effectArray[counter]["effect"] == "pop")
787                         done &= pop(parseInt(effectArray[counter]["dir"]) * dir, effectArray[counter]["element"], transCounter, effectArray[counter]["options"]);
788                 else if (effectArray[counter]["effect"] == "view")
789                         done &= view(parseInt(effectArray[counter]["dir"]) * dir, effectArray[counter]["element"], transCounter, effectArray[counter]["options"]);
790         }
792         ROOT_NODE.unsuspendRedraw(suspendHandle);
793         ROOT_NODE.forceRedraw();
795         if (!done)
796         {
797                 var currentTime = (new Date()).getTime();
798                 var timeDiff = 1;
800                 transCounter = currentTime - startTime;
802                 if (lastFrameTime != null)
803                 {
804                         timeDiff = timeStep - (currentTime - lastFrameTime);
806                         if (timeDiff <= 0)
807                                 timeDiff = 1;
808                 }
810                 lastFrameTime = currentTime;
812                 window.setTimeout("effect(" + dir + ")", timeDiff);
813         }
814         else
815         {
816                 window.location.hash = (activeSlide + 1) + '_' + activeEffect;
817                 processingEffect = false;
818         }
821 /** Function to display the index sheet.
822  *
823  *  @param offsetNumber offset number
824  */
825 function displayIndex(offsetNumber)
827         var offsetX = 0;
828         var offsetY = 0;
830         if (offsetNumber < 0)
831                 offsetNumber = 0;
832         else if (offsetNumber >= slides.length)
833                 offsetNumber = slides.length - 1;
835         for (var counter = 0; counter < slides.length; counter++)
836         {
837                 if ((counter < offsetNumber) || (counter > offsetNumber + INDEX_COLUMNS * INDEX_COLUMNS - 1))
838                 {
839                         slides[counter]["element"].setAttribute("opacity",0);
840                         slides[counter]["element"].style.display = "none";
841                 }
842                 else
843                 {
844                         offsetX = ((counter - offsetNumber) % INDEX_COLUMNS) * WIDTH;
845                         offsetY = Math.floor((counter - offsetNumber) / INDEX_COLUMNS) * HEIGHT;
847                         slides[counter]["element"].setAttribute("transform","scale("+1/INDEX_COLUMNS+") translate("+offsetX+","+offsetY+")");
848                         slides[counter]["element"].style.display = "inherit";
849                         slides[counter]["element"].setAttribute("opacity",0.5);
850                 }
852                 setSlideToState(counter, STATE_END);
853         }
855         //do we need to save the current offset?
856         if (INDEX_OFFSET != offsetNumber)
857                 INDEX_OFFSET = offsetNumber;
860 /** Function to set the active slide in the slide view.
861  *
862  *  @param nbr index of the active slide
863  */
864 function slideSetActiveSlide(nbr)
866         if (nbr >= slides.length)
867                 nbr = slides.length - 1;
868         else if (nbr < 0)
869                 nbr = 0;
871         slides[activeSlide]["element"].setAttribute("opacity",0);
872         slides[activeSlide]["element"].style.display = "none";
874         activeSlide = parseInt(nbr);
876         setSlideToState(activeSlide, STATE_START);
877         slides[activeSlide]["element"].style.display = "inherit";
878         slides[activeSlide]["element"].setAttribute("opacity",1);
880         activeEffect = 0;
881         setProgressBarValue(nbr);
884 /** Function to set the active slide in the index view.
885  *
886  *  @param nbr index of the active slide
887  */
888 function indexSetActiveSlide(nbr)
890         if (nbr >= slides.length)
891                 nbr = slides.length - 1;
892         else if (nbr < 0)
893                 nbr = 0;
895         slides[activeSlide]["element"].setAttribute("opacity",0.5);
897         activeSlide = parseInt(nbr);
898         window.location.hash = (activeSlide + 1) + '_0';
900         slides[activeSlide]["element"].setAttribute("opacity",1);
903 /** Function to set the page and active slide in index view. 
904  *
905  *  @param nbr index of the active slide
906  *
907  *  NOTE: To force a redraw,
908  *  set INDEX_OFFSET to -1 before calling indexSetPageSlide().
909  *
910  *  This is necessary for zooming (otherwise the index might not
911  *  get redrawn) and when switching to index mode.
912  *
913  *  INDEX_OFFSET = -1
914  *  indexSetPageSlide(activeSlide);
915  */
916 function indexSetPageSlide(nbr)
918         if (nbr >= slides.length)
919                 nbr = slides.length - 1;
920         else if (nbr < 0)
921                 nbr = 0;
923         //calculate the offset
924         var offset = nbr - nbr % (INDEX_COLUMNS * INDEX_COLUMNS);
926         if (offset < 0)
927                 offset = 0;
929         //if different from kept offset, then record and change the page
930         if (offset != INDEX_OFFSET)
931         {
932                 INDEX_OFFSET = offset;
933                 displayIndex(INDEX_OFFSET);
934         }
936         //set the active slide
937         indexSetActiveSlide(nbr);
940 /** Event handler for key press.
941  *
942  *  @param e the event
943  */
944 function keydown(e)
946         if (!e)
947                 e = window.event;
949         code = e.keyCode || e.charCode;
951         if (!processingEffect && keyCodeDictionary[currentMode] && keyCodeDictionary[currentMode][code])
952                 return keyCodeDictionary[currentMode][code]();
953         else
954                 document.onkeypress = keypress;
956 // Set event handler for key down.
957 document.onkeydown = keydown;
959 /** Event handler for key press.
960  *
961  *  @param e the event
962  */
963 function keypress(e)
965         document.onkeypress = null;
967         if (!e)
968                 e = window.event;
970         str = String.fromCharCode(e.keyCode || e.charCode);
972         if (!processingEffect && charCodeDictionary[currentMode] && charCodeDictionary[currentMode][str])
973                 return charCodeDictionary[currentMode][str]();
976 /** Function to supply the default char code dictionary.
977  *
978  * @returns default char code dictionary
979  */
980 function getDefaultCharCodeDictionary()
982         var charCodeDict = new Object();
984         charCodeDict[SLIDE_MODE] = new Object();
985         charCodeDict[INDEX_MODE] = new Object();
986         charCodeDict[DRAWING_MODE] = new Object();
988         charCodeDict[SLIDE_MODE]["i"] = function () { return toggleSlideIndex(); };
989         charCodeDict[SLIDE_MODE]["d"] = function () { return slideSwitchToDrawingMode(); };
990         charCodeDict[SLIDE_MODE]["D"] = function () { return slideQueryDuration(); };
991         charCodeDict[SLIDE_MODE]["n"] = function () { return slideAddSlide(activeSlide); };
992         charCodeDict[SLIDE_MODE]["p"] = function () { return slideToggleProgressBarVisibility(); };
993         charCodeDict[SLIDE_MODE]["t"] = function () { return slideResetTimer(); };
994         charCodeDict[SLIDE_MODE]["e"] = function () { return slideUpdateExportLayer(); };
996         charCodeDict[DRAWING_MODE]["d"] = function () { return drawingSwitchToSlideMode(); };
997         charCodeDict[DRAWING_MODE]["0"] = function () { return drawingResetPathWidth(); };
998         charCodeDict[DRAWING_MODE]["1"] = function () { return drawingSetPathWidth(1.0); };
999         charCodeDict[DRAWING_MODE]["3"] = function () { return drawingSetPathWidth(3.0); };
1000         charCodeDict[DRAWING_MODE]["5"] = function () { return drawingSetPathWidth(5.0); };
1001         charCodeDict[DRAWING_MODE]["7"] = function () { return drawingSetPathWidth(7.0); };
1002         charCodeDict[DRAWING_MODE]["9"] = function () { return drawingSetPathWidth(9.0); };
1003         charCodeDict[DRAWING_MODE]["b"] = function () { return drawingSetPathColour("blue"); };
1004         charCodeDict[DRAWING_MODE]["c"] = function () { return drawingSetPathColour("cyan"); };
1005         charCodeDict[DRAWING_MODE]["g"] = function () { return drawingSetPathColour("green"); };
1006         charCodeDict[DRAWING_MODE]["k"] = function () { return drawingSetPathColour("black"); };
1007         charCodeDict[DRAWING_MODE]["m"] = function () { return drawingSetPathColour("magenta"); };
1008         charCodeDict[DRAWING_MODE]["o"] = function () { return drawingSetPathColour("orange"); };
1009         charCodeDict[DRAWING_MODE]["r"] = function () { return drawingSetPathColour("red"); };
1010         charCodeDict[DRAWING_MODE]["w"] = function () { return drawingSetPathColour("white"); };
1011         charCodeDict[DRAWING_MODE]["y"] = function () { return drawingSetPathColour("yellow"); };
1012         charCodeDict[DRAWING_MODE]["z"] = function () { return drawingUndo(); };
1014         charCodeDict[INDEX_MODE]["i"] = function () { return toggleSlideIndex(); };
1015         charCodeDict[INDEX_MODE]["-"] = function () { return indexDecreaseNumberOfColumns(); };
1016         charCodeDict[INDEX_MODE]["="] = function () { return indexIncreaseNumberOfColumns(); };
1017         charCodeDict[INDEX_MODE]["+"] = function () { return indexIncreaseNumberOfColumns(); };
1018         charCodeDict[INDEX_MODE]["0"] = function () { return indexResetNumberOfColumns(); };
1020         return charCodeDict;
1023 /** Function to supply the default key code dictionary.
1024  *
1025  * @returns default key code dictionary
1026  */
1027 function getDefaultKeyCodeDictionary()
1029         var keyCodeDict = new Object();
1031         keyCodeDict[SLIDE_MODE] = new Object();
1032         keyCodeDict[INDEX_MODE] = new Object();
1033         keyCodeDict[DRAWING_MODE] = new Object();
1035         keyCodeDict[SLIDE_MODE][LEFT_KEY] = function() { return dispatchEffects(-1); };
1036         keyCodeDict[SLIDE_MODE][RIGHT_KEY] = function() { return dispatchEffects(1); };
1037         keyCodeDict[SLIDE_MODE][UP_KEY] = function() { return skipEffects(-1); };
1038         keyCodeDict[SLIDE_MODE][DOWN_KEY] = function() { return skipEffects(1); };
1039         keyCodeDict[SLIDE_MODE][PAGE_UP_KEY] = function() { return dispatchEffects(-1); };
1040         keyCodeDict[SLIDE_MODE][PAGE_DOWN_KEY] = function() { return dispatchEffects(1); };
1041         keyCodeDict[SLIDE_MODE][HOME_KEY] = function() { return slideSetActiveSlide(0); };
1042         keyCodeDict[SLIDE_MODE][END_KEY] = function() { return slideSetActiveSlide(slides.length - 1); };
1043         keyCodeDict[SLIDE_MODE][SPACE_KEY] = function() { return dispatchEffects(1); };
1045         keyCodeDict[INDEX_MODE][LEFT_KEY] = function() { return indexSetPageSlide(activeSlide - 1); };
1046         keyCodeDict[INDEX_MODE][RIGHT_KEY] = function() { return indexSetPageSlide(activeSlide + 1); };
1047         keyCodeDict[INDEX_MODE][UP_KEY] = function() { return indexSetPageSlide(activeSlide - INDEX_COLUMNS); };
1048         keyCodeDict[INDEX_MODE][DOWN_KEY] = function() { return indexSetPageSlide(activeSlide + INDEX_COLUMNS); };
1049         keyCodeDict[INDEX_MODE][PAGE_UP_KEY] = function() { return indexSetPageSlide(activeSlide - INDEX_COLUMNS * INDEX_COLUMNS); };
1050         keyCodeDict[INDEX_MODE][PAGE_DOWN_KEY] = function() { return indexSetPageSlide(activeSlide + INDEX_COLUMNS * INDEX_COLUMNS); };
1051         keyCodeDict[INDEX_MODE][HOME_KEY] = function() { return indexSetPageSlide(0); };
1052         keyCodeDict[INDEX_MODE][END_KEY] = function() { return indexSetPageSlide(slides.length - 1); };
1053         keyCodeDict[INDEX_MODE][ENTER_KEY] = function() { return toggleSlideIndex(); };
1055         keyCodeDict[DRAWING_MODE][ESCAPE_KEY] = function () { return drawingSwitchToSlideMode(); };
1057         return keyCodeDict;
1060 /** Function to handle all mouse events.
1061  *
1062  *      @param  evnt    event
1063  *      @param  action  type of event (e.g. mouse up, mouse wheel)
1064  */
1065 function mouseHandlerDispatch(evnt, action)
1067         if (!evnt)
1068                 evnt = window.event;
1070         var retVal = true;
1072         if (!processingEffect && mouseHandlerDictionary[currentMode] && mouseHandlerDictionary[currentMode][action])
1073         {
1074                 var subRetVal = mouseHandlerDictionary[currentMode][action](evnt);
1076                 if (subRetVal != null && subRetVal != undefined)
1077                         retVal = subRetVal;
1078         }
1080         if (evnt.preventDefault && !retVal)
1081                 evnt.preventDefault();
1083         evnt.returnValue = retVal;
1085         return retVal;
1088 // Set mouse event handler.
1089 document.onmousedown = function(e) { return mouseHandlerDispatch(e, MOUSE_DOWN); };
1090 document.onmouseup = function(e) { return mouseHandlerDispatch(e, MOUSE_UP); };
1091 document.onmousemove = function(e) { return mouseHandlerDispatch(e, MOUSE_MOVE); };
1093 // Moz
1094 if (window.addEventListener)
1096         window.addEventListener('DOMMouseScroll', function(e) { return mouseHandlerDispatch(e, MOUSE_WHEEL); }, false);
1099 // Opera Safari OK - may not work in IE
1100 window.onmousewheel = function(e) { return mouseHandlerDispatch(e, MOUSE_WHEEL); };
1102 /** Function to supply the default mouse handler dictionary.
1103  *
1104  * @returns default mouse handler dictionary
1105  */
1106 function getDefaultMouseHandlerDictionary()
1108         var mouseHandlerDict = new Object();
1110         mouseHandlerDict[SLIDE_MODE] = new Object();
1111         mouseHandlerDict[INDEX_MODE] = new Object();
1112         mouseHandlerDict[DRAWING_MODE] = new Object();
1114         mouseHandlerDict[SLIDE_MODE][MOUSE_DOWN] = function(evnt) { return dispatchEffects(1); };
1115         mouseHandlerDict[SLIDE_MODE][MOUSE_WHEEL] = function(evnt) { return slideMousewheel(evnt); };
1117         mouseHandlerDict[INDEX_MODE][MOUSE_DOWN] = function(evnt) { return toggleSlideIndex(); };
1119         mouseHandlerDict[DRAWING_MODE][MOUSE_DOWN] = function(evnt) { return drawingMousedown(evnt); };
1120         mouseHandlerDict[DRAWING_MODE][MOUSE_UP] = function(evnt) { return drawingMouseup(evnt); };
1121         mouseHandlerDict[DRAWING_MODE][MOUSE_MOVE] = function(evnt) { return drawingMousemove(evnt); };
1123         return mouseHandlerDict;
1126 /** Function to switch from slide mode to drawing mode.
1127 */
1128 function slideSwitchToDrawingMode()
1130         currentMode = DRAWING_MODE;
1132         var tempDict;
1134         if (ROOT_NODE.hasAttribute("style"))
1135                 tempDict = propStrToDict(ROOT_NODE.getAttribute("style"));
1136         else
1137                 tempDict = new Object();
1139         tempDict["cursor"] = "crosshair";
1140         ROOT_NODE.setAttribute("style", dictToPropStr(tempDict));
1143 /** Function to switch from drawing mode to slide mode.
1144 */
1145 function drawingSwitchToSlideMode()
1147         currentMode = SLIDE_MODE;
1149         var tempDict;
1151         if (ROOT_NODE.hasAttribute("style"))
1152                 tempDict = propStrToDict(ROOT_NODE.getAttribute("style"));
1153         else
1154                 tempDict = new Object();
1156         tempDict["cursor"] = "auto";
1157         ROOT_NODE.setAttribute("style", dictToPropStr(tempDict));
1160 /** Function to decrease the number of columns in index mode.
1161 */
1162 function indexDecreaseNumberOfColumns()
1164         if (INDEX_COLUMNS >= 3)
1165         {
1166                 INDEX_COLUMNS -= 1;
1167                 INDEX_OFFSET = -1
1168                         indexSetPageSlide(activeSlide);
1169         }
1172 /** Function to increase the number of columns in index mode.
1173 */
1174 function indexIncreaseNumberOfColumns()
1176         if (INDEX_COLUMNS < 7)
1177         {
1178                 INDEX_COLUMNS += 1;
1179                 INDEX_OFFSET = -1
1180                         indexSetPageSlide(activeSlide);
1181         }
1184 /** Function to reset the number of columns in index mode.
1185 */
1186 function indexResetNumberOfColumns()
1188         if (INDEX_COLUMNS != INDEX_COLUMNS_DEFAULT)
1189         {
1190                 INDEX_COLUMNS = INDEX_COLUMNS_DEFAULT;
1191                 INDEX_OFFSET = -1
1192                         indexSetPageSlide(activeSlide);
1193         }
1196 /** Function to reset path width in drawing mode.
1197 */
1198 function drawingResetPathWidth()
1200         path_width = path_width_default;
1201         set_path_paint_width();
1204 /** Function to set path width in drawing mode.
1205  *
1206  * @param width new path width
1207  */
1208 function drawingSetPathWidth(width)
1210         path_width = width;
1211         set_path_paint_width();
1214 /** Function to set path colour in drawing mode.
1215  *
1216  * @param colour new path colour
1217  */
1218 function drawingSetPathColour(colour)
1220         path_colour = colour;
1223 /** Function to query the duration of the presentation from the user in slide mode.
1224 */
1225 function slideQueryDuration()
1227         var new_duration = prompt("Length of presentation in minutes?", timer_duration);
1229         if ((new_duration != null) && (new_duration != ''))
1230         {
1231                 timer_duration = new_duration;
1232         }
1234         updateTimer();
1237 /** Function to add new slide in slide mode.
1238  *
1239  * @param afterSlide after which slide to insert the new one
1240  */
1241 function slideAddSlide(afterSlide)
1243         addSlide(afterSlide);
1244         slideSetActiveSlide(afterSlide + 1);
1245         updateTimer();
1248 /** Function to toggle the visibility of the progress bar in slide mode.
1249 */
1250 function slideToggleProgressBarVisibility()
1252         if (progress_bar_visible)
1253         {
1254                 progress_bar_visible = false;
1255                 hideProgressBar();
1256         }
1257         else
1258         {
1259                 progress_bar_visible = true;
1260                 showProgressBar();
1261         }
1264 /** Function to reset the timer in slide mode.
1265 */
1266 function slideResetTimer()
1268         timer_start = timer_elapsed;
1269         updateTimer();
1272 /** Convenience function to pad a string with zero in front up to a certain length.
1273  */
1274 function padString(str, len)
1276         var outStr = str;
1278         while (outStr.length < len)
1279         {
1280                 outStr = '0' + outStr;
1281         }
1283         return outStr;
1286 /** Function to update the export layer.
1287  */
1288 function slideUpdateExportLayer()
1290         // Suspend redraw since we are going to mess with the slides.
1291         var suspendHandle = ROOT_NODE.suspendRedraw(2000);
1293         var tmpActiveSlide = activeSlide;
1294         var tmpActiveEffect = activeEffect;
1295         var exportedLayers = new Array();
1297         for (var counterSlides = 0; counterSlides < slides.length; counterSlides++)
1298         {
1299                 var exportNode;
1301                 setSlideToState(counterSlides, STATE_START);
1303                 var maxEffect = 0;
1305                 if (slides[counterSlides].effects)
1306                 {
1307                         maxEffect = slides[counterSlides].effects.length;
1308                 }
1310                 exportNode = slides[counterSlides].element.cloneNode(true);
1311                 exportNode.setAttributeNS(NSS["inkscape"], "groupmode", "layer");
1312                 exportNode.setAttributeNS(NSS["inkscape"], "label", "slide_" + padString((counterSlides + 1).toString(), slides.length.toString().length) + "_effect_" + padString("0", maxEffect.toString().length));
1314                 exportedLayers.push(exportNode);
1316                 if (slides[counterSlides]["effects"])
1317                 {       
1318                         for (var counter = 0; counter < slides[counterSlides]["effects"].length; counter++)
1319                         {
1320                                 for (var subCounter = 0; subCounter < slides[counterSlides]["effects"][counter].length; subCounter++)
1321                                 {
1322                                         var effect = slides[counterSlides]["effects"][counter][subCounter];
1323                                         if (effect["effect"] == "fade")
1324                                                 fade(parseInt(effect["dir"]), effect["element"], STATE_END, effect["options"]); 
1325                                         else if (effect["effect"] == "appear")
1326                                                 appear(parseInt(effect["dir"]), effect["element"], STATE_END, effect["options"]);       
1327                                         else if (effect["effect"] == "pop")
1328                                                 pop(parseInt(effect["dir"]), effect["element"], STATE_END, effect["options"]);  
1329                                         else if (effect["effect"] == "view")
1330                                                 view(parseInt(effect["dir"]), effect["element"], STATE_END, effect["options"]); 
1331                                 }
1333                                 var layerName = "slide_" + padString((counterSlides + 1).toString(), slides.length.toString().length) + "_effect_" + padString((counter + 1).toString(), maxEffect.toString().length);
1334                                 exportNode = slides[counterSlides].element.cloneNode(true);
1335                                 exportNode.setAttributeNS(NSS["inkscape"], "groupmode", "layer");
1336                                 exportNode.setAttributeNS(NSS["inkscape"], "label", layerName);
1337                                 exportNode.setAttribute("id", layerName);
1339                                 exportedLayers.push(exportNode);
1340                         }
1341                 }
1342         }
1344         activeSlide = tmpActiveSlide;
1345         activeEffect = tmpActiveEffect;
1346         setSlideToState(activeSlide, activeEffect);
1348         // Copy image.
1349         var newDoc = document.documentElement.cloneNode(true);
1351         // Delete viewbox form new imag and set width and height.
1352         newDoc.removeAttribute('viewbox');
1353         newDoc.setAttribute('width', WIDTH);
1354         newDoc.setAttribute('height', HEIGHT);
1356         // Delete all layers and script elements.
1357         var nodesToBeRemoved = new Array();
1359         for (var childCounter = 0; childCounter <  newDoc.childNodes.length; childCounter++)
1360         {
1361                 var child = newDoc.childNodes[childCounter];
1363                 if (child.nodeType == 1)
1364                 {
1365                         if ((child.nodeName.toUpperCase() == 'G') || (child.nodeName.toUpperCase() == 'SCRIPT'))
1366                         {
1367                                 nodesToBeRemoved.push(child);
1368                         }
1369                 }
1370         }
1372         for (var ndCounter = 0; ndCounter < nodesToBeRemoved.length; ndCounter++)
1373         {
1374                 var nd = nodesToBeRemoved[ndCounter];
1376                 nd.parentNode.removeChild(nd);
1377         }
1379         // Set current layer.
1380         if (exportedLayers[0])
1381         {
1382                 var namedView;
1384                 for (var nodeCounter = 0; nodeCounter < newDoc.childNodes.length; nodeCounter++)
1385                 {
1386                         if ((newDoc.childNodes[nodeCounter].nodeType == 1) && (newDoc.childNodes[nodeCounter].getAttribute('id') == 'base'))
1387                         {
1388                                 namedView = newDoc.childNodes[nodeCounter];
1389                         }
1390                 }
1392                 if (namedView)
1393                 {
1394                         namedView.setAttributeNS(NSS['inkscape'], 'current-layer', exportedLayers[0].getAttributeNS(NSS['inkscape'], 'label'));
1395                 }
1396         }
1398         // Add exported layers.
1399         while (exportedLayers.length > 0)
1400         {
1401                 var nd = exportedLayers.pop();
1403                 nd.setAttribute("opacity",1);
1404                 nd.style.display = "inherit";
1406                 newDoc.appendChild(nd);
1407         }
1409         // Serialise the new document.
1410         var serializer = new XMLSerializer();
1411         var strm = 
1412         {
1413                 content : "",
1414                 close : function() {},  
1415                 flush : function() {},  
1416                 write : function(str, count) { this.content += str; }  
1417         };
1419         var xml = serializer.serializeToStream(newDoc, strm, 'UTF-8');
1421         window.open('data:image/svg+xml;base64;charset=utf-8,' + window.btoa(strm.content), '_blank');
1423         // Unsuspend redraw.
1424         ROOT_NODE.unsuspendRedraw(suspendHandle);
1425         ROOT_NODE.forceRedraw();
1428 /** Function to undo last drawing operation.
1429 */
1430 function drawingUndo()
1432         mouse_presentation_path = null;
1433         mouse_original_path = null;
1435         if (history_presentation_elements.length > 0)
1436         {
1437                 var p = history_presentation_elements.pop();
1438                 var parent = p.parentNode.removeChild(p);
1440                 p = history_original_elements.pop();
1441                 parent = p.parentNode.removeChild(p);
1442         }
1445 /** Event handler for mouse down in drawing mode.
1446  *
1447  *  @param e the event
1448  */
1449 function drawingMousedown(e)
1451         var value = 0;
1453         if (e.button)
1454                 value = e.button;
1455         else if (e.which)
1456                 value = e.which;
1458         if (value == 1)
1459         {
1460                 history_counter++;
1462                 var p = calcCoord(e);
1464                 mouse_last_x = e.clientX;
1465                 mouse_last_y = e.clientY;
1466                 mouse_original_path = document.createElementNS(NSS["svg"], "path");
1467                 mouse_original_path.setAttribute("stroke", path_colour);
1468                 mouse_original_path.setAttribute("stroke-width", path_paint_width);
1469                 mouse_original_path.setAttribute("fill", "none");
1470                 mouse_original_path.setAttribute("id", "path " + Date());
1471                 mouse_original_path.setAttribute("d", "M" + p.x + "," + p.y);
1472                 slides[activeSlide]["original_element"].appendChild(mouse_original_path);
1473                 history_original_elements.push(mouse_original_path);
1475                 mouse_presentation_path = document.createElementNS(NSS["svg"], "path");
1476                 mouse_presentation_path.setAttribute("stroke", path_colour);
1477                 mouse_presentation_path.setAttribute("stroke-width", path_paint_width);
1478                 mouse_presentation_path.setAttribute("fill", "none");
1479                 mouse_presentation_path.setAttribute("id", "path " + Date() + " presentation copy");
1480                 mouse_presentation_path.setAttribute("d", "M" + p.x + "," + p.y);
1482                 if (slides[activeSlide]["viewGroup"])
1483                         slides[activeSlide]["viewGroup"].appendChild(mouse_presentation_path);
1484                 else
1485                         slides[activeSlide]["element"].appendChild(mouse_presentation_path);
1487                 history_presentation_elements.push(mouse_presentation_path);
1489                 return false;
1490         }
1492         return true;
1495 /** Event handler for mouse up in drawing mode.
1496  *
1497  *  @param e the event
1498  */
1499 function drawingMouseup(e)
1501         if(!e)
1502                 e = window.event;
1504         if (mouse_presentation_path != null)
1505         {
1506                 var p = calcCoord(e);
1507                 var d = mouse_presentation_path.getAttribute("d");
1508                 d += " L" + p.x + "," + p.y;
1509                 mouse_presentation_path.setAttribute("d", d);
1510                 mouse_presentation_path = null;
1511                 mouse_original_path.setAttribute("d", d);
1512                 mouse_original_path = null;
1514                 return false;
1515         }
1517         return true;
1520 /** Event handler for mouse move in drawing mode.
1521  *
1522  *  @param e the event
1523  */
1524 function drawingMousemove(e)
1526         if(!e)
1527                 e = window.event;
1529         var dist = (mouse_last_x - e.clientX) * (mouse_last_x - e.clientX) + (mouse_last_y - e.clientY) * (mouse_last_y - e.clientY);
1531         if (mouse_presentation_path == null)
1532         {
1533                 return true;
1534         }
1536         if (dist >= mouse_min_dist_sqr)
1537         {
1538                 var p = calcCoord(e);
1539                 var d = mouse_presentation_path.getAttribute("d");
1540                 d += " L" + p.x + "," + p.y;
1541                 mouse_presentation_path.setAttribute("d", d);
1542                 mouse_original_path.setAttribute("d", d);
1543                 mouse_last_x = e.clientX;
1544                 mouse_last_y = e.clientY;
1545         }
1547         return false;
1550 /** Event handler for mouse wheel events in slide mode.
1551  *  based on http://adomas.org/javascript-mouse-wheel/
1552  *
1553  *  @param e the event
1554  */
1555 function slideMousewheel(e)
1557         var delta = 0;
1559         if (!e)
1560                 e = window.event;
1562         if (e.wheelDelta)
1563         { // IE Opera
1564                 delta = e.wheelDelta/120;
1565         }
1566         else if (e.detail)
1567         { // MOZ
1568                 delta = -e.detail/3;
1569         }
1571         if (delta > 0)
1572                 skipEffects(-1);
1573         else if (delta < 0)
1574                 skipEffects(1);
1576         if (e.preventDefault)
1577                 e.preventDefault();
1579         e.returnValue = false;
1582 /** Event handler for mouse wheel events in index mode.
1583  *  based on http://adomas.org/javascript-mouse-wheel/
1584  *
1585  *  @param e the event
1586  */
1587 function indexMousewheel(e)
1589         var delta = 0;
1591         if (!e)
1592                 e = window.event;
1594         if (e.wheelDelta)
1595         { // IE Opera
1596                 delta = e.wheelDelta/120;
1597         }
1598         else if (e.detail)
1599         { // MOZ
1600                 delta = -e.detail/3;
1601         }
1603         if (delta > 0)
1604                 indexSetPageSlide(activeSlide - INDEX_COLUMNS * INDEX_COLUMNS);
1605         else if (delta < 0)
1606                 indexSetPageSlide(activeSlide + INDEX_COLUMNS * INDEX_COLUMNS);
1608         if (e.preventDefault)
1609                 e.preventDefault();
1611         e.returnValue = false;
1614 /** Function to set the path paint width.
1615 */
1616 function set_path_paint_width()
1618         var svgPoint1 = document.documentElement.createSVGPoint();
1619         var svgPoint2 = document.documentElement.createSVGPoint();
1621         svgPoint1.x = 0.0;
1622         svgPoint1.y = 0.0;
1623         svgPoint2.x = 1.0;
1624         svgPoint2.y = 0.0;
1626         var matrix = slides[activeSlide]["element"].getTransformToElement(ROOT_NODE);
1628         if (slides[activeSlide]["viewGroup"])
1629                 matrix = slides[activeSlide]["viewGroup"].getTransformToElement(ROOT_NODE);
1631         svgPoint1 = svgPoint1.matrixTransform(matrix);
1632         svgPoint2 = svgPoint2.matrixTransform(matrix);
1634         path_paint_width = path_width / Math.sqrt((svgPoint2.x - svgPoint1.x) * (svgPoint2.x - svgPoint1.x) + (svgPoint2.y - svgPoint1.y) * (svgPoint2.y - svgPoint1.y));
1637 /** The view effect.
1638  *
1639  *  @param dir direction the effect should be played (1 = forwards, -1 = backwards)
1640  *  @param element the element the effect should be applied to
1641  *  @param time the time that has elapsed since the beginning of the effect
1642  *  @param options a dictionary with additional options (e.g. length of the effect); for the view effect the options need to contain the old and the new matrix.
1643  */
1644 function view(dir, element, time, options)
1646         var length = 250;
1647         var fraction;
1649         if (!options["matrixInitial"])
1650         {
1651                 var tempString = slides[activeSlide]["viewGroup"].getAttribute("transform");
1653                 if (tempString)
1654                         options["matrixInitial"] = (new matrixSVG()).fromAttribute(tempString);
1655                 else
1656                         options["matrixInitial"] = (new matrixSVG()).fromSVGElements(1, 0, 0, 1, 0, 0);
1657         }
1659         if ((time == STATE_END) || (time == STATE_START))
1660                 fraction = 1;
1661         else
1662         {
1663                 if (options && options["length"])
1664                         length = options["length"];
1666                 fraction = time / length;
1667         }
1669         if (dir == 1)
1670         {
1671                 if (fraction <= 0)
1672                 {
1673                         element.setAttribute("transform", options["matrixInitial"].toAttribute());
1674                 }
1675                 else if (fraction >= 1)
1676                 {
1677                         element.setAttribute("transform", options["matrixNew"].toAttribute());
1679                         set_path_paint_width();
1681                         options["matrixInitial"] = null;
1682                         return true;
1683                 }
1684                 else
1685                 {
1686                         element.setAttribute("transform", options["matrixInitial"].mix(options["matrixNew"], fraction).toAttribute());
1687                 }
1688         }
1689         else if (dir == -1)
1690         {
1691                 if (fraction <= 0)
1692                 {
1693                         element.setAttribute("transform", options["matrixInitial"].toAttribute());
1694                 }
1695                 else if (fraction >= 1)
1696                 {
1697                         element.setAttribute("transform", options["matrixOld"].toAttribute());
1698                         set_path_paint_width();
1700                         options["matrixInitial"] = null;
1701                         return true;
1702                 }
1703                 else
1704                 {
1705                         element.setAttribute("transform", options["matrixInitial"].mix(options["matrixOld"], fraction).toAttribute());
1706                 }
1707         }
1709         return false;
1712 /** The fade effect.
1713  *
1714  *  @param dir direction the effect should be played (1 = forwards, -1 = backwards)
1715  *  @param element the element the effect should be applied to
1716  *  @param time the time that has elapsed since the beginning of the effect
1717  *  @param options a dictionary with additional options (e.g. length of the effect)
1718  */
1719 function fade(dir, element, time, options)
1721         var length = 250;
1722         var fraction;
1724         if ((time == STATE_END) || (time == STATE_START))
1725                 fraction = 1;
1726         else
1727         {
1728                 if (options && options["length"])
1729                         length = options["length"];
1731                 fraction = time / length;
1732         }
1734         if (dir == 1)
1735         {
1736                 if (fraction <= 0)
1737                 {
1738                         element.style.display = "none";
1739                         element.setAttribute("opacity", 0);
1740                 }
1741                 else if (fraction >= 1)
1742                 {
1743                         element.style.display = "inherit";
1744                         element.setAttribute("opacity", 1);
1745                         return true;
1746                 }
1747                 else
1748                 {
1749                         element.style.display = "inherit";
1750                         element.setAttribute("opacity", fraction);
1751                 }
1752         }
1753         else if (dir == -1)
1754         {
1755                 if (fraction <= 0)
1756                 {
1757                         element.style.display = "inherit";
1758                         element.setAttribute("opacity", 1);
1759                 }
1760                 else if (fraction >= 1)
1761                 {
1762                         element.setAttribute("opacity", 0);
1763                         element.style.display = "none";
1764                         return true;
1765                 }
1766                 else
1767                 {
1768                         element.style.display = "inherit";
1769                         element.setAttribute("opacity", 1 - fraction);
1770                 }
1771         }
1772         return false;
1775 /** The appear effect.
1776  *
1777  *  @param dir direction the effect should be played (1 = forwards, -1 = backwards)
1778  *  @param element the element the effect should be applied to
1779  *  @param time the time that has elapsed since the beginning of the effect
1780  *  @param options a dictionary with additional options (e.g. length of the effect)
1781  */
1782 function appear(dir, element, time, options)
1784         if (dir == 1)
1785         {
1786                 element.style.display = "inherit";
1787                 element.setAttribute("opacity",1);
1788         }
1789         else if (dir == -1)
1790         {
1791                 element.style.display = "none";
1792                 element.setAttribute("opacity",0);
1793         }
1794         return true;
1797 /** The pop effect.
1798  *
1799  *  @param dir direction the effect should be played (1 = forwards, -1 = backwards)
1800  *  @param element the element the effect should be applied to
1801  *  @param time the time that has elapsed since the beginning of the effect
1802  *  @param options a dictionary with additional options (e.g. length of the effect)
1803  */
1804 function pop(dir, element, time, options)
1806         var length = 500;
1807         var fraction;
1809         if ((time == STATE_END) || (time == STATE_START))
1810                 fraction = 1;
1811         else
1812         {
1813                 if (options && options["length"])
1814                         length = options["length"];
1816                 fraction = time / length;
1817         }
1819         if (dir == 1)
1820         {
1821                 if (fraction <= 0)
1822                 {
1823                         element.setAttribute("opacity", 0);
1824                         element.setAttribute("transform", "scale(0)");
1825                         element.style.display = "none";
1826                 }
1827                 else if (fraction >= 1)
1828                 {
1829                         element.setAttribute("opacity", 1);
1830                         element.removeAttribute("transform");
1831                         element.style.display = "inherit";
1832                         return true;
1833                 }
1834                 else
1835                 {
1836                         element.style.display = "inherit";
1837                         var opacityFraction = fraction * 3;
1838                         if (opacityFraction > 1)
1839                                 opacityFraction = 1;
1840                         element.setAttribute("opacity", opacityFraction);
1841                         var offsetX = WIDTH * (1.0 - fraction) / 2.0;
1842                         var offsetY = HEIGHT * (1.0 - fraction) / 2.0;
1843                         element.setAttribute("transform", "translate(" + offsetX + "," + offsetY + ") scale(" + fraction + ")");
1844                 }
1845         }
1846         else if (dir == -1)
1847         {
1848                 if (fraction <= 0)
1849                 {
1850                         element.setAttribute("opacity", 1);
1851                         element.setAttribute("transform", "scale(1)");
1852                         element.style.display = "inherit";
1853                 }
1854                 else if (fraction >= 1)
1855                 {
1856                         element.setAttribute("opacity", 0);
1857                         element.removeAttribute("transform");
1858                         element.style.display = "none";
1859                         return true;
1860                 }
1861                 else
1862                 {
1863                         element.setAttribute("opacity", 1 - fraction);
1864                         element.setAttribute("transform", "scale(" + 1 - fraction + ")");
1865                         element.style.display = "inherit";
1866                 }
1867         }
1868         return false;
1871 /** Function to set a slide either to the start or the end state.
1872  *  
1873  *  @param slide the slide to use
1874  *  @param state the state into which the slide should be set
1875  */
1876 function setSlideToState(slide, state)
1878         slides[slide]["viewGroup"].setAttribute("transform", slides[slide].initialView);
1880         if (slides[slide]["effects"])
1881         {       
1882                 if (state == STATE_END)
1883                 {
1884                         for (var counter = 0; counter < slides[slide]["effects"].length; counter++)
1885                         {
1886                                 for (var subCounter = 0; subCounter < slides[slide]["effects"][counter].length; subCounter++)
1887                                 {
1888                                         var effect = slides[slide]["effects"][counter][subCounter];
1889                                         if (effect["effect"] == "fade")
1890                                                 fade(effect["dir"], effect["element"], STATE_END, effect["options"]);   
1891                                         else if (effect["effect"] == "appear")
1892                                                 appear(effect["dir"], effect["element"], STATE_END, effect["options"]); 
1893                                         else if (effect["effect"] == "pop")
1894                                                 pop(effect["dir"], effect["element"], STATE_END, effect["options"]);    
1895                                         else if (effect["effect"] == "view")
1896                                                 view(effect["dir"], effect["element"], STATE_END, effect["options"]);   
1897                                 }
1898                         }
1899                 }
1900                 else if (state == STATE_START)
1901                 {
1902                         for (var counter = slides[slide]["effects"].length - 1; counter >= 0; counter--)
1903                         {
1904                                 for (var subCounter = 0; subCounter < slides[slide]["effects"][counter].length; subCounter++)
1905                                 {
1906                                         var effect = slides[slide]["effects"][counter][subCounter];
1907                                         if (effect["effect"] == "fade")
1908                                                 fade(parseInt(effect["dir"]) * -1, effect["element"], STATE_START, effect["options"]);  
1909                                         else if (effect["effect"] == "appear")
1910                                                 appear(parseInt(effect["dir"]) * -1, effect["element"], STATE_START, effect["options"]);        
1911                                         else if (effect["effect"] == "pop")
1912                                                 pop(parseInt(effect["dir"]) * -1, effect["element"], STATE_START, effect["options"]);   
1913                                         else if (effect["effect"] == "view")
1914                                                 view(parseInt(effect["dir"]) * -1, effect["element"], STATE_START, effect["options"]);  
1915                                 }
1916                         }
1917                 }
1918                 else
1919                 {
1920                         setSlideToState(slide, STATE_START);
1922                         for (var counter = 0; counter < slides[slide]["effects"].length && counter < state; counter++)
1923                         {
1924                                 for (var subCounter = 0; subCounter < slides[slide]["effects"][counter].length; subCounter++)
1925                                 {
1926                                         var effect = slides[slide]["effects"][counter][subCounter];
1927                                         if (effect["effect"] == "fade")
1928                                                 fade(effect["dir"], effect["element"], STATE_END, effect["options"]);   
1929                                         else if (effect["effect"] == "appear")
1930                                                 appear(effect["dir"], effect["element"], STATE_END, effect["options"]); 
1931                                         else if (effect["effect"] == "pop")
1932                                                 pop(effect["dir"], effect["element"], STATE_END, effect["options"]);    
1933                                         else if (effect["effect"] == "view")
1934                                                 view(effect["dir"], effect["element"], STATE_END, effect["options"]);   
1935                                 }
1936                         }
1937                 }
1938         }
1940         window.location.hash = (activeSlide + 1) + '_' + activeEffect;
1943 /** Convenience function to translate a attribute string into a dictionary.
1944  *
1945  *      @param str the attribute string
1946  *  @return a dictionary
1947  *  @see dictToPropStr
1948  */
1949 function propStrToDict(str)
1951         var list = str.split(";");
1952         var obj = new Object();
1954         for (var counter = 0; counter < list.length; counter++)
1955         {
1956                 var subStr = list[counter];
1957                 var subList = subStr.split(":");
1958                 if (subList.length == 2)
1959                 {
1960                         obj[subList[0]] = subList[1];
1961                 }       
1962         }
1964         return obj;
1967 /** Convenience function to translate a dictionary into a string that can be used as an attribute.
1968  *
1969  *  @param dict the dictionary to convert
1970  *  @return a string that can be used as an attribute
1971  *  @see propStrToDict
1972  */
1973 function dictToPropStr(dict)
1975         var str = "";
1977         for (var key in dict)
1978         {
1979                 str += key + ":" + dict[key] + ";";
1980         }
1982         return str;
1985 /** Sub-function to add a suffix to the ids of the node and all its children.
1986  *      
1987  *      @param node the node to change
1988  *      @param suffix the suffix to add
1989  *      @param replace dictionary of replaced ids
1990  *  @see suffixNodeIds
1991  */
1992 function suffixNoneIds_sub(node, suffix, replace)
1994         if (node.nodeType == 1)
1995         {
1996                 if (node.getAttribute("id"))
1997                 {
1998                         var id = node.getAttribute("id")
1999                                 replace["#" + id] = id + suffix;
2000                         node.setAttribute("id", id + suffix);
2001                 }
2003                 if ((node.nodeName == "use") && (node.getAttributeNS(NSS["xlink"], "href")) && (replace[node.getAttribute(NSS["xlink"], "href")]))
2004                         node.setAttribute(NSS["xlink"], "href", node.getAttribute(NSS["xlink"], "href") + suffix);
2006                 if (node.childNodes)
2007                 {
2008                         for (var counter = 0; counter < node.childNodes.length; counter++)
2009                                 suffixNoneIds_sub(node.childNodes[counter], suffix, replace);
2010                 }
2011         }
2014 /** Function to add a suffix to the ids of the node and all its children.
2015  *      
2016  *      @param node the node to change
2017  *      @param suffix the suffix to add
2018  *  @return the changed node
2019  *  @see suffixNodeIds_sub
2020  */
2021 function suffixNodeIds(node, suffix)
2023         var replace = new Object();
2025         suffixNoneIds_sub(node, suffix, replace);
2027         return node;
2030 /** Function to build a progress bar.
2031  *      
2032  *  @param parent node to attach the progress bar to
2033  */
2034 function createProgressBar(parent_node)
2036         var g = document.createElementNS(NSS["svg"], "g");
2037         g.setAttribute("clip-path", "url(#jessyInkSlideClipPath)");
2038         g.setAttribute("id", "layer_progress_bar");
2039         g.setAttribute("style", "display: none;");
2041         var rect_progress_bar = document.createElementNS(NSS["svg"], "rect");
2042         rect_progress_bar.setAttribute("style", "marker: none; fill: rgb(128, 128, 128); stroke: none;");
2043         rect_progress_bar.setAttribute("id", "rect_progress_bar");
2044         rect_progress_bar.setAttribute("x", 0);
2045         rect_progress_bar.setAttribute("y", 0.99 * HEIGHT);
2046         rect_progress_bar.setAttribute("width", 0);
2047         rect_progress_bar.setAttribute("height", 0.01 * HEIGHT);
2048         g.appendChild(rect_progress_bar);
2050         var circle_timer_indicator = document.createElementNS(NSS["svg"], "circle");
2051         circle_timer_indicator.setAttribute("style", "marker: none; fill: rgb(255, 0, 0); stroke: none;");
2052         circle_timer_indicator.setAttribute("id", "circle_timer_indicator");
2053         circle_timer_indicator.setAttribute("cx", 0.005 * HEIGHT);
2054         circle_timer_indicator.setAttribute("cy", 0.995 * HEIGHT);
2055         circle_timer_indicator.setAttribute("r", 0.005 * HEIGHT);
2056         g.appendChild(circle_timer_indicator);
2058         parent_node.appendChild(g);
2061 /** Function to hide the progress bar.
2062  *      
2063  */
2064 function hideProgressBar()
2066         var progress_bar = document.getElementById("layer_progress_bar");
2068         if (!progress_bar)
2069         {
2070                 return;
2071         }
2073         progress_bar.setAttribute("style", "display: none;");
2076 /** Function to show the progress bar.
2077  *      
2078  */
2079 function showProgressBar()
2081         var progress_bar = document.getElementById("layer_progress_bar");
2083         if (!progress_bar)
2084         {
2085                 return;
2086         }
2088         progress_bar.setAttribute("style", "display: inherit;");
2091 /** Set progress bar value.
2092  *      
2093  *      @param value the current slide number
2094  *
2095  */
2096 function setProgressBarValue(value)
2098         var rect_progress_bar = document.getElementById("rect_progress_bar");
2100         if (!rect_progress_bar)
2101         {
2102                 return;
2103         }
2105         if (value < 1)
2106         {
2107                 // First slide, assumed to be the title of the presentation
2108                 var x = 0;
2109                 var w = 0.01 * HEIGHT;
2110         }
2111         else if (value >= slides.length - 1)
2112         {
2113                 // Last slide, assumed to be the end of the presentation
2114                 var x = WIDTH - 0.01 * HEIGHT;
2115                 var w = 0.01 * HEIGHT;
2116         }
2117         else
2118         {
2119                 value -= 1;
2120                 value /= (slides.length - 2);
2122                 var x = WIDTH * value;
2123                 var w = WIDTH / (slides.length - 2);
2124         }
2126         rect_progress_bar.setAttribute("x", x);
2127         rect_progress_bar.setAttribute("width", w);
2130 /** Set time indicator.
2131  *      
2132  *      @param value the percentage of time elapse so far between 0.0 and 1.0
2133  *
2134  */
2135 function setTimeIndicatorValue(value)
2137         var circle_timer_indicator = document.getElementById("circle_timer_indicator");
2139         if (!circle_timer_indicator)
2140         {
2141                 return;
2142         }
2144         if (value < 0.0)
2145         {
2146                 value = 0.0;
2147         }
2149         if (value > 1.0)
2150         {
2151                 value = 1.0;
2152         }
2154         var cx = (WIDTH - 0.01 * HEIGHT) * value + 0.005 * HEIGHT;
2155         circle_timer_indicator.setAttribute("cx", cx);
2158 /** Update timer.
2159  *      
2160  */
2161 function updateTimer()
2163         timer_elapsed += 1;
2164         setTimeIndicatorValue((timer_elapsed - timer_start) / (60 * timer_duration));
2167 /** Convert screen coordinates to document coordinates.
2168  *
2169  *  @param e event with screen coordinates
2170  *
2171  *  @return coordinates in SVG file coordinate system   
2172  */
2173 function calcCoord(e)
2175         var svgPoint = document.documentElement.createSVGPoint();
2176         svgPoint.x = e.clientX + window.pageXOffset;
2177         svgPoint.y = e.clientY + window.pageYOffset;
2179         var matrix = slides[activeSlide]["element"].getScreenCTM();
2181         if (slides[activeSlide]["viewGroup"])
2182                 matrix = slides[activeSlide]["viewGroup"].getScreenCTM();
2184         svgPoint = svgPoint.matrixTransform(matrix.inverse());
2185         return svgPoint;
2188 /** Add slide.
2189  *
2190  *      @param after_slide after which slide the new slide should be inserted into the presentation
2191  */
2192 function addSlide(after_slide)
2194         number_of_added_slides++;
2196         var g = document.createElementNS(NSS["svg"], "g");
2197         g.setAttribute("clip-path", "url(#jessyInkSlideClipPath)");
2198         g.setAttribute("id", "Whiteboard " + Date() + " presentation copy");
2199         g.setAttribute("style", "display: none;");
2201         var new_slide = new Object();
2202         new_slide["element"] = g;
2204         // Set build in transition.
2205         new_slide["transitionIn"] = new Object();
2206         var dict = defaultTransitionInDict;
2207         new_slide["transitionIn"]["name"] = dict["name"];
2208         new_slide["transitionIn"]["options"] = new Object();
2210         for (key in dict)
2211                 if (key != "name")
2212                         new_slide["transitionIn"]["options"][key] = dict[key];
2214         // Set build out transition.
2215         new_slide["transitionOut"] = new Object();
2216         dict = defaultTransitionOutDict;
2217         new_slide["transitionOut"]["name"] = dict["name"];
2218         new_slide["transitionOut"]["options"] = new Object();
2220         for (key in dict)
2221                 if (key != "name")
2222                         new_slide["transitionOut"]["options"][key] = dict[key];
2224         // Copy master slide content.
2225         if (masterSlide)
2226         {
2227                 var clonedNode = suffixNodeIds(masterSlide.cloneNode(true), "_" + Date() + " presentation_copy");
2228                 clonedNode.removeAttributeNS(NSS["inkscape"], "groupmode");
2229                 clonedNode.removeAttributeNS(NSS["inkscape"], "label");
2230                 clonedNode.style.display = "inherit";
2232                 g.appendChild(clonedNode);
2233         }
2235         // Substitute auto texts.
2236         substituteAutoTexts(g, "Whiteboard " + number_of_added_slides, "W" + number_of_added_slides, slides.length);
2238         g.setAttribute("onmouseover", "if ((currentMode == INDEX_MODE) && ( activeSlide != " + (after_slide + 1) + ")) { indexSetActiveSlide(" + (after_slide + 1) + "); };");
2240         // Create a transform group.
2241         var transformGroup = document.createElementNS(NSS["svg"], "g");
2243         // Add content to transform group.
2244         while (g.firstChild)
2245                 transformGroup.appendChild(g.firstChild);
2247         // Transfer the transform attribute from the node to the transform group.
2248         if (g.getAttribute("transform"))
2249         {
2250                 transformGroup.setAttribute("transform", g.getAttribute("transform"));
2251                 g.removeAttribute("transform");
2252         }
2254         // Create a view group.
2255         var viewGroup = document.createElementNS(NSS["svg"], "g");
2257         viewGroup.appendChild(transformGroup);
2258         new_slide["viewGroup"] = g.appendChild(viewGroup);
2260         // Insert background.
2261         if (BACKGROUND_COLOR != null)
2262         {
2263                 var rectNode = document.createElementNS(NSS["svg"], "rect");
2265                 rectNode.setAttribute("x", 0);
2266                 rectNode.setAttribute("y", 0);
2267                 rectNode.setAttribute("width", WIDTH);
2268                 rectNode.setAttribute("height", HEIGHT);
2269                 rectNode.setAttribute("id", "jessyInkBackground" + Date());
2270                 rectNode.setAttribute("fill", BACKGROUND_COLOR);
2272                 new_slide["viewGroup"].insertBefore(rectNode, new_slide["viewGroup"].firstChild);
2273         }
2275         // Set initial view even if there are no other views.
2276         var matrixOld = (new matrixSVG()).fromElements(1, 0, 0, 0, 1, 0, 0, 0, 1);
2278         new_slide["viewGroup"].setAttribute("transform", matrixOld.toAttribute());
2279         new_slide.initialView = matrixOld.toAttribute();
2281         // Insert slide
2282         var node = slides[after_slide]["element"];
2283         var next_node = node.nextSibling;
2284         var parent_node = node.parentNode;
2286         if (next_node)
2287         {
2288                 parent_node.insertBefore(g, next_node);
2289         }
2290         else
2291         {
2292                 parent_node.appendChild(g);
2293         }
2295         g = document.createElementNS(NSS["svg"], "g");
2296         g.setAttributeNS(NSS["inkscape"], "groupmode", "layer");
2297         g.setAttributeNS(NSS["inkscape"], "label", "Whiteboard " + number_of_added_slides);
2298         g.setAttribute("clip-path", "url(#jessyInkSlideClipPath)");
2299         g.setAttribute("id", "Whiteboard " + Date());
2300         g.setAttribute("style", "display: none;");
2302         new_slide["original_element"] = g;
2304         node = slides[after_slide]["original_element"];
2305         next_node = node.nextSibling;
2306         parent_node = node.parentNode;
2308         if (next_node)
2309         {
2310                 parent_node.insertBefore(g, next_node);
2311         }
2312         else
2313         {
2314                 parent_node.appendChild(g);
2315         }
2317         before_new_slide = slides.slice(0, after_slide + 1);
2318         after_new_slide = slides.slice(after_slide + 1);
2319         slides = before_new_slide.concat(new_slide, after_new_slide);
2321         //resetting the counter attributes on the slides that follow the new slide...
2322         for (var counter = after_slide+2; counter < slides.length; counter++)
2323         {
2324                 slides[counter]["element"].setAttribute("onmouseover", "if ((currentMode == INDEX_MODE) && ( activeSlide != " + counter + ")) { indexSetActiveSlide(" + counter + "); };");
2325         }
2328 /** Convenience function to obtain a transformation matrix from a point matrix.
2329  *
2330  *      @param mPoints Point matrix.
2331  *      @return A transformation matrix.
2332  */
2333 function pointMatrixToTransformation(mPoints)
2335         mPointsOld = (new matrixSVG()).fromElements(0, WIDTH, WIDTH, 0, 0, HEIGHT, 1, 1, 1);
2337         return mPointsOld.mult(mPoints.inv());
2340 /** Convenience function to obtain a matrix with three corners of a rectangle.
2341  *
2342  *      @param rect an svg rectangle
2343  *      @return a matrixSVG containing three corners of the rectangle
2344  */
2345 function rectToMatrix(rect)
2347         rectWidth = rect.getBBox().width;
2348         rectHeight = rect.getBBox().height;
2349         rectX = rect.getBBox().x;
2350         rectY = rect.getBBox().y;
2351         rectXcorr = 0;
2352         rectYcorr = 0;
2354         scaleX = WIDTH / rectWidth;
2355         scaleY = HEIGHT / rectHeight;
2357         if (scaleX > scaleY)
2358         {
2359                 scaleX = scaleY;
2360                 rectXcorr -= (WIDTH / scaleX - rectWidth) / 2;
2361                 rectWidth = WIDTH / scaleX;
2362         }       
2363         else
2364         {
2365                 scaleY = scaleX;
2366                 rectYcorr -= (HEIGHT / scaleY - rectHeight) / 2;
2367                 rectHeight = HEIGHT / scaleY;
2368         }
2370         if (rect.transform.baseVal.numberOfItems < 1)
2371         {
2372                 mRectTrans = (new matrixSVG()).fromElements(1, 0, 0, 0, 1, 0, 0, 0, 1);
2373         }
2374         else
2375         {
2376                 mRectTrans = (new matrixSVG()).fromSVGMatrix(rect.transform.baseVal.consolidate().matrix);
2377         }
2379         newBasePoints = (new matrixSVG()).fromElements(rectX, rectX, rectX, rectY, rectY, rectY, 1, 1, 1);
2380         newVectors = (new matrixSVG()).fromElements(rectXcorr, rectXcorr + rectWidth, rectXcorr + rectWidth, rectYcorr, rectYcorr, rectYcorr + rectHeight, 0, 0, 0);
2382         return mRectTrans.mult(newBasePoints.add(newVectors));
2385 /** Function to handle JessyInk elements.
2386  *
2387  *      @param  node    Element node.
2388  */
2389 function handleElement(node)
2391         if (node.getAttributeNS(NSS['jessyink'], 'element') == 'core.video')
2392         {
2393                 var url;
2394                 var width;
2395                 var height;
2396                 var x;
2397                 var y;
2398                 var transform;
2400                 var tspans = node.getElementsByTagNameNS("http://www.w3.org/2000/svg", "tspan");
2402                 for (var tspanCounter = 0; tspanCounter < tspans.length; tspanCounter++)
2403                 {
2404                         if (tspans[tspanCounter].getAttributeNS("https://launchpad.net/jessyink", "video") == "url")
2405                         {
2406                                 url = tspans[tspanCounter].firstChild.nodeValue;
2407                         }
2408                 }
2410                 var rects = node.getElementsByTagNameNS("http://www.w3.org/2000/svg", "rect");
2412                 for (var rectCounter = 0; rectCounter < rects.length; rectCounter++)
2413                 {
2414                         if (rects[rectCounter].getAttributeNS("https://launchpad.net/jessyink", "video") == "rect")
2415                         {
2416                                 x = rects[rectCounter].getAttribute("x");
2417                                 y = rects[rectCounter].getAttribute("y");
2418                                 width = rects[rectCounter].getAttribute("width");
2419                                 height = rects[rectCounter].getAttribute("height");
2420                                 transform = rects[rectCounter].getAttribute("transform");
2421                         }
2422                 }
2424                 for (var childCounter = 0; childCounter < node.childNodes.length; childCounter++)
2425                 {
2426                         if (node.childNodes[childCounter].nodeType == 1)
2427                         {
2428                                 if (node.childNodes[childCounter].style)
2429                                 {
2430                                         node.childNodes[childCounter].style.display = 'none';
2431                                 }
2432                                 else
2433                                 {
2434                                         node.childNodes[childCounter].setAttribute("style", "display: none;");
2435                                 }
2436                         }
2437                 }
2439                 var foreignNode = document.createElementNS("http://www.w3.org/2000/svg", "foreignObject");
2440                 foreignNode.setAttribute("x", x);
2441                 foreignNode.setAttribute("y", y);
2442                 foreignNode.setAttribute("width", width);
2443                 foreignNode.setAttribute("height", height);
2444                 foreignNode.setAttribute("transform", transform);
2446                 var videoNode = document.createElementNS("http://www.w3.org/1999/xhtml", "video");
2447                 videoNode.setAttribute("src", url);
2449                 foreignNode.appendChild(videoNode);
2450                 node.appendChild(foreignNode);
2451         }
2454 /** Class processing the location hash.
2455  *
2456  *      @param str location hash
2457  */
2458 function LocationHash(str)
2460         this.slideNumber = 0;
2461         this.effectNumber = 0;
2463         str = str.substr(1, str.length - 1);
2465         var parts = str.split('_');
2467         // Try to extract slide number.
2468         if (parts.length >= 1)
2469         {
2470                 try
2471                 {
2472                         var slideNumber = parseInt(parts[0]);
2474                         if (!isNaN(slideNumber))
2475                         {
2476                                 this.slideNumber = slideNumber - 1;
2477                         }
2478                 }
2479                 catch (e)
2480                 {
2481                 }
2482         }
2483         
2484         // Try to extract effect number.
2485         if (parts.length >= 2)
2486         {
2487                 try
2488                 {
2489                         var effectNumber = parseInt(parts[1]);
2491                         if (!isNaN(effectNumber))
2492                         {
2493                                 this.effectNumber = effectNumber;
2494                         }
2495                 }
2496                 catch (e)
2497                 {
2498                 }
2499         }
2502 /** Class representing an svg matrix.
2503 */
2504 function matrixSVG()
2506         this.e11 = 0; // a
2507         this.e12 = 0; // c
2508         this.e13 = 0; // e
2509         this.e21 = 0; // b
2510         this.e22 = 0; // d
2511         this.e23 = 0; // f
2512         this.e31 = 0;
2513         this.e32 = 0;
2514         this.e33 = 0;
2517 /** Constructor function.
2518  *
2519  *      @param a element a (i.e. 1, 1) as described in the svg standard.
2520  *      @param b element b (i.e. 2, 1) as described in the svg standard.
2521  *      @param c element c (i.e. 1, 2) as described in the svg standard.
2522  *      @param d element d (i.e. 2, 2) as described in the svg standard.
2523  *      @param e element e (i.e. 1, 3) as described in the svg standard.
2524  *      @param f element f (i.e. 2, 3) as described in the svg standard.
2525  */
2526 matrixSVG.prototype.fromSVGElements = function(a, b, c, d, e, f)
2528         this.e11 = a;
2529         this.e12 = c;
2530         this.e13 = e;
2531         this.e21 = b;
2532         this.e22 = d;
2533         this.e23 = f;
2534         this.e31 = 0;
2535         this.e32 = 0;
2536         this.e33 = 1;
2538         return this;
2541 /** Constructor function.
2542  *
2543  *      @param matrix an svg matrix as described in the svg standard.
2544  */
2545 matrixSVG.prototype.fromSVGMatrix = function(m)
2547         this.e11 = m.a;
2548         this.e12 = m.c;
2549         this.e13 = m.e;
2550         this.e21 = m.b;
2551         this.e22 = m.d;
2552         this.e23 = m.f;
2553         this.e31 = 0;
2554         this.e32 = 0;
2555         this.e33 = 1;
2557         return this;
2560 /** Constructor function.
2561  *
2562  *      @param e11 element 1, 1 of the matrix.
2563  *      @param e12 element 1, 2 of the matrix.
2564  *      @param e13 element 1, 3 of the matrix.
2565  *      @param e21 element 2, 1 of the matrix.
2566  *      @param e22 element 2, 2 of the matrix.
2567  *      @param e23 element 2, 3 of the matrix.
2568  *      @param e31 element 3, 1 of the matrix.
2569  *      @param e32 element 3, 2 of the matrix.
2570  *      @param e33 element 3, 3 of the matrix.
2571  */
2572 matrixSVG.prototype.fromElements = function(e11, e12, e13, e21, e22, e23, e31, e32, e33)
2574         this.e11 = e11;
2575         this.e12 = e12;
2576         this.e13 = e13;
2577         this.e21 = e21;
2578         this.e22 = e22;
2579         this.e23 = e23;
2580         this.e31 = e31;
2581         this.e32 = e32;
2582         this.e33 = e33;
2584         return this;
2587 /** Constructor function.
2588  *
2589  *      @param attrString string value of the "transform" attribute (currently only "matrix" is accepted)
2590  */
2591 matrixSVG.prototype.fromAttribute = function(attrString)
2593         str = attrString.substr(7, attrString.length - 8);
2595         str = str.trim();
2597         strArray = str.split(",");
2599         // Opera does not use commas to separate the values of the matrix, only spaces.
2600         if (strArray.length != 6)
2601                 strArray = str.split(" ");
2603         this.e11 = parseFloat(strArray[0]);
2604         this.e21 = parseFloat(strArray[1]);
2605         this.e31 = 0;
2606         this.e12 = parseFloat(strArray[2]);
2607         this.e22 = parseFloat(strArray[3]);
2608         this.e32 = 0;
2609         this.e13 = parseFloat(strArray[4]);
2610         this.e23 = parseFloat(strArray[5]);
2611         this.e33 = 1;
2613         return this;
2616 /** Output function
2617  *
2618  *      @return a string that can be used as the "transform" attribute.
2619  */
2620 matrixSVG.prototype.toAttribute = function()
2622         return "matrix(" + this.e11 + ", " + this.e21 + ", " + this.e12 + ", " + this.e22 + ", " + this.e13 + ", " + this.e23 + ")";
2625 /** Matrix nversion.
2626  *
2627  *      @return the inverse of the matrix
2628  */
2629 matrixSVG.prototype.inv = function()
2631         out = new matrixSVG();
2633         det = this.e11 * (this.e33 * this.e22 - this.e32 * this.e23) - this.e21 * (this.e33 * this.e12 - this.e32 * this.e13) + this.e31 * (this.e23 * this.e12 - this.e22 * this.e13);
2635         out.e11 =  (this.e33 * this.e22 - this.e32 * this.e23) / det;
2636         out.e12 = -(this.e33 * this.e12 - this.e32 * this.e13) / det;
2637         out.e13 =  (this.e23 * this.e12 - this.e22 * this.e13) / det;
2638         out.e21 = -(this.e33 * this.e21 - this.e31 * this.e23) / det;
2639         out.e22 =  (this.e33 * this.e11 - this.e31 * this.e13) / det;
2640         out.e23 = -(this.e23 * this.e11 - this.e21 * this.e13) / det;
2641         out.e31 =  (this.e32 * this.e21 - this.e31 * this.e22) / det;
2642         out.e32 = -(this.e32 * this.e11 - this.e31 * this.e12) / det;
2643         out.e33 =  (this.e22 * this.e11 - this.e21 * this.e12) / det;
2645         return out;
2648 /** Matrix multiplication.
2649  *
2650  *      @param op another svg matrix
2651  *      @return this * op
2652  */
2653 matrixSVG.prototype.mult = function(op)
2655         out = new matrixSVG();
2657         out.e11 = this.e11 * op.e11 + this.e12 * op.e21 + this.e13 * op.e31;
2658         out.e12 = this.e11 * op.e12 + this.e12 * op.e22 + this.e13 * op.e32;
2659         out.e13 = this.e11 * op.e13 + this.e12 * op.e23 + this.e13 * op.e33;
2660         out.e21 = this.e21 * op.e11 + this.e22 * op.e21 + this.e23 * op.e31;
2661         out.e22 = this.e21 * op.e12 + this.e22 * op.e22 + this.e23 * op.e32;
2662         out.e23 = this.e21 * op.e13 + this.e22 * op.e23 + this.e23 * op.e33;
2663         out.e31 = this.e31 * op.e11 + this.e32 * op.e21 + this.e33 * op.e31;
2664         out.e32 = this.e31 * op.e12 + this.e32 * op.e22 + this.e33 * op.e32;
2665         out.e33 = this.e31 * op.e13 + this.e32 * op.e23 + this.e33 * op.e33;
2667         return out;
2670 /** Matrix addition.
2671  *
2672  *      @param op another svg matrix
2673  *      @return this + op
2674  */
2675 matrixSVG.prototype.add = function(op)
2677         out = new matrixSVG();
2679         out.e11 = this.e11 + op.e11;
2680         out.e12 = this.e12 + op.e12;
2681         out.e13 = this.e13 + op.e13;
2682         out.e21 = this.e21 + op.e21;
2683         out.e22 = this.e22 + op.e22;
2684         out.e23 = this.e23 + op.e23;
2685         out.e31 = this.e31 + op.e31;
2686         out.e32 = this.e32 + op.e32;
2687         out.e33 = this.e33 + op.e33;
2689         return out;
2692 /** Matrix mixing.
2693  *
2694  *      @param op another svg matrix
2695  *      @parma contribOp contribution of the other matrix (0 <= contribOp <= 1)
2696  *      @return (1 - contribOp) * this + contribOp * op
2697  */
2698 matrixSVG.prototype.mix = function(op, contribOp)
2700         contribThis = 1.0 - contribOp;
2701         out = new matrixSVG();
2703         out.e11 = contribThis * this.e11 + contribOp * op.e11;
2704         out.e12 = contribThis * this.e12 + contribOp * op.e12;
2705         out.e13 = contribThis * this.e13 + contribOp * op.e13;
2706         out.e21 = contribThis * this.e21 + contribOp * op.e21;
2707         out.e22 = contribThis * this.e22 + contribOp * op.e22;
2708         out.e23 = contribThis * this.e23 + contribOp * op.e23;
2709         out.e31 = contribThis * this.e31 + contribOp * op.e31;
2710         out.e32 = contribThis * this.e32 + contribOp * op.e32;
2711         out.e33 = contribThis * this.e33 + contribOp * op.e33;
2713         return out;
2716 /** Trimming function for strings.
2717 */
2718 String.prototype.trim = function()
2720         return this.replace(/^\s+|\s+$/g, '');