integrate changes from github
authorymh <ymh.work@gmail.com>
Fri, 13 Feb 2015 16:57:53 +0100
changeset 1033 c20df1c080e6
parent 1032 74ac0be7655c
child 1034 31fd2904ec57
integrate changes from github
sbin/build/compil.sh
src/js/libs/jquery.splitter.css
src/js/libs/jquery.splitter.js
src/js/libs/mousetrap-global-bind.js
src/js/libs/mousetrap.min.js
src/js/serializers/ldt_localstorage.js
src/js/widgets-container/defaults.js
src/js/widgets-container/widget.js
src/widgets/Annotation.js
src/widgets/AnnotationsList.css
src/widgets/AnnotationsList.js
src/widgets/Controller.css
src/widgets/Controller.js
src/widgets/CreateAnnotation.css
src/widgets/CreateAnnotation.js
src/widgets/Highlighter.css
src/widgets/Highlighter.js
src/widgets/ImageDisplay.css
src/widgets/ImageDisplay.js
src/widgets/MultiSegments.js
src/widgets/Segments.js
src/widgets/Shortcuts.css
src/widgets/Shortcuts.js
src/widgets/Slice.js
src/widgets/SlideVideoPlayer.css
src/widgets/SlideVideoPlayer.js
src/widgets/Trace.js
src/widgets/Transcript.js
src/widgets/img/hand_left.png
src/widgets/img/hand_right.png
--- a/sbin/build/compil.sh	Fri Feb 13 16:48:05 2015 +0100
+++ b/sbin/build/compil.sh	Fri Feb 13 16:57:53 2015 +0100
@@ -1,2 +1,3 @@
 #!/bin/sh
