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