Code

Fixes bug #590047: assertion crash in libavoid when marking shapes as not
[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;
42 // Presentation modes.
43 var SLIDE_MODE = 1;
44 var INDEX_MODE = 2;
45 var DRAWING_MODE = 3;
47 // Mouse handler actions.
48 var MOUSE_UP = 1;
49 var MOUSE_DOWN = 2;
50 var MOUSE_MOVE = 3;
51 var MOUSE_WHEEL = 4;
53 // Parameters.
54 var ROOT_NODE = document.getElementsByTagNameNS(NSS["svg"], "svg")[0];
55 var HEIGHT = 0;
56 var WIDTH = 0;
57 var INDEX_COLUMNS_DEFAULT = 4;
58 var INDEX_COLUMNS = INDEX_COLUMNS_DEFAULT;
59 var INDEX_OFFSET = 0;
60 var STATE_START = -1;
61 var STATE_END = -2;
62 var BACKGROUND_COLOR = null;
63 var slides = new Array();
65 // Initialisation.
66 var currentMode = SLIDE_MODE;
67 var masterSlide = null;
68 var activeSlide = 0;
69 var activeEffect = 0;
70 var timeStep = 30; // 40 ms equal 25 frames per second.
71 var lastFrameTime = null;
72 var processingEffect = false;
73 var transCounter = 0;
74 var effectArray = 0;
75 var defaultTransitionInDict = new Object();
76 defaultTransitionInDict["name"] = "appear";
77 var defaultTransitionOutDict = new Object();
78 defaultTransitionOutDict["name"] = "appear";
79 var jessyInkInitialised = false;
81 // Initialise char and key code dictionaries.
82 var charCodeDictionary = getDefaultCharCodeDictionary();
83 var keyCodeDictionary = getDefaultKeyCodeDictionary();
85 // Initialise mouse handler dictionary.
86 var mouseHandlerDictionary = getDefaultMouseHandlerDictionary();
88 var progress_bar_visible = false;
89 var timer_elapsed = 0;
90 var timer_start = timer_elapsed;
91 var timer_duration = 15; // 15 minutes
93 var history_counter = 0;
94 var history_original_elements = new Array();
95 var history_presentation_elements = new Array();
97 var mouse_original_path = null;
98 var mouse_presentation_path = null;
99 var mouse_last_x = -1;
100 var mouse_last_y = -1;
101 var mouse_min_dist_sqr = 3 * 3;
102 var path_colour = "red";
103 var path_width_default = 3;
104 var path_width = path_width_default;
105 var path_paint_width = path_width;
107 var number_of_added_slides = 0;
109 /** Initialisation function.
110  *  The whole presentation is set-up in this function.
111  */
112 function jessyInkInit()
114         // Make sure we only execute this code once. Double execution can occur if the onload event handler is set
115         // in the main svg tag as well (as was recommended in earlier versions). Executing this function twice does
116         // not lead to any problems, but it takes more time.
117         if (jessyInkInitialised)
118                 return;
120         // Making the presentation scaleable.
121         var VIEWBOX = ROOT_NODE.getAttribute("viewBox");
123         if (VIEWBOX)
124         {
125                 WIDTH = ROOT_NODE.viewBox.animVal.width;
126                 HEIGHT = ROOT_NODE.viewBox.animVal.height;
127         }
128         else
129         {
130                 HEIGHT = parseFloat(ROOT_NODE.getAttribute("height"));
131                 WIDTH = parseFloat(ROOT_NODE.getAttribute("width"));
132                 ROOT_NODE.setAttribute("viewBox", "0 0 " + WIDTH + " " + HEIGHT);
133         }
135         ROOT_NODE.setAttribute("width", "100%");
136         ROOT_NODE.setAttribute("height", "100%");
138         // Setting the background color.
139         var namedViews = document.getElementsByTagNameNS(NSS["sodipodi"], "namedview");
141         for (var counter = 0; counter < namedViews.length; counter++)
142         {
143                 if (namedViews[counter].hasAttribute("id") && namedViews[counter].hasAttribute("pagecolor"))
144                 {
145                         if (namedViews[counter].getAttribute("id") == "base")
146                         {
147                                 BACKGROUND_COLOR = namedViews[counter].getAttribute("pagecolor");
148                                 var newAttribute = "background-color:" + BACKGROUND_COLOR + ";";
150                                 if (ROOT_NODE.hasAttribute("style"))
151                                         newAttribute += ROOT_NODE.getAttribute("style");
153                                 ROOT_NODE.setAttribute("style", newAttribute);
154                         }
155                 }
156         }
158         // Defining clip-path.
159         var defsNodes = document.getElementsByTagNameNS(NSS["svg"], "defs");
161         if (defsNodes.length > 0)
162         {
163                 var existingClipPath = document.getElementById("jessyInkSlideClipPath");
165                 if (!existingClipPath)
166                 {
167                         var rectNode = document.createElementNS(NSS["svg"], "rect");
168                         var clipPath = document.createElementNS(NSS["svg"], "clipPath");
170                         rectNode.setAttribute("x", 0);
171                         rectNode.setAttribute("y", 0);
172                         rectNode.setAttribute("width", WIDTH);
173                         rectNode.setAttribute("height", HEIGHT);
175                         clipPath.setAttribute("id", "jessyInkSlideClipPath");
176                         clipPath.setAttribute("clipPathUnits", "userSpaceOnUse");
178                         clipPath.appendChild(rectNode);
179                         defsNodes[0].appendChild(clipPath);
180                 }
181         }
183         // Making a list of the slide and finding the master slide.
184         var nodes = document.getElementsByTagNameNS(NSS["svg"], "g");
185         var tempSlides = new Array();
186         var existingJessyInkPresentationLayer = null;
188         for (var counter = 0; counter < nodes.length; counter++)
189         {
190                 if (nodes[counter].getAttributeNS(NSS["inkscape"], "groupmode") && (nodes[counter].getAttributeNS(NSS["inkscape"], "groupmode") == "layer"))
191                 {
192                         if (nodes[counter].getAttributeNS(NSS["inkscape"], "label") && nodes[counter].getAttributeNS(NSS["jessyink"], "masterSlide") == "masterSlide")
193                                 masterSlide = nodes[counter];
194                         else if (nodes[counter].getAttributeNS(NSS["inkscape"], "label") && nodes[counter].getAttributeNS(NSS["jessyink"], "presentationLayer") == "presentationLayer")
195                                 existingJessyInkPresentationLayer = nodes[counter];
196                         else
197                                 tempSlides.push(nodes[counter].getAttribute("id"));
198                 }
199                 else if (nodes[counter].getAttributeNS(NSS['jessyink'], 'element'))
200                 {
201                         handleElement(nodes[counter]);
202                 }
203         }
205         // Hide master slide set default transitions.
206         if (masterSlide)
207         {
208                 masterSlide.style.display = "none";
210                 if (masterSlide.hasAttributeNS(NSS["jessyink"], "transitionIn"))
211                         defaultTransitionInDict = propStrToDict(masterSlide.getAttributeNS(NSS["jessyink"], "transitionIn"));
213                 if (masterSlide.hasAttributeNS(NSS["jessyink"], "transitionOut"))
214                         defaultTransitionOutDict = propStrToDict(masterSlide.getAttributeNS(NSS["jessyink"], "transitionOut"));
215         }
217         if (existingJessyInkPresentationLayer != null)
218         {
219                 existingJessyInkPresentationLayer.parentNode.removeChild(existingJessyInkPresentationLayer);
220         }
222         // Set start slide.
223         var hashObj = new LocationHash(window.location.hash);
225         activeSlide = hashObj.slideNumber;
226         activeEffect = hashObj.effectNumber;
228         if (activeSlide < 0)
229                 activeSlide = 0;
230         else if (activeSlide >= tempSlides.length)
231                 activeSlide = tempSlides.length - 1;
233         var originalNode = document.getElementById(tempSlides[counter]);
235         var JessyInkPresentationLayer = document.createElementNS(NSS["svg"], "g");
236         JessyInkPresentationLayer.setAttributeNS(NSS["inkscape"], "groupmode", "layer");
237         JessyInkPresentationLayer.setAttributeNS(NSS["inkscape"], "label", "JessyInk Presentation Layer");
238         JessyInkPresentationLayer.setAttributeNS(NSS["jessyink"], "presentationLayer", "presentationLayer");
239         JessyInkPresentationLayer.setAttribute("id", "jessyink_presentation_layer");
240         JessyInkPresentationLayer.style.display = "inherit";
241         ROOT_NODE.appendChild(JessyInkPresentationLayer);
243         // Gathering all the information about the transitions and effects of the slides, set the background
244         // from the master slide and substitute the auto-texts.
245         for (var counter = 0; counter < tempSlides.length; counter++)
246         {
247                 var originalNode = document.getElementById(tempSlides[counter]);
248                 originalNode.style.display = "none";
249                 var node = suffixNodeIds(originalNode.cloneNode(true), "_" + counter);
250                 JessyInkPresentationLayer.appendChild(node);
251                 slides[counter] = new Object();
252                 slides[counter]["original_element"] = originalNode;
253                 slides[counter]["element"] = node;
255                 // Set build in transition.
256                 slides[counter]["transitionIn"] = new Object();
258                 var dict;
260                 if (node.hasAttributeNS(NSS["jessyink"], "transitionIn"))
261                         dict = propStrToDict(node.getAttributeNS(NSS["jessyink"], "transitionIn"));
262                 else
263                         dict = defaultTransitionInDict;
265                 slides[counter]["transitionIn"]["name"] = dict["name"];
266                 slides[counter]["transitionIn"]["options"] = new Object();
268                 for (key in dict)
269                         if (key != "name")
270                                 slides[counter]["transitionIn"]["options"][key] = dict[key];
272                 // Set build out transition.
273                 slides[counter]["transitionOut"] = new Object();
275                 if (node.hasAttributeNS(NSS["jessyink"], "transitionOut"))
276                         dict = propStrToDict(node.getAttributeNS(NSS["jessyink"], "transitionOut"));
277                 else
278                         dict = defaultTransitionOutDict;
280                 slides[counter]["transitionOut"]["name"] = dict["name"];
281                 slides[counter]["transitionOut"]["options"] = new Object();
283                 for (key in dict)
284                         if (key != "name")
285                                 slides[counter]["transitionOut"]["options"][key] = dict[key];
287                 // Copy master slide content.
288                 if (masterSlide)
289                 {
290                         var clonedNode = suffixNodeIds(masterSlide.cloneNode(true), "_" + counter);
291                         clonedNode.removeAttributeNS(NSS["inkscape"], "groupmode");
292                         clonedNode.removeAttributeNS(NSS["inkscape"], "label");
293                         clonedNode.style.display = "inherit";
295                         node.insertBefore(clonedNode, node.firstChild);
296                 }
298                 // Setting clip path.
299                 node.setAttribute("clip-path", "url(#jessyInkSlideClipPath)");
301                 // Substitute auto texts.
302                 substituteAutoTexts(node, node.getAttributeNS(NSS["inkscape"], "label"), counter + 1, tempSlides.length);
304                 node.removeAttributeNS(NSS["inkscape"], "groupmode");
305                 node.removeAttributeNS(NSS["inkscape"], "label");
307                 // Set effects.
308                 var tempEffects = new Array();
309                 var groups = new Object();
311                 for (var IOCounter = 0; IOCounter <= 1; IOCounter++)
312                 {
313                         var propName = "";
314                         var dir = 0;
316                         if (IOCounter == 0)
317                         {
318                                 propName = "effectIn";
319                                 dir = 1;
320                         }
321                         else if (IOCounter == 1)
322                         {
323                                 propName = "effectOut";
324                                 dir = -1;
325                         }
327                         var effects = getElementsByPropertyNS(node, NSS["jessyink"], propName);
329                         for (var effectCounter = 0; effectCounter < effects.length; effectCounter++)
330                         {
331                                 var element = document.getElementById(effects[effectCounter]);
332                                 var dict = propStrToDict(element.getAttributeNS(NSS["jessyink"], propName));
334                                 // Put every element that has an effect associated with it, into its own group.
335                                 // Unless of course, we already put it into its own group.
336                                 if (!(groups[element.id]))
337                                 {
338                                         var newGroup = document.createElementNS(NSS["svg"], "g");
340                                         element.parentNode.insertBefore(newGroup, element);
341                                         newGroup.appendChild(element.parentNode.removeChild(element));
342                                         groups[element.id] = newGroup;
343                                 }
345                                 var effectDict = new Object();
347                                 effectDict["effect"] = dict["name"];
348                                 effectDict["dir"] = dir;
349                                 effectDict["element"] = groups[element.id];
351                                 for (var option in dict)
352                                 {
353                                         if ((option != "name") && (option != "order"))
354                                         {
355                                                 if (!effectDict["options"])
356                                                         effectDict["options"] = new Object();
358                                                 effectDict["options"][option] = dict[option];
359                                         }
360                                 }
362                                 if (!tempEffects[dict["order"]])
363                                         tempEffects[dict["order"]] = new Array();
365                                 tempEffects[dict["order"]][tempEffects[dict["order"]].length] = effectDict;
366                         }
367                 }
369                 // Make invisible, but keep in rendering tree to ensure that bounding box can be calculated.
370                 node.setAttribute("opacity",0);
371                 node.style.display = "inherit";
373                 // Create a transform group.
374                 var transformGroup = document.createElementNS(NSS["svg"], "g");
376                 // Add content to transform group.
377                 while (node.firstChild)
378                         transformGroup.appendChild(node.firstChild);
380                 // Transfer the transform attribute from the node to the transform group.
381                 if (node.getAttribute("transform"))
382                 {
383                         transformGroup.setAttribute("transform", node.getAttribute("transform"));
384                         node.removeAttribute("transform");
385                 }
387                 // Create a view group.
388                 var viewGroup = document.createElementNS(NSS["svg"], "g");
390                 viewGroup.appendChild(transformGroup);
391                 slides[counter]["viewGroup"] = node.appendChild(viewGroup);
393                 // Insert background.
394                 if (BACKGROUND_COLOR != null)
395                 {
396                         var rectNode = document.createElementNS(NSS["svg"], "rect");
398                         rectNode.setAttribute("x", 0);
399                         rectNode.setAttribute("y", 0);
400                         rectNode.setAttribute("width", WIDTH);
401                         rectNode.setAttribute("height", HEIGHT);
402                         rectNode.setAttribute("id", "jessyInkBackground" + counter);
403                         rectNode.setAttribute("fill", BACKGROUND_COLOR);
405                         slides[counter]["viewGroup"].insertBefore(rectNode, slides[counter]["viewGroup"].firstChild);
406                 }
408                 // Set views.
409                 var tempViews = new Array();
410                 var views = getElementsByPropertyNS(node, NSS["jessyink"], "view");
411                 var matrixOld = (new matrixSVG()).fromElements(1, 0, 0, 0, 1, 0, 0, 0, 1);
413                 // Set initial view even if there are no other views.
414                 slides[counter]["viewGroup"].setAttribute("transform", matrixOld.toAttribute());
415                 slides[counter].initialView = matrixOld.toAttribute();
417                 for (var viewCounter = 0; viewCounter < views.length; viewCounter++)
418                 {
419                         var element = document.getElementById(views[viewCounter]);
420                         var dict = propStrToDict(element.getAttributeNS(NSS["jessyink"], "view"));
422                         if (dict["order"] == 0)
423                         {
424                                 matrixOld = pointMatrixToTransformation(rectToMatrix(element)).mult((new matrixSVG()).fromSVGMatrix(slides[counter].viewGroup.getScreenCTM()).inv().mult((new matrixSVG()).fromSVGMatrix(element.parentNode.getScreenCTM())).inv());
425                                 slides[counter].initialView = matrixOld.toAttribute();
426                         }
427                         else
428                         {
429                                 var effectDict = new Object();
431                                 effectDict["effect"] = dict["name"];
432                                 effectDict["dir"] = 1;
433                                 effectDict["element"] = slides[counter]["viewGroup"];
434                                 effectDict["order"] = dict["order"];
436                                 for (var option in dict)
437                                 {
438                                         if ((option != "name") && (option != "order"))
439                                         {
440                                                 if (!effectDict["options"])
441                                                         effectDict["options"] = new Object();
443                                                 effectDict["options"][option] = dict[option];
444                                         }
445                                 }
447                                 effectDict["options"]["matrixNew"] = pointMatrixToTransformation(rectToMatrix(element)).mult((new matrixSVG()).fromSVGMatrix(slides[counter].viewGroup.getScreenCTM()).inv().mult((new matrixSVG()).fromSVGMatrix(element.parentNode.getScreenCTM())).inv());
449                                 tempViews[dict["order"]] = effectDict;
450                         }
452                         // Remove element.
453                         element.parentNode.removeChild(element);
454                 }
456                 // Consolidate view array and append it to the effect array.
457                 if (tempViews.length > 0)
458                 {
459                         for (var viewCounter = 0; viewCounter < tempViews.length; viewCounter++)
460                         {
461                                 if (tempViews[viewCounter])
462                                 {
463                                         tempViews[viewCounter]["options"]["matrixOld"] = matrixOld;
464                                         matrixOld = tempViews[viewCounter]["options"]["matrixNew"];
466                                         if (!tempEffects[tempViews[viewCounter]["order"]])
467                                                 tempEffects[tempViews[viewCounter]["order"]] = new Array();
469                                         tempEffects[tempViews[viewCounter]["order"]][tempEffects[tempViews[viewCounter]["order"]].length] = tempViews[viewCounter];
470                                 }
471                         }
472                 }
474                 // Set consolidated effect array.
475                 if (tempEffects.length > 0)
476                 {
477                         slides[counter]["effects"] = new Array();
479                         for (var effectCounter = 0; effectCounter < tempEffects.length; effectCounter++)
480                         {
481                                 if (tempEffects[effectCounter])
482                                         slides[counter]["effects"][slides[counter]["effects"].length] = tempEffects[effectCounter];
483                         }
484                 }
486                 node.setAttribute("onmouseover", "if ((currentMode == INDEX_MODE) && ( activeSlide != " + counter + ")) { indexSetActiveSlide(" + counter + "); };");
488                 // Set visibility for initial state.
489                 if (counter == activeSlide)
490                 {
491                         node.style.display = "inherit";
492                         node.setAttribute("opacity",1);
493                 }
494                 else
495                 {
496                         node.style.display = "none";
497                         node.setAttribute("opacity",0);
498                 }
499         }
501         // Set key handler.
502         var jessyInkObjects = document.getElementsByTagNameNS(NSS["svg"], "g");
504         for (var counter = 0; counter < jessyInkObjects.length; counter++)
505         {
506                 var elem = jessyInkObjects[counter];
508                 if (elem.getAttributeNS(NSS["jessyink"], "customKeyBindings"))
509                 {
510                         if (elem.getCustomKeyBindings != undefined)
511                                 keyCodeDictionary = elem.getCustomKeyBindings();
513                         if (elem.getCustomCharBindings != undefined)
514                                 charCodeDictionary = elem.getCustomCharBindings();
515                 }
516         }
518         // Set mouse handler.
519         var jessyInkMouseHandler = document.getElementsByTagNameNS(NSS["jessyink"], "mousehandler");
521         for (var counter = 0; counter < jessyInkMouseHandler.length; counter++)
522         {
523                 var elem = jessyInkMouseHandler[counter];
525                 if (elem.getMouseHandler != undefined)
526                 {
527                         var tempDict = elem.getMouseHandler();
529                         for (mode in tempDict)
530                         {
531                                 if (!mouseHandlerDictionary[mode])
532                                         mouseHandlerDictionary[mode] = new Object();
534                                 for (handler in tempDict[mode])
535                                         mouseHandlerDictionary[mode][handler] = tempDict[mode][handler];
536                         }
537                 }
538         }
540         // Check effect number.
541         if ((activeEffect < 0) || (!slides[activeSlide].effects))
542         {
543                 activeEffect = 0;
544         }
545         else if (activeEffect > slides[activeSlide].effects.length)
546         {
547                 activeEffect = slides[activeSlide].effects.length;
548         }
550         createProgressBar(JessyInkPresentationLayer);
551         hideProgressBar();
552         setProgressBarValue(activeSlide);
553         setTimeIndicatorValue(0);
554         setInterval("updateTimer()", 1000);
555         setSlideToState(activeSlide, activeEffect);
556         jessyInkInitialised = true;
559 /** Function to subtitute the auto-texts.
560  *
561  *  @param node the node
562  *  @param slideName name of the slide the node is on
563  *  @param slideNumber number of the slide the node is on
564  *  @param numberOfSlides number of slides in the presentation
565  */
566 function substituteAutoTexts(node, slideName, slideNumber, numberOfSlides)
568         var texts = node.getElementsByTagNameNS(NSS["svg"], "tspan");
570         for (var textCounter = 0; textCounter < texts.length; textCounter++)
571         {
572                 if (texts[textCounter].getAttributeNS(NSS["jessyink"], "autoText") == "slideNumber")
573                         texts[textCounter].firstChild.nodeValue = slideNumber;
574                 else if (texts[textCounter].getAttributeNS(NSS["jessyink"], "autoText") == "numberOfSlides")
575                         texts[textCounter].firstChild.nodeValue = numberOfSlides;
576                 else if (texts[textCounter].getAttributeNS(NSS["jessyink"], "autoText") == "slideTitle")
577                         texts[textCounter].firstChild.nodeValue = slideName;
578         }
581 /** Convenience function to get an element depending on whether it has a property with a particular name.
582  *      This function emulates some dearly missed XPath functionality.
583  *
584  *  @param node the node
585  *  @param namespace namespace of the attribute
586  *  @param name attribute name
587  */
588 function getElementsByPropertyNS(node, namespace, name)
590         var elems = new Array();
592         if (node.getAttributeNS(namespace, name))
593                 elems.push(node.getAttribute("id"));
595         for (var counter = 0; counter < node.childNodes.length; counter++)
596         {
597                 if (node.childNodes[counter].nodeType == 1)
598                         elems = elems.concat(getElementsByPropertyNS(node.childNodes[counter], namespace, name));
599         }
601         return elems;
604 /** Function to dispatch the next effect, if there is none left, change the slide.
605  *
606  *  @param dir direction of the change (1 = forwards, -1 = backwards)
607  */
608 function dispatchEffects(dir)
610         if (slides[activeSlide]["effects"] && (((dir == 1) && (activeEffect < slides[activeSlide]["effects"].length)) || ((dir == -1) && (activeEffect > 0))))
611         {
612                 processingEffect = true;
614                 if (dir == 1)
615                 {
616                         effectArray = slides[activeSlide]["effects"][activeEffect];
617                         activeEffect += dir;
618                 }
619                 else if (dir == -1)
620                 {
621                         activeEffect += dir;
622                         effectArray = slides[activeSlide]["effects"][activeEffect];
623                 }
625                 transCounter = 0;
626                 startTime = (new Date()).getTime();
627                 lastFrameTime = null;
628                 effect(dir);
629         }
630         else if (((dir == 1) && (activeSlide < (slides.length - 1))) || (((dir == -1) && (activeSlide > 0))))
631         {
632                 changeSlide(dir);
633         }
636 /** Function to skip effects and directly either put the slide into start or end state or change slides.
637  *
638  *  @param dir direction of the change (1 = forwards, -1 = backwards)
639  */
640 function skipEffects(dir)
642         if (slides[activeSlide]["effects"] && (((dir == 1) && (activeEffect < slides[activeSlide]["effects"].length)) || ((dir == -1) && (activeEffect > 0))))
643         {
644                 processingEffect = true;
646                 if (slides[activeSlide]["effects"] && (dir == 1))
647                         activeEffect = slides[activeSlide]["effects"].length;
648                 else
649                         activeEffect = 0;
651                 if (dir == 1)
652                         setSlideToState(activeSlide, STATE_END);
653                 else
654                         setSlideToState(activeSlide, STATE_START);
656                 processingEffect = false;
657         }
658         else if (((dir == 1) && (activeSlide < (slides.length - 1))) || (((dir == -1) && (activeSlide > 0))))
659         {
660                 changeSlide(dir);
661         }
664 /** Function to change between slides.
665  *
666  *  @param dir direction (1 = forwards, -1 = backwards)
667  */
668 function changeSlide(dir)
670         processingEffect = true;
671         effectArray = new Array();
673         effectArray[0] = new Object();
674         if (dir == 1)
675         {
676                 effectArray[0]["effect"] = slides[activeSlide]["transitionOut"]["name"];
677                 effectArray[0]["options"] = slides[activeSlide]["transitionOut"]["options"];
678                 effectArray[0]["dir"] = -1;
679         }
680         else if (dir == -1)
681         {
682                 effectArray[0]["effect"] = slides[activeSlide]["transitionIn"]["name"];
683                 effectArray[0]["options"] = slides[activeSlide]["transitionIn"]["options"];
684                 effectArray[0]["dir"] = 1;
685         }
686         effectArray[0]["element"] = slides[activeSlide]["element"];
688         activeSlide += dir;
689         setProgressBarValue(activeSlide);
691         effectArray[1] = new Object();
693         if (dir == 1)
694         {
695                 effectArray[1]["effect"] = slides[activeSlide]["transitionIn"]["name"];
696                 effectArray[1]["options"] = slides[activeSlide]["transitionIn"]["options"];
697                 effectArray[1]["dir"] = 1;
698         }
699         else if (dir == -1)
700         {
701                 effectArray[1]["effect"] = slides[activeSlide]["transitionOut"]["name"];
702                 effectArray[1]["options"] = slides[activeSlide]["transitionOut"]["options"];
703                 effectArray[1]["dir"] = -1;
704         }
706         effectArray[1]["element"] = slides[activeSlide]["element"];
708         if (slides[activeSlide]["effects"] && (dir == -1))
709                 activeEffect = slides[activeSlide]["effects"].length;
710         else
711                 activeEffect = 0;
713         if (dir == -1)
714                 setSlideToState(activeSlide, STATE_END);
715         else
716                 setSlideToState(activeSlide, STATE_START);
718         transCounter = 0;
719         startTime = (new Date()).getTime();
720         lastFrameTime = null;
721         effect(dir);
724 /** Function to toggle between index and slide mode.
725 */
726 function toggleSlideIndex()
728         var suspendHandle = ROOT_NODE.suspendRedraw(500);
730         if (currentMode == SLIDE_MODE)
731         {
732                 hideProgressBar();              
733                 INDEX_OFFSET = -1;
734                 indexSetPageSlide(activeSlide);
735                 currentMode = INDEX_MODE;
736         }
737         else if (currentMode == INDEX_MODE)
738         {
739                 for (var counter = 0; counter < slides.length; counter++)
740                 {
741                         slides[counter]["element"].setAttribute("transform","scale(1)");
743                         if (counter == activeSlide)
744                         {
745                                 slides[counter]["element"].style.display = "inherit";
746                                 slides[counter]["element"].setAttribute("opacity",1);
747                                 activeEffect = 0;
748                         }
749                         else
750                         {
751                                 slides[counter]["element"].setAttribute("opacity",0);
752                                 slides[counter]["element"].style.display = "none";
753                         }
754                 }
755                 currentMode = SLIDE_MODE;
756                 setSlideToState(activeSlide, STATE_START);
757                 setProgressBarValue(activeSlide);
759                 if (progress_bar_visible)
760                 {
761                         showProgressBar();
762                 }
763         }
765         ROOT_NODE.unsuspendRedraw(suspendHandle);
766         ROOT_NODE.forceRedraw();
769 /** Function to run an effect.
770  *
771  *  @param dir direction in which to play the effect (1 = forwards, -1 = backwards)
772  */
773 function effect(dir)
775         var done = true;
777         var suspendHandle = ROOT_NODE.suspendRedraw(200);
779         for (var counter = 0; counter < effectArray.length; counter++)
780         {
781                 if (effectArray[counter]["effect"] == "fade")
782                         done &= fade(parseInt(effectArray[counter]["dir"]) * dir, effectArray[counter]["element"], transCounter, effectArray[counter]["options"]);
783                 else if (effectArray[counter]["effect"] == "appear")
784                         done &= appear(parseInt(effectArray[counter]["dir"]) * dir, effectArray[counter]["element"], transCounter, effectArray[counter]["options"]);
785                 else if (effectArray[counter]["effect"] == "pop")
786                         done &= pop(parseInt(effectArray[counter]["dir"]) * dir, effectArray[counter]["element"], transCounter, effectArray[counter]["options"]);
787                 else if (effectArray[counter]["effect"] == "view")
788                         done &= view(parseInt(effectArray[counter]["dir"]) * dir, effectArray[counter]["element"], transCounter, effectArray[counter]["options"]);
789         }
791         ROOT_NODE.unsuspendRedraw(suspendHandle);
792         ROOT_NODE.forceRedraw();
794         if (!done)
795         {
796                 var currentTime = (new Date()).getTime();
797                 var timeDiff = 1;
799                 transCounter = currentTime - startTime;
801                 if (lastFrameTime != null)
802                 {
803                         timeDiff = timeStep - (currentTime - lastFrameTime);
805                         if (timeDiff <= 0)
806                                 timeDiff = 1;
807                 }
809                 lastFrameTime = currentTime;
811                 window.setTimeout("effect(" + dir + ")", timeDiff);
812         }
813         else
814         {
815                 window.location.hash = (activeSlide + 1) + '_' + activeEffect;
816                 processingEffect = false;
817         }
820 /** Function to display the index sheet.
821  *
822  *  @param offsetNumber offset number
823  */
824 function displayIndex(offsetNumber)
826         var offsetX = 0;
827         var offsetY = 0;
829         if (offsetNumber < 0)
830                 offsetNumber = 0;
831         else if (offsetNumber >= slides.length)
832                 offsetNumber = slides.length - 1;
834         for (var counter = 0; counter < slides.length; counter++)
835         {
836                 if ((counter < offsetNumber) || (counter > offsetNumber + INDEX_COLUMNS * INDEX_COLUMNS - 1))
837                 {
838                         slides[counter]["element"].setAttribute("opacity",0);
839                         slides[counter]["element"].style.display = "none";
840                 }
841                 else
842                 {
843                         offsetX = ((counter - offsetNumber) % INDEX_COLUMNS) * WIDTH;
844                         offsetY = Math.floor((counter - offsetNumber) / INDEX_COLUMNS) * HEIGHT;
846                         slides[counter]["element"].setAttribute("transform","scale("+1/INDEX_COLUMNS+") translate("+offsetX+","+offsetY+")");
847                         slides[counter]["element"].style.display = "inherit";
848                         slides[counter]["element"].setAttribute("opacity",0.5);
849                 }
851                 setSlideToState(counter, STATE_END);
852         }
854         //do we need to save the current offset?
855         if (INDEX_OFFSET != offsetNumber)
856                 INDEX_OFFSET = offsetNumber;
859 /** Function to set the active slide in the slide view.
860  *
861  *  @param nbr index of the active slide
862  */
863 function slideSetActiveSlide(nbr)
865         if (nbr >= slides.length)
866                 nbr = slides.length - 1;
867         else if (nbr < 0)
868                 nbr = 0;
870         slides[activeSlide]["element"].setAttribute("opacity",0);
871         slides[activeSlide]["element"].style.display = "none";
873         activeSlide = parseInt(nbr);
875         setSlideToState(activeSlide, STATE_START);
876         slides[activeSlide]["element"].style.display = "inherit";
877         slides[activeSlide]["element"].setAttribute("opacity",1);
879         activeEffect = 0;
880         setProgressBarValue(nbr);
883 /** Function to set the active slide in the index view.
884  *
885  *  @param nbr index of the active slide
886  */
887 function indexSetActiveSlide(nbr)
889         if (nbr >= slides.length)
890                 nbr = slides.length - 1;
891         else if (nbr < 0)
892                 nbr = 0;
894         slides[activeSlide]["element"].setAttribute("opacity",0.5);
896         activeSlide = parseInt(nbr);
897         window.location.hash = (activeSlide + 1) + '_0';
899         slides[activeSlide]["element"].setAttribute("opacity",1);
902 /** Function to set the page and active slide in index view. 
903  *
904  *  @param nbr index of the active slide
905  *
906  *  NOTE: To force a redraw,
907  *  set INDEX_OFFSET to -1 before calling indexSetPageSlide().
908  *
909  *  This is necessary for zooming (otherwise the index might not
910  *  get redrawn) and when switching to index mode.
911  *
912  *  INDEX_OFFSET = -1
913  *  indexSetPageSlide(activeSlide);
914  */
915 function indexSetPageSlide(nbr)
917         if (nbr >= slides.length)
918                 nbr = slides.length - 1;
919         else if (nbr < 0)
920                 nbr = 0;
922         //calculate the offset
923         var offset = nbr - nbr % (INDEX_COLUMNS * INDEX_COLUMNS);
925         if (offset < 0)
926                 offset = 0;
928         //if different from kept offset, then record and change the page
929         if (offset != INDEX_OFFSET)
930         {
931                 INDEX_OFFSET = offset;
932                 displayIndex(INDEX_OFFSET);
933         }
935         //set the active slide
936         indexSetActiveSlide(nbr);
939 /** Event handler for key press.
940  *
941  *  @param e the event
942  */
943 function keydown(e)
945         if (!e)
946                 e = window.event;
948         code = e.keyCode || e.charCode;
950         if (!processingEffect && keyCodeDictionary[currentMode] && keyCodeDictionary[currentMode][code])
951                 keyCodeDictionary[currentMode][code]();
952         else
953                 document.onkeypress = keypress;
955 // Set event handler for key down.
956 document.onkeydown = keydown;
958 /** Event handler for key press.
959  *
960  *  @param e the event
961  */
962 function keypress(e)
964         document.onkeypress = null;
966         if (!e)
967                 e = window.event;
969         str = String.fromCharCode(e.keyCode || e.charCode);
971         if (!processingEffect && charCodeDictionary[currentMode] && charCodeDictionary[currentMode][str])
972                 charCodeDictionary[currentMode][str]();
975 /** Function to supply the default char code dictionary.
976  *
977  * @returns default char code dictionary
978  */
979 function getDefaultCharCodeDictionary()
981         var charCodeDict = new Object();
983         charCodeDict[SLIDE_MODE] = new Object();
984         charCodeDict[INDEX_MODE] = new Object();
985         charCodeDict[DRAWING_MODE] = new Object();
987         charCodeDict[SLIDE_MODE]["i"] = function () { toggleSlideIndex(); };
988         charCodeDict[SLIDE_MODE]["d"] = function () { slideSwitchToDrawingMode(); };
989         charCodeDict[SLIDE_MODE]["D"] = function () { slideQueryDuration(); };
990         charCodeDict[SLIDE_MODE]["n"] = function () { slideAddSlide(activeSlide); };
991         charCodeDict[SLIDE_MODE]["p"] = function () { slideToggleProgressBarVisibility(); };
992         charCodeDict[SLIDE_MODE]["t"] = function () { slideResetTimer(); };
993         charCodeDict[SLIDE_MODE]["e"] = function () { slideUpdateExportLayer(); };
995         charCodeDict[DRAWING_MODE]["d"] = function () { drawingSwitchToSlideMode(); };
996         charCodeDict[DRAWING_MODE]["0"] = function () { drawingResetPathWidth(); };
997         charCodeDict[DRAWING_MODE]["1"] = function () { drawingSetPathWidth(1.0); };
998         charCodeDict[DRAWING_MODE]["3"] = function () { drawingSetPathWidth(3.0); };
999         charCodeDict[DRAWING_MODE]["5"] = function () { drawingSetPathWidth(5.0); };
1000         charCodeDict[DRAWING_MODE]["7"] = function () { drawingSetPathWidth(7.0); };
1001         charCodeDict[DRAWING_MODE]["9"] = function () { drawingSetPathWidth(9.0); };
1002         charCodeDict[DRAWING_MODE]["b"] = function () { drawingSetPathColour("blue"); };
1003         charCodeDict[DRAWING_MODE]["c"] = function () { drawingSetPathColour("cyan"); };
1004         charCodeDict[DRAWING_MODE]["g"] = function () { drawingSetPathColour("green"); };
1005         charCodeDict[DRAWING_MODE]["k"] = function () { drawingSetPathColour("black"); };
1006         charCodeDict[DRAWING_MODE]["m"] = function () { drawingSetPathColour("magenta"); };
1007         charCodeDict[DRAWING_MODE]["o"] = function () { drawingSetPathColour("orange"); };
1008         charCodeDict[DRAWING_MODE]["r"] = function () { drawingSetPathColour("red"); };
1009         charCodeDict[DRAWING_MODE]["w"] = function () { drawingSetPathColour("white"); };
1010         charCodeDict[DRAWING_MODE]["y"] = function () { drawingSetPathColour("yellow"); };
1011         charCodeDict[DRAWING_MODE]["z"] = function () { drawingUndo(); };
1013         charCodeDict[INDEX_MODE]["i"] = function () { toggleSlideIndex(); };
1014         charCodeDict[INDEX_MODE]["-"] = function () { indexDecreaseNumberOfColumns(); };
1015         charCodeDict[INDEX_MODE]["="] = function () { indexIncreaseNumberOfColumns(); };
1016         charCodeDict[INDEX_MODE]["+"] = function () { indexIncreaseNumberOfColumns(); };
1017         charCodeDict[INDEX_MODE]["0"] = function () { indexResetNumberOfColumns(); };
1019         return charCodeDict;
1022 /** Function to supply the default key code dictionary.
1023  *
1024  * @returns default key code dictionary
1025  */
1026 function getDefaultKeyCodeDictionary()
1028         var keyCodeDict = new Object();
1030         keyCodeDict[SLIDE_MODE] = new Object();
1031         keyCodeDict[INDEX_MODE] = new Object();
1032         keyCodeDict[DRAWING_MODE] = new Object();
1034         keyCodeDict[SLIDE_MODE][LEFT_KEY] = function() { dispatchEffects(-1); };
1035         keyCodeDict[SLIDE_MODE][RIGHT_KEY] = function() { dispatchEffects(1); };
1036         keyCodeDict[SLIDE_MODE][UP_KEY] = function() { skipEffects(-1); };
1037         keyCodeDict[SLIDE_MODE][DOWN_KEY] = function() { skipEffects(1); };
1038         keyCodeDict[SLIDE_MODE][PAGE_UP_KEY] = function() { dispatchEffects(-1); };
1039         keyCodeDict[SLIDE_MODE][PAGE_DOWN_KEY] = function() { dispatchEffects(1); };
1040         keyCodeDict[SLIDE_MODE][HOME_KEY] = function() { slideSetActiveSlide(0); };
1041         keyCodeDict[SLIDE_MODE][END_KEY] = function() { slideSetActiveSlide(slides.length - 1); };
1042         keyCodeDict[SLIDE_MODE][SPACE_KEY] = function() { dispatchEffects(1); };
1044         keyCodeDict[INDEX_MODE][LEFT_KEY] = function() { indexSetPageSlide(activeSlide - 1); };
1045         keyCodeDict[INDEX_MODE][RIGHT_KEY] = function() { indexSetPageSlide(activeSlide + 1); };
1046         keyCodeDict[INDEX_MODE][UP_KEY] = function() { indexSetPageSlide(activeSlide - INDEX_COLUMNS); };
1047         keyCodeDict[INDEX_MODE][DOWN_KEY] = function() { indexSetPageSlide(activeSlide + INDEX_COLUMNS); };
1048         keyCodeDict[INDEX_MODE][PAGE_UP_KEY] = function() { indexSetPageSlide(activeSlide - INDEX_COLUMNS * INDEX_COLUMNS); };
1049         keyCodeDict[INDEX_MODE][PAGE_DOWN_KEY] = function() { indexSetPageSlide(activeSlide + INDEX_COLUMNS * INDEX_COLUMNS); };
1050         keyCodeDict[INDEX_MODE][HOME_KEY] = function() { indexSetPageSlide(0); };
1051         keyCodeDict[INDEX_MODE][END_KEY] = function() { indexSetPageSlide(slides.length - 1); };
1052         keyCodeDict[INDEX_MODE][ENTER_KEY] = function() { toggleSlideIndex(); };
1054         return keyCodeDict;
1057 /** Function to handle all mouse events.
1058  *
1059  *      @param  evnt    event
1060  *      @param  action  type of event (e.g. mouse up, mouse wheel)
1061  */
1062 function mouseHandlerDispatch(evnt, action)
1064         if (!evnt)
1065                 evnt = window.event;
1067         var retVal = true;
1069         if (!processingEffect && mouseHandlerDictionary[currentMode] && mouseHandlerDictionary[currentMode][action])
1070         {
1071                 var subRetVal = mouseHandlerDictionary[currentMode][action](evnt);
1073                 if (subRetVal != null && subRetVal != undefined)
1074                         retVal = subRetVal;
1075         }
1077         if (evnt.preventDefault && !retVal)
1078                 evnt.preventDefault();
1080         evnt.returnValue = retVal;
1082         return retVal;
1085 // Set mouse event handler.
1086 document.onmousedown = function(e) { return mouseHandlerDispatch(e, MOUSE_DOWN); };
1087 document.onmouseup = function(e) { return mouseHandlerDispatch(e, MOUSE_UP); };
1088 document.onmousemove = function(e) { return mouseHandlerDispatch(e, MOUSE_MOVE); };
1090 // Moz
1091 if (window.addEventListener)
1093         window.addEventListener('DOMMouseScroll', function(e) { return mouseHandlerDispatch(e, MOUSE_WHEEL); }, false);
1096 // Opera Safari OK - may not work in IE
1097 window.onmousewheel = function(e) { return mouseHandlerDispatch(e, MOUSE_WHEEL); };
1099 /** Function to supply the default mouse handler dictionary.
1100  *
1101  * @returns default mouse handler dictionary
1102  */
1103 function getDefaultMouseHandlerDictionary()
1105         var mouseHandlerDict = new Object();
1107         mouseHandlerDict[SLIDE_MODE] = new Object();
1108         mouseHandlerDict[INDEX_MODE] = new Object();
1109         mouseHandlerDict[DRAWING_MODE] = new Object();
1111         mouseHandlerDict[SLIDE_MODE][MOUSE_DOWN] = function(evnt) { dispatchEffects(1); };
1112         mouseHandlerDict[SLIDE_MODE][MOUSE_WHEEL] = function(evnt) { slideMousewheel(evnt); };
1114         mouseHandlerDict[INDEX_MODE][MOUSE_DOWN] = function(evnt) { toggleSlideIndex(); };
1116         mouseHandlerDict[DRAWING_MODE][MOUSE_DOWN] = function(evnt) { drawingMousedown(evnt); };
1117         mouseHandlerDict[DRAWING_MODE][MOUSE_UP] = function(evnt) { drawingMouseup(evnt); };
1118         mouseHandlerDict[DRAWING_MODE][MOUSE_MOVE] = function(evnt) { drawingMousemove(evnt); };
1120         return mouseHandlerDict;
1123 /** Function to switch from slide mode to drawing mode.
1124 */
1125 function slideSwitchToDrawingMode()
1127         currentMode = DRAWING_MODE;
1129         var tempDict;
1131         if (ROOT_NODE.hasAttribute("style"))
1132                 tempDict = propStrToDict(ROOT_NODE.getAttribute("style"));
1133         else
1134                 tempDict = new Object();
1136         tempDict["cursor"] = "crosshair";
1137         ROOT_NODE.setAttribute("style", dictToPropStr(tempDict));
1140 /** Function to switch from drawing mode to slide mode.
1141 */
1142 function drawingSwitchToSlideMode()
1144         currentMode = SLIDE_MODE;
1146         var tempDict;
1148         if (ROOT_NODE.hasAttribute("style"))
1149                 tempDict = propStrToDict(ROOT_NODE.getAttribute("style"));
1150         else
1151                 tempDict = new Object();
1153         tempDict["cursor"] = "auto";
1154         ROOT_NODE.setAttribute("style", dictToPropStr(tempDict));
1157 /** Function to decrease the number of columns in index mode.
1158 */
1159 function indexDecreaseNumberOfColumns()
1161         if (INDEX_COLUMNS >= 3)
1162         {
1163                 INDEX_COLUMNS -= 1;
1164                 INDEX_OFFSET = -1
1165                         indexSetPageSlide(activeSlide);
1166         }
1169 /** Function to increase the number of columns in index mode.
1170 */
1171 function indexIncreaseNumberOfColumns()
1173         if (INDEX_COLUMNS < 7)
1174         {
1175                 INDEX_COLUMNS += 1;
1176                 INDEX_OFFSET = -1
1177                         indexSetPageSlide(activeSlide);
1178         }
1181 /** Function to reset the number of columns in index mode.
1182 */
1183 function indexResetNumberOfColumns()
1185         if (INDEX_COLUMNS != INDEX_COLUMNS_DEFAULT)
1186         {
1187                 INDEX_COLUMNS = INDEX_COLUMNS_DEFAULT;
1188                 INDEX_OFFSET = -1
1189                         indexSetPageSlide(activeSlide);
1190         }
1193 /** Function to reset path width in drawing mode.
1194 */
1195 function drawingResetPathWidth()
1197         path_width = path_width_default;
1198         set_path_paint_width();
1201 /** Function to set path width in drawing mode.
1202  *
1203  * @param width new path width
1204  */
1205 function drawingSetPathWidth(width)
1207         path_width = width;
1208         set_path_paint_width();
1211 /** Function to set path colour in drawing mode.
1212  *
1213  * @param colour new path colour
1214  */
1215 function drawingSetPathColour(colour)
1217         path_colour = colour;
1220 /** Function to query the duration of the presentation from the user in slide mode.
1221 */
1222 function slideQueryDuration()
1224         var new_duration = prompt("Length of presentation in minutes?", timer_duration);
1226         if ((new_duration != null) && (new_duration != ''))
1227         {
1228                 timer_duration = new_duration;
1229         }
1231         updateTimer();
1234 /** Function to add new slide in slide mode.
1235  *
1236  * @param afterSlide after which slide to insert the new one
1237  */
1238 function slideAddSlide(afterSlide)
1240         addSlide(afterSlide);
1241         slideSetActiveSlide(afterSlide + 1);
1242         updateTimer();
1245 /** Function to toggle the visibility of the progress bar in slide mode.
1246 */
1247 function slideToggleProgressBarVisibility()
1249         if (progress_bar_visible)
1250         {
1251                 progress_bar_visible = false;
1252                 hideProgressBar();
1253         }
1254         else
1255         {
1256                 progress_bar_visible = true;
1257                 showProgressBar();
1258         }
1261 /** Function to reset the timer in slide mode.
1262 */
1263 function slideResetTimer()
1265         timer_start = timer_elapsed;
1266         updateTimer();
1269 /** Convenience function to pad a string with zero in front up to a certain length.
1270  */
1271 function padString(str, len)
1273         var outStr = str;
1275         while (outStr.length < len)
1276         {
1277                 outStr = '0' + outStr;
1278         }
1280         return outStr;
1283 /** Function to update the export layer.
1284  */
1285 function slideUpdateExportLayer()
1287         // Suspend redraw since we are going to mess with the slides.
1288         var suspendHandle = ROOT_NODE.suspendRedraw(2000);
1290         var tmpActiveSlide = activeSlide;
1291         var tmpActiveEffect = activeEffect;
1292         var exportedLayers = new Array();
1294         for (var counterSlides = 0; counterSlides < slides.length; counterSlides++)
1295         {
1296                 var exportNode;
1298                 setSlideToState(counterSlides, STATE_START);
1300                 var maxEffect = 0;
1302                 if (slides[counterSlides].effects)
1303                 {
1304                         maxEffect = slides[counterSlides].effects.length;
1305                 }
1307                 exportNode = slides[counterSlides].element.cloneNode(true);
1308                 exportNode.setAttributeNS(NSS["inkscape"], "groupmode", "layer");
1309                 exportNode.setAttributeNS(NSS["inkscape"], "label", "slide_" + padString((counterSlides + 1).toString(), slides.length.toString().length) + "_effect_" + padString("0", maxEffect.toString().length));
1311                 exportedLayers.push(exportNode);
1313                 if (slides[counterSlides]["effects"])
1314                 {       
1315                         for (var counter = 0; counter < slides[counterSlides]["effects"].length; counter++)
1316                         {
1317                                 for (var subCounter = 0; subCounter < slides[counterSlides]["effects"][counter].length; subCounter++)
1318                                 {
1319                                         var effect = slides[counterSlides]["effects"][counter][subCounter];
1320                                         if (effect["effect"] == "fade")
1321                                                 fade(parseInt(effect["dir"]), effect["element"], STATE_END, effect["options"]); 
1322                                         else if (effect["effect"] == "appear")
1323                                                 appear(parseInt(effect["dir"]), effect["element"], STATE_END, effect["options"]);       
1324                                         else if (effect["effect"] == "pop")
1325                                                 pop(parseInt(effect["dir"]), effect["element"], STATE_END, effect["options"]);  
1326                                         else if (effect["effect"] == "view")
1327                                                 view(parseInt(effect["dir"]), effect["element"], STATE_END, effect["options"]); 
1328                                 }
1330                                 var layerName = "slide_" + padString((counterSlides + 1).toString(), slides.length.toString().length) + "_effect_" + padString((counter + 1).toString(), maxEffect.toString().length);
1331                                 exportNode = slides[counterSlides].element.cloneNode(true);
1332                                 exportNode.setAttributeNS(NSS["inkscape"], "groupmode", "layer");
1333                                 exportNode.setAttributeNS(NSS["inkscape"], "label", layerName);
1334                                 exportNode.setAttribute("id", layerName);
1336                                 exportedLayers.push(exportNode);
1337                         }
1338                 }
1339         }
1341         activeSlide = tmpActiveSlide;
1342         activeEffect = tmpActiveEffect;
1343         setSlideToState(activeSlide, activeEffect);
1345         // Copy image.
1346         var newDoc = document.documentElement.cloneNode(true);
1348         // Delete viewbox form new imag and set width and height.
1349         newDoc.removeAttribute('viewbox');
1350         newDoc.setAttribute('width', WIDTH);
1351         newDoc.setAttribute('height', HEIGHT);
1353         // Delete all layers and script elements.
1354         var nodesToBeRemoved = new Array();
1356         for (var childCounter = 0; childCounter <  newDoc.childNodes.length; childCounter++)
1357         {
1358                 var child = newDoc.childNodes[childCounter];
1360                 if (child.nodeType == 1)
1361                 {
1362                         if ((child.nodeName.toUpperCase() == 'G') || (child.nodeName.toUpperCase() == 'SCRIPT'))
1363                         {
1364                                 nodesToBeRemoved.push(child);
1365                         }
1366                 }
1367         }
1369         for (var ndCounter = 0; ndCounter < nodesToBeRemoved.length; ndCounter++)
1370         {
1371                 var nd = nodesToBeRemoved[ndCounter];
1373                 nd.parentNode.removeChild(nd);
1374         }
1376         // Set current layer.
1377         if (exportedLayers[0])
1378         {
1379                 var namedView;
1381                 for (var nodeCounter = 0; nodeCounter < newDoc.childNodes.length; nodeCounter++)
1382                 {
1383                         if ((newDoc.childNodes[nodeCounter].nodeType == 1) && (newDoc.childNodes[nodeCounter].getAttribute('id') == 'base'))
1384                         {
1385                                 namedView = newDoc.childNodes[nodeCounter];
1386                         }
1387                 }
1389                 if (namedView)
1390                 {
1391                         namedView.setAttributeNS(NSS['inkscape'], 'current-layer', exportedLayers[0].getAttributeNS(NSS['inkscape'], 'label'));
1392                 }
1393         }
1395         // Add exported layers.
1396         while (exportedLayers.length > 0)
1397         {
1398                 var nd = exportedLayers.pop();
1400                 nd.setAttribute("opacity",1);
1401                 nd.style.display = "inherit";
1403                 newDoc.appendChild(nd);
1404         }
1406         // Serialise the new document.
1407         var serializer = new XMLSerializer();
1408         var xml = serializer.serializeToString(newDoc);
1410         window.open('data:image/svg+xml;base64,' + window.btoa(xml), '_blank');
1412         // Unsuspend redraw.
1413         ROOT_NODE.unsuspendRedraw(suspendHandle);
1414         ROOT_NODE.forceRedraw();
1417 /** Function to undo last drawing operation.
1418 */
1419 function drawingUndo()
1421         mouse_presentation_path = null;
1422         mouse_original_path = null;
1424         if (history_presentation_elements.length > 0)
1425         {
1426                 var p = history_presentation_elements.pop();
1427                 var parent = p.parentNode.removeChild(p);
1429                 p = history_original_elements.pop();
1430                 parent = p.parentNode.removeChild(p);
1431         }
1434 /** Event handler for mouse down in drawing mode.
1435  *
1436  *  @param e the event
1437  */
1438 function drawingMousedown(e)
1440         var value = 0;
1442         if (e.button)
1443                 value = e.button;
1444         else if (e.which)
1445                 value = e.which;
1447         if (value == 1)
1448         {
1449                 history_counter++;
1451                 var p = calcCoord(e);
1453                 mouse_last_x = e.clientX;
1454                 mouse_last_y = e.clientY;
1455                 mouse_original_path = document.createElementNS(NSS["svg"], "path");
1456                 mouse_original_path.setAttribute("stroke", path_colour);
1457                 mouse_original_path.setAttribute("stroke-width", path_paint_width);
1458                 mouse_original_path.setAttribute("fill", "none");
1459                 mouse_original_path.setAttribute("id", "path " + Date());
1460                 mouse_original_path.setAttribute("d", "M" + p.x + "," + p.y);
1461                 slides[activeSlide]["original_element"].appendChild(mouse_original_path);
1462                 history_original_elements.push(mouse_original_path);
1464                 mouse_presentation_path = document.createElementNS(NSS["svg"], "path");
1465                 mouse_presentation_path.setAttribute("stroke", path_colour);
1466                 mouse_presentation_path.setAttribute("stroke-width", path_paint_width);
1467                 mouse_presentation_path.setAttribute("fill", "none");
1468                 mouse_presentation_path.setAttribute("id", "path " + Date() + " presentation copy");
1469                 mouse_presentation_path.setAttribute("d", "M" + p.x + "," + p.y);
1471                 if (slides[activeSlide]["viewGroup"])
1472                         slides[activeSlide]["viewGroup"].appendChild(mouse_presentation_path);
1473                 else
1474                         slides[activeSlide]["element"].appendChild(mouse_presentation_path);
1476                 history_presentation_elements.push(mouse_presentation_path);
1478                 return false;
1479         }
1481         return true;
1484 /** Event handler for mouse up in drawing mode.
1485  *
1486  *  @param e the event
1487  */
1488 function drawingMouseup(e)
1490         if(!e)
1491                 e = window.event;
1493         if (mouse_presentation_path != null)
1494         {
1495                 var p = calcCoord(e);
1496                 var d = mouse_presentation_path.getAttribute("d");
1497                 d += " L" + p.x + "," + p.y;
1498                 mouse_presentation_path.setAttribute("d", d);
1499                 mouse_presentation_path = null;
1500                 mouse_original_path.setAttribute("d", d);
1501                 mouse_original_path = null;
1503                 return false;
1504         }
1506         return true;
1509 /** Event handler for mouse move in drawing mode.
1510  *
1511  *  @param e the event
1512  */
1513 function drawingMousemove(e)
1515         if(!e)
1516                 e = window.event;
1518         var dist = (mouse_last_x - e.clientX) * (mouse_last_x - e.clientX) + (mouse_last_y - e.clientY) * (mouse_last_y - e.clientY);
1520         if (mouse_presentation_path == null)
1521         {
1522                 return true;
1523         }
1525         if (dist >= mouse_min_dist_sqr)
1526         {
1527                 var p = calcCoord(e);
1528                 var d = mouse_presentation_path.getAttribute("d");
1529                 d += " L" + p.x + "," + p.y;
1530                 mouse_presentation_path.setAttribute("d", d);
1531                 mouse_original_path.setAttribute("d", d);
1532                 mouse_last_x = e.clientX;
1533                 mouse_last_y = e.clientY;
1534         }
1536         return false;
1539 /** Event handler for mouse wheel events in slide mode.
1540  *  based on http://adomas.org/javascript-mouse-wheel/
1541  *
1542  *  @param e the event
1543  */
1544 function slideMousewheel(e)
1546         var delta = 0;
1548         if (!e)
1549                 e = window.event;
1551         if (e.wheelDelta)
1552         { // IE Opera
1553                 delta = e.wheelDelta/120;
1554         }
1555         else if (e.detail)
1556         { // MOZ
1557                 delta = -e.detail/3;
1558         }
1560         if (delta > 0)
1561                 skipEffects(-1);
1562         else if (delta < 0)
1563                 skipEffects(1);
1565         if (e.preventDefault)
1566                 e.preventDefault();
1568         e.returnValue = false;
1571 /** Event handler for mouse wheel events in index mode.
1572  *  based on http://adomas.org/javascript-mouse-wheel/
1573  *
1574  *  @param e the event
1575  */
1576 function indexMousewheel(e)
1578         var delta = 0;
1580         if (!e)
1581                 e = window.event;
1583         if (e.wheelDelta)
1584         { // IE Opera
1585                 delta = e.wheelDelta/120;
1586         }
1587         else if (e.detail)
1588         { // MOZ
1589                 delta = -e.detail/3;
1590         }
1592         if (delta > 0)
1593                 indexSetPageSlide(activeSlide - INDEX_COLUMNS * INDEX_COLUMNS);
1594         else if (delta < 0)
1595                 indexSetPageSlide(activeSlide + INDEX_COLUMNS * INDEX_COLUMNS);
1597         if (e.preventDefault)
1598                 e.preventDefault();
1600         e.returnValue = false;
1603 /** Function to set the path paint width.
1604 */
1605 function set_path_paint_width()
1607         var svgPoint1 = document.documentElement.createSVGPoint();
1608         var svgPoint2 = document.documentElement.createSVGPoint();
1610         svgPoint1.x = 0.0;
1611         svgPoint1.y = 0.0;
1612         svgPoint2.x = 1.0;
1613         svgPoint2.y = 0.0;
1615         var matrix = slides[activeSlide]["element"].getTransformToElement(ROOT_NODE);
1617         if (slides[activeSlide]["viewGroup"])
1618                 matrix = slides[activeSlide]["viewGroup"].getTransformToElement(ROOT_NODE);
1620         svgPoint1 = svgPoint1.matrixTransform(matrix);
1621         svgPoint2 = svgPoint2.matrixTransform(matrix);
1623         path_paint_width = path_width / Math.sqrt((svgPoint2.x - svgPoint1.x) * (svgPoint2.x - svgPoint1.x) + (svgPoint2.y - svgPoint1.y) * (svgPoint2.y - svgPoint1.y));
1626 /** The view effect.
1627  *
1628  *  @param dir direction the effect should be played (1 = forwards, -1 = backwards)
1629  *  @param element the element the effect should be applied to
1630  *  @param time the time that has elapsed since the beginning of the effect
1631  *  @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.
1632  */
1633 function view(dir, element, time, options)
1635         var length = 250;
1636         var fraction;
1638         if (!options["matrixInitial"])
1639         {
1640                 var tempString = slides[activeSlide]["viewGroup"].getAttribute("transform");
1642                 if (tempString)
1643                         options["matrixInitial"] = (new matrixSVG()).fromAttribute(tempString);
1644                 else
1645                         options["matrixInitial"] = (new matrixSVG()).fromSVGElements(1, 0, 0, 1, 0, 0);
1646         }
1648         if ((time == STATE_END) || (time == STATE_START))
1649                 fraction = 1;
1650         else
1651         {
1652                 if (options && options["length"])
1653                         length = options["length"];
1655                 fraction = time / length;
1656         }
1658         if (dir == 1)
1659         {
1660                 if (fraction <= 0)
1661                 {
1662                         element.setAttribute("transform", options["matrixInitial"].toAttribute());
1663                 }
1664                 else if (fraction >= 1)
1665                 {
1666                         element.setAttribute("transform", options["matrixNew"].toAttribute());
1668                         set_path_paint_width();
1670                         options["matrixInitial"] = null;
1671                         return true;
1672                 }
1673                 else
1674                 {
1675                         element.setAttribute("transform", options["matrixInitial"].mix(options["matrixNew"], fraction).toAttribute());
1676                 }
1677         }
1678         else if (dir == -1)
1679         {
1680                 if (fraction <= 0)
1681                 {
1682                         element.setAttribute("transform", options["matrixInitial"].toAttribute());
1683                 }
1684                 else if (fraction >= 1)
1685                 {
1686                         element.setAttribute("transform", options["matrixOld"].toAttribute());
1687                         set_path_paint_width();
1689                         options["matrixInitial"] = null;
1690                         return true;
1691                 }
1692                 else
1693                 {
1694                         element.setAttribute("transform", options["matrixInitial"].mix(options["matrixOld"], fraction).toAttribute());
1695                 }
1696         }
1698         return false;
1701 /** The fade effect.
1702  *
1703  *  @param dir direction the effect should be played (1 = forwards, -1 = backwards)
1704  *  @param element the element the effect should be applied to
1705  *  @param time the time that has elapsed since the beginning of the effect
1706  *  @param options a dictionary with additional options (e.g. length of the effect)
1707  */
1708 function fade(dir, element, time, options)
1710         var length = 250;
1711         var fraction;
1713         if ((time == STATE_END) || (time == STATE_START))
1714                 fraction = 1;
1715         else
1716         {
1717                 if (options && options["length"])
1718                         length = options["length"];
1720                 fraction = time / length;
1721         }
1723         if (dir == 1)
1724         {
1725                 if (fraction <= 0)
1726                 {
1727                         element.style.display = "none";
1728                         element.setAttribute("opacity", 0);
1729                 }
1730                 else if (fraction >= 1)
1731                 {
1732                         element.style.display = "inherit";
1733                         element.setAttribute("opacity", 1);
1734                         return true;
1735                 }
1736                 else
1737                 {
1738                         element.style.display = "inherit";
1739                         element.setAttribute("opacity", fraction);
1740                 }
1741         }
1742         else if (dir == -1)
1743         {
1744                 if (fraction <= 0)
1745                 {
1746                         element.style.display = "inherit";
1747                         element.setAttribute("opacity", 1);
1748                 }
1749                 else if (fraction >= 1)
1750                 {
1751                         element.setAttribute("opacity", 0);
1752                         element.style.display = "none";
1753                         return true;
1754                 }
1755                 else
1756                 {
1757                         element.style.display = "inherit";
1758                         element.setAttribute("opacity", 1 - fraction);
1759                 }
1760         }
1761         return false;
1764 /** The appear effect.
1765  *
1766  *  @param dir direction the effect should be played (1 = forwards, -1 = backwards)
1767  *  @param element the element the effect should be applied to
1768  *  @param time the time that has elapsed since the beginning of the effect
1769  *  @param options a dictionary with additional options (e.g. length of the effect)
1770  */
1771 function appear(dir, element, time, options)
1773         if (dir == 1)
1774         {
1775                 element.style.display = "inherit";
1776                 element.setAttribute("opacity",1);
1777         }
1778         else if (dir == -1)
1779         {
1780                 element.style.display = "none";
1781                 element.setAttribute("opacity",0);
1782         }
1783         return true;
1786 /** The pop effect.
1787  *
1788  *  @param dir direction the effect should be played (1 = forwards, -1 = backwards)
1789  *  @param element the element the effect should be applied to
1790  *  @param time the time that has elapsed since the beginning of the effect
1791  *  @param options a dictionary with additional options (e.g. length of the effect)
1792  */
1793 function pop(dir, element, time, options)
1795         var length = 500;
1796         var fraction;
1798         if ((time == STATE_END) || (time == STATE_START))
1799                 fraction = 1;
1800         else
1801         {
1802                 if (options && options["length"])
1803                         length = options["length"];
1805                 fraction = time / length;
1806         }
1808         if (dir == 1)
1809         {
1810                 if (fraction <= 0)
1811                 {
1812                         element.setAttribute("opacity", 0);
1813                         element.setAttribute("transform", "scale(0)");
1814                         element.style.display = "none";
1815                 }
1816                 else if (fraction >= 1)
1817                 {
1818                         element.setAttribute("opacity", 1);
1819                         element.removeAttribute("transform");
1820                         element.style.display = "inherit";
1821                         return true;
1822                 }
1823                 else
1824                 {
1825                         element.style.display = "inherit";
1826                         var opacityFraction = fraction * 3;
1827                         if (opacityFraction > 1)
1828                                 opacityFraction = 1;
1829                         element.setAttribute("opacity", opacityFraction);
1830                         var offsetX = WIDTH * (1.0 - fraction) / 2.0;
1831                         var offsetY = HEIGHT * (1.0 - fraction) / 2.0;
1832                         element.setAttribute("transform", "translate(" + offsetX + "," + offsetY + ") scale(" + fraction + ")");
1833                 }
1834         }
1835         else if (dir == -1)
1836         {
1837                 if (fraction <= 0)
1838                 {
1839                         element.setAttribute("opacity", 1);
1840                         element.setAttribute("transform", "scale(1)");
1841                         element.style.display = "inherit";
1842                 }
1843                 else if (fraction >= 1)
1844                 {
1845                         element.setAttribute("opacity", 0);
1846                         element.removeAttribute("transform");
1847                         element.style.display = "none";
1848                         return true;
1849                 }
1850                 else
1851                 {
1852                         element.setAttribute("opacity", 1 - fraction);
1853                         element.setAttribute("transform", "scale(" + 1 - fraction + ")");
1854                         element.style.display = "inherit";
1855                 }
1856         }
1857         return false;
1860 /** Function to set a slide either to the start or the end state.
1861  *  
1862  *  @param slide the slide to use
1863  *  @param state the state into which the slide should be set
1864  */
1865 function setSlideToState(slide, state)
1867         slides[slide]["viewGroup"].setAttribute("transform", slides[slide].initialView);
1869         if (slides[slide]["effects"])
1870         {       
1871                 if (state == STATE_END)
1872                 {
1873                         for (var counter = 0; counter < slides[slide]["effects"].length; counter++)
1874                         {
1875                                 for (var subCounter = 0; subCounter < slides[slide]["effects"][counter].length; subCounter++)
1876                                 {
1877                                         var effect = slides[slide]["effects"][counter][subCounter];
1878                                         if (effect["effect"] == "fade")
1879                                                 fade(effect["dir"], effect["element"], STATE_END, effect["options"]);   
1880                                         else if (effect["effect"] == "appear")
1881                                                 appear(effect["dir"], effect["element"], STATE_END, effect["options"]); 
1882                                         else if (effect["effect"] == "pop")
1883                                                 pop(effect["dir"], effect["element"], STATE_END, effect["options"]);    
1884                                         else if (effect["effect"] == "view")
1885                                                 view(effect["dir"], effect["element"], STATE_END, effect["options"]);   
1886                                 }
1887                         }
1888                 }
1889                 else if (state == STATE_START)
1890                 {
1891                         for (var counter = slides[slide]["effects"].length - 1; counter >= 0; counter--)
1892                         {
1893                                 for (var subCounter = 0; subCounter < slides[slide]["effects"][counter].length; subCounter++)
1894                                 {
1895                                         var effect = slides[slide]["effects"][counter][subCounter];
1896                                         if (effect["effect"] == "fade")
1897                                                 fade(parseInt(effect["dir"]) * -1, effect["element"], STATE_START, effect["options"]);  
1898                                         else if (effect["effect"] == "appear")
1899                                                 appear(parseInt(effect["dir"]) * -1, effect["element"], STATE_START, effect["options"]);        
1900                                         else if (effect["effect"] == "pop")
1901                                                 pop(parseInt(effect["dir"]) * -1, effect["element"], STATE_START, effect["options"]);   
1902                                         else if (effect["effect"] == "view")
1903                                                 view(parseInt(effect["dir"]) * -1, effect["element"], STATE_START, effect["options"]);  
1904                                 }
1905                         }
1906                 }
1907                 else
1908                 {
1909                         setSlideToState(slide, STATE_START);
1911                         for (var counter = 0; counter < slides[slide]["effects"].length && counter < state; counter++)
1912                         {
1913                                 for (var subCounter = 0; subCounter < slides[slide]["effects"][counter].length; subCounter++)
1914                                 {
1915                                         var effect = slides[slide]["effects"][counter][subCounter];
1916                                         if (effect["effect"] == "fade")
1917                                                 fade(effect["dir"], effect["element"], STATE_END, effect["options"]);   
1918                                         else if (effect["effect"] == "appear")
1919                                                 appear(effect["dir"], effect["element"], STATE_END, effect["options"]); 
1920                                         else if (effect["effect"] == "pop")
1921                                                 pop(effect["dir"], effect["element"], STATE_END, effect["options"]);    
1922                                         else if (effect["effect"] == "view")
1923                                                 view(effect["dir"], effect["element"], STATE_END, effect["options"]);   
1924                                 }
1925                         }
1926                 }
1927         }
1929         window.location.hash = (activeSlide + 1) + '_' + activeEffect;
1932 /** Convenience function to translate a attribute string into a dictionary.
1933  *
1934  *      @param str the attribute string
1935  *  @return a dictionary
1936  *  @see dictToPropStr
1937  */
1938 function propStrToDict(str)
1940         var list = str.split(";");
1941         var obj = new Object();
1943         for (var counter = 0; counter < list.length; counter++)
1944         {
1945                 var subStr = list[counter];
1946                 var subList = subStr.split(":");
1947                 if (subList.length == 2)
1948                 {
1949                         obj[subList[0]] = subList[1];
1950                 }       
1951         }
1953         return obj;
1956 /** Convenience function to translate a dictionary into a string that can be used as an attribute.
1957  *
1958  *  @param dict the dictionary to convert
1959  *  @return a string that can be used as an attribute
1960  *  @see propStrToDict
1961  */
1962 function dictToPropStr(dict)
1964         var str = "";
1966         for (var key in dict)
1967         {
1968                 str += key + ":" + dict[key] + ";";
1969         }
1971         return str;
1974 /** Sub-function to add a suffix to the ids of the node and all its children.
1975  *      
1976  *      @param node the node to change
1977  *      @param suffix the suffix to add
1978  *      @param replace dictionary of replaced ids
1979  *  @see suffixNodeIds
1980  */
1981 function suffixNoneIds_sub(node, suffix, replace)
1983         if (node.nodeType == 1)
1984         {
1985                 if (node.getAttribute("id"))
1986                 {
1987                         var id = node.getAttribute("id")
1988                                 replace["#" + id] = id + suffix;
1989                         node.setAttribute("id", id + suffix);
1990                 }
1992                 if ((node.nodeName == "use") && (node.getAttributeNS(NSS["xlink"], "href")) && (replace[node.getAttribute(NSS["xlink"], "href")]))
1993                         node.setAttribute(NSS["xlink"], "href", node.getAttribute(NSS["xlink"], "href") + suffix);
1995                 if (node.childNodes)
1996                 {
1997                         for (var counter = 0; counter < node.childNodes.length; counter++)
1998                                 suffixNoneIds_sub(node.childNodes[counter], suffix, replace);
1999                 }
2000         }
2003 /** Function to add a suffix to the ids of the node and all its children.
2004  *      
2005  *      @param node the node to change
2006  *      @param suffix the suffix to add
2007  *  @return the changed node
2008  *  @see suffixNodeIds_sub
2009  */
2010 function suffixNodeIds(node, suffix)
2012         var replace = new Object();
2014         suffixNoneIds_sub(node, suffix, replace);
2016         return node;
2019 /** Function to build a progress bar.
2020  *      
2021  *  @param parent node to attach the progress bar to
2022  */
2023 function createProgressBar(parent_node)
2025         var g = document.createElementNS(NSS["svg"], "g");
2026         g.setAttribute("clip-path", "url(#jessyInkSlideClipPath)");
2027         g.setAttribute("id", "layer_progress_bar");
2028         g.setAttribute("style", "display: none;");
2030         var rect_progress_bar = document.createElementNS(NSS["svg"], "rect");
2031         rect_progress_bar.setAttribute("style", "marker: none; fill: rgb(128, 128, 128); stroke: none;");
2032         rect_progress_bar.setAttribute("id", "rect_progress_bar");
2033         rect_progress_bar.setAttribute("x", 0);
2034         rect_progress_bar.setAttribute("y", 0.99 * HEIGHT);
2035         rect_progress_bar.setAttribute("width", 0);
2036         rect_progress_bar.setAttribute("height", 0.01 * HEIGHT);
2037         g.appendChild(rect_progress_bar);
2039         var circle_timer_indicator = document.createElementNS(NSS["svg"], "circle");
2040         circle_timer_indicator.setAttribute("style", "marker: none; fill: rgb(255, 0, 0); stroke: none;");
2041         circle_timer_indicator.setAttribute("id", "circle_timer_indicator");
2042         circle_timer_indicator.setAttribute("cx", 0.005 * HEIGHT);
2043         circle_timer_indicator.setAttribute("cy", 0.995 * HEIGHT);
2044         circle_timer_indicator.setAttribute("r", 0.005 * HEIGHT);
2045         g.appendChild(circle_timer_indicator);
2047         parent_node.appendChild(g);
2050 /** Function to hide the progress bar.
2051  *      
2052  */
2053 function hideProgressBar()
2055         var progress_bar = document.getElementById("layer_progress_bar");
2057         if (!progress_bar)
2058         {
2059                 return;
2060         }
2062         progress_bar.setAttribute("style", "display: none;");
2065 /** Function to show the progress bar.
2066  *      
2067  */
2068 function showProgressBar()
2070         var progress_bar = document.getElementById("layer_progress_bar");
2072         if (!progress_bar)
2073         {
2074                 return;
2075         }
2077         progress_bar.setAttribute("style", "display: inherit;");
2080 /** Set progress bar value.
2081  *      
2082  *      @param value the current slide number
2083  *
2084  */
2085 function setProgressBarValue(value)
2087         var rect_progress_bar = document.getElementById("rect_progress_bar");
2089         if (!rect_progress_bar)
2090         {
2091                 return;
2092         }
2094         if (value < 1)
2095         {
2096                 // First slide, assumed to be the title of the presentation
2097                 var x = 0;
2098                 var w = 0.01 * HEIGHT;
2099         }
2100         else if (value >= slides.length - 1)
2101         {
2102                 // Last slide, assumed to be the end of the presentation
2103                 var x = WIDTH - 0.01 * HEIGHT;
2104                 var w = 0.01 * HEIGHT;
2105         }
2106         else
2107         {
2108                 value -= 1;
2109                 value /= (slides.length - 2);
2111                 var x = WIDTH * value;
2112                 var w = WIDTH / (slides.length - 2);
2113         }
2115         rect_progress_bar.setAttribute("x", x);
2116         rect_progress_bar.setAttribute("width", w);
2119 /** Set time indicator.
2120  *      
2121  *      @param value the percentage of time elapse so far between 0.0 and 1.0
2122  *
2123  */
2124 function setTimeIndicatorValue(value)
2126         var circle_timer_indicator = document.getElementById("circle_timer_indicator");
2128         if (!circle_timer_indicator)
2129         {
2130                 return;
2131         }
2133         if (value < 0.0)
2134         {
2135                 value = 0.0;
2136         }
2138         if (value > 1.0)
2139         {
2140                 value = 1.0;
2141         }
2143         var cx = (WIDTH - 0.01 * HEIGHT) * value + 0.005 * HEIGHT;
2144         circle_timer_indicator.setAttribute("cx", cx);
2147 /** Update timer.
2148  *      
2149  */
2150 function updateTimer()
2152         timer_elapsed += 1;
2153         setTimeIndicatorValue((timer_elapsed - timer_start) / (60 * timer_duration));
2156 /** Convert screen coordinates to document coordinates.
2157  *
2158  *  @param e event with screen coordinates
2159  *
2160  *  @return coordinates in SVG file coordinate system   
2161  */
2162 function calcCoord(e)
2164         var svgPoint = document.documentElement.createSVGPoint();
2165         svgPoint.x = e.clientX + window.pageXOffset;
2166         svgPoint.y = e.clientY + window.pageYOffset;
2168         // The following is needed for Google Chrome, but causes problems
2169         // with Firefox, as viewport is not implemented in Firefox 3.5.
2170         try
2171         {
2172                 svgPoint.x += document.rootElement.viewport.x;
2173                 svgPoint.y += document.rootElement.viewport.y;
2174         }
2175         catch (e)
2176         {
2177         }
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, '');