diff --git a/TP/TP01/P01/dahuapp.js b/TP/TP01/P01/dahuapp.js
new file mode 100644
index 0000000000000000000000000000000000000000..41c69e9169345a9a6703325d5557a4cc36913e19
--- /dev/null
+++ b/TP/TP01/P01/dahuapp.js
@@ -0,0 +1,880 @@
+"use strict";
+
+/**
+ * Dahuapp core module.
+ *
+ * @param   window      window javascript object.
+ * @param   $           jQuery
+ * @returns dahuapp core module.
+ */
+(function (window, $) {
+    var dahuapp = (function () {
+
+        var self = {};
+
+        /* private API */
+
+        var DahuScreencastGenerator = function () {
+
+            /*
+             * Format the specified string in a readable format.
+             */
+            function htmlFormat(htmlString) {
+                var i;
+                var readableHTML = htmlString;
+                var lb = '\n';
+                var htags = ["<html", "</html>", "</head>", "<title", "</title>", "<meta", "<link", "</body>"];
+                for (i = 0; i < htags.length; ++i) {
+                    var hhh = htags[i];
+                    readableHTML = readableHTML.replace(new RegExp(hhh, 'gi'), lb + hhh);
+                }
+                var btags = ["</div>", "</section>", "</span>", "<br>", "<br />", "<blockquote", "</blockquote>", "<ul", "</ul>", "<ol", "</ol>", "<li", "<\!--", "<script", "</script>"];
+                for (i = 0; i < btags.length; ++i) {
+                    var bbb = btags[i];
+                    readableHTML = readableHTML.replace(new RegExp(bbb, 'gi'), lb + bbb);
+                }
+                var ftags = ["<img", "<legend", "</legend>", "<button", "</button>"];
+                for (i = 0; i < ftags.length; ++i) {
+                    var fff = ftags[i];
+                    readableHTML = readableHTML.replace(new RegExp(fff, 'gi'), lb + fff);
+                }
+                var xtags = ["<body", "<head", "<div", "<section", "<span", "<p"];
+                for (i = 0; i < xtags.length; ++i) {
+                    var xxx = xtags[i];
+                    readableHTML = readableHTML.replace(new RegExp(xxx, 'gi'), lb + lb + xxx);
+                }
+                return readableHTML;
+            }
+
+            /*
+             * Generates the html header.
+             */
+            var generateHtmlHeader = function ($generated, cssGen) {
+                $('head', $generated)
+                    .append($(document.createElement('title'))
+                        .append("Dahu Presentation"))/* TODO: make this customizable */
+                    .append($(document.createElement('meta'))
+                        .attr({'charset': 'utf-8'}))
+                    .append($(document.createElement('script'))
+                        .attr({'src': 'http://code.jquery.com/jquery-1.9.1.min.js'})
+                        .attr({'type': 'text/javascript'}))
+                    .append($(document.createElement('script'))
+                        .attr({'src': 'parse-search.js'})
+                        .attr({'type': 'text/javascript'}))
+                    .append($(document.createElement('link'))
+                        .attr({'rel': 'stylesheet', 'href': 'dahuapp.viewer.css'}))
+                    .append($(document.createElement('style'))
+                        .append(cssGen));
+            };
+
+            /*
+             * Generates the objects.
+             */
+
+            var generateHtmlBackgroundImage = function ($generated, object) {
+                $('.object-list', $generated)
+                    .append($(document.createElement('img'))
+                        .attr({'src': object.img, 'alt': object.img, 'class': 'background ' + object.id}));
+            };
+            var generateHtmlTooltip = function ($generated, object) {
+                $('.object-list', $generated)
+                    .append($(document.createElement('div'))
+                        .attr({'class': 'tooltip ' + object.id})
+                        .append(object.text));
+            };
+
+            var generateCssTooltip = function ($generated, object) {
+                var style = [];
+
+                if( object.color != null ) {
+                    style.push('background-color:   ' + object.color);
+                }
+                if( object.width != null ) {
+                    style.push('width:  ' + object.width);
+                }
+
+                if( style.length != 0 ) {
+                    $generated.append(
+                    '.' + object.id + '{' + style.join(';') + '}\n');
+                }
+
+                return $generated;
+            };
+
+            /*
+             * Returns the code used to call the viewer to the presentation.
+             */
+            var getBasicCallCode = function (jsonModel) {
+                var code = '(function($) {\n';
+                code += '    var myPresentation = dahuapp.viewer.createDahuViewer("#my-dahu-presentation", window.getParams);\n';
+                code += '    myPresentation.load(' + jsonModel + ');\n';
+                code += '    myPresentation.start();\n';
+                code += '})(jQuery);\n';
+                return code;
+            };
+
+            /*
+             * Generates the html body.
+             */
+            var generateHtmlBody = function ($generated, jsonModel, jsonGen) {
+                $('body', $generated)
+                    /* We could use a <section> here too, but it does not work with MS IE 8.
+                     Alternatively, adding this in the header would work:
+
+                     <!--[if lt IE 9]>
+                     <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
+                     <![endif]-->
+                     */
+                    .append($(document.createElement('div'))
+                        .attr({'id': 'my-dahu-presentation'}))
+                    .append($(document.createElement('script'))
+                        .attr({'src': 'dahuapp.js'})
+                        .attr({'type': 'text/javascript'}))
+                    .append($(document.createElement('script'))
+                        .attr({'src': 'dahuapp.viewer.js'})
+                        .attr({'type': 'text/javascript'}))
+                    .append($(document.createElement('script'))
+                        .attr({'type': 'text/javascript'})
+                        .append(getBasicCallCode(jsonGen)));
+                $('#my-dahu-presentation', $generated)
+                    .append($(document.createElement('div'))
+                        .attr({'id': 'loading'}).append("Loading presentation..."))
+                    .append($(document.createElement('div'))
+                        .attr({'class': 'object-list',
+                            'style': 'display: none'}))
+                    .append($(document.createElement('div'))
+                        .attr({'class': 'control'}));
+
+                /* Adding the objects to the page */
+                $.each(jsonModel.getObjectList(), function (id, object) {
+                    switch (object.type) {
+                        case "background":
+                            generateHtmlBackgroundImage($generated, object);
+                            break;
+                        case "tooltip":
+                            generateHtmlTooltip($generated, object);
+                            break;
+                        /* no mouse image generated here */
+                    }
+                });
+
+                /* Warning here, i'm not sure if $.each is always over, here */
+
+                /* Adding the control buttons to the page */
+                $('.control', $generated)
+                    .css({'top': (jsonModel.getImageHeight() + 16) + 'px'})
+                    .append($(document.createElement('button'))
+                        .attr({'class': 'previous'})
+                        .append('Previous'))
+                    .append($(document.createElement('button'))
+                        .attr({'class': 'next'})
+                        .append('Next'));
+
+                /* Adding the mouse cursor image */
+                $('.object-list', $generated)
+                    .append($(document.createElement('div'))
+                        .attr({'class': 'mouse-cursor'})
+                        .append($(document.createElement('img'))
+                            .attr({'src': 'img/cursor.png', 'alt': 'img/cursor.png', 'class': 'mouse-cursor-normal'}))
+                        .append($(document.createElement('img'))
+                            .attr({'src': 'img/cursor-pause.png', 'alt': 'img/cursor-pause.png',
+                                'style': 'display: none', 'class': 'mouse-cursor-pause'})));
+            };
+
+            /*
+             * Generates the css String with the Json model.
+             */
+            this.generateCssString = function (jsonModel) {
+                var $generated = $('<style></style>');
+
+                /* Going through each object */
+                $.each(jsonModel.getObjectList(), function (id, object) {
+                    switch (object.type) {
+                        case "tooltip":
+                            generateCssTooltip($generated, object);
+                            break;
+                        /* no mouse image generated here */
+                    }
+                });
+
+                return $generated.text();
+            }
+
+            /*
+             * Generates the html String with the Json model.
+             */
+            this.generateHtmlString = function (jsonModel, jsonGen, cssGen) {
+                /* Initialising the compilation area */
+                var $generated = $(document.createElement('div'));
+
+                /* We create the html using the json */
+                $generated.append($(document.createElement('html'))
+                    .attr({'lang': 'en'}));
+                $('html', $generated)
+                    .append($(document.createElement('head')))
+                    .append($(document.createElement('body')));
+
+                generateHtmlHeader($generated, cssGen);
+                generateHtmlBody($generated, jsonModel, jsonGen);
+
+                var result = htmlFormat($generated.html());
+
+                return '<!DOCTYPE html>\n' + result;
+            };
+
+            /*
+             * Generates the generated JSON using the JSONmodel.
+             * @param {object} jsonModel The json model to transform.
+             * @param {java.awt.Dimension} imgDim The dimension of images.
+             */
+            this.generateJsonString = function (jsonModel, imgDim) {
+                var generated = self.createScreencastGeneratedModel();
+                generated.setImageSize(imgDim.width, imgDim.height);
+                generated.setInitialBackground(jsonModel.getInitialBackground());
+                generated.setInitialMousePos(jsonModel.getInitialMousePos());
+                for (var i = 0; i < jsonModel.getNbSlide(); i++) {
+                    var actionList = jsonModel.getActionList(i);
+                    for (var j = 0; j < actionList.length; j++) {
+                        /* 
+                         * We don't add the two first actions of the first slide
+                         * because they are present in the presentation metadata.
+                         * It corresponds to the mouse initial pos and first background.
+                         */
+                        if (i > 0 || j > 1) {
+                            generated.addAction(actionList[j], imgDim.width, imgDim.height);
+                        }
+                    }
+                }
+                return generated.getJson();
+            };
+        };
+
+        /*
+         * Model for a JSON object that will only be used by the viewer, never
+         * saved on a file, used to transform the properties of actions
+         * specified on the JSON file of a presentation to executable
+         * functions for each action.
+         */
+        var DahuScreencastExecutableModel = function () {
+
+            /* Private API */
+
+            var json = {
+                metaData: {},
+                action: new Array()
+            };
+
+            /*
+             * Creates a functions representing the specified action, and adds
+             * it to this object.
+             * @param {object} action
+             */
+            var addExecutableAction = function (action) {
+                var executableAction = {
+                    'id': action.id,
+                    'trigger': action.trigger,
+                    'target': action.target,
+                    'delayAfter': action.delayAfter,
+                    'doneFunction': function (events, selector) {
+                        setTimeout(function () {
+                            events.onActionOver.publish(events, selector);
+                        }, this.delayAfter);
+                    }
+                };
+                if (executableAction.delayAfter == null) {
+                    executableAction.delayAfter = 200;
+                }
+                switch (action.type.toLowerCase()) {
+                    case "appear":
+                        executableAction.abs = (action.abs * json.metaData.imageWidth) + 'px';
+                        executableAction.ord = (action.ord * json.metaData.imageHeight) + 'px';
+                        executableAction.duration = action.duration;
+                        executableAction.execute = function (events, selector) {
+                            events.onActionStart.publish(events, selector);
+                            var sel = selector + ' .' + this.target;
+                            $(sel).css({
+                                'left': this.abs,
+                                'top': this.ord
+                            });
+                            $(sel).show(this.duration, function () {
+                                executableAction.doneFunction(events, selector);
+                            });
+                        };
+                        executableAction.executeReverse = function (selector) {
+                            $(selector + ' .' + this.target).hide();
+                        };
+                        executableAction.executeImmediately = function (selector) {
+                            var sel = selector + ' .' + this.target;
+                            $(sel).css({
+                                'left': this.abs,
+                                'top': this.ord
+                            });
+                            $(sel).show();
+                        };
+                        break;
+                    case "disappear":
+                        executableAction.duration = action.duration;
+                        executableAction.execute = function (events, selector) {
+                            events.onActionStart.publish(events, selector);
+                            $(selector + ' .' + this.target).hide(this.duration, function () {
+                                executableAction.doneFunction(events, selector);
+                            });
+                        };
+                        executableAction.executeReverse = function (selector) {
+                            $(selector + ' .' + this.target).show();
+                        };
+                        executableAction.executeImmediately = function (selector) {
+                            $(selector + ' .' + this.target).hide();
+                        };
+                        break;
+                    case "move":
+                        executableAction.finalAbs = (action.finalAbs * json.metaData.imageWidth) + 'px';
+                        executableAction.finalOrd = (action.finalOrd * json.metaData.imageHeight) + 'px';
+                        executableAction.duration = action.duration;
+                        executableAction.speed = action.speed;
+                        executableAction.execute = function (events, selector) {
+                            events.onActionStart.publish(events, selector);
+                            var sel = selector + ' .' + this.target;
+                            this.initialAbs = $(sel).css('left');
+                            this.initialOrd = $(sel).css('top');
+                            if (this.duration == null) {
+                                var initialAbsPix = this.initialAbs.replace('px', '');
+                                var initialOrdPix = this.initialOrd.replace('px', '');
+                                var finalAbsPix = this.finalAbs.replace('px', '');
+                                var finalOrdPix = this.finalOrd.replace('px', '');
+                                var distance = Math.sqrt(Math.pow(finalAbsPix - initialAbsPix, 2) +
+                                    Math.pow(finalOrdPix - initialOrdPix, 2));
+                                if (!this.speed) {
+                                    this.speed = .8; // in pixel per milisecond: fast, but not too much.
+                                }
+                                this.duration = distance + this.speed;
+                                if (this.duration < 200) { // Slow down a bit for short distances.
+                                    this.duration = 200;
+                                }
+                            }
+                            $(sel).animate({
+                                'left': this.finalAbs,
+                                'top': this.finalOrd
+                            }, this.duration, 'linear', function () {
+                                executableAction.doneFunction(events, selector);
+                            });
+                        };
+                        executableAction.executeReverse = function (selector) {
+                            $(selector + ' .' + this.target).css({
+                                'left': this.initialAbs,
+                                'top': this.initialOrd
+                            });
+                        };
+                        executableAction.executeImmediately = function (selector) {
+                            var sel = selector + ' .' + this.target;
+                            this.initialAbs = $(sel).css('left');
+                            this.initialOrd = $(sel).css('top');
+                            $(sel).css({
+                                'left': this.finalAbs,
+                                'top': this.finalOrd
+                            });
+                        };
+                        break;
+                    case "delay":
+                        executableAction.duration = action.duration;
+                        executableAction.execute = function (events, selector) {
+                            events.onActionStart.publish(events, selector);
+                            setTimeout(function () {
+                                events.onActionOver.publish(events, selector);
+                            }, this.duration);
+                        };
+                        executableAction.executeReverse = function (selector) {
+                            // Nothing!
+                        };
+                        executableAction.executeImmediately = function (selector) {
+                            // Nothing too!
+                        };
+                        break;
+                }
+                json.action.push(executableAction);
+            };
+
+            /* Public API */
+
+            /*
+             * Loads the specified JSON read from a 'presentation.json' file,
+             * and stores the functions representing each actions in an object.
+             * @param {object} jsonToLoad
+             */
+            this.loadJson = function (jsonToLoad) {
+                json.metaData = jsonToLoad.metaData;
+                for (var i = 0; i < jsonToLoad.action.length; i++) {
+                    addExecutableAction(jsonToLoad.action[i]);
+                }
+            };
+
+            /*
+             * Returns the object containing the functions.
+             */
+            this.getJson = function () {
+                return json;
+            };
+        };
+
+        /*
+         * Used to transform the 'dahu' file to a JSON file used by the viewer,
+         * which only contains some metaData and a list of actions.
+         */
+        var DahuScreencastGeneratedModel = function () {
+
+            /* Private API */
+
+            var json = {
+                metaData: {},
+                action: new Array()
+            };
+
+            /* Public API */
+
+            /*
+             * Returns a string representation of this json.
+             * @returns {String}
+             */
+            this.getJson = function () {
+                return JSON.stringify(json, null, '    ');
+            };
+
+            /*
+             * Setters for the generated json metadata.
+             */
+            this.setImageSize = function (width, height) {
+                json.metaData.imageWidth = width;
+                json.metaData.imageHeight = height;
+            };
+
+            this.setInitialMousePos = function (pos) {
+                json.metaData.initialMouseX = pos.x;
+                json.metaData.initialMouseY = pos.y;
+            };
+
+            this.setInitialBackground = function (id) {
+                json.metaData.initialBackgroundId = id;
+            };
+
+            this.addAction = function (action) {
+                json.action.push(action);
+            };
+
+            /*
+             * Transforms a JSON containing action properties to a JSON containing
+             * the execution functions.
+             * @param {Object} json
+             * @returns {Object} A json object containing the execution functions
+             * for all the actions of the presentation.
+             */
+            this.toExecutableList = function (json) {
+                var executableList = {
+                    metaData: json.metaData,
+                    action: new Array()
+                };
+                for (var i = 0; i < json.action.length; i++) {
+                    addExecutableAction(executableList, json.action[i]);
+                }
+                return executableList;
+            };
+        };
+
+        /*
+         * Represents a 'dahu' file for a project.
+         */
+        var DahuScreencastModel = function () {
+
+            /* Private API */
+
+            var json = {};
+
+            /*
+             * Generates a unique action ID.
+             *
+             * With this implementation, this ID is unique as long as the user
+             * doesn't replace it manually with a not kind value or replace
+             * the nextUniqueId with bad intentions.
+             * But maybe that a UUID is a bit tiresome to put as an anchor...
+             */
+            var generateUniqueActionId = function () {
+                json.metaData.nextUniqueId++;
+                return json.metaData.nextUniqueId.toString();
+            };
+
+            /*
+             * This method checks if the json representing a dahu project is
+             * in the good version. It's in case a project file was created
+             * with a previous version of Dahu, and that some fields are
+             * missing.
+             *
+             * It doesn't control each field (at the moment) but only the
+             * fields that can be missing due to a new Dahu version and not
+             * to a manual editing.
+             *
+             * This method doesn't check any Json syntax or something like that.
+             */
+            var upgradeJsonVersion = function () {
+                // Checks if unique IDs are in the project file
+                if (!json.metaData.nextUniqueId) {
+                    var currentId = 0;
+                    for (var i = 0; i < json.data.length; i++) {
+                        for (var j = 0; j < json.data[i].action.length; j++) {
+                            json.data[i].action[j].id = currentId.toString();
+                            currentId++;
+                        }
+                    }
+                    json.metaData.nextUniqueId = currentId;
+                }
+            };
+
+            /* Public API */
+
+            /*
+             * json variable generated from a JSON file.
+             * @param String stringJson String loaded from JSON file.
+             */
+            this.loadJson = function (stringJson) {
+                json = JSON.parse(stringJson);
+                upgradeJsonVersion();
+            };
+
+            /*
+             * Create a new presentation variable in the JSON file which will contain slides.
+             */
+            this.createPresentation = function (width, height) {
+                json.metaData = {};
+                json.metaData.imageWidth = width;
+                json.metaData.imageHeight = height;
+                json.metaData.nextUniqueId = 0;
+                json.data = new Array();
+            };
+
+            /*
+             * Add a new slide in the presentation variable of the JSON file.
+             * @param int index wanted for the slide.
+             * @param String idSlide Unique identifier for the slide.
+             * @param String img Related to pathname of the image.
+             * @param double mouseX Abscissa mouse position in %.
+             * @param double mouseY Ordinate mouse position in %.
+             * @return Index of the newly added slide.
+             */
+            this.addSlide = function (indexSlide, idSlide, img, mouseX, mouseY, speed) {
+                var slide = {
+                    "object": new Array(),
+                    "action": new Array()
+                };
+                json.data.splice(indexSlide, 0, slide);
+                this.addObject(indexSlide, "background", idSlide, img);
+                this.addObject(indexSlide, "mouse");
+                this.addAction(indexSlide, "move", json.data[indexSlide].object[1].id, "onClick", mouseX, mouseY, speed);
+                this.addAction(indexSlide, "appear", json.data[indexSlide].object[0].id, "afterPrevious");
+            };
+
+            /*
+             * Object factory.
+             * Add a new Object in the slide idSlide.
+             * @param int idSlide
+             * @param string type
+             * Other params can be specified depending on the object's type.
+             */
+            this.addObject = function (idSlide, type) {
+				var object = {
+                    "type": type
+                };
+                switch (type.toLowerCase()) {
+                    case "background":
+                        object.id = arguments[2] + json.data[idSlide].object.length;
+                        object.img = arguments[3] || "";
+                        break;
+                    case "mouse":
+                        object.id = "mouse-cursor";
+                        break;
+                    case "tooltip":
+                        /*
+                         * TODO: we'll need a more robust unique name
+                         * when we start actually using this.
+                         */
+                        object.id = "s" + idSlide + "-o" + json.data[idSlide].object.length;
+                        object.text = arguments[2] || "";
+                        object.color = arguments[3] || null;
+						object.width = arguments[4] || "";
+						json.data[idSlide].object.push(object);
+						var objectLength = json.data[idSlide].object.length;
+						var last = json.data[idSlide].object[objectLength-1];
+						json.data[idSlide].object[objectLength-1] = 
+								json.data[idSlide].object[objectLength-2];
+						json.data[idSlide].object[objectLength-2] = last;
+                        break;
+                }
+				if(type.toLowerCase() != "tooltip"){
+					json.data[idSlide].object.push(object);
+				}
+            };
+
+            /*
+             * Action factory.
+             * Add a new Action in the slide idSlide whose target is the id of an object.
+             * Three types of trigger : "withPrevious", "afterPrevious", "onClick".
+             * @param int idSlide
+             * @param string type
+             * @param string target
+             * @param string trigger
+             * Other params can be specified depending on the object's type.
+             */
+            this.addAction = function (idSlide, type, target, trigger) {
+                var action = {
+                    "id": generateUniqueActionId(),
+                    "type": type,
+                    "target": target,
+                    "trigger": trigger
+                };
+                switch (type.toLowerCase()) {
+                    case "appear":
+                        action.abs = arguments[4] || 0.0;
+                        action.ord = arguments[5] || 0.0;
+                        action.duration = arguments[6] || 0;
+                        break;
+                    case "disappear":
+                        action.duration = arguments[4] || 0;
+                        break;
+                    case "move":
+                        action.finalAbs = arguments[4] || 0.0;
+                        action.finalOrd = arguments[5] || 0.0;
+                        action.speed = arguments[6] || 0;
+                        break;
+                }
+                json.data[idSlide].action.push(action);
+            };
+
+            this.editMouse = function (idSlide, idAction, mouseX, mouseY) {
+                json.data[idSlide].action[idAction].finalAbs = mouseX;
+                json.data[idSlide].action[idAction].finalOrd = mouseY;
+            };
+
+            /*
+             * Sets a title for the presentation.
+             * @param String title Title to set.
+             */
+            this.setTitle = function (title) {
+                json.metaData.title = title;
+            };
+
+            /*
+             * Sets an annotation for the presentation.
+             * @param String annotation Annotation to set.
+             */
+            this.setAnnotation = function (annotation) {
+                json.metaData.annotation = annotation;
+            };
+
+            /*
+             * Inverts the two slides (their positions on the table).
+             * @param int idSlide1 Index of the first slide.
+             * @param int idSlide2 Index of the second slide.
+             */
+            this.invertSlides = function (idSlide1, idSlide2) {
+                var tmp = json.data[idSlide1];
+                json.data[idSlide1] = json.data[idSlide2];
+                json.data[idSlide2] = tmp;
+            };
+
+            /*
+             * Inverts the two actions (their positions on the table).
+             * @param int idSlide
+             * @param int idAction1
+             * @param int idAction2
+             */
+            this.invertActions = function (idSlide, idAction1, idAction2) {
+                var tmp = json.data[idSlide].action[idAction1];
+                json.data[idSlide].action[idAction1] = json.data[idSlide].action[idAction2];
+                json.data[idSlide].action[idAction2] = tmp;
+            };
+
+            /*
+             * Returns the actions on the specified slide.
+             * @returns {Array}
+             */
+            this.getActionList = function (idSlide) {
+                return json.data[idSlide].action;
+            };
+
+            /*
+             * Catches all the objects of the presentation
+             * @returns {Array} List of objects of the presentation
+             */
+            this.getObjectList = function () {
+                var objectList = new Array();
+                var indexSlide = 0;
+                while (json.data[indexSlide]) {
+                    var indexObject = 0;
+                    while (json.data[indexSlide].object[indexObject]) {
+                        objectList.push(json.data[indexSlide].object[indexObject]);
+                        indexObject++;
+                    }
+                    indexSlide++;
+                }
+                return objectList;
+            };
+
+            /*
+             * Returns an array containing all the background images.
+             * @returns {Array} List of background images.
+             */
+            this.getImageList = function () {
+                var list = new Array();
+                var indexSlide = 0;
+                while (json.data[indexSlide]) {
+                    list.push(json.data[indexSlide].object[0].img);
+                    indexSlide++;
+                }
+                ;
+                return list;
+            };
+
+            /*
+             * Returns an object containing the id of the first background.
+             * @returns {string} Id of the first background.
+             */
+            this.getInitialBackground = function () {
+                return json.data[0].object[0].id;
+            };
+
+            /*
+             * Returns an object containing the initial mouse position.
+             * @returns {object} Initial position of mouse.
+             */
+            this.getInitialMousePos = function () {
+                var pos = {};
+                pos.x = json.data[0].action[0].finalAbs;
+                pos.y = json.data[0].action[0].finalOrd;
+                return pos;
+            };
+
+            /*
+             * Removes the slide at the specified index.
+             * @param {int} idSlide
+             */
+            this.removeSlide = function (idSlide) {
+                json.data.splice(idSlide, 1);
+            };
+
+            /*
+             * Removes the action at the specified slide.
+             * @param {int} idSlide
+             * @param {int} idAction
+             */
+            this.removeAction = function (idSlide, idAction) {
+                json.data[idSlide].action.splice(idAction, 1);
+            };
+
+            /*
+             * Removes the specified object from the slide.
+             * Also removes all the actions attached to this object.
+             * @param {int} idSlide
+             * @param {int} idObject
+             */
+            this.removeObject = function (idSlide, idObject) {
+                var removed = json.data[idSlide].object.splice(idObject, 1);
+                for (var i = 0; i < json.data.length; i++) {
+                    var j = 0;
+                    while (json.data[i].action[j]) {
+                        if (json.data[i].action[j].target === removed.id) {
+                            json.data[i].action.splice(j, 1);
+                        } else {
+                            j++;
+                        }
+                    }
+                }
+            };
+
+            /*
+             * @returns {String}
+             */
+            this.getJson = function () {
+                return JSON.stringify(json, null, '    ');
+            };
+
+            /*
+             * @returns {String} returns the index for the next slide.
+             */
+            this.getNbSlide = function () {
+                return json.data.length;
+            };
+
+            /*
+             * @return {object} returns the object identified by idSlide
+             */
+            this.getSlide = function (idSlide) {
+                return json.data[idSlide];
+            };
+
+            /*
+             * Sets the size of images for the generated presentation.
+             * The parameters can be null : it means there are no
+             * requirement for this dimension.
+             * @param {int} width New width for the generated images.
+             * @param {int} height New height for the generated images.
+             */
+            this.setImageSizeRequirements = function (width, height) {
+                json.metaData.imageWidth = width;
+                json.metaData.imageHeight = height;
+            };
+
+            /*
+             * Gets the width of images for the generated presentation.
+             * @return {int or null} Width of generated images.
+             */
+            this.getImageWidth = function () {
+                return json.metaData.imageWidth;
+            };
+
+            /*
+             * Gets the height of images for the generated presentation.
+             * @return {int or null} Height of generated images.
+             */
+            this.getImageHeight = function () {
+                return json.metaData.imageHeight;
+            };
+
+            /*
+             * Gets a background image (no one in particular, the first met).
+             * @return {string} The name of a background image.
+             */
+            this.getABackgroundImage = function () {
+                for (var i = 0; i < json.data.length; i++) {
+                    for (var j = 0; j < json.data[i].object.length; j++) {
+                        if (json.data[i].object[j].type === "background") {
+                            return json.data[i].object[j].img;
+                        }
+                    }
+                }
+                return null;
+            };
+        };
+
+        /* public API */
+
+        self.version = "0.0.1";
+
+        self.createScreencastGenerator = function createScreencastGenerator() {
+            return new DahuScreencastGenerator();
+        };
+
+        self.createScreencastModel = function createScreencastModel() {
+            return new DahuScreencastModel();
+        };
+
+        self.createScreencastExecutableModel = function createScreencastExecutableModel() {
+            return new DahuScreencastExecutableModel();
+        };
+
+        self.createScreencastGeneratedModel = function createScreencastGeneratedModel() {
+            return new DahuScreencastGeneratedModel();
+        };
+
+        return self;
+    })();
+
+    window.dahuapp = dahuapp;
+
+})(window, jQuery);
\ No newline at end of file
diff --git a/TP/TP01/P01/dahuapp.viewer.css b/TP/TP01/P01/dahuapp.viewer.css
new file mode 100644
index 0000000000000000000000000000000000000000..ea934576dad6ca9263a6163de58f5fe4534d1f0b
--- /dev/null
+++ b/TP/TP01/P01/dahuapp.viewer.css
@@ -0,0 +1,25 @@
+.object-list {
+    position: absolute;
+    margin: 0px;
+    padding: 0px;
+}
+
+.mouse-cursor {
+    position: absolute;
+}
+
+.control {
+    position: relative;
+}
+
+.background {
+    position: absolute;
+}
+
+.tooltip {
+    position: absolute;
+    width: 100px;
+    padding: 4px;
+    margin: 2px;
+    border: 2px black solid;
+}
\ No newline at end of file
diff --git a/TP/TP01/P01/dahuapp.viewer.js b/TP/TP01/P01/dahuapp.viewer.js
new file mode 100644
index 0000000000000000000000000000000000000000..12ad03b2cdc503a8fd6ff3f5fdc2a74edbb36edb
--- /dev/null
+++ b/TP/TP01/P01/dahuapp.viewer.js
@@ -0,0 +1,456 @@
+"use strict";
+
+/**
+ * Dahuapp viewer module.
+ * 
+ * @param   dahuapp     dahuapp object to augment with module.
+ * @param   $           jQuery
+ * @returns dahuapp extended with viewer module.
+ */
+
+(function(dahuapp, $) {
+    var viewer = (function() {
+
+        var self = {};
+
+        var DahuViewerModel = function(select, getParams) {
+
+            /* Private API */
+
+            var json = null;
+            var selector = select;
+            var parseAutoOption = function(name, defaultValue) {
+                var res = getParams[name];
+                if (res == null) {
+                    if (name == 'auto') {
+                        return false;
+                    } else {
+                        return parseAutoOption('auto', defaultValue);
+                    }
+                }
+                if (res.toLowerCase() === 'false') {
+                    return false;
+                }
+                res = parseInt(res);
+                if (isNaN(res)) {
+                    // e.g. ?autoplay or ?autoplay=true
+                    return defaultValue;
+                }
+                return res;
+            }
+
+            /* Whether to wait for "next" event between actions */
+            var autoPlay = parseAutoOption('autoplay', 5000);
+            /* Whether to wait for "next" event on page load */
+            var autoStart = parseAutoOption('autostart', 5000);
+            /* Whether to loop back to start at the end of presentation */
+            var autoLoop = parseAutoOption('autoloop', 5000);
+
+            var events = (function() {
+                var self = {};
+                /*
+                 * Creates a generic event.
+                 */
+                var createEvent = function() {
+                    var callbacks = $.Callbacks();
+                    return {
+                        publish: callbacks.fire,
+                        subscribe: callbacks.add,
+                        unsubscribe: callbacks.remove,
+                        unsubscribeAll: callbacks.empty
+                    };
+                };
+                /*
+                 * Called when the button next is pressed.
+                 */
+                self.onNext = createEvent();
+                /*
+                 * Called when the button previous is pressed.
+                 */
+                self.onPrevious = createEvent();
+                /*
+                 * Called when an action is over.
+                 */
+                self.onActionOver = createEvent();
+                /*
+                 * Called when an action starts.
+                 */
+                self.onActionStart = createEvent();
+                /*
+                 * Called when at least one action was running and has finished.
+                 */
+                self.onAllActionFinish = createEvent();
+                
+                return self;
+            })();
+
+            /*
+             * Variables used like index for methodes subscribed.
+             */
+            var currentAction = 0;    /* Action currently running */
+            var nextAction = 0;       /* Action to execute on 'Next' click */
+            var nbActionsRunning = 0;
+            var previousAnchor = -1;  /* Last entered anchor, only used in
+                                       * case the 'onhashchange' event is
+                                       * not supported by the web browser */
+            
+            /*
+             * Functions to reinitialise running actions and subscribed
+             * callbacks (reinitialise is called once without stopping
+             * all the actions, so the two functions are separated).
+             */
+            var stopAllActions = function() {
+                $(selector + " .object-list").children().stop(true, true);
+                reinitialiseCallbackLists();
+                nbActionsRunning = 0;
+            };
+            var reinitialiseCallbackLists = function() {
+                events.onActionStart.unsubscribeAll();
+                events.onActionStart.subscribe(onActionStartEventHandler);
+                events.onAllActionFinish.unsubscribeAll();
+            };
+            
+            /*
+             * Timer used to program onNext event in autoplay mode.
+             */
+            var playTimer = 0;
+
+            /*
+             * Function used when an "onNextEvent" event is caught.
+             */
+            var onNextEventHandler = function() {
+                /*
+                 * If the user pressed "next" before an autoplay event
+                 * is triggered, it replaces the autoplay event, hence
+                 * cancels it:
+                 */
+                window.clearTimeout(playTimer);
+
+                enterAnimationMode();
+                stopAllActions();
+                var tmpAction = nextAction;
+                var onlyWithPrevious = true;
+                nextAction++;
+                currentAction = nextAction;
+                while (json.action[currentAction] && onlyWithPrevious) {
+                    switch (json.action[currentAction].trigger) {
+                        case 'onClick':
+                            nextAction = currentAction;
+                            onlyWithPrevious = false;
+                            break;
+                        case 'withPrevious':
+                            var tmp = currentAction;
+                            /*
+                             * We can't directly pass 'execute' as callback because we
+                             * allow the 'execute' function to reference a property of the
+                             * object (by using 'this.property') so we have to call the
+                             * function in the containing object to make that available
+                             */
+                            events.onActionStart.subscribe(function(events, selector) {
+                                json.action[tmp].execute(events, selector);
+                            });
+                            break;
+                        case 'afterPrevious':
+                            var tmp = currentAction;
+                            events.onAllActionFinish.subscribe(function(events, selector) {
+                                json.action[tmp].execute(events, selector);
+                            });
+                            while (json.action[nextAction] && json.action[nextAction].trigger !== 'onClick') {
+                                nextAction++;
+                            }
+                            onlyWithPrevious = false;
+                            break;
+                    }
+                    currentAction++;
+                }
+                if (json.action[tmpAction]) {
+                    launch(json.action[tmpAction]);
+                }
+                if (nextAction > json.action.length) {
+                    nextAction = json.action.length;
+                }
+            };
+            
+            /*
+             * Function used when an "onPreviousEvent" event is caught.
+             */
+            var onPreviousEventHandler = function() {
+                stopAllActions();
+                currentAction = nextAction - 1;
+                while (json.action[currentAction] && json.action[currentAction].trigger !== 'onClick') {
+                    launchReverse(json.action[currentAction]);
+                    currentAction--;
+                }
+                /* currentAction = -1 means that it's the beginning */
+                if (currentAction > -1) {
+                    launchReverse(json.action[currentAction]);
+                }
+                if (currentAction < 0) {
+                    currentAction = 0;
+                }
+                nextAction = currentAction;
+            };
+
+            /*
+             * Function used when an "onActionOverEvent" event is caught.
+             */
+            var onActionOverEventHandler = function() {
+                nbActionsRunning--;
+                if (nbActionsRunning === 0) {
+                    events.onAllActionFinish.publish(events, selector);
+                    reinitialiseCallbackLists();
+                    while (json.action[currentAction]) {
+                        switch (json.action[currentAction].trigger) {
+                            case 'onClick':
+                                leaveAnimationMode();
+                                if (autoPlay && nbActionsRunning === 0) {
+                                    playTimer = setTimeout(function () {
+                                        events.onNext.publish();
+                                    }, autoPlay);
+                                }
+                                return;
+                            case 'withPrevious':
+                                var tmp = currentAction;
+                                events.onActionStart.subscribe(function(events, selector) {
+                                    json.action[tmp].execute(events, selector);
+                                });
+                                break;
+                            case 'afterPrevious':
+                                var tmp = currentAction;
+                                events.onAllActionFinish.subscribe(function(events, selector) {
+                                    json.action[tmp].execute(events, selector);
+                                });
+                                currentAction++;
+                                return;
+                        }
+                        currentAction++;
+
+                    }
+                    if (autoLoop && nbActionsRunning === 0) {
+                        setTimeout(function () {
+                            resetPresentation();
+                            startPresentationMaybe();
+                        }, autoLoop);
+                    }
+                }
+                leaveAnimationMode();
+            };
+
+            /*
+             * Function used when an "onActionStartEvent" event is caught.
+             */
+            var onActionStartEventHandler = function() {
+                nbActionsRunning++;
+            };
+
+            /*
+             * Enter and leave "animation" mode. The viewer is in
+             * animation mode when something is going on without human
+             * interaction (i.e. executing actions before the next
+             * "onClick").
+             */
+            var enterAnimationMode = function () {
+                $(selector + ' .mouse-cursor-pause').hide();
+                $(selector + ' .mouse-cursor-normal').show();
+            };
+
+            var leaveAnimationMode = function () {
+                $(selector + ' .mouse-cursor-pause').show();
+                $(selector + ' .mouse-cursor-normal').hide();
+            };
+
+            /*
+             * Function used to perform actions.
+             */
+            var launch = function(action) {
+                action.execute(events, selector);
+            };
+            var launchReverse = function(action) {
+                action.executeReverse(selector);
+            };
+
+            /*
+             * The two following methods each returns an array containing the
+             * actions to execute to reach the given anchor (respectively
+             * forward or backwards).
+             *
+             * If the given anchor matches none of the actions, then an empty
+             * array is returned.
+             */
+            var getActionsToJumpForward = function(anchor) {
+                var actions = new Array();
+                for (var i = nextAction; i < json.action.length; i++) {
+                    // '==' and not '===' because we compare indifferently a
+                    // number or a string or anything else
+                    if (json.action[i].id == anchor) {
+                        return actions;
+                    } else {
+                        actions.push(json.action[i]);
+                    }
+                }
+                // Here, the anchor has not been found during the action scan
+                return null;
+            };
+            var getActionsToJumpBackwards = function(anchor) {
+                var actions = new Array();
+                for (var i = nextAction - 1; i >= 0; i--) {
+                    // '==' and not '===' because we compare indifferently a
+                    // number or a string or anything else
+                    actions.push(json.action[i]);
+                    if (json.action[i].id == anchor) {
+                        return actions;
+                    }
+                }
+                // Here, the anchor has not been found during the action scan
+                return null;
+            };
+            
+            /*
+             * Updates the position of the presentation depending on the
+             * given anchor (next action wanted).
+             */
+            var jumpToAnchor = function(anchor) {
+                if (anchor !== '') {
+                    stopAllActions();
+                    if (anchor > nextAction) {
+                        // forward
+                        var actions = getActionsToJumpForward(anchor);
+                        for (var i = 0; i < actions.length; i++) {
+                            actions[i].executeImmediately(selector);
+                        }
+                        nextAction += actions.length;
+                    } else {
+                        // backwards
+                        var actions = getActionsToJumpBackwards(anchor);
+                        for (var i = 0; i < actions.length; i++) {
+                            actions[i].executeReverse(selector);
+                        }
+                        nextAction -= actions.length;
+                    }
+                }
+            };
+
+            var resetPresentation = function() {
+                window.clearTimeout(playTimer);
+
+                currentAction = 0;
+                nextAction = 0;
+                nbActionsRunning = 0;
+
+                /*
+                 * At the beginning, the visible image is the first one of the presentation
+                 */
+                $(selector + " .object-list").children().hide();
+
+                $(selector + " ." + json.metaData.initialBackgroundId).show();
+
+                $(selector + " .mouse-cursor").css({
+                    'top': (json.metaData.initialMouseY * json.metaData.imageHeight) + "px",
+                    'left': (json.metaData.initialMouseX * json.metaData.imageWidth) + "px"
+                });
+
+                $(selector + " .mouse-cursor").show();
+            }
+
+            var startPresentationMaybe = function() {
+                if (autoStart) {
+                    playTimer = setTimeout(function () {
+                        events.onNext.publish();
+                    }, autoStart);
+                }
+            }
+
+            /* Public API */
+
+            this.loadUrl = function(url) {
+                var dataJson;
+                $.ajax({
+                    'async': false,
+                    'global': false,
+                    'url': url,
+                    'dataType': "json",
+                    'success': function(data) {
+                        dataJson = data;
+                    }
+                });
+                
+                load(dataJson);
+            };
+
+            this.load = function(dataJson) {
+                /* Transforms data JSON to executable functions for actions */
+                var execModel = dahuapp.createScreencastExecutableModel();
+                execModel.loadJson(dataJson);
+                json = execModel.getJson();
+            };
+
+            this.start = function() {
+                
+                /*
+                 * Subscription of methods to their events.
+                 */
+                events.onNext.subscribe(onNextEventHandler);
+                events.onPrevious.subscribe(onPreviousEventHandler);
+                events.onActionOver.subscribe(onActionOverEventHandler);
+                events.onActionStart.subscribe(onActionStartEventHandler);
+
+                resetPresentation();
+
+                /*
+                 * If an anchor has been specified, we place the presentation
+                 * in the right position.
+                 */
+                jumpToAnchor(window.location.hash.substring(1));
+                
+                /*
+                 * If the anchor changes during the presentation, then the
+                 * presentation is updated
+                 */
+                if ("onhashchange" in window) {
+                    // event supported
+                    window.onhashchange = function () {
+                        jumpToAnchor(window.location.hash.substring(1));
+                    };
+                } else {
+                    // event not supported : periodical check
+                    window.setInterval(function () {
+                        if (window.location.hash.substring(1) !== previousAnchor) {
+                            previousAnchor = window.location.hash.substring(1);
+                            jumpToAnchor(window.location.hash.substring(1));
+                        }
+                    }, 100);
+                }
+
+                /*
+                 * A click on the "next" button publishes a nextSlide event
+                 */
+                $(selector + " .next").click(function() {
+                    events.onNext.publish();
+                });
+                
+                /*
+                 * A click on the "previous" button publishes a previousSlide event
+                 */
+                $(selector + " .previous").click(function() {
+                    events.onPrevious.publish();
+                });
+
+                /*
+                 * Everything is ready, show the presentation
+                 * (hidden with style="display: none" from HTML).
+                 */
+                $("#loading").hide();
+                $(selector + " .object-list").show();
+                startPresentationMaybe();
+            };
+        };
+
+        self.createDahuViewer = function(selector, getParams) {
+            return new DahuViewerModel(selector, getParams);
+        };
+
+        return self;
+    })();
+
+    dahuapp.viewer = viewer;
+})(dahuapp || {}, jQuery);
diff --git a/TP/TP01/P01/img/04821769-3cf8-3d93-1512-390866bc0347.png b/TP/TP01/P01/img/04821769-3cf8-3d93-1512-390866bc0347.png
new file mode 100644
index 0000000000000000000000000000000000000000..638585ccbe338871616610bac3e6126333a49ca5
Binary files /dev/null and b/TP/TP01/P01/img/04821769-3cf8-3d93-1512-390866bc0347.png differ
diff --git a/TP/TP01/P01/img/38164498-3401-469c-2d51-be0716f10da6.png b/TP/TP01/P01/img/38164498-3401-469c-2d51-be0716f10da6.png
new file mode 100644
index 0000000000000000000000000000000000000000..9ae61b6ea710fb8f446e13fc59bbe16febe24ff3
Binary files /dev/null and b/TP/TP01/P01/img/38164498-3401-469c-2d51-be0716f10da6.png differ
diff --git a/TP/TP01/P01/img/474855e6-20f7-77f4-1868-0dba1ebf98f3.png b/TP/TP01/P01/img/474855e6-20f7-77f4-1868-0dba1ebf98f3.png
new file mode 100644
index 0000000000000000000000000000000000000000..9bc0da30a4dc38c1061bfe2fc177bc412a556dd3
Binary files /dev/null and b/TP/TP01/P01/img/474855e6-20f7-77f4-1868-0dba1ebf98f3.png differ
diff --git a/TP/TP01/P01/img/4e3c77eb-c2c2-46ed-31d5-e4f233330135.png b/TP/TP01/P01/img/4e3c77eb-c2c2-46ed-31d5-e4f233330135.png
new file mode 100644
index 0000000000000000000000000000000000000000..748fa1ed0aa6bfcfdd4a5d673037fb86eb4509b9
Binary files /dev/null and b/TP/TP01/P01/img/4e3c77eb-c2c2-46ed-31d5-e4f233330135.png differ
diff --git a/TP/TP01/P01/img/78f85c35-2c9b-3a8b-24e8-e6fce4becebd.png b/TP/TP01/P01/img/78f85c35-2c9b-3a8b-24e8-e6fce4becebd.png
new file mode 100644
index 0000000000000000000000000000000000000000..2b37c2a413fbb88036f6c5144bb8554e1c9563c8
Binary files /dev/null and b/TP/TP01/P01/img/78f85c35-2c9b-3a8b-24e8-e6fce4becebd.png differ
diff --git a/TP/TP01/P01/img/7beb5165-3202-df48-c03b-507674983fce.png b/TP/TP01/P01/img/7beb5165-3202-df48-c03b-507674983fce.png
new file mode 100644
index 0000000000000000000000000000000000000000..81b3b7681a25b9c4db17fd8d0010d532755a0dde
Binary files /dev/null and b/TP/TP01/P01/img/7beb5165-3202-df48-c03b-507674983fce.png differ
diff --git a/TP/TP01/P01/img/a30bf35e-dffb-6d60-d1dd-32bf5a9e9be2.png b/TP/TP01/P01/img/a30bf35e-dffb-6d60-d1dd-32bf5a9e9be2.png
new file mode 100644
index 0000000000000000000000000000000000000000..ece2cd5d966d8a7e82a34e91e79686900997be26
Binary files /dev/null and b/TP/TP01/P01/img/a30bf35e-dffb-6d60-d1dd-32bf5a9e9be2.png differ
diff --git a/TP/TP01/P01/img/bdfa5054-0bc1-d7be-5906-15afa7ed845a.png b/TP/TP01/P01/img/bdfa5054-0bc1-d7be-5906-15afa7ed845a.png
new file mode 100644
index 0000000000000000000000000000000000000000..57d10a733db5d4b140e16a1f4a80b9cfc827eafb
Binary files /dev/null and b/TP/TP01/P01/img/bdfa5054-0bc1-d7be-5906-15afa7ed845a.png differ
diff --git a/TP/TP01/P01/img/cc0bda09-26de-1127-e7fb-f8c52fb215a7.png b/TP/TP01/P01/img/cc0bda09-26de-1127-e7fb-f8c52fb215a7.png
new file mode 100644
index 0000000000000000000000000000000000000000..521e7c84a0e8faf1bf398337c57135eaee5deb03
Binary files /dev/null and b/TP/TP01/P01/img/cc0bda09-26de-1127-e7fb-f8c52fb215a7.png differ
diff --git a/TP/TP01/P01/img/cursor-pause.png b/TP/TP01/P01/img/cursor-pause.png
new file mode 100644
index 0000000000000000000000000000000000000000..9c93cc142f4326a188eafc3c0fe96f2975dcab0c
Binary files /dev/null and b/TP/TP01/P01/img/cursor-pause.png differ
diff --git a/TP/TP01/P01/img/cursor.png b/TP/TP01/P01/img/cursor.png
new file mode 100644
index 0000000000000000000000000000000000000000..5b4a20e63078ce18bb11a4e601a1994b261522ef
Binary files /dev/null and b/TP/TP01/P01/img/cursor.png differ
diff --git a/TP/TP01/P01/img/d32cc79e-d5bc-5af1-2e81-c555a9ac549e.png b/TP/TP01/P01/img/d32cc79e-d5bc-5af1-2e81-c555a9ac549e.png
new file mode 100644
index 0000000000000000000000000000000000000000..fea2ec88722c84a5cdfaf3b602621bec8e84ac80
Binary files /dev/null and b/TP/TP01/P01/img/d32cc79e-d5bc-5af1-2e81-c555a9ac549e.png differ
diff --git a/TP/TP01/P01/img/e3ab3ff3-9c0e-b7c9-6ebe-9b724e0b4020.png b/TP/TP01/P01/img/e3ab3ff3-9c0e-b7c9-6ebe-9b724e0b4020.png
new file mode 100644
index 0000000000000000000000000000000000000000..562d1f827f222ad0d464bb30fae72614b3a9dc33
Binary files /dev/null and b/TP/TP01/P01/img/e3ab3ff3-9c0e-b7c9-6ebe-9b724e0b4020.png differ
diff --git a/TP/TP01/P01/img/f94c5951-79d1-3d55-ce8b-e7de32fab48d.png b/TP/TP01/P01/img/f94c5951-79d1-3d55-ce8b-e7de32fab48d.png
new file mode 100644
index 0000000000000000000000000000000000000000..7f8f6e212789a4b95fd0d65cbf09d76a0ec36c69
Binary files /dev/null and b/TP/TP01/P01/img/f94c5951-79d1-3d55-ce8b-e7de32fab48d.png differ
diff --git a/TP/TP01/P01/index.html b/TP/TP01/P01/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..329efc825ad6bd6db7ba9920b8a3fcf67b0f8ba0
--- /dev/null
+++ b/TP/TP01/P01/index.html
@@ -0,0 +1,427 @@
+<!DOCTYPE html>
+
+<html lang="en">
+
+<head>
+<title>Dahu Presentation
+</title>
+<meta charset="utf-8">
+<script src="http://code.jquery.com/jquery-1.9.1.min.js" type="text/javascript">
+</script>
+<script src="parse-search.js" type="text/javascript">
+</script>
+
+<link rel="stylesheet" href="dahuapp.viewer.css"><style>.s0-o2{background-color:   #FFFFDD;width:  240px}
+.s1-o2{background-color:   #FFFFDD;width:  240px}
+.s2-o2{background-color:   #FFFFDD;width:  240px}
+.s3-o2{background-color:   #FFFFDD;width:  240px}
+.s4-o2{background-color:   #FFFFDD;width:  240px}
+.s5-o2{background-color:   #FFFFDD;width:  240px}
+.s6-o2{background-color:   #FFFFDD;width:  240px}
+.s7-o2{background-color:   #FFFFDD;width:  240px}
+.s8-o2{background-color:   #FFFFDD;width:  240px}
+.s9-o2{background-color:   #FFFFDD;width:  240px}
+.s10-o2{background-color:   #FFFFDD;width:  240px}
+.s11-o2{background-color:   #FFFFDD;width:  240px}
+</style>
+</head>
+
+<body>
+
+<div id="my-dahu-presentation">
+
+<div id="loading">Loading presentation...
+</div>
+
+<div class="object-list" style="display: none">
+<img src="img/78f85c35-2c9b-3a8b-24e8-e6fce4becebd.png" alt="img/78f85c35-2c9b-3a8b-24e8-e6fce4becebd.png" class="background 78f85c35-2c9b-3a8b-24e8-e6fce4becebd0">
+
+<div class="tooltip s0-o2">Démarrer l'application MatLab avec la commande usuelle. Vérifier qu'il s'agit d'une version supérieure à 2012a.
+</div>
+<img src="img/e3ab3ff3-9c0e-b7c9-6ebe-9b724e0b4020.png" alt="img/e3ab3ff3-9c0e-b7c9-6ebe-9b724e0b4020.png" class="background e3ab3ff3-9c0e-b7c9-6ebe-9b724e0b40200">
+
+<div class="tooltip s1-o2">Créer un modèle Simulink depuis le menu New et l'entrée Simulink Model.
+</div>
+<img src="img/4e3c77eb-c2c2-46ed-31d5-e4f233330135.png" alt="img/4e3c77eb-c2c2-46ed-31d5-e4f233330135.png" class="background 4e3c77eb-c2c2-46ed-31d5-e4f2333301350">
+
+<div class="tooltip s2-o2">Voici un modèle Simulink vide.
+</div>
+<img src="img/d32cc79e-d5bc-5af1-2e81-c555a9ac549e.png" alt="img/d32cc79e-d5bc-5af1-2e81-c555a9ac549e.png" class="background d32cc79e-d5bc-5af1-2e81-c555a9ac549e0">
+
+<div class="tooltip s3-o2">Ouvrir la boite à outils Simulink.
+</div>
+<img src="img/f94c5951-79d1-3d55-ce8b-e7de32fab48d.png" alt="img/f94c5951-79d1-3d55-ce8b-e7de32fab48d.png" class="background f94c5951-79d1-3d55-ce8b-e7de32fab48d0">
+
+<div class="tooltip s4-o2">Ouvrir l'onglet Source.
+</div>
+<img src="img/38164498-3401-469c-2d51-be0716f10da6.png" alt="img/38164498-3401-469c-2d51-be0716f10da6.png" class="background 38164498-3401-469c-2d51-be0716f10da60">
+
+<div class="tooltip s5-o2">Sélectionner le bloc Générateur de Sinusoïde.
+</div>
+<img src="img/a30bf35e-dffb-6d60-d1dd-32bf5a9e9be2.png" alt="img/a30bf35e-dffb-6d60-d1dd-32bf5a9e9be2.png" class="background a30bf35e-dffb-6d60-d1dd-32bf5a9e9be20">
+
+<div class="tooltip s6-o2">Déposer un bloc Générateur de Sinusoïde dans votre modèle.
+</div>
+<img src="img/cc0bda09-26de-1127-e7fb-f8c52fb215a7.png" alt="img/cc0bda09-26de-1127-e7fb-f8c52fb215a7.png" class="background cc0bda09-26de-1127-e7fb-f8c52fb215a70">
+
+<div class="tooltip s7-o2">Ouvrir l'onglet Sink (puit de données).
+</div>
+<img src="img/bdfa5054-0bc1-d7be-5906-15afa7ed845a.png" alt="img/bdfa5054-0bc1-d7be-5906-15afa7ed845a.png" class="background bdfa5054-0bc1-d7be-5906-15afa7ed845a0">
+
+<div class="tooltip s8-o2">Sélectionner le bloc Oscilloscope.
+</div>
+<img src="img/474855e6-20f7-77f4-1868-0dba1ebf98f3.png" alt="img/474855e6-20f7-77f4-1868-0dba1ebf98f3.png" class="background 474855e6-20f7-77f4-1868-0dba1ebf98f30">
+
+<div class="tooltip s9-o2">Déposer un bloc Oscilloscope dans votre modèle.
+</div>
+<img src="img/04821769-3cf8-3d93-1512-390866bc0347.png" alt="img/04821769-3cf8-3d93-1512-390866bc0347.png" class="background 04821769-3cf8-3d93-1512-390866bc03470">
+
+<div class="tooltip s10-o2">Connecter l'entrée du bloc Oscilloscope avec la sortie du bloc Générateur de Sinusoïde.
+</div>
+<img src="img/7beb5165-3202-df48-c03b-507674983fce.png" alt="img/7beb5165-3202-df48-c03b-507674983fce.png" class="background 7beb5165-3202-df48-c03b-507674983fce0">
+
+<div class="tooltip s11-o2">Sauvegarder votre modèle.
+</div>
+
+<div class="mouse-cursor">
+<img src="img/cursor.png" alt="img/cursor.png" class="mouse-cursor-normal">
+<img src="img/cursor-pause.png" alt="img/cursor-pause.png" style="display: none" class="mouse-cursor-pause">
+</div>
+</div>
+
+<div class="control" style="top: 616px;">
+<button class="previous">Previous
+</button>
+<button class="next">Next
+</button>
+</div>
+</div>
+<script src="dahuapp.js" type="text/javascript">
+</script>
+<script src="dahuapp.viewer.js" type="text/javascript">
+</script>
+<script type="text/javascript">(function($) {
+    var myPresentation = dahuapp.viewer.createDahuViewer("#my-dahu-presentation", window.getParams);
+    myPresentation.load({
+    "metaData": {
+        "imageWidth": 800,
+        "imageHeight": 600,
+        "initialBackgroundId": "78f85c35-2c9b-3a8b-24e8-e6fce4becebd0",
+        "initialMouseX": 0.33541666666666664,
+        "initialMouseY": 0.4444444444444444
+    },
+    "action": [
+        {
+            "id": "25",
+            "type": "appear",
+            "target": "s0-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.305,
+            "ord": 0.1625,
+            "duration": 400
+        },
+        {
+            "id": "3",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.06666666666666667,
+            "finalOrd": 0.4766666666666667,
+            "speed": 0.8
+        },
+        {
+            "id": "4",
+            "type": "appear",
+            "target": "e3ab3ff3-9c0e-b7c9-6ebe-9b724e0b40200",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "26",
+            "type": "appear",
+            "target": "s1-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.17666666666666667,
+            "ord": 0.1525,
+            "duration": 400
+        },
+        {
+            "id": "5",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.23333333333333334,
+            "finalOrd": 0.29444444444444445,
+            "speed": 0.8
+        },
+        {
+            "id": "6",
+            "type": "appear",
+            "target": "4e3c77eb-c2c2-46ed-31d5-e4f2333301350",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "27",
+            "type": "appear",
+            "target": "s2-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.25,
+            "ord": 0.1575,
+            "duration": 400
+        },
+        {
+            "id": "7",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.175,
+            "finalOrd": 0.09666666666666666,
+            "speed": 0.8
+        },
+        {
+            "id": "8",
+            "type": "appear",
+            "target": "d32cc79e-d5bc-5af1-2e81-c555a9ac549e0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "28",
+            "type": "appear",
+            "target": "s3-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.2,
+            "ord": 0.0875,
+            "duration": 400
+        },
+        {
+            "id": "9",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.8951388888888889,
+            "finalOrd": 0.4888888888888889,
+            "speed": 0.8
+        },
+        {
+            "id": "10",
+            "type": "appear",
+            "target": "f94c5951-79d1-3d55-ce8b-e7de32fab48d0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "29",
+            "type": "appear",
+            "target": "s4-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.42,
+            "ord": 0.26375,
+            "duration": 400
+        },
+        {
+            "id": "11",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.7805555555555556,
+            "finalOrd": 0.6944444444444444,
+            "speed": 0.8
+        },
+        {
+            "id": "12",
+            "type": "appear",
+            "target": "38164498-3401-469c-2d51-be0716f10da60",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "30",
+            "type": "appear",
+            "target": "s5-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.34833333333333333,
+            "ord": 0.32125,
+            "duration": 400
+        },
+        {
+            "id": "13",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.19027777777777777,
+            "finalOrd": 0.26,
+            "speed": 0.8
+        },
+        {
+            "id": "14",
+            "type": "appear",
+            "target": "a30bf35e-dffb-6d60-d1dd-32bf5a9e9be20",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "31",
+            "type": "appear",
+            "target": "s6-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.165,
+            "ord": 0.1475,
+            "duration": 400
+        },
+        {
+            "id": "15",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.6513888888888889,
+            "finalOrd": 0.4033333333333333,
+            "speed": 0.8
+        },
+        {
+            "id": "16",
+            "type": "appear",
+            "target": "cc0bda09-26de-1127-e7fb-f8c52fb215a70",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "33",
+            "type": "appear",
+            "target": "s7-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.38666666666666666,
+            "ord": 0.19625,
+            "duration": 400
+        },
+        {
+            "id": "17",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.9,
+            "finalOrd": 0.21666666666666667,
+            "speed": 0.8
+        },
+        {
+            "id": "18",
+            "type": "appear",
+            "target": "bdfa5054-0bc1-d7be-5906-15afa7ed845a0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "32",
+            "type": "appear",
+            "target": "s8-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.3516666666666667,
+            "ord": 0.2725,
+            "duration": 400
+        },
+        {
+            "id": "19",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.31666666666666665,
+            "finalOrd": 0.2611111111111111,
+            "speed": 0.8
+        },
+        {
+            "id": "20",
+            "type": "appear",
+            "target": "474855e6-20f7-77f4-1868-0dba1ebf98f30",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "34",
+            "type": "appear",
+            "target": "s9-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.10833333333333334,
+            "ord": 0.15,
+            "duration": 400
+        },
+        {
+            "id": "21",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.20347222222222222,
+            "finalOrd": 0.26666666666666666,
+            "speed": 0.8
+        },
+        {
+            "id": "22",
+            "type": "appear",
+            "target": "04821769-3cf8-3d93-1512-390866bc03470",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "35",
+            "type": "appear",
+            "target": "s10-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.17,
+            "ord": 0.14625,
+            "duration": 400
+        },
+        {
+            "id": "23",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.03819444444444445,
+            "finalOrd": 0.16666666666666666,
+            "speed": 0.8
+        },
+        {
+            "id": "24",
+            "type": "appear",
+            "target": "7beb5165-3202-df48-c03b-507674983fce0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "36",
+            "type": "appear",
+            "target": "s11-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.195,
+            "ord": 0.145,
+            "duration": 400
+        }
+    ]
+});
+    myPresentation.start();
+})(jQuery);
+
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/TP/TP01/P01/parse-search.js b/TP/TP01/P01/parse-search.js
new file mode 100644
index 0000000000000000000000000000000000000000..e7d01c07f6a5d45b9fcabd5d271a8c6cfc5f00e2
--- /dev/null
+++ b/TP/TP01/P01/parse-search.js
@@ -0,0 +1,14 @@
+// Adapted from http://stackoverflow.com/questions/3234125/creating-array-from-window-location-hash
+(function () {
+    var search = window.location.search.slice(1);
+    var array = search.split("&");
+    
+    var values, form_data = {};
+    
+    for (var i = 0; i < array.length; i += 1) {
+	values = array[i].split("=");
+	form_data[values[0]] = unescape(values[1]);
+    }
+    window.getParams = form_data;
+}())
+
diff --git a/TP/TP01/P02/dahuapp.js b/TP/TP01/P02/dahuapp.js
new file mode 100644
index 0000000000000000000000000000000000000000..41c69e9169345a9a6703325d5557a4cc36913e19
--- /dev/null
+++ b/TP/TP01/P02/dahuapp.js
@@ -0,0 +1,880 @@
+"use strict";
+
+/**
+ * Dahuapp core module.
+ *
+ * @param   window      window javascript object.
+ * @param   $           jQuery
+ * @returns dahuapp core module.
+ */
+(function (window, $) {
+    var dahuapp = (function () {
+
+        var self = {};
+
+        /* private API */
+
+        var DahuScreencastGenerator = function () {
+
+            /*
+             * Format the specified string in a readable format.
+             */
+            function htmlFormat(htmlString) {
+                var i;
+                var readableHTML = htmlString;
+                var lb = '\n';
+                var htags = ["<html", "</html>", "</head>", "<title", "</title>", "<meta", "<link", "</body>"];
+                for (i = 0; i < htags.length; ++i) {
+                    var hhh = htags[i];
+                    readableHTML = readableHTML.replace(new RegExp(hhh, 'gi'), lb + hhh);
+                }
+                var btags = ["</div>", "</section>", "</span>", "<br>", "<br />", "<blockquote", "</blockquote>", "<ul", "</ul>", "<ol", "</ol>", "<li", "<\!--", "<script", "</script>"];
+                for (i = 0; i < btags.length; ++i) {
+                    var bbb = btags[i];
+                    readableHTML = readableHTML.replace(new RegExp(bbb, 'gi'), lb + bbb);
+                }
+                var ftags = ["<img", "<legend", "</legend>", "<button", "</button>"];
+                for (i = 0; i < ftags.length; ++i) {
+                    var fff = ftags[i];
+                    readableHTML = readableHTML.replace(new RegExp(fff, 'gi'), lb + fff);
+                }
+                var xtags = ["<body", "<head", "<div", "<section", "<span", "<p"];
+                for (i = 0; i < xtags.length; ++i) {
+                    var xxx = xtags[i];
+                    readableHTML = readableHTML.replace(new RegExp(xxx, 'gi'), lb + lb + xxx);
+                }
+                return readableHTML;
+            }
+
+            /*
+             * Generates the html header.
+             */
+            var generateHtmlHeader = function ($generated, cssGen) {
+                $('head', $generated)
+                    .append($(document.createElement('title'))
+                        .append("Dahu Presentation"))/* TODO: make this customizable */
+                    .append($(document.createElement('meta'))
+                        .attr({'charset': 'utf-8'}))
+                    .append($(document.createElement('script'))
+                        .attr({'src': 'http://code.jquery.com/jquery-1.9.1.min.js'})
+                        .attr({'type': 'text/javascript'}))
+                    .append($(document.createElement('script'))
+                        .attr({'src': 'parse-search.js'})
+                        .attr({'type': 'text/javascript'}))
+                    .append($(document.createElement('link'))
+                        .attr({'rel': 'stylesheet', 'href': 'dahuapp.viewer.css'}))
+                    .append($(document.createElement('style'))
+                        .append(cssGen));
+            };
+
+            /*
+             * Generates the objects.
+             */
+
+            var generateHtmlBackgroundImage = function ($generated, object) {
+                $('.object-list', $generated)
+                    .append($(document.createElement('img'))
+                        .attr({'src': object.img, 'alt': object.img, 'class': 'background ' + object.id}));
+            };
+            var generateHtmlTooltip = function ($generated, object) {
+                $('.object-list', $generated)
+                    .append($(document.createElement('div'))
+                        .attr({'class': 'tooltip ' + object.id})
+                        .append(object.text));
+            };
+
+            var generateCssTooltip = function ($generated, object) {
+                var style = [];
+
+                if( object.color != null ) {
+                    style.push('background-color:   ' + object.color);
+                }
+                if( object.width != null ) {
+                    style.push('width:  ' + object.width);
+                }
+
+                if( style.length != 0 ) {
+                    $generated.append(
+                    '.' + object.id + '{' + style.join(';') + '}\n');
+                }
+
+                return $generated;
+            };
+
+            /*
+             * Returns the code used to call the viewer to the presentation.
+             */
+            var getBasicCallCode = function (jsonModel) {
+                var code = '(function($) {\n';
+                code += '    var myPresentation = dahuapp.viewer.createDahuViewer("#my-dahu-presentation", window.getParams);\n';
+                code += '    myPresentation.load(' + jsonModel + ');\n';
+                code += '    myPresentation.start();\n';
+                code += '})(jQuery);\n';
+                return code;
+            };
+
+            /*
+             * Generates the html body.
+             */
+            var generateHtmlBody = function ($generated, jsonModel, jsonGen) {
+                $('body', $generated)
+                    /* We could use a <section> here too, but it does not work with MS IE 8.
+                     Alternatively, adding this in the header would work:
+
+                     <!--[if lt IE 9]>
+                     <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
+                     <![endif]-->
+                     */
+                    .append($(document.createElement('div'))
+                        .attr({'id': 'my-dahu-presentation'}))
+                    .append($(document.createElement('script'))
+                        .attr({'src': 'dahuapp.js'})
+                        .attr({'type': 'text/javascript'}))
+                    .append($(document.createElement('script'))
+                        .attr({'src': 'dahuapp.viewer.js'})
+                        .attr({'type': 'text/javascript'}))
+                    .append($(document.createElement('script'))
+                        .attr({'type': 'text/javascript'})
+                        .append(getBasicCallCode(jsonGen)));
+                $('#my-dahu-presentation', $generated)
+                    .append($(document.createElement('div'))
+                        .attr({'id': 'loading'}).append("Loading presentation..."))
+                    .append($(document.createElement('div'))
+                        .attr({'class': 'object-list',
+                            'style': 'display: none'}))
+                    .append($(document.createElement('div'))
+                        .attr({'class': 'control'}));
+
+                /* Adding the objects to the page */
+                $.each(jsonModel.getObjectList(), function (id, object) {
+                    switch (object.type) {
+                        case "background":
+                            generateHtmlBackgroundImage($generated, object);
+                            break;
+                        case "tooltip":
+                            generateHtmlTooltip($generated, object);
+                            break;
+                        /* no mouse image generated here */
+                    }
+                });
+
+                /* Warning here, i'm not sure if $.each is always over, here */
+
+                /* Adding the control buttons to the page */
+                $('.control', $generated)
+                    .css({'top': (jsonModel.getImageHeight() + 16) + 'px'})
+                    .append($(document.createElement('button'))
+                        .attr({'class': 'previous'})
+                        .append('Previous'))
+                    .append($(document.createElement('button'))
+                        .attr({'class': 'next'})
+                        .append('Next'));
+
+                /* Adding the mouse cursor image */
+                $('.object-list', $generated)
+                    .append($(document.createElement('div'))
+                        .attr({'class': 'mouse-cursor'})
+                        .append($(document.createElement('img'))
+                            .attr({'src': 'img/cursor.png', 'alt': 'img/cursor.png', 'class': 'mouse-cursor-normal'}))
+                        .append($(document.createElement('img'))
+                            .attr({'src': 'img/cursor-pause.png', 'alt': 'img/cursor-pause.png',
+                                'style': 'display: none', 'class': 'mouse-cursor-pause'})));
+            };
+
+            /*
+             * Generates the css String with the Json model.
+             */
+            this.generateCssString = function (jsonModel) {
+                var $generated = $('<style></style>');
+
+                /* Going through each object */
+                $.each(jsonModel.getObjectList(), function (id, object) {
+                    switch (object.type) {
+                        case "tooltip":
+                            generateCssTooltip($generated, object);
+                            break;
+                        /* no mouse image generated here */
+                    }
+                });
+
+                return $generated.text();
+            }
+
+            /*
+             * Generates the html String with the Json model.
+             */
+            this.generateHtmlString = function (jsonModel, jsonGen, cssGen) {
+                /* Initialising the compilation area */
+                var $generated = $(document.createElement('div'));
+
+                /* We create the html using the json */
+                $generated.append($(document.createElement('html'))
+                    .attr({'lang': 'en'}));
+                $('html', $generated)
+                    .append($(document.createElement('head')))
+                    .append($(document.createElement('body')));
+
+                generateHtmlHeader($generated, cssGen);
+                generateHtmlBody($generated, jsonModel, jsonGen);
+
+                var result = htmlFormat($generated.html());
+
+                return '<!DOCTYPE html>\n' + result;
+            };
+
+            /*
+             * Generates the generated JSON using the JSONmodel.
+             * @param {object} jsonModel The json model to transform.
+             * @param {java.awt.Dimension} imgDim The dimension of images.
+             */
+            this.generateJsonString = function (jsonModel, imgDim) {
+                var generated = self.createScreencastGeneratedModel();
+                generated.setImageSize(imgDim.width, imgDim.height);
+                generated.setInitialBackground(jsonModel.getInitialBackground());
+                generated.setInitialMousePos(jsonModel.getInitialMousePos());
+                for (var i = 0; i < jsonModel.getNbSlide(); i++) {
+                    var actionList = jsonModel.getActionList(i);
+                    for (var j = 0; j < actionList.length; j++) {
+                        /* 
+                         * We don't add the two first actions of the first slide
+                         * because they are present in the presentation metadata.
+                         * It corresponds to the mouse initial pos and first background.
+                         */
+                        if (i > 0 || j > 1) {
+                            generated.addAction(actionList[j], imgDim.width, imgDim.height);
+                        }
+                    }
+                }
+                return generated.getJson();
+            };
+        };
+
+        /*
+         * Model for a JSON object that will only be used by the viewer, never
+         * saved on a file, used to transform the properties of actions
+         * specified on the JSON file of a presentation to executable
+         * functions for each action.
+         */
+        var DahuScreencastExecutableModel = function () {
+
+            /* Private API */
+
+            var json = {
+                metaData: {},
+                action: new Array()
+            };
+
+            /*
+             * Creates a functions representing the specified action, and adds
+             * it to this object.
+             * @param {object} action
+             */
+            var addExecutableAction = function (action) {
+                var executableAction = {
+                    'id': action.id,
+                    'trigger': action.trigger,
+                    'target': action.target,
+                    'delayAfter': action.delayAfter,
+                    'doneFunction': function (events, selector) {
+                        setTimeout(function () {
+                            events.onActionOver.publish(events, selector);
+                        }, this.delayAfter);
+                    }
+                };
+                if (executableAction.delayAfter == null) {
+                    executableAction.delayAfter = 200;
+                }
+                switch (action.type.toLowerCase()) {
+                    case "appear":
+                        executableAction.abs = (action.abs * json.metaData.imageWidth) + 'px';
+                        executableAction.ord = (action.ord * json.metaData.imageHeight) + 'px';
+                        executableAction.duration = action.duration;
+                        executableAction.execute = function (events, selector) {
+                            events.onActionStart.publish(events, selector);
+                            var sel = selector + ' .' + this.target;
+                            $(sel).css({
+                                'left': this.abs,
+                                'top': this.ord
+                            });
+                            $(sel).show(this.duration, function () {
+                                executableAction.doneFunction(events, selector);
+                            });
+                        };
+                        executableAction.executeReverse = function (selector) {
+                            $(selector + ' .' + this.target).hide();
+                        };
+                        executableAction.executeImmediately = function (selector) {
+                            var sel = selector + ' .' + this.target;
+                            $(sel).css({
+                                'left': this.abs,
+                                'top': this.ord
+                            });
+                            $(sel).show();
+                        };
+                        break;
+                    case "disappear":
+                        executableAction.duration = action.duration;
+                        executableAction.execute = function (events, selector) {
+                            events.onActionStart.publish(events, selector);
+                            $(selector + ' .' + this.target).hide(this.duration, function () {
+                                executableAction.doneFunction(events, selector);
+                            });
+                        };
+                        executableAction.executeReverse = function (selector) {
+                            $(selector + ' .' + this.target).show();
+                        };
+                        executableAction.executeImmediately = function (selector) {
+                            $(selector + ' .' + this.target).hide();
+                        };
+                        break;
+                    case "move":
+                        executableAction.finalAbs = (action.finalAbs * json.metaData.imageWidth) + 'px';
+                        executableAction.finalOrd = (action.finalOrd * json.metaData.imageHeight) + 'px';
+                        executableAction.duration = action.duration;
+                        executableAction.speed = action.speed;
+                        executableAction.execute = function (events, selector) {
+                            events.onActionStart.publish(events, selector);
+                            var sel = selector + ' .' + this.target;
+                            this.initialAbs = $(sel).css('left');
+                            this.initialOrd = $(sel).css('top');
+                            if (this.duration == null) {
+                                var initialAbsPix = this.initialAbs.replace('px', '');
+                                var initialOrdPix = this.initialOrd.replace('px', '');
+                                var finalAbsPix = this.finalAbs.replace('px', '');
+                                var finalOrdPix = this.finalOrd.replace('px', '');
+                                var distance = Math.sqrt(Math.pow(finalAbsPix - initialAbsPix, 2) +
+                                    Math.pow(finalOrdPix - initialOrdPix, 2));
+                                if (!this.speed) {
+                                    this.speed = .8; // in pixel per milisecond: fast, but not too much.
+                                }
+                                this.duration = distance + this.speed;
+                                if (this.duration < 200) { // Slow down a bit for short distances.
+                                    this.duration = 200;
+                                }
+                            }
+                            $(sel).animate({
+                                'left': this.finalAbs,
+                                'top': this.finalOrd
+                            }, this.duration, 'linear', function () {
+                                executableAction.doneFunction(events, selector);
+                            });
+                        };
+                        executableAction.executeReverse = function (selector) {
+                            $(selector + ' .' + this.target).css({
+                                'left': this.initialAbs,
+                                'top': this.initialOrd
+                            });
+                        };
+                        executableAction.executeImmediately = function (selector) {
+                            var sel = selector + ' .' + this.target;
+                            this.initialAbs = $(sel).css('left');
+                            this.initialOrd = $(sel).css('top');
+                            $(sel).css({
+                                'left': this.finalAbs,
+                                'top': this.finalOrd
+                            });
+                        };
+                        break;
+                    case "delay":
+                        executableAction.duration = action.duration;
+                        executableAction.execute = function (events, selector) {
+                            events.onActionStart.publish(events, selector);
+                            setTimeout(function () {
+                                events.onActionOver.publish(events, selector);
+                            }, this.duration);
+                        };
+                        executableAction.executeReverse = function (selector) {
+                            // Nothing!
+                        };
+                        executableAction.executeImmediately = function (selector) {
+                            // Nothing too!
+                        };
+                        break;
+                }
+                json.action.push(executableAction);
+            };
+
+            /* Public API */
+
+            /*
+             * Loads the specified JSON read from a 'presentation.json' file,
+             * and stores the functions representing each actions in an object.
+             * @param {object} jsonToLoad
+             */
+            this.loadJson = function (jsonToLoad) {
+                json.metaData = jsonToLoad.metaData;
+                for (var i = 0; i < jsonToLoad.action.length; i++) {
+                    addExecutableAction(jsonToLoad.action[i]);
+                }
+            };
+
+            /*
+             * Returns the object containing the functions.
+             */
+            this.getJson = function () {
+                return json;
+            };
+        };
+
+        /*
+         * Used to transform the 'dahu' file to a JSON file used by the viewer,
+         * which only contains some metaData and a list of actions.
+         */
+        var DahuScreencastGeneratedModel = function () {
+
+            /* Private API */
+
+            var json = {
+                metaData: {},
+                action: new Array()
+            };
+
+            /* Public API */
+
+            /*
+             * Returns a string representation of this json.
+             * @returns {String}
+             */
+            this.getJson = function () {
+                return JSON.stringify(json, null, '    ');
+            };
+
+            /*
+             * Setters for the generated json metadata.
+             */
+            this.setImageSize = function (width, height) {
+                json.metaData.imageWidth = width;
+                json.metaData.imageHeight = height;
+            };
+
+            this.setInitialMousePos = function (pos) {
+                json.metaData.initialMouseX = pos.x;
+                json.metaData.initialMouseY = pos.y;
+            };
+
+            this.setInitialBackground = function (id) {
+                json.metaData.initialBackgroundId = id;
+            };
+
+            this.addAction = function (action) {
+                json.action.push(action);
+            };
+
+            /*
+             * Transforms a JSON containing action properties to a JSON containing
+             * the execution functions.
+             * @param {Object} json
+             * @returns {Object} A json object containing the execution functions
+             * for all the actions of the presentation.
+             */
+            this.toExecutableList = function (json) {
+                var executableList = {
+                    metaData: json.metaData,
+                    action: new Array()
+                };
+                for (var i = 0; i < json.action.length; i++) {
+                    addExecutableAction(executableList, json.action[i]);
+                }
+                return executableList;
+            };
+        };
+
+        /*
+         * Represents a 'dahu' file for a project.
+         */
+        var DahuScreencastModel = function () {
+
+            /* Private API */
+
+            var json = {};
+
+            /*
+             * Generates a unique action ID.
+             *
+             * With this implementation, this ID is unique as long as the user
+             * doesn't replace it manually with a not kind value or replace
+             * the nextUniqueId with bad intentions.
+             * But maybe that a UUID is a bit tiresome to put as an anchor...
+             */
+            var generateUniqueActionId = function () {
+                json.metaData.nextUniqueId++;
+                return json.metaData.nextUniqueId.toString();
+            };
+
+            /*
+             * This method checks if the json representing a dahu project is
+             * in the good version. It's in case a project file was created
+             * with a previous version of Dahu, and that some fields are
+             * missing.
+             *
+             * It doesn't control each field (at the moment) but only the
+             * fields that can be missing due to a new Dahu version and not
+             * to a manual editing.
+             *
+             * This method doesn't check any Json syntax or something like that.
+             */
+            var upgradeJsonVersion = function () {
+                // Checks if unique IDs are in the project file
+                if (!json.metaData.nextUniqueId) {
+                    var currentId = 0;
+                    for (var i = 0; i < json.data.length; i++) {
+                        for (var j = 0; j < json.data[i].action.length; j++) {
+                            json.data[i].action[j].id = currentId.toString();
+                            currentId++;
+                        }
+                    }
+                    json.metaData.nextUniqueId = currentId;
+                }
+            };
+
+            /* Public API */
+
+            /*
+             * json variable generated from a JSON file.
+             * @param String stringJson String loaded from JSON file.
+             */
+            this.loadJson = function (stringJson) {
+                json = JSON.parse(stringJson);
+                upgradeJsonVersion();
+            };
+
+            /*
+             * Create a new presentation variable in the JSON file which will contain slides.
+             */
+            this.createPresentation = function (width, height) {
+                json.metaData = {};
+                json.metaData.imageWidth = width;
+                json.metaData.imageHeight = height;
+                json.metaData.nextUniqueId = 0;
+                json.data = new Array();
+            };
+
+            /*
+             * Add a new slide in the presentation variable of the JSON file.
+             * @param int index wanted for the slide.
+             * @param String idSlide Unique identifier for the slide.
+             * @param String img Related to pathname of the image.
+             * @param double mouseX Abscissa mouse position in %.
+             * @param double mouseY Ordinate mouse position in %.
+             * @return Index of the newly added slide.
+             */
+            this.addSlide = function (indexSlide, idSlide, img, mouseX, mouseY, speed) {
+                var slide = {
+                    "object": new Array(),
+                    "action": new Array()
+                };
+                json.data.splice(indexSlide, 0, slide);
+                this.addObject(indexSlide, "background", idSlide, img);
+                this.addObject(indexSlide, "mouse");
+                this.addAction(indexSlide, "move", json.data[indexSlide].object[1].id, "onClick", mouseX, mouseY, speed);
+                this.addAction(indexSlide, "appear", json.data[indexSlide].object[0].id, "afterPrevious");
+            };
+
+            /*
+             * Object factory.
+             * Add a new Object in the slide idSlide.
+             * @param int idSlide
+             * @param string type
+             * Other params can be specified depending on the object's type.
+             */
+            this.addObject = function (idSlide, type) {
+				var object = {
+                    "type": type
+                };
+                switch (type.toLowerCase()) {
+                    case "background":
+                        object.id = arguments[2] + json.data[idSlide].object.length;
+                        object.img = arguments[3] || "";
+                        break;
+                    case "mouse":
+                        object.id = "mouse-cursor";
+                        break;
+                    case "tooltip":
+                        /*
+                         * TODO: we'll need a more robust unique name
+                         * when we start actually using this.
+                         */
+                        object.id = "s" + idSlide + "-o" + json.data[idSlide].object.length;
+                        object.text = arguments[2] || "";
+                        object.color = arguments[3] || null;
+						object.width = arguments[4] || "";
+						json.data[idSlide].object.push(object);
+						var objectLength = json.data[idSlide].object.length;
+						var last = json.data[idSlide].object[objectLength-1];
+						json.data[idSlide].object[objectLength-1] = 
+								json.data[idSlide].object[objectLength-2];
+						json.data[idSlide].object[objectLength-2] = last;
+                        break;
+                }
+				if(type.toLowerCase() != "tooltip"){
+					json.data[idSlide].object.push(object);
+				}
+            };
+
+            /*
+             * Action factory.
+             * Add a new Action in the slide idSlide whose target is the id of an object.
+             * Three types of trigger : "withPrevious", "afterPrevious", "onClick".
+             * @param int idSlide
+             * @param string type
+             * @param string target
+             * @param string trigger
+             * Other params can be specified depending on the object's type.
+             */
+            this.addAction = function (idSlide, type, target, trigger) {
+                var action = {
+                    "id": generateUniqueActionId(),
+                    "type": type,
+                    "target": target,
+                    "trigger": trigger
+                };
+                switch (type.toLowerCase()) {
+                    case "appear":
+                        action.abs = arguments[4] || 0.0;
+                        action.ord = arguments[5] || 0.0;
+                        action.duration = arguments[6] || 0;
+                        break;
+                    case "disappear":
+                        action.duration = arguments[4] || 0;
+                        break;
+                    case "move":
+                        action.finalAbs = arguments[4] || 0.0;
+                        action.finalOrd = arguments[5] || 0.0;
+                        action.speed = arguments[6] || 0;
+                        break;
+                }
+                json.data[idSlide].action.push(action);
+            };
+
+            this.editMouse = function (idSlide, idAction, mouseX, mouseY) {
+                json.data[idSlide].action[idAction].finalAbs = mouseX;
+                json.data[idSlide].action[idAction].finalOrd = mouseY;
+            };
+
+            /*
+             * Sets a title for the presentation.
+             * @param String title Title to set.
+             */
+            this.setTitle = function (title) {
+                json.metaData.title = title;
+            };
+
+            /*
+             * Sets an annotation for the presentation.
+             * @param String annotation Annotation to set.
+             */
+            this.setAnnotation = function (annotation) {
+                json.metaData.annotation = annotation;
+            };
+
+            /*
+             * Inverts the two slides (their positions on the table).
+             * @param int idSlide1 Index of the first slide.
+             * @param int idSlide2 Index of the second slide.
+             */
+            this.invertSlides = function (idSlide1, idSlide2) {
+                var tmp = json.data[idSlide1];
+                json.data[idSlide1] = json.data[idSlide2];
+                json.data[idSlide2] = tmp;
+            };
+
+            /*
+             * Inverts the two actions (their positions on the table).
+             * @param int idSlide
+             * @param int idAction1
+             * @param int idAction2
+             */
+            this.invertActions = function (idSlide, idAction1, idAction2) {
+                var tmp = json.data[idSlide].action[idAction1];
+                json.data[idSlide].action[idAction1] = json.data[idSlide].action[idAction2];
+                json.data[idSlide].action[idAction2] = tmp;
+            };
+
+            /*
+             * Returns the actions on the specified slide.
+             * @returns {Array}
+             */
+            this.getActionList = function (idSlide) {
+                return json.data[idSlide].action;
+            };
+
+            /*
+             * Catches all the objects of the presentation
+             * @returns {Array} List of objects of the presentation
+             */
+            this.getObjectList = function () {
+                var objectList = new Array();
+                var indexSlide = 0;
+                while (json.data[indexSlide]) {
+                    var indexObject = 0;
+                    while (json.data[indexSlide].object[indexObject]) {
+                        objectList.push(json.data[indexSlide].object[indexObject]);
+                        indexObject++;
+                    }
+                    indexSlide++;
+                }
+                return objectList;
+            };
+
+            /*
+             * Returns an array containing all the background images.
+             * @returns {Array} List of background images.
+             */
+            this.getImageList = function () {
+                var list = new Array();
+                var indexSlide = 0;
+                while (json.data[indexSlide]) {
+                    list.push(json.data[indexSlide].object[0].img);
+                    indexSlide++;
+                }
+                ;
+                return list;
+            };
+
+            /*
+             * Returns an object containing the id of the first background.
+             * @returns {string} Id of the first background.
+             */
+            this.getInitialBackground = function () {
+                return json.data[0].object[0].id;
+            };
+
+            /*
+             * Returns an object containing the initial mouse position.
+             * @returns {object} Initial position of mouse.
+             */
+            this.getInitialMousePos = function () {
+                var pos = {};
+                pos.x = json.data[0].action[0].finalAbs;
+                pos.y = json.data[0].action[0].finalOrd;
+                return pos;
+            };
+
+            /*
+             * Removes the slide at the specified index.
+             * @param {int} idSlide
+             */
+            this.removeSlide = function (idSlide) {
+                json.data.splice(idSlide, 1);
+            };
+
+            /*
+             * Removes the action at the specified slide.
+             * @param {int} idSlide
+             * @param {int} idAction
+             */
+            this.removeAction = function (idSlide, idAction) {
+                json.data[idSlide].action.splice(idAction, 1);
+            };
+
+            /*
+             * Removes the specified object from the slide.
+             * Also removes all the actions attached to this object.
+             * @param {int} idSlide
+             * @param {int} idObject
+             */
+            this.removeObject = function (idSlide, idObject) {
+                var removed = json.data[idSlide].object.splice(idObject, 1);
+                for (var i = 0; i < json.data.length; i++) {
+                    var j = 0;
+                    while (json.data[i].action[j]) {
+                        if (json.data[i].action[j].target === removed.id) {
+                            json.data[i].action.splice(j, 1);
+                        } else {
+                            j++;
+                        }
+                    }
+                }
+            };
+
+            /*
+             * @returns {String}
+             */
+            this.getJson = function () {
+                return JSON.stringify(json, null, '    ');
+            };
+
+            /*
+             * @returns {String} returns the index for the next slide.
+             */
+            this.getNbSlide = function () {
+                return json.data.length;
+            };
+
+            /*
+             * @return {object} returns the object identified by idSlide
+             */
+            this.getSlide = function (idSlide) {
+                return json.data[idSlide];
+            };
+
+            /*
+             * Sets the size of images for the generated presentation.
+             * The parameters can be null : it means there are no
+             * requirement for this dimension.
+             * @param {int} width New width for the generated images.
+             * @param {int} height New height for the generated images.
+             */
+            this.setImageSizeRequirements = function (width, height) {
+                json.metaData.imageWidth = width;
+                json.metaData.imageHeight = height;
+            };
+
+            /*
+             * Gets the width of images for the generated presentation.
+             * @return {int or null} Width of generated images.
+             */
+            this.getImageWidth = function () {
+                return json.metaData.imageWidth;
+            };
+
+            /*
+             * Gets the height of images for the generated presentation.
+             * @return {int or null} Height of generated images.
+             */
+            this.getImageHeight = function () {
+                return json.metaData.imageHeight;
+            };
+
+            /*
+             * Gets a background image (no one in particular, the first met).
+             * @return {string} The name of a background image.
+             */
+            this.getABackgroundImage = function () {
+                for (var i = 0; i < json.data.length; i++) {
+                    for (var j = 0; j < json.data[i].object.length; j++) {
+                        if (json.data[i].object[j].type === "background") {
+                            return json.data[i].object[j].img;
+                        }
+                    }
+                }
+                return null;
+            };
+        };
+
+        /* public API */
+
+        self.version = "0.0.1";
+
+        self.createScreencastGenerator = function createScreencastGenerator() {
+            return new DahuScreencastGenerator();
+        };
+
+        self.createScreencastModel = function createScreencastModel() {
+            return new DahuScreencastModel();
+        };
+
+        self.createScreencastExecutableModel = function createScreencastExecutableModel() {
+            return new DahuScreencastExecutableModel();
+        };
+
+        self.createScreencastGeneratedModel = function createScreencastGeneratedModel() {
+            return new DahuScreencastGeneratedModel();
+        };
+
+        return self;
+    })();
+
+    window.dahuapp = dahuapp;
+
+})(window, jQuery);
\ No newline at end of file
diff --git a/TP/TP01/P02/dahuapp.viewer.css b/TP/TP01/P02/dahuapp.viewer.css
new file mode 100644
index 0000000000000000000000000000000000000000..ea934576dad6ca9263a6163de58f5fe4534d1f0b
--- /dev/null
+++ b/TP/TP01/P02/dahuapp.viewer.css
@@ -0,0 +1,25 @@
+.object-list {
+    position: absolute;
+    margin: 0px;
+    padding: 0px;
+}
+
+.mouse-cursor {
+    position: absolute;
+}
+
+.control {
+    position: relative;
+}
+
+.background {
+    position: absolute;
+}
+
+.tooltip {
+    position: absolute;
+    width: 100px;
+    padding: 4px;
+    margin: 2px;
+    border: 2px black solid;
+}
\ No newline at end of file
diff --git a/TP/TP01/P02/dahuapp.viewer.js b/TP/TP01/P02/dahuapp.viewer.js
new file mode 100644
index 0000000000000000000000000000000000000000..12ad03b2cdc503a8fd6ff3f5fdc2a74edbb36edb
--- /dev/null
+++ b/TP/TP01/P02/dahuapp.viewer.js
@@ -0,0 +1,456 @@
+"use strict";
+
+/**
+ * Dahuapp viewer module.
+ * 
+ * @param   dahuapp     dahuapp object to augment with module.
+ * @param   $           jQuery
+ * @returns dahuapp extended with viewer module.
+ */
+
+(function(dahuapp, $) {
+    var viewer = (function() {
+
+        var self = {};
+
+        var DahuViewerModel = function(select, getParams) {
+
+            /* Private API */
+
+            var json = null;
+            var selector = select;
+            var parseAutoOption = function(name, defaultValue) {
+                var res = getParams[name];
+                if (res == null) {
+                    if (name == 'auto') {
+                        return false;
+                    } else {
+                        return parseAutoOption('auto', defaultValue);
+                    }
+                }
+                if (res.toLowerCase() === 'false') {
+                    return false;
+                }
+                res = parseInt(res);
+                if (isNaN(res)) {
+                    // e.g. ?autoplay or ?autoplay=true
+                    return defaultValue;
+                }
+                return res;
+            }
+
+            /* Whether to wait for "next" event between actions */
+            var autoPlay = parseAutoOption('autoplay', 5000);
+            /* Whether to wait for "next" event on page load */
+            var autoStart = parseAutoOption('autostart', 5000);
+            /* Whether to loop back to start at the end of presentation */
+            var autoLoop = parseAutoOption('autoloop', 5000);
+
+            var events = (function() {
+                var self = {};
+                /*
+                 * Creates a generic event.
+                 */
+                var createEvent = function() {
+                    var callbacks = $.Callbacks();
+                    return {
+                        publish: callbacks.fire,
+                        subscribe: callbacks.add,
+                        unsubscribe: callbacks.remove,
+                        unsubscribeAll: callbacks.empty
+                    };
+                };
+                /*
+                 * Called when the button next is pressed.
+                 */
+                self.onNext = createEvent();
+                /*
+                 * Called when the button previous is pressed.
+                 */
+                self.onPrevious = createEvent();
+                /*
+                 * Called when an action is over.
+                 */
+                self.onActionOver = createEvent();
+                /*
+                 * Called when an action starts.
+                 */
+                self.onActionStart = createEvent();
+                /*
+                 * Called when at least one action was running and has finished.
+                 */
+                self.onAllActionFinish = createEvent();
+                
+                return self;
+            })();
+
+            /*
+             * Variables used like index for methodes subscribed.
+             */
+            var currentAction = 0;    /* Action currently running */
+            var nextAction = 0;       /* Action to execute on 'Next' click */
+            var nbActionsRunning = 0;
+            var previousAnchor = -1;  /* Last entered anchor, only used in
+                                       * case the 'onhashchange' event is
+                                       * not supported by the web browser */
+            
+            /*
+             * Functions to reinitialise running actions and subscribed
+             * callbacks (reinitialise is called once without stopping
+             * all the actions, so the two functions are separated).
+             */
+            var stopAllActions = function() {
+                $(selector + " .object-list").children().stop(true, true);
+                reinitialiseCallbackLists();
+                nbActionsRunning = 0;
+            };
+            var reinitialiseCallbackLists = function() {
+                events.onActionStart.unsubscribeAll();
+                events.onActionStart.subscribe(onActionStartEventHandler);
+                events.onAllActionFinish.unsubscribeAll();
+            };
+            
+            /*
+             * Timer used to program onNext event in autoplay mode.
+             */
+            var playTimer = 0;
+
+            /*
+             * Function used when an "onNextEvent" event is caught.
+             */
+            var onNextEventHandler = function() {
+                /*
+                 * If the user pressed "next" before an autoplay event
+                 * is triggered, it replaces the autoplay event, hence
+                 * cancels it:
+                 */
+                window.clearTimeout(playTimer);
+
+                enterAnimationMode();
+                stopAllActions();
+                var tmpAction = nextAction;
+                var onlyWithPrevious = true;
+                nextAction++;
+                currentAction = nextAction;
+                while (json.action[currentAction] && onlyWithPrevious) {
+                    switch (json.action[currentAction].trigger) {
+                        case 'onClick':
+                            nextAction = currentAction;
+                            onlyWithPrevious = false;
+                            break;
+                        case 'withPrevious':
+                            var tmp = currentAction;
+                            /*
+                             * We can't directly pass 'execute' as callback because we
+                             * allow the 'execute' function to reference a property of the
+                             * object (by using 'this.property') so we have to call the
+                             * function in the containing object to make that available
+                             */
+                            events.onActionStart.subscribe(function(events, selector) {
+                                json.action[tmp].execute(events, selector);
+                            });
+                            break;
+                        case 'afterPrevious':
+                            var tmp = currentAction;
+                            events.onAllActionFinish.subscribe(function(events, selector) {
+                                json.action[tmp].execute(events, selector);
+                            });
+                            while (json.action[nextAction] && json.action[nextAction].trigger !== 'onClick') {
+                                nextAction++;
+                            }
+                            onlyWithPrevious = false;
+                            break;
+                    }
+                    currentAction++;
+                }
+                if (json.action[tmpAction]) {
+                    launch(json.action[tmpAction]);
+                }
+                if (nextAction > json.action.length) {
+                    nextAction = json.action.length;
+                }
+            };
+            
+            /*
+             * Function used when an "onPreviousEvent" event is caught.
+             */
+            var onPreviousEventHandler = function() {
+                stopAllActions();
+                currentAction = nextAction - 1;
+                while (json.action[currentAction] && json.action[currentAction].trigger !== 'onClick') {
+                    launchReverse(json.action[currentAction]);
+                    currentAction--;
+                }
+                /* currentAction = -1 means that it's the beginning */
+                if (currentAction > -1) {
+                    launchReverse(json.action[currentAction]);
+                }
+                if (currentAction < 0) {
+                    currentAction = 0;
+                }
+                nextAction = currentAction;
+            };
+
+            /*
+             * Function used when an "onActionOverEvent" event is caught.
+             */
+            var onActionOverEventHandler = function() {
+                nbActionsRunning--;
+                if (nbActionsRunning === 0) {
+                    events.onAllActionFinish.publish(events, selector);
+                    reinitialiseCallbackLists();
+                    while (json.action[currentAction]) {
+                        switch (json.action[currentAction].trigger) {
+                            case 'onClick':
+                                leaveAnimationMode();
+                                if (autoPlay && nbActionsRunning === 0) {
+                                    playTimer = setTimeout(function () {
+                                        events.onNext.publish();
+                                    }, autoPlay);
+                                }
+                                return;
+                            case 'withPrevious':
+                                var tmp = currentAction;
+                                events.onActionStart.subscribe(function(events, selector) {
+                                    json.action[tmp].execute(events, selector);
+                                });
+                                break;
+                            case 'afterPrevious':
+                                var tmp = currentAction;
+                                events.onAllActionFinish.subscribe(function(events, selector) {
+                                    json.action[tmp].execute(events, selector);
+                                });
+                                currentAction++;
+                                return;
+                        }
+                        currentAction++;
+
+                    }
+                    if (autoLoop && nbActionsRunning === 0) {
+                        setTimeout(function () {
+                            resetPresentation();
+                            startPresentationMaybe();
+                        }, autoLoop);
+                    }
+                }
+                leaveAnimationMode();
+            };
+
+            /*
+             * Function used when an "onActionStartEvent" event is caught.
+             */
+            var onActionStartEventHandler = function() {
+                nbActionsRunning++;
+            };
+
+            /*
+             * Enter and leave "animation" mode. The viewer is in
+             * animation mode when something is going on without human
+             * interaction (i.e. executing actions before the next
+             * "onClick").
+             */
+            var enterAnimationMode = function () {
+                $(selector + ' .mouse-cursor-pause').hide();
+                $(selector + ' .mouse-cursor-normal').show();
+            };
+
+            var leaveAnimationMode = function () {
+                $(selector + ' .mouse-cursor-pause').show();
+                $(selector + ' .mouse-cursor-normal').hide();
+            };
+
+            /*
+             * Function used to perform actions.
+             */
+            var launch = function(action) {
+                action.execute(events, selector);
+            };
+            var launchReverse = function(action) {
+                action.executeReverse(selector);
+            };
+
+            /*
+             * The two following methods each returns an array containing the
+             * actions to execute to reach the given anchor (respectively
+             * forward or backwards).
+             *
+             * If the given anchor matches none of the actions, then an empty
+             * array is returned.
+             */
+            var getActionsToJumpForward = function(anchor) {
+                var actions = new Array();
+                for (var i = nextAction; i < json.action.length; i++) {
+                    // '==' and not '===' because we compare indifferently a
+                    // number or a string or anything else
+                    if (json.action[i].id == anchor) {
+                        return actions;
+                    } else {
+                        actions.push(json.action[i]);
+                    }
+                }
+                // Here, the anchor has not been found during the action scan
+                return null;
+            };
+            var getActionsToJumpBackwards = function(anchor) {
+                var actions = new Array();
+                for (var i = nextAction - 1; i >= 0; i--) {
+                    // '==' and not '===' because we compare indifferently a
+                    // number or a string or anything else
+                    actions.push(json.action[i]);
+                    if (json.action[i].id == anchor) {
+                        return actions;
+                    }
+                }
+                // Here, the anchor has not been found during the action scan
+                return null;
+            };
+            
+            /*
+             * Updates the position of the presentation depending on the
+             * given anchor (next action wanted).
+             */
+            var jumpToAnchor = function(anchor) {
+                if (anchor !== '') {
+                    stopAllActions();
+                    if (anchor > nextAction) {
+                        // forward
+                        var actions = getActionsToJumpForward(anchor);
+                        for (var i = 0; i < actions.length; i++) {
+                            actions[i].executeImmediately(selector);
+                        }
+                        nextAction += actions.length;
+                    } else {
+                        // backwards
+                        var actions = getActionsToJumpBackwards(anchor);
+                        for (var i = 0; i < actions.length; i++) {
+                            actions[i].executeReverse(selector);
+                        }
+                        nextAction -= actions.length;
+                    }
+                }
+            };
+
+            var resetPresentation = function() {
+                window.clearTimeout(playTimer);
+
+                currentAction = 0;
+                nextAction = 0;
+                nbActionsRunning = 0;
+
+                /*
+                 * At the beginning, the visible image is the first one of the presentation
+                 */
+                $(selector + " .object-list").children().hide();
+
+                $(selector + " ." + json.metaData.initialBackgroundId).show();
+
+                $(selector + " .mouse-cursor").css({
+                    'top': (json.metaData.initialMouseY * json.metaData.imageHeight) + "px",
+                    'left': (json.metaData.initialMouseX * json.metaData.imageWidth) + "px"
+                });
+
+                $(selector + " .mouse-cursor").show();
+            }
+
+            var startPresentationMaybe = function() {
+                if (autoStart) {
+                    playTimer = setTimeout(function () {
+                        events.onNext.publish();
+                    }, autoStart);
+                }
+            }
+
+            /* Public API */
+
+            this.loadUrl = function(url) {
+                var dataJson;
+                $.ajax({
+                    'async': false,
+                    'global': false,
+                    'url': url,
+                    'dataType': "json",
+                    'success': function(data) {
+                        dataJson = data;
+                    }
+                });
+                
+                load(dataJson);
+            };
+
+            this.load = function(dataJson) {
+                /* Transforms data JSON to executable functions for actions */
+                var execModel = dahuapp.createScreencastExecutableModel();
+                execModel.loadJson(dataJson);
+                json = execModel.getJson();
+            };
+
+            this.start = function() {
+                
+                /*
+                 * Subscription of methods to their events.
+                 */
+                events.onNext.subscribe(onNextEventHandler);
+                events.onPrevious.subscribe(onPreviousEventHandler);
+                events.onActionOver.subscribe(onActionOverEventHandler);
+                events.onActionStart.subscribe(onActionStartEventHandler);
+
+                resetPresentation();
+
+                /*
+                 * If an anchor has been specified, we place the presentation
+                 * in the right position.
+                 */
+                jumpToAnchor(window.location.hash.substring(1));
+                
+                /*
+                 * If the anchor changes during the presentation, then the
+                 * presentation is updated
+                 */
+                if ("onhashchange" in window) {
+                    // event supported
+                    window.onhashchange = function () {
+                        jumpToAnchor(window.location.hash.substring(1));
+                    };
+                } else {
+                    // event not supported : periodical check
+                    window.setInterval(function () {
+                        if (window.location.hash.substring(1) !== previousAnchor) {
+                            previousAnchor = window.location.hash.substring(1);
+                            jumpToAnchor(window.location.hash.substring(1));
+                        }
+                    }, 100);
+                }
+
+                /*
+                 * A click on the "next" button publishes a nextSlide event
+                 */
+                $(selector + " .next").click(function() {
+                    events.onNext.publish();
+                });
+                
+                /*
+                 * A click on the "previous" button publishes a previousSlide event
+                 */
+                $(selector + " .previous").click(function() {
+                    events.onPrevious.publish();
+                });
+
+                /*
+                 * Everything is ready, show the presentation
+                 * (hidden with style="display: none" from HTML).
+                 */
+                $("#loading").hide();
+                $(selector + " .object-list").show();
+                startPresentationMaybe();
+            };
+        };
+
+        self.createDahuViewer = function(selector, getParams) {
+            return new DahuViewerModel(selector, getParams);
+        };
+
+        return self;
+    })();
+
+    dahuapp.viewer = viewer;
+})(dahuapp || {}, jQuery);
diff --git a/TP/TP01/P02/img/0c3cede8-b8ad-467d-2057-2a73747a585f.png b/TP/TP01/P02/img/0c3cede8-b8ad-467d-2057-2a73747a585f.png
new file mode 100644
index 0000000000000000000000000000000000000000..a2b62c8590b52c579a210545fcb22eee8c640e8f
Binary files /dev/null and b/TP/TP01/P02/img/0c3cede8-b8ad-467d-2057-2a73747a585f.png differ
diff --git a/TP/TP01/P02/img/16f02396-3c95-ab6a-c7af-c6e6aa4c708a.png b/TP/TP01/P02/img/16f02396-3c95-ab6a-c7af-c6e6aa4c708a.png
new file mode 100644
index 0000000000000000000000000000000000000000..ec340a9b4ac81609b87d295dc5cb8561aef93c63
Binary files /dev/null and b/TP/TP01/P02/img/16f02396-3c95-ab6a-c7af-c6e6aa4c708a.png differ
diff --git a/TP/TP01/P02/img/1895ad3f-8322-65f5-cc85-a730ee6a9102.png b/TP/TP01/P02/img/1895ad3f-8322-65f5-cc85-a730ee6a9102.png
new file mode 100644
index 0000000000000000000000000000000000000000..f1111b244e6dd6f23827824bf964550efddb0ff7
Binary files /dev/null and b/TP/TP01/P02/img/1895ad3f-8322-65f5-cc85-a730ee6a9102.png differ
diff --git a/TP/TP01/P02/img/21b4ef3c-3d71-77be-69e4-b4afe30311f5.png b/TP/TP01/P02/img/21b4ef3c-3d71-77be-69e4-b4afe30311f5.png
new file mode 100644
index 0000000000000000000000000000000000000000..449ce2b6fd11233e65607372f8e70effdb0c141c
Binary files /dev/null and b/TP/TP01/P02/img/21b4ef3c-3d71-77be-69e4-b4afe30311f5.png differ
diff --git a/TP/TP01/P02/img/3f6a7fc7-c0ae-d0e6-416c-9164a0311aa4.png b/TP/TP01/P02/img/3f6a7fc7-c0ae-d0e6-416c-9164a0311aa4.png
new file mode 100644
index 0000000000000000000000000000000000000000..1ba5b0a67ed6c3d190519d43a51959a8957a8080
Binary files /dev/null and b/TP/TP01/P02/img/3f6a7fc7-c0ae-d0e6-416c-9164a0311aa4.png differ
diff --git a/TP/TP01/P02/img/47b1d9d8-377e-8956-12e0-1a5855936ad9.png b/TP/TP01/P02/img/47b1d9d8-377e-8956-12e0-1a5855936ad9.png
new file mode 100644
index 0000000000000000000000000000000000000000..a089ae943dbb760b33af4a965f4682750fe8a148
Binary files /dev/null and b/TP/TP01/P02/img/47b1d9d8-377e-8956-12e0-1a5855936ad9.png differ
diff --git a/TP/TP01/P02/img/4cd13680-2e47-849f-6779-e9f8967d51b8.png b/TP/TP01/P02/img/4cd13680-2e47-849f-6779-e9f8967d51b8.png
new file mode 100644
index 0000000000000000000000000000000000000000..531f221177c07e44686c378afaf7a9fa42d1d9bd
Binary files /dev/null and b/TP/TP01/P02/img/4cd13680-2e47-849f-6779-e9f8967d51b8.png differ
diff --git a/TP/TP01/P02/img/551c31b6-7df8-0500-8bc8-55e8283c8ca9.png b/TP/TP01/P02/img/551c31b6-7df8-0500-8bc8-55e8283c8ca9.png
new file mode 100644
index 0000000000000000000000000000000000000000..643034c4a5bcde90936851c6bdd6643a5ab04090
Binary files /dev/null and b/TP/TP01/P02/img/551c31b6-7df8-0500-8bc8-55e8283c8ca9.png differ
diff --git a/TP/TP01/P02/img/5cc446dc-d0c1-0797-7b20-7b193c939258.png b/TP/TP01/P02/img/5cc446dc-d0c1-0797-7b20-7b193c939258.png
new file mode 100644
index 0000000000000000000000000000000000000000..2c5ace1513edf3ad66c44e6a2d5d74c5b3938f04
Binary files /dev/null and b/TP/TP01/P02/img/5cc446dc-d0c1-0797-7b20-7b193c939258.png differ
diff --git a/TP/TP01/P02/img/6ad4fffd-1771-3264-ee1b-f257b1b545fc.png b/TP/TP01/P02/img/6ad4fffd-1771-3264-ee1b-f257b1b545fc.png
new file mode 100644
index 0000000000000000000000000000000000000000..a2b62c8590b52c579a210545fcb22eee8c640e8f
Binary files /dev/null and b/TP/TP01/P02/img/6ad4fffd-1771-3264-ee1b-f257b1b545fc.png differ
diff --git a/TP/TP01/P02/img/88d9520b-2d83-1911-c07c-adc370f752ad.png b/TP/TP01/P02/img/88d9520b-2d83-1911-c07c-adc370f752ad.png
new file mode 100644
index 0000000000000000000000000000000000000000..19ef410489368434daa56b7fcc630e592ad13d2b
Binary files /dev/null and b/TP/TP01/P02/img/88d9520b-2d83-1911-c07c-adc370f752ad.png differ
diff --git a/TP/TP01/P02/img/99e6c70e-5d6f-796d-d931-04aef1b87fe2.png b/TP/TP01/P02/img/99e6c70e-5d6f-796d-d931-04aef1b87fe2.png
new file mode 100644
index 0000000000000000000000000000000000000000..850b51e32f25d775ff8d32207bd4246265fae6ab
Binary files /dev/null and b/TP/TP01/P02/img/99e6c70e-5d6f-796d-d931-04aef1b87fe2.png differ
diff --git a/TP/TP01/P02/img/9ebb6e2b-6fd7-b953-582b-c32f016bc519.png b/TP/TP01/P02/img/9ebb6e2b-6fd7-b953-582b-c32f016bc519.png
new file mode 100644
index 0000000000000000000000000000000000000000..38e07ec599176a6eb32f7b6f6815dbac74d67a83
Binary files /dev/null and b/TP/TP01/P02/img/9ebb6e2b-6fd7-b953-582b-c32f016bc519.png differ
diff --git a/TP/TP01/P02/img/9f513610-b961-16aa-61b5-06227f763eb4.png b/TP/TP01/P02/img/9f513610-b961-16aa-61b5-06227f763eb4.png
new file mode 100644
index 0000000000000000000000000000000000000000..81c4e5c535a67ef94b23422328cd52875c23db0a
Binary files /dev/null and b/TP/TP01/P02/img/9f513610-b961-16aa-61b5-06227f763eb4.png differ
diff --git a/TP/TP01/P02/img/a83c11e4-e0c1-7255-ab82-a769973581eb.png b/TP/TP01/P02/img/a83c11e4-e0c1-7255-ab82-a769973581eb.png
new file mode 100644
index 0000000000000000000000000000000000000000..226a9218c0bf7dcd22789f3129b9e8b5a3d89d44
Binary files /dev/null and b/TP/TP01/P02/img/a83c11e4-e0c1-7255-ab82-a769973581eb.png differ
diff --git a/TP/TP01/P02/img/a8a2a39c-ff74-dc5b-d479-61864f684c7a.png b/TP/TP01/P02/img/a8a2a39c-ff74-dc5b-d479-61864f684c7a.png
new file mode 100644
index 0000000000000000000000000000000000000000..4d152d35e2bcc34193f241bcf86f3f502e51cd75
Binary files /dev/null and b/TP/TP01/P02/img/a8a2a39c-ff74-dc5b-d479-61864f684c7a.png differ
diff --git a/TP/TP01/P02/img/cafbe82e-d416-c25c-87cd-1822187ecc57.png b/TP/TP01/P02/img/cafbe82e-d416-c25c-87cd-1822187ecc57.png
new file mode 100644
index 0000000000000000000000000000000000000000..951b6ebad107b2569aec2ac87977ff3bbbf2ff10
Binary files /dev/null and b/TP/TP01/P02/img/cafbe82e-d416-c25c-87cd-1822187ecc57.png differ
diff --git a/TP/TP01/P02/img/cb1dac10-3623-e6b5-6b6c-39560bd09319.png b/TP/TP01/P02/img/cb1dac10-3623-e6b5-6b6c-39560bd09319.png
new file mode 100644
index 0000000000000000000000000000000000000000..0ad370f944dda0692683ba7d8460fba62b45b870
Binary files /dev/null and b/TP/TP01/P02/img/cb1dac10-3623-e6b5-6b6c-39560bd09319.png differ
diff --git a/TP/TP01/P02/img/cf7e765a-2ea5-10ec-9a89-50717ef4e657.png b/TP/TP01/P02/img/cf7e765a-2ea5-10ec-9a89-50717ef4e657.png
new file mode 100644
index 0000000000000000000000000000000000000000..a2b62c8590b52c579a210545fcb22eee8c640e8f
Binary files /dev/null and b/TP/TP01/P02/img/cf7e765a-2ea5-10ec-9a89-50717ef4e657.png differ
diff --git a/TP/TP01/P02/img/cursor-pause.png b/TP/TP01/P02/img/cursor-pause.png
new file mode 100644
index 0000000000000000000000000000000000000000..9c93cc142f4326a188eafc3c0fe96f2975dcab0c
Binary files /dev/null and b/TP/TP01/P02/img/cursor-pause.png differ
diff --git a/TP/TP01/P02/img/cursor.png b/TP/TP01/P02/img/cursor.png
new file mode 100644
index 0000000000000000000000000000000000000000..5b4a20e63078ce18bb11a4e601a1994b261522ef
Binary files /dev/null and b/TP/TP01/P02/img/cursor.png differ
diff --git a/TP/TP01/P02/img/d1db9286-d696-3dc3-2016-ac6bac7caa42.png b/TP/TP01/P02/img/d1db9286-d696-3dc3-2016-ac6bac7caa42.png
new file mode 100644
index 0000000000000000000000000000000000000000..d75d0e360b3c9581788ee9b23b3d40913c0020d9
Binary files /dev/null and b/TP/TP01/P02/img/d1db9286-d696-3dc3-2016-ac6bac7caa42.png differ
diff --git a/TP/TP01/P02/img/e1f3f8d9-b4c2-e9b1-78be-b5384c0608a5.png b/TP/TP01/P02/img/e1f3f8d9-b4c2-e9b1-78be-b5384c0608a5.png
new file mode 100644
index 0000000000000000000000000000000000000000..752dfb28cbd1c1d7d47ac5822b2d53ca80d8baf2
Binary files /dev/null and b/TP/TP01/P02/img/e1f3f8d9-b4c2-e9b1-78be-b5384c0608a5.png differ
diff --git a/TP/TP01/P02/img/f6edf30b-b3c9-992b-5a29-2899155fc088.png b/TP/TP01/P02/img/f6edf30b-b3c9-992b-5a29-2899155fc088.png
new file mode 100644
index 0000000000000000000000000000000000000000..0eee77f040bb93a93846dd9166ce9612dc4aa6ab
Binary files /dev/null and b/TP/TP01/P02/img/f6edf30b-b3c9-992b-5a29-2899155fc088.png differ
diff --git a/TP/TP01/P02/img/fa328a63-2ce8-9ff6-de8b-cadc7cca7571.png b/TP/TP01/P02/img/fa328a63-2ce8-9ff6-de8b-cadc7cca7571.png
new file mode 100644
index 0000000000000000000000000000000000000000..939d65d68ec9ea111f3d2d22a11945e615aebc83
Binary files /dev/null and b/TP/TP01/P02/img/fa328a63-2ce8-9ff6-de8b-cadc7cca7571.png differ
diff --git a/TP/TP01/P02/img/fd27154d-26f4-d228-a651-ea2d3867a896.png b/TP/TP01/P02/img/fd27154d-26f4-d228-a651-ea2d3867a896.png
new file mode 100644
index 0000000000000000000000000000000000000000..addb4ebd8aba8c9fa6c9fae1bbb8ceb9e18bf315
Binary files /dev/null and b/TP/TP01/P02/img/fd27154d-26f4-d228-a651-ea2d3867a896.png differ
diff --git a/TP/TP01/P02/index.html b/TP/TP01/P02/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..ccff12ed96c3dc04d37577ee86195e6e0af8d602
--- /dev/null
+++ b/TP/TP01/P02/index.html
@@ -0,0 +1,420 @@
+<!DOCTYPE html>
+
+<html lang="en">
+
+<head>
+<title>Dahu Presentation
+</title>
+<meta charset="utf-8">
+<script src="http://code.jquery.com/jquery-1.9.1.min.js" type="text/javascript">
+</script>
+<script src="parse-search.js" type="text/javascript">
+</script>
+
+<link rel="stylesheet" href="dahuapp.viewer.css"><style>.s0-o2{background-color:   #FFFFDD;width:  240px}
+.s2-o2{background-color:   #FFFFDD;width:  240px}
+.s4-o2{background-color:   #FFFFDD;width:  240px}
+.s6-o2{background-color:   #FFFFDD;width:  240px}
+.s7-o2{background-color:   #FFFFDD;width:  240px}
+.s8-o2{background-color:   #FFFFDD;width:  240px}
+.s9-o2{background-color:   #FFFFDD;width:  240px}
+.s10-o2{background-color:   #FFFFDD;width:  240px}
+.s11-o2{background-color:   #FFFFDD;width:  240px}
+.s12-o2{background-color:   #FFFFDD;width:  240px}
+</style>
+</head>
+
+<body>
+
+<div id="my-dahu-presentation">
+
+<div id="loading">Loading presentation...
+</div>
+
+<div class="object-list" style="display: none">
+<img src="img/47b1d9d8-377e-8956-12e0-1a5855936ad9.png" alt="img/47b1d9d8-377e-8956-12e0-1a5855936ad9.png" class="background 47b1d9d8-377e-8956-12e0-1a5855936ad90">
+
+<div class="tooltip s0-o2">Ouvrir l'écran du bloc Oscilloscope par un double-clic sur le bloc.
+</div>
+<img src="img/88d9520b-2d83-1911-c07c-adc370f752ad.png" alt="img/88d9520b-2d83-1911-c07c-adc370f752ad.png" class="background 88d9520b-2d83-1911-c07c-adc370f752ad0">
+<img src="img/5cc446dc-d0c1-0797-7b20-7b193c939258.png" alt="img/5cc446dc-d0c1-0797-7b20-7b193c939258.png" class="background 5cc446dc-d0c1-0797-7b20-7b193c9392580">
+
+<div class="tooltip s2-o2">Ouvrir la configuration de la simulation.
+</div>
+<img src="img/1895ad3f-8322-65f5-cc85-a730ee6a9102.png" alt="img/1895ad3f-8322-65f5-cc85-a730ee6a9102.png" class="background 1895ad3f-8322-65f5-cc85-a730ee6a91020">
+<img src="img/f6edf30b-b3c9-992b-5a29-2899155fc088.png" alt="img/f6edf30b-b3c9-992b-5a29-2899155fc088.png" class="background f6edf30b-b3c9-992b-5a29-2899155fc0880">
+
+<div class="tooltip s4-o2">Démarrer la simulation.
+</div>
+<img src="img/3f6a7fc7-c0ae-d0e6-416c-9164a0311aa4.png" alt="img/3f6a7fc7-c0ae-d0e6-416c-9164a0311aa4.png" class="background 3f6a7fc7-c0ae-d0e6-416c-9164a0311aa40">
+<img src="img/a8a2a39c-ff74-dc5b-d479-61864f684c7a.png" alt="img/a8a2a39c-ff74-dc5b-d479-61864f684c7a.png" class="background a8a2a39c-ff74-dc5b-d479-61864f684c7a0">
+
+<div class="tooltip s6-o2">Mettre à l'échelle l'écran de l'oscilloscope.
+</div>
+<img src="img/551c31b6-7df8-0500-8bc8-55e8283c8ca9.png" alt="img/551c31b6-7df8-0500-8bc8-55e8283c8ca9.png" class="background 551c31b6-7df8-0500-8bc8-55e8283c8ca90">
+
+<div class="tooltip s7-o2">Passer en mode de simulation à pas fixe (Fixed step).
+</div>
+<img src="img/99e6c70e-5d6f-796d-d931-04aef1b87fe2.png" alt="img/99e6c70e-5d6f-796d-d931-04aef1b87fe2.png" class="background 99e6c70e-5d6f-796d-d931-04aef1b87fe20">
+
+<div class="tooltip s8-o2">Démarrer la simulation.
+</div>
+<img src="img/e1f3f8d9-b4c2-e9b1-78be-b5384c0608a5.png" alt="img/e1f3f8d9-b4c2-e9b1-78be-b5384c0608a5.png" class="background e1f3f8d9-b4c2-e9b1-78be-b5384c0608a50">
+
+<div class="tooltip s9-o2">Mettre à l'échelle l'écran de l'oscilloscope. Vous ne constatez pas de différence avec la simulation à pas variable.
+</div>
+<img src="img/4cd13680-2e47-849f-6779-e9f8967d51b8.png" alt="img/4cd13680-2e47-849f-6779-e9f8967d51b8.png" class="background 4cd13680-2e47-849f-6779-e9f8967d51b80">
+
+<div class="tooltip s10-o2">Changer le pas de simulation. Passer à 1 s.
+</div>
+<img src="img/9f513610-b961-16aa-61b5-06227f763eb4.png" alt="img/9f513610-b961-16aa-61b5-06227f763eb4.png" class="background 9f513610-b961-16aa-61b5-06227f763eb40">
+
+<div class="tooltip s11-o2">Démarrer la simulation.
+</div>
+<img src="img/16f02396-3c95-ab6a-c7af-c6e6aa4c708a.png" alt="img/16f02396-3c95-ab6a-c7af-c6e6aa4c708a.png" class="background 16f02396-3c95-ab6a-c7af-c6e6aa4c708a0">
+
+<div class="tooltip s12-o2">Mettre à l'échelle l'écran de l'oscilloscope. Constater que la sinusoïde affichée est maintenant linéaire par morceau.
+</div>
+
+<div class="mouse-cursor">
+<img src="img/cursor.png" alt="img/cursor.png" class="mouse-cursor-normal">
+<img src="img/cursor-pause.png" alt="img/cursor-pause.png" style="display: none" class="mouse-cursor-pause">
+</div>
+</div>
+
+<div class="control" style="top: 616px;">
+<button class="previous">Previous
+</button>
+<button class="next">Next
+</button>
+</div>
+</div>
+<script src="dahuapp.js" type="text/javascript">
+</script>
+<script src="dahuapp.viewer.js" type="text/javascript">
+</script>
+<script type="text/javascript">(function($) {
+    var myPresentation = dahuapp.viewer.createDahuViewer("#my-dahu-presentation", window.getParams);
+    myPresentation.load({
+    "metaData": {
+        "imageWidth": 800,
+        "imageHeight": 600,
+        "initialBackgroundId": "47b1d9d8-377e-8956-12e0-1a5855936ad90",
+        "initialMouseX": 0.3173611111111111,
+        "initialMouseY": 0.25555555555555554
+    },
+    "action": [
+        {
+            "id": "49",
+            "type": "appear",
+            "target": "s0-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.21666666666666667,
+            "ord": 0.14125,
+            "duration": 400
+        },
+        {
+            "id": "3",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.3173611111111111,
+            "finalOrd": 0.2577777777777778,
+            "speed": 0.8
+        },
+        {
+            "id": "4",
+            "type": "appear",
+            "target": "88d9520b-2d83-1911-c07c-adc370f752ad0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "5",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.20694444444444443,
+            "finalOrd": 0.10555555555555556,
+            "speed": 0.8
+        },
+        {
+            "id": "6",
+            "type": "appear",
+            "target": "5cc446dc-d0c1-0797-7b20-7b193c9392580",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "50",
+            "type": "appear",
+            "target": "s2-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.17833333333333334,
+            "ord": 0.07875,
+            "duration": 400
+        },
+        {
+            "id": "7",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.20694444444444443,
+            "finalOrd": 0.10666666666666667,
+            "speed": 0.8
+        },
+        {
+            "id": "8",
+            "type": "appear",
+            "target": "1895ad3f-8322-65f5-cc85-a730ee6a91020",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "9",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.28958333333333336,
+            "finalOrd": 0.10555555555555556,
+            "speed": 0.8
+        },
+        {
+            "id": "10",
+            "type": "appear",
+            "target": "f6edf30b-b3c9-992b-5a29-2899155fc0880",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "51",
+            "type": "appear",
+            "target": "s4-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.22833333333333333,
+            "ord": 0.085,
+            "duration": 400
+        },
+        {
+            "id": "13",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.2916666666666667,
+            "finalOrd": 0.1,
+            "speed": 0.8
+        },
+        {
+            "id": "14",
+            "type": "appear",
+            "target": "3f6a7fc7-c0ae-d0e6-416c-9164a0311aa40",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "15",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.14583333333333334,
+            "finalOrd": 0.6633333333333333,
+            "speed": 0.8
+        },
+        {
+            "id": "16",
+            "type": "appear",
+            "target": "a8a2a39c-ff74-dc5b-d479-61864f684c7a0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "52",
+            "type": "appear",
+            "target": "s6-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.09833333333333333,
+            "ord": 0.2975,
+            "duration": 400
+        },
+        {
+            "id": "17",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.5638888888888889,
+            "finalOrd": 0.7466666666666667,
+            "speed": 0.8
+        },
+        {
+            "id": "18",
+            "type": "appear",
+            "target": "551c31b6-7df8-0500-8bc8-55e8283c8ca90",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "53",
+            "type": "appear",
+            "target": "s7-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.39666666666666667,
+            "ord": 0.32875,
+            "duration": 400
+        },
+        {
+            "id": "19",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.2923611111111111,
+            "finalOrd": 0.09666666666666666,
+            "speed": 0.8
+        },
+        {
+            "id": "20",
+            "type": "appear",
+            "target": "99e6c70e-5d6f-796d-d931-04aef1b87fe20",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "54",
+            "type": "appear",
+            "target": "s8-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.17833333333333334,
+            "ord": 0.0775,
+            "duration": 400
+        },
+        {
+            "id": "27",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.14375,
+            "finalOrd": 0.6666666666666666,
+            "speed": 0.8
+        },
+        {
+            "id": "28",
+            "type": "appear",
+            "target": "e1f3f8d9-b4c2-e9b1-78be-b5384c0608a50",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "55",
+            "type": "appear",
+            "target": "s9-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.075,
+            "ord": 0.3025,
+            "duration": 400
+        },
+        {
+            "id": "29",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.7118055555555556,
+            "finalOrd": 0.7722222222222223,
+            "speed": 0.8
+        },
+        {
+            "id": "30",
+            "type": "appear",
+            "target": "4cd13680-2e47-849f-6779-e9f8967d51b80",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "56",
+            "type": "appear",
+            "target": "s10-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.24166666666666667,
+            "ord": 0.33375,
+            "duration": 400
+        },
+        {
+            "id": "39",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.28680555555555554,
+            "finalOrd": 0.1,
+            "speed": 0.8
+        },
+        {
+            "id": "40",
+            "type": "appear",
+            "target": "9f513610-b961-16aa-61b5-06227f763eb40",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "57",
+            "type": "appear",
+            "target": "s11-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.2,
+            "ord": 0.08625,
+            "duration": 400
+        },
+        {
+            "id": "31",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.1451388888888889,
+            "finalOrd": 0.6666666666666666,
+            "speed": 0.8
+        },
+        {
+            "id": "32",
+            "type": "appear",
+            "target": "16f02396-3c95-ab6a-c7af-c6e6aa4c708a0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "58",
+            "type": "appear",
+            "target": "s12-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.22166666666666668,
+            "ord": 0.28125,
+            "duration": 400
+        }
+    ]
+});
+    myPresentation.start();
+})(jQuery);
+
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/TP/TP01/P02/parse-search.js b/TP/TP01/P02/parse-search.js
new file mode 100644
index 0000000000000000000000000000000000000000..e7d01c07f6a5d45b9fcabd5d271a8c6cfc5f00e2
--- /dev/null
+++ b/TP/TP01/P02/parse-search.js
@@ -0,0 +1,14 @@
+// Adapted from http://stackoverflow.com/questions/3234125/creating-array-from-window-location-hash
+(function () {
+    var search = window.location.search.slice(1);
+    var array = search.split("&");
+    
+    var values, form_data = {};
+    
+    for (var i = 0; i < array.length; i += 1) {
+	values = array[i].split("=");
+	form_data[values[0]] = unescape(values[1]);
+    }
+    window.getParams = form_data;
+}())
+
diff --git a/TP/TP01/P03/dahuapp.js b/TP/TP01/P03/dahuapp.js
new file mode 100644
index 0000000000000000000000000000000000000000..41c69e9169345a9a6703325d5557a4cc36913e19
--- /dev/null
+++ b/TP/TP01/P03/dahuapp.js
@@ -0,0 +1,880 @@
+"use strict";
+
+/**
+ * Dahuapp core module.
+ *
+ * @param   window      window javascript object.
+ * @param   $           jQuery
+ * @returns dahuapp core module.
+ */
+(function (window, $) {
+    var dahuapp = (function () {
+
+        var self = {};
+
+        /* private API */
+
+        var DahuScreencastGenerator = function () {
+
+            /*
+             * Format the specified string in a readable format.
+             */
+            function htmlFormat(htmlString) {
+                var i;
+                var readableHTML = htmlString;
+                var lb = '\n';
+                var htags = ["<html", "</html>", "</head>", "<title", "</title>", "<meta", "<link", "</body>"];
+                for (i = 0; i < htags.length; ++i) {
+                    var hhh = htags[i];
+                    readableHTML = readableHTML.replace(new RegExp(hhh, 'gi'), lb + hhh);
+                }
+                var btags = ["</div>", "</section>", "</span>", "<br>", "<br />", "<blockquote", "</blockquote>", "<ul", "</ul>", "<ol", "</ol>", "<li", "<\!--", "<script", "</script>"];
+                for (i = 0; i < btags.length; ++i) {
+                    var bbb = btags[i];
+                    readableHTML = readableHTML.replace(new RegExp(bbb, 'gi'), lb + bbb);
+                }
+                var ftags = ["<img", "<legend", "</legend>", "<button", "</button>"];
+                for (i = 0; i < ftags.length; ++i) {
+                    var fff = ftags[i];
+                    readableHTML = readableHTML.replace(new RegExp(fff, 'gi'), lb + fff);
+                }
+                var xtags = ["<body", "<head", "<div", "<section", "<span", "<p"];
+                for (i = 0; i < xtags.length; ++i) {
+                    var xxx = xtags[i];
+                    readableHTML = readableHTML.replace(new RegExp(xxx, 'gi'), lb + lb + xxx);
+                }
+                return readableHTML;
+            }
+
+            /*
+             * Generates the html header.
+             */
+            var generateHtmlHeader = function ($generated, cssGen) {
+                $('head', $generated)
+                    .append($(document.createElement('title'))
+                        .append("Dahu Presentation"))/* TODO: make this customizable */
+                    .append($(document.createElement('meta'))
+                        .attr({'charset': 'utf-8'}))
+                    .append($(document.createElement('script'))
+                        .attr({'src': 'http://code.jquery.com/jquery-1.9.1.min.js'})
+                        .attr({'type': 'text/javascript'}))
+                    .append($(document.createElement('script'))
+                        .attr({'src': 'parse-search.js'})
+                        .attr({'type': 'text/javascript'}))
+                    .append($(document.createElement('link'))
+                        .attr({'rel': 'stylesheet', 'href': 'dahuapp.viewer.css'}))
+                    .append($(document.createElement('style'))
+                        .append(cssGen));
+            };
+
+            /*
+             * Generates the objects.
+             */
+
+            var generateHtmlBackgroundImage = function ($generated, object) {
+                $('.object-list', $generated)
+                    .append($(document.createElement('img'))
+                        .attr({'src': object.img, 'alt': object.img, 'class': 'background ' + object.id}));
+            };
+            var generateHtmlTooltip = function ($generated, object) {
+                $('.object-list', $generated)
+                    .append($(document.createElement('div'))
+                        .attr({'class': 'tooltip ' + object.id})
+                        .append(object.text));
+            };
+
+            var generateCssTooltip = function ($generated, object) {
+                var style = [];
+
+                if( object.color != null ) {
+                    style.push('background-color:   ' + object.color);
+                }
+                if( object.width != null ) {
+                    style.push('width:  ' + object.width);
+                }
+
+                if( style.length != 0 ) {
+                    $generated.append(
+                    '.' + object.id + '{' + style.join(';') + '}\n');
+                }
+
+                return $generated;
+            };
+
+            /*
+             * Returns the code used to call the viewer to the presentation.
+             */
+            var getBasicCallCode = function (jsonModel) {
+                var code = '(function($) {\n';
+                code += '    var myPresentation = dahuapp.viewer.createDahuViewer("#my-dahu-presentation", window.getParams);\n';
+                code += '    myPresentation.load(' + jsonModel + ');\n';
+                code += '    myPresentation.start();\n';
+                code += '})(jQuery);\n';
+                return code;
+            };
+
+            /*
+             * Generates the html body.
+             */
+            var generateHtmlBody = function ($generated, jsonModel, jsonGen) {
+                $('body', $generated)
+                    /* We could use a <section> here too, but it does not work with MS IE 8.
+                     Alternatively, adding this in the header would work:
+
+                     <!--[if lt IE 9]>
+                     <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
+                     <![endif]-->
+                     */
+                    .append($(document.createElement('div'))
+                        .attr({'id': 'my-dahu-presentation'}))
+                    .append($(document.createElement('script'))
+                        .attr({'src': 'dahuapp.js'})
+                        .attr({'type': 'text/javascript'}))
+                    .append($(document.createElement('script'))
+                        .attr({'src': 'dahuapp.viewer.js'})
+                        .attr({'type': 'text/javascript'}))
+                    .append($(document.createElement('script'))
+                        .attr({'type': 'text/javascript'})
+                        .append(getBasicCallCode(jsonGen)));
+                $('#my-dahu-presentation', $generated)
+                    .append($(document.createElement('div'))
+                        .attr({'id': 'loading'}).append("Loading presentation..."))
+                    .append($(document.createElement('div'))
+                        .attr({'class': 'object-list',
+                            'style': 'display: none'}))
+                    .append($(document.createElement('div'))
+                        .attr({'class': 'control'}));
+
+                /* Adding the objects to the page */
+                $.each(jsonModel.getObjectList(), function (id, object) {
+                    switch (object.type) {
+                        case "background":
+                            generateHtmlBackgroundImage($generated, object);
+                            break;
+                        case "tooltip":
+                            generateHtmlTooltip($generated, object);
+                            break;
+                        /* no mouse image generated here */
+                    }
+                });
+
+                /* Warning here, i'm not sure if $.each is always over, here */
+
+                /* Adding the control buttons to the page */
+                $('.control', $generated)
+                    .css({'top': (jsonModel.getImageHeight() + 16) + 'px'})
+                    .append($(document.createElement('button'))
+                        .attr({'class': 'previous'})
+                        .append('Previous'))
+                    .append($(document.createElement('button'))
+                        .attr({'class': 'next'})
+                        .append('Next'));
+
+                /* Adding the mouse cursor image */
+                $('.object-list', $generated)
+                    .append($(document.createElement('div'))
+                        .attr({'class': 'mouse-cursor'})
+                        .append($(document.createElement('img'))
+                            .attr({'src': 'img/cursor.png', 'alt': 'img/cursor.png', 'class': 'mouse-cursor-normal'}))
+                        .append($(document.createElement('img'))
+                            .attr({'src': 'img/cursor-pause.png', 'alt': 'img/cursor-pause.png',
+                                'style': 'display: none', 'class': 'mouse-cursor-pause'})));
+            };
+
+            /*
+             * Generates the css String with the Json model.
+             */
+            this.generateCssString = function (jsonModel) {
+                var $generated = $('<style></style>');
+
+                /* Going through each object */
+                $.each(jsonModel.getObjectList(), function (id, object) {
+                    switch (object.type) {
+                        case "tooltip":
+                            generateCssTooltip($generated, object);
+                            break;
+                        /* no mouse image generated here */
+                    }
+                });
+
+                return $generated.text();
+            }
+
+            /*
+             * Generates the html String with the Json model.
+             */
+            this.generateHtmlString = function (jsonModel, jsonGen, cssGen) {
+                /* Initialising the compilation area */
+                var $generated = $(document.createElement('div'));
+
+                /* We create the html using the json */
+                $generated.append($(document.createElement('html'))
+                    .attr({'lang': 'en'}));
+                $('html', $generated)
+                    .append($(document.createElement('head')))
+                    .append($(document.createElement('body')));
+
+                generateHtmlHeader($generated, cssGen);
+                generateHtmlBody($generated, jsonModel, jsonGen);
+
+                var result = htmlFormat($generated.html());
+
+                return '<!DOCTYPE html>\n' + result;
+            };
+
+            /*
+             * Generates the generated JSON using the JSONmodel.
+             * @param {object} jsonModel The json model to transform.
+             * @param {java.awt.Dimension} imgDim The dimension of images.
+             */
+            this.generateJsonString = function (jsonModel, imgDim) {
+                var generated = self.createScreencastGeneratedModel();
+                generated.setImageSize(imgDim.width, imgDim.height);
+                generated.setInitialBackground(jsonModel.getInitialBackground());
+                generated.setInitialMousePos(jsonModel.getInitialMousePos());
+                for (var i = 0; i < jsonModel.getNbSlide(); i++) {
+                    var actionList = jsonModel.getActionList(i);
+                    for (var j = 0; j < actionList.length; j++) {
+                        /* 
+                         * We don't add the two first actions of the first slide
+                         * because they are present in the presentation metadata.
+                         * It corresponds to the mouse initial pos and first background.
+                         */
+                        if (i > 0 || j > 1) {
+                            generated.addAction(actionList[j], imgDim.width, imgDim.height);
+                        }
+                    }
+                }
+                return generated.getJson();
+            };
+        };
+
+        /*
+         * Model for a JSON object that will only be used by the viewer, never
+         * saved on a file, used to transform the properties of actions
+         * specified on the JSON file of a presentation to executable
+         * functions for each action.
+         */
+        var DahuScreencastExecutableModel = function () {
+
+            /* Private API */
+
+            var json = {
+                metaData: {},
+                action: new Array()
+            };
+
+            /*
+             * Creates a functions representing the specified action, and adds
+             * it to this object.
+             * @param {object} action
+             */
+            var addExecutableAction = function (action) {
+                var executableAction = {
+                    'id': action.id,
+                    'trigger': action.trigger,
+                    'target': action.target,
+                    'delayAfter': action.delayAfter,
+                    'doneFunction': function (events, selector) {
+                        setTimeout(function () {
+                            events.onActionOver.publish(events, selector);
+                        }, this.delayAfter);
+                    }
+                };
+                if (executableAction.delayAfter == null) {
+                    executableAction.delayAfter = 200;
+                }
+                switch (action.type.toLowerCase()) {
+                    case "appear":
+                        executableAction.abs = (action.abs * json.metaData.imageWidth) + 'px';
+                        executableAction.ord = (action.ord * json.metaData.imageHeight) + 'px';
+                        executableAction.duration = action.duration;
+                        executableAction.execute = function (events, selector) {
+                            events.onActionStart.publish(events, selector);
+                            var sel = selector + ' .' + this.target;
+                            $(sel).css({
+                                'left': this.abs,
+                                'top': this.ord
+                            });
+                            $(sel).show(this.duration, function () {
+                                executableAction.doneFunction(events, selector);
+                            });
+                        };
+                        executableAction.executeReverse = function (selector) {
+                            $(selector + ' .' + this.target).hide();
+                        };
+                        executableAction.executeImmediately = function (selector) {
+                            var sel = selector + ' .' + this.target;
+                            $(sel).css({
+                                'left': this.abs,
+                                'top': this.ord
+                            });
+                            $(sel).show();
+                        };
+                        break;
+                    case "disappear":
+                        executableAction.duration = action.duration;
+                        executableAction.execute = function (events, selector) {
+                            events.onActionStart.publish(events, selector);
+                            $(selector + ' .' + this.target).hide(this.duration, function () {
+                                executableAction.doneFunction(events, selector);
+                            });
+                        };
+                        executableAction.executeReverse = function (selector) {
+                            $(selector + ' .' + this.target).show();
+                        };
+                        executableAction.executeImmediately = function (selector) {
+                            $(selector + ' .' + this.target).hide();
+                        };
+                        break;
+                    case "move":
+                        executableAction.finalAbs = (action.finalAbs * json.metaData.imageWidth) + 'px';
+                        executableAction.finalOrd = (action.finalOrd * json.metaData.imageHeight) + 'px';
+                        executableAction.duration = action.duration;
+                        executableAction.speed = action.speed;
+                        executableAction.execute = function (events, selector) {
+                            events.onActionStart.publish(events, selector);
+                            var sel = selector + ' .' + this.target;
+                            this.initialAbs = $(sel).css('left');
+                            this.initialOrd = $(sel).css('top');
+                            if (this.duration == null) {
+                                var initialAbsPix = this.initialAbs.replace('px', '');
+                                var initialOrdPix = this.initialOrd.replace('px', '');
+                                var finalAbsPix = this.finalAbs.replace('px', '');
+                                var finalOrdPix = this.finalOrd.replace('px', '');
+                                var distance = Math.sqrt(Math.pow(finalAbsPix - initialAbsPix, 2) +
+                                    Math.pow(finalOrdPix - initialOrdPix, 2));
+                                if (!this.speed) {
+                                    this.speed = .8; // in pixel per milisecond: fast, but not too much.
+                                }
+                                this.duration = distance + this.speed;
+                                if (this.duration < 200) { // Slow down a bit for short distances.
+                                    this.duration = 200;
+                                }
+                            }
+                            $(sel).animate({
+                                'left': this.finalAbs,
+                                'top': this.finalOrd
+                            }, this.duration, 'linear', function () {
+                                executableAction.doneFunction(events, selector);
+                            });
+                        };
+                        executableAction.executeReverse = function (selector) {
+                            $(selector + ' .' + this.target).css({
+                                'left': this.initialAbs,
+                                'top': this.initialOrd
+                            });
+                        };
+                        executableAction.executeImmediately = function (selector) {
+                            var sel = selector + ' .' + this.target;
+                            this.initialAbs = $(sel).css('left');
+                            this.initialOrd = $(sel).css('top');
+                            $(sel).css({
+                                'left': this.finalAbs,
+                                'top': this.finalOrd
+                            });
+                        };
+                        break;
+                    case "delay":
+                        executableAction.duration = action.duration;
+                        executableAction.execute = function (events, selector) {
+                            events.onActionStart.publish(events, selector);
+                            setTimeout(function () {
+                                events.onActionOver.publish(events, selector);
+                            }, this.duration);
+                        };
+                        executableAction.executeReverse = function (selector) {
+                            // Nothing!
+                        };
+                        executableAction.executeImmediately = function (selector) {
+                            // Nothing too!
+                        };
+                        break;
+                }
+                json.action.push(executableAction);
+            };
+
+            /* Public API */
+
+            /*
+             * Loads the specified JSON read from a 'presentation.json' file,
+             * and stores the functions representing each actions in an object.
+             * @param {object} jsonToLoad
+             */
+            this.loadJson = function (jsonToLoad) {
+                json.metaData = jsonToLoad.metaData;
+                for (var i = 0; i < jsonToLoad.action.length; i++) {
+                    addExecutableAction(jsonToLoad.action[i]);
+                }
+            };
+
+            /*
+             * Returns the object containing the functions.
+             */
+            this.getJson = function () {
+                return json;
+            };
+        };
+
+        /*
+         * Used to transform the 'dahu' file to a JSON file used by the viewer,
+         * which only contains some metaData and a list of actions.
+         */
+        var DahuScreencastGeneratedModel = function () {
+
+            /* Private API */
+
+            var json = {
+                metaData: {},
+                action: new Array()
+            };
+
+            /* Public API */
+
+            /*
+             * Returns a string representation of this json.
+             * @returns {String}
+             */
+            this.getJson = function () {
+                return JSON.stringify(json, null, '    ');
+            };
+
+            /*
+             * Setters for the generated json metadata.
+             */
+            this.setImageSize = function (width, height) {
+                json.metaData.imageWidth = width;
+                json.metaData.imageHeight = height;
+            };
+
+            this.setInitialMousePos = function (pos) {
+                json.metaData.initialMouseX = pos.x;
+                json.metaData.initialMouseY = pos.y;
+            };
+
+            this.setInitialBackground = function (id) {
+                json.metaData.initialBackgroundId = id;
+            };
+
+            this.addAction = function (action) {
+                json.action.push(action);
+            };
+
+            /*
+             * Transforms a JSON containing action properties to a JSON containing
+             * the execution functions.
+             * @param {Object} json
+             * @returns {Object} A json object containing the execution functions
+             * for all the actions of the presentation.
+             */
+            this.toExecutableList = function (json) {
+                var executableList = {
+                    metaData: json.metaData,
+                    action: new Array()
+                };
+                for (var i = 0; i < json.action.length; i++) {
+                    addExecutableAction(executableList, json.action[i]);
+                }
+                return executableList;
+            };
+        };
+
+        /*
+         * Represents a 'dahu' file for a project.
+         */
+        var DahuScreencastModel = function () {
+
+            /* Private API */
+
+            var json = {};
+
+            /*
+             * Generates a unique action ID.
+             *
+             * With this implementation, this ID is unique as long as the user
+             * doesn't replace it manually with a not kind value or replace
+             * the nextUniqueId with bad intentions.
+             * But maybe that a UUID is a bit tiresome to put as an anchor...
+             */
+            var generateUniqueActionId = function () {
+                json.metaData.nextUniqueId++;
+                return json.metaData.nextUniqueId.toString();
+            };
+
+            /*
+             * This method checks if the json representing a dahu project is
+             * in the good version. It's in case a project file was created
+             * with a previous version of Dahu, and that some fields are
+             * missing.
+             *
+             * It doesn't control each field (at the moment) but only the
+             * fields that can be missing due to a new Dahu version and not
+             * to a manual editing.
+             *
+             * This method doesn't check any Json syntax or something like that.
+             */
+            var upgradeJsonVersion = function () {
+                // Checks if unique IDs are in the project file
+                if (!json.metaData.nextUniqueId) {
+                    var currentId = 0;
+                    for (var i = 0; i < json.data.length; i++) {
+                        for (var j = 0; j < json.data[i].action.length; j++) {
+                            json.data[i].action[j].id = currentId.toString();
+                            currentId++;
+                        }
+                    }
+                    json.metaData.nextUniqueId = currentId;
+                }
+            };
+
+            /* Public API */
+
+            /*
+             * json variable generated from a JSON file.
+             * @param String stringJson String loaded from JSON file.
+             */
+            this.loadJson = function (stringJson) {
+                json = JSON.parse(stringJson);
+                upgradeJsonVersion();
+            };
+
+            /*
+             * Create a new presentation variable in the JSON file which will contain slides.
+             */
+            this.createPresentation = function (width, height) {
+                json.metaData = {};
+                json.metaData.imageWidth = width;
+                json.metaData.imageHeight = height;
+                json.metaData.nextUniqueId = 0;
+                json.data = new Array();
+            };
+
+            /*
+             * Add a new slide in the presentation variable of the JSON file.
+             * @param int index wanted for the slide.
+             * @param String idSlide Unique identifier for the slide.
+             * @param String img Related to pathname of the image.
+             * @param double mouseX Abscissa mouse position in %.
+             * @param double mouseY Ordinate mouse position in %.
+             * @return Index of the newly added slide.
+             */
+            this.addSlide = function (indexSlide, idSlide, img, mouseX, mouseY, speed) {
+                var slide = {
+                    "object": new Array(),
+                    "action": new Array()
+                };
+                json.data.splice(indexSlide, 0, slide);
+                this.addObject(indexSlide, "background", idSlide, img);
+                this.addObject(indexSlide, "mouse");
+                this.addAction(indexSlide, "move", json.data[indexSlide].object[1].id, "onClick", mouseX, mouseY, speed);
+                this.addAction(indexSlide, "appear", json.data[indexSlide].object[0].id, "afterPrevious");
+            };
+
+            /*
+             * Object factory.
+             * Add a new Object in the slide idSlide.
+             * @param int idSlide
+             * @param string type
+             * Other params can be specified depending on the object's type.
+             */
+            this.addObject = function (idSlide, type) {
+				var object = {
+                    "type": type
+                };
+                switch (type.toLowerCase()) {
+                    case "background":
+                        object.id = arguments[2] + json.data[idSlide].object.length;
+                        object.img = arguments[3] || "";
+                        break;
+                    case "mouse":
+                        object.id = "mouse-cursor";
+                        break;
+                    case "tooltip":
+                        /*
+                         * TODO: we'll need a more robust unique name
+                         * when we start actually using this.
+                         */
+                        object.id = "s" + idSlide + "-o" + json.data[idSlide].object.length;
+                        object.text = arguments[2] || "";
+                        object.color = arguments[3] || null;
+						object.width = arguments[4] || "";
+						json.data[idSlide].object.push(object);
+						var objectLength = json.data[idSlide].object.length;
+						var last = json.data[idSlide].object[objectLength-1];
+						json.data[idSlide].object[objectLength-1] = 
+								json.data[idSlide].object[objectLength-2];
+						json.data[idSlide].object[objectLength-2] = last;
+                        break;
+                }
+				if(type.toLowerCase() != "tooltip"){
+					json.data[idSlide].object.push(object);
+				}
+            };
+
+            /*
+             * Action factory.
+             * Add a new Action in the slide idSlide whose target is the id of an object.
+             * Three types of trigger : "withPrevious", "afterPrevious", "onClick".
+             * @param int idSlide
+             * @param string type
+             * @param string target
+             * @param string trigger
+             * Other params can be specified depending on the object's type.
+             */
+            this.addAction = function (idSlide, type, target, trigger) {
+                var action = {
+                    "id": generateUniqueActionId(),
+                    "type": type,
+                    "target": target,
+                    "trigger": trigger
+                };
+                switch (type.toLowerCase()) {
+                    case "appear":
+                        action.abs = arguments[4] || 0.0;
+                        action.ord = arguments[5] || 0.0;
+                        action.duration = arguments[6] || 0;
+                        break;
+                    case "disappear":
+                        action.duration = arguments[4] || 0;
+                        break;
+                    case "move":
+                        action.finalAbs = arguments[4] || 0.0;
+                        action.finalOrd = arguments[5] || 0.0;
+                        action.speed = arguments[6] || 0;
+                        break;
+                }
+                json.data[idSlide].action.push(action);
+            };
+
+            this.editMouse = function (idSlide, idAction, mouseX, mouseY) {
+                json.data[idSlide].action[idAction].finalAbs = mouseX;
+                json.data[idSlide].action[idAction].finalOrd = mouseY;
+            };
+
+            /*
+             * Sets a title for the presentation.
+             * @param String title Title to set.
+             */
+            this.setTitle = function (title) {
+                json.metaData.title = title;
+            };
+
+            /*
+             * Sets an annotation for the presentation.
+             * @param String annotation Annotation to set.
+             */
+            this.setAnnotation = function (annotation) {
+                json.metaData.annotation = annotation;
+            };
+
+            /*
+             * Inverts the two slides (their positions on the table).
+             * @param int idSlide1 Index of the first slide.
+             * @param int idSlide2 Index of the second slide.
+             */
+            this.invertSlides = function (idSlide1, idSlide2) {
+                var tmp = json.data[idSlide1];
+                json.data[idSlide1] = json.data[idSlide2];
+                json.data[idSlide2] = tmp;
+            };
+
+            /*
+             * Inverts the two actions (their positions on the table).
+             * @param int idSlide
+             * @param int idAction1
+             * @param int idAction2
+             */
+            this.invertActions = function (idSlide, idAction1, idAction2) {
+                var tmp = json.data[idSlide].action[idAction1];
+                json.data[idSlide].action[idAction1] = json.data[idSlide].action[idAction2];
+                json.data[idSlide].action[idAction2] = tmp;
+            };
+
+            /*
+             * Returns the actions on the specified slide.
+             * @returns {Array}
+             */
+            this.getActionList = function (idSlide) {
+                return json.data[idSlide].action;
+            };
+
+            /*
+             * Catches all the objects of the presentation
+             * @returns {Array} List of objects of the presentation
+             */
+            this.getObjectList = function () {
+                var objectList = new Array();
+                var indexSlide = 0;
+                while (json.data[indexSlide]) {
+                    var indexObject = 0;
+                    while (json.data[indexSlide].object[indexObject]) {
+                        objectList.push(json.data[indexSlide].object[indexObject]);
+                        indexObject++;
+                    }
+                    indexSlide++;
+                }
+                return objectList;
+            };
+
+            /*
+             * Returns an array containing all the background images.
+             * @returns {Array} List of background images.
+             */
+            this.getImageList = function () {
+                var list = new Array();
+                var indexSlide = 0;
+                while (json.data[indexSlide]) {
+                    list.push(json.data[indexSlide].object[0].img);
+                    indexSlide++;
+                }
+                ;
+                return list;
+            };
+
+            /*
+             * Returns an object containing the id of the first background.
+             * @returns {string} Id of the first background.
+             */
+            this.getInitialBackground = function () {
+                return json.data[0].object[0].id;
+            };
+
+            /*
+             * Returns an object containing the initial mouse position.
+             * @returns {object} Initial position of mouse.
+             */
+            this.getInitialMousePos = function () {
+                var pos = {};
+                pos.x = json.data[0].action[0].finalAbs;
+                pos.y = json.data[0].action[0].finalOrd;
+                return pos;
+            };
+
+            /*
+             * Removes the slide at the specified index.
+             * @param {int} idSlide
+             */
+            this.removeSlide = function (idSlide) {
+                json.data.splice(idSlide, 1);
+            };
+
+            /*
+             * Removes the action at the specified slide.
+             * @param {int} idSlide
+             * @param {int} idAction
+             */
+            this.removeAction = function (idSlide, idAction) {
+                json.data[idSlide].action.splice(idAction, 1);
+            };
+
+            /*
+             * Removes the specified object from the slide.
+             * Also removes all the actions attached to this object.
+             * @param {int} idSlide
+             * @param {int} idObject
+             */
+            this.removeObject = function (idSlide, idObject) {
+                var removed = json.data[idSlide].object.splice(idObject, 1);
+                for (var i = 0; i < json.data.length; i++) {
+                    var j = 0;
+                    while (json.data[i].action[j]) {
+                        if (json.data[i].action[j].target === removed.id) {
+                            json.data[i].action.splice(j, 1);
+                        } else {
+                            j++;
+                        }
+                    }
+                }
+            };
+
+            /*
+             * @returns {String}
+             */
+            this.getJson = function () {
+                return JSON.stringify(json, null, '    ');
+            };
+
+            /*
+             * @returns {String} returns the index for the next slide.
+             */
+            this.getNbSlide = function () {
+                return json.data.length;
+            };
+
+            /*
+             * @return {object} returns the object identified by idSlide
+             */
+            this.getSlide = function (idSlide) {
+                return json.data[idSlide];
+            };
+
+            /*
+             * Sets the size of images for the generated presentation.
+             * The parameters can be null : it means there are no
+             * requirement for this dimension.
+             * @param {int} width New width for the generated images.
+             * @param {int} height New height for the generated images.
+             */
+            this.setImageSizeRequirements = function (width, height) {
+                json.metaData.imageWidth = width;
+                json.metaData.imageHeight = height;
+            };
+
+            /*
+             * Gets the width of images for the generated presentation.
+             * @return {int or null} Width of generated images.
+             */
+            this.getImageWidth = function () {
+                return json.metaData.imageWidth;
+            };
+
+            /*
+             * Gets the height of images for the generated presentation.
+             * @return {int or null} Height of generated images.
+             */
+            this.getImageHeight = function () {
+                return json.metaData.imageHeight;
+            };
+
+            /*
+             * Gets a background image (no one in particular, the first met).
+             * @return {string} The name of a background image.
+             */
+            this.getABackgroundImage = function () {
+                for (var i = 0; i < json.data.length; i++) {
+                    for (var j = 0; j < json.data[i].object.length; j++) {
+                        if (json.data[i].object[j].type === "background") {
+                            return json.data[i].object[j].img;
+                        }
+                    }
+                }
+                return null;
+            };
+        };
+
+        /* public API */
+
+        self.version = "0.0.1";
+
+        self.createScreencastGenerator = function createScreencastGenerator() {
+            return new DahuScreencastGenerator();
+        };
+
+        self.createScreencastModel = function createScreencastModel() {
+            return new DahuScreencastModel();
+        };
+
+        self.createScreencastExecutableModel = function createScreencastExecutableModel() {
+            return new DahuScreencastExecutableModel();
+        };
+
+        self.createScreencastGeneratedModel = function createScreencastGeneratedModel() {
+            return new DahuScreencastGeneratedModel();
+        };
+
+        return self;
+    })();
+
+    window.dahuapp = dahuapp;
+
+})(window, jQuery);
\ No newline at end of file
diff --git a/TP/TP01/P03/dahuapp.viewer.css b/TP/TP01/P03/dahuapp.viewer.css
new file mode 100644
index 0000000000000000000000000000000000000000..ea934576dad6ca9263a6163de58f5fe4534d1f0b
--- /dev/null
+++ b/TP/TP01/P03/dahuapp.viewer.css
@@ -0,0 +1,25 @@
+.object-list {
+    position: absolute;
+    margin: 0px;
+    padding: 0px;
+}
+
+.mouse-cursor {
+    position: absolute;
+}
+
+.control {
+    position: relative;
+}
+
+.background {
+    position: absolute;
+}
+
+.tooltip {
+    position: absolute;
+    width: 100px;
+    padding: 4px;
+    margin: 2px;
+    border: 2px black solid;
+}
\ No newline at end of file
diff --git a/TP/TP01/P03/dahuapp.viewer.js b/TP/TP01/P03/dahuapp.viewer.js
new file mode 100644
index 0000000000000000000000000000000000000000..12ad03b2cdc503a8fd6ff3f5fdc2a74edbb36edb
--- /dev/null
+++ b/TP/TP01/P03/dahuapp.viewer.js
@@ -0,0 +1,456 @@
+"use strict";
+
+/**
+ * Dahuapp viewer module.
+ * 
+ * @param   dahuapp     dahuapp object to augment with module.
+ * @param   $           jQuery
+ * @returns dahuapp extended with viewer module.
+ */
+
+(function(dahuapp, $) {
+    var viewer = (function() {
+
+        var self = {};
+
+        var DahuViewerModel = function(select, getParams) {
+
+            /* Private API */
+
+            var json = null;
+            var selector = select;
+            var parseAutoOption = function(name, defaultValue) {
+                var res = getParams[name];
+                if (res == null) {
+                    if (name == 'auto') {
+                        return false;
+                    } else {
+                        return parseAutoOption('auto', defaultValue);
+                    }
+                }
+                if (res.toLowerCase() === 'false') {
+                    return false;
+                }
+                res = parseInt(res);
+                if (isNaN(res)) {
+                    // e.g. ?autoplay or ?autoplay=true
+                    return defaultValue;
+                }
+                return res;
+            }
+
+            /* Whether to wait for "next" event between actions */
+            var autoPlay = parseAutoOption('autoplay', 5000);
+            /* Whether to wait for "next" event on page load */
+            var autoStart = parseAutoOption('autostart', 5000);
+            /* Whether to loop back to start at the end of presentation */
+            var autoLoop = parseAutoOption('autoloop', 5000);
+
+            var events = (function() {
+                var self = {};
+                /*
+                 * Creates a generic event.
+                 */
+                var createEvent = function() {
+                    var callbacks = $.Callbacks();
+                    return {
+                        publish: callbacks.fire,
+                        subscribe: callbacks.add,
+                        unsubscribe: callbacks.remove,
+                        unsubscribeAll: callbacks.empty
+                    };
+                };
+                /*
+                 * Called when the button next is pressed.
+                 */
+                self.onNext = createEvent();
+                /*
+                 * Called when the button previous is pressed.
+                 */
+                self.onPrevious = createEvent();
+                /*
+                 * Called when an action is over.
+                 */
+                self.onActionOver = createEvent();
+                /*
+                 * Called when an action starts.
+                 */
+                self.onActionStart = createEvent();
+                /*
+                 * Called when at least one action was running and has finished.
+                 */
+                self.onAllActionFinish = createEvent();
+                
+                return self;
+            })();
+
+            /*
+             * Variables used like index for methodes subscribed.
+             */
+            var currentAction = 0;    /* Action currently running */
+            var nextAction = 0;       /* Action to execute on 'Next' click */
+            var nbActionsRunning = 0;
+            var previousAnchor = -1;  /* Last entered anchor, only used in
+                                       * case the 'onhashchange' event is
+                                       * not supported by the web browser */
+            
+            /*
+             * Functions to reinitialise running actions and subscribed
+             * callbacks (reinitialise is called once without stopping
+             * all the actions, so the two functions are separated).
+             */
+            var stopAllActions = function() {
+                $(selector + " .object-list").children().stop(true, true);
+                reinitialiseCallbackLists();
+                nbActionsRunning = 0;
+            };
+            var reinitialiseCallbackLists = function() {
+                events.onActionStart.unsubscribeAll();
+                events.onActionStart.subscribe(onActionStartEventHandler);
+                events.onAllActionFinish.unsubscribeAll();
+            };
+            
+            /*
+             * Timer used to program onNext event in autoplay mode.
+             */
+            var playTimer = 0;
+
+            /*
+             * Function used when an "onNextEvent" event is caught.
+             */
+            var onNextEventHandler = function() {
+                /*
+                 * If the user pressed "next" before an autoplay event
+                 * is triggered, it replaces the autoplay event, hence
+                 * cancels it:
+                 */
+                window.clearTimeout(playTimer);
+
+                enterAnimationMode();
+                stopAllActions();
+                var tmpAction = nextAction;
+                var onlyWithPrevious = true;
+                nextAction++;
+                currentAction = nextAction;
+                while (json.action[currentAction] && onlyWithPrevious) {
+                    switch (json.action[currentAction].trigger) {
+                        case 'onClick':
+                            nextAction = currentAction;
+                            onlyWithPrevious = false;
+                            break;
+                        case 'withPrevious':
+                            var tmp = currentAction;
+                            /*
+                             * We can't directly pass 'execute' as callback because we
+                             * allow the 'execute' function to reference a property of the
+                             * object (by using 'this.property') so we have to call the
+                             * function in the containing object to make that available
+                             */
+                            events.onActionStart.subscribe(function(events, selector) {
+                                json.action[tmp].execute(events, selector);
+                            });
+                            break;
+                        case 'afterPrevious':
+                            var tmp = currentAction;
+                            events.onAllActionFinish.subscribe(function(events, selector) {
+                                json.action[tmp].execute(events, selector);
+                            });
+                            while (json.action[nextAction] && json.action[nextAction].trigger !== 'onClick') {
+                                nextAction++;
+                            }
+                            onlyWithPrevious = false;
+                            break;
+                    }
+                    currentAction++;
+                }
+                if (json.action[tmpAction]) {
+                    launch(json.action[tmpAction]);
+                }
+                if (nextAction > json.action.length) {
+                    nextAction = json.action.length;
+                }
+            };
+            
+            /*
+             * Function used when an "onPreviousEvent" event is caught.
+             */
+            var onPreviousEventHandler = function() {
+                stopAllActions();
+                currentAction = nextAction - 1;
+                while (json.action[currentAction] && json.action[currentAction].trigger !== 'onClick') {
+                    launchReverse(json.action[currentAction]);
+                    currentAction--;
+                }
+                /* currentAction = -1 means that it's the beginning */
+                if (currentAction > -1) {
+                    launchReverse(json.action[currentAction]);
+                }
+                if (currentAction < 0) {
+                    currentAction = 0;
+                }
+                nextAction = currentAction;
+            };
+
+            /*
+             * Function used when an "onActionOverEvent" event is caught.
+             */
+            var onActionOverEventHandler = function() {
+                nbActionsRunning--;
+                if (nbActionsRunning === 0) {
+                    events.onAllActionFinish.publish(events, selector);
+                    reinitialiseCallbackLists();
+                    while (json.action[currentAction]) {
+                        switch (json.action[currentAction].trigger) {
+                            case 'onClick':
+                                leaveAnimationMode();
+                                if (autoPlay && nbActionsRunning === 0) {
+                                    playTimer = setTimeout(function () {
+                                        events.onNext.publish();
+                                    }, autoPlay);
+                                }
+                                return;
+                            case 'withPrevious':
+                                var tmp = currentAction;
+                                events.onActionStart.subscribe(function(events, selector) {
+                                    json.action[tmp].execute(events, selector);
+                                });
+                                break;
+                            case 'afterPrevious':
+                                var tmp = currentAction;
+                                events.onAllActionFinish.subscribe(function(events, selector) {
+                                    json.action[tmp].execute(events, selector);
+                                });
+                                currentAction++;
+                                return;
+                        }
+                        currentAction++;
+
+                    }
+                    if (autoLoop && nbActionsRunning === 0) {
+                        setTimeout(function () {
+                            resetPresentation();
+                            startPresentationMaybe();
+                        }, autoLoop);
+                    }
+                }
+                leaveAnimationMode();
+            };
+
+            /*
+             * Function used when an "onActionStartEvent" event is caught.
+             */
+            var onActionStartEventHandler = function() {
+                nbActionsRunning++;
+            };
+
+            /*
+             * Enter and leave "animation" mode. The viewer is in
+             * animation mode when something is going on without human
+             * interaction (i.e. executing actions before the next
+             * "onClick").
+             */
+            var enterAnimationMode = function () {
+                $(selector + ' .mouse-cursor-pause').hide();
+                $(selector + ' .mouse-cursor-normal').show();
+            };
+
+            var leaveAnimationMode = function () {
+                $(selector + ' .mouse-cursor-pause').show();
+                $(selector + ' .mouse-cursor-normal').hide();
+            };
+
+            /*
+             * Function used to perform actions.
+             */
+            var launch = function(action) {
+                action.execute(events, selector);
+            };
+            var launchReverse = function(action) {
+                action.executeReverse(selector);
+            };
+
+            /*
+             * The two following methods each returns an array containing the
+             * actions to execute to reach the given anchor (respectively
+             * forward or backwards).
+             *
+             * If the given anchor matches none of the actions, then an empty
+             * array is returned.
+             */
+            var getActionsToJumpForward = function(anchor) {
+                var actions = new Array();
+                for (var i = nextAction; i < json.action.length; i++) {
+                    // '==' and not '===' because we compare indifferently a
+                    // number or a string or anything else
+                    if (json.action[i].id == anchor) {
+                        return actions;
+                    } else {
+                        actions.push(json.action[i]);
+                    }
+                }
+                // Here, the anchor has not been found during the action scan
+                return null;
+            };
+            var getActionsToJumpBackwards = function(anchor) {
+                var actions = new Array();
+                for (var i = nextAction - 1; i >= 0; i--) {
+                    // '==' and not '===' because we compare indifferently a
+                    // number or a string or anything else
+                    actions.push(json.action[i]);
+                    if (json.action[i].id == anchor) {
+                        return actions;
+                    }
+                }
+                // Here, the anchor has not been found during the action scan
+                return null;
+            };
+            
+            /*
+             * Updates the position of the presentation depending on the
+             * given anchor (next action wanted).
+             */
+            var jumpToAnchor = function(anchor) {
+                if (anchor !== '') {
+                    stopAllActions();
+                    if (anchor > nextAction) {
+                        // forward
+                        var actions = getActionsToJumpForward(anchor);
+                        for (var i = 0; i < actions.length; i++) {
+                            actions[i].executeImmediately(selector);
+                        }
+                        nextAction += actions.length;
+                    } else {
+                        // backwards
+                        var actions = getActionsToJumpBackwards(anchor);
+                        for (var i = 0; i < actions.length; i++) {
+                            actions[i].executeReverse(selector);
+                        }
+                        nextAction -= actions.length;
+                    }
+                }
+            };
+
+            var resetPresentation = function() {
+                window.clearTimeout(playTimer);
+
+                currentAction = 0;
+                nextAction = 0;
+                nbActionsRunning = 0;
+
+                /*
+                 * At the beginning, the visible image is the first one of the presentation
+                 */
+                $(selector + " .object-list").children().hide();
+
+                $(selector + " ." + json.metaData.initialBackgroundId).show();
+
+                $(selector + " .mouse-cursor").css({
+                    'top': (json.metaData.initialMouseY * json.metaData.imageHeight) + "px",
+                    'left': (json.metaData.initialMouseX * json.metaData.imageWidth) + "px"
+                });
+
+                $(selector + " .mouse-cursor").show();
+            }
+
+            var startPresentationMaybe = function() {
+                if (autoStart) {
+                    playTimer = setTimeout(function () {
+                        events.onNext.publish();
+                    }, autoStart);
+                }
+            }
+
+            /* Public API */
+
+            this.loadUrl = function(url) {
+                var dataJson;
+                $.ajax({
+                    'async': false,
+                    'global': false,
+                    'url': url,
+                    'dataType': "json",
+                    'success': function(data) {
+                        dataJson = data;
+                    }
+                });
+                
+                load(dataJson);
+            };
+
+            this.load = function(dataJson) {
+                /* Transforms data JSON to executable functions for actions */
+                var execModel = dahuapp.createScreencastExecutableModel();
+                execModel.loadJson(dataJson);
+                json = execModel.getJson();
+            };
+
+            this.start = function() {
+                
+                /*
+                 * Subscription of methods to their events.
+                 */
+                events.onNext.subscribe(onNextEventHandler);
+                events.onPrevious.subscribe(onPreviousEventHandler);
+                events.onActionOver.subscribe(onActionOverEventHandler);
+                events.onActionStart.subscribe(onActionStartEventHandler);
+
+                resetPresentation();
+
+                /*
+                 * If an anchor has been specified, we place the presentation
+                 * in the right position.
+                 */
+                jumpToAnchor(window.location.hash.substring(1));
+                
+                /*
+                 * If the anchor changes during the presentation, then the
+                 * presentation is updated
+                 */
+                if ("onhashchange" in window) {
+                    // event supported
+                    window.onhashchange = function () {
+                        jumpToAnchor(window.location.hash.substring(1));
+                    };
+                } else {
+                    // event not supported : periodical check
+                    window.setInterval(function () {
+                        if (window.location.hash.substring(1) !== previousAnchor) {
+                            previousAnchor = window.location.hash.substring(1);
+                            jumpToAnchor(window.location.hash.substring(1));
+                        }
+                    }, 100);
+                }
+
+                /*
+                 * A click on the "next" button publishes a nextSlide event
+                 */
+                $(selector + " .next").click(function() {
+                    events.onNext.publish();
+                });
+                
+                /*
+                 * A click on the "previous" button publishes a previousSlide event
+                 */
+                $(selector + " .previous").click(function() {
+                    events.onPrevious.publish();
+                });
+
+                /*
+                 * Everything is ready, show the presentation
+                 * (hidden with style="display: none" from HTML).
+                 */
+                $("#loading").hide();
+                $(selector + " .object-list").show();
+                startPresentationMaybe();
+            };
+        };
+
+        self.createDahuViewer = function(selector, getParams) {
+            return new DahuViewerModel(selector, getParams);
+        };
+
+        return self;
+    })();
+
+    dahuapp.viewer = viewer;
+})(dahuapp || {}, jQuery);
diff --git a/TP/TP01/P03/img/01f96120-1e41-b4d3-b844-e73128943106.png b/TP/TP01/P03/img/01f96120-1e41-b4d3-b844-e73128943106.png
new file mode 100644
index 0000000000000000000000000000000000000000..5f9eef58766110297995e501937aed46ba98fe35
Binary files /dev/null and b/TP/TP01/P03/img/01f96120-1e41-b4d3-b844-e73128943106.png differ
diff --git a/TP/TP01/P03/img/0edbde4a-26ff-f2d1-a88f-5a13affc536a.png b/TP/TP01/P03/img/0edbde4a-26ff-f2d1-a88f-5a13affc536a.png
new file mode 100644
index 0000000000000000000000000000000000000000..36ad307589db8451026710a4dcd80a262bf28cee
Binary files /dev/null and b/TP/TP01/P03/img/0edbde4a-26ff-f2d1-a88f-5a13affc536a.png differ
diff --git a/TP/TP01/P03/img/1d9e476d-7d77-fc65-07f3-77174933ac05.png b/TP/TP01/P03/img/1d9e476d-7d77-fc65-07f3-77174933ac05.png
new file mode 100644
index 0000000000000000000000000000000000000000..92449bd3cf3a46b6efd78899aa0ff65126793dd6
Binary files /dev/null and b/TP/TP01/P03/img/1d9e476d-7d77-fc65-07f3-77174933ac05.png differ
diff --git a/TP/TP01/P03/img/30962ad0-d4f1-7ca3-4eaa-45b0066b27eb.png b/TP/TP01/P03/img/30962ad0-d4f1-7ca3-4eaa-45b0066b27eb.png
new file mode 100644
index 0000000000000000000000000000000000000000..cba92a12052b130e626c1768aa4cbd2df781f617
Binary files /dev/null and b/TP/TP01/P03/img/30962ad0-d4f1-7ca3-4eaa-45b0066b27eb.png differ
diff --git a/TP/TP01/P03/img/321ec853-941a-e69e-7fa1-eac7f318f366.png b/TP/TP01/P03/img/321ec853-941a-e69e-7fa1-eac7f318f366.png
new file mode 100644
index 0000000000000000000000000000000000000000..b300b3db44c9291fcbf085907c3a7decc20b7baf
Binary files /dev/null and b/TP/TP01/P03/img/321ec853-941a-e69e-7fa1-eac7f318f366.png differ
diff --git a/TP/TP01/P03/img/343ed3f0-798d-32ed-eb69-f5b428471c19.png b/TP/TP01/P03/img/343ed3f0-798d-32ed-eb69-f5b428471c19.png
new file mode 100644
index 0000000000000000000000000000000000000000..23da656ec16eadc9f5675587c57f2b9424a87e46
Binary files /dev/null and b/TP/TP01/P03/img/343ed3f0-798d-32ed-eb69-f5b428471c19.png differ
diff --git a/TP/TP01/P03/img/4a0ff2c8-7524-5a5b-6e82-b37642bf6def.png b/TP/TP01/P03/img/4a0ff2c8-7524-5a5b-6e82-b37642bf6def.png
new file mode 100644
index 0000000000000000000000000000000000000000..23feed0211d841efd9b9c73d382348a2f8f40071
Binary files /dev/null and b/TP/TP01/P03/img/4a0ff2c8-7524-5a5b-6e82-b37642bf6def.png differ
diff --git a/TP/TP01/P03/img/4fb5ffee-c72d-a569-8ff7-c133d14386ed.png b/TP/TP01/P03/img/4fb5ffee-c72d-a569-8ff7-c133d14386ed.png
new file mode 100644
index 0000000000000000000000000000000000000000..74db8c961b541871e7de6be496d35c53d3f336c3
Binary files /dev/null and b/TP/TP01/P03/img/4fb5ffee-c72d-a569-8ff7-c133d14386ed.png differ
diff --git a/TP/TP01/P03/img/69e4196e-0ab1-2975-bcca-7ad56cb9f246.png b/TP/TP01/P03/img/69e4196e-0ab1-2975-bcca-7ad56cb9f246.png
new file mode 100644
index 0000000000000000000000000000000000000000..bbcbe390c886d91a82afe8f12ab309e6a26184ff
Binary files /dev/null and b/TP/TP01/P03/img/69e4196e-0ab1-2975-bcca-7ad56cb9f246.png differ
diff --git a/TP/TP01/P03/img/8248ea8c-777a-3f5f-4441-4e7e65f06321.png b/TP/TP01/P03/img/8248ea8c-777a-3f5f-4441-4e7e65f06321.png
new file mode 100644
index 0000000000000000000000000000000000000000..15a46083d07ff3bceae1ca7007c194801cd5bc49
Binary files /dev/null and b/TP/TP01/P03/img/8248ea8c-777a-3f5f-4441-4e7e65f06321.png differ
diff --git a/TP/TP01/P03/img/88ed11b7-b2c8-fdc3-4433-c3a5f9400f6d.png b/TP/TP01/P03/img/88ed11b7-b2c8-fdc3-4433-c3a5f9400f6d.png
new file mode 100644
index 0000000000000000000000000000000000000000..73284b03571c4c91522b120229921e8fd943c88e
Binary files /dev/null and b/TP/TP01/P03/img/88ed11b7-b2c8-fdc3-4433-c3a5f9400f6d.png differ
diff --git a/TP/TP01/P03/img/9e61a3ea-c0cf-ce23-4caa-ea8be8b8180d.png b/TP/TP01/P03/img/9e61a3ea-c0cf-ce23-4caa-ea8be8b8180d.png
new file mode 100644
index 0000000000000000000000000000000000000000..184c7a280b9aaca34cb05860f1ab12dccb94bc74
Binary files /dev/null and b/TP/TP01/P03/img/9e61a3ea-c0cf-ce23-4caa-ea8be8b8180d.png differ
diff --git a/TP/TP01/P03/img/cursor-pause.png b/TP/TP01/P03/img/cursor-pause.png
new file mode 100644
index 0000000000000000000000000000000000000000..9c93cc142f4326a188eafc3c0fe96f2975dcab0c
Binary files /dev/null and b/TP/TP01/P03/img/cursor-pause.png differ
diff --git a/TP/TP01/P03/img/cursor.png b/TP/TP01/P03/img/cursor.png
new file mode 100644
index 0000000000000000000000000000000000000000..5b4a20e63078ce18bb11a4e601a1994b261522ef
Binary files /dev/null and b/TP/TP01/P03/img/cursor.png differ
diff --git a/TP/TP01/P03/img/e198e134-62de-f434-c767-5c759618a866.png b/TP/TP01/P03/img/e198e134-62de-f434-c767-5c759618a866.png
new file mode 100644
index 0000000000000000000000000000000000000000..2503f6e0d09087043e3615ae1e5b0cced2ad0fa7
Binary files /dev/null and b/TP/TP01/P03/img/e198e134-62de-f434-c767-5c759618a866.png differ
diff --git a/TP/TP01/P03/img/edcbee3f-34e7-d5af-c956-28b9d0680a7b.png b/TP/TP01/P03/img/edcbee3f-34e7-d5af-c956-28b9d0680a7b.png
new file mode 100644
index 0000000000000000000000000000000000000000..cba92a12052b130e626c1768aa4cbd2df781f617
Binary files /dev/null and b/TP/TP01/P03/img/edcbee3f-34e7-d5af-c956-28b9d0680a7b.png differ
diff --git a/TP/TP01/P03/img/ee3f999c-8cdc-3624-ed00-917c9f8025b0.png b/TP/TP01/P03/img/ee3f999c-8cdc-3624-ed00-917c9f8025b0.png
new file mode 100644
index 0000000000000000000000000000000000000000..67cc3bd3d236ee38f57d1d27502f4d602b72dd71
Binary files /dev/null and b/TP/TP01/P03/img/ee3f999c-8cdc-3624-ed00-917c9f8025b0.png differ
diff --git a/TP/TP01/P03/img/f5a01d52-fc33-d9e0-2ddf-4ec7f9f4799f.png b/TP/TP01/P03/img/f5a01d52-fc33-d9e0-2ddf-4ec7f9f4799f.png
new file mode 100644
index 0000000000000000000000000000000000000000..954c399b01c000298ebbf481a450fd1d1e83193d
Binary files /dev/null and b/TP/TP01/P03/img/f5a01d52-fc33-d9e0-2ddf-4ec7f9f4799f.png differ
diff --git a/TP/TP01/P03/img/f716c082-83a5-4cd2-1e69-4c66c05f2c48.png b/TP/TP01/P03/img/f716c082-83a5-4cd2-1e69-4c66c05f2c48.png
new file mode 100644
index 0000000000000000000000000000000000000000..d94082e69a1b8faac4b5aebbe003799e265af3a5
Binary files /dev/null and b/TP/TP01/P03/img/f716c082-83a5-4cd2-1e69-4c66c05f2c48.png differ
diff --git a/TP/TP01/P03/img/f7902f1c-c5d4-b6f3-23a9-0d3d21e8cf88.png b/TP/TP01/P03/img/f7902f1c-c5d4-b6f3-23a9-0d3d21e8cf88.png
new file mode 100644
index 0000000000000000000000000000000000000000..4fdd6716793b13ba207f4a16a32613bddb91bab4
Binary files /dev/null and b/TP/TP01/P03/img/f7902f1c-c5d4-b6f3-23a9-0d3d21e8cf88.png differ
diff --git a/TP/TP01/P03/index.html b/TP/TP01/P03/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..ce5660aa5c8f2e2d2a522ec6f5e759018a1dedd4
--- /dev/null
+++ b/TP/TP01/P03/index.html
@@ -0,0 +1,542 @@
+<!DOCTYPE html>
+
+<html lang="en">
+
+<head>
+<title>Dahu Presentation
+</title>
+<meta charset="utf-8">
+<script src="http://code.jquery.com/jquery-1.9.1.min.js" type="text/javascript">
+</script>
+<script src="parse-search.js" type="text/javascript">
+</script>
+
+<link rel="stylesheet" href="dahuapp.viewer.css"><style>.s0-o2{background-color:   #FFFFDD;width:  240px}
+.s1-o2{background-color:   #FFFFDD;width:  240px}
+.s2-o2{background-color:   #FFFFDD;width:  240px}
+.s3-o2{background-color:   #FFFFDD;width:  240px}
+.s4-o2{background-color:   #FFFFDD;width:  240px}
+.s5-o2{background-color:   #FFFFDD;width:  240px}
+.s6-o2{background-color:   #FFFFDD;width:  240px}
+.s7-o2{background-color:   #FFFFDD;width:  240px}
+.s9-o2{background-color:   #FFFFDD;width:  240px}
+.s10-o2{background-color:   #FFFFDD;width:  240px}
+.s11-o2{background-color:   #FFFFDD;width:  240px}
+.s12-o2{background-color:   #FFFFDD;width:  240px}
+.s13-o2{background-color:   #FFFFDD;width:  240px}
+.s14-o2{background-color:   #FFFFDD;width:  240px}
+.s15-o2{background-color:   #FFFFDD;width:  240px}
+</style>
+</head>
+
+<body>
+
+<div id="my-dahu-presentation">
+
+<div id="loading">Loading presentation...
+</div>
+
+<div class="object-list" style="display: none">
+<img src="img/69e4196e-0ab1-2975-bcca-7ad56cb9f246.png" alt="img/69e4196e-0ab1-2975-bcca-7ad56cb9f246.png" class="background 69e4196e-0ab1-2975-bcca-7ad56cb9f2460">
+
+<div class="tooltip s0-o2">Sélectionner l'onglet Continuous dans la boite à outils Simulink.
+</div>
+<img src="img/4a0ff2c8-7524-5a5b-6e82-b37642bf6def.png" alt="img/4a0ff2c8-7524-5a5b-6e82-b37642bf6def.png" class="background 4a0ff2c8-7524-5a5b-6e82-b37642bf6def0">
+
+<div class="tooltip s1-o2">Sélectionner le bloc Derivator.
+</div>
+<img src="img/f716c082-83a5-4cd2-1e69-4c66c05f2c48.png" alt="img/f716c082-83a5-4cd2-1e69-4c66c05f2c48.png" class="background f716c082-83a5-4cd2-1e69-4c66c05f2c480">
+
+<div class="tooltip s2-o2">Déposer un bloc Derivator dans votre modèle.
+</div>
+<img src="img/f5a01d52-fc33-d9e0-2ddf-4ec7f9f4799f.png" alt="img/f5a01d52-fc33-d9e0-2ddf-4ec7f9f4799f.png" class="background f5a01d52-fc33-d9e0-2ddf-4ec7f9f4799f0">
+
+<div class="tooltip s3-o2">Sélectionner le bloc Integrator.
+</div>
+<img src="img/88ed11b7-b2c8-fdc3-4433-c3a5f9400f6d.png" alt="img/88ed11b7-b2c8-fdc3-4433-c3a5f9400f6d.png" class="background 88ed11b7-b2c8-fdc3-4433-c3a5f9400f6d0">
+
+<div class="tooltip s4-o2">Déposer un bloc Integrator dans votre modèle.
+</div>
+<img src="img/0edbde4a-26ff-f2d1-a88f-5a13affc536a.png" alt="img/0edbde4a-26ff-f2d1-a88f-5a13affc536a.png" class="background 0edbde4a-26ff-f2d1-a88f-5a13affc536a0">
+
+<div class="tooltip s5-o2">Connecter l'entrée du bloc Derivator au signal de sortie du bloc Générateur de sinusoïde.
+</div>
+<img src="img/321ec853-941a-e69e-7fa1-eac7f318f366.png" alt="img/321ec853-941a-e69e-7fa1-eac7f318f366.png" class="background 321ec853-941a-e69e-7fa1-eac7f318f3660">
+
+<div class="tooltip s6-o2">Connecter l'entrée du bloc Integrator au signal de sortie du bloc Générateur de sinusoïde.
+</div>
+<img src="img/edcbee3f-34e7-d5af-c956-28b9d0680a7b.png" alt="img/edcbee3f-34e7-d5af-c956-28b9d0680a7b.png" class="background edcbee3f-34e7-d5af-c956-28b9d0680a7b0">
+
+<div class="tooltip s7-o2">Ouvrir les paramètres de l'écran de l'oscilloscope.
+</div>
+<img src="img/9e61a3ea-c0cf-ce23-4caa-ea8be8b8180d.png" alt="img/9e61a3ea-c0cf-ce23-4caa-ea8be8b8180d.png" class="background 9e61a3ea-c0cf-ce23-4caa-ea8be8b8180d0">
+<img src="img/1d9e476d-7d77-fc65-07f3-77174933ac05.png" alt="img/1d9e476d-7d77-fc65-07f3-77174933ac05.png" class="background 1d9e476d-7d77-fc65-07f3-77174933ac050">
+
+<div class="tooltip s9-o2">Indiquer que l'oscilloscope doit avoir 3 entrées.
+</div>
+<img src="img/ee3f999c-8cdc-3624-ed00-917c9f8025b0.png" alt="img/ee3f999c-8cdc-3624-ed00-917c9f8025b0.png" class="background ee3f999c-8cdc-3624-ed00-917c9f8025b00">
+
+<div class="tooltip s10-o2">Les 3 entrées apparaissent sur le bloc Oscilloscope.
+</div>
+<img src="img/8248ea8c-777a-3f5f-4441-4e7e65f06321.png" alt="img/8248ea8c-777a-3f5f-4441-4e7e65f06321.png" class="background 8248ea8c-777a-3f5f-4441-4e7e65f063210">
+
+<div class="tooltip s11-o2">Connecter la sortie du bloc Derivator sur la deuxième entrée du bloc Oscilloscope.
+</div>
+<img src="img/01f96120-1e41-b4d3-b844-e73128943106.png" alt="img/01f96120-1e41-b4d3-b844-e73128943106.png" class="background 01f96120-1e41-b4d3-b844-e731289431060">
+
+<div class="tooltip s12-o2">Connecter la sortie du bloc Integrator sur la troisième entrée du bloc Oscilloscope.
+</div>
+<img src="img/343ed3f0-798d-32ed-eb69-f5b428471c19.png" alt="img/343ed3f0-798d-32ed-eb69-f5b428471c19.png" class="background 343ed3f0-798d-32ed-eb69-f5b428471c190">
+
+<div class="tooltip s13-o2">Retourner en simulation à pas variable (Variable step).
+</div>
+<img src="img/f7902f1c-c5d4-b6f3-23a9-0d3d21e8cf88.png" alt="img/f7902f1c-c5d4-b6f3-23a9-0d3d21e8cf88.png" class="background f7902f1c-c5d4-b6f3-23a9-0d3d21e8cf880">
+
+<div class="tooltip s14-o2">Démarrer la simulation.
+</div>
+<img src="img/e198e134-62de-f434-c767-5c759618a866.png" alt="img/e198e134-62de-f434-c767-5c759618a866.png" class="background e198e134-62de-f434-c767-5c759618a8660">
+
+<div class="tooltip s15-o2">Mettre à l'échelle l'écran de l'oscilloscope. Observer les résultats obtenus pour la dérivation et l'intégration d'une fonction sinusoïde.
+</div>
+
+<div class="mouse-cursor">
+<img src="img/cursor.png" alt="img/cursor.png" class="mouse-cursor-normal">
+<img src="img/cursor-pause.png" alt="img/cursor-pause.png" style="display: none" class="mouse-cursor-pause">
+</div>
+</div>
+
+<div class="control" style="top: 616px;">
+<button class="previous">Previous
+</button>
+<button class="next">Next
+</button>
+</div>
+</div>
+<script src="dahuapp.js" type="text/javascript">
+</script>
+<script src="dahuapp.viewer.js" type="text/javascript">
+</script>
+<script type="text/javascript">(function($) {
+    var myPresentation = dahuapp.viewer.createDahuViewer("#my-dahu-presentation", window.getParams);
+    myPresentation.load({
+    "metaData": {
+        "imageWidth": 800,
+        "imageHeight": 600,
+        "initialBackgroundId": "69e4196e-0ab1-2975-bcca-7ad56cb9f2460",
+        "initialMouseX": 0.6715277777777777,
+        "initialMouseY": 0.18222222222222223
+    },
+    "action": [
+        {
+            "id": "37",
+            "type": "appear",
+            "target": "s0-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.35833333333333334,
+            "ord": 0.11625,
+            "duration": 400
+        },
+        {
+            "id": "3",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.775,
+            "finalOrd": 0.17,
+            "speed": 0.8
+        },
+        {
+            "id": "4",
+            "type": "appear",
+            "target": "4a0ff2c8-7524-5a5b-6e82-b37642bf6def0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "38",
+            "type": "appear",
+            "target": "s1-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.3933333333333333,
+            "ord": 0.11625,
+            "duration": 400
+        },
+        {
+            "id": "5",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.25,
+            "finalOrd": 0.3477777777777778,
+            "speed": 0.8
+        },
+        {
+            "id": "6",
+            "type": "appear",
+            "target": "f716c082-83a5-4cd2-1e69-4c66c05f2c480",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "39",
+            "type": "appear",
+            "target": "s2-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.17833333333333334,
+            "ord": 0.1675,
+            "duration": 400
+        },
+        {
+            "id": "7",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.9041666666666667,
+            "finalOrd": 0.17444444444444446,
+            "speed": 0.8
+        },
+        {
+            "id": "8",
+            "type": "appear",
+            "target": "f5a01d52-fc33-d9e0-2ddf-4ec7f9f4799f0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "40",
+            "type": "appear",
+            "target": "s3-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.405,
+            "ord": 0.105,
+            "duration": 400
+        },
+        {
+            "id": "9",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.24375,
+            "finalOrd": 0.45111111111111113,
+            "speed": 0.8
+        },
+        {
+            "id": "10",
+            "type": "appear",
+            "target": "88ed11b7-b2c8-fdc3-4433-c3a5f9400f6d0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "41",
+            "type": "appear",
+            "target": "s4-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.26666666666666666,
+            "ord": 0.17125,
+            "duration": 400
+        },
+        {
+            "id": "11",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.21736111111111112,
+            "finalOrd": 0.2633333333333333,
+            "speed": 0.8
+        },
+        {
+            "id": "12",
+            "type": "appear",
+            "target": "0edbde4a-26ff-f2d1-a88f-5a13affc536a0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "42",
+            "type": "appear",
+            "target": "s5-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.25833333333333336,
+            "ord": 0.13375,
+            "duration": 400
+        },
+        {
+            "id": "13",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.21666666666666667,
+            "finalOrd": 0.3511111111111111,
+            "speed": 0.8
+        },
+        {
+            "id": "14",
+            "type": "appear",
+            "target": "321ec853-941a-e69e-7fa1-eac7f318f3660",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "43",
+            "type": "appear",
+            "target": "s6-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.27,
+            "ord": 0.1425,
+            "duration": 400
+        },
+        {
+            "id": "31",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.06597222222222222,
+            "finalOrd": 0.6666666666666666,
+            "speed": 0.8
+        },
+        {
+            "id": "32",
+            "type": "appear",
+            "target": "edcbee3f-34e7-d5af-c956-28b9d0680a7b0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "44",
+            "type": "appear",
+            "target": "s7-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.12,
+            "ord": 0.2975,
+            "duration": 400
+        },
+        {
+            "id": "15",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.06388888888888888,
+            "finalOrd": 0.6666666666666666,
+            "speed": 0.8
+        },
+        {
+            "id": "16",
+            "type": "appear",
+            "target": "9e61a3ea-c0cf-ce23-4caa-ea8be8b8180d0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "17",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.09444444444444444,
+            "finalOrd": 0.7566666666666667,
+            "speed": 0.8
+        },
+        {
+            "id": "18",
+            "type": "appear",
+            "target": "1d9e476d-7d77-fc65-07f3-77174933ac050",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "45",
+            "type": "appear",
+            "target": "s9-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.14833333333333334,
+            "ord": 0.315,
+            "duration": 400
+        },
+        {
+            "id": "19",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.3013888888888889,
+            "finalOrd": 0.37,
+            "speed": 0.8
+        },
+        {
+            "id": "20",
+            "type": "appear",
+            "target": "ee3f999c-8cdc-3624-ed00-917c9f8025b00",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "46",
+            "type": "appear",
+            "target": "s10-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.315,
+            "ord": 0.10125,
+            "duration": 400
+        },
+        {
+            "id": "21",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.25763888888888886,
+            "finalOrd": 0.24555555555555555,
+            "speed": 0.8
+        },
+        {
+            "id": "22",
+            "type": "appear",
+            "target": "8248ea8c-777a-3f5f-4441-4e7e65f063210",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "47",
+            "type": "appear",
+            "target": "s11-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.2816666666666667,
+            "ord": 0.125,
+            "duration": 400
+        },
+        {
+            "id": "23",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.30416666666666664,
+            "finalOrd": 0.2777777777777778,
+            "speed": 0.8
+        },
+        {
+            "id": "24",
+            "type": "appear",
+            "target": "01f96120-1e41-b4d3-b844-e731289431060",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "48",
+            "type": "appear",
+            "target": "s12-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.31,
+            "ord": 0.10875,
+            "duration": 400
+        },
+        {
+            "id": "25",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.56875,
+            "finalOrd": 0.7166666666666667,
+            "speed": 0.8
+        },
+        {
+            "id": "26",
+            "type": "appear",
+            "target": "343ed3f0-798d-32ed-eb69-f5b428471c190",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "49",
+            "type": "appear",
+            "target": "s13-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.3333333333333333,
+            "ord": 0.31625,
+            "duration": 400
+        },
+        {
+            "id": "27",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.2916666666666667,
+            "finalOrd": 0.10111111111111111,
+            "speed": 0.8
+        },
+        {
+            "id": "28",
+            "type": "appear",
+            "target": "f7902f1c-c5d4-b6f3-23a9-0d3d21e8cf880",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "50",
+            "type": "appear",
+            "target": "s14-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.2966666666666667,
+            "ord": 0.1275,
+            "duration": 400
+        },
+        {
+            "id": "29",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.14791666666666667,
+            "finalOrd": 0.6666666666666666,
+            "speed": 0.8
+        },
+        {
+            "id": "30",
+            "type": "appear",
+            "target": "e198e134-62de-f434-c767-5c759618a8660",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "51",
+            "type": "appear",
+            "target": "s15-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.20833333333333334,
+            "ord": 0.27375,
+            "duration": 400
+        }
+    ]
+});
+    myPresentation.start();
+})(jQuery);
+
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/TP/TP01/P03/parse-search.js b/TP/TP01/P03/parse-search.js
new file mode 100644
index 0000000000000000000000000000000000000000..e7d01c07f6a5d45b9fcabd5d271a8c6cfc5f00e2
--- /dev/null
+++ b/TP/TP01/P03/parse-search.js
@@ -0,0 +1,14 @@
+// Adapted from http://stackoverflow.com/questions/3234125/creating-array-from-window-location-hash
+(function () {
+    var search = window.location.search.slice(1);
+    var array = search.split("&");
+    
+    var values, form_data = {};
+    
+    for (var i = 0; i < array.length; i += 1) {
+	values = array[i].split("=");
+	form_data[values[0]] = unescape(values[1]);
+    }
+    window.getParams = form_data;
+}())
+
diff --git a/TP/TP01/P04/dahuapp.js b/TP/TP01/P04/dahuapp.js
new file mode 100644
index 0000000000000000000000000000000000000000..41c69e9169345a9a6703325d5557a4cc36913e19
--- /dev/null
+++ b/TP/TP01/P04/dahuapp.js
@@ -0,0 +1,880 @@
+"use strict";
+
+/**
+ * Dahuapp core module.
+ *
+ * @param   window      window javascript object.
+ * @param   $           jQuery
+ * @returns dahuapp core module.
+ */
+(function (window, $) {
+    var dahuapp = (function () {
+
+        var self = {};
+
+        /* private API */
+
+        var DahuScreencastGenerator = function () {
+
+            /*
+             * Format the specified string in a readable format.
+             */
+            function htmlFormat(htmlString) {
+                var i;
+                var readableHTML = htmlString;
+                var lb = '\n';
+                var htags = ["<html", "</html>", "</head>", "<title", "</title>", "<meta", "<link", "</body>"];
+                for (i = 0; i < htags.length; ++i) {
+                    var hhh = htags[i];
+                    readableHTML = readableHTML.replace(new RegExp(hhh, 'gi'), lb + hhh);
+                }
+                var btags = ["</div>", "</section>", "</span>", "<br>", "<br />", "<blockquote", "</blockquote>", "<ul", "</ul>", "<ol", "</ol>", "<li", "<\!--", "<script", "</script>"];
+                for (i = 0; i < btags.length; ++i) {
+                    var bbb = btags[i];
+                    readableHTML = readableHTML.replace(new RegExp(bbb, 'gi'), lb + bbb);
+                }
+                var ftags = ["<img", "<legend", "</legend>", "<button", "</button>"];
+                for (i = 0; i < ftags.length; ++i) {
+                    var fff = ftags[i];
+                    readableHTML = readableHTML.replace(new RegExp(fff, 'gi'), lb + fff);
+                }
+                var xtags = ["<body", "<head", "<div", "<section", "<span", "<p"];
+                for (i = 0; i < xtags.length; ++i) {
+                    var xxx = xtags[i];
+                    readableHTML = readableHTML.replace(new RegExp(xxx, 'gi'), lb + lb + xxx);
+                }
+                return readableHTML;
+            }
+
+            /*
+             * Generates the html header.
+             */
+            var generateHtmlHeader = function ($generated, cssGen) {
+                $('head', $generated)
+                    .append($(document.createElement('title'))
+                        .append("Dahu Presentation"))/* TODO: make this customizable */
+                    .append($(document.createElement('meta'))
+                        .attr({'charset': 'utf-8'}))
+                    .append($(document.createElement('script'))
+                        .attr({'src': 'http://code.jquery.com/jquery-1.9.1.min.js'})
+                        .attr({'type': 'text/javascript'}))
+                    .append($(document.createElement('script'))
+                        .attr({'src': 'parse-search.js'})
+                        .attr({'type': 'text/javascript'}))
+                    .append($(document.createElement('link'))
+                        .attr({'rel': 'stylesheet', 'href': 'dahuapp.viewer.css'}))
+                    .append($(document.createElement('style'))
+                        .append(cssGen));
+            };
+
+            /*
+             * Generates the objects.
+             */
+
+            var generateHtmlBackgroundImage = function ($generated, object) {
+                $('.object-list', $generated)
+                    .append($(document.createElement('img'))
+                        .attr({'src': object.img, 'alt': object.img, 'class': 'background ' + object.id}));
+            };
+            var generateHtmlTooltip = function ($generated, object) {
+                $('.object-list', $generated)
+                    .append($(document.createElement('div'))
+                        .attr({'class': 'tooltip ' + object.id})
+                        .append(object.text));
+            };
+
+            var generateCssTooltip = function ($generated, object) {
+                var style = [];
+
+                if( object.color != null ) {
+                    style.push('background-color:   ' + object.color);
+                }
+                if( object.width != null ) {
+                    style.push('width:  ' + object.width);
+                }
+
+                if( style.length != 0 ) {
+                    $generated.append(
+                    '.' + object.id + '{' + style.join(';') + '}\n');
+                }
+
+                return $generated;
+            };
+
+            /*
+             * Returns the code used to call the viewer to the presentation.
+             */
+            var getBasicCallCode = function (jsonModel) {
+                var code = '(function($) {\n';
+                code += '    var myPresentation = dahuapp.viewer.createDahuViewer("#my-dahu-presentation", window.getParams);\n';
+                code += '    myPresentation.load(' + jsonModel + ');\n';
+                code += '    myPresentation.start();\n';
+                code += '})(jQuery);\n';
+                return code;
+            };
+
+            /*
+             * Generates the html body.
+             */
+            var generateHtmlBody = function ($generated, jsonModel, jsonGen) {
+                $('body', $generated)
+                    /* We could use a <section> here too, but it does not work with MS IE 8.
+                     Alternatively, adding this in the header would work:
+
+                     <!--[if lt IE 9]>
+                     <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
+                     <![endif]-->
+                     */
+                    .append($(document.createElement('div'))
+                        .attr({'id': 'my-dahu-presentation'}))
+                    .append($(document.createElement('script'))
+                        .attr({'src': 'dahuapp.js'})
+                        .attr({'type': 'text/javascript'}))
+                    .append($(document.createElement('script'))
+                        .attr({'src': 'dahuapp.viewer.js'})
+                        .attr({'type': 'text/javascript'}))
+                    .append($(document.createElement('script'))
+                        .attr({'type': 'text/javascript'})
+                        .append(getBasicCallCode(jsonGen)));
+                $('#my-dahu-presentation', $generated)
+                    .append($(document.createElement('div'))
+                        .attr({'id': 'loading'}).append("Loading presentation..."))
+                    .append($(document.createElement('div'))
+                        .attr({'class': 'object-list',
+                            'style': 'display: none'}))
+                    .append($(document.createElement('div'))
+                        .attr({'class': 'control'}));
+
+                /* Adding the objects to the page */
+                $.each(jsonModel.getObjectList(), function (id, object) {
+                    switch (object.type) {
+                        case "background":
+                            generateHtmlBackgroundImage($generated, object);
+                            break;
+                        case "tooltip":
+                            generateHtmlTooltip($generated, object);
+                            break;
+                        /* no mouse image generated here */
+                    }
+                });
+
+                /* Warning here, i'm not sure if $.each is always over, here */
+
+                /* Adding the control buttons to the page */
+                $('.control', $generated)
+                    .css({'top': (jsonModel.getImageHeight() + 16) + 'px'})
+                    .append($(document.createElement('button'))
+                        .attr({'class': 'previous'})
+                        .append('Previous'))
+                    .append($(document.createElement('button'))
+                        .attr({'class': 'next'})
+                        .append('Next'));
+
+                /* Adding the mouse cursor image */
+                $('.object-list', $generated)
+                    .append($(document.createElement('div'))
+                        .attr({'class': 'mouse-cursor'})
+                        .append($(document.createElement('img'))
+                            .attr({'src': 'img/cursor.png', 'alt': 'img/cursor.png', 'class': 'mouse-cursor-normal'}))
+                        .append($(document.createElement('img'))
+                            .attr({'src': 'img/cursor-pause.png', 'alt': 'img/cursor-pause.png',
+                                'style': 'display: none', 'class': 'mouse-cursor-pause'})));
+            };
+
+            /*
+             * Generates the css String with the Json model.
+             */
+            this.generateCssString = function (jsonModel) {
+                var $generated = $('<style></style>');
+
+                /* Going through each object */
+                $.each(jsonModel.getObjectList(), function (id, object) {
+                    switch (object.type) {
+                        case "tooltip":
+                            generateCssTooltip($generated, object);
+                            break;
+                        /* no mouse image generated here */
+                    }
+                });
+
+                return $generated.text();
+            }
+
+            /*
+             * Generates the html String with the Json model.
+             */
+            this.generateHtmlString = function (jsonModel, jsonGen, cssGen) {
+                /* Initialising the compilation area */
+                var $generated = $(document.createElement('div'));
+
+                /* We create the html using the json */
+                $generated.append($(document.createElement('html'))
+                    .attr({'lang': 'en'}));
+                $('html', $generated)
+                    .append($(document.createElement('head')))
+                    .append($(document.createElement('body')));
+
+                generateHtmlHeader($generated, cssGen);
+                generateHtmlBody($generated, jsonModel, jsonGen);
+
+                var result = htmlFormat($generated.html());
+
+                return '<!DOCTYPE html>\n' + result;
+            };
+
+            /*
+             * Generates the generated JSON using the JSONmodel.
+             * @param {object} jsonModel The json model to transform.
+             * @param {java.awt.Dimension} imgDim The dimension of images.
+             */
+            this.generateJsonString = function (jsonModel, imgDim) {
+                var generated = self.createScreencastGeneratedModel();
+                generated.setImageSize(imgDim.width, imgDim.height);
+                generated.setInitialBackground(jsonModel.getInitialBackground());
+                generated.setInitialMousePos(jsonModel.getInitialMousePos());
+                for (var i = 0; i < jsonModel.getNbSlide(); i++) {
+                    var actionList = jsonModel.getActionList(i);
+                    for (var j = 0; j < actionList.length; j++) {
+                        /* 
+                         * We don't add the two first actions of the first slide
+                         * because they are present in the presentation metadata.
+                         * It corresponds to the mouse initial pos and first background.
+                         */
+                        if (i > 0 || j > 1) {
+                            generated.addAction(actionList[j], imgDim.width, imgDim.height);
+                        }
+                    }
+                }
+                return generated.getJson();
+            };
+        };
+
+        /*
+         * Model for a JSON object that will only be used by the viewer, never
+         * saved on a file, used to transform the properties of actions
+         * specified on the JSON file of a presentation to executable
+         * functions for each action.
+         */
+        var DahuScreencastExecutableModel = function () {
+
+            /* Private API */
+
+            var json = {
+                metaData: {},
+                action: new Array()
+            };
+
+            /*
+             * Creates a functions representing the specified action, and adds
+             * it to this object.
+             * @param {object} action
+             */
+            var addExecutableAction = function (action) {
+                var executableAction = {
+                    'id': action.id,
+                    'trigger': action.trigger,
+                    'target': action.target,
+                    'delayAfter': action.delayAfter,
+                    'doneFunction': function (events, selector) {
+                        setTimeout(function () {
+                            events.onActionOver.publish(events, selector);
+                        }, this.delayAfter);
+                    }
+                };
+                if (executableAction.delayAfter == null) {
+                    executableAction.delayAfter = 200;
+                }
+                switch (action.type.toLowerCase()) {
+                    case "appear":
+                        executableAction.abs = (action.abs * json.metaData.imageWidth) + 'px';
+                        executableAction.ord = (action.ord * json.metaData.imageHeight) + 'px';
+                        executableAction.duration = action.duration;
+                        executableAction.execute = function (events, selector) {
+                            events.onActionStart.publish(events, selector);
+                            var sel = selector + ' .' + this.target;
+                            $(sel).css({
+                                'left': this.abs,
+                                'top': this.ord
+                            });
+                            $(sel).show(this.duration, function () {
+                                executableAction.doneFunction(events, selector);
+                            });
+                        };
+                        executableAction.executeReverse = function (selector) {
+                            $(selector + ' .' + this.target).hide();
+                        };
+                        executableAction.executeImmediately = function (selector) {
+                            var sel = selector + ' .' + this.target;
+                            $(sel).css({
+                                'left': this.abs,
+                                'top': this.ord
+                            });
+                            $(sel).show();
+                        };
+                        break;
+                    case "disappear":
+                        executableAction.duration = action.duration;
+                        executableAction.execute = function (events, selector) {
+                            events.onActionStart.publish(events, selector);
+                            $(selector + ' .' + this.target).hide(this.duration, function () {
+                                executableAction.doneFunction(events, selector);
+                            });
+                        };
+                        executableAction.executeReverse = function (selector) {
+                            $(selector + ' .' + this.target).show();
+                        };
+                        executableAction.executeImmediately = function (selector) {
+                            $(selector + ' .' + this.target).hide();
+                        };
+                        break;
+                    case "move":
+                        executableAction.finalAbs = (action.finalAbs * json.metaData.imageWidth) + 'px';
+                        executableAction.finalOrd = (action.finalOrd * json.metaData.imageHeight) + 'px';
+                        executableAction.duration = action.duration;
+                        executableAction.speed = action.speed;
+                        executableAction.execute = function (events, selector) {
+                            events.onActionStart.publish(events, selector);
+                            var sel = selector + ' .' + this.target;
+                            this.initialAbs = $(sel).css('left');
+                            this.initialOrd = $(sel).css('top');
+                            if (this.duration == null) {
+                                var initialAbsPix = this.initialAbs.replace('px', '');
+                                var initialOrdPix = this.initialOrd.replace('px', '');
+                                var finalAbsPix = this.finalAbs.replace('px', '');
+                                var finalOrdPix = this.finalOrd.replace('px', '');
+                                var distance = Math.sqrt(Math.pow(finalAbsPix - initialAbsPix, 2) +
+                                    Math.pow(finalOrdPix - initialOrdPix, 2));
+                                if (!this.speed) {
+                                    this.speed = .8; // in pixel per milisecond: fast, but not too much.
+                                }
+                                this.duration = distance + this.speed;
+                                if (this.duration < 200) { // Slow down a bit for short distances.
+                                    this.duration = 200;
+                                }
+                            }
+                            $(sel).animate({
+                                'left': this.finalAbs,
+                                'top': this.finalOrd
+                            }, this.duration, 'linear', function () {
+                                executableAction.doneFunction(events, selector);
+                            });
+                        };
+                        executableAction.executeReverse = function (selector) {
+                            $(selector + ' .' + this.target).css({
+                                'left': this.initialAbs,
+                                'top': this.initialOrd
+                            });
+                        };
+                        executableAction.executeImmediately = function (selector) {
+                            var sel = selector + ' .' + this.target;
+                            this.initialAbs = $(sel).css('left');
+                            this.initialOrd = $(sel).css('top');
+                            $(sel).css({
+                                'left': this.finalAbs,
+                                'top': this.finalOrd
+                            });
+                        };
+                        break;
+                    case "delay":
+                        executableAction.duration = action.duration;
+                        executableAction.execute = function (events, selector) {
+                            events.onActionStart.publish(events, selector);
+                            setTimeout(function () {
+                                events.onActionOver.publish(events, selector);
+                            }, this.duration);
+                        };
+                        executableAction.executeReverse = function (selector) {
+                            // Nothing!
+                        };
+                        executableAction.executeImmediately = function (selector) {
+                            // Nothing too!
+                        };
+                        break;
+                }
+                json.action.push(executableAction);
+            };
+
+            /* Public API */
+
+            /*
+             * Loads the specified JSON read from a 'presentation.json' file,
+             * and stores the functions representing each actions in an object.
+             * @param {object} jsonToLoad
+             */
+            this.loadJson = function (jsonToLoad) {
+                json.metaData = jsonToLoad.metaData;
+                for (var i = 0; i < jsonToLoad.action.length; i++) {
+                    addExecutableAction(jsonToLoad.action[i]);
+                }
+            };
+
+            /*
+             * Returns the object containing the functions.
+             */
+            this.getJson = function () {
+                return json;
+            };
+        };
+
+        /*
+         * Used to transform the 'dahu' file to a JSON file used by the viewer,
+         * which only contains some metaData and a list of actions.
+         */
+        var DahuScreencastGeneratedModel = function () {
+
+            /* Private API */
+
+            var json = {
+                metaData: {},
+                action: new Array()
+            };
+
+            /* Public API */
+
+            /*
+             * Returns a string representation of this json.
+             * @returns {String}
+             */
+            this.getJson = function () {
+                return JSON.stringify(json, null, '    ');
+            };
+
+            /*
+             * Setters for the generated json metadata.
+             */
+            this.setImageSize = function (width, height) {
+                json.metaData.imageWidth = width;
+                json.metaData.imageHeight = height;
+            };
+
+            this.setInitialMousePos = function (pos) {
+                json.metaData.initialMouseX = pos.x;
+                json.metaData.initialMouseY = pos.y;
+            };
+
+            this.setInitialBackground = function (id) {
+                json.metaData.initialBackgroundId = id;
+            };
+
+            this.addAction = function (action) {
+                json.action.push(action);
+            };
+
+            /*
+             * Transforms a JSON containing action properties to a JSON containing
+             * the execution functions.
+             * @param {Object} json
+             * @returns {Object} A json object containing the execution functions
+             * for all the actions of the presentation.
+             */
+            this.toExecutableList = function (json) {
+                var executableList = {
+                    metaData: json.metaData,
+                    action: new Array()
+                };
+                for (var i = 0; i < json.action.length; i++) {
+                    addExecutableAction(executableList, json.action[i]);
+                }
+                return executableList;
+            };
+        };
+
+        /*
+         * Represents a 'dahu' file for a project.
+         */
+        var DahuScreencastModel = function () {
+
+            /* Private API */
+
+            var json = {};
+
+            /*
+             * Generates a unique action ID.
+             *
+             * With this implementation, this ID is unique as long as the user
+             * doesn't replace it manually with a not kind value or replace
+             * the nextUniqueId with bad intentions.
+             * But maybe that a UUID is a bit tiresome to put as an anchor...
+             */
+            var generateUniqueActionId = function () {
+                json.metaData.nextUniqueId++;
+                return json.metaData.nextUniqueId.toString();
+            };
+
+            /*
+             * This method checks if the json representing a dahu project is
+             * in the good version. It's in case a project file was created
+             * with a previous version of Dahu, and that some fields are
+             * missing.
+             *
+             * It doesn't control each field (at the moment) but only the
+             * fields that can be missing due to a new Dahu version and not
+             * to a manual editing.
+             *
+             * This method doesn't check any Json syntax or something like that.
+             */
+            var upgradeJsonVersion = function () {
+                // Checks if unique IDs are in the project file
+                if (!json.metaData.nextUniqueId) {
+                    var currentId = 0;
+                    for (var i = 0; i < json.data.length; i++) {
+                        for (var j = 0; j < json.data[i].action.length; j++) {
+                            json.data[i].action[j].id = currentId.toString();
+                            currentId++;
+                        }
+                    }
+                    json.metaData.nextUniqueId = currentId;
+                }
+            };
+
+            /* Public API */
+
+            /*
+             * json variable generated from a JSON file.
+             * @param String stringJson String loaded from JSON file.
+             */
+            this.loadJson = function (stringJson) {
+                json = JSON.parse(stringJson);
+                upgradeJsonVersion();
+            };
+
+            /*
+             * Create a new presentation variable in the JSON file which will contain slides.
+             */
+            this.createPresentation = function (width, height) {
+                json.metaData = {};
+                json.metaData.imageWidth = width;
+                json.metaData.imageHeight = height;
+                json.metaData.nextUniqueId = 0;
+                json.data = new Array();
+            };
+
+            /*
+             * Add a new slide in the presentation variable of the JSON file.
+             * @param int index wanted for the slide.
+             * @param String idSlide Unique identifier for the slide.
+             * @param String img Related to pathname of the image.
+             * @param double mouseX Abscissa mouse position in %.
+             * @param double mouseY Ordinate mouse position in %.
+             * @return Index of the newly added slide.
+             */
+            this.addSlide = function (indexSlide, idSlide, img, mouseX, mouseY, speed) {
+                var slide = {
+                    "object": new Array(),
+                    "action": new Array()
+                };
+                json.data.splice(indexSlide, 0, slide);
+                this.addObject(indexSlide, "background", idSlide, img);
+                this.addObject(indexSlide, "mouse");
+                this.addAction(indexSlide, "move", json.data[indexSlide].object[1].id, "onClick", mouseX, mouseY, speed);
+                this.addAction(indexSlide, "appear", json.data[indexSlide].object[0].id, "afterPrevious");
+            };
+
+            /*
+             * Object factory.
+             * Add a new Object in the slide idSlide.
+             * @param int idSlide
+             * @param string type
+             * Other params can be specified depending on the object's type.
+             */
+            this.addObject = function (idSlide, type) {
+				var object = {
+                    "type": type
+                };
+                switch (type.toLowerCase()) {
+                    case "background":
+                        object.id = arguments[2] + json.data[idSlide].object.length;
+                        object.img = arguments[3] || "";
+                        break;
+                    case "mouse":
+                        object.id = "mouse-cursor";
+                        break;
+                    case "tooltip":
+                        /*
+                         * TODO: we'll need a more robust unique name
+                         * when we start actually using this.
+                         */
+                        object.id = "s" + idSlide + "-o" + json.data[idSlide].object.length;
+                        object.text = arguments[2] || "";
+                        object.color = arguments[3] || null;
+						object.width = arguments[4] || "";
+						json.data[idSlide].object.push(object);
+						var objectLength = json.data[idSlide].object.length;
+						var last = json.data[idSlide].object[objectLength-1];
+						json.data[idSlide].object[objectLength-1] = 
+								json.data[idSlide].object[objectLength-2];
+						json.data[idSlide].object[objectLength-2] = last;
+                        break;
+                }
+				if(type.toLowerCase() != "tooltip"){
+					json.data[idSlide].object.push(object);
+				}
+            };
+
+            /*
+             * Action factory.
+             * Add a new Action in the slide idSlide whose target is the id of an object.
+             * Three types of trigger : "withPrevious", "afterPrevious", "onClick".
+             * @param int idSlide
+             * @param string type
+             * @param string target
+             * @param string trigger
+             * Other params can be specified depending on the object's type.
+             */
+            this.addAction = function (idSlide, type, target, trigger) {
+                var action = {
+                    "id": generateUniqueActionId(),
+                    "type": type,
+                    "target": target,
+                    "trigger": trigger
+                };
+                switch (type.toLowerCase()) {
+                    case "appear":
+                        action.abs = arguments[4] || 0.0;
+                        action.ord = arguments[5] || 0.0;
+                        action.duration = arguments[6] || 0;
+                        break;
+                    case "disappear":
+                        action.duration = arguments[4] || 0;
+                        break;
+                    case "move":
+                        action.finalAbs = arguments[4] || 0.0;
+                        action.finalOrd = arguments[5] || 0.0;
+                        action.speed = arguments[6] || 0;
+                        break;
+                }
+                json.data[idSlide].action.push(action);
+            };
+
+            this.editMouse = function (idSlide, idAction, mouseX, mouseY) {
+                json.data[idSlide].action[idAction].finalAbs = mouseX;
+                json.data[idSlide].action[idAction].finalOrd = mouseY;
+            };
+
+            /*
+             * Sets a title for the presentation.
+             * @param String title Title to set.
+             */
+            this.setTitle = function (title) {
+                json.metaData.title = title;
+            };
+
+            /*
+             * Sets an annotation for the presentation.
+             * @param String annotation Annotation to set.
+             */
+            this.setAnnotation = function (annotation) {
+                json.metaData.annotation = annotation;
+            };
+
+            /*
+             * Inverts the two slides (their positions on the table).
+             * @param int idSlide1 Index of the first slide.
+             * @param int idSlide2 Index of the second slide.
+             */
+            this.invertSlides = function (idSlide1, idSlide2) {
+                var tmp = json.data[idSlide1];
+                json.data[idSlide1] = json.data[idSlide2];
+                json.data[idSlide2] = tmp;
+            };
+
+            /*
+             * Inverts the two actions (their positions on the table).
+             * @param int idSlide
+             * @param int idAction1
+             * @param int idAction2
+             */
+            this.invertActions = function (idSlide, idAction1, idAction2) {
+                var tmp = json.data[idSlide].action[idAction1];
+                json.data[idSlide].action[idAction1] = json.data[idSlide].action[idAction2];
+                json.data[idSlide].action[idAction2] = tmp;
+            };
+
+            /*
+             * Returns the actions on the specified slide.
+             * @returns {Array}
+             */
+            this.getActionList = function (idSlide) {
+                return json.data[idSlide].action;
+            };
+
+            /*
+             * Catches all the objects of the presentation
+             * @returns {Array} List of objects of the presentation
+             */
+            this.getObjectList = function () {
+                var objectList = new Array();
+                var indexSlide = 0;
+                while (json.data[indexSlide]) {
+                    var indexObject = 0;
+                    while (json.data[indexSlide].object[indexObject]) {
+                        objectList.push(json.data[indexSlide].object[indexObject]);
+                        indexObject++;
+                    }
+                    indexSlide++;
+                }
+                return objectList;
+            };
+
+            /*
+             * Returns an array containing all the background images.
+             * @returns {Array} List of background images.
+             */
+            this.getImageList = function () {
+                var list = new Array();
+                var indexSlide = 0;
+                while (json.data[indexSlide]) {
+                    list.push(json.data[indexSlide].object[0].img);
+                    indexSlide++;
+                }
+                ;
+                return list;
+            };
+
+            /*
+             * Returns an object containing the id of the first background.
+             * @returns {string} Id of the first background.
+             */
+            this.getInitialBackground = function () {
+                return json.data[0].object[0].id;
+            };
+
+            /*
+             * Returns an object containing the initial mouse position.
+             * @returns {object} Initial position of mouse.
+             */
+            this.getInitialMousePos = function () {
+                var pos = {};
+                pos.x = json.data[0].action[0].finalAbs;
+                pos.y = json.data[0].action[0].finalOrd;
+                return pos;
+            };
+
+            /*
+             * Removes the slide at the specified index.
+             * @param {int} idSlide
+             */
+            this.removeSlide = function (idSlide) {
+                json.data.splice(idSlide, 1);
+            };
+
+            /*
+             * Removes the action at the specified slide.
+             * @param {int} idSlide
+             * @param {int} idAction
+             */
+            this.removeAction = function (idSlide, idAction) {
+                json.data[idSlide].action.splice(idAction, 1);
+            };
+
+            /*
+             * Removes the specified object from the slide.
+             * Also removes all the actions attached to this object.
+             * @param {int} idSlide
+             * @param {int} idObject
+             */
+            this.removeObject = function (idSlide, idObject) {
+                var removed = json.data[idSlide].object.splice(idObject, 1);
+                for (var i = 0; i < json.data.length; i++) {
+                    var j = 0;
+                    while (json.data[i].action[j]) {
+                        if (json.data[i].action[j].target === removed.id) {
+                            json.data[i].action.splice(j, 1);
+                        } else {
+                            j++;
+                        }
+                    }
+                }
+            };
+
+            /*
+             * @returns {String}
+             */
+            this.getJson = function () {
+                return JSON.stringify(json, null, '    ');
+            };
+
+            /*
+             * @returns {String} returns the index for the next slide.
+             */
+            this.getNbSlide = function () {
+                return json.data.length;
+            };
+
+            /*
+             * @return {object} returns the object identified by idSlide
+             */
+            this.getSlide = function (idSlide) {
+                return json.data[idSlide];
+            };
+
+            /*
+             * Sets the size of images for the generated presentation.
+             * The parameters can be null : it means there are no
+             * requirement for this dimension.
+             * @param {int} width New width for the generated images.
+             * @param {int} height New height for the generated images.
+             */
+            this.setImageSizeRequirements = function (width, height) {
+                json.metaData.imageWidth = width;
+                json.metaData.imageHeight = height;
+            };
+
+            /*
+             * Gets the width of images for the generated presentation.
+             * @return {int or null} Width of generated images.
+             */
+            this.getImageWidth = function () {
+                return json.metaData.imageWidth;
+            };
+
+            /*
+             * Gets the height of images for the generated presentation.
+             * @return {int or null} Height of generated images.
+             */
+            this.getImageHeight = function () {
+                return json.metaData.imageHeight;
+            };
+
+            /*
+             * Gets a background image (no one in particular, the first met).
+             * @return {string} The name of a background image.
+             */
+            this.getABackgroundImage = function () {
+                for (var i = 0; i < json.data.length; i++) {
+                    for (var j = 0; j < json.data[i].object.length; j++) {
+                        if (json.data[i].object[j].type === "background") {
+                            return json.data[i].object[j].img;
+                        }
+                    }
+                }
+                return null;
+            };
+        };
+
+        /* public API */
+
+        self.version = "0.0.1";
+
+        self.createScreencastGenerator = function createScreencastGenerator() {
+            return new DahuScreencastGenerator();
+        };
+
+        self.createScreencastModel = function createScreencastModel() {
+            return new DahuScreencastModel();
+        };
+
+        self.createScreencastExecutableModel = function createScreencastExecutableModel() {
+            return new DahuScreencastExecutableModel();
+        };
+
+        self.createScreencastGeneratedModel = function createScreencastGeneratedModel() {
+            return new DahuScreencastGeneratedModel();
+        };
+
+        return self;
+    })();
+
+    window.dahuapp = dahuapp;
+
+})(window, jQuery);
\ No newline at end of file
diff --git a/TP/TP01/P04/dahuapp.viewer.css b/TP/TP01/P04/dahuapp.viewer.css
new file mode 100644
index 0000000000000000000000000000000000000000..ea934576dad6ca9263a6163de58f5fe4534d1f0b
--- /dev/null
+++ b/TP/TP01/P04/dahuapp.viewer.css
@@ -0,0 +1,25 @@
+.object-list {
+    position: absolute;
+    margin: 0px;
+    padding: 0px;
+}
+
+.mouse-cursor {
+    position: absolute;
+}
+
+.control {
+    position: relative;
+}
+
+.background {
+    position: absolute;
+}
+
+.tooltip {
+    position: absolute;
+    width: 100px;
+    padding: 4px;
+    margin: 2px;
+    border: 2px black solid;
+}
\ No newline at end of file
diff --git a/TP/TP01/P04/dahuapp.viewer.js b/TP/TP01/P04/dahuapp.viewer.js
new file mode 100644
index 0000000000000000000000000000000000000000..12ad03b2cdc503a8fd6ff3f5fdc2a74edbb36edb
--- /dev/null
+++ b/TP/TP01/P04/dahuapp.viewer.js
@@ -0,0 +1,456 @@
+"use strict";
+
+/**
+ * Dahuapp viewer module.
+ * 
+ * @param   dahuapp     dahuapp object to augment with module.
+ * @param   $           jQuery
+ * @returns dahuapp extended with viewer module.
+ */
+
+(function(dahuapp, $) {
+    var viewer = (function() {
+
+        var self = {};
+
+        var DahuViewerModel = function(select, getParams) {
+
+            /* Private API */
+
+            var json = null;
+            var selector = select;
+            var parseAutoOption = function(name, defaultValue) {
+                var res = getParams[name];
+                if (res == null) {
+                    if (name == 'auto') {
+                        return false;
+                    } else {
+                        return parseAutoOption('auto', defaultValue);
+                    }
+                }
+                if (res.toLowerCase() === 'false') {
+                    return false;
+                }
+                res = parseInt(res);
+                if (isNaN(res)) {
+                    // e.g. ?autoplay or ?autoplay=true
+                    return defaultValue;
+                }
+                return res;
+            }
+
+            /* Whether to wait for "next" event between actions */
+            var autoPlay = parseAutoOption('autoplay', 5000);
+            /* Whether to wait for "next" event on page load */
+            var autoStart = parseAutoOption('autostart', 5000);
+            /* Whether to loop back to start at the end of presentation */
+            var autoLoop = parseAutoOption('autoloop', 5000);
+
+            var events = (function() {
+                var self = {};
+                /*
+                 * Creates a generic event.
+                 */
+                var createEvent = function() {
+                    var callbacks = $.Callbacks();
+                    return {
+                        publish: callbacks.fire,
+                        subscribe: callbacks.add,
+                        unsubscribe: callbacks.remove,
+                        unsubscribeAll: callbacks.empty
+                    };
+                };
+                /*
+                 * Called when the button next is pressed.
+                 */
+                self.onNext = createEvent();
+                /*
+                 * Called when the button previous is pressed.
+                 */
+                self.onPrevious = createEvent();
+                /*
+                 * Called when an action is over.
+                 */
+                self.onActionOver = createEvent();
+                /*
+                 * Called when an action starts.
+                 */
+                self.onActionStart = createEvent();
+                /*
+                 * Called when at least one action was running and has finished.
+                 */
+                self.onAllActionFinish = createEvent();
+                
+                return self;
+            })();
+
+            /*
+             * Variables used like index for methodes subscribed.
+             */
+            var currentAction = 0;    /* Action currently running */
+            var nextAction = 0;       /* Action to execute on 'Next' click */
+            var nbActionsRunning = 0;
+            var previousAnchor = -1;  /* Last entered anchor, only used in
+                                       * case the 'onhashchange' event is
+                                       * not supported by the web browser */
+            
+            /*
+             * Functions to reinitialise running actions and subscribed
+             * callbacks (reinitialise is called once without stopping
+             * all the actions, so the two functions are separated).
+             */
+            var stopAllActions = function() {
+                $(selector + " .object-list").children().stop(true, true);
+                reinitialiseCallbackLists();
+                nbActionsRunning = 0;
+            };
+            var reinitialiseCallbackLists = function() {
+                events.onActionStart.unsubscribeAll();
+                events.onActionStart.subscribe(onActionStartEventHandler);
+                events.onAllActionFinish.unsubscribeAll();
+            };
+            
+            /*
+             * Timer used to program onNext event in autoplay mode.
+             */
+            var playTimer = 0;
+
+            /*
+             * Function used when an "onNextEvent" event is caught.
+             */
+            var onNextEventHandler = function() {
+                /*
+                 * If the user pressed "next" before an autoplay event
+                 * is triggered, it replaces the autoplay event, hence
+                 * cancels it:
+                 */
+                window.clearTimeout(playTimer);
+
+                enterAnimationMode();
+                stopAllActions();
+                var tmpAction = nextAction;
+                var onlyWithPrevious = true;
+                nextAction++;
+                currentAction = nextAction;
+                while (json.action[currentAction] && onlyWithPrevious) {
+                    switch (json.action[currentAction].trigger) {
+                        case 'onClick':
+                            nextAction = currentAction;
+                            onlyWithPrevious = false;
+                            break;
+                        case 'withPrevious':
+                            var tmp = currentAction;
+                            /*
+                             * We can't directly pass 'execute' as callback because we
+                             * allow the 'execute' function to reference a property of the
+                             * object (by using 'this.property') so we have to call the
+                             * function in the containing object to make that available
+                             */
+                            events.onActionStart.subscribe(function(events, selector) {
+                                json.action[tmp].execute(events, selector);
+                            });
+                            break;
+                        case 'afterPrevious':
+                            var tmp = currentAction;
+                            events.onAllActionFinish.subscribe(function(events, selector) {
+                                json.action[tmp].execute(events, selector);
+                            });
+                            while (json.action[nextAction] && json.action[nextAction].trigger !== 'onClick') {
+                                nextAction++;
+                            }
+                            onlyWithPrevious = false;
+                            break;
+                    }
+                    currentAction++;
+                }
+                if (json.action[tmpAction]) {
+                    launch(json.action[tmpAction]);
+                }
+                if (nextAction > json.action.length) {
+                    nextAction = json.action.length;
+                }
+            };
+            
+            /*
+             * Function used when an "onPreviousEvent" event is caught.
+             */
+            var onPreviousEventHandler = function() {
+                stopAllActions();
+                currentAction = nextAction - 1;
+                while (json.action[currentAction] && json.action[currentAction].trigger !== 'onClick') {
+                    launchReverse(json.action[currentAction]);
+                    currentAction--;
+                }
+                /* currentAction = -1 means that it's the beginning */
+                if (currentAction > -1) {
+                    launchReverse(json.action[currentAction]);
+                }
+                if (currentAction < 0) {
+                    currentAction = 0;
+                }
+                nextAction = currentAction;
+            };
+
+            /*
+             * Function used when an "onActionOverEvent" event is caught.
+             */
+            var onActionOverEventHandler = function() {
+                nbActionsRunning--;
+                if (nbActionsRunning === 0) {
+                    events.onAllActionFinish.publish(events, selector);
+                    reinitialiseCallbackLists();
+                    while (json.action[currentAction]) {
+                        switch (json.action[currentAction].trigger) {
+                            case 'onClick':
+                                leaveAnimationMode();
+                                if (autoPlay && nbActionsRunning === 0) {
+                                    playTimer = setTimeout(function () {
+                                        events.onNext.publish();
+                                    }, autoPlay);
+                                }
+                                return;
+                            case 'withPrevious':
+                                var tmp = currentAction;
+                                events.onActionStart.subscribe(function(events, selector) {
+                                    json.action[tmp].execute(events, selector);
+                                });
+                                break;
+                            case 'afterPrevious':
+                                var tmp = currentAction;
+                                events.onAllActionFinish.subscribe(function(events, selector) {
+                                    json.action[tmp].execute(events, selector);
+                                });
+                                currentAction++;
+                                return;
+                        }
+                        currentAction++;
+
+                    }
+                    if (autoLoop && nbActionsRunning === 0) {
+                        setTimeout(function () {
+                            resetPresentation();
+                            startPresentationMaybe();
+                        }, autoLoop);
+                    }
+                }
+                leaveAnimationMode();
+            };
+
+            /*
+             * Function used when an "onActionStartEvent" event is caught.
+             */
+            var onActionStartEventHandler = function() {
+                nbActionsRunning++;
+            };
+
+            /*
+             * Enter and leave "animation" mode. The viewer is in
+             * animation mode when something is going on without human
+             * interaction (i.e. executing actions before the next
+             * "onClick").
+             */
+            var enterAnimationMode = function () {
+                $(selector + ' .mouse-cursor-pause').hide();
+                $(selector + ' .mouse-cursor-normal').show();
+            };
+
+            var leaveAnimationMode = function () {
+                $(selector + ' .mouse-cursor-pause').show();
+                $(selector + ' .mouse-cursor-normal').hide();
+            };
+
+            /*
+             * Function used to perform actions.
+             */
+            var launch = function(action) {
+                action.execute(events, selector);
+            };
+            var launchReverse = function(action) {
+                action.executeReverse(selector);
+            };
+
+            /*
+             * The two following methods each returns an array containing the
+             * actions to execute to reach the given anchor (respectively
+             * forward or backwards).
+             *
+             * If the given anchor matches none of the actions, then an empty
+             * array is returned.
+             */
+            var getActionsToJumpForward = function(anchor) {
+                var actions = new Array();
+                for (var i = nextAction; i < json.action.length; i++) {
+                    // '==' and not '===' because we compare indifferently a
+                    // number or a string or anything else
+                    if (json.action[i].id == anchor) {
+                        return actions;
+                    } else {
+                        actions.push(json.action[i]);
+                    }
+                }
+                // Here, the anchor has not been found during the action scan
+                return null;
+            };
+            var getActionsToJumpBackwards = function(anchor) {
+                var actions = new Array();
+                for (var i = nextAction - 1; i >= 0; i--) {
+                    // '==' and not '===' because we compare indifferently a
+                    // number or a string or anything else
+                    actions.push(json.action[i]);
+                    if (json.action[i].id == anchor) {
+                        return actions;
+                    }
+                }
+                // Here, the anchor has not been found during the action scan
+                return null;
+            };
+            
+            /*
+             * Updates the position of the presentation depending on the
+             * given anchor (next action wanted).
+             */
+            var jumpToAnchor = function(anchor) {
+                if (anchor !== '') {
+                    stopAllActions();
+                    if (anchor > nextAction) {
+                        // forward
+                        var actions = getActionsToJumpForward(anchor);
+                        for (var i = 0; i < actions.length; i++) {
+                            actions[i].executeImmediately(selector);
+                        }
+                        nextAction += actions.length;
+                    } else {
+                        // backwards
+                        var actions = getActionsToJumpBackwards(anchor);
+                        for (var i = 0; i < actions.length; i++) {
+                            actions[i].executeReverse(selector);
+                        }
+                        nextAction -= actions.length;
+                    }
+                }
+            };
+
+            var resetPresentation = function() {
+                window.clearTimeout(playTimer);
+
+                currentAction = 0;
+                nextAction = 0;
+                nbActionsRunning = 0;
+
+                /*
+                 * At the beginning, the visible image is the first one of the presentation
+                 */
+                $(selector + " .object-list").children().hide();
+
+                $(selector + " ." + json.metaData.initialBackgroundId).show();
+
+                $(selector + " .mouse-cursor").css({
+                    'top': (json.metaData.initialMouseY * json.metaData.imageHeight) + "px",
+                    'left': (json.metaData.initialMouseX * json.metaData.imageWidth) + "px"
+                });
+
+                $(selector + " .mouse-cursor").show();
+            }
+
+            var startPresentationMaybe = function() {
+                if (autoStart) {
+                    playTimer = setTimeout(function () {
+                        events.onNext.publish();
+                    }, autoStart);
+                }
+            }
+
+            /* Public API */
+
+            this.loadUrl = function(url) {
+                var dataJson;
+                $.ajax({
+                    'async': false,
+                    'global': false,
+                    'url': url,
+                    'dataType': "json",
+                    'success': function(data) {
+                        dataJson = data;
+                    }
+                });
+                
+                load(dataJson);
+            };
+
+            this.load = function(dataJson) {
+                /* Transforms data JSON to executable functions for actions */
+                var execModel = dahuapp.createScreencastExecutableModel();
+                execModel.loadJson(dataJson);
+                json = execModel.getJson();
+            };
+
+            this.start = function() {
+                
+                /*
+                 * Subscription of methods to their events.
+                 */
+                events.onNext.subscribe(onNextEventHandler);
+                events.onPrevious.subscribe(onPreviousEventHandler);
+                events.onActionOver.subscribe(onActionOverEventHandler);
+                events.onActionStart.subscribe(onActionStartEventHandler);
+
+                resetPresentation();
+
+                /*
+                 * If an anchor has been specified, we place the presentation
+                 * in the right position.
+                 */
+                jumpToAnchor(window.location.hash.substring(1));
+                
+                /*
+                 * If the anchor changes during the presentation, then the
+                 * presentation is updated
+                 */
+                if ("onhashchange" in window) {
+                    // event supported
+                    window.onhashchange = function () {
+                        jumpToAnchor(window.location.hash.substring(1));
+                    };
+                } else {
+                    // event not supported : periodical check
+                    window.setInterval(function () {
+                        if (window.location.hash.substring(1) !== previousAnchor) {
+                            previousAnchor = window.location.hash.substring(1);
+                            jumpToAnchor(window.location.hash.substring(1));
+                        }
+                    }, 100);
+                }
+
+                /*
+                 * A click on the "next" button publishes a nextSlide event
+                 */
+                $(selector + " .next").click(function() {
+                    events.onNext.publish();
+                });
+                
+                /*
+                 * A click on the "previous" button publishes a previousSlide event
+                 */
+                $(selector + " .previous").click(function() {
+                    events.onPrevious.publish();
+                });
+
+                /*
+                 * Everything is ready, show the presentation
+                 * (hidden with style="display: none" from HTML).
+                 */
+                $("#loading").hide();
+                $(selector + " .object-list").show();
+                startPresentationMaybe();
+            };
+        };
+
+        self.createDahuViewer = function(selector, getParams) {
+            return new DahuViewerModel(selector, getParams);
+        };
+
+        return self;
+    })();
+
+    dahuapp.viewer = viewer;
+})(dahuapp || {}, jQuery);
diff --git a/TP/TP01/P04/img/22f57dfb-d5d9-24ac-3822-44cdf750c340.png b/TP/TP01/P04/img/22f57dfb-d5d9-24ac-3822-44cdf750c340.png
new file mode 100644
index 0000000000000000000000000000000000000000..3fbf26f279759f0f58d8a7338aaffbfe558f3b57
Binary files /dev/null and b/TP/TP01/P04/img/22f57dfb-d5d9-24ac-3822-44cdf750c340.png differ
diff --git a/TP/TP01/P04/img/32df9339-a8f1-add7-c8f7-5bad61108d22.png b/TP/TP01/P04/img/32df9339-a8f1-add7-c8f7-5bad61108d22.png
new file mode 100644
index 0000000000000000000000000000000000000000..f1266f2a39446e4260526f6b7b1c027d12ef3938
Binary files /dev/null and b/TP/TP01/P04/img/32df9339-a8f1-add7-c8f7-5bad61108d22.png differ
diff --git a/TP/TP01/P04/img/34af7a1e-a62c-bf60-4b6d-1d5bba8fbab3.png b/TP/TP01/P04/img/34af7a1e-a62c-bf60-4b6d-1d5bba8fbab3.png
new file mode 100644
index 0000000000000000000000000000000000000000..52482414ce6df08b6c7b071f6d3732bdd2d3c336
Binary files /dev/null and b/TP/TP01/P04/img/34af7a1e-a62c-bf60-4b6d-1d5bba8fbab3.png differ
diff --git a/TP/TP01/P04/img/7df40e66-e67e-7070-6f6a-adc26e6841f6.png b/TP/TP01/P04/img/7df40e66-e67e-7070-6f6a-adc26e6841f6.png
new file mode 100644
index 0000000000000000000000000000000000000000..cc84ce5ed3d0552e88f48cb291208885940fee67
Binary files /dev/null and b/TP/TP01/P04/img/7df40e66-e67e-7070-6f6a-adc26e6841f6.png differ
diff --git a/TP/TP01/P04/img/8f253579-c94d-29b5-6f1e-e239b16c6a7d.png b/TP/TP01/P04/img/8f253579-c94d-29b5-6f1e-e239b16c6a7d.png
new file mode 100644
index 0000000000000000000000000000000000000000..801e80a45e43a07ae6cdc01395905b3154aa22a6
Binary files /dev/null and b/TP/TP01/P04/img/8f253579-c94d-29b5-6f1e-e239b16c6a7d.png differ
diff --git a/TP/TP01/P04/img/a53fecfe-4d94-0def-1a43-91162745c337.png b/TP/TP01/P04/img/a53fecfe-4d94-0def-1a43-91162745c337.png
new file mode 100644
index 0000000000000000000000000000000000000000..2dc963034c7ded6db13dd59a847ddfa29da2c64b
Binary files /dev/null and b/TP/TP01/P04/img/a53fecfe-4d94-0def-1a43-91162745c337.png differ
diff --git a/TP/TP01/P04/img/b91ea9d5-6005-3dc0-3c68-2229b7f23e3d.png b/TP/TP01/P04/img/b91ea9d5-6005-3dc0-3c68-2229b7f23e3d.png
new file mode 100644
index 0000000000000000000000000000000000000000..f1ea0c31ccf29bfb76e3d9332cc6bf9d8d8711dd
Binary files /dev/null and b/TP/TP01/P04/img/b91ea9d5-6005-3dc0-3c68-2229b7f23e3d.png differ
diff --git a/TP/TP01/P04/img/c92f1c4d-511d-9b6d-0af1-d38d47a82980.png b/TP/TP01/P04/img/c92f1c4d-511d-9b6d-0af1-d38d47a82980.png
new file mode 100644
index 0000000000000000000000000000000000000000..dcf03054082b13dd8116da3c9a01024adb0e53ed
Binary files /dev/null and b/TP/TP01/P04/img/c92f1c4d-511d-9b6d-0af1-d38d47a82980.png differ
diff --git a/TP/TP01/P04/img/cbd2a76c-7f63-2a57-a82e-ca585b89e917.png b/TP/TP01/P04/img/cbd2a76c-7f63-2a57-a82e-ca585b89e917.png
new file mode 100644
index 0000000000000000000000000000000000000000..65455926d9de613f4eb0364a1a44621cc4fdc4b6
Binary files /dev/null and b/TP/TP01/P04/img/cbd2a76c-7f63-2a57-a82e-ca585b89e917.png differ
diff --git a/TP/TP01/P04/img/cursor-pause.png b/TP/TP01/P04/img/cursor-pause.png
new file mode 100644
index 0000000000000000000000000000000000000000..9c93cc142f4326a188eafc3c0fe96f2975dcab0c
Binary files /dev/null and b/TP/TP01/P04/img/cursor-pause.png differ
diff --git a/TP/TP01/P04/img/cursor.png b/TP/TP01/P04/img/cursor.png
new file mode 100644
index 0000000000000000000000000000000000000000..5b4a20e63078ce18bb11a4e601a1994b261522ef
Binary files /dev/null and b/TP/TP01/P04/img/cursor.png differ
diff --git a/TP/TP01/P04/img/d0874d2b-ad91-6fac-ec2d-53b7588ffe67.png b/TP/TP01/P04/img/d0874d2b-ad91-6fac-ec2d-53b7588ffe67.png
new file mode 100644
index 0000000000000000000000000000000000000000..2f57eff22f1e262f02db67da22a510138b7cb1b2
Binary files /dev/null and b/TP/TP01/P04/img/d0874d2b-ad91-6fac-ec2d-53b7588ffe67.png differ
diff --git a/TP/TP01/P04/img/d59762c8-7b29-dc5e-1005-176ef0163664.png b/TP/TP01/P04/img/d59762c8-7b29-dc5e-1005-176ef0163664.png
new file mode 100644
index 0000000000000000000000000000000000000000..b4f2db23598087e98c77d09c774c1a72ca0215ad
Binary files /dev/null and b/TP/TP01/P04/img/d59762c8-7b29-dc5e-1005-176ef0163664.png differ
diff --git a/TP/TP01/P04/img/ea723de1-bb7a-95c7-935a-530021dffd8e.png b/TP/TP01/P04/img/ea723de1-bb7a-95c7-935a-530021dffd8e.png
new file mode 100644
index 0000000000000000000000000000000000000000..1923aa3f5fa62f4930fc827bb9cf4b61e51b580b
Binary files /dev/null and b/TP/TP01/P04/img/ea723de1-bb7a-95c7-935a-530021dffd8e.png differ
diff --git a/TP/TP01/P04/index.html b/TP/TP01/P04/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..d283446ff7baa36d57a86520586ba6c04f2a6c05
--- /dev/null
+++ b/TP/TP01/P04/index.html
@@ -0,0 +1,375 @@
+<!DOCTYPE html>
+
+<html lang="en">
+
+<head>
+<title>Dahu Presentation
+</title>
+<meta charset="utf-8">
+<script src="http://code.jquery.com/jquery-1.9.1.min.js" type="text/javascript">
+</script>
+<script src="parse-search.js" type="text/javascript">
+</script>
+
+<link rel="stylesheet" href="dahuapp.viewer.css"><style>.s1-o2{background-color:   #FFFFDD;width:  240px}
+.s2-o2{background-color:   #FFFFDD;width:  240px}
+.s3-o2{background-color:   #FFFFDD;width:  240px}
+.s5-o2{background-color:   #FFFFDD;width:  240px}
+.s6-o2{background-color:   #FFFFDD;width:  240px}
+.s9-o2{background-color:   #FFFFDD;width:  240px}
+.s10-o2{background-color:   #FFFFDD;width:  240px}
+.s11-o2{background-color:   #FFFFDD;width:  240px}
+</style>
+</head>
+
+<body>
+
+<div id="my-dahu-presentation">
+
+<div id="loading">Loading presentation...
+</div>
+
+<div class="object-list" style="display: none">
+<img src="img/b91ea9d5-6005-3dc0-3c68-2229b7f23e3d.png" alt="img/b91ea9d5-6005-3dc0-3c68-2229b7f23e3d.png" class="background b91ea9d5-6005-3dc0-3c68-2229b7f23e3d0">
+<img src="img/c92f1c4d-511d-9b6d-0af1-d38d47a82980.png" alt="img/c92f1c4d-511d-9b6d-0af1-d38d47a82980.png" class="background c92f1c4d-511d-9b6d-0af1-d38d47a829800">
+
+<div class="tooltip s1-o2">Supprimer le bloc Derivator.
+</div>
+<img src="img/32df9339-a8f1-add7-c8f7-5bad61108d22.png" alt="img/32df9339-a8f1-add7-c8f7-5bad61108d22.png" class="background 32df9339-a8f1-add7-c8f7-5bad61108d220">
+
+<div class="tooltip s2-o2">Supprimer les signaux qui ne sont plus connectés.
+</div>
+<img src="img/22f57dfb-d5d9-24ac-3822-44cdf750c340.png" alt="img/22f57dfb-d5d9-24ac-3822-44cdf750c340.png" class="background 22f57dfb-d5d9-24ac-3822-44cdf750c3400">
+
+<div class="tooltip s3-o2">Supprimer les signaux qui ne sont plus connectés.
+</div>
+<img src="img/d59762c8-7b29-dc5e-1005-176ef0163664.png" alt="img/d59762c8-7b29-dc5e-1005-176ef0163664.png" class="background d59762c8-7b29-dc5e-1005-176ef01636640">
+<img src="img/ea723de1-bb7a-95c7-935a-530021dffd8e.png" alt="img/ea723de1-bb7a-95c7-935a-530021dffd8e.png" class="background ea723de1-bb7a-95c7-935a-530021dffd8e0">
+
+<div class="tooltip s5-o2">Sélectionner les paramètres de l'oscilloscope.
+</div>
+<img src="img/7df40e66-e67e-7070-6f6a-adc26e6841f6.png" alt="img/7df40e66-e67e-7070-6f6a-adc26e6841f6.png" class="background 7df40e66-e67e-7070-6f6a-adc26e6841f60">
+
+<div class="tooltip s6-o2">Réduire le nombre d'entrées à 2.
+</div>
+<img src="img/d0874d2b-ad91-6fac-ec2d-53b7588ffe67.png" alt="img/d0874d2b-ad91-6fac-ec2d-53b7588ffe67.png" class="background d0874d2b-ad91-6fac-ec2d-53b7588ffe670">
+<img src="img/34af7a1e-a62c-bf60-4b6d-1d5bba8fbab3.png" alt="img/34af7a1e-a62c-bf60-4b6d-1d5bba8fbab3.png" class="background 34af7a1e-a62c-bf60-4b6d-1d5bba8fbab30">
+<img src="img/cbd2a76c-7f63-2a57-a82e-ca585b89e917.png" alt="img/cbd2a76c-7f63-2a57-a82e-ca585b89e917.png" class="background cbd2a76c-7f63-2a57-a82e-ca585b89e9170">
+
+<div class="tooltip s9-o2">Ouvrir les paramètres du bloc Integrator. Choisir la valeur initiale de l'intégrale (-1 dans ce cas).
+</div>
+<img src="img/a53fecfe-4d94-0def-1a43-91162745c337.png" alt="img/a53fecfe-4d94-0def-1a43-91162745c337.png" class="background a53fecfe-4d94-0def-1a43-91162745c3370">
+
+<div class="tooltip s10-o2">Démarrer la simulation.
+</div>
+<img src="img/8f253579-c94d-29b5-6f1e-e239b16c6a7d.png" alt="img/8f253579-c94d-29b5-6f1e-e239b16c6a7d.png" class="background 8f253579-c94d-29b5-6f1e-e239b16c6a7d0">
+
+<div class="tooltip s11-o2">Mettre à l'échelle l'écran de l'oscilloscope. Oberver le résultat obtenu pour l'intégration de la fonction sinusoïde.
+</div>
+
+<div class="mouse-cursor">
+<img src="img/cursor.png" alt="img/cursor.png" class="mouse-cursor-normal">
+<img src="img/cursor-pause.png" alt="img/cursor-pause.png" style="display: none" class="mouse-cursor-pause">
+</div>
+</div>
+
+<div class="control" style="top: 616px;">
+<button class="previous">Previous
+</button>
+<button class="next">Next
+</button>
+</div>
+</div>
+<script src="dahuapp.js" type="text/javascript">
+</script>
+<script src="dahuapp.viewer.js" type="text/javascript">
+</script>
+<script type="text/javascript">(function($) {
+    var myPresentation = dahuapp.viewer.createDahuViewer("#my-dahu-presentation", window.getParams);
+    myPresentation.load({
+    "metaData": {
+        "imageWidth": 800,
+        "imageHeight": 600,
+        "initialBackgroundId": "b91ea9d5-6005-3dc0-3c68-2229b7f23e3d0",
+        "initialMouseX": 0.2520833333333333,
+        "initialMouseY": 0.3522222222222222
+    },
+    "action": [
+        {
+            "id": "3",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.2923611111111111,
+            "finalOrd": 0.47444444444444445,
+            "speed": 0.8
+        },
+        {
+            "id": "4",
+            "type": "appear",
+            "target": "c92f1c4d-511d-9b6d-0af1-d38d47a829800",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "26",
+            "type": "appear",
+            "target": "s1-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.41,
+            "ord": 0.1725,
+            "duration": 400
+        },
+        {
+            "id": "5",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.29375,
+            "finalOrd": 0.4288888888888889,
+            "speed": 0.8
+        },
+        {
+            "id": "6",
+            "type": "appear",
+            "target": "32df9339-a8f1-add7-c8f7-5bad61108d220",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "27",
+            "type": "appear",
+            "target": "s2-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.43,
+            "ord": 0.15125,
+            "duration": 400
+        },
+        {
+            "id": "7",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.2638888888888889,
+            "finalOrd": 0.43666666666666665,
+            "speed": 0.8
+        },
+        {
+            "id": "8",
+            "type": "appear",
+            "target": "22f57dfb-d5d9-24ac-3822-44cdf750c3400",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "28",
+            "type": "appear",
+            "target": "s3-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.3933333333333333,
+            "ord": 0.155,
+            "duration": 400
+        },
+        {
+            "id": "9",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.2520833333333333,
+            "finalOrd": 0.45222222222222225,
+            "speed": 0.8
+        },
+        {
+            "id": "10",
+            "type": "appear",
+            "target": "d59762c8-7b29-dc5e-1005-176ef01636640",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "11",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.06388888888888888,
+            "finalOrd": 0.6655555555555556,
+            "speed": 0.8
+        },
+        {
+            "id": "12",
+            "type": "appear",
+            "target": "ea723de1-bb7a-95c7-935a-530021dffd8e0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "29",
+            "type": "appear",
+            "target": "s5-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.22166666666666668,
+            "ord": 0.29375,
+            "duration": 400
+        },
+        {
+            "id": "13",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.09722222222222222,
+            "finalOrd": 0.7555555555555555,
+            "speed": 0.8
+        },
+        {
+            "id": "14",
+            "type": "appear",
+            "target": "7df40e66-e67e-7070-6f6a-adc26e6841f60",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "30",
+            "type": "appear",
+            "target": "s6-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.15333333333333332,
+            "ord": 0.30875,
+            "duration": 400
+        },
+        {
+            "id": "15",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.2659722222222222,
+            "finalOrd": 0.31777777777777777,
+            "speed": 0.8
+        },
+        {
+            "id": "16",
+            "type": "appear",
+            "target": "d0874d2b-ad91-6fac-ec2d-53b7588ffe670",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "17",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.24930555555555556,
+            "finalOrd": 0.45222222222222225,
+            "speed": 0.8
+        },
+        {
+            "id": "18",
+            "type": "appear",
+            "target": "34af7a1e-a62c-bf60-4b6d-1d5bba8fbab30",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "19",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.35347222222222224,
+            "finalOrd": 0.3466666666666667,
+            "speed": 0.8
+        },
+        {
+            "id": "20",
+            "type": "appear",
+            "target": "cbd2a76c-7f63-2a57-a82e-ca585b89e9170",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "31",
+            "type": "appear",
+            "target": "s9-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.35833333333333334,
+            "ord": 0.16125,
+            "duration": 400
+        },
+        {
+            "id": "21",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.28888888888888886,
+            "finalOrd": 0.10111111111111111,
+            "speed": 0.8
+        },
+        {
+            "id": "22",
+            "type": "appear",
+            "target": "a53fecfe-4d94-0def-1a43-91162745c3370",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "32",
+            "type": "appear",
+            "target": "s10-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.30333333333333334,
+            "ord": 0.05875,
+            "duration": 400
+        },
+        {
+            "id": "23",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.1486111111111111,
+            "finalOrd": 0.6633333333333333,
+            "speed": 0.8
+        },
+        {
+            "id": "24",
+            "type": "appear",
+            "target": "8f253579-c94d-29b5-6f1e-e239b16c6a7d0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "33",
+            "type": "appear",
+            "target": "s11-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.21166666666666667,
+            "ord": 0.295,
+            "duration": 400
+        }
+    ]
+});
+    myPresentation.start();
+})(jQuery);
+
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/TP/TP01/P04/parse-search.js b/TP/TP01/P04/parse-search.js
new file mode 100644
index 0000000000000000000000000000000000000000..e7d01c07f6a5d45b9fcabd5d271a8c6cfc5f00e2
--- /dev/null
+++ b/TP/TP01/P04/parse-search.js
@@ -0,0 +1,14 @@
+// Adapted from http://stackoverflow.com/questions/3234125/creating-array-from-window-location-hash
+(function () {
+    var search = window.location.search.slice(1);
+    var array = search.split("&");
+    
+    var values, form_data = {};
+    
+    for (var i = 0; i < array.length; i += 1) {
+	values = array[i].split("=");
+	form_data[values[0]] = unescape(values[1]);
+    }
+    window.getParams = form_data;
+}())
+
diff --git a/TP/TP01/P05/dahuapp.js b/TP/TP01/P05/dahuapp.js
new file mode 100644
index 0000000000000000000000000000000000000000..41c69e9169345a9a6703325d5557a4cc36913e19
--- /dev/null
+++ b/TP/TP01/P05/dahuapp.js
@@ -0,0 +1,880 @@
+"use strict";
+
+/**
+ * Dahuapp core module.
+ *
+ * @param   window      window javascript object.
+ * @param   $           jQuery
+ * @returns dahuapp core module.
+ */
+(function (window, $) {
+    var dahuapp = (function () {
+
+        var self = {};
+
+        /* private API */
+
+        var DahuScreencastGenerator = function () {
+
+            /*
+             * Format the specified string in a readable format.
+             */
+            function htmlFormat(htmlString) {
+                var i;
+                var readableHTML = htmlString;
+                var lb = '\n';
+                var htags = ["<html", "</html>", "</head>", "<title", "</title>", "<meta", "<link", "</body>"];
+                for (i = 0; i < htags.length; ++i) {
+                    var hhh = htags[i];
+                    readableHTML = readableHTML.replace(new RegExp(hhh, 'gi'), lb + hhh);
+                }
+                var btags = ["</div>", "</section>", "</span>", "<br>", "<br />", "<blockquote", "</blockquote>", "<ul", "</ul>", "<ol", "</ol>", "<li", "<\!--", "<script", "</script>"];
+                for (i = 0; i < btags.length; ++i) {
+                    var bbb = btags[i];
+                    readableHTML = readableHTML.replace(new RegExp(bbb, 'gi'), lb + bbb);
+                }
+                var ftags = ["<img", "<legend", "</legend>", "<button", "</button>"];
+                for (i = 0; i < ftags.length; ++i) {
+                    var fff = ftags[i];
+                    readableHTML = readableHTML.replace(new RegExp(fff, 'gi'), lb + fff);
+                }
+                var xtags = ["<body", "<head", "<div", "<section", "<span", "<p"];
+                for (i = 0; i < xtags.length; ++i) {
+                    var xxx = xtags[i];
+                    readableHTML = readableHTML.replace(new RegExp(xxx, 'gi'), lb + lb + xxx);
+                }
+                return readableHTML;
+            }
+
+            /*
+             * Generates the html header.
+             */
+            var generateHtmlHeader = function ($generated, cssGen) {
+                $('head', $generated)
+                    .append($(document.createElement('title'))
+                        .append("Dahu Presentation"))/* TODO: make this customizable */
+                    .append($(document.createElement('meta'))
+                        .attr({'charset': 'utf-8'}))
+                    .append($(document.createElement('script'))
+                        .attr({'src': 'http://code.jquery.com/jquery-1.9.1.min.js'})
+                        .attr({'type': 'text/javascript'}))
+                    .append($(document.createElement('script'))
+                        .attr({'src': 'parse-search.js'})
+                        .attr({'type': 'text/javascript'}))
+                    .append($(document.createElement('link'))
+                        .attr({'rel': 'stylesheet', 'href': 'dahuapp.viewer.css'}))
+                    .append($(document.createElement('style'))
+                        .append(cssGen));
+            };
+
+            /*
+             * Generates the objects.
+             */
+
+            var generateHtmlBackgroundImage = function ($generated, object) {
+                $('.object-list', $generated)
+                    .append($(document.createElement('img'))
+                        .attr({'src': object.img, 'alt': object.img, 'class': 'background ' + object.id}));
+            };
+            var generateHtmlTooltip = function ($generated, object) {
+                $('.object-list', $generated)
+                    .append($(document.createElement('div'))
+                        .attr({'class': 'tooltip ' + object.id})
+                        .append(object.text));
+            };
+
+            var generateCssTooltip = function ($generated, object) {
+                var style = [];
+
+                if( object.color != null ) {
+                    style.push('background-color:   ' + object.color);
+                }
+                if( object.width != null ) {
+                    style.push('width:  ' + object.width);
+                }
+
+                if( style.length != 0 ) {
+                    $generated.append(
+                    '.' + object.id + '{' + style.join(';') + '}\n');
+                }
+
+                return $generated;
+            };
+
+            /*
+             * Returns the code used to call the viewer to the presentation.
+             */
+            var getBasicCallCode = function (jsonModel) {
+                var code = '(function($) {\n';
+                code += '    var myPresentation = dahuapp.viewer.createDahuViewer("#my-dahu-presentation", window.getParams);\n';
+                code += '    myPresentation.load(' + jsonModel + ');\n';
+                code += '    myPresentation.start();\n';
+                code += '})(jQuery);\n';
+                return code;
+            };
+
+            /*
+             * Generates the html body.
+             */
+            var generateHtmlBody = function ($generated, jsonModel, jsonGen) {
+                $('body', $generated)
+                    /* We could use a <section> here too, but it does not work with MS IE 8.
+                     Alternatively, adding this in the header would work:
+
+                     <!--[if lt IE 9]>
+                     <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
+                     <![endif]-->
+                     */
+                    .append($(document.createElement('div'))
+                        .attr({'id': 'my-dahu-presentation'}))
+                    .append($(document.createElement('script'))
+                        .attr({'src': 'dahuapp.js'})
+                        .attr({'type': 'text/javascript'}))
+                    .append($(document.createElement('script'))
+                        .attr({'src': 'dahuapp.viewer.js'})
+                        .attr({'type': 'text/javascript'}))
+                    .append($(document.createElement('script'))
+                        .attr({'type': 'text/javascript'})
+                        .append(getBasicCallCode(jsonGen)));
+                $('#my-dahu-presentation', $generated)
+                    .append($(document.createElement('div'))
+                        .attr({'id': 'loading'}).append("Loading presentation..."))
+                    .append($(document.createElement('div'))
+                        .attr({'class': 'object-list',
+                            'style': 'display: none'}))
+                    .append($(document.createElement('div'))
+                        .attr({'class': 'control'}));
+
+                /* Adding the objects to the page */
+                $.each(jsonModel.getObjectList(), function (id, object) {
+                    switch (object.type) {
+                        case "background":
+                            generateHtmlBackgroundImage($generated, object);
+                            break;
+                        case "tooltip":
+                            generateHtmlTooltip($generated, object);
+                            break;
+                        /* no mouse image generated here */
+                    }
+                });
+
+                /* Warning here, i'm not sure if $.each is always over, here */
+
+                /* Adding the control buttons to the page */
+                $('.control', $generated)
+                    .css({'top': (jsonModel.getImageHeight() + 16) + 'px'})
+                    .append($(document.createElement('button'))
+                        .attr({'class': 'previous'})
+                        .append('Previous'))
+                    .append($(document.createElement('button'))
+                        .attr({'class': 'next'})
+                        .append('Next'));
+
+                /* Adding the mouse cursor image */
+                $('.object-list', $generated)
+                    .append($(document.createElement('div'))
+                        .attr({'class': 'mouse-cursor'})
+                        .append($(document.createElement('img'))
+                            .attr({'src': 'img/cursor.png', 'alt': 'img/cursor.png', 'class': 'mouse-cursor-normal'}))
+                        .append($(document.createElement('img'))
+                            .attr({'src': 'img/cursor-pause.png', 'alt': 'img/cursor-pause.png',
+                                'style': 'display: none', 'class': 'mouse-cursor-pause'})));
+            };
+
+            /*
+             * Generates the css String with the Json model.
+             */
+            this.generateCssString = function (jsonModel) {
+                var $generated = $('<style></style>');
+
+                /* Going through each object */
+                $.each(jsonModel.getObjectList(), function (id, object) {
+                    switch (object.type) {
+                        case "tooltip":
+                            generateCssTooltip($generated, object);
+                            break;
+                        /* no mouse image generated here */
+                    }
+                });
+
+                return $generated.text();
+            }
+
+            /*
+             * Generates the html String with the Json model.
+             */
+            this.generateHtmlString = function (jsonModel, jsonGen, cssGen) {
+                /* Initialising the compilation area */
+                var $generated = $(document.createElement('div'));
+
+                /* We create the html using the json */
+                $generated.append($(document.createElement('html'))
+                    .attr({'lang': 'en'}));
+                $('html', $generated)
+                    .append($(document.createElement('head')))
+                    .append($(document.createElement('body')));
+
+                generateHtmlHeader($generated, cssGen);
+                generateHtmlBody($generated, jsonModel, jsonGen);
+
+                var result = htmlFormat($generated.html());
+
+                return '<!DOCTYPE html>\n' + result;
+            };
+
+            /*
+             * Generates the generated JSON using the JSONmodel.
+             * @param {object} jsonModel The json model to transform.
+             * @param {java.awt.Dimension} imgDim The dimension of images.
+             */
+            this.generateJsonString = function (jsonModel, imgDim) {
+                var generated = self.createScreencastGeneratedModel();
+                generated.setImageSize(imgDim.width, imgDim.height);
+                generated.setInitialBackground(jsonModel.getInitialBackground());
+                generated.setInitialMousePos(jsonModel.getInitialMousePos());
+                for (var i = 0; i < jsonModel.getNbSlide(); i++) {
+                    var actionList = jsonModel.getActionList(i);
+                    for (var j = 0; j < actionList.length; j++) {
+                        /* 
+                         * We don't add the two first actions of the first slide
+                         * because they are present in the presentation metadata.
+                         * It corresponds to the mouse initial pos and first background.
+                         */
+                        if (i > 0 || j > 1) {
+                            generated.addAction(actionList[j], imgDim.width, imgDim.height);
+                        }
+                    }
+                }
+                return generated.getJson();
+            };
+        };
+
+        /*
+         * Model for a JSON object that will only be used by the viewer, never
+         * saved on a file, used to transform the properties of actions
+         * specified on the JSON file of a presentation to executable
+         * functions for each action.
+         */
+        var DahuScreencastExecutableModel = function () {
+
+            /* Private API */
+
+            var json = {
+                metaData: {},
+                action: new Array()
+            };
+
+            /*
+             * Creates a functions representing the specified action, and adds
+             * it to this object.
+             * @param {object} action
+             */
+            var addExecutableAction = function (action) {
+                var executableAction = {
+                    'id': action.id,
+                    'trigger': action.trigger,
+                    'target': action.target,
+                    'delayAfter': action.delayAfter,
+                    'doneFunction': function (events, selector) {
+                        setTimeout(function () {
+                            events.onActionOver.publish(events, selector);
+                        }, this.delayAfter);
+                    }
+                };
+                if (executableAction.delayAfter == null) {
+                    executableAction.delayAfter = 200;
+                }
+                switch (action.type.toLowerCase()) {
+                    case "appear":
+                        executableAction.abs = (action.abs * json.metaData.imageWidth) + 'px';
+                        executableAction.ord = (action.ord * json.metaData.imageHeight) + 'px';
+                        executableAction.duration = action.duration;
+                        executableAction.execute = function (events, selector) {
+                            events.onActionStart.publish(events, selector);
+                            var sel = selector + ' .' + this.target;
+                            $(sel).css({
+                                'left': this.abs,
+                                'top': this.ord
+                            });
+                            $(sel).show(this.duration, function () {
+                                executableAction.doneFunction(events, selector);
+                            });
+                        };
+                        executableAction.executeReverse = function (selector) {
+                            $(selector + ' .' + this.target).hide();
+                        };
+                        executableAction.executeImmediately = function (selector) {
+                            var sel = selector + ' .' + this.target;
+                            $(sel).css({
+                                'left': this.abs,
+                                'top': this.ord
+                            });
+                            $(sel).show();
+                        };
+                        break;
+                    case "disappear":
+                        executableAction.duration = action.duration;
+                        executableAction.execute = function (events, selector) {
+                            events.onActionStart.publish(events, selector);
+                            $(selector + ' .' + this.target).hide(this.duration, function () {
+                                executableAction.doneFunction(events, selector);
+                            });
+                        };
+                        executableAction.executeReverse = function (selector) {
+                            $(selector + ' .' + this.target).show();
+                        };
+                        executableAction.executeImmediately = function (selector) {
+                            $(selector + ' .' + this.target).hide();
+                        };
+                        break;
+                    case "move":
+                        executableAction.finalAbs = (action.finalAbs * json.metaData.imageWidth) + 'px';
+                        executableAction.finalOrd = (action.finalOrd * json.metaData.imageHeight) + 'px';
+                        executableAction.duration = action.duration;
+                        executableAction.speed = action.speed;
+                        executableAction.execute = function (events, selector) {
+                            events.onActionStart.publish(events, selector);
+                            var sel = selector + ' .' + this.target;
+                            this.initialAbs = $(sel).css('left');
+                            this.initialOrd = $(sel).css('top');
+                            if (this.duration == null) {
+                                var initialAbsPix = this.initialAbs.replace('px', '');
+                                var initialOrdPix = this.initialOrd.replace('px', '');
+                                var finalAbsPix = this.finalAbs.replace('px', '');
+                                var finalOrdPix = this.finalOrd.replace('px', '');
+                                var distance = Math.sqrt(Math.pow(finalAbsPix - initialAbsPix, 2) +
+                                    Math.pow(finalOrdPix - initialOrdPix, 2));
+                                if (!this.speed) {
+                                    this.speed = .8; // in pixel per milisecond: fast, but not too much.
+                                }
+                                this.duration = distance + this.speed;
+                                if (this.duration < 200) { // Slow down a bit for short distances.
+                                    this.duration = 200;
+                                }
+                            }
+                            $(sel).animate({
+                                'left': this.finalAbs,
+                                'top': this.finalOrd
+                            }, this.duration, 'linear', function () {
+                                executableAction.doneFunction(events, selector);
+                            });
+                        };
+                        executableAction.executeReverse = function (selector) {
+                            $(selector + ' .' + this.target).css({
+                                'left': this.initialAbs,
+                                'top': this.initialOrd
+                            });
+                        };
+                        executableAction.executeImmediately = function (selector) {
+                            var sel = selector + ' .' + this.target;
+                            this.initialAbs = $(sel).css('left');
+                            this.initialOrd = $(sel).css('top');
+                            $(sel).css({
+                                'left': this.finalAbs,
+                                'top': this.finalOrd
+                            });
+                        };
+                        break;
+                    case "delay":
+                        executableAction.duration = action.duration;
+                        executableAction.execute = function (events, selector) {
+                            events.onActionStart.publish(events, selector);
+                            setTimeout(function () {
+                                events.onActionOver.publish(events, selector);
+                            }, this.duration);
+                        };
+                        executableAction.executeReverse = function (selector) {
+                            // Nothing!
+                        };
+                        executableAction.executeImmediately = function (selector) {
+                            // Nothing too!
+                        };
+                        break;
+                }
+                json.action.push(executableAction);
+            };
+
+            /* Public API */
+
+            /*
+             * Loads the specified JSON read from a 'presentation.json' file,
+             * and stores the functions representing each actions in an object.
+             * @param {object} jsonToLoad
+             */
+            this.loadJson = function (jsonToLoad) {
+                json.metaData = jsonToLoad.metaData;
+                for (var i = 0; i < jsonToLoad.action.length; i++) {
+                    addExecutableAction(jsonToLoad.action[i]);
+                }
+            };
+
+            /*
+             * Returns the object containing the functions.
+             */
+            this.getJson = function () {
+                return json;
+            };
+        };
+
+        /*
+         * Used to transform the 'dahu' file to a JSON file used by the viewer,
+         * which only contains some metaData and a list of actions.
+         */
+        var DahuScreencastGeneratedModel = function () {
+
+            /* Private API */
+
+            var json = {
+                metaData: {},
+                action: new Array()
+            };
+
+            /* Public API */
+
+            /*
+             * Returns a string representation of this json.
+             * @returns {String}
+             */
+            this.getJson = function () {
+                return JSON.stringify(json, null, '    ');
+            };
+
+            /*
+             * Setters for the generated json metadata.
+             */
+            this.setImageSize = function (width, height) {
+                json.metaData.imageWidth = width;
+                json.metaData.imageHeight = height;
+            };
+
+            this.setInitialMousePos = function (pos) {
+                json.metaData.initialMouseX = pos.x;
+                json.metaData.initialMouseY = pos.y;
+            };
+
+            this.setInitialBackground = function (id) {
+                json.metaData.initialBackgroundId = id;
+            };
+
+            this.addAction = function (action) {
+                json.action.push(action);
+            };
+
+            /*
+             * Transforms a JSON containing action properties to a JSON containing
+             * the execution functions.
+             * @param {Object} json
+             * @returns {Object} A json object containing the execution functions
+             * for all the actions of the presentation.
+             */
+            this.toExecutableList = function (json) {
+                var executableList = {
+                    metaData: json.metaData,
+                    action: new Array()
+                };
+                for (var i = 0; i < json.action.length; i++) {
+                    addExecutableAction(executableList, json.action[i]);
+                }
+                return executableList;
+            };
+        };
+
+        /*
+         * Represents a 'dahu' file for a project.
+         */
+        var DahuScreencastModel = function () {
+
+            /* Private API */
+
+            var json = {};
+
+            /*
+             * Generates a unique action ID.
+             *
+             * With this implementation, this ID is unique as long as the user
+             * doesn't replace it manually with a not kind value or replace
+             * the nextUniqueId with bad intentions.
+             * But maybe that a UUID is a bit tiresome to put as an anchor...
+             */
+            var generateUniqueActionId = function () {
+                json.metaData.nextUniqueId++;
+                return json.metaData.nextUniqueId.toString();
+            };
+
+            /*
+             * This method checks if the json representing a dahu project is
+             * in the good version. It's in case a project file was created
+             * with a previous version of Dahu, and that some fields are
+             * missing.
+             *
+             * It doesn't control each field (at the moment) but only the
+             * fields that can be missing due to a new Dahu version and not
+             * to a manual editing.
+             *
+             * This method doesn't check any Json syntax or something like that.
+             */
+            var upgradeJsonVersion = function () {
+                // Checks if unique IDs are in the project file
+                if (!json.metaData.nextUniqueId) {
+                    var currentId = 0;
+                    for (var i = 0; i < json.data.length; i++) {
+                        for (var j = 0; j < json.data[i].action.length; j++) {
+                            json.data[i].action[j].id = currentId.toString();
+                            currentId++;
+                        }
+                    }
+                    json.metaData.nextUniqueId = currentId;
+                }
+            };
+
+            /* Public API */
+
+            /*
+             * json variable generated from a JSON file.
+             * @param String stringJson String loaded from JSON file.
+             */
+            this.loadJson = function (stringJson) {
+                json = JSON.parse(stringJson);
+                upgradeJsonVersion();
+            };
+
+            /*
+             * Create a new presentation variable in the JSON file which will contain slides.
+             */
+            this.createPresentation = function (width, height) {
+                json.metaData = {};
+                json.metaData.imageWidth = width;
+                json.metaData.imageHeight = height;
+                json.metaData.nextUniqueId = 0;
+                json.data = new Array();
+            };
+
+            /*
+             * Add a new slide in the presentation variable of the JSON file.
+             * @param int index wanted for the slide.
+             * @param String idSlide Unique identifier for the slide.
+             * @param String img Related to pathname of the image.
+             * @param double mouseX Abscissa mouse position in %.
+             * @param double mouseY Ordinate mouse position in %.
+             * @return Index of the newly added slide.
+             */
+            this.addSlide = function (indexSlide, idSlide, img, mouseX, mouseY, speed) {
+                var slide = {
+                    "object": new Array(),
+                    "action": new Array()
+                };
+                json.data.splice(indexSlide, 0, slide);
+                this.addObject(indexSlide, "background", idSlide, img);
+                this.addObject(indexSlide, "mouse");
+                this.addAction(indexSlide, "move", json.data[indexSlide].object[1].id, "onClick", mouseX, mouseY, speed);
+                this.addAction(indexSlide, "appear", json.data[indexSlide].object[0].id, "afterPrevious");
+            };
+
+            /*
+             * Object factory.
+             * Add a new Object in the slide idSlide.
+             * @param int idSlide
+             * @param string type
+             * Other params can be specified depending on the object's type.
+             */
+            this.addObject = function (idSlide, type) {
+				var object = {
+                    "type": type
+                };
+                switch (type.toLowerCase()) {
+                    case "background":
+                        object.id = arguments[2] + json.data[idSlide].object.length;
+                        object.img = arguments[3] || "";
+                        break;
+                    case "mouse":
+                        object.id = "mouse-cursor";
+                        break;
+                    case "tooltip":
+                        /*
+                         * TODO: we'll need a more robust unique name
+                         * when we start actually using this.
+                         */
+                        object.id = "s" + idSlide + "-o" + json.data[idSlide].object.length;
+                        object.text = arguments[2] || "";
+                        object.color = arguments[3] || null;
+						object.width = arguments[4] || "";
+						json.data[idSlide].object.push(object);
+						var objectLength = json.data[idSlide].object.length;
+						var last = json.data[idSlide].object[objectLength-1];
+						json.data[idSlide].object[objectLength-1] = 
+								json.data[idSlide].object[objectLength-2];
+						json.data[idSlide].object[objectLength-2] = last;
+                        break;
+                }
+				if(type.toLowerCase() != "tooltip"){
+					json.data[idSlide].object.push(object);
+				}
+            };
+
+            /*
+             * Action factory.
+             * Add a new Action in the slide idSlide whose target is the id of an object.
+             * Three types of trigger : "withPrevious", "afterPrevious", "onClick".
+             * @param int idSlide
+             * @param string type
+             * @param string target
+             * @param string trigger
+             * Other params can be specified depending on the object's type.
+             */
+            this.addAction = function (idSlide, type, target, trigger) {
+                var action = {
+                    "id": generateUniqueActionId(),
+                    "type": type,
+                    "target": target,
+                    "trigger": trigger
+                };
+                switch (type.toLowerCase()) {
+                    case "appear":
+                        action.abs = arguments[4] || 0.0;
+                        action.ord = arguments[5] || 0.0;
+                        action.duration = arguments[6] || 0;
+                        break;
+                    case "disappear":
+                        action.duration = arguments[4] || 0;
+                        break;
+                    case "move":
+                        action.finalAbs = arguments[4] || 0.0;
+                        action.finalOrd = arguments[5] || 0.0;
+                        action.speed = arguments[6] || 0;
+                        break;
+                }
+                json.data[idSlide].action.push(action);
+            };
+
+            this.editMouse = function (idSlide, idAction, mouseX, mouseY) {
+                json.data[idSlide].action[idAction].finalAbs = mouseX;
+                json.data[idSlide].action[idAction].finalOrd = mouseY;
+            };
+
+            /*
+             * Sets a title for the presentation.
+             * @param String title Title to set.
+             */
+            this.setTitle = function (title) {
+                json.metaData.title = title;
+            };
+
+            /*
+             * Sets an annotation for the presentation.
+             * @param String annotation Annotation to set.
+             */
+            this.setAnnotation = function (annotation) {
+                json.metaData.annotation = annotation;
+            };
+
+            /*
+             * Inverts the two slides (their positions on the table).
+             * @param int idSlide1 Index of the first slide.
+             * @param int idSlide2 Index of the second slide.
+             */
+            this.invertSlides = function (idSlide1, idSlide2) {
+                var tmp = json.data[idSlide1];
+                json.data[idSlide1] = json.data[idSlide2];
+                json.data[idSlide2] = tmp;
+            };
+
+            /*
+             * Inverts the two actions (their positions on the table).
+             * @param int idSlide
+             * @param int idAction1
+             * @param int idAction2
+             */
+            this.invertActions = function (idSlide, idAction1, idAction2) {
+                var tmp = json.data[idSlide].action[idAction1];
+                json.data[idSlide].action[idAction1] = json.data[idSlide].action[idAction2];
+                json.data[idSlide].action[idAction2] = tmp;
+            };
+
+            /*
+             * Returns the actions on the specified slide.
+             * @returns {Array}
+             */
+            this.getActionList = function (idSlide) {
+                return json.data[idSlide].action;
+            };
+
+            /*
+             * Catches all the objects of the presentation
+             * @returns {Array} List of objects of the presentation
+             */
+            this.getObjectList = function () {
+                var objectList = new Array();
+                var indexSlide = 0;
+                while (json.data[indexSlide]) {
+                    var indexObject = 0;
+                    while (json.data[indexSlide].object[indexObject]) {
+                        objectList.push(json.data[indexSlide].object[indexObject]);
+                        indexObject++;
+                    }
+                    indexSlide++;
+                }
+                return objectList;
+            };
+
+            /*
+             * Returns an array containing all the background images.
+             * @returns {Array} List of background images.
+             */
+            this.getImageList = function () {
+                var list = new Array();
+                var indexSlide = 0;
+                while (json.data[indexSlide]) {
+                    list.push(json.data[indexSlide].object[0].img);
+                    indexSlide++;
+                }
+                ;
+                return list;
+            };
+
+            /*
+             * Returns an object containing the id of the first background.
+             * @returns {string} Id of the first background.
+             */
+            this.getInitialBackground = function () {
+                return json.data[0].object[0].id;
+            };
+
+            /*
+             * Returns an object containing the initial mouse position.
+             * @returns {object} Initial position of mouse.
+             */
+            this.getInitialMousePos = function () {
+                var pos = {};
+                pos.x = json.data[0].action[0].finalAbs;
+                pos.y = json.data[0].action[0].finalOrd;
+                return pos;
+            };
+
+            /*
+             * Removes the slide at the specified index.
+             * @param {int} idSlide
+             */
+            this.removeSlide = function (idSlide) {
+                json.data.splice(idSlide, 1);
+            };
+
+            /*
+             * Removes the action at the specified slide.
+             * @param {int} idSlide
+             * @param {int} idAction
+             */
+            this.removeAction = function (idSlide, idAction) {
+                json.data[idSlide].action.splice(idAction, 1);
+            };
+
+            /*
+             * Removes the specified object from the slide.
+             * Also removes all the actions attached to this object.
+             * @param {int} idSlide
+             * @param {int} idObject
+             */
+            this.removeObject = function (idSlide, idObject) {
+                var removed = json.data[idSlide].object.splice(idObject, 1);
+                for (var i = 0; i < json.data.length; i++) {
+                    var j = 0;
+                    while (json.data[i].action[j]) {
+                        if (json.data[i].action[j].target === removed.id) {
+                            json.data[i].action.splice(j, 1);
+                        } else {
+                            j++;
+                        }
+                    }
+                }
+            };
+
+            /*
+             * @returns {String}
+             */
+            this.getJson = function () {
+                return JSON.stringify(json, null, '    ');
+            };
+
+            /*
+             * @returns {String} returns the index for the next slide.
+             */
+            this.getNbSlide = function () {
+                return json.data.length;
+            };
+
+            /*
+             * @return {object} returns the object identified by idSlide
+             */
+            this.getSlide = function (idSlide) {
+                return json.data[idSlide];
+            };
+
+            /*
+             * Sets the size of images for the generated presentation.
+             * The parameters can be null : it means there are no
+             * requirement for this dimension.
+             * @param {int} width New width for the generated images.
+             * @param {int} height New height for the generated images.
+             */
+            this.setImageSizeRequirements = function (width, height) {
+                json.metaData.imageWidth = width;
+                json.metaData.imageHeight = height;
+            };
+
+            /*
+             * Gets the width of images for the generated presentation.
+             * @return {int or null} Width of generated images.
+             */
+            this.getImageWidth = function () {
+                return json.metaData.imageWidth;
+            };
+
+            /*
+             * Gets the height of images for the generated presentation.
+             * @return {int or null} Height of generated images.
+             */
+            this.getImageHeight = function () {
+                return json.metaData.imageHeight;
+            };
+
+            /*
+             * Gets a background image (no one in particular, the first met).
+             * @return {string} The name of a background image.
+             */
+            this.getABackgroundImage = function () {
+                for (var i = 0; i < json.data.length; i++) {
+                    for (var j = 0; j < json.data[i].object.length; j++) {
+                        if (json.data[i].object[j].type === "background") {
+                            return json.data[i].object[j].img;
+                        }
+                    }
+                }
+                return null;
+            };
+        };
+
+        /* public API */
+
+        self.version = "0.0.1";
+
+        self.createScreencastGenerator = function createScreencastGenerator() {
+            return new DahuScreencastGenerator();
+        };
+
+        self.createScreencastModel = function createScreencastModel() {
+            return new DahuScreencastModel();
+        };
+
+        self.createScreencastExecutableModel = function createScreencastExecutableModel() {
+            return new DahuScreencastExecutableModel();
+        };
+
+        self.createScreencastGeneratedModel = function createScreencastGeneratedModel() {
+            return new DahuScreencastGeneratedModel();
+        };
+
+        return self;
+    })();
+
+    window.dahuapp = dahuapp;
+
+})(window, jQuery);
\ No newline at end of file
diff --git a/TP/TP01/P05/dahuapp.viewer.css b/TP/TP01/P05/dahuapp.viewer.css
new file mode 100644
index 0000000000000000000000000000000000000000..ea934576dad6ca9263a6163de58f5fe4534d1f0b
--- /dev/null
+++ b/TP/TP01/P05/dahuapp.viewer.css
@@ -0,0 +1,25 @@
+.object-list {
+    position: absolute;
+    margin: 0px;
+    padding: 0px;
+}
+
+.mouse-cursor {
+    position: absolute;
+}
+
+.control {
+    position: relative;
+}
+
+.background {
+    position: absolute;
+}
+
+.tooltip {
+    position: absolute;
+    width: 100px;
+    padding: 4px;
+    margin: 2px;
+    border: 2px black solid;
+}
\ No newline at end of file
diff --git a/TP/TP01/P05/dahuapp.viewer.js b/TP/TP01/P05/dahuapp.viewer.js
new file mode 100644
index 0000000000000000000000000000000000000000..12ad03b2cdc503a8fd6ff3f5fdc2a74edbb36edb
--- /dev/null
+++ b/TP/TP01/P05/dahuapp.viewer.js
@@ -0,0 +1,456 @@
+"use strict";
+
+/**
+ * Dahuapp viewer module.
+ * 
+ * @param   dahuapp     dahuapp object to augment with module.
+ * @param   $           jQuery
+ * @returns dahuapp extended with viewer module.
+ */
+
+(function(dahuapp, $) {
+    var viewer = (function() {
+
+        var self = {};
+
+        var DahuViewerModel = function(select, getParams) {
+
+            /* Private API */
+
+            var json = null;
+            var selector = select;
+            var parseAutoOption = function(name, defaultValue) {
+                var res = getParams[name];
+                if (res == null) {
+                    if (name == 'auto') {
+                        return false;
+                    } else {
+                        return parseAutoOption('auto', defaultValue);
+                    }
+                }
+                if (res.toLowerCase() === 'false') {
+                    return false;
+                }
+                res = parseInt(res);
+                if (isNaN(res)) {
+                    // e.g. ?autoplay or ?autoplay=true
+                    return defaultValue;
+                }
+                return res;
+            }
+
+            /* Whether to wait for "next" event between actions */
+            var autoPlay = parseAutoOption('autoplay', 5000);
+            /* Whether to wait for "next" event on page load */
+            var autoStart = parseAutoOption('autostart', 5000);
+            /* Whether to loop back to start at the end of presentation */
+            var autoLoop = parseAutoOption('autoloop', 5000);
+
+            var events = (function() {
+                var self = {};
+                /*
+                 * Creates a generic event.
+                 */
+                var createEvent = function() {
+                    var callbacks = $.Callbacks();
+                    return {
+                        publish: callbacks.fire,
+                        subscribe: callbacks.add,
+                        unsubscribe: callbacks.remove,
+                        unsubscribeAll: callbacks.empty
+                    };
+                };
+                /*
+                 * Called when the button next is pressed.
+                 */
+                self.onNext = createEvent();
+                /*
+                 * Called when the button previous is pressed.
+                 */
+                self.onPrevious = createEvent();
+                /*
+                 * Called when an action is over.
+                 */
+                self.onActionOver = createEvent();
+                /*
+                 * Called when an action starts.
+                 */
+                self.onActionStart = createEvent();
+                /*
+                 * Called when at least one action was running and has finished.
+                 */
+                self.onAllActionFinish = createEvent();
+                
+                return self;
+            })();
+
+            /*
+             * Variables used like index for methodes subscribed.
+             */
+            var currentAction = 0;    /* Action currently running */
+            var nextAction = 0;       /* Action to execute on 'Next' click */
+            var nbActionsRunning = 0;
+            var previousAnchor = -1;  /* Last entered anchor, only used in
+                                       * case the 'onhashchange' event is
+                                       * not supported by the web browser */
+            
+            /*
+             * Functions to reinitialise running actions and subscribed
+             * callbacks (reinitialise is called once without stopping
+             * all the actions, so the two functions are separated).
+             */
+            var stopAllActions = function() {
+                $(selector + " .object-list").children().stop(true, true);
+                reinitialiseCallbackLists();
+                nbActionsRunning = 0;
+            };
+            var reinitialiseCallbackLists = function() {
+                events.onActionStart.unsubscribeAll();
+                events.onActionStart.subscribe(onActionStartEventHandler);
+                events.onAllActionFinish.unsubscribeAll();
+            };
+            
+            /*
+             * Timer used to program onNext event in autoplay mode.
+             */
+            var playTimer = 0;
+
+            /*
+             * Function used when an "onNextEvent" event is caught.
+             */
+            var onNextEventHandler = function() {
+                /*
+                 * If the user pressed "next" before an autoplay event
+                 * is triggered, it replaces the autoplay event, hence
+                 * cancels it:
+                 */
+                window.clearTimeout(playTimer);
+
+                enterAnimationMode();
+                stopAllActions();
+                var tmpAction = nextAction;
+                var onlyWithPrevious = true;
+                nextAction++;
+                currentAction = nextAction;
+                while (json.action[currentAction] && onlyWithPrevious) {
+                    switch (json.action[currentAction].trigger) {
+                        case 'onClick':
+                            nextAction = currentAction;
+                            onlyWithPrevious = false;
+                            break;
+                        case 'withPrevious':
+                            var tmp = currentAction;
+                            /*
+                             * We can't directly pass 'execute' as callback because we
+                             * allow the 'execute' function to reference a property of the
+                             * object (by using 'this.property') so we have to call the
+                             * function in the containing object to make that available
+                             */
+                            events.onActionStart.subscribe(function(events, selector) {
+                                json.action[tmp].execute(events, selector);
+                            });
+                            break;
+                        case 'afterPrevious':
+                            var tmp = currentAction;
+                            events.onAllActionFinish.subscribe(function(events, selector) {
+                                json.action[tmp].execute(events, selector);
+                            });
+                            while (json.action[nextAction] && json.action[nextAction].trigger !== 'onClick') {
+                                nextAction++;
+                            }
+                            onlyWithPrevious = false;
+                            break;
+                    }
+                    currentAction++;
+                }
+                if (json.action[tmpAction]) {
+                    launch(json.action[tmpAction]);
+                }
+                if (nextAction > json.action.length) {
+                    nextAction = json.action.length;
+                }
+            };
+            
+            /*
+             * Function used when an "onPreviousEvent" event is caught.
+             */
+            var onPreviousEventHandler = function() {
+                stopAllActions();
+                currentAction = nextAction - 1;
+                while (json.action[currentAction] && json.action[currentAction].trigger !== 'onClick') {
+                    launchReverse(json.action[currentAction]);
+                    currentAction--;
+                }
+                /* currentAction = -1 means that it's the beginning */
+                if (currentAction > -1) {
+                    launchReverse(json.action[currentAction]);
+                }
+                if (currentAction < 0) {
+                    currentAction = 0;
+                }
+                nextAction = currentAction;
+            };
+
+            /*
+             * Function used when an "onActionOverEvent" event is caught.
+             */
+            var onActionOverEventHandler = function() {
+                nbActionsRunning--;
+                if (nbActionsRunning === 0) {
+                    events.onAllActionFinish.publish(events, selector);
+                    reinitialiseCallbackLists();
+                    while (json.action[currentAction]) {
+                        switch (json.action[currentAction].trigger) {
+                            case 'onClick':
+                                leaveAnimationMode();
+                                if (autoPlay && nbActionsRunning === 0) {
+                                    playTimer = setTimeout(function () {
+                                        events.onNext.publish();
+                                    }, autoPlay);
+                                }
+                                return;
+                            case 'withPrevious':
+                                var tmp = currentAction;
+                                events.onActionStart.subscribe(function(events, selector) {
+                                    json.action[tmp].execute(events, selector);
+                                });
+                                break;
+                            case 'afterPrevious':
+                                var tmp = currentAction;
+                                events.onAllActionFinish.subscribe(function(events, selector) {
+                                    json.action[tmp].execute(events, selector);
+                                });
+                                currentAction++;
+                                return;
+                        }
+                        currentAction++;
+
+                    }
+                    if (autoLoop && nbActionsRunning === 0) {
+                        setTimeout(function () {
+                            resetPresentation();
+                            startPresentationMaybe();
+                        }, autoLoop);
+                    }
+                }
+                leaveAnimationMode();
+            };
+
+            /*
+             * Function used when an "onActionStartEvent" event is caught.
+             */
+            var onActionStartEventHandler = function() {
+                nbActionsRunning++;
+            };
+
+            /*
+             * Enter and leave "animation" mode. The viewer is in
+             * animation mode when something is going on without human
+             * interaction (i.e. executing actions before the next
+             * "onClick").
+             */
+            var enterAnimationMode = function () {
+                $(selector + ' .mouse-cursor-pause').hide();
+                $(selector + ' .mouse-cursor-normal').show();
+            };
+
+            var leaveAnimationMode = function () {
+                $(selector + ' .mouse-cursor-pause').show();
+                $(selector + ' .mouse-cursor-normal').hide();
+            };
+
+            /*
+             * Function used to perform actions.
+             */
+            var launch = function(action) {
+                action.execute(events, selector);
+            };
+            var launchReverse = function(action) {
+                action.executeReverse(selector);
+            };
+
+            /*
+             * The two following methods each returns an array containing the
+             * actions to execute to reach the given anchor (respectively
+             * forward or backwards).
+             *
+             * If the given anchor matches none of the actions, then an empty
+             * array is returned.
+             */
+            var getActionsToJumpForward = function(anchor) {
+                var actions = new Array();
+                for (var i = nextAction; i < json.action.length; i++) {
+                    // '==' and not '===' because we compare indifferently a
+                    // number or a string or anything else
+                    if (json.action[i].id == anchor) {
+                        return actions;
+                    } else {
+                        actions.push(json.action[i]);
+                    }
+                }
+                // Here, the anchor has not been found during the action scan
+                return null;
+            };
+            var getActionsToJumpBackwards = function(anchor) {
+                var actions = new Array();
+                for (var i = nextAction - 1; i >= 0; i--) {
+                    // '==' and not '===' because we compare indifferently a
+                    // number or a string or anything else
+                    actions.push(json.action[i]);
+                    if (json.action[i].id == anchor) {
+                        return actions;
+                    }
+                }
+                // Here, the anchor has not been found during the action scan
+                return null;
+            };
+            
+            /*
+             * Updates the position of the presentation depending on the
+             * given anchor (next action wanted).
+             */
+            var jumpToAnchor = function(anchor) {
+                if (anchor !== '') {
+                    stopAllActions();
+                    if (anchor > nextAction) {
+                        // forward
+                        var actions = getActionsToJumpForward(anchor);
+                        for (var i = 0; i < actions.length; i++) {
+                            actions[i].executeImmediately(selector);
+                        }
+                        nextAction += actions.length;
+                    } else {
+                        // backwards
+                        var actions = getActionsToJumpBackwards(anchor);
+                        for (var i = 0; i < actions.length; i++) {
+                            actions[i].executeReverse(selector);
+                        }
+                        nextAction -= actions.length;
+                    }
+                }
+            };
+
+            var resetPresentation = function() {
+                window.clearTimeout(playTimer);
+
+                currentAction = 0;
+                nextAction = 0;
+                nbActionsRunning = 0;
+
+                /*
+                 * At the beginning, the visible image is the first one of the presentation
+                 */
+                $(selector + " .object-list").children().hide();
+
+                $(selector + " ." + json.metaData.initialBackgroundId).show();
+
+                $(selector + " .mouse-cursor").css({
+                    'top': (json.metaData.initialMouseY * json.metaData.imageHeight) + "px",
+                    'left': (json.metaData.initialMouseX * json.metaData.imageWidth) + "px"
+                });
+
+                $(selector + " .mouse-cursor").show();
+            }
+
+            var startPresentationMaybe = function() {
+                if (autoStart) {
+                    playTimer = setTimeout(function () {
+                        events.onNext.publish();
+                    }, autoStart);
+                }
+            }
+
+            /* Public API */
+
+            this.loadUrl = function(url) {
+                var dataJson;
+                $.ajax({
+                    'async': false,
+                    'global': false,
+                    'url': url,
+                    'dataType': "json",
+                    'success': function(data) {
+                        dataJson = data;
+                    }
+                });
+                
+                load(dataJson);
+            };
+
+            this.load = function(dataJson) {
+                /* Transforms data JSON to executable functions for actions */
+                var execModel = dahuapp.createScreencastExecutableModel();
+                execModel.loadJson(dataJson);
+                json = execModel.getJson();
+            };
+
+            this.start = function() {
+                
+                /*
+                 * Subscription of methods to their events.
+                 */
+                events.onNext.subscribe(onNextEventHandler);
+                events.onPrevious.subscribe(onPreviousEventHandler);
+                events.onActionOver.subscribe(onActionOverEventHandler);
+                events.onActionStart.subscribe(onActionStartEventHandler);
+
+                resetPresentation();
+
+                /*
+                 * If an anchor has been specified, we place the presentation
+                 * in the right position.
+                 */
+                jumpToAnchor(window.location.hash.substring(1));
+                
+                /*
+                 * If the anchor changes during the presentation, then the
+                 * presentation is updated
+                 */
+                if ("onhashchange" in window) {
+                    // event supported
+                    window.onhashchange = function () {
+                        jumpToAnchor(window.location.hash.substring(1));
+                    };
+                } else {
+                    // event not supported : periodical check
+                    window.setInterval(function () {
+                        if (window.location.hash.substring(1) !== previousAnchor) {
+                            previousAnchor = window.location.hash.substring(1);
+                            jumpToAnchor(window.location.hash.substring(1));
+                        }
+                    }, 100);
+                }
+
+                /*
+                 * A click on the "next" button publishes a nextSlide event
+                 */
+                $(selector + " .next").click(function() {
+                    events.onNext.publish();
+                });
+                
+                /*
+                 * A click on the "previous" button publishes a previousSlide event
+                 */
+                $(selector + " .previous").click(function() {
+                    events.onPrevious.publish();
+                });
+
+                /*
+                 * Everything is ready, show the presentation
+                 * (hidden with style="display: none" from HTML).
+                 */
+                $("#loading").hide();
+                $(selector + " .object-list").show();
+                startPresentationMaybe();
+            };
+        };
+
+        self.createDahuViewer = function(selector, getParams) {
+            return new DahuViewerModel(selector, getParams);
+        };
+
+        return self;
+    })();
+
+    dahuapp.viewer = viewer;
+})(dahuapp || {}, jQuery);
diff --git a/TP/TP01/P05/img/00ac7398-6f83-2ebc-85e3-1feb000bc364.png b/TP/TP01/P05/img/00ac7398-6f83-2ebc-85e3-1feb000bc364.png
new file mode 100644
index 0000000000000000000000000000000000000000..39204b52e286d85b8f26c712863e695ba2a9478d
Binary files /dev/null and b/TP/TP01/P05/img/00ac7398-6f83-2ebc-85e3-1feb000bc364.png differ
diff --git a/TP/TP01/P05/img/014336b3-9a2a-1817-25b9-50ff3ccbccd4.png b/TP/TP01/P05/img/014336b3-9a2a-1817-25b9-50ff3ccbccd4.png
new file mode 100644
index 0000000000000000000000000000000000000000..54242b296a5638f08834280a7cf08b9808e62e76
Binary files /dev/null and b/TP/TP01/P05/img/014336b3-9a2a-1817-25b9-50ff3ccbccd4.png differ
diff --git a/TP/TP01/P05/img/135e6dd3-3dee-7be0-6d6c-3407ce073d0e.png b/TP/TP01/P05/img/135e6dd3-3dee-7be0-6d6c-3407ce073d0e.png
new file mode 100644
index 0000000000000000000000000000000000000000..4442249df56774a8650d0ea83986bbe12ea5831b
Binary files /dev/null and b/TP/TP01/P05/img/135e6dd3-3dee-7be0-6d6c-3407ce073d0e.png differ
diff --git a/TP/TP01/P05/img/40772f5f-7b7e-ea5b-18a7-dbeecc8d5774.png b/TP/TP01/P05/img/40772f5f-7b7e-ea5b-18a7-dbeecc8d5774.png
new file mode 100644
index 0000000000000000000000000000000000000000..e7ce24ddce5012e77b40ef83201a327c680871d5
Binary files /dev/null and b/TP/TP01/P05/img/40772f5f-7b7e-ea5b-18a7-dbeecc8d5774.png differ
diff --git a/TP/TP01/P05/img/4233bd5f-4100-b71d-e0e8-99a696a682f4.png b/TP/TP01/P05/img/4233bd5f-4100-b71d-e0e8-99a696a682f4.png
new file mode 100644
index 0000000000000000000000000000000000000000..70bfa43103e7e4e24dee922458b9253e68dda50b
Binary files /dev/null and b/TP/TP01/P05/img/4233bd5f-4100-b71d-e0e8-99a696a682f4.png differ
diff --git a/TP/TP01/P05/img/537c2394-0d22-e637-7dc9-4dcefe8ffb78.png b/TP/TP01/P05/img/537c2394-0d22-e637-7dc9-4dcefe8ffb78.png
new file mode 100644
index 0000000000000000000000000000000000000000..2d4fc5176f5e5be564cbb6aef60082e0ba359b03
Binary files /dev/null and b/TP/TP01/P05/img/537c2394-0d22-e637-7dc9-4dcefe8ffb78.png differ
diff --git a/TP/TP01/P05/img/58f2a040-93d0-140f-ad3b-f16229898b92.png b/TP/TP01/P05/img/58f2a040-93d0-140f-ad3b-f16229898b92.png
new file mode 100644
index 0000000000000000000000000000000000000000..33def4d21d9a127bd0c72b5c6a5b07ebaf2c8ded
Binary files /dev/null and b/TP/TP01/P05/img/58f2a040-93d0-140f-ad3b-f16229898b92.png differ
diff --git a/TP/TP01/P05/img/7c27ec34-4ec2-09ac-de29-aa514a30772a.png b/TP/TP01/P05/img/7c27ec34-4ec2-09ac-de29-aa514a30772a.png
new file mode 100644
index 0000000000000000000000000000000000000000..10de7e9b750f0b6400d36b4e89cd9cc5021be6e3
Binary files /dev/null and b/TP/TP01/P05/img/7c27ec34-4ec2-09ac-de29-aa514a30772a.png differ
diff --git a/TP/TP01/P05/img/86ae6435-0fa1-a536-2439-e635f1606fa1.png b/TP/TP01/P05/img/86ae6435-0fa1-a536-2439-e635f1606fa1.png
new file mode 100644
index 0000000000000000000000000000000000000000..3b658a7b27ef68d70f46c8a97447a30e31f928b0
Binary files /dev/null and b/TP/TP01/P05/img/86ae6435-0fa1-a536-2439-e635f1606fa1.png differ
diff --git a/TP/TP01/P05/img/879ecee9-00a7-fdcd-df0d-5064a975f45e.png b/TP/TP01/P05/img/879ecee9-00a7-fdcd-df0d-5064a975f45e.png
new file mode 100644
index 0000000000000000000000000000000000000000..76d0b207fd8d7f705ec068f2c23205b55af4e724
Binary files /dev/null and b/TP/TP01/P05/img/879ecee9-00a7-fdcd-df0d-5064a975f45e.png differ
diff --git a/TP/TP01/P05/img/8f7f1ecc-209c-441c-0085-134fe4149974.png b/TP/TP01/P05/img/8f7f1ecc-209c-441c-0085-134fe4149974.png
new file mode 100644
index 0000000000000000000000000000000000000000..5d52747d6eac6de2c3f8793a06a16127c8bbb8e4
Binary files /dev/null and b/TP/TP01/P05/img/8f7f1ecc-209c-441c-0085-134fe4149974.png differ
diff --git a/TP/TP01/P05/img/937accb2-2984-b27a-4773-8006ee4f5cbf.png b/TP/TP01/P05/img/937accb2-2984-b27a-4773-8006ee4f5cbf.png
new file mode 100644
index 0000000000000000000000000000000000000000..7cb630bdd18312c10214ce4b1879cfd307d8b120
Binary files /dev/null and b/TP/TP01/P05/img/937accb2-2984-b27a-4773-8006ee4f5cbf.png differ
diff --git a/TP/TP01/P05/img/c70af604-3703-1c9e-a874-d3f25434a0af.png b/TP/TP01/P05/img/c70af604-3703-1c9e-a874-d3f25434a0af.png
new file mode 100644
index 0000000000000000000000000000000000000000..bbe5769c636705dfc2b5e8de076b40b2e96e4887
Binary files /dev/null and b/TP/TP01/P05/img/c70af604-3703-1c9e-a874-d3f25434a0af.png differ
diff --git a/TP/TP01/P05/img/cursor-pause.png b/TP/TP01/P05/img/cursor-pause.png
new file mode 100644
index 0000000000000000000000000000000000000000..9c93cc142f4326a188eafc3c0fe96f2975dcab0c
Binary files /dev/null and b/TP/TP01/P05/img/cursor-pause.png differ
diff --git a/TP/TP01/P05/img/cursor.png b/TP/TP01/P05/img/cursor.png
new file mode 100644
index 0000000000000000000000000000000000000000..5b4a20e63078ce18bb11a4e601a1994b261522ef
Binary files /dev/null and b/TP/TP01/P05/img/cursor.png differ
diff --git a/TP/TP01/P05/img/d0a0106a-1ab7-84b8-7288-16f3157ab0b5.png b/TP/TP01/P05/img/d0a0106a-1ab7-84b8-7288-16f3157ab0b5.png
new file mode 100644
index 0000000000000000000000000000000000000000..192fbbccf436fdb4ccc9b9140139af9971f81309
Binary files /dev/null and b/TP/TP01/P05/img/d0a0106a-1ab7-84b8-7288-16f3157ab0b5.png differ
diff --git a/TP/TP01/P05/img/d45eafbf-4984-698e-3e8a-da3623a0ec58.png b/TP/TP01/P05/img/d45eafbf-4984-698e-3e8a-da3623a0ec58.png
new file mode 100644
index 0000000000000000000000000000000000000000..aa07f938e7cd0b12f024a8f84764be869a0b12a3
Binary files /dev/null and b/TP/TP01/P05/img/d45eafbf-4984-698e-3e8a-da3623a0ec58.png differ
diff --git a/TP/TP01/P05/img/f579b961-95d6-c335-ce0a-7c3d96400b6a.png b/TP/TP01/P05/img/f579b961-95d6-c335-ce0a-7c3d96400b6a.png
new file mode 100644
index 0000000000000000000000000000000000000000..f1b0db3546abd89057810aceb1b221b7d692426f
Binary files /dev/null and b/TP/TP01/P05/img/f579b961-95d6-c335-ce0a-7c3d96400b6a.png differ
diff --git a/TP/TP01/P05/index.html b/TP/TP01/P05/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..b198ce829b296a8db0c377abcfd75b764dcf9309
--- /dev/null
+++ b/TP/TP01/P05/index.html
@@ -0,0 +1,542 @@
+<!DOCTYPE html>
+
+<html lang="en">
+
+<head>
+<title>Dahu Presentation
+</title>
+<meta charset="utf-8">
+<script src="http://code.jquery.com/jquery-1.9.1.min.js" type="text/javascript">
+</script>
+<script src="parse-search.js" type="text/javascript">
+</script>
+
+<link rel="stylesheet" href="dahuapp.viewer.css"><style>.s0-o2{background-color:   #FFFFDD;width:  240px}
+.s1-o2{background-color:   #FFFFDD;width:  240px}
+.s3-o2{background-color:   #FFFFDD;width:  240px}
+.s4-o2{background-color:   #FFFFDD;width:  240px}
+.s5-o2{background-color:   #FFFFDD;width:  240px}
+.s6-o2{background-color:   #FFFFDD;width:  240px}
+.s7-o2{background-color:   #FFFFDD;width:  240px}
+.s8-o2{background-color:   #FFFFDD;width:  240px}
+.s9-o2{background-color:   #FFFFDD;width:  240px}
+.s10-o2{background-color:   #FFFFDD;width:  240px}
+.s11-o2{background-color:   #FFFFDD;width:  240px}
+.s12-o2{background-color:   #FFFFDD;width:  240px}
+.s13-o2{background-color:   #FFFFDD;width:  240px}
+.s14-o2{background-color:   #FFFFDD;width:  240px}
+.s15-o2{background-color:   #FFFFDD;width:  240px}
+</style>
+</head>
+
+<body>
+
+<div id="my-dahu-presentation">
+
+<div id="loading">Loading presentation...
+</div>
+
+<div class="object-list" style="display: none">
+<img src="img/014336b3-9a2a-1817-25b9-50ff3ccbccd4.png" alt="img/014336b3-9a2a-1817-25b9-50ff3ccbccd4.png" class="background 014336b3-9a2a-1817-25b9-50ff3ccbccd40">
+
+<div class="tooltip s0-o2">Ouvrir les paramètres du bloc Integrator.
+</div>
+<img src="img/00ac7398-6f83-2ebc-85e3-1feb000bc364.png" alt="img/00ac7398-6f83-2ebc-85e3-1feb000bc364.png" class="background 00ac7398-6f83-2ebc-85e3-1feb000bc3640">
+
+<div class="tooltip s1-o2">Indiquer que la valeur initiale est donnée à l'extérieur du bloc.
+</div>
+<img src="img/537c2394-0d22-e637-7dc9-4dcefe8ffb78.png" alt="img/537c2394-0d22-e637-7dc9-4dcefe8ffb78.png" class="background 537c2394-0d22-e637-7dc9-4dcefe8ffb780">
+<img src="img/d45eafbf-4984-698e-3e8a-da3623a0ec58.png" alt="img/d45eafbf-4984-698e-3e8a-da3623a0ec58.png" class="background d45eafbf-4984-698e-3e8a-da3623a0ec580">
+
+<div class="tooltip s3-o2">Un port x0 supplémentaire apparait sur le bloc Integrator.
+</div>
+<img src="img/d0a0106a-1ab7-84b8-7288-16f3157ab0b5.png" alt="img/d0a0106a-1ab7-84b8-7288-16f3157ab0b5.png" class="background d0a0106a-1ab7-84b8-7288-16f3157ab0b50">
+
+<div class="tooltip s4-o2">Sélectionner le bloc Constant dans la boite à outils Simulink.
+</div>
+<img src="img/c70af604-3703-1c9e-a874-d3f25434a0af.png" alt="img/c70af604-3703-1c9e-a874-d3f25434a0af.png" class="background c70af604-3703-1c9e-a874-d3f25434a0af0">
+
+<div class="tooltip s5-o2">Déposer un bloc Constant dans votre modèle.
+</div>
+<img src="img/86ae6435-0fa1-a536-2439-e635f1606fa1.png" alt="img/86ae6435-0fa1-a536-2439-e635f1606fa1.png" class="background 86ae6435-0fa1-a536-2439-e635f1606fa10">
+
+<div class="tooltip s6-o2">Connecter la sortie du bloc Constant à l'entrée x0 du bloc Integrator.
+</div>
+<img src="img/4233bd5f-4100-b71d-e0e8-99a696a682f4.png" alt="img/4233bd5f-4100-b71d-e0e8-99a696a682f4.png" class="background 4233bd5f-4100-b71d-e0e8-99a696a682f40">
+
+<div class="tooltip s7-o2">Ouvrir les paramètres du bloc Constant. Choisir la valeur -1.
+</div>
+<img src="img/f579b961-95d6-c335-ce0a-7c3d96400b6a.png" alt="img/f579b961-95d6-c335-ce0a-7c3d96400b6a.png" class="background f579b961-95d6-c335-ce0a-7c3d96400b6a0">
+
+<div class="tooltip s8-o2">Démarrer la simulation.
+</div>
+<img src="img/40772f5f-7b7e-ea5b-18a7-dbeecc8d5774.png" alt="img/40772f5f-7b7e-ea5b-18a7-dbeecc8d5774.png" class="background 40772f5f-7b7e-ea5b-18a7-dbeecc8d57740">
+
+<div class="tooltip s9-o2">Mettre à l'échelle l'écran de l'oscilloscope. Observer que le résultat est identique à l'étape précédente.
+</div>
+<img src="img/58f2a040-93d0-140f-ad3b-f16229898b92.png" alt="img/58f2a040-93d0-140f-ad3b-f16229898b92.png" class="background 58f2a040-93d0-140f-ad3b-f16229898b920">
+
+<div class="tooltip s10-o2">Ouvrir la fenêtre MatLab.
+</div>
+<img src="img/7c27ec34-4ec2-09ac-de29-aa514a30772a.png" alt="img/7c27ec34-4ec2-09ac-de29-aa514a30772a.png" class="background 7c27ec34-4ec2-09ac-de29-aa514a30772a0">
+
+<div class="tooltip s11-o2">Créer une variable V0 de valeur -1.
+</div>
+<img src="img/879ecee9-00a7-fdcd-df0d-5064a975f45e.png" alt="img/879ecee9-00a7-fdcd-df0d-5064a975f45e.png" class="background 879ecee9-00a7-fdcd-df0d-5064a975f45e0">
+
+<div class="tooltip s12-o2">Ouvrir la fenêtre Simulink. Ouvrir les paramètres du bloc Constant.
+</div>
+<img src="img/135e6dd3-3dee-7be0-6d6c-3407ce073d0e.png" alt="img/135e6dd3-3dee-7be0-6d6c-3407ce073d0e.png" class="background 135e6dd3-3dee-7be0-6d6c-3407ce073d0e0">
+
+<div class="tooltip s13-o2">Choisir la variable V0 comme valeur du bloc Constant.
+</div>
+<img src="img/8f7f1ecc-209c-441c-0085-134fe4149974.png" alt="img/8f7f1ecc-209c-441c-0085-134fe4149974.png" class="background 8f7f1ecc-209c-441c-0085-134fe41499740">
+
+<div class="tooltip s14-o2">Démarrer la simulation.
+</div>
+<img src="img/937accb2-2984-b27a-4773-8006ee4f5cbf.png" alt="img/937accb2-2984-b27a-4773-8006ee4f5cbf.png" class="background 937accb2-2984-b27a-4773-8006ee4f5cbf0">
+
+<div class="tooltip s15-o2">Mettre à l'échelle l'écran de l'oscilloscope. Observer que le résultat est identique à l'étape précédente.
+</div>
+
+<div class="mouse-cursor">
+<img src="img/cursor.png" alt="img/cursor.png" class="mouse-cursor-normal">
+<img src="img/cursor-pause.png" alt="img/cursor-pause.png" style="display: none" class="mouse-cursor-pause">
+</div>
+</div>
+
+<div class="control" style="top: 616px;">
+<button class="previous">Previous
+</button>
+<button class="next">Next
+</button>
+</div>
+</div>
+<script src="dahuapp.js" type="text/javascript">
+</script>
+<script src="dahuapp.viewer.js" type="text/javascript">
+</script>
+<script type="text/javascript">(function($) {
+    var myPresentation = dahuapp.viewer.createDahuViewer("#my-dahu-presentation", window.getParams);
+    myPresentation.load({
+    "metaData": {
+        "imageWidth": 800,
+        "imageHeight": 600,
+        "initialBackgroundId": "014336b3-9a2a-1817-25b9-50ff3ccbccd40",
+        "initialMouseX": 0.25,
+        "initialMouseY": 0.44555555555555554
+    },
+    "action": [
+        {
+            "id": "33",
+            "type": "appear",
+            "target": "s0-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.2833333333333333,
+            "ord": 0.16375,
+            "duration": 400
+        },
+        {
+            "id": "3",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.3840277777777778,
+            "finalOrd": 0.3055555555555556,
+            "speed": 0.8
+        },
+        {
+            "id": "4",
+            "type": "appear",
+            "target": "00ac7398-6f83-2ebc-85e3-1feb000bc3640",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "34",
+            "type": "appear",
+            "target": "s1-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.36833333333333335,
+            "ord": 0.135,
+            "duration": 400
+        },
+        {
+            "id": "5",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.3798611111111111,
+            "finalOrd": 0.6866666666666666,
+            "speed": 0.8
+        },
+        {
+            "id": "6",
+            "type": "appear",
+            "target": "537c2394-0d22-e637-7dc9-4dcefe8ffb780",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "7",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.6583333333333333,
+            "finalOrd": 0.42,
+            "speed": 0.8
+        },
+        {
+            "id": "8",
+            "type": "appear",
+            "target": "d45eafbf-4984-698e-3e8a-da3623a0ec580",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "35",
+            "type": "appear",
+            "target": "s3-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.28,
+            "ord": 0.1575,
+            "duration": 400
+        },
+        {
+            "id": "9",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.8986111111111111,
+            "finalOrd": 0.24222222222222223,
+            "speed": 0.8
+        },
+        {
+            "id": "10",
+            "type": "appear",
+            "target": "d0a0106a-1ab7-84b8-7288-16f3157ab0b50",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "36",
+            "type": "appear",
+            "target": "s4-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.4166666666666667,
+            "ord": 0.12125,
+            "duration": 400
+        },
+        {
+            "id": "11",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.1701388888888889,
+            "finalOrd": 0.5255555555555556,
+            "speed": 0.8
+        },
+        {
+            "id": "12",
+            "type": "appear",
+            "target": "c70af604-3703-1c9e-a874-d3f25434a0af0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "37",
+            "type": "appear",
+            "target": "s5-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.295,
+            "ord": 0.16625,
+            "duration": 400
+        },
+        {
+            "id": "13",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.1840277777777778,
+            "finalOrd": 0.5266666666666666,
+            "speed": 0.8
+        },
+        {
+            "id": "14",
+            "type": "appear",
+            "target": "86ae6435-0fa1-a536-2439-e635f1606fa10",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "38",
+            "type": "appear",
+            "target": "s6-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.285,
+            "ord": 0.165,
+            "duration": 400
+        },
+        {
+            "id": "15",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.22291666666666668,
+            "finalOrd": 0.5055555555555555,
+            "speed": 0.8
+        },
+        {
+            "id": "16",
+            "type": "appear",
+            "target": "4233bd5f-4100-b71d-e0e8-99a696a682f40",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "39",
+            "type": "appear",
+            "target": "s7-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.32166666666666666,
+            "ord": 0.07125,
+            "duration": 400
+        },
+        {
+            "id": "17",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.2902777777777778,
+            "finalOrd": 0.10222222222222223,
+            "speed": 0.8
+        },
+        {
+            "id": "18",
+            "type": "appear",
+            "target": "f579b961-95d6-c335-ce0a-7c3d96400b6a0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "40",
+            "type": "appear",
+            "target": "s8-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.19333333333333333,
+            "ord": 0.08125,
+            "duration": 400
+        },
+        {
+            "id": "19",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.1486111111111111,
+            "finalOrd": 0.6622222222222223,
+            "speed": 0.8
+        },
+        {
+            "id": "20",
+            "type": "appear",
+            "target": "40772f5f-7b7e-ea5b-18a7-dbeecc8d57740",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "41",
+            "type": "appear",
+            "target": "s9-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.22166666666666668,
+            "ord": 0.2875,
+            "duration": 400
+        },
+        {
+            "id": "21",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.39166666666666666,
+            "finalOrd": 0.43555555555555553,
+            "speed": 0.8
+        },
+        {
+            "id": "22",
+            "type": "appear",
+            "target": "58f2a040-93d0-140f-ad3b-f16229898b920",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "42",
+            "type": "appear",
+            "target": "s10-o2",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 400
+        },
+        {
+            "id": "23",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.39166666666666666,
+            "finalOrd": 0.43555555555555553,
+            "speed": 0.8
+        },
+        {
+            "id": "24",
+            "type": "appear",
+            "target": "7c27ec34-4ec2-09ac-de29-aa514a30772a0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "43",
+            "type": "appear",
+            "target": "s11-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.22166666666666668,
+            "ord": 0.14,
+            "duration": 400
+        },
+        {
+            "id": "25",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.17291666666666666,
+            "finalOrd": 0.5344444444444445,
+            "speed": 0.8
+        },
+        {
+            "id": "26",
+            "type": "appear",
+            "target": "879ecee9-00a7-fdcd-df0d-5064a975f45e0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "44",
+            "type": "appear",
+            "target": "s12-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.22,
+            "ord": 0.20875,
+            "duration": 400
+        },
+        {
+            "id": "27",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.2263888888888889,
+            "finalOrd": 0.5055555555555555,
+            "speed": 0.8
+        },
+        {
+            "id": "28",
+            "type": "appear",
+            "target": "135e6dd3-3dee-7be0-6d6c-3407ce073d0e0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "45",
+            "type": "appear",
+            "target": "s13-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.25166666666666665,
+            "ord": 0.2225,
+            "duration": 400
+        },
+        {
+            "id": "29",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.28958333333333336,
+            "finalOrd": 0.09444444444444444,
+            "speed": 0.8
+        },
+        {
+            "id": "30",
+            "type": "appear",
+            "target": "8f7f1ecc-209c-441c-0085-134fe41499740",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "46",
+            "type": "appear",
+            "target": "s14-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.20666666666666667,
+            "ord": 0.0775,
+            "duration": 400
+        },
+        {
+            "id": "31",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.14305555555555555,
+            "finalOrd": 0.6611111111111111,
+            "speed": 0.8
+        },
+        {
+            "id": "32",
+            "type": "appear",
+            "target": "937accb2-2984-b27a-4773-8006ee4f5cbf0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "47",
+            "type": "appear",
+            "target": "s15-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.21166666666666667,
+            "ord": 0.2775,
+            "duration": 400
+        }
+    ]
+});
+    myPresentation.start();
+})(jQuery);
+
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/TP/TP01/P05/parse-search.js b/TP/TP01/P05/parse-search.js
new file mode 100644
index 0000000000000000000000000000000000000000..e7d01c07f6a5d45b9fcabd5d271a8c6cfc5f00e2
--- /dev/null
+++ b/TP/TP01/P05/parse-search.js
@@ -0,0 +1,14 @@
+// Adapted from http://stackoverflow.com/questions/3234125/creating-array-from-window-location-hash
+(function () {
+    var search = window.location.search.slice(1);
+    var array = search.split("&");
+    
+    var values, form_data = {};
+    
+    for (var i = 0; i < array.length; i += 1) {
+	values = array[i].split("=");
+	form_data[values[0]] = unescape(values[1]);
+    }
+    window.getParams = form_data;
+}())
+
diff --git a/TP/TP01/P06/dahuapp.js b/TP/TP01/P06/dahuapp.js
new file mode 100644
index 0000000000000000000000000000000000000000..41c69e9169345a9a6703325d5557a4cc36913e19
--- /dev/null
+++ b/TP/TP01/P06/dahuapp.js
@@ -0,0 +1,880 @@
+"use strict";
+
+/**
+ * Dahuapp core module.
+ *
+ * @param   window      window javascript object.
+ * @param   $           jQuery
+ * @returns dahuapp core module.
+ */
+(function (window, $) {
+    var dahuapp = (function () {
+
+        var self = {};
+
+        /* private API */
+
+        var DahuScreencastGenerator = function () {
+
+            /*
+             * Format the specified string in a readable format.
+             */
+            function htmlFormat(htmlString) {
+                var i;
+                var readableHTML = htmlString;
+                var lb = '\n';
+                var htags = ["<html", "</html>", "</head>", "<title", "</title>", "<meta", "<link", "</body>"];
+                for (i = 0; i < htags.length; ++i) {
+                    var hhh = htags[i];
+                    readableHTML = readableHTML.replace(new RegExp(hhh, 'gi'), lb + hhh);
+                }
+                var btags = ["</div>", "</section>", "</span>", "<br>", "<br />", "<blockquote", "</blockquote>", "<ul", "</ul>", "<ol", "</ol>", "<li", "<\!--", "<script", "</script>"];
+                for (i = 0; i < btags.length; ++i) {
+                    var bbb = btags[i];
+                    readableHTML = readableHTML.replace(new RegExp(bbb, 'gi'), lb + bbb);
+                }
+                var ftags = ["<img", "<legend", "</legend>", "<button", "</button>"];
+                for (i = 0; i < ftags.length; ++i) {
+                    var fff = ftags[i];
+                    readableHTML = readableHTML.replace(new RegExp(fff, 'gi'), lb + fff);
+                }
+                var xtags = ["<body", "<head", "<div", "<section", "<span", "<p"];
+                for (i = 0; i < xtags.length; ++i) {
+                    var xxx = xtags[i];
+                    readableHTML = readableHTML.replace(new RegExp(xxx, 'gi'), lb + lb + xxx);
+                }
+                return readableHTML;
+            }
+
+            /*
+             * Generates the html header.
+             */
+            var generateHtmlHeader = function ($generated, cssGen) {
+                $('head', $generated)
+                    .append($(document.createElement('title'))
+                        .append("Dahu Presentation"))/* TODO: make this customizable */
+                    .append($(document.createElement('meta'))
+                        .attr({'charset': 'utf-8'}))
+                    .append($(document.createElement('script'))
+                        .attr({'src': 'http://code.jquery.com/jquery-1.9.1.min.js'})
+                        .attr({'type': 'text/javascript'}))
+                    .append($(document.createElement('script'))
+                        .attr({'src': 'parse-search.js'})
+                        .attr({'type': 'text/javascript'}))
+                    .append($(document.createElement('link'))
+                        .attr({'rel': 'stylesheet', 'href': 'dahuapp.viewer.css'}))
+                    .append($(document.createElement('style'))
+                        .append(cssGen));
+            };
+
+            /*
+             * Generates the objects.
+             */
+
+            var generateHtmlBackgroundImage = function ($generated, object) {
+                $('.object-list', $generated)
+                    .append($(document.createElement('img'))
+                        .attr({'src': object.img, 'alt': object.img, 'class': 'background ' + object.id}));
+            };
+            var generateHtmlTooltip = function ($generated, object) {
+                $('.object-list', $generated)
+                    .append($(document.createElement('div'))
+                        .attr({'class': 'tooltip ' + object.id})
+                        .append(object.text));
+            };
+
+            var generateCssTooltip = function ($generated, object) {
+                var style = [];
+
+                if( object.color != null ) {
+                    style.push('background-color:   ' + object.color);
+                }
+                if( object.width != null ) {
+                    style.push('width:  ' + object.width);
+                }
+
+                if( style.length != 0 ) {
+                    $generated.append(
+                    '.' + object.id + '{' + style.join(';') + '}\n');
+                }
+
+                return $generated;
+            };
+
+            /*
+             * Returns the code used to call the viewer to the presentation.
+             */
+            var getBasicCallCode = function (jsonModel) {
+                var code = '(function($) {\n';
+                code += '    var myPresentation = dahuapp.viewer.createDahuViewer("#my-dahu-presentation", window.getParams);\n';
+                code += '    myPresentation.load(' + jsonModel + ');\n';
+                code += '    myPresentation.start();\n';
+                code += '})(jQuery);\n';
+                return code;
+            };
+
+            /*
+             * Generates the html body.
+             */
+            var generateHtmlBody = function ($generated, jsonModel, jsonGen) {
+                $('body', $generated)
+                    /* We could use a <section> here too, but it does not work with MS IE 8.
+                     Alternatively, adding this in the header would work:
+
+                     <!--[if lt IE 9]>
+                     <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
+                     <![endif]-->
+                     */
+                    .append($(document.createElement('div'))
+                        .attr({'id': 'my-dahu-presentation'}))
+                    .append($(document.createElement('script'))
+                        .attr({'src': 'dahuapp.js'})
+                        .attr({'type': 'text/javascript'}))
+                    .append($(document.createElement('script'))
+                        .attr({'src': 'dahuapp.viewer.js'})
+                        .attr({'type': 'text/javascript'}))
+                    .append($(document.createElement('script'))
+                        .attr({'type': 'text/javascript'})
+                        .append(getBasicCallCode(jsonGen)));
+                $('#my-dahu-presentation', $generated)
+                    .append($(document.createElement('div'))
+                        .attr({'id': 'loading'}).append("Loading presentation..."))
+                    .append($(document.createElement('div'))
+                        .attr({'class': 'object-list',
+                            'style': 'display: none'}))
+                    .append($(document.createElement('div'))
+                        .attr({'class': 'control'}));
+
+                /* Adding the objects to the page */
+                $.each(jsonModel.getObjectList(), function (id, object) {
+                    switch (object.type) {
+                        case "background":
+                            generateHtmlBackgroundImage($generated, object);
+                            break;
+                        case "tooltip":
+                            generateHtmlTooltip($generated, object);
+                            break;
+                        /* no mouse image generated here */
+                    }
+                });
+
+                /* Warning here, i'm not sure if $.each is always over, here */
+
+                /* Adding the control buttons to the page */
+                $('.control', $generated)
+                    .css({'top': (jsonModel.getImageHeight() + 16) + 'px'})
+                    .append($(document.createElement('button'))
+                        .attr({'class': 'previous'})
+                        .append('Previous'))
+                    .append($(document.createElement('button'))
+                        .attr({'class': 'next'})
+                        .append('Next'));
+
+                /* Adding the mouse cursor image */
+                $('.object-list', $generated)
+                    .append($(document.createElement('div'))
+                        .attr({'class': 'mouse-cursor'})
+                        .append($(document.createElement('img'))
+                            .attr({'src': 'img/cursor.png', 'alt': 'img/cursor.png', 'class': 'mouse-cursor-normal'}))
+                        .append($(document.createElement('img'))
+                            .attr({'src': 'img/cursor-pause.png', 'alt': 'img/cursor-pause.png',
+                                'style': 'display: none', 'class': 'mouse-cursor-pause'})));
+            };
+
+            /*
+             * Generates the css String with the Json model.
+             */
+            this.generateCssString = function (jsonModel) {
+                var $generated = $('<style></style>');
+
+                /* Going through each object */
+                $.each(jsonModel.getObjectList(), function (id, object) {
+                    switch (object.type) {
+                        case "tooltip":
+                            generateCssTooltip($generated, object);
+                            break;
+                        /* no mouse image generated here */
+                    }
+                });
+
+                return $generated.text();
+            }
+
+            /*
+             * Generates the html String with the Json model.
+             */
+            this.generateHtmlString = function (jsonModel, jsonGen, cssGen) {
+                /* Initialising the compilation area */
+                var $generated = $(document.createElement('div'));
+
+                /* We create the html using the json */
+                $generated.append($(document.createElement('html'))
+                    .attr({'lang': 'en'}));
+                $('html', $generated)
+                    .append($(document.createElement('head')))
+                    .append($(document.createElement('body')));
+
+                generateHtmlHeader($generated, cssGen);
+                generateHtmlBody($generated, jsonModel, jsonGen);
+
+                var result = htmlFormat($generated.html());
+
+                return '<!DOCTYPE html>\n' + result;
+            };
+
+            /*
+             * Generates the generated JSON using the JSONmodel.
+             * @param {object} jsonModel The json model to transform.
+             * @param {java.awt.Dimension} imgDim The dimension of images.
+             */
+            this.generateJsonString = function (jsonModel, imgDim) {
+                var generated = self.createScreencastGeneratedModel();
+                generated.setImageSize(imgDim.width, imgDim.height);
+                generated.setInitialBackground(jsonModel.getInitialBackground());
+                generated.setInitialMousePos(jsonModel.getInitialMousePos());
+                for (var i = 0; i < jsonModel.getNbSlide(); i++) {
+                    var actionList = jsonModel.getActionList(i);
+                    for (var j = 0; j < actionList.length; j++) {
+                        /* 
+                         * We don't add the two first actions of the first slide
+                         * because they are present in the presentation metadata.
+                         * It corresponds to the mouse initial pos and first background.
+                         */
+                        if (i > 0 || j > 1) {
+                            generated.addAction(actionList[j], imgDim.width, imgDim.height);
+                        }
+                    }
+                }
+                return generated.getJson();
+            };
+        };
+
+        /*
+         * Model for a JSON object that will only be used by the viewer, never
+         * saved on a file, used to transform the properties of actions
+         * specified on the JSON file of a presentation to executable
+         * functions for each action.
+         */
+        var DahuScreencastExecutableModel = function () {
+
+            /* Private API */
+
+            var json = {
+                metaData: {},
+                action: new Array()
+            };
+
+            /*
+             * Creates a functions representing the specified action, and adds
+             * it to this object.
+             * @param {object} action
+             */
+            var addExecutableAction = function (action) {
+                var executableAction = {
+                    'id': action.id,
+                    'trigger': action.trigger,
+                    'target': action.target,
+                    'delayAfter': action.delayAfter,
+                    'doneFunction': function (events, selector) {
+                        setTimeout(function () {
+                            events.onActionOver.publish(events, selector);
+                        }, this.delayAfter);
+                    }
+                };
+                if (executableAction.delayAfter == null) {
+                    executableAction.delayAfter = 200;
+                }
+                switch (action.type.toLowerCase()) {
+                    case "appear":
+                        executableAction.abs = (action.abs * json.metaData.imageWidth) + 'px';
+                        executableAction.ord = (action.ord * json.metaData.imageHeight) + 'px';
+                        executableAction.duration = action.duration;
+                        executableAction.execute = function (events, selector) {
+                            events.onActionStart.publish(events, selector);
+                            var sel = selector + ' .' + this.target;
+                            $(sel).css({
+                                'left': this.abs,
+                                'top': this.ord
+                            });
+                            $(sel).show(this.duration, function () {
+                                executableAction.doneFunction(events, selector);
+                            });
+                        };
+                        executableAction.executeReverse = function (selector) {
+                            $(selector + ' .' + this.target).hide();
+                        };
+                        executableAction.executeImmediately = function (selector) {
+                            var sel = selector + ' .' + this.target;
+                            $(sel).css({
+                                'left': this.abs,
+                                'top': this.ord
+                            });
+                            $(sel).show();
+                        };
+                        break;
+                    case "disappear":
+                        executableAction.duration = action.duration;
+                        executableAction.execute = function (events, selector) {
+                            events.onActionStart.publish(events, selector);
+                            $(selector + ' .' + this.target).hide(this.duration, function () {
+                                executableAction.doneFunction(events, selector);
+                            });
+                        };
+                        executableAction.executeReverse = function (selector) {
+                            $(selector + ' .' + this.target).show();
+                        };
+                        executableAction.executeImmediately = function (selector) {
+                            $(selector + ' .' + this.target).hide();
+                        };
+                        break;
+                    case "move":
+                        executableAction.finalAbs = (action.finalAbs * json.metaData.imageWidth) + 'px';
+                        executableAction.finalOrd = (action.finalOrd * json.metaData.imageHeight) + 'px';
+                        executableAction.duration = action.duration;
+                        executableAction.speed = action.speed;
+                        executableAction.execute = function (events, selector) {
+                            events.onActionStart.publish(events, selector);
+                            var sel = selector + ' .' + this.target;
+                            this.initialAbs = $(sel).css('left');
+                            this.initialOrd = $(sel).css('top');
+                            if (this.duration == null) {
+                                var initialAbsPix = this.initialAbs.replace('px', '');
+                                var initialOrdPix = this.initialOrd.replace('px', '');
+                                var finalAbsPix = this.finalAbs.replace('px', '');
+                                var finalOrdPix = this.finalOrd.replace('px', '');
+                                var distance = Math.sqrt(Math.pow(finalAbsPix - initialAbsPix, 2) +
+                                    Math.pow(finalOrdPix - initialOrdPix, 2));
+                                if (!this.speed) {
+                                    this.speed = .8; // in pixel per milisecond: fast, but not too much.
+                                }
+                                this.duration = distance + this.speed;
+                                if (this.duration < 200) { // Slow down a bit for short distances.
+                                    this.duration = 200;
+                                }
+                            }
+                            $(sel).animate({
+                                'left': this.finalAbs,
+                                'top': this.finalOrd
+                            }, this.duration, 'linear', function () {
+                                executableAction.doneFunction(events, selector);
+                            });
+                        };
+                        executableAction.executeReverse = function (selector) {
+                            $(selector + ' .' + this.target).css({
+                                'left': this.initialAbs,
+                                'top': this.initialOrd
+                            });
+                        };
+                        executableAction.executeImmediately = function (selector) {
+                            var sel = selector + ' .' + this.target;
+                            this.initialAbs = $(sel).css('left');
+                            this.initialOrd = $(sel).css('top');
+                            $(sel).css({
+                                'left': this.finalAbs,
+                                'top': this.finalOrd
+                            });
+                        };
+                        break;
+                    case "delay":
+                        executableAction.duration = action.duration;
+                        executableAction.execute = function (events, selector) {
+                            events.onActionStart.publish(events, selector);
+                            setTimeout(function () {
+                                events.onActionOver.publish(events, selector);
+                            }, this.duration);
+                        };
+                        executableAction.executeReverse = function (selector) {
+                            // Nothing!
+                        };
+                        executableAction.executeImmediately = function (selector) {
+                            // Nothing too!
+                        };
+                        break;
+                }
+                json.action.push(executableAction);
+            };
+
+            /* Public API */
+
+            /*
+             * Loads the specified JSON read from a 'presentation.json' file,
+             * and stores the functions representing each actions in an object.
+             * @param {object} jsonToLoad
+             */
+            this.loadJson = function (jsonToLoad) {
+                json.metaData = jsonToLoad.metaData;
+                for (var i = 0; i < jsonToLoad.action.length; i++) {
+                    addExecutableAction(jsonToLoad.action[i]);
+                }
+            };
+
+            /*
+             * Returns the object containing the functions.
+             */
+            this.getJson = function () {
+                return json;
+            };
+        };
+
+        /*
+         * Used to transform the 'dahu' file to a JSON file used by the viewer,
+         * which only contains some metaData and a list of actions.
+         */
+        var DahuScreencastGeneratedModel = function () {
+
+            /* Private API */
+
+            var json = {
+                metaData: {},
+                action: new Array()
+            };
+
+            /* Public API */
+
+            /*
+             * Returns a string representation of this json.
+             * @returns {String}
+             */
+            this.getJson = function () {
+                return JSON.stringify(json, null, '    ');
+            };
+
+            /*
+             * Setters for the generated json metadata.
+             */
+            this.setImageSize = function (width, height) {
+                json.metaData.imageWidth = width;
+                json.metaData.imageHeight = height;
+            };
+
+            this.setInitialMousePos = function (pos) {
+                json.metaData.initialMouseX = pos.x;
+                json.metaData.initialMouseY = pos.y;
+            };
+
+            this.setInitialBackground = function (id) {
+                json.metaData.initialBackgroundId = id;
+            };
+
+            this.addAction = function (action) {
+                json.action.push(action);
+            };
+
+            /*
+             * Transforms a JSON containing action properties to a JSON containing
+             * the execution functions.
+             * @param {Object} json
+             * @returns {Object} A json object containing the execution functions
+             * for all the actions of the presentation.
+             */
+            this.toExecutableList = function (json) {
+                var executableList = {
+                    metaData: json.metaData,
+                    action: new Array()
+                };
+                for (var i = 0; i < json.action.length; i++) {
+                    addExecutableAction(executableList, json.action[i]);
+                }
+                return executableList;
+            };
+        };
+
+        /*
+         * Represents a 'dahu' file for a project.
+         */
+        var DahuScreencastModel = function () {
+
+            /* Private API */
+
+            var json = {};
+
+            /*
+             * Generates a unique action ID.
+             *
+             * With this implementation, this ID is unique as long as the user
+             * doesn't replace it manually with a not kind value or replace
+             * the nextUniqueId with bad intentions.
+             * But maybe that a UUID is a bit tiresome to put as an anchor...
+             */
+            var generateUniqueActionId = function () {
+                json.metaData.nextUniqueId++;
+                return json.metaData.nextUniqueId.toString();
+            };
+
+            /*
+             * This method checks if the json representing a dahu project is
+             * in the good version. It's in case a project file was created
+             * with a previous version of Dahu, and that some fields are
+             * missing.
+             *
+             * It doesn't control each field (at the moment) but only the
+             * fields that can be missing due to a new Dahu version and not
+             * to a manual editing.
+             *
+             * This method doesn't check any Json syntax or something like that.
+             */
+            var upgradeJsonVersion = function () {
+                // Checks if unique IDs are in the project file
+                if (!json.metaData.nextUniqueId) {
+                    var currentId = 0;
+                    for (var i = 0; i < json.data.length; i++) {
+                        for (var j = 0; j < json.data[i].action.length; j++) {
+                            json.data[i].action[j].id = currentId.toString();
+                            currentId++;
+                        }
+                    }
+                    json.metaData.nextUniqueId = currentId;
+                }
+            };
+
+            /* Public API */
+
+            /*
+             * json variable generated from a JSON file.
+             * @param String stringJson String loaded from JSON file.
+             */
+            this.loadJson = function (stringJson) {
+                json = JSON.parse(stringJson);
+                upgradeJsonVersion();
+            };
+
+            /*
+             * Create a new presentation variable in the JSON file which will contain slides.
+             */
+            this.createPresentation = function (width, height) {
+                json.metaData = {};
+                json.metaData.imageWidth = width;
+                json.metaData.imageHeight = height;
+                json.metaData.nextUniqueId = 0;
+                json.data = new Array();
+            };
+
+            /*
+             * Add a new slide in the presentation variable of the JSON file.
+             * @param int index wanted for the slide.
+             * @param String idSlide Unique identifier for the slide.
+             * @param String img Related to pathname of the image.
+             * @param double mouseX Abscissa mouse position in %.
+             * @param double mouseY Ordinate mouse position in %.
+             * @return Index of the newly added slide.
+             */
+            this.addSlide = function (indexSlide, idSlide, img, mouseX, mouseY, speed) {
+                var slide = {
+                    "object": new Array(),
+                    "action": new Array()
+                };
+                json.data.splice(indexSlide, 0, slide);
+                this.addObject(indexSlide, "background", idSlide, img);
+                this.addObject(indexSlide, "mouse");
+                this.addAction(indexSlide, "move", json.data[indexSlide].object[1].id, "onClick", mouseX, mouseY, speed);
+                this.addAction(indexSlide, "appear", json.data[indexSlide].object[0].id, "afterPrevious");
+            };
+
+            /*
+             * Object factory.
+             * Add a new Object in the slide idSlide.
+             * @param int idSlide
+             * @param string type
+             * Other params can be specified depending on the object's type.
+             */
+            this.addObject = function (idSlide, type) {
+				var object = {
+                    "type": type
+                };
+                switch (type.toLowerCase()) {
+                    case "background":
+                        object.id = arguments[2] + json.data[idSlide].object.length;
+                        object.img = arguments[3] || "";
+                        break;
+                    case "mouse":
+                        object.id = "mouse-cursor";
+                        break;
+                    case "tooltip":
+                        /*
+                         * TODO: we'll need a more robust unique name
+                         * when we start actually using this.
+                         */
+                        object.id = "s" + idSlide + "-o" + json.data[idSlide].object.length;
+                        object.text = arguments[2] || "";
+                        object.color = arguments[3] || null;
+						object.width = arguments[4] || "";
+						json.data[idSlide].object.push(object);
+						var objectLength = json.data[idSlide].object.length;
+						var last = json.data[idSlide].object[objectLength-1];
+						json.data[idSlide].object[objectLength-1] = 
+								json.data[idSlide].object[objectLength-2];
+						json.data[idSlide].object[objectLength-2] = last;
+                        break;
+                }
+				if(type.toLowerCase() != "tooltip"){
+					json.data[idSlide].object.push(object);
+				}
+            };
+
+            /*
+             * Action factory.
+             * Add a new Action in the slide idSlide whose target is the id of an object.
+             * Three types of trigger : "withPrevious", "afterPrevious", "onClick".
+             * @param int idSlide
+             * @param string type
+             * @param string target
+             * @param string trigger
+             * Other params can be specified depending on the object's type.
+             */
+            this.addAction = function (idSlide, type, target, trigger) {
+                var action = {
+                    "id": generateUniqueActionId(),
+                    "type": type,
+                    "target": target,
+                    "trigger": trigger
+                };
+                switch (type.toLowerCase()) {
+                    case "appear":
+                        action.abs = arguments[4] || 0.0;
+                        action.ord = arguments[5] || 0.0;
+                        action.duration = arguments[6] || 0;
+                        break;
+                    case "disappear":
+                        action.duration = arguments[4] || 0;
+                        break;
+                    case "move":
+                        action.finalAbs = arguments[4] || 0.0;
+                        action.finalOrd = arguments[5] || 0.0;
+                        action.speed = arguments[6] || 0;
+                        break;
+                }
+                json.data[idSlide].action.push(action);
+            };
+
+            this.editMouse = function (idSlide, idAction, mouseX, mouseY) {
+                json.data[idSlide].action[idAction].finalAbs = mouseX;
+                json.data[idSlide].action[idAction].finalOrd = mouseY;
+            };
+
+            /*
+             * Sets a title for the presentation.
+             * @param String title Title to set.
+             */
+            this.setTitle = function (title) {
+                json.metaData.title = title;
+            };
+
+            /*
+             * Sets an annotation for the presentation.
+             * @param String annotation Annotation to set.
+             */
+            this.setAnnotation = function (annotation) {
+                json.metaData.annotation = annotation;
+            };
+
+            /*
+             * Inverts the two slides (their positions on the table).
+             * @param int idSlide1 Index of the first slide.
+             * @param int idSlide2 Index of the second slide.
+             */
+            this.invertSlides = function (idSlide1, idSlide2) {
+                var tmp = json.data[idSlide1];
+                json.data[idSlide1] = json.data[idSlide2];
+                json.data[idSlide2] = tmp;
+            };
+
+            /*
+             * Inverts the two actions (their positions on the table).
+             * @param int idSlide
+             * @param int idAction1
+             * @param int idAction2
+             */
+            this.invertActions = function (idSlide, idAction1, idAction2) {
+                var tmp = json.data[idSlide].action[idAction1];
+                json.data[idSlide].action[idAction1] = json.data[idSlide].action[idAction2];
+                json.data[idSlide].action[idAction2] = tmp;
+            };
+
+            /*
+             * Returns the actions on the specified slide.
+             * @returns {Array}
+             */
+            this.getActionList = function (idSlide) {
+                return json.data[idSlide].action;
+            };
+
+            /*
+             * Catches all the objects of the presentation
+             * @returns {Array} List of objects of the presentation
+             */
+            this.getObjectList = function () {
+                var objectList = new Array();
+                var indexSlide = 0;
+                while (json.data[indexSlide]) {
+                    var indexObject = 0;
+                    while (json.data[indexSlide].object[indexObject]) {
+                        objectList.push(json.data[indexSlide].object[indexObject]);
+                        indexObject++;
+                    }
+                    indexSlide++;
+                }
+                return objectList;
+            };
+
+            /*
+             * Returns an array containing all the background images.
+             * @returns {Array} List of background images.
+             */
+            this.getImageList = function () {
+                var list = new Array();
+                var indexSlide = 0;
+                while (json.data[indexSlide]) {
+                    list.push(json.data[indexSlide].object[0].img);
+                    indexSlide++;
+                }
+                ;
+                return list;
+            };
+
+            /*
+             * Returns an object containing the id of the first background.
+             * @returns {string} Id of the first background.
+             */
+            this.getInitialBackground = function () {
+                return json.data[0].object[0].id;
+            };
+
+            /*
+             * Returns an object containing the initial mouse position.
+             * @returns {object} Initial position of mouse.
+             */
+            this.getInitialMousePos = function () {
+                var pos = {};
+                pos.x = json.data[0].action[0].finalAbs;
+                pos.y = json.data[0].action[0].finalOrd;
+                return pos;
+            };
+
+            /*
+             * Removes the slide at the specified index.
+             * @param {int} idSlide
+             */
+            this.removeSlide = function (idSlide) {
+                json.data.splice(idSlide, 1);
+            };
+
+            /*
+             * Removes the action at the specified slide.
+             * @param {int} idSlide
+             * @param {int} idAction
+             */
+            this.removeAction = function (idSlide, idAction) {
+                json.data[idSlide].action.splice(idAction, 1);
+            };
+
+            /*
+             * Removes the specified object from the slide.
+             * Also removes all the actions attached to this object.
+             * @param {int} idSlide
+             * @param {int} idObject
+             */
+            this.removeObject = function (idSlide, idObject) {
+                var removed = json.data[idSlide].object.splice(idObject, 1);
+                for (var i = 0; i < json.data.length; i++) {
+                    var j = 0;
+                    while (json.data[i].action[j]) {
+                        if (json.data[i].action[j].target === removed.id) {
+                            json.data[i].action.splice(j, 1);
+                        } else {
+                            j++;
+                        }
+                    }
+                }
+            };
+
+            /*
+             * @returns {String}
+             */
+            this.getJson = function () {
+                return JSON.stringify(json, null, '    ');
+            };
+
+            /*
+             * @returns {String} returns the index for the next slide.
+             */
+            this.getNbSlide = function () {
+                return json.data.length;
+            };
+
+            /*
+             * @return {object} returns the object identified by idSlide
+             */
+            this.getSlide = function (idSlide) {
+                return json.data[idSlide];
+            };
+
+            /*
+             * Sets the size of images for the generated presentation.
+             * The parameters can be null : it means there are no
+             * requirement for this dimension.
+             * @param {int} width New width for the generated images.
+             * @param {int} height New height for the generated images.
+             */
+            this.setImageSizeRequirements = function (width, height) {
+                json.metaData.imageWidth = width;
+                json.metaData.imageHeight = height;
+            };
+
+            /*
+             * Gets the width of images for the generated presentation.
+             * @return {int or null} Width of generated images.
+             */
+            this.getImageWidth = function () {
+                return json.metaData.imageWidth;
+            };
+
+            /*
+             * Gets the height of images for the generated presentation.
+             * @return {int or null} Height of generated images.
+             */
+            this.getImageHeight = function () {
+                return json.metaData.imageHeight;
+            };
+
+            /*
+             * Gets a background image (no one in particular, the first met).
+             * @return {string} The name of a background image.
+             */
+            this.getABackgroundImage = function () {
+                for (var i = 0; i < json.data.length; i++) {
+                    for (var j = 0; j < json.data[i].object.length; j++) {
+                        if (json.data[i].object[j].type === "background") {
+                            return json.data[i].object[j].img;
+                        }
+                    }
+                }
+                return null;
+            };
+        };
+
+        /* public API */
+
+        self.version = "0.0.1";
+
+        self.createScreencastGenerator = function createScreencastGenerator() {
+            return new DahuScreencastGenerator();
+        };
+
+        self.createScreencastModel = function createScreencastModel() {
+            return new DahuScreencastModel();
+        };
+
+        self.createScreencastExecutableModel = function createScreencastExecutableModel() {
+            return new DahuScreencastExecutableModel();
+        };
+
+        self.createScreencastGeneratedModel = function createScreencastGeneratedModel() {
+            return new DahuScreencastGeneratedModel();
+        };
+
+        return self;
+    })();
+
+    window.dahuapp = dahuapp;
+
+})(window, jQuery);
\ No newline at end of file
diff --git a/TP/TP01/P06/dahuapp.viewer.css b/TP/TP01/P06/dahuapp.viewer.css
new file mode 100644
index 0000000000000000000000000000000000000000..ea934576dad6ca9263a6163de58f5fe4534d1f0b
--- /dev/null
+++ b/TP/TP01/P06/dahuapp.viewer.css
@@ -0,0 +1,25 @@
+.object-list {
+    position: absolute;
+    margin: 0px;
+    padding: 0px;
+}
+
+.mouse-cursor {
+    position: absolute;
+}
+
+.control {
+    position: relative;
+}
+
+.background {
+    position: absolute;
+}
+
+.tooltip {
+    position: absolute;
+    width: 100px;
+    padding: 4px;
+    margin: 2px;
+    border: 2px black solid;
+}
\ No newline at end of file
diff --git a/TP/TP01/P06/dahuapp.viewer.js b/TP/TP01/P06/dahuapp.viewer.js
new file mode 100644
index 0000000000000000000000000000000000000000..12ad03b2cdc503a8fd6ff3f5fdc2a74edbb36edb
--- /dev/null
+++ b/TP/TP01/P06/dahuapp.viewer.js
@@ -0,0 +1,456 @@
+"use strict";
+
+/**
+ * Dahuapp viewer module.
+ * 
+ * @param   dahuapp     dahuapp object to augment with module.
+ * @param   $           jQuery
+ * @returns dahuapp extended with viewer module.
+ */
+
+(function(dahuapp, $) {
+    var viewer = (function() {
+
+        var self = {};
+
+        var DahuViewerModel = function(select, getParams) {
+
+            /* Private API */
+
+            var json = null;
+            var selector = select;
+            var parseAutoOption = function(name, defaultValue) {
+                var res = getParams[name];
+                if (res == null) {
+                    if (name == 'auto') {
+                        return false;
+                    } else {
+                        return parseAutoOption('auto', defaultValue);
+                    }
+                }
+                if (res.toLowerCase() === 'false') {
+                    return false;
+                }
+                res = parseInt(res);
+                if (isNaN(res)) {
+                    // e.g. ?autoplay or ?autoplay=true
+                    return defaultValue;
+                }
+                return res;
+            }
+
+            /* Whether to wait for "next" event between actions */
+            var autoPlay = parseAutoOption('autoplay', 5000);
+            /* Whether to wait for "next" event on page load */
+            var autoStart = parseAutoOption('autostart', 5000);
+            /* Whether to loop back to start at the end of presentation */
+            var autoLoop = parseAutoOption('autoloop', 5000);
+
+            var events = (function() {
+                var self = {};
+                /*
+                 * Creates a generic event.
+                 */
+                var createEvent = function() {
+                    var callbacks = $.Callbacks();
+                    return {
+                        publish: callbacks.fire,
+                        subscribe: callbacks.add,
+                        unsubscribe: callbacks.remove,
+                        unsubscribeAll: callbacks.empty
+                    };
+                };
+                /*
+                 * Called when the button next is pressed.
+                 */
+                self.onNext = createEvent();
+                /*
+                 * Called when the button previous is pressed.
+                 */
+                self.onPrevious = createEvent();
+                /*
+                 * Called when an action is over.
+                 */
+                self.onActionOver = createEvent();
+                /*
+                 * Called when an action starts.
+                 */
+                self.onActionStart = createEvent();
+                /*
+                 * Called when at least one action was running and has finished.
+                 */
+                self.onAllActionFinish = createEvent();
+                
+                return self;
+            })();
+
+            /*
+             * Variables used like index for methodes subscribed.
+             */
+            var currentAction = 0;    /* Action currently running */
+            var nextAction = 0;       /* Action to execute on 'Next' click */
+            var nbActionsRunning = 0;
+            var previousAnchor = -1;  /* Last entered anchor, only used in
+                                       * case the 'onhashchange' event is
+                                       * not supported by the web browser */
+            
+            /*
+             * Functions to reinitialise running actions and subscribed
+             * callbacks (reinitialise is called once without stopping
+             * all the actions, so the two functions are separated).
+             */
+            var stopAllActions = function() {
+                $(selector + " .object-list").children().stop(true, true);
+                reinitialiseCallbackLists();
+                nbActionsRunning = 0;
+            };
+            var reinitialiseCallbackLists = function() {
+                events.onActionStart.unsubscribeAll();
+                events.onActionStart.subscribe(onActionStartEventHandler);
+                events.onAllActionFinish.unsubscribeAll();
+            };
+            
+            /*
+             * Timer used to program onNext event in autoplay mode.
+             */
+            var playTimer = 0;
+
+            /*
+             * Function used when an "onNextEvent" event is caught.
+             */
+            var onNextEventHandler = function() {
+                /*
+                 * If the user pressed "next" before an autoplay event
+                 * is triggered, it replaces the autoplay event, hence
+                 * cancels it:
+                 */
+                window.clearTimeout(playTimer);
+
+                enterAnimationMode();
+                stopAllActions();
+                var tmpAction = nextAction;
+                var onlyWithPrevious = true;
+                nextAction++;
+                currentAction = nextAction;
+                while (json.action[currentAction] && onlyWithPrevious) {
+                    switch (json.action[currentAction].trigger) {
+                        case 'onClick':
+                            nextAction = currentAction;
+                            onlyWithPrevious = false;
+                            break;
+                        case 'withPrevious':
+                            var tmp = currentAction;
+                            /*
+                             * We can't directly pass 'execute' as callback because we
+                             * allow the 'execute' function to reference a property of the
+                             * object (by using 'this.property') so we have to call the
+                             * function in the containing object to make that available
+                             */
+                            events.onActionStart.subscribe(function(events, selector) {
+                                json.action[tmp].execute(events, selector);
+                            });
+                            break;
+                        case 'afterPrevious':
+                            var tmp = currentAction;
+                            events.onAllActionFinish.subscribe(function(events, selector) {
+                                json.action[tmp].execute(events, selector);
+                            });
+                            while (json.action[nextAction] && json.action[nextAction].trigger !== 'onClick') {
+                                nextAction++;
+                            }
+                            onlyWithPrevious = false;
+                            break;
+                    }
+                    currentAction++;
+                }
+                if (json.action[tmpAction]) {
+                    launch(json.action[tmpAction]);
+                }
+                if (nextAction > json.action.length) {
+                    nextAction = json.action.length;
+                }
+            };
+            
+            /*
+             * Function used when an "onPreviousEvent" event is caught.
+             */
+            var onPreviousEventHandler = function() {
+                stopAllActions();
+                currentAction = nextAction - 1;
+                while (json.action[currentAction] && json.action[currentAction].trigger !== 'onClick') {
+                    launchReverse(json.action[currentAction]);
+                    currentAction--;
+                }
+                /* currentAction = -1 means that it's the beginning */
+                if (currentAction > -1) {
+                    launchReverse(json.action[currentAction]);
+                }
+                if (currentAction < 0) {
+                    currentAction = 0;
+                }
+                nextAction = currentAction;
+            };
+
+            /*
+             * Function used when an "onActionOverEvent" event is caught.
+             */
+            var onActionOverEventHandler = function() {
+                nbActionsRunning--;
+                if (nbActionsRunning === 0) {
+                    events.onAllActionFinish.publish(events, selector);
+                    reinitialiseCallbackLists();
+                    while (json.action[currentAction]) {
+                        switch (json.action[currentAction].trigger) {
+                            case 'onClick':
+                                leaveAnimationMode();
+                                if (autoPlay && nbActionsRunning === 0) {
+                                    playTimer = setTimeout(function () {
+                                        events.onNext.publish();
+                                    }, autoPlay);
+                                }
+                                return;
+                            case 'withPrevious':
+                                var tmp = currentAction;
+                                events.onActionStart.subscribe(function(events, selector) {
+                                    json.action[tmp].execute(events, selector);
+                                });
+                                break;
+                            case 'afterPrevious':
+                                var tmp = currentAction;
+                                events.onAllActionFinish.subscribe(function(events, selector) {
+                                    json.action[tmp].execute(events, selector);
+                                });
+                                currentAction++;
+                                return;
+                        }
+                        currentAction++;
+
+                    }
+                    if (autoLoop && nbActionsRunning === 0) {
+                        setTimeout(function () {
+                            resetPresentation();
+                            startPresentationMaybe();
+                        }, autoLoop);
+                    }
+                }
+                leaveAnimationMode();
+            };
+
+            /*
+             * Function used when an "onActionStartEvent" event is caught.
+             */
+            var onActionStartEventHandler = function() {
+                nbActionsRunning++;
+            };
+
+            /*
+             * Enter and leave "animation" mode. The viewer is in
+             * animation mode when something is going on without human
+             * interaction (i.e. executing actions before the next
+             * "onClick").
+             */
+            var enterAnimationMode = function () {
+                $(selector + ' .mouse-cursor-pause').hide();
+                $(selector + ' .mouse-cursor-normal').show();
+            };
+
+            var leaveAnimationMode = function () {
+                $(selector + ' .mouse-cursor-pause').show();
+                $(selector + ' .mouse-cursor-normal').hide();
+            };
+
+            /*
+             * Function used to perform actions.
+             */
+            var launch = function(action) {
+                action.execute(events, selector);
+            };
+            var launchReverse = function(action) {
+                action.executeReverse(selector);
+            };
+
+            /*
+             * The two following methods each returns an array containing the
+             * actions to execute to reach the given anchor (respectively
+             * forward or backwards).
+             *
+             * If the given anchor matches none of the actions, then an empty
+             * array is returned.
+             */
+            var getActionsToJumpForward = function(anchor) {
+                var actions = new Array();
+                for (var i = nextAction; i < json.action.length; i++) {
+                    // '==' and not '===' because we compare indifferently a
+                    // number or a string or anything else
+                    if (json.action[i].id == anchor) {
+                        return actions;
+                    } else {
+                        actions.push(json.action[i]);
+                    }
+                }
+                // Here, the anchor has not been found during the action scan
+                return null;
+            };
+            var getActionsToJumpBackwards = function(anchor) {
+                var actions = new Array();
+                for (var i = nextAction - 1; i >= 0; i--) {
+                    // '==' and not '===' because we compare indifferently a
+                    // number or a string or anything else
+                    actions.push(json.action[i]);
+                    if (json.action[i].id == anchor) {
+                        return actions;
+                    }
+                }
+                // Here, the anchor has not been found during the action scan
+                return null;
+            };
+            
+            /*
+             * Updates the position of the presentation depending on the
+             * given anchor (next action wanted).
+             */
+            var jumpToAnchor = function(anchor) {
+                if (anchor !== '') {
+                    stopAllActions();
+                    if (anchor > nextAction) {
+                        // forward
+                        var actions = getActionsToJumpForward(anchor);
+                        for (var i = 0; i < actions.length; i++) {
+                            actions[i].executeImmediately(selector);
+                        }
+                        nextAction += actions.length;
+                    } else {
+                        // backwards
+                        var actions = getActionsToJumpBackwards(anchor);
+                        for (var i = 0; i < actions.length; i++) {
+                            actions[i].executeReverse(selector);
+                        }
+                        nextAction -= actions.length;
+                    }
+                }
+            };
+
+            var resetPresentation = function() {
+                window.clearTimeout(playTimer);
+
+                currentAction = 0;
+                nextAction = 0;
+                nbActionsRunning = 0;
+
+                /*
+                 * At the beginning, the visible image is the first one of the presentation
+                 */
+                $(selector + " .object-list").children().hide();
+
+                $(selector + " ." + json.metaData.initialBackgroundId).show();
+
+                $(selector + " .mouse-cursor").css({
+                    'top': (json.metaData.initialMouseY * json.metaData.imageHeight) + "px",
+                    'left': (json.metaData.initialMouseX * json.metaData.imageWidth) + "px"
+                });
+
+                $(selector + " .mouse-cursor").show();
+            }
+
+            var startPresentationMaybe = function() {
+                if (autoStart) {
+                    playTimer = setTimeout(function () {
+                        events.onNext.publish();
+                    }, autoStart);
+                }
+            }
+
+            /* Public API */
+
+            this.loadUrl = function(url) {
+                var dataJson;
+                $.ajax({
+                    'async': false,
+                    'global': false,
+                    'url': url,
+                    'dataType': "json",
+                    'success': function(data) {
+                        dataJson = data;
+                    }
+                });
+                
+                load(dataJson);
+            };
+
+            this.load = function(dataJson) {
+                /* Transforms data JSON to executable functions for actions */
+                var execModel = dahuapp.createScreencastExecutableModel();
+                execModel.loadJson(dataJson);
+                json = execModel.getJson();
+            };
+
+            this.start = function() {
+                
+                /*
+                 * Subscription of methods to their events.
+                 */
+                events.onNext.subscribe(onNextEventHandler);
+                events.onPrevious.subscribe(onPreviousEventHandler);
+                events.onActionOver.subscribe(onActionOverEventHandler);
+                events.onActionStart.subscribe(onActionStartEventHandler);
+
+                resetPresentation();
+
+                /*
+                 * If an anchor has been specified, we place the presentation
+                 * in the right position.
+                 */
+                jumpToAnchor(window.location.hash.substring(1));
+                
+                /*
+                 * If the anchor changes during the presentation, then the
+                 * presentation is updated
+                 */
+                if ("onhashchange" in window) {
+                    // event supported
+                    window.onhashchange = function () {
+                        jumpToAnchor(window.location.hash.substring(1));
+                    };
+                } else {
+                    // event not supported : periodical check
+                    window.setInterval(function () {
+                        if (window.location.hash.substring(1) !== previousAnchor) {
+                            previousAnchor = window.location.hash.substring(1);
+                            jumpToAnchor(window.location.hash.substring(1));
+                        }
+                    }, 100);
+                }
+
+                /*
+                 * A click on the "next" button publishes a nextSlide event
+                 */
+                $(selector + " .next").click(function() {
+                    events.onNext.publish();
+                });
+                
+                /*
+                 * A click on the "previous" button publishes a previousSlide event
+                 */
+                $(selector + " .previous").click(function() {
+                    events.onPrevious.publish();
+                });
+
+                /*
+                 * Everything is ready, show the presentation
+                 * (hidden with style="display: none" from HTML).
+                 */
+                $("#loading").hide();
+                $(selector + " .object-list").show();
+                startPresentationMaybe();
+            };
+        };
+
+        self.createDahuViewer = function(selector, getParams) {
+            return new DahuViewerModel(selector, getParams);
+        };
+
+        return self;
+    })();
+
+    dahuapp.viewer = viewer;
+})(dahuapp || {}, jQuery);
diff --git a/TP/TP01/P06/img/042b3794-32ee-9789-5a5e-7492c70ea14b.png b/TP/TP01/P06/img/042b3794-32ee-9789-5a5e-7492c70ea14b.png
new file mode 100644
index 0000000000000000000000000000000000000000..470dbb11df174b7a238fc959bfcc3b7b046797c5
Binary files /dev/null and b/TP/TP01/P06/img/042b3794-32ee-9789-5a5e-7492c70ea14b.png differ
diff --git a/TP/TP01/P06/img/05133e21-8ad0-1342-3568-c459d579b14a.png b/TP/TP01/P06/img/05133e21-8ad0-1342-3568-c459d579b14a.png
new file mode 100644
index 0000000000000000000000000000000000000000..443a16dce6d215211225ab83fb731ae64cd1f281
Binary files /dev/null and b/TP/TP01/P06/img/05133e21-8ad0-1342-3568-c459d579b14a.png differ
diff --git a/TP/TP01/P06/img/104c2f49-b0ff-9eb6-1993-577584ae77ea.png b/TP/TP01/P06/img/104c2f49-b0ff-9eb6-1993-577584ae77ea.png
new file mode 100644
index 0000000000000000000000000000000000000000..b0635c5368ec4e0f148dd533c2d2cb365d5218da
Binary files /dev/null and b/TP/TP01/P06/img/104c2f49-b0ff-9eb6-1993-577584ae77ea.png differ
diff --git a/TP/TP01/P06/img/146e7fd1-79a9-d471-f996-f583ba0fe8f9.png b/TP/TP01/P06/img/146e7fd1-79a9-d471-f996-f583ba0fe8f9.png
new file mode 100644
index 0000000000000000000000000000000000000000..08a8b11f86747b45e6a88e0d4ed947b26c9ef726
Binary files /dev/null and b/TP/TP01/P06/img/146e7fd1-79a9-d471-f996-f583ba0fe8f9.png differ
diff --git a/TP/TP01/P06/img/24ac7258-308f-3469-7ff3-73cd1714356b.png b/TP/TP01/P06/img/24ac7258-308f-3469-7ff3-73cd1714356b.png
new file mode 100644
index 0000000000000000000000000000000000000000..d6e4db98af50d46bf69565c8266f066de4e36b34
Binary files /dev/null and b/TP/TP01/P06/img/24ac7258-308f-3469-7ff3-73cd1714356b.png differ
diff --git a/TP/TP01/P06/img/28cc4893-5425-5aac-05e8-a7e32ca109b8.png b/TP/TP01/P06/img/28cc4893-5425-5aac-05e8-a7e32ca109b8.png
new file mode 100644
index 0000000000000000000000000000000000000000..34da5a709ba0a5dc085f7b6eaebb8a48561fa968
Binary files /dev/null and b/TP/TP01/P06/img/28cc4893-5425-5aac-05e8-a7e32ca109b8.png differ
diff --git a/TP/TP01/P06/img/2ab20dfd-b528-4e65-28d0-5bbe0ce71d26.png b/TP/TP01/P06/img/2ab20dfd-b528-4e65-28d0-5bbe0ce71d26.png
new file mode 100644
index 0000000000000000000000000000000000000000..435b0e8d6bf73c0ad5a42ee859f1610184ab55c6
Binary files /dev/null and b/TP/TP01/P06/img/2ab20dfd-b528-4e65-28d0-5bbe0ce71d26.png differ
diff --git a/TP/TP01/P06/img/30445c44-4357-cb29-da5c-dd8a9418ce31.png b/TP/TP01/P06/img/30445c44-4357-cb29-da5c-dd8a9418ce31.png
new file mode 100644
index 0000000000000000000000000000000000000000..f556d1e47eabf050f6c00dd6bd79bef0feb5146a
Binary files /dev/null and b/TP/TP01/P06/img/30445c44-4357-cb29-da5c-dd8a9418ce31.png differ
diff --git a/TP/TP01/P06/img/338b5367-d1f7-58fd-7ac9-8fa9ee456d82.png b/TP/TP01/P06/img/338b5367-d1f7-58fd-7ac9-8fa9ee456d82.png
new file mode 100644
index 0000000000000000000000000000000000000000..1fb8547a8cb30fe758c5cea1027d5daa7e283b1d
Binary files /dev/null and b/TP/TP01/P06/img/338b5367-d1f7-58fd-7ac9-8fa9ee456d82.png differ
diff --git a/TP/TP01/P06/img/37c23d3f-b713-19c2-ff10-7e177eb69c38.png b/TP/TP01/P06/img/37c23d3f-b713-19c2-ff10-7e177eb69c38.png
new file mode 100644
index 0000000000000000000000000000000000000000..f6959c7e4bfc5c76c3aec20d2517a3842d290367
Binary files /dev/null and b/TP/TP01/P06/img/37c23d3f-b713-19c2-ff10-7e177eb69c38.png differ
diff --git a/TP/TP01/P06/img/44cbf940-d047-a39e-6fee-2d91ef83f91c.png b/TP/TP01/P06/img/44cbf940-d047-a39e-6fee-2d91ef83f91c.png
new file mode 100644
index 0000000000000000000000000000000000000000..2e9777084a002873fc76072840163038713c851a
Binary files /dev/null and b/TP/TP01/P06/img/44cbf940-d047-a39e-6fee-2d91ef83f91c.png differ
diff --git a/TP/TP01/P06/img/46b08559-5496-a26e-c7f2-14ac0a92fecf.png b/TP/TP01/P06/img/46b08559-5496-a26e-c7f2-14ac0a92fecf.png
new file mode 100644
index 0000000000000000000000000000000000000000..06c6c8d48a39a003a8822d6698517775b6b47af7
Binary files /dev/null and b/TP/TP01/P06/img/46b08559-5496-a26e-c7f2-14ac0a92fecf.png differ
diff --git a/TP/TP01/P06/img/4daad903-6dbe-5e6c-cc1b-12ad9749eb53.png b/TP/TP01/P06/img/4daad903-6dbe-5e6c-cc1b-12ad9749eb53.png
new file mode 100644
index 0000000000000000000000000000000000000000..28baa3c9cc2a975ed3abf28d0f49d310d2f109e3
Binary files /dev/null and b/TP/TP01/P06/img/4daad903-6dbe-5e6c-cc1b-12ad9749eb53.png differ
diff --git a/TP/TP01/P06/img/4decb081-5029-2428-8d10-d7f6c0808425.png b/TP/TP01/P06/img/4decb081-5029-2428-8d10-d7f6c0808425.png
new file mode 100644
index 0000000000000000000000000000000000000000..90626f563b0d0ed7b221a31115cdcb43a68871d4
Binary files /dev/null and b/TP/TP01/P06/img/4decb081-5029-2428-8d10-d7f6c0808425.png differ
diff --git a/TP/TP01/P06/img/50450763-9cfc-4865-8c1d-24b1fa9cac8c.png b/TP/TP01/P06/img/50450763-9cfc-4865-8c1d-24b1fa9cac8c.png
new file mode 100644
index 0000000000000000000000000000000000000000..4e00f1a91c8e18667328f0c7098ae692466633f2
Binary files /dev/null and b/TP/TP01/P06/img/50450763-9cfc-4865-8c1d-24b1fa9cac8c.png differ
diff --git a/TP/TP01/P06/img/571c8ded-4b8d-dfed-d850-b84ae3ec961e.png b/TP/TP01/P06/img/571c8ded-4b8d-dfed-d850-b84ae3ec961e.png
new file mode 100644
index 0000000000000000000000000000000000000000..b7c6b7eb9418c45aacafe9fd0c1f748cef6e548b
Binary files /dev/null and b/TP/TP01/P06/img/571c8ded-4b8d-dfed-d850-b84ae3ec961e.png differ
diff --git a/TP/TP01/P06/img/636bdb73-2612-3933-1f1b-6022d6e33625.png b/TP/TP01/P06/img/636bdb73-2612-3933-1f1b-6022d6e33625.png
new file mode 100644
index 0000000000000000000000000000000000000000..c44abbfb45c97dd373c4dbd9a4279b7b2324bae0
Binary files /dev/null and b/TP/TP01/P06/img/636bdb73-2612-3933-1f1b-6022d6e33625.png differ
diff --git a/TP/TP01/P06/img/7a1eeb94-4ce9-7b79-aa1f-87d376e76b5c.png b/TP/TP01/P06/img/7a1eeb94-4ce9-7b79-aa1f-87d376e76b5c.png
new file mode 100644
index 0000000000000000000000000000000000000000..f77a762a95697ccd946426d5196e6c48c75ee4e7
Binary files /dev/null and b/TP/TP01/P06/img/7a1eeb94-4ce9-7b79-aa1f-87d376e76b5c.png differ
diff --git a/TP/TP01/P06/img/7d4ed9e8-6561-a816-39b3-fdb15edfb716.png b/TP/TP01/P06/img/7d4ed9e8-6561-a816-39b3-fdb15edfb716.png
new file mode 100644
index 0000000000000000000000000000000000000000..8e91d6c8a3cc595b5a4639c8b80240652de1b7b7
Binary files /dev/null and b/TP/TP01/P06/img/7d4ed9e8-6561-a816-39b3-fdb15edfb716.png differ
diff --git a/TP/TP01/P06/img/831240cd-35ec-6d3d-14a9-dfe09cf71828.png b/TP/TP01/P06/img/831240cd-35ec-6d3d-14a9-dfe09cf71828.png
new file mode 100644
index 0000000000000000000000000000000000000000..820199db65d42cd9dade22e248149dd8852b8338
Binary files /dev/null and b/TP/TP01/P06/img/831240cd-35ec-6d3d-14a9-dfe09cf71828.png differ
diff --git a/TP/TP01/P06/img/8446d790-b98c-3feb-a82e-457056e97ae7.png b/TP/TP01/P06/img/8446d790-b98c-3feb-a82e-457056e97ae7.png
new file mode 100644
index 0000000000000000000000000000000000000000..eac0f21d9e3136700f3486e25cddecf0a5c3df0e
Binary files /dev/null and b/TP/TP01/P06/img/8446d790-b98c-3feb-a82e-457056e97ae7.png differ
diff --git a/TP/TP01/P06/img/88d7e60a-0e26-8bf4-c221-2e6769762253.png b/TP/TP01/P06/img/88d7e60a-0e26-8bf4-c221-2e6769762253.png
new file mode 100644
index 0000000000000000000000000000000000000000..5ed53176cc4b88f04003cafaef6d5c1ef7d5e4ed
Binary files /dev/null and b/TP/TP01/P06/img/88d7e60a-0e26-8bf4-c221-2e6769762253.png differ
diff --git a/TP/TP01/P06/img/8e345aed-81be-c51b-678b-a64a5a106b65.png b/TP/TP01/P06/img/8e345aed-81be-c51b-678b-a64a5a106b65.png
new file mode 100644
index 0000000000000000000000000000000000000000..bb57f494e33f867ad06ed9a29ad7d46da3160b80
Binary files /dev/null and b/TP/TP01/P06/img/8e345aed-81be-c51b-678b-a64a5a106b65.png differ
diff --git a/TP/TP01/P06/img/8fc3bdb8-9794-002e-4e4f-c744c9573321.png b/TP/TP01/P06/img/8fc3bdb8-9794-002e-4e4f-c744c9573321.png
new file mode 100644
index 0000000000000000000000000000000000000000..08f5b018eaa07c6eb048a901eab1205211b45dfb
Binary files /dev/null and b/TP/TP01/P06/img/8fc3bdb8-9794-002e-4e4f-c744c9573321.png differ
diff --git a/TP/TP01/P06/img/9bc4b9d8-0bc8-9842-65c2-708b6c282bbe.png b/TP/TP01/P06/img/9bc4b9d8-0bc8-9842-65c2-708b6c282bbe.png
new file mode 100644
index 0000000000000000000000000000000000000000..90d7bf768f3dfbaa713d31f15455b4bf5c29e5a4
Binary files /dev/null and b/TP/TP01/P06/img/9bc4b9d8-0bc8-9842-65c2-708b6c282bbe.png differ
diff --git a/TP/TP01/P06/img/a13c8786-beda-c3aa-6449-4ff54a82353f.png b/TP/TP01/P06/img/a13c8786-beda-c3aa-6449-4ff54a82353f.png
new file mode 100644
index 0000000000000000000000000000000000000000..2bc14912b5abb76bffbdac845251de8cd6ffbd85
Binary files /dev/null and b/TP/TP01/P06/img/a13c8786-beda-c3aa-6449-4ff54a82353f.png differ
diff --git a/TP/TP01/P06/img/a15d4d58-d871-6be4-2ce5-0c61ab7c9ae8.png b/TP/TP01/P06/img/a15d4d58-d871-6be4-2ce5-0c61ab7c9ae8.png
new file mode 100644
index 0000000000000000000000000000000000000000..2fa32344d8253b70a1e844a17ed6f1765ed85a3b
Binary files /dev/null and b/TP/TP01/P06/img/a15d4d58-d871-6be4-2ce5-0c61ab7c9ae8.png differ
diff --git a/TP/TP01/P06/img/a3412540-2a35-9f51-5766-507c274f9c4e.png b/TP/TP01/P06/img/a3412540-2a35-9f51-5766-507c274f9c4e.png
new file mode 100644
index 0000000000000000000000000000000000000000..6af60928ea78e14fa5bbf1ec2b31cd1bf01a8828
Binary files /dev/null and b/TP/TP01/P06/img/a3412540-2a35-9f51-5766-507c274f9c4e.png differ
diff --git a/TP/TP01/P06/img/ac367b60-a112-3de1-c9c4-a6aa62ab4104.png b/TP/TP01/P06/img/ac367b60-a112-3de1-c9c4-a6aa62ab4104.png
new file mode 100644
index 0000000000000000000000000000000000000000..90626f563b0d0ed7b221a31115cdcb43a68871d4
Binary files /dev/null and b/TP/TP01/P06/img/ac367b60-a112-3de1-c9c4-a6aa62ab4104.png differ
diff --git a/TP/TP01/P06/img/b1b7f3d1-a8d1-00ae-d25d-e998d6bcfa1c.png b/TP/TP01/P06/img/b1b7f3d1-a8d1-00ae-d25d-e998d6bcfa1c.png
new file mode 100644
index 0000000000000000000000000000000000000000..1ac4ab1b03184d5fad510acc6550541894ba34a5
Binary files /dev/null and b/TP/TP01/P06/img/b1b7f3d1-a8d1-00ae-d25d-e998d6bcfa1c.png differ
diff --git a/TP/TP01/P06/img/b23dbd78-03f6-9546-8086-f7d03719892c.png b/TP/TP01/P06/img/b23dbd78-03f6-9546-8086-f7d03719892c.png
new file mode 100644
index 0000000000000000000000000000000000000000..5ed53176cc4b88f04003cafaef6d5c1ef7d5e4ed
Binary files /dev/null and b/TP/TP01/P06/img/b23dbd78-03f6-9546-8086-f7d03719892c.png differ
diff --git a/TP/TP01/P06/img/b2bddae4-bdf1-37c1-a226-4058695bd7da.png b/TP/TP01/P06/img/b2bddae4-bdf1-37c1-a226-4058695bd7da.png
new file mode 100644
index 0000000000000000000000000000000000000000..3cf9ddbdb2ffb5df88ad9818050459b04f60c5ff
Binary files /dev/null and b/TP/TP01/P06/img/b2bddae4-bdf1-37c1-a226-4058695bd7da.png differ
diff --git a/TP/TP01/P06/img/b9d6028a-bcab-ac9f-313e-484ef0e934de.png b/TP/TP01/P06/img/b9d6028a-bcab-ac9f-313e-484ef0e934de.png
new file mode 100644
index 0000000000000000000000000000000000000000..aa20a6b98eb22e4f6161345966ad7db81fa99409
Binary files /dev/null and b/TP/TP01/P06/img/b9d6028a-bcab-ac9f-313e-484ef0e934de.png differ
diff --git a/TP/TP01/P06/img/ba5b9967-b9d2-11c0-a118-74e1868f8b27.png b/TP/TP01/P06/img/ba5b9967-b9d2-11c0-a118-74e1868f8b27.png
new file mode 100644
index 0000000000000000000000000000000000000000..ebde888a2d334e458997a08717b1936bdce6fca2
Binary files /dev/null and b/TP/TP01/P06/img/ba5b9967-b9d2-11c0-a118-74e1868f8b27.png differ
diff --git a/TP/TP01/P06/img/bc45d770-bdca-7037-94e1-5ebc99a790df.png b/TP/TP01/P06/img/bc45d770-bdca-7037-94e1-5ebc99a790df.png
new file mode 100644
index 0000000000000000000000000000000000000000..2edc0e8dfbd73510f7bb977f51977f6edf57c884
Binary files /dev/null and b/TP/TP01/P06/img/bc45d770-bdca-7037-94e1-5ebc99a790df.png differ
diff --git a/TP/TP01/P06/img/bf35e754-3248-b1e0-2135-f1f932cc64b8.png b/TP/TP01/P06/img/bf35e754-3248-b1e0-2135-f1f932cc64b8.png
new file mode 100644
index 0000000000000000000000000000000000000000..32eedfed498c1d916411ebd367585d33f2b33136
Binary files /dev/null and b/TP/TP01/P06/img/bf35e754-3248-b1e0-2135-f1f932cc64b8.png differ
diff --git a/TP/TP01/P06/img/cursor-pause.png b/TP/TP01/P06/img/cursor-pause.png
new file mode 100644
index 0000000000000000000000000000000000000000..9c93cc142f4326a188eafc3c0fe96f2975dcab0c
Binary files /dev/null and b/TP/TP01/P06/img/cursor-pause.png differ
diff --git a/TP/TP01/P06/img/cursor.png b/TP/TP01/P06/img/cursor.png
new file mode 100644
index 0000000000000000000000000000000000000000..5b4a20e63078ce18bb11a4e601a1994b261522ef
Binary files /dev/null and b/TP/TP01/P06/img/cursor.png differ
diff --git a/TP/TP01/P06/img/d7949e73-b929-6a59-1897-4f72d87bacc1.png b/TP/TP01/P06/img/d7949e73-b929-6a59-1897-4f72d87bacc1.png
new file mode 100644
index 0000000000000000000000000000000000000000..16808792a0cedf34e72c462172fb44797136bfe1
Binary files /dev/null and b/TP/TP01/P06/img/d7949e73-b929-6a59-1897-4f72d87bacc1.png differ
diff --git a/TP/TP01/P06/img/dea2d804-30cd-960c-f463-85d43ed71728.png b/TP/TP01/P06/img/dea2d804-30cd-960c-f463-85d43ed71728.png
new file mode 100644
index 0000000000000000000000000000000000000000..20399f39e7a77b1f57814b86341f1449448b73d4
Binary files /dev/null and b/TP/TP01/P06/img/dea2d804-30cd-960c-f463-85d43ed71728.png differ
diff --git a/TP/TP01/P06/img/e334e4c2-8999-1d11-f12c-3f0c78205e18.png b/TP/TP01/P06/img/e334e4c2-8999-1d11-f12c-3f0c78205e18.png
new file mode 100644
index 0000000000000000000000000000000000000000..820199db65d42cd9dade22e248149dd8852b8338
Binary files /dev/null and b/TP/TP01/P06/img/e334e4c2-8999-1d11-f12c-3f0c78205e18.png differ
diff --git a/TP/TP01/P06/img/ef2ad1aa-8dd4-0fbb-e149-0bf3fa50128f.png b/TP/TP01/P06/img/ef2ad1aa-8dd4-0fbb-e149-0bf3fa50128f.png
new file mode 100644
index 0000000000000000000000000000000000000000..bf25c32570e934f0dc50bd2201f68e424b2840e9
Binary files /dev/null and b/TP/TP01/P06/img/ef2ad1aa-8dd4-0fbb-e149-0bf3fa50128f.png differ
diff --git a/TP/TP01/P06/img/f170cb4d-cbc5-9b97-65e3-f88a4518fa8e.png b/TP/TP01/P06/img/f170cb4d-cbc5-9b97-65e3-f88a4518fa8e.png
new file mode 100644
index 0000000000000000000000000000000000000000..25a4b4675ca9d7ad57ac1e6d6ff2b856a03f25b1
Binary files /dev/null and b/TP/TP01/P06/img/f170cb4d-cbc5-9b97-65e3-f88a4518fa8e.png differ
diff --git a/TP/TP01/P06/img/f79f8a7d-895d-32f8-126c-288b8e56cdd5.png b/TP/TP01/P06/img/f79f8a7d-895d-32f8-126c-288b8e56cdd5.png
new file mode 100644
index 0000000000000000000000000000000000000000..e2b84f4146f689d4d7cdd5f8eed64974e6851ee0
Binary files /dev/null and b/TP/TP01/P06/img/f79f8a7d-895d-32f8-126c-288b8e56cdd5.png differ
diff --git a/TP/TP01/P06/img/fe0f12ec-4234-8b06-3daf-e42e03d5726d.png b/TP/TP01/P06/img/fe0f12ec-4234-8b06-3daf-e42e03d5726d.png
new file mode 100644
index 0000000000000000000000000000000000000000..2b6cf000ede95a69011accc5bf7ca4e71b040648
Binary files /dev/null and b/TP/TP01/P06/img/fe0f12ec-4234-8b06-3daf-e42e03d5726d.png differ
diff --git a/TP/TP01/P06/index.html b/TP/TP01/P06/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..d202e7594e68ea47b785981a166f667bfd19bb6a
--- /dev/null
+++ b/TP/TP01/P06/index.html
@@ -0,0 +1,817 @@
+<!DOCTYPE html>
+
+<html lang="en">
+
+<head>
+<title>Dahu Presentation
+</title>
+<meta charset="utf-8">
+<script src="http://code.jquery.com/jquery-1.9.1.min.js" type="text/javascript">
+</script>
+<script src="parse-search.js" type="text/javascript">
+</script>
+
+<link rel="stylesheet" href="dahuapp.viewer.css"><style>.s0-o2{background-color:   #FFFFDD;width:  240px}
+.s2-o2{background-color:   #FFFFDD;width:  240px}
+.s3-o2{background-color:   #FFFFDD;width:  240px}
+.s4-o2{background-color:   #FFFFDD;width:  240px}
+.s5-o2{background-color:   #FFFFDD;width:  240px}
+.s6-o2{background-color:   #FFFFDD;width:  240px}
+.s7-o2{background-color:   #FFFFDD;width:  240px}
+.s8-o2{background-color:   #FFFFDD;width:  240px}
+.s9-o2{background-color:   #FFFFDD;width:  240px}
+.s11-o2{background-color:   #FFFFDD;width:  240px}
+.s12-o2{background-color:   #FFFFDD;width:  240px}
+.s13-o2{background-color:   #FFFFDD;width:  240px}
+.s15-o2{background-color:   #FFFFDD;width:  240px}
+.s15-o2{background-color:   #FFFFDD;width:  240px}
+.s16-o2{background-color:   #FFFFDD;width:  240px}
+.s17-o2{background-color:   #FFFFDD;width:  240px}
+.s18-o2{background-color:   #FFFFDD;width:  240px}
+.s19-o2{background-color:   #FFFFDD;width:  240px}
+.s20-o2{background-color:   #FFFFDD;width:  240px}
+.s21-o2{background-color:   #FFFFDD;width:  240px}
+.s22-o2{background-color:   #FFFFDD;width:  240px}
+.s23-o2{background-color:   #FFFFDD;width:  240px}
+.s38-o2{background-color:   #FFFFDD;width:  240px}
+</style>
+</head>
+
+<body>
+
+<div id="my-dahu-presentation">
+
+<div id="loading">Loading presentation...
+</div>
+
+<div class="object-list" style="display: none">
+<img src="img/d7949e73-b929-6a59-1897-4f72d87bacc1.png" alt="img/d7949e73-b929-6a59-1897-4f72d87bacc1.png" class="background d7949e73-b929-6a59-1897-4f72d87bacc10">
+
+<div class="tooltip s0-o2">Créer un nouveau modèle Simulink.
+</div>
+<img src="img/a3412540-2a35-9f51-5766-507c274f9c4e.png" alt="img/a3412540-2a35-9f51-5766-507c274f9c4e.png" class="background a3412540-2a35-9f51-5766-507c274f9c4e0">
+<img src="img/b1b7f3d1-a8d1-00ae-d25d-e998d6bcfa1c.png" alt="img/b1b7f3d1-a8d1-00ae-d25d-e998d6bcfa1c.png" class="background b1b7f3d1-a8d1-00ae-d25d-e998d6bcfa1c0">
+
+<div class="tooltip s2-o2">Sélectionner le bloc Constante dans la boite à outils Simulink.
+</div>
+<img src="img/636bdb73-2612-3933-1f1b-6022d6e33625.png" alt="img/636bdb73-2612-3933-1f1b-6022d6e33625.png" class="background 636bdb73-2612-3933-1f1b-6022d6e336250">
+
+<div class="tooltip s3-o2">Déposer un bloc Constante pour la gravité g.
+</div>
+<img src="img/b2bddae4-bdf1-37c1-a226-4058695bd7da.png" alt="img/b2bddae4-bdf1-37c1-a226-4058695bd7da.png" class="background b2bddae4-bdf1-37c1-a226-4058695bd7da0">
+
+<div class="tooltip s4-o2">Choisir la valeur [ 0 -9.81 ] pour le bloc Constante. Il s'agit d'un vecteur composée de l'horizontale et la verticale.
+</div>
+<img src="img/44cbf940-d047-a39e-6fee-2d91ef83f91c.png" alt="img/44cbf940-d047-a39e-6fee-2d91ef83f91c.png" class="background 44cbf940-d047-a39e-6fee-2d91ef83f91c0">
+
+<div class="tooltip s5-o2">Sélectionner le bloc Integrator.
+</div>
+<img src="img/2ab20dfd-b528-4e65-28d0-5bbe0ce71d26.png" alt="img/2ab20dfd-b528-4e65-28d0-5bbe0ce71d26.png" class="background 2ab20dfd-b528-4e65-28d0-5bbe0ce71d260">
+
+<div class="tooltip s6-o2">Positionner un bloc Integrator pour calculer la vitesse à partir de l'accéleration.
+</div>
+<img src="img/bf35e754-3248-b1e0-2135-f1f932cc64b8.png" alt="img/bf35e754-3248-b1e0-2135-f1f932cc64b8.png" class="background bf35e754-3248-b1e0-2135-f1f932cc64b80">
+
+<div class="tooltip s7-o2">Positionner un bloc Integrator pour calculer la position du mobile à partir de la vitesse.
+</div>
+<img src="img/37c23d3f-b713-19c2-ff10-7e177eb69c38.png" alt="img/37c23d3f-b713-19c2-ff10-7e177eb69c38.png" class="background 37c23d3f-b713-19c2-ff10-7e177eb69c380">
+
+<div class="tooltip s8-o2">Configurer le bloc Integrator pour fournir la valeur initiale depuis un port externe.
+</div>
+<img src="img/f79f8a7d-895d-32f8-126c-288b8e56cdd5.png" alt="img/f79f8a7d-895d-32f8-126c-288b8e56cdd5.png" class="background f79f8a7d-895d-32f8-126c-288b8e56cdd50">
+
+<div class="tooltip s9-o2">Configurer le bloc Integrator pour fournir la valeur initiale depuis un port externe.
+</div>
+<img src="img/4daad903-6dbe-5e6c-cc1b-12ad9749eb53.png" alt="img/4daad903-6dbe-5e6c-cc1b-12ad9749eb53.png" class="background 4daad903-6dbe-5e6c-cc1b-12ad9749eb530">
+<img src="img/8e345aed-81be-c51b-678b-a64a5a106b65.png" alt="img/8e345aed-81be-c51b-678b-a64a5a106b65.png" class="background 8e345aed-81be-c51b-678b-a64a5a106b650">
+
+<div class="tooltip s11-o2">Déposer deux blocs Constante pour les valeurs initiales de la vitesse et de la position.
+</div>
+<img src="img/05133e21-8ad0-1342-3568-c459d579b14a.png" alt="img/05133e21-8ad0-1342-3568-c459d579b14a.png" class="background 05133e21-8ad0-1342-3568-c459d579b14a0">
+
+<div class="tooltip s12-o2">Nommer les blocs Constant G, V0 et P0.
+</div>
+<img src="img/f170cb4d-cbc5-9b97-65e3-f88a4518fa8e.png" alt="img/f170cb4d-cbc5-9b97-65e3-f88a4518fa8e.png" class="background f170cb4d-cbc5-9b97-65e3-f88a4518fa8e0">
+
+<div class="tooltip s13-o2">Choisir la valeur [ 10 10 ] pour le bloc Constante correspondant à la vitesse initiale. Il s'agit d'un vecteur composée de l'horizontale et la verticale.
+</div>
+<img src="img/30445c44-4357-cb29-da5c-dd8a9418ce31.png" alt="img/30445c44-4357-cb29-da5c-dd8a9418ce31.png" class="background 30445c44-4357-cb29-da5c-dd8a9418ce310">
+
+<div class="tooltip s15-o2">Choisir la valeur [ 0 20 ] pour le bloc Constante correspondant à la position initiale. Il s'agit d'un vecteur composée de l'horizontale et la verticale.
+</div>
+<img src="img/9bc4b9d8-0bc8-9842-65c2-708b6c282bbe.png" alt="img/9bc4b9d8-0bc8-9842-65c2-708b6c282bbe.png" class="background 9bc4b9d8-0bc8-9842-65c2-708b6c282bbe0">
+
+<div class="tooltip s15-o2">Connecter les ports d'entrée et de sortie des différents blocs.
+</div>
+<img src="img/24ac7258-308f-3469-7ff3-73cd1714356b.png" alt="img/24ac7258-308f-3469-7ff3-73cd1714356b.png" class="background 24ac7258-308f-3469-7ff3-73cd1714356b0">
+
+<div class="tooltip s16-o2">Déposer un bloc Oscilloscope.
+</div>
+<img src="img/28cc4893-5425-5aac-05e8-a7e32ca109b8.png" alt="img/28cc4893-5425-5aac-05e8-a7e32ca109b8.png" class="background 28cc4893-5425-5aac-05e8-a7e32ca109b80">
+
+<div class="tooltip s17-o2">Connecter les ports d'entrée et de sortie.
+</div>
+<img src="img/571c8ded-4b8d-dfed-d850-b84ae3ec961e.png" alt="img/571c8ded-4b8d-dfed-d850-b84ae3ec961e.png" class="background 571c8ded-4b8d-dfed-d850-b84ae3ec961e0">
+
+<div class="tooltip s18-o2">Démarrer la simulation. L'écran de l'oscilloscope affiche les deux valeurs du vecteur (position horizontale en jaune et verticale en violet).
+</div>
+<img src="img/146e7fd1-79a9-d471-f996-f583ba0fe8f9.png" alt="img/146e7fd1-79a9-d471-f996-f583ba0fe8f9.png" class="background 146e7fd1-79a9-d471-f996-f583ba0fe8f90">
+
+<div class="tooltip s19-o2">Déposer un bloc Oscilloscope X-Y (afficheur 2 dimensions).
+</div>
+<img src="img/8446d790-b98c-3feb-a82e-457056e97ae7.png" alt="img/8446d790-b98c-3feb-a82e-457056e97ae7.png" class="background 8446d790-b98c-3feb-a82e-457056e97ae70">
+
+<div class="tooltip s20-o2">Déposer un bloc Demux qui sépare le vecteur en 2 scalaires.
+</div>
+<img src="img/042b3794-32ee-9789-5a5e-7492c70ea14b.png" alt="img/042b3794-32ee-9789-5a5e-7492c70ea14b.png" class="background 042b3794-32ee-9789-5a5e-7492c70ea14b0">
+
+<div class="tooltip s21-o2">Connecter les deux composantes du vecteur sur les deux entrées du bloc Oscilloscope X-Y.
+</div>
+<img src="img/a13c8786-beda-c3aa-6449-4ff54a82353f.png" alt="img/a13c8786-beda-c3aa-6449-4ff54a82353f.png" class="background a13c8786-beda-c3aa-6449-4ff54a82353f0">
+
+<div class="tooltip s22-o2">Configurer le bloc Oscilloscope X-Y avec le domaine [ 0 100 ] pour l'abscisse et [ -400 50 ] pour l'ordonnée.
+</div>
+<img src="img/b9d6028a-bcab-ac9f-313e-484ef0e934de.png" alt="img/b9d6028a-bcab-ac9f-313e-484ef0e934de.png" class="background b9d6028a-bcab-ac9f-313e-484ef0e934de0">
+
+<div class="tooltip s23-o2">Démarrer la simulation.
+</div>
+<img src="img/ac367b60-a112-3de1-c9c4-a6aa62ab4104.png" alt="img/ac367b60-a112-3de1-c9c4-a6aa62ab4104.png" class="background ac367b60-a112-3de1-c9c4-a6aa62ab41040">
+
+<div class="tooltip s38-o2">Donner un nom à tous les signaux.
+</div>
+
+<div class="mouse-cursor">
+<img src="img/cursor.png" alt="img/cursor.png" class="mouse-cursor-normal">
+<img src="img/cursor-pause.png" alt="img/cursor-pause.png" style="display: none" class="mouse-cursor-pause">
+</div>
+</div>
+
+<div class="control" style="top: 616px;">
+<button class="previous">Previous
+</button>
+<button class="next">Next
+</button>
+</div>
+</div>
+<script src="dahuapp.js" type="text/javascript">
+</script>
+<script src="dahuapp.viewer.js" type="text/javascript">
+</script>
+<script type="text/javascript">(function($) {
+    var myPresentation = dahuapp.viewer.createDahuViewer("#my-dahu-presentation", window.getParams);
+    myPresentation.load({
+    "metaData": {
+        "imageWidth": 800,
+        "imageHeight": 600,
+        "initialBackgroundId": "d7949e73-b929-6a59-1897-4f72d87bacc10",
+        "initialMouseX": 0.20416666666666666,
+        "initialMouseY": 0.08555555555555555
+    },
+    "action": [
+        {
+            "id": "87",
+            "type": "appear",
+            "target": "s0-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.20166666666666666,
+            "ord": 0.0825,
+            "duration": 400
+        },
+        {
+            "id": "3",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.1125,
+            "finalOrd": 0.29,
+            "speed": 0.8
+        },
+        {
+            "id": "4",
+            "type": "appear",
+            "target": "a3412540-2a35-9f51-5766-507c274f9c4e0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "5",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.8958333333333334,
+            "finalOrd": 0.2222222222222222,
+            "speed": 0.8
+        },
+        {
+            "id": "6",
+            "type": "appear",
+            "target": "b1b7f3d1-a8d1-00ae-d25d-e998d6bcfa1c0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "88",
+            "type": "appear",
+            "target": "s2-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.4066666666666667,
+            "ord": 0.11875,
+            "duration": 400
+        },
+        {
+            "id": "7",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.18472222222222223,
+            "finalOrd": 0.2511111111111111,
+            "speed": 0.8
+        },
+        {
+            "id": "8",
+            "type": "appear",
+            "target": "636bdb73-2612-3933-1f1b-6022d6e336250",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "89",
+            "type": "appear",
+            "target": "s3-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.25,
+            "ord": 0.1775,
+            "duration": 400
+        },
+        {
+            "id": "9",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.26319444444444445,
+            "finalOrd": 0.23777777777777778,
+            "speed": 0.8
+        },
+        {
+            "id": "10",
+            "type": "appear",
+            "target": "b2bddae4-bdf1-37c1-a226-4058695bd7da0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "90",
+            "type": "appear",
+            "target": "s4-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.22166666666666668,
+            "ord": 0.265,
+            "duration": 400
+        },
+        {
+            "id": "11",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.9034722222222222,
+            "finalOrd": 0.1688888888888889,
+            "speed": 0.8
+        },
+        {
+            "id": "12",
+            "type": "appear",
+            "target": "44cbf940-d047-a39e-6fee-2d91ef83f91c0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "91",
+            "type": "appear",
+            "target": "s5-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.7916666666666666,
+            "ord": 0.1275,
+            "duration": 400
+        },
+        {
+            "id": "13",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.2902777777777778,
+            "finalOrd": 0.2577777777777778,
+            "speed": 0.8
+        },
+        {
+            "id": "14",
+            "type": "appear",
+            "target": "2ab20dfd-b528-4e65-28d0-5bbe0ce71d260",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "92",
+            "type": "appear",
+            "target": "s6-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.21333333333333335,
+            "ord": 0.18875,
+            "duration": 400
+        },
+        {
+            "id": "15",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.3875,
+            "finalOrd": 0.26555555555555554,
+            "speed": 0.8
+        },
+        {
+            "id": "16",
+            "type": "appear",
+            "target": "bf35e754-3248-b1e0-2135-f1f932cc64b80",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "93",
+            "type": "appear",
+            "target": "s7-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.23,
+            "ord": 0.20625,
+            "duration": 400
+        },
+        {
+            "id": "17",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.4027777777777778,
+            "finalOrd": 0.19666666666666666,
+            "speed": 0.8
+        },
+        {
+            "id": "18",
+            "type": "appear",
+            "target": "37c23d3f-b713-19c2-ff10-7e177eb69c380",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "94",
+            "type": "appear",
+            "target": "s8-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.23833333333333334,
+            "ord": 0.18875,
+            "duration": 400
+        },
+        {
+            "id": "19",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.50625,
+            "finalOrd": 0.19777777777777777,
+            "speed": 0.8
+        },
+        {
+            "id": "20",
+            "type": "appear",
+            "target": "f79f8a7d-895d-32f8-126c-288b8e56cdd50",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "95",
+            "type": "appear",
+            "target": "s9-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.39666666666666667,
+            "ord": 0.15875,
+            "duration": 400
+        },
+        {
+            "id": "21",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.8916666666666667,
+            "finalOrd": 0.21888888888888888,
+            "speed": 0.8
+        },
+        {
+            "id": "22",
+            "type": "appear",
+            "target": "4daad903-6dbe-5e6c-cc1b-12ad9749eb530",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "23",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.18333333333333332,
+            "finalOrd": 0.45555555555555555,
+            "speed": 0.8
+        },
+        {
+            "id": "24",
+            "type": "appear",
+            "target": "8e345aed-81be-c51b-678b-a64a5a106b650",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "96",
+            "type": "appear",
+            "target": "s11-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.28833333333333333,
+            "ord": 0.20875,
+            "duration": 400
+        },
+        {
+            "id": "29",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.18819444444444444,
+            "finalOrd": 0.4811111111111111,
+            "speed": 0.8
+        },
+        {
+            "id": "30",
+            "type": "appear",
+            "target": "05133e21-8ad0-1342-3568-c459d579b14a0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "97",
+            "type": "appear",
+            "target": "s12-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.275,
+            "ord": 0.21,
+            "duration": 400
+        },
+        {
+            "id": "31",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.25416666666666665,
+            "finalOrd": 0.3477777777777778,
+            "speed": 0.8
+        },
+        {
+            "id": "32",
+            "type": "appear",
+            "target": "f170cb4d-cbc5-9b97-65e3-f88a4518fa8e0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "98",
+            "type": "appear",
+            "target": "s13-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.16333333333333333,
+            "ord": 0.3575,
+            "duration": 400
+        },
+        {
+            "id": "35",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.2590277777777778,
+            "finalOrd": 0.42777777777777776,
+            "speed": 0.8
+        },
+        {
+            "id": "36",
+            "type": "appear",
+            "target": "30445c44-4357-cb29-da5c-dd8a9418ce310",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "99",
+            "type": "appear",
+            "target": "s15-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.12833333333333333,
+            "ord": 0.37625,
+            "duration": 400
+        },
+        {
+            "id": "37",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.3680555555555556,
+            "finalOrd": 0.31,
+            "speed": 0.8
+        },
+        {
+            "id": "38",
+            "type": "appear",
+            "target": "9bc4b9d8-0bc8-9842-65c2-708b6c282bbe0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "100",
+            "type": "appear",
+            "target": "s15-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.25,
+            "ord": 0.27125,
+            "duration": 400
+        },
+        {
+            "id": "41",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.5083333333333333,
+            "finalOrd": 0.2722222222222222,
+            "speed": 0.8
+        },
+        {
+            "id": "42",
+            "type": "appear",
+            "target": "24ac7258-308f-3469-7ff3-73cd1714356b0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "101",
+            "type": "appear",
+            "target": "s16-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.485,
+            "ord": 0.19375,
+            "duration": 400
+        },
+        {
+            "id": "43",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.4979166666666667,
+            "finalOrd": 0.27444444444444444,
+            "speed": 0.8
+        },
+        {
+            "id": "44",
+            "type": "appear",
+            "target": "28cc4893-5425-5aac-05e8-a7e32ca109b80",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "102",
+            "type": "appear",
+            "target": "s17-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.44666666666666666,
+            "ord": 0.19375,
+            "duration": 400
+        },
+        {
+            "id": "45",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.2902777777777778,
+            "finalOrd": 0.1,
+            "speed": 0.8
+        },
+        {
+            "id": "46",
+            "type": "appear",
+            "target": "571c8ded-4b8d-dfed-d850-b84ae3ec961e0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "103",
+            "type": "appear",
+            "target": "s18-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.31833333333333336,
+            "ord": 0.4075,
+            "duration": 400
+        },
+        {
+            "id": "47",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.5069444444444444,
+            "finalOrd": 0.3933333333333333,
+            "speed": 0.8
+        },
+        {
+            "id": "48",
+            "type": "appear",
+            "target": "146e7fd1-79a9-d471-f996-f583ba0fe8f90",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "104",
+            "type": "appear",
+            "target": "s19-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.455,
+            "ord": 0.2575,
+            "duration": 400
+        },
+        {
+            "id": "49",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.4375,
+            "finalOrd": 0.3611111111111111,
+            "speed": 0.8
+        },
+        {
+            "id": "50",
+            "type": "appear",
+            "target": "8446d790-b98c-3feb-a82e-457056e97ae70",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "105",
+            "type": "appear",
+            "target": "s20-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.365,
+            "ord": 0.255,
+            "duration": 400
+        },
+        {
+            "id": "51",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.45694444444444443,
+            "finalOrd": 0.44666666666666666,
+            "speed": 0.8
+        },
+        {
+            "id": "52",
+            "type": "appear",
+            "target": "042b3794-32ee-9789-5a5e-7492c70ea14b0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "106",
+            "type": "appear",
+            "target": "s21-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.3016666666666667,
+            "ord": 0.2825,
+            "duration": 400
+        },
+        {
+            "id": "53",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.5770833333333333,
+            "finalOrd": 0.5411111111111111,
+            "speed": 0.8
+        },
+        {
+            "id": "54",
+            "type": "appear",
+            "target": "a13c8786-beda-c3aa-6449-4ff54a82353f0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "107",
+            "type": "appear",
+            "target": "s22-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.58,
+            "ord": 0.38125,
+            "duration": 400
+        },
+        {
+            "id": "55",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.2875,
+            "finalOrd": 0.1,
+            "speed": 0.8
+        },
+        {
+            "id": "56",
+            "type": "appear",
+            "target": "b9d6028a-bcab-ac9f-313e-484ef0e934de0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "108",
+            "type": "appear",
+            "target": "s23-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.16166666666666665,
+            "ord": 0.2875,
+            "duration": 400
+        },
+        {
+            "id": "85",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.2965277777777778,
+            "finalOrd": 0.49666666666666665,
+            "speed": 0.8
+        },
+        {
+            "id": "86",
+            "type": "appear",
+            "target": "ac367b60-a112-3de1-c9c4-a6aa62ab41040",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "109",
+            "type": "appear",
+            "target": "s38-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.27,
+            "ord": 0.2875,
+            "duration": 400
+        }
+    ]
+});
+    myPresentation.start();
+})(jQuery);
+
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/TP/TP01/P06/parse-search.js b/TP/TP01/P06/parse-search.js
new file mode 100644
index 0000000000000000000000000000000000000000..e7d01c07f6a5d45b9fcabd5d271a8c6cfc5f00e2
--- /dev/null
+++ b/TP/TP01/P06/parse-search.js
@@ -0,0 +1,14 @@
+// Adapted from http://stackoverflow.com/questions/3234125/creating-array-from-window-location-hash
+(function () {
+    var search = window.location.search.slice(1);
+    var array = search.split("&");
+    
+    var values, form_data = {};
+    
+    for (var i = 0; i < array.length; i += 1) {
+	values = array[i].split("=");
+	form_data[values[0]] = unescape(values[1]);
+    }
+    window.getParams = form_data;
+}())
+
diff --git a/TP/TP01/P07/dahuapp.js b/TP/TP01/P07/dahuapp.js
new file mode 100644
index 0000000000000000000000000000000000000000..41c69e9169345a9a6703325d5557a4cc36913e19
--- /dev/null
+++ b/TP/TP01/P07/dahuapp.js
@@ -0,0 +1,880 @@
+"use strict";
+
+/**
+ * Dahuapp core module.
+ *
+ * @param   window      window javascript object.
+ * @param   $           jQuery
+ * @returns dahuapp core module.
+ */
+(function (window, $) {
+    var dahuapp = (function () {
+
+        var self = {};
+
+        /* private API */
+
+        var DahuScreencastGenerator = function () {
+
+            /*
+             * Format the specified string in a readable format.
+             */
+            function htmlFormat(htmlString) {
+                var i;
+                var readableHTML = htmlString;
+                var lb = '\n';
+                var htags = ["<html", "</html>", "</head>", "<title", "</title>", "<meta", "<link", "</body>"];
+                for (i = 0; i < htags.length; ++i) {
+                    var hhh = htags[i];
+                    readableHTML = readableHTML.replace(new RegExp(hhh, 'gi'), lb + hhh);
+                }
+                var btags = ["</div>", "</section>", "</span>", "<br>", "<br />", "<blockquote", "</blockquote>", "<ul", "</ul>", "<ol", "</ol>", "<li", "<\!--", "<script", "</script>"];
+                for (i = 0; i < btags.length; ++i) {
+                    var bbb = btags[i];
+                    readableHTML = readableHTML.replace(new RegExp(bbb, 'gi'), lb + bbb);
+                }
+                var ftags = ["<img", "<legend", "</legend>", "<button", "</button>"];
+                for (i = 0; i < ftags.length; ++i) {
+                    var fff = ftags[i];
+                    readableHTML = readableHTML.replace(new RegExp(fff, 'gi'), lb + fff);
+                }
+                var xtags = ["<body", "<head", "<div", "<section", "<span", "<p"];
+                for (i = 0; i < xtags.length; ++i) {
+                    var xxx = xtags[i];
+                    readableHTML = readableHTML.replace(new RegExp(xxx, 'gi'), lb + lb + xxx);
+                }
+                return readableHTML;
+            }
+
+            /*
+             * Generates the html header.
+             */
+            var generateHtmlHeader = function ($generated, cssGen) {
+                $('head', $generated)
+                    .append($(document.createElement('title'))
+                        .append("Dahu Presentation"))/* TODO: make this customizable */
+                    .append($(document.createElement('meta'))
+                        .attr({'charset': 'utf-8'}))
+                    .append($(document.createElement('script'))
+                        .attr({'src': 'http://code.jquery.com/jquery-1.9.1.min.js'})
+                        .attr({'type': 'text/javascript'}))
+                    .append($(document.createElement('script'))
+                        .attr({'src': 'parse-search.js'})
+                        .attr({'type': 'text/javascript'}))
+                    .append($(document.createElement('link'))
+                        .attr({'rel': 'stylesheet', 'href': 'dahuapp.viewer.css'}))
+                    .append($(document.createElement('style'))
+                        .append(cssGen));
+            };
+
+            /*
+             * Generates the objects.
+             */
+
+            var generateHtmlBackgroundImage = function ($generated, object) {
+                $('.object-list', $generated)
+                    .append($(document.createElement('img'))
+                        .attr({'src': object.img, 'alt': object.img, 'class': 'background ' + object.id}));
+            };
+            var generateHtmlTooltip = function ($generated, object) {
+                $('.object-list', $generated)
+                    .append($(document.createElement('div'))
+                        .attr({'class': 'tooltip ' + object.id})
+                        .append(object.text));
+            };
+
+            var generateCssTooltip = function ($generated, object) {
+                var style = [];
+
+                if( object.color != null ) {
+                    style.push('background-color:   ' + object.color);
+                }
+                if( object.width != null ) {
+                    style.push('width:  ' + object.width);
+                }
+
+                if( style.length != 0 ) {
+                    $generated.append(
+                    '.' + object.id + '{' + style.join(';') + '}\n');
+                }
+
+                return $generated;
+            };
+
+            /*
+             * Returns the code used to call the viewer to the presentation.
+             */
+            var getBasicCallCode = function (jsonModel) {
+                var code = '(function($) {\n';
+                code += '    var myPresentation = dahuapp.viewer.createDahuViewer("#my-dahu-presentation", window.getParams);\n';
+                code += '    myPresentation.load(' + jsonModel + ');\n';
+                code += '    myPresentation.start();\n';
+                code += '})(jQuery);\n';
+                return code;
+            };
+
+            /*
+             * Generates the html body.
+             */
+            var generateHtmlBody = function ($generated, jsonModel, jsonGen) {
+                $('body', $generated)
+                    /* We could use a <section> here too, but it does not work with MS IE 8.
+                     Alternatively, adding this in the header would work:
+
+                     <!--[if lt IE 9]>
+                     <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
+                     <![endif]-->
+                     */
+                    .append($(document.createElement('div'))
+                        .attr({'id': 'my-dahu-presentation'}))
+                    .append($(document.createElement('script'))
+                        .attr({'src': 'dahuapp.js'})
+                        .attr({'type': 'text/javascript'}))
+                    .append($(document.createElement('script'))
+                        .attr({'src': 'dahuapp.viewer.js'})
+                        .attr({'type': 'text/javascript'}))
+                    .append($(document.createElement('script'))
+                        .attr({'type': 'text/javascript'})
+                        .append(getBasicCallCode(jsonGen)));
+                $('#my-dahu-presentation', $generated)
+                    .append($(document.createElement('div'))
+                        .attr({'id': 'loading'}).append("Loading presentation..."))
+                    .append($(document.createElement('div'))
+                        .attr({'class': 'object-list',
+                            'style': 'display: none'}))
+                    .append($(document.createElement('div'))
+                        .attr({'class': 'control'}));
+
+                /* Adding the objects to the page */
+                $.each(jsonModel.getObjectList(), function (id, object) {
+                    switch (object.type) {
+                        case "background":
+                            generateHtmlBackgroundImage($generated, object);
+                            break;
+                        case "tooltip":
+                            generateHtmlTooltip($generated, object);
+                            break;
+                        /* no mouse image generated here */
+                    }
+                });
+
+                /* Warning here, i'm not sure if $.each is always over, here */
+
+                /* Adding the control buttons to the page */
+                $('.control', $generated)
+                    .css({'top': (jsonModel.getImageHeight() + 16) + 'px'})
+                    .append($(document.createElement('button'))
+                        .attr({'class': 'previous'})
+                        .append('Previous'))
+                    .append($(document.createElement('button'))
+                        .attr({'class': 'next'})
+                        .append('Next'));
+
+                /* Adding the mouse cursor image */
+                $('.object-list', $generated)
+                    .append($(document.createElement('div'))
+                        .attr({'class': 'mouse-cursor'})
+                        .append($(document.createElement('img'))
+                            .attr({'src': 'img/cursor.png', 'alt': 'img/cursor.png', 'class': 'mouse-cursor-normal'}))
+                        .append($(document.createElement('img'))
+                            .attr({'src': 'img/cursor-pause.png', 'alt': 'img/cursor-pause.png',
+                                'style': 'display: none', 'class': 'mouse-cursor-pause'})));
+            };
+
+            /*
+             * Generates the css String with the Json model.
+             */
+            this.generateCssString = function (jsonModel) {
+                var $generated = $('<style></style>');
+
+                /* Going through each object */
+                $.each(jsonModel.getObjectList(), function (id, object) {
+                    switch (object.type) {
+                        case "tooltip":
+                            generateCssTooltip($generated, object);
+                            break;
+                        /* no mouse image generated here */
+                    }
+                });
+
+                return $generated.text();
+            }
+
+            /*
+             * Generates the html String with the Json model.
+             */
+            this.generateHtmlString = function (jsonModel, jsonGen, cssGen) {
+                /* Initialising the compilation area */
+                var $generated = $(document.createElement('div'));
+
+                /* We create the html using the json */
+                $generated.append($(document.createElement('html'))
+                    .attr({'lang': 'en'}));
+                $('html', $generated)
+                    .append($(document.createElement('head')))
+                    .append($(document.createElement('body')));
+
+                generateHtmlHeader($generated, cssGen);
+                generateHtmlBody($generated, jsonModel, jsonGen);
+
+                var result = htmlFormat($generated.html());
+
+                return '<!DOCTYPE html>\n' + result;
+            };
+
+            /*
+             * Generates the generated JSON using the JSONmodel.
+             * @param {object} jsonModel The json model to transform.
+             * @param {java.awt.Dimension} imgDim The dimension of images.
+             */
+            this.generateJsonString = function (jsonModel, imgDim) {
+                var generated = self.createScreencastGeneratedModel();
+                generated.setImageSize(imgDim.width, imgDim.height);
+                generated.setInitialBackground(jsonModel.getInitialBackground());
+                generated.setInitialMousePos(jsonModel.getInitialMousePos());
+                for (var i = 0; i < jsonModel.getNbSlide(); i++) {
+                    var actionList = jsonModel.getActionList(i);
+                    for (var j = 0; j < actionList.length; j++) {
+                        /* 
+                         * We don't add the two first actions of the first slide
+                         * because they are present in the presentation metadata.
+                         * It corresponds to the mouse initial pos and first background.
+                         */
+                        if (i > 0 || j > 1) {
+                            generated.addAction(actionList[j], imgDim.width, imgDim.height);
+                        }
+                    }
+                }
+                return generated.getJson();
+            };
+        };
+
+        /*
+         * Model for a JSON object that will only be used by the viewer, never
+         * saved on a file, used to transform the properties of actions
+         * specified on the JSON file of a presentation to executable
+         * functions for each action.
+         */
+        var DahuScreencastExecutableModel = function () {
+
+            /* Private API */
+
+            var json = {
+                metaData: {},
+                action: new Array()
+            };
+
+            /*
+             * Creates a functions representing the specified action, and adds
+             * it to this object.
+             * @param {object} action
+             */
+            var addExecutableAction = function (action) {
+                var executableAction = {
+                    'id': action.id,
+                    'trigger': action.trigger,
+                    'target': action.target,
+                    'delayAfter': action.delayAfter,
+                    'doneFunction': function (events, selector) {
+                        setTimeout(function () {
+                            events.onActionOver.publish(events, selector);
+                        }, this.delayAfter);
+                    }
+                };
+                if (executableAction.delayAfter == null) {
+                    executableAction.delayAfter = 200;
+                }
+                switch (action.type.toLowerCase()) {
+                    case "appear":
+                        executableAction.abs = (action.abs * json.metaData.imageWidth) + 'px';
+                        executableAction.ord = (action.ord * json.metaData.imageHeight) + 'px';
+                        executableAction.duration = action.duration;
+                        executableAction.execute = function (events, selector) {
+                            events.onActionStart.publish(events, selector);
+                            var sel = selector + ' .' + this.target;
+                            $(sel).css({
+                                'left': this.abs,
+                                'top': this.ord
+                            });
+                            $(sel).show(this.duration, function () {
+                                executableAction.doneFunction(events, selector);
+                            });
+                        };
+                        executableAction.executeReverse = function (selector) {
+                            $(selector + ' .' + this.target).hide();
+                        };
+                        executableAction.executeImmediately = function (selector) {
+                            var sel = selector + ' .' + this.target;
+                            $(sel).css({
+                                'left': this.abs,
+                                'top': this.ord
+                            });
+                            $(sel).show();
+                        };
+                        break;
+                    case "disappear":
+                        executableAction.duration = action.duration;
+                        executableAction.execute = function (events, selector) {
+                            events.onActionStart.publish(events, selector);
+                            $(selector + ' .' + this.target).hide(this.duration, function () {
+                                executableAction.doneFunction(events, selector);
+                            });
+                        };
+                        executableAction.executeReverse = function (selector) {
+                            $(selector + ' .' + this.target).show();
+                        };
+                        executableAction.executeImmediately = function (selector) {
+                            $(selector + ' .' + this.target).hide();
+                        };
+                        break;
+                    case "move":
+                        executableAction.finalAbs = (action.finalAbs * json.metaData.imageWidth) + 'px';
+                        executableAction.finalOrd = (action.finalOrd * json.metaData.imageHeight) + 'px';
+                        executableAction.duration = action.duration;
+                        executableAction.speed = action.speed;
+                        executableAction.execute = function (events, selector) {
+                            events.onActionStart.publish(events, selector);
+                            var sel = selector + ' .' + this.target;
+                            this.initialAbs = $(sel).css('left');
+                            this.initialOrd = $(sel).css('top');
+                            if (this.duration == null) {
+                                var initialAbsPix = this.initialAbs.replace('px', '');
+                                var initialOrdPix = this.initialOrd.replace('px', '');
+                                var finalAbsPix = this.finalAbs.replace('px', '');
+                                var finalOrdPix = this.finalOrd.replace('px', '');
+                                var distance = Math.sqrt(Math.pow(finalAbsPix - initialAbsPix, 2) +
+                                    Math.pow(finalOrdPix - initialOrdPix, 2));
+                                if (!this.speed) {
+                                    this.speed = .8; // in pixel per milisecond: fast, but not too much.
+                                }
+                                this.duration = distance + this.speed;
+                                if (this.duration < 200) { // Slow down a bit for short distances.
+                                    this.duration = 200;
+                                }
+                            }
+                            $(sel).animate({
+                                'left': this.finalAbs,
+                                'top': this.finalOrd
+                            }, this.duration, 'linear', function () {
+                                executableAction.doneFunction(events, selector);
+                            });
+                        };
+                        executableAction.executeReverse = function (selector) {
+                            $(selector + ' .' + this.target).css({
+                                'left': this.initialAbs,
+                                'top': this.initialOrd
+                            });
+                        };
+                        executableAction.executeImmediately = function (selector) {
+                            var sel = selector + ' .' + this.target;
+                            this.initialAbs = $(sel).css('left');
+                            this.initialOrd = $(sel).css('top');
+                            $(sel).css({
+                                'left': this.finalAbs,
+                                'top': this.finalOrd
+                            });
+                        };
+                        break;
+                    case "delay":
+                        executableAction.duration = action.duration;
+                        executableAction.execute = function (events, selector) {
+                            events.onActionStart.publish(events, selector);
+                            setTimeout(function () {
+                                events.onActionOver.publish(events, selector);
+                            }, this.duration);
+                        };
+                        executableAction.executeReverse = function (selector) {
+                            // Nothing!
+                        };
+                        executableAction.executeImmediately = function (selector) {
+                            // Nothing too!
+                        };
+                        break;
+                }
+                json.action.push(executableAction);
+            };
+
+            /* Public API */
+
+            /*
+             * Loads the specified JSON read from a 'presentation.json' file,
+             * and stores the functions representing each actions in an object.
+             * @param {object} jsonToLoad
+             */
+            this.loadJson = function (jsonToLoad) {
+                json.metaData = jsonToLoad.metaData;
+                for (var i = 0; i < jsonToLoad.action.length; i++) {
+                    addExecutableAction(jsonToLoad.action[i]);
+                }
+            };
+
+            /*
+             * Returns the object containing the functions.
+             */
+            this.getJson = function () {
+                return json;
+            };
+        };
+
+        /*
+         * Used to transform the 'dahu' file to a JSON file used by the viewer,
+         * which only contains some metaData and a list of actions.
+         */
+        var DahuScreencastGeneratedModel = function () {
+
+            /* Private API */
+
+            var json = {
+                metaData: {},
+                action: new Array()
+            };
+
+            /* Public API */
+
+            /*
+             * Returns a string representation of this json.
+             * @returns {String}
+             */
+            this.getJson = function () {
+                return JSON.stringify(json, null, '    ');
+            };
+
+            /*
+             * Setters for the generated json metadata.
+             */
+            this.setImageSize = function (width, height) {
+                json.metaData.imageWidth = width;
+                json.metaData.imageHeight = height;
+            };
+
+            this.setInitialMousePos = function (pos) {
+                json.metaData.initialMouseX = pos.x;
+                json.metaData.initialMouseY = pos.y;
+            };
+
+            this.setInitialBackground = function (id) {
+                json.metaData.initialBackgroundId = id;
+            };
+
+            this.addAction = function (action) {
+                json.action.push(action);
+            };
+
+            /*
+             * Transforms a JSON containing action properties to a JSON containing
+             * the execution functions.
+             * @param {Object} json
+             * @returns {Object} A json object containing the execution functions
+             * for all the actions of the presentation.
+             */
+            this.toExecutableList = function (json) {
+                var executableList = {
+                    metaData: json.metaData,
+                    action: new Array()
+                };
+                for (var i = 0; i < json.action.length; i++) {
+                    addExecutableAction(executableList, json.action[i]);
+                }
+                return executableList;
+            };
+        };
+
+        /*
+         * Represents a 'dahu' file for a project.
+         */
+        var DahuScreencastModel = function () {
+
+            /* Private API */
+
+            var json = {};
+
+            /*
+             * Generates a unique action ID.
+             *
+             * With this implementation, this ID is unique as long as the user
+             * doesn't replace it manually with a not kind value or replace
+             * the nextUniqueId with bad intentions.
+             * But maybe that a UUID is a bit tiresome to put as an anchor...
+             */
+            var generateUniqueActionId = function () {
+                json.metaData.nextUniqueId++;
+                return json.metaData.nextUniqueId.toString();
+            };
+
+            /*
+             * This method checks if the json representing a dahu project is
+             * in the good version. It's in case a project file was created
+             * with a previous version of Dahu, and that some fields are
+             * missing.
+             *
+             * It doesn't control each field (at the moment) but only the
+             * fields that can be missing due to a new Dahu version and not
+             * to a manual editing.
+             *
+             * This method doesn't check any Json syntax or something like that.
+             */
+            var upgradeJsonVersion = function () {
+                // Checks if unique IDs are in the project file
+                if (!json.metaData.nextUniqueId) {
+                    var currentId = 0;
+                    for (var i = 0; i < json.data.length; i++) {
+                        for (var j = 0; j < json.data[i].action.length; j++) {
+                            json.data[i].action[j].id = currentId.toString();
+                            currentId++;
+                        }
+                    }
+                    json.metaData.nextUniqueId = currentId;
+                }
+            };
+
+            /* Public API */
+
+            /*
+             * json variable generated from a JSON file.
+             * @param String stringJson String loaded from JSON file.
+             */
+            this.loadJson = function (stringJson) {
+                json = JSON.parse(stringJson);
+                upgradeJsonVersion();
+            };
+
+            /*
+             * Create a new presentation variable in the JSON file which will contain slides.
+             */
+            this.createPresentation = function (width, height) {
+                json.metaData = {};
+                json.metaData.imageWidth = width;
+                json.metaData.imageHeight = height;
+                json.metaData.nextUniqueId = 0;
+                json.data = new Array();
+            };
+
+            /*
+             * Add a new slide in the presentation variable of the JSON file.
+             * @param int index wanted for the slide.
+             * @param String idSlide Unique identifier for the slide.
+             * @param String img Related to pathname of the image.
+             * @param double mouseX Abscissa mouse position in %.
+             * @param double mouseY Ordinate mouse position in %.
+             * @return Index of the newly added slide.
+             */
+            this.addSlide = function (indexSlide, idSlide, img, mouseX, mouseY, speed) {
+                var slide = {
+                    "object": new Array(),
+                    "action": new Array()
+                };
+                json.data.splice(indexSlide, 0, slide);
+                this.addObject(indexSlide, "background", idSlide, img);
+                this.addObject(indexSlide, "mouse");
+                this.addAction(indexSlide, "move", json.data[indexSlide].object[1].id, "onClick", mouseX, mouseY, speed);
+                this.addAction(indexSlide, "appear", json.data[indexSlide].object[0].id, "afterPrevious");
+            };
+
+            /*
+             * Object factory.
+             * Add a new Object in the slide idSlide.
+             * @param int idSlide
+             * @param string type
+             * Other params can be specified depending on the object's type.
+             */
+            this.addObject = function (idSlide, type) {
+				var object = {
+                    "type": type
+                };
+                switch (type.toLowerCase()) {
+                    case "background":
+                        object.id = arguments[2] + json.data[idSlide].object.length;
+                        object.img = arguments[3] || "";
+                        break;
+                    case "mouse":
+                        object.id = "mouse-cursor";
+                        break;
+                    case "tooltip":
+                        /*
+                         * TODO: we'll need a more robust unique name
+                         * when we start actually using this.
+                         */
+                        object.id = "s" + idSlide + "-o" + json.data[idSlide].object.length;
+                        object.text = arguments[2] || "";
+                        object.color = arguments[3] || null;
+						object.width = arguments[4] || "";
+						json.data[idSlide].object.push(object);
+						var objectLength = json.data[idSlide].object.length;
+						var last = json.data[idSlide].object[objectLength-1];
+						json.data[idSlide].object[objectLength-1] = 
+								json.data[idSlide].object[objectLength-2];
+						json.data[idSlide].object[objectLength-2] = last;
+                        break;
+                }
+				if(type.toLowerCase() != "tooltip"){
+					json.data[idSlide].object.push(object);
+				}
+            };
+
+            /*
+             * Action factory.
+             * Add a new Action in the slide idSlide whose target is the id of an object.
+             * Three types of trigger : "withPrevious", "afterPrevious", "onClick".
+             * @param int idSlide
+             * @param string type
+             * @param string target
+             * @param string trigger
+             * Other params can be specified depending on the object's type.
+             */
+            this.addAction = function (idSlide, type, target, trigger) {
+                var action = {
+                    "id": generateUniqueActionId(),
+                    "type": type,
+                    "target": target,
+                    "trigger": trigger
+                };
+                switch (type.toLowerCase()) {
+                    case "appear":
+                        action.abs = arguments[4] || 0.0;
+                        action.ord = arguments[5] || 0.0;
+                        action.duration = arguments[6] || 0;
+                        break;
+                    case "disappear":
+                        action.duration = arguments[4] || 0;
+                        break;
+                    case "move":
+                        action.finalAbs = arguments[4] || 0.0;
+                        action.finalOrd = arguments[5] || 0.0;
+                        action.speed = arguments[6] || 0;
+                        break;
+                }
+                json.data[idSlide].action.push(action);
+            };
+
+            this.editMouse = function (idSlide, idAction, mouseX, mouseY) {
+                json.data[idSlide].action[idAction].finalAbs = mouseX;
+                json.data[idSlide].action[idAction].finalOrd = mouseY;
+            };
+
+            /*
+             * Sets a title for the presentation.
+             * @param String title Title to set.
+             */
+            this.setTitle = function (title) {
+                json.metaData.title = title;
+            };
+
+            /*
+             * Sets an annotation for the presentation.
+             * @param String annotation Annotation to set.
+             */
+            this.setAnnotation = function (annotation) {
+                json.metaData.annotation = annotation;
+            };
+
+            /*
+             * Inverts the two slides (their positions on the table).
+             * @param int idSlide1 Index of the first slide.
+             * @param int idSlide2 Index of the second slide.
+             */
+            this.invertSlides = function (idSlide1, idSlide2) {
+                var tmp = json.data[idSlide1];
+                json.data[idSlide1] = json.data[idSlide2];
+                json.data[idSlide2] = tmp;
+            };
+
+            /*
+             * Inverts the two actions (their positions on the table).
+             * @param int idSlide
+             * @param int idAction1
+             * @param int idAction2
+             */
+            this.invertActions = function (idSlide, idAction1, idAction2) {
+                var tmp = json.data[idSlide].action[idAction1];
+                json.data[idSlide].action[idAction1] = json.data[idSlide].action[idAction2];
+                json.data[idSlide].action[idAction2] = tmp;
+            };
+
+            /*
+             * Returns the actions on the specified slide.
+             * @returns {Array}
+             */
+            this.getActionList = function (idSlide) {
+                return json.data[idSlide].action;
+            };
+
+            /*
+             * Catches all the objects of the presentation
+             * @returns {Array} List of objects of the presentation
+             */
+            this.getObjectList = function () {
+                var objectList = new Array();
+                var indexSlide = 0;
+                while (json.data[indexSlide]) {
+                    var indexObject = 0;
+                    while (json.data[indexSlide].object[indexObject]) {
+                        objectList.push(json.data[indexSlide].object[indexObject]);
+                        indexObject++;
+                    }
+                    indexSlide++;
+                }
+                return objectList;
+            };
+
+            /*
+             * Returns an array containing all the background images.
+             * @returns {Array} List of background images.
+             */
+            this.getImageList = function () {
+                var list = new Array();
+                var indexSlide = 0;
+                while (json.data[indexSlide]) {
+                    list.push(json.data[indexSlide].object[0].img);
+                    indexSlide++;
+                }
+                ;
+                return list;
+            };
+
+            /*
+             * Returns an object containing the id of the first background.
+             * @returns {string} Id of the first background.
+             */
+            this.getInitialBackground = function () {
+                return json.data[0].object[0].id;
+            };
+
+            /*
+             * Returns an object containing the initial mouse position.
+             * @returns {object} Initial position of mouse.
+             */
+            this.getInitialMousePos = function () {
+                var pos = {};
+                pos.x = json.data[0].action[0].finalAbs;
+                pos.y = json.data[0].action[0].finalOrd;
+                return pos;
+            };
+
+            /*
+             * Removes the slide at the specified index.
+             * @param {int} idSlide
+             */
+            this.removeSlide = function (idSlide) {
+                json.data.splice(idSlide, 1);
+            };
+
+            /*
+             * Removes the action at the specified slide.
+             * @param {int} idSlide
+             * @param {int} idAction
+             */
+            this.removeAction = function (idSlide, idAction) {
+                json.data[idSlide].action.splice(idAction, 1);
+            };
+
+            /*
+             * Removes the specified object from the slide.
+             * Also removes all the actions attached to this object.
+             * @param {int} idSlide
+             * @param {int} idObject
+             */
+            this.removeObject = function (idSlide, idObject) {
+                var removed = json.data[idSlide].object.splice(idObject, 1);
+                for (var i = 0; i < json.data.length; i++) {
+                    var j = 0;
+                    while (json.data[i].action[j]) {
+                        if (json.data[i].action[j].target === removed.id) {
+                            json.data[i].action.splice(j, 1);
+                        } else {
+                            j++;
+                        }
+                    }
+                }
+            };
+
+            /*
+             * @returns {String}
+             */
+            this.getJson = function () {
+                return JSON.stringify(json, null, '    ');
+            };
+
+            /*
+             * @returns {String} returns the index for the next slide.
+             */
+            this.getNbSlide = function () {
+                return json.data.length;
+            };
+
+            /*
+             * @return {object} returns the object identified by idSlide
+             */
+            this.getSlide = function (idSlide) {
+                return json.data[idSlide];
+            };
+
+            /*
+             * Sets the size of images for the generated presentation.
+             * The parameters can be null : it means there are no
+             * requirement for this dimension.
+             * @param {int} width New width for the generated images.
+             * @param {int} height New height for the generated images.
+             */
+            this.setImageSizeRequirements = function (width, height) {
+                json.metaData.imageWidth = width;
+                json.metaData.imageHeight = height;
+            };
+
+            /*
+             * Gets the width of images for the generated presentation.
+             * @return {int or null} Width of generated images.
+             */
+            this.getImageWidth = function () {
+                return json.metaData.imageWidth;
+            };
+
+            /*
+             * Gets the height of images for the generated presentation.
+             * @return {int or null} Height of generated images.
+             */
+            this.getImageHeight = function () {
+                return json.metaData.imageHeight;
+            };
+
+            /*
+             * Gets a background image (no one in particular, the first met).
+             * @return {string} The name of a background image.
+             */
+            this.getABackgroundImage = function () {
+                for (var i = 0; i < json.data.length; i++) {
+                    for (var j = 0; j < json.data[i].object.length; j++) {
+                        if (json.data[i].object[j].type === "background") {
+                            return json.data[i].object[j].img;
+                        }
+                    }
+                }
+                return null;
+            };
+        };
+
+        /* public API */
+
+        self.version = "0.0.1";
+
+        self.createScreencastGenerator = function createScreencastGenerator() {
+            return new DahuScreencastGenerator();
+        };
+
+        self.createScreencastModel = function createScreencastModel() {
+            return new DahuScreencastModel();
+        };
+
+        self.createScreencastExecutableModel = function createScreencastExecutableModel() {
+            return new DahuScreencastExecutableModel();
+        };
+
+        self.createScreencastGeneratedModel = function createScreencastGeneratedModel() {
+            return new DahuScreencastGeneratedModel();
+        };
+
+        return self;
+    })();
+
+    window.dahuapp = dahuapp;
+
+})(window, jQuery);
\ No newline at end of file
diff --git a/TP/TP01/P07/dahuapp.viewer.css b/TP/TP01/P07/dahuapp.viewer.css
new file mode 100644
index 0000000000000000000000000000000000000000..ea934576dad6ca9263a6163de58f5fe4534d1f0b
--- /dev/null
+++ b/TP/TP01/P07/dahuapp.viewer.css
@@ -0,0 +1,25 @@
+.object-list {
+    position: absolute;
+    margin: 0px;
+    padding: 0px;
+}
+
+.mouse-cursor {
+    position: absolute;
+}
+
+.control {
+    position: relative;
+}
+
+.background {
+    position: absolute;
+}
+
+.tooltip {
+    position: absolute;
+    width: 100px;
+    padding: 4px;
+    margin: 2px;
+    border: 2px black solid;
+}
\ No newline at end of file
diff --git a/TP/TP01/P07/dahuapp.viewer.js b/TP/TP01/P07/dahuapp.viewer.js
new file mode 100644
index 0000000000000000000000000000000000000000..12ad03b2cdc503a8fd6ff3f5fdc2a74edbb36edb
--- /dev/null
+++ b/TP/TP01/P07/dahuapp.viewer.js
@@ -0,0 +1,456 @@
+"use strict";
+
+/**
+ * Dahuapp viewer module.
+ * 
+ * @param   dahuapp     dahuapp object to augment with module.
+ * @param   $           jQuery
+ * @returns dahuapp extended with viewer module.
+ */
+
+(function(dahuapp, $) {
+    var viewer = (function() {
+
+        var self = {};
+
+        var DahuViewerModel = function(select, getParams) {
+
+            /* Private API */
+
+            var json = null;
+            var selector = select;
+            var parseAutoOption = function(name, defaultValue) {
+                var res = getParams[name];
+                if (res == null) {
+                    if (name == 'auto') {
+                        return false;
+                    } else {
+                        return parseAutoOption('auto', defaultValue);
+                    }
+                }
+                if (res.toLowerCase() === 'false') {
+                    return false;
+                }
+                res = parseInt(res);
+                if (isNaN(res)) {
+                    // e.g. ?autoplay or ?autoplay=true
+                    return defaultValue;
+                }
+                return res;
+            }
+
+            /* Whether to wait for "next" event between actions */
+            var autoPlay = parseAutoOption('autoplay', 5000);
+            /* Whether to wait for "next" event on page load */
+            var autoStart = parseAutoOption('autostart', 5000);
+            /* Whether to loop back to start at the end of presentation */
+            var autoLoop = parseAutoOption('autoloop', 5000);
+
+            var events = (function() {
+                var self = {};
+                /*
+                 * Creates a generic event.
+                 */
+                var createEvent = function() {
+                    var callbacks = $.Callbacks();
+                    return {
+                        publish: callbacks.fire,
+                        subscribe: callbacks.add,
+                        unsubscribe: callbacks.remove,
+                        unsubscribeAll: callbacks.empty
+                    };
+                };
+                /*
+                 * Called when the button next is pressed.
+                 */
+                self.onNext = createEvent();
+                /*
+                 * Called when the button previous is pressed.
+                 */
+                self.onPrevious = createEvent();
+                /*
+                 * Called when an action is over.
+                 */
+                self.onActionOver = createEvent();
+                /*
+                 * Called when an action starts.
+                 */
+                self.onActionStart = createEvent();
+                /*
+                 * Called when at least one action was running and has finished.
+                 */
+                self.onAllActionFinish = createEvent();
+                
+                return self;
+            })();
+
+            /*
+             * Variables used like index for methodes subscribed.
+             */
+            var currentAction = 0;    /* Action currently running */
+            var nextAction = 0;       /* Action to execute on 'Next' click */
+            var nbActionsRunning = 0;
+            var previousAnchor = -1;  /* Last entered anchor, only used in
+                                       * case the 'onhashchange' event is
+                                       * not supported by the web browser */
+            
+            /*
+             * Functions to reinitialise running actions and subscribed
+             * callbacks (reinitialise is called once without stopping
+             * all the actions, so the two functions are separated).
+             */
+            var stopAllActions = function() {
+                $(selector + " .object-list").children().stop(true, true);
+                reinitialiseCallbackLists();
+                nbActionsRunning = 0;
+            };
+            var reinitialiseCallbackLists = function() {
+                events.onActionStart.unsubscribeAll();
+                events.onActionStart.subscribe(onActionStartEventHandler);
+                events.onAllActionFinish.unsubscribeAll();
+            };
+            
+            /*
+             * Timer used to program onNext event in autoplay mode.
+             */
+            var playTimer = 0;
+
+            /*
+             * Function used when an "onNextEvent" event is caught.
+             */
+            var onNextEventHandler = function() {
+                /*
+                 * If the user pressed "next" before an autoplay event
+                 * is triggered, it replaces the autoplay event, hence
+                 * cancels it:
+                 */
+                window.clearTimeout(playTimer);
+
+                enterAnimationMode();
+                stopAllActions();
+                var tmpAction = nextAction;
+                var onlyWithPrevious = true;
+                nextAction++;
+                currentAction = nextAction;
+                while (json.action[currentAction] && onlyWithPrevious) {
+                    switch (json.action[currentAction].trigger) {
+                        case 'onClick':
+                            nextAction = currentAction;
+                            onlyWithPrevious = false;
+                            break;
+                        case 'withPrevious':
+                            var tmp = currentAction;
+                            /*
+                             * We can't directly pass 'execute' as callback because we
+                             * allow the 'execute' function to reference a property of the
+                             * object (by using 'this.property') so we have to call the
+                             * function in the containing object to make that available
+                             */
+                            events.onActionStart.subscribe(function(events, selector) {
+                                json.action[tmp].execute(events, selector);
+                            });
+                            break;
+                        case 'afterPrevious':
+                            var tmp = currentAction;
+                            events.onAllActionFinish.subscribe(function(events, selector) {
+                                json.action[tmp].execute(events, selector);
+                            });
+                            while (json.action[nextAction] && json.action[nextAction].trigger !== 'onClick') {
+                                nextAction++;
+                            }
+                            onlyWithPrevious = false;
+                            break;
+                    }
+                    currentAction++;
+                }
+                if (json.action[tmpAction]) {
+                    launch(json.action[tmpAction]);
+                }
+                if (nextAction > json.action.length) {
+                    nextAction = json.action.length;
+                }
+            };
+            
+            /*
+             * Function used when an "onPreviousEvent" event is caught.
+             */
+            var onPreviousEventHandler = function() {
+                stopAllActions();
+                currentAction = nextAction - 1;
+                while (json.action[currentAction] && json.action[currentAction].trigger !== 'onClick') {
+                    launchReverse(json.action[currentAction]);
+                    currentAction--;
+                }
+                /* currentAction = -1 means that it's the beginning */
+                if (currentAction > -1) {
+                    launchReverse(json.action[currentAction]);
+                }
+                if (currentAction < 0) {
+                    currentAction = 0;
+                }
+                nextAction = currentAction;
+            };
+
+            /*
+             * Function used when an "onActionOverEvent" event is caught.
+             */
+            var onActionOverEventHandler = function() {
+                nbActionsRunning--;
+                if (nbActionsRunning === 0) {
+                    events.onAllActionFinish.publish(events, selector);
+                    reinitialiseCallbackLists();
+                    while (json.action[currentAction]) {
+                        switch (json.action[currentAction].trigger) {
+                            case 'onClick':
+                                leaveAnimationMode();
+                                if (autoPlay && nbActionsRunning === 0) {
+                                    playTimer = setTimeout(function () {
+                                        events.onNext.publish();
+                                    }, autoPlay);
+                                }
+                                return;
+                            case 'withPrevious':
+                                var tmp = currentAction;
+                                events.onActionStart.subscribe(function(events, selector) {
+                                    json.action[tmp].execute(events, selector);
+                                });
+                                break;
+                            case 'afterPrevious':
+                                var tmp = currentAction;
+                                events.onAllActionFinish.subscribe(function(events, selector) {
+                                    json.action[tmp].execute(events, selector);
+                                });
+                                currentAction++;
+                                return;
+                        }
+                        currentAction++;
+
+                    }
+                    if (autoLoop && nbActionsRunning === 0) {
+                        setTimeout(function () {
+                            resetPresentation();
+                            startPresentationMaybe();
+                        }, autoLoop);
+                    }
+                }
+                leaveAnimationMode();
+            };
+
+            /*
+             * Function used when an "onActionStartEvent" event is caught.
+             */
+            var onActionStartEventHandler = function() {
+                nbActionsRunning++;
+            };
+
+            /*
+             * Enter and leave "animation" mode. The viewer is in
+             * animation mode when something is going on without human
+             * interaction (i.e. executing actions before the next
+             * "onClick").
+             */
+            var enterAnimationMode = function () {
+                $(selector + ' .mouse-cursor-pause').hide();
+                $(selector + ' .mouse-cursor-normal').show();
+            };
+
+            var leaveAnimationMode = function () {
+                $(selector + ' .mouse-cursor-pause').show();
+                $(selector + ' .mouse-cursor-normal').hide();
+            };
+
+            /*
+             * Function used to perform actions.
+             */
+            var launch = function(action) {
+                action.execute(events, selector);
+            };
+            var launchReverse = function(action) {
+                action.executeReverse(selector);
+            };
+
+            /*
+             * The two following methods each returns an array containing the
+             * actions to execute to reach the given anchor (respectively
+             * forward or backwards).
+             *
+             * If the given anchor matches none of the actions, then an empty
+             * array is returned.
+             */
+            var getActionsToJumpForward = function(anchor) {
+                var actions = new Array();
+                for (var i = nextAction; i < json.action.length; i++) {
+                    // '==' and not '===' because we compare indifferently a
+                    // number or a string or anything else
+                    if (json.action[i].id == anchor) {
+                        return actions;
+                    } else {
+                        actions.push(json.action[i]);
+                    }
+                }
+                // Here, the anchor has not been found during the action scan
+                return null;
+            };
+            var getActionsToJumpBackwards = function(anchor) {
+                var actions = new Array();
+                for (var i = nextAction - 1; i >= 0; i--) {
+                    // '==' and not '===' because we compare indifferently a
+                    // number or a string or anything else
+                    actions.push(json.action[i]);
+                    if (json.action[i].id == anchor) {
+                        return actions;
+                    }
+                }
+                // Here, the anchor has not been found during the action scan
+                return null;
+            };
+            
+            /*
+             * Updates the position of the presentation depending on the
+             * given anchor (next action wanted).
+             */
+            var jumpToAnchor = function(anchor) {
+                if (anchor !== '') {
+                    stopAllActions();
+                    if (anchor > nextAction) {
+                        // forward
+                        var actions = getActionsToJumpForward(anchor);
+                        for (var i = 0; i < actions.length; i++) {
+                            actions[i].executeImmediately(selector);
+                        }
+                        nextAction += actions.length;
+                    } else {
+                        // backwards
+                        var actions = getActionsToJumpBackwards(anchor);
+                        for (var i = 0; i < actions.length; i++) {
+                            actions[i].executeReverse(selector);
+                        }
+                        nextAction -= actions.length;
+                    }
+                }
+            };
+
+            var resetPresentation = function() {
+                window.clearTimeout(playTimer);
+
+                currentAction = 0;
+                nextAction = 0;
+                nbActionsRunning = 0;
+
+                /*
+                 * At the beginning, the visible image is the first one of the presentation
+                 */
+                $(selector + " .object-list").children().hide();
+
+                $(selector + " ." + json.metaData.initialBackgroundId).show();
+
+                $(selector + " .mouse-cursor").css({
+                    'top': (json.metaData.initialMouseY * json.metaData.imageHeight) + "px",
+                    'left': (json.metaData.initialMouseX * json.metaData.imageWidth) + "px"
+                });
+
+                $(selector + " .mouse-cursor").show();
+            }
+
+            var startPresentationMaybe = function() {
+                if (autoStart) {
+                    playTimer = setTimeout(function () {
+                        events.onNext.publish();
+                    }, autoStart);
+                }
+            }
+
+            /* Public API */
+
+            this.loadUrl = function(url) {
+                var dataJson;
+                $.ajax({
+                    'async': false,
+                    'global': false,
+                    'url': url,
+                    'dataType': "json",
+                    'success': function(data) {
+                        dataJson = data;
+                    }
+                });
+                
+                load(dataJson);
+            };
+
+            this.load = function(dataJson) {
+                /* Transforms data JSON to executable functions for actions */
+                var execModel = dahuapp.createScreencastExecutableModel();
+                execModel.loadJson(dataJson);
+                json = execModel.getJson();
+            };
+
+            this.start = function() {
+                
+                /*
+                 * Subscription of methods to their events.
+                 */
+                events.onNext.subscribe(onNextEventHandler);
+                events.onPrevious.subscribe(onPreviousEventHandler);
+                events.onActionOver.subscribe(onActionOverEventHandler);
+                events.onActionStart.subscribe(onActionStartEventHandler);
+
+                resetPresentation();
+
+                /*
+                 * If an anchor has been specified, we place the presentation
+                 * in the right position.
+                 */
+                jumpToAnchor(window.location.hash.substring(1));
+                
+                /*
+                 * If the anchor changes during the presentation, then the
+                 * presentation is updated
+                 */
+                if ("onhashchange" in window) {
+                    // event supported
+                    window.onhashchange = function () {
+                        jumpToAnchor(window.location.hash.substring(1));
+                    };
+                } else {
+                    // event not supported : periodical check
+                    window.setInterval(function () {
+                        if (window.location.hash.substring(1) !== previousAnchor) {
+                            previousAnchor = window.location.hash.substring(1);
+                            jumpToAnchor(window.location.hash.substring(1));
+                        }
+                    }, 100);
+                }
+
+                /*
+                 * A click on the "next" button publishes a nextSlide event
+                 */
+                $(selector + " .next").click(function() {
+                    events.onNext.publish();
+                });
+                
+                /*
+                 * A click on the "previous" button publishes a previousSlide event
+                 */
+                $(selector + " .previous").click(function() {
+                    events.onPrevious.publish();
+                });
+
+                /*
+                 * Everything is ready, show the presentation
+                 * (hidden with style="display: none" from HTML).
+                 */
+                $("#loading").hide();
+                $(selector + " .object-list").show();
+                startPresentationMaybe();
+            };
+        };
+
+        self.createDahuViewer = function(selector, getParams) {
+            return new DahuViewerModel(selector, getParams);
+        };
+
+        return self;
+    })();
+
+    dahuapp.viewer = viewer;
+})(dahuapp || {}, jQuery);
diff --git a/TP/TP01/P07/img/19b1d70a-cda5-4dcc-39dc-39cc6ba9db54.png b/TP/TP01/P07/img/19b1d70a-cda5-4dcc-39dc-39cc6ba9db54.png
new file mode 100644
index 0000000000000000000000000000000000000000..df3f343f817bc7a8c9cc0aafedfa9dcdf977f624
Binary files /dev/null and b/TP/TP01/P07/img/19b1d70a-cda5-4dcc-39dc-39cc6ba9db54.png differ
diff --git a/TP/TP01/P07/img/35b70b02-d1ba-934a-f7ae-8c67f5b6d591.png b/TP/TP01/P07/img/35b70b02-d1ba-934a-f7ae-8c67f5b6d591.png
new file mode 100644
index 0000000000000000000000000000000000000000..fd9aa65497315f24f529a59113a25700cc8c2105
Binary files /dev/null and b/TP/TP01/P07/img/35b70b02-d1ba-934a-f7ae-8c67f5b6d591.png differ
diff --git a/TP/TP01/P07/img/383e00ab-046d-3c6a-1b47-afb0f883d8d6.png b/TP/TP01/P07/img/383e00ab-046d-3c6a-1b47-afb0f883d8d6.png
new file mode 100644
index 0000000000000000000000000000000000000000..2e45eada327109ab094698d543044ff0ec70b769
Binary files /dev/null and b/TP/TP01/P07/img/383e00ab-046d-3c6a-1b47-afb0f883d8d6.png differ
diff --git a/TP/TP01/P07/img/3e70d3db-b54f-2223-ff57-003273c7f1fd.png b/TP/TP01/P07/img/3e70d3db-b54f-2223-ff57-003273c7f1fd.png
new file mode 100644
index 0000000000000000000000000000000000000000..6d6429eeef4aedc8b53da9087c45faee341c708a
Binary files /dev/null and b/TP/TP01/P07/img/3e70d3db-b54f-2223-ff57-003273c7f1fd.png differ
diff --git a/TP/TP01/P07/img/42c31ce5-a47e-ad8f-d1b7-12191c9d464b.png b/TP/TP01/P07/img/42c31ce5-a47e-ad8f-d1b7-12191c9d464b.png
new file mode 100644
index 0000000000000000000000000000000000000000..1823147cd445934dc31d948105636924adba135a
Binary files /dev/null and b/TP/TP01/P07/img/42c31ce5-a47e-ad8f-d1b7-12191c9d464b.png differ
diff --git a/TP/TP01/P07/img/494d5fbc-7710-81cb-2bd1-9b7f5e04cfbb.png b/TP/TP01/P07/img/494d5fbc-7710-81cb-2bd1-9b7f5e04cfbb.png
new file mode 100644
index 0000000000000000000000000000000000000000..e7812f5bd814a42e72ccf713141cf5ad64877c58
Binary files /dev/null and b/TP/TP01/P07/img/494d5fbc-7710-81cb-2bd1-9b7f5e04cfbb.png differ
diff --git a/TP/TP01/P07/img/5303de8b-2766-351a-f426-c109e4df9d32.png b/TP/TP01/P07/img/5303de8b-2766-351a-f426-c109e4df9d32.png
new file mode 100644
index 0000000000000000000000000000000000000000..76d27cb1f9c791369dfb8f247381582588f428bd
Binary files /dev/null and b/TP/TP01/P07/img/5303de8b-2766-351a-f426-c109e4df9d32.png differ
diff --git a/TP/TP01/P07/img/54037052-0fd4-1be4-b371-cac4e8c5f7ca.png b/TP/TP01/P07/img/54037052-0fd4-1be4-b371-cac4e8c5f7ca.png
new file mode 100644
index 0000000000000000000000000000000000000000..89355470649d0a7d0b957595b6c9ed58d866f4eb
Binary files /dev/null and b/TP/TP01/P07/img/54037052-0fd4-1be4-b371-cac4e8c5f7ca.png differ
diff --git a/TP/TP01/P07/img/6b93f0f7-10f2-8bb5-85a6-428b72c183cb.png b/TP/TP01/P07/img/6b93f0f7-10f2-8bb5-85a6-428b72c183cb.png
new file mode 100644
index 0000000000000000000000000000000000000000..0232afa54a03e3928c7addbba07b4ff9c4557747
Binary files /dev/null and b/TP/TP01/P07/img/6b93f0f7-10f2-8bb5-85a6-428b72c183cb.png differ
diff --git a/TP/TP01/P07/img/6bdbf5c7-a99a-1f31-0046-461682be575b.png b/TP/TP01/P07/img/6bdbf5c7-a99a-1f31-0046-461682be575b.png
new file mode 100644
index 0000000000000000000000000000000000000000..a9c2dbac8ef099da5444e0d9146ee2f09408bc2e
Binary files /dev/null and b/TP/TP01/P07/img/6bdbf5c7-a99a-1f31-0046-461682be575b.png differ
diff --git a/TP/TP01/P07/img/70d9c961-62ff-9470-06da-617bab7e1041.png b/TP/TP01/P07/img/70d9c961-62ff-9470-06da-617bab7e1041.png
new file mode 100644
index 0000000000000000000000000000000000000000..f6709afd85708e444f7492fa9e37b8f01aad4e5e
Binary files /dev/null and b/TP/TP01/P07/img/70d9c961-62ff-9470-06da-617bab7e1041.png differ
diff --git a/TP/TP01/P07/img/826ee375-9184-a716-a352-586fbb1d439d.png b/TP/TP01/P07/img/826ee375-9184-a716-a352-586fbb1d439d.png
new file mode 100644
index 0000000000000000000000000000000000000000..a2b1fff04fa97256ef2080252656d58b35df90e1
Binary files /dev/null and b/TP/TP01/P07/img/826ee375-9184-a716-a352-586fbb1d439d.png differ
diff --git a/TP/TP01/P07/img/87f55d84-813d-19ce-b06f-cf5b79be31d7.png b/TP/TP01/P07/img/87f55d84-813d-19ce-b06f-cf5b79be31d7.png
new file mode 100644
index 0000000000000000000000000000000000000000..3644839b3266d94905c78192f8fa9bd4ce3998c6
Binary files /dev/null and b/TP/TP01/P07/img/87f55d84-813d-19ce-b06f-cf5b79be31d7.png differ
diff --git a/TP/TP01/P07/img/b777af85-02d8-11ad-969c-cf3c589cd92c.png b/TP/TP01/P07/img/b777af85-02d8-11ad-969c-cf3c589cd92c.png
new file mode 100644
index 0000000000000000000000000000000000000000..62c78ced34f90d859c921f1dbe01366e2de00e93
Binary files /dev/null and b/TP/TP01/P07/img/b777af85-02d8-11ad-969c-cf3c589cd92c.png differ
diff --git a/TP/TP01/P07/img/cf8c5592-1b83-11db-194b-4e27e48986f3.png b/TP/TP01/P07/img/cf8c5592-1b83-11db-194b-4e27e48986f3.png
new file mode 100644
index 0000000000000000000000000000000000000000..a2b1fff04fa97256ef2080252656d58b35df90e1
Binary files /dev/null and b/TP/TP01/P07/img/cf8c5592-1b83-11db-194b-4e27e48986f3.png differ
diff --git a/TP/TP01/P07/img/cursor-pause.png b/TP/TP01/P07/img/cursor-pause.png
new file mode 100644
index 0000000000000000000000000000000000000000..9c93cc142f4326a188eafc3c0fe96f2975dcab0c
Binary files /dev/null and b/TP/TP01/P07/img/cursor-pause.png differ
diff --git a/TP/TP01/P07/img/cursor.png b/TP/TP01/P07/img/cursor.png
new file mode 100644
index 0000000000000000000000000000000000000000..5b4a20e63078ce18bb11a4e601a1994b261522ef
Binary files /dev/null and b/TP/TP01/P07/img/cursor.png differ
diff --git a/TP/TP01/P07/img/eb2b9cec-88ce-fd11-0b2d-c0d1706b65ec.png b/TP/TP01/P07/img/eb2b9cec-88ce-fd11-0b2d-c0d1706b65ec.png
new file mode 100644
index 0000000000000000000000000000000000000000..a2b1fff04fa97256ef2080252656d58b35df90e1
Binary files /dev/null and b/TP/TP01/P07/img/eb2b9cec-88ce-fd11-0b2d-c0d1706b65ec.png differ
diff --git a/TP/TP01/P07/img/f09992e3-2ec1-fc7b-307e-01e4abe14557.png b/TP/TP01/P07/img/f09992e3-2ec1-fc7b-307e-01e4abe14557.png
new file mode 100644
index 0000000000000000000000000000000000000000..5a807adceca77b61b3c5010a101144fff5a88fed
Binary files /dev/null and b/TP/TP01/P07/img/f09992e3-2ec1-fc7b-307e-01e4abe14557.png differ
diff --git a/TP/TP01/P07/index.html b/TP/TP01/P07/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..f4a529889b3ea28b4d084f30bcd808fad96b71ab
--- /dev/null
+++ b/TP/TP01/P07/index.html
@@ -0,0 +1,427 @@
+<!DOCTYPE html>
+
+<html lang="en">
+
+<head>
+<title>Dahu Presentation
+</title>
+<meta charset="utf-8">
+<script src="http://code.jquery.com/jquery-1.9.1.min.js" type="text/javascript">
+</script>
+<script src="parse-search.js" type="text/javascript">
+</script>
+
+<link rel="stylesheet" href="dahuapp.viewer.css"><style>.s0-o2{background-color:   #FFFFDD;width:  240px}
+.s1-o2{background-color:   #FFFFDD;width:  240px}
+.s2-o2{background-color:   #FFFFDD;width:  240px}
+.s3-o2{background-color:   #FFFFDD;width:  240px}
+.s4-o2{background-color:   #FFFFDD;width:  240px}
+.s5-o2{background-color:   #FFFFDD;width:  240px}
+.s6-o2{background-color:   #FFFFDD;width:  240px}
+.s7-o2{background-color:   #FFFFDD;width:  240px}
+.s8-o2{background-color:   #FFFFDD;width:  240px}
+.s9-o2{background-color:   #FFFFDD;width:  240px}
+.s10-o2{background-color:   #FFFFDD;width:  240px}
+.s11-o2{background-color:   #FFFFDD;width:  240px}
+</style>
+</head>
+
+<body>
+
+<div id="my-dahu-presentation">
+
+<div id="loading">Loading presentation...
+</div>
+
+<div class="object-list" style="display: none">
+<img src="img/6bdbf5c7-a99a-1f31-0046-461682be575b.png" alt="img/6bdbf5c7-a99a-1f31-0046-461682be575b.png" class="background 6bdbf5c7-a99a-1f31-0046-461682be575b0">
+
+<div class="tooltip s0-o2">Déposer un bloc Integrator.
+</div>
+<img src="img/494d5fbc-7710-81cb-2bd1-9b7f5e04cfbb.png" alt="img/494d5fbc-7710-81cb-2bd1-9b7f5e04cfbb.png" class="background 494d5fbc-7710-81cb-2bd1-9b7f5e04cfbb0">
+
+<div class="tooltip s1-o2">Déposer un bloc Gain (multiplication par une constante).
+</div>
+<img src="img/19b1d70a-cda5-4dcc-39dc-39cc6ba9db54.png" alt="img/19b1d70a-cda5-4dcc-39dc-39cc6ba9db54.png" class="background 19b1d70a-cda5-4dcc-39dc-39cc6ba9db540">
+
+<div class="tooltip s2-o2">Tourner la représentation graphique du bloc Gain.
+</div>
+<img src="img/f09992e3-2ec1-fc7b-307e-01e4abe14557.png" alt="img/f09992e3-2ec1-fc7b-307e-01e4abe14557.png" class="background f09992e3-2ec1-fc7b-307e-01e4abe145570">
+
+<div class="tooltip s3-o2">Tourner la représentation graphique du bloc Gain.
+</div>
+<img src="img/3e70d3db-b54f-2223-ff57-003273c7f1fd.png" alt="img/3e70d3db-b54f-2223-ff57-003273c7f1fd.png" class="background 3e70d3db-b54f-2223-ff57-003273c7f1fd0">
+
+<div class="tooltip s4-o2">Choisir le contenu de la variable K comme valeur du Gain.
+</div>
+<img src="img/6b93f0f7-10f2-8bb5-85a6-428b72c183cb.png" alt="img/6b93f0f7-10f2-8bb5-85a6-428b72c183cb.png" class="background 6b93f0f7-10f2-8bb5-85a6-428b72c183cb0">
+
+<div class="tooltip s5-o2">Définir la variable K dans MatLab avec la valeur -1.
+</div>
+<img src="img/b777af85-02d8-11ad-969c-cf3c589cd92c.png" alt="img/b777af85-02d8-11ad-969c-cf3c589cd92c.png" class="background b777af85-02d8-11ad-969c-cf3c589cd92c0">
+
+<div class="tooltip s6-o2">Connecter les ports d'entrée et de sortie pour faire une boucle correspondant à l'équation aux dérivées ordinaires (dx/dt + K x = 0).
+</div>
+<img src="img/5303de8b-2766-351a-f426-c109e4df9d32.png" alt="img/5303de8b-2766-351a-f426-c109e4df9d32.png" class="background 5303de8b-2766-351a-f426-c109e4df9d320">
+
+<div class="tooltip s7-o2">Nommer les signaux pour identifier x.
+</div>
+<img src="img/35b70b02-d1ba-934a-f7ae-8c67f5b6d591.png" alt="img/35b70b02-d1ba-934a-f7ae-8c67f5b6d591.png" class="background 35b70b02-d1ba-934a-f7ae-8c67f5b6d5910">
+
+<div class="tooltip s8-o2">Ajouter un bloc Oscilloscope pour observer la valeur de x.
+</div>
+<img src="img/54037052-0fd4-1be4-b371-cac4e8c5f7ca.png" alt="img/54037052-0fd4-1be4-b371-cac4e8c5f7ca.png" class="background 54037052-0fd4-1be4-b371-cac4e8c5f7ca0">
+
+<div class="tooltip s9-o2">Démarrer la simulation. x reste nul car la valeur initiale vaut 0.
+</div>
+<img src="img/42c31ce5-a47e-ad8f-d1b7-12191c9d464b.png" alt="img/42c31ce5-a47e-ad8f-d1b7-12191c9d464b.png" class="background 42c31ce5-a47e-ad8f-d1b7-12191c9d464b0">
+
+<div class="tooltip s10-o2">Modifier la valeur initiale du bloc Integrator.
+</div>
+<img src="img/826ee375-9184-a716-a352-586fbb1d439d.png" alt="img/826ee375-9184-a716-a352-586fbb1d439d.png" class="background 826ee375-9184-a716-a352-586fbb1d439d0">
+
+<div class="tooltip s11-o2">Démarrer la simulation.
+</div>
+
+<div class="mouse-cursor">
+<img src="img/cursor.png" alt="img/cursor.png" class="mouse-cursor-normal">
+<img src="img/cursor-pause.png" alt="img/cursor-pause.png" style="display: none" class="mouse-cursor-pause">
+</div>
+</div>
+
+<div class="control" style="top: 616px;">
+<button class="previous">Previous
+</button>
+<button class="next">Next
+</button>
+</div>
+</div>
+<script src="dahuapp.js" type="text/javascript">
+</script>
+<script src="dahuapp.viewer.js" type="text/javascript">
+</script>
+<script type="text/javascript">(function($) {
+    var myPresentation = dahuapp.viewer.createDahuViewer("#my-dahu-presentation", window.getParams);
+    myPresentation.load({
+    "metaData": {
+        "imageWidth": 800,
+        "imageHeight": 600,
+        "initialBackgroundId": "6bdbf5c7-a99a-1f31-0046-461682be575b0",
+        "initialMouseX": 0.7736111111111111,
+        "initialMouseY": 0.43222222222222223
+    },
+    "action": [
+        {
+            "id": "35",
+            "type": "appear",
+            "target": "s0-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.18666666666666668,
+            "ord": 0.14375,
+            "duration": 400
+        },
+        {
+            "id": "5",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.32083333333333336,
+            "finalOrd": 0.41555555555555557,
+            "speed": 0.8
+        },
+        {
+            "id": "6",
+            "type": "appear",
+            "target": "494d5fbc-7710-81cb-2bd1-9b7f5e04cfbb0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "36",
+            "type": "appear",
+            "target": "s1-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.095,
+            "ord": 0.20375,
+            "duration": 400
+        },
+        {
+            "id": "7",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.5555555555555556,
+            "finalOrd": 0.6522222222222223,
+            "speed": 0.8
+        },
+        {
+            "id": "8",
+            "type": "appear",
+            "target": "19b1d70a-cda5-4dcc-39dc-39cc6ba9db540",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "37",
+            "type": "appear",
+            "target": "s2-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.35833333333333334,
+            "ord": 0.32,
+            "duration": 400
+        },
+        {
+            "id": "9",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.5791666666666667,
+            "finalOrd": 0.6511111111111111,
+            "speed": 0.8
+        },
+        {
+            "id": "10",
+            "type": "appear",
+            "target": "f09992e3-2ec1-fc7b-307e-01e4abe145570",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "38",
+            "type": "appear",
+            "target": "s3-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.34,
+            "ord": 0.32125,
+            "duration": 400
+        },
+        {
+            "id": "11",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.3611111111111111,
+            "finalOrd": 0.38,
+            "speed": 0.8
+        },
+        {
+            "id": "12",
+            "type": "appear",
+            "target": "3e70d3db-b54f-2223-ff57-003273c7f1fd0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "39",
+            "type": "appear",
+            "target": "s4-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.19833333333333333,
+            "ord": 0.19875,
+            "duration": 400
+        },
+        {
+            "id": "13",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.3347222222222222,
+            "finalOrd": 0.3511111111111111,
+            "speed": 0.8
+        },
+        {
+            "id": "14",
+            "type": "appear",
+            "target": "6b93f0f7-10f2-8bb5-85a6-428b72c183cb0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "40",
+            "type": "appear",
+            "target": "s5-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.10333333333333333,
+            "ord": 0.18,
+            "duration": 400
+        },
+        {
+            "id": "15",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.3298611111111111,
+            "finalOrd": 0.2388888888888889,
+            "speed": 0.8
+        },
+        {
+            "id": "16",
+            "type": "appear",
+            "target": "b777af85-02d8-11ad-969c-cf3c589cd92c0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "41",
+            "type": "appear",
+            "target": "s6-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.185,
+            "ord": 0.18125,
+            "duration": 400
+        },
+        {
+            "id": "17",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.3597222222222222,
+            "finalOrd": 0.38555555555555554,
+            "speed": 0.8
+        },
+        {
+            "id": "18",
+            "type": "appear",
+            "target": "5303de8b-2766-351a-f426-c109e4df9d320",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "42",
+            "type": "appear",
+            "target": "s7-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.09833333333333333,
+            "ord": 0.18375,
+            "duration": 400
+        },
+        {
+            "id": "21",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.3951388888888889,
+            "finalOrd": 0.38333333333333336,
+            "speed": 0.8
+        },
+        {
+            "id": "22",
+            "type": "appear",
+            "target": "35b70b02-d1ba-934a-f7ae-8c67f5b6d5910",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "43",
+            "type": "appear",
+            "target": "s8-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.16333333333333333,
+            "ord": 0.195,
+            "duration": 400
+        },
+        {
+            "id": "23",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.17708333333333334,
+            "finalOrd": 0.6644444444444444,
+            "speed": 0.8
+        },
+        {
+            "id": "24",
+            "type": "appear",
+            "target": "54037052-0fd4-1be4-b371-cac4e8c5f7ca0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "44",
+            "type": "appear",
+            "target": "s9-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.17833333333333334,
+            "ord": 0.1825,
+            "duration": 400
+        },
+        {
+            "id": "25",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.3875,
+            "finalOrd": 0.24,
+            "speed": 0.8
+        },
+        {
+            "id": "26",
+            "type": "appear",
+            "target": "42c31ce5-a47e-ad8f-d1b7-12191c9d464b0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "45",
+            "type": "appear",
+            "target": "s10-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.36333333333333334,
+            "ord": 0.13,
+            "duration": 400
+        },
+        {
+            "id": "29",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.1798611111111111,
+            "finalOrd": 0.6666666666666666,
+            "speed": 0.8
+        },
+        {
+            "id": "30",
+            "type": "appear",
+            "target": "826ee375-9184-a716-a352-586fbb1d439d0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "46",
+            "type": "appear",
+            "target": "s11-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.165,
+            "ord": 0.19125,
+            "duration": 400
+        }
+    ]
+});
+    myPresentation.start();
+})(jQuery);
+
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/TP/TP01/P07/parse-search.js b/TP/TP01/P07/parse-search.js
new file mode 100644
index 0000000000000000000000000000000000000000..e7d01c07f6a5d45b9fcabd5d271a8c6cfc5f00e2
--- /dev/null
+++ b/TP/TP01/P07/parse-search.js
@@ -0,0 +1,14 @@
+// Adapted from http://stackoverflow.com/questions/3234125/creating-array-from-window-location-hash
+(function () {
+    var search = window.location.search.slice(1);
+    var array = search.split("&");
+    
+    var values, form_data = {};
+    
+    for (var i = 0; i < array.length; i += 1) {
+	values = array[i].split("=");
+	form_data[values[0]] = unescape(values[1]);
+    }
+    window.getParams = form_data;
+}())
+
diff --git a/TP/TP01/P08/dahuapp.js b/TP/TP01/P08/dahuapp.js
new file mode 100644
index 0000000000000000000000000000000000000000..41c69e9169345a9a6703325d5557a4cc36913e19
--- /dev/null
+++ b/TP/TP01/P08/dahuapp.js
@@ -0,0 +1,880 @@
+"use strict";
+
+/**
+ * Dahuapp core module.
+ *
+ * @param   window      window javascript object.
+ * @param   $           jQuery
+ * @returns dahuapp core module.
+ */
+(function (window, $) {
+    var dahuapp = (function () {
+
+        var self = {};
+
+        /* private API */
+
+        var DahuScreencastGenerator = function () {
+
+            /*
+             * Format the specified string in a readable format.
+             */
+            function htmlFormat(htmlString) {
+                var i;
+                var readableHTML = htmlString;
+                var lb = '\n';
+                var htags = ["<html", "</html>", "</head>", "<title", "</title>", "<meta", "<link", "</body>"];
+                for (i = 0; i < htags.length; ++i) {
+                    var hhh = htags[i];
+                    readableHTML = readableHTML.replace(new RegExp(hhh, 'gi'), lb + hhh);
+                }
+                var btags = ["</div>", "</section>", "</span>", "<br>", "<br />", "<blockquote", "</blockquote>", "<ul", "</ul>", "<ol", "</ol>", "<li", "<\!--", "<script", "</script>"];
+                for (i = 0; i < btags.length; ++i) {
+                    var bbb = btags[i];
+                    readableHTML = readableHTML.replace(new RegExp(bbb, 'gi'), lb + bbb);
+                }
+                var ftags = ["<img", "<legend", "</legend>", "<button", "</button>"];
+                for (i = 0; i < ftags.length; ++i) {
+                    var fff = ftags[i];
+                    readableHTML = readableHTML.replace(new RegExp(fff, 'gi'), lb + fff);
+                }
+                var xtags = ["<body", "<head", "<div", "<section", "<span", "<p"];
+                for (i = 0; i < xtags.length; ++i) {
+                    var xxx = xtags[i];
+                    readableHTML = readableHTML.replace(new RegExp(xxx, 'gi'), lb + lb + xxx);
+                }
+                return readableHTML;
+            }
+
+            /*
+             * Generates the html header.
+             */
+            var generateHtmlHeader = function ($generated, cssGen) {
+                $('head', $generated)
+                    .append($(document.createElement('title'))
+                        .append("Dahu Presentation"))/* TODO: make this customizable */
+                    .append($(document.createElement('meta'))
+                        .attr({'charset': 'utf-8'}))
+                    .append($(document.createElement('script'))
+                        .attr({'src': 'http://code.jquery.com/jquery-1.9.1.min.js'})
+                        .attr({'type': 'text/javascript'}))
+                    .append($(document.createElement('script'))
+                        .attr({'src': 'parse-search.js'})
+                        .attr({'type': 'text/javascript'}))
+                    .append($(document.createElement('link'))
+                        .attr({'rel': 'stylesheet', 'href': 'dahuapp.viewer.css'}))
+                    .append($(document.createElement('style'))
+                        .append(cssGen));
+            };
+
+            /*
+             * Generates the objects.
+             */
+
+            var generateHtmlBackgroundImage = function ($generated, object) {
+                $('.object-list', $generated)
+                    .append($(document.createElement('img'))
+                        .attr({'src': object.img, 'alt': object.img, 'class': 'background ' + object.id}));
+            };
+            var generateHtmlTooltip = function ($generated, object) {
+                $('.object-list', $generated)
+                    .append($(document.createElement('div'))
+                        .attr({'class': 'tooltip ' + object.id})
+                        .append(object.text));
+            };
+
+            var generateCssTooltip = function ($generated, object) {
+                var style = [];
+
+                if( object.color != null ) {
+                    style.push('background-color:   ' + object.color);
+                }
+                if( object.width != null ) {
+                    style.push('width:  ' + object.width);
+                }
+
+                if( style.length != 0 ) {
+                    $generated.append(
+                    '.' + object.id + '{' + style.join(';') + '}\n');
+                }
+
+                return $generated;
+            };
+
+            /*
+             * Returns the code used to call the viewer to the presentation.
+             */
+            var getBasicCallCode = function (jsonModel) {
+                var code = '(function($) {\n';
+                code += '    var myPresentation = dahuapp.viewer.createDahuViewer("#my-dahu-presentation", window.getParams);\n';
+                code += '    myPresentation.load(' + jsonModel + ');\n';
+                code += '    myPresentation.start();\n';
+                code += '})(jQuery);\n';
+                return code;
+            };
+
+            /*
+             * Generates the html body.
+             */
+            var generateHtmlBody = function ($generated, jsonModel, jsonGen) {
+                $('body', $generated)
+                    /* We could use a <section> here too, but it does not work with MS IE 8.
+                     Alternatively, adding this in the header would work:
+
+                     <!--[if lt IE 9]>
+                     <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
+                     <![endif]-->
+                     */
+                    .append($(document.createElement('div'))
+                        .attr({'id': 'my-dahu-presentation'}))
+                    .append($(document.createElement('script'))
+                        .attr({'src': 'dahuapp.js'})
+                        .attr({'type': 'text/javascript'}))
+                    .append($(document.createElement('script'))
+                        .attr({'src': 'dahuapp.viewer.js'})
+                        .attr({'type': 'text/javascript'}))
+                    .append($(document.createElement('script'))
+                        .attr({'type': 'text/javascript'})
+                        .append(getBasicCallCode(jsonGen)));
+                $('#my-dahu-presentation', $generated)
+                    .append($(document.createElement('div'))
+                        .attr({'id': 'loading'}).append("Loading presentation..."))
+                    .append($(document.createElement('div'))
+                        .attr({'class': 'object-list',
+                            'style': 'display: none'}))
+                    .append($(document.createElement('div'))
+                        .attr({'class': 'control'}));
+
+                /* Adding the objects to the page */
+                $.each(jsonModel.getObjectList(), function (id, object) {
+                    switch (object.type) {
+                        case "background":
+                            generateHtmlBackgroundImage($generated, object);
+                            break;
+                        case "tooltip":
+                            generateHtmlTooltip($generated, object);
+                            break;
+                        /* no mouse image generated here */
+                    }
+                });
+
+                /* Warning here, i'm not sure if $.each is always over, here */
+
+                /* Adding the control buttons to the page */
+                $('.control', $generated)
+                    .css({'top': (jsonModel.getImageHeight() + 16) + 'px'})
+                    .append($(document.createElement('button'))
+                        .attr({'class': 'previous'})
+                        .append('Previous'))
+                    .append($(document.createElement('button'))
+                        .attr({'class': 'next'})
+                        .append('Next'));
+
+                /* Adding the mouse cursor image */
+                $('.object-list', $generated)
+                    .append($(document.createElement('div'))
+                        .attr({'class': 'mouse-cursor'})
+                        .append($(document.createElement('img'))
+                            .attr({'src': 'img/cursor.png', 'alt': 'img/cursor.png', 'class': 'mouse-cursor-normal'}))
+                        .append($(document.createElement('img'))
+                            .attr({'src': 'img/cursor-pause.png', 'alt': 'img/cursor-pause.png',
+                                'style': 'display: none', 'class': 'mouse-cursor-pause'})));
+            };
+
+            /*
+             * Generates the css String with the Json model.
+             */
+            this.generateCssString = function (jsonModel) {
+                var $generated = $('<style></style>');
+
+                /* Going through each object */
+                $.each(jsonModel.getObjectList(), function (id, object) {
+                    switch (object.type) {
+                        case "tooltip":
+                            generateCssTooltip($generated, object);
+                            break;
+                        /* no mouse image generated here */
+                    }
+                });
+
+                return $generated.text();
+            }
+
+            /*
+             * Generates the html String with the Json model.
+             */
+            this.generateHtmlString = function (jsonModel, jsonGen, cssGen) {
+                /* Initialising the compilation area */
+                var $generated = $(document.createElement('div'));
+
+                /* We create the html using the json */
+                $generated.append($(document.createElement('html'))
+                    .attr({'lang': 'en'}));
+                $('html', $generated)
+                    .append($(document.createElement('head')))
+                    .append($(document.createElement('body')));
+
+                generateHtmlHeader($generated, cssGen);
+                generateHtmlBody($generated, jsonModel, jsonGen);
+
+                var result = htmlFormat($generated.html());
+
+                return '<!DOCTYPE html>\n' + result;
+            };
+
+            /*
+             * Generates the generated JSON using the JSONmodel.
+             * @param {object} jsonModel The json model to transform.
+             * @param {java.awt.Dimension} imgDim The dimension of images.
+             */
+            this.generateJsonString = function (jsonModel, imgDim) {
+                var generated = self.createScreencastGeneratedModel();
+                generated.setImageSize(imgDim.width, imgDim.height);
+                generated.setInitialBackground(jsonModel.getInitialBackground());
+                generated.setInitialMousePos(jsonModel.getInitialMousePos());
+                for (var i = 0; i < jsonModel.getNbSlide(); i++) {
+                    var actionList = jsonModel.getActionList(i);
+                    for (var j = 0; j < actionList.length; j++) {
+                        /* 
+                         * We don't add the two first actions of the first slide
+                         * because they are present in the presentation metadata.
+                         * It corresponds to the mouse initial pos and first background.
+                         */
+                        if (i > 0 || j > 1) {
+                            generated.addAction(actionList[j], imgDim.width, imgDim.height);
+                        }
+                    }
+                }
+                return generated.getJson();
+            };
+        };
+
+        /*
+         * Model for a JSON object that will only be used by the viewer, never
+         * saved on a file, used to transform the properties of actions
+         * specified on the JSON file of a presentation to executable
+         * functions for each action.
+         */
+        var DahuScreencastExecutableModel = function () {
+
+            /* Private API */
+
+            var json = {
+                metaData: {},
+                action: new Array()
+            };
+
+            /*
+             * Creates a functions representing the specified action, and adds
+             * it to this object.
+             * @param {object} action
+             */
+            var addExecutableAction = function (action) {
+                var executableAction = {
+                    'id': action.id,
+                    'trigger': action.trigger,
+                    'target': action.target,
+                    'delayAfter': action.delayAfter,
+                    'doneFunction': function (events, selector) {
+                        setTimeout(function () {
+                            events.onActionOver.publish(events, selector);
+                        }, this.delayAfter);
+                    }
+                };
+                if (executableAction.delayAfter == null) {
+                    executableAction.delayAfter = 200;
+                }
+                switch (action.type.toLowerCase()) {
+                    case "appear":
+                        executableAction.abs = (action.abs * json.metaData.imageWidth) + 'px';
+                        executableAction.ord = (action.ord * json.metaData.imageHeight) + 'px';
+                        executableAction.duration = action.duration;
+                        executableAction.execute = function (events, selector) {
+                            events.onActionStart.publish(events, selector);
+                            var sel = selector + ' .' + this.target;
+                            $(sel).css({
+                                'left': this.abs,
+                                'top': this.ord
+                            });
+                            $(sel).show(this.duration, function () {
+                                executableAction.doneFunction(events, selector);
+                            });
+                        };
+                        executableAction.executeReverse = function (selector) {
+                            $(selector + ' .' + this.target).hide();
+                        };
+                        executableAction.executeImmediately = function (selector) {
+                            var sel = selector + ' .' + this.target;
+                            $(sel).css({
+                                'left': this.abs,
+                                'top': this.ord
+                            });
+                            $(sel).show();
+                        };
+                        break;
+                    case "disappear":
+                        executableAction.duration = action.duration;
+                        executableAction.execute = function (events, selector) {
+                            events.onActionStart.publish(events, selector);
+                            $(selector + ' .' + this.target).hide(this.duration, function () {
+                                executableAction.doneFunction(events, selector);
+                            });
+                        };
+                        executableAction.executeReverse = function (selector) {
+                            $(selector + ' .' + this.target).show();
+                        };
+                        executableAction.executeImmediately = function (selector) {
+                            $(selector + ' .' + this.target).hide();
+                        };
+                        break;
+                    case "move":
+                        executableAction.finalAbs = (action.finalAbs * json.metaData.imageWidth) + 'px';
+                        executableAction.finalOrd = (action.finalOrd * json.metaData.imageHeight) + 'px';
+                        executableAction.duration = action.duration;
+                        executableAction.speed = action.speed;
+                        executableAction.execute = function (events, selector) {
+                            events.onActionStart.publish(events, selector);
+                            var sel = selector + ' .' + this.target;
+                            this.initialAbs = $(sel).css('left');
+                            this.initialOrd = $(sel).css('top');
+                            if (this.duration == null) {
+                                var initialAbsPix = this.initialAbs.replace('px', '');
+                                var initialOrdPix = this.initialOrd.replace('px', '');
+                                var finalAbsPix = this.finalAbs.replace('px', '');
+                                var finalOrdPix = this.finalOrd.replace('px', '');
+                                var distance = Math.sqrt(Math.pow(finalAbsPix - initialAbsPix, 2) +
+                                    Math.pow(finalOrdPix - initialOrdPix, 2));
+                                if (!this.speed) {
+                                    this.speed = .8; // in pixel per milisecond: fast, but not too much.
+                                }
+                                this.duration = distance + this.speed;
+                                if (this.duration < 200) { // Slow down a bit for short distances.
+                                    this.duration = 200;
+                                }
+                            }
+                            $(sel).animate({
+                                'left': this.finalAbs,
+                                'top': this.finalOrd
+                            }, this.duration, 'linear', function () {
+                                executableAction.doneFunction(events, selector);
+                            });
+                        };
+                        executableAction.executeReverse = function (selector) {
+                            $(selector + ' .' + this.target).css({
+                                'left': this.initialAbs,
+                                'top': this.initialOrd
+                            });
+                        };
+                        executableAction.executeImmediately = function (selector) {
+                            var sel = selector + ' .' + this.target;
+                            this.initialAbs = $(sel).css('left');
+                            this.initialOrd = $(sel).css('top');
+                            $(sel).css({
+                                'left': this.finalAbs,
+                                'top': this.finalOrd
+                            });
+                        };
+                        break;
+                    case "delay":
+                        executableAction.duration = action.duration;
+                        executableAction.execute = function (events, selector) {
+                            events.onActionStart.publish(events, selector);
+                            setTimeout(function () {
+                                events.onActionOver.publish(events, selector);
+                            }, this.duration);
+                        };
+                        executableAction.executeReverse = function (selector) {
+                            // Nothing!
+                        };
+                        executableAction.executeImmediately = function (selector) {
+                            // Nothing too!
+                        };
+                        break;
+                }
+                json.action.push(executableAction);
+            };
+
+            /* Public API */
+
+            /*
+             * Loads the specified JSON read from a 'presentation.json' file,
+             * and stores the functions representing each actions in an object.
+             * @param {object} jsonToLoad
+             */
+            this.loadJson = function (jsonToLoad) {
+                json.metaData = jsonToLoad.metaData;
+                for (var i = 0; i < jsonToLoad.action.length; i++) {
+                    addExecutableAction(jsonToLoad.action[i]);
+                }
+            };
+
+            /*
+             * Returns the object containing the functions.
+             */
+            this.getJson = function () {
+                return json;
+            };
+        };
+
+        /*
+         * Used to transform the 'dahu' file to a JSON file used by the viewer,
+         * which only contains some metaData and a list of actions.
+         */
+        var DahuScreencastGeneratedModel = function () {
+
+            /* Private API */
+
+            var json = {
+                metaData: {},
+                action: new Array()
+            };
+
+            /* Public API */
+
+            /*
+             * Returns a string representation of this json.
+             * @returns {String}
+             */
+            this.getJson = function () {
+                return JSON.stringify(json, null, '    ');
+            };
+
+            /*
+             * Setters for the generated json metadata.
+             */
+            this.setImageSize = function (width, height) {
+                json.metaData.imageWidth = width;
+                json.metaData.imageHeight = height;
+            };
+
+            this.setInitialMousePos = function (pos) {
+                json.metaData.initialMouseX = pos.x;
+                json.metaData.initialMouseY = pos.y;
+            };
+
+            this.setInitialBackground = function (id) {
+                json.metaData.initialBackgroundId = id;
+            };
+
+            this.addAction = function (action) {
+                json.action.push(action);
+            };
+
+            /*
+             * Transforms a JSON containing action properties to a JSON containing
+             * the execution functions.
+             * @param {Object} json
+             * @returns {Object} A json object containing the execution functions
+             * for all the actions of the presentation.
+             */
+            this.toExecutableList = function (json) {
+                var executableList = {
+                    metaData: json.metaData,
+                    action: new Array()
+                };
+                for (var i = 0; i < json.action.length; i++) {
+                    addExecutableAction(executableList, json.action[i]);
+                }
+                return executableList;
+            };
+        };
+
+        /*
+         * Represents a 'dahu' file for a project.
+         */
+        var DahuScreencastModel = function () {
+
+            /* Private API */
+
+            var json = {};
+
+            /*
+             * Generates a unique action ID.
+             *
+             * With this implementation, this ID is unique as long as the user
+             * doesn't replace it manually with a not kind value or replace
+             * the nextUniqueId with bad intentions.
+             * But maybe that a UUID is a bit tiresome to put as an anchor...
+             */
+            var generateUniqueActionId = function () {
+                json.metaData.nextUniqueId++;
+                return json.metaData.nextUniqueId.toString();
+            };
+
+            /*
+             * This method checks if the json representing a dahu project is
+             * in the good version. It's in case a project file was created
+             * with a previous version of Dahu, and that some fields are
+             * missing.
+             *
+             * It doesn't control each field (at the moment) but only the
+             * fields that can be missing due to a new Dahu version and not
+             * to a manual editing.
+             *
+             * This method doesn't check any Json syntax or something like that.
+             */
+            var upgradeJsonVersion = function () {
+                // Checks if unique IDs are in the project file
+                if (!json.metaData.nextUniqueId) {
+                    var currentId = 0;
+                    for (var i = 0; i < json.data.length; i++) {
+                        for (var j = 0; j < json.data[i].action.length; j++) {
+                            json.data[i].action[j].id = currentId.toString();
+                            currentId++;
+                        }
+                    }
+                    json.metaData.nextUniqueId = currentId;
+                }
+            };
+
+            /* Public API */
+
+            /*
+             * json variable generated from a JSON file.
+             * @param String stringJson String loaded from JSON file.
+             */
+            this.loadJson = function (stringJson) {
+                json = JSON.parse(stringJson);
+                upgradeJsonVersion();
+            };
+
+            /*
+             * Create a new presentation variable in the JSON file which will contain slides.
+             */
+            this.createPresentation = function (width, height) {
+                json.metaData = {};
+                json.metaData.imageWidth = width;
+                json.metaData.imageHeight = height;
+                json.metaData.nextUniqueId = 0;
+                json.data = new Array();
+            };
+
+            /*
+             * Add a new slide in the presentation variable of the JSON file.
+             * @param int index wanted for the slide.
+             * @param String idSlide Unique identifier for the slide.
+             * @param String img Related to pathname of the image.
+             * @param double mouseX Abscissa mouse position in %.
+             * @param double mouseY Ordinate mouse position in %.
+             * @return Index of the newly added slide.
+             */
+            this.addSlide = function (indexSlide, idSlide, img, mouseX, mouseY, speed) {
+                var slide = {
+                    "object": new Array(),
+                    "action": new Array()
+                };
+                json.data.splice(indexSlide, 0, slide);
+                this.addObject(indexSlide, "background", idSlide, img);
+                this.addObject(indexSlide, "mouse");
+                this.addAction(indexSlide, "move", json.data[indexSlide].object[1].id, "onClick", mouseX, mouseY, speed);
+                this.addAction(indexSlide, "appear", json.data[indexSlide].object[0].id, "afterPrevious");
+            };
+
+            /*
+             * Object factory.
+             * Add a new Object in the slide idSlide.
+             * @param int idSlide
+             * @param string type
+             * Other params can be specified depending on the object's type.
+             */
+            this.addObject = function (idSlide, type) {
+				var object = {
+                    "type": type
+                };
+                switch (type.toLowerCase()) {
+                    case "background":
+                        object.id = arguments[2] + json.data[idSlide].object.length;
+                        object.img = arguments[3] || "";
+                        break;
+                    case "mouse":
+                        object.id = "mouse-cursor";
+                        break;
+                    case "tooltip":
+                        /*
+                         * TODO: we'll need a more robust unique name
+                         * when we start actually using this.
+                         */
+                        object.id = "s" + idSlide + "-o" + json.data[idSlide].object.length;
+                        object.text = arguments[2] || "";
+                        object.color = arguments[3] || null;
+						object.width = arguments[4] || "";
+						json.data[idSlide].object.push(object);
+						var objectLength = json.data[idSlide].object.length;
+						var last = json.data[idSlide].object[objectLength-1];
+						json.data[idSlide].object[objectLength-1] = 
+								json.data[idSlide].object[objectLength-2];
+						json.data[idSlide].object[objectLength-2] = last;
+                        break;
+                }
+				if(type.toLowerCase() != "tooltip"){
+					json.data[idSlide].object.push(object);
+				}
+            };
+
+            /*
+             * Action factory.
+             * Add a new Action in the slide idSlide whose target is the id of an object.
+             * Three types of trigger : "withPrevious", "afterPrevious", "onClick".
+             * @param int idSlide
+             * @param string type
+             * @param string target
+             * @param string trigger
+             * Other params can be specified depending on the object's type.
+             */
+            this.addAction = function (idSlide, type, target, trigger) {
+                var action = {
+                    "id": generateUniqueActionId(),
+                    "type": type,
+                    "target": target,
+                    "trigger": trigger
+                };
+                switch (type.toLowerCase()) {
+                    case "appear":
+                        action.abs = arguments[4] || 0.0;
+                        action.ord = arguments[5] || 0.0;
+                        action.duration = arguments[6] || 0;
+                        break;
+                    case "disappear":
+                        action.duration = arguments[4] || 0;
+                        break;
+                    case "move":
+                        action.finalAbs = arguments[4] || 0.0;
+                        action.finalOrd = arguments[5] || 0.0;
+                        action.speed = arguments[6] || 0;
+                        break;
+                }
+                json.data[idSlide].action.push(action);
+            };
+
+            this.editMouse = function (idSlide, idAction, mouseX, mouseY) {
+                json.data[idSlide].action[idAction].finalAbs = mouseX;
+                json.data[idSlide].action[idAction].finalOrd = mouseY;
+            };
+
+            /*
+             * Sets a title for the presentation.
+             * @param String title Title to set.
+             */
+            this.setTitle = function (title) {
+                json.metaData.title = title;
+            };
+
+            /*
+             * Sets an annotation for the presentation.
+             * @param String annotation Annotation to set.
+             */
+            this.setAnnotation = function (annotation) {
+                json.metaData.annotation = annotation;
+            };
+
+            /*
+             * Inverts the two slides (their positions on the table).
+             * @param int idSlide1 Index of the first slide.
+             * @param int idSlide2 Index of the second slide.
+             */
+            this.invertSlides = function (idSlide1, idSlide2) {
+                var tmp = json.data[idSlide1];
+                json.data[idSlide1] = json.data[idSlide2];
+                json.data[idSlide2] = tmp;
+            };
+
+            /*
+             * Inverts the two actions (their positions on the table).
+             * @param int idSlide
+             * @param int idAction1
+             * @param int idAction2
+             */
+            this.invertActions = function (idSlide, idAction1, idAction2) {
+                var tmp = json.data[idSlide].action[idAction1];
+                json.data[idSlide].action[idAction1] = json.data[idSlide].action[idAction2];
+                json.data[idSlide].action[idAction2] = tmp;
+            };
+
+            /*
+             * Returns the actions on the specified slide.
+             * @returns {Array}
+             */
+            this.getActionList = function (idSlide) {
+                return json.data[idSlide].action;
+            };
+
+            /*
+             * Catches all the objects of the presentation
+             * @returns {Array} List of objects of the presentation
+             */
+            this.getObjectList = function () {
+                var objectList = new Array();
+                var indexSlide = 0;
+                while (json.data[indexSlide]) {
+                    var indexObject = 0;
+                    while (json.data[indexSlide].object[indexObject]) {
+                        objectList.push(json.data[indexSlide].object[indexObject]);
+                        indexObject++;
+                    }
+                    indexSlide++;
+                }
+                return objectList;
+            };
+
+            /*
+             * Returns an array containing all the background images.
+             * @returns {Array} List of background images.
+             */
+            this.getImageList = function () {
+                var list = new Array();
+                var indexSlide = 0;
+                while (json.data[indexSlide]) {
+                    list.push(json.data[indexSlide].object[0].img);
+                    indexSlide++;
+                }
+                ;
+                return list;
+            };
+
+            /*
+             * Returns an object containing the id of the first background.
+             * @returns {string} Id of the first background.
+             */
+            this.getInitialBackground = function () {
+                return json.data[0].object[0].id;
+            };
+
+            /*
+             * Returns an object containing the initial mouse position.
+             * @returns {object} Initial position of mouse.
+             */
+            this.getInitialMousePos = function () {
+                var pos = {};
+                pos.x = json.data[0].action[0].finalAbs;
+                pos.y = json.data[0].action[0].finalOrd;
+                return pos;
+            };
+
+            /*
+             * Removes the slide at the specified index.
+             * @param {int} idSlide
+             */
+            this.removeSlide = function (idSlide) {
+                json.data.splice(idSlide, 1);
+            };
+
+            /*
+             * Removes the action at the specified slide.
+             * @param {int} idSlide
+             * @param {int} idAction
+             */
+            this.removeAction = function (idSlide, idAction) {
+                json.data[idSlide].action.splice(idAction, 1);
+            };
+
+            /*
+             * Removes the specified object from the slide.
+             * Also removes all the actions attached to this object.
+             * @param {int} idSlide
+             * @param {int} idObject
+             */
+            this.removeObject = function (idSlide, idObject) {
+                var removed = json.data[idSlide].object.splice(idObject, 1);
+                for (var i = 0; i < json.data.length; i++) {
+                    var j = 0;
+                    while (json.data[i].action[j]) {
+                        if (json.data[i].action[j].target === removed.id) {
+                            json.data[i].action.splice(j, 1);
+                        } else {
+                            j++;
+                        }
+                    }
+                }
+            };
+
+            /*
+             * @returns {String}
+             */
+            this.getJson = function () {
+                return JSON.stringify(json, null, '    ');
+            };
+
+            /*
+             * @returns {String} returns the index for the next slide.
+             */
+            this.getNbSlide = function () {
+                return json.data.length;
+            };
+
+            /*
+             * @return {object} returns the object identified by idSlide
+             */
+            this.getSlide = function (idSlide) {
+                return json.data[idSlide];
+            };
+
+            /*
+             * Sets the size of images for the generated presentation.
+             * The parameters can be null : it means there are no
+             * requirement for this dimension.
+             * @param {int} width New width for the generated images.
+             * @param {int} height New height for the generated images.
+             */
+            this.setImageSizeRequirements = function (width, height) {
+                json.metaData.imageWidth = width;
+                json.metaData.imageHeight = height;
+            };
+
+            /*
+             * Gets the width of images for the generated presentation.
+             * @return {int or null} Width of generated images.
+             */
+            this.getImageWidth = function () {
+                return json.metaData.imageWidth;
+            };
+
+            /*
+             * Gets the height of images for the generated presentation.
+             * @return {int or null} Height of generated images.
+             */
+            this.getImageHeight = function () {
+                return json.metaData.imageHeight;
+            };
+
+            /*
+             * Gets a background image (no one in particular, the first met).
+             * @return {string} The name of a background image.
+             */
+            this.getABackgroundImage = function () {
+                for (var i = 0; i < json.data.length; i++) {
+                    for (var j = 0; j < json.data[i].object.length; j++) {
+                        if (json.data[i].object[j].type === "background") {
+                            return json.data[i].object[j].img;
+                        }
+                    }
+                }
+                return null;
+            };
+        };
+
+        /* public API */
+
+        self.version = "0.0.1";
+
+        self.createScreencastGenerator = function createScreencastGenerator() {
+            return new DahuScreencastGenerator();
+        };
+
+        self.createScreencastModel = function createScreencastModel() {
+            return new DahuScreencastModel();
+        };
+
+        self.createScreencastExecutableModel = function createScreencastExecutableModel() {
+            return new DahuScreencastExecutableModel();
+        };
+
+        self.createScreencastGeneratedModel = function createScreencastGeneratedModel() {
+            return new DahuScreencastGeneratedModel();
+        };
+
+        return self;
+    })();
+
+    window.dahuapp = dahuapp;
+
+})(window, jQuery);
\ No newline at end of file
diff --git a/TP/TP01/P08/dahuapp.viewer.css b/TP/TP01/P08/dahuapp.viewer.css
new file mode 100644
index 0000000000000000000000000000000000000000..ea934576dad6ca9263a6163de58f5fe4534d1f0b
--- /dev/null
+++ b/TP/TP01/P08/dahuapp.viewer.css
@@ -0,0 +1,25 @@
+.object-list {
+    position: absolute;
+    margin: 0px;
+    padding: 0px;
+}
+
+.mouse-cursor {
+    position: absolute;
+}
+
+.control {
+    position: relative;
+}
+
+.background {
+    position: absolute;
+}
+
+.tooltip {
+    position: absolute;
+    width: 100px;
+    padding: 4px;
+    margin: 2px;
+    border: 2px black solid;
+}
\ No newline at end of file
diff --git a/TP/TP01/P08/dahuapp.viewer.js b/TP/TP01/P08/dahuapp.viewer.js
new file mode 100644
index 0000000000000000000000000000000000000000..12ad03b2cdc503a8fd6ff3f5fdc2a74edbb36edb
--- /dev/null
+++ b/TP/TP01/P08/dahuapp.viewer.js
@@ -0,0 +1,456 @@
+"use strict";
+
+/**
+ * Dahuapp viewer module.
+ * 
+ * @param   dahuapp     dahuapp object to augment with module.
+ * @param   $           jQuery
+ * @returns dahuapp extended with viewer module.
+ */
+
+(function(dahuapp, $) {
+    var viewer = (function() {
+
+        var self = {};
+
+        var DahuViewerModel = function(select, getParams) {
+
+            /* Private API */
+
+            var json = null;
+            var selector = select;
+            var parseAutoOption = function(name, defaultValue) {
+                var res = getParams[name];
+                if (res == null) {
+                    if (name == 'auto') {
+                        return false;
+                    } else {
+                        return parseAutoOption('auto', defaultValue);
+                    }
+                }
+                if (res.toLowerCase() === 'false') {
+                    return false;
+                }
+                res = parseInt(res);
+                if (isNaN(res)) {
+                    // e.g. ?autoplay or ?autoplay=true
+                    return defaultValue;
+                }
+                return res;
+            }
+
+            /* Whether to wait for "next" event between actions */
+            var autoPlay = parseAutoOption('autoplay', 5000);
+            /* Whether to wait for "next" event on page load */
+            var autoStart = parseAutoOption('autostart', 5000);
+            /* Whether to loop back to start at the end of presentation */
+            var autoLoop = parseAutoOption('autoloop', 5000);
+
+            var events = (function() {
+                var self = {};
+                /*
+                 * Creates a generic event.
+                 */
+                var createEvent = function() {
+                    var callbacks = $.Callbacks();
+                    return {
+                        publish: callbacks.fire,
+                        subscribe: callbacks.add,
+                        unsubscribe: callbacks.remove,
+                        unsubscribeAll: callbacks.empty
+                    };
+                };
+                /*
+                 * Called when the button next is pressed.
+                 */
+                self.onNext = createEvent();
+                /*
+                 * Called when the button previous is pressed.
+                 */
+                self.onPrevious = createEvent();
+                /*
+                 * Called when an action is over.
+                 */
+                self.onActionOver = createEvent();
+                /*
+                 * Called when an action starts.
+                 */
+                self.onActionStart = createEvent();
+                /*
+                 * Called when at least one action was running and has finished.
+                 */
+                self.onAllActionFinish = createEvent();
+                
+                return self;
+            })();
+
+            /*
+             * Variables used like index for methodes subscribed.
+             */
+            var currentAction = 0;    /* Action currently running */
+            var nextAction = 0;       /* Action to execute on 'Next' click */
+            var nbActionsRunning = 0;
+            var previousAnchor = -1;  /* Last entered anchor, only used in
+                                       * case the 'onhashchange' event is
+                                       * not supported by the web browser */
+            
+            /*
+             * Functions to reinitialise running actions and subscribed
+             * callbacks (reinitialise is called once without stopping
+             * all the actions, so the two functions are separated).
+             */
+            var stopAllActions = function() {
+                $(selector + " .object-list").children().stop(true, true);
+                reinitialiseCallbackLists();
+                nbActionsRunning = 0;
+            };
+            var reinitialiseCallbackLists = function() {
+                events.onActionStart.unsubscribeAll();
+                events.onActionStart.subscribe(onActionStartEventHandler);
+                events.onAllActionFinish.unsubscribeAll();
+            };
+            
+            /*
+             * Timer used to program onNext event in autoplay mode.
+             */
+            var playTimer = 0;
+
+            /*
+             * Function used when an "onNextEvent" event is caught.
+             */
+            var onNextEventHandler = function() {
+                /*
+                 * If the user pressed "next" before an autoplay event
+                 * is triggered, it replaces the autoplay event, hence
+                 * cancels it:
+                 */
+                window.clearTimeout(playTimer);
+
+                enterAnimationMode();
+                stopAllActions();
+                var tmpAction = nextAction;
+                var onlyWithPrevious = true;
+                nextAction++;
+                currentAction = nextAction;
+                while (json.action[currentAction] && onlyWithPrevious) {
+                    switch (json.action[currentAction].trigger) {
+                        case 'onClick':
+                            nextAction = currentAction;
+                            onlyWithPrevious = false;
+                            break;
+                        case 'withPrevious':
+                            var tmp = currentAction;
+                            /*
+                             * We can't directly pass 'execute' as callback because we
+                             * allow the 'execute' function to reference a property of the
+                             * object (by using 'this.property') so we have to call the
+                             * function in the containing object to make that available
+                             */
+                            events.onActionStart.subscribe(function(events, selector) {
+                                json.action[tmp].execute(events, selector);
+                            });
+                            break;
+                        case 'afterPrevious':
+                            var tmp = currentAction;
+                            events.onAllActionFinish.subscribe(function(events, selector) {
+                                json.action[tmp].execute(events, selector);
+                            });
+                            while (json.action[nextAction] && json.action[nextAction].trigger !== 'onClick') {
+                                nextAction++;
+                            }
+                            onlyWithPrevious = false;
+                            break;
+                    }
+                    currentAction++;
+                }
+                if (json.action[tmpAction]) {
+                    launch(json.action[tmpAction]);
+                }
+                if (nextAction > json.action.length) {
+                    nextAction = json.action.length;
+                }
+            };
+            
+            /*
+             * Function used when an "onPreviousEvent" event is caught.
+             */
+            var onPreviousEventHandler = function() {
+                stopAllActions();
+                currentAction = nextAction - 1;
+                while (json.action[currentAction] && json.action[currentAction].trigger !== 'onClick') {
+                    launchReverse(json.action[currentAction]);
+                    currentAction--;
+                }
+                /* currentAction = -1 means that it's the beginning */
+                if (currentAction > -1) {
+                    launchReverse(json.action[currentAction]);
+                }
+                if (currentAction < 0) {
+                    currentAction = 0;
+                }
+                nextAction = currentAction;
+            };
+
+            /*
+             * Function used when an "onActionOverEvent" event is caught.
+             */
+            var onActionOverEventHandler = function() {
+                nbActionsRunning--;
+                if (nbActionsRunning === 0) {
+                    events.onAllActionFinish.publish(events, selector);
+                    reinitialiseCallbackLists();
+                    while (json.action[currentAction]) {
+                        switch (json.action[currentAction].trigger) {
+                            case 'onClick':
+                                leaveAnimationMode();
+                                if (autoPlay && nbActionsRunning === 0) {
+                                    playTimer = setTimeout(function () {
+                                        events.onNext.publish();
+                                    }, autoPlay);
+                                }
+                                return;
+                            case 'withPrevious':
+                                var tmp = currentAction;
+                                events.onActionStart.subscribe(function(events, selector) {
+                                    json.action[tmp].execute(events, selector);
+                                });
+                                break;
+                            case 'afterPrevious':
+                                var tmp = currentAction;
+                                events.onAllActionFinish.subscribe(function(events, selector) {
+                                    json.action[tmp].execute(events, selector);
+                                });
+                                currentAction++;
+                                return;
+                        }
+                        currentAction++;
+
+                    }
+                    if (autoLoop && nbActionsRunning === 0) {
+                        setTimeout(function () {
+                            resetPresentation();
+                            startPresentationMaybe();
+                        }, autoLoop);
+                    }
+                }
+                leaveAnimationMode();
+            };
+
+            /*
+             * Function used when an "onActionStartEvent" event is caught.
+             */
+            var onActionStartEventHandler = function() {
+                nbActionsRunning++;
+            };
+
+            /*
+             * Enter and leave "animation" mode. The viewer is in
+             * animation mode when something is going on without human
+             * interaction (i.e. executing actions before the next
+             * "onClick").
+             */
+            var enterAnimationMode = function () {
+                $(selector + ' .mouse-cursor-pause').hide();
+                $(selector + ' .mouse-cursor-normal').show();
+            };
+
+            var leaveAnimationMode = function () {
+                $(selector + ' .mouse-cursor-pause').show();
+                $(selector + ' .mouse-cursor-normal').hide();
+            };
+
+            /*
+             * Function used to perform actions.
+             */
+            var launch = function(action) {
+                action.execute(events, selector);
+            };
+            var launchReverse = function(action) {
+                action.executeReverse(selector);
+            };
+
+            /*
+             * The two following methods each returns an array containing the
+             * actions to execute to reach the given anchor (respectively
+             * forward or backwards).
+             *
+             * If the given anchor matches none of the actions, then an empty
+             * array is returned.
+             */
+            var getActionsToJumpForward = function(anchor) {
+                var actions = new Array();
+                for (var i = nextAction; i < json.action.length; i++) {
+                    // '==' and not '===' because we compare indifferently a
+                    // number or a string or anything else
+                    if (json.action[i].id == anchor) {
+                        return actions;
+                    } else {
+                        actions.push(json.action[i]);
+                    }
+                }
+                // Here, the anchor has not been found during the action scan
+                return null;
+            };
+            var getActionsToJumpBackwards = function(anchor) {
+                var actions = new Array();
+                for (var i = nextAction - 1; i >= 0; i--) {
+                    // '==' and not '===' because we compare indifferently a
+                    // number or a string or anything else
+                    actions.push(json.action[i]);
+                    if (json.action[i].id == anchor) {
+                        return actions;
+                    }
+                }
+                // Here, the anchor has not been found during the action scan
+                return null;
+            };
+            
+            /*
+             * Updates the position of the presentation depending on the
+             * given anchor (next action wanted).
+             */
+            var jumpToAnchor = function(anchor) {
+                if (anchor !== '') {
+                    stopAllActions();
+                    if (anchor > nextAction) {
+                        // forward
+                        var actions = getActionsToJumpForward(anchor);
+                        for (var i = 0; i < actions.length; i++) {
+                            actions[i].executeImmediately(selector);
+                        }
+                        nextAction += actions.length;
+                    } else {
+                        // backwards
+                        var actions = getActionsToJumpBackwards(anchor);
+                        for (var i = 0; i < actions.length; i++) {
+                            actions[i].executeReverse(selector);
+                        }
+                        nextAction -= actions.length;
+                    }
+                }
+            };
+
+            var resetPresentation = function() {
+                window.clearTimeout(playTimer);
+
+                currentAction = 0;
+                nextAction = 0;
+                nbActionsRunning = 0;
+
+                /*
+                 * At the beginning, the visible image is the first one of the presentation
+                 */
+                $(selector + " .object-list").children().hide();
+
+                $(selector + " ." + json.metaData.initialBackgroundId).show();
+
+                $(selector + " .mouse-cursor").css({
+                    'top': (json.metaData.initialMouseY * json.metaData.imageHeight) + "px",
+                    'left': (json.metaData.initialMouseX * json.metaData.imageWidth) + "px"
+                });
+
+                $(selector + " .mouse-cursor").show();
+            }
+
+            var startPresentationMaybe = function() {
+                if (autoStart) {
+                    playTimer = setTimeout(function () {
+                        events.onNext.publish();
+                    }, autoStart);
+                }
+            }
+
+            /* Public API */
+
+            this.loadUrl = function(url) {
+                var dataJson;
+                $.ajax({
+                    'async': false,
+                    'global': false,
+                    'url': url,
+                    'dataType': "json",
+                    'success': function(data) {
+                        dataJson = data;
+                    }
+                });
+                
+                load(dataJson);
+            };
+
+            this.load = function(dataJson) {
+                /* Transforms data JSON to executable functions for actions */
+                var execModel = dahuapp.createScreencastExecutableModel();
+                execModel.loadJson(dataJson);
+                json = execModel.getJson();
+            };
+
+            this.start = function() {
+                
+                /*
+                 * Subscription of methods to their events.
+                 */
+                events.onNext.subscribe(onNextEventHandler);
+                events.onPrevious.subscribe(onPreviousEventHandler);
+                events.onActionOver.subscribe(onActionOverEventHandler);
+                events.onActionStart.subscribe(onActionStartEventHandler);
+
+                resetPresentation();
+
+                /*
+                 * If an anchor has been specified, we place the presentation
+                 * in the right position.
+                 */
+                jumpToAnchor(window.location.hash.substring(1));
+                
+                /*
+                 * If the anchor changes during the presentation, then the
+                 * presentation is updated
+                 */
+                if ("onhashchange" in window) {
+                    // event supported
+                    window.onhashchange = function () {
+                        jumpToAnchor(window.location.hash.substring(1));
+                    };
+                } else {
+                    // event not supported : periodical check
+                    window.setInterval(function () {
+                        if (window.location.hash.substring(1) !== previousAnchor) {
+                            previousAnchor = window.location.hash.substring(1);
+                            jumpToAnchor(window.location.hash.substring(1));
+                        }
+                    }, 100);
+                }
+
+                /*
+                 * A click on the "next" button publishes a nextSlide event
+                 */
+                $(selector + " .next").click(function() {
+                    events.onNext.publish();
+                });
+                
+                /*
+                 * A click on the "previous" button publishes a previousSlide event
+                 */
+                $(selector + " .previous").click(function() {
+                    events.onPrevious.publish();
+                });
+
+                /*
+                 * Everything is ready, show the presentation
+                 * (hidden with style="display: none" from HTML).
+                 */
+                $("#loading").hide();
+                $(selector + " .object-list").show();
+                startPresentationMaybe();
+            };
+        };
+
+        self.createDahuViewer = function(selector, getParams) {
+            return new DahuViewerModel(selector, getParams);
+        };
+
+        return self;
+    })();
+
+    dahuapp.viewer = viewer;
+})(dahuapp || {}, jQuery);
diff --git a/TP/TP01/P08/img/069aaa71-3b56-79ea-e75e-63811f68fe4a.png b/TP/TP01/P08/img/069aaa71-3b56-79ea-e75e-63811f68fe4a.png
new file mode 100644
index 0000000000000000000000000000000000000000..836c7ace6f4e18cedb4f2161b38dfa66dc622ab6
Binary files /dev/null and b/TP/TP01/P08/img/069aaa71-3b56-79ea-e75e-63811f68fe4a.png differ
diff --git a/TP/TP01/P08/img/0af5556e-a441-f350-4097-3555d13216ed.png b/TP/TP01/P08/img/0af5556e-a441-f350-4097-3555d13216ed.png
new file mode 100644
index 0000000000000000000000000000000000000000..3106a14e1c607e104bebf4b60cd5959019f06f24
Binary files /dev/null and b/TP/TP01/P08/img/0af5556e-a441-f350-4097-3555d13216ed.png differ
diff --git a/TP/TP01/P08/img/15382a62-0ab0-a407-3084-eaf9dcfbf5ff.png b/TP/TP01/P08/img/15382a62-0ab0-a407-3084-eaf9dcfbf5ff.png
new file mode 100644
index 0000000000000000000000000000000000000000..c7e9ebf43a61b167804e6292c15ae838d9a07606
Binary files /dev/null and b/TP/TP01/P08/img/15382a62-0ab0-a407-3084-eaf9dcfbf5ff.png differ
diff --git a/TP/TP01/P08/img/236a104c-cb6b-160f-f8ce-1a7f1ee05074.png b/TP/TP01/P08/img/236a104c-cb6b-160f-f8ce-1a7f1ee05074.png
new file mode 100644
index 0000000000000000000000000000000000000000..998f2d5196fcdafc8ff3ecfff270938bcab6ce35
Binary files /dev/null and b/TP/TP01/P08/img/236a104c-cb6b-160f-f8ce-1a7f1ee05074.png differ
diff --git a/TP/TP01/P08/img/2c60718e-61a6-59a8-8af1-cd0d41b5e373.png b/TP/TP01/P08/img/2c60718e-61a6-59a8-8af1-cd0d41b5e373.png
new file mode 100644
index 0000000000000000000000000000000000000000..eb2eef94da51c1ff6a298c3d72e6cc5c04db5b4a
Binary files /dev/null and b/TP/TP01/P08/img/2c60718e-61a6-59a8-8af1-cd0d41b5e373.png differ
diff --git a/TP/TP01/P08/img/43eedad3-474b-b7e5-97fd-847e21828079.png b/TP/TP01/P08/img/43eedad3-474b-b7e5-97fd-847e21828079.png
new file mode 100644
index 0000000000000000000000000000000000000000..d073fc6916a23c28b311b8ad601b91849f940a28
Binary files /dev/null and b/TP/TP01/P08/img/43eedad3-474b-b7e5-97fd-847e21828079.png differ
diff --git a/TP/TP01/P08/img/94181b34-2c0b-9cbe-4c87-1f0088fbca9b.png b/TP/TP01/P08/img/94181b34-2c0b-9cbe-4c87-1f0088fbca9b.png
new file mode 100644
index 0000000000000000000000000000000000000000..ac108c4d00008d492b172860b6be43491744ca2c
Binary files /dev/null and b/TP/TP01/P08/img/94181b34-2c0b-9cbe-4c87-1f0088fbca9b.png differ
diff --git a/TP/TP01/P08/img/9b9bbd73-13f7-d649-3df8-0a86d795991b.png b/TP/TP01/P08/img/9b9bbd73-13f7-d649-3df8-0a86d795991b.png
new file mode 100644
index 0000000000000000000000000000000000000000..f5f7cdfac3a735646ae6d4c8cf4db14ed3bf7500
Binary files /dev/null and b/TP/TP01/P08/img/9b9bbd73-13f7-d649-3df8-0a86d795991b.png differ
diff --git a/TP/TP01/P08/img/9e01253b-aeaf-d9a3-ae09-f315bda59ae5.png b/TP/TP01/P08/img/9e01253b-aeaf-d9a3-ae09-f315bda59ae5.png
new file mode 100644
index 0000000000000000000000000000000000000000..baeda64365b64074907e342d9300f6cd182f13b3
Binary files /dev/null and b/TP/TP01/P08/img/9e01253b-aeaf-d9a3-ae09-f315bda59ae5.png differ
diff --git a/TP/TP01/P08/img/a7c8540c-fc03-44ff-1f9f-0b85413ce823.png b/TP/TP01/P08/img/a7c8540c-fc03-44ff-1f9f-0b85413ce823.png
new file mode 100644
index 0000000000000000000000000000000000000000..f8038000e7fa95d83294eb4e37c8f77f6d6e8c61
Binary files /dev/null and b/TP/TP01/P08/img/a7c8540c-fc03-44ff-1f9f-0b85413ce823.png differ
diff --git a/TP/TP01/P08/img/a9008f63-c085-525e-9221-5fc5e475a0a5.png b/TP/TP01/P08/img/a9008f63-c085-525e-9221-5fc5e475a0a5.png
new file mode 100644
index 0000000000000000000000000000000000000000..e7c9708b1cf4adfaa41dde46998913c573286294
Binary files /dev/null and b/TP/TP01/P08/img/a9008f63-c085-525e-9221-5fc5e475a0a5.png differ
diff --git a/TP/TP01/P08/img/b985855c-63b1-6f17-4017-7110897dc1ab.png b/TP/TP01/P08/img/b985855c-63b1-6f17-4017-7110897dc1ab.png
new file mode 100644
index 0000000000000000000000000000000000000000..fa041feb8e4ad0241c7238afef4bc58a6c45cf4d
Binary files /dev/null and b/TP/TP01/P08/img/b985855c-63b1-6f17-4017-7110897dc1ab.png differ
diff --git a/TP/TP01/P08/img/be074647-825d-d17e-f05b-8e7d48446660.png b/TP/TP01/P08/img/be074647-825d-d17e-f05b-8e7d48446660.png
new file mode 100644
index 0000000000000000000000000000000000000000..127dee310f88085f0c41664a5fdcd0782a8b6a17
Binary files /dev/null and b/TP/TP01/P08/img/be074647-825d-d17e-f05b-8e7d48446660.png differ
diff --git a/TP/TP01/P08/img/c82d39c7-94bb-d6ec-f21a-53f6606b7882.png b/TP/TP01/P08/img/c82d39c7-94bb-d6ec-f21a-53f6606b7882.png
new file mode 100644
index 0000000000000000000000000000000000000000..66bdf40f5ad1e32c5b8519a3ad33c54e88d6a0d2
Binary files /dev/null and b/TP/TP01/P08/img/c82d39c7-94bb-d6ec-f21a-53f6606b7882.png differ
diff --git a/TP/TP01/P08/img/cursor-pause.png b/TP/TP01/P08/img/cursor-pause.png
new file mode 100644
index 0000000000000000000000000000000000000000..9c93cc142f4326a188eafc3c0fe96f2975dcab0c
Binary files /dev/null and b/TP/TP01/P08/img/cursor-pause.png differ
diff --git a/TP/TP01/P08/img/cursor.png b/TP/TP01/P08/img/cursor.png
new file mode 100644
index 0000000000000000000000000000000000000000..5b4a20e63078ce18bb11a4e601a1994b261522ef
Binary files /dev/null and b/TP/TP01/P08/img/cursor.png differ
diff --git a/TP/TP01/P08/img/e56d6655-5a00-524d-53f5-0b43e69122e0.png b/TP/TP01/P08/img/e56d6655-5a00-524d-53f5-0b43e69122e0.png
new file mode 100644
index 0000000000000000000000000000000000000000..d9a69c031ffa14beee6628ca7060c0a08de6ec7b
Binary files /dev/null and b/TP/TP01/P08/img/e56d6655-5a00-524d-53f5-0b43e69122e0.png differ
diff --git a/TP/TP01/P08/img/fa839127-dceb-6619-d9d7-0c60b4b4601d.png b/TP/TP01/P08/img/fa839127-dceb-6619-d9d7-0c60b4b4601d.png
new file mode 100644
index 0000000000000000000000000000000000000000..24c3c23bae21b63ddc30cb30eb79c15d20c271c6
Binary files /dev/null and b/TP/TP01/P08/img/fa839127-dceb-6619-d9d7-0c60b4b4601d.png differ
diff --git a/TP/TP01/P08/index.html b/TP/TP01/P08/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..055d67a0c5c9ccfbe6b1d5f767b255a73532d092
--- /dev/null
+++ b/TP/TP01/P08/index.html
@@ -0,0 +1,497 @@
+<!DOCTYPE html>
+
+<html lang="en">
+
+<head>
+<title>Dahu Presentation
+</title>
+<meta charset="utf-8">
+<script src="http://code.jquery.com/jquery-1.9.1.min.js" type="text/javascript">
+</script>
+<script src="parse-search.js" type="text/javascript">
+</script>
+
+<link rel="stylesheet" href="dahuapp.viewer.css"><style>.s0-o2{background-color:   #FFFFDD;width:  240px}
+.s1-o2{background-color:   #FFFFDD;width:  240px}
+.s2-o2{background-color:   #FFFFDD;width:  240px}
+.s3-o2{background-color:   #FFFFDD;width:  240px}
+.s4-o2{background-color:   #FFFFDD;width:  240px}
+.s6-o2{background-color:   #FFFFDD;width:  240px}
+.s7-o2{background-color:   #FFFFDD;width:  240px}
+.s8-o2{background-color:   #FFFFDD;width:  240px}
+.s10-o2{background-color:   #FFFFDD;width:  240px}
+.s11-o2{background-color:   #FFFFDD;width:  240px}
+.s12-o2{background-color:   #FFFFDD;width:  240px}
+.s13-o2{background-color:   #FFFFDD;width:  240px}
+.s14-o2{background-color:   #FFFFDD;width:  240px}
+</style>
+</head>
+
+<body>
+
+<div id="my-dahu-presentation">
+
+<div id="loading">Loading presentation...
+</div>
+
+<div class="object-list" style="display: none">
+<img src="img/43eedad3-474b-b7e5-97fd-847e21828079.png" alt="img/43eedad3-474b-b7e5-97fd-847e21828079.png" class="background 43eedad3-474b-b7e5-97fd-847e218280790">
+
+<div class="tooltip s0-o2">Nous prenons le modèle du pendule inversé réalisé à la fin de la séance précédente comme point de départ.
+</div>
+<img src="img/2c60718e-61a6-59a8-8af1-cd0d41b5e373.png" alt="img/2c60718e-61a6-59a8-8af1-cd0d41b5e373.png" class="background 2c60718e-61a6-59a8-8af1-cd0d41b5e3730">
+
+<div class="tooltip s1-o2">Sélectionner l'ongle Ports and Subsystems dans la boite à outils Simulink.
+</div>
+<img src="img/b985855c-63b1-6f17-4017-7110897dc1ab.png" alt="img/b985855c-63b1-6f17-4017-7110897dc1ab.png" class="background b985855c-63b1-6f17-4017-7110897dc1ab0">
+
+<div class="tooltip s2-o2">Sélectionner le bloc SubSystem.
+</div>
+<img src="img/c82d39c7-94bb-d6ec-f21a-53f6606b7882.png" alt="img/c82d39c7-94bb-d6ec-f21a-53f6606b7882.png" class="background c82d39c7-94bb-d6ec-f21a-53f6606b78820">
+
+<div class="tooltip s3-o2">Déposer un bloc SubSystem dans votre modèle. Noter que celui-ci comporte une entrée et une sortie.
+</div>
+<img src="img/0af5556e-a441-f350-4097-3555d13216ed.png" alt="img/0af5556e-a441-f350-4097-3555d13216ed.png" class="background 0af5556e-a441-f350-4097-3555d13216ed0">
+
+<div class="tooltip s4-o2">Découper le modèle en deux parties : le système et les afficheurs.
+</div>
+<img src="img/be074647-825d-d17e-f05b-8e7d48446660.png" alt="img/be074647-825d-d17e-f05b-8e7d48446660.png" class="background be074647-825d-d17e-f05b-8e7d484466600">
+<img src="img/94181b34-2c0b-9cbe-4c87-1f0088fbca9b.png" alt="img/94181b34-2c0b-9cbe-4c87-1f0088fbca9b.png" class="background 94181b34-2c0b-9cbe-4c87-1f0088fbca9b0">
+
+<div class="tooltip s6-o2">Découper la partie système que nous allons déplacer dans le bloc SubSystem.
+</div>
+<img src="img/9e01253b-aeaf-d9a3-ae09-f315bda59ae5.png" alt="img/9e01253b-aeaf-d9a3-ae09-f315bda59ae5.png" class="background 9e01253b-aeaf-d9a3-ae09-f315bda59ae50">
+
+<div class="tooltip s7-o2">Ouvrir le bloc SubSystem par un double clic.
+</div>
+<img src="img/fa839127-dceb-6619-d9d7-0c60b4b4601d.png" alt="img/fa839127-dceb-6619-d9d7-0c60b4b4601d.png" class="background fa839127-dceb-6619-d9d7-0c60b4b4601d0">
+
+<div class="tooltip s8-o2">Coller le système découpé dans une étape précédente.
+</div>
+<img src="img/236a104c-cb6b-160f-f8ce-1a7f1ee05074.png" alt="img/236a104c-cb6b-160f-f8ce-1a7f1ee05074.png" class="background 236a104c-cb6b-160f-f8ce-1a7f1ee050740">
+<img src="img/a9008f63-c085-525e-9221-5fc5e475a0a5.png" alt="img/a9008f63-c085-525e-9221-5fc5e475a0a5.png" class="background a9008f63-c085-525e-9221-5fc5e475a0a50">
+
+<div class="tooltip s10-o2">Supprimer le signal entre le port d'entrée et le port de sortie.
+</div>
+<img src="img/a7c8540c-fc03-44ff-1f9f-0b85413ce823.png" alt="img/a7c8540c-fc03-44ff-1f9f-0b85413ce823.png" class="background a7c8540c-fc03-44ff-1f9f-0b85413ce8230">
+
+<div class="tooltip s11-o2">Connecter le port de sortie avec le signal portant l'angle alpha.
+</div>
+<img src="img/15382a62-0ab0-a407-3084-eaf9dcfbf5ff.png" alt="img/15382a62-0ab0-a407-3084-eaf9dcfbf5ff.png" class="background 15382a62-0ab0-a407-3084-eaf9dcfbf5ff0">
+
+<div class="tooltip s12-o2">Supprimer le port d'entrée.
+</div>
+<img src="img/9b9bbd73-13f7-d649-3df8-0a86d795991b.png" alt="img/9b9bbd73-13f7-d649-3df8-0a86d795991b.png" class="background 9b9bbd73-13f7-d649-3df8-0a86d795991b0">
+
+<div class="tooltip s13-o2">Aller à la racine du modèle hiérarchique.
+</div>
+<img src="img/069aaa71-3b56-79ea-e75e-63811f68fe4a.png" alt="img/069aaa71-3b56-79ea-e75e-63811f68fe4a.png" class="background 069aaa71-3b56-79ea-e75e-63811f68fe4a0">
+
+<div class="tooltip s14-o2">Connecter la sortie du bloc sur les afficheurs.
+</div>
+
+<div class="mouse-cursor">
+<img src="img/cursor.png" alt="img/cursor.png" class="mouse-cursor-normal">
+<img src="img/cursor-pause.png" alt="img/cursor-pause.png" style="display: none" class="mouse-cursor-pause">
+</div>
+</div>
+
+<div class="control" style="top: 616px;">
+<button class="previous">Previous
+</button>
+<button class="next">Next
+</button>
+</div>
+</div>
+<script src="dahuapp.js" type="text/javascript">
+</script>
+<script src="dahuapp.viewer.js" type="text/javascript">
+</script>
+<script type="text/javascript">(function($) {
+    var myPresentation = dahuapp.viewer.createDahuViewer("#my-dahu-presentation", window.getParams);
+    myPresentation.load({
+    "metaData": {
+        "imageWidth": 800,
+        "imageHeight": 600,
+        "initialBackgroundId": "43eedad3-474b-b7e5-97fd-847e218280790",
+        "initialMouseX": 0.36944444444444446,
+        "initialMouseY": 0.34
+    },
+    "action": [
+        {
+            "id": "33",
+            "type": "appear",
+            "target": "s0-o2",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 400
+        },
+        {
+            "id": "3",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.7680555555555556,
+            "finalOrd": 0.3411111111111111,
+            "speed": 0.8
+        },
+        {
+            "id": "4",
+            "type": "appear",
+            "target": "2c60718e-61a6-59a8-8af1-cd0d41b5e3730",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "34",
+            "type": "appear",
+            "target": "s1-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.39166666666666666,
+            "ord": 0.16,
+            "duration": 400
+        },
+        {
+            "id": "5",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.8701388888888889,
+            "finalOrd": 0.48,
+            "speed": 0.8
+        },
+        {
+            "id": "6",
+            "type": "appear",
+            "target": "b985855c-63b1-6f17-4017-7110897dc1ab0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "35",
+            "type": "appear",
+            "target": "s2-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.38666666666666666,
+            "ord": 0.2275,
+            "duration": 400
+        },
+        {
+            "id": "7",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.3076388888888889,
+            "finalOrd": 0.6455555555555555,
+            "speed": 0.8
+        },
+        {
+            "id": "8",
+            "type": "appear",
+            "target": "c82d39c7-94bb-d6ec-f21a-53f6606b78820",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "36",
+            "type": "appear",
+            "target": "s3-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.32166666666666666,
+            "ord": 0.26125,
+            "duration": 400
+        },
+        {
+            "id": "9",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.3611111111111111,
+            "finalOrd": 0.43,
+            "speed": 0.8
+        },
+        {
+            "id": "10",
+            "type": "appear",
+            "target": "0af5556e-a441-f350-4097-3555d13216ed0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "37",
+            "type": "appear",
+            "target": "s4-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.28,
+            "ord": 0.06125,
+            "duration": 400
+        },
+        {
+            "id": "11",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.27291666666666664,
+            "finalOrd": 0.4411111111111111,
+            "speed": 0.8
+        },
+        {
+            "id": "12",
+            "type": "appear",
+            "target": "be074647-825d-d17e-f05b-8e7d484466600",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "13",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.31180555555555556,
+            "finalOrd": 0.46111111111111114,
+            "speed": 0.8
+        },
+        {
+            "id": "14",
+            "type": "appear",
+            "target": "94181b34-2c0b-9cbe-4c87-1f0088fbca9b0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "38",
+            "type": "appear",
+            "target": "s6-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.25,
+            "ord": 0.0775,
+            "duration": 400
+        },
+        {
+            "id": "15",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.29791666666666666,
+            "finalOrd": 0.6533333333333333,
+            "speed": 0.8
+        },
+        {
+            "id": "16",
+            "type": "appear",
+            "target": "9e01253b-aeaf-d9a3-ae09-f315bda59ae50",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "39",
+            "type": "appear",
+            "target": "s7-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.145,
+            "ord": 0.31625,
+            "duration": 400
+        },
+        {
+            "id": "17",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.3145833333333333,
+            "finalOrd": 0.12555555555555556,
+            "speed": 0.8
+        },
+        {
+            "id": "18",
+            "type": "appear",
+            "target": "fa839127-dceb-6619-d9d7-0c60b4b4601d0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "40",
+            "type": "appear",
+            "target": "s8-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.15333333333333332,
+            "ord": 0.24875,
+            "duration": 400
+        },
+        {
+            "id": "19",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.3138888888888889,
+            "finalOrd": 0.33666666666666667,
+            "speed": 0.8
+        },
+        {
+            "id": "20",
+            "type": "appear",
+            "target": "236a104c-cb6b-160f-f8ce-1a7f1ee050740",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "21",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.25069444444444444,
+            "finalOrd": 0.3322222222222222,
+            "speed": 0.8
+        },
+        {
+            "id": "22",
+            "type": "appear",
+            "target": "a9008f63-c085-525e-9221-5fc5e475a0a50",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "41",
+            "type": "appear",
+            "target": "s10-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.19666666666666666,
+            "ord": 0.05875,
+            "duration": 400
+        },
+        {
+            "id": "23",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.4097222222222222,
+            "finalOrd": 0.5422222222222223,
+            "speed": 0.8
+        },
+        {
+            "id": "24",
+            "type": "appear",
+            "target": "a7c8540c-fc03-44ff-1f9f-0b85413ce8230",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "42",
+            "type": "appear",
+            "target": "s11-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.21333333333333335,
+            "ord": 0.09375,
+            "duration": 400
+        },
+        {
+            "id": "27",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.11875,
+            "finalOrd": 0.31555555555555553,
+            "speed": 0.8
+        },
+        {
+            "id": "28",
+            "type": "appear",
+            "target": "15382a62-0ab0-a407-3084-eaf9dcfbf5ff0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "43",
+            "type": "appear",
+            "target": "s12-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.16,
+            "ord": 0.0975,
+            "duration": 400
+        },
+        {
+            "id": "29",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.21944444444444444,
+            "finalOrd": 0.4066666666666667,
+            "speed": 0.8
+        },
+        {
+            "id": "30",
+            "type": "appear",
+            "target": "9b9bbd73-13f7-d649-3df8-0a86d795991b0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "44",
+            "type": "appear",
+            "target": "s13-o2",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 400
+        },
+        {
+            "id": "31",
+            "type": "move",
+            "target": "mouse-cursor",
+            "trigger": "onClick",
+            "finalAbs": 0.2701388888888889,
+            "finalOrd": 0.5422222222222223,
+            "speed": 0.8
+        },
+        {
+            "id": "32",
+            "type": "appear",
+            "target": "069aaa71-3b56-79ea-e75e-63811f68fe4a0",
+            "trigger": "afterPrevious",
+            "abs": 0,
+            "ord": 0,
+            "duration": 0
+        },
+        {
+            "id": "45",
+            "type": "appear",
+            "target": "s14-o2",
+            "trigger": "afterPrevious",
+            "abs": 0.04,
+            "ord": 0.085,
+            "duration": 400
+        }
+    ]
+});
+    myPresentation.start();
+})(jQuery);
+
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/TP/TP01/P08/parse-search.js b/TP/TP01/P08/parse-search.js
new file mode 100644
index 0000000000000000000000000000000000000000..e7d01c07f6a5d45b9fcabd5d271a8c6cfc5f00e2
--- /dev/null
+++ b/TP/TP01/P08/parse-search.js
@@ -0,0 +1,14 @@
+// Adapted from http://stackoverflow.com/questions/3234125/creating-array-from-window-location-hash
+(function () {
+    var search = window.location.search.slice(1);
+    var array = search.split("&");
+    
+    var values, form_data = {};
+    
+    for (var i = 0; i < array.length; i += 1) {
+	values = array[i].split("=");
+	form_data[values[0]] = unescape(values[1]);
+    }
+    window.getParams = form_data;
+}())
+
diff --git a/TP/TP01/automatique-sn-tp-01.pdf b/TP/TP01/automatique-sn-tp-01.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..e5c57e47edaa5a4fbb70a229b94d879f6e5ca3a2
Binary files /dev/null and b/TP/TP01/automatique-sn-tp-01.pdf differ
diff --git a/TP/TP02/TP-2-tp-simulation.pdf b/TP/TP02/TP-2-tp-simulation.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..582444f63ed91c2033d86e852549f26ebc61a3ce
Binary files /dev/null and b/TP/TP02/TP-2-tp-simulation.pdf differ
diff --git a/TP/TP02/pendule_inverse.zip b/TP/TP02/pendule_inverse.zip
new file mode 100644
index 0000000000000000000000000000000000000000..0c4bbfe01a509704c9bdb88a962edb8a8a235572
Binary files /dev/null and b/TP/TP02/pendule_inverse.zip differ
diff --git a/TP/TP03/TP-3-tp-simulation.pdf b/TP/TP03/TP-3-tp-simulation.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..dee8b6b3807d077eb2a6f3ed648810b4fe4ddc0a
Binary files /dev/null and b/TP/TP03/TP-3-tp-simulation.pdf differ