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 // 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();
1438 }
1440 /** Function to undo last drawing operation.
1441 */
1442 function drawingUndo()
1443 {
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 }
1455 }
1457 /** Event handler for mouse down in drawing mode.
1458 *
1459 * @param e the event
1460 */
1461 function drawingMousedown(e)
1462 {
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;
1505 }
1507 /** Event handler for mouse up in drawing mode.
1508 *
1509 * @param e the event
1510 */
1511 function drawingMouseup(e)
1512 {
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;
1530 }
1532 /** Event handler for mouse move in drawing mode.
1533 *
1534 * @param e the event
1535 */
1536 function drawingMousemove(e)
1537 {
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;
1560 }
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)
1568 {
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;
1592 }
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)
1600 {
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;
1624 }
1626 /** Function to set the path paint width.
1627 */
1628 function set_path_paint_width()
1629 {
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));
1647 }
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)
1657 {
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;
1722 }
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)
1732 {
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;
1785 }
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)
1795 {
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;
1807 }
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)
1817 {
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;
1881 }
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)
1889 {
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;
1953 }
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)
1962 {
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;
1977 }
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)
1986 {
1987 var str = "";
1989 for (var key in dict)
1990 {
1991 str += key + ":" + dict[key] + ";";
1992 }
1994 return str;
1995 }
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)
2005 {
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 }
2024 }
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)
2034 {
2035 var replace = new Object();
2037 suffixNoneIds_sub(node, suffix, replace);
2039 return node;
2040 }
2042 /** Function to build a progress bar.
2043 *
2044 * @param parent node to attach the progress bar to
2045 */
2046 function createProgressBar(parent_node)
2047 {
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);
2071 }
2073 /** Function to hide the progress bar.
2074 *
2075 */
2076 function hideProgressBar()
2077 {
2078 var progress_bar = document.getElementById("layer_progress_bar");
2080 if (!progress_bar)
2081 {
2082 return;
2083 }
2085 progress_bar.setAttribute("style", "display: none;");
2086 }
2088 /** Function to show the progress bar.
2089 *
2090 */
2091 function showProgressBar()
2092 {
2093 var progress_bar = document.getElementById("layer_progress_bar");
2095 if (!progress_bar)
2096 {
2097 return;
2098 }
2100 progress_bar.setAttribute("style", "display: inherit;");
2101 }
2103 /** Set progress bar value.
2104 *
2105 * @param value the current slide number
2106 *
2107 */
2108 function setProgressBarValue(value)
2109 {
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);
2140 }
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)
2148 {
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);
2168 }
2170 /** Update timer.
2171 *
2172 */
2173 function updateTimer()
2174 {
2175 timer_elapsed += 1;
2176 setTimeIndicatorValue((timer_elapsed - timer_start) / (60 * timer_duration));
2177 }
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)
2186 {
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;
2198 }
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)
2205 {
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 }
2338 }
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)
2346 {
2347 mPointsOld = (new matrixSVG()).fromElements(0, WIDTH, WIDTH, 0, 0, HEIGHT, 1, 1, 1);
2349 return mPointsOld.mult(mPoints.inv());
2350 }
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)
2358 {
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));
2395 }
2397 /** Function to handle JessyInk elements.
2398 *
2399 * @param node Element node.
2400 */
2401 function handleElement(node)
2402 {
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 }
2464 }
2466 /** Class processing the location hash.
2467 *
2468 * @param str location hash
2469 */
2470 function LocationHash(str)
2471 {
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 }
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 }
2512 }
2514 /** Class representing an svg matrix.
2515 */
2516 function matrixSVG()
2517 {
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;
2527 }
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)
2539 {
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;
2551 }
2553 /** Constructor function.
2554 *
2555 * @param matrix an svg matrix as described in the svg standard.
2556 */
2557 matrixSVG.prototype.fromSVGMatrix = function(m)
2558 {
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;
2570 }
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)
2585 {
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;
2597 }
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)
2604 {
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;
2626 }
2628 /** Output function
2629 *
2630 * @return a string that can be used as the "transform" attribute.
2631 */
2632 matrixSVG.prototype.toAttribute = function()
2633 {
2634 return "matrix(" + this.e11 + ", " + this.e21 + ", " + this.e12 + ", " + this.e22 + ", " + this.e13 + ", " + this.e23 + ")";
2635 }
2637 /** Matrix nversion.
2638 *
2639 * @return the inverse of the matrix
2640 */
2641 matrixSVG.prototype.inv = function()
2642 {
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;
2658 }
2660 /** Matrix multiplication.
2661 *
2662 * @param op another svg matrix
2663 * @return this * op
2664 */
2665 matrixSVG.prototype.mult = function(op)
2666 {
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;
2680 }
2682 /** Matrix addition.
2683 *
2684 * @param op another svg matrix
2685 * @return this + op
2686 */
2687 matrixSVG.prototype.add = function(op)
2688 {
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;
2702 }
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)
2711 {
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;
2726 }
2728 /** Trimming function for strings.
2729 */
2730 String.prototype.trim = function()
2731 {
2732 return this.replace(/^\s+|\s+$/g, '');
2733 }