Code

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