web/res/js/LdtPlayer-release.js
changeset 598 d366aa22bd79
parent 597 07ab28bca482
child 599 42bce53bd476
equal deleted inserted replaced
597:07ab28bca482 598:d366aa22bd79
     1 /* 
       
     2  * 	
       
     3  *	Copyright 2010-2012 Institut de recherche et d'innovation 
       
     4  *	contributor(s) : Karim Hamidou, Samuel Huron, Raphael Velt, Thibaut Cavalie
       
     5  *	 
       
     6  *	contact@iri.centrepompidou.fr
       
     7  *	http://www.iri.centrepompidou.fr 
       
     8  *	 
       
     9  *	This software is a computer program whose purpose is to show and add annotations on a video .
       
    10  *	This software is governed by the CeCILL-C license under French law and
       
    11  *	abiding by the rules of distribution of free software. You can  use, 
       
    12  *	modify and/ or redistribute the software under the terms of the CeCILL-C
       
    13  *	license as circulated by CEA, CNRS and INRIA at the following URL
       
    14  *	"http://www.cecill.info". 
       
    15  *	
       
    16  *	The fact that you are presently reading this means that you have had
       
    17  *	knowledge of the CeCILL-C license and that you accept its terms.
       
    18 */
       
    19 /*! LAB.js (LABjs :: Loading And Blocking JavaScript)
       
    20     v2.0.3 (c) Kyle Simpson
       
    21     MIT License
       
    22 */
       
    23 
       
    24 (function(global){
       
    25 	var _$LAB = global.$LAB,
       
    26 	
       
    27 		// constants for the valid keys of the options object
       
    28 		_UseLocalXHR = "UseLocalXHR",
       
    29 		_AlwaysPreserveOrder = "AlwaysPreserveOrder",
       
    30 		_AllowDuplicates = "AllowDuplicates",
       
    31 		_CacheBust = "CacheBust",
       
    32 		/*!START_DEBUG*/_Debug = "Debug",/*!END_DEBUG*/
       
    33 		_BasePath = "BasePath",
       
    34 		
       
    35 		// stateless variables used across all $LAB instances
       
    36 		root_page = /^[^?#]*\//.exec(location.href)[0],
       
    37 		root_domain = /^\w+\:\/\/\/?[^\/]+/.exec(root_page)[0],
       
    38 		append_to = document.head || document.getElementsByTagName("head"),
       
    39 		
       
    40 		// inferences... ick, but still necessary
       
    41 		opera_or_gecko = (global.opera && Object.prototype.toString.call(global.opera) == "[object Opera]") || ("MozAppearance" in document.documentElement.style),
       
    42 
       
    43 /*!START_DEBUG*/
       
    44 		// console.log() and console.error() wrappers
       
    45 		log_msg = function(){}, 
       
    46 		log_error = log_msg,
       
    47 /*!END_DEBUG*/
       
    48 		
       
    49 		// feature sniffs (yay!)
       
    50 		test_script_elem = document.createElement("script"),
       
    51 		explicit_preloading = typeof test_script_elem.preload == "boolean", // http://wiki.whatwg.org/wiki/Script_Execution_Control#Proposal_1_.28Nicholas_Zakas.29
       
    52 		real_preloading = explicit_preloading || (test_script_elem.readyState && test_script_elem.readyState == "uninitialized"), // will a script preload with `src` set before DOM append?
       
    53 		script_ordered_async = !real_preloading && test_script_elem.async === true, // http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order
       
    54 		
       
    55 		// XHR preloading (same-domain) and cache-preloading (remote-domain) are the fallbacks (for some browsers)
       
    56 		xhr_or_cache_preloading = !real_preloading && !script_ordered_async && !opera_or_gecko
       
    57 	;
       
    58 
       
    59 /*!START_DEBUG*/
       
    60 	// define console wrapper functions if applicable
       
    61 	if (global.console && global.console.log) {
       
    62 		if (!global.console.error) global.console.error = global.console.log;
       
    63 		log_msg = function(msg) { global.console.log(msg); };
       
    64 		log_error = function(msg,err) { global.console.error(msg,err); };
       
    65 	}
       
    66 /*!END_DEBUG*/
       
    67 
       
    68 	// test for function
       
    69 	function is_func(func) { return Object.prototype.toString.call(func) == "[object Function]"; }
       
    70 
       
    71 	// test for array
       
    72 	function is_array(arr) { return Object.prototype.toString.call(arr) == "[object Array]"; }
       
    73 
       
    74 	// make script URL absolute/canonical
       
    75 	function canonical_uri(src,base_path) {
       
    76 		var absolute_regex = /^\w+\:\/\//;
       
    77 		
       
    78 		// is `src` is protocol-relative (begins with // or ///), prepend protocol
       
    79 		if (/^\/\/\/?/.test(src)) {
       
    80 			src = location.protocol + src;
       
    81 		}
       
    82 		// is `src` page-relative? (not an absolute URL, and not a domain-relative path, beginning with /)
       
    83 		else if (!absolute_regex.test(src) && src.charAt(0) != "/") {
       
    84 			// prepend `base_path`, if any
       
    85 			src = (base_path || "") + src;
       
    86 		}
       
    87 		// make sure to return `src` as absolute
       
    88 		return absolute_regex.test(src) ? src : ((src.charAt(0) == "/" ? root_domain : root_page) + src);
       
    89 	}
       
    90 
       
    91 	// merge `source` into `target`
       
    92 	function merge_objs(source,target) {
       
    93 		for (var k in source) { if (source.hasOwnProperty(k)) {
       
    94 			target[k] = source[k]; // TODO: does this need to be recursive for our purposes?
       
    95 		}}
       
    96 		return target;
       
    97 	}
       
    98 
       
    99 	// does the chain group have any ready-to-execute scripts?
       
   100 	function check_chain_group_scripts_ready(chain_group) {
       
   101 		var any_scripts_ready = false;
       
   102 		for (var i=0; i<chain_group.scripts.length; i++) {
       
   103 			if (chain_group.scripts[i].ready && chain_group.scripts[i].exec_trigger) {
       
   104 				any_scripts_ready = true;
       
   105 				chain_group.scripts[i].exec_trigger();
       
   106 				chain_group.scripts[i].exec_trigger = null;
       
   107 			}
       
   108 		}
       
   109 		return any_scripts_ready;
       
   110 	}
       
   111 
       
   112 	// creates a script load listener
       
   113 	function create_script_load_listener(elem,registry_item,flag,onload) {
       
   114 		elem.onload = elem.onreadystatechange = function() {
       
   115 			if ((elem.readyState && elem.readyState != "complete" && elem.readyState != "loaded") || registry_item[flag]) return;
       
   116 			elem.onload = elem.onreadystatechange = null;
       
   117 			onload();
       
   118 		};
       
   119 	}
       
   120 
       
   121 	// script executed handler
       
   122 	function script_executed(registry_item) {
       
   123 		registry_item.ready = registry_item.finished = true;
       
   124 		for (var i=0; i<registry_item.finished_listeners.length; i++) {
       
   125 			registry_item.finished_listeners[i]();
       
   126 		}
       
   127 		registry_item.ready_listeners = [];
       
   128 		registry_item.finished_listeners = [];
       
   129 	}
       
   130 
       
   131 	// make the request for a scriptha
       
   132 	function request_script(chain_opts,script_obj,registry_item,onload,preload_this_script) {
       
   133 		// setTimeout() "yielding" prevents some weird race/crash conditions in older browsers
       
   134 		setTimeout(function(){
       
   135 			var script, src = script_obj.real_src, xhr;
       
   136 			
       
   137 			// don't proceed until `append_to` is ready to append to
       
   138 			if ("item" in append_to) { // check if `append_to` ref is still a live node list
       
   139 				if (!append_to[0]) { // `append_to` node not yet ready
       
   140 					// try again in a little bit -- note: will re-call the anonymous function in the outer setTimeout, not the parent `request_script()`
       
   141 					setTimeout(arguments.callee,25);
       
   142 					return;
       
   143 				}
       
   144 				// reassign from live node list ref to pure node ref -- avoids nasty IE bug where changes to DOM invalidate live node lists
       
   145 				append_to = append_to[0];
       
   146 			}
       
   147 			script = document.createElement("script");
       
   148 			if (script_obj.type) script.type = script_obj.type;
       
   149 			if (script_obj.charset) script.charset = script_obj.charset;
       
   150 			
       
   151 			// should preloading be used for this script?
       
   152 			if (preload_this_script) {
       
   153 				// real script preloading?
       
   154 				if (real_preloading) {
       
   155 					/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script preload: "+src);/*!END_DEBUG*/
       
   156 					registry_item.elem = script;
       
   157 					if (explicit_preloading) { // explicit preloading (aka, Zakas' proposal)
       
   158 						script.preload = true;
       
   159 						script.onpreload = onload;
       
   160 					}
       
   161 					else {
       
   162 						script.onreadystatechange = function(){
       
   163 							if (script.readyState == "loaded") onload();
       
   164 						};
       
   165 					}
       
   166 					script.src = src;
       
   167 					// NOTE: no append to DOM yet, appending will happen when ready to execute
       
   168 				}
       
   169 				// same-domain and XHR allowed? use XHR preloading
       
   170 				else if (preload_this_script && src.indexOf(root_domain) == 0 && chain_opts[_UseLocalXHR]) {
       
   171 					xhr = new XMLHttpRequest(); // note: IE never uses XHR (it supports true preloading), so no more need for ActiveXObject fallback for IE <= 7
       
   172 					/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script preload (xhr): "+src);/*!END_DEBUG*/
       
   173 					xhr.onreadystatechange = function() {
       
   174 						if (xhr.readyState == 4) {
       
   175 							xhr.onreadystatechange = function(){}; // fix a memory leak in IE
       
   176 							registry_item.text = xhr.responseText + "\n//@ sourceURL=" + src; // http://blog.getfirebug.com/2009/08/11/give-your-eval-a-name-with-sourceurl/
       
   177 							onload();
       
   178 						}
       
   179 					};
       
   180 					xhr.open("GET",src);
       
   181 					xhr.send();
       
   182 				}
       
   183 				// as a last resort, use cache-preloading
       
   184 				else {
       
   185 					/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script preload (cache): "+src);/*!END_DEBUG*/
       
   186 					script.type = "text/cache-script";
       
   187 					create_script_load_listener(script,registry_item,"ready",function() {
       
   188 						append_to.removeChild(script);
       
   189 						onload();
       
   190 					});
       
   191 					script.src = src;
       
   192 					append_to.insertBefore(script,append_to.firstChild);
       
   193 				}
       
   194 			}
       
   195 			// use async=false for ordered async? parallel-load-serial-execute http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order
       
   196 			else if (script_ordered_async) {
       
   197 				/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script load (ordered async): "+src);/*!END_DEBUG*/
       
   198 				script.async = false;
       
   199 				create_script_load_listener(script,registry_item,"finished",onload);
       
   200 				script.src = src;
       
   201 				append_to.insertBefore(script,append_to.firstChild);
       
   202 			}
       
   203 			// otherwise, just a normal script element
       
   204 			else {
       
   205 				/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script load: "+src);/*!END_DEBUG*/
       
   206 				create_script_load_listener(script,registry_item,"finished",onload);
       
   207 				script.src = src;
       
   208 				append_to.insertBefore(script,append_to.firstChild);
       
   209 			}
       
   210 		},0);
       
   211 	}
       
   212 		
       
   213 	// create a clean instance of $LAB
       
   214 	function create_sandbox() {
       
   215 		var global_defaults = {},
       
   216 			can_use_preloading = real_preloading || xhr_or_cache_preloading,
       
   217 			queue = [],
       
   218 			registry = {},
       
   219 			instanceAPI
       
   220 		;
       
   221 		
       
   222 		// global defaults
       
   223 		global_defaults[_UseLocalXHR] = true;
       
   224 		global_defaults[_AlwaysPreserveOrder] = false;
       
   225 		global_defaults[_AllowDuplicates] = false;
       
   226 		global_defaults[_CacheBust] = false;
       
   227 		/*!START_DEBUG*/global_defaults[_Debug] = false;/*!END_DEBUG*/
       
   228 		global_defaults[_BasePath] = "";
       
   229 
       
   230 		// execute a script that has been preloaded already
       
   231 		function execute_preloaded_script(chain_opts,script_obj,registry_item) {
       
   232 			var script;
       
   233 			
       
   234 			function preload_execute_finished() {
       
   235 				if (script != null) { // make sure this only ever fires once
       
   236 					script = null;
       
   237 					script_executed(registry_item);
       
   238 				}
       
   239 			}
       
   240 			
       
   241 			if (registry[script_obj.src].finished) return;
       
   242 			if (!chain_opts[_AllowDuplicates]) registry[script_obj.src].finished = true;
       
   243 			
       
   244 			script = registry_item.elem || document.createElement("script");
       
   245 			if (script_obj.type) script.type = script_obj.type;
       
   246 			if (script_obj.charset) script.charset = script_obj.charset;
       
   247 			create_script_load_listener(script,registry_item,"finished",preload_execute_finished);
       
   248 			
       
   249 			// script elem was real-preloaded
       
   250 			if (registry_item.elem) {
       
   251 				registry_item.elem = null;
       
   252 			}
       
   253 			// script was XHR preloaded
       
   254 			else if (registry_item.text) {
       
   255 				script.onload = script.onreadystatechange = null;	// script injection doesn't fire these events
       
   256 				script.text = registry_item.text;
       
   257 			}
       
   258 			// script was cache-preloaded
       
   259 			else {
       
   260 				script.src = script_obj.real_src;
       
   261 			}
       
   262 			append_to.insertBefore(script,append_to.firstChild);
       
   263 
       
   264 			// manually fire execution callback for injected scripts, since events don't fire
       
   265 			if (registry_item.text) {
       
   266 				preload_execute_finished();
       
   267 			}
       
   268 		}
       
   269 	
       
   270 		// process the script request setup
       
   271 		function do_script(chain_opts,script_obj,chain_group,preload_this_script) {
       
   272 			var registry_item,
       
   273 				registry_items,
       
   274 				ready_cb = function(){ script_obj.ready_cb(script_obj,function(){ execute_preloaded_script(chain_opts,script_obj,registry_item); }); },
       
   275 				finished_cb = function(){ script_obj.finished_cb(script_obj,chain_group); }
       
   276 			;
       
   277 			
       
   278 			script_obj.src = canonical_uri(script_obj.src,chain_opts[_BasePath]);
       
   279 			script_obj.real_src = script_obj.src + 
       
   280 				// append cache-bust param to URL?
       
   281 				(chain_opts[_CacheBust] ? ((/\?.*$/.test(script_obj.src) ? "&_" : "?_") + ~~(Math.random()*1E9) + "=") : "")
       
   282 			;
       
   283 			
       
   284 			if (!registry[script_obj.src]) registry[script_obj.src] = {items:[],finished:false};
       
   285 			registry_items = registry[script_obj.src].items;
       
   286 
       
   287 			// allowing duplicates, or is this the first recorded load of this script?
       
   288 			if (chain_opts[_AllowDuplicates] || registry_items.length == 0) {
       
   289 				registry_item = registry_items[registry_items.length] = {
       
   290 					ready:false,
       
   291 					finished:false,
       
   292 					ready_listeners:[ready_cb],
       
   293 					finished_listeners:[finished_cb]
       
   294 				};
       
   295 
       
   296 				request_script(chain_opts,script_obj,registry_item,
       
   297 					// which callback type to pass?
       
   298 					(
       
   299 					 	(preload_this_script) ? // depends on script-preloading
       
   300 						function(){
       
   301 							registry_item.ready = true;
       
   302 							for (var i=0; i<registry_item.ready_listeners.length; i++) {
       
   303 								registry_item.ready_listeners[i]();
       
   304 							}
       
   305 							registry_item.ready_listeners = [];
       
   306 						} :
       
   307 						function(){ script_executed(registry_item); }
       
   308 					),
       
   309 					// signal if script-preloading should be used or not
       
   310 					preload_this_script
       
   311 				);
       
   312 			}
       
   313 			else {
       
   314 				registry_item = registry_items[0];
       
   315 				if (registry_item.finished) {
       
   316 					finished_cb();
       
   317 				}
       
   318 				else {
       
   319 					registry_item.finished_listeners.push(finished_cb);
       
   320 				}
       
   321 			}
       
   322 		}
       
   323 
       
   324 		// creates a closure for each separate chain spawned from this $LAB instance, to keep state cleanly separated between chains
       
   325 		function create_chain() {
       
   326 			var chainedAPI,
       
   327 				chain_opts = merge_objs(global_defaults,{}),
       
   328 				chain = [],
       
   329 				exec_cursor = 0,
       
   330 				scripts_currently_loading = false,
       
   331 				group
       
   332 			;
       
   333 			
       
   334 			// called when a script has finished preloading
       
   335 			function chain_script_ready(script_obj,exec_trigger) {
       
   336 				/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("script preload finished: "+script_obj.real_src);/*!END_DEBUG*/
       
   337 				script_obj.ready = true;
       
   338 				script_obj.exec_trigger = exec_trigger;
       
   339 				advance_exec_cursor(); // will only check for 'ready' scripts to be executed
       
   340 			}
       
   341 
       
   342 			// called when a script has finished executing
       
   343 			function chain_script_executed(script_obj,chain_group) {
       
   344 				/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("script execution finished: "+script_obj.real_src);/*!END_DEBUG*/
       
   345 				script_obj.ready = script_obj.finished = true;
       
   346 				script_obj.exec_trigger = null;
       
   347 				// check if chain group is all finished
       
   348 				for (var i=0; i<chain_group.scripts.length; i++) {
       
   349 					if (!chain_group.scripts[i].finished) return;
       
   350 				}
       
   351 				// chain_group is all finished if we get this far
       
   352 				chain_group.finished = true;
       
   353 				advance_exec_cursor();
       
   354 			}
       
   355 
       
   356 			// main driver for executing each part of the chain
       
   357 			function advance_exec_cursor() {
       
   358 				while (exec_cursor < chain.length) {
       
   359 					if (is_func(chain[exec_cursor])) {
       
   360 						/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("$LAB.wait() executing: "+chain[exec_cursor]);/*!END_DEBUG*/
       
   361 						try { chain[exec_cursor++](); } catch (err) {
       
   362 							/*!START_DEBUG*/if (chain_opts[_Debug]) log_error("$LAB.wait() error caught: ",err);/*!END_DEBUG*/
       
   363 						}
       
   364 						continue;
       
   365 					}
       
   366 					else if (!chain[exec_cursor].finished) {
       
   367 						if (check_chain_group_scripts_ready(chain[exec_cursor])) continue;
       
   368 						break;
       
   369 					}
       
   370 					exec_cursor++;
       
   371 				}
       
   372 				// we've reached the end of the chain (so far)
       
   373 				if (exec_cursor == chain.length) {
       
   374 					scripts_currently_loading = false;
       
   375 					group = false;
       
   376 				}
       
   377 			}
       
   378 			
       
   379 			// setup next chain script group
       
   380 			function init_script_chain_group() {
       
   381 				if (!group || !group.scripts) {
       
   382 					chain.push(group = {scripts:[],finished:true});
       
   383 				}
       
   384 			}
       
   385 
       
   386 			// API for $LAB chains
       
   387 			chainedAPI = {
       
   388 				// start loading one or more scripts
       
   389 				script:function(){
       
   390 					for (var i=0; i<arguments.length; i++) {
       
   391 						(function(script_obj,script_list){
       
   392 							var splice_args;
       
   393 							
       
   394 							if (!is_array(script_obj)) {
       
   395 								script_list = [script_obj];
       
   396 							}
       
   397 							for (var j=0; j<script_list.length; j++) {
       
   398 								init_script_chain_group();
       
   399 								script_obj = script_list[j];
       
   400 								
       
   401 								if (is_func(script_obj)) script_obj = script_obj();
       
   402 								if (!script_obj) continue;
       
   403 								if (is_array(script_obj)) {
       
   404 									// set up an array of arguments to pass to splice()
       
   405 									splice_args = [].slice.call(script_obj); // first include the actual array elements we want to splice in
       
   406 									splice_args.unshift(j,1); // next, put the `index` and `howMany` parameters onto the beginning of the splice-arguments array
       
   407 									[].splice.apply(script_list,splice_args); // use the splice-arguments array as arguments for splice()
       
   408 									j--; // adjust `j` to account for the loop's subsequent `j++`, so that the next loop iteration uses the same `j` index value
       
   409 									continue;
       
   410 								}
       
   411 								if (typeof script_obj == "string") script_obj = {src:script_obj};
       
   412 								script_obj = merge_objs(script_obj,{
       
   413 									ready:false,
       
   414 									ready_cb:chain_script_ready,
       
   415 									finished:false,
       
   416 									finished_cb:chain_script_executed
       
   417 								});
       
   418 								group.finished = false;
       
   419 								group.scripts.push(script_obj);
       
   420 								
       
   421 								do_script(chain_opts,script_obj,group,(can_use_preloading && scripts_currently_loading));
       
   422 								scripts_currently_loading = true;
       
   423 								
       
   424 								if (chain_opts[_AlwaysPreserveOrder]) chainedAPI.wait();
       
   425 							}
       
   426 						})(arguments[i],arguments[i]);
       
   427 					}
       
   428 					return chainedAPI;
       
   429 				},
       
   430 				// force LABjs to pause in execution at this point in the chain, until the execution thus far finishes, before proceeding
       
   431 				wait:function(){
       
   432 					if (arguments.length > 0) {
       
   433 						for (var i=0; i<arguments.length; i++) {
       
   434 							chain.push(arguments[i]);
       
   435 						}
       
   436 						group = chain[chain.length-1];
       
   437 					}
       
   438 					else group = false;
       
   439 					
       
   440 					advance_exec_cursor();
       
   441 					
       
   442 					return chainedAPI;
       
   443 				}
       
   444 			};
       
   445 
       
   446 			// the first chain link API (includes `setOptions` only this first time)
       
   447 			return {
       
   448 				script:chainedAPI.script, 
       
   449 				wait:chainedAPI.wait, 
       
   450 				setOptions:function(opts){
       
   451 					merge_objs(opts,chain_opts);
       
   452 					return chainedAPI;
       
   453 				}
       
   454 			};
       
   455 		}
       
   456 
       
   457 		// API for each initial $LAB instance (before chaining starts)
       
   458 		instanceAPI = {
       
   459 			// main API functions
       
   460 			setGlobalDefaults:function(opts){
       
   461 				merge_objs(opts,global_defaults);
       
   462 				return instanceAPI;
       
   463 			},
       
   464 			setOptions:function(){
       
   465 				return create_chain().setOptions.apply(null,arguments);
       
   466 			},
       
   467 			script:function(){
       
   468 				return create_chain().script.apply(null,arguments);
       
   469 			},
       
   470 			wait:function(){
       
   471 				return create_chain().wait.apply(null,arguments);
       
   472 			},
       
   473 
       
   474 			// built-in queuing for $LAB `script()` and `wait()` calls
       
   475 			// useful for building up a chain programmatically across various script locations, and simulating
       
   476 			// execution of the chain
       
   477 			queueScript:function(){
       
   478 				queue[queue.length] = {type:"script", args:[].slice.call(arguments)};
       
   479 				return instanceAPI;
       
   480 			},
       
   481 			queueWait:function(){
       
   482 				queue[queue.length] = {type:"wait", args:[].slice.call(arguments)};
       
   483 				return instanceAPI;
       
   484 			},
       
   485 			runQueue:function(){
       
   486 				var $L = instanceAPI, len=queue.length, i=len, val;
       
   487 				for (;--i>=0;) {
       
   488 					val = queue.shift();
       
   489 					$L = $L[val.type].apply(null,val.args);
       
   490 				}
       
   491 				return $L;
       
   492 			},
       
   493 
       
   494 			// rollback `[global].$LAB` to what it was before this file was loaded, the return this current instance of $LAB
       
   495 			noConflict:function(){
       
   496 				global.$LAB = _$LAB;
       
   497 				return instanceAPI;
       
   498 			},
       
   499 
       
   500 			// create another clean instance of $LAB
       
   501 			sandbox:function(){
       
   502 				return create_sandbox();
       
   503 			}
       
   504 		};
       
   505 
       
   506 		return instanceAPI;
       
   507 	}
       
   508 
       
   509 	// create the main instance of $LAB
       
   510 	global.$LAB = create_sandbox();
       
   511 
       
   512 
       
   513 	/* The following "hack" was suggested by Andrea Giammarchi and adapted from: http://webreflection.blogspot.com/2009/11/195-chars-to-help-lazy-loading.html
       
   514 	   NOTE: this hack only operates in FF and then only in versions where document.readyState is not present (FF < 3.6?).
       
   515 	   
       
   516 	   The hack essentially "patches" the **page** that LABjs is loaded onto so that it has a proper conforming document.readyState, so that if a script which does 
       
   517 	   proper and safe dom-ready detection is loaded onto a page, after dom-ready has passed, it will still be able to detect this state, by inspecting the now hacked 
       
   518 	   document.readyState property. The loaded script in question can then immediately trigger any queued code executions that were waiting for the DOM to be ready. 
       
   519 	   For instance, jQuery 1.4+ has been patched to take advantage of document.readyState, which is enabled by this hack. But 1.3.2 and before are **not** safe or 
       
   520 	   fixed by this hack, and should therefore **not** be lazy-loaded by script loader tools such as LABjs.
       
   521 	*/ 
       
   522 	(function(addEvent,domLoaded,handler){
       
   523 		if (document.readyState == null && document[addEvent]){
       
   524 			document.readyState = "loading";
       
   525 			document[addEvent](domLoaded,handler = function(){
       
   526 				document.removeEventListener(domLoaded,handler,false);
       
   527 				document.readyState = "complete";
       
   528 			},false);
       
   529 		}
       
   530 	})("addEventListener","DOMContentLoaded");
       
   531 
       
   532 })(this);/*
       
   533   mustache.js — Logic-less templates in JavaScript
       
   534 
       
   535   See http://mustache.github.com/ for more info.
       
   536 */
       
   537 
       
   538 var Mustache = function () {
       
   539   var _toString = Object.prototype.toString;
       
   540 
       
   541   Array.isArray = Array.isArray || function (obj) {
       
   542     return _toString.call(obj) == "[object Array]";
       
   543   }
       
   544 
       
   545   var _trim = String.prototype.trim, trim;
       
   546 
       
   547   if (_trim) {
       
   548     trim = function (text) {
       
   549       return text == null ? "" : _trim.call(text);
       
   550     }
       
   551   } else {
       
   552     var trimLeft, trimRight;
       
   553 
       
   554     // IE doesn't match non-breaking spaces with \s.
       
   555     if ((/\S/).test("\xA0")) {
       
   556       trimLeft = /^[\s\xA0]+/;
       
   557       trimRight = /[\s\xA0]+$/;
       
   558     } else {
       
   559       trimLeft = /^\s+/;
       
   560       trimRight = /\s+$/;
       
   561     }
       
   562 
       
   563     trim = function (text) {
       
   564       return text == null ? "" :
       
   565         text.toString().replace(trimLeft, "").replace(trimRight, "");
       
   566     }
       
   567   }
       
   568 
       
   569   var escapeMap = {
       
   570     "&": "&amp;",
       
   571     "<": "&lt;",
       
   572     ">": "&gt;",
       
   573     '"': '&quot;',
       
   574     "'": '&#39;'
       
   575   };
       
   576 
       
   577   function escapeHTML(string) {
       
   578     return String(string).replace(/&(?!\w+;)|[<>"']/g, function (s) {
       
   579       return escapeMap[s] || s;
       
   580     });
       
   581   }
       
   582 
       
   583   var regexCache = {};
       
   584   var Renderer = function () {};
       
   585 
       
   586   Renderer.prototype = {
       
   587     otag: "{{",
       
   588     ctag: "}}",
       
   589     pragmas: {},
       
   590     buffer: [],
       
   591     pragmas_implemented: {
       
   592       "IMPLICIT-ITERATOR": true
       
   593     },
       
   594     context: {},
       
   595 
       
   596     render: function (template, context, partials, in_recursion) {
       
   597       // reset buffer & set context
       
   598       if (!in_recursion) {
       
   599         this.context = context;
       
   600         this.buffer = []; // TODO: make this non-lazy
       
   601       }
       
   602 
       
   603       // fail fast
       
   604       if (!this.includes("", template)) {
       
   605         if (in_recursion) {
       
   606           return template;
       
   607         } else {
       
   608           this.send(template);
       
   609           return;
       
   610         }
       
   611       }
       
   612 
       
   613       // get the pragmas together
       
   614       template = this.render_pragmas(template);
       
   615 
       
   616       // render the template
       
   617       var html = this.render_section(template, context, partials);
       
   618 
       
   619       // render_section did not find any sections, we still need to render the tags
       
   620       if (html === false) {
       
   621         html = this.render_tags(template, context, partials, in_recursion);
       
   622       }
       
   623 
       
   624       if (in_recursion) {
       
   625         return html;
       
   626       } else {
       
   627         this.sendLines(html);
       
   628       }
       
   629     },
       
   630 
       
   631     /*
       
   632       Sends parsed lines
       
   633     */
       
   634     send: function (line) {
       
   635       if (line !== "") {
       
   636         this.buffer.push(line);
       
   637       }
       
   638     },
       
   639 
       
   640     sendLines: function (text) {
       
   641       if (text) {
       
   642         var lines = text.split("\n");
       
   643         for (var i = 0; i < lines.length; i++) {
       
   644           this.send(lines[i]);
       
   645         }
       
   646       }
       
   647     },
       
   648 
       
   649     /*
       
   650       Looks for %PRAGMAS
       
   651     */
       
   652     render_pragmas: function (template) {
       
   653       // no pragmas
       
   654       if (!this.includes("%", template)) {
       
   655         return template;
       
   656       }
       
   657 
       
   658       var that = this;
       
   659       var regex = this.getCachedRegex("render_pragmas", function (otag, ctag) {
       
   660         return new RegExp(otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" + ctag, "g");
       
   661       });
       
   662 
       
   663       return template.replace(regex, function (match, pragma, options) {
       
   664         if (!that.pragmas_implemented[pragma]) {
       
   665           throw({message:
       
   666             "This implementation of mustache doesn't understand the '" +
       
   667             pragma + "' pragma"});
       
   668         }
       
   669         that.pragmas[pragma] = {};
       
   670         if (options) {
       
   671           var opts = options.split("=");
       
   672           that.pragmas[pragma][opts[0]] = opts[1];
       
   673         }
       
   674         return "";
       
   675         // ignore unknown pragmas silently
       
   676       });
       
   677     },
       
   678 
       
   679     /*
       
   680       Tries to find a partial in the curent scope and render it
       
   681     */
       
   682     render_partial: function (name, context, partials) {
       
   683       name = trim(name);
       
   684       if (!partials || partials[name] === undefined) {
       
   685         throw({message: "unknown_partial '" + name + "'"});
       
   686       }
       
   687       if (!context || typeof context[name] != "object") {
       
   688         return this.render(partials[name], context, partials, true);
       
   689       }
       
   690       return this.render(partials[name], context[name], partials, true);
       
   691     },
       
   692 
       
   693     /*
       
   694       Renders inverted (^) and normal (#) sections
       
   695     */
       
   696     render_section: function (template, context, partials) {
       
   697       if (!this.includes("#", template) && !this.includes("^", template)) {
       
   698         // did not render anything, there were no sections
       
   699         return false;
       
   700       }
       
   701 
       
   702       var that = this;
       
   703 
       
   704       var regex = this.getCachedRegex("render_section", function (otag, ctag) {
       
   705         // This regex matches _the first_ section ({{#foo}}{{/foo}}), and captures the remainder
       
   706         return new RegExp(
       
   707           "^([\\s\\S]*?)" +         // all the crap at the beginning that is not {{*}} ($1)
       
   708 
       
   709           otag +                    // {{
       
   710           "(\\^|\\#)\\s*(.+)\\s*" + //  #foo (# == $2, foo == $3)
       
   711           ctag +                    // }}
       
   712 
       
   713           "\n*([\\s\\S]*?)" +       // between the tag ($2). leading newlines are dropped
       
   714 
       
   715           otag +                    // {{
       
   716           "\\/\\s*\\3\\s*" +        //  /foo (backreference to the opening tag).
       
   717           ctag +                    // }}
       
   718 
       
   719           "\\s*([\\s\\S]*)$",       // everything else in the string ($4). leading whitespace is dropped.
       
   720 
       
   721         "g");
       
   722       });
       
   723 
       
   724 
       
   725       // for each {{#foo}}{{/foo}} section do...
       
   726       return template.replace(regex, function (match, before, type, name, content, after) {
       
   727         // before contains only tags, no sections
       
   728         var renderedBefore = before ? that.render_tags(before, context, partials, true) : "",
       
   729 
       
   730         // after may contain both sections and tags, so use full rendering function
       
   731             renderedAfter = after ? that.render(after, context, partials, true) : "",
       
   732 
       
   733         // will be computed below
       
   734             renderedContent,
       
   735 
       
   736             value = that.find(name, context);
       
   737 
       
   738         if (type === "^") { // inverted section
       
   739           if (!value || Array.isArray(value) && value.length === 0) {
       
   740             // false or empty list, render it
       
   741             renderedContent = that.render(content, context, partials, true);
       
   742           } else {
       
   743             renderedContent = "";
       
   744           }
       
   745         } else if (type === "#") { // normal section
       
   746           if (Array.isArray(value)) { // Enumerable, Let's loop!
       
   747             renderedContent = that.map(value, function (row) {
       
   748               return that.render(content, that.create_context(row), partials, true);
       
   749             }).join("");
       
   750           } else if (that.is_object(value)) { // Object, Use it as subcontext!
       
   751             renderedContent = that.render(content, that.create_context(value),
       
   752               partials, true);
       
   753           } else if (typeof value == "function") {
       
   754             // higher order section
       
   755             renderedContent = value.call(context, content, function (text) {
       
   756               return that.render(text, context, partials, true);
       
   757             });
       
   758           } else if (value) { // boolean section
       
   759             renderedContent = that.render(content, context, partials, true);
       
   760           } else {
       
   761             renderedContent = "";
       
   762           }
       
   763         }
       
   764 
       
   765         return renderedBefore + renderedContent + renderedAfter;
       
   766       });
       
   767     },
       
   768 
       
   769     /*
       
   770       Replace {{foo}} and friends with values from our view
       
   771     */
       
   772     render_tags: function (template, context, partials, in_recursion) {
       
   773       // tit for tat
       
   774       var that = this;
       
   775 
       
   776       var new_regex = function () {
       
   777         return that.getCachedRegex("render_tags", function (otag, ctag) {
       
   778           return new RegExp(otag + "(=|!|>|&|\\{|%)?([^#\\^]+?)\\1?" + ctag + "+", "g");
       
   779         });
       
   780       };
       
   781 
       
   782       var regex = new_regex();
       
   783       var tag_replace_callback = function (match, operator, name) {
       
   784         switch(operator) {
       
   785         case "!": // ignore comments
       
   786           return "";
       
   787         case "=": // set new delimiters, rebuild the replace regexp
       
   788           that.set_delimiters(name);
       
   789           regex = new_regex();
       
   790           return "";
       
   791         case ">": // render partial
       
   792           return that.render_partial(name, context, partials);
       
   793         case "{": // the triple mustache is unescaped
       
   794         case "&": // & operator is an alternative unescape method
       
   795           return that.find(name, context);
       
   796         default: // escape the value
       
   797           return escapeHTML(that.find(name, context));
       
   798         }
       
   799       };
       
   800       var lines = template.split("\n");
       
   801       for(var i = 0; i < lines.length; i++) {
       
   802         lines[i] = lines[i].replace(regex, tag_replace_callback, this);
       
   803         if (!in_recursion) {
       
   804           this.send(lines[i]);
       
   805         }
       
   806       }
       
   807 
       
   808       if (in_recursion) {
       
   809         return lines.join("\n");
       
   810       }
       
   811     },
       
   812 
       
   813     set_delimiters: function (delimiters) {
       
   814       var dels = delimiters.split(" ");
       
   815       this.otag = this.escape_regex(dels[0]);
       
   816       this.ctag = this.escape_regex(dels[1]);
       
   817     },
       
   818 
       
   819     escape_regex: function (text) {
       
   820       // thank you Simon Willison
       
   821       if (!arguments.callee.sRE) {
       
   822         var specials = [
       
   823           '/', '.', '*', '+', '?', '|',
       
   824           '(', ')', '[', ']', '{', '}', '\\'
       
   825         ];
       
   826         arguments.callee.sRE = new RegExp(
       
   827           '(\\' + specials.join('|\\') + ')', 'g'
       
   828         );
       
   829       }
       
   830       return text.replace(arguments.callee.sRE, '\\$1');
       
   831     },
       
   832 
       
   833     /*
       
   834       find `name` in current `context`. That is find me a value
       
   835       from the view object
       
   836     */
       
   837     find: function (name, context) {
       
   838       name = trim(name);
       
   839 
       
   840       // Checks whether a value is thruthy or false or 0
       
   841       function is_kinda_truthy(bool) {
       
   842         return bool === false || bool === 0 || bool;
       
   843       }
       
   844 
       
   845       var value;
       
   846 
       
   847       // check for dot notation eg. foo.bar
       
   848       if (name.match(/([a-z_]+)\./ig)) {
       
   849         var childValue = this.walk_context(name, context);
       
   850         if (is_kinda_truthy(childValue)) {
       
   851           value = childValue;
       
   852         }
       
   853       } else {
       
   854         if (is_kinda_truthy(context[name])) {
       
   855           value = context[name];
       
   856         } else if (is_kinda_truthy(this.context[name])) {
       
   857           value = this.context[name];
       
   858         }
       
   859       }
       
   860 
       
   861       if (typeof value == "function") {
       
   862         return value.apply(context);
       
   863       }
       
   864       if (value !== undefined) {
       
   865         return value;
       
   866       }
       
   867       // silently ignore unkown variables
       
   868       return "";
       
   869     },
       
   870 
       
   871     walk_context: function (name, context) {
       
   872       var path = name.split('.');
       
   873       // if the var doesn't exist in current context, check the top level context
       
   874       var value_context = (context[path[0]] != undefined) ? context : this.context;
       
   875       var value = value_context[path.shift()];
       
   876       while (value != undefined && path.length > 0) {
       
   877         value_context = value;
       
   878         value = value[path.shift()];
       
   879       }
       
   880       // if the value is a function, call it, binding the correct context
       
   881       if (typeof value == "function") {
       
   882         return value.apply(value_context);
       
   883       }
       
   884       return value;
       
   885     },
       
   886 
       
   887     // Utility methods
       
   888 
       
   889     /* includes tag */
       
   890     includes: function (needle, haystack) {
       
   891       return haystack.indexOf(this.otag + needle) != -1;
       
   892     },
       
   893 
       
   894     // by @langalex, support for arrays of strings
       
   895     create_context: function (_context) {
       
   896       if (this.is_object(_context)) {
       
   897         return _context;
       
   898       } else {
       
   899         var iterator = ".";
       
   900         if (this.pragmas["IMPLICIT-ITERATOR"]) {
       
   901           iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator;
       
   902         }
       
   903         var ctx = {};
       
   904         ctx[iterator] = _context;
       
   905         return ctx;
       
   906       }
       
   907     },
       
   908 
       
   909     is_object: function (a) {
       
   910       return a && typeof a == "object";
       
   911     },
       
   912 
       
   913     /*
       
   914       Why, why, why? Because IE. Cry, cry cry.
       
   915     */
       
   916     map: function (array, fn) {
       
   917       if (typeof array.map == "function") {
       
   918         return array.map(fn);
       
   919       } else {
       
   920         var r = [];
       
   921         var l = array.length;
       
   922         for(var i = 0; i < l; i++) {
       
   923           r.push(fn(array[i]));
       
   924         }
       
   925         return r;
       
   926       }
       
   927     },
       
   928 
       
   929     getCachedRegex: function (name, generator) {
       
   930       var byOtag = regexCache[this.otag];
       
   931       if (!byOtag) {
       
   932         byOtag = regexCache[this.otag] = {};
       
   933       }
       
   934 
       
   935       var byCtag = byOtag[this.ctag];
       
   936       if (!byCtag) {
       
   937         byCtag = byOtag[this.ctag] = {};
       
   938       }
       
   939 
       
   940       var regex = byCtag[name];
       
   941       if (!regex) {
       
   942         regex = byCtag[name] = generator(this.otag, this.ctag);
       
   943       }
       
   944 
       
   945       return regex;
       
   946     }
       
   947   };
       
   948 
       
   949   return({
       
   950     name: "mustache.js",
       
   951     version: "0.5.0-dev",
       
   952 
       
   953     /*
       
   954       Turns a template and view into HTML
       
   955     */
       
   956     to_html: function (template, view, partials, send_fun) {
       
   957       var renderer = new Renderer();
       
   958       if (send_fun) {
       
   959         renderer.send = send_fun;
       
   960       }
       
   961       renderer.render(template, view || {}, partials);
       
   962       if (!send_fun) {
       
   963         return renderer.buffer.join("\n");
       
   964       }
       
   965     }
       
   966   });
       
   967 }();
       
   968 // Underscore.js 1.2.3
       
   969 // (c) 2009-2011 Jeremy Ashkenas, DocumentCloud Inc.
       
   970 // Underscore is freely distributable under the MIT license.
       
   971 // Portions of Underscore are inspired or borrowed from Prototype,
       
   972 // Oliver Steele's Functional, and John Resig's Micro-Templating.
       
   973 // For all details and documentation:
       
   974 // http://documentcloud.github.com/underscore
       
   975 (function(){function r(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return a==String(c);case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==
       
   976 c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&r(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(m.call(a,h)&&(f++,!(g=m.call(c,h)&&r(a[h],c[h],d))))break;if(g){for(h in c)if(m.call(c,
       
   977 h)&&!f--)break;g=!f}}d.pop();return g}var s=this,F=s._,o={},k=Array.prototype,p=Object.prototype,i=k.slice,G=k.concat,H=k.unshift,l=p.toString,m=p.hasOwnProperty,v=k.forEach,w=k.map,x=k.reduce,y=k.reduceRight,z=k.filter,A=k.every,B=k.some,q=k.indexOf,C=k.lastIndexOf,p=Array.isArray,I=Object.keys,t=Function.prototype.bind,b=function(a){return new n(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else typeof define==="function"&&
       
   978 define.amd?define("underscore",function(){return b}):s._=b;b.VERSION="1.2.3";var j=b.each=b.forEach=function(a,c,b){if(a!=null)if(v&&a.forEach===v)a.forEach(c,b);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(e in a&&c.call(b,a[e],e,a)===o)break}else for(e in a)if(m.call(a,e)&&c.call(b,a[e],e,a)===o)break};b.map=function(a,c,b){var e=[];if(a==null)return e;if(w&&a.map===w)return a.map(c,b);j(a,function(a,g,h){e[e.length]=c.call(b,a,g,h)});return e};b.reduce=b.foldl=b.inject=function(a,
       
   979 c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(x&&a.reduce===x)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(y&&a.reduceRight===y)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,
       
   980 c,d,e):b.reduce(g,c)};b.find=b.detect=function(a,c,b){var e;D(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(z&&a.filter===z)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(A&&a.every===A)return a.every(c,
       
   981 b);j(a,function(a,g,h){if(!(e=e&&c.call(b,a,g,h)))return o});return e};var D=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(B&&a.some===B)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return o});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return q&&a.indexOf===q?a.indexOf(c)!=-1:b=D(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(c.call?c||a:a[c]).apply(a,
       
   982 d)})};b.pluck=function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b<e.computed&&(e={value:a,
       
   983 computed:b})});return e.value};b.shuffle=function(a){var c=[],b;j(a,function(a,f){f==0?c[0]=a:(b=Math.floor(Math.random()*(f+1)),c[f]=c[b],c[b]=a)});return c};b.sortBy=function(a,c,d){return b.pluck(b.map(a,function(a,b,g){return{value:a,criteria:c.call(d,a,b,g)}}).sort(function(a,c){var b=a.criteria,d=c.criteria;return b<d?-1:b>d?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=
       
   984 function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:a.toArray?a.toArray():b.isArray(a)?i.call(a):b.isArguments(a)?i.call(a):b.values(a)};b.size=function(a){return b.toArray(a).length};b.first=b.head=function(a,b,d){return b!=null&&!d?i.call(a,0,b):a[0]};b.initial=function(a,b,d){return i.call(a,0,a.length-(b==null||d?1:b))};b.last=function(a,b,d){return b!=null&&!d?i.call(a,Math.max(a.length-b,0)):a[a.length-
       
   985 1]};b.rest=b.tail=function(a,b,d){return i.call(a,b==null||d?1:b)};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a,c){return b.reduce(a,function(a,e){if(b.isArray(e))return a.concat(c?e:b.flatten(e));a[a.length]=e;return a},[])};b.without=function(a){return b.difference(a,i.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,e=[];b.reduce(d,function(d,g,h){if(0==h||(c===true?b.last(d)!=g:!b.include(d,g)))d[d.length]=g,e[e.length]=a[h];return d},
       
   986 []);return e};b.union=function(){return b.uniq(b.flatten(arguments,true))};b.intersection=b.intersect=function(a){var c=i.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,
       
   987 c,d){if(a==null)return-1;var e;if(d)return d=b.sortedIndex(a,c),a[d]===c?d:-1;if(q&&a.indexOf===q)return a.indexOf(c);for(d=0,e=a.length;d<e;d++)if(d in a&&a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(C&&a.lastIndexOf===C)return a.lastIndexOf(b);for(var d=a.length;d--;)if(d in a&&a[d]===b)return d;return-1};b.range=function(a,b,d){arguments.length<=1&&(b=a||0,a=0);for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;)g[f++]=a,a+=d;return g};
       
   988 var E=function(){};b.bind=function(a,c){var d,e;if(a.bind===t&&t)return t.apply(a,i.call(arguments,1));if(!b.isFunction(a))throw new TypeError;e=i.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(i.call(arguments)));E.prototype=a.prototype;var b=new E,g=a.apply(b,e.concat(i.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c=i.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,
       
   989 c){var d={};c||(c=b.identity);return function(){var b=c.apply(this,arguments);return m.call(d,b)?d[b]:d[b]=a.apply(this,arguments)}};b.delay=function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(a,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(i.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,h,i=b.debounce(function(){h=g=false},c);return function(){d=this;e=arguments;var b;f||(f=setTimeout(function(){f=null;h&&a.apply(d,e);i()},c));g?h=true:
       
   990 a.apply(d,e);i();g=true}};b.debounce=function(a,b){var d;return function(){var e=this,f=arguments;clearTimeout(d);d=setTimeout(function(){d=null;a.apply(e,f)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=G.apply([a],arguments);return b.apply(this,d)}};b.compose=function(){var a=arguments;return function(){for(var b=arguments,d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=
       
   991 function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=I||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var b=[],d;for(d in a)m.call(a,d)&&(b[b.length]=d);return b};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)b[d]!==void 0&&(a[d]=b[d])});return a};b.defaults=function(a){j(i.call(arguments,
       
   992 1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return r(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(m.call(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=p||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===
       
   993 Object(a)};b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!m.call(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)==
       
   994 "[object Date]"};b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.noConflict=function(){s._=F;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.escape=function(a){return(""+a).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;").replace(/\//g,"&#x2F;")};b.mixin=function(a){j(b.functions(a),function(c){J(c,
       
   995 b[c]=a[c])})};var K=0;b.uniqueId=function(a){var b=K++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape,function(a,b){return"',_.escape("+b.replace(/\\'/g,"'")+"),'"}).replace(d.interpolate,function(a,b){return"',"+b.replace(/\\'/g,
       
   996 "'")+",'"}).replace(d.evaluate||null,function(a,b){return"');"+b.replace(/\\'/g,"'").replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};var n=function(a){this._wrapped=a};b.prototype=n.prototype;var u=function(a,c){return c?b(a).chain():a},J=function(a,c){n.prototype[a]=function(){var a=i.call(arguments);H.call(a,this._wrapped);return u(c.apply(b,
       
   997 a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];n.prototype[a]=function(){b.apply(this._wrapped,arguments);return u(this._wrapped,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];n.prototype[a]=function(){return u(b.apply(this._wrapped,arguments),this._chain)}});n.prototype.chain=function(){this._chain=true;return this};n.prototype.value=function(){return this._wrapped}}).call(this);
       
   998 /* main file */
       
   999 // Why is it called main ? It only loads the libs !
       
  1000 
       
  1001 if ( window.IriSP === undefined && window.__IriSP === undefined ) { 
       
  1002   /**
       
  1003     @class
       
  1004     the object under which everything goes.        
       
  1005   */
       
  1006 	IriSP = {}; 
       
  1007   
       
  1008   /** Alias to IriSP for backward compatibility */
       
  1009 	__IriSP = IriSP;
       
  1010 }
       
  1011 
       
  1012 /* underscore comes bundled with the player and we need 
       
  1013    it ASAP, so load it that way
       
  1014 */
       
  1015 
       
  1016 IriSP._ = window._.noConflict();
       
  1017 IriSP.underscore = IriSP._;
       
  1018 
       
  1019 IriSP.getLib = function(lib) {
       
  1020     return (
       
  1021         IriSP.libFiles.useCdn && typeof IriSP.libFiles.cdn[lib] == "string"
       
  1022         ? IriSP.libFiles.cdn[lib]
       
  1023         : (
       
  1024             typeof IriSP.libFiles.locations[lib] == "string"
       
  1025             ? IriSP.libFiles.locations[lib]
       
  1026             : (
       
  1027                 typeof IriSP.libFiles.inDefaultDir[lib] == "string"
       
  1028                 ? IriSP.libFiles.defaultDir + IriSP.libFiles.inDefaultDir[lib]
       
  1029                 : null
       
  1030             )
       
  1031         )
       
  1032     )
       
  1033 }
       
  1034 
       
  1035 IriSP.loadLibs = function( config, metadata_url, callback ) {
       
  1036     // Localize jQuery variable
       
  1037 		IriSP.jQuery = null;
       
  1038     var $L = $LAB.script(IriSP.getLib("jQuery")).script(IriSP.getLib("swfObject")).wait()
       
  1039                 .script(IriSP.getLib("jQueryUI"));
       
  1040                                    
       
  1041     if (config.player.type === "jwplayer" || config.player.type === "allocine") {
       
  1042       // load our popcorn.js lookalike
       
  1043       $L.script(IriSP.getLib("jwplayer"));
       
  1044     } else {
       
  1045       // load the real popcorn
       
  1046       $L.script(IriSP.getLib("popcorn")).script(IriSP.getLib("popcorn.code"));
       
  1047       if (config.player.type === "youtube") {
       
  1048         $L.script(IriSP.getLib("popcorn.youtube"));
       
  1049       } 
       
  1050       if (config.player.type === "vimeo")
       
  1051         $L.script(IriSP.getLib("popcorn.vimeo"));
       
  1052       
       
  1053       /* do nothing for html5 */
       
  1054     }       
       
  1055     
       
  1056     /* widget specific requirements */
       
  1057     for (var idx in config.gui.widgets) {
       
  1058       if (config.gui.widgets[idx].type === "PolemicWidget" ||
       
  1059           config.gui.widgets[idx].type === "StackGraphWidget" ||
       
  1060           config.gui.widgets[idx].type === "SparklineWidget") {        
       
  1061         $L.script(IriSP.getLib("raphael"));
       
  1062       }
       
  1063       if (config.gui.widgets[idx].type === "TraceWidget") {
       
  1064           $L.script(IriSP.getLib("tracemanager"))
       
  1065       }
       
  1066     }
       
  1067     
       
  1068     // same for modules
       
  1069     /*
       
  1070     for (var idx in config.modules) {
       
  1071       if (config.modules[idx].type === "PolemicWidget")
       
  1072         $L.script(IriSP.getLib("raphaelJs"));
       
  1073     }
       
  1074     */
       
  1075 
       
  1076     $L.wait(function() {
       
  1077       IriSP.jQuery = window.jQuery.noConflict( true );
       
  1078       
       
  1079       var css_link_jquery = IriSP.jQuery( "<link>", { 
       
  1080         rel: "stylesheet", 
       
  1081         type: "text/css", 
       
  1082         href: IriSP.getLib("cssjQueryUI"),
       
  1083         'class': "dynamic_css"
       
  1084       } );
       
  1085       var css_link_custom = IriSP.jQuery( "<link>", { 
       
  1086         rel: "stylesheet", 
       
  1087         type: "text/css", 
       
  1088         href: config.gui.css,
       
  1089         'class': "dynamic_css"
       
  1090       } );
       
  1091       
       
  1092       css_link_jquery.appendTo('head');
       
  1093       css_link_custom.appendTo('head');
       
  1094           
       
  1095       IriSP.setupDataLoader();
       
  1096       IriSP.__dataloader.get(metadata_url, 
       
  1097           function(data) {
       
  1098             /* save the data so that we could re-use it to
       
  1099                configure the video
       
  1100             */
       
  1101             IriSP.__jsonMetadata = data;
       
  1102             callback.call(window) });
       
  1103     });
       
  1104 };
       
  1105 IriSP.annotation_template = "{{! template for an annotation displayed in a segmentWidget }}<div title='{{divTitle}}' id='{{id}}'	class='Ldt-iri-chapter Ldt-TraceMe' 	style='left: {{startPixel}}px;          width: {{pxWidth}}px;          background-color:{{hexa_color}};'    data-seek='{{seekPlace}}'    thumbnail-url='{{thumbnailUrl}}'	></div>";
       
  1106 IriSP.annotationWidget_template = "{{! template for the annotation widget }}<div class='Ldt-AnnotationsWidget'>  <!-- ugly div because we want to have a double border -->  <div class='Ldt-Annotation-DoubleBorder'>      <div class='Ldt-AnnotationContent'>          <div class='Ldt-AnnotationShareIcons'>         <a target='_blank' class='Ldt-fbShare Ldt-TraceMe' title='{{i10n.share_on}} Facebook'></a>         <a target='_blank' class='Ldt-TwShare Ldt-TraceMe' title='{{i10n.share_on}} Twitter'></a>         <a target='_blank'  class='Ldt-GplusShare Ldt-TraceMe' title='{{i10n.share_on}} Google+'></a>        </div>        <div class='Ldt-SaTitle'></div>        <div class='Ldt-SaDescription'></div>        <div class='Ldt-SaKeywords'></div>    </div>  </div></div>";
       
  1107 IriSP.annotation_loading_template = "{{! template shown while the annotation widget is loading }}<div id='Ldt-load-container'><div id='Ldt-loader'>&nbsp;</div> Chargement... </div>";
       
  1108 IriSP.annotationsListWidget_template = "{{! template for the annotation list widget }}<div class='Ldt-AnnotationsListWidget'>    <ul class='Ldt-AnnotationsList-ul'>        {{#annotations}}        <li id='Ldt-Annotation-li-{{id}}' class='Ldt-AnnotationsList-li Ldt-TraceMe'>            {{^url}} <a href='#id={{id}}'> {{/url}}            {{! otherwise link to url }}            {{#url}} <a href='{{url}}'> {{/url}}                <img class='Ldt-AnnotationsList-Thumbnail' src='{{thumbnail}}' />            </a>            <div class='Ldt-AnnotationsList-Duration'>                <span class='Ldt-AnnotationsList-Begin'>{{begin}}</span>                <span class='Ldt-AnnotationsList-TcSeparator'>-</span>                <span class='Ldt-AnnotationsList-End'>{{end}}</span>            </div>            <div class='Ldt-AnnotationsList-Title'>            {{! if the url is not present, it means that the annotation exists            in the current project }}                {{title}}            </div>            <div class='Ldt-AnnotationsList-Description'>            {{^url}} <a href='#id={{id}}'> {{/url}}            {{! otherwise link to url }}            {{#url}} <a href='{{url}}'> {{/url}}                {{desc}}                </a>            </div>            {{#tags.length}}            <ul class='Ldt-AnnotationsList-Tags'>                {{#tags}}                <li class='Ldt-AnnotationsList-Tag-Li'>                    <div class='Ldt-AnnotationsList-Tag-Div'>{{.}}</div>                </li>                {{/tags}}            </ul>            {{/tags.length}}        </li>        {{/annotations}}    </ul></div>";
       
  1109 IriSP.arrowWidget_template = "<div class='Ldt-arrowWidget Ldt-arrowLeftEdge'></div>";
       
  1110 IriSP.createAnnotationWidget_template = "{{! template for the annotation creation widget }}<div class='Ldt-createAnnotationWidget'>    <!-- ugly div because we want to have a double border -->    <div class='Ldt-createAnnotation-DoubleBorder'>        <div class='Ldt-createAnnotation-screen Ldt-createAnnotation-startScreen'>            <div style='margin-bottom: 7px; overflow: auto;'>                <div class='Ldt-createAnnotation-Title'></div>                <div class='Ldt-createAnnotation-TimeFrame'></div>                {{^cinecast_version}} <div class='Ldt-createAnnotation-Minimize Ldt-TraceMe' title='Cancel'></div>                {{/cinecast_version}}            </div>            <div class='Ldt-createAnnotation-Container'>                {{#show_from_field}}                <label>{{l10n.your_name}}&nbsp;: </label><input class='Ldt-createAnnotation-userName Ldt-TraceMe' value='{{user_name}}' />                {{/show_from_field}}                <textarea class='Ldt-createAnnotation-Description Ldt-TraceMe'></textarea>                <div class='Ldt-createAnnotation-userAvatar Ldt-TraceMe'>                    {{^user_avatar}} <img src='https://si0.twimg.com/sticky/default_profile_images/default_profile_1_normal.png'></img>                    {{/user_avatar}}                    {{#user_avatar}} <img src='{{ user_avatar }}'></img>                    {{/user_avatar}}                </div>                <div class='Ldt-createAnnotation-profileArrow'></div>            </div>            <button class='Ldt-createAnnotation-submitButton Ldt-TraceMe'>{{l10n.submit}}</button>            {{#tags.length}}            <div class='Ldt-createAnnotation-btnblock Ldt-createAnnotation-keywords'>                <label>{{l10n.add_keywords}} :</label>                <ul class='Ldt-floatList'>                {{#tags}}                    <li><button class='Ldt-createAnnotation-keyword-button Ldt-TraceMe' tag-id='{{id}}'>{{meta.description}}</button></li>                {{/tags}}                </ul>            </div>            {{#random_tags}}                <button class='Ldt-createAnnotation-moar-keywordz'>{{l10n.more_tags}}</button>            {{/random_tags}}            {{/tags.length}}            {{#polemic_mode}}            {{#polemics.length}}            <div class='Ldt-createAnnotation-btnblock Ldt-createAnnotation-polemics'>                <label>{{l10n.add_polemic_keywords}} :</label>                <ul class='Ldt-floatList'>                {{#polemics}}                    <li><button class='Ldt-createAnnotation-polemic-{{className}} Ldt-createAnnotation-polemic-button Ldt-TraceMe'>{{keyword}}</button></li>                {{/polemics}}                </ul>            </div>            {{/polemics.length}}            {{/polemic_mode}}        </div>        <div class='Ldt-createAnnotation-screen Ldt-createAnnotation-waitScreen' style='display: none; text-align: center'>            <div class='Ldt-createAnnotation-spinner'></div>            {{l10n.wait_while_processed}}        </div>        <div class='Ldt-createAnnotation-screen Ldt-createAnnotation-errorScreen' style='display: none; text-align: center'>            <div class='Ldt-createAnnotation-Minimize' title='Hide'></div>            {{l10n.error_while_contacting}}        </div>        <div class='Ldt-createAnnotation-screen Ldt-createAnnotation-endScreen' style='display: none'>            <div class='Ldt-createAnnotation-Minimize' title='Hide'></div>            {{l10n.annotation_saved}}            <br>            {{^disable_share}}            {{l10n.share_annotation}}            <div style='margin-top: 12px; text-align: center;'>                <a target='_blank' class='Ldt-createAnnotation-endScreen-TweetLink Ldt-TraceMe'></a>                <a target='_blank' class='Ldt-createAnnotation-endScreen-FbLink Ldt-TraceMe'></a>                <a target='_blank' class='Ldt-createAnnotation-endScreen-GplusLink Ldt-TraceMe'></a>            </div>            {{/disable_share}}        </div>        <div class='Ldt-floatClear'></div>    </div></div>";
       
  1111 IriSP.createAnnotation_errorMessage_template = "<p class='Ldt-createAnnotation-errorMessage'>  {{l10n.empty_annotation}}</p>";
       
  1112 IriSP.loading_template = "<div id='Ldt-loader' style='width: {{width}}px; height: {{height}}px;'>{{l10n.loading_wait}}</div>";
       
  1113 IriSP.overlay_marker_template = "{{! the template for the small bars which is z-indexed over our segment widget }}<div class='Ldt-SegmentPositionMarker' style='background-color: #F7268E;'></div>";
       
  1114 IriSP.player_template = "{{! template for the radio player }}<div class='Ldt-controler'>	<div class='Ldt-LeftPlayerControls'>        <div class='Ldt-Ctrl-button Ldt-CtrlPlay Ldt-CtrlPlay-PlayState Ldt-TraceMe' title='{{l10n.play_pause}}'></div>        <div class='Ldt-Ctrl-spacer'></div>        {{^disable_annotate_btn}}    	<div class='Ldt-Ctrl-button Ldt-CtrlAnnotate Ldt-TraceMe' title='{{l10n.annotate}}'></div>        <div class='Ldt-Ctrl-spacer'></div>        {{/disable_annotate_btn}}        {{^disable_search_btn}}        <div class='Ldt-Ctrl-button Ldt-CtrlSearch Ldt-TraceMe' title='{{l10n.search}}'></div>        <div class='Ldt-Ctrl-spacer'></div>        {{/disable_search_btn}}        <div class='LdtSearch'>          <input class='LdtSearchInput Ldt-TraceMe'></input>        </div>	</div>	<div class='Ldt-RightPlayerControls'>        <div class='Ldt-Ctrl-spacer'></div>        <div class='Ldt-Time'>          <div class='Ldt-ElapsedTime' title='{{l10n.elapsed_time}}'>00:00</div>          <div class='Ldt-TimeSeparator'>/</div>          <div class='Ldt-TotalTime' title='{{l10n.total_time}}'>00:00</div>        </div>        <div class='Ldt-Ctrl-spacer'></div>		<div class='Ldt-Ctrl-button Ldt-CtrlSound Ldt-CtrlSound-Full Ldt-TraceMe' title='{{l10n.mute_unmute}}'></div>	</div>	<div class='Ldt-Ctrl-Volume-Control' title='{{l10n.volume_control}}'>	    <div class='Ldt-Ctrl-Volume-Bar'></div>	    <div class='Ldt-Ctrl-Volume-Cursor'></div>	</div></div>";
       
  1115 IriSP.search_template = "{{! template for the search container }}<div class='LdtSearchContainer'	style='margin-left: {{margin_left}}; position: absolute; margin-top: -60px;'>	<div class='LdtSearch'		style='display: none; background-color: #EEE; width: 165px; border-color: #CFCFCF; position: absolute; text-align: center;'>		<input class='LdtSearchInput'			style='margin-top: 1px; margin-bottom: 2px;' />	</div></div><div class='cleaner'></div>";
       
  1116 IriSP.share_template = "{{! social network sharing template }}<a onclick='__IriSP.MyApiPlayer.share(\'delicious\');' title='{{l10n.share_on}} delicious'><span class='share shareDelicious'>&nbsp;</span></a>		<a onclick='__IriSP.MyApiPlayer.share(\'facebook\');' title='{{l10n.share_on}} facebook'> <span class='share shareFacebook'>&nbsp;</span></a><a onclick='__IriSP.MyApiPlayer.share(\'twitter\');' title='{{l10n.share_on}} twitter'>  <span class='share shareTwitter'>&nbsp;</span></a><a onclick='__IriSP.MyApiPlayer.share(\'myspace\');' title='{{l10n.share_on}} Myspace'>  <span class='share shareMySpace'>&nbsp;</span></a>";
       
  1117 IriSP.sliceWidget_template = "{{! template for the slice widget }}<div class='Ldt-sliceWidget'>  {{! the whole bar }}  <div class='Ldt-sliceBackground'></div>    <div class='Ldt-sliceLeftHandle'></div>  {{! the zone which represents our slice }}  <div class='Ldt-sliceZone'></div>     <div class='Ldt-sliceRightHandle'></div></div>";
       
  1118 IriSP.sliderWidget_template = "{{! template for the slider widget - it's composed of two divs we one overlayed on top    of the other }}<div class='Ldt-sliderBackground'></div><div class='Ldt-sliderForeground'></div><div class='Ldt-sliderPositionMarker Ldt-TraceMe'></div>";
       
  1119 IriSP.tooltip_template = "{{! template used by the jquery ui tooltip }}<div class='Ldt-tooltip'>  <div class='title'>{{title}}</div>  <div class='time'>{{begin}} : {{end}} </div>  <div class='description'>{{description}}</div></div>";
       
  1120 IriSP.tooltipWidget_template = "{{! template for the tooltip widget }}<div class='tip'>	<div class='tipcolor' style='height:10px;width:10px'></div>	<div class='tiptext'></div>";
       
  1121 IriSP.tweetWidget_template = "{{! template for the tweet widget }}<div class='Ldt-tweetWidget'>  <div class='Ldt-tweet-DoubleBorder'>      <div class='Ldt-tweetWidgetKeepOpen Ldt-TraceMe' title='dont minimize automatically'></div>      <div class='Ldt-tweetWidgetMinimize Ldt-TraceMe' title='minimize window'></div>      <div class='Ldt-tweetAvatar'></div>      <div class='Ldt-tweetAvatar-profileArrow'></div>      <div class='Ldt-tweetContents'></div>      <a href='' target='_blank' class='Ldt-Retweet Ldt-TraceMe'><div class='Ldt-RetweetIcon'></div> - Retweet </a>      <a href='' target='_blank' class='Ldt-TweetReply Ldt-TraceMe'><div class='Ldt-TweetReplyIcon'></div> - Reply</a>  </div></div>";/* utils.js - various utils that don't belong anywhere else */
       
  1122 
       
  1123 /* trace function, for debugging */
       
  1124 
       
  1125 IriSP.traceNum = 0;
       
  1126 IriSP.trace = function( msg, value ) {
       
  1127 /*
       
  1128 	if( IriSP.config.gui.debug === true ) {
       
  1129 		IriSP.traceNum += 1;
       
  1130 		IriSP.jQuery( "<div>"+IriSP.traceNum+" - "+msg+" : "+value+"</div>" ).appendTo( "#Ldt-output" );
       
  1131 	}
       
  1132 */
       
  1133 };
       
  1134 
       
  1135 /* used in callbacks - because in callbacks we lose "this",
       
  1136    we need to have a special function which wraps "this" in 
       
  1137    a closure. This way, the 
       
  1138 */   
       
  1139 IriSP.wrap = function (obj, fn) {
       
  1140   return function() {    
       
  1141     var args = Array.prototype.slice.call(arguments, 0);
       
  1142     return fn.apply(obj, args);
       
  1143   }
       
  1144 }
       
  1145 
       
  1146 /* convert a time to a percentage in the media */
       
  1147 IriSP.timeToPourcent = function(time, timetotal){
       
  1148 	var time = Math.abs(time);
       
  1149   var timetotal = Math.abs(timetotal);
       
  1150   
       
  1151 	return Math.floor((time/timetotal) * 100);
       
  1152 };
       
  1153 
       
  1154 IriSP.padWithZeros = function(num) {
       
  1155   if (Math.abs(num) < 10) {
       
  1156     return "0" + num.toString();
       
  1157   } else {
       
  1158     return num.toString();
       
  1159   }
       
  1160 };
       
  1161 
       
  1162 /* convert a number of milliseconds to a tuple of the form 
       
  1163    [hours, minutes, seconds]
       
  1164 */
       
  1165 IriSP.msToTime = function(ms) {
       
  1166   return IriSP.secondsToTime(ms / 1000);
       
  1167 }
       
  1168 /* convert a number of seconds to a tuple of the form 
       
  1169    [hours, minutes, seconds]
       
  1170 */
       
  1171 IriSP.secondsToTime = function(secs) {  
       
  1172   var hours = Math.abs(parseInt( secs / 3600 ) % 24);
       
  1173   var minutes = Math.abs(parseInt( secs / 60 ) % 60);
       
  1174   var seconds = parseFloat(Math.abs(secs % 60).toFixed(0));
       
  1175   
       
  1176   var toString_fn = function() {
       
  1177     var ret = "";
       
  1178     if (hours > 0)
       
  1179        ret = IriSP.padWithZeros(this.hours) + ":";
       
  1180     ret += IriSP.padWithZeros(this.minutes) + ":" + IriSP.padWithZeros(this.seconds);
       
  1181 
       
  1182     return ret;
       
  1183   }
       
  1184   return {"hours" : hours, "minutes" : minutes, "seconds" : seconds, toString: toString_fn};
       
  1185 };
       
  1186 
       
  1187 /* format a tweet - replaces @name by a link to the profile, #hashtag, etc. */
       
  1188 IriSP.formatTweet = function(tweet) {
       
  1189   /*
       
  1190     an array of arrays which hold a regexp and its replacement.
       
  1191   */
       
  1192   var regExps = [
       
  1193     /* copied from http://codegolf.stackexchange.com/questions/464/shortest-url-regex-match-in-javascript/480#480 */
       
  1194     [/((https?:\/\/)?[\w-]+(\.[\w-]+)+\.?(:\d+)?(\/\S*)?)/gi, "<a href='$1'>$1</a>"],
       
  1195     [/@(\w+)/gi, "<a href='http://twitter.com/$1'>@$1</a>"], // matches a @handle
       
  1196     [/#(\w+)/gi, "<a href='http://twitter.com/search?q=%23$1'>#$1</a>"],// matches a hashtag
       
  1197     [/(\+\+)/gi, "<span class='Ldt-PolemicPlusPlus'>$1</span>"],
       
  1198     [/(--)/gi, "<span class='Ldt-PolemicMinusMinus'>$1</span>"],
       
  1199     [/(==)/gi, "<span class='Ldt-PolemicEqualEqual'>$1</span>"],
       
  1200     [/(\?\?)/gi, "<span class='Ldt-PolemicQuestion'>$1</span>"]
       
  1201   ]; 
       
  1202 
       
  1203   var i = 0;
       
  1204   for(i = 0; i < regExps.length; i++) {
       
  1205      tweet = tweet.replace(regExps[i][0], regExps[i][1]);
       
  1206   }
       
  1207   
       
  1208   return tweet;
       
  1209 };
       
  1210 
       
  1211 IriSP.countProperties = function(obj) {
       
  1212     var count = 0;
       
  1213 
       
  1214     for(var prop in obj) {
       
  1215         if(obj.hasOwnProperty(prop))
       
  1216                 ++count;
       
  1217     }
       
  1218 
       
  1219     return count;
       
  1220 };
       
  1221 
       
  1222 // conversion de couleur Decimal vers HexaDecimal || 000 si fff
       
  1223 IriSP.DEC_HEXA_COLOR = function (dec) {
       
  1224   var val = +dec;
       
  1225   var str = val.toString(16);
       
  1226   var zeroes = "";
       
  1227   if (str.length < 6) {
       
  1228     for (var i = 0; i < 6 - str.length; i++)
       
  1229       zeroes += "0";
       
  1230   }
       
  1231   return zeroes + str;
       
  1232 };
       
  1233 
       
  1234 /* shortcut to have global variables in templates */
       
  1235 IriSP.templToHTML = function(template, values) {
       
  1236   var params = IriSP.underscore.extend(
       
  1237       { "defaults" : IriSP.default_templates_vars,
       
  1238         "l10n" : IriSP.i18n.getMessages()
       
  1239         },
       
  1240       values);
       
  1241   return Mustache.to_html(template, params);
       
  1242 };
       
  1243 
       
  1244 /* we need to be stricter than encodeURIComponent,
       
  1245    because of twitter
       
  1246 */  
       
  1247 IriSP.encodeURI = function(str) {
       
  1248   return encodeURIComponent(str).replace(/!/g, '%21').replace(/'/g, '%27').replace(/\(/g, '%28').  
       
  1249                                  replace(/\)/g, '%29').replace(/\*/g, '%2A');  
       
  1250 }
       
  1251 
       
  1252 IriSP.jqEscape = function(text) {
       
  1253    return text.replace(/(:|\.)/g,'\\$1')
       
  1254 }
       
  1255 
       
  1256 IriSP.jqId = function (text) { 
       
  1257    return IriSP.jQuery('#' + IriSP.jqEscape(text));
       
  1258  }  
       
  1259 
       
  1260 IriSP.__guidCounter = 0;
       
  1261 IriSP.guid = function(prefix) {
       
  1262   IriSP.__guidCounter += 1;
       
  1263   return prefix + IriSP.__guidCounter;
       
  1264 };
       
  1265 
       
  1266 /** returns an url to share on facebook */
       
  1267 IriSP.mkFbUrl = function(url, text) {
       
  1268   if (typeof(text) === "undefined")
       
  1269     text = "I'm watching ";
       
  1270   
       
  1271   return "http://www.facebook.com/share.php?u=" + IriSP.encodeURI(text) + IriSP.shorten_url(url);
       
  1272 };
       
  1273 
       
  1274 /** returns an url to share on twitter */
       
  1275 IriSP.mkTweetUrl = function(url, text) {
       
  1276   if (typeof(text) === "undefined")
       
  1277     text = "I'm watching ";
       
  1278   
       
  1279   return "http://twitter.com/home?status=" + IriSP.encodeURI(text) + IriSP.shorten_url(url);
       
  1280 };
       
  1281 
       
  1282 /** returns an url to share on google + */
       
  1283 IriSP.mkGplusUrl = function(url, text) {
       
  1284   return "https://plusone.google.com/_/+1/confirm?hl=en&url=" + IriSP.shorten_url(url);
       
  1285 };
       
  1286 
       
  1287 /** test if a value is null or undefined */
       
  1288 IriSP.null_or_undefined = function(val) {
       
  1289   return (typeof(val) === "undefined" || val === null);
       
  1290 };
       
  1291 
       
  1292 /** get a property that can have multiple names **/
       
  1293 
       
  1294 IriSP.get_aliased = function(_obj, _aliases) {
       
  1295     for (var _i = 0; _i < _aliases.length; _i++) {
       
  1296         if (typeof _obj[_aliases[_i]] !== "undefined") {
       
  1297             return _obj[_aliases[_i]];
       
  1298         }
       
  1299     }
       
  1300     return null;
       
  1301 }
       
  1302 
       
  1303 /** issue a call to an url shortener and return the shortened url */
       
  1304 IriSP.shorten_url = function(url) {
       
  1305   return encodeURIComponent(url);
       
  1306 };
       
  1307 
       
  1308 
       
  1309 /* for ie compatibility
       
  1310 if (Object.prototype.__defineGetter__&&!Object.defineProperty) {
       
  1311    Object.defineProperty=function(obj,prop,desc) {
       
  1312       if ("get" in desc) obj.__defineGetter__(prop,desc.get);
       
  1313       if ("set" in desc) obj.__defineSetter__(prop,desc.set);
       
  1314    }
       
  1315 }
       
  1316 */
       
  1317 
       
  1318 /* Creates regexps from text */
       
  1319 IriSP.regexpFromText = function(_text) {
       
  1320     return new RegExp('(' + _text.replace(/(\W)/gim,'\\$1') + ')','gim');
       
  1321 }
       
  1322 /* wrapper that simulates popcorn.js because
       
  1323    popcorn is a bit unstable at the time */
       
  1324 
       
  1325 IriSP.PopcornReplacement = {  
       
  1326 };
       
  1327 
       
  1328 /** base class for our popcorn-compatible players.
       
  1329  */
       
  1330 IriSP.PopcornReplacement.player = function(container, options) {
       
  1331   /* the jwplayer calls the callbacks in the global space so we need to 
       
  1332      preserve them using IriSP.wrap */
       
  1333   this.callbacks = {
       
  1334       onReady:  IriSP.wrap(this, this.__initApi),
       
  1335       onTime:   IriSP.wrap(this, this.__timeHandler),
       
  1336       onPlay:   IriSP.wrap(this, this.__playHandler),
       
  1337       onPause:  IriSP.wrap(this, this.__pauseHandler),
       
  1338       onSeek:   IriSP.wrap(this, this.__seekHandler) 
       
  1339   };
       
  1340   
       
  1341   this.media = { 
       
  1342     "paused": true,
       
  1343     "muted": false
       
  1344   };
       
  1345     
       
  1346   this.container = container.slice(1); //eschew the '#'
       
  1347   
       
  1348   this.msgPump = {}; /* dictionnary used to receive and send messages */
       
  1349   this.__codes = []; /* used to schedule the execution of a piece of code in 
       
  1350                         a segment (similar to the popcorn.code plugin). */
       
  1351   
       
  1352   this._options = options;
       
  1353                           
       
  1354 };
       
  1355 
       
  1356 IriSP.PopcornReplacement.player.prototype.listen = function(msg, callback) {
       
  1357   if (!this.msgPump.hasOwnProperty(msg))
       
  1358     this.msgPump[msg] = [];
       
  1359 
       
  1360   this.msgPump[msg].push(callback);
       
  1361 };
       
  1362 
       
  1363 IriSP.PopcornReplacement.player.prototype.trigger = function(msg, params) {
       
  1364   if (!this.msgPump.hasOwnProperty(msg))
       
  1365     return;
       
  1366 
       
  1367   var d = this.msgPump[msg];
       
  1368 
       
  1369   for(var i = 0; i < d.length; i++) {
       
  1370     d[i].call(window, params);
       
  1371   }
       
  1372 
       
  1373 };
       
  1374 
       
  1375 IriSP.PopcornReplacement.player.prototype.guid = function(prefix) {
       
  1376   var str = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
       
  1377       var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
       
  1378       return v.toString(16);
       
  1379    });
       
  1380 
       
  1381   return prefix + str;
       
  1382 };
       
  1383 
       
  1384 /** init the api after that flash player has been setup - called by the callback
       
  1385     defined by the embedded flash player 
       
  1386 */
       
  1387 IriSP.PopcornReplacement.player.prototype.__initApi = function() {
       
  1388   this.trigger("loadedmetadata"); // we've done more than loading metadata of course,
       
  1389                                                       // but popcorn doesn't need to know more.
       
  1390   this.media.muted = this.playerFns.getMute();
       
  1391   /* some programmed segments are supposed to be run at the beginning */
       
  1392   var i = 0;
       
  1393   for(i = 0; i < this.__codes.length; i++) {
       
  1394     var c = this.__codes[i];
       
  1395     if (0 == c.start) {
       
  1396       c.onStart();
       
  1397     }
       
  1398     
       
  1399     if (0 == c.end) {
       
  1400       c.onEnd();
       
  1401     }
       
  1402   }
       
  1403 };
       
  1404 
       
  1405 /*
       
  1406 IriSP.PopcornReplacement.jwplayer = function(container, options) {
       
  1407   IriSP.PopcornReplacement._container = container.slice(1); //eschew the '#'
       
  1408   options.events = {
       
  1409       onReady: IriSP.PopcornReplacement.__initApi,
       
  1410       onTime: IriSP.PopcornReplacement.__timeHandler,
       
  1411       onPlay: IriSP.PopcornReplacement.__playHandler,
       
  1412       onPause: IriSP.PopcornReplacement.__pauseHandler,
       
  1413       onSeek: IriSP.PopcornReplacement.__seekHandler 
       
  1414       }
       
  1415     
       
  1416   jwplayer(IriSP.PopcornReplacement._container).setup(options);
       
  1417   IriSP.PopcornReplacement.media.duration = options.duration;
       
  1418   return IriSP.PopcornReplacement;
       
  1419 };
       
  1420 */
       
  1421 
       
  1422 IriSP.PopcornReplacement.player.prototype.currentTime = function(time) {
       
  1423   if (typeof(time) === "undefined") {        
       
  1424       return this.playerFns.getPosition();            
       
  1425   } else {
       
  1426      var currentTime = +time;
       
  1427      this.playerFns.seek(currentTime);              
       
  1428      return currentTime;
       
  1429   }
       
  1430 };
       
  1431 
       
  1432 IriSP.PopcornReplacement.player.prototype.play = function() {
       
  1433   this.media.paused = false;
       
  1434   this.trigger("play");
       
  1435   //IriSP.PopcornReplacement.trigger("playing");
       
  1436   this.playerFns.play();
       
  1437 };
       
  1438     
       
  1439 IriSP.PopcornReplacement.player.prototype.pause = function() {
       
  1440 //  if ( !this.media.paused ) {
       
  1441     this.media.paused = true;
       
  1442     this.trigger( "pause" );
       
  1443     this.playerFns.pause();
       
  1444 //  }
       
  1445 };
       
  1446 
       
  1447 IriSP.PopcornReplacement.player.prototype.muted = function(val) {
       
  1448   if (typeof(val) !== "undefined") {
       
  1449 
       
  1450     if (this.playerFns.getMute() !== val) {
       
  1451       if (val) {
       
  1452         this.playerFns.setMute(true);
       
  1453         this.media.muted = true;
       
  1454       } else {
       
  1455         this.playerFns.setMute(false);
       
  1456         this.media.muted = false;
       
  1457       }
       
  1458 
       
  1459       this.trigger( "volumechange" );
       
  1460     }
       
  1461     
       
  1462     return this.playerFns.getMute();
       
  1463   } else {
       
  1464     return this.playerFns.getMute();
       
  1465   }
       
  1466 };
       
  1467 
       
  1468 IriSP.PopcornReplacement.player.prototype.volume = function(val) {
       
  1469     if (typeof this.playerFns.getVolume == "undefined" || typeof this.playerFns.setVolume == "undefined") {
       
  1470         return false;
       
  1471     }
       
  1472     var _vol = this.playerFns.getVolume();
       
  1473     if (typeof(val) !== "undefined" && parseFloat(val) !== NaN) {
       
  1474         val = Math.max(0, Math.min(1, val));
       
  1475         if (parseFloat(val) != parseFloat(_vol)) {
       
  1476             this.playerFns.setVolume(val);
       
  1477             this.trigger("volumechange");
       
  1478             _vol = this.playerFns.getVolume();
       
  1479         }
       
  1480     }
       
  1481     return _vol;
       
  1482 };
       
  1483 
       
  1484 IriSP.PopcornReplacement.player.prototype.mute = IriSP.PopcornReplacement.player.prototype.muted;
       
  1485 
       
  1486 IriSP.PopcornReplacement.player.prototype.code = function(options) {
       
  1487   this.__codes.push(options);
       
  1488   return this;
       
  1489 };
       
  1490 
       
  1491 /* called everytime the player updates itself 
       
  1492    (onTime event)
       
  1493  */
       
  1494 
       
  1495 IriSP.PopcornReplacement.player.prototype.__timeHandler = function(event) {
       
  1496   var pos = event.position;
       
  1497 
       
  1498   var i = 0;
       
  1499   for(i = 0; i < this.__codes.length; i++) {
       
  1500      var c = this.__codes[i];
       
  1501 
       
  1502      if (pos >= c.start && pos < c.end && 
       
  1503          pos - 1 <= c.start) {       
       
  1504         c.onStart();
       
  1505      }
       
  1506  
       
  1507      if (pos > c.start && pos > c.end && 
       
  1508          pos - 1 <= c.end) {
       
  1509          c.onEnd();
       
  1510      }
       
  1511    
       
  1512   }
       
  1513  
       
  1514   this.trigger("timeupdate");
       
  1515 };
       
  1516 
       
  1517 IriSP.PopcornReplacement.player.prototype.__seekHandler = function(event) {
       
  1518   var i = 0;
       
  1519   
       
  1520   for(i = 0; i < this.__codes.length; i++) {
       
  1521      var c = this.__codes[i];
       
  1522     
       
  1523      if (event.position >= c.start && event.position < c.end) {        
       
  1524         c.onEnd();
       
  1525      }         
       
  1526    }
       
  1527   
       
  1528    for(i = 0; i < this.__codes.length; i++) {
       
  1529      var c = this.__codes[i];
       
  1530 
       
  1531      if (typeof(event.offset) === "undefined")
       
  1532        event.offset = 0;
       
  1533            
       
  1534      if (event.offset >= c.start && event.offset < c.end) { 
       
  1535        c.onStart();
       
  1536      }
       
  1537      
       
  1538    }
       
  1539   
       
  1540   /* this signal sends as an extra argument the position in the video.
       
  1541      As far as I know, this argument is not provided by popcorn */
       
  1542   this.trigger("seeked", event.offset);  
       
  1543 };
       
  1544 
       
  1545 IriSP.PopcornReplacement.player.prototype.__playHandler = function(event) {
       
  1546   this.media.paused = false;
       
  1547   this.trigger("play");
       
  1548 };
       
  1549 
       
  1550 IriSP.PopcornReplacement.player.prototype.__pauseHandler = function(event) {
       
  1551   this.media.paused = true;
       
  1552   this.trigger("pause");
       
  1553 };
       
  1554 
       
  1555 IriSP.PopcornReplacement.player.prototype.roundTime = function() {
       
  1556   var currentTime = this.currentTime();
       
  1557   return Math.round(currentTime);
       
  1558 };/* data.js - this file deals with how the players gets and sends data */
       
  1559 
       
  1560 IriSP.DataLoader = function() {
       
  1561   this._cache = {};
       
  1562   
       
  1563   /*
       
  1564     A structure to hold callbacks for specific urls. We need it because
       
  1565     ajax calls are asynchronous, so it means that sometimes we ask
       
  1566     multiple times for a ressource because the first call hasn't been
       
  1567     received yet.
       
  1568   */
       
  1569   this._callbacks = {};
       
  1570 };
       
  1571 
       
  1572 IriSP.DataLoader.prototype.get = function(url, callback, force_reload) {
       
  1573   var base_url = url.split("&")[0];
       
  1574   if (typeof force_reload != "undefined" && force_reload && this._cache.hasOwnProperty(base_url)) {
       
  1575       delete this._cache[base_url]
       
  1576   }
       
  1577   if (this._cache.hasOwnProperty(base_url)) {
       
  1578     callback(this._cache[base_url]);
       
  1579   } else {  
       
  1580     if (!this._callbacks.hasOwnProperty(base_url)) {
       
  1581       this._callbacks[base_url] = [callback];
       
  1582       /* we need a closure because this gets lost when it's called back */
       
  1583   
       
  1584       // uncomment you don't want to use caching.
       
  1585       // IriSP.jQuery.get(url, callback);
       
  1586       
       
  1587       var func = function(data) {
       
  1588                   this._cache[base_url] = data;                                
       
  1589                   var i = 0;
       
  1590                   
       
  1591                   for (i = 0; i < this._callbacks[base_url].length; i++) {
       
  1592                     this._callbacks[base_url][i](this._cache[base_url]);                                  
       
  1593                   }
       
  1594                   delete this._callbacks[base_url];
       
  1595       };
       
  1596       
       
  1597       /* automagically choose between json and jsonp */
       
  1598       if (url.indexOf(document.location.hostname) === -1 &&
       
  1599           url.indexOf("http://") !== -1 /* not a relative url */ ) {
       
  1600         // we contacting a foreign domain, use JSONP
       
  1601 
       
  1602         IriSP.jQuery.get(url, {}, IriSP.wrap(this, func), "jsonp");
       
  1603       } else {
       
  1604 
       
  1605         // otherwise, hey, whatever rows your boat
       
  1606         IriSP.jQuery.get(url, IriSP.wrap(this, func));
       
  1607       }
       
  1608     
       
  1609     } else {
       
  1610       /* simply push the callback - it'll get called when the ressource
       
  1611          has been received */
       
  1612       
       
  1613       this._callbacks[base_url].push(callback);   
       
  1614    
       
  1615     }
       
  1616   }
       
  1617 }
       
  1618 
       
  1619 /* the base abstract "class" */
       
  1620 IriSP.Serializer = function(DataLoader, url) {
       
  1621   this._DataLoader = DataLoader;
       
  1622   this._url = url;
       
  1623   this._data = [];
       
  1624 };
       
  1625 
       
  1626 IriSP.Serializer.prototype.serialize = function(data) { };
       
  1627 IriSP.Serializer.prototype.deserialize = function(data) {};
       
  1628 
       
  1629 IriSP.Serializer.prototype.currentMedia = function() {  
       
  1630 };
       
  1631 
       
  1632 IriSP.Serializer.prototype.getDuration = function() {  
       
  1633 };
       
  1634 
       
  1635 IriSP.Serializer.prototype.sync = function(callback) {
       
  1636     callback.call(this, this._data);
       
  1637 };
       
  1638 
       
  1639 IriSP.SerializerFactory = function(DataLoader) {
       
  1640   this._dataloader = DataLoader;
       
  1641 };
       
  1642 
       
  1643 IriSP.SerializerFactory.prototype.getSerializer = function(metadataOptions) {
       
  1644   /* This function returns serializer set-up with the correct
       
  1645      configuration - takes a metadata struct describing the metadata source
       
  1646   */
       
  1647   if (metadataOptions === undefined)
       
  1648     /* return an empty serializer */
       
  1649     return IriSP.Serializer("", "");
       
  1650             
       
  1651   switch(metadataOptions.type) {
       
  1652     case "json":
       
  1653       return new IriSP.JSONSerializer(this._dataloader, metadataOptions.src);
       
  1654       break;
       
  1655     
       
  1656     case "dummy": /* only used for unit testing - not defined in production */
       
  1657       return new IriSP.MockSerializer(this._dataloader, metadataOptions.src);
       
  1658       break;
       
  1659     
       
  1660     case "empty":
       
  1661       return new IriSP.Serializer("", "empty");
       
  1662       break;
       
  1663       
       
  1664     default:      
       
  1665       return undefined;
       
  1666   }
       
  1667 };
       
  1668 IriSP.language = 'en';
       
  1669 
       
  1670 IriSP.libFiles = {
       
  1671     defaultDir : "js/libs/",
       
  1672     inDefaultDir : {
       
  1673         jQuery : "jquery.min.js",
       
  1674         jQueryUI : "jquery-ui.min.js",
       
  1675         jQueryToolTip : "jquery.tools.min.js",
       
  1676         swfObject : "swfobject.js",
       
  1677         cssjQueryUI : "jquery-ui.css",
       
  1678         popcorn : "popcorn.js",
       
  1679         jwplayer : "jwplayer.js",
       
  1680         raphael : "raphael.js",
       
  1681         "popcorn.mediafragment" : "popcorn.mediafragment.js",
       
  1682         "popcorn.code" : "popcorn.code.js",
       
  1683         "popcorn.jwplayer" : "popcorn.jwplayer.js",
       
  1684         "popcorn.youtube" : "popcorn.youtube.js",
       
  1685         "tracemanager" : "tracemanager.js"
       
  1686     },
       
  1687     locations : {
       
  1688         // use to define locations outside defautl_dir
       
  1689     },
       
  1690     cdn : {
       
  1691         jQueryUI : "http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.17/jquery-ui.js",
       
  1692         jQueryToolTip : "http://cdn.jquerytools.org/1.2.4/all/jquery.tools.min.js",
       
  1693         swfObject : "http://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js",
       
  1694         cssjQueryUI : "http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.4/themes/base/jquery-ui.css"
       
  1695     },
       
  1696     useCdn : false
       
  1697 }
       
  1698 
       
  1699 IriSP.widgetsDefaults = {
       
  1700     "LayoutManager" : {
       
  1701         spacer_div_height : 0
       
  1702     },
       
  1703     "PlayerWidget" : {
       
  1704         
       
  1705     },
       
  1706     "AnnotationsWidget" : {
       
  1707         "share_text" : "I'm watching "
       
  1708     },
       
  1709     "TweetsWidget" : {
       
  1710         default_profile_picture : "https://si0.twimg.com/sticky/default_profile_images/default_profile_1_normal.png",
       
  1711         tweet_display_period : 10000 // how long do we show a tweet ?
       
  1712     },
       
  1713     "SliderWidget" : {
       
  1714         minimize_period : 850 // how long does the slider stays maximized after the user leaves the zone ?
       
  1715     },
       
  1716     "SegmentsWidget" : {
       
  1717         cinecast_version : false
       
  1718     },
       
  1719     "createAnnotationWidget" : {
       
  1720         tags : [
       
  1721             {
       
  1722                 "id" : "digitalstudies",
       
  1723                 "meta" : {
       
  1724                     "description" : "#digital-studies"
       
  1725                 }
       
  1726             },
       
  1727             {
       
  1728                 "id" : "amateur",
       
  1729                 "meta" : {
       
  1730                     "description" : "#amateur"
       
  1731                 },
       
  1732             }
       
  1733         ],
       
  1734         remote_tags : false,
       
  1735         random_tags : false,
       
  1736         show_from_field : false,
       
  1737         disable_share : false,
       
  1738         polemic_mode : true, /* enable polemics ? */
       
  1739         polemics : [{
       
  1740             "className" : "positive",
       
  1741             "keyword" : "++"
       
  1742         }, {
       
  1743             "className" : "negative",
       
  1744             "keyword" : "--"
       
  1745         }, {
       
  1746             "className" : "reference",
       
  1747             "keyword" : "=="
       
  1748         }, {
       
  1749             "className" : "question",
       
  1750             "keyword" : "??"
       
  1751         }],
       
  1752         cinecast_version : false, /* put to false to enable the platform version, true for the festival cinecast one. */
       
  1753 
       
  1754         /* where does the widget PUT the annotations - this is a mustache template. id refers to the id of the media ans is filled
       
  1755          by the widget.
       
  1756          */
       
  1757         api_endpoint_template : "", // platform_url + "/ldtplatform/api/ldt/annotations/{{id}}.json",
       
  1758         api_method : "PUT"
       
  1759     },
       
  1760     "SparklineWidget" : {
       
  1761        lineColor : "#7492b4",
       
  1762        fillColor : "#aeaeb8",
       
  1763        lineWidth : 2,
       
  1764        cinecast_version : false
       
  1765     },
       
  1766     "AnnotationsListWidget" : {
       
  1767         ajax_mode : true, /* use ajax to get information about the annotations.
       
  1768          if set to false, only search in the annotations for the
       
  1769          current project. */
       
  1770         /* the platform generates some funky urls. We replace them afterwards to point to the
       
  1771          correct place - this setting will probably be overwritten by the platform
       
  1772          implementers.
       
  1773          Note that the player has to replace the variables between {{ and }} by its own values.
       
  1774          */
       
  1775         ajax_url : "", //platform_url + "/ldtplatform/api/ldt/segments/{{media}}/{{begin}}/{{end}}",
       
  1776         ajax_granularity : 10000, /* how much ms should we look before and after the current timecode */
       
  1777         default_thumbnail : "http://ldt.iri.centrepompidou.fr/static/site/ldt/css/imgs/video_sequence.png",
       
  1778         project_url : "", //platform_url + "/ldtplatform/ldt/front/player/"
       
  1779         /* the beginning of a link to the new front */
       
  1780         cinecast_version : false,
       
  1781         refresh_interval : 10000
       
  1782     },
       
  1783     "StackGraphWidget" : {
       
  1784          defaultcolor : "#585858",
       
  1785          tags : [
       
  1786             {
       
  1787                 "keywords" : [ "++" ],
       
  1788                 "description" : "positif",
       
  1789                 "color" : "#1D973D"
       
  1790             },
       
  1791             {
       
  1792                 "keywords" : [ "--" ],
       
  1793                 "description" : "negatif",
       
  1794                 "color" : "#CE0A15"
       
  1795             },
       
  1796             {
       
  1797                 "keywords" : [ "==" ],
       
  1798                 "description" : "reference",
       
  1799                 "color" : "#C5A62D"  
       
  1800             },
       
  1801             {
       
  1802                 "keywords" : [ "??" ],
       
  1803                 "description" : "question",
       
  1804                 "color" : "#036AAE"
       
  1805             }
       
  1806         ],
       
  1807         streamgraph : false
       
  1808     }
       
  1809 }/* the widget classes and definitions */
       
  1810 
       
  1811 /**
       
  1812   * @class Widget is an "abstract" class. It's mostly used to define some properties common to every widget.
       
  1813   *
       
  1814   *  Note that widget constructors are never called directly by the user. Instead, the widgets are instantiated by functions
       
  1815   *  defined in init.js
       
  1816   *  
       
  1817   * @constructor
       
  1818   * @param Popcorn a reference to the popcorn Object
       
  1819   * @param config configuration options for the widget
       
  1820   * @param Serializer a serializer instance from which the widget reads data fromCharCode  
       
  1821 */
       
  1822 IriSP.Widget = function(Popcorn, config, Serializer) {
       
  1823 
       
  1824   if (config === undefined || config === null) {
       
  1825     config = {}
       
  1826   }
       
  1827   
       
  1828   this._Popcorn = Popcorn;
       
  1829   this._config = config;  
       
  1830   this._serializer = Serializer;
       
  1831   
       
  1832   if (config.hasOwnProperty("container")) {
       
  1833      this._id = config.container;
       
  1834      this.selector = IriSP.jQuery("#" + this._id);
       
  1835   }
       
  1836 
       
  1837   if (config.hasOwnProperty("spacer")) {
       
  1838      this._spacerId = config.spacer;
       
  1839      this.spacer = IriSP.jQuery("#" + this._spacerId);
       
  1840   }
       
  1841 
       
  1842 
       
  1843   if (config.hasOwnProperty("width")) {
       
  1844      // this.width and not this._width because we consider it public.
       
  1845      this.width = config.width;     
       
  1846   }
       
  1847   
       
  1848   if (config.hasOwnProperty("height")) {    
       
  1849      this.height = config.height;     
       
  1850   }
       
  1851   
       
  1852   if (config.hasOwnProperty("heightmax")) {
       
  1853      this.heightmax = config.heightmax;     
       
  1854   }
       
  1855 
       
  1856   if (config.hasOwnProperty("widthmax")) {
       
  1857      this.widthmax = config.widthmax;     
       
  1858   } 
       
  1859 
       
  1860   if (config.hasOwnProperty("layoutManager")) {
       
  1861      this.layoutManager = config.layoutManager;
       
  1862   }
       
  1863   if (typeof this.selector != "undefined") {
       
  1864       this.selector.addClass("Ldt-TraceMe").addClass("Ldt-Widget");
       
  1865       this.selector.attr("widget-type", this._config.type);
       
  1866   }
       
  1867   
       
  1868   // Parsing Widget Defaults
       
  1869   var _this = this;
       
  1870   
       
  1871   if (typeof config.type == "string" && typeof IriSP.widgetsDefaults[config.type] == "object") {
       
  1872       IriSP._(IriSP.widgetsDefaults[config.type]).each(function(_v, _k) {
       
  1873           if (typeof config[_k] != "undefined") {
       
  1874               _this[_k] = config[_k];
       
  1875           } else {
       
  1876               _this[_k] = _v;
       
  1877           }
       
  1878       });
       
  1879   }
       
  1880   
       
  1881 };
       
  1882 
       
  1883 
       
  1884 IriSP.Widget.prototype.currentMedia = function() {
       
  1885     return this._serializer.currentMedia();
       
  1886 }
       
  1887 
       
  1888 IriSP.Widget.prototype.getDuration = function() {
       
  1889     return this._serializer.getDuration();
       
  1890 }
       
  1891 
       
  1892 /**
       
  1893   * This method responsible of drawing a widget on screen.
       
  1894   */
       
  1895 IriSP.Widget.prototype.draw = function() {
       
  1896   /* implemented by "sub-classes" */  
       
  1897 };
       
  1898 
       
  1899 /**
       
  1900   * Optional method if you want your widget to support redraws.
       
  1901   */
       
  1902 IriSP.Widget.prototype.redraw = function() {
       
  1903   /* implemented by "sub-classes" */  
       
  1904 };
       
  1905 /* modules are non-graphical entities, similar to widgets */
       
  1906 
       
  1907 IriSP.Module = function(Popcorn, config, Serializer) {
       
  1908 
       
  1909   if (config === undefined || config === null) {
       
  1910     config = {}
       
  1911   }
       
  1912   
       
  1913   this._Popcorn = Popcorn;
       
  1914   this._config = config;  
       
  1915   this._serializer = Serializer;
       
  1916 };
       
  1917 /* layout.js - very basic layout management */
       
  1918 
       
  1919 /**
       
  1920   @class a layout manager manages a div and the layout of objects
       
  1921   inside it.
       
  1922 */
       
  1923 IriSP.LayoutManager = function(options) {
       
  1924     this._Popcorn = null;
       
  1925     this._widgets = [];
       
  1926     
       
  1927     this._div = "LdtPlayer";
       
  1928     this._width = 640;
       
  1929     
       
  1930     if (options === undefined) {
       
  1931       options = {};
       
  1932     };
       
  1933     
       
  1934     if (options.hasOwnProperty('container')) {
       
  1935       this._div = options.container;
       
  1936     }
       
  1937 
       
  1938     if (options.hasOwnProperty('width')) {
       
  1939       this._width = options.width;
       
  1940     }    
       
  1941     
       
  1942     if (options.hasOwnProperty('height')) {
       
  1943       this._height = options.height;
       
  1944     } 
       
  1945     
       
  1946     /* this is a shortcut */
       
  1947     this.selector = IriSP.jQuery("#" + this._div);
       
  1948     
       
  1949     this.selector.css({
       
  1950         "width": this._width,
       
  1951         "clear": "both"
       
  1952     });
       
  1953     
       
  1954     if (this._height !== undefined)
       
  1955       this.selector.css("height", this._height);
       
  1956 };
       
  1957 
       
  1958 /** 
       
  1959    Set the popcorn instance used by the manager.
       
  1960    
       
  1961    we need this special setter because of a chicken and egg problem :
       
  1962    we want the manager to use popcorn but the popcorn div will be managed
       
  1963    by the manager. So we need a way to set the instance the manager uses
       
  1964 */
       
  1965    
       
  1966 IriSP.LayoutManager.prototype.setPopcornInstance = function(popcorn) {
       
  1967     this._Popcorn = popcorn;
       
  1968 }
       
  1969 
       
  1970 /** create a subdiv with an unique id, and a spacer div as well.
       
  1971     @param widgetName the name of the widget.
       
  1972     @return an array of the form [createdivId, spacerdivId].
       
  1973 */
       
  1974 IriSP.LayoutManager.prototype.createDiv = function(widgetName) {
       
  1975     if (typeof(widgetName) === "undefined")
       
  1976        widgetName = "";
       
  1977 
       
  1978     var newDiv = IriSP.guid(this._div + "_widget_" + widgetName + "_");
       
  1979     var spacerDiv = IriSP.guid("LdtPlayer_spacer_");
       
  1980     this._widgets.push([widgetName, newDiv]);    
       
  1981 
       
  1982     var divTempl = "<div id='{{id}}' style='width: {{width}}px; position: relative; clear: both;'></div";
       
  1983     var spacerTempl = "<div id='{{spacer_id}}' style='width: {{width}}px; position: relative; height: {{spacer_div_height}}px;'></div";
       
  1984     
       
  1985     var divCode = Mustache.to_html(divTempl, {id: newDiv, width: this._width});
       
  1986     var spacerCode = Mustache.to_html(spacerTempl, {spacer_id: spacerDiv, width: this._width,
       
  1987                                                     spacer_div_height: IriSP.widgetsDefaults.LayoutManager.spacer_div_height });
       
  1988 
       
  1989     this.selector.append(divCode);
       
  1990     this.selector.append(spacerCode);
       
  1991 
       
  1992     return [newDiv, spacerDiv];
       
  1993 };/* init.js - initialization and configuration of Popcorn and the widgets
       
  1994 exemple json configuration:
       
  1995  
       
  1996  */
       
  1997 
       
  1998 /**
       
  1999     set up the IriSP.__dataloader instance - 
       
  2000     we need it because we have to get the metadata
       
  2001     about the video before that the widget have even
       
  2002     loaded.
       
  2003 */
       
  2004 IriSP.setupDataLoader = function() {
       
  2005   /* we set it up separately because we need to
       
  2006      get data at the very beginning, for instance when
       
  2007      setting up the video */
       
  2008   IriSP.__dataloader = new IriSP.DataLoader();
       
  2009 };
       
  2010 
       
  2011 /** do some magic to configure popcorn according to the options object passed.
       
  2012     Works for html5, jwplayer and youtube videos 
       
  2013 */
       
  2014 IriSP.configurePopcorn = function (layoutManager, options) {
       
  2015     var pop;
       
  2016     var ret = layoutManager.createDiv(); 
       
  2017     var containerDiv = ret[0];
       
  2018     var spacerDiv = ret[1];
       
  2019     
       
  2020     /* insert one pixel of margin between the video and the first widget, using the 
       
  2021        spacer.
       
  2022     */
       
  2023     IriSP.jQuery("#" + spacerDiv).css("height", "1px");
       
  2024     
       
  2025     switch(options.type) {
       
  2026       /*
       
  2027         todo : dynamically create the div/video tag which
       
  2028         will contain the video.
       
  2029       */
       
  2030       case "html5":
       
  2031            var tmpId = Popcorn.guid("video"); 
       
  2032            IriSP.jQuery("#" + containerDiv).append("<video src='" + options.file + "' id='" + tmpId + "'></video>");
       
  2033 
       
  2034            if (options.hasOwnProperty("width"))
       
  2035              IriSP.jQuery("#" + containerDiv).css("width", options.width);
       
  2036            
       
  2037            if (options.hasOwnProperty("height"))
       
  2038              IriSP.jQuery("#" + containerDiv).css("height", options.height);
       
  2039 
       
  2040            pop = Popcorn("#" + tmpId);
       
  2041         break;
       
  2042         
       
  2043       case "jwplayer":
       
  2044           var opts = IriSP.jQuery.extend({}, options);
       
  2045           delete opts.container;
       
  2046           delete opts.type;
       
  2047 
       
  2048           
       
  2049           /* Try to guess options.file and options.streamer only if file and streamer
       
  2050              are not already defined in the configuration */
       
  2051           if (options.provider === "rtmp" && !opts.hasOwnProperty("file") && !opts.hasOwnProperty("streamer")) {
       
  2052             /* exit if we can't access the metadata */
       
  2053             if (typeof(IriSP.__jsonMetadata) === "undefined") {
       
  2054                 break;
       
  2055             };
       
  2056 
       
  2057             // the json format is totally illogical
       
  2058             //opts.streamer = IriSP.__jsonMetadata["medias"][0]["meta"]["item"]["value"];
       
  2059             //var source = IriSP.__jsonMetadata["medias"][0]["href"];
       
  2060 
       
  2061             // the source if a full url but jwplayer wants an url relative to the
       
  2062             // streamer url, so we've got to remove the common part.
       
  2063             //opts.file = source.slice(opts.streamer.length);
       
  2064             
       
  2065             /* sometimes we get served a file with a wrong path and streamer.
       
  2066                as a streamer is of the form rtmp://domain/path/ and the media is
       
  2067                the rest, we uglily do this :
       
  2068             */
       
  2069             opts.file = "";
       
  2070             opts.streamer = "";
       
  2071             var fullPath = IriSP.get_aliased(IriSP.__jsonMetadata["medias"][0], ["href","url"]);
       
  2072             
       
  2073             if (fullPath === null) {
       
  2074               console.log("no url or href field defined in the metadata.");
       
  2075             }
       
  2076             
       
  2077             var pathSplit = fullPath.split('/');
       
  2078             
       
  2079             for (var i = 0; i < pathSplit.length; i++) {
       
  2080               if (i < 4) {
       
  2081                  opts.streamer += pathSplit[i] + "/";
       
  2082               } else {
       
  2083                  opts.file += pathSplit[i];
       
  2084                  /* omit the last slash if we're on the last element */
       
  2085                  if (i < pathSplit.length - 1)
       
  2086                   opts.file += "/";
       
  2087               }
       
  2088             }            
       
  2089           } else {
       
  2090             /* other providers type, video for instance -
       
  2091                pass everything as is */
       
  2092           }
       
  2093 
       
  2094           if (!options.hasOwnProperty("flashplayer")) {
       
  2095             opts.flashplayer = IriSP.jwplayer_swf_path;
       
  2096           }
       
  2097 
       
  2098           if (!options.hasOwnProperty("controlbar.position")) {
       
  2099             opts["controlbar.position"] = "none";
       
  2100           }
       
  2101 
       
  2102           pop = new IriSP.PopcornReplacement.jwplayer("#" + containerDiv, opts);
       
  2103         break;
       
  2104       
       
  2105       case "youtube":
       
  2106           var opts = IriSP.jQuery.extend({}, options);
       
  2107           delete opts.container;
       
  2108           opts.controls = 0;
       
  2109           opts.autostart = false;
       
  2110           templ = "width: {{width}}px; height: {{height}}px;";
       
  2111           var str = Mustache.to_html(templ, {width: opts.width, height: opts.height});    
       
  2112           // Popcorn.youtube wants us to specify the size of the player in the style attribute of its container div.
       
  2113           IriSP.jQuery("#" + containerDiv).attr("style", str);
       
  2114           
       
  2115           pop = Popcorn.youtube("#" + containerDiv, opts.video, opts);
       
  2116         break;
       
  2117       
       
  2118     case "dailymotion":
       
  2119         pop = new IriSP.PopcornReplacement.dailymotion("#" + containerDiv, options);
       
  2120         break;
       
  2121              
       
  2122       case "allocine":
       
  2123           /* pass the options as-is to the allocine player and let it handle everything */
       
  2124           pop = new IriSP.PopcornReplacement.allocine("#" + containerDiv, options);
       
  2125           break;
       
  2126           
       
  2127       default:
       
  2128         pop = undefined;
       
  2129     };
       
  2130     
       
  2131     return pop;
       
  2132 };
       
  2133 
       
  2134 /** Configure the gui and instantiate the widgets passed as parameters
       
  2135     @param guiOptions the gui object as seen in the examples.
       
  2136  */
       
  2137 IriSP.configureWidgets = function (popcornInstance, layoutManager, guiOptions) {
       
  2138  
       
  2139   var serialFactory = new IriSP.SerializerFactory(IriSP.__dataloader);
       
  2140   var params = {width: guiOptions.width, height: guiOptions.height};
       
  2141 
       
  2142   var default_options = guiOptions.default_options;
       
  2143   if (IriSP.null_or_undefined(default_options))
       
  2144     default_options = {};
       
  2145   
       
  2146   var ret_widgets = [];
       
  2147   var index;
       
  2148   
       
  2149   for (index = 0; index < guiOptions.widgets.length; index++) {    
       
  2150     var widget = IriSP.instantiateWidget(popcornInstance, serialFactory, layoutManager, guiOptions.widgets[index], default_options);
       
  2151    
       
  2152     ret_widgets.push(widget);   
       
  2153   };
       
  2154 
       
  2155   return ret_widgets;
       
  2156 };
       
  2157 
       
  2158 /** configure modules. @see configureWidgets */
       
  2159 IriSP.configureModules = function (popcornInstance, modulesList) {
       
  2160   if (IriSP.null_or_undefined(modulesList))
       
  2161     return;
       
  2162   
       
  2163   var serialFactory = new IriSP.SerializerFactory(IriSP.__dataloader);
       
  2164   var ret_modules = [];
       
  2165   var index;
       
  2166   
       
  2167   for (index = 0; index < modulesList.length; index++) {    
       
  2168     var moduleConfig = modulesList[index];
       
  2169     
       
  2170     var serializer = serialFactory.getSerializer(moduleConfig.metadata);
       
  2171     var module = new IriSP[moduleConfig.type](popcornInstance, moduleConfig, serializer);    
       
  2172     ret_modules.push(module);
       
  2173   };
       
  2174 
       
  2175   return ret_modules;
       
  2176 };
       
  2177 
       
  2178 /** instantiate a widget - only called by configureWidgets, never by the user. Handles widget 
       
  2179     dependencies.
       
  2180     @param popcornInstance popcorn instance the widget will user
       
  2181     @param serialFactory serializer factory to instantiate the widget with
       
  2182     @param layoutManager layout manager
       
  2183     @param widgetConfig configuration options for the widget
       
  2184     @param defaultOptions a dictionnary with some options defined for every widget.
       
  2185  */
       
  2186 IriSP.instantiateWidget = function(popcornInstance, serialFactory, layoutManager, widgetConfig, defaultOptions) {
       
  2187 
       
  2188     if (IriSP.null_or_undefined(defaultOptions))
       
  2189       defaultOptions = {};
       
  2190     
       
  2191     widgetConfig = IriSP.underscore.defaults(widgetConfig, defaultOptions);
       
  2192 
       
  2193     var arr = IriSP.jQuery.extend({}, widgetConfig);
       
  2194     
       
  2195     /* create a div for those widgets who didn't already specify a container; */
       
  2196     if (!arr.hasOwnProperty("container")) {
       
  2197       /* create div returns us a container for the widget and a spacer */    
       
  2198       var ret = layoutManager.createDiv(widgetConfig.type);        
       
  2199       var container = ret[0];
       
  2200       var spacer = ret[1];           
       
  2201       arr.container = container;
       
  2202       arr.spacer = spacer;
       
  2203       arr.layoutManager = layoutManager;
       
  2204     }
       
  2205     var serializer = serialFactory.getSerializer(widgetConfig.metadata);    
       
  2206     
       
  2207     if (typeof serializer == "undefined")   
       
  2208       debugger;
       
  2209     
       
  2210     // instantiate the object passed as a string
       
  2211     var widget = new IriSP[widgetConfig.type](popcornInstance, arr, serializer);    
       
  2212     
       
  2213     if (widgetConfig.hasOwnProperty("requires")) {
       
  2214       // also create the widgets this one depends on.
       
  2215       // the dependency widget is available in the parent widget context as
       
  2216       // this.WidgetName (for instance, this.TipWidget);
       
  2217       
       
  2218       var i = 0;
       
  2219       for(i = 0; i < widgetConfig.requires.length; i++) {
       
  2220         var widgetName = widgetConfig.requires[i]["type"],
       
  2221             _configobj = IriSP.jQuery.extend({}, widgetConfig.requires[i]),
       
  2222             _div = document.createElement('div'),
       
  2223             _container = IriSP.guid(arr.container + '_' + widgetName + '_');
       
  2224         _configobj.container = _container;
       
  2225         _div.id = _container;
       
  2226         widget.selector.append(_div);
       
  2227         widget[widgetName] = IriSP.instantiateWidget(popcornInstance, serialFactory, layoutManager, _configobj, defaultOptions);
       
  2228       }
       
  2229     }       
       
  2230      
       
  2231     serializer.sync(IriSP.wrap(widget, function() { this.draw(); }));
       
  2232     return widget;
       
  2233 };
       
  2234 
       
  2235 /** single point of entry for the metadataplayer */
       
  2236 IriSP.initPlayer = function(config, metadata_url) {
       
  2237     document.getElementById(config.gui.container).innerHTML = IriSP.templToHTML(IriSP.loading_template, config.gui);
       
  2238     IriSP.loadLibs(config, metadata_url,
       
  2239       function() {   
       
  2240               
       
  2241               var layoutManager = new IriSP.LayoutManager(config.gui);
       
  2242 
       
  2243               var pop = IriSP.configurePopcorn(layoutManager, config.player);
       
  2244               
       
  2245               IriSP._widgets = IriSP.configureWidgets(pop, layoutManager, config.gui); 
       
  2246               IriSP._modules = IriSP.configureModules(pop, config.modules); 
       
  2247               IriSP.jQuery('#Ldt-loader').detach();
       
  2248       });
       
  2249 };IriSP.I18n = function() {
       
  2250     this.messages = {};
       
  2251     this.base_lang = 'en';
       
  2252 }
       
  2253 
       
  2254 IriSP.I18n.prototype.getLanguage = function(lang) {
       
  2255     var _lang = (
       
  2256         typeof lang != "undefined"
       
  2257         ? lang
       
  2258         : (
       
  2259             typeof IriSP.language != "undefined"
       
  2260             ? IriSP.language
       
  2261             : this.base_lang
       
  2262         )
       
  2263     );
       
  2264     return (
       
  2265         typeof this.messages[_lang] == "object"
       
  2266         ? _lang
       
  2267         : (
       
  2268             typeof this.messages[this.base_lang] == "object"
       
  2269             ? this.base_lang
       
  2270             : null
       
  2271         )
       
  2272     )
       
  2273 }
       
  2274 
       
  2275 IriSP.I18n.prototype.getMessages = function(lang) {
       
  2276     var _lang = this.getLanguage(lang);
       
  2277     return (
       
  2278         _lang != null
       
  2279         ? this.messages[_lang]
       
  2280         : {}
       
  2281     );
       
  2282 }
       
  2283 
       
  2284 IriSP.I18n.prototype.getMessage = function(message, lang) {
       
  2285     var _msgs = this.getMessages(lang);
       
  2286     return (
       
  2287         typeof _msgs[message] != "undefined"
       
  2288         ? _msgs[message]
       
  2289         : message
       
  2290     )
       
  2291 }
       
  2292 
       
  2293 IriSP.I18n.prototype.addMessage = function(lang, messagekey, messagevalue) {
       
  2294     if (typeof this.messages[lang] == "undefined") {
       
  2295         this.messages[lang] = {};
       
  2296     }
       
  2297     this.messages[lang][messagekey] = messagevalue;
       
  2298 }
       
  2299 
       
  2300 IriSP.I18n.prototype.addMessages = function(messagesObj) {
       
  2301     var _this = this;
       
  2302     IriSP.underscore(messagesObj).each(function(_messages, _lang) {
       
  2303         IriSP.underscore(_messages).each(function(_value, _key) {
       
  2304             _this.addMessage(_lang, _key, _value);
       
  2305         })
       
  2306     });
       
  2307 }
       
  2308 
       
  2309 IriSP.i18n = new IriSP.I18n();
       
  2310 
       
  2311 IriSP.i18n.addMessages({
       
  2312     "fr": {
       
  2313         "loading_wait": "Chargement en cours, veuillez patienter&hellip;"
       
  2314     },
       
  2315     "en": {
       
  2316         "loading_wait": "Loading, please wait&hellip;"
       
  2317     }
       
  2318 })
       
  2319 /* To wrap a player the develop should create a new class derived from
       
  2320 the IriSP.PopcornReplacement.player and defining the correct functions */
       
  2321 
       
  2322 /** allocine player wrapper */
       
  2323 IriSP.PopcornReplacement.allocine = function(container, options) {
       
  2324 //    console.log("Calling allocine player");
       
  2325     /* appel du parent pour initialiser les structures communes à tous les players */
       
  2326     IriSP.PopcornReplacement.player.call(this, container, options);   
       
  2327     
       
  2328     var _this = this;
       
  2329 
       
  2330     /* Définition des fonctions de l'API -  */
       
  2331 
       
  2332     this.playerFns = {
       
  2333         play : function() {
       
  2334             return _this.apiCall("play");
       
  2335         },
       
  2336         pause : function() {
       
  2337             return _this.apiCall("pause");
       
  2338         },
       
  2339         getPosition : function() {
       
  2340             return _this.apiCall("getSeek","return") || 0;
       
  2341         },
       
  2342         seek : function(pos) {
       
  2343             return _this.apiCall("seek",pos);
       
  2344         },
       
  2345         getMute : function() {
       
  2346             return _this.apiCall("getMute","return");
       
  2347         },
       
  2348         setMute : function(p) {
       
  2349             return _this.apiCall("setMute", p);
       
  2350         }
       
  2351     }
       
  2352 
       
  2353     window.onReady = IriSP.wrap(this, this.ready);
       
  2354     window.onAllocineStateChange = IriSP.wrap(this, this.stateHandler);
       
  2355     window.onTime = IriSP.wrap(this, this.progressHandler);
       
  2356     
       
  2357     var _videoUrl = (
       
  2358         typeof options.directVideoPath == "string"
       
  2359         ? options.directVideoPath
       
  2360         : IriSP.get_aliased(IriSP.__jsonMetadata["medias"][0], ["href","url"])
       
  2361     );
       
  2362     var _flashVars = {
       
  2363         "streamFMS" : true,
       
  2364         "adVast" : false,
       
  2365         "lg" : "fr_cinecast",
       
  2366         "autoPlay" : options.autoPlay,
       
  2367         "directVideoTitle" : "",
       
  2368         "urlAcData" : options.urlAcData,
       
  2369         "directVideoPath" : _videoUrl,
       
  2370         "host" : "http://allocine.fr"
       
  2371     }
       
  2372     
       
  2373     if (typeof IriSP.__jsonMetadata["medias"][0].meta == "object" && typeof IriSP.__jsonMetadata["medias"][0].meta.subtitles == "string") {
       
  2374         _flashVars.subTitlePath = IriSP.__jsonMetadata["medias"][0].meta.subtitles;
       
  2375     }
       
  2376     
       
  2377 
       
  2378     var params = {
       
  2379         "allowScriptAccess" : "always",
       
  2380         "wmode": "opaque",
       
  2381         "flashvars" : IriSP.jQuery.param(_flashVars),
       
  2382         "allowfullscreen" : true
       
  2383     };
       
  2384     var atts = {
       
  2385         id : this.container
       
  2386     };
       
  2387     swfobject.embedSWF(options.acPlayerUrl, this.container, options.width, options.height, "10", null, null, params, atts);
       
  2388 
       
  2389 };
       
  2390 
       
  2391 IriSP.PopcornReplacement.allocine.prototype = new IriSP.PopcornReplacement.player("", {});
       
  2392 
       
  2393 IriSP.PopcornReplacement.allocine.prototype.ready = function() {
       
  2394     this.player = document.getElementById(this.container);
       
  2395     this.player.addEventListener("onStateChange", "onAllocineStateChange");
       
  2396     this.player.cueVideoByUrl(this._options.video);
       
  2397     this.callbacks.onReady();
       
  2398 };
       
  2399 
       
  2400 IriSP.PopcornReplacement.allocine.prototype.progressHandler = function(progressInfo) {
       
  2401     this.callbacks.onTime({
       
  2402         position: progressInfo.mediaTime
       
  2403     });
       
  2404 }
       
  2405 
       
  2406 
       
  2407 IriSP.PopcornReplacement.allocine.prototype.apiCall = function(_method, _arg) {
       
  2408     if (this.player) {
       
  2409         try {
       
  2410             if (typeof _arg == "undefined") {
       
  2411                 return this.player.sendToActionScript(_method);
       
  2412             } else {
       
  2413                 return this.player.sendToActionScript(_method, _arg);
       
  2414             }
       
  2415         } catch(e) {
       
  2416             console.error('Exception while requesting AcPlayer for "' + _method + (typeof _arg == "undefined" ? '' : '" with argument "' + _arg ) + '"\n', e);
       
  2417             return false;
       
  2418         }
       
  2419     } else {
       
  2420         return false;
       
  2421     }
       
  2422 }
       
  2423 
       
  2424 IriSP.PopcornReplacement.allocine.prototype.stateHandler = function(state) {
       
  2425     console.log("stateHandler");
       
  2426     switch(state) {
       
  2427         case 1:
       
  2428             this.callbacks.onPlay();
       
  2429             break;
       
  2430 
       
  2431         case 2:
       
  2432             this.callbacks.onPause();
       
  2433             break;
       
  2434 
       
  2435         case 3:
       
  2436             this.callbacks.onSeek({
       
  2437                 position: this.player.getCurrentTime()
       
  2438             });
       
  2439             break;
       
  2440 
       
  2441         /*
       
  2442         case 5:
       
  2443             this.callbacks.onReady();
       
  2444             break;
       
  2445         */
       
  2446     }
       
  2447     
       
  2448 };/* To wrap a player the develop should create a new class derived from
       
  2449 the IriSP.PopcornReplacement.player and defining the correct functions */
       
  2450 
       
  2451 /** jwplayer player wrapper */
       
  2452 IriSP.PopcornReplacement.dailymotion = function(container, options) {
       
  2453     console.log("Calling");
       
  2454     /* appel du parent pour initialiser les structures communes à tous les players */
       
  2455     IriSP.PopcornReplacement.player.call(this, container, options);   
       
  2456     
       
  2457     var _this = this;
       
  2458 
       
  2459     /* Définition des fonctions de l'API -  */
       
  2460 
       
  2461     this.playerFns = {
       
  2462         play : function() {
       
  2463             if (_this.player) {
       
  2464                 return _this.player.playVideo();
       
  2465             } else {
       
  2466                 return false;
       
  2467             }
       
  2468         },
       
  2469         pause : function() {
       
  2470             if (_this.player) {
       
  2471                 return _this.player.pauseVideo();
       
  2472             } else {
       
  2473                 return false;
       
  2474             }
       
  2475         },
       
  2476         getPosition : function() {
       
  2477             if (_this.player) {
       
  2478                 return _this.player.getCurrentTime();
       
  2479             } else {
       
  2480                 return 0;
       
  2481             }
       
  2482         },
       
  2483         seek : function(pos) {
       
  2484             if (_this.player) {
       
  2485                 return _this.player.seekTo(pos);
       
  2486             } else {
       
  2487                 return false;
       
  2488             }
       
  2489         },
       
  2490         getMute : function() {
       
  2491             if (_this.player) {
       
  2492                 return _this.player.isMuted();
       
  2493             } else {
       
  2494                 return false;
       
  2495             }
       
  2496         },
       
  2497         setMute : function(p) {
       
  2498             if (_this.player) {
       
  2499                 if (p) {
       
  2500                     _this.player.mute();
       
  2501                 }
       
  2502                 else {
       
  2503                     _this.player.unMute();
       
  2504                 }
       
  2505             }
       
  2506         },
       
  2507         getVolume : function() {
       
  2508             if (_this.player) {
       
  2509                 return _this.player.getVolume() / 100;
       
  2510             } else {
       
  2511                 return false;
       
  2512             }
       
  2513         },
       
  2514         setVolume : function(p) {
       
  2515             if (_this.player) {
       
  2516                 _this.player.setVolume(Math.floor(100 * p));
       
  2517             }
       
  2518         },
       
  2519     }
       
  2520 
       
  2521     window.onDailymotionPlayerReady = IriSP.wrap(this, this.ready);
       
  2522     window.onDailymotionStateChange = IriSP.wrap(this, this.stateHandler);
       
  2523     window.onDailymotionVideoProgress = IriSP.wrap(this, this.progressHandler);
       
  2524 
       
  2525     var params = {
       
  2526         "allowScriptAccess" : "always",
       
  2527         "wmode": "opaque"
       
  2528     };
       
  2529     var atts = {
       
  2530         id : this.container
       
  2531     };
       
  2532     swfobject.embedSWF("http://www.dailymotion.com/swf?chromeless=1&enableApi=1", this.container, options.width, options.height, "8", null, null, params, atts);
       
  2533 
       
  2534 };
       
  2535 
       
  2536 IriSP.PopcornReplacement.dailymotion.prototype = new IriSP.PopcornReplacement.player("", {});
       
  2537 
       
  2538 IriSP.PopcornReplacement.dailymotion.prototype.ready = function() {
       
  2539     
       
  2540     this.player = document.getElementById(this.container);
       
  2541     
       
  2542     this.player.addEventListener("onStateChange", "onDailymotionStateChange");
       
  2543     this.player.addEventListener("onVideoProgress", "onDailymotionVideoProgress");
       
  2544     this.player.cueVideoByUrl(this._options.video);
       
  2545     
       
  2546     this.callbacks.onReady();
       
  2547 };
       
  2548 
       
  2549 IriSP.PopcornReplacement.dailymotion.prototype.progressHandler = function(progressInfo) {
       
  2550     
       
  2551     this.callbacks.onTime({
       
  2552         position: progressInfo.mediaTime
       
  2553     });
       
  2554 }
       
  2555 
       
  2556 IriSP.PopcornReplacement.dailymotion.prototype.stateHandler = function(state) {
       
  2557     
       
  2558     switch(state) {
       
  2559         case 1:
       
  2560             this.callbacks.onPlay();
       
  2561             break;
       
  2562 
       
  2563         case 2:
       
  2564             this.callbacks.onPause();
       
  2565             break;
       
  2566 
       
  2567         case 3:
       
  2568             this.callbacks.onSeek({
       
  2569                 position: this.player.getCurrentTime()
       
  2570             });
       
  2571             break;
       
  2572 
       
  2573         /*
       
  2574         case 5:
       
  2575             this.callbacks.onReady();
       
  2576             break;
       
  2577         */
       
  2578     }
       
  2579     
       
  2580 };/* To wrap a player the develop should create a new class derived from 
       
  2581    the IriSP.PopcornReplacement.player and defining the correct functions */
       
  2582 
       
  2583 /** jwplayer player wrapper */
       
  2584 IriSP.PopcornReplacement.jwplayer = function(container, options) {
       
  2585   /* appel du parent pour initialiser les structures communes à tous les players */
       
  2586   IriSP.PopcornReplacement.player.call(this, container, options);
       
  2587   
       
  2588   this.media.duration = options.duration; /* optional */
       
  2589   
       
  2590   /* Définition des fonctions de l'API -  */
       
  2591   this.playerFns = {
       
  2592     play: function() { return jwplayer(this.container).play(); },
       
  2593     pause: function() { return jwplayer(this.container).pause(); },
       
  2594     getPosition: function() { return jwplayer(this.container).getPosition(); },
       
  2595     seek: function(pos) { return jwplayer(this.container).seek(pos); },
       
  2596     getMute: function() { return jwplayer(this.container).getMute() },
       
  2597     setMute: function(p) { return jwplayer(this.container).setMute(p); },
       
  2598     getVolume: function() { return jwplayer(this.container).getVolume() / 100; },
       
  2599     setVolume: function(p) { return jwplayer(this.container).setVolume(Math.floor(100*p)); }
       
  2600   }
       
  2601 
       
  2602   options.events = this.callbacks;
       
  2603 
       
  2604   jwplayer(this.container).setup(options);
       
  2605 };
       
  2606 
       
  2607 IriSP.PopcornReplacement.jwplayer.prototype = new IriSP.PopcornReplacement.player("", {});
       
  2608 /* embed module - listens and relay hash changes to a parent window. */
       
  2609 
       
  2610 IriSP.EmbedModule = function(Popcorn, config, Serializer) {
       
  2611   IriSP.Module.call(this, Popcorn, config, Serializer);
       
  2612 
       
  2613   window.addEventListener('message', IriSP.wrap(this, this.handleMessages), false);
       
  2614   this._Popcorn.listen("IriSP.Mediafragment.hashchange", IriSP.wrap(this, this.relayChanges));
       
  2615 };
       
  2616 
       
  2617 IriSP.EmbedModule.prototype = new IriSP.Module();
       
  2618 
       
  2619 IriSP.EmbedModule.prototype.handleMessages = function(e) {
       
  2620   if (e.data.type === "hashchange") {
       
  2621     window.location.hash = e.data.value;    
       
  2622   }  
       
  2623 };
       
  2624 
       
  2625 IriSP.EmbedModule.prototype.relayChanges = function(newHash) {
       
  2626   window.parent.postMessage({type: "hashchange", value: newHash}, "*"); 
       
  2627   return;
       
  2628 };/* mediafragment module */
       
  2629 
       
  2630 IriSP.MediaFragment = function(Popcorn, config, Serializer) {
       
  2631   IriSP.Module.call(this, Popcorn, config, Serializer);
       
  2632 
       
  2633   this.mutex = false; /* a mutex because we access the url from two different functions */
       
  2634 
       
  2635   this._Popcorn.listen( "loadedmetadata", IriSP.wrap(this,this.advanceTime));
       
  2636   this._Popcorn.listen( "pause", IriSP.wrap(this,this.updateTime));
       
  2637   this._Popcorn.listen( "seeked", IriSP.wrap(this,this.updateTime));
       
  2638   this._Popcorn.listen( "IriSP.PolemicTweet.click", IriSP.wrap(this,this.updateAnnotation));
       
  2639   this._Popcorn.listen( "IriSP.SegmentsWidget.click", IriSP.wrap(this,this.updateAnnotation));
       
  2640   
       
  2641   window.onhashchange = IriSP.wrap(this, this.advanceTime);
       
  2642 };
       
  2643 
       
  2644 IriSP.MediaFragment.prototype = new IriSP.Module();
       
  2645 
       
  2646 IriSP.MediaFragment.prototype.advanceTime = function() {
       
  2647              var url = window.location.href;
       
  2648 
       
  2649               if ( url.split( "#" )[ 1 ] != null ) {
       
  2650                   pageoffset = url.split( "#" )[1];
       
  2651 
       
  2652                   if ( pageoffset.substring(0, 2) === "t=") {
       
  2653                     // timecode 
       
  2654                     if ( pageoffset.substring( 2 ) != null ) {
       
  2655                     var offsettime = pageoffset.substring( 2 );
       
  2656                     this._Popcorn.currentTime( parseFloat(offsettime) );
       
  2657                     
       
  2658                     /* we have to trigger this signal manually because of a
       
  2659                      bug in the jwplayer */
       
  2660                     this._Popcorn.trigger("seeked", parseFloat(offsettime));
       
  2661                     }
       
  2662                   } else if ( pageoffset.substring(0, 3) === "id=") {
       
  2663                     // annotation
       
  2664                     var annotationId = pageoffset.substring( 3 );
       
  2665                     // there's no better way than that because
       
  2666                     // of possible race conditions
       
  2667                     this._serializer.sync(IriSP.wrap(this, function() {
       
  2668                           this.lookupAnnotation.call(this, annotationId); 
       
  2669                           }));
       
  2670                   }
       
  2671               }
       
  2672 };
       
  2673 
       
  2674 /** handler for the seeked signal. It may have or may have not an argument.
       
  2675     @param time if not undefined, the time we're seeking to 
       
  2676 */
       
  2677 IriSP.MediaFragment.prototype.updateTime = function(time) {
       
  2678   if (this.mutex === true) {
       
  2679     return;
       
  2680   }
       
  2681 
       
  2682   var history = window.history;
       
  2683   if ( !history.pushState ) {
       
  2684     return false;
       
  2685   }
       
  2686     
       
  2687   if (IriSP.null_or_undefined(time) || typeof(time) != "number") {
       
  2688     var ntime = this._Popcorn.currentTime().toFixed(2)
       
  2689   } else {
       
  2690     var ntime = time.toFixed(2);
       
  2691   }
       
  2692 
       
  2693   // used to relay the new hash to the embedder
       
  2694   this._Popcorn.trigger("IriSP.Mediafragment.hashchange", "#t=" + ntime);
       
  2695   
       
  2696   splitArr = window.location.href.split( "#" )
       
  2697   history.replaceState( {}, "", splitArr[0] + "#t=" + ntime );
       
  2698 };
       
  2699 
       
  2700 
       
  2701 IriSP.MediaFragment.prototype.updateAnnotation = function(annotationId) {
       
  2702   var _this = this;
       
  2703   this.mutex = true;
       
  2704 
       
  2705   var history = window.history;
       
  2706   if ( !history.pushState ) {
       
  2707     return false;
       
  2708   }
       
  2709  
       
  2710   
       
  2711   // used to relay the new hash to the embedder
       
  2712   this._Popcorn.trigger("IriSP.Mediafragment.hashchange", "#id=" + annotationId);
       
  2713   
       
  2714   splitArr = window.location.href.split( "#" )
       
  2715   history.replaceState( {}, "", splitArr[0] + "#id=" + annotationId);
       
  2716 
       
  2717   
       
  2718   // reset the mutex afterwards to prevent the module from reacting to his own changes.
       
  2719   window.setTimeout(function() { _this.mutex = false }, 50);
       
  2720 };
       
  2721 
       
  2722 // lookup and seek to the beginning of an annotation
       
  2723 IriSP.MediaFragment.prototype.lookupAnnotation = function(annotationId) {
       
  2724   var _this = this;
       
  2725   this.mutex = true;
       
  2726 
       
  2727   var annotation = undefined;
       
  2728   var annotations = this._serializer._data.annotations;
       
  2729 
       
  2730   var i;
       
  2731   for (i = 0; i < annotations.length; i++) {
       
  2732       if (annotations[i].id === annotationId) {
       
  2733         annotation = annotations[i];
       
  2734         break;
       
  2735       }
       
  2736   }
       
  2737 
       
  2738   if (typeof(annotation) !== "undefined") {
       
  2739     this._Popcorn.currentTime(annotation.begin / 1000);
       
  2740 
       
  2741     /* we have to trigger this signal manually because of a
       
  2742      bug in the jwplayer */
       
  2743     this._Popcorn.trigger("seeked", annotation.begin / 1000);
       
  2744     this._Popcorn.trigger("IriSP.Mediafragment.showAnnotation", annotationId);
       
  2745   }
       
  2746   
       
  2747   window.setTimeout(function() { _this.mutex = false }, 50);
       
  2748 };
       
  2749 IriSP.AnnotationsListWidget = function(Popcorn, config, Serializer) {
       
  2750   IriSP.Widget.call(this, Popcorn, config, Serializer);
       
  2751   this.__counter = 0;
       
  2752   this.__oldList = [];
       
  2753   this.searchRe = null;
       
  2754   this._ajax_cache = [];
       
  2755   var _this = this;
       
  2756   
       
  2757   this._Popcorn.listen("IriSP.search", function(searchString) {
       
  2758       _this.searchHandler(searchString);
       
  2759   });
       
  2760   this._Popcorn.listen("IriSP.search.closed", function() {
       
  2761       _this.searchHandler(false);
       
  2762   });
       
  2763   this._Popcorn.listen("IriSP.search.cleared", function() {
       
  2764       _this.searchHandler(false);
       
  2765   });
       
  2766 };
       
  2767 
       
  2768 
       
  2769 IriSP.AnnotationsListWidget.prototype = new IriSP.Widget();
       
  2770 
       
  2771 IriSP.AnnotationsListWidget.prototype.clear = function() {
       
  2772 };
       
  2773 
       
  2774 IriSP.AnnotationsListWidget.prototype.clearWidget = function() {
       
  2775 };
       
  2776 
       
  2777 IriSP.AnnotationsListWidget.prototype.searchHandler = function(searchString) {
       
  2778   this.searchRe = (searchString && searchString.length) ? IriSP.regexpFromText(searchString) : null;
       
  2779   if (this.ajax_mode && !this.cinecast_version) {
       
  2780       var _this = this,
       
  2781         _annotations = (
       
  2782             this.searchRe === null
       
  2783             ? this._ajax_cache
       
  2784             : IriSP.underscore.filter(this._ajax_cache, function(_a) {
       
  2785                return (_this.searchRe.test(_a.desc) || _this.searchRe.test(_a.title)); 
       
  2786             })
       
  2787         );
       
  2788     this.do_redraw(_annotations);
       
  2789     if (_annotations.length) {
       
  2790         this._Popcorn.trigger("IriSP.search.matchFound");
       
  2791       } else {
       
  2792         this._Popcorn.trigger("IriSP.search.noMatchFound");
       
  2793       }    
       
  2794   } else {
       
  2795       this.drawList();
       
  2796   }
       
  2797 }
       
  2798 
       
  2799 /** effectively redraw the widget - called by drawList */
       
  2800 IriSP.AnnotationsListWidget.prototype.do_redraw = function(list) {
       
  2801     var _html = IriSP.templToHTML(
       
  2802         IriSP.annotationsListWidget_template, {
       
  2803             annotations: list
       
  2804         }),
       
  2805         _this = this;
       
  2806       
       
  2807     this.selector.html(_html);
       
  2808     
       
  2809     this.selector.find('.Ldt-AnnotationsList-Tag-Li').click(function() {
       
  2810         _this._Popcorn.trigger("IriSP.search.triggeredSearch", IriSP.jQuery(this).text().trim());
       
  2811     })
       
  2812     
       
  2813     if (this.searchRe !== null) {
       
  2814         this.selector.find(".Ldt-AnnotationsList-Title a, .Ldt-AnnotationsList-Description")
       
  2815             .each(function()  {
       
  2816                 var _$ = IriSP.jQuery(this);
       
  2817                 _$.html(_$.text().trim().replace(_this.searchRe, '<span class="Ldt-AnnotationsList-highlight">$1</span>'))
       
  2818             })
       
  2819     }
       
  2820 };
       
  2821 
       
  2822 IriSP.AnnotationsListWidget.prototype.transformAnnotation = function(a) {
       
  2823     var _this = this;
       
  2824     return {
       
  2825         "id" : a.id,
       
  2826         "title": this.cinecast_version ? IriSP.get_aliased(a.meta, ['creator_name', 'creator']) : a.content.title,
       
  2827         "desc" : this.cinecast_version ? a.content.data : a.content.description,
       
  2828         "begin": IriSP.msToTime(a.begin),
       
  2829         "end" : IriSP.msToTime(a.end),
       
  2830         "thumbnail" : (typeof a.meta == "object" && typeof a.meta.thumbnail == "string") ? a.meta.thumbnail : this.default_thumbnail,
       
  2831         "url" : (typeof a.meta == "object" && typeof a.meta.url == "string") ? a.meta.url : null,
       
  2832         "created_at" :(typeof a.meta == "object" && typeof a.meta.created == "string") ? Date.parse(a.meta.created.replace(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}\:\d{2}\:\d{2}).*$/,"$2/$3/$1 $4 UTC+0000")) : null,
       
  2833         "tags": typeof a.tags == "object"
       
  2834             ? IriSP.underscore(a.tags)
       
  2835                 .chain()
       
  2836                 .map(function(_t) {
       
  2837                     if (typeof _t == "string") {
       
  2838                         return _t.replace(/^.*:/,'#');
       
  2839                     } else {
       
  2840                         if (typeof _t['id-ref'] != "undefined") {
       
  2841                             var _f = IriSP.underscore.find(_this._serializer._data.tags, function(_tag) {
       
  2842                                 return _tag.id == _t['id-ref'];
       
  2843                             });
       
  2844                             if (typeof _f != "undefined") {
       
  2845                                 return IriSP.get_aliased(_f.meta, ['dc:title', 'title']);
       
  2846                             }
       
  2847                         }
       
  2848                     }
       
  2849                     return null;
       
  2850                 })
       
  2851                 .filter(function(_t) {
       
  2852                     return _t !== null && _t !== ""
       
  2853                 })
       
  2854                 .value()
       
  2855             : []
       
  2856     }    
       
  2857 }
       
  2858 
       
  2859 /** draw the annotation list */
       
  2860 IriSP.AnnotationsListWidget.prototype.drawList = function(force_redraw) {
       
  2861   var _this = this;
       
  2862   
       
  2863 //  var view_type = this._serializer.getContributions();
       
  2864   var annotations = this._serializer._data.annotations;
       
  2865   var currentTime = this._Popcorn.currentTime();
       
  2866   var list = [];
       
  2867 
       
  2868 /*  if (typeof(view_type) === "undefined") {    
       
  2869     return;
       
  2870 } */
       
  2871   for (i = 0; i < annotations.length; i++) {
       
  2872     var obj = this.transformAnnotation(annotations[i]);
       
  2873     obj.iterator = i;
       
  2874     obj.distance = Math.abs((annotations[i].end + annotations[i].begin) / 2000 - currentTime);
       
  2875     if (!this.cinecast_version || annotations[i].type == "cinecast:UserAnnotation") {
       
  2876         list.push(obj);
       
  2877     }
       
  2878     
       
  2879   }
       
  2880   
       
  2881     if (this.searchRe !== null) {
       
  2882         list = list.filter(function(_a) {
       
  2883             return (_this.searchRe.test(_a.desc) || _this.searchRe.test(_a.title)); 
       
  2884         });
       
  2885         if (list.length) {
       
  2886             this._Popcorn.trigger("IriSP.search.matchFound");
       
  2887           } else {
       
  2888             this._Popcorn.trigger("IriSP.search.noMatchFound");
       
  2889           }
       
  2890     }
       
  2891   list = IriSP.underscore(list)
       
  2892     .chain()
       
  2893     .sortBy(function(_o) {
       
  2894         return _o.distance;
       
  2895     })
       
  2896     .first(10)
       
  2897     .sortBy(function(_o) {
       
  2898         return (_this.cinecast_version ? - _o.created_at : _o.iterator);
       
  2899     })
       
  2900     .value();
       
  2901   var idList = IriSP.underscore.pluck(list, "id").sort();
       
  2902 
       
  2903   if (!IriSP.underscore.isEqual(this.__oldList, idList) || this.lastSearch !== this.searchRe || typeof(force_redraw) !== "undefined") {
       
  2904     this.do_redraw(list);
       
  2905     this.__oldList = idList;
       
  2906     this.lastSearch = this.searchRe;
       
  2907   }
       
  2908    /* save for next call */
       
  2909   
       
  2910   
       
  2911 };
       
  2912 
       
  2913 IriSP.AnnotationsListWidget.prototype.ajaxRedraw = function(timecode) {
       
  2914 
       
  2915   /* the seeked signal sometimes passes an argument - depending on if we're using
       
  2916      our popcorn lookalike or the real thing - if it's the case, use it as it's
       
  2917      more precise than currentTime which sometimes contains the place we where at */
       
  2918   if (IriSP.null_or_undefined(timecode) || typeof(timecode) != "number") {
       
  2919      var tcode = this._Popcorn.currentTime();     
       
  2920    } else {
       
  2921      var tcode = timecode;     
       
  2922   }
       
  2923    
       
  2924   
       
  2925   /* the platform gives us a special url - of the type : http://path/{{media}}/{{begin}}/{{end}}
       
  2926      we double the braces using regexps and we feed it to mustache to build the correct url
       
  2927      we have to do that because the platform only knows at run time what view it's displaying.
       
  2928   */
       
  2929      
       
  2930   var media_id = this.currentMedia()["id"];
       
  2931   var duration = this.getDuration();
       
  2932   
       
  2933   var begin_timecode = (Math.floor(tcode) - 300) * 1000;
       
  2934   if (begin_timecode < 0)
       
  2935     begin_timecode = 0;
       
  2936     
       
  2937   var end_timecode = (Math.floor(tcode) + 300) * 1000;
       
  2938   if (end_timecode > duration)
       
  2939     end_timecode = duration;
       
  2940   
       
  2941   var templ = Mustache.to_html(this.ajax_url, {media: media_id, begin: begin_timecode,
       
  2942                                  end: end_timecode});
       
  2943 
       
  2944   /* we create on the fly a serializer to get the ajax */
       
  2945   var serializer = new IriSP.JSONSerializer(IriSP.__dataloader, templ);
       
  2946   serializer.sync(IriSP.wrap(this, function(json) { this.processJson(json, serializer) }));
       
  2947 };
       
  2948 
       
  2949 /** process the received json - it's a bit hackish */
       
  2950 IriSP.AnnotationsListWidget.prototype.processJson = function(json, serializer) {
       
  2951   /* FIXME: DRY the whole thing */
       
  2952   var annotations = serializer._data.annotations;
       
  2953   if (IriSP.null_or_undefined(annotations))
       
  2954     return;
       
  2955   
       
  2956   /*
       
  2957   commented in case we wanted to discriminate against some annotation types.
       
  2958   var view_types = serializer.getIds("Contributions");
       
  2959   */
       
  2960   var l = [];
       
  2961   
       
  2962   var media = this.currentMedia()["id"];
       
  2963   
       
  2964   for (i = 0; i < annotations.length; i++) {
       
  2965     var obj = this.transformAnnotation(annotations[i])
       
  2966       if (typeof obj.url == "undefined" || !obj.url) {
       
  2967           /* only if the annotation isn't present in the document create an
       
  2968              external link */
       
  2969           if (this.annotations_ids.indexOf(obj.id.toLowerCase()) == -1) {
       
  2970             // braindead url; jacques didn't want to create a new one in the platform,
       
  2971             // so we append the cutting id to the url.
       
  2972             obj.url = this.project_url + "/" + media + "/" + 
       
  2973                          annotations[i].meta.project + "/" +
       
  2974                          annotations[i].meta["id-ref"] + '#id=' + annotations[i].id;
       
  2975                          
       
  2976             // obj.url = document.location.href.split("#")[0] + "/" + annotation.meta.project;
       
  2977           }
       
  2978           }
       
  2979       l.push(obj);
       
  2980   }
       
  2981   this._ajax_cache = l;
       
  2982   this.do_redraw(l);
       
  2983 };
       
  2984 IriSP.AnnotationsListWidget.prototype.draw = function() {
       
  2985   
       
  2986   /* build a table of the annotations present in the document for faster 
       
  2987      lookup
       
  2988   */
       
  2989   this.annotations_ids = IriSP.underscore(this._serializer._data.annotations).map(function(_a) {
       
  2990     return _a.id.toLowerCase();
       
  2991   });
       
  2992   
       
  2993   var _this = this;
       
  2994     
       
  2995     if (!this.ajax_mode || this.cinecast_version) {
       
  2996         var _throttled = IriSP.underscore.throttle(function() {
       
  2997             _this.drawList();
       
  2998         }, 1500);
       
  2999         _throttled();
       
  3000         this._Popcorn.listen("IriSP.createAnnotationWidget.addedAnnotation", _throttled);
       
  3001         this._Popcorn.listen("timeupdate", _throttled);
       
  3002         if (this.cinecast_version) {
       
  3003             window.setInterval(function() {
       
  3004                 var _tmpSerializer = new IriSP.JSONSerializer(IriSP.__dataloader,  _this._config.metadata.src, true);
       
  3005                 _tmpSerializer.sync(function(json) {
       
  3006                     _this.annotations_ids = IriSP.underscore(_this._serializer._data.annotations).map(function(_a) {
       
  3007                       return _a.id.toLowerCase();
       
  3008                     });
       
  3009                     IriSP.underscore(json.annotations).each(function(_a) {
       
  3010                         var _j = _this.annotations_ids.indexOf(_a.id);
       
  3011                         if (_j == -1) {
       
  3012                             _this._serializer._data.annotations.push(_a);
       
  3013                             _this.annotations_ids.push(_a.id);
       
  3014                         } else {
       
  3015                             _this._serializer._data.annotations[_j] = _a;
       
  3016                         }
       
  3017                         _throttled();
       
  3018                     });
       
  3019                 }, true); // true is for force_refresh
       
  3020             },this.refresh_interval);
       
  3021         }
       
  3022   } else {
       
  3023     /* update the widget when the video has finished loading and when it's seeked and paused */
       
  3024     this._Popcorn.listen("seeked", IriSP.wrap(this, this.ajaxRedraw));
       
  3025     this._Popcorn.listen("loadedmetadata", IriSP.wrap(this, this.ajaxRedraw));
       
  3026     this._Popcorn.listen("paused", IriSP.wrap(this, this.ajaxRedraw));
       
  3027     
       
  3028     this._Popcorn.listen("IriSP.createAnnotationWidget.addedAnnotation", IriSP.wrap(this, this.ajaxRedraw));
       
  3029   }
       
  3030 
       
  3031 };/* Internationalization for this widget */
       
  3032 
       
  3033 IriSP.i18n.addMessages(
       
  3034     {
       
  3035         "fr": {
       
  3036             "keywords": "Mots-clés"
       
  3037         },
       
  3038         "en": {
       
  3039             "keywords": "Keywords"
       
  3040         }
       
  3041     }
       
  3042 );
       
  3043 
       
  3044 IriSP.AnnotationsWidget = function(Popcorn, config, Serializer) {
       
  3045   IriSP.Widget.call(this, Popcorn, config, Serializer);
       
  3046   /* flag used when we're creating an annotation */
       
  3047   this._hidden = false;
       
  3048 };
       
  3049 
       
  3050 
       
  3051 IriSP.AnnotationsWidget.prototype = new IriSP.Widget();
       
  3052 
       
  3053 IriSP.AnnotationsWidget.prototype.clear = function() {
       
  3054     this.selector.find(".Ldt-SaTitle").text("");
       
  3055     this.selector.find(".Ldt-SaDescription").text("");
       
  3056     this.selector.find(".Ldt-SaKeywordText").text("");
       
  3057 };
       
  3058 
       
  3059 IriSP.AnnotationsWidget.prototype.displayAnnotation = function(annotation) {       
       
  3060     var title = annotation.content.title;
       
  3061     var description = annotation.content.description;
       
  3062     var keywords =  "";
       
  3063     var begin = +annotation.begin / 1000;
       
  3064     var end = +annotation.end / 1000;
       
  3065     var duration = this.getDuration();
       
  3066     var tags = "";
       
  3067     
       
  3068     var title_templ = "{{title}} - ( {{begin}} - {{end}} )";
       
  3069     var endstr = Mustache.to_html(title_templ, {title: title, begin: IriSP.secondsToTime(begin), end: IriSP.secondsToTime(end)});
       
  3070 
       
  3071     this.selector.find(".Ldt-SaTitle").text(endstr);
       
  3072     this.selector.find(".Ldt-SaDescription").text(description);
       
  3073     
       
  3074     
       
  3075     if (!IriSP.null_or_undefined(annotation.tags) && !IriSP.null_or_undefined(this._serializer._data.tags)) {
       
  3076       /* save the tag id and keywords in a unique structure */
       
  3077       var tag_list = {};
       
  3078       for (var i = 0; i < this._serializer._data.tags.length; i++) {
       
  3079         var id = this._serializer._data.tags[i]["id"];
       
  3080         var keyword = IriSP.get_aliased(this._serializer._data.tags[i]["meta"], ["dc:title", "title"]);
       
  3081 
       
  3082         tag_list[id] = keyword;
       
  3083       }
       
  3084 
       
  3085       /* then browse the list of defined tags for the current annotation */
       
  3086       for (var i = 0; i < annotation.tags.length; i++) {
       
  3087         if (tag_list.hasOwnProperty(annotation.tags[i]["id-ref"]))
       
  3088           tags += tag_list[annotation.tags[i]["id-ref"]] + ", ";
       
  3089       }
       
  3090     }
       
  3091     
       
  3092     tags = IriSP.i18n.getMessage("keywords") + ": " + tags.slice(0, tags.length - 2);
       
  3093     
       
  3094     this.selector.find(".Ldt-SaKeywords").text(tags);
       
  3095     
       
  3096     // update sharing buttons
       
  3097     var url = document.location.href + "#id=" + annotation.id;
       
  3098     this.selector.find(".Ldt-fbShare").attr("href", IriSP.mkFbUrl(url, this.share_text));
       
  3099     this.selector.find(".Ldt-TwShare").attr("href", IriSP.mkTweetUrl(url, this.share_text));
       
  3100     this.selector.find(".Ldt-GplusShare").attr("href", IriSP.mkGplusUrl(url, this.share_text));
       
  3101 };
       
  3102 
       
  3103 IriSP.AnnotationsWidget.prototype.clearWidget = function() {   
       
  3104     /* retract the pane between two annotations */
       
  3105     this.selector.find(".Ldt-SaTitle").text("");
       
  3106     this.selector.find(".Ldt-SaDescription").text("");
       
  3107     this.selector.find(".Ldt-SaKeywordText").html("");
       
  3108     this.selector.find(".Ldt-ShowAnnotation").slideUp();
       
  3109 };
       
  3110 
       
  3111 IriSP.AnnotationsWidget.prototype.draw = function() {
       
  3112   var _this = this;
       
  3113 
       
  3114   var annotationMarkup = IriSP.templToHTML(IriSP.annotationWidget_template);
       
  3115 	this.selector.append(annotationMarkup);
       
  3116 
       
  3117   this._Popcorn.listen("IriSP.AnnotationsWidget.show", 
       
  3118                         IriSP.wrap(this, this.show));
       
  3119   this._Popcorn.listen("IriSP.AnnotationsWidget.hide", 
       
  3120                         IriSP.wrap(this, this.hide));
       
  3121  
       
  3122   var legal_ids = [];
       
  3123   if (typeof(this._serializer.getChapitrage()) !== "undefined")
       
  3124     legal_ids.push(this._serializer.getChapitrage());
       
  3125   else 
       
  3126     legal_ids = this._serializer.getNonTweetIds();
       
  3127   
       
  3128   var annotations = this._serializer._data.annotations;
       
  3129   var i;
       
  3130   
       
  3131 	for (i in annotations) {    
       
  3132     var annotation = annotations[i];
       
  3133     var begin = Math.round((+ annotation.begin) / 1000);
       
  3134     var end = Math.round((+ annotation.end) / 1000);
       
  3135 
       
  3136     if (typeof(annotation.meta) !== "undefined" && typeof(annotation.meta["id-ref"]) !== "undefined"
       
  3137           && !IriSP.underscore.include(legal_ids, annotation.meta["id-ref"])) {
       
  3138         continue;
       
  3139     }
       
  3140 
       
  3141 
       
  3142     var conf = {start: begin, end: end, 
       
  3143                 onStart: 
       
  3144                        function(annotation) { 
       
  3145                         return function() { 
       
  3146                             _this.displayAnnotation(annotation); 
       
  3147                           
       
  3148                         } }(annotation),
       
  3149                 onEnd: 
       
  3150                        function() { _this.clearWidget.call(_this); }
       
  3151                 };
       
  3152     this._Popcorn = this._Popcorn.code(conf);                                             
       
  3153   }
       
  3154 
       
  3155 };
       
  3156 
       
  3157 IriSP.AnnotationsWidget.prototype.hide = function() {
       
  3158   if (this._hidden == false) {
       
  3159     this.selector.hide();
       
  3160     this._hidden = true;
       
  3161   }
       
  3162 };
       
  3163 
       
  3164 IriSP.AnnotationsWidget.prototype.show = function() {
       
  3165   if (this._hidden == true) {
       
  3166     this.selector.show();
       
  3167     this._hidden = false;
       
  3168   }
       
  3169 };IriSP.ArrowWidget = function(Popcorn, config, Serializer) {
       
  3170   IriSP.Widget.call(this, Popcorn, config, Serializer);
       
  3171 
       
  3172   this._oldAnnotation = null;
       
  3173   this._blockArrow = false;
       
  3174 };
       
  3175 
       
  3176 
       
  3177 IriSP.ArrowWidget.prototype = new IriSP.Widget();
       
  3178 
       
  3179 IriSP.ArrowWidget.prototype.clear = function() {
       
  3180 
       
  3181 };
       
  3182 
       
  3183 IriSP.ArrowWidget.prototype.clearWidget = function() {
       
  3184 };
       
  3185 
       
  3186 IriSP.ArrowWidget.prototype.draw = function() {
       
  3187   var templ = Mustache.to_html(IriSP.arrowWidget_template, {});
       
  3188   this.selector.append(templ);
       
  3189   this._Popcorn.listen("timeupdate", IriSP.wrap(this, this.timeUpdateHandler));
       
  3190   this._Popcorn.listen("IriSP.ArrowWidget.blockArrow", IriSP.wrap(this, this.blockArrow));
       
  3191   this._Popcorn.listen("IriSP.ArrowWidget.releaseArrow", IriSP.wrap(this, this.releaseArrow));
       
  3192   
       
  3193 };
       
  3194 
       
  3195 IriSP.ArrowWidget.prototype.timeUpdateHandler = function(percents) {
       
  3196   if (this._blockArrow)
       
  3197     return;
       
  3198   
       
  3199   var currentTime = this._Popcorn.currentTime();
       
  3200   var currentAnnotation = this._serializer.currentChapitre(currentTime);
       
  3201   if (IriSP.null_or_undefined(currentAnnotation)) {
       
  3202     var c_annots = this._serializer.currentAnnotation(currentTime)
       
  3203     if (c_annots.length != 0)
       
  3204       var currentAnnotation = c_annots[0]; // FIXME : use the others ?
       
  3205     else
       
  3206       return;
       
  3207   }
       
  3208   
       
  3209   /* move the arrow only if the current annotation changes */
       
  3210   if (currentAnnotation != this._oldAnnotation) {
       
  3211     var begin = (+ currentAnnotation.begin) / 1000;
       
  3212     var end = (+ currentAnnotation.end) / 1000;
       
  3213 
       
  3214     var duration = this.getDuration() / 1000;
       
  3215     var middle_time = (begin + end) / 2;
       
  3216     var percents = middle_time / duration;
       
  3217 
       
  3218     // we need to apply a fix because the arrow has a certain length
       
  3219     // it's half the length of the arrow (27 / 2). We need to convert
       
  3220     // it in percents though.
       
  3221     var totalWidth = this.selector.width();    
       
  3222     var pixels = percents * totalWidth;
       
  3223     var correction = (27 / 2);
       
  3224     var corrected_pixels = pixels - correction;
       
  3225     
       
  3226     /* make sure that the arrow is aligned with the pattern
       
  3227        of the widget under it */
       
  3228     if (corrected_pixels % 3 != 0)
       
  3229       corrected_pixels -= (corrected_pixels % 3 - 1);
       
  3230     
       
  3231     /* don't move out of the screen */
       
  3232     if (corrected_pixels <= 0)
       
  3233       corrected_pixels = 0;
       
  3234     
       
  3235     if (corrected_pixels <= 15) {      
       
  3236       this.selector.children(".Ldt-arrowWidget").removeClass("Ldt-arrowLeftEdge Ldt-arrowCenter Ldt-arrowRightEdge")
       
  3237                                                 .addClass("Ldt-arrowLeftEdge"); 
       
  3238     } else if (corrected_pixels >= totalWidth - 25) {
       
  3239            this.selector.children(".Ldt-arrowWidget").removeClass("Ldt-arrowLeftEdge Ldt-arrowCenter Ldt-arrowRightEdge")
       
  3240                                                 .addClass("Ldt-arrowRightEdge"); 
       
  3241     } else {
       
  3242       this.selector.children(".Ldt-arrowWidget").removeClass("Ldt-arrowLeftEdge Ldt-arrowCenter Ldt-arrowRightEdge")
       
  3243                                                 .addClass("Ldt-arrowCenter"); 
       
  3244     }
       
  3245     
       
  3246     this.selector.children(".Ldt-arrowWidget").animate({"left" : corrected_pixels + "px"});
       
  3247 
       
  3248     this._oldAnnotation = currentAnnotation;
       
  3249   }
       
  3250 };
       
  3251 
       
  3252 /** Block the arrow for instance when the user is annotating */
       
  3253 IriSP.ArrowWidget.prototype.blockArrow = function() {
       
  3254   this._blockArrow = true;
       
  3255 };
       
  3256 
       
  3257 IriSP.ArrowWidget.prototype.releaseArrow = function() {
       
  3258   this._blockArrow = false;   
       
  3259 };
       
  3260 /* Internationalization for this widget */
       
  3261 
       
  3262 IriSP.i18n.addMessages(
       
  3263     {
       
  3264         "en": {
       
  3265             "submit": "Submit",
       
  3266             "add_keywords": "Add keywords",
       
  3267             "add_polemic_keywords": "Add polemic keywords",
       
  3268             "your_name": "Your name",
       
  3269             "type_here": "Type your annotation here.",
       
  3270             "wait_while_processed": "Please wait while your request is being processed...",
       
  3271             "error_while_contacting": "An error happened while contacting the server. Your annotation has not been saved.",
       
  3272             "empty_annotation": "Your annotation is empty. Please write something before submitting.",
       
  3273             "annotation_saved": "Thank you, your annotation has been saved.",
       
  3274             "share_annotation": "Would you like to share it on social networks ?",
       
  3275             "share_on": "Share on",
       
  3276             "more_tags": "More tags"
       
  3277         },
       
  3278         "fr": {
       
  3279             "submit": "Envoyer",
       
  3280             "add_keywords": "Ajouter des mots-clés",
       
  3281             "add_polemic_keywords": "Ajouter des mots-clés polémiques",
       
  3282             "your_name": "Votre nom",
       
  3283             "type_here": "Rédigez votre annotation ici.",
       
  3284             "wait_while_processed": "Veuillez patienter pendant le traitement de votre requête...",
       
  3285             "error_while_contacting": "Une erreur s'est produite en contactant le serveur. Votre annotation n'a pas été enregistrée",
       
  3286             "empty_annotation": "Votre annotation est vide. Merci de rédiger un texte avant de l'envoyer.",
       
  3287             "annotation_saved": "Merci, votre annotation a été enregistrée.",
       
  3288             "share_annotation": "Souhaitez-vous la partager sur les réseaux sociaux ?",
       
  3289             "share_on": "Partager sur",
       
  3290             "more_tags": "Plus de mots-clés"
       
  3291         }
       
  3292     }
       
  3293 );
       
  3294 
       
  3295 IriSP.createAnnotationWidget = function(Popcorn, config, Serializer) {
       
  3296   IriSP.Widget.call(this, Popcorn, config, Serializer);
       
  3297   this._hidden = true;
       
  3298                          
       
  3299   if (!IriSP.null_or_undefined(IriSP.user)) {
       
  3300       if (!IriSP.null_or_undefined(IriSP.user.avatar)) {
       
  3301         this.user_avatar = IriSP.user.avatar;
       
  3302       }
       
  3303       if (!IriSP.null_or_undefined(IriSP.user.name)) {
       
  3304         this.user_name = IriSP.user.name;
       
  3305       }
       
  3306   }
       
  3307   
       
  3308   /* variables to save the current position of the slicer */
       
  3309   if (this.cinecast_version) {
       
  3310     this.sliceLeft = 0;
       
  3311     this.sliceWidth = 0;
       
  3312   }
       
  3313 };
       
  3314 
       
  3315 
       
  3316 IriSP.createAnnotationWidget.prototype = new IriSP.Widget();
       
  3317 
       
  3318 IriSP.createAnnotationWidget.prototype.clear = function() {
       
  3319     this.selector.find(".Ldt-SaTitle").text("");
       
  3320     this.selector.find(".Ldt-SaDescription").text("");
       
  3321     this.selector.find(".Ldt-SaKeywordText").text("");
       
  3322 };
       
  3323 
       
  3324 IriSP.createAnnotationWidget.prototype.draw = function() {
       
  3325     var _this = this;
       
  3326     if (typeof this.remote_tags == "object") {
       
  3327         IriSP.jQuery.getJSON((typeof this.remote_tags.alias == "string" ? this.remote_tags.alias : this.remote_tags.url), function(_json) {
       
  3328             _this.tags = _json.tags;
       
  3329             _this.drawCallback();
       
  3330         });
       
  3331     } else {
       
  3332         this.drawCallback();
       
  3333     }
       
  3334 }
       
  3335 
       
  3336 IriSP.createAnnotationWidget.prototype.drawCallback = function() {
       
  3337   var _this = this;
       
  3338   
       
  3339   var annotationMarkup = IriSP.templToHTML(IriSP.createAnnotationWidget_template, 
       
  3340                                            this);
       
  3341   
       
  3342 	this.selector.append(annotationMarkup);
       
  3343   
       
  3344   if (!this.cinecast_version)
       
  3345     this.selector.hide();
       
  3346   else {
       
  3347     this.showStartScreen();
       
  3348   }
       
  3349   
       
  3350   if (this.random_tags) {
       
  3351       this.selector.find(".Ldt-createAnnotation-keywords li").hide();
       
  3352       this.showMoreTags();
       
  3353       this.selector.find('.Ldt-createAnnotation-moar-keywordz').click(function() {
       
  3354           _this.showMoreTags();
       
  3355       })
       
  3356   }
       
  3357   // Add onclick event to both polemic and keywords buttons
       
  3358   
       
  3359   this.selector.find(".Ldt-createAnnotation-keyword-button, .Ldt-createAnnotation-polemic-button").click(function() {
       
  3360       _this.addKeyword(IriSP.jQuery(this).text());
       
  3361       return false;
       
  3362   });
       
  3363   
       
  3364   // js_mod is a custom event because there's no simple way to test for a js
       
  3365   // change in a textfield.                    
       
  3366   this.selector.find(".Ldt-createAnnotation-Description")
       
  3367                .bind("propertychange keyup input paste click js_mod", IriSP.wrap(this, this.handleTextChanges));
       
  3368                
       
  3369   /* the cinecast version of the player is supposed to pause when the user clicks on the button */
       
  3370 
       
  3371   /* the cinecast version expects the user to comment on a defined segment.
       
  3372      As the widget is always shown, we need a way to update it's content as
       
  3373      time passes. We do this like we did with the annotationsWidget : we schedule
       
  3374      a .code start function which will be called at the right time.
       
  3375   */
       
  3376   if (this.cinecast_version) {
       
  3377     var legal_ids;
       
  3378     if (typeof(this._serializer.getChapitrage()) !== "undefined")
       
  3379       legal_id = this._serializer.getChapitrage();
       
  3380     else 
       
  3381       legal_id = this._serializer.getNonTweetIds()[0];
       
  3382     
       
  3383     var annotations = this._serializer._data.annotations;
       
  3384     var i;
       
  3385   
       
  3386     for (i in annotations) {     
       
  3387       var annotation = annotations[i];
       
  3388       if (typeof(annotation.meta) !== "undefined" && typeof(annotation.meta["id-ref"]) !== "undefined"
       
  3389             && legal_id !== annotation.meta["id-ref"]) {
       
  3390           continue;
       
  3391       }
       
  3392       
       
  3393       code = {start: annotation.begin / 1000, end: annotation.end / 1000,
       
  3394               onStart: function(annotation) { return function() {
       
  3395                       if (typeof(annotation.content) !== "undefined")
       
  3396                         _this.selector.find(".Ldt-createAnnotation-Title").html(annotation.content.title);
       
  3397 
       
  3398                       _this._currentAnnotation = annotation;
       
  3399                       var beginTime = IriSP.msToTime(annotation.begin);
       
  3400                       var endTime = IriSP.msToTime(annotation.end);
       
  3401                       var timeTemplate = IriSP.templToHTML("- ({{begin}} - {{ end }})", {begin: beginTime, end: endTime });
       
  3402                       _this.selector.find(".Ldt-createAnnotation-TimeFrame").html(timeTemplate);
       
  3403               } }(annotation)
       
  3404             };
       
  3405       
       
  3406       this._Popcorn.code(code);
       
  3407     }
       
  3408   }
       
  3409   
       
  3410   this.selector.find(".Ldt-createAnnotation-submitButton").click(IriSP.wrap(this, this.handleButtonClick));
       
  3411   
       
  3412   if (!this.cinecast_version) {
       
  3413     this._Popcorn.listen("IriSP.PlayerWidget.AnnotateButton.clicked", 
       
  3414                           IriSP.wrap(this, this.handleAnnotateSignal));
       
  3415     
       
  3416     // handle clicks on the cancel button too.
       
  3417     this.selector.find(".Ldt-createAnnotation-Minimize").click(IriSP.wrap(this, 
       
  3418       function() {
       
  3419         // we've got to simulate the pressing of the button because there's no
       
  3420         // other way to minimize the widget and show the widgets that were hidden
       
  3421         // same time
       
  3422         this._Popcorn.trigger("IriSP.PlayerWidget.AnnotateButton.clicked");
       
  3423       }
       
  3424     ));
       
  3425   }
       
  3426 };
       
  3427 
       
  3428 IriSP.createAnnotationWidget.prototype.showMoreTags = function() {
       
  3429     for (var j=0; j < this.random_tags; j++) {
       
  3430         var _jq = this.selector.find(".Ldt-createAnnotation-keywords li:hidden");
       
  3431         if (_jq.length > 1) {
       
  3432             IriSP.jQuery(_jq[Math.floor(_jq.length*Math.random())]).show();
       
  3433         } else {
       
  3434             _jq.show();
       
  3435             break;
       
  3436         }     
       
  3437     }
       
  3438     if (this.selector.find(".Ldt-createAnnotation-keywords li:hidden").length == 0) {
       
  3439         this.selector.find('.Ldt-createAnnotation-moar-keywordz').hide();
       
  3440     }
       
  3441 }
       
  3442 
       
  3443 /* Handles adding keywords and polemics */
       
  3444 IriSP.createAnnotationWidget.prototype.addKeyword = function(_keyword) {
       
  3445     var _field = this.selector.find(".Ldt-createAnnotation-Description"),
       
  3446         _rx = IriSP.regexpFromText(_keyword),
       
  3447         _contents = _field.val();
       
  3448     _contents = ( _rx.test(_contents)
       
  3449         ? _contents.replace(_rx,"").replace("  "," ").trim()
       
  3450         : _contents.trim() + " " + _keyword
       
  3451     );
       
  3452     _field.val(_contents.trim()).trigger("js_mod");
       
  3453 }
       
  3454 
       
  3455 /** handles clicks on the annotate button. Works only for the non-cinecast version */
       
  3456 IriSP.createAnnotationWidget.prototype.handleAnnotateSignal = function() {
       
  3457   
       
  3458   if (this._hidden == false && this._state == 'startScreen') {
       
  3459     this.selector.hide();
       
  3460     this._hidden = true;
       
  3461     
       
  3462     // free the arrow.
       
  3463     this._Popcorn.trigger("IriSP.ArrowWidget.releaseArrow");
       
  3464     this._Popcorn.trigger("IriSP.SliceWidget.hide");
       
  3465     this._Popcorn.trigger("IriSP.AnnotationsWidget.show");
       
  3466     
       
  3467   } else {
       
  3468     this._Popcorn.trigger("IriSP.AnnotationsWidget.hide");
       
  3469     this.showStartScreen();    
       
  3470     this.selector.show();
       
  3471     this._hidden = false;
       
  3472     var currentTime = this._Popcorn.currentTime();
       
  3473     
       
  3474     // block the arrow.
       
  3475     this._Popcorn.trigger("IriSP.ArrowWidget.blockArrow");
       
  3476     
       
  3477     var duration = this.getDuration();
       
  3478         
       
  3479     var currentChapter = this._serializer.currentChapitre(currentTime);
       
  3480 
       
  3481     if (IriSP.null_or_undefined(currentChapter)) {      
       
  3482       var left = this.selector.width() / 2;
       
  3483       var width = this.selector.width() / 10;
       
  3484     } else {
       
  3485       var left = (currentChapter.begin / duration) * this.selector.width();
       
  3486       var width = (currentChapter.end / duration) * this.selector.width() - left;
       
  3487     }
       
  3488     
       
  3489     // slider position and length is kept in percents.
       
  3490     this.sliceLeft = (left / this.selector.width()) * 100;
       
  3491     this.sliceWidth = (width / this.selector.width()) * 100;
       
  3492     
       
  3493     this._Popcorn.trigger("IriSP.SliceWidget.position", [left, width]);
       
  3494     this._Popcorn.listen("IriSP.SliceWidget.zoneChange", IriSP.wrap(this, this.handleSliderChanges));
       
  3495     this._Popcorn.trigger("IriSP.SliceWidget.show");
       
  3496     
       
  3497     if (!IriSP.null_or_undefined(currentChapter)) {
       
  3498       this.selector.find(".Ldt-createAnnotation-Title").html(currentChapter.content.title);
       
  3499 
       
  3500       this._currentcurrentChapter = currentChapter;
       
  3501       var beginTime = IriSP.msToTime(currentChapter.begin);
       
  3502       var endTime = IriSP.msToTime(currentChapter.end);
       
  3503       var timeTemplate = IriSP.templToHTML("- ({{begin}} - {{ end }})", {begin: beginTime, end: endTime });
       
  3504       this.selector.find(".Ldt-createAnnotation-TimeFrame").html(timeTemplate);
       
  3505     }
       
  3506   }
       
  3507 };
       
  3508 
       
  3509 
       
  3510 /** watch for changes in the textfield and change the buttons accordingly */
       
  3511 IriSP.createAnnotationWidget.prototype.handleTextChanges = function(event) {
       
  3512   var contents = this.selector.find(".Ldt-createAnnotation-Description").val();
       
  3513   if (this.cinecast_version) {
       
  3514       this._Popcorn.pause();
       
  3515   }
       
  3516   this.selector.find(".Ldt-createAnnotation-btnblock button").each(function() {
       
  3517       var _rx = IriSP.regexpFromText(IriSP.jQuery(this).text());
       
  3518       if (_rx.test(contents)) {
       
  3519           IriSP.jQuery(this).parent().addClass("Ldt-createAnnotation-active-button");
       
  3520       } else {
       
  3521           IriSP.jQuery(this).parent().removeClass("Ldt-createAnnotation-active-button");
       
  3522       }
       
  3523   });
       
  3524   
       
  3525 };
       
  3526 
       
  3527 IriSP.createAnnotationWidget.prototype.showStartScreen = function() {
       
  3528   this.selector.find(".Ldt-createAnnotation-screen").hide();
       
  3529   this.selector.find(".Ldt-createAnnotation-startScreen").show();
       
  3530   
       
  3531   var jqTextfield = this.selector.find(".Ldt-createAnnotation-Description"); // handle on the textfield. used for the closure
       
  3532   
       
  3533   /* test if the browser supports the placeholder attribute */
       
  3534   if (!IriSP.null_or_undefined(jqTextfield.get(0).placeholder)) {
       
  3535     jqTextfield.attr("placeholder", IriSP.i18n.getMessage('type_here')); 
       
  3536   } else {
       
  3537     jqTextfield.val(IriSP.i18n.getMessage('type_here'));
       
  3538     jqTextfield.one("click", IriSP.wrap(this, function() { jqTextfield.val(""); }));    
       
  3539   }
       
  3540   
       
  3541  
       
  3542   
       
  3543   this._state = "startScreen";
       
  3544 };
       
  3545 
       
  3546 IriSP.createAnnotationWidget.prototype.showWaitScreen = function() {
       
  3547   this.selector.find(".Ldt-createAnnotation-screen").hide();
       
  3548   this.selector.find(".Ldt-createAnnotation-waitScreen").show();
       
  3549   this._state = "waitScreen";
       
  3550 };
       
  3551 
       
  3552 IriSP.createAnnotationWidget.prototype.showErrorScreen = function() {
       
  3553   this.selector.find(".Ldt-createAnnotation-screen").hide();
       
  3554   this.selector.find(".Ldt-createAnnotation-errorScreen").show();
       
  3555   this._state = "errorScreen";
       
  3556   var _this = this;
       
  3557   window.setTimeout(function() { _this.showStartScreen(); }, 2000);
       
  3558 };
       
  3559 
       
  3560 /** update show the final screen with links to share the created annotation */
       
  3561 IriSP.createAnnotationWidget.prototype.showEndScreen = function(annotation) {
       
  3562   this.selector.find(".Ldt-createAnnotation-screen").hide();
       
  3563   
       
  3564   if (this.cinecast_version) {
       
  3565     this.selector.find(".Ldt-createAnnotation-Title").parent().show();      
       
  3566   }
       
  3567 
       
  3568   var url = ( (typeof annotation.meta == "object" && typeof annotation.meta.url == "string" && annotation.meta.url.length)
       
  3569     ? annotation.meta.url
       
  3570     : ( document.location.href + "#id=" + annotation.id ) );
       
  3571   var twStatus = IriSP.mkTweetUrl(url);
       
  3572   var gpStatus = IriSP.mkGplusUrl(url);
       
  3573   var fbStatus = IriSP.mkFbUrl(url);
       
  3574   
       
  3575   this.selector.find(".Ldt-createAnnotation-endScreen-TweetLink").attr("href", twStatus);
       
  3576   this.selector.find(".Ldt-createAnnotation-endScreen-FbLink").attr("href", fbStatus);
       
  3577   this.selector.find(".Ldt-createAnnotation-endScreen-GplusLink").attr("href", gpStatus);
       
  3578           
       
  3579   this.selector.find(".Ldt-createAnnotation-endScreen").show();
       
  3580   this._state = "endScreen";
       
  3581 };
       
  3582 
       
  3583 /** handle clicks on "send annotation" button */
       
  3584 IriSP.createAnnotationWidget.prototype.handleButtonClick = function(event) {
       
  3585   var _this = this;
       
  3586   var textfield = this.selector.find(".Ldt-createAnnotation-Description");
       
  3587   var contents = textfield.val();
       
  3588   
       
  3589   if (contents === "") {  
       
  3590     if (this.selector.find(".Ldt-createAnnotation-errorMessage").length === 0) {
       
  3591       this.selector.find(".Ldt-createAnnotation-Container")
       
  3592                    .after(IriSP.templToHTML(IriSP.createAnnotation_errorMessage_template));
       
  3593       textfield.css("background-color", "#d93c71");      
       
  3594     } else {      
       
  3595       this.selector.find(".Ldt-createAnnotation-errorMessage").show();
       
  3596     }
       
  3597 
       
  3598       textfield.one("js_mod propertychange keyup input paste", IriSP.wrap(this, function() {
       
  3599                       var contents = textfield.val();
       
  3600                       
       
  3601                       if (contents !== "") {
       
  3602                         this.selector.find(".Ldt-createAnnotation-errorMessage").hide();
       
  3603                         textfield.css("background-color", "");
       
  3604                       }
       
  3605                    }));
       
  3606   } else {
       
  3607     this.showWaitScreen();
       
  3608     
       
  3609     this.sendLdtData(contents, function(annotation) {
       
  3610                       if (_this.cinecast_version) {
       
  3611                           if (_this._Popcorn.media.paused)
       
  3612                             _this._Popcorn.play();
       
  3613                       }
       
  3614 
       
  3615                       if (_this._state == "waitScreen") {
       
  3616                         _this.showEndScreen(annotation);
       
  3617                         if (_this.cinecast_version) {
       
  3618                           window.setTimeout(function() { _this.showStartScreen(); }, 5000);
       
  3619                         }
       
  3620                       }
       
  3621                       // hide the slicer widget
       
  3622                       if (!_this.cinecast_version) {                      
       
  3623                         _this._Popcorn.trigger("IriSP.SliceWidget.hide");
       
  3624                       }           
       
  3625                     });
       
  3626   }
       
  3627 };
       
  3628 
       
  3629 IriSP.createAnnotationWidget.prototype.handleSliderChanges = function(params) {
       
  3630   this.sliceLeft = params[0];
       
  3631   this.sliceWidth = params[1];
       
  3632 };
       
  3633 
       
  3634 IriSP.createAnnotationWidget.prototype.sendLdtData = function(contents, callback) {
       
  3635   var _this = this;
       
  3636   var apiJson = {
       
  3637       format : "http://advene.org/ns/cinelab/",
       
  3638       annotations : [
       
  3639         {}
       
  3640         ],
       
  3641         meta: {}};
       
  3642   var annotation = apiJson.annotations[0];
       
  3643   
       
  3644   annotation.media = this.currentMedia()["id"];
       
  3645   
       
  3646   if (this.cinecast_version) {   
       
  3647       annotation.begin = Math.round(this._Popcorn.currentTime() * 1000);
       
  3648       annotation.end = annotation.begin;      
       
  3649   } else {
       
  3650     var duration = this.getDuration();    
       
  3651     annotation.begin = +((duration * (this.sliceLeft / 100)).toFixed(0));
       
  3652     annotation.end = +((duration * ((this.sliceWidth + this.sliceLeft) / 100)).toFixed(0));
       
  3653   }
       
  3654 
       
  3655   // boundary checks
       
  3656   annotation.begin = Math.max(0, annotation.begin);
       
  3657   annotation.end = Math.min(this.getDuration(), annotation.end);
       
  3658   
       
  3659   annotation.type = ( this.cinecast_version ? "cinecast:UserAnnotation" : ( this._serializer.getContributions() || "" ));
       
  3660   if (typeof(annotation.type) === "undefined")
       
  3661     annotation.type = "";
       
  3662   
       
  3663   annotation.type_title = "Contributions";
       
  3664   annotation.content = {};
       
  3665   annotation.content.data = contents;
       
  3666   if (this.cinecast_version) {
       
  3667       var _extract = IriSP.underscore(this._serializer._data.annotations)
       
  3668           .filter(function(_a) {
       
  3669               return (_a.begin <= annotation.begin && _a.end >= annotation.begin && _a.type == "cinecast:MovieExtract");
       
  3670           })
       
  3671       if (_extract.length) {
       
  3672           annotation.extract = _extract[0].id;
       
  3673       }
       
  3674   }
       
  3675   
       
  3676   var meta = apiJson.meta;
       
  3677   
       
  3678   
       
  3679   var _username = this.selector.find(".Ldt-createAnnotation-userName").val();
       
  3680   meta.creator = (
       
  3681       (_username && _username.length)
       
  3682       ? _username
       
  3683       : (
       
  3684           (!IriSP.null_or_undefined(IriSP.user) && !IriSP.null_or_undefined(IriSP.user.name))
       
  3685           ? IriSP.user.name
       
  3686           : "Anonymous user"
       
  3687       )
       
  3688   );
       
  3689   
       
  3690   meta.created = Date().toString();
       
  3691   
       
  3692   var _tags = [];
       
  3693   IriSP._(this.tags).each(function(_v) {
       
  3694       var _rx = IriSP.regexpFromText(_v.meta.description);
       
  3695         if (_rx.test(contents)) {
       
  3696             _tags.push(_v.id);
       
  3697         }
       
  3698   });
       
  3699 
       
  3700   if (typeof this.remote_tags == "object") {
       
  3701      _tags = IriSP._(_tags).map(function(_t) {
       
  3702          return _this.remote_tags.id + ':' + _t
       
  3703      });
       
  3704     if (typeof apiJson.imports == "undefined") {
       
  3705        apiJson.imports = [];
       
  3706     }
       
  3707     apiJson.imports.push({
       
  3708         "id" : this.remote_tags.id,
       
  3709         "url" : this.remote_tags.url
       
  3710     })
       
  3711   }
       
  3712   annotation.tags = IriSP.underscore.uniq(_tags);
       
  3713   
       
  3714   var jsonString = JSON.stringify(apiJson);
       
  3715   var project_id = this._serializer._data.meta.id;
       
  3716   
       
  3717   //TODO: extract magic url
       
  3718   var url = Mustache.to_html(this.api_endpoint_template,
       
  3719                               {id: project_id});
       
  3720                           
       
  3721   IriSP.jQuery.ajax({
       
  3722       url: url,
       
  3723       type: this.api_method,
       
  3724       contentType: 'application/json',
       
  3725       data: jsonString,               
       
  3726       //dataType: 'json',
       
  3727       success: IriSP.wrap(this, function(json, textStatus, XMLHttpRequest) {                   
       
  3728                     /* add the annotation to the annotation and tell the world */
       
  3729                     var annotation = json.annotations[0];
       
  3730                     
       
  3731                     if (!this.cinecast_version) {
       
  3732                     /* if the media doesn't have a contributions line, we need to add one */
       
  3733                         if (typeof(this._serializer.getContributions()) === "undefined") {
       
  3734                           /* set up a basic view */
       
  3735                           var tmp_view = {"dc:contributor": "perso", "dc:creator": "perso", "dc:title": "Contributions",
       
  3736                                           "id": json.annotations[0].type}
       
  3737     
       
  3738                           
       
  3739                             IriSP.get_aliased(this._serializer._data, ["annotation_types", "annotation-types"]).push(tmp_view);
       
  3740                         }
       
  3741                         
       
  3742                         delete annotation.tags;
       
  3743                         annotation.content.description = annotation.content.data;
       
  3744                         annotation.content.title = "";
       
  3745                         delete annotation.content.data;
       
  3746                         annotation.id = json.annotations[0].id;
       
  3747     
       
  3748                         annotation.meta = meta;
       
  3749                         annotation.meta["id-ref"] = json.annotations[0]["type"];
       
  3750                     } else {
       
  3751                         annotation.type = "cinecast:UserAnnotation";
       
  3752                     }
       
  3753                     // everything is shared so there's no need to propagate the change
       
  3754                     var _an_ids = IriSP.underscore(this._serializer._data.annotations).map(function(_a) {
       
  3755                         return _a.id.toLowerCase();
       
  3756                     });
       
  3757                     if (_an_ids.indexOf(annotation.id.toLowerCase()) == -1) {
       
  3758                         _this._serializer._data.annotations.push(annotation);
       
  3759                     }
       
  3760                     
       
  3761                     _this._Popcorn.trigger("IriSP.createAnnotationWidget.addedAnnotation", annotation);
       
  3762                     this.selector.find(".Ldt-createAnnotation-Description").val("").trigger("js_mod");
       
  3763                     callback(annotation);
       
  3764       }), 
       
  3765       error: 
       
  3766               function(jqXHR, textStatus, errorThrown) { 
       
  3767                             console.log("an error occured while contacting " 
       
  3768                             + url + " and sending " + jsonString + textStatus ); 
       
  3769                             _this.showErrorScreen(); } });
       
  3770 };IriSP.HelloWorldWidget = function(Popcorn, config, Serializer) {
       
  3771   IriSP.Widget.call(this, Popcorn, config, Serializer);
       
  3772 }
       
  3773 
       
  3774 IriSP.HelloWorldWidget.prototype = new IriSP.Widget();
       
  3775 
       
  3776 IriSP.HelloWorldWidget.prototype.draw = function() {
       
  3777     this.selector
       
  3778         .html('Hello, world')
       
  3779         .css({
       
  3780             "text-align" : "center",
       
  3781             "padding": "10px 0",
       
  3782             "font-size" : "14px"
       
  3783         });
       
  3784         
       
  3785     console.log(this);
       
  3786 }
       
  3787 /* Internationalization for this widget */
       
  3788 
       
  3789 IriSP.i18n.addMessages(
       
  3790     {
       
  3791         "en": {
       
  3792             "play_pause": "Play/Pause",
       
  3793             "mute_unmute": "Mute/Unmute",
       
  3794             "play": "Play",
       
  3795             "pause": "Pause",
       
  3796             "mute": "Mute",
       
  3797             "unmute": "Unmute",
       
  3798             "annotate": "Annotate",
       
  3799             "search": "Search",
       
  3800             "elapsed_time": "Elapsed time",
       
  3801             "total_time": "Total time",
       
  3802             "volume": "Volume",
       
  3803             "volume_control": "Volume control"
       
  3804         },
       
  3805         "fr": {
       
  3806             "play_pause": "Lecture/Pause",
       
  3807             "mute_unmute": "Couper/Activer le son",
       
  3808             "play": "Lecture",
       
  3809             "pause": "Pause",
       
  3810             "mute": "Couper le son",
       
  3811             "unmute": "Activer le son",
       
  3812             "annotate": "Annoter",
       
  3813             "search": "Rechercher",
       
  3814             "elapsed_time": "Durée écoulée",
       
  3815             "total_time": "Durée totale",
       
  3816             "volume": "Niveau sonore",
       
  3817             "volume_control": "Réglage du niveau sonore"
       
  3818         }
       
  3819     }
       
  3820 );
       
  3821 
       
  3822 
       
  3823 IriSP.PlayerWidget = function(Popcorn, config, Serializer) {
       
  3824   IriSP.Widget.call(this, Popcorn, config, Serializer);
       
  3825   
       
  3826   this._searchBlockOpen = false;
       
  3827   this._searchLastValue = "";
       
  3828 };
       
  3829 
       
  3830 IriSP.PlayerWidget.prototype = new IriSP.Widget();
       
  3831 
       
  3832 IriSP.PlayerWidget.prototype.draw = function() {
       
  3833   var self = this;
       
  3834   var width = this.width;
       
  3835 	var height = this.height;
       
  3836 	var heightS = this.height-20;
       
  3837 	  
       
  3838 	var playerTempl = IriSP.templToHTML(IriSP.player_template, this._config);
       
  3839   this.selector.append(playerTempl);		
       
  3840 	
       
  3841   this.selector.children(".Ldt-controler").show();
       
  3842     
       
  3843   // handle clicks by the user on the video.
       
  3844   this._Popcorn.listen("play", IriSP.wrap(this, this.playButtonUpdater));
       
  3845   this._Popcorn.listen("pause", IriSP.wrap(this, this.playButtonUpdater));
       
  3846   
       
  3847   this._Popcorn.listen("volumechange", IriSP.wrap(this, this.volumeUpdater));
       
  3848 
       
  3849   this._Popcorn.listen("timeupdate", IriSP.wrap(this, this.timeDisplayUpdater));  
       
  3850   // update the time display for the first time.
       
  3851   this._Popcorn.listen("loadedmetadata", IriSP.wrap(this, this.timeDisplayUpdater));
       
  3852   
       
  3853   this._Popcorn.listen("IriSP.search.matchFound", IriSP.wrap(this, this.searchMatch));
       
  3854   this._Popcorn.listen("IriSP.search.noMatchFound", IriSP.wrap(this, this.searchNoMatch));
       
  3855   this._Popcorn.listen("IriSP.search.triggeredSearch", IriSP.wrap(this, this.triggeredSearch));
       
  3856   
       
  3857   
       
  3858   this.selector.find(".Ldt-CtrlPlay").click(function() { self.playHandler.call(self); });
       
  3859   this.selector.find(".Ldt-CtrlAnnotate").click(function() 
       
  3860                                             { self._Popcorn.trigger("IriSP.PlayerWidget.AnnotateButton.clicked"); });
       
  3861   this.selector.find(".Ldt-CtrlSearch").click(function() { self.searchButtonHandler.call(self); });
       
  3862   
       
  3863 	var _volctrl = this.selector.find(".Ldt-Ctrl-Volume-Control");
       
  3864     this.selector.find('.Ldt-CtrlSound')
       
  3865         .click(function() { self.muteHandler.call(self); } )
       
  3866         .mouseover(function() {
       
  3867             _volctrl.show();
       
  3868         })
       
  3869         .mouseout(function() {
       
  3870             _volctrl.hide();
       
  3871         });
       
  3872     _volctrl.mouseover(function() {
       
  3873         _volctrl.show();
       
  3874     }).mouseout(function() {
       
  3875         _volctrl.hide();
       
  3876     });
       
  3877   
       
  3878   /*
       
  3879   var searchButtonPos = this.selector.find(".Ldt-CtrlSearch").position();
       
  3880   var searchBox = Mustache.to_html(IriSP.search_template, {margin_left : searchButtonPos.left + "px"});
       
  3881   this.selector.find(".Ldt-CtrlSearch").after(searchBox);
       
  3882   */
       
  3883   
       
  3884   // trigger an IriSP.PlayerWidget.MouseOver to the widgets that are interested (i.e : sliderWidget)
       
  3885   this.selector.hover(function() { self._Popcorn.trigger("IriSP.PlayerWidget.MouseOver"); }, 
       
  3886                       function() { self._Popcorn.trigger("IriSP.PlayerWidget.MouseOut"); });
       
  3887   this.selector.find(".Ldt-Ctrl-Volume-Cursor").draggable({
       
  3888       axis: "x",
       
  3889       drag: function(event, ui) {
       
  3890           var _vol = Math.max(0, Math.min( 1, ui.position.left / (ui.helper.parent().width() - ui.helper.outerWidth())));
       
  3891           ui.helper.attr("title",IriSP.i18n.getMessage('volume')+': ' + Math.floor(100*_vol) + '%');
       
  3892           self._Popcorn.volume(_vol);
       
  3893       },
       
  3894       containment: "parent"
       
  3895   });
       
  3896  
       
  3897  setTimeout(function() {
       
  3898      self.volumeUpdater();
       
  3899  }, 1000); /* some player - jwplayer notable - save the state of the mute button between sessions */
       
  3900 };
       
  3901 
       
  3902 /* Update the elasped time div */
       
  3903 IriSP.PlayerWidget.prototype.timeDisplayUpdater = function() {
       
  3904   
       
  3905   if (this._previousSecond === undefined) {
       
  3906     this._previousSecond = this._Popcorn.roundTime();
       
  3907   }
       
  3908   else {
       
  3909     /* we're still in the same second, so it's not necessary to update time */
       
  3910     if (this._Popcorn.roundTime() == this._previousSecond)
       
  3911       return;
       
  3912       
       
  3913   }
       
  3914   
       
  3915   // we get it at each call because it may change.
       
  3916   var duration = this.getDuration() / 1000; 
       
  3917   var totalTime = IriSP.secondsToTime(duration);
       
  3918   var elapsedTime = IriSP.secondsToTime(this._Popcorn.currentTime());
       
  3919   
       
  3920   this.selector.find(".Ldt-ElapsedTime").html(elapsedTime.toString());
       
  3921   this.selector.find(".Ldt-TotalTime").html(totalTime.toString());
       
  3922   this._previousSecond = this._Popcorn.roundTime();
       
  3923 };
       
  3924 
       
  3925 /* update the icon of the button - separate function from playHandler
       
  3926    because in some cases (for instance, when the user directly clicks on
       
  3927    the jwplayer window) we have to change the icon without playing/pausing
       
  3928 */
       
  3929 IriSP.PlayerWidget.prototype.playButtonUpdater = function() {
       
  3930   var status = this._Popcorn.media.paused;
       
  3931   
       
  3932   if ( status == true ){
       
  3933     /* the background sprite is changed by adding/removing the correct classes */
       
  3934     this.selector.find(".Ldt-CtrlPlay").attr("title", IriSP.i18n.getMessage('play'));
       
  3935     this.selector.find(".Ldt-CtrlPlay").removeClass("Ldt-CtrlPlay-PauseState").addClass("Ldt-CtrlPlay-PlayState");
       
  3936   } else {
       
  3937     this.selector.find(".Ldt-CtrlPlay").attr("title", IriSP.i18n.getMessage('pause'));
       
  3938     this.selector.find(".Ldt-CtrlPlay").removeClass("Ldt-CtrlPlay-PlayState").addClass("Ldt-CtrlPlay-PauseState");
       
  3939   }  
       
  3940 
       
  3941   return;
       
  3942 };
       
  3943 
       
  3944 
       
  3945 IriSP.PlayerWidget.prototype.playHandler = function() {
       
  3946   var status = this._Popcorn.media.paused;
       
  3947   
       
  3948   if ( status == true ){        
       
  3949     this._Popcorn.play();   
       
  3950   } else {
       
  3951     this._Popcorn.pause();
       
  3952   }  
       
  3953 };
       
  3954 
       
  3955 IriSP.PlayerWidget.prototype.muteHandler = function() {
       
  3956   this._Popcorn.mute(!this._Popcorn.muted());
       
  3957 };
       
  3958 
       
  3959 IriSP.PlayerWidget.prototype.volumeUpdater = function() {
       
  3960     var _muted = this._Popcorn.muted(),
       
  3961         _vol = this._Popcorn.volume();
       
  3962     if (_vol === false) {
       
  3963         _vol = .5;
       
  3964     }
       
  3965     var _soundCtl = this.selector.find(".Ldt-CtrlSound");
       
  3966     _soundCtl.removeClass("Ldt-CtrlSound-Mute Ldt-CtrlSound-Half Ldt-CtrlSound-Full");
       
  3967     if (_muted) {        
       
  3968         _soundCtl.attr("title", IriSP.i18n.getMessage('unmute'))
       
  3969             .addClass("Ldt-CtrlSound-Mute");    
       
  3970     } else {
       
  3971         _soundCtl.attr("title", IriSP.i18n.getMessage('mute'))
       
  3972             .addClass(_vol < .5 ? "Ldt-CtrlSound-Half" : "Ldt-CtrlSound-Full" )
       
  3973     }
       
  3974     var _cursor = this.selector.find(".Ldt-Ctrl-Volume-Cursor");
       
  3975     _cursor.css({
       
  3976         "left": ( _muted ? 0 : Math.floor(_vol * (_cursor.parent().width() - _cursor.outerWidth())) ) + "px"
       
  3977     })
       
  3978 };
       
  3979 
       
  3980 IriSP.PlayerWidget.prototype.showSearchBlock = function() {
       
  3981   var self = this;
       
  3982   
       
  3983   if (this._searchBlockOpen == false) {
       
  3984     this.selector.find(".LdtSearch").show("blind", { direction: "horizontal"}, 100);
       
  3985     this.selector.find(".LdtSearchInput").css('background-color','#fff');
       
  3986    
       
  3987     this._searchBlockOpen = true;           
       
  3988     this.selector.find(".LdtSearchInput").bind('keyup', null, function() { self.searchHandler.call(self); } );
       
  3989     this.selector.find(".LdtSearchInput").focus();
       
  3990     
       
  3991     // we need this variable because some widget can find a match in
       
  3992     // their data while at the same time other's don't. As we want the
       
  3993     // search field to become green when there's a match, we need a 
       
  3994     // variable to remember that we had one.
       
  3995     this._positiveMatch = false;
       
  3996 
       
  3997     // tell the world the field is open
       
  3998     this._Popcorn.trigger("IriSP.search.open");     
       
  3999 	}
       
  4000 };
       
  4001 
       
  4002 IriSP.PlayerWidget.prototype.hideSearchBlock = function() {
       
  4003  if (this._searchBlockOpen == true) {
       
  4004     this._searchLastValue = this.selector.find(".LdtSearchInput").attr('value');
       
  4005     this.selector.find(".LdtSearchInput").attr('value','');
       
  4006     this.selector.find(".LdtSearch").hide("blind", { direction: "horizontal"}, 75);
       
  4007     
       
  4008     // unbind the watcher event.
       
  4009     this.selector.find(".LdtSearchInput").unbind('keypress set');
       
  4010     this._searchBlockOpen = false;
       
  4011 
       
  4012     this._positiveMatch = false;
       
  4013     
       
  4014     this._Popcorn.trigger("IriSP.search.closed");
       
  4015 	}
       
  4016 };
       
  4017 
       
  4018 /** react to clicks on the search button */
       
  4019 IriSP.PlayerWidget.prototype.searchButtonHandler = function() {
       
  4020   var self = this;
       
  4021 
       
  4022   /* show the search field if it is not shown */
       
  4023   if ( this._searchBlockOpen == false ) {
       
  4024     this.showSearchBlock();
       
  4025     this.selector.find(".LdtSearchInput").attr('value', this._searchLastValue);      
       
  4026     this._Popcorn.trigger("IriSP.search", this._searchLastValue); // trigger the search to make it more natural.
       
  4027 	} else {
       
  4028     this.hideSearchBlock();
       
  4029   }
       
  4030 };
       
  4031 
       
  4032 /** this handler is called whenever the content of the search
       
  4033    field changes */
       
  4034 IriSP.PlayerWidget.prototype.searchHandler = function() {
       
  4035   this._searchLastValue = this.selector.find(".LdtSearchInput").attr('value');
       
  4036   this._positiveMatch = false;
       
  4037   
       
  4038   // do nothing if the search field is empty, instead of highlighting everything.
       
  4039   if (this._searchLastValue == "") {
       
  4040     this._Popcorn.trigger("IriSP.search.cleared");
       
  4041     this.selector.find(".LdtSearchInput").css('background-color','');
       
  4042   } else {
       
  4043     this._Popcorn.trigger("IriSP.search", this._searchLastValue);
       
  4044   }
       
  4045 };
       
  4046 
       
  4047 /**
       
  4048   handler for the IriSP.search.found message, which is sent by some views when they
       
  4049   highlight a match.
       
  4050 */
       
  4051 IriSP.PlayerWidget.prototype.searchMatch = function() {
       
  4052   this._positiveMatch = true;
       
  4053   this.selector.find(".LdtSearchInput").css('background-color','#e1ffe1');
       
  4054 };
       
  4055 
       
  4056 /** the same, except that no value could be found */
       
  4057 IriSP.PlayerWidget.prototype.searchNoMatch = function() {
       
  4058   if (this._positiveMatch !== true)
       
  4059     this.selector.find(".LdtSearchInput").css('background-color', "#d62e3a");
       
  4060 };
       
  4061 
       
  4062 /** react to an IriSP.Player.triggeredSearch - that is, when
       
  4063     a widget ask the PlayerWidget to do a search on his behalf */
       
  4064 IriSP.PlayerWidget.prototype.triggeredSearch = function(searchString) {
       
  4065   this.showSearchBlock();
       
  4066   this.selector.find(".LdtSearchInput").attr('value', searchString);      
       
  4067   this._Popcorn.trigger("IriSP.search", searchString); // trigger the search to make it more natural.
       
  4068 };
       
  4069 
       
  4070 
       
  4071 /* 
       
  4072  *   
       
  4073  *  Copyright 2010 Institut de recherche et d'innovation 
       
  4074  *  contributor(s) : Samuel Huron 
       
  4075  *   
       
  4076  *  contact@iri.centrepompidou.fr
       
  4077  *  http://www.iri.centrepompidou.fr 
       
  4078  *   
       
  4079  *  This software is a computer program whose purpose is to show and add annotations on a video .
       
  4080  *  This software is governed by the CeCILL-C license under French law and
       
  4081  *  abiding by the rules of distribution of free software. You can  use, 
       
  4082  *  modify and/ or redistribute the software under the terms of the CeCILL-C
       
  4083  *  license as circulated by CEA, CNRS and INRIA at the following URL
       
  4084  *  "http://www.cecill.info". 
       
  4085  *  
       
  4086  *  The fact that you are presently reading this means that you have had
       
  4087  *  knowledge of the CeCILL-C license and that you accept its terms.
       
  4088 */
       
  4089 // CHART TIMELINE / VERSION PROTOTYPE  ::
       
  4090 
       
  4091 /** the polemic widget */
       
  4092 IriSP.PolemicWidget = function(Popcorn, config, Serializer) {
       
  4093   IriSP.Widget.call(this, Popcorn, config, Serializer);
       
  4094  
       
  4095   this.userPol    = new Array();
       
  4096   this.userNoPol  = new Array();
       
  4097   this.userst      = new Array();
       
  4098   this.numberOfTweet = 0;
       
  4099   this.Users;
       
  4100   this.TweetPolemic;
       
  4101   this.yMax        = this.height; 
       
  4102   this.PaperSlider;
       
  4103   this.heightOfChart;
       
  4104   this.tweets  = new Array();
       
  4105   this.svgElements = {};
       
  4106   
       
  4107   this.oldSearchMatches = [];
       
  4108 };
       
  4109 
       
  4110 IriSP.PolemicWidget.prototype = new IriSP.Widget();
       
  4111   
       
  4112 IriSP.PolemicWidget.prototype.draw = function() {
       
  4113   
       
  4114     // variable 
       
  4115     // yMax
       
  4116     
       
  4117     var self = this;
       
  4118     var yCoef        = 2;             // coef for height of 1 tweet 
       
  4119     var frameSize     = 5;             // frame size 
       
  4120     var margin         = 1;            // marge between frame
       
  4121     var lineSize      = this.width;        // timeline pixel width 
       
  4122     var nbrframes     = lineSize/frameSize;     // frame numbers
       
  4123     var numberOfTweet   = 0;            // number of tweet overide later 
       
  4124     var duration      = this.getDuration();      // timescale width 
       
  4125     var frameLength   = lineSize / frameSize;    // frame timescale  
       
  4126     var timeline;
       
  4127     var colors  = new Array("","#1D973D","#036AAE","#CE0A15","#C5A62D","#585858");
       
  4128     
       
  4129     // array 
       
  4130     //var tweets  = new Array();
       
  4131     var element = new Array();
       
  4132     var cluster = new Array();
       
  4133     var frames  = new Array(frameLength);
       
  4134     var slices  = new Array();
       
  4135     
       
  4136     
       
  4137     // Classes =======================================================================
       
  4138     var Frames = function(){
       
  4139       
       
  4140       var Myclusters;
       
  4141       var x;
       
  4142       var y;
       
  4143       var width;
       
  4144       var height;
       
  4145     };
       
  4146     Frames = function(json){
       
  4147       // make my clusters
       
  4148       // ou Frame vide 
       
  4149     };
       
  4150     Frames.prototype.draw = function(){
       
  4151     };
       
  4152     Frames.prototype.zoom = function(){
       
  4153     };
       
  4154     Frames.prototype.inside = function(){
       
  4155     };
       
  4156     var Clusters = function(){
       
  4157       var Object;
       
  4158       var yDist;
       
  4159       var x;
       
  4160       var y;
       
  4161       var width;
       
  4162       var height;
       
  4163     };
       
  4164     Clusters = function(json){
       
  4165       // make my object
       
  4166     };
       
  4167     var Tweet = function(){
       
  4168     };
       
  4169     // Classes =======================================================================
       
  4170 
       
  4171     // Refactoring (parametere) ************************************************************
       
  4172     // color translastion
       
  4173     var qTweet_0  =0;
       
  4174     var qTweet_Q  =0;
       
  4175     var qTweet_REF=0;
       
  4176     var qTweet_OK =0;
       
  4177     var qTweet_KO =0;
       
  4178     function colorTranslation(value){
       
  4179       if(value == "Q"){
       
  4180         qTweet_Q+=1;
       
  4181         return 2;
       
  4182       }else if(value =="REF"){
       
  4183         qTweet_REF+=1;
       
  4184         return 4;
       
  4185       }else if(value =="OK"){
       
  4186         qTweet_OK+=1;
       
  4187         return 1;
       
  4188       }else if(value =="KO"){
       
  4189         qTweet_KO+=1;
       
  4190         return 3;
       
  4191       }else if(value ==""){
       
  4192         qTweet_0+=1;
       
  4193         return 5;
       
  4194       }
       
  4195     }
       
  4196     
       
  4197 
       
  4198       this._serializer.sync(function(data) { loaded_callback.call(self, data); return; });
       
  4199       
       
  4200       function loaded_callback (json) {
       
  4201       var view_type = this._serializer.getTweets();
       
  4202 
       
  4203       
       
  4204       if (typeof(view_type) === "undefined") {
       
  4205         var view_type = this._serializer.getTweetIds()[0];
       
  4206         if (typeof(view_type) === "undefined") {
       
  4207           // default to guessing if nothing else works.
       
  4208           var view = json.views[0];
       
  4209           
       
  4210           if(typeof(view.annotation_types) !== "undefined") {
       
  4211             /* we need to be backward compatible with the old files which used to
       
  4212                feature only two lines : Chapitrage and Tweets. We've added a
       
  4213                "Contributions" line so we need to discriminate against that */
       
  4214             if (view.annotation_types.length === 2 && typeof(this._serializer.getContributions()) === "undefined") {
       
  4215               var view_type = view.annotation_types[1];
       
  4216             } else {
       
  4217               console.log("PolemicWidget: invalid file - minimizing");
       
  4218               return;
       
  4219             }
       
  4220           }      
       
  4221         }
       
  4222       }
       
  4223       
       
  4224       // Make and define the Raphael area
       
  4225       this.paper = Raphael(document.getElementById(this._id), this._config.width, this._config.height);
       
  4226       
       
  4227       // event handlers
       
  4228       this._Popcorn.listen("IriSP.search", IriSP.wrap(this, function(searchString) { this.searchHandler(searchString); }));
       
  4229       this._Popcorn.listen("IriSP.search.closed", IriSP.wrap(this, this.searchFieldClosedHandler));
       
  4230       this._Popcorn.listen("IriSP.search.cleared", IriSP.wrap(this, this.searchFieldClearedHandler));
       
  4231       this.selector.mouseleave(IriSP.wrap(this, function() { self.TooltipWidget.hide.call(self.TooltipWidget); }));
       
  4232       this._Popcorn.listen("timeupdate", IriSP.wrap(this, this.sliderUpdater));
       
  4233       this._Popcorn.listen("IriSP.Mediafragment.showAnnotation", IriSP.wrap(this, this.showAnnotation));
       
  4234       
       
  4235       for(var i = 0; i < json.annotations.length; i++) {
       
  4236         var item = json.annotations[i];        
       
  4237         var MyTime  = Math.floor(item.begin/duration*lineSize);
       
  4238         var Myframe = Math.floor(MyTime/lineSize*frameLength);
       
  4239 
       
  4240         if (typeof(item.meta) !== "undefined" 
       
  4241           && typeof(item.meta["id-ref"]) !== "undefined"
       
  4242           && item.meta["id-ref"] === view_type) {
       
  4243             
       
  4244             var MyTJson = {},
       
  4245                 _source = IriSP.get_aliased(item.meta, ['dc:source', 'source']);
       
  4246             if (_source !== null) {
       
  4247               var MyTJson = JSON.parse(_source['content']);
       
  4248             }
       
  4249             
       
  4250             if (item.content['polemics'] != undefined 
       
  4251             && item.content['polemics'][0] != null) {
       
  4252             
       
  4253               // a tweet can have many polemics at the same time.
       
  4254               for(var j=0; j<item.content['polemics'].length; j++){
       
  4255                   
       
  4256                   this.tweets[numberOfTweet] = {
       
  4257                         id:i,
       
  4258                         qualification:colorTranslation(item.content['polemics'][j]),
       
  4259                         yIndicator:MyTime,
       
  4260                         yframe:Myframe,
       
  4261                         title:item.content['title'],
       
  4262                         timeframe:item.begin,
       
  4263                         userId: MyTJson.id,
       
  4264                         userScreenName: MyTJson.screen_name,
       
  4265                         tsource:MyTJson,
       
  4266                         cinecast_id: item.id
       
  4267                         };
       
  4268                   numberOfTweet+=1;
       
  4269                   
       
  4270               }
       
  4271           }
       
  4272           else {
       
  4273             this.tweets[numberOfTweet] = {
       
  4274                   id:i,
       
  4275                   qualification:colorTranslation(""),
       
  4276                   yIndicator:MyTime,
       
  4277                   yframe:Myframe,
       
  4278                   title:item.content['title'],
       
  4279                   timeframe:item.begin,
       
  4280                   userId: MyTJson.id,
       
  4281                   userScreenName: MyTJson.screen_name,
       
  4282                   tsource:MyTJson,
       
  4283                   cinecast_id: item.id
       
  4284             };
       
  4285             numberOfTweet+=1;
       
  4286           }
       
  4287           
       
  4288         } 
       
  4289       };  
       
  4290       
       
  4291        DrawTweets.call (this); // FIXME: ugly.
       
  4292        
       
  4293       };      
       
  4294 
       
  4295     // tweet Drawing (in raphael) 
       
  4296     function DrawTweets (){
       
  4297     // GROUPES TWEET ============================================
       
  4298     // Count nbr of cluster and tweet in a frame an save int in "frames"
       
  4299       numberOfTweet = this.tweets.length;
       
  4300       for(var i=0; i<nbrframes; i++) {  
       
  4301         for(var j=0; j<numberOfTweet; j++) {  
       
  4302         
       
  4303           if (i==this.tweets[j].yframe){
       
  4304             
       
  4305             var k = this.tweets[j].qualification;
       
  4306             
       
  4307             // make array for frame cluster
       
  4308             if(frames[i]==undefined){
       
  4309               frames[i] = {id:i,
       
  4310                      qualifVol:new Array(),
       
  4311                      mytweetsID:new Array()
       
  4312                     };
       
  4313             }
       
  4314             // add my tweet to frame
       
  4315             frames[i].mytweetsID.push(this.tweets[j]);
       
  4316             
       
  4317             // count opinion by frame
       
  4318             if( frames[i].qualifVol[k] == undefined){
       
  4319               frames[i].qualifVol[k] = 1;
       
  4320             }else{
       
  4321               frames[i].qualifVol[k] += 1;
       
  4322             }
       
  4323             
       
  4324           }
       
  4325         }
       
  4326       }
       
  4327     
       
  4328     // GROUPES TWEET ============================================    
       
  4329     // max of tweet by Frame 
       
  4330       var max = 0; 
       
  4331       for(var i = 0; i < nbrframes; i++) {
       
  4332         var moy  = 0;
       
  4333         for (var j = 0; j < 6; j++) {    
       
  4334           if (frames[i] != undefined) {
       
  4335             if (frames[i].qualifVol[j] != undefined) {
       
  4336               moy += frames[i].qualifVol[j];
       
  4337             }
       
  4338           }
       
  4339         }
       
  4340         
       
  4341         if (moy > max) {
       
  4342           max = moy;
       
  4343         }
       
  4344       }
       
  4345     
       
  4346       var tweetDrawed = new Array();
       
  4347       var TweetHeight = 5;
       
  4348       var newHeight = TweetHeight * max + 10;
       
  4349 
       
  4350       
       
  4351       if (newHeight > this.height) {
       
  4352         this.paper.setSize(this.width, newHeight);
       
  4353         this.height = newHeight;
       
  4354         console.log("resizeing");
       
  4355       }
       
  4356       
       
  4357   
       
  4358       // DRAW  TWEETS ============================================
       
  4359       for(var i = 0; i < nbrframes; i++) {
       
  4360         var addEheight = 5;
       
  4361         if (frames[i] != undefined){                
       
  4362           // by type 
       
  4363           
       
  4364           for (var j = 6; j > -1; j--) {
       
  4365             if (frames[i].qualifVol[j] != undefined) {
       
  4366               // show tweet by type 
       
  4367               for (var k = 0; k < frames[i].mytweetsID.length; k++) {
       
  4368               
       
  4369                 if (frames[i].mytweetsID[k].qualification == j) {                
       
  4370                   var x = i * frameSize;
       
  4371                   var y = this.height - addEheight;
       
  4372                   
       
  4373                   if (this.yMax > y) {
       
  4374                     this.yMax = y;
       
  4375                   }
       
  4376                   
       
  4377                   /* some tweets seem to be duplicated - so we make a check before
       
  4378                      creating a new rect */
       
  4379                   if (this.svgElements.hasOwnProperty(frames[i].mytweetsID[k].cinecast_id))
       
  4380                     continue;
       
  4381                   
       
  4382                   var e = this.paper.rect(x, y, frameSize - margin, TweetHeight /* height */)
       
  4383                                     .attr({stroke:"#00","stroke-width":0.1,  fill: colors[j]});  
       
  4384                   
       
  4385                   addEheight += TweetHeight;
       
  4386                   
       
  4387                   /* stick a lot of things into e because that's the easiest way
       
  4388                      to do it */
       
  4389                   e.color = colors[j];
       
  4390                   e.time = frames[i].mytweetsID[k].timeframe;
       
  4391                   e.title = frames[i].mytweetsID[k].title;
       
  4392                   e.id = frames[i].mytweetsID[k].cinecast_id;
       
  4393                   this.svgElements[e.id] = e;
       
  4394                   
       
  4395                   IriSP.jQuery(e.node).mouseenter(function(element) { return function (_e) {                    
       
  4396                         self.TooltipWidget.show.call(self.TooltipWidget, element.title, element.attr("fill"), element.attrs.x + element.attrs.width / 2, element.attrs.y - 2);
       
  4397                         element.displayed = true;
       
  4398                         self._Popcorn.trigger("IriSP.TraceWidget.MouseEvents", {
       
  4399                             "widget" : "StackGraphWidget",
       
  4400                             "type": "mousemove",
       
  4401                             "x": _e.pageX,
       
  4402                             "y": _e.pageY,
       
  4403                             "annotation_id": element.id
       
  4404                         });
       
  4405                   }}(e)).mousedown(function(element) { return function () {                    
       
  4406                     self._Popcorn.currentTime(element.time/1000);
       
  4407                     self._Popcorn.trigger("IriSP.PolemicTweet.click", element.id); 
       
  4408                     }
       
  4409                   }(e));                  
       
  4410                   
       
  4411                   IriSP.jQuery(e.node).attr('id', 't' + k + '');
       
  4412                   IriSP.jQuery(e.node).attr('title', frames[i].mytweetsID[k].title);
       
  4413                   IriSP.jQuery(e.node).attr('begin',  frames[i].mytweetsID[k].timeframe);                  
       
  4414                 }
       
  4415               }
       
  4416             }
       
  4417           }
       
  4418         }
       
  4419 
       
  4420       }    
       
  4421       // DRAW UI :: resize border and bgd      
       
  4422       this.paperBackground = this.paper.rect(0, 0, this.width, this.height).attr({fill:"#F8F8F8","stroke-width":0.1,opacity: 1});  
       
  4423 
       
  4424       // outer borders
       
  4425       this.outerBorders   = [];
       
  4426       this.outerBorders.push(this.paper.rect(0, this.height - 1, this.width, 1).attr({fill:"#ababab",stroke: "none",opacity: 1}));  
       
  4427       this.outerBorders.push(this.paper.rect(0, 0, this.width, 1).attr({fill:"#ababab",stroke: "none",opacity: 1}));  
       
  4428 
       
  4429       // inner borders
       
  4430       this.innerBorders   = [];
       
  4431       this.innerBorders.push(this.paper.rect(1, this.height - 2, this.width, 1).attr({fill:"#efefef",stroke: "none",opacity: 1}));  
       
  4432       this.innerBorders.push(this.paper.rect(1, 1, this.width, 1).attr({fill:"#efefef",stroke: "none",opacity: 1}));  
       
  4433       this.innerBorders.push(this.paper.rect(1, 1, 1, this.height - 2).attr({fill:"#d0d1d1",stroke: "none",opacity: 0.8}));  
       
  4434       this.innerBorders.push(this.paper.rect(this.width - 2, 1, 1, this.height - 2).attr({fill:"#efefef",stroke: "none",opacity: 1}));  
       
  4435 
       
  4436 
       
  4437 
       
  4438       this.paperSlider   = this.paper.rect(0, 0, 0, this.height).attr({fill:"#D4D5D5", stroke: "none", opacity: 1});
       
  4439       
       
  4440       // the small white line displayed over the slider.
       
  4441       this.sliderTip = this.paper.rect(0, 0, 1, this.height).attr({fill:"#fc00ff", stroke: "none", opacity: 1});
       
  4442       // decalage 
       
  4443       // tweetSelection = this.paper.rect(-100,-100,5,5).attr({fill:"#fff",stroke: "none",opacity: 1});  
       
  4444       
       
  4445       
       
  4446       this.paperSlider.toBack();
       
  4447       this.paperBackground.toBack();
       
  4448       this.sliderTip.toFront();
       
  4449     }
       
  4450     
       
  4451 
       
  4452 }
       
  4453 
       
  4454 /** update the positionMarker as time passes */
       
  4455 IriSP.PolemicWidget.prototype.sliderUpdater = function() {
       
  4456 
       
  4457     var time = +this._Popcorn.currentTime();
       
  4458     var duration = this.getDuration();
       
  4459     
       
  4460     this.paperSlider.attr("width", time * (this.width / (duration / 1000)));
       
  4461         
       
  4462     this.sliderTip.attr("x", time * (this.width / (duration / 1000)));
       
  4463 };
       
  4464 
       
  4465 /** reacts to IriSP.search events */    
       
  4466 IriSP.PolemicWidget.prototype.searchHandler = function(searchString) {
       
  4467   if (searchString == "")
       
  4468     return;
       
  4469 
       
  4470   var matches = this._serializer.searchTweetsOccurences(searchString);
       
  4471 
       
  4472   if (IriSP.countProperties(matches) > 0) {
       
  4473     this._Popcorn.trigger("IriSP.search.matchFound");
       
  4474   } else {
       
  4475     this._Popcorn.trigger("IriSP.search.noMatchFound");
       
  4476   }
       
  4477 
       
  4478   
       
  4479   // decrease the opacity of the other elements.
       
  4480   for (var id in this.svgElements) {
       
  4481     var e = this.svgElements[id];
       
  4482     e.attr({fill: e.color, opacity: 0.4});   
       
  4483   }
       
  4484   
       
  4485 
       
  4486   for (var id in matches) {    
       
  4487     if (this.svgElements.hasOwnProperty(id)) {
       
  4488       var e = this.svgElements[id];
       
  4489       this.svgElements[id].attr({fill: "#fc00ff", opacity: 1});
       
  4490     }
       
  4491   }
       
  4492 
       
  4493   this.oldSearchMatches = matches;
       
  4494 };
       
  4495 
       
  4496 /** reacts to IriSP.search.cleared messages */
       
  4497 IriSP.PolemicWidget.prototype.searchFieldClearedHandler = function() {
       
  4498   for (var id in this.svgElements) {
       
  4499     var e = this.svgElements[id];
       
  4500     e.attr({fill: e.color, opacity: 1});
       
  4501   }
       
  4502 };
       
  4503 
       
  4504 /** reacts to IriSP.search.closed messages by clearing the highlighted elements */
       
  4505 IriSP.PolemicWidget.prototype.searchFieldClosedHandler = function() {
       
  4506   for (var id in this.svgElements) {
       
  4507     var e = this.svgElements[id];
       
  4508     e.attr({fill: e.color, opacity: 1});
       
  4509   }
       
  4510  
       
  4511 };
       
  4512    
       
  4513 IriSP.PolemicWidget.prototype.showAnnotation = function(id) {
       
  4514   if (this.svgElements.hasOwnProperty(id)) {
       
  4515     var e = this.svgElements[id];
       
  4516     this.TooltipWidget.show(e.title, e.attr("fill"), e.x - 103, e.y - 160);
       
  4517   }
       
  4518 };   
       
  4519 IriSP.SegmentsWidget = function(Popcorn, config, Serializer) {
       
  4520 
       
  4521   var self = this;
       
  4522   IriSP.Widget.call(this, Popcorn, config, Serializer);
       
  4523   this.oldSearchMatches = [];
       
  4524 
       
  4525   // event handlers
       
  4526   this._Popcorn.listen("IriSP.search", function(searchString) { self.searchHandler.call(self, searchString); });
       
  4527   this._Popcorn.listen("IriSP.search.closed", function() { self.searchFieldClosedHandler.call(self); });
       
  4528   this._Popcorn.listen("IriSP.search.cleared", function() { self.searchFieldClearedHandler.call(self); });
       
  4529   
       
  4530   this.defaultColors = ["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"]
       
  4531 };
       
  4532 
       
  4533 IriSP.SegmentsWidget.prototype = new IriSP.Widget();
       
  4534 
       
  4535 IriSP.SegmentsWidget.prototype.draw = function() {
       
  4536 
       
  4537   var self = this;
       
  4538   var annotations = this._serializer._data.annotations;
       
  4539 
       
  4540   this.selector.addClass("Ldt-SegmentsWidget");
       
  4541   this.selector.append(Mustache.to_html(IriSP.overlay_marker_template));
       
  4542   
       
  4543   this.positionMarker = this.selector.find(".Ldt-SegmentPositionMarker");
       
  4544   
       
  4545   this._Popcorn.listen("timeupdate", IriSP.wrap(this, this.positionUpdater));
       
  4546   var duration = this.getDuration();
       
  4547   
       
  4548   if (this.cinecast_version) {
       
  4549       var segments_annotations = IriSP.underscore.filter(
       
  4550           this._serializer._data.annotations,
       
  4551           function(_a) {
       
  4552               return _a.type == "cinecast:MovieExtract";
       
  4553           }
       
  4554       );
       
  4555   }
       
  4556   else {
       
  4557 
       
  4558       var view_type = this._serializer.getChapitrage();
       
  4559       if (typeof(view_type) === "undefined") {
       
  4560         view_type = this._serializer.getNonTweetIds()[0];  
       
  4561       }
       
  4562     
       
  4563       
       
  4564       var i = 0;
       
  4565       
       
  4566       var segments_annotations = [];
       
  4567       
       
  4568       for (i = 0; i < annotations.length; i++) {
       
  4569         var annotation = annotations[i];
       
  4570     
       
  4571         /* filter the annotations whose type is not the one we want */
       
  4572         if (view_type != "" && typeof(annotation.meta) !== "undefined" && typeof(annotation.meta["id-ref"]) !== "undefined"
       
  4573               && annotation.meta["id-ref"] != view_type) {
       
  4574             continue;
       
  4575         }
       
  4576     
       
  4577         segments_annotations.push(annotation);
       
  4578       }
       
  4579 }    
       
  4580     var _w = this.selector.width();
       
  4581   var lastSegment = IriSP.underscore.max(segments_annotations, function(annotation) { return annotation.end; });
       
  4582   
       
  4583   for (i = 0; i < segments_annotations.length; i++) {
       
  4584   
       
  4585     var annotation = segments_annotations[i];
       
  4586     var begin = (+ annotation.begin);
       
  4587     var end = (+ annotation.end);
       
  4588     var id = annotation.id;
       
  4589         
       
  4590     var startPixel = Math.floor(_w * (begin / duration));
       
  4591 
       
  4592     var endPixel = Math.floor(_w * (end / duration));
       
  4593     if (annotation.id !== lastSegment.id) 
       
  4594       var pxWidth = endPixel - startPixel -1;
       
  4595     else
       
  4596       /* the last segment has no segment following it */
       
  4597       var pxWidth = endPixel - startPixel;
       
  4598     
       
  4599     var divTitle = this.cinecast_version
       
  4600         ? annotation.content.data
       
  4601         : annotation.content.title + ( annotation.content.title ? "<br />" : "" ) + annotation.content.description.replace(/(^.{120,140})[\s].+$/,'$1&hellip;');
       
  4602     
       
  4603     var thumbUrl = annotation.meta.thumbnail || '';
       
  4604     
       
  4605     var hexa_color = typeof(annotation.content.color) !== "undefined"
       
  4606         ? '#' + IriSP.DEC_HEXA_COLOR(annotation.content.color)
       
  4607         : typeof(annotation.color) !== "undefined"
       
  4608             ? '#' + IriSP.DEC_HEXA_COLOR(annotation.color)
       
  4609             : this.defaultColors[i % this.defaultColors.length];
       
  4610 
       
  4611     /*
       
  4612     if (hexa_color === "FFCC00")
       
  4613       hexa_color = "333";
       
  4614     */
       
  4615     if (hexa_color.length == 5)
       
  4616       hexa_color = hexa_color + '00';
       
  4617     
       
  4618     
       
  4619     var annotationTemplate = Mustache.to_html(IriSP.annotation_template,
       
  4620         {"divTitle" : divTitle, "id" : id, "startPixel" : startPixel,
       
  4621         "pxWidth" : pxWidth, "hexa_color" : hexa_color,
       
  4622         "seekPlace" : Math.round(begin/1000), "thumbnailUrl": thumbUrl});
       
  4623 
       
  4624         
       
  4625     this.selector.append(annotationTemplate);
       
  4626     
       
  4627     /* add a special class to the last segment and change its border */
       
  4628     if (annotation.id === lastSegment.id) {
       
  4629         IriSP.jqId(id).addClass("Ldt-lastSegment").css("border-color", hexa_color);  
       
  4630     }
       
  4631    }
       
  4632     // react to mediafragment messages.
       
  4633     this._Popcorn.listen("IriSP.Mediafragment.showAnnotation", 
       
  4634         function(id, divTitle) {
       
  4635         
       
  4636             var divObject = IriSP.jqId(id);
       
  4637             if (divObject.length) {
       
  4638             divObject.fadeTo(0,1);
       
  4639             var offset_x = divObject.position().left + divObject.outerWidth() / 2;
       
  4640             self.TooltipWidget.show(divObject.attr("title"), IriSP.jQuery(this).css("background-color"), offset_x, 0);
       
  4641             IriSP.jQuery(document).one("mousemove", function() { divObject.fadeTo(0,.5);
       
  4642                                                                 self.TooltipWidget.hide(); });
       
  4643         }
       
  4644       });
       
  4645       
       
  4646     this.selector.find(".Ldt-iri-chapter")
       
  4647         .fadeTo(0, .5)
       
  4648         .click(function() {
       
  4649             self._Popcorn.trigger("IriSP.SegmentsWidget.click", this.id);
       
  4650             self._Popcorn.currentTime(IriSP.jQuery(this).attr("data-seek"));
       
  4651         })
       
  4652         .mouseover( function(event) {
       
  4653             var divObject = IriSP.jQuery(this);
       
  4654             divObject.fadeTo(0,1);
       
  4655             var offset_x = divObject.position().left + divObject.outerWidth() / 2;
       
  4656             var thumb = divObject.attr("thumbnail-url");
       
  4657             var txt = divObject.attr("title") + (thumb && thumb.length ? '<br /><img src="' + thumb + '" />' : '');
       
  4658             self.TooltipWidget.show(txt, IriSP.jQuery(this).css("background-color"), offset_x, 0);
       
  4659         })
       
  4660         .mouseout(function(){
       
  4661             IriSP.jQuery(this).fadeTo(0,.5);
       
  4662             self.TooltipWidget.hide();
       
  4663         });
       
  4664 };
       
  4665 
       
  4666 /* restores the view after a search */
       
  4667 IriSP.SegmentsWidget.prototype.clear = function() {
       
  4668   this.selector.children(".Ldt-iri-chapter").fadeTo(0,.5);
       
  4669 };
       
  4670 
       
  4671 IriSP.SegmentsWidget.prototype.clickHandler = function(annotation) {
       
  4672   this._Popcorn.trigger("IriSP.SegmentsWidget.click", annotation.id);
       
  4673   var begin = (+ annotation.begin) / 1000;
       
  4674   this._Popcorn.currentTime(Math.round(begin));
       
  4675 };
       
  4676 
       
  4677 IriSP.SegmentsWidget.prototype.searchHandler = function(searchString) {
       
  4678 
       
  4679   if (searchString == "")
       
  4680     return;
       
  4681 
       
  4682   var matches = this._serializer.searchOccurences(searchString);
       
  4683 
       
  4684   if (IriSP.countProperties(matches) > 0) {
       
  4685     this._Popcorn.trigger("IriSP.search.matchFound");
       
  4686   } else {
       
  4687     this._Popcorn.trigger("IriSP.search.noMatchFound");
       
  4688   }
       
  4689 
       
  4690   // un-highlight all the blocks
       
  4691   this.selector.children(".Ldt-iri-chapter").css("opacity", 0.1);
       
  4692  
       
  4693   // then highlight the ones with matches.
       
  4694   for (var id in matches) {
       
  4695     var factor = 0.5 + matches[id] * 0.2;
       
  4696     this.selector.find("#"+id).dequeue();
       
  4697     this.selector.find("#"+id).animate({opacity:factor}, 200);
       
  4698   }
       
  4699 
       
  4700  
       
  4701   this.oldSearchMatches = matches;
       
  4702 };
       
  4703 
       
  4704 IriSP.SegmentsWidget.prototype.searchFieldClearedHandler = function() {
       
  4705   this.clear();
       
  4706 };
       
  4707 
       
  4708 IriSP.SegmentsWidget.prototype.searchFieldClosedHandler = function() {
       
  4709   this.clear();
       
  4710 };
       
  4711 
       
  4712 IriSP.SegmentsWidget.prototype.positionUpdater = function() {  
       
  4713   var duration = this.getDuration() / 1000;
       
  4714   var time = this._Popcorn.currentTime();
       
  4715   //var position 	= ((time / duration) * 100).toFixed(2);
       
  4716   var position 	= ((time / duration) * 100).toFixed(2);
       
  4717 
       
  4718   this.positionMarker.css("left", position + "%");  
       
  4719 };
       
  4720 
       
  4721 IriSP.SegmentsWidget.prototype.showAnnotation = function() {
       
  4722 
       
  4723 };
       
  4724 /** A widget to create a new segment */
       
  4725 IriSP.SliceWidget = function(Popcorn, config, Serializer) {
       
  4726   IriSP.Widget.call(this, Popcorn, config, Serializer);
       
  4727   
       
  4728 };
       
  4729 
       
  4730 IriSP.SliceWidget.prototype = new IriSP.Widget();
       
  4731 
       
  4732 IriSP.SliceWidget.prototype.draw = function() {
       
  4733   var templ = Mustache.to_html(IriSP.sliceWidget_template);
       
  4734   this.selector.append(templ);
       
  4735   
       
  4736   this.sliceZone = this.selector.find(".Ldt-sliceZone");
       
  4737   
       
  4738   /* global variables used to keep the position and width
       
  4739      of the zone.
       
  4740   */  
       
  4741   this.zoneLeft = 0;
       
  4742   this.zoneWidth = 0;
       
  4743   
       
  4744   this.leftHandle = this.selector.find(".Ldt-sliceLeftHandle");
       
  4745   this.rightHandle = this.selector.find(".Ldt-sliceRightHandle");
       
  4746 
       
  4747   var left = this.selector.offset().left;
       
  4748   var top = this.selector.offset().top;
       
  4749 
       
  4750   // a bug in firefox makes it use the wrong format
       
  4751   if (!IriSP.jQuery.browser.mozilla) {
       
  4752     // contain the handles correctly - we cannot set
       
  4753     // containment: parent because it wouldn't allow to select the 
       
  4754     // whole slice, so we have to compute a box in which the slice is
       
  4755     // allowed to move.
       
  4756     var containment = [left - 8, top, this.selector.width() + left, top];
       
  4757 
       
  4758     // var containment = [left - 16, top, this.selector.width() + left - 8, top];
       
  4759     this.leftHandle.draggable({axis: "x",
       
  4760     drag: IriSP.wrap(this, this.leftHandleDragged),  
       
  4761     containment: containment
       
  4762     });
       
  4763 
       
  4764     containment = [left, top, this.selector.width() + left, top];
       
  4765     // containment = [left, top, this.selector.width() + left - 8, top];
       
  4766     this.rightHandle.draggable({axis: "x",
       
  4767     drag: IriSP.wrap(this, this.rightHandleDragged),    
       
  4768     containment: containment
       
  4769     });
       
  4770   
       
  4771   } else { // firefox
       
  4772     // we need to define a containment specific to firefox.
       
  4773     
       
  4774     var containment = [left - 16, top, this.selector.width() + left - 8, top];
       
  4775     this.leftHandle.draggable({axis: "x",
       
  4776     drag: IriSP.wrap(this, this.leftHandleDragged),  
       
  4777     containment: containment
       
  4778     });
       
  4779 
       
  4780     containment = [left, top, this.selector.width() + left - 8, top];
       
  4781     this.rightHandle.draggable({axis: "x",
       
  4782     drag: IriSP.wrap(this, this.rightHandleDragged),    
       
  4783     containment: containment
       
  4784     });
       
  4785   }
       
  4786   
       
  4787   this.leftHandle.css("position", "absolute");
       
  4788   this.rightHandle.css("position", "absolute");
       
  4789   
       
  4790   this._Popcorn.listen("IriSP.SliceWidget.position", 
       
  4791                         IriSP.wrap(this, this.positionSliceHandler));
       
  4792   
       
  4793   this._Popcorn.listen("IriSP.SliceWidget.show", IriSP.wrap(this, this.show));
       
  4794   this._Popcorn.listen("IriSP.SliceWidget.hide", IriSP.wrap(this, this.hide));
       
  4795   this.selector.hide();
       
  4796 };
       
  4797 
       
  4798 /** responds to an "IriSP.SliceWidget.position" message
       
  4799     @param params an array with the first element being the left distance in
       
  4800            percents and the second element the width of the slice in pixels
       
  4801 */        
       
  4802 IriSP.SliceWidget.prototype.positionSliceHandler = function(params) {
       
  4803   left = params[0];
       
  4804   width = params[1];
       
  4805   
       
  4806   this.zoneLeft = left;
       
  4807   this.zoneWidth = width;
       
  4808   this.sliceZone.css("left", left + "px");
       
  4809   this.sliceZone.css("width", width + "px");
       
  4810   this.leftHandle.css("left", (left - 7) + "px");
       
  4811   this.rightHandle.css("left", left + width + "px");
       
  4812   
       
  4813   this._leftHandleOldLeft = left - 7;
       
  4814   this._rightHandleOldLeft = left + width;
       
  4815 };
       
  4816 
       
  4817 /** handle a dragging of the left handle */
       
  4818 IriSP.SliceWidget.prototype.leftHandleDragged = function(event, ui) {
       
  4819   /* we have a special variable, this._leftHandleOldLeft, to keep the
       
  4820      previous position of the handle. We do that to know in what direction
       
  4821      is the handle being dragged
       
  4822   */
       
  4823   
       
  4824   var currentX = this.leftHandle.offset().left;
       
  4825   var rightHandleX = Math.floor(this.rightHandle.position()["left"]);
       
  4826   
       
  4827   var container_offset = this.selector.offset().left;
       
  4828 
       
  4829   if (Math.floor(ui.position.left) >= rightHandleX - 7) {
       
  4830     /* prevent the handle from moving past the right handle */
       
  4831     ui.position.left = rightHandleX - 7;
       
  4832   }
       
  4833 
       
  4834   this.zoneWidth = rightHandleX - Math.floor(ui.position.left) - 7;  
       
  4835   this.zoneLeft = Math.floor(ui.position.left) + 8;
       
  4836   
       
  4837   this.sliceZone.css("width", this.zoneWidth);
       
  4838   this.sliceZone.css("left", this.zoneLeft + "px");
       
  4839   
       
  4840   this._leftHandleOldLeft = ui.position.left;  
       
  4841   this.broadcastChanges();
       
  4842     
       
  4843 };
       
  4844 
       
  4845 /** handle a dragging of the right handle */
       
  4846 IriSP.SliceWidget.prototype.rightHandleDragged = function(event, ui) { 
       
  4847   /* we have a special variable, this._leftHandleOldLeft, to keep the
       
  4848      previous position of the handle. We do that to know in what direction
       
  4849      is the handle being dragged
       
  4850   */
       
  4851   
       
  4852   var currentX = this.leftHandle.position()["left"];
       
  4853   var leftHandleX = Math.floor(this.leftHandle.position()["left"]);
       
  4854 
       
  4855   var container_offset = this.selector.offset().left + this.selector.width();
       
  4856   
       
  4857   if (Math.floor(ui.position.left) < leftHandleX + 7) {
       
  4858     /* prevent the handle from moving past the left handle */
       
  4859     ui.position.left = leftHandleX + 7;
       
  4860   }
       
  4861 
       
  4862   this.zoneWidth = Math.floor(ui.position.left) - (leftHandleX + 7);    
       
  4863   
       
  4864   this.sliceZone.css("width", this.zoneWidth);
       
  4865   //this.sliceZone.css("left", this.zoneLeft + "px");
       
  4866   this._rightHandleOldLeft = Math.floor(this._rightHandleOldLeft);  
       
  4867   this.broadcastChanges();
       
  4868 };
       
  4869 
       
  4870 /** tell to the world that the coordinates of the slice have
       
  4871     changed 
       
  4872 */
       
  4873 IriSP.SliceWidget.prototype.broadcastChanges = function() {
       
  4874   var leftPercent = (this.zoneLeft / this.selector.width()) * 100;
       
  4875   var zonePercent = (this.zoneWidth / this.selector.width()) * 100;
       
  4876 
       
  4877   this._Popcorn.trigger("IriSP.SliceWidget.zoneChange", [leftPercent, zonePercent]);  
       
  4878 };
       
  4879 
       
  4880 IriSP.SliceWidget.prototype.show = function() {
       
  4881   this.selector.show();
       
  4882 };
       
  4883 
       
  4884 IriSP.SliceWidget.prototype.hide = function() {
       
  4885   this.selector.hide();
       
  4886 };IriSP.SliderWidget = function(Popcorn, config, Serializer) {
       
  4887   IriSP.Widget.call(this, Popcorn, config, Serializer);
       
  4888 };
       
  4889 
       
  4890 IriSP.SliderWidget.prototype = new IriSP.Widget();
       
  4891 
       
  4892 IriSP.SliderWidget.prototype.draw = function() {
       
  4893   var self = this;
       
  4894 
       
  4895   this.selector.append(Mustache.to_html(IriSP.sliderWidget_template, {}));
       
  4896   this.selector.addClass("Ldt-SliderMinimized");
       
  4897 
       
  4898   this.sliderBackground = this.selector.find(".Ldt-sliderBackground");
       
  4899   this.sliderForeground = this.selector.find(".Ldt-sliderForeground");
       
  4900   this.positionMarker = this.selector.find(".Ldt-sliderPositionMarker");
       
  4901 
       
  4902 
       
  4903   // a special variable to stop methods from tinkering
       
  4904   // with the positionMarker when the user is dragging it
       
  4905   this.draggingOngoing = false;
       
  4906 
       
  4907   // another special variable used by the timeout handler to
       
  4908   // open or close the slider.
       
  4909   this.sliderMaximized = false;
       
  4910   this.timeOutId = null;
       
  4911 
       
  4912   
       
  4913   this.positionMarker.draggable({axis: "x",
       
  4914   start: IriSP.wrap(this, this.positionMarkerDraggingStartedHandler),
       
  4915   stop: IriSP.wrap(this, this.positionMarkerDraggedHandler),
       
  4916   containment: "parent"
       
  4917   });
       
  4918   this.positionMarker.css("position", "absolute");
       
  4919   
       
  4920   this.sliderBackground.click(function(event) { self.backgroundClickHandler.call(self, event); });
       
  4921   this.sliderForeground.click(function(event) { self.foregroundClickHandler.call(self, event); });
       
  4922 
       
  4923   this.selector.hover(IriSP.wrap(this, this.mouseOverHandler), IriSP.wrap(this, this.mouseOutHandler));
       
  4924 
       
  4925   // update the positions
       
  4926   this._Popcorn.listen("timeupdate", IriSP.wrap(this, this.sliderUpdater));
       
  4927 
       
  4928   // special messages :
       
  4929   this._Popcorn.listen("IriSP.PlayerWidget.MouseOver", IriSP.wrap(this, this.mouseOverHandler));
       
  4930   this._Popcorn.listen("IriSP.PlayerWidget.MouseOut", IriSP.wrap(this, this.mouseOutHandler));
       
  4931 };
       
  4932 
       
  4933 /* update the slider and the position marker as time passes */
       
  4934 IriSP.SliderWidget.prototype.sliderUpdater = function() {
       
  4935   if(this.draggingOngoing || this._disableUpdate)
       
  4936     return;
       
  4937   
       
  4938   var time = this._Popcorn.currentTime();
       
  4939 
       
  4940   var duration = this.getDuration() / 1000;
       
  4941   var percents = time / duration;
       
  4942   
       
  4943   /* we do these complicated calculations to center exactly
       
  4944      the position Marker */
       
  4945 
       
  4946   var divWidth = this.selector.width();
       
  4947   var pixels = Math.floor(this.selector.width() * percents);
       
  4948   var positionMarker_width = this.positionMarker.width();
       
  4949   var correction = (positionMarker_width / 2);
       
  4950 
       
  4951   /* check that we don't leave the left side */
       
  4952   var newPos = pixels - correction;
       
  4953   if (newPos <= 0)
       
  4954     newPos = 0;
       
  4955   
       
  4956   /* check that we don't leave the right side */
       
  4957   var rightEdgePos = pixels + 1 * correction;
       
  4958 
       
  4959   if (rightEdgePos >= divWidth)
       
  4960     newPos = divWidth - 1 * correction - 1;
       
  4961   
       
  4962   this.sliderForeground.css("width", pixels + "px");
       
  4963   this.positionMarker.css("left", newPos + "px");
       
  4964 
       
  4965 };
       
  4966 
       
  4967 IriSP.SliderWidget.prototype.backgroundClickHandler = function(event) {
       
  4968   /* this piece of code is a little bit convoluted - here's how it works :
       
  4969      we want to handle clicks on the progress bar and convert those to seeks in the media.
       
  4970      However, jquery only gives us a global position, and we want a number of pixels relative
       
  4971      to our container div, so we get the parent position, and compute an offset to this position,
       
  4972      and finally compute the progress ratio in the media.
       
  4973      Finally we multiply this ratio with the duration to get the correct time
       
  4974   */
       
  4975 
       
  4976   var parentOffset = this.sliderBackground.parent().offset();
       
  4977   var width = this.sliderBackground.width();
       
  4978   var relX = event.pageX - parentOffset.left;
       
  4979 
       
  4980   var duration = this.getDuration() / 1000;
       
  4981   var newTime = ((relX / width) * duration).toFixed(2);
       
  4982 
       
  4983   this._Popcorn.currentTime(newTime);
       
  4984 };
       
  4985 
       
  4986 /* same function as the previous one, except that it handles clicks
       
  4987    on the foreground element */
       
  4988 IriSP.SliderWidget.prototype.foregroundClickHandler = function(event) {
       
  4989   var parentOffset = this.sliderForeground.parent().offset();
       
  4990   var width = this.sliderBackground.width();
       
  4991   var relX = event.pageX - parentOffset.left;
       
  4992 
       
  4993   var duration = this.getDuration() / 1000;
       
  4994   var newTime = ((relX / width) * duration).toFixed(2);
       
  4995 
       
  4996   this._Popcorn.currentTime(newTime);
       
  4997 };
       
  4998 
       
  4999 /* handles mouse over the slider */
       
  5000 IriSP.SliderWidget.prototype.mouseOverHandler = function(event) {
       
  5001   
       
  5002   if (this.timeOutId !== null) {
       
  5003     window.clearTimeout(this.timeOutId);
       
  5004   }
       
  5005  
       
  5006   this.sliderMaximized = true;
       
  5007 
       
  5008   this.sliderBackground.animate({"height": "9px"}, 100);
       
  5009   this.sliderForeground.animate({"height": "9px"}, 100);
       
  5010   this.positionMarker.animate({"height": "9px", "width": "9px"}, 100);
       
  5011   //this.positionMarker.css("margin-top", "-4px");
       
  5012   
       
  5013 //  this.selector.removeClass("Ldt-SliderMinimized");
       
  5014 //  this.selector.addClass("Ldt-SliderMaximized");
       
  5015 };
       
  5016 
       
  5017 /* handles when the mouse leaves the slider */
       
  5018 IriSP.SliderWidget.prototype.mouseOutHandler = function(event) {
       
  5019 
       
  5020   this.timeOutId = window.setTimeout(IriSP.wrap(this, this.minimizeOnTimeout),
       
  5021                                      this.minimize_period);
       
  5022 };
       
  5023 
       
  5024 IriSP.SliderWidget.prototype.minimizeOnTimeout = function(event) {
       
  5025   this.sliderBackground.animate({"height": "5px"}, 100);
       
  5026   this.sliderForeground.animate({"height": "5px"}, 100);
       
  5027   this.positionMarker.animate({"height": "5px", "width": "5px"}, 100);
       
  5028   this.positionMarker.css("margin-top", "0px");
       
  5029   this.sliderMinimized = true;
       
  5030   
       
  5031 //  this.selector.removeClass("Ldt-SliderMaximized");
       
  5032 //  this.selector.addClass("Ldt-SliderMinimized");
       
  5033 
       
  5034 };
       
  5035 
       
  5036 // called when the user starts dragging the position indicator
       
  5037 IriSP.SliderWidget.prototype.positionMarkerDraggingStartedHandler = function(event, ui) {  
       
  5038   this.draggingOngoing = true;
       
  5039 };
       
  5040 
       
  5041 IriSP.SliderWidget.prototype.positionMarkerDraggedHandler = function(event, ui) {   
       
  5042 
       
  5043 /*  this._disableUpdate = true; // disable slider position updates while dragging is ongoing.
       
  5044   window.setTimeout(IriSP.wrap(this, function() { this._disableUpdate = false; }), 500);
       
  5045 */
       
  5046   var parentOffset = this.sliderForeground.parent().offset();
       
  5047   var width = this.sliderBackground.width();
       
  5048   var relX = event.originalEvent.pageX - parentOffset.left;
       
  5049 
       
  5050   var duration = this.getDuration() / 1000;
       
  5051   var newTime = ((relX / width) * duration).toFixed(2);
       
  5052   this._Popcorn.currentTime(newTime);
       
  5053   
       
  5054   this.draggingOngoing = false;
       
  5055 };
       
  5056 
       
  5057 /** @class The constructor for the sparkline widget */
       
  5058 IriSP.SparklineWidget = function(Popcorn, config, Serializer) {
       
  5059   IriSP.Widget.call(this, Popcorn, config, Serializer);
       
  5060 
       
  5061   this._oldAnnotation = null;
       
  5062   this._results = [];
       
  5063   
       
  5064   this.slices = this._config.slices || Math.floor(this.width/20);
       
  5065   if (!this.width) {
       
  5066       this.width = this.selector.width();
       
  5067   }
       
  5068   if (!this.height) {
       
  5069       this.height = 40;
       
  5070   }
       
  5071   this.selector.css("height", this.height + "px");
       
  5072   if (this._config.background) {
       
  5073       this.selector.css("background", this._config.background);
       
  5074   }
       
  5075 };
       
  5076 
       
  5077 
       
  5078 IriSP.SparklineWidget.prototype = new IriSP.Widget();
       
  5079 
       
  5080 IriSP.SparklineWidget.prototype.clear = function() {
       
  5081 
       
  5082 };
       
  5083 
       
  5084 /** draw the sparkline using jquery sparkline */
       
  5085 IriSP.SparklineWidget.prototype.draw = function() {
       
  5086     this.duration = this.getDuration();
       
  5087     this.paper = new Raphael(this.selector[0], this.width, this.height);
       
  5088     var _this = this;
       
  5089   
       
  5090   var views = this._serializer._data.views;
       
  5091   var stat_view;
       
  5092   if (!IriSP.null_or_undefined(views)) {
       
  5093     for (var i = 0; i < views.length; i++) {
       
  5094       var view = views[i];
       
  5095       if (view.id === "stat") {
       
  5096           stat_view = view;
       
  5097           break;
       
  5098       }
       
  5099     }
       
  5100   }
       
  5101   
       
  5102     var _ = IriSP.underscore;
       
  5103   // If we've found the correct view, feed the directly the data from the view
       
  5104   // to jquery sparkline. Otherwise, compute it ourselves.
       
  5105     if (!IriSP.null_or_undefined(stat_view)) {
       
  5106         //console.log("sparklinewidget : using stats embedded in the json");
       
  5107         var _results = stat_view.meta.stat.split(",");      
       
  5108     } else {
       
  5109         var _annotations = this._serializer._data.annotations;
       
  5110         if (this.cinecast_version) {
       
  5111             _annotations = _(_annotations).filter(function(_a) {
       
  5112                 return _a.type !== "cinecast:MovieExtract";
       
  5113             });
       
  5114         }
       
  5115         var _sliceDuration = Math.floor( this.duration / this.slices),
       
  5116             _results = _(_.range(this.slices)).map(function(_i) {
       
  5117                 return _(_annotations).filter(function(_a){
       
  5118                     return (_a.begin <= (1 + _i) * _sliceDuration) && (_a.end >= _i * _sliceDuration)
       
  5119                 }).length;
       
  5120             });
       
  5121     }
       
  5122     var _max = Math.max(1, _(_results).max()),
       
  5123         _h = this.height,
       
  5124         _scale = (_h - this.lineWidth) / _max,
       
  5125         _width = this.width / this.slices,
       
  5126         _y = _(_results).map(function(_v) {
       
  5127             return _h - (_scale * _v);
       
  5128         }),
       
  5129         _d = _(_y).reduce(function(_memo, _v, _k) {
       
  5130                return _memo + ( _k
       
  5131                    ? 'C' + (_k * _width) + ' ' + _y[_k - 1] + ' ' + (_k * _width) + ' ' + _v + ' ' + ((_k + .5) * _width) + ' ' + _v
       
  5132                    : 'M0 ' + _v + 'L' + (.5*_width) + ' ' + _v )
       
  5133             },'') + 'L' + this.width + ' ' + _y[_y.length - 1],
       
  5134         _d2 = _d + 'L' + this.width + ' ' + this.height + 'L0 ' + this.height;
       
  5135     this.paper.path(_d2).attr({
       
  5136         "stroke" : "none",
       
  5137         "fill" : this.fillColor
       
  5138     });
       
  5139          
       
  5140     this.paper.path(_d).attr({
       
  5141         "fill" : "none",
       
  5142         "stroke" : this.lineColor,
       
  5143         "stroke-width" : this.lineWidth
       
  5144     });
       
  5145   
       
  5146     this.rectangleProgress = this.paper.rect(0,0,0,this.height)
       
  5147         .attr({
       
  5148             "stroke" : "none",
       
  5149             "fill" : "#808080",
       
  5150             "opacity" : .3
       
  5151         });
       
  5152     this.ligneProgress = this.paper.path("M0 0L0 "+this.height).attr({"stroke":"#ff00ff", "line-width" : 2});
       
  5153   // save the results in an array so that we can re-use them when a new annotation
       
  5154   // is added.
       
  5155   this._results = _results;
       
  5156   
       
  5157   this._Popcorn.listen("timeupdate", IriSP.wrap(this, this.timeUpdateHandler));
       
  5158 //  this._Popcorn.listen("IriSP.createAnnotationWidget.addedAnnotation", IriSP.wrap(this, this.handleNewAnnotation));
       
  5159   
       
  5160   this.selector.click(IriSP.wrap(this, this.clickHandler));  
       
  5161 };
       
  5162 
       
  5163 /** react to a timeupdate event */
       
  5164 IriSP.SparklineWidget.prototype.timeUpdateHandler = function() {
       
  5165     var _currentTime = this._Popcorn.currentTime(),
       
  5166         _x = (1000 * _currentTime / this.duration) * this.width;
       
  5167     this.rectangleProgress.attr({
       
  5168         "width" : _x
       
  5169     });
       
  5170     this.ligneProgress.attr({
       
  5171         "path" : "M" + _x + " 0L" + _x + " " + this.height
       
  5172     });
       
  5173                                   
       
  5174 }
       
  5175 
       
  5176 /** handle clicks on the widget */
       
  5177 IriSP.SparklineWidget.prototype.clickHandler = function(event) {
       
  5178   var relX = event.pageX - this.selector.offset().left;
       
  5179   var newTime = ((relX / this.width) * this.duration/1000).toFixed(2);
       
  5180     
       
  5181   this._Popcorn.trigger("IriSP.SparklineWidget.clicked", newTime);
       
  5182   this._Popcorn.currentTime(newTime);
       
  5183 };
       
  5184 
       
  5185 /** react when a new annotation is added */
       
  5186 IriSP.SparklineWidget.prototype.handleNewAnnotation = function(annotation) {
       
  5187 //  var num_columns = this._results.length;
       
  5188 //  var duration = this._serializer.getDuration();
       
  5189 //  var time_step = Math.round(duration / num_columns); /* the time interval between two columns */
       
  5190 //  var begin = +annotation.begin;
       
  5191 //  var end = +annotation.end;
       
  5192 //  
       
  5193 //  /* increment all the values between the beginning and the end of the annotation */
       
  5194 //  var index_begin = Math.floor(begin / time_step);
       
  5195 //  var index_end = Math.floor(end / time_step);
       
  5196 //  
       
  5197 //  for (var i = index_begin; i < Math.min(index_end, this._results.length); i++) {
       
  5198 //    this._results[i]++;
       
  5199 //  }
       
  5200 //  
       
  5201 //  this.selector.find(".Ldt-sparkLine").sparkline(this._results, {lineColor: "#7492b4", fillColor: "#aeaeb8",
       
  5202 //                                                           spotColor: "#b70056",
       
  5203 //                                                           width: this.width, height: this.height});
       
  5204 };IriSP.StackGraphWidget = function(Popcorn, config, Serializer) {
       
  5205   IriSP.Widget.call(this, Popcorn, config, Serializer);
       
  5206 }
       
  5207 
       
  5208 IriSP.StackGraphWidget.prototype = new IriSP.Widget();
       
  5209 
       
  5210 IriSP.StackGraphWidget.prototype.draw = function() {
       
  5211     var _ = IriSP._;
       
  5212     this.height =  this._config.height || 50;
       
  5213     this.width = this.selector.width();
       
  5214     this.slices = this._config.slices || ~~(this.width/(this.streamgraph ? 20 : 5));
       
  5215     _(this.tags).each(function(_a) {
       
  5216         _a.regexp = new RegExp(_(_a.keywords).map(function(_k) {
       
  5217             return _k.replace(/([\W])/gm,'\\$1');
       
  5218         }).join("|"),"im")
       
  5219     });
       
  5220     this.paper = new Raphael(this.selector[0], this.width, this.height);
       
  5221     this.groups = [];
       
  5222     this.duration = this.getDuration();
       
  5223     
       
  5224     var _annotationType = this._serializer.getTweets(),
       
  5225         _sliceDuration = ~~ ( this.duration / this.slices),
       
  5226         _annotations = this._serializer._data.annotations,
       
  5227         _groupedAnnotations = _(_.range(this.slices)).map(function(_i) {
       
  5228             return _(_annotations).filter(function(_a){
       
  5229                 return (_a.begin <= (1 + _i) * _sliceDuration) && (_a.end >= _i * _sliceDuration)
       
  5230             });
       
  5231         }),
       
  5232         _max = IriSP._(_groupedAnnotations).max(function(_g) {
       
  5233             return _g.length
       
  5234         }).length,
       
  5235         _scale = this.height / _max,
       
  5236         _width = this.width / this.slices,
       
  5237         _showTitle = !this._config.excludeTitle,
       
  5238         _showDescription = !this._config.excludeDescription;
       
  5239     
       
  5240     
       
  5241     var _paths = _(this.tags).map(function() {
       
  5242         return [];
       
  5243     });
       
  5244     _paths.push([]);
       
  5245     
       
  5246     for (var i = 0; i < this.slices; i++) {
       
  5247         var _group = _groupedAnnotations[i];
       
  5248         if (_group) {
       
  5249             var _vol = _(this.tags).map(function() {
       
  5250                 return 0;
       
  5251             });
       
  5252             for (var j = 0; j < _group.length; j++){
       
  5253            var _txt = (_showTitle ? _group[j].content.title : '') + ' ' + (_showDescription ? _group[j].content.description : '')
       
  5254                 var _tags = _(this.tags).map(function(_tag) {
       
  5255                         return (_txt.search(_tag.regexp) == -1 ? 0 : 1)
       
  5256                     }),
       
  5257                     _nbtags = _(_tags).reduce(function(_a,_b) {
       
  5258                         return _a + _b;
       
  5259                     }, 0);
       
  5260                 if (_nbtags) {
       
  5261                     IriSP._(_tags).each(function(_v, _k) {
       
  5262                         _vol[_k] += (_v / _nbtags);
       
  5263                     });
       
  5264                 }
       
  5265             }
       
  5266             var _nbtags = _(_vol).reduce(function(_a,_b) {
       
  5267                     return _a + _b;
       
  5268                 }, 0),
       
  5269                 _nbneutre = _group.length - _nbtags,
       
  5270                 _h = _nbneutre * _scale,
       
  5271                 _base = this.height - _h;
       
  5272             if (!this.streamgraph) {
       
  5273                 this.paper.rect(i * _width, _base, _width - 1, _h ).attr({
       
  5274                     "stroke" : "none",
       
  5275                     "fill" : this.defaultcolor
       
  5276                 });
       
  5277             }
       
  5278            _paths[0].push(_base);
       
  5279             for (var j = 0; j < this.tags.length; j++) {
       
  5280                 _h = _vol[j] * _scale;
       
  5281                 _base = _base - _h;
       
  5282                 if (!this.streamgraph) {
       
  5283                     this.paper.rect(i * _width, _base, _width - 1, _h ).attr({
       
  5284                         "stroke" : "none",
       
  5285                         "fill" : this.tags[j].color
       
  5286                     });
       
  5287                 }
       
  5288                 _paths[j+1].push(_base);
       
  5289             }
       
  5290             this.groups.push(_(_vol).map(function(_v) {
       
  5291                 return _v / _group.length;
       
  5292             }))
       
  5293         } else {
       
  5294             for (var j = 0; j < _paths.length; j++) {
       
  5295                 _paths[j].push(this.height);
       
  5296             }
       
  5297             this.groups.push(_(this.tags).map(function() {
       
  5298                 return 0;
       
  5299             }));
       
  5300         }
       
  5301     }
       
  5302     
       
  5303     if (this.streamgraph) {
       
  5304         for (var j = _paths.length - 1; j >= 0; j--) {
       
  5305             var _d = _(_paths[j]).reduce(function(_memo, _v, _k) {
       
  5306                return _memo + ( _k
       
  5307                    ? 'C' + (_k * _width) + ' ' + _paths[j][_k - 1] + ' ' + (_k * _width) + ' ' + _v + ' ' + ((_k + .5) * _width) + ' ' + _v
       
  5308                    : 'M0 ' + _v + 'L' + (.5*_width) + ' ' + _v )
       
  5309             },'') + 'L' + this.width + ' ' + _paths[j][_paths[j].length - 1] + 'L' + this.width + ' ' + this.height + 'L0 ' + this.height;
       
  5310             this.paper.path(_d).attr({
       
  5311                 "stroke" : "none",
       
  5312                 "fill" : (j ? this.tags[j-1].color : this.defaultcolor)
       
  5313             });
       
  5314         }
       
  5315     }
       
  5316     this.rectangleFocus = this.paper.rect(0,0,_width,this.height)
       
  5317         .attr({
       
  5318             "stroke" : "none",
       
  5319             "fill" : "#ff00ff",
       
  5320             "opacity" : 0
       
  5321         })
       
  5322     this.rectangleProgress = this.paper.rect(0,0,0,this.height)
       
  5323         .attr({
       
  5324             "stroke" : "none",
       
  5325             "fill" : "#808080",
       
  5326             "opacity" : .3
       
  5327         });
       
  5328     this.ligneProgress = this.paper.path("M0 0L0 "+this.height).attr({"stroke":"#ff00ff", "line-width" : 2})
       
  5329     
       
  5330     this._Popcorn.listen("timeupdate", IriSP.wrap(this, this.timeUpdateHandler));
       
  5331     var _this = this;
       
  5332     this.selector
       
  5333         .click(IriSP.wrap(this, this.clickHandler))
       
  5334         .mousemove(function(_e) {
       
  5335             _this.updateTooltip(_e);
       
  5336             // Trace
       
  5337             var relX = _e.pageX - _this.selector.offset().left;
       
  5338             var _duration = _this.getDuration();
       
  5339             var _time = parseInt((relX / _this.width) * _duration);
       
  5340             _this._Popcorn.trigger("IriSP.TraceWidget.MouseEvents", {
       
  5341                 "widget" : "StackGraphWidget",
       
  5342                 "type": "mousemove",
       
  5343                 "x": _e.pageX,
       
  5344                 "y": _e.pageY,
       
  5345                 "time": _time
       
  5346             });
       
  5347 
       
  5348         })
       
  5349         .mouseout(function() {
       
  5350             _this.TooltipWidget.hide();
       
  5351             _this.rectangleFocus.attr({
       
  5352                 "opacity" : 0
       
  5353             })
       
  5354         })
       
  5355 }
       
  5356 
       
  5357 IriSP.StackGraphWidget.prototype.timeUpdateHandler = function() {
       
  5358     var _currentTime = this._Popcorn.currentTime(),
       
  5359         _x = (1000 * _currentTime / this.duration) * this.width;
       
  5360     this.rectangleProgress.attr({
       
  5361         "width" : _x
       
  5362     });
       
  5363     this.ligneProgress.attr({
       
  5364         "path" : "M" + _x + " 0L" + _x + " " + this.height
       
  5365     })
       
  5366 }
       
  5367 
       
  5368 IriSP.StackGraphWidget.prototype.clickHandler = function(event) {
       
  5369   /* Ctrl-C Ctrl-V'ed from another widget
       
  5370   */
       
  5371 
       
  5372   var relX = event.pageX - this.selector.offset().left;
       
  5373   var newTime = ((relX / this.width) * this.duration/1000).toFixed(2);
       
  5374   this._Popcorn.trigger("IriSP.StackGraphWidget.clicked", newTime);
       
  5375   this._Popcorn.currentTime(newTime);                                 
       
  5376 };
       
  5377 
       
  5378 IriSP.StackGraphWidget.prototype.updateTooltip = function(event) {
       
  5379     var _segment = Math.max(0,Math.min(this.groups.length - 1, Math.floor(this.slices * (event.pageX - this.selector.offset().left)/this.width))),
       
  5380         _valeurs = this.groups[_segment],
       
  5381         _width = this.width / this.slices,
       
  5382         _html = '<ul style="list-style: none; margin: 0; padding: 0;">' + IriSP._(this.tags).map(function(_tag, _i) {
       
  5383             return '<li style="clear: both;"><span style="float: left; width: 10px; height: 10px; margin: 2px; background: '
       
  5384                 + _tag.color
       
  5385                 + ';"></span>'
       
  5386                 + ~~(100 * _valeurs[_i])
       
  5387                 + '% de '
       
  5388                 + _tag.description
       
  5389                 + '</li>';
       
  5390         }).join('') + '</ul>';
       
  5391     this.TooltipWidget._shown = false; // Vraiment, on ne peut pas ouvrir le widget s'il n'est pas encore ouvert ?
       
  5392     this.TooltipWidget.show('','',(_segment + .5)* this.width / this.slices, 0);
       
  5393     this.TooltipWidget.selector.find(".tip").html(_html);
       
  5394     this.rectangleFocus.attr({
       
  5395         "x" : _segment * _width,
       
  5396         "opacity" : .4
       
  5397     })
       
  5398 }
       
  5399 
       
  5400 IriSP.TagCloudWidget = function(Popcorn, config, Serializer) {
       
  5401   IriSP.Widget.call(this, Popcorn, config, Serializer);
       
  5402 }
       
  5403 
       
  5404 IriSP.TagCloudWidget.prototype = new IriSP.Widget();
       
  5405 
       
  5406 IriSP.TagCloudWidget.prototype.draw = function() {
       
  5407     
       
  5408     var _urlRegExp = /https?:\/\/[0-9a-zA-Z\.%\/-_]+/g,
       
  5409         _stopWords = [
       
  5410             'aussi', 'and', 'avec', 'aux', 'bien', 'car', 'cette', 'comme', 'dans', 'donc', 'des', 'elle', 'encore', 'entre', 'est',
       
  5411             'être', 'eux', 'faire', 'fait', 'http', 'ici', 'ils', 'les', 'leur', 'leurs', 'mais', 'mes', 'même', 'mon', 'notre',
       
  5412             'non', 'nos', 'nous', 'ont', 'par', 'pas', 'peu', 'peut', 'plus', 'pour', 'que', 'qui', 'sans', 'ses' ,'son', 'sont', 'sur',
       
  5413             'tes', 'très', 'the', 'ton', 'tous', 'tout', 'une', 'votre', 'vos', 'vous' ],
       
  5414         _regexpword = /[^\s\.&;,'"!\?\d\(\)\+\[\]\\\…\-«»:\/]{3,}/g,
       
  5415         _words = {},
       
  5416         _showTitle = !this._config.excludeTitle,
       
  5417         _showDescription = !this._config.excludeDescription,
       
  5418         _excludePattern = this._config.excludePattern || null,
       
  5419         _tagCount = this._config.tagCount || 30;
       
  5420     if (typeof this._config.excludeWords !== "undefined" && this._config.excludeWords.length) {
       
  5421         IriSP._(this._config.excludeWords).each(function(_w) {
       
  5422             _stopWords.push(_w.toLowerCase());
       
  5423         });
       
  5424     }
       
  5425     
       
  5426     IriSP._(this._serializer._data.annotations).each(function(_annotation) {
       
  5427        if (_annotation.content && _annotation.content.description) {
       
  5428            var _txt = (_showTitle ? _annotation.content.title : '') + ' ' + (_showDescription ? _annotation.content.description : '')
       
  5429            IriSP._(_txt.toLowerCase().replace(_urlRegExp, '').match(_regexpword)).each(function(_mot) {
       
  5430                if (_stopWords.indexOf(_mot) == -1 && (_excludePattern == null || !_excludePattern.test(_mot))) {
       
  5431                    _words[_mot] = 1 + (_words[_mot] || 0);
       
  5432                }
       
  5433            })
       
  5434        } 
       
  5435     });
       
  5436     
       
  5437     _words = IriSP._(_words)
       
  5438         .chain()
       
  5439         .map(function(_v, _k) {
       
  5440             return {
       
  5441                 "word" : _k,
       
  5442                 "count" : _v
       
  5443             }
       
  5444         })
       
  5445         .filter(function(_v) {
       
  5446             return _v.count > 2;
       
  5447         })
       
  5448         .sortBy(function(_v) {
       
  5449             return - _v.count;
       
  5450         })
       
  5451         .first(_tagCount)
       
  5452         .value();
       
  5453     var _max = _words[0].count,
       
  5454         _min = Math.min(_words[_words.length - 1].count, _max - 1),
       
  5455         _scale = 16 / Math.sqrt(_max - _min),
       
  5456         _this = this,
       
  5457         _html = '<ul>'
       
  5458             + IriSP._(_words)
       
  5459                 .chain()
       
  5460                 .shuffle()
       
  5461                 .map(function(_word) {
       
  5462                     var _size = 10 + _scale * Math.sqrt(_word.count - _min);
       
  5463                     return '<li class="Ldt-TraceMe" style="font-size:'
       
  5464                         + _size
       
  5465                         + 'px;">'
       
  5466                         + _word.word
       
  5467                         + '</li>'
       
  5468                 })
       
  5469                 .value()
       
  5470                 .join("")
       
  5471             + '</ul>';
       
  5472     this.selector
       
  5473         .addClass("Ldt-TagCloud")
       
  5474         .html(_html);
       
  5475     this.selector.find("li").click(function() {
       
  5476         var _txt = this.textContent.replace(/(^[\s]+|[\s]+$)/g,'');
       
  5477         _this._Popcorn.trigger("IriSP.search.triggeredSearch", _txt);
       
  5478     });
       
  5479     this._Popcorn.listen("IriSP.search", IriSP.wrap(this, function(searchString) {
       
  5480         var _rgxp = new RegExp("(" + searchString.replace(/(\W)/g,'\\$1') + ")","gi");
       
  5481         this.selector.find("li").each(function(_i, _e) {
       
  5482             _e.innerHTML = searchString.length ?
       
  5483                 _e.textContent.replace(_rgxp,'<span class="Ldt-TagCloud-actif Ldt-TraceMe">$1</span>')
       
  5484                 : _e.textContent;
       
  5485         });
       
  5486     }));
       
  5487     this._Popcorn.listen("IriSP.search.closed", IriSP.wrap(this, this.endsearch));
       
  5488     this._Popcorn.listen("IriSP.search.cleared", IriSP.wrap(this, this.endsearch));
       
  5489 }
       
  5490 
       
  5491 IriSP.TagCloudWidget.prototype.endsearch = function() {
       
  5492     this.selector.find("li").each(function(_i, _e) {
       
  5493         _e.innerHTML = _e.textContent;
       
  5494     });
       
  5495 }
       
  5496 /* this widget displays a small tooltip */
       
  5497 IriSP.TooltipWidget = function(Popcorn, config, Serializer) {
       
  5498   IriSP.Widget.call(this, Popcorn, config, Serializer);
       
  5499   this._shown = false;
       
  5500   this._displayedText = "";
       
  5501   this._hideTimeout = -1;
       
  5502 };
       
  5503 
       
  5504 
       
  5505 IriSP.TooltipWidget.prototype = new IriSP.Widget();
       
  5506 
       
  5507 IriSP.TooltipWidget.prototype.draw = function() {
       
  5508   var templ = Mustache.to_html(IriSP.tooltipWidget_template);
       
  5509   // position the widget absolutely relative to document. --- NOOOO !!!!
       
  5510   this.selector.css({
       
  5511       "position": "absolute",
       
  5512       "top": 0,
       
  5513       "left": 0
       
  5514   });
       
  5515   this.selector.parent().css({
       
  5516       "position": "relative"
       
  5517   });
       
  5518   this.selector.append(templ);
       
  5519   var _this = this;
       
  5520   this.selector.mouseover(function() {
       
  5521       _this.hide();
       
  5522   });
       
  5523   this.hide();
       
  5524 
       
  5525 };
       
  5526 
       
  5527 IriSP.TooltipWidget.prototype.clear = function() {
       
  5528 	this.selector.find(".tiptext").html("");
       
  5529 };
       
  5530 
       
  5531 IriSP.TooltipWidget.prototype.show = function(text, color, x, y) {
       
  5532 
       
  5533   if (this._displayedText == text && this._shown)
       
  5534     return;
       
  5535 
       
  5536   this.selector.find(".tipcolor").css("background-color", color);
       
  5537   this._displayedText = text;
       
  5538   this.selector.find(".tiptext").html(text);
       
  5539   
       
  5540   var _tip = this.selector.find(".tip");
       
  5541   _tip.show();
       
  5542   _tip.css({
       
  5543       "left": Math.floor(x - _tip.outerWidth() / 2)+"px",
       
  5544       "top": Math.floor(y - _tip.outerHeight())+"px"
       
  5545   });
       
  5546   this._shown = true;
       
  5547 };
       
  5548 
       
  5549 IriSP.TooltipWidget.prototype.hide = function() {                                                   
       
  5550   this.selector.find(".tip").hide();
       
  5551   this._shown = false;  
       
  5552 };IriSP.TraceWidget = function(Popcorn, config, Serializer) {
       
  5553   IriSP.Widget.call(this, Popcorn, config, Serializer);
       
  5554   this.lastEvent = "";
       
  5555   var _this = this,
       
  5556     _listeners = {
       
  5557         "IriSP.createAnnotationWidget.addedAnnotation" : 0,
       
  5558         "IriSP.search.open" : 0,
       
  5559         "IriSP.search.closed" : 0,
       
  5560         "IriSP.search" : 0,
       
  5561         "IriSP.search.cleared" : 0,
       
  5562         "IriSP.search.matchFound" : 0,
       
  5563         "IriSP.search.noMatchFound" : 0,
       
  5564         "IriSP.search.triggeredSearch" : 0,
       
  5565         "IriSP.TraceWidget.MouseEvents" : 0,
       
  5566         "play" : 0,
       
  5567         "pause" : 0,
       
  5568         "volumechange" : 0,
       
  5569         "seeked" : 0,
       
  5570         "play" : 0,
       
  5571         "pause" : 0,
       
  5572         "timeupdate" : 2000
       
  5573     };
       
  5574     IriSP._(_listeners).each(function(_ms, _listener) {
       
  5575         var _f = function(_arg) {
       
  5576             _this.eventHandler(_listener, _arg);
       
  5577         }
       
  5578         if (_ms) {
       
  5579             _f = IriSP.underscore.throttle(_f, _ms);
       
  5580         }
       
  5581         _this._Popcorn.listen(_listener, _f);
       
  5582     });
       
  5583     this._Popcorn.listen("timeupdate", IriSP.underscore.throttle(function(_arg) {
       
  5584         _this.eventHandler(_listener, _arg);
       
  5585     }));
       
  5586     
       
  5587     this.tracer = IriSP.TraceManager(IriSP.jQuery).init_trace("test", this._config);
       
  5588     this.tracer.set_default_subject("default_subject");
       
  5589     this.tracer.trace("StartTracing", { "hello": "world" });
       
  5590     
       
  5591 }
       
  5592 
       
  5593 IriSP.TraceWidget.prototype = new IriSP.Widget();
       
  5594 
       
  5595 IriSP.TraceWidget.prototype.draw = function() {
       
  5596     this.mouseLocation = '';
       
  5597     var _this = this;
       
  5598     IriSP.jQuery(".Ldt-Widget").bind("click mouseover mouseout dragstart dragstop", function(_e) {
       
  5599         var _widget = IriSP.jQuery(this).attr("widget-type"),
       
  5600             _class = _e.target.className;
       
  5601         var _data = {
       
  5602             "type": _e.type,
       
  5603             "x": _e.clientX,
       
  5604             "y": _e.clientY,
       
  5605             "widget": _widget
       
  5606         }
       
  5607         if (typeof _class == "string" && _class.indexOf('Ldt-TraceMe') != -1) {
       
  5608             var _name = _e.target.localName,
       
  5609                 _id = _e.target.id,
       
  5610                 _text = _e.target.textContent.trim(),
       
  5611                 _title = _e.target.title,
       
  5612                 _value = _e.target.value;
       
  5613             _data.target = _name + (_id.length ? '#' + IriSP.jqEscape(_id) : '') + (_class.length ? ('.' + IriSP.jqEscape(_class).replace(/\s/g,'.')).replace(/\.Ldt-(Widget|TraceMe)/g,'') : '');
       
  5614             if (typeof _title == "string" && _title.length && _title.length < 140) {
       
  5615                 _data.title = _title;
       
  5616             }
       
  5617             if (typeof _text == "string" && _text.length && _text.length < 140) {
       
  5618                 _data.text = _text;
       
  5619             }
       
  5620             if (typeof _value == "string" && _value.length) {
       
  5621                 _data.value = _value;
       
  5622             }
       
  5623             _this._Popcorn.trigger('IriSP.TraceWidget.MouseEvents', _data);
       
  5624         } else {
       
  5625             //console.log(_e.type+','+_this.mouseLocation+','+_widget);
       
  5626             if (_e.type == "mouseover") {
       
  5627                 if (_this.mouseLocation != _widget) {
       
  5628                     _this._Popcorn.trigger('IriSP.TraceWidget.MouseEvents', _data);
       
  5629                 } else {
       
  5630                     if (typeof _this.moTimeout != "undefined") {
       
  5631                         clearTimeout(_this.moTimeout);
       
  5632                         delete _this.moTimeout;
       
  5633                     }
       
  5634                 }
       
  5635             }
       
  5636             if (_e.type == "click") {
       
  5637                 _this._Popcorn.trigger('IriSP.TraceWidget.MouseEvents', _data);
       
  5638             }
       
  5639             if (_e.type == "mouseout") {
       
  5640                 if (typeof _this.moTimeout != "undefined") {
       
  5641                     clearTimeout(_this.moTimeout);
       
  5642                 }
       
  5643                 _this.moTimeout = setTimeout(function() {
       
  5644                    if (_data.widget != _this.mouseLocation) {
       
  5645                        _this._Popcorn.trigger('IriSP.TraceWidget.MouseEvents', _data);
       
  5646                    }
       
  5647                 },100);
       
  5648             }
       
  5649         }
       
  5650         _this.mouseLocation = _widget;
       
  5651     });
       
  5652 }
       
  5653 
       
  5654 IriSP.TraceWidget.prototype.eventHandler = function(_listener, _arg) {
       
  5655     var _traceName = 'Mdp_';
       
  5656     if (typeof _arg == "string" || typeof _arg == "number") {
       
  5657         _arg = { "value" : _arg }
       
  5658     }
       
  5659     if (typeof _arg == "undefined") {
       
  5660         _arg = {}
       
  5661     }
       
  5662     switch(_listener) {
       
  5663         case 'IriSP.TraceWidget.MouseEvents':
       
  5664             _traceName += _arg.widget + '_' + _arg.type;
       
  5665             delete _arg.widget;
       
  5666             delete _arg.type;
       
  5667         break;
       
  5668         case 'timeupdate':
       
  5669         case 'play':
       
  5670         case 'pause':
       
  5671             _arg.time = this._Popcorn.currentTime() * 1000;
       
  5672         case 'seeked':
       
  5673         case 'volumechange':
       
  5674             _traceName += 'Popcorn_' + _listener;
       
  5675         break;
       
  5676         default:
       
  5677             _traceName += _listener.replace('IriSP.','').replace('.','_');
       
  5678     }
       
  5679     this.lastEvent = _traceName;
       
  5680     this.tracer.trace(_traceName, _arg);
       
  5681     if (this._config.js_console) {
       
  5682         console.log("tracer.trace('" + _traceName + "', " + JSON.stringify(_arg) + ");");
       
  5683     }
       
  5684 }
       
  5685 /* a widget that displays tweet - used in conjunction with the polemicWidget */
       
  5686 
       
  5687 IriSP.TweetsWidget = function(Popcorn, config, Serializer) {
       
  5688   IriSP.Widget.call(this, Popcorn, config, Serializer);
       
  5689 
       
  5690   this._displayingTweet = false;
       
  5691   this._timeoutId = undefined; 
       
  5692   this._hidden = false; /* hidden means that the createAnnotationWidget is shown */
       
  5693 };
       
  5694 
       
  5695 
       
  5696 IriSP.TweetsWidget.prototype = new IriSP.Widget();
       
  5697 
       
  5698 
       
  5699 IriSP.TweetsWidget.prototype.drawTweet = function(annotation) {
       
  5700     if (this._hidden)
       
  5701       return;
       
  5702     
       
  5703     var title = IriSP.formatTweet(annotation.content.title);
       
  5704     var img = annotation.content.img.src;
       
  5705     if (typeof(img) === "undefined" || img === "" || img === "None") {
       
  5706       img = this.default_profile_picture;
       
  5707     }
       
  5708 
       
  5709     var imageMarkup = IriSP.templToHTML("<img src='{{src}}' alt='user image'></img>", 
       
  5710                                        {src : img});
       
  5711     
       
  5712     if (typeof(IriSP.get_aliased(annotation.meta, ["dc:source", "source"]).content) !== "undefined") {
       
  5713       var tweetContents = JSON.parse(IriSP.get_aliased(annotation.meta, ["dc:source", "source"]).content);
       
  5714       var creator = tweetContents.user.screen_name;
       
  5715       var real_name = tweetContents.user.name;
       
  5716 
       
  5717       imageMarkup = IriSP.templToHTML("<a href='http://twitter.com/{{creator}}'><img src='{{src}}' alt='user image'></img></a>", 
       
  5718                                        {src : img, creator: creator});
       
  5719             
       
  5720       var formatted_date = new Date(tweetContents.created_at).toLocaleDateString();
       
  5721       title = IriSP.templToHTML("<a class='Ldt-tweet_userHandle' href='http://twitter.com/{{creator}}'>@{{creator}}</a> - " + 
       
  5722                                 "<div class='Ldt-tweet_realName'>{{real_name}}</div>" +
       
  5723                                 "<div class='Ldt-tweet_tweetContents'>{{{ contents }}}</div>" +
       
  5724                                 "<div class='Ldt-tweet_date'>{{ date }}</div>", 
       
  5725                                 {creator: creator, real_name: real_name, contents : title, date : formatted_date});
       
  5726 
       
  5727       this.selector.find(".Ldt-TweetReply").attr("href", "http://twitter.com/home?status=@" + creator + ":%20");
       
  5728 
       
  5729 
       
  5730       var rtText = Mustache.to_html("http://twitter.com/home?status=RT @{{creator}}: {{text}}",
       
  5731                                     {creator: creator, text: IriSP.encodeURI(annotation.content.title)});
       
  5732       this.selector.find(".Ldt-Retweet").attr("href", rtText);
       
  5733     }
       
  5734 
       
  5735     this.selector.find(".Ldt-tweetContents").html(title);
       
  5736     this.selector.find(".Ldt-tweetAvatar").html(imageMarkup);
       
  5737     this.selector.show("blind", 250); 
       
  5738 };
       
  5739 
       
  5740 IriSP.TweetsWidget.prototype.displayTweet = function(annotation) {
       
  5741   if (this._displayingTweet === false) {
       
  5742     this._displayingTweet = true;
       
  5743   } else {
       
  5744     window.clearTimeout(this._timeoutId);
       
  5745   }
       
  5746 
       
  5747   this.drawTweet(annotation);
       
  5748 
       
  5749   var time = this._Popcorn.currentTime();  
       
  5750   this._timeoutId = window.setTimeout(IriSP.wrap(this, this.clearPanel), this.tweet_display_period);
       
  5751 };
       
  5752 
       
  5753 
       
  5754 IriSP.TweetsWidget.prototype.clearPanel = function() {  
       
  5755     this._displayingTweet = false;
       
  5756     this._timeoutId = undefined;
       
  5757     this.closePanel();
       
  5758     
       
  5759 };
       
  5760 
       
  5761 IriSP.TweetsWidget.prototype.closePanel = function() {
       
  5762     if (this._timeoutId != undefined) {
       
  5763       /* we're called from the "close window" link */
       
  5764       /* cancel the timeout */
       
  5765       window.clearTimeout(this._timeoutId);
       
  5766       this._timeoutId = null;
       
  5767     }
       
  5768     
       
  5769     this.selector.hide("blind", 400);
       
  5770     
       
  5771 };
       
  5772 
       
  5773 /* cancel the timeout if the user clicks on the keep panel open button */
       
  5774 IriSP.TweetsWidget.prototype.keepPanel = function() {
       
  5775     if (this._timeoutId != undefined) {
       
  5776       /* we're called from the "close window" link */
       
  5777       /* cancel the timeout */
       
  5778       window.clearTimeout(this._timeoutId);
       
  5779       this._timeoutId = null;
       
  5780     }
       
  5781 };
       
  5782 
       
  5783 IriSP.TweetsWidget.prototype.draw = function() {
       
  5784   var _this = this;
       
  5785   
       
  5786   var tweetMarkup = IriSP.templToHTML(IriSP.tweetWidget_template, {"share_template" : IriSP.share_template});
       
  5787   this.selector.append(tweetMarkup);
       
  5788   this.selector.hide();
       
  5789   this.selector.find(".Ldt-tweetWidgetMinimize").click(IriSP.wrap(this, this.closePanel));
       
  5790   this.selector.find(".Ldt-tweetWidgetKeepOpen").click(IriSP.wrap(this, this.keepPanel));
       
  5791   
       
  5792   this._Popcorn.listen("IriSP.PolemicTweet.click", IriSP.wrap(this, this.PolemicTweetClickHandler));
       
  5793   this._Popcorn.listen("IriSP.PlayerWidget.AnnotateButton.clicked", 
       
  5794                         IriSP.wrap(this, this.handleAnnotateSignal));  
       
  5795 };
       
  5796 
       
  5797 IriSP.TweetsWidget.prototype.PolemicTweetClickHandler = function(tweet_id) {  
       
  5798   var index, annotation;
       
  5799   for (index in this._serializer._data.annotations) {
       
  5800     annotation = this._serializer._data.annotations[index];
       
  5801     
       
  5802     if (annotation.id === tweet_id)
       
  5803       break;
       
  5804   }
       
  5805     
       
  5806   if (annotation.id !== tweet_id)
       
  5807       /* we haven't found it */
       
  5808       return;
       
  5809   
       
  5810   this.displayTweet(annotation);
       
  5811   return;
       
  5812 };
       
  5813 
       
  5814 /** handle clicks on the annotate button by hiding/showing itself */
       
  5815 IriSP.TweetsWidget.prototype.handleAnnotateSignal = function() {
       
  5816   if (this._hidden == false) {
       
  5817     this.selector.hide();
       
  5818     this._hidden = true;
       
  5819   } else {
       
  5820     if (this._displayingTweet !== false)
       
  5821       this.selector.show();
       
  5822       
       
  5823     
       
  5824     this._hidden = false;
       
  5825   }
       
  5826 };/** @class This class implement a serializer for the JSON-Cinelab format
       
  5827     @params DataLoader a dataloader reference
       
  5828     @url the url from which to get our cinelab
       
  5829  */
       
  5830 IriSP.JSONSerializer = function(DataLoader, url) {
       
  5831   IriSP.Serializer.call(this, DataLoader, url);
       
  5832 };
       
  5833 
       
  5834 IriSP.JSONSerializer.prototype = new IriSP.Serializer();
       
  5835 
       
  5836 /** serialize data */
       
  5837 IriSP.JSONSerializer.prototype.serialize = function(data) {
       
  5838   return JSON.stringify(data);
       
  5839 };
       
  5840 
       
  5841 /** deserialize data */
       
  5842 IriSP.JSONSerializer.prototype.deserialize = function(data) {
       
  5843   return JSON.parse(data);
       
  5844 };
       
  5845 
       
  5846 /** load JSON-cinelab data and also sort the annotations by start time
       
  5847     @param callback function to call when the data is ready.
       
  5848  */
       
  5849 IriSP.JSONSerializer.prototype.sync = function(callback, force_refresh) {
       
  5850   /* we don't have to do much because jQuery handles json for us */
       
  5851 
       
  5852   var self = this;
       
  5853 
       
  5854   var fn = function(data) {
       
  5855 	  //TODO: seems taht data can be null here
       
  5856 	  if (data !== null) {
       
  5857 		  self._data = data;  
       
  5858 	      if (typeof(self._data["annotations"]) === "undefined" ||
       
  5859 	          self._data["annotations"] === null)
       
  5860 	          self._data["annotations"] = [];
       
  5861 	      
       
  5862 	      // sort the data too       
       
  5863 	      self._data["annotations"].sort(function(a, b) 
       
  5864 	          { var a_begin = +a.begin;
       
  5865 	            var b_begin = +b.begin;
       
  5866 	            return a_begin - b_begin;
       
  5867 	          });
       
  5868 	  }     
       
  5869       callback(data);      
       
  5870   };
       
  5871   this._DataLoader.get(this._url, fn, force_refresh);
       
  5872 };
       
  5873 
       
  5874 /** @return the metadata about the media being read FIXME: always return the first media. */
       
  5875 IriSP.JSONSerializer.prototype.currentMedia = function() {  
       
  5876   return (typeof this._data.medias == "object" && this._data.medias.length) ? this._data.medias[0] : IriSP.__jsonMetadata.medias[0];
       
  5877 };
       
  5878 
       
  5879 IriSP.JSONSerializer.prototype.getDuration = function() {
       
  5880     var _m = this.currentMedia();
       
  5881     if (_m === null || typeof _m.meta == "undefined") {
       
  5882         return 0;
       
  5883     }
       
  5884     return +(IriSP.get_aliased(_m.meta, ["dc:duration", "duration"]) || 0);
       
  5885 }
       
  5886 
       
  5887 
       
  5888 /** searches for an annotation which matches title, description and keyword 
       
  5889    "" matches any field. 
       
  5890    Note: it ignores tweets.
       
  5891    @return a list of matching ids.
       
  5892 */    
       
  5893 IriSP.JSONSerializer.prototype.searchAnnotations = function(title, description, keyword) {
       
  5894     /* we can have many types of annotations. We want search to only look for regular segments */
       
  5895     /* the next two lines are a bit verbose because for some test data, _serializer.data.view is either
       
  5896        null or undefined.
       
  5897     */
       
  5898     var view;
       
  5899 
       
  5900     if (typeof(this._data.views) !== "undefined" && this._data.views !== null)
       
  5901        view = this._data.views[0];
       
  5902 
       
  5903     var searchViewType = "";
       
  5904 
       
  5905     if(typeof(view) !== "undefined" && typeof(view.annotation_types) !== "undefined" && view.annotation_types.length > 1) {
       
  5906             searchViewType = view.annotation_types[0];
       
  5907     }
       
  5908 
       
  5909     var filterfn = function(annotation) {
       
  5910       if( searchViewType  != "" && 
       
  5911           typeof(annotation.meta) !== "undefined" && 
       
  5912           typeof(annotation.meta["id-ref"]) !== "undefined" &&
       
  5913           annotation.meta["id-ref"] !== searchViewType) {
       
  5914         return true; // don't pass
       
  5915       } else {
       
  5916           return false;
       
  5917       }
       
  5918     };
       
  5919 
       
  5920     return this.searchAnnotationsFilter(title, description, keyword, filterfn);
       
  5921 
       
  5922 };
       
  5923 
       
  5924 /* only look for tweets */
       
  5925 IriSP.JSONSerializer.prototype.searchTweets = function(title, description, keyword) {
       
  5926     /* we can have many types of annotations. We want search to only look for regular segments */
       
  5927     /* the next two lines are a bit verbose because for some test data, _serializer.data.view is either
       
  5928        null or undefined.
       
  5929     */
       
  5930     
       
  5931     var searchViewType = this.getTweets();
       
  5932     if (typeof(searchViewType) === "undefined") {
       
  5933       var view;
       
  5934       
       
  5935       if (typeof(this._data.views) !== "undefined" && this._data.views !== null)
       
  5936          view = this._data.views[0];    
       
  5937 
       
  5938       if(typeof(view) !== "undefined" && typeof(view.annotation_types) !== "undefined" && view.annotation_types.length > 1) {
       
  5939               searchViewType = view.annotation_types[0];
       
  5940       }
       
  5941     }
       
  5942     var filterfn = function(annotation) {
       
  5943       if( searchViewType  != "" && 
       
  5944           typeof(annotation.meta) !== "undefined" && 
       
  5945           typeof(annotation.meta["id-ref"]) !== "undefined" &&
       
  5946           annotation.meta["id-ref"] === searchViewType) {
       
  5947         return false; // pass
       
  5948       } else {
       
  5949           return true;
       
  5950       }
       
  5951     };
       
  5952 
       
  5953     return this.searchAnnotationsFilter(title, description, keyword, filterfn);
       
  5954 
       
  5955 };
       
  5956 
       
  5957 /**
       
  5958   search an annotation according to its title, description and keyword
       
  5959   @param filter a function to filter the results with. Used to select between annotation types.
       
  5960  */    
       
  5961 IriSP.JSONSerializer.prototype.searchAnnotationsFilter = function(title, description, keyword, filter) {
       
  5962 
       
  5963     var rTitle;
       
  5964     var rDescription;
       
  5965     var rKeyword;
       
  5966     /* match anything if given the empty string */
       
  5967     if (title == "")
       
  5968       title = ".*";
       
  5969     if (description == "")
       
  5970       description = ".*";
       
  5971     if (keyword == "")
       
  5972       keyword = ".*";
       
  5973     
       
  5974     rTitle = new RegExp(title, "i");  
       
  5975     rDescription = new RegExp(description, "i");  
       
  5976     rKeyword = new RegExp(keyword, "i");  
       
  5977     
       
  5978     var ret_array = [];
       
  5979     
       
  5980     var i;
       
  5981     for (i in this._data.annotations) {
       
  5982       var annotation = this._data.annotations[i];
       
  5983       
       
  5984       /* filter the annotations whose type is not the one we want */
       
  5985       if (filter(annotation)) {
       
  5986           continue;
       
  5987       }
       
  5988       
       
  5989       if (rTitle.test(annotation.content.title) && 
       
  5990           rDescription.test(annotation.content.description)) {
       
  5991           /* FIXME : implement keyword support */
       
  5992           ret_array.push(annotation);
       
  5993       }
       
  5994     }
       
  5995     
       
  5996     return ret_array;
       
  5997 };
       
  5998 
       
  5999 /** breaks a string in words and searches each of these words. Returns an array
       
  6000    of objects with the id of the annotation and its number of occurences.
       
  6001    
       
  6002    @param searchString a string of words.
       
  6003    FIXME: optimize ? seems to be n^2 in the worst case.
       
  6004 */
       
  6005 IriSP.JSONSerializer.prototype.searchOccurences = function(searchString) {
       
  6006   var ret = { };
       
  6007   var keywords = searchString.split(/\s+/);
       
  6008   
       
  6009   for (var i in keywords) {
       
  6010     var keyword = keywords[i];
       
  6011     
       
  6012     // search this keyword in descriptions and title
       
  6013     var found_annotations = []
       
  6014     found_annotations = found_annotations.concat(this.searchAnnotations(keyword, "", ""));
       
  6015     found_annotations = found_annotations.concat(this.searchAnnotations("", keyword, ""));
       
  6016     
       
  6017     for (var j in found_annotations) {
       
  6018       var current_annotation = found_annotations[j];
       
  6019       
       
  6020       if (!ret.hasOwnProperty(current_annotation.id)) {
       
  6021         ret[current_annotation.id] = 1;
       
  6022       } else {
       
  6023         ret[current_annotation.id] += 1;
       
  6024       }
       
  6025       
       
  6026     }
       
  6027 
       
  6028   };
       
  6029   
       
  6030   return ret;
       
  6031 };
       
  6032 
       
  6033 /** breaks a string in words and searches each of these words. Returns an array
       
  6034    of objects with the id of the annotation and its number of occurences.
       
  6035    
       
  6036    FIXME: optimize ? seems to be n^2 in the worst case.
       
  6037 */
       
  6038 IriSP.JSONSerializer.prototype.searchTweetsOccurences = function(searchString) {
       
  6039   var ret = { };
       
  6040   var keywords = searchString.split(/\s+/);
       
  6041   
       
  6042   for (var i in keywords) {
       
  6043     var keyword = keywords[i];
       
  6044     
       
  6045     // search this keyword in descriptions and title
       
  6046     var found_annotations = []
       
  6047     found_annotations = found_annotations.concat(this.searchTweets(keyword, "", ""));
       
  6048     found_annotations = found_annotations.concat(this.searchTweets("", keyword, ""));
       
  6049     
       
  6050     for (var j in found_annotations) {
       
  6051       var current_annotation = found_annotations[j];
       
  6052       
       
  6053       if (!ret.hasOwnProperty(current_annotation.id)) {
       
  6054         ret[current_annotation.id] = 1;
       
  6055       } else {
       
  6056         ret[current_annotation.id] += 1;
       
  6057       }
       
  6058       
       
  6059     }
       
  6060 
       
  6061   };
       
  6062   
       
  6063   return ret;
       
  6064 };
       
  6065 
       
  6066 /** returns all the annotations that are displayable at the moment 
       
  6067    NB: only takes account the first type of annotations - ignores tweets 
       
  6068    currentTime is in seconds.
       
  6069    
       
  6070    @param currentTime the time at which we search.
       
  6071    @param (optional) the if of the type of the annotations we want to get.
       
  6072  */
       
  6073 
       
  6074 IriSP.JSONSerializer.prototype.currentAnnotations = function(currentTime, id) {
       
  6075   var view;
       
  6076   var currentTimeMs = 1000 * currentTime;
       
  6077 
       
  6078   if (typeof(id) === "undefined") {
       
  6079       var legal_ids = this.getNonTweetIds();
       
  6080   } else {
       
  6081       legal_ids = [id];
       
  6082   }
       
  6083   
       
  6084   var ret_array = [];
       
  6085   
       
  6086   var i;
       
  6087   
       
  6088   for (i in this._data.annotations) {
       
  6089     var annotation = this._data.annotations[i];
       
  6090     
       
  6091     if (IriSP.underscore.include(legal_ids, annotation.meta["id-ref"]) && 
       
  6092         annotation.begin <= currentTimeMs &&
       
  6093         annotation.end >= currentTimeMs)
       
  6094           ret_array.push(annotation);
       
  6095   }
       
  6096  
       
  6097   if (ret_array == []) {
       
  6098     console.log("ret_array empty, ", legal_ids);
       
  6099   }
       
  6100   
       
  6101   return ret_array;
       
  6102 };
       
  6103 
       
  6104 /** return the current chapitre
       
  6105     @param currentTime the current time, in seconds.
       
  6106 */
       
  6107 IriSP.JSONSerializer.prototype.currentChapitre = function(currentTime) {
       
  6108   return this.currentAnnotations(currentTime, this.getChapitrage())[0];
       
  6109 };
       
  6110 
       
  6111 /** returns a list of ids of tweet lines (aka: groups in cinelab) */
       
  6112 IriSP.JSONSerializer.prototype.getTweetIds = function() {
       
  6113   if (IriSP.null_or_undefined(this._data.lists) || IriSP.null_or_undefined(this._data.lists) ||
       
  6114       IriSP.null_or_undefined(this._data.views) || IriSP.null_or_undefined(this._data.views[0]))
       
  6115     return [];
       
  6116 
       
  6117   
       
  6118   /* Get the displayable types
       
  6119      We've got to jump through a few hoops because the json sometimes defines
       
  6120      fields with underscores and sometimes with dashes
       
  6121   */
       
  6122   var annotation_types = IriSP.get_aliased(this._data.views[0], ["annotation_types", "annotation-types"]);
       
  6123   if (annotation_types === null) {
       
  6124       console.log("neither view.annotation_types nor view.annotation-types are defined");      
       
  6125       return;
       
  6126   }
       
  6127 
       
  6128   var available_types = IriSP.get_aliased(this._data, ["annotation_types", "annotation-types"]);    
       
  6129   if (available_types === null) {
       
  6130       console.log("neither view.annotation_types nor view.annotation-types are defined");      
       
  6131       return;
       
  6132   }
       
  6133   
       
  6134   var potential_types = [];
       
  6135   
       
  6136   // Get the list of types which contain "Tw" in their content
       
  6137   for (var i = 0; i < available_types.length; i++) {
       
  6138     if (/Tw/i.test(IriSP.get_aliased(available_types[i], ['dc:title', 'title']))) {
       
  6139       potential_types.push(available_types[i].id);
       
  6140     }
       
  6141   }
       
  6142   
       
  6143   // Get the intersection of both.
       
  6144   var tweetsId = IriSP.underscore.intersection(annotation_types, potential_types);
       
  6145   
       
  6146   return tweetsId;
       
  6147 };
       
  6148 
       
  6149 /** this function returns a list of lines which are not tweet lines */
       
  6150 IriSP.JSONSerializer.prototype.getNonTweetIds = function() {
       
  6151   if (IriSP.null_or_undefined(this._data.lists) || IriSP.null_or_undefined(this._data.lists) ||
       
  6152       IriSP.null_or_undefined(this._data.views) || IriSP.null_or_undefined(this._data.views[0]))
       
  6153     return [];
       
  6154 
       
  6155   /* Get the displayable types
       
  6156      We've got to jump through a few hoops because the json sometimes defines
       
  6157      fields with underscores and sometimes with dashes
       
  6158   */
       
  6159   var annotation_types = IriSP.get_aliased(this._data.views[0], ["annotation_types", "annotation-types"]);
       
  6160   if (annotation_types === null) {
       
  6161       console.log("neither view.annotation_types nor view.annotation-types are defined");      
       
  6162       return;
       
  6163   }
       
  6164 
       
  6165   var available_types = IriSP.get_aliased(this._data, ["annotation_types", "annotation-types"]);    
       
  6166   if (available_types === null) {
       
  6167       console.log("neither view.annotation_types nor view.annotation-types are defined");      
       
  6168       return;
       
  6169   }
       
  6170 
       
  6171   var potential_types = [];
       
  6172   
       
  6173   // Get the list of types which do not contain "Tw" in their content
       
  6174   for (var i = 0; i < available_types.length; i++) {
       
  6175     if (!(/Tw/i.test(IriSP.get_aliased(available_types[i], ['dc:title', 'title'])))) {
       
  6176       potential_types.push(available_types[i].id);
       
  6177     }
       
  6178   }
       
  6179 
       
  6180   // Get the intersection of both.
       
  6181   var nonTweetsId = IriSP.underscore.intersection(annotation_types, potential_types);
       
  6182   
       
  6183   return nonTweetsId;
       
  6184   
       
  6185 };
       
  6186 
       
  6187 /** return the id of the ligne de temps which contains name
       
  6188     @param name of the ligne de temps
       
  6189 */
       
  6190 IriSP.JSONSerializer.prototype.getId = function(name) {
       
  6191    var available_types = IriSP.get_aliased(this._data, ["annotation_types", "annotation-types"]);  
       
  6192    
       
  6193   if (available_types == null)
       
  6194     return;
       
  6195 
       
  6196   name = name.toUpperCase();
       
  6197   var e;  
       
  6198   e = IriSP.underscore.find(available_types, 
       
  6199     function(entry) {
       
  6200         if (IriSP.get_aliased(entry, ['dc:title', 'title']) === null)
       
  6201           return false;
       
  6202         return (entry["dc:title"].toUpperCase().indexOf(name) !== -1);
       
  6203     });
       
  6204   
       
  6205   if (typeof(e) === "undefined")
       
  6206     return;
       
  6207     
       
  6208   var id = e.id;
       
  6209 
       
  6210   return id;
       
  6211 };
       
  6212 
       
  6213 /** return the list of id's of the ligne de temps which contains name
       
  6214     @param name of the ligne de temps
       
  6215 */
       
  6216 IriSP.JSONSerializer.prototype.getIds = function(name) {
       
  6217    var available_types = IriSP.get_aliased(this._data, ["annotation_types", "annotation-types"]);  
       
  6218    
       
  6219   if (available_types == null)
       
  6220     return;
       
  6221 
       
  6222   name = name.toUpperCase();
       
  6223   var e = [];  
       
  6224   e = IriSP.underscore.filter(available_types, 
       
  6225                                   function(entry) { return (IriSP.get_aliased(entry, ['dc:title', 'title']).toUpperCase().indexOf(name) !== -1) });
       
  6226   return IriSP.underscore.pluck(e, "id");  
       
  6227 };
       
  6228 
       
  6229 /** return the id of the ligne de temps named "Chapitrage" */
       
  6230 IriSP.JSONSerializer.prototype.getChapitrage = function() {
       
  6231   var val = this.getId("Chapitrage");
       
  6232   if (typeof(val) === "undefined")
       
  6233     val = this.getId("Chapter");   
       
  6234   if (typeof(val) === "undefined")
       
  6235     val = this.getId("Chapit");
       
  6236   if (typeof(val) === "undefined")
       
  6237     val = this.getId("Chap");
       
  6238     
       
  6239   return val;
       
  6240 };
       
  6241 
       
  6242 /** return the id of the ligne de temps named "Tweets" */
       
  6243 IriSP.JSONSerializer.prototype.getTweets = function() {
       
  6244   var val = this.getId("Tweets");
       
  6245   if (typeof(val) === "undefined")
       
  6246     val = this.getId("Tweet");
       
  6247   if (typeof(val) === "undefined")
       
  6248     val = this.getId("Twitter");
       
  6249   if (typeof(val) === "undefined")
       
  6250     val = this.getId("twit");
       
  6251   if (typeof(val) === "undefined")
       
  6252     val = this.getId("Polemic");
       
  6253   
       
  6254   return val;
       
  6255 };
       
  6256 
       
  6257 /** return the id of the ligne de temps named "Contributions" */
       
  6258 IriSP.JSONSerializer.prototype.getContributions = function() {
       
  6259   var val = this.getId("Contribution");
       
  6260   if (typeof(val) === "undefined")
       
  6261     val = this.getId("Particip");   
       
  6262   if (typeof(val) === "undefined")
       
  6263     val = this.getId("Contr");
       
  6264   if (typeof(val) === "undefined")
       
  6265     val = this.getId("Publ");
       
  6266     
       
  6267   return val;
       
  6268 };