-sh ../res/ant/bin/ant -f client.xml
+DIR=$(dirname $0)
+sh ${DIR}/../res/ant/bin/ant -f ${DIR}/client.xml
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/js/libs/jquery.splitter.css	Fri Feb 13 16:57:53 2015 +0100
@@ -0,0 +1,57 @@
+/*!
+ * StyleSheet for JQuery splitter Plugin
+ * Copyright (C) 2010 Jakub Jankiewicz <http://jcubic.pl>
+ *
+ * Same license as plugin
+ */
+.splitter_panel {
+  position: relative;
+}
+.splitter_panel .vsplitter {
+    background-color: grey;
+    cursor: col-resize;
+    z-index:900;
+    width: 4px;
+}
+
+.splitter_panel .hsplitter {
+    background-color: #5F5F5F;
+    cursor: row-resize;
+    z-index: 800;
+    height: 4px;
+}
+.splitter_panel .vsplitter.splitter-invisible,
+.splitter_panel .hsplitter.splitter-invisible {
+    background: none;
+}
+.splitter_panel .vsplitter, .splitter_panel .left_panel, .splitter_panel .right_panel,
+.splitter_panel .hsplitter, .splitter_panel .top_panel, .splitter_panel .bottom_panel {
+    position: absolute;
+    overflow: auto;
+}
+.splitter_panel .vsplitter, .splitter_panel .left_panel, .splitter_panel .right_panel {
+  height: 100%;
+}
+.splitter_panel .hsplitter, .splitter_panel .top_panel, .splitter_panel .bottom_panel {
+  width: 100%;
+}
+.splitter_panel .top_panel, .splitter_panel .left_panel, .splitter_panel .vsplitter {
+   top: 0;
+}
+.splitter_panel .top_panel, .splitter_panel .bottom_panel, .splitter_panel .left_panel, .splitter_panel .hsplitter {
+   left: 0;
+}
+.splitter_panel .bottom_panel {
+   bottom: 0;
+}
+.splitter_panel .right_panel {
+   right: 0;
+}
+.splitterMask {
+  position: absolute;
+  left: 0;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 1000;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/js/libs/jquery.splitter.js	Fri Feb 13 16:57:53 2015 +0100
@@ -0,0 +1,278 @@
+/*!
+ * JQuery Spliter Plugin
+ * Copyright (C) 2010-2013 Jakub Jankiewicz <http://jcubic.pl> 
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+(function($, undefined) {
+    var count = 0;
+    var splitter_id = null;
+    var splitters = [];
+    var current_splitter = null;
+    $.fn.split = function(options) {
+        var data = this.data('splitter');
+        if (data) {
+            return data;
+        }
+        var panel_1;
+        var panel_2;
+        var settings = $.extend({
+            limit: 100,
+            orientation: 'horizontal',
+            position: '50%',
+            invisible: false,
+            onDragStart: $.noop,
+            onDragEnd: $.noop,
+            onDrag: $.noop
+        }, options || {});
+        this.settings = settings;
+        var cls;
+        var children = this.children();
+        if (settings.orientation == 'vertical') {
+            panel_1 = children.first().addClass('left_panel');
+            panel_2 = panel_1.next().addClass('right_panel');
+            cls = 'vsplitter';
+        } else if (settings.orientation == 'horizontal') {
+            panel_1 = children.first().addClass('top_panel')
+            panel_2 = panel_1.next().addClass('bottom_panel');
+            cls = 'hsplitter';
+        }
+        if (settings.invisible) {
+            cls += ' splitter-invisible';
+        }
+        var width = this.width();
+        var height = this.height();
+        var id = count++;
+        this.addClass('splitter_panel');
+        var splitter = $('<div/>').addClass(cls).mouseenter(function() {
+            splitter_id = id;
+        }).mouseleave(function() {
+            splitter_id = null;
+        }).insertAfter(panel_1);
+        var position;
+
+        function get_position(position) {
+            if (typeof position === 'number') {
+                return position;
+            } else if (typeof position === 'string') {
+                var match = position.match(/^([0-9]+)(px|%)$/);
+                if (match) {
+                    if (match[2] == 'px') {
+                        return +match[1];
+                    } else {
+                        if (settings.orientation == 'vertical') {
+                            return (width * +match[1]) / 100;
+                        } else if (settings.orientation == 'horizontal') {
+                            return (height * +match[1]) / 100;
+                        }
+                    }
+                } else {
+                    //throw position + ' is invalid value';
+                }
+            } else {
+                //throw 'position have invalid type';
+            }
+        }
+
+        var self = $.extend(this, {
+            refresh: function() {
+                var new_width = this.width();
+                var new_height = this.height();
+                if (width != new_width || height != new_height) {
+                    width = this.width();
+                    height = this.height();
+                    self.position(position);
+                }
+            },
+            position: (function() {
+                if (settings.orientation == 'vertical') {
+                    return function(n, silent) {
+                        if (n === undefined) {
+                            return position;
+                        } else {
+                            position = get_position(n);
+                            var sw = splitter.width();
+                            var sw2 = sw/2;
+                            if (settings.invisible) {
+                                var pw = panel_1.width(position).outerWidth();
+                                panel_2.width(self.width()-pw);
+                                splitter.css('left', pw-sw2);
+                            } else {
+                                var pw = panel_1.width(position-sw2).outerWidth();
+                                panel_2.width(self.width()-pw-sw);
+                                splitter.css('left', pw);
+                            }
+                        }
+                        if (!silent) {
+                            self.find('.splitter_panel').trigger('splitter.resize');
+                        }
+                        return self;
+                    };
+                } else if (settings.orientation == 'horizontal') {
+                    return function(n, silent) {
+                        if (n === undefined) {
+                            return position;
+                        } else {
+                            position = get_position(n);
+                            var sw = splitter.height();
+                            var sw2 = sw/2;
+                            if (settings.invisible) {
+                                var pw = panel_1.height(position).outerHeight();
+                                panel_2.height(self.height()-pw);
+                                splitter.css('top', pw-sw2);
+                            } else {
+                                var pw = panel_1.height(position-sw2).outerHeight();
+                                panel_2.height(self.height()-pw-sw);
+                                splitter.css('top', pw);
+                            }
+                        }
+                        if (!silent) {
+                            self.find('.splitter_panel').trigger('splitter.resize');
+                        }
+                        return self;
+                    };
+                } else {
+                    return $.noop;
+                }
+            })(),
+            orientation: settings.orientation,
+            limit: settings.limit,
+            isActive: function() {
+                return splitter_id === id;
+            },
+            destroy: function() {
+                self.removeClass('splitter_panel');
+                splitter.unbind('mouseenter');
+                splitter.unbind('mouseleave');
+                if (settings.orientation == 'vertical') {
+                    panel_1.removeClass('left_panel');
+                    panel_2.removeClass('right_panel');
+                } else if (settings.orientation == 'horizontal') {
+                    panel_1.removeClass('top_panel');
+                    panel_2.removeClass('bottom_panel');
+                }
+                self.unbind('splitter.resize');
+                self.find('.splitter_panel').trigger('splitter.resize');
+                splitters[id] = null;
+                splitter.remove();
+                var not_null = false;
+                for (var i=splitters.length; i--;) {
+                    if (splitters[i] !== null) {
+                        not_null = true;
+                        break;
+                    }
+                }
+                //remove document events when no splitters
+                if (!not_null) {
+                    $(document.documentElement).unbind('.splitter');
+                    $(window).unbind('resize.splitter');
+                    self.data('splitter', null);
+                    splitters = [];
+                    count = 0;
+                }
+            }
+        });
+        self.bind('splitter.resize', function(e) {
+            var pos = self.position();
+            if (self.orientation == 'vertical' && 
+                pos > self.width()) {
+                pos = self.width() - self.limit-1;
+            } else if (self.orientation == 'horizontal' && 
+                       pos > self.height()) {
+                pos = self.height() - self.limit-1;
+            }
+            if (pos < self.limit) {
+                pos = self.limit + 1;
+            }
+            self.position(pos, true);
+        });
+        //inital position of splitter
+        var pos;
+        if (settings.orientation == 'vertical') {
+            if (pos > width-settings.limit) {
+                pos = width-settings.limit;
+            } else {
+                pos = get_position(settings.position);
+            }
+        } else if (settings.orientation == 'horizontal') {
+            //position = height/2;
+            if (pos > height-settings.limit) {
+                pos = height-settings.limit;
+            } else {
+                pos = get_position(settings.position);
+            }
+        }
+        if (pos < settings.limit) {
+            pos = settings.limit;
+        }
+        self.position(pos, true);
+        if (splitters.length == 0) { // first time bind events to document
+            $(window).bind('resize.splitter', function() {
+                $.each(splitters, function(i, splitter) {
+                    splitter.refresh();
+                });
+            });
+            $(document.documentElement).bind('mousedown.splitter', function(e) {
+                if (splitter_id !== null && e.which == 1) {
+                    current_splitter = splitters[splitter_id];
+                    $('<div class="splitterMask"></div>').css('cursor', splitter.css('cursor')).insertAfter(current_splitter);
+                    current_splitter.settings.onDragStart(e);
+                    return false;
+                }
+            }).bind('mouseup.splitter', function(e) {
+                if (current_splitter) {
+                    $('.splitterMask').remove();
+                    current_splitter.settings.onDragEnd(e);
+                    current_splitter = null;
+                }
+            }).bind('mousemove.splitter', function(e) {
+                if (current_splitter !== null) {
+                    var limit = current_splitter.limit;
+                    var offset = current_splitter.offset();
+                    if (current_splitter.orientation == 'vertical') {
+                        var x = e.pageX - offset.left;
+                        if (x <= current_splitter.limit) {
+                            x = current_splitter.limit + 1;
+                        } else if (x >= current_splitter.width() - limit) {
+                            x = current_splitter.width() - limit - 1;
+                        }
+                        if (x > current_splitter.limit &&
+                            x < current_splitter.width()-limit) {
+                            current_splitter.position(x, true);
+                            current_splitter.trigger('splitter.resize');
+                            e.preventDefault();
+                        }
+                    } else if (current_splitter.orientation == 'horizontal') {
+                        var y = e.pageY-offset.top;
+                        if (y <= current_splitter.limit) {
+                            y = current_splitter.limit + 1;
+                        } else if (y >= current_splitter.height() - limit) {
+                            y = current_splitter.height() - limit - 1;
+                        }
+                        if (y > current_splitter.limit &&
+                            y < current_splitter.height()-limit) {
+                            current_splitter.position(y, true);
+                            current_splitter.trigger('splitter.resize');
+                            e.preventDefault();
+                        }
+                    }
+                    current_splitter.settings.onDrag(e);
+                }
+            });
+        }
+        splitters.push(self);
+        self.data('splitter', self);
+        return self;
+    };
+})(jQuery);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/js/libs/mousetrap-global-bind.js	Fri Feb 13 16:57:53 2015 +0100
@@ -0,0 +1,36 @@
+/**
+ * adds a bindGlobal method to Mousetrap that allows you to
+ * bind specific keyboard shortcuts that will still work
+ * inside a text input field
+ *
+ * usage:
+ * Mousetrap.bindGlobal('ctrl+s', _saveChanges);
+ */
+/* global Mousetrap:true */
+Mousetrap = (function(Mousetrap) {
+    var _globalCallbacks = {},
+        _originalStopCallback = Mousetrap.stopCallback;
+
+    Mousetrap.stopCallback = function(e, element, combo, sequence) {
+        if (_globalCallbacks[combo] || _globalCallbacks[sequence]) {
+            return false;
+        }
+
+        return _originalStopCallback(e, element, combo);
+    };
+
+    Mousetrap.bindGlobal = function(keys, callback, action) {
+        Mousetrap.bind(keys, callback, action);
+
+        if (keys instanceof Array) {
+            for (var i = 0; i < keys.length; i++) {
+                _globalCallbacks[keys[i]] = true;
+            }
+            return;
+        }
+
+        _globalCallbacks[keys] = true;
+    };
+
+    return Mousetrap;
+}) (Mousetrap);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/js/libs/mousetrap.min.js	Fri Feb 13 16:57:53 2015 +0100
@@ -0,0 +1,9 @@
+/* mousetrap v1.4.6 craig.is/killing/mice */
+(function(J,r,f){function s(a,b,d){a.addEventListener?a.addEventListener(b,d,!1):a.attachEvent("on"+b,d)}function A(a){if("keypress"==a.type){var b=String.fromCharCode(a.which);a.shiftKey||(b=b.toLowerCase());return b}return h[a.which]?h[a.which]:B[a.which]?B[a.which]:String.fromCharCode(a.which).toLowerCase()}function t(a){a=a||{};var b=!1,d;for(d in n)a[d]?b=!0:n[d]=0;b||(u=!1)}function C(a,b,d,c,e,v){var g,k,f=[],h=d.type;if(!l[a])return[];"keyup"==h&&w(a)&&(b=[a]);for(g=0;g<l[a].length;++g)if(k=
+l[a][g],!(!c&&k.seq&&n[k.seq]!=k.level||h!=k.action||("keypress"!=h||d.metaKey||d.ctrlKey)&&b.sort().join(",")!==k.modifiers.sort().join(","))){var m=c&&k.seq==c&&k.level==v;(!c&&k.combo==e||m)&&l[a].splice(g,1);f.push(k)}return f}function K(a){var b=[];a.shiftKey&&b.push("shift");a.altKey&&b.push("alt");a.ctrlKey&&b.push("ctrl");a.metaKey&&b.push("meta");return b}function x(a,b,d,c){m.stopCallback(b,b.target||b.srcElement,d,c)||!1!==a(b,d)||(b.preventDefault?b.preventDefault():b.returnValue=!1,b.stopPropagation?
+b.stopPropagation():b.cancelBubble=!0)}function y(a){"number"!==typeof a.which&&(a.which=a.keyCode);var b=A(a);b&&("keyup"==a.type&&z===b?z=!1:m.handleKey(b,K(a),a))}function w(a){return"shift"==a||"ctrl"==a||"alt"==a||"meta"==a}function L(a,b,d,c){function e(b){return function(){u=b;++n[a];clearTimeout(D);D=setTimeout(t,1E3)}}function v(b){x(d,b,a);"keyup"!==c&&(z=A(b));setTimeout(t,10)}for(var g=n[a]=0;g<b.length;++g){var f=g+1===b.length?v:e(c||E(b[g+1]).action);F(b[g],f,c,a,g)}}function E(a,b){var d,
+c,e,f=[];d="+"===a?["+"]:a.split("+");for(e=0;e<d.length;++e)c=d[e],G[c]&&(c=G[c]),b&&"keypress"!=b&&H[c]&&(c=H[c],f.push("shift")),w(c)&&f.push(c);d=c;e=b;if(!e){if(!p){p={};for(var g in h)95<g&&112>g||h.hasOwnProperty(g)&&(p[h[g]]=g)}e=p[d]?"keydown":"keypress"}"keypress"==e&&f.length&&(e="keydown");return{key:c,modifiers:f,action:e}}function F(a,b,d,c,e){q[a+":"+d]=b;a=a.replace(/\s+/g," ");var f=a.split(" ");1<f.length?L(a,f,b,d):(d=E(a,d),l[d.key]=l[d.key]||[],C(d.key,d.modifiers,{type:d.action},
+c,a,e),l[d.key][c?"unshift":"push"]({callback:b,modifiers:d.modifiers,action:d.action,seq:c,level:e,combo:a}))}var h={8:"backspace",9:"tab",13:"enter",16:"shift",17:"ctrl",18:"alt",20:"capslock",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"ins",46:"del",91:"meta",93:"meta",224:"meta"},B={106:"*",107:"+",109:"-",110:".",111:"/",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},H={"~":"`","!":"1",
+"@":"2","#":"3",$:"4","%":"5","^":"6","&":"7","*":"8","(":"9",")":"0",_:"-","+":"=",":":";",'"':"'","<":",",">":".","?":"/","|":"\\"},G={option:"alt",command:"meta","return":"enter",escape:"esc",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},p,l={},q={},n={},D,z=!1,I=!1,u=!1;for(f=1;20>f;++f)h[111+f]="f"+f;for(f=0;9>=f;++f)h[f+96]=f;s(r,"keypress",y);s(r,"keydown",y);s(r,"keyup",y);var m={bind:function(a,b,d){a=a instanceof Array?a:[a];for(var c=0;c<a.length;++c)F(a[c],b,d);return this},
+unbind:function(a,b){return m.bind(a,function(){},b)},trigger:function(a,b){if(q[a+":"+b])q[a+":"+b]({},a);return this},reset:function(){l={};q={};return this},stopCallback:function(a,b){return-1<(" "+b.className+" ").indexOf(" mousetrap ")?!1:"INPUT"==b.tagName||"SELECT"==b.tagName||"TEXTAREA"==b.tagName||b.isContentEditable},handleKey:function(a,b,d){var c=C(a,b,d),e;b={};var f=0,g=!1;for(e=0;e<c.length;++e)c[e].seq&&(f=Math.max(f,c[e].level));for(e=0;e<c.length;++e)c[e].seq?c[e].level==f&&(g=!0,
+b[c[e].seq]=1,x(c[e].callback,d,c[e].combo,c[e].seq)):g||x(c[e].callback,d,c[e].combo);c="keypress"==d.type&&I;d.type!=u||w(a)||c||t(b);I=g&&"keydown"==d.type}};J.Mousetrap=m;"function"===typeof define&&define.amd&&define(m)})(window,document);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/js/serializers/ldt_localstorage.js	Fri Feb 13 16:57:53 2015 +0100
@@ -0,0 +1,79 @@
+/* ldt_localstorage serializer: Used to store personal annotations in local storage */
+
+if (typeof IriSP.serializers === "undefined") {
+    IriSP.serializers = {};
+}
+
+IriSP.serializers.ldt_localstorage = {
+    serializeAnnotation : function(_data, _source) {
+        var _annType = _data.getAnnotationType();
+        return {
+            begin: _data.begin.milliseconds,
+            end: _data.end.milliseconds,
+            content: {
+                description: _data.description,
+                title: _data.title,
+                audio: _data.audio
+            },
+            tags: _data.getTagTexts(),
+            media: _data.getMedia().id,
+            type_title: _annType.title,
+            type: ( typeof _annType.dont_send_id !== "undefined" && _annType.dont_send_id ? "" : _annType.id ),
+            meta: {
+                created: _data.created,
+                creator: _data.creator
+            }
+        };
+    },
+    deserializeAnnotation : function(_anndata, _source) {
+        var _ann = new IriSP.Model.Annotation(_anndata.id, _source);
+        _ann.description = _anndata.content.description || "";
+        _ann.title = _anndata.content.title || "";
+        _ann.creator = _anndata.meta.creator || "";
+        _ann.created = new Date(_anndata.meta.created);
+        _ann.setMedia(_anndata.media, _source);
+        var _anntype = _source.getElement(_anndata.type);
+        if (!_anntype) {
+            _anntype = new IriSP.Model.AnnotationType(_anndata.type, _source);
+            _anntype.title = _anndata.type_title;
+            _source.getAnnotationTypes().push(_anntype);
+        }
+        _ann.setAnnotationType(_anntype.id);
+        var _tagIds = IriSP._(_anndata.tags).map(function(_title) {
+            var _tags = _source.getTags(true).searchByTitle(_title, true);
+            if (_tags.length) {
+                var _tag = _tags[0];
+            }
+            else {
+                _tag = new IriSP.Model.Tag(_title.replace(/\W/g,'_'),_source);
+                _tag.title = _title;
+                _source.getTags().push(_tag);
+            }
+            return _tag.id;
+        });
+        _ann.setTags(_tagIds);
+        _ann.setBegin(_anndata.begin);
+        _ann.setEnd(_anndata.end);
+        if (typeof _anndata.content.audio !== "undefined" && _anndata.content.audio.href) {
+            _ann.audio = _anndata.content.audio;
+        }
+        _source.getAnnotations().push(_ann);
+    },
+    serialize : function(_source) {
+        var _this = this;
+        return JSON.stringify(_source.getAnnotations().map(function (a) { return _this.serializeAnnotation(a, _source); }));
+    },
+    deSerialize : function(_data, _source) {
+        var _this = this;
+        if (typeof _data == "string") {
+            _data = JSON.parse(_data);
+        }
+
+        _source.addList('tag', new IriSP.Model.List(_source.directory));
+        _source.addList('annotationType', new IriSP.Model.List(_source.directory));
+        _source.addList('annotation', new IriSP.Model.List(_source.directory));
+        _data.map( function (a) { _this.deserializeAnnotation(a, _source); });
+    }
+};
+
+/* End ldt_localstorage serializer */
--- a/src/js/widgets-container/defaults.js	Fri Feb 13 16:48:05 2015 +0100
+++ b/src/js/widgets-container/defaults.js	Fri Feb 13 16:57:53 2015 +0100
@@ -23,9 +23,13 @@
         backboneRelational: "backbone-relational.js",
         paper: "paper.js",
         jqueryMousewheel: "jquery.mousewheel.min.js",
+        splitter: "jquery.splitter.js",
+        cssSplitter: "jquery.splitter.css",
         renkanPublish: "renkan.js",
         processing: "processing-1.3.6.min.js",
-        recordMicSwf: "record_mic.swf"
+        recordMicSwf: "record_mic.swf",
+        mousetrap: "mousetrap.min.js",
+        mousetrapGlobal: "mousetrap-global-bind.js"
     },
     locations : {
         // use to define locations outside default_dir
@@ -100,6 +104,12 @@
     },
     MultiSegments: {
         noCss: true
+    },
+    SlideVideoPlayer: {
+        requires: [ "jQuery", "jQueryUI", "splitter" ]
+    },
+    Shortcuts: {
+        requires: [ "mousetrap", "mousetrapGlobal" ]
     }
 };
 
