summaryrefslogtreecommitdiffstats
path: root/share/extensions/jessyInk.js
diff options
context:
space:
mode:
Diffstat (limited to 'share/extensions/jessyInk.js')
-rw-r--r--share/extensions/jessyInk.js2727
1 files changed, 2727 insertions, 0 deletions
diff --git a/share/extensions/jessyInk.js b/share/extensions/jessyInk.js
new file mode 100644
index 0000000..433c1d1
--- /dev/null
+++ b/share/extensions/jessyInk.js
@@ -0,0 +1,2727 @@
+// Copyright 2008, 2009 Hannes Hochreiner
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+
+// Set onload event handler.
+window.onload = jessyInkInit;
+
+// Creating a namespace dictionary. The standard Inkscape namespaces are taken from inkex.py.
+var NSS = new Object();
+NSS['sodipodi']='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd';
+NSS['cc']='http://web.resource.org/cc/';
+NSS['svg']='http://www.w3.org/2000/svg';
+NSS['dc']='http://purl.org/dc/elements/1.1/';
+NSS['rdf']='http://www.w3.org/1999/02/22-rdf-syntax-ns#';
+NSS['inkscape']='http://www.inkscape.org/namespaces/inkscape';
+NSS['xlink']='http://www.w3.org/1999/xlink';
+NSS['xml']='http://www.w3.org/XML/1998/namespace';
+NSS['jessyink']='https://launchpad.net/jessyink';
+
+// Keycodes.
+var LEFT_KEY = 37; // cursor left keycode
+var UP_KEY = 38; // cursor up keycode
+var RIGHT_KEY = 39; // cursor right keycode
+var DOWN_KEY = 40; // cursor down keycode
+var PAGE_UP_KEY = 33; // page up keycode
+var PAGE_DOWN_KEY = 34; // page down keycode
+var HOME_KEY = 36; // home keycode
+var END_KEY = 35; // end keycode
+var ENTER_KEY = 13; // next slide
+var SPACE_KEY = 32;
+var ESCAPE_KEY = 27;
+
+// Presentation modes.
+var SLIDE_MODE = 1;
+var INDEX_MODE = 2;
+var DRAWING_MODE = 3;
+
+// Mouse handler actions.
+var MOUSE_UP = 1;
+var MOUSE_DOWN = 2;
+var MOUSE_MOVE = 3;
+var MOUSE_WHEEL = 4;
+
+// Parameters.
+var ROOT_NODE = document.getElementsByTagNameNS(NSS["svg"], "svg")[0];
+var HEIGHT = 0;
+var WIDTH = 0;
+var INDEX_COLUMNS_DEFAULT = 4;
+var INDEX_COLUMNS = INDEX_COLUMNS_DEFAULT;
+var INDEX_OFFSET = 0;
+var STATE_START = -1;
+var STATE_END = -2;
+var BACKGROUND_COLOR = null;
+var slides = new Array();
+
+// Initialisation.
+var currentMode = SLIDE_MODE;
+var masterSlide = null;
+var activeSlide = 0;
+var activeEffect = 0;
+var timeStep = 30; // 40 ms equal 25 frames per second.
+var lastFrameTime = null;
+var processingEffect = false;
+var transCounter = 0;
+var effectArray = 0;
+var defaultTransitionInDict = new Object();
+defaultTransitionInDict["name"] = "appear";
+var defaultTransitionOutDict = new Object();
+defaultTransitionOutDict["name"] = "appear";
+var jessyInkInitialised = false;
+
+// Initialise char and key code dictionaries.
+var charCodeDictionary = getDefaultCharCodeDictionary();
+var keyCodeDictionary = getDefaultKeyCodeDictionary();
+
+// Initialise mouse handler dictionary.
+var mouseHandlerDictionary = getDefaultMouseHandlerDictionary();
+
+var progress_bar_visible = false;
+var timer_elapsed = 0;
+var timer_start = timer_elapsed;
+var timer_duration = 15; // 15 minutes
+
+var history_counter = 0;
+var history_original_elements = new Array();
+var history_presentation_elements = new Array();
+
+var mouse_original_path = null;
+var mouse_presentation_path = null;
+var mouse_last_x = -1;
+var mouse_last_y = -1;
+var mouse_min_dist_sqr = 3 * 3;
+var path_colour = "red";
+var path_width_default = 3;
+var path_width = path_width_default;
+var path_paint_width = path_width;
+
+var number_of_added_slides = 0;
+
+/** Initialisation function.
+ * The whole presentation is set-up in this function.
+ */
+function jessyInkInit()
+{
+ // Make sure we only execute this code once. Double execution can occur if the onload event handler is set
+ // in the main svg tag as well (as was recommended in earlier versions). Executing this function twice does
+ // not lead to any problems, but it takes more time.
+ if (jessyInkInitialised)
+ return;
+
+ // Making the presentation scalable.
+ var VIEWBOX = ROOT_NODE.getAttribute("viewBox");
+
+ if (VIEWBOX)
+ {
+ WIDTH = ROOT_NODE.viewBox.animVal.width;
+ HEIGHT = ROOT_NODE.viewBox.animVal.height;
+ }
+ else
+ {
+ HEIGHT = parseFloat(ROOT_NODE.getAttribute("height"));
+ WIDTH = parseFloat(ROOT_NODE.getAttribute("width"));
+ ROOT_NODE.setAttribute("viewBox", "0 0 " + WIDTH + " " + HEIGHT);
+ }
+
+ ROOT_NODE.setAttribute("width", "100%");
+ ROOT_NODE.setAttribute("height", "100%");
+
+ // Setting the background color.
+ var namedViews = document.getElementsByTagNameNS(NSS["sodipodi"], "namedview");
+
+ for (var counter = 0; counter < namedViews.length; counter++)
+ {
+ if (namedViews[counter].hasAttribute("id") && namedViews[counter].hasAttribute("pagecolor"))
+ {
+ if (namedViews[counter].getAttribute("id") == "base")
+ {
+ BACKGROUND_COLOR = namedViews[counter].getAttribute("pagecolor");
+ var newAttribute = "background-color:" + BACKGROUND_COLOR + ";";
+
+ if (ROOT_NODE.hasAttribute("style"))
+ newAttribute += ROOT_NODE.getAttribute("style");
+
+ ROOT_NODE.setAttribute("style", newAttribute);
+ }
+ }
+ }
+
+ // Defining clip-path.
+ var defsNodes = document.getElementsByTagNameNS(NSS["svg"], "defs");
+
+ if (defsNodes.length > 0)
+ {
+ var existingClipPath = document.getElementById("jessyInkSlideClipPath");
+
+ if (!existingClipPath)
+ {
+ var rectNode = document.createElementNS(NSS["svg"], "rect");
+ var clipPath = document.createElementNS(NSS["svg"], "clipPath");
+
+ rectNode.setAttribute("x", 0);
+ rectNode.setAttribute("y", 0);
+ rectNode.setAttribute("width", WIDTH);
+ rectNode.setAttribute("height", HEIGHT);
+
+ clipPath.setAttribute("id", "jessyInkSlideClipPath");
+ clipPath.setAttribute("clipPathUnits", "userSpaceOnUse");
+
+ clipPath.appendChild(rectNode);
+ defsNodes[0].appendChild(clipPath);
+ }
+ }
+
+ // Making a list of the slide and finding the master slide.
+ var nodes = document.getElementsByTagNameNS(NSS["svg"], "g");
+ var tempSlides = new Array();
+ var existingJessyInkPresentationLayer = null;
+
+ for (var counter = 0; counter < nodes.length; counter++)
+ {
+ if (nodes[counter].getAttributeNS(NSS["inkscape"], "groupmode") && (nodes[counter].getAttributeNS(NSS["inkscape"], "groupmode") == "layer"))
+ {
+ if (nodes[counter].getAttributeNS(NSS["inkscape"], "label") && nodes[counter].getAttributeNS(NSS["jessyink"], "masterSlide") == "masterSlide")
+ masterSlide = nodes[counter];
+ else if (nodes[counter].getAttributeNS(NSS["inkscape"], "label") && nodes[counter].getAttributeNS(NSS["jessyink"], "presentationLayer") == "presentationLayer")
+ existingJessyInkPresentationLayer = nodes[counter];
+ else
+ tempSlides.push(nodes[counter].getAttribute("id"));
+ }
+ else if (nodes[counter].getAttributeNS(NSS['jessyink'], 'element'))
+ {
+ handleElement(nodes[counter]);
+ }
+ }
+
+ // Hide master slide set default transitions.
+ if (masterSlide)
+ {
+ masterSlide.style.display = "none";
+
+ if (masterSlide.hasAttributeNS(NSS["jessyink"], "transitionIn"))
+ defaultTransitionInDict = propStrToDict(masterSlide.getAttributeNS(NSS["jessyink"], "transitionIn"));
+
+ if (masterSlide.hasAttributeNS(NSS["jessyink"], "transitionOut"))
+ defaultTransitionOutDict = propStrToDict(masterSlide.getAttributeNS(NSS["jessyink"], "transitionOut"));
+ }
+
+ if (existingJessyInkPresentationLayer != null)
+ {
+ existingJessyInkPresentationLayer.parentNode.removeChild(existingJessyInkPresentationLayer);
+ }
+
+ // Set start slide.
+ var hashObj = new LocationHash(window.location.hash);
+
+ activeSlide = hashObj.slideNumber;
+ activeEffect = hashObj.effectNumber;
+
+ if (activeSlide < 0)
+ activeSlide = 0;
+ else if (activeSlide >= tempSlides.length)
+ activeSlide = tempSlides.length - 1;
+
+ var originalNode = document.getElementById(tempSlides[counter]);
+
+ var JessyInkPresentationLayer = document.createElementNS(NSS["svg"], "g");
+ JessyInkPresentationLayer.setAttributeNS(NSS["inkscape"], "groupmode", "layer");
+ JessyInkPresentationLayer.setAttributeNS(NSS["inkscape"], "label", "JessyInk Presentation Layer");
+ JessyInkPresentationLayer.setAttributeNS(NSS["jessyink"], "presentationLayer", "presentationLayer");
+ JessyInkPresentationLayer.setAttribute("id", "jessyink_presentation_layer");
+ JessyInkPresentationLayer.style.display = "inherit";
+ ROOT_NODE.appendChild(JessyInkPresentationLayer);
+
+ // Gathering all the information about the transitions and effects of the slides, set the background
+ // from the master slide and substitute the auto-texts.
+ for (var counter = 0; counter < tempSlides.length; counter++)
+ {
+ var originalNode = document.getElementById(tempSlides[counter]);
+ originalNode.style.display = "none";
+ var node = suffixNodeIds(originalNode.cloneNode(true), "_" + counter);
+ JessyInkPresentationLayer.appendChild(node);
+ slides[counter] = new Object();
+ slides[counter]["original_element"] = originalNode;
+ slides[counter]["element"] = node;
+
+ // Set build in transition.
+ slides[counter]["transitionIn"] = new Object();
+
+ var dict;
+
+ if (node.hasAttributeNS(NSS["jessyink"], "transitionIn"))
+ dict = propStrToDict(node.getAttributeNS(NSS["jessyink"], "transitionIn"));
+ else
+ dict = defaultTransitionInDict;
+
+ slides[counter]["transitionIn"]["name"] = dict["name"];
+ slides[counter]["transitionIn"]["options"] = new Object();
+
+ for (key in dict)
+ if (key != "name")
+ slides[counter]["transitionIn"]["options"][key] = dict[key];
+
+ // Set build out transition.
+ slides[counter]["transitionOut"] = new Object();
+
+ if (node.hasAttributeNS(NSS["jessyink"], "transitionOut"))
+ dict = propStrToDict(node.getAttributeNS(NSS["jessyink"], "transitionOut"));
+ else
+ dict = defaultTransitionOutDict;
+
+ slides[counter]["transitionOut"]["name"] = dict["name"];
+ slides[counter]["transitionOut"]["options"] = new Object();
+
+ for (key in dict)
+ if (key != "name")
+ slides[counter]["transitionOut"]["options"][key] = dict[key];
+
+ // Copy master slide content.
+ if (masterSlide)
+ {
+ var clonedNode = suffixNodeIds(masterSlide.cloneNode(true), "_" + counter);
+ clonedNode.removeAttributeNS(NSS["inkscape"], "groupmode");
+ clonedNode.removeAttributeNS(NSS["inkscape"], "label");
+ clonedNode.style.display = "inherit";
+
+ node.insertBefore(clonedNode, node.firstChild);
+ }
+
+ // Setting clip path.
+ node.setAttribute("clip-path", "url(#jessyInkSlideClipPath)");
+
+ // Substitute auto texts.
+ substituteAutoTexts(node, node.getAttributeNS(NSS["inkscape"], "label"), counter + 1, tempSlides.length);
+
+ node.removeAttributeNS(NSS["inkscape"], "groupmode");
+ node.removeAttributeNS(NSS["inkscape"], "label");
+
+ // Set effects.
+ var tempEffects = new Array();
+ var groups = new Object();
+
+ for (var IOCounter = 0; IOCounter <= 1; IOCounter++)
+ {
+ var propName = "";
+ var dir = 0;
+
+ if (IOCounter == 0)
+ {
+ propName = "effectIn";
+ dir = 1;
+ }
+ else if (IOCounter == 1)
+ {
+ propName = "effectOut";
+ dir = -1;
+ }
+
+ var effects = getElementsByPropertyNS(node, NSS["jessyink"], propName);
+
+ for (var effectCounter = 0; effectCounter < effects.length; effectCounter++)
+ {
+ var element = document.getElementById(effects[effectCounter]);
+ var dict = propStrToDict(element.getAttributeNS(NSS["jessyink"], propName));
+
+ // Put every element that has an effect associated with it, into its own group.
+ // Unless of course, we already put it into its own group.
+ if (!(groups[element.id]))
+ {
+ var newGroup = document.createElementNS(NSS["svg"], "g");
+
+ element.parentNode.insertBefore(newGroup, element);
+ newGroup.appendChild(element.parentNode.removeChild(element));
+ groups[element.id] = newGroup;
+ }
+
+ var effectDict = new Object();
+
+ effectDict["effect"] = dict["name"];
+ effectDict["dir"] = dir;
+ effectDict["element"] = groups[element.id];
+
+ for (var option in dict)
+ {
+ if ((option != "name") && (option != "order"))
+ {
+ if (!effectDict["options"])
+ effectDict["options"] = new Object();
+
+ effectDict["options"][option] = dict[option];
+ }
+ }
+
+ if (!tempEffects[dict["order"]])
+ tempEffects[dict["order"]] = new Array();
+
+ tempEffects[dict["order"]][tempEffects[dict["order"]].length] = effectDict;
+ }
+ }
+
+ // Make invisible, but keep in rendering tree to ensure that bounding box can be calculated.
+ node.setAttribute("opacity",0);
+ node.style.display = "inherit";
+
+ // Create a transform group.
+ var transformGroup = document.createElementNS(NSS["svg"], "g");
+
+ // Add content to transform group.
+ while (node.firstChild)
+ transformGroup.appendChild(node.firstChild);
+
+ // Transfer the transform attribute from the node to the transform group.
+ if (node.getAttribute("transform"))
+ {
+ transformGroup.setAttribute("transform", node.getAttribute("transform"));
+ node.removeAttribute("transform");
+ }
+
+ // Create a view group.
+ var viewGroup = document.createElementNS(NSS["svg"], "g");
+
+ viewGroup.appendChild(transformGroup);
+ slides[counter]["viewGroup"] = node.appendChild(viewGroup);
+
+ // Insert background.
+ if (BACKGROUND_COLOR != null)
+ {
+ var rectNode = document.createElementNS(NSS["svg"], "rect");
+
+ rectNode.setAttribute("x", 0);
+ rectNode.setAttribute("y", 0);
+ rectNode.setAttribute("width", WIDTH);
+ rectNode.setAttribute("height", HEIGHT);
+ rectNode.setAttribute("id", "jessyInkBackground" + counter);
+ rectNode.setAttribute("fill", BACKGROUND_COLOR);
+
+ slides[counter]["viewGroup"].insertBefore(rectNode, slides[counter]["viewGroup"].firstChild);
+ }
+
+ // Set views.
+ var tempViews = new Array();
+ var views = getElementsByPropertyNS(node, NSS["jessyink"], "view");
+ var matrixOld = (new matrixSVG()).fromElements(1, 0, 0, 0, 1, 0, 0, 0, 1);
+
+ // Set initial view even if there are no other views.
+ slides[counter]["viewGroup"].setAttribute("transform", matrixOld.toAttribute());
+ slides[counter].initialView = matrixOld.toAttribute();
+
+ for (var viewCounter = 0; viewCounter < views.length; viewCounter++)
+ {
+ var element = document.getElementById(views[viewCounter]);
+ var dict = propStrToDict(element.getAttributeNS(NSS["jessyink"], "view"));
+
+ if (dict["order"] == 0)
+ {
+ matrixOld = pointMatrixToTransformation(rectToMatrix(element)).mult((new matrixSVG()).fromSVGMatrix(slides[counter].viewGroup.getScreenCTM()).inv().mult((new matrixSVG()).fromSVGMatrix(element.parentNode.getScreenCTM())).inv());
+ slides[counter].initialView = matrixOld.toAttribute();
+ }
+ else
+ {
+ var effectDict = new Object();
+
+ effectDict["effect"] = dict["name"];
+ effectDict["dir"] = 1;
+ effectDict["element"] = slides[counter]["viewGroup"];
+ effectDict["order"] = dict["order"];
+
+ for (var option in dict)
+ {
+ if ((option != "name") && (option != "order"))
+ {
+ if (!effectDict["options"])
+ effectDict["options"] = new Object();
+
+ effectDict["options"][option] = dict[option];
+ }
+ }
+
+ effectDict["options"]["matrixNew"] = pointMatrixToTransformation(rectToMatrix(element)).mult((new matrixSVG()).fromSVGMatrix(slides[counter].viewGroup.getScreenCTM()).inv().mult((new matrixSVG()).fromSVGMatrix(element.parentNode.getScreenCTM())).inv());
+
+ tempViews[dict["order"]] = effectDict;
+ }
+
+ // Remove element.
+ element.parentNode.removeChild(element);
+ }
+
+ // Consolidate view array and append it to the effect array.
+ if (tempViews.length > 0)
+ {
+ for (var viewCounter = 0; viewCounter < tempViews.length; viewCounter++)
+ {
+ if (tempViews[viewCounter])
+ {
+ tempViews[viewCounter]["options"]["matrixOld"] = matrixOld;
+ matrixOld = tempViews[viewCounter]["options"]["matrixNew"];
+
+ if (!tempEffects[tempViews[viewCounter]["order"]])
+ tempEffects[tempViews[viewCounter]["order"]] = new Array();
+
+ tempEffects[tempViews[viewCounter]["order"]][tempEffects[tempViews[viewCounter]["order"]].length] = tempViews[viewCounter];
+ }
+ }
+ }
+
+ // Set consolidated effect array.
+ if (tempEffects.length > 0)
+ {
+ slides[counter]["effects"] = new Array();
+
+ for (var effectCounter = 0; effectCounter < tempEffects.length; effectCounter++)
+ {
+ if (tempEffects[effectCounter])
+ slides[counter]["effects"][slides[counter]["effects"].length] = tempEffects[effectCounter];
+ }
+ }
+
+ node.setAttribute("onmouseover", "if ((currentMode == INDEX_MODE) && ( activeSlide != " + counter + ")) { indexSetActiveSlide(" + counter + "); };");
+
+ // Set visibility for initial state.
+ if (counter == activeSlide)
+ {
+ node.style.display = "inherit";
+ node.setAttribute("opacity",1);
+ }
+ else
+ {
+ node.style.display = "none";
+ node.setAttribute("opacity",0);
+ }
+ }
+
+ // Set key handler.
+ var jessyInkObjects = document.getElementsByTagNameNS(NSS["svg"], "g");
+
+ for (var counter = 0; counter < jessyInkObjects.length; counter++)
+ {
+ var elem = jessyInkObjects[counter];
+
+ if (elem.getAttributeNS(NSS["jessyink"], "customKeyBindings"))
+ {
+ if (elem.getCustomKeyBindings != undefined)
+ keyCodeDictionary = elem.getCustomKeyBindings();
+
+ if (elem.getCustomCharBindings != undefined)
+ charCodeDictionary = elem.getCustomCharBindings();
+ }
+ }
+
+ // Set mouse handler.
+ var jessyInkMouseHandler = document.getElementsByTagNameNS(NSS["jessyink"], "mousehandler");
+
+ for (var counter = 0; counter < jessyInkMouseHandler.length; counter++)
+ {
+ var elem = jessyInkMouseHandler[counter];
+
+ if (elem.getMouseHandler != undefined)
+ {
+ var tempDict = elem.getMouseHandler();
+
+ for (mode in tempDict)
+ {
+ if (!mouseHandlerDictionary[mode])
+ mouseHandlerDictionary[mode] = new Object();
+
+ for (handler in tempDict[mode])
+ mouseHandlerDictionary[mode][handler] = tempDict[mode][handler];
+ }
+ }
+ }
+
+ // Check effect number.
+ if ((activeEffect < 0) || (!slides[activeSlide].effects))
+ {
+ activeEffect = 0;
+ }
+ else if (activeEffect > slides[activeSlide].effects.length)
+ {
+ activeEffect = slides[activeSlide].effects.length;
+ }
+
+ createProgressBar(JessyInkPresentationLayer);
+ hideProgressBar();
+ setProgressBarValue(activeSlide);
+ setTimeIndicatorValue(0);
+ setInterval("updateTimer()", 1000);
+ setSlideToState(activeSlide, activeEffect);
+ jessyInkInitialised = true;
+}
+
+/** Function to substitute the auto-texts.
+ *
+ * @param node the node
+ * @param slideName name of the slide the node is on
+ * @param slideNumber number of the slide the node is on
+ * @param numberOfSlides number of slides in the presentation
+ */
+function substituteAutoTexts(node, slideName, slideNumber, numberOfSlides)
+{
+ var texts = node.getElementsByTagNameNS(NSS["svg"], "tspan");
+
+ for (var textCounter = 0; textCounter < texts.length; textCounter++)
+ {
+ if (texts[textCounter].getAttributeNS(NSS["jessyink"], "autoText") == "slideNumber")
+ texts[textCounter].firstChild.nodeValue = slideNumber;
+ else if (texts[textCounter].getAttributeNS(NSS["jessyink"], "autoText") == "numberOfSlides")
+ texts[textCounter].firstChild.nodeValue = numberOfSlides;
+ else if (texts[textCounter].getAttributeNS(NSS["jessyink"], "autoText") == "slideTitle")
+ texts[textCounter].firstChild.nodeValue = slideName;
+ }
+}
+
+/** Convenience function to get an element depending on whether it has a property with a particular name.
+ * This function emulates some dearly missed XPath functionality.
+ *
+ * @param node the node
+ * @param namespace namespace of the attribute
+ * @param name attribute name
+ */
+function getElementsByPropertyNS(node, namespace, name)
+{
+ var elems = new Array();
+
+ if (node.getAttributeNS(namespace, name))
+ elems.push(node.getAttribute("id"));
+
+ for (var counter = 0; counter < node.childNodes.length; counter++)
+ {
+ if (node.childNodes[counter].nodeType == 1)
+ elems = elems.concat(getElementsByPropertyNS(node.childNodes[counter], namespace, name));
+ }
+
+ return elems;
+}
+
+/** Function to dispatch the next effect, if there is none left, change the slide.
+ *
+ * @param dir direction of the change (1 = forwards, -1 = backwards)
+ */
+function dispatchEffects(dir)
+{
+ if (slides[activeSlide]["effects"] && (((dir == 1) && (activeEffect < slides[activeSlide]["effects"].length)) || ((dir == -1) && (activeEffect > 0))))
+ {
+ processingEffect = true;
+
+ if (dir == 1)
+ {
+ effectArray = slides[activeSlide]["effects"][activeEffect];
+ activeEffect += dir;
+ }
+ else if (dir == -1)
+ {
+ activeEffect += dir;
+ effectArray = slides[activeSlide]["effects"][activeEffect];
+ }
+
+ transCounter = 0;
+ startTime = (new Date()).getTime();
+ lastFrameTime = null;
+ effect(dir);
+ }
+ else if (((dir == 1) && (activeSlide < (slides.length - 1))) || (((dir == -1) && (activeSlide > 0))))
+ {
+ changeSlide(dir);
+ }
+}
+
+/** Function to skip effects and directly either put the slide into start or end state or change slides.
+ *
+ * @param dir direction of the change (1 = forwards, -1 = backwards)
+ */
+function skipEffects(dir)
+{
+ if (slides[activeSlide]["effects"] && (((dir == 1) && (activeEffect < slides[activeSlide]["effects"].length)) || ((dir == -1) && (activeEffect > 0))))
+ {
+ processingEffect = true;
+
+ if (slides[activeSlide]["effects"] && (dir == 1))
+ activeEffect = slides[activeSlide]["effects"].length;
+ else
+ activeEffect = 0;
+
+ if (dir == 1)
+ setSlideToState(activeSlide, STATE_END);
+ else
+ setSlideToState(activeSlide, STATE_START);
+
+ processingEffect = false;
+ }
+ else if (((dir == 1) && (activeSlide < (slides.length - 1))) || (((dir == -1) && (activeSlide > 0))))
+ {
+ changeSlide(dir);
+ }
+}
+
+/** Function to change between slides.
+ *
+ * @param dir direction (1 = forwards, -1 = backwards)
+ */
+function changeSlide(dir)
+{
+ processingEffect = true;
+ effectArray = new Array();
+
+ effectArray[0] = new Object();
+ if (dir == 1)
+ {
+ effectArray[0]["effect"] = slides[activeSlide]["transitionOut"]["name"];
+ effectArray[0]["options"] = slides[activeSlide]["transitionOut"]["options"];
+ effectArray[0]["dir"] = -1;
+ }
+ else if (dir == -1)
+ {
+ effectArray[0]["effect"] = slides[activeSlide]["transitionIn"]["name"];
+ effectArray[0]["options"] = slides[activeSlide]["transitionIn"]["options"];
+ effectArray[0]["dir"] = 1;
+ }
+ effectArray[0]["element"] = slides[activeSlide]["element"];
+
+ activeSlide += dir;
+ setProgressBarValue(activeSlide);
+
+ effectArray[1] = new Object();
+
+ if (dir == 1)
+ {
+ effectArray[1]["effect"] = slides[activeSlide]["transitionIn"]["name"];
+ effectArray[1]["options"] = slides[activeSlide]["transitionIn"]["options"];
+ effectArray[1]["dir"] = 1;
+ }
+ else if (dir == -1)
+ {
+ effectArray[1]["effect"] = slides[activeSlide]["transitionOut"]["name"];
+ effectArray[1]["options"] = slides[activeSlide]["transitionOut"]["options"];
+ effectArray[1]["dir"] = -1;
+ }
+
+ effectArray[1]["element"] = slides[activeSlide]["element"];
+
+ if (slides[activeSlide]["effects"] && (dir == -1))
+ activeEffect = slides[activeSlide]["effects"].length;
+ else
+ activeEffect = 0;
+
+ if (dir == -1)
+ setSlideToState(activeSlide, STATE_END);
+ else
+ setSlideToState(activeSlide, STATE_START);
+
+ transCounter = 0;
+ startTime = (new Date()).getTime();
+ lastFrameTime = null;
+ effect(dir);
+}
+
+/** Function to toggle between index and slide mode.
+*/
+function toggleSlideIndex()
+{
+ var suspendHandle = ROOT_NODE.suspendRedraw(500);
+
+ if (currentMode == SLIDE_MODE)
+ {
+ hideProgressBar();
+ INDEX_OFFSET = -1;
+ indexSetPageSlide(activeSlide);
+ currentMode = INDEX_MODE;
+ }
+ else if (currentMode == INDEX_MODE)
+ {
+ for (var counter = 0; counter < slides.length; counter++)
+ {
+ slides[counter]["element"].setAttribute("transform","scale(1)");
+
+ if (counter == activeSlide)
+ {
+ slides[counter]["element"].style.display = "inherit";
+ slides[counter]["element"].setAttribute("opacity",1);
+ activeEffect = 0;
+ }
+ else
+ {
+ slides[counter]["element"].setAttribute("opacity",0);
+ slides[counter]["element"].style.display = "none";
+ }
+ }
+ currentMode = SLIDE_MODE;
+ setSlideToState(activeSlide, STATE_START);
+ setProgressBarValue(activeSlide);
+
+ if (progress_bar_visible)
+ {
+ showProgressBar();
+ }
+ }
+
+ ROOT_NODE.unsuspendRedraw(suspendHandle);
+ ROOT_NODE.forceRedraw();
+}
+
+/** Function to run an effect.
+ *
+ * @param dir direction in which to play the effect (1 = forwards, -1 = backwards)
+ */
+function effect(dir)
+{
+ var done = true;
+
+ var suspendHandle = ROOT_NODE.suspendRedraw(200);
+
+ for (var counter = 0; counter < effectArray.length; counter++)
+ {
+ if (effectArray[counter]["effect"] == "fade")
+ done &= fade(parseInt(effectArray[counter]["dir"]) * dir, effectArray[counter]["element"], transCounter, effectArray[counter]["options"]);
+ else if (effectArray[counter]["effect"] == "appear")
+ done &= appear(parseInt(effectArray[counter]["dir"]) * dir, effectArray[counter]["element"], transCounter, effectArray[counter]["options"]);
+ else if (effectArray[counter]["effect"] == "pop")
+ done &= pop(parseInt(effectArray[counter]["dir"]) * dir, effectArray[counter]["element"], transCounter, effectArray[counter]["options"]);
+ else if (effectArray[counter]["effect"] == "view")
+ done &= view(parseInt(effectArray[counter]["dir"]) * dir, effectArray[counter]["element"], transCounter, effectArray[counter]["options"]);
+ }
+
+ ROOT_NODE.unsuspendRedraw(suspendHandle);
+ ROOT_NODE.forceRedraw();
+
+ if (!done)
+ {
+ var currentTime = (new Date()).getTime();
+ var timeDiff = 1;
+
+ transCounter = currentTime - startTime;
+
+ if (lastFrameTime != null)
+ {
+ timeDiff = timeStep - (currentTime - lastFrameTime);
+
+ if (timeDiff <= 0)
+ timeDiff = 1;
+ }
+
+ lastFrameTime = currentTime;
+
+ window.setTimeout("effect(" + dir + ")", timeDiff);
+ }
+ else
+ {
+ window.location.hash = (activeSlide + 1) + '_' + activeEffect;
+ processingEffect = false;
+ }
+}
+
+/** Function to display the index sheet.
+ *
+ * @param offsetNumber offset number
+ */
+function displayIndex(offsetNumber)
+{
+ var offsetX = 0;
+ var offsetY = 0;
+
+ if (offsetNumber < 0)
+ offsetNumber = 0;
+ else if (offsetNumber >= slides.length)
+ offsetNumber = slides.length - 1;
+
+ for (var counter = 0; counter < slides.length; counter++)
+ {
+ if ((counter < offsetNumber) || (counter > offsetNumber + INDEX_COLUMNS * INDEX_COLUMNS - 1))
+ {
+ slides[counter]["element"].setAttribute("opacity",0);
+ slides[counter]["element"].style.display = "none";
+ }
+ else
+ {
+ offsetX = ((counter - offsetNumber) % INDEX_COLUMNS) * WIDTH;
+ offsetY = Math.floor((counter - offsetNumber) / INDEX_COLUMNS) * HEIGHT;
+
+ slides[counter]["element"].setAttribute("transform","scale("+1/INDEX_COLUMNS+") translate("+offsetX+","+offsetY+")");
+ slides[counter]["element"].style.display = "inherit";
+ slides[counter]["element"].setAttribute("opacity",0.5);
+ }
+
+ setSlideToState(counter, STATE_END);
+ }
+
+ //do we need to save the current offset?
+ if (INDEX_OFFSET != offsetNumber)
+ INDEX_OFFSET = offsetNumber;
+}
+
+/** Function to set the active slide in the slide view.
+ *
+ * @param nbr index of the active slide
+ */
+function slideSetActiveSlide(nbr)
+{
+ if (nbr >= slides.length)
+ nbr = slides.length - 1;
+ else if (nbr < 0)
+ nbr = 0;
+
+ slides[activeSlide]["element"].setAttribute("opacity",0);
+ slides[activeSlide]["element"].style.display = "none";
+
+ activeSlide = parseInt(nbr);
+
+ setSlideToState(activeSlide, STATE_START);
+ slides[activeSlide]["element"].style.display = "inherit";
+ slides[activeSlide]["element"].setAttribute("opacity",1);
+
+ activeEffect = 0;
+ setProgressBarValue(nbr);
+}
+
+/** Function to set the active slide in the index view.
+ *
+ * @param nbr index of the active slide
+ */
+function indexSetActiveSlide(nbr)
+{
+ if (nbr >= slides.length)
+ nbr = slides.length - 1;
+ else if (nbr < 0)
+ nbr = 0;
+
+ slides[activeSlide]["element"].setAttribute("opacity",0.5);
+
+ activeSlide = parseInt(nbr);
+ window.location.hash = (activeSlide + 1) + '_0';
+
+ slides[activeSlide]["element"].setAttribute("opacity",1);
+}
+
+/** Function to set the page and active slide in index view.
+ *
+ * @param nbr index of the active slide
+ *
+ * NOTE: To force a redraw,
+ * set INDEX_OFFSET to -1 before calling indexSetPageSlide().
+ *
+ * This is necessary for zooming (otherwise the index might not
+ * get redrawn) and when switching to index mode.
+ *
+ * INDEX_OFFSET = -1
+ * indexSetPageSlide(activeSlide);
+ */
+function indexSetPageSlide(nbr)
+{
+ if (nbr >= slides.length)
+ nbr = slides.length - 1;
+ else if (nbr < 0)
+ nbr = 0;
+
+ //calculate the offset
+ var offset = nbr - nbr % (INDEX_COLUMNS * INDEX_COLUMNS);
+
+ if (offset < 0)
+ offset = 0;
+
+ //if different from kept offset, then record and change the page
+ if (offset != INDEX_OFFSET)
+ {
+ INDEX_OFFSET = offset;
+ displayIndex(INDEX_OFFSET);
+ }
+
+ //set the active slide
+ indexSetActiveSlide(nbr);
+}
+
+/** Event handler for key press.
+ *
+ * @param e the event
+ */
+function keydown(e)
+{
+ if (!e)
+ e = window.event;
+
+ code = e.keyCode || e.charCode;
+
+ if (!processingEffect && keyCodeDictionary[currentMode] && keyCodeDictionary[currentMode][code])
+ return keyCodeDictionary[currentMode][code]();
+ else
+ document.onkeypress = keypress;
+}
+// Set event handler for key down.
+document.onkeydown = keydown;
+
+/** Event handler for key press.
+ *
+ * @param e the event
+ */
+function keypress(e)
+{
+ document.onkeypress = null;
+
+ if (!e)
+ e = window.event;
+
+ str = String.fromCharCode(e.keyCode || e.charCode);
+
+ if (!processingEffect && charCodeDictionary[currentMode] && charCodeDictionary[currentMode][str])
+ return charCodeDictionary[currentMode][str]();
+}
+
+/** Function to supply the default char code dictionary.
+ *
+ * @returns default char code dictionary
+ */
+function getDefaultCharCodeDictionary()
+{
+ var charCodeDict = new Object();
+
+ charCodeDict[SLIDE_MODE] = new Object();
+ charCodeDict[INDEX_MODE] = new Object();
+ charCodeDict[DRAWING_MODE] = new Object();
+
+ charCodeDict[SLIDE_MODE]["i"] = function () { return toggleSlideIndex(); };
+ charCodeDict[SLIDE_MODE]["d"] = function () { return slideSwitchToDrawingMode(); };
+ charCodeDict[SLIDE_MODE]["D"] = function () { return slideQueryDuration(); };
+ charCodeDict[SLIDE_MODE]["n"] = function () { return slideAddSlide(activeSlide); };
+ charCodeDict[SLIDE_MODE]["p"] = function () { return slideToggleProgressBarVisibility(); };
+ charCodeDict[SLIDE_MODE]["t"] = function () { return slideResetTimer(); };
+ charCodeDict[SLIDE_MODE]["e"] = function () { return slideUpdateExportLayer(); };
+
+ charCodeDict[DRAWING_MODE]["d"] = function () { return drawingSwitchToSlideMode(); };
+ charCodeDict[DRAWING_MODE]["0"] = function () { return drawingResetPathWidth(); };
+ charCodeDict[DRAWING_MODE]["1"] = function () { return drawingSetPathWidth(1.0); };
+ charCodeDict[DRAWING_MODE]["3"] = function () { return drawingSetPathWidth(3.0); };
+ charCodeDict[DRAWING_MODE]["5"] = function () { return drawingSetPathWidth(5.0); };
+ charCodeDict[DRAWING_MODE]["7"] = function () { return drawingSetPathWidth(7.0); };
+ charCodeDict[DRAWING_MODE]["9"] = function () { return drawingSetPathWidth(9.0); };
+ charCodeDict[DRAWING_MODE]["b"] = function () { return drawingSetPathColour("blue"); };
+ charCodeDict[DRAWING_MODE]["c"] = function () { return drawingSetPathColour("cyan"); };
+ charCodeDict[DRAWING_MODE]["g"] = function () { return drawingSetPathColour("green"); };
+ charCodeDict[DRAWING_MODE]["k"] = function () { return drawingSetPathColour("black"); };
+ charCodeDict[DRAWING_MODE]["m"] = function () { return drawingSetPathColour("magenta"); };
+ charCodeDict[DRAWING_MODE]["o"] = function () { return drawingSetPathColour("orange"); };
+ charCodeDict[DRAWING_MODE]["r"] = function () { return drawingSetPathColour("red"); };
+ charCodeDict[DRAWING_MODE]["w"] = function () { return drawingSetPathColour("white"); };
+ charCodeDict[DRAWING_MODE]["y"] = function () { return drawingSetPathColour("yellow"); };
+ charCodeDict[DRAWING_MODE]["z"] = function () { return drawingUndo(); };
+
+ charCodeDict[INDEX_MODE]["i"] = function () { return toggleSlideIndex(); };
+ charCodeDict[INDEX_MODE]["-"] = function () { return indexDecreaseNumberOfColumns(); };
+ charCodeDict[INDEX_MODE]["="] = function () { return indexIncreaseNumberOfColumns(); };
+ charCodeDict[INDEX_MODE]["+"] = function () { return indexIncreaseNumberOfColumns(); };
+ charCodeDict[INDEX_MODE]["0"] = function () { return indexResetNumberOfColumns(); };
+
+ return charCodeDict;
+}
+
+/** Function to supply the default key code dictionary.
+ *
+ * @returns default key code dictionary
+ */
+function getDefaultKeyCodeDictionary()
+{
+ var keyCodeDict = new Object();
+
+ keyCodeDict[SLIDE_MODE] = new Object();
+ keyCodeDict[INDEX_MODE] = new Object();
+ keyCodeDict[DRAWING_MODE] = new Object();
+
+ keyCodeDict[SLIDE_MODE][LEFT_KEY] = function() { return dispatchEffects(-1); };
+ keyCodeDict[SLIDE_MODE][RIGHT_KEY] = function() { return dispatchEffects(1); };
+ keyCodeDict[SLIDE_MODE][UP_KEY] = function() { return skipEffects(-1); };
+ keyCodeDict[SLIDE_MODE][DOWN_KEY] = function() { return skipEffects(1); };
+ keyCodeDict[SLIDE_MODE][PAGE_UP_KEY] = function() { return dispatchEffects(-1); };
+ keyCodeDict[SLIDE_MODE][PAGE_DOWN_KEY] = function() { return dispatchEffects(1); };
+ keyCodeDict[SLIDE_MODE][HOME_KEY] = function() { return slideSetActiveSlide(0); };
+ keyCodeDict[SLIDE_MODE][END_KEY] = function() { return slideSetActiveSlide(slides.length - 1); };
+ keyCodeDict[SLIDE_MODE][SPACE_KEY] = function() { return dispatchEffects(1); };
+
+ keyCodeDict[INDEX_MODE][LEFT_KEY] = function() { return indexSetPageSlide(activeSlide - 1); };
+ keyCodeDict[INDEX_MODE][RIGHT_KEY] = function() { return indexSetPageSlide(activeSlide + 1); };
+ keyCodeDict[INDEX_MODE][UP_KEY] = function() { return indexSetPageSlide(activeSlide - INDEX_COLUMNS); };
+ keyCodeDict[INDEX_MODE][DOWN_KEY] = function() { return indexSetPageSlide(activeSlide + INDEX_COLUMNS); };
+ keyCodeDict[INDEX_MODE][PAGE_UP_KEY] = function() { return indexSetPageSlide(activeSlide - INDEX_COLUMNS * INDEX_COLUMNS); };
+ keyCodeDict[INDEX_MODE][PAGE_DOWN_KEY] = function() { return indexSetPageSlide(activeSlide + INDEX_COLUMNS * INDEX_COLUMNS); };
+ keyCodeDict[INDEX_MODE][HOME_KEY] = function() { return indexSetPageSlide(0); };
+ keyCodeDict[INDEX_MODE][END_KEY] = function() { return indexSetPageSlide(slides.length - 1); };
+ keyCodeDict[INDEX_MODE][ENTER_KEY] = function() { return toggleSlideIndex(); };
+
+ keyCodeDict[DRAWING_MODE][ESCAPE_KEY] = function () { return drawingSwitchToSlideMode(); };
+
+ return keyCodeDict;
+}
+
+/** Function to handle all mouse events.
+ *
+ * @param evnt event
+ * @param action type of event (e.g. mouse up, mouse wheel)
+ */
+function mouseHandlerDispatch(evnt, action)
+{
+ if (!evnt)
+ evnt = window.event;
+
+ var retVal = true;
+
+ if (!processingEffect && mouseHandlerDictionary[currentMode] && mouseHandlerDictionary[currentMode][action])
+ {
+ var subRetVal = mouseHandlerDictionary[currentMode][action](evnt);
+
+ if (subRetVal != null && subRetVal != undefined)
+ retVal = subRetVal;
+ }
+
+ if (evnt.preventDefault && !retVal)
+ evnt.preventDefault();
+
+ evnt.returnValue = retVal;
+
+ return retVal;
+}
+
+// Set mouse event handler.
+document.onmousedown = function(e) { return mouseHandlerDispatch(e, MOUSE_DOWN); };
+document.onmouseup = function(e) { return mouseHandlerDispatch(e, MOUSE_UP); };
+document.onmousemove = function(e) { return mouseHandlerDispatch(e, MOUSE_MOVE); };
+
+// Moz
+if (window.addEventListener)
+{
+ window.addEventListener('DOMMouseScroll', function(e) { return mouseHandlerDispatch(e, MOUSE_WHEEL); }, false);
+}
+
+// Opera Safari OK - may not work in IE
+window.onmousewheel = function(e) { return mouseHandlerDispatch(e, MOUSE_WHEEL); };
+
+/** Function to supply the default mouse handler dictionary.
+ *
+ * @returns default mouse handler dictionary
+ */
+function getDefaultMouseHandlerDictionary()
+{
+ var mouseHandlerDict = new Object();
+
+ mouseHandlerDict[SLIDE_MODE] = new Object();
+ mouseHandlerDict[INDEX_MODE] = new Object();
+ mouseHandlerDict[DRAWING_MODE] = new Object();
+
+ mouseHandlerDict[SLIDE_MODE][MOUSE_DOWN] = function(evnt) { return dispatchEffects(1); };
+ mouseHandlerDict[SLIDE_MODE][MOUSE_WHEEL] = function(evnt) { return slideMousewheel(evnt); };
+
+ mouseHandlerDict[INDEX_MODE][MOUSE_DOWN] = function(evnt) { return toggleSlideIndex(); };
+
+ mouseHandlerDict[DRAWING_MODE][MOUSE_DOWN] = function(evnt) { return drawingMousedown(evnt); };
+ mouseHandlerDict[DRAWING_MODE][MOUSE_UP] = function(evnt) { return drawingMouseup(evnt); };
+ mouseHandlerDict[DRAWING_MODE][MOUSE_MOVE] = function(evnt) { return drawingMousemove(evnt); };
+
+ return mouseHandlerDict;
+}
+
+/** Function to switch from slide mode to drawing mode.
+*/
+function slideSwitchToDrawingMode()
+{
+ currentMode = DRAWING_MODE;
+
+ var tempDict;
+
+ if (ROOT_NODE.hasAttribute("style"))
+ tempDict = propStrToDict(ROOT_NODE.getAttribute("style"));
+ else
+ tempDict = new Object();
+
+ tempDict["cursor"] = "crosshair";
+ ROOT_NODE.setAttribute("style", dictToPropStr(tempDict));
+}
+
+/** Function to switch from drawing mode to slide mode.
+*/
+function drawingSwitchToSlideMode()
+{
+ currentMode = SLIDE_MODE;
+
+ var tempDict;
+
+ if (ROOT_NODE.hasAttribute("style"))
+ tempDict = propStrToDict(ROOT_NODE.getAttribute("style"));
+ else
+ tempDict = new Object();
+
+ tempDict["cursor"] = "auto";
+ ROOT_NODE.setAttribute("style", dictToPropStr(tempDict));
+}
+
+/** Function to decrease the number of columns in index mode.
+*/
+function indexDecreaseNumberOfColumns()
+{
+ if (INDEX_COLUMNS >= 3)
+ {
+ INDEX_COLUMNS -= 1;
+ INDEX_OFFSET = -1
+ indexSetPageSlide(activeSlide);
+ }
+}
+
+/** Function to increase the number of columns in index mode.
+*/
+function indexIncreaseNumberOfColumns()
+{
+ if (INDEX_COLUMNS < 7)
+ {
+ INDEX_COLUMNS += 1;
+ INDEX_OFFSET = -1
+ indexSetPageSlide(activeSlide);
+ }
+}
+
+/** Function to reset the number of columns in index mode.
+*/
+function indexResetNumberOfColumns()
+{
+ if (INDEX_COLUMNS != INDEX_COLUMNS_DEFAULT)
+ {
+ INDEX_COLUMNS = INDEX_COLUMNS_DEFAULT;
+ INDEX_OFFSET = -1
+ indexSetPageSlide(activeSlide);
+ }
+}
+
+/** Function to reset path width in drawing mode.
+*/
+function drawingResetPathWidth()
+{
+ path_width = path_width_default;
+ set_path_paint_width();
+}
+
+/** Function to set path width in drawing mode.
+ *
+ * @param width new path width
+ */
+function drawingSetPathWidth(width)
+{
+ path_width = width;
+ set_path_paint_width();
+}
+
+/** Function to set path colour in drawing mode.
+ *
+ * @param colour new path colour
+ */
+function drawingSetPathColour(colour)
+{
+ path_colour = colour;
+}
+
+/** Function to query the duration of the presentation from the user in slide mode.
+*/
+function slideQueryDuration()
+{
+ var new_duration = prompt("Length of presentation in minutes?", timer_duration);
+
+ if ((new_duration != null) && (new_duration != ''))
+ {
+ timer_duration = new_duration;
+ }
+
+ updateTimer();
+}
+
+/** Function to add new slide in slide mode.
+ *
+ * @param afterSlide after which slide to insert the new one
+ */
+function slideAddSlide(afterSlide)
+{
+ addSlide(afterSlide);
+ slideSetActiveSlide(afterSlide + 1);
+ updateTimer();
+}
+
+/** Function to toggle the visibility of the progress bar in slide mode.
+*/
+function slideToggleProgressBarVisibility()
+{
+ if (progress_bar_visible)
+ {
+ progress_bar_visible = false;
+ hideProgressBar();
+ }
+ else
+ {
+ progress_bar_visible = true;
+ showProgressBar();
+ }
+}
+
+/** Function to reset the timer in slide mode.
+*/
+function slideResetTimer()
+{
+ timer_start = timer_elapsed;
+ updateTimer();
+}
+
+/** Convenience function to pad a string with zero in front up to a certain length.
+ */
+function padString(str, len)
+{
+ var outStr = str;
+
+ while (outStr.length < len)
+ {
+ outStr = '0' + outStr;
+ }
+
+ return outStr;
+}
+
+/** Function to update the export layer.
+ */
+function slideUpdateExportLayer()
+{
+ // Suspend redraw since we are going to mess with the slides.
+ var suspendHandle = ROOT_NODE.suspendRedraw(2000);
+
+ var tmpActiveSlide = activeSlide;
+ var tmpActiveEffect = activeEffect;
+ var exportedLayers = new Array();
+
+ for (var counterSlides = 0; counterSlides < slides.length; counterSlides++)
+ {
+ var exportNode;
+
+ setSlideToState(counterSlides, STATE_START);
+
+ var maxEffect = 0;
+
+ if (slides[counterSlides].effects)
+ {
+ maxEffect = slides[counterSlides].effects.length;
+ }
+
+ exportNode = slides[counterSlides].element.cloneNode(true);
+ exportNode.setAttributeNS(NSS["inkscape"], "groupmode", "layer");
+ exportNode.setAttributeNS(NSS["inkscape"], "label", "slide_" + padString((counterSlides + 1).toString(), slides.length.toString().length) + "_effect_" + padString("0", maxEffect.toString().length));
+
+ exportedLayers.push(exportNode);
+
+ if (slides[counterSlides]["effects"])
+ {
+ for (var counter = 0; counter < slides[counterSlides]["effects"].length; counter++)
+ {
+ for (var subCounter = 0; subCounter < slides[counterSlides]["effects"][counter].length; subCounter++)
+ {
+ var effect = slides[counterSlides]["effects"][counter][subCounter];
+ if (effect["effect"] == "fade")
+ fade(parseInt(effect["dir"]), effect["element"], STATE_END, effect["options"]);
+ else if (effect["effect"] == "appear")
+ appear(parseInt(effect["dir"]), effect["element"], STATE_END, effect["options"]);
+ else if (effect["effect"] == "pop")
+ pop(parseInt(effect["dir"]), effect["element"], STATE_END, effect["options"]);
+ else if (effect["effect"] == "view")
+ view(parseInt(effect["dir"]), effect["element"], STATE_END, effect["options"]);
+ }
+
+ var layerName = "slide_" + padString((counterSlides + 1).toString(), slides.length.toString().length) + "_effect_" + padString((counter + 1).toString(), maxEffect.toString().length);
+ exportNode = slides[counterSlides].element.cloneNode(true);
+ exportNode.setAttributeNS(NSS["inkscape"], "groupmode", "layer");
+ exportNode.setAttributeNS(NSS["inkscape"], "label", layerName);
+ exportNode.setAttribute("id", layerName);
+
+ exportedLayers.push(exportNode);
+ }
+ }
+ }
+
+ activeSlide = tmpActiveSlide;
+ activeEffect = tmpActiveEffect;
+ setSlideToState(activeSlide, activeEffect);
+
+ // Copy image.
+ var newDoc = document.documentElement.cloneNode(true);
+
+ // Delete viewbox form new imag and set width and height.
+ newDoc.removeAttribute('viewbox');
+ newDoc.setAttribute('width', WIDTH);
+ newDoc.setAttribute('height', HEIGHT);
+
+ // Delete all layers and script elements.
+ var nodesToBeRemoved = new Array();
+
+ for (var childCounter = 0; childCounter < newDoc.childNodes.length; childCounter++)
+ {
+ var child = newDoc.childNodes[childCounter];
+
+ if (child.nodeType == 1)
+ {
+ if ((child.nodeName.toUpperCase() == 'G') || (child.nodeName.toUpperCase() == 'SCRIPT'))
+ {
+ nodesToBeRemoved.push(child);
+ }
+ }
+ }
+
+ for (var ndCounter = 0; ndCounter < nodesToBeRemoved.length; ndCounter++)
+ {
+ var nd = nodesToBeRemoved[ndCounter];
+
+ // Before removing the node, check whether it contains any definitions.
+ var defs = nd.getElementsByTagNameNS(NSS["svg"], "defs");
+
+ for (var defsCounter = 0; defsCounter < defs.length; defsCounter++)
+ {
+ if (defs[defsCounter].id)
+ {
+ newDoc.appendChild(defs[defsCounter].cloneNode(true));
+ }
+ }
+
+ // Remove node.
+ nd.parentNode.removeChild(nd);
+ }
+
+ // Set current layer.
+ if (exportedLayers[0])
+ {
+ var namedView;
+
+ for (var nodeCounter = 0; nodeCounter < newDoc.childNodes.length; nodeCounter++)
+ {
+ if ((newDoc.childNodes[nodeCounter].nodeType == 1) && (newDoc.childNodes[nodeCounter].getAttribute('id') == 'base'))
+ {
+ namedView = newDoc.childNodes[nodeCounter];
+ }
+ }
+
+ if (namedView)
+ {
+ namedView.setAttributeNS(NSS['inkscape'], 'current-layer', exportedLayers[0].getAttributeNS(NSS['inkscape'], 'label'));
+ }
+ }
+
+ // Add exported layers.
+ while (exportedLayers.length > 0)
+ {
+ var nd = exportedLayers.pop();
+
+ nd.setAttribute("opacity",1);
+ nd.style.display = "inherit";
+
+ newDoc.appendChild(nd);
+ }
+
+ // Serialise the new document.
+ window.location = 'data:application/svg+xml;base64;charset=utf-8,' + window.btoa(unescape(encodeURIComponent((new XMLSerializer()).serializeToString(newDoc))));
+
+ // Unsuspend redraw.
+ ROOT_NODE.unsuspendRedraw(suspendHandle);
+ ROOT_NODE.forceRedraw();
+}
+
+/** Function to undo last drawing operation.
+*/
+function drawingUndo()
+{
+ mouse_presentation_path = null;
+ mouse_original_path = null;
+
+ if (history_presentation_elements.length > 0)
+ {
+ var p = history_presentation_elements.pop();
+ var parent = p.parentNode.removeChild(p);
+
+ p = history_original_elements.pop();
+ parent = p.parentNode.removeChild(p);
+ }
+}
+
+/** Event handler for mouse down in drawing mode.
+ *
+ * @param e the event
+ */
+function drawingMousedown(e)
+{
+ var value = 0;
+
+ if (e.button)
+ value = e.button;
+ else if (e.which)
+ value = e.which;
+
+ if (value == 1)
+ {
+ history_counter++;
+
+ var p = calcCoord(e);
+
+ mouse_last_x = e.clientX;
+ mouse_last_y = e.clientY;
+ mouse_original_path = document.createElementNS(NSS["svg"], "path");
+ mouse_original_path.setAttribute("stroke", path_colour);
+ mouse_original_path.setAttribute("stroke-width", path_paint_width);
+ mouse_original_path.setAttribute("fill", "none");
+ mouse_original_path.setAttribute("id", "path " + Date());
+ mouse_original_path.setAttribute("d", "M" + p.x + "," + p.y);
+ slides[activeSlide]["original_element"].appendChild(mouse_original_path);
+ history_original_elements.push(mouse_original_path);
+
+ mouse_presentation_path = document.createElementNS(NSS["svg"], "path");
+ mouse_presentation_path.setAttribute("stroke", path_colour);
+ mouse_presentation_path.setAttribute("stroke-width", path_paint_width);
+ mouse_presentation_path.setAttribute("fill", "none");
+ mouse_presentation_path.setAttribute("id", "path " + Date() + " presentation copy");
+ mouse_presentation_path.setAttribute("d", "M" + p.x + "," + p.y);
+
+ if (slides[activeSlide]["viewGroup"])
+ slides[activeSlide]["viewGroup"].appendChild(mouse_presentation_path);
+ else
+ slides[activeSlide]["element"].appendChild(mouse_presentation_path);
+
+ history_presentation_elements.push(mouse_presentation_path);
+
+ return false;
+ }
+
+ return true;
+}
+
+/** Event handler for mouse up in drawing mode.
+ *
+ * @param e the event
+ */
+function drawingMouseup(e)
+{
+ if(!e)
+ e = window.event;
+
+ if (mouse_presentation_path != null)
+ {
+ var p = calcCoord(e);
+ var d = mouse_presentation_path.getAttribute("d");
+ d += " L" + p.x + "," + p.y;
+ mouse_presentation_path.setAttribute("d", d);
+ mouse_presentation_path = null;
+ mouse_original_path.setAttribute("d", d);
+ mouse_original_path = null;
+
+ return false;
+ }
+
+ return true;
+}
+
+/** Event handler for mouse move in drawing mode.
+ *
+ * @param e the event
+ */
+function drawingMousemove(e)
+{
+ if(!e)
+ e = window.event;
+
+ var dist = (mouse_last_x - e.clientX) * (mouse_last_x - e.clientX) + (mouse_last_y - e.clientY) * (mouse_last_y - e.clientY);
+
+ if (mouse_presentation_path == null)
+ {
+ return true;
+ }
+
+ if (dist >= mouse_min_dist_sqr)
+ {
+ var p = calcCoord(e);
+ var d = mouse_presentation_path.getAttribute("d");
+ d += " L" + p.x + "," + p.y;
+ mouse_presentation_path.setAttribute("d", d);
+ mouse_original_path.setAttribute("d", d);
+ mouse_last_x = e.clientX;
+ mouse_last_y = e.clientY;
+ }
+
+ return false;
+}
+
+/** Event handler for mouse wheel events in slide mode.
+ * based on http://adomas.org/javascript-mouse-wheel/
+ *
+ * @param e the event
+ */
+function slideMousewheel(e)
+{
+ var delta = 0;
+
+ if (!e)
+ e = window.event;
+
+ if (e.wheelDelta)
+ { // IE Opera
+ delta = e.wheelDelta/120;
+ }
+ else if (e.detail)
+ { // MOZ
+ delta = -e.detail/3;
+ }
+
+ if (delta > 0)
+ skipEffects(-1);
+ else if (delta < 0)
+ skipEffects(1);
+
+ if (e.preventDefault)
+ e.preventDefault();
+
+ e.returnValue = false;
+}
+
+/** Event handler for mouse wheel events in index mode.
+ * based on http://adomas.org/javascript-mouse-wheel/
+ *
+ * @param e the event
+ */
+function indexMousewheel(e)
+{
+ var delta = 0;
+
+ if (!e)
+ e = window.event;
+
+ if (e.wheelDelta)
+ { // IE Opera
+ delta = e.wheelDelta/120;
+ }
+ else if (e.detail)
+ { // MOZ
+ delta = -e.detail/3;
+ }
+
+ if (delta > 0)
+ indexSetPageSlide(activeSlide - INDEX_COLUMNS * INDEX_COLUMNS);
+ else if (delta < 0)
+ indexSetPageSlide(activeSlide + INDEX_COLUMNS * INDEX_COLUMNS);
+
+ if (e.preventDefault)
+ e.preventDefault();
+
+ e.returnValue = false;
+}
+
+/** Function to set the path paint width.
+*/
+function set_path_paint_width()
+{
+ var svgPoint1 = document.documentElement.createSVGPoint();
+ var svgPoint2 = document.documentElement.createSVGPoint();
+
+ svgPoint1.x = 0.0;
+ svgPoint1.y = 0.0;
+ svgPoint2.x = 1.0;
+ svgPoint2.y = 0.0;
+
+ var matrix = slides[activeSlide]["element"].getTransformToElement(ROOT_NODE);
+
+ if (slides[activeSlide]["viewGroup"])
+ matrix = slides[activeSlide]["viewGroup"].getTransformToElement(ROOT_NODE);
+
+ svgPoint1 = svgPoint1.matrixTransform(matrix);
+ svgPoint2 = svgPoint2.matrixTransform(matrix);
+
+ path_paint_width = path_width / Math.sqrt((svgPoint2.x - svgPoint1.x) * (svgPoint2.x - svgPoint1.x) + (svgPoint2.y - svgPoint1.y) * (svgPoint2.y - svgPoint1.y));
+}
+
+/** The view effect.
+ *
+ * @param dir direction the effect should be played (1 = forwards, -1 = backwards)
+ * @param element the element the effect should be applied to
+ * @param time the time that has elapsed since the beginning of the effect
+ * @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.
+ */
+function view(dir, element, time, options)
+{
+ var length = 250;
+ var fraction;
+
+ if (!options["matrixInitial"])
+ {
+ var tempString = slides[activeSlide]["viewGroup"].getAttribute("transform");
+
+ if (tempString)
+ options["matrixInitial"] = (new matrixSVG()).fromAttribute(tempString);
+ else
+ options["matrixInitial"] = (new matrixSVG()).fromSVGElements(1, 0, 0, 1, 0, 0);
+ }
+
+ if ((time == STATE_END) || (time == STATE_START))
+ fraction = 1;
+ else
+ {
+ if (options && options["length"])
+ length = options["length"];
+
+ fraction = time / length;
+ }
+
+ if (dir == 1)
+ {
+ if (fraction <= 0)
+ {
+ element.setAttribute("transform", options["matrixInitial"].toAttribute());
+ }
+ else if (fraction >= 1)
+ {
+ element.setAttribute("transform", options["matrixNew"].toAttribute());
+
+ set_path_paint_width();
+
+ options["matrixInitial"] = null;
+ return true;
+ }
+ else
+ {
+ element.setAttribute("transform", options["matrixInitial"].mix(options["matrixNew"], fraction).toAttribute());
+ }
+ }
+ else if (dir == -1)
+ {
+ if (fraction <= 0)
+ {
+ element.setAttribute("transform", options["matrixInitial"].toAttribute());
+ }
+ else if (fraction >= 1)
+ {
+ element.setAttribute("transform", options["matrixOld"].toAttribute());
+ set_path_paint_width();
+
+ options["matrixInitial"] = null;
+ return true;
+ }
+ else
+ {
+ element.setAttribute("transform", options["matrixInitial"].mix(options["matrixOld"], fraction).toAttribute());
+ }
+ }
+
+ return false;
+}
+
+/** The fade effect.
+ *
+ * @param dir direction the effect should be played (1 = forwards, -1 = backwards)
+ * @param element the element the effect should be applied to
+ * @param time the time that has elapsed since the beginning of the effect
+ * @param options a dictionary with additional options (e.g. length of the effect)
+ */
+function fade(dir, element, time, options)
+{
+ var length = 250;
+ var fraction;
+
+ if ((time == STATE_END) || (time == STATE_START))
+ fraction = 1;
+ else
+ {
+ if (options && options["length"])
+ length = options["length"];
+
+ fraction = time / length;
+ }
+
+ if (dir == 1)
+ {
+ if (fraction <= 0)
+ {
+ element.style.display = "none";
+ element.setAttribute("opacity", 0);
+ }
+ else if (fraction >= 1)
+ {
+ element.style.display = "inherit";
+ element.setAttribute("opacity", 1);
+ return true;
+ }
+ else
+ {
+ element.style.display = "inherit";
+ element.setAttribute("opacity", fraction);
+ }
+ }
+ else if (dir == -1)
+ {
+ if (fraction <= 0)
+ {
+ element.style.display = "inherit";
+ element.setAttribute("opacity", 1);
+ }
+ else if (fraction >= 1)
+ {
+ element.setAttribute("opacity", 0);
+ element.style.display = "none";
+ return true;
+ }
+ else
+ {
+ element.style.display = "inherit";
+ element.setAttribute("opacity", 1 - fraction);
+ }
+ }
+ return false;
+}
+
+/** The appear effect.
+ *
+ * @param dir direction the effect should be played (1 = forwards, -1 = backwards)
+ * @param element the element the effect should be applied to
+ * @param time the time that has elapsed since the beginning of the effect
+ * @param options a dictionary with additional options (e.g. length of the effect)
+ */
+function appear(dir, element, time, options)
+{
+ if (dir == 1)
+ {
+ element.style.display = "inherit";
+ element.setAttribute("opacity",1);
+ }
+ else if (dir == -1)
+ {
+ element.style.display = "none";
+ element.setAttribute("opacity",0);
+ }
+ return true;
+}
+
+/** The pop effect.
+ *
+ * @param dir direction the effect should be played (1 = forwards, -1 = backwards)
+ * @param element the element the effect should be applied to
+ * @param time the time that has elapsed since the beginning of the effect
+ * @param options a dictionary with additional options (e.g. length of the effect)
+ */
+function pop(dir, element, time, options)
+{
+ var length = 500;
+ var fraction;
+
+ if ((time == STATE_END) || (time == STATE_START))
+ fraction = 1;
+ else
+ {
+ if (options && options["length"])
+ length = options["length"];
+
+ fraction = time / length;
+ }
+
+ if (dir == 1)
+ {
+ if (fraction <= 0)
+ {
+ element.setAttribute("opacity", 0);
+ element.setAttribute("transform", "scale(0)");
+ element.style.display = "none";
+ }
+ else if (fraction >= 1)
+ {
+ element.setAttribute("opacity", 1);
+ element.removeAttribute("transform");
+ element.style.display = "inherit";
+ return true;
+ }
+ else
+ {
+ element.style.display = "inherit";
+ var opacityFraction = fraction * 3;
+ if (opacityFraction > 1)
+ opacityFraction = 1;
+ element.setAttribute("opacity", opacityFraction);
+ var offsetX = WIDTH * (1.0 - fraction) / 2.0;
+ var offsetY = HEIGHT * (1.0 - fraction) / 2.0;
+ element.setAttribute("transform", "translate(" + offsetX + "," + offsetY + ") scale(" + fraction + ")");
+ }
+ }
+ else if (dir == -1)
+ {
+ if (fraction <= 0)
+ {
+ element.setAttribute("opacity", 1);
+ element.setAttribute("transform", "scale(1)");
+ element.style.display = "inherit";
+ }
+ else if (fraction >= 1)
+ {
+ element.setAttribute("opacity", 0);
+ element.removeAttribute("transform");
+ element.style.display = "none";
+ return true;
+ }
+ else
+ {
+ element.setAttribute("opacity", 1 - fraction);
+ element.setAttribute("transform", "scale(" + 1 - fraction + ")");
+ element.style.display = "inherit";
+ }
+ }
+ return false;
+}
+
+/** Function to set a slide either to the start or the end state.
+ *
+ * @param slide the slide to use
+ * @param state the state into which the slide should be set
+ */
+function setSlideToState(slide, state)
+{
+ slides[slide]["viewGroup"].setAttribute("transform", slides[slide].initialView);
+
+ if (slides[slide]["effects"])
+ {
+ if (state == STATE_END)
+ {
+ for (var counter = 0; counter < slides[slide]["effects"].length; counter++)
+ {
+ for (var subCounter = 0; subCounter < slides[slide]["effects"][counter].length; subCounter++)
+ {
+ var effect = slides[slide]["effects"][counter][subCounter];
+ if (effect["effect"] == "fade")
+ fade(effect["dir"], effect["element"], STATE_END, effect["options"]);
+ else if (effect["effect"] == "appear")
+ appear(effect["dir"], effect["element"], STATE_END, effect["options"]);
+ else if (effect["effect"] == "pop")
+ pop(effect["dir"], effect["element"], STATE_END, effect["options"]);
+ else if (effect["effect"] == "view")
+ view(effect["dir"], effect["element"], STATE_END, effect["options"]);
+ }
+ }
+ }
+ else if (state == STATE_START)
+ {
+ for (var counter = slides[slide]["effects"].length - 1; counter >= 0; counter--)
+ {
+ for (var subCounter = 0; subCounter < slides[slide]["effects"][counter].length; subCounter++)
+ {
+ var effect = slides[slide]["effects"][counter][subCounter];
+ if (effect["effect"] == "fade")
+ fade(parseInt(effect["dir"]) * -1, effect["element"], STATE_START, effect["options"]);
+ else if (effect["effect"] == "appear")
+ appear(parseInt(effect["dir"]) * -1, effect["element"], STATE_START, effect["options"]);
+ else if (effect["effect"] == "pop")
+ pop(parseInt(effect["dir"]) * -1, effect["element"], STATE_START, effect["options"]);
+ else if (effect["effect"] == "view")
+ view(parseInt(effect["dir"]) * -1, effect["element"], STATE_START, effect["options"]);
+ }
+ }
+ }
+ else
+ {
+ setSlideToState(slide, STATE_START);
+
+ for (var counter = 0; counter < slides[slide]["effects"].length && counter < state; counter++)
+ {
+ for (var subCounter = 0; subCounter < slides[slide]["effects"][counter].length; subCounter++)
+ {
+ var effect = slides[slide]["effects"][counter][subCounter];
+ if (effect["effect"] == "fade")
+ fade(effect["dir"], effect["element"], STATE_END, effect["options"]);
+ else if (effect["effect"] == "appear")
+ appear(effect["dir"], effect["element"], STATE_END, effect["options"]);
+ else if (effect["effect"] == "pop")
+ pop(effect["dir"], effect["element"], STATE_END, effect["options"]);
+ else if (effect["effect"] == "view")
+ view(effect["dir"], effect["element"], STATE_END, effect["options"]);
+ }
+ }
+ }
+ }
+
+ window.location.hash = (activeSlide + 1) + '_' + activeEffect;
+}
+
+/** Convenience function to translate a attribute string into a dictionary.
+ *
+ * @param str the attribute string
+ * @return a dictionary
+ * @see dictToPropStr
+ */
+function propStrToDict(str)
+{
+ var list = str.split(";");
+ var obj = new Object();
+
+ for (var counter = 0; counter < list.length; counter++)
+ {
+ var subStr = list[counter];
+ var subList = subStr.split(":");
+ if (subList.length == 2)
+ {
+ obj[subList[0]] = subList[1];
+ }
+ }
+
+ return obj;
+}
+
+/** Convenience function to translate a dictionary into a string that can be used as an attribute.
+ *
+ * @param dict the dictionary to convert
+ * @return a string that can be used as an attribute
+ * @see propStrToDict
+ */
+function dictToPropStr(dict)
+{
+ var str = "";
+
+ for (var key in dict)
+ {
+ str += key + ":" + dict[key] + ";";
+ }
+
+ return str;
+}
+
+/** Sub-function to add a suffix to the ids of the node and all its children.
+ *
+ * @param node the node to change
+ * @param suffix the suffix to add
+ * @param replace dictionary of replaced ids
+ * @see suffixNodeIds
+ */
+function suffixNoneIds_sub(node, suffix, replace)
+{
+ if (node.nodeType == 1)
+ {
+ if (node.getAttribute("id"))
+ {
+ var id = node.getAttribute("id")
+ replace["#" + id] = id + suffix;
+ node.setAttribute("id", id + suffix);
+ }
+
+ if ((node.nodeName == "use") && (node.getAttributeNS(NSS["xlink"], "href")) && (replace[node.getAttribute(NSS["xlink"], "href")]))
+ node.setAttribute(NSS["xlink"], "href", node.getAttribute(NSS["xlink"], "href") + suffix);
+
+ if (node.childNodes)
+ {
+ for (var counter = 0; counter < node.childNodes.length; counter++)
+ suffixNoneIds_sub(node.childNodes[counter], suffix, replace);
+ }
+ }
+}
+
+/** Function to add a suffix to the ids of the node and all its children.
+ *
+ * @param node the node to change
+ * @param suffix the suffix to add
+ * @return the changed node
+ * @see suffixNodeIds_sub
+ */
+function suffixNodeIds(node, suffix)
+{
+ var replace = new Object();
+
+ suffixNoneIds_sub(node, suffix, replace);
+
+ return node;
+}
+
+/** Function to build a progress bar.
+ *
+ * @param parent node to attach the progress bar to
+ */
+function createProgressBar(parent_node)
+{
+ var g = document.createElementNS(NSS["svg"], "g");
+ g.setAttribute("clip-path", "url(#jessyInkSlideClipPath)");
+ g.setAttribute("id", "layer_progress_bar");
+ g.setAttribute("style", "display: none;");
+
+ var rect_progress_bar = document.createElementNS(NSS["svg"], "rect");
+ rect_progress_bar.setAttribute("style", "marker: none; fill: rgb(128, 128, 128); stroke: none;");
+ rect_progress_bar.setAttribute("id", "rect_progress_bar");
+ rect_progress_bar.setAttribute("x", 0);
+ rect_progress_bar.setAttribute("y", 0.99 * HEIGHT);
+ rect_progress_bar.setAttribute("width", 0);
+ rect_progress_bar.setAttribute("height", 0.01 * HEIGHT);
+ g.appendChild(rect_progress_bar);
+
+ var circle_timer_indicator = document.createElementNS(NSS["svg"], "circle");
+ circle_timer_indicator.setAttribute("style", "marker: none; fill: rgb(255, 0, 0); stroke: none;");
+ circle_timer_indicator.setAttribute("id", "circle_timer_indicator");
+ circle_timer_indicator.setAttribute("cx", 0.005 * HEIGHT);
+ circle_timer_indicator.setAttribute("cy", 0.995 * HEIGHT);
+ circle_timer_indicator.setAttribute("r", 0.005 * HEIGHT);
+ g.appendChild(circle_timer_indicator);
+
+ parent_node.appendChild(g);
+}
+
+/** Function to hide the progress bar.
+ *
+ */
+function hideProgressBar()
+{
+ var progress_bar = document.getElementById("layer_progress_bar");
+
+ if (!progress_bar)
+ {
+ return;
+ }
+
+ progress_bar.setAttribute("style", "display: none;");
+}
+
+/** Function to show the progress bar.
+ *
+ */
+function showProgressBar()
+{
+ var progress_bar = document.getElementById("layer_progress_bar");
+
+ if (!progress_bar)
+ {
+ return;
+ }
+
+ progress_bar.setAttribute("style", "display: inherit;");
+}
+
+/** Set progress bar value.
+ *
+ * @param value the current slide number
+ *
+ */
+function setProgressBarValue(value)
+{
+ var rect_progress_bar = document.getElementById("rect_progress_bar");
+
+ if (!rect_progress_bar)
+ {
+ return;
+ }
+
+ if (value < 1)
+ {
+ // First slide, assumed to be the title of the presentation
+ var x = 0;
+ var w = 0.01 * HEIGHT;
+ }
+ else if (value >= slides.length - 1)
+ {
+ // Last slide, assumed to be the end of the presentation
+ var x = WIDTH - 0.01 * HEIGHT;
+ var w = 0.01 * HEIGHT;
+ }
+ else
+ {
+ value -= 1;
+ value /= (slides.length - 2);
+
+ var x = WIDTH * value;
+ var w = WIDTH / (slides.length - 2);
+ }
+
+ rect_progress_bar.setAttribute("x", x);
+ rect_progress_bar.setAttribute("width", w);
+}
+
+/** Set time indicator.
+ *
+ * @param value the percentage of time elapse so far between 0.0 and 1.0
+ *
+ */
+function setTimeIndicatorValue(value)
+{
+ var circle_timer_indicator = document.getElementById("circle_timer_indicator");
+
+ if (!circle_timer_indicator)
+ {
+ return;
+ }
+
+ if (value < 0.0)
+ {
+ value = 0.0;
+ }
+
+ if (value > 1.0)
+ {
+ value = 1.0;
+ }
+
+ var cx = (WIDTH - 0.01 * HEIGHT) * value + 0.005 * HEIGHT;
+ circle_timer_indicator.setAttribute("cx", cx);
+}
+
+/** Update timer.
+ *
+ */
+function updateTimer()
+{
+ timer_elapsed += 1;
+ setTimeIndicatorValue((timer_elapsed - timer_start) / (60 * timer_duration));
+}
+
+/** Convert screen coordinates to document coordinates.
+ *
+ * @param e event with screen coordinates
+ *
+ * @return coordinates in SVG file coordinate system
+ */
+function calcCoord(e)
+{
+ var svgPoint = document.documentElement.createSVGPoint();
+ svgPoint.x = e.clientX + window.pageXOffset;
+ svgPoint.y = e.clientY + window.pageYOffset;
+
+ var matrix = slides[activeSlide]["element"].getScreenCTM();
+
+ if (slides[activeSlide]["viewGroup"])
+ matrix = slides[activeSlide]["viewGroup"].getScreenCTM();
+
+ svgPoint = svgPoint.matrixTransform(matrix.inverse());
+ return svgPoint;
+}
+
+/** Add slide.
+ *
+ * @param after_slide after which slide the new slide should be inserted into the presentation
+ */
+function addSlide(after_slide)
+{
+ number_of_added_slides++;
+
+ var g = document.createElementNS(NSS["svg"], "g");
+ g.setAttribute("clip-path", "url(#jessyInkSlideClipPath)");
+ g.setAttribute("id", "Whiteboard " + Date() + " presentation copy");
+ g.setAttribute("style", "display: none;");
+
+ var new_slide = new Object();
+ new_slide["element"] = g;
+
+ // Set build in transition.
+ new_slide["transitionIn"] = new Object();
+ var dict = defaultTransitionInDict;
+ new_slide["transitionIn"]["name"] = dict["name"];
+ new_slide["transitionIn"]["options"] = new Object();
+
+ for (key in dict)
+ if (key != "name")
+ new_slide["transitionIn"]["options"][key] = dict[key];
+
+ // Set build out transition.
+ new_slide["transitionOut"] = new Object();
+ dict = defaultTransitionOutDict;
+ new_slide["transitionOut"]["name"] = dict["name"];
+ new_slide["transitionOut"]["options"] = new Object();
+
+ for (key in dict)
+ if (key != "name")
+ new_slide["transitionOut"]["options"][key] = dict[key];
+
+ // Copy master slide content.
+ if (masterSlide)
+ {
+ var clonedNode = suffixNodeIds(masterSlide.cloneNode(true), "_" + Date() + " presentation_copy");
+ clonedNode.removeAttributeNS(NSS["inkscape"], "groupmode");
+ clonedNode.removeAttributeNS(NSS["inkscape"], "label");
+ clonedNode.style.display = "inherit";
+
+ g.appendChild(clonedNode);
+ }
+
+ // Substitute auto texts.
+ substituteAutoTexts(g, "Whiteboard " + number_of_added_slides, "W" + number_of_added_slides, slides.length);
+
+ g.setAttribute("onmouseover", "if ((currentMode == INDEX_MODE) && ( activeSlide != " + (after_slide + 1) + ")) { indexSetActiveSlide(" + (after_slide + 1) + "); };");
+
+ // Create a transform group.
+ var transformGroup = document.createElementNS(NSS["svg"], "g");
+
+ // Add content to transform group.
+ while (g.firstChild)
+ transformGroup.appendChild(g.firstChild);
+
+ // Transfer the transform attribute from the node to the transform group.
+ if (g.getAttribute("transform"))
+ {
+ transformGroup.setAttribute("transform", g.getAttribute("transform"));
+ g.removeAttribute("transform");
+ }
+
+ // Create a view group.
+ var viewGroup = document.createElementNS(NSS["svg"], "g");
+
+ viewGroup.appendChild(transformGroup);
+ new_slide["viewGroup"] = g.appendChild(viewGroup);
+
+ // Insert background.
+ if (BACKGROUND_COLOR != null)
+ {
+ var rectNode = document.createElementNS(NSS["svg"], "rect");
+
+ rectNode.setAttribute("x", 0);
+ rectNode.setAttribute("y", 0);
+ rectNode.setAttribute("width", WIDTH);
+ rectNode.setAttribute("height", HEIGHT);
+ rectNode.setAttribute("id", "jessyInkBackground" + Date());
+ rectNode.setAttribute("fill", BACKGROUND_COLOR);
+
+ new_slide["viewGroup"].insertBefore(rectNode, new_slide["viewGroup"].firstChild);
+ }
+
+ // Set initial view even if there are no other views.
+ var matrixOld = (new matrixSVG()).fromElements(1, 0, 0, 0, 1, 0, 0, 0, 1);
+
+ new_slide["viewGroup"].setAttribute("transform", matrixOld.toAttribute());
+ new_slide.initialView = matrixOld.toAttribute();
+
+ // Insert slide
+ var node = slides[after_slide]["element"];
+ var next_node = node.nextSibling;
+ var parent_node = node.parentNode;
+
+ if (next_node)
+ {
+ parent_node.insertBefore(g, next_node);
+ }
+ else
+ {
+ parent_node.appendChild(g);
+ }
+
+ g = document.createElementNS(NSS["svg"], "g");
+ g.setAttributeNS(NSS["inkscape"], "groupmode", "layer");
+ g.setAttributeNS(NSS["inkscape"], "label", "Whiteboard " + number_of_added_slides);
+ g.setAttribute("clip-path", "url(#jessyInkSlideClipPath)");
+ g.setAttribute("id", "Whiteboard " + Date());
+ g.setAttribute("style", "display: none;");
+
+ new_slide["original_element"] = g;
+
+ node = slides[after_slide]["original_element"];
+ next_node = node.nextSibling;
+ parent_node = node.parentNode;
+
+ if (next_node)
+ {
+ parent_node.insertBefore(g, next_node);
+ }
+ else
+ {
+ parent_node.appendChild(g);
+ }
+
+ before_new_slide = slides.slice(0, after_slide + 1);
+ after_new_slide = slides.slice(after_slide + 1);
+ slides = before_new_slide.concat(new_slide, after_new_slide);
+
+ //resetting the counter attributes on the slides that follow the new slide...
+ for (var counter = after_slide+2; counter < slides.length; counter++)
+ {
+ slides[counter]["element"].setAttribute("onmouseover", "if ((currentMode == INDEX_MODE) && ( activeSlide != " + counter + ")) { indexSetActiveSlide(" + counter + "); };");
+ }
+}
+
+/** Convenience function to obtain a transformation matrix from a point matrix.
+ *
+ * @param mPoints Point matrix.
+ * @return A transformation matrix.
+ */
+function pointMatrixToTransformation(mPoints)
+{
+ mPointsOld = (new matrixSVG()).fromElements(0, WIDTH, WIDTH, 0, 0, HEIGHT, 1, 1, 1);
+
+ return mPointsOld.mult(mPoints.inv());
+}
+
+/** Convenience function to obtain a matrix with three corners of a rectangle.
+ *
+ * @param rect an svg rectangle
+ * @return a matrixSVG containing three corners of the rectangle
+ */
+function rectToMatrix(rect)
+{
+ rectWidth = rect.getBBox().width;
+ rectHeight = rect.getBBox().height;
+ rectX = rect.getBBox().x;
+ rectY = rect.getBBox().y;
+ rectXcorr = 0;
+ rectYcorr = 0;
+
+ scaleX = WIDTH / rectWidth;
+ scaleY = HEIGHT / rectHeight;
+
+ if (scaleX > scaleY)
+ {
+ scaleX = scaleY;
+ rectXcorr -= (WIDTH / scaleX - rectWidth) / 2;
+ rectWidth = WIDTH / scaleX;
+ }
+ else
+ {
+ scaleY = scaleX;
+ rectYcorr -= (HEIGHT / scaleY - rectHeight) / 2;
+ rectHeight = HEIGHT / scaleY;
+ }
+
+ if (rect.transform.baseVal.numberOfItems < 1)
+ {
+ mRectTrans = (new matrixSVG()).fromElements(1, 0, 0, 0, 1, 0, 0, 0, 1);
+ }
+ else
+ {
+ mRectTrans = (new matrixSVG()).fromSVGMatrix(rect.transform.baseVal.consolidate().matrix);
+ }
+
+ newBasePoints = (new matrixSVG()).fromElements(rectX, rectX, rectX, rectY, rectY, rectY, 1, 1, 1);
+ newVectors = (new matrixSVG()).fromElements(rectXcorr, rectXcorr + rectWidth, rectXcorr + rectWidth, rectYcorr, rectYcorr, rectYcorr + rectHeight, 0, 0, 0);
+
+ return mRectTrans.mult(newBasePoints.add(newVectors));
+}
+
+/** Function to handle JessyInk elements.
+ *
+ * @param node Element node.
+ */
+function handleElement(node)
+{
+ if (node.getAttributeNS(NSS['jessyink'], 'element') == 'core.video')
+ {
+ var url;
+ var width;
+ var height;
+ var x;
+ var y;
+ var transform;
+
+ var tspans = node.getElementsByTagNameNS("http://www.w3.org/2000/svg", "tspan");
+
+ for (var tspanCounter = 0; tspanCounter < tspans.length; tspanCounter++)
+ {
+ if (tspans[tspanCounter].getAttributeNS("https://launchpad.net/jessyink", "video") == "url")
+ {
+ url = tspans[tspanCounter].firstChild.nodeValue;
+ }
+ }
+
+ var rects = node.getElementsByTagNameNS("http://www.w3.org/2000/svg", "rect");
+
+ for (var rectCounter = 0; rectCounter < rects.length; rectCounter++)
+ {
+ if (rects[rectCounter].getAttributeNS("https://launchpad.net/jessyink", "video") == "rect")
+ {
+ x = rects[rectCounter].getAttribute("x");
+ y = rects[rectCounter].getAttribute("y");
+ width = rects[rectCounter].getAttribute("width");
+ height = rects[rectCounter].getAttribute("height");
+ transform = rects[rectCounter].getAttribute("transform");
+ }
+ }
+
+ for (var childCounter = 0; childCounter < node.childNodes.length; childCounter++)
+ {
+ if (node.childNodes[childCounter].nodeType == 1)
+ {
+ if (node.childNodes[childCounter].style)
+ {
+ node.childNodes[childCounter].style.display = 'none';
+ }
+ else
+ {
+ node.childNodes[childCounter].setAttribute("style", "display: none;");
+ }
+ }
+ }
+
+ var foreignNode = document.createElementNS("http://www.w3.org/2000/svg", "foreignObject");
+ foreignNode.setAttribute("x", x);
+ foreignNode.setAttribute("y", y);
+ foreignNode.setAttribute("width", width);
+ foreignNode.setAttribute("height", height);
+ foreignNode.setAttribute("transform", transform);
+
+ var videoNode = document.createElementNS("http://www.w3.org/1999/xhtml", "video");
+ videoNode.setAttribute("src", url);
+
+ foreignNode.appendChild(videoNode);
+ node.appendChild(foreignNode);
+ }
+}
+
+/** Class processing the location hash.
+ *
+ * @param str location hash
+ */
+function LocationHash(str)
+{
+ this.slideNumber = 0;
+ this.effectNumber = 0;
+
+ str = str.substr(1, str.length - 1);
+
+ var parts = str.split('_');
+
+ // Try to extract slide number.
+ if (parts.length >= 1)
+ {
+ try
+ {
+ var slideNumber = parseInt(parts[0]);
+
+ if (!isNaN(slideNumber))
+ {
+ this.slideNumber = slideNumber - 1;
+ }
+ }
+ catch (e)
+ {
+ }
+ }
+
+ // Try to extract effect number.
+ if (parts.length >= 2)
+ {
+ try
+ {
+ var effectNumber = parseInt(parts[1]);
+
+ if (!isNaN(effectNumber))
+ {
+ this.effectNumber = effectNumber;
+ }
+ }
+ catch (e)
+ {
+ }
+ }
+}
+
+/** Class representing an svg matrix.
+*/
+function matrixSVG()
+{
+ this.e11 = 0; // a
+ this.e12 = 0; // c
+ this.e13 = 0; // e
+ this.e21 = 0; // b
+ this.e22 = 0; // d
+ this.e23 = 0; // f
+ this.e31 = 0;
+ this.e32 = 0;
+ this.e33 = 0;
+}
+
+/** Constructor function.
+ *
+ * @param a element a (i.e. 1, 1) as described in the svg standard.
+ * @param b element b (i.e. 2, 1) as described in the svg standard.
+ * @param c element c (i.e. 1, 2) as described in the svg standard.
+ * @param d element d (i.e. 2, 2) as described in the svg standard.
+ * @param e element e (i.e. 1, 3) as described in the svg standard.
+ * @param f element f (i.e. 2, 3) as described in the svg standard.
+ */
+matrixSVG.prototype.fromSVGElements = function(a, b, c, d, e, f)
+{
+ this.e11 = a;
+ this.e12 = c;
+ this.e13 = e;
+ this.e21 = b;
+ this.e22 = d;
+ this.e23 = f;
+ this.e31 = 0;
+ this.e32 = 0;
+ this.e33 = 1;
+
+ return this;
+}
+
+/** Constructor function.
+ *
+ * @param matrix an svg matrix as described in the svg standard.
+ */
+matrixSVG.prototype.fromSVGMatrix = function(m)
+{
+ this.e11 = m.a;
+ this.e12 = m.c;
+ this.e13 = m.e;
+ this.e21 = m.b;
+ this.e22 = m.d;
+ this.e23 = m.f;
+ this.e31 = 0;
+ this.e32 = 0;
+ this.e33 = 1;
+
+ return this;
+}
+
+/** Constructor function.
+ *
+ * @param e11 element 1, 1 of the matrix.
+ * @param e12 element 1, 2 of the matrix.
+ * @param e13 element 1, 3 of the matrix.
+ * @param e21 element 2, 1 of the matrix.
+ * @param e22 element 2, 2 of the matrix.
+ * @param e23 element 2, 3 of the matrix.
+ * @param e31 element 3, 1 of the matrix.
+ * @param e32 element 3, 2 of the matrix.
+ * @param e33 element 3, 3 of the matrix.
+ */
+matrixSVG.prototype.fromElements = function(e11, e12, e13, e21, e22, e23, e31, e32, e33)
+{
+ this.e11 = e11;
+ this.e12 = e12;
+ this.e13 = e13;
+ this.e21 = e21;
+ this.e22 = e22;
+ this.e23 = e23;
+ this.e31 = e31;
+ this.e32 = e32;
+ this.e33 = e33;
+
+ return this;
+}
+
+/** Constructor function.
+ *
+ * @param attrString string value of the "transform" attribute (currently only "matrix" is accepted)
+ */
+matrixSVG.prototype.fromAttribute = function(attrString)
+{
+ str = attrString.substr(7, attrString.length - 8);
+
+ str = str.trim();
+
+ strArray = str.split(",");
+
+ // Opera does not use commas to separate the values of the matrix, only spaces.
+ if (strArray.length != 6)
+ strArray = str.split(" ");
+
+ this.e11 = parseFloat(strArray[0]);
+ this.e21 = parseFloat(strArray[1]);
+ this.e31 = 0;
+ this.e12 = parseFloat(strArray[2]);
+ this.e22 = parseFloat(strArray[3]);
+ this.e32 = 0;
+ this.e13 = parseFloat(strArray[4]);
+ this.e23 = parseFloat(strArray[5]);
+ this.e33 = 1;
+
+ return this;
+}
+
+/** Output function
+ *
+ * @return a string that can be used as the "transform" attribute.
+ */
+matrixSVG.prototype.toAttribute = function()
+{
+ return "matrix(" + this.e11 + ", " + this.e21 + ", " + this.e12 + ", " + this.e22 + ", " + this.e13 + ", " + this.e23 + ")";
+}
+
+/** Matrix nversion.
+ *
+ * @return the inverse of the matrix
+ */
+matrixSVG.prototype.inv = function()
+{
+ out = new matrixSVG();
+
+ 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);
+
+ out.e11 = (this.e33 * this.e22 - this.e32 * this.e23) / det;
+ out.e12 = -(this.e33 * this.e12 - this.e32 * this.e13) / det;
+ out.e13 = (this.e23 * this.e12 - this.e22 * this.e13) / det;
+ out.e21 = -(this.e33 * this.e21 - this.e31 * this.e23) / det;
+ out.e22 = (this.e33 * this.e11 - this.e31 * this.e13) / det;
+ out.e23 = -(this.e23 * this.e11 - this.e21 * this.e13) / det;
+ out.e31 = (this.e32 * this.e21 - this.e31 * this.e22) / det;
+ out.e32 = -(this.e32 * this.e11 - this.e31 * this.e12) / det;
+ out.e33 = (this.e22 * this.e11 - this.e21 * this.e12) / det;
+
+ return out;
+}
+
+/** Matrix multiplication.
+ *
+ * @param op another svg matrix
+ * @return this * op
+ */
+matrixSVG.prototype.mult = function(op)
+{
+ out = new matrixSVG();
+
+ out.e11 = this.e11 * op.e11 + this.e12 * op.e21 + this.e13 * op.e31;
+ out.e12 = this.e11 * op.e12 + this.e12 * op.e22 + this.e13 * op.e32;
+ out.e13 = this.e11 * op.e13 + this.e12 * op.e23 + this.e13 * op.e33;
+ out.e21 = this.e21 * op.e11 + this.e22 * op.e21 + this.e23 * op.e31;
+ out.e22 = this.e21 * op.e12 + this.e22 * op.e22 + this.e23 * op.e32;
+ out.e23 = this.e21 * op.e13 + this.e22 * op.e23 + this.e23 * op.e33;
+ out.e31 = this.e31 * op.e11 + this.e32 * op.e21 + this.e33 * op.e31;
+ out.e32 = this.e31 * op.e12 + this.e32 * op.e22 + this.e33 * op.e32;
+ out.e33 = this.e31 * op.e13 + this.e32 * op.e23 + this.e33 * op.e33;
+
+ return out;
+}
+
+/** Matrix addition.
+ *
+ * @param op another svg matrix
+ * @return this + op
+ */
+matrixSVG.prototype.add = function(op)
+{
+ out = new matrixSVG();
+
+ out.e11 = this.e11 + op.e11;
+ out.e12 = this.e12 + op.e12;
+ out.e13 = this.e13 + op.e13;
+ out.e21 = this.e21 + op.e21;
+ out.e22 = this.e22 + op.e22;
+ out.e23 = this.e23 + op.e23;
+ out.e31 = this.e31 + op.e31;
+ out.e32 = this.e32 + op.e32;
+ out.e33 = this.e33 + op.e33;
+
+ return out;
+}
+
+/** Matrix mixing.
+ *
+ * @param op another svg matrix
+ * @parma contribOp contribution of the other matrix (0 <= contribOp <= 1)
+ * @return (1 - contribOp) * this + contribOp * op
+ */
+matrixSVG.prototype.mix = function(op, contribOp)
+{
+ contribThis = 1.0 - contribOp;
+ out = new matrixSVG();
+
+ out.e11 = contribThis * this.e11 + contribOp * op.e11;
+ out.e12 = contribThis * this.e12 + contribOp * op.e12;
+ out.e13 = contribThis * this.e13 + contribOp * op.e13;
+ out.e21 = contribThis * this.e21 + contribOp * op.e21;
+ out.e22 = contribThis * this.e22 + contribOp * op.e22;
+ out.e23 = contribThis * this.e23 + contribOp * op.e23;
+ out.e31 = contribThis * this.e31 + contribOp * op.e31;
+ out.e32 = contribThis * this.e32 + contribOp * op.e32;
+ out.e33 = contribThis * this.e33 + contribOp * op.e33;
+
+ return out;
+}
+
+/** Trimming function for strings.
+*/
+String.prototype.trim = function()
+{
+ return this.replace(/^\s+|\s+$/g, '');
+}
+
+/** SVGElement.getTransformToElement polyfill */
+SVGElement.prototype.getTransformToElement = SVGElement.prototype.getTransformToElement || function(elem) {
+ return elem.getScreenCTM().inverse().multiply(this.getScreenCTM());
+};