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()
114 {
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;
558 }
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)
568 {
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 }
580 }
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)
590 {
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;
603 }
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)
610 {
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 }
635 }
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)
642 {
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 }
663 }
665 /** Function to change between slides.
666 *
667 * @param dir direction (1 = forwards, -1 = backwards)
668 */
669 function changeSlide(dir)
670 {
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);
723 }
725 /** Function to toggle between index and slide mode.
726 */
727 function toggleSlideIndex()
728 {
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();
768 }
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)
775 {
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 }
819 }
821 /** Function to display the index sheet.
822 *
823 * @param offsetNumber offset number
824 */
825 function displayIndex(offsetNumber)
826 {
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;
858 }
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)
865 {
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);
882 }
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)
889 {
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);
901 }
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)
917 {
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);
938 }
940 /** Event handler for key press.
941 *
942 * @param e the event
943 */
944 function keydown(e)
945 {
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;
955 }
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)
964 {
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]();
974 }
976 /** Function to supply the default char code dictionary.
977 *
978 * @returns default char code dictionary
979 */
980 function getDefaultCharCodeDictionary()
981 {
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;
1021 }
1023 /** Function to supply the default key code dictionary.
1024 *
1025 * @returns default key code dictionary
1026 */
1027 function getDefaultKeyCodeDictionary()
1028 {
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;
1058 }
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)
1066 {
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;
1086 }
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)
1095 {
1096 window.addEventListener('DOMMouseScroll', function(e) { return mouseHandlerDispatch(e, MOUSE_WHEEL); }, false);
1097 }
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()
1107 {
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;
1124 }
1126 /** Function to switch from slide mode to drawing mode.
1127 */
1128 function slideSwitchToDrawingMode()
1129 {
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));
1141 }
1143 /** Function to switch from drawing mode to slide mode.
1144 */
1145 function drawingSwitchToSlideMode()
1146 {
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));
1158 }
1160 /** Function to decrease the number of columns in index mode.
1161 */
1162 function indexDecreaseNumberOfColumns()
1163 {
1164 if (INDEX_COLUMNS >= 3)
1165 {
1166 INDEX_COLUMNS -= 1;
1167 INDEX_OFFSET = -1
1168 indexSetPageSlide(activeSlide);
1169 }
1170 }
1172 /** Function to increase the number of columns in index mode.
1173 */
1174 function indexIncreaseNumberOfColumns()
1175 {
1176 if (INDEX_COLUMNS < 7)
1177 {
1178 INDEX_COLUMNS += 1;
1179 INDEX_OFFSET = -1
1180 indexSetPageSlide(activeSlide);
1181 }
1182 }
1184 /** Function to reset the number of columns in index mode.
1185 */
1186 function indexResetNumberOfColumns()
1187 {
1188 if (INDEX_COLUMNS != INDEX_COLUMNS_DEFAULT)
1189 {
1190 INDEX_COLUMNS = INDEX_COLUMNS_DEFAULT;
1191 INDEX_OFFSET = -1
1192 indexSetPageSlide(activeSlide);
1193 }
1194 }
1196 /** Function to reset path width in drawing mode.
1197 */
1198 function drawingResetPathWidth()
1199 {
1200 path_width = path_width_default;
1201 set_path_paint_width();
1202 }
1204 /** Function to set path width in drawing mode.
1205 *
1206 * @param width new path width
1207 */
1208 function drawingSetPathWidth(width)
1209 {
1210 path_width = width;
1211 set_path_paint_width();
1212 }
1214 /** Function to set path colour in drawing mode.
1215 *
1216 * @param colour new path colour
1217 */
1218 function drawingSetPathColour(colour)
1219 {
1220 path_colour = colour;
1221 }
1223 /** Function to query the duration of the presentation from the user in slide mode.
1224 */
1225 function slideQueryDuration()
1226 {
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();
1235 }
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)
1242 {
1243 addSlide(afterSlide);
1244 slideSetActiveSlide(afterSlide + 1);
1245 updateTimer();
1246 }
1248 /** Function to toggle the visibility of the progress bar in slide mode.
1249 */
1250 function slideToggleProgressBarVisibility()
1251 {
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 }
1262 }
1264 /** Function to reset the timer in slide mode.
1265 */
1266 function slideResetTimer()
1267 {
1268 timer_start = timer_elapsed;
1269 updateTimer();
1270 }
1272 /** Convenience function to pad a string with zero in front up to a certain length.
1273 */
1274 function padString(str, len)
1275 {
1276 var outStr = str;
1278 while (outStr.length < len)
1279 {
1280 outStr = '0' + outStr;
1281 }
1283 return outStr;
1284 }
1286 /** Function to update the export layer.
1287 */
1288 function slideUpdateExportLayer()
1289 {
1290 // Suspend redraw since we are going to mess with the slides.
1291 var suspendHandle = ROOT_NODE.suspendRedraw(2000);
1293 var tmpActiveSlide = activeSlide;
1294 var tmpActiveEffect = activeEffect;
1295 var exportedLayers = new Array();
1297 for (var counterSlides = 0; counterSlides < slides.length; counterSlides++)
1298 {
1299 var exportNode;
1301 setSlideToState(counterSlides, STATE_START);
1303 var maxEffect = 0;
1305 if (slides[counterSlides].effects)
1306 {
1307 maxEffect = slides[counterSlides].effects.length;
1308 }
1310 exportNode = slides[counterSlides].element.cloneNode(true);
1311 exportNode.setAttributeNS(NSS["inkscape"], "groupmode", "layer");
1312 exportNode.setAttributeNS(NSS["inkscape"], "label", "slide_" + padString((counterSlides + 1).toString(), slides.length.toString().length) + "_effect_" + padString("0", maxEffect.toString().length));
1314 exportedLayers.push(exportNode);
1316 if (slides[counterSlides]["effects"])
1317 {
1318 for (var counter = 0; counter < slides[counterSlides]["effects"].length; counter++)
1319 {
1320 for (var subCounter = 0; subCounter < slides[counterSlides]["effects"][counter].length; subCounter++)
1321 {
1322 var effect = slides[counterSlides]["effects"][counter][subCounter];
1323 if (effect["effect"] == "fade")
1324 fade(parseInt(effect["dir"]), effect["element"], STATE_END, effect["options"]);
1325 else if (effect["effect"] == "appear")
1326 appear(parseInt(effect["dir"]), effect["element"], STATE_END, effect["options"]);
1327 else if (effect["effect"] == "pop")
1328 pop(parseInt(effect["dir"]), effect["element"], STATE_END, effect["options"]);
1329 else if (effect["effect"] == "view")
1330 view(parseInt(effect["dir"]), effect["element"], STATE_END, effect["options"]);
1331 }
1333 var layerName = "slide_" + padString((counterSlides + 1).toString(), slides.length.toString().length) + "_effect_" + padString((counter + 1).toString(), maxEffect.toString().length);
1334 exportNode = slides[counterSlides].element.cloneNode(true);
1335 exportNode.setAttributeNS(NSS["inkscape"], "groupmode", "layer");
1336 exportNode.setAttributeNS(NSS["inkscape"], "label", layerName);
1337 exportNode.setAttribute("id", layerName);
1339 exportedLayers.push(exportNode);
1340 }
1341 }
1342 }
1344 activeSlide = tmpActiveSlide;
1345 activeEffect = tmpActiveEffect;
1346 setSlideToState(activeSlide, activeEffect);
1348 // Copy image.
1349 var newDoc = document.documentElement.cloneNode(true);
1351 // Delete viewbox form new imag and set width and height.
1352 newDoc.removeAttribute('viewbox');
1353 newDoc.setAttribute('width', WIDTH);
1354 newDoc.setAttribute('height', HEIGHT);
1356 // Delete all layers and script elements.
1357 var nodesToBeRemoved = new Array();
1359 for (var childCounter = 0; childCounter < newDoc.childNodes.length; childCounter++)
1360 {
1361 var child = newDoc.childNodes[childCounter];
1363 if (child.nodeType == 1)
1364 {
1365 if ((child.nodeName.toUpperCase() == 'G') || (child.nodeName.toUpperCase() == 'SCRIPT'))
1366 {
1367 nodesToBeRemoved.push(child);
1368 }
1369 }
1370 }
1372 for (var ndCounter = 0; ndCounter < nodesToBeRemoved.length; ndCounter++)
1373 {
1374 var nd = nodesToBeRemoved[ndCounter];
1376 nd.parentNode.removeChild(nd);
1377 }
1379 // Set current layer.
1380 if (exportedLayers[0])
1381 {
1382 var namedView;
1384 for (var nodeCounter = 0; nodeCounter < newDoc.childNodes.length; nodeCounter++)
1385 {
1386 if ((newDoc.childNodes[nodeCounter].nodeType == 1) && (newDoc.childNodes[nodeCounter].getAttribute('id') == 'base'))
1387 {
1388 namedView = newDoc.childNodes[nodeCounter];
1389 }
1390 }
1392 if (namedView)
1393 {
1394 namedView.setAttributeNS(NSS['inkscape'], 'current-layer', exportedLayers[0].getAttributeNS(NSS['inkscape'], 'label'));
1395 }
1396 }
1398 // Add exported layers.
1399 while (exportedLayers.length > 0)
1400 {
1401 var nd = exportedLayers.pop();
1403 nd.setAttribute("opacity",1);
1404 nd.style.display = "inherit";
1406 newDoc.appendChild(nd);
1407 }
1409 // Serialise the new document.
1410 var serializer = new XMLSerializer();
1411 var strm =
1412 {
1413 content : "",
1414 close : function() {},
1415 flush : function() {},
1416 write : function(str, count) { this.content += str; }
1417 };
1419 var xml = serializer.serializeToStream(newDoc, strm, 'UTF-8');
1421 window.open('data:image/svg+xml;base64;charset=utf-8,' + window.btoa(strm.content), '_blank');
1423 // Unsuspend redraw.
1424 ROOT_NODE.unsuspendRedraw(suspendHandle);
1425 ROOT_NODE.forceRedraw();
1426 }
1428 /** Function to undo last drawing operation.
1429 */
1430 function drawingUndo()
1431 {
1432 mouse_presentation_path = null;
1433 mouse_original_path = null;
1435 if (history_presentation_elements.length > 0)
1436 {
1437 var p = history_presentation_elements.pop();
1438 var parent = p.parentNode.removeChild(p);
1440 p = history_original_elements.pop();
1441 parent = p.parentNode.removeChild(p);
1442 }
1443 }
1445 /** Event handler for mouse down in drawing mode.
1446 *
1447 * @param e the event
1448 */
1449 function drawingMousedown(e)
1450 {
1451 var value = 0;
1453 if (e.button)
1454 value = e.button;
1455 else if (e.which)
1456 value = e.which;
1458 if (value == 1)
1459 {
1460 history_counter++;
1462 var p = calcCoord(e);
1464 mouse_last_x = e.clientX;
1465 mouse_last_y = e.clientY;
1466 mouse_original_path = document.createElementNS(NSS["svg"], "path");
1467 mouse_original_path.setAttribute("stroke", path_colour);
1468 mouse_original_path.setAttribute("stroke-width", path_paint_width);
1469 mouse_original_path.setAttribute("fill", "none");
1470 mouse_original_path.setAttribute("id", "path " + Date());
1471 mouse_original_path.setAttribute("d", "M" + p.x + "," + p.y);
1472 slides[activeSlide]["original_element"].appendChild(mouse_original_path);
1473 history_original_elements.push(mouse_original_path);
1475 mouse_presentation_path = document.createElementNS(NSS["svg"], "path");
1476 mouse_presentation_path.setAttribute("stroke", path_colour);
1477 mouse_presentation_path.setAttribute("stroke-width", path_paint_width);
1478 mouse_presentation_path.setAttribute("fill", "none");
1479 mouse_presentation_path.setAttribute("id", "path " + Date() + " presentation copy");
1480 mouse_presentation_path.setAttribute("d", "M" + p.x + "," + p.y);
1482 if (slides[activeSlide]["viewGroup"])
1483 slides[activeSlide]["viewGroup"].appendChild(mouse_presentation_path);
1484 else
1485 slides[activeSlide]["element"].appendChild(mouse_presentation_path);
1487 history_presentation_elements.push(mouse_presentation_path);
1489 return false;
1490 }
1492 return true;
1493 }
1495 /** Event handler for mouse up in drawing mode.
1496 *
1497 * @param e the event
1498 */
1499 function drawingMouseup(e)
1500 {
1501 if(!e)
1502 e = window.event;
1504 if (mouse_presentation_path != null)
1505 {
1506 var p = calcCoord(e);
1507 var d = mouse_presentation_path.getAttribute("d");
1508 d += " L" + p.x + "," + p.y;
1509 mouse_presentation_path.setAttribute("d", d);
1510 mouse_presentation_path = null;
1511 mouse_original_path.setAttribute("d", d);
1512 mouse_original_path = null;
1514 return false;
1515 }
1517 return true;
1518 }
1520 /** Event handler for mouse move in drawing mode.
1521 *
1522 * @param e the event
1523 */
1524 function drawingMousemove(e)
1525 {
1526 if(!e)
1527 e = window.event;
1529 var dist = (mouse_last_x - e.clientX) * (mouse_last_x - e.clientX) + (mouse_last_y - e.clientY) * (mouse_last_y - e.clientY);
1531 if (mouse_presentation_path == null)
1532 {
1533 return true;
1534 }
1536 if (dist >= mouse_min_dist_sqr)
1537 {
1538 var p = calcCoord(e);
1539 var d = mouse_presentation_path.getAttribute("d");
1540 d += " L" + p.x + "," + p.y;
1541 mouse_presentation_path.setAttribute("d", d);
1542 mouse_original_path.setAttribute("d", d);
1543 mouse_last_x = e.clientX;
1544 mouse_last_y = e.clientY;
1545 }
1547 return false;
1548 }
1550 /** Event handler for mouse wheel events in slide mode.
1551 * based on http://adomas.org/javascript-mouse-wheel/
1552 *
1553 * @param e the event
1554 */
1555 function slideMousewheel(e)
1556 {
1557 var delta = 0;
1559 if (!e)
1560 e = window.event;
1562 if (e.wheelDelta)
1563 { // IE Opera
1564 delta = e.wheelDelta/120;
1565 }
1566 else if (e.detail)
1567 { // MOZ
1568 delta = -e.detail/3;
1569 }
1571 if (delta > 0)
1572 skipEffects(-1);
1573 else if (delta < 0)
1574 skipEffects(1);
1576 if (e.preventDefault)
1577 e.preventDefault();
1579 e.returnValue = false;
1580 }
1582 /** Event handler for mouse wheel events in index mode.
1583 * based on http://adomas.org/javascript-mouse-wheel/
1584 *
1585 * @param e the event
1586 */
1587 function indexMousewheel(e)
1588 {
1589 var delta = 0;
1591 if (!e)
1592 e = window.event;
1594 if (e.wheelDelta)
1595 { // IE Opera
1596 delta = e.wheelDelta/120;
1597 }
1598 else if (e.detail)
1599 { // MOZ
1600 delta = -e.detail/3;
1601 }
1603 if (delta > 0)
1604 indexSetPageSlide(activeSlide - INDEX_COLUMNS * INDEX_COLUMNS);
1605 else if (delta < 0)
1606 indexSetPageSlide(activeSlide + INDEX_COLUMNS * INDEX_COLUMNS);
1608 if (e.preventDefault)
1609 e.preventDefault();
1611 e.returnValue = false;
1612 }
1614 /** Function to set the path paint width.
1615 */
1616 function set_path_paint_width()
1617 {
1618 var svgPoint1 = document.documentElement.createSVGPoint();
1619 var svgPoint2 = document.documentElement.createSVGPoint();
1621 svgPoint1.x = 0.0;
1622 svgPoint1.y = 0.0;
1623 svgPoint2.x = 1.0;
1624 svgPoint2.y = 0.0;
1626 var matrix = slides[activeSlide]["element"].getTransformToElement(ROOT_NODE);
1628 if (slides[activeSlide]["viewGroup"])
1629 matrix = slides[activeSlide]["viewGroup"].getTransformToElement(ROOT_NODE);
1631 svgPoint1 = svgPoint1.matrixTransform(matrix);
1632 svgPoint2 = svgPoint2.matrixTransform(matrix);
1634 path_paint_width = path_width / Math.sqrt((svgPoint2.x - svgPoint1.x) * (svgPoint2.x - svgPoint1.x) + (svgPoint2.y - svgPoint1.y) * (svgPoint2.y - svgPoint1.y));
1635 }
1637 /** The view effect.
1638 *
1639 * @param dir direction the effect should be played (1 = forwards, -1 = backwards)
1640 * @param element the element the effect should be applied to
1641 * @param time the time that has elapsed since the beginning of the effect
1642 * @param options a dictionary with additional options (e.g. length of the effect); for the view effect the options need to contain the old and the new matrix.
1643 */
1644 function view(dir, element, time, options)
1645 {
1646 var length = 250;
1647 var fraction;
1649 if (!options["matrixInitial"])
1650 {
1651 var tempString = slides[activeSlide]["viewGroup"].getAttribute("transform");
1653 if (tempString)
1654 options["matrixInitial"] = (new matrixSVG()).fromAttribute(tempString);
1655 else
1656 options["matrixInitial"] = (new matrixSVG()).fromSVGElements(1, 0, 0, 1, 0, 0);
1657 }
1659 if ((time == STATE_END) || (time == STATE_START))
1660 fraction = 1;
1661 else
1662 {
1663 if (options && options["length"])
1664 length = options["length"];
1666 fraction = time / length;
1667 }
1669 if (dir == 1)
1670 {
1671 if (fraction <= 0)
1672 {
1673 element.setAttribute("transform", options["matrixInitial"].toAttribute());
1674 }
1675 else if (fraction >= 1)
1676 {
1677 element.setAttribute("transform", options["matrixNew"].toAttribute());
1679 set_path_paint_width();
1681 options["matrixInitial"] = null;
1682 return true;
1683 }
1684 else
1685 {
1686 element.setAttribute("transform", options["matrixInitial"].mix(options["matrixNew"], fraction).toAttribute());
1687 }
1688 }
1689 else if (dir == -1)
1690 {
1691 if (fraction <= 0)
1692 {
1693 element.setAttribute("transform", options["matrixInitial"].toAttribute());
1694 }
1695 else if (fraction >= 1)
1696 {
1697 element.setAttribute("transform", options["matrixOld"].toAttribute());
1698 set_path_paint_width();
1700 options["matrixInitial"] = null;
1701 return true;
1702 }
1703 else
1704 {
1705 element.setAttribute("transform", options["matrixInitial"].mix(options["matrixOld"], fraction).toAttribute());
1706 }
1707 }
1709 return false;
1710 }
1712 /** The fade effect.
1713 *
1714 * @param dir direction the effect should be played (1 = forwards, -1 = backwards)
1715 * @param element the element the effect should be applied to
1716 * @param time the time that has elapsed since the beginning of the effect
1717 * @param options a dictionary with additional options (e.g. length of the effect)
1718 */
1719 function fade(dir, element, time, options)
1720 {
1721 var length = 250;
1722 var fraction;
1724 if ((time == STATE_END) || (time == STATE_START))
1725 fraction = 1;
1726 else
1727 {
1728 if (options && options["length"])
1729 length = options["length"];
1731 fraction = time / length;
1732 }
1734 if (dir == 1)
1735 {
1736 if (fraction <= 0)
1737 {
1738 element.style.display = "none";
1739 element.setAttribute("opacity", 0);
1740 }
1741 else if (fraction >= 1)
1742 {
1743 element.style.display = "inherit";
1744 element.setAttribute("opacity", 1);
1745 return true;
1746 }
1747 else
1748 {
1749 element.style.display = "inherit";
1750 element.setAttribute("opacity", fraction);
1751 }
1752 }
1753 else if (dir == -1)
1754 {
1755 if (fraction <= 0)
1756 {
1757 element.style.display = "inherit";
1758 element.setAttribute("opacity", 1);
1759 }
1760 else if (fraction >= 1)
1761 {
1762 element.setAttribute("opacity", 0);
1763 element.style.display = "none";
1764 return true;
1765 }
1766 else
1767 {
1768 element.style.display = "inherit";
1769 element.setAttribute("opacity", 1 - fraction);
1770 }
1771 }
1772 return false;
1773 }
1775 /** The appear effect.
1776 *
1777 * @param dir direction the effect should be played (1 = forwards, -1 = backwards)
1778 * @param element the element the effect should be applied to
1779 * @param time the time that has elapsed since the beginning of the effect
1780 * @param options a dictionary with additional options (e.g. length of the effect)
1781 */
1782 function appear(dir, element, time, options)
1783 {
1784 if (dir == 1)
1785 {
1786 element.style.display = "inherit";
1787 element.setAttribute("opacity",1);
1788 }
1789 else if (dir == -1)
1790 {
1791 element.style.display = "none";
1792 element.setAttribute("opacity",0);
1793 }
1794 return true;
1795 }
1797 /** The pop effect.
1798 *
1799 * @param dir direction the effect should be played (1 = forwards, -1 = backwards)
1800 * @param element the element the effect should be applied to
1801 * @param time the time that has elapsed since the beginning of the effect
1802 * @param options a dictionary with additional options (e.g. length of the effect)
1803 */
1804 function pop(dir, element, time, options)
1805 {
1806 var length = 500;
1807 var fraction;
1809 if ((time == STATE_END) || (time == STATE_START))
1810 fraction = 1;
1811 else
1812 {
1813 if (options && options["length"])
1814 length = options["length"];
1816 fraction = time / length;
1817 }
1819 if (dir == 1)
1820 {
1821 if (fraction <= 0)
1822 {
1823 element.setAttribute("opacity", 0);
1824 element.setAttribute("transform", "scale(0)");
1825 element.style.display = "none";
1826 }
1827 else if (fraction >= 1)
1828 {
1829 element.setAttribute("opacity", 1);
1830 element.removeAttribute("transform");
1831 element.style.display = "inherit";
1832 return true;
1833 }
1834 else
1835 {
1836 element.style.display = "inherit";
1837 var opacityFraction = fraction * 3;
1838 if (opacityFraction > 1)
1839 opacityFraction = 1;
1840 element.setAttribute("opacity", opacityFraction);
1841 var offsetX = WIDTH * (1.0 - fraction) / 2.0;
1842 var offsetY = HEIGHT * (1.0 - fraction) / 2.0;
1843 element.setAttribute("transform", "translate(" + offsetX + "," + offsetY + ") scale(" + fraction + ")");
1844 }
1845 }
1846 else if (dir == -1)
1847 {
1848 if (fraction <= 0)
1849 {
1850 element.setAttribute("opacity", 1);
1851 element.setAttribute("transform", "scale(1)");
1852 element.style.display = "inherit";
1853 }
1854 else if (fraction >= 1)
1855 {
1856 element.setAttribute("opacity", 0);
1857 element.removeAttribute("transform");
1858 element.style.display = "none";
1859 return true;
1860 }
1861 else
1862 {
1863 element.setAttribute("opacity", 1 - fraction);
1864 element.setAttribute("transform", "scale(" + 1 - fraction + ")");
1865 element.style.display = "inherit";
1866 }
1867 }
1868 return false;
1869 }
1871 /** Function to set a slide either to the start or the end state.
1872 *
1873 * @param slide the slide to use
1874 * @param state the state into which the slide should be set
1875 */
1876 function setSlideToState(slide, state)
1877 {
1878 slides[slide]["viewGroup"].setAttribute("transform", slides[slide].initialView);
1880 if (slides[slide]["effects"])
1881 {
1882 if (state == STATE_END)
1883 {
1884 for (var counter = 0; counter < slides[slide]["effects"].length; counter++)
1885 {
1886 for (var subCounter = 0; subCounter < slides[slide]["effects"][counter].length; subCounter++)
1887 {
1888 var effect = slides[slide]["effects"][counter][subCounter];
1889 if (effect["effect"] == "fade")
1890 fade(effect["dir"], effect["element"], STATE_END, effect["options"]);
1891 else if (effect["effect"] == "appear")
1892 appear(effect["dir"], effect["element"], STATE_END, effect["options"]);
1893 else if (effect["effect"] == "pop")
1894 pop(effect["dir"], effect["element"], STATE_END, effect["options"]);
1895 else if (effect["effect"] == "view")
1896 view(effect["dir"], effect["element"], STATE_END, effect["options"]);
1897 }
1898 }
1899 }
1900 else if (state == STATE_START)
1901 {
1902 for (var counter = slides[slide]["effects"].length - 1; counter >= 0; counter--)
1903 {
1904 for (var subCounter = 0; subCounter < slides[slide]["effects"][counter].length; subCounter++)
1905 {
1906 var effect = slides[slide]["effects"][counter][subCounter];
1907 if (effect["effect"] == "fade")
1908 fade(parseInt(effect["dir"]) * -1, effect["element"], STATE_START, effect["options"]);
1909 else if (effect["effect"] == "appear")
1910 appear(parseInt(effect["dir"]) * -1, effect["element"], STATE_START, effect["options"]);
1911 else if (effect["effect"] == "pop")
1912 pop(parseInt(effect["dir"]) * -1, effect["element"], STATE_START, effect["options"]);
1913 else if (effect["effect"] == "view")
1914 view(parseInt(effect["dir"]) * -1, effect["element"], STATE_START, effect["options"]);
1915 }
1916 }
1917 }
1918 else
1919 {
1920 setSlideToState(slide, STATE_START);
1922 for (var counter = 0; counter < slides[slide]["effects"].length && counter < state; counter++)
1923 {
1924 for (var subCounter = 0; subCounter < slides[slide]["effects"][counter].length; subCounter++)
1925 {
1926 var effect = slides[slide]["effects"][counter][subCounter];
1927 if (effect["effect"] == "fade")
1928 fade(effect["dir"], effect["element"], STATE_END, effect["options"]);
1929 else if (effect["effect"] == "appear")
1930 appear(effect["dir"], effect["element"], STATE_END, effect["options"]);
1931 else if (effect["effect"] == "pop")
1932 pop(effect["dir"], effect["element"], STATE_END, effect["options"]);
1933 else if (effect["effect"] == "view")
1934 view(effect["dir"], effect["element"], STATE_END, effect["options"]);
1935 }
1936 }
1937 }
1938 }
1940 window.location.hash = (activeSlide + 1) + '_' + activeEffect;
1941 }
1943 /** Convenience function to translate a attribute string into a dictionary.
1944 *
1945 * @param str the attribute string
1946 * @return a dictionary
1947 * @see dictToPropStr
1948 */
1949 function propStrToDict(str)
1950 {
1951 var list = str.split(";");
1952 var obj = new Object();
1954 for (var counter = 0; counter < list.length; counter++)
1955 {
1956 var subStr = list[counter];
1957 var subList = subStr.split(":");
1958 if (subList.length == 2)
1959 {
1960 obj[subList[0]] = subList[1];
1961 }
1962 }
1964 return obj;
1965 }
1967 /** Convenience function to translate a dictionary into a string that can be used as an attribute.
1968 *
1969 * @param dict the dictionary to convert
1970 * @return a string that can be used as an attribute
1971 * @see propStrToDict
1972 */
1973 function dictToPropStr(dict)
1974 {
1975 var str = "";
1977 for (var key in dict)
1978 {
1979 str += key + ":" + dict[key] + ";";
1980 }
1982 return str;
1983 }
1985 /** Sub-function to add a suffix to the ids of the node and all its children.
1986 *
1987 * @param node the node to change
1988 * @param suffix the suffix to add
1989 * @param replace dictionary of replaced ids
1990 * @see suffixNodeIds
1991 */
1992 function suffixNoneIds_sub(node, suffix, replace)
1993 {
1994 if (node.nodeType == 1)
1995 {
1996 if (node.getAttribute("id"))
1997 {
1998 var id = node.getAttribute("id")
1999 replace["#" + id] = id + suffix;
2000 node.setAttribute("id", id + suffix);
2001 }
2003 if ((node.nodeName == "use") && (node.getAttributeNS(NSS["xlink"], "href")) && (replace[node.getAttribute(NSS["xlink"], "href")]))
2004 node.setAttribute(NSS["xlink"], "href", node.getAttribute(NSS["xlink"], "href") + suffix);
2006 if (node.childNodes)
2007 {
2008 for (var counter = 0; counter < node.childNodes.length; counter++)
2009 suffixNoneIds_sub(node.childNodes[counter], suffix, replace);
2010 }
2011 }
2012 }
2014 /** Function to add a suffix to the ids of the node and all its children.
2015 *
2016 * @param node the node to change
2017 * @param suffix the suffix to add
2018 * @return the changed node
2019 * @see suffixNodeIds_sub
2020 */
2021 function suffixNodeIds(node, suffix)
2022 {
2023 var replace = new Object();
2025 suffixNoneIds_sub(node, suffix, replace);
2027 return node;
2028 }
2030 /** Function to build a progress bar.
2031 *
2032 * @param parent node to attach the progress bar to
2033 */
2034 function createProgressBar(parent_node)
2035 {
2036 var g = document.createElementNS(NSS["svg"], "g");
2037 g.setAttribute("clip-path", "url(#jessyInkSlideClipPath)");
2038 g.setAttribute("id", "layer_progress_bar");
2039 g.setAttribute("style", "display: none;");
2041 var rect_progress_bar = document.createElementNS(NSS["svg"], "rect");
2042 rect_progress_bar.setAttribute("style", "marker: none; fill: rgb(128, 128, 128); stroke: none;");
2043 rect_progress_bar.setAttribute("id", "rect_progress_bar");
2044 rect_progress_bar.setAttribute("x", 0);
2045 rect_progress_bar.setAttribute("y", 0.99 * HEIGHT);
2046 rect_progress_bar.setAttribute("width", 0);
2047 rect_progress_bar.setAttribute("height", 0.01 * HEIGHT);
2048 g.appendChild(rect_progress_bar);
2050 var circle_timer_indicator = document.createElementNS(NSS["svg"], "circle");
2051 circle_timer_indicator.setAttribute("style", "marker: none; fill: rgb(255, 0, 0); stroke: none;");
2052 circle_timer_indicator.setAttribute("id", "circle_timer_indicator");
2053 circle_timer_indicator.setAttribute("cx", 0.005 * HEIGHT);
2054 circle_timer_indicator.setAttribute("cy", 0.995 * HEIGHT);
2055 circle_timer_indicator.setAttribute("r", 0.005 * HEIGHT);
2056 g.appendChild(circle_timer_indicator);
2058 parent_node.appendChild(g);
2059 }
2061 /** Function to hide the progress bar.
2062 *
2063 */
2064 function hideProgressBar()
2065 {
2066 var progress_bar = document.getElementById("layer_progress_bar");
2068 if (!progress_bar)
2069 {
2070 return;
2071 }
2073 progress_bar.setAttribute("style", "display: none;");
2074 }
2076 /** Function to show the progress bar.
2077 *
2078 */
2079 function showProgressBar()
2080 {
2081 var progress_bar = document.getElementById("layer_progress_bar");
2083 if (!progress_bar)
2084 {
2085 return;
2086 }
2088 progress_bar.setAttribute("style", "display: inherit;");
2089 }
2091 /** Set progress bar value.
2092 *
2093 * @param value the current slide number
2094 *
2095 */
2096 function setProgressBarValue(value)
2097 {
2098 var rect_progress_bar = document.getElementById("rect_progress_bar");
2100 if (!rect_progress_bar)
2101 {
2102 return;
2103 }
2105 if (value < 1)
2106 {
2107 // First slide, assumed to be the title of the presentation
2108 var x = 0;
2109 var w = 0.01 * HEIGHT;
2110 }
2111 else if (value >= slides.length - 1)
2112 {
2113 // Last slide, assumed to be the end of the presentation
2114 var x = WIDTH - 0.01 * HEIGHT;
2115 var w = 0.01 * HEIGHT;
2116 }
2117 else
2118 {
2119 value -= 1;
2120 value /= (slides.length - 2);
2122 var x = WIDTH * value;
2123 var w = WIDTH / (slides.length - 2);
2124 }
2126 rect_progress_bar.setAttribute("x", x);
2127 rect_progress_bar.setAttribute("width", w);
2128 }
2130 /** Set time indicator.
2131 *
2132 * @param value the percentage of time elapse so far between 0.0 and 1.0
2133 *
2134 */
2135 function setTimeIndicatorValue(value)
2136 {
2137 var circle_timer_indicator = document.getElementById("circle_timer_indicator");
2139 if (!circle_timer_indicator)
2140 {
2141 return;
2142 }
2144 if (value < 0.0)
2145 {
2146 value = 0.0;
2147 }
2149 if (value > 1.0)
2150 {
2151 value = 1.0;
2152 }
2154 var cx = (WIDTH - 0.01 * HEIGHT) * value + 0.005 * HEIGHT;
2155 circle_timer_indicator.setAttribute("cx", cx);
2156 }
2158 /** Update timer.
2159 *
2160 */
2161 function updateTimer()
2162 {
2163 timer_elapsed += 1;
2164 setTimeIndicatorValue((timer_elapsed - timer_start) / (60 * timer_duration));
2165 }
2167 /** Convert screen coordinates to document coordinates.
2168 *
2169 * @param e event with screen coordinates
2170 *
2171 * @return coordinates in SVG file coordinate system
2172 */
2173 function calcCoord(e)
2174 {
2175 var svgPoint = document.documentElement.createSVGPoint();
2176 svgPoint.x = e.clientX + window.pageXOffset;
2177 svgPoint.y = e.clientY + window.pageYOffset;
2179 var matrix = slides[activeSlide]["element"].getScreenCTM();
2181 if (slides[activeSlide]["viewGroup"])
2182 matrix = slides[activeSlide]["viewGroup"].getScreenCTM();
2184 svgPoint = svgPoint.matrixTransform(matrix.inverse());
2185 return svgPoint;
2186 }
2188 /** Add slide.
2189 *
2190 * @param after_slide after which slide the new slide should be inserted into the presentation
2191 */
2192 function addSlide(after_slide)
2193 {
2194 number_of_added_slides++;
2196 var g = document.createElementNS(NSS["svg"], "g");
2197 g.setAttribute("clip-path", "url(#jessyInkSlideClipPath)");
2198 g.setAttribute("id", "Whiteboard " + Date() + " presentation copy");
2199 g.setAttribute("style", "display: none;");
2201 var new_slide = new Object();
2202 new_slide["element"] = g;
2204 // Set build in transition.
2205 new_slide["transitionIn"] = new Object();
2206 var dict = defaultTransitionInDict;
2207 new_slide["transitionIn"]["name"] = dict["name"];
2208 new_slide["transitionIn"]["options"] = new Object();
2210 for (key in dict)
2211 if (key != "name")
2212 new_slide["transitionIn"]["options"][key] = dict[key];
2214 // Set build out transition.
2215 new_slide["transitionOut"] = new Object();
2216 dict = defaultTransitionOutDict;
2217 new_slide["transitionOut"]["name"] = dict["name"];
2218 new_slide["transitionOut"]["options"] = new Object();
2220 for (key in dict)
2221 if (key != "name")
2222 new_slide["transitionOut"]["options"][key] = dict[key];
2224 // Copy master slide content.
2225 if (masterSlide)
2226 {
2227 var clonedNode = suffixNodeIds(masterSlide.cloneNode(true), "_" + Date() + " presentation_copy");
2228 clonedNode.removeAttributeNS(NSS["inkscape"], "groupmode");
2229 clonedNode.removeAttributeNS(NSS["inkscape"], "label");
2230 clonedNode.style.display = "inherit";
2232 g.appendChild(clonedNode);
2233 }
2235 // Substitute auto texts.
2236 substituteAutoTexts(g, "Whiteboard " + number_of_added_slides, "W" + number_of_added_slides, slides.length);
2238 g.setAttribute("onmouseover", "if ((currentMode == INDEX_MODE) && ( activeSlide != " + (after_slide + 1) + ")) { indexSetActiveSlide(" + (after_slide + 1) + "); };");
2240 // Create a transform group.
2241 var transformGroup = document.createElementNS(NSS["svg"], "g");
2243 // Add content to transform group.
2244 while (g.firstChild)
2245 transformGroup.appendChild(g.firstChild);
2247 // Transfer the transform attribute from the node to the transform group.
2248 if (g.getAttribute("transform"))
2249 {
2250 transformGroup.setAttribute("transform", g.getAttribute("transform"));
2251 g.removeAttribute("transform");
2252 }
2254 // Create a view group.
2255 var viewGroup = document.createElementNS(NSS["svg"], "g");
2257 viewGroup.appendChild(transformGroup);
2258 new_slide["viewGroup"] = g.appendChild(viewGroup);
2260 // Insert background.
2261 if (BACKGROUND_COLOR != null)
2262 {
2263 var rectNode = document.createElementNS(NSS["svg"], "rect");
2265 rectNode.setAttribute("x", 0);
2266 rectNode.setAttribute("y", 0);
2267 rectNode.setAttribute("width", WIDTH);
2268 rectNode.setAttribute("height", HEIGHT);
2269 rectNode.setAttribute("id", "jessyInkBackground" + Date());
2270 rectNode.setAttribute("fill", BACKGROUND_COLOR);
2272 new_slide["viewGroup"].insertBefore(rectNode, new_slide["viewGroup"].firstChild);
2273 }
2275 // Set initial view even if there are no other views.
2276 var matrixOld = (new matrixSVG()).fromElements(1, 0, 0, 0, 1, 0, 0, 0, 1);
2278 new_slide["viewGroup"].setAttribute("transform", matrixOld.toAttribute());
2279 new_slide.initialView = matrixOld.toAttribute();
2281 // Insert slide
2282 var node = slides[after_slide]["element"];
2283 var next_node = node.nextSibling;
2284 var parent_node = node.parentNode;
2286 if (next_node)
2287 {
2288 parent_node.insertBefore(g, next_node);
2289 }
2290 else
2291 {
2292 parent_node.appendChild(g);
2293 }
2295 g = document.createElementNS(NSS["svg"], "g");
2296 g.setAttributeNS(NSS["inkscape"], "groupmode", "layer");
2297 g.setAttributeNS(NSS["inkscape"], "label", "Whiteboard " + number_of_added_slides);
2298 g.setAttribute("clip-path", "url(#jessyInkSlideClipPath)");
2299 g.setAttribute("id", "Whiteboard " + Date());
2300 g.setAttribute("style", "display: none;");
2302 new_slide["original_element"] = g;
2304 node = slides[after_slide]["original_element"];
2305 next_node = node.nextSibling;
2306 parent_node = node.parentNode;
2308 if (next_node)
2309 {
2310 parent_node.insertBefore(g, next_node);
2311 }
2312 else
2313 {
2314 parent_node.appendChild(g);
2315 }
2317 before_new_slide = slides.slice(0, after_slide + 1);
2318 after_new_slide = slides.slice(after_slide + 1);
2319 slides = before_new_slide.concat(new_slide, after_new_slide);
2321 //resetting the counter attributes on the slides that follow the new slide...
2322 for (var counter = after_slide+2; counter < slides.length; counter++)
2323 {
2324 slides[counter]["element"].setAttribute("onmouseover", "if ((currentMode == INDEX_MODE) && ( activeSlide != " + counter + ")) { indexSetActiveSlide(" + counter + "); };");
2325 }
2326 }
2328 /** Convenience function to obtain a transformation matrix from a point matrix.
2329 *
2330 * @param mPoints Point matrix.
2331 * @return A transformation matrix.
2332 */
2333 function pointMatrixToTransformation(mPoints)
2334 {
2335 mPointsOld = (new matrixSVG()).fromElements(0, WIDTH, WIDTH, 0, 0, HEIGHT, 1, 1, 1);
2337 return mPointsOld.mult(mPoints.inv());
2338 }
2340 /** Convenience function to obtain a matrix with three corners of a rectangle.
2341 *
2342 * @param rect an svg rectangle
2343 * @return a matrixSVG containing three corners of the rectangle
2344 */
2345 function rectToMatrix(rect)
2346 {
2347 rectWidth = rect.getBBox().width;
2348 rectHeight = rect.getBBox().height;
2349 rectX = rect.getBBox().x;
2350 rectY = rect.getBBox().y;
2351 rectXcorr = 0;
2352 rectYcorr = 0;
2354 scaleX = WIDTH / rectWidth;
2355 scaleY = HEIGHT / rectHeight;
2357 if (scaleX > scaleY)
2358 {
2359 scaleX = scaleY;
2360 rectXcorr -= (WIDTH / scaleX - rectWidth) / 2;
2361 rectWidth = WIDTH / scaleX;
2362 }
2363 else
2364 {
2365 scaleY = scaleX;
2366 rectYcorr -= (HEIGHT / scaleY - rectHeight) / 2;
2367 rectHeight = HEIGHT / scaleY;
2368 }
2370 if (rect.transform.baseVal.numberOfItems < 1)
2371 {
2372 mRectTrans = (new matrixSVG()).fromElements(1, 0, 0, 0, 1, 0, 0, 0, 1);
2373 }
2374 else
2375 {
2376 mRectTrans = (new matrixSVG()).fromSVGMatrix(rect.transform.baseVal.consolidate().matrix);
2377 }
2379 newBasePoints = (new matrixSVG()).fromElements(rectX, rectX, rectX, rectY, rectY, rectY, 1, 1, 1);
2380 newVectors = (new matrixSVG()).fromElements(rectXcorr, rectXcorr + rectWidth, rectXcorr + rectWidth, rectYcorr, rectYcorr, rectYcorr + rectHeight, 0, 0, 0);
2382 return mRectTrans.mult(newBasePoints.add(newVectors));
2383 }
2385 /** Function to handle JessyInk elements.
2386 *
2387 * @param node Element node.
2388 */
2389 function handleElement(node)
2390 {
2391 if (node.getAttributeNS(NSS['jessyink'], 'element') == 'core.video')
2392 {
2393 var url;
2394 var width;
2395 var height;
2396 var x;
2397 var y;
2398 var transform;
2400 var tspans = node.getElementsByTagNameNS("http://www.w3.org/2000/svg", "tspan");
2402 for (var tspanCounter = 0; tspanCounter < tspans.length; tspanCounter++)
2403 {
2404 if (tspans[tspanCounter].getAttributeNS("https://launchpad.net/jessyink", "video") == "url")
2405 {
2406 url = tspans[tspanCounter].firstChild.nodeValue;
2407 }
2408 }
2410 var rects = node.getElementsByTagNameNS("http://www.w3.org/2000/svg", "rect");
2412 for (var rectCounter = 0; rectCounter < rects.length; rectCounter++)
2413 {
2414 if (rects[rectCounter].getAttributeNS("https://launchpad.net/jessyink", "video") == "rect")
2415 {
2416 x = rects[rectCounter].getAttribute("x");
2417 y = rects[rectCounter].getAttribute("y");
2418 width = rects[rectCounter].getAttribute("width");
2419 height = rects[rectCounter].getAttribute("height");
2420 transform = rects[rectCounter].getAttribute("transform");
2421 }
2422 }
2424 for (var childCounter = 0; childCounter < node.childNodes.length; childCounter++)
2425 {
2426 if (node.childNodes[childCounter].nodeType == 1)
2427 {
2428 if (node.childNodes[childCounter].style)
2429 {
2430 node.childNodes[childCounter].style.display = 'none';
2431 }
2432 else
2433 {
2434 node.childNodes[childCounter].setAttribute("style", "display: none;");
2435 }
2436 }
2437 }
2439 var foreignNode = document.createElementNS("http://www.w3.org/2000/svg", "foreignObject");
2440 foreignNode.setAttribute("x", x);
2441 foreignNode.setAttribute("y", y);
2442 foreignNode.setAttribute("width", width);
2443 foreignNode.setAttribute("height", height);
2444 foreignNode.setAttribute("transform", transform);
2446 var videoNode = document.createElementNS("http://www.w3.org/1999/xhtml", "video");
2447 videoNode.setAttribute("src", url);
2449 foreignNode.appendChild(videoNode);
2450 node.appendChild(foreignNode);
2451 }
2452 }
2454 /** Class processing the location hash.
2455 *
2456 * @param str location hash
2457 */
2458 function LocationHash(str)
2459 {
2460 this.slideNumber = 0;
2461 this.effectNumber = 0;
2463 str = str.substr(1, str.length - 1);
2465 var parts = str.split('_');
2467 // Try to extract slide number.
2468 if (parts.length >= 1)
2469 {
2470 try
2471 {
2472 var slideNumber = parseInt(parts[0]);
2474 if (!isNaN(slideNumber))
2475 {
2476 this.slideNumber = slideNumber - 1;
2477 }
2478 }
2479 catch (e)
2480 {
2481 }
2482 }
2484 // Try to extract effect number.
2485 if (parts.length >= 2)
2486 {
2487 try
2488 {
2489 var effectNumber = parseInt(parts[1]);
2491 if (!isNaN(effectNumber))
2492 {
2493 this.effectNumber = effectNumber;
2494 }
2495 }
2496 catch (e)
2497 {
2498 }
2499 }
2500 }
2502 /** Class representing an svg matrix.
2503 */
2504 function matrixSVG()
2505 {
2506 this.e11 = 0; // a
2507 this.e12 = 0; // c
2508 this.e13 = 0; // e
2509 this.e21 = 0; // b
2510 this.e22 = 0; // d
2511 this.e23 = 0; // f
2512 this.e31 = 0;
2513 this.e32 = 0;
2514 this.e33 = 0;
2515 }
2517 /** Constructor function.
2518 *
2519 * @param a element a (i.e. 1, 1) as described in the svg standard.
2520 * @param b element b (i.e. 2, 1) as described in the svg standard.
2521 * @param c element c (i.e. 1, 2) as described in the svg standard.
2522 * @param d element d (i.e. 2, 2) as described in the svg standard.
2523 * @param e element e (i.e. 1, 3) as described in the svg standard.
2524 * @param f element f (i.e. 2, 3) as described in the svg standard.
2525 */
2526 matrixSVG.prototype.fromSVGElements = function(a, b, c, d, e, f)
2527 {
2528 this.e11 = a;
2529 this.e12 = c;
2530 this.e13 = e;
2531 this.e21 = b;
2532 this.e22 = d;
2533 this.e23 = f;
2534 this.e31 = 0;
2535 this.e32 = 0;
2536 this.e33 = 1;
2538 return this;
2539 }
2541 /** Constructor function.
2542 *
2543 * @param matrix an svg matrix as described in the svg standard.
2544 */
2545 matrixSVG.prototype.fromSVGMatrix = function(m)
2546 {
2547 this.e11 = m.a;
2548 this.e12 = m.c;
2549 this.e13 = m.e;
2550 this.e21 = m.b;
2551 this.e22 = m.d;
2552 this.e23 = m.f;
2553 this.e31 = 0;
2554 this.e32 = 0;
2555 this.e33 = 1;
2557 return this;
2558 }
2560 /** Constructor function.
2561 *
2562 * @param e11 element 1, 1 of the matrix.
2563 * @param e12 element 1, 2 of the matrix.
2564 * @param e13 element 1, 3 of the matrix.
2565 * @param e21 element 2, 1 of the matrix.
2566 * @param e22 element 2, 2 of the matrix.
2567 * @param e23 element 2, 3 of the matrix.
2568 * @param e31 element 3, 1 of the matrix.
2569 * @param e32 element 3, 2 of the matrix.
2570 * @param e33 element 3, 3 of the matrix.
2571 */
2572 matrixSVG.prototype.fromElements = function(e11, e12, e13, e21, e22, e23, e31, e32, e33)
2573 {
2574 this.e11 = e11;
2575 this.e12 = e12;
2576 this.e13 = e13;
2577 this.e21 = e21;
2578 this.e22 = e22;
2579 this.e23 = e23;
2580 this.e31 = e31;
2581 this.e32 = e32;
2582 this.e33 = e33;
2584 return this;
2585 }
2587 /** Constructor function.
2588 *
2589 * @param attrString string value of the "transform" attribute (currently only "matrix" is accepted)
2590 */
2591 matrixSVG.prototype.fromAttribute = function(attrString)
2592 {
2593 str = attrString.substr(7, attrString.length - 8);
2595 str = str.trim();
2597 strArray = str.split(",");
2599 // Opera does not use commas to separate the values of the matrix, only spaces.
2600 if (strArray.length != 6)
2601 strArray = str.split(" ");
2603 this.e11 = parseFloat(strArray[0]);
2604 this.e21 = parseFloat(strArray[1]);
2605 this.e31 = 0;
2606 this.e12 = parseFloat(strArray[2]);
2607 this.e22 = parseFloat(strArray[3]);
2608 this.e32 = 0;
2609 this.e13 = parseFloat(strArray[4]);
2610 this.e23 = parseFloat(strArray[5]);
2611 this.e33 = 1;
2613 return this;
2614 }
2616 /** Output function
2617 *
2618 * @return a string that can be used as the "transform" attribute.
2619 */
2620 matrixSVG.prototype.toAttribute = function()
2621 {
2622 return "matrix(" + this.e11 + ", " + this.e21 + ", " + this.e12 + ", " + this.e22 + ", " + this.e13 + ", " + this.e23 + ")";
2623 }
2625 /** Matrix nversion.
2626 *
2627 * @return the inverse of the matrix
2628 */
2629 matrixSVG.prototype.inv = function()
2630 {
2631 out = new matrixSVG();
2633 det = this.e11 * (this.e33 * this.e22 - this.e32 * this.e23) - this.e21 * (this.e33 * this.e12 - this.e32 * this.e13) + this.e31 * (this.e23 * this.e12 - this.e22 * this.e13);
2635 out.e11 = (this.e33 * this.e22 - this.e32 * this.e23) / det;
2636 out.e12 = -(this.e33 * this.e12 - this.e32 * this.e13) / det;
2637 out.e13 = (this.e23 * this.e12 - this.e22 * this.e13) / det;
2638 out.e21 = -(this.e33 * this.e21 - this.e31 * this.e23) / det;
2639 out.e22 = (this.e33 * this.e11 - this.e31 * this.e13) / det;
2640 out.e23 = -(this.e23 * this.e11 - this.e21 * this.e13) / det;
2641 out.e31 = (this.e32 * this.e21 - this.e31 * this.e22) / det;
2642 out.e32 = -(this.e32 * this.e11 - this.e31 * this.e12) / det;
2643 out.e33 = (this.e22 * this.e11 - this.e21 * this.e12) / det;
2645 return out;
2646 }
2648 /** Matrix multiplication.
2649 *
2650 * @param op another svg matrix
2651 * @return this * op
2652 */
2653 matrixSVG.prototype.mult = function(op)
2654 {
2655 out = new matrixSVG();
2657 out.e11 = this.e11 * op.e11 + this.e12 * op.e21 + this.e13 * op.e31;
2658 out.e12 = this.e11 * op.e12 + this.e12 * op.e22 + this.e13 * op.e32;
2659 out.e13 = this.e11 * op.e13 + this.e12 * op.e23 + this.e13 * op.e33;
2660 out.e21 = this.e21 * op.e11 + this.e22 * op.e21 + this.e23 * op.e31;
2661 out.e22 = this.e21 * op.e12 + this.e22 * op.e22 + this.e23 * op.e32;
2662 out.e23 = this.e21 * op.e13 + this.e22 * op.e23 + this.e23 * op.e33;
2663 out.e31 = this.e31 * op.e11 + this.e32 * op.e21 + this.e33 * op.e31;
2664 out.e32 = this.e31 * op.e12 + this.e32 * op.e22 + this.e33 * op.e32;
2665 out.e33 = this.e31 * op.e13 + this.e32 * op.e23 + this.e33 * op.e33;
2667 return out;
2668 }
2670 /** Matrix addition.
2671 *
2672 * @param op another svg matrix
2673 * @return this + op
2674 */
2675 matrixSVG.prototype.add = function(op)
2676 {
2677 out = new matrixSVG();
2679 out.e11 = this.e11 + op.e11;
2680 out.e12 = this.e12 + op.e12;
2681 out.e13 = this.e13 + op.e13;
2682 out.e21 = this.e21 + op.e21;
2683 out.e22 = this.e22 + op.e22;
2684 out.e23 = this.e23 + op.e23;
2685 out.e31 = this.e31 + op.e31;
2686 out.e32 = this.e32 + op.e32;
2687 out.e33 = this.e33 + op.e33;
2689 return out;
2690 }
2692 /** Matrix mixing.
2693 *
2694 * @param op another svg matrix
2695 * @parma contribOp contribution of the other matrix (0 <= contribOp <= 1)
2696 * @return (1 - contribOp) * this + contribOp * op
2697 */
2698 matrixSVG.prototype.mix = function(op, contribOp)
2699 {
2700 contribThis = 1.0 - contribOp;
2701 out = new matrixSVG();
2703 out.e11 = contribThis * this.e11 + contribOp * op.e11;
2704 out.e12 = contribThis * this.e12 + contribOp * op.e12;
2705 out.e13 = contribThis * this.e13 + contribOp * op.e13;
2706 out.e21 = contribThis * this.e21 + contribOp * op.e21;
2707 out.e22 = contribThis * this.e22 + contribOp * op.e22;
2708 out.e23 = contribThis * this.e23 + contribOp * op.e23;
2709 out.e31 = contribThis * this.e31 + contribOp * op.e31;
2710 out.e32 = contribThis * this.e32 + contribOp * op.e32;
2711 out.e33 = contribThis * this.e33 + contribOp * op.e33;
2713 return out;
2714 }
2716 /** Trimming function for strings.
2717 */
2718 String.prototype.trim = function()
2719 {
2720 return this.replace(/^\s+|\s+$/g, '');
2721 }