--- a/src/js/widgets-container/widget.js	Fri Feb 13 16:48:05 2015 +0100
+++ b/src/js/widgets-container/widget.js	Fri Feb 13 16:57:53 2015 +0100
@@ -146,13 +146,19 @@
 };
 
 IriSP.Widgets.Widget.prototype.getWidgetAnnotations = function() {
+    var result = null;
     if (typeof this.annotation_type === "undefined") {
-        return this.media.getAnnotations();
+        result = this.media.getAnnotations();
+    } else if (this.annotation_type.elementType === "annotationType") {
+        result = this.annotation_type.getAnnotations();
+    } else {
+        result = this.media.getAnnotationsByTypeTitle(this.annotation_type);
     }
-    if (this.annotation_type.elementType === "annotationType") {
-        return this.annotation_type.getAnnotations();
+    if (typeof this.annotation_filter !== "undefined") {
+        return this.annotation_filter(result);
+    } else {
+        return result;
     }
-    return this.media.getAnnotationsByTypeTitle(this.annotation_type);
 };
 
 IriSP.Widgets.Widget.prototype.getWidgetAnnotationsAtTime = function() {
@@ -196,9 +202,32 @@
     });
 };
 
+/*
+ * Position the player to the next/previous annotations based on current player position
+ *
+ * Parameter: offset: -1 for previous annotation, +1 for next annotation
+ */
+IriSP.Widgets.Widget.prototype.navigate = function(offset) {
+    // offset is normally either -1 (previous slide) or +1 (next slide)
+    var _this = this;
+    var currentTime = _this.media.getCurrentTime();
+    var annotations = _this.source.getAnnotations().sortBy(function(_annotation) {
+        return _annotation.begin;
+    });
+    for (var i = 0; i < annotations.length; i++) {
+        if (annotations[i].begin <= currentTime && currentTime < annotations[i].end) {
+            // Found a current annotation - clamp i+offset value to [0, length - 1]
+            i = Math.min(annotations.length - 1, Math.max(0, i + offset));
+            _this.media.setCurrentTime(annotations[i].begin);
+            break;
+        }
+    };
+};
+
+
 /**
  * This method responsible of drawing a widget on screen.
  */
 IriSP.Widgets.Widget.prototype.draw = function() {
     /* implemented by "sub-classes" */
-};
\ No newline at end of file
+};
--- a/src/widgets/Annotation.js	Fri Feb 13 16:48:05 2015 +0100
+++ b/src/widgets/Annotation.js	Fri Feb 13 16:57:53 2015 +0100
@@ -41,9 +41,9 @@
     + '<h3 class="Ldt-Annotation-MashupOrigin Ldt-Annotation-HiddenWhenEmpty">{{l10n.excerpt_from}} <span class="Ldt-Annotation-MashupMedia"></span> <span class="Ldt-Annotation-Time Ldt-Annotation-HiddenWhenMinimized">'
     + '(<span class="Ldt-Annotation-MashupBegin"></span> - <span class="Ldt-Annotation-MashupEnd"></span>)</span></h3>'
     + '<div class="Ldt-Annotation-Cleared Ldt-Annotation-HiddenWhenMinimized Ldt-Annotation-HiddenWhenEmpty Ldt-Annotation-Creator-Block"><div class="Ldt-Annotation-Label">{{l10n.creator_}}</div>'
-    + '{{#show_creator}}<p class="Ldt-Annotation-Labelled Ldt-Annotation-Creator"></p></div>'
-    + '<div class="Ldt-Annotation-Cleared Ldt-Annotation-HiddenWhenMinimized Ldt-Annotation-HiddenWhenEmpty Ldt-Annotation-Description-Block"><div class="Ldt-Annotation-Label">{{l10n.description_}}</div>{{/show_creator}}'
-    + '<p class="Ldt-Annotation-Labelled Ldt-Annotation-Description"></p></div>'
+    + '{{#show_creator}}<p class="Ldt-Annotation-Labelled Ldt-Annotation-Creator"></p></div>{{/show_creator}}'
+    + '{{#show_description}}<div class="Ldt-Annotation-Cleared Ldt-Annotation-HiddenWhenMinimized Ldt-Annotation-HiddenWhenEmpty Ldt-Annotation-Description-Block"><div class="Ldt-Annotation-Label">{{l10n.description_}}</div>'
+    + '<p class="Ldt-Annotation-Labelled Ldt-Annotation-Description"></p></div>{{/show_description}}'
     + '<div class="Ldt-Annotation-Tags-Block Ldt-Annotation-HiddenWhenMinimized Ldt-Annotation-HiddenWhenEmpty Ldt-Annotation-Cleared">'
     + '<div class="Ldt-Annotation-Label">{{l10n.tags_}}</div><ul class="Ldt-Annotation-Labelled Ldt-Annotation-Tags"></ul>'
     + '</div></div></div></div>';
@@ -53,6 +53,7 @@
     start_minimized: false,
     show_arrow : true,
     show_creator: true,
+    show_description: true,
     arrow_position: .5,
     site_name : "Lignes de Temps",
     search_on_tag_click: true,
@@ -231,4 +232,4 @@
 IriSP.Widgets.Annotation.prototype.maximize = function() {
     this.minimized = false;
     this.$.find('.Ldt-Annotation-Inner').removeClass("Ldt-Annotation-Minimized");
-};
\ No newline at end of file
+};
--- a/src/widgets/AnnotationsList.css	Fri Feb 13 16:48:05 2015 +0100
+++ b/src/widgets/AnnotationsList.css	Fri Feb 13 16:57:53 2015 +0100
@@ -106,4 +106,12 @@
 
 .Ldt-AnnotationsList-Play:hover {
     background-position: 0 bottom;
-}
\ No newline at end of file
+}
+
+.Ldt-AnnotationsList-Control-Prev {
+    cursor: url(img/hand_left.png), pointer;
+}
+
+.Ldt-AnnotationsList-Control-Next {
+    cursor: url(img/hand_right.png), pointer;
+}
--- a/src/widgets/AnnotationsList.js	Fri Feb 13 16:48:05 2015 +0100
+++ b/src/widgets/AnnotationsList.js	Fri Feb 13 16:57:53 2015 +0100
@@ -29,6 +29,8 @@
     limit_count : 20,
     newest_first : false,
     show_audio: true,
+    show_creator: false,
+    show_controls: false,
     polemics : [{
         keyword: "++",
         background_color: "#c9ecc6"
@@ -58,15 +60,16 @@
 IriSP.Widgets.AnnotationsList.prototype.template =
     '<div class="Ldt-AnnotationsListWidget">'
     + '{{#show_audio}}<div class="Ldt-AnnotationsList-Audio"></div>{{/show_audio}}'
+    + '{{#show_controls}}<div class="Ldt-AnnotationsList-Controls"><span class="Ldt-AnnotationsList-Control-Prev">Previous</span> | <span class="Ldt-AnnotationsList-Control-Next">Next</span></div>{{/show_controls}}'
     + '<ul class="Ldt-AnnotationsList-ul">'
     + '</ul>'
     + '</div>';
 
 IriSP.Widgets.AnnotationsList.prototype.annotationTemplate = 
-    '<li class="Ldt-AnnotationsList-li Ldt-TraceMe" trace-info="annotation-id:{{id}}, media-id:{{media_id}}" style="{{specific_style}}">'
+    '<li class="Ldt-AnnotationsList-li Ldt-Highlighter-Annotation Ldt-TraceMe" data-annotation="{{ id }}" data-begin="{{ begin_ms }}" data-end="{{ end_ms }}" trace-info="annotation-id:{{id}}, media-id:{{media_id}}" style="{{specific_style}}">'
     + '<div class="Ldt-AnnotationsList-ThumbContainer">'
     + '<a href="{{url}}" draggable="true">'
-    + '<img class="Ldt-AnnotationsList-Thumbnail" src="{{thumbnail}}" />'
+    + '<img title="{{ begin }} - {{ atitle }}" class="Ldt-AnnotationsList-Thumbnail" src="{{thumbnail}}" />'
     + '</a>'
     + '</div>'
     + '<div class="Ldt-AnnotationsList-Duration">{{begin}} - {{end}}</div>'
@@ -187,7 +190,7 @@
                 _description = _annotation.description,
                 _thumbnail = (typeof _annotation.thumbnail !== "undefined" && _annotation.thumbnail ? _annotation.thumbnail : _this.default_thumbnail);
             // Update : display creator
-            if (_annotation.creator) {
+            if (_annotation.creator && _this.show_creator) {
             	_title = _annotation.creator;
             }
             if (_annotation.title) {
@@ -207,16 +210,23 @@
             var _data = {
                 id : _annotation.id,
                 media_id : _annotation.getMedia().id,
+                atitle: IriSP.textFieldHtml(_annotation.title),
                 htitle : IriSP.textFieldHtml(_title),
                 hdescription : IriSP.textFieldHtml(_description),
                 begin : _annotation.begin.toString(),
                 end : _annotation.end.toString(),
+                begin_ms : _annotation.begin.milliseconds,
+                end_ms : _annotation.end.milliseconds,
                 thumbnail : _thumbnail,
                 url : _url,
                 tags : _annotation.getTagTexts(),
                 specific_style : (typeof _bgcolor !== "undefined" ? "background-color: " + _bgcolor : ""),
                 l10n: _this.l10n
             };
+            if (_this.show_controls) {
+                _this.$.find(".Ldt-AnnotationsList-Control-Prev").on("click", function (e) { e.preventDefault(); _this.navigate(-1); });
+                _this.$.find(".Ldt-AnnotationsList-Control-Next").on("click", function (e) { e.preventDefault(); _this.navigate(+1); });
+           }
             if (_this.show_audio && _annotation.audio && _annotation.audio.href && _annotation.audio.href != "null") {
                 _data.audio = true;
                 if (!_this.jwplayers[_annotation.id]) {
--- a/src/widgets/Controller.css	Fri Feb 13 16:48:05 2015 +0100
+++ b/src/widgets/Controller.css	Fri Feb 13 16:57:53 2015 +0100
@@ -86,7 +86,7 @@
 }
 
 input.Ldt-Ctrl-SearchInput {
-    width: 145px; height: 13px; margin: 2px; padding: 3px;
+    width: 145px; height: 20px; margin: 2px; padding: 3px;
     border: 1px solid #8080a0; border-radius: 3px; font-size: 13px;
 }
 
--- a/src/widgets/Controller.js	Fri Feb 13 16:48:05 2015 +0100
+++ b/src/widgets/Controller.js	Fri Feb 13 16:57:53 2015 +0100
@@ -10,7 +10,8 @@
 IriSP.Widgets.Controller.prototype.defaults = {
     disable_annotate_btn: false,
     disable_search_btn: false,
-    disable_ctrl_f: false
+    disable_ctrl_f: false,
+    always_show_search: false
 };
 
 IriSP.Widgets.Controller.prototype.template =
@@ -27,7 +28,7 @@
     + '<div class="Ldt-Ctrl-spacer"></div>'
     + '{{/disable_search_btn}}'
     + '<div class="Ldt-Ctrl-Search">'
-    + '<input class="Ldt-Ctrl-SearchInput Ldt-TraceMe"></input>'
+    + '<input placeholder="{{ l10n.search }}" type="search" class="Ldt-Ctrl-SearchInput Ldt-TraceMe"></input>'
     + '</div>'
     + '</div>'
     + '<div class="Ldt-Ctrl-Right">'
@@ -102,6 +103,7 @@
     this.$.find(".Ldt-Ctrl-SearchBtn").click(this.functionWrapper("searchButtonHandler"));
     
     this.$searchInput.keyup(this.functionWrapper("searchHandler"));
+    this.$searchInput.on("search", this.functionWrapper("searchHandler"));
   
 	var _volctrl = this.$.find(".Ldt-Ctrl-Volume-Control");
     this.$.find('.Ldt-Ctrl-Sound')
@@ -168,7 +170,9 @@
     annotations.on("search-cleared", function() {
         _this.hideSearchBlock();
     });
-   
+    if (_this.always_show_search) {
+        _this.showSearchBlock();
+    }
 };
 
 /* Update the elasped time div */
@@ -237,7 +241,9 @@
 };
 
 IriSP.Widgets.Controller.prototype.hideSearchBlock = function() {
-    this.$searchBlock.animate( { width: 0 }, 200);
+    if (! this.always_show_search) {
+        this.$searchBlock.animate( { width: 0 }, 200);
+    }
 };
 
 /** react to clicks on the search button */
--- a/src/widgets/CreateAnnotation.css	Fri Feb 13 16:48:05 2015 +0100
+++ b/src/widgets/CreateAnnotation.css	Fri Feb 13 16:57:53 2015 +0100
@@ -23,7 +23,7 @@
 }
 
 .Ldt-CreateAnnotation-Main {
-    min-height: 150px;
+    min-height: 100px;
 }
 
 .Ldt-CreateAnnotation-Title, .Ldt-CreateAnnotation-Creator {
@@ -47,6 +47,7 @@
     position: absolute;
     bottom: 7px;
     right: 7px;
+    display: inline;
     color: #ffffff;
     cursor: pointer;
     background: url('img/submit_annotation.png');
@@ -163,4 +164,18 @@
 
 a.Ldt-CreateAnnotation-Close:hover {
     background-position: -17px 0;
-}
\ No newline at end of file
+}
+
+.Ldt-CreateAnnotation-Controls {
+    position: absolute;
+    top: 7px;
+    right: 7px;
+}
+
+span[class^='Ldt-CreateAnnotation-Control-'] {
+    background-color: #ddd;
+    padding: 2px;
+    margin: 4px;
+    border-radius: 3px;
+    border: 1px solid #888;
+}
--- a/src/widgets/CreateAnnotation.js	Fri Feb 13 16:48:05 2015 +0100
+++ b/src/widgets/CreateAnnotation.js	Fri Feb 13 16:57:53 2015 +0100
@@ -1,7 +1,16 @@
 /* TODO: Add Social Network Sharing */
 
 IriSP.Widgets.CreateAnnotation = function(player, config) {
+    var _this = this;
     IriSP.Widgets.Widget.call(this, player, config);
+    if (_this.api_method == 'local' && window.localStorage[_this.api_endpoint_template]) {
+        this.source.onLoad(function () {
+            var _export = _this.player.sourceManager.newLocalSource({serializer: IriSP.serializers[_this.api_serializer]});
+            _export.deSerialize(window.localStorage[_this.api_endpoint_template]);
+            console.log("Loaded personal annotations", _export);
+            _this.source.merge(_export);
+        });
+    };
 };
 
 IriSP.Widgets.CreateAnnotation.prototype = new IriSP.Widgets.Widget();
@@ -12,6 +21,7 @@
     start_visible : true,
     always_visible : false,
     show_slice : true,
+    show_controls: false,
     show_arrow : true,
     show_mic_record: false,
     show_mic_play: false,
@@ -46,7 +56,8 @@
     api_method: "POST",
     after_send_timeout: 0,
     close_after_send: false,
-    tag_prefix: "#"
+    tag_prefix: "#",
+    slice_widget: null
 };
 
 IriSP.Widgets.CreateAnnotation.prototype.messages = {
@@ -104,8 +115,13 @@
     + '<span class="Ldt-CreateAnnotation-Times"> {{#show_slice}}{{l10n.from_time}} {{/show_slice}}{{^show_slice}}{{l10n.at_time}} {{/show_slice}} <span class="Ldt-CreateAnnotation-Begin">00:00</span>'
     + '{{#show_slice}} {{l10n.to_time}} <span class="Ldt-CreateAnnotation-End">{{end}}</span>{{/show_slice}}</span></span>'
     + '{{#show_creator_field}}{{l10n.your_name_}} <input class="Ldt-CreateAnnotation-Creator empty" value="{{creator_name}}" />{{/show_creator_field}}</h3>'
+    + '{{#show_controls}}<div class="Ldt-CreateAnnotation-Controls">'
+    +   '<span class="Ldt-CreateAnnotation-Control-In">IN</span>'
+    +   '<span class="Ldt-CreateAnnotation-Control-Out">OUT</span>'
+    +   '<span class="Ldt-CreateAnnotation-Control-Play">Play</span>'
+    + '</div>{{/show_controls}}'
     + '<textarea class="Ldt-CreateAnnotation-Description empty" placeholder="{{l10n.type_description}}"></textarea>'
-    + '<div class="Ldt-CreateAnnotation-Avatar"><img src="{{creator_avatar}}" title="{{creator_name}}"></img></div>'
+    + '{{#show_creator_field}}<div class="Ldt-CreateAnnotation-Avatar"><img src="{{creator_avatar}}" title="{{creator_name}}"></img></div>{{/show_creator_field}}'
     + '<input type="submit" class="Ldt-CreateAnnotation-Submit" value="{{l10n.submit}}" />'
     + '{{#show_mic_record}}<div class="Ldt-CreateAnnotation-RecBlock"><div class="Ldt-CreateAnnotation-RecLabel">Add voice annotation</div>'
     + '    <object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="220" height="160">'
@@ -130,7 +146,7 @@
     + '             pluginspage="http://www.macromedia.com/go/getflashplayer">'
     + '        </embed>'
     + '    </object>'
-    + '</div>{{/show_mic_record}}' 
+    + '</div>{{/show_mic_record}}'
     + '{{#tags.length}}<div class="Ldt-CreateAnnotation-Tags"><div class="Ldt-CreateAnnotation-TagTitle">{{l10n.add_keywords_}}</div><ul class="Ldt-CreateAnnotation-TagList">'
     + '{{#tags}}<li class="Ldt-CreateAnnotation-TagLi" tag-id="{{id}}" data-text="{{tag_prefix}}{{title}}"><span class="Ldt-CreateAnnotation-TagButton">{{title}}</span></li>{{/tags}}</ul></div>{{/tags.length}}'
     + '{{#polemics.length}}<div class="Ldt-CreateAnnotation-Polemics"><div class="Ldt-CreateAnnotation-PolemicTitle">{{l10n.add_polemic_keywords_}}</div><ul class="Ldt-CreateAnnotation-PolemicList">'
@@ -140,15 +156,15 @@
     + '<div class="Ldt-CreateAnnotation-Screen Ldt-CreateAnnotation-Error">{{^always_visible}}<a title="{{l10n.close_widget}}" class="Ldt-CreateAnnotation-Close" href="#"></a>{{/always_visible}}<div class="Ldt-CreateAnnotation-InnerBox">{{l10n.error_while_contacting}}</div></div>'
     + '<div class="Ldt-CreateAnnotation-Screen Ldt-CreateAnnotation-Saved">{{^always_visible}}<a title="{{l10n.close_widget}}" class="Ldt-CreateAnnotation-Close" href="#"></a>{{/always_visible}}<div class="Ldt-CreateAnnotation-InnerBox">{{l10n.annotation_saved}}</div></div>'
     + '</div></div>';
-    
+
 IriSP.Widgets.CreateAnnotation.prototype.draw = function() {
     var _this = this;
-    
+
     this.begin = new IriSP.Model.Time();
     this.end = this.source.getDuration();
-    
+
     this.tag_prefix = this.tag_prefix || "";
-    
+
     if (this.tag_titles && !this.tags) {
 		if(!(this.tag_titles.length==1 && this.tag_titles[0]=="")){
 			this.tags = IriSP._(this.tag_titles).map(function(_tag_title) {
@@ -184,13 +200,13 @@
     this.renderTemplate();
     if (this.show_mic_record) {
         this.recorder = this.$.find("embed")[0];
-        
+
         window.setAudioUrl = function(_url) {
             _this.audio_url = _url;
-        }
+        };
     }
     if (this.show_slice) {
-        this.insertSubwidget(
+        this.slice_widget = this.insertSubwidget(
             this.$.find(".Ldt-CreateAnnotation-Slice"),
             {
                 type: "Slice",
@@ -243,14 +259,34 @@
     if (this.show_creator_field) {
         this.$.find(".Ldt-CreateAnnotation-Creator").bind("change keyup input paste", this.functionWrapper("onCreatorChange"));
     }
-    
+    this.$.find("[class^='Ldt-CreateAnnotation-Control-']").click(function() {
+        var action = this.className.replace('Ldt-CreateAnnotation-Control-', '');
+        switch (action) {
+            case "In":
+               // Set In bound to current player time
+               _this.begin = new IriSP.Model.Time(_this.media.getCurrentTime() || 0);
+               _this.$.find(".Ldt-CreateAnnotation-Begin").html(_this.begin.toString());
+               break;
+            case "Out":
+               // Set In bound to current player time
+               _this.end = new IriSP.Model.Time(_this.media.getCurrentTime() || _this.media.duration);
+               _this.$.find(".Ldt-CreateAnnotation-End").html(_this.end.toString());
+               break;
+            case "Play":
+               _this.media.setCurrentTime(_this.begin);
+               _this.media.play()
+               break;
+        }
+        return false;
+    });
+
     if (this.start_visible) {
         this.show();
     } else {
         this.$.hide();
         this.hide();
     }
-    
+
     this.onMdpEvent("CreateAnnotation.toggle","toggle");
     this.$.find("form").submit(this.functionWrapper("onSubmit"));
 };
@@ -294,10 +330,18 @@
 };
 
 IriSP.Widgets.CreateAnnotation.prototype.toggle = function() {
+    var _this = this;
     if (!this.always_visible) {
         if (this.visible) {
             this.hide();
         } else {
+            _this.begin = new IriSP.Model.Time(_this.media.getCurrentTime() || 0);
+            _this.end = new IriSP.Model.Time(_this.media.getCurrentTime() || 0);
+            _this.$.find(".Ldt-CreateAnnotation-Begin").html(_this.begin.toString());
+            _this.$.find(".Ldt-CreateAnnotation-End").html(_this.end.toString());
+            if (_this.slice_widget) {
+                _this.slice_widget.setBounds(_this.begin, _this.end);
+            }
             this.show();
         }
     }
@@ -375,11 +419,11 @@
     if (!this.onDescriptionChange() || (this.show_title_field && !this.onTitleChange()) || (this.show_creator_field && !this.onCreatorChange())) {
         return false;
     }
-    
+
     if (this.recorder) {
         this.recorder.stopRecord();
     }
-    
+
     var _this = this,
         _exportedAnnotations = new IriSP.Model.List(this.player.sourceManager), /* Création d'une liste d'annotations contenant une annotation afin de l'envoyer au serveur */
         _export = this.player.sourceManager.newLocalSource({serializer: IriSP.serializers[this.api_serializer]}), /* Création d'un objet source utilisant un sérialiseur spécifique pour l'export */
@@ -387,7 +431,7 @@
         _annotationTypes = this.source.getAnnotationTypes().searchByTitle(this.annotation_type, true), /* Récupération du type d'annotation dans lequel l'annotation doit être ajoutée */
         _annotationType = (_annotationTypes.length ? _annotationTypes[0] : new IriSP.Model.AnnotationType(false, _export)), /* Si le Type d'Annotation n'existe pas, il est créé à la volée */
         _url = Mustache.to_html(this.api_endpoint_template, {id: this.source.projectId}); /* Génération de l'URL à laquelle l'annotation doit être envoyée, qui doit inclure l'ID du projet */
-    
+
     /* Si nous avons dû générer un ID d'annotationType à la volée... */
     if (!_annotationTypes.length) {
         /* Il ne faudra pas envoyer l'ID généré au serveur */
@@ -395,7 +439,7 @@
         /* Il faut inclure le titre dans le type d'annotation */
         _annotationType.title = this.annotation_type;
     }
-    
+
     /*
      * Nous remplissons les données de l'annotation générée à la volée
      * ATTENTION: Si nous sommes sur un MASHUP, ces éléments doivent se référer AU MEDIA D'ORIGINE
@@ -403,20 +447,22 @@
     _annotation.setMedia(this.source.currentMedia.id); /* Id du média annoté */
     _annotation.setBegin(this.begin); /*Timecode de début */
     _annotation.setEnd(this.end); /* Timecode de fin */
-   
+    _annotation.created = new Date(); /* Date de création de l'annotation */
+
     _annotation.setAnnotationType(_annotationType.id); /* Id du type d'annotation */
+    _annotation.description = this.$.find(".Ldt-CreateAnnotation-Description").val(); /* Champ description */
     if (this.show_title_field) {
         /* Champ titre, seulement s'il est visible */
         _annotation.title = this.$.find(".Ldt-CreateAnnotation-Title").val();
+    } else {
+        _annotation.title = _annotation.description;
     }
-    _annotation.created = new Date(); /* Date de création de l'annotation */
-    _annotation.description = this.$.find(".Ldt-CreateAnnotation-Description").val(); /* Champ description */
-   
+
     var tagIds = Array.prototype.map.call(
         this.$.find(".Ldt-CreateAnnotation-TagLi.selected"),
         function(el) { return IriSP.jQuery(el).attr("tag-id")}
     );
-        
+
     IriSP._(_annotation.description.match(/#[^\s#.,;]+/g)).each(function(_tt) {
         var _tag,
             _tag_title = _tt.replace(/^#/,''),
@@ -431,9 +477,9 @@
         if (tagIds.indexOf(_tag.id) === -1) {
             tagIds.push(_tag.id);
         }
-        
+
     })
-   
+
     _annotation.setTags(IriSP._(tagIds).uniq()); /*Liste des ids de tags */
     if (this.audio_url) {
         _annotation.audio = {
@@ -448,47 +494,65 @@
         _annotation.creator = this.creator_name;
     }
     _exportedAnnotations.push(_annotation); /* Ajout de l'annotation à la liste à exporter */
-    _export.addList("annotation",_exportedAnnotations); /* Ajout de la liste à exporter à l'objet Source */
-    
-    var _this = this;
-    /* Envoi de l'annotation via AJAX au serveur ! */
-    IriSP.jQuery.ajax({
-        url: _url,
-        type: this.api_method,
-        contentType: 'application/json',
-        data: _export.serialize(), /* L'objet Source est sérialisé */
-        success: function(_data) {
-            _this.showScreen('Saved'); /* Si l'appel a fonctionné, on affiche l'écran "Annotation enregistrée" */
-            if (_this.after_send_timeout) { /* Selon les options de configuration, on revient à l'écran principal ou on ferme le widget, ou rien */
-                window.setTimeout(
-                    function() {
-                        _this.close_after_send
-                        ? _this.hide()
-                        : _this.show();
-                    },
-                    _this.after_send_timeout
-                );
+
+    if (this.api_method == 'local') {
+        // Append to existing localStorage annotations
+        // FIXME: handle movie ids
+        /* Use localStorage */
+        /* Data will be serialized in the localStore property designated by api_endpoint_template */
+        _export.addList("annotation", _exportedAnnotations); /* Ajout de la liste à exporter à l'objet Source */
+        _this.source.merge(_export); /* On ajoute la nouvelle annotation au recueil original */
+        // Import previously saved local annotations
+        if (window.localStorage[_this.api_endpoint_template]) {
+            _export.deSerialize(window.localStorage[_this.api_endpoint_template]);
+        }
+        // Save everything back
+        window.localStorage[_this.api_endpoint_template] = _export.serialize();
+        if (_this.pause_on_write && _this.media.getPaused()) {
+            _this.media.play();
+        }
+        _this.player.trigger("AnnotationsList.refresh"); /* On force le rafraîchissement du widget AnnotationsList */
+        _this.$.find(".Ldt-CreateAnnotation-Description").val("");
+    } else {
+        _export.addList("annotation",_exportedAnnotations); /* Ajout de la liste à exporter à l'objet Source */
+       /* Envoi de l'annotation via AJAX au serveur ! */
+        IriSP.jQuery.ajax({
+            url: _url,
+            type: this.api_method,
+            contentType: 'application/json',
+            data: _export.serialize(), /* L'objet Source est sérialisé */
+            success: function(_data) {
+                _this.showScreen('Saved'); /* Si l'appel a fonctionné, on affiche l'écran "Annotation enregistrée" */
+                if (_this.after_send_timeout) { /* Selon les options de configuration, on revient à l'écran principal ou on ferme le widget, ou rien */
+                    window.setTimeout(
+                        function() {
+                            _this.close_after_send
+                                ? _this.hide()
+                                : _this.show();
+                        },
+                        _this.after_send_timeout
+                    );
+                }
+                _export.getAnnotations().removeElement(_annotation, true); /* Pour éviter les doublons, on supprime l'annotation qui a été envoyée */
+                _export.deSerialize(_data); /* On désérialise les données reçues pour les réinjecter */
+                _this.source.merge(_export); /* On récupère les données réimportées dans l'espace global des données */
+                if (_this.pause_on_write && _this.media.getPaused()) {
+                    _this.media.play();
+                }
+                _this.player.trigger("AnnotationsList.refresh"); /* On force le rafraîchissement du widget AnnotationsList */
+            },
+            error: function(_xhr, _error, _thrown) {
+                IriSP.log("Error when sending annotation", _thrown);
+                _export.getAnnotations().removeElement(_annotation, true);
+                _this.showScreen('Error');
+                window.setTimeout(function(){
+                    _this.showScreen("Main")
+                },
+                                  (_this.after_send_timeout || 5000));
             }
-            _export.getAnnotations().removeElement(_annotation, true); /* Pour éviter les doublons, on supprime l'annotation qui a été envoyée */
-            _export.deSerialize(_data); /* On désérialise les données reçues pour les réinjecter */
-            _this.source.merge(_export); /* On récupère les données réimportées dans l'espace global des données */
-            if (_this.pause_on_write && _this.media.getPaused()) {
-                _this.media.play();
-            }
-            _this.player.trigger("AnnotationsList.refresh"); /* On force le rafraîchissement du widget AnnotationsList */
-        },
-        error: function(_xhr, _error, _thrown) {
-            IriSP.log("Error when sending annotation", _thrown);
-            _export.getAnnotations().removeElement(_annotation, true);
-            _this.showScreen('Error');
-            window.setTimeout(function(){
-                _this.showScreen("Main")
-            },
-            (_this.after_send_timeout || 5000));
-        }
-    });
-    this.showScreen('Wait');
-    
+        });
+        this.showScreen('Wait');
+    }
+
     return false;
 };
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/widgets/Highlighter.css	Fri Feb 13 16:57:53 2015 +0100
@@ -0,0 +1,3 @@
+.currentAnnotation  {
+    border: solid 3px red;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/widgets/Highlighter.js	Fri Feb 13 16:57:53 2015 +0100
@@ -0,0 +1,43 @@
+IriSP.Widgets.Highlighter = function(player, config) {
+    var _this = this;
+    IriSP.Widgets.Widget.call(this, player, config);
+    this.throttledRefresh = IriSP._.throttle(function() {
+        console.log("highlighter Refresh");
+        _this.update();
+    }, 800);
+};
+
+/**
+ * Highlighter widget
+ * This widgets highlights the current annotations by setting the
+ * .activeAnnotation class on appropriate .Ldt-Highlighter-Annotation
+ * elements. These elements *must* have data-begin and data-end properties specifying their bounds (in ms) (and data-media specifying the media-id)
+ */
+IriSP.Widgets.Highlighter.prototype = new IriSP.Widgets.Widget();
+
+IriSP.Widgets.Highlighter.prototype.defaults =  {
+}
+
+IriSP.Widgets.Highlighter.prototype.update = function() {
+    var  _this = this;
+    var _currentTime = _this.media.getCurrentTime();
+    _this.$.find(".Ldt-Highlighter-Annotation", document).toggleClass("currentAnnotation", function () {
+        return (this.dataset.media === _this.media.id && this.dataset.begin <= _currentTime && _currentTime < this.dataset.end);
+    });
+    console.log(_this.$.find(".currentAnnotation"));
+    return false;
+};
+
+IriSP.Widgets.Highlighter.prototype.draw = function() {
+    var  _this = this;
+    
+    var _events = [
+        "timeupdate",
+        "seeked",
+        "loadedmetadata"
+    ];
+    for (var _i = 0; _i < _events.length; _i++) {
+        _this.onMediaEvent(_events[_i], _this.throttledRefresh);
+    }
+    _this.throttledRefresh();
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/widgets/ImageDisplay.css	Fri Feb 13 16:57:53 2015 +0100
@@ -0,0 +1,32 @@
+/* Nothing */
+.Ldt-ImageDisplay-Container {
+    margin: auto;
+}
+
+.Ldt-ImageDisplay-Image {
+    max-width: 100%;
+    max-height: 100%;
+}
+
+.Ldt-ImageDisplay-Overlay {
+    width: 20%;
+    min-width: 20px;
+    height: 100%;
+    opacity: 0.1;
+    position: absolute;
+    top: 0px;
+    bottom: 0px;
+}
+
+.Ldt-ImageDisplay-Overlay:hover {
+}
+
+.Ldt-ImageDisplay-Overlay-Left {
+    left: 0px;
+    cursor: url(img/hand_left.png), pointer;
+}
+
+.Ldt-ImageDisplay-Overlay-Right {
+    right: 0px;
+    cursor: url(img/hand_right.png), pointer;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/widgets/ImageDisplay.js	Fri Feb 13 16:57:53 2015 +0100
@@ -0,0 +1,42 @@
+/* This widget displays the image associated to the annotation in the given container */
+
+IriSP.Widgets.ImageDisplay = function(player, config) {
+    IriSP.Widgets.Widget.call(this, player, config);
+}
+
+IriSP.Widgets.ImageDisplay.prototype = new IriSP.Widgets.Widget();
+
+IriSP.Widgets.ImageDisplay.prototype.defaults = {
+    annotation_type: "Slides",
+    // container: "imageContainer"
+}
+
+IriSP.Widgets.ImageDisplay.prototype.template = '<div class="Ldt-ImageDisplay-Container"><img class="Ldt-ImageDisplay-Image" title="" alt="Slide Image" src=""/><div class="Ldt-ImageDisplay-Overlay Ldt-ImageDisplay-Overlay-Left"></div><div class="Ldt-ImageDisplay-Overlay Ldt-ImageDisplay-Overlay-Right"></div></div>';
+
+IriSP.Widgets.ImageDisplay.prototype.annotationTemplate = '';
+
+IriSP.Widgets.ImageDisplay.prototype.update = function(annotation) {
+    // Update the widget with data corresponding to the annotation
+    this.image.setAttribute("title", IriSP.textFieldHtml(annotation.title) + " - " + annotation.begin.toString());
+    this.image.setAttribute("src", annotation.thumbnail);
+};
+
+IriSP.Widgets.ImageDisplay.prototype.draw = function() {    
+    var _annotations = this.getWidgetAnnotations().sortBy(function(_annotation) {
+        return _annotation.begin;
+    });
+    var _this = this;
+    _this.renderTemplate();
+    _this.image = _this.$.find("img")[0];
+
+    _this.$.find(".Ldt-ImageDisplay-Overlay-Left").on("click", function () { _this.navigate(-1); });
+    _this.$.find(".Ldt-ImageDisplay-Overlay-Right").on("click", function () { _this.navigate(+1); });
+
+    _annotations.forEach(function(_a) {
+        _a.on("enter", function() {
+            _this.update(_a);
+        });
+    });
+    if (_annotations.length)
+        _this.update(_annotations[0]);
+}
--- a/src/widgets/MultiSegments.js	Fri Feb 13 16:48:05 2015 +0100
+++ b/src/widgets/MultiSegments.js	Fri Feb 13 16:57:53 2015 +0100
@@ -133,4 +133,4 @@
             }
         });*/
     }
-};
\ No newline at end of file
+};
--- a/src/widgets/Segments.js	Fri Feb 13 16:48:05 2015 +0100
+++ b/src/widgets/Segments.js	Fri Feb 13 16:57:53 2015 +0100
@@ -13,7 +13,9 @@
     background: "#e0e0e0",
     overlap: .25,
     found_color: "#FF00FC",
-    faded_found_color: "#ff80fc"
+    faded_found_color: "#ff80fc",
+    // Display creator info in segment tooltip
+    show_creator: true
 };
 
 IriSP.Widgets.Segments.prototype.template =
@@ -77,7 +79,7 @@
             color : color,
             medcolor: medcolor,
             lowcolor: lowcolor,
-            text: (_annotation.creator ? (_annotation.creator + " : ") : "" ) + _fulltext.replace(/(\n|\r|\r\n)/mg,' ').replace(/(^.{120,140})[\s].+$/m,'$1&hellip;'),
+            text: ((_this.show_creator && _annotation.creator) ? (_annotation.creator + " : ") : "" ) + _fulltext.replace(/(\n|\r|\r\n)/mg,' ').replace(/(^.{120,140})[\s].+$/m,'$1&hellip;'),
             left : _left,
             width : _width,
             top: _top,
@@ -89,14 +91,17 @@
         };
         var _html = Mustache.to_html(_this.annotationTemplate, _data),
             _el = IriSP.jQuery(_html);
-        _el.mouseover(function() {
+            _el.mouseover(function() {
                 _annotation.trigger("select");
+                _this.player.trigger('annotation-select', _annotation);
             })
             .mouseout(function() {
                 _annotation.trigger("unselect");
+                _this.player.trigger('annotation-unselect', _annotation);
             })
             .click(function() {
                 _annotation.trigger("click");
+                _this.player.trigger('annotation-click', _annotation);
             })
             .appendTo(list_$);
         IriSP.attachDndData(_el, {
@@ -172,4 +177,4 @@
     this.$.find('.Ldt-Segments-Position').css({
         left: _x + "px"
     });
-};
\ No newline at end of file
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/widgets/Shortcuts.css	Fri Feb 13 16:57:53 2015 +0100
@@ -0,0 +1,1 @@
+/* Empty file */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/widgets/Shortcuts.js	Fri Feb 13 16:57:53 2015 +0100
@@ -0,0 +1,58 @@
+IriSP.Widgets.Shortcuts = function(player, config) {
+    IriSP.Widgets.Widget.call(this, player, config);
+};
+
+/**
+ * Keyboard shortcuts widget
+ * This widgets add global shortcuts for common actions.
+ * The default shortcuts are: 
+ * - Escape or Control-space for play/pause
+ * - Control-left for rewind (+shift to go faster)
+ * - Control-right for forward (+shift to go faster)
+ */
+IriSP.Widgets.Shortcuts.prototype = new IriSP.Widgets.Widget();
+
+IriSP.Widgets.Shortcuts.prototype.defaults =  {
+    // Time increment, in ms, for backward/forward navigation
+    time_increment: 2000
+}
+
+IriSP.Widgets.Shortcuts.prototype.draw = function() {
+    var  _this = this;
+    
+    /* Standard shortcuts */
+    Mousetrap.bindGlobal(["esc", "ctrl+space"], function (e) {
+        e.preventDefault();
+        if (! _this.media.getPaused()) {
+            _this.media.pause();
+        } else {
+            _this.media.play();
+        }
+        return false;
+    });
+    Mousetrap.bindGlobal("ctrl+left", function (e) {
+        // Backward
+        e.preventDefault();
+        _this.media.setCurrentTime(Math.max(0, _this.media.getCurrentTime() - _this.time_increment));
+        return false;
+    });
+    Mousetrap.bindGlobal("ctrl+shift+left", function (e) {
+        // Backward
+        e.preventDefault();
+        _this.media.setCurrentTime(Math.max(0, _this.media.getCurrentTime() - 5 * _this.time_increment));
+        return false;
+    });
+    Mousetrap.bindGlobal("ctrl+right", function (e) {
+        // Forward
+        e.preventDefault();
+        _this.media.setCurrentTime(Math.min(_this.media.duration, _this.media.getCurrentTime() + _this.time_increment));
+        return false;
+    });
+    Mousetrap.bindGlobal("ctrl+shift+right", function (e) {
+        // Forward
+        e.preventDefault();
+        _this.media.setCurrentTime(Math.min(_this.media.duration, _this.media.getCurrentTime() + 5 * _this.time_increment));
+        return false;
+    });
+
+};
--- a/src/widgets/Slice.js	Fri Feb 13 16:48:05 2015 +0100
+++ b/src/widgets/Slice.js	Fri Feb 13 16:57:53 2015 +0100
@@ -67,9 +67,16 @@
     
     this.getWidgetAnnotations().forEach(function(_a) {
         _a.on("enter", function() {
-            _this.$slider.slider("values",[_a.begin, _a.end]);
+            _this.setBounds(_a.begin, _a.end);
         });
     });
+    this.player.on("annotation-click", function(_a) {
+        _this.setBounds(_a.begin, _a.end);
+    });
+};
+
+IriSP.Widgets.Slice.prototype.setBounds = function(begin, end) {
+    this.$slider.slider("values", [ begin, end ]);
 };
 
 IriSP.Widgets.Slice.prototype.show = function() {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/widgets/SlideVideoPlayer.css	Fri Feb 13 16:57:53 2015 +0100
@@ -0,0 +1,23 @@
+/* Empty for now */
+
+.Ldt-SlideVideoPlayer-panel {
+    display: flex;
+    width: 50%;
+    float: left;
+}
+
+.Ldt-SlideVideoPlayer {
+    min-height: 400px;
+    max-height: 100%;
+}
+
+.Ldt-SlideVideoPlayer {
+    width: 100%;
+    max-width: 100%;
+    height: 100%;
+    max-height: 100%;
+}
+
+.Ldt-SlideVideoPlayer h2 {
+    display: none;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/widgets/SlideVideoPlayer.js	Fri Feb 13 16:57:53 2015 +0100
@@ -0,0 +1,46 @@
+IriSP.Widgets.SlideVideoPlayer = function(player, config) {
+    IriSP.loadCss(IriSP.getLib("cssSplitter"));
+    IriSP.Widgets.Widget.call(this, player, config);
+};
+
+IriSP.Widgets.SlideVideoPlayer.prototype = new IriSP.Widgets.Widget();
+
+
+IriSP.Widgets.SlideVideoPlayer.prototype.defaults = {
+};
+
+IriSP.Widgets.SlideVideoPlayer.prototype.template = '<div class="Ldt-SlideVideoPlayer">\
+<div class="Ldt-SlideVideoPlayer-slide Ldt-SlideVideoPlayer-panel">\
+</div>\
+<div class="Ldt-SlideVideoPlayer-video Ldt-SlideVideoPlayer-panel"></div>\
+</div>';
+
+IriSP.Widgets.SlideVideoPlayer.prototype.draw = function() {
+    var _this = this;
+
+    _this.renderTemplate();
+    this.insertSubwidget(
+        _this.$.find(".Ldt-SlideVideoPlayer-panel.Ldt-SlideVideoPlayer-slide"),
+            {
+                type: "ImageDisplay",
+                annotation_type: _this.annotation_type,
+                width: '100%'
+            },
+            "slide"
+        );
+    this.insertSubwidget(
+        _this.$.find(".Ldt-SlideVideoPlayer-panel.Ldt-SlideVideoPlayer-video"),
+            {
+                type: "HtmlPlayer",
+                video: _this.video,
+                width: '100%',
+                url_transform: _this.url_transform
+            },
+            "player"
+        );
+    // FIXME: this should be better implemented through a signal sent
+    // when widgets are ready (and not just loaded)
+    window.setTimeout(function () {
+        _this.$.find(".Ldt-SlideVideoPlayer").split({ orientation: 'vertical' });
+    }, 1000);
+}
--- a/src/widgets/Trace.js	Fri Feb 13 16:48:05 2015 +0100
+++ b/src/widgets/Trace.js	Fri Feb 13 16:57:53 2015 +0100
@@ -68,9 +68,9 @@
     
     
     this.tracer.trace("TraceWidgetInit", {});
-    
+    _this.player.trigger("trace-ready");
     this.mouseLocation = '';
-    IriSP.jQuery(".Ldt-Widget").on("click mouseenter mouseleave", ".Ldt-TraceMe", function(_e) {
+    IriSP.jQuery(".Ldt-Widget").on("mousedown mouseenter mouseleave", ".Ldt-TraceMe", function(_e) {
         var _target = IriSP.jQuery(this);
         
         var _widget = _target.attr("widget-type") || _target.parents(".Ldt-Widget").attr("widget-type"),
@@ -127,4 +127,4 @@
     if (this.js_console && typeof window.console !== "undefined" && typeof console.log !== "undefined") {
         console.log("tracer.trace('" + _traceName + "', " + JSON.stringify(_arg) + ");");
     }
-};
\ No newline at end of file
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/widgets/Transcript.js	Fri Feb 13 16:57:53 2015 +0100
@@ -0,0 +1,37 @@
+/* This widget displays annotations as a transcript */
+
+IriSP.Widgets.Transcript = function(player, config) {
+    IriSP.Widgets.Widget.call(this, player, config);
+}
+
+IriSP.Widgets.Transcript.prototype = new IriSP.Widgets.Widget();
+
+IriSP.Widgets.Transcript.prototype.defaults = {
+    annotation_type: "Caption"
+    // container: "transcriptContainer"
+}
+
+IriSP.Widgets.Transcript.prototype.template = '<div class="Ldt-TranscriptWidget"></div>';
+
+IriSP.Widgets.Transcript.prototype.annotationTemplate = '<a data-begin="{{ begin }}" data-end="{{ end }}" data-id="{{ id }}" class="Ldt-Transcript-Annotation" href="#{{id}}">{{ content }}</a> ';
+
+IriSP.Widgets.Transcript.prototype.draw = function() {
+    var _annotations = this.getWidgetAnnotations();
+    var _this = this;
+    var content;
+
+    _this.renderTemplate();
+    content = _this.$.find('.Ldt-TranscriptWidget');
+
+    _annotations.forEach(function(_a) {
+        var _data = {
+            id : _a.id,
+            content : IriSP.textFieldHtml(_a.title),
+            begin : _a.begin.toString(),
+            end : _a.end.toString()
+        };
+        var _html = Mustache.to_html(_this.annotationTemplate, _data);
+        var _el = IriSP.jQuery(_html);
+        content.append(_el);
+    });
+};
Binary file src/widgets/img/hand_left.png has changed
Binary file src/widgets/img/hand_right.png has changed