From 3697ced7893f3ba2363f6709516937fe98a2ef82 Mon Sep 17 00:00:00 2001 From: riccardo Date: Tue, 26 Apr 2011 19:27:44 +0200 Subject: [PATCH] restyle of the code. Still to be added: click on the image to set position and optionally double click (necessary?). Fix the popup dialog on play (optimization, mouse events while playing do not update the popup position, popup position: where? etcetera) --- telemeta/htdocs/css/telemeta.css | 137 +-- telemeta/htdocs/images/transparent.png | Bin 0 -> 172 bytes telemeta/htdocs/js/application.js | 165 ++-- telemeta/htdocs/js/playerUtils.js | 182 +--- telemeta/htdocs/timeside/src/divmarker.js | 558 +++++------ telemeta/htdocs/timeside/src/markermap.js | 422 ++++---- telemeta/htdocs/timeside/src/player.js | 927 +++++++++++------- telemeta/htdocs/timeside/src/ruler.js | 927 +++++++++--------- telemeta/htdocs/timeside/src/rulermarker.js | 415 +++----- telemeta/htdocs/timeside/src/timeside.js | 709 ++++++++++++-- telemeta/templates/telemeta_default/base.html | 4 + .../telemeta_default/mediaitem_detail.html | 5 +- telemeta/urls.py | 9 +- 13 files changed, 2496 insertions(+), 1964 deletions(-) create mode 100644 telemeta/htdocs/images/transparent.png diff --git a/telemeta/htdocs/css/telemeta.css b/telemeta/htdocs/css/telemeta.css index 3ee4759a..49ed6ece 100644 --- a/telemeta/htdocs/css/telemeta.css +++ b/telemeta/htdocs/css/telemeta.css @@ -862,14 +862,14 @@ a.image-link { } -.tab_unselected { +.tab_unselected, .tab_unselected:hover, .tab_unselected:visited { background-color: #cccccc; font-weight: normal; color: #333333; border: 1px solid #cccccc; - z-index: 0; + } -.tab_selected { +.tab_selected, .tab_selected:hover, .tab_selected:visited { background-color: #ffffff; color: #000000; font-weight: bold; @@ -877,9 +877,16 @@ a.image-link { border-right: 1px solid #999999; border-left: 1px solid #999999; border-bottom: 1px solid #ffffff; - z-index: 10; + } +.tab, .tab:hover, .tab:visited{ + margin-top: 1ex; + display: inline-block; + margin-left: 0px; + padding: 1ex; + -moz-border-radius: 1ex 1ex 0ex 0ex; -webkit-border-radius: 1ex 1ex 0ex 0ex; border-radius: 1ex 1ex 0ex 0ex; +} .roundBorder4{ /*padding: 0.3em 0.8em 0.8em 0.8em; @@ -918,103 +925,61 @@ a.image-link { border: 0px !important; } -.markersdivIndexLabel{ - color: #fff; - background-image:url("/images/marker_tiny.png"); - background-repeat:no-repeat; - background-position:center center; - font-size: 90%; - font-weight:bold; - display:inline-block; - width:3ex; - text-align: center; - font-family: monospace; - margin-right:2ex; +.markerdiv div{ + padding:1ex; } -.markersdivIndexLabel span{ - position:relative; - top:-2px; +.markerdiv div[zero_top_padding]{ /*mathces any div with the attribute zero_top_padding set (whatever the value)*/ + padding-top:0px; } -.markersdivOffset{ - margin-right:1ex; - font-style: italic; +.markerdiv div *{ + vertical-align:middle; + font-family: sans-serif; } -.markersdivTopElement{ - /* vertical-align:middle;*/ +.markerdiv div input, .markerdiv div textarea{ + margin:0px; + padding:2px; } -.markersdivTitle{ - font-weight:bold; - margin:0; - padding:0; - position:relative; - top:-0.5ex; - /*margin-right:margin*/ -} -.markersdivEdit, .markersdivEdit:hover, .markersdivEdit:visited, -.markersdivEdit:link, .markersdivEdit:link:hover, .markersdivEdit:visited:hover{ - font-weight:bold; - float:right; - color:#333; - background-image: url('/images/edit_marker.png'); - background-repeat:no-repeat; - background-position: center left; - border:2px solid #666; - height:2.2ex; - width: 8.5ex; - /*padding:0px 1ex 2ex 3ex;*/ /*top right bottom left*/ - font-size: 65%; - margin-left: 1ex; - margin-right:1ex; - position:relative; - /*top:-0.5ex;*/ +.markerdiv .ts-marker{ + background-image: url('/images/marker_tiny.png'); text-align: center; min-width:3ex; } -.markersdivEdit span{ - position:absolute;top:-0.75ex;left:3ex; -} -.markersdivDelete, .markersdivAddPlaylist{ - border:0px; - float:right; - background-image: url('/images/del_marker.png'); +.markerdiv .ts-marker, .markerdiv .markersdivOffset, .markerdiv .markersdivTitle, .markerdiv .markersdivAddPlaylist, .markerdiv .markersdivEdit{margin-right:.8ex;} +.markerdiv div a, .markerdiv div a:visited, .markerdiv div a:hover{ + display: inline-block; /*otherwise width and height do not work*/ background-repeat: no-repeat; - background-position: top center; - /*padding:1ex 1ex 1ex 1ex; top right bottom left. The padding is only to show the element */ - width: 15px; - height:15px; -} -.markersdivDelete{ - background-image: url('/images/del_marker.png'); -} -.markersdivAddPlaylist{ - background-image: url('/images/add_playlist_marker.png'); + text-decoration: none; } -.markersdivDescription{ - margin:0ex 0px 0ex 0px; /*top right bottom left*/ - font-family: sans-serif; - padding:0px; - width:100%; +.markerdiv .ts-marker, .markerdiv .ts-marker:hover, .markerdiv .ts-marker:visited{ + font-family: monospace; background: #e65911;color: #FFF;padding-left: .3ex; padding-right:.3ex; +} +.markersdivDelete{ background-image: url('/images/del_marker.png');width:15px;height:2ex;} +.markersdivAddPlaylist{ background-image: url('/images/add_playlist_marker.png');width:13px;height:2ex; } +.markersdivTitle{ font-weight:bold;} +.markersdivEdit, .markersdivEdit:hover, .markersdivEdit:visited{ + line-height: normal; + color:#000; + background-position: -2px center; + padding-left: 13px; padding-right: 2px; + font-size: 65%; + border:2px solid #666; + background-color: #fff; + background-image: url('/images/edit_marker.png'); + -moz-border-radius: 1ex; -webkit-border-radius: 1ex; border-radius: 1ex; } -.markersdivSave, .markersdivSave:hover, .markersdivSave:visited, -.markersdivSave:link, .markersdivSave:link:hover, .markersdivSave:visited:hover{ +.markersdivSave, .markersdivSave:hover, .markersdivSave:visited{ background-color: #087714; color: #fff; font-weight: bold; - /*padding: 0.3em 0.8em 0.3em 0.8em; - padding:5px 10px 5px 26px;*/ - display:block; - width: 4ex; - padding: 1ex .5ex 1ex 3.5ex; - background-repeat: no-repeat; - background-position: 1ex 1ex; - -moz-border-radius: 1ex 1ex 1ex 1ex; - -webkit-border-radius: 1ex 1ex 1ex 1ex; - border-radius: 1ex 1ex 1ex 1ex; + padding:.7ex; padding-left: 20px; + background-position: 5px center; + -moz-border-radius: 1ex;-webkit-border-radius: 1ex;border-radius: 1ex; background-image: url('/images/ok.png'); } - -.markerdiv{ - padding:1ex 1ex 1ex 1ex; /*top right bottom left*/ +.markerdiv{ border: 1px solid #aaaaaa; margin-bottom: 1ex; + -moz-border-radius: 1e; + -webkit-border-radius: 1ex; + border-radius: 1ex; } /*----------------------------------*/ diff --git a/telemeta/htdocs/images/transparent.png b/telemeta/htdocs/images/transparent.png new file mode 100644 index 0000000000000000000000000000000000000000..e1ab124e70796286a6a57842f31fdf22148a20f5 GIT binary patch literal 172 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@GyZm#X;^)4C~IxyaaNXlDyqr82-2S zpV<%OaTa()7Bet#3xhBt!>lS|xv6<2 z49-QVi6yBi3gww484B*6z5(HleBwYw+@3CuAsp9}6B2+NW(G!ut5c2xSqz@8elF{r G5}E+yKqqlen)||(to<0 || to>len)){ - return from; - } - //if we moved left to right, the insertion index is actually - //newIndex-1, as we must also consider to remove the current index markerIndex, so: - if(to>from){ - to--; - } - if(from != to){ - var elm = this.splice(from,1)[0]; - this.splice(to,0,elm); - return to; - } - return from; -} +//Array.prototype.move = function(from, to){ +// var pInt = parseInt; +// if(pInt(from)!==from || pInt(to)!==to){ +// return from; +// } +// var len = this.length; +// if((from<0 || from>len)||(to<0 || to>len)){ +// return from; +// } +// //if we moved left to right, the insertion index is actually +// //newIndex-1, as we must also consider to remove the current index markerIndex, so: +// if(to>from){ +// to--; +// } +// if(from != to){ +// var elm = this.splice(from,1)[0]; +// this.splice(to,0,elm); +// return to; +// } +// return from; +//} function foldInfoBlocks() { var $J = jQuery; @@ -50,7 +50,9 @@ function urlNormalized(){ return sPath; } - +/** + * Global telemeta function to set the current selected menu active according toi the current url + */ function setSelectedMenu(){ var $J = jQuery; var menus = $J('#menu a'); @@ -87,7 +89,7 @@ function setSelectedMenu(){ } }else{ //here, on the other hand, we select if a link points to a page or super page - //of the current paqge + //of the current page if(linkHref!=pageOrigin && pageHref.match("^"+linkHref+".*")){ elm.addClass('active'); }else{ @@ -116,14 +118,14 @@ $(document).ready(function() { //method: the json method, eg "telemeta.update_marker". See base.py // //onSuccesFcn(data, textStatus, jqXHR) OPTIONAL --IF MISSING, NOTHING HAPPENS -- -// A function to be called if the request succeeds. -// The function gets passed three arguments: +// A function to be called if the request succeeds with the same syntax of jQuery's ajax onSuccess function. +// The function gets passed three arguments // The data returned from the server, formatted according to the dataType parameter; // a string describing the status; // and the jqXHR (in jQuery 1.4.x, XMLHttpRequest) object // //onErrorFcn(jqXHR, textStatus, errorThrown) OPTIONAL. --IF MISSING, THE DEFAULT ERROR DIALOG IS SHOWN-- -// A function to be called if the request fails. +// A function to be called if the request fails with the same syntax of jQuery ajax onError function.. // The function receives three arguments: // The jqXHR (in jQuery 1.4.x, XMLHttpRequest) object, // a string describing the type of error that occurred and @@ -227,20 +229,6 @@ var popup={ return div; }, className: 'component', -// divShadow: function(){ -// var divShadow = this.jQuery('
').css({ //this is _cfg_ -// position: 'absolute', -// display: 'none', -// overflow:'visible', -// padding: '0 !important', //otherwise setting divShadow dimension is tricky -// backgroundColor:'#000 !important', //shadow must be black -// zIndex:999 -// }); -// if(this.className){ -// divShadow.addClass(this.className); -// } -// return divShadow; -// }, // mouseDownNamespace : "mousedown.popup__", // keyDownNamespace : "keydown.popup__", @@ -528,45 +516,78 @@ var popup={ } - -//function loadScripts(scriptArrayName, callback){ -// if(!(scriptArrayName) && callback){ -// callback(); -// return; -// } -// var len = scriptArrayName.length; -// var script = function(i){ -// if(i>=len){ -// if(callback){ -// callback(); -// return; -// } -// } -// jQuery.getScript(scriptArrayName[i], function(){script(i+1)}); -// }; -// script(0); -//} - -function loadScripts(scriptArray, callback, loadInSeries){ - loadInSeries = false; +/** + * Loads scripts asynchronously + * can take up to four arguments: + * root (optional): a string specifying the root (such as '/usr/local/'). Must end with slash, otherwise + * each element in scriptArray must begin with a slash + * scriptArray: a string array of js script filenames, such as ['script1.js','script2.js'] + * callback (optional): callback to be executed when ALL scripts are succesfully loaded + * loadInSeries (optional): if true scripts are loaded in synchronously, ie each script is loaded only once the + * previous has been loaded. The default (argument missing) is false + * + * Examples. Given scripts = ['s1.js', 's2.js'] + * loadScripts(scripts) //loads (asynchronously) scripts + * loadScripts('/usr/', scripts) //loads (asynchronously) ['/usr/s1.js', '/usr/s2.js'] + * loadScripts(scripts, callback) //loads (asynchronously) scripts. When loaded, executes callback + * loadScripts('/usr/', scripts, callback) //loads (asynchronously) ['/usr/s1.js', '/usr/s2.js']. When loaded, executes callback + * loadScripts(scripts, callback, true) //loads (synchronously) scripts. When loaded, executes callback + * loadScripts('/usr/', scripts, callback, true) //loads (synchronously) ['/usr/s1.js', '/usr/s2.js']. When loaded, executes callback + * + */ +function loadScripts(){ + var optionalRoot='', scriptArray=[], callback=undefined, loadInSeries=false; + var len = arguments.length; + if(len==1){ + scriptArray = arguments[0]; + }else if(len==2){ + if(typeof arguments[0] == 'string'){ + optionalRoot = arguments[0]; + scriptArray = arguments[1]; + }else{ + scriptArray = arguments[0]; + callback = arguments[1]; + } + }else if(len>2){ + if(typeof arguments[0] == 'string'){ + optionalRoot = arguments[0]; + scriptArray = arguments[1]; + callback = arguments[2]; + if(len>3){ + loadInSeries = arguments[3]; + } + }else{ + scriptArray = arguments[0]; + callback = arguments[1]; + loadInSeries = arguments[2]; + } + } + if(!scriptArray){ if(callback){ callback(); } return; } - var len = scriptArray.length; + len = scriptArray.length; + var i=0; + if(optionalRoot){ + for(i =0; i'+elm.attr('name')+''+elm.attr('value')+'' +elm.attr('unit')+''); - }); - //loaded analizer, loading player - if(msgElm){ - msgElm.html('Loading player...'); - } - var duration = $J(data).find('#duration').attr('value'); - duration = duration.split(":"); - consolelog('analyzer loaded, duration: '+duration); - //format duration - var pin = parseInt; - var pfl = parseFloat; - var timeInMSecs=pin(duration[0])*3600+pin(duration[1])*60+pfl(duration[2]); - timeInMSecs = Math.round(timeInMSecs*1000); - load_player(soundUrl, timeInMSecs); + }); + //loaded analizer, loading player + if(msgElm){ + msgElm.html('Loading player...'); + } + var duration = $J(data).find('#duration').attr('value'); + duration = duration.split(":"); + consolelog('analyzer loaded, duration: '+duration); + //format duration + var pin = parseInt; + var pfl = parseFloat; + var timeInMSecs=pin(duration[0])*3600+pin(duration[1])*60+pfl(duration[2]); + timeInMSecs = Math.round(timeInMSecs*1000); + load_player(soundUrl, timeInMSecs); }, error:function(){ msgElm.parent().html(""); @@ -110,9 +96,6 @@ function loadPlayer(analizerUrl, soundUrl){ //loads a player WAITING for the sound identified by soundUrl to be FULLY LOADED!!!! function load_player(soundUrl, durationInMsecs) { -// if (!$('#player').length){ -// return; -// } consolelog('PlayerUtils.load_player: '+soundUrl+' '+durationInMsecs); var load_player2 = this.load_player2; @@ -136,33 +119,31 @@ function load_player(soundUrl, durationInMsecs) { } }); if(!loadImmediately){ - load_player2(sound,durationInMsecs); + //TODO: remove this code is only temporary here!!!!!!!!!!!!!!!!!!!!1 + loadScripts('/timeside/src/',['rulermarker.js', //'markerlist.js', + 'markermap.js', 'player.js', 'ruler.js','divmarker.js'], function(){ + load_player2(sound,durationInMsecs); + }); } + } -//NOTE: the sound MUST be FULLY LOADED!!!!!! otherwise the duration is null. This method is called from load_player +//NOTE: the duration must be present. Loaded from xmlanalyzer (see above) function load_player2(sound, durationInMsec) { if (!$('#player').length){ return; } consolelog("entered load_player2"); + //TODO: what are we doing here???? $('.ts-wave a img').insertAfter('.ts-wave a'); $('.ts-wave a').remove(); - TimeSide.load(function() { - map = new TimeSide.MarkerMap(); + var p = new Player(jQuery('#player'), sound, durationInMsec); + consolelog('initialized player'); + p._setupInterface(CURRENT_USER_NAME ? true : false); + //p.loadMarkers(); - player = new TimeSide.Player('#player', { - image: get_player_image_src, - 'sound': sound, - 'soundDurationInMsec': durationInMsec - }); - change_visualizer(); - controller = new TimeSide.Controller({ - player: player, - map: map - }); - }); + player = p; var change_visualizer_click = change_visualizer_clicked; $('#visualizer_id').change(change_visualizer_click); @@ -176,98 +157,3 @@ function load_player2(sound, durationInMsec) { } -function set_player_image_url(str) { - player_image_url = str; -} - -function get_player_image_src(width, height) { - var src = null; - if (player_image_url && (width || height)) { - src = player_image_url.replace('WIDTH', width + '').replace('HEIGHT', height + ''); - } - return src; -} - -//====================================================================== -//OLD STUFF -//====================================================================== - - -//TODO REMOVE NOTE: the sound MUST be FULLY LOADED!!!!!! otherwise the duration is null. This method is called from load_player -function load_player2Old(sound) { - if (!$('#player').length){ - return; - } - consolelog("entered load_player2"); - // sound = soundManager.createSound({ - // id: 'sound', - // url: soundUrl - // }); - //soundUrl = $('.ts-wave a').attr('href'); - - $('.ts-wave a img').insertAfter('.ts-wave a'); - $('.ts-wave a').remove(); - - TimeSide.load(function() { - map = new TimeSide.MarkerMap(); - // provider = new TimeSide.SoundProvider({ - // duration: sound.duration - // }); - // provider.setSource(sound); - player = new TimeSide.Player('#player', { - image: get_player_image_src, - 'sound': sound - }); - change_visualizer(); - controller = new TimeSide.Controller({ - player: player, - //soundProvider: provider, - map: map - }); - //change_visualizer(); - //player.resize(); - //player.updateVolumeAnchor(provider.getVolume()); - }); - - // $('#visualizer_id').change(change_visualizer); - // $('#visualizer_id_form').submit(change_visualizer); - $('#visualizer_id').change(change_visualizer_clicked); - $('#visualizer_id_form').submit(change_visualizer_clicked); - - $('#player_maximized .toggle, #player_minimized .toggle').click(function() { - togglePlayerMaximization(); - this.blur(); - return false; - }); - - -} - -//TODO: REMOVE loads a player WAITING for the sound identified by soundUrl to be FULLY LOADED!!!! -function load_playerOld(soundUrl) { - if (!$('#player').length){ - return; - } - consolelog('PlayerUtils.load_player: '+soundUrl); - var callback = this.load_player2; - - //this variable can be changed to load a sound immediately or not - var loadImmediately = true; - var sound = soundManager.createSound({ - id: 'sound', - autoLoad: loadImmediately, - url: soundUrl, - whileloading: function() { - //PROBLEM/BUG: on chrome and firefox whileloading is the same as onload, - //ie it is not called at regular interval but when the whole file has loaded - if(loadImmediately){ - consolelog('entering while loading setting up---------------'+this.bytesLoaded+' of '+this.bytesTotal); - loadImmediately=false; - callback(this); - } - } - }); - if(!loadImmediately){ - callback(sound); - } -} diff --git a/telemeta/htdocs/timeside/src/divmarker.js b/telemeta/htdocs/timeside/src/divmarker.js index 86cea419..98e293d0 100644 --- a/telemeta/htdocs/timeside/src/divmarker.js +++ b/telemeta/htdocs/timeside/src/divmarker.js @@ -1,315 +1,275 @@ /** * TimeSide - Web Audio Components - * Copyright (c) 2008-2009 Samalyse + * Copyright (c) 2011 Parisson * Author: Riccardo Zaccarelli * License: GNU General Public License version 2.0 */ -TimeSide(function($N, $J) { - - $N.Class.create("DivMarker", $N.Core, { - //static constant variables to retireve the Marker Html Elements (MHE) - //to be used with the function below getHtmElm, eg: - //getHtmElm(marker, this.MHE_OFFSET_LABEL) - e_indexLabel:null, - e_descriptionText:null, - e_offsetLabel:null, - e_deleteButton:null, - e_okButton:null, - e_header:null, - e_editButton:null, - e_titleText:null, - e_addplaylistButton:null, - me:null, - markerMap:null, - - - initialize: function($super, markermap) { - $super(); - //sets the fields required???? see ruler.js createPointer - this.configure({ - //why instantiating a variable to null? - parent: [null, 'required'] - }); - this.cfg.parent = $J("#markers_div_id"); - this.markerMap = markermap; - this.me = this.createDiv(); - this.cfg.parent.append(this.me); - }, - - - //creates a new div. By default, text is hidden and edit button is visible - createDiv: function(){ - - var div = this.cfg.parent; - var markerDiv; - if(div){ - - //index label - this.e_indexLabel = $J('') - .addClass('markersdivIndexLabel') - .addClass('markersdivTopElement'); - - //offset label - this.e_offsetLabel = $J('') - .addClass('markersdivTopElement') - .addClass('markersdivOffset'); - - - //title text - this.e_titleText = $J('') - .attr('type','text') - .addClass('markersdivTitle') - .addClass('markersdivTopElement'); - - //add playlist button - this.e_addplaylistButton = $J('') - .addClass('markersdivAddPlaylist') - .addClass('markersdivTopElement') - .attr('title','add to playlist') - .attr("href","#"); - - //close button - this.e_deleteButton = $J('') - .addClass('markersdivDelete') - .addClass('markersdivTopElement') - .attr('title','delete marker') - .attr("href","#"); - - //edit button - this.e_editButton = $J('') - .addClass('roundBorder4') - .addClass('markersdivEdit') - .addClass('markersdivTopElement') - .attr('title','edit marker description') - .attr("href","#") - .html('EDIT'); - - //add all elements to header: - this.e_header = $J('
') //.css('margin','0.1ex 0ex 0.5ex 0ex') - .append(this.e_indexLabel) - .append(this.e_offsetLabel) - .append(this.e_titleText) - .append(this.e_deleteButton) - .append(this.e_editButton) - .append(this.e_addplaylistButton); - - //description text - this.e_descriptionText = $J('
'+ + '
'); //TODO: avoid text nodes + div.find('a').attr('href','#'); + //todo: remove markerlabel from css!!!!!!! + //new RulerMarker(div.find('.markerlbl'),div.find('.markercanvas'),'marker',false); + + var e_indexLabel = div.find('.ts-marker'); + //var e_offsetLabel =div.find('.markersdivOffset'); + var e_okButton = div.find('.markersdivSave'); + var e_editButton = div.find('.markersdivEdit'); + var e_deleteButton = div.find('.markersdivDelete'); + var e_addplaylistButton = div.find('.markersdivAddPlaylist'); + var e_descriptionText = div.find('.markersdivDescription'); + var e_titleText = div.find('.markersdivTitle'); + + //set defualt element values regardeless of the marker state + e_indexLabel.attr('title',marker.toString()); + this.setIndex(div, index); + + //e_offsetLabel.html(this.makeTimeLabel(marker.offset)); + this.setOffset(div,marker.offset); + //set visibility and attach events according to the marker state: + //first, is editing or not + //var isEditing = marker.isEditable && marker.isModified; + // (!marker.isSavedOnServer || !(this.e_editButton.is(':visible'))); + + //if(!isEditing){ + e_descriptionText.val(marker.desc ? marker.desc : ""); + e_titleText.val(marker.title ? marker.title : ""); + //} + + e_okButton.hide(); + e_editButton.show(); + e_deleteButton.show(); + e_addplaylistButton.show(); + e_descriptionText.attr('readonly','readonly').addClass('markersdivUneditable').unbind('focus'); + e_titleText.attr('readonly','readonly').addClass('markersdivUneditable').unbind('focus'); + + + if(!marker.isEditable){ + e_editButton.hide(); + e_deleteButton.hide(); + //we unbind events to be sure + e_addplaylistButton.unbind('click').hide(); + e_okButton.unbind('click') + e_deleteButton.unbind('click').hide(); + e_editButton.remove(); //so that if edit button is not present, we do not edit (safety reasons) see this.setEditMode + return div; } - - - - }); - - $N.notifyScriptLoad(); - -}); - - -Object.prototype.toString = function(){ - var s=""; - for(var k in this){ - s+=k+": "+this[k]+"\n"; + + var me = this; + e_deleteButton.unbind('click').click( function(){ + if(!(marker.isSavedOnServer) || confirm('delete the marker permanently?')){ + me.fire('remove',{ + 'marker':marker + }); + } + return false; //avoid scrolling of the page on anchor click + }) + + e_addplaylistButton.unbind('click').bind('click',function(evtObj_){ + playlistUtils.showPopupAddToPlaylist(evtObj_,'marker',""+marker.id,'marker added to selected playlist'); + return false; + }); + + //action for ok button + e_okButton.unbind('click').click( function(){ + //if(marker.desc !== descriptionText.val()){ //strict equality needed. See note below + marker.desc = e_descriptionText.val(); + marker.title = e_titleText.val(); + me.fire('save',{ + 'marker':marker + }); + return false; //avoid scrolling of the page on anchor click + }); + + // if(isEditing){ + // startEdit(); + // }else{ + // this.updateTitleWidth(); + // } + return div; } - return s; -} \ No newline at end of file + +}); \ No newline at end of file diff --git a/telemeta/htdocs/timeside/src/markermap.js b/telemeta/htdocs/timeside/src/markermap.js index b7ecf7b1..839a2a25 100644 --- a/telemeta/htdocs/timeside/src/markermap.js +++ b/telemeta/htdocs/timeside/src/markermap.js @@ -1,198 +1,280 @@ /** * TimeSide - Web Audio Components - * Copyright (c) 2008-2009 Samalyse - * Author: Olivier Guilyardi and Riccardo Zaccarelli + * Copyright (c) 2011 Parisson + * Author: Riccardo Zaccarelli * License: GNU General Public License version 2.0 */ +var MarkerMap = TimesideArray.extend({ -TimeSide(function($N, $J) { + init: function() { + this._super(); + var ui = uniqid; //defined in application.js (global vars and functions) + this.uniqid = function(){ + return ui(); + }; + }, - $N.Class.create("MarkerMap", $N.Core, { - markers: null, - //the main div container: - divContainer: $J("#markers_div_id"), - initialize: function($super, markers) { - $super(); - if (!markers){ - markers = []; - } - this.markers = markers; - }, - - get: function(index){ - return this.markers[index]; - - }, + //overridden + add: function(obj) { + //var markers = this.toArray(); + var marker = this.createMarker(obj); + var idx = this.insertionIndex(marker); + if(idx>=0){ //it exists? there is a problem.... + this.debug('adding a marker already existing!!'); //should not happen. however... + return -1; + } + + idx = -idx-1; + //we do not call the super add cause we want to insert at a specified index + this._super(marker,idx); + //notifies controller.onMarkerMapAdd + this.fire('add', { + marker: marker, + index: idx, + isNew: (typeof obj == 'number' || typeof obj == 'string') + }); + //var temp = new MarkerDiv(); + // this.debug(this.createMarkerDiv()); + + + return idx; + }, + //TODO: remove from here + - add: function(obj) { - var marker = this.createMarker(obj); - var idx = this.insertionIndex(marker); + //argument is either an object loaded from server or a number specifying the marker offset + createMarker: function(argument){ + var marker = null; + var pFloat = parseFloat; + if(typeof argument == 'string'){ //to be sure, it might be that we pass an offset in string format + argument = pFloat(argument); + } + if(typeof argument == 'object'){ + var editable = CURRENT_USER_NAME === argument.author; + marker = { + id: argument.public_id, + offset: pFloat(argument.time), //IMPORTANT: IT IS A STRING!!!!!! + desc: argument.description, + title: argument.title, + author: argument.author, + isEditable: editable, + isSavedOnServer: true + }; + }else if(typeof argument == 'number'){ + marker = { + id: this.uniqid(), + offset: pFloat(argument), + desc: "", + title: "", + author: CURRENT_USER_NAME, + isEditable: true, + isSavedOnServer: false + }; + } + marker.toString = function(){ + var props = []; + for(var prop in this){ + if(!(prop == 'toString')){ + props.push(prop+': '+this[prop]); + } + } + return props.sort().join("\n"); + } + return marker; - //if exists (ix>0) add it AFTER the existing item - idx = idx<0 ? -idx-1 : idx+1; + }, - this.markers.splice(idx,0,marker); - //notifies controller.onMarkerMapAdd - this.fire('add', { - marker: marker, - index: idx + //overridden + //markerOrIndex can be an number (marker index) or a marker (the index will be aearched) + remove: function(identifier) { + var idx = -1; + if(typeof index == 'number'){ + idx = identifier; + }else{ + idx = this.insertionIndex(identifier); + } + if(idx<0 || idx>=this.length){ + this.each(function(i,m){ + consolelog(m); }); - return idx; - }, - - //argument is either an object loaded from server or a number specifying the marker offset - createMarker: function(argument){ - var marker = null; - if(typeof argument == 'string'){ //to be sure, it might be that we pass an offset in string format - argument = parseFloat(argument); - } - if(typeof argument == 'object'){ - var editable = CURRENT_USER_NAME === argument.author; - marker = { - id: argument.public_id, - offset: argument.time, - desc: argument.description, - title: argument.title, - author: argument.author, - isEditable: editable, - isSavedOnServer: true, - isModified:false - }; - }else if(typeof argument == 'number'){ - marker = { - id: this.uniqid(), - offset: parseFloat(argument), - desc: "", - title: "", - author: CURRENT_USER_NAME, - isEditable: true, - isSavedOnServer: false, - isModified: true - }; - } - return marker; + consolelog(identifier); + //TODO: handle error + this.debug('remove: marker not found'); + return; + } - }, + //build the function to be called if the marker is deleted + //if the marker is NOT saved on server, call the function immediately + var marker = this.toArray()[idx]; + var me = this; + var superRemove = me._super; + var functionOnSuccess = function(){ + superRemove.apply(me,[idx]); + me.fire('remove',{ + 'index':idx + }) + } - remove: function(index) { - var marker = this.get(index); - if (marker) { - if(marker.isSavedOnServer){ - this.removeHTTP(marker); - } - this.markers.splice(index, 1); - //notifies controller.js - this.fire('remove', { - index: index - }); - } - return marker; - }, + if(marker.isSavedOnServer){ + //json(param,method,onSuccessFcn,onErrorFcn){ + json([marker.id], "telemeta.del_marker",functionOnSuccess); + }else{ + functionOnSuccess(); + } + }, - move: function(markerIndex, newOffset){ - var newIndex = this.indexOf(newOffset); - var realIndex = this.markers.move(markerIndex,newIndex); + save: function(marker){ + var idx = this.insertionIndex(marker); + if(idx<0 || idx>=this.length){ + //TODO: habdle error + this.debug('marker not found'); + } + + //TODO: item public id defined elsewhere up, not here inside + var itemid = ITEM_PUBLIC_ID; + var isSavedOnServer = marker.isSavedOnServer; + var method = isSavedOnServer ? "telemeta.update_marker" : "telemeta.add_marker"; + var param = { + 'item_id':itemid, + 'public_id': marker.id, + 'time':marker.offset, + 'author': marker.author, + 'title':marker.title, + 'description':marker.desc + }; - var marker = this.markers[realIndex]; - marker.offset = newOffset; - marker.isModified = true; - - this.fire('moved', { - fromIndex: markerIndex, - toIndex: newIndex, - newIndex: realIndex + //function on success: + var me = this; + var success = function(){ + if(!isSavedOnServer){ + marker.isSavedOnServer = true; + marker.isModified = false; + } + me.fire('save',{ + 'index':idx }); + }; + //json(param,method,onSuccessFcn,onErrorFcn){ + json([param], method, success); + + }, + //TODO: there is no need of a public method +// removeHTTP: function(marker){ +// var public_id = marker.id +// //json(param,method,onSuccessFcn,onErrorFcn){ +// json([public_id], "telemeta.del_marker"); +// }, - }, - // - //The core search index function: returns insertionIndex if object is found according to comparatorFunction, - //(-insertionIndex-1) if object is not found. This assures that if the returned - //number is >=0, the array contains the element, otherwise not and the element can be inserted at - //-insertionIndex-1 - insertionIndex: function(object){ + //overridden method + move: function(markerIndex, newOffset){ + var newIndex = this.insertionIndex(newOffset); + //select the case: + if(newIndex<0){ + //we didn't move the marker on another marker (newOffset does not correspond to any marker) + //just return the real insertionIndex + newIndex = -newIndex-1; + } + // var markers = this.getMarkers(); + // //TODO: remove move from array prototype!!!! + var realIndex = this._super(markerIndex,newIndex); + // //var realIndex = markers.move(markerIndex,newIndex); + // this.debug('fromindex '+markerIndex+' to: '+newIndex+' results in '+realIndex); + var markers = this.toArray(); + var marker = markers[realIndex]; + marker.offset = newOffset; + marker.isModified = true; + this.fire('move', { + fromIndex: markerIndex, + toIndex: newIndex, + newOffset: newOffset + //,newIndex: realIndex + }); + }, + + + //returns the insertion index of object in this sorted array by means of a binary search algorithm. + // A) If object is a marker and: + // a1) Is found (ie, there is a marker in this map + // with same offset and same id), returns the index of the marker found, in the range [0, this.length-1]. Otherwise, if + // a2) Is not found, then returns -(insertionIndex-1), where insertionIndex is the + // index at which object would be inserted preserving the array order. Note that this assures that a + // number lower than zero means that object is not present in the array, and viceversa + // B) If object is a number or a string number (eg, "12.567"), then a marker with offset = object is built and compared + // against the markers in the map. Note however that in this case that equality between marker's offset is sufficient, + // as object is not provided with an id. THEREFORE, IF THE MAP CONTAINS SEVERAL MARKERS AT INDICES i, i+1, ... i+n + // WITH SAME OFFSET == object, THERE IS NO WAY TO DETERMINE WHICH INDEX IN [i, i+1, ... i+n] WILL BE RETURNED. + // See player.forward and player.rewind for an example of the B) case. + //LAST NOTE: BE SURE object is either a number (float) or object.offset is a number (float). + //In case it is not known, If it is a string number such as + //"4.562" the comparison falis (eg, "2.567" > "10.544") but obviously, no error is thrown in javascript + // + insertionIndex: function(object){ + //default comparator function: + //returns 1 as the first argument is greater than the second + //returns -1 as the first argument is lower than the second + //returns 0 if the arguments are equal + var comparatorFunction = function(markerInMap,newMarker){ + var a = markerInMap.offset; + var b = newMarker.offset; + if(ab){ + return 1; + }else{ + var a1 = markerInMap.id; + var b1 = newMarker.id; + if(a1b1){ + return 1; + } + } + return 0; + //var ret = a < b ? -1 : (a>b ? 1 : (markerInMap.id === newMarker.id ? 0 : -1)); + //return ret; + }; + if(!(typeof object == 'object')){ var offset; - if(typeof object == 'object'){ - offset = object.offset; - }else if(typeof object == 'number'){ + if(typeof object == 'number'){ offset = object; }else{ //to be sure... offset = parseFloat(object); } - var pInt = parseInt; //reference to parseInt (to increase algorithm performances) - var comparatorFunction = function(a,b){ - return (ab ? 1 : 0)); + object = { + 'offset':offset }; - var data = this.markers; - var low = 0; - var high = data.length-1; - - while (low <= high) { - //int mid = (low + high) >>> 1; - var mid = pInt((low + high)/2); - var midVal = data[mid]; - var cmp = comparatorFunction(midVal.offset,offset); - if (cmp < 0){ - low = mid + 1; - }else if (cmp > 0){ - high = mid - 1; - }else{ - return mid; // key found - } - } - return -(low + 1); // key not found - }, - //indexOf is the same as insertionIndex, but returns a positive number. - //in other words, it is useful when we do not want to know if obj is already present - //in the map, but only WHERE WOULD be inserted obj in the map. obj can be a marker - //or an offset (time). In the latter case a dummy marker with that offset will be considered - indexOf: function(obj){ - var idx = this.insertionIndex(obj); - return idx<0 ? -idx-1 : idx; - }, - each: function(callback) { - $J(this.markers).each(callback); - }, - // length: function(){ - // return this.markers ? this.markers.length : 0; - // }, - - - sendHTTP: function(marker, functionOnSuccess, showAlertOnError){ - var itemid = ITEM_PUBLIC_ID; - var isSaved = marker.isSavedOnServer; - var method = isSaved ? "telemeta.update_marker" : "telemeta.add_marker"; - var param = { - 'item_id':itemid, - 'public_id': marker.id, - 'time':marker.offset, - 'author': marker.author, - 'title':marker.title, - 'description':marker.desc + //key will never be found, so return either 1 or -1: + comparatorFunction = function(markerInMap,newMarker){ + var a = markerInMap.offset; + var b = newMarker.offset; + return a < b ? -1 : (a>b ? 1 : 0); }; - var success = function(){ - if(!isSaved){ - marker.isSavedOnServer = true; - marker.isModified = false; - } - if(functionOnSuccess){ - functionOnSuccess(); - } - }; - //json(param,method,onSuccessFcn,onErrorFcn){ - json([param], method, success); - - }, - - removeHTTP: function(marker){ - var public_id = marker.id - //json(param,method,onSuccessFcn,onErrorFcn){ - json([public_id], "telemeta.del_marker"); } + var pInt = parseInt; //reference to parseInt outside the loop below + //(to increase algorithm performances) - }); - - $N.notifyScriptLoad(); + var data = this.toArray(); + var low = 0; + var high = data.length-1; -}); + while (low <= high) { + //int mid = (low + high) >>> 1; + var mid = pInt((low + high)/2); + var midVal = data[mid]; + var cmp = comparatorFunction(midVal,object); + if (cmp < 0){ + //the midvalue is lower than the searched index element + low = mid + 1; + }else if (cmp > 0){ + //the midvalue is greater than the searched index element + high = mid - 1; + }else{ + return mid; // key found + } + } + return -(low + 1); // key not found + } +} +); \ No newline at end of file diff --git a/telemeta/htdocs/timeside/src/player.js b/telemeta/htdocs/timeside/src/player.js index f9719c5c..f5224759 100644 --- a/telemeta/htdocs/timeside/src/player.js +++ b/telemeta/htdocs/timeside/src/player.js @@ -1,405 +1,608 @@ -/** - * TimeSide - Web Audio Components - * Copyright (c) 2008-2009 Samalyse - * Author: Olivier Guilyardi - * License: GNU General Public License version 2.0 - */ - -TimeSide(function($N, $J) { - - $N.Class.create("Player", $N.Core, { - skeleton: { - 'div.viewer': { - 'div.ruler': {}, - 'div.wave': { - 'div.image-canvas': {}, - 'div.image-container': ['img.image'] - } - }, - 'div.control': { - 'div.layout': { - 'div.playback': ['a.play', 'a.pause', 'a.rewind', 'a.forward', 'a.set-marker' //] - ,'a.volume'] - } - }/*, - 'div.marker-control': ['a.set-marker']*/ - }, - defaultContents: { - play: 'Play', - pause: 'Pause', - rewind: 'Rewind', - forward: 'Forward', - 'set-marker': 'Set marker' - //,'text-marker' : 'textmarker' - }, - elements: {}, - ruler: null, - map: null, - container: null, - imageWidth: null, - imageHeight: null, - - initialize: function($super, container, cfg) { - $super(); - if (!container){ - throw new $N.RequiredArgumentError(this, 'container'); - } - this.container = $J(container); - - this.configure(cfg, { - image: null, - sound:null, - soundDurationInMsec:0 - }); - //if(this.cfg.sound && this.cfg.sound.) - }, - - free: function($super) { - this.elements = null; - this.container = null; - //this.sound.destruct(); //should be called here? - $super(); - }, - - - setMarkerMap: function(map) { - this.map = map; - return this; - }, - - setImage: function(expr) { - this.cfg.image = expr; - this.refreshImage(); - }, - - refreshImage: function() { - var src = null; - if (typeof this.cfg.image == 'function') { - src = this.cfg.image(this.imageWidth, this.imageHeight); - } else if (typeof this.cfg.image == 'string') { - src = this.cfg.image; - } +var Player = TimesideClass.extend({ + + //sound duration is in milliseconds because the soundmanager has that unit, + //player (according to timeside syntax) has durations in seconds + init: function(container, sound, soundDurationInMsec) { + this._super(); + if (!container){ + this.debug('ERROR: container is null in initializing the player') + } + this.getContainer = function(){ + return container; + } + this.getSound = function(){ + return sound; + } - if (src) { - this.elements.image.attr('src', src); - } - }, - - // draw: function() { - // this.debug('drawing'); - // $N.domReady(this.attach(this._setupInterface)); - // return this; - // }, - - _setupInterface: function() { - consolelog('player _setupInterface sound.readyState:'+this.cfg.sound.readyState); //handle also cases 0 and 2???? - //0 = uninitialised - //1 = loading - //2 = failed/error - //3 = loaded/success - if(this.cfg.sound.readyState==1){ - var rsz = this.resize; - //attach an event when fully loaded and repaint all. - //For the moment we will display the ruler and other stuff - //based on durationEstimate property - this.cfg.sound.options.onload = function() { - rsz(); - } - } - this.elements = $N.Util.loadUI(this.container, this.skeleton, this.defaultContents); + //rpivate functions for converting + //soundmanager has milliseconds, we use here seconds + var pInt = Math.round; //instantiate once for faster lookup + var pFloat = parseFloat; //instantiate once for faster lookup + function toMsec(seconds){ + return pInt(seconds*1000); + } + function toSec(msec){ + return pFloat(msec)/1000; + } - // IE apparently doesn't send the second mousedown on double click: - var jump = $J.browser.msie ? 'mousedown dblclick' : 'mousedown'; - this.elements.rewind.attr('href', '#').bind(jump, this.attach(this._onRewind)) - .click(function() { - return false; - }); - this.elements.forward.attr('href', '#').bind(jump, this.attach(this._onForward)) - .click(function() { + + + var sd = toSec(soundDurationInMsec); + this.getSoundDuration = function(){ + return sd; + } + + this.isPlaying = function(){ + /*Numeric value indicating the current playing state of the sound. + * 0 = stopped/uninitialised + * 1 = playing or buffering sound (play has been called, waiting for data etc.) + *Note that a 1 may not always guarantee that sound is being heard, given buffering and autoPlay status.*/ + return sound && sound.playState==1; + }; + + //setting the position=============================================== + //if sound is not loaded, position is buggy. Moreover, we have to handle the conversions between units: + //seconds (here) and milliseconds (swmanager sound). So we store a private variable + //private variable and function + var soundPos = sound.position ? toSec(sound.position) : 0.0; + //private method: updates just the internal variable (called in whilePlaying below) + function setPos(value){ + soundPos = value; + } + //public methods: calls setPos above AND updates sounbd position + this.setSoundPosition = function(newPositionInSeconds){ + //for some odd reason, if we set sound.setPosition here soundPos + //is rounded till the 3rd decimal integer AND WILL BE ROUNDED THIS WAY IN THE FUTURE + //don't know why, however we set the sound position before playing (see below) + //however, now it works. Even odder.... + setPos(newPositionInSeconds); + if(sound){ + var s = toMsec(this.getSoundPosition()); + sound.setPosition(s); + } + } + //public methods: returns the sound position + this.getSoundPosition = function(){ + return soundPos; + }; + + + //implement play here: while playing we do not have to update the sound position, so + //we call the private variable soundPos + this.play = function(){ + var player = this; + if(!player || player.isPlaying()){ return false; - }); - - // - this.elements.volume.attr('href', '#').click(function(){ + } + var sound = player.getSound(); + if(!sound){ return false; - }).bind('mousedown', this.attach( - function(e){ - if(e.which===1){ //left button - this.setVolume(e); - } - return false; - } - )); + } + var map = player.getMarkerMap(); + var indexToShow = map.insertionIndex(player.getSoundPosition()); + + var mydiv = this.$J('
').addClass('markerDiv').css({'position':'absolute','zIndex':1000}); - //assigning title string to all anchors??????? - this.elements.control.find('a').add(this.elements.setMarker) - .attr('href', '#') - .each(function(i, a){ - a = $J(a); - if (!a.attr('title')){ - a.attr('title', a.text()); - } - }); + var ruler = player.getRuler(); - //this.elements.markerControl.find('a').attr('href', '#'); - if (this.map && CURRENT_USER_NAME) { - //configureMarkersDiv(); - this.elements.setMarker.bind('click', this.attach(this._onSetMarker)); - //this.elements.setMarker2.bind('click', this.attach(this._onSetMarker2)); - //this.elements.textMarker.attr('type', 'text'); - //this.elements.textMarker.bind('click', this.attach(this._onSetMarker2)); - - } else { - this.elements.setMarker.remove(); - } - //creating the ruler - var ruler = new $N.Ruler({ - viewer: this.elements.viewer, - //map: this.map, - sound: this.cfg.sound, - soundDurationInMsec: this.cfg.soundDurationInMsec - }); - this.ruler = ruler; - //bind events to the ruler (see function observe in core.js, I guess, - //which overrides jQuery bind function): - //the first arg is basically the event name, the second - //arg is a function to execute each time the event is triggered - this.ruler - .observe('markermove', this.forwardEvent) - .observe('markeradd', this.forwardEvent) - //.observe('move', this.forwardEvent) - .draw(); - this.refreshImage(); - this.resize(); - - // var resizeTimer = null; - // $J(window).resize(this.attach(function() { - // if (resizeTimer){ - // clearTimeout(resizeTimer); - // } - // resizeTimer = setTimeout(this.attach(this.resize), 100); - // })); - - this.setSoundVolume(this.getSoundVolume()); - //finally, binds events to play and pause. At the end cause this.ruler has to be fully initialized - var sound = this.cfg.sound; - this.elements.pause.attr('href', '#').bind('click', function(){ - sound.pause(); - return false; - }); - //var r = this.ruler; - var player = this; var playOptions = { whileplaying: function(){ - ruler._movePointer(this.position/1000); //this will refer to the sound object - } - }; - this.elements.play.attr('href', '#').bind('click', function(){ - - consolelog('playstate'+sound.playState); - consolelog('readystate'+sound.readyState); - if(sound.playState!=1 || sound.paused){ - //if sound has to be loaded and position is not zero, load it first - if(sound.readyState==0 && player.getSoundPosition()){ - sound.options.onload=function(){ - this.setPosition(player.getSoundPosition()*1000); - //consolelog('loaded and played from '+player.getSoundPosition() +' '+this.position); - sound.play(playOptions); + var sPos = toSec(this.position); //this will refer to the sound object (see below) + setPos(sPos); + if(ruler && !ruler.isPointerMovingFromMouse()){ + ruler.movePointer(sPos); + } + if(indexToShow=sPos-spanSec && offzet<=sPos+spanSec){ + indexToShow++; + popup.show(jQuery('
').html(map.toArray()[indexToShow-1].toString())); + consolelog('showing marker '+(indexToShow-1)); } - sound.load(); - }else{ - //consolelog('NOT loaded and played'); - sound.play(playOptions); } + }, + onfinish: function() { + setPos(0); //reset position, not cursor, so that clicking play restarts from zero } - return false; - }); - }, - - resize: function(overrideHeight) { - this.debug("resizing"); - var height; - if (overrideHeight === true) { - this.debug("override height"); - height = this.elements.image.css('height', 'auto').height(); - } else { - height = this.elements.wave.height(); - this.debug("wave height:" + height); - if (!height) { - this.elements.image.one('load', this.attach(function() { - this.resize(true); - this.debug("image loaded"); - })); - height = this.elements.image.height(); + }; + //internal play function. Set all properties and play: + var play_ = function(sound, positionInSec){ + sound.setPosition(toMsec(positionInSec)); + indexToShow = map.insertionIndex(positionInSec); //if we are at zero + if(indexToShow<0){ + indexToShow = -indexToShow-1; + } + sound.setVolume(sound.volume); //workaround. Just to be sure. Sometimes it fails when we re-play + sound.play(playOptions); + }; + //var s = toMsec(player.getSoundPosition()); + if(sound.readyState != 3){ + /*sound.readyState + * Numeric value indicating a sound's current load status + * 0 = uninitialised + * 1 = loading + * 2 = failed/error + * 3 = loaded/success + */ + sound.options.onload=function(){ + //consolelog('LOADED AND PLAY '+' volume '+ sound.volume+' sPos: '+sound.position); + //we set position and volume to be sure. Sometimes they are buggy + //sound.setPosition(s); + //sound.setVolume(sound.volume); //workaround. Just to be sure + //sound.play(playOptions); + play_(sound, player.getSoundPosition()); } + sound.load(); + }else{ + //consolelog('PLAY IMMEDIATELY'+' volume '+ sound.volume+' sPos: '+sound.position); + //we set position and volume to be sure. Sometimes they are buggy + // sound.setPosition(s); + // sound.setVolume(sound.volume); //workaround. Just to be sure/ + // sound.play(playOptions); + play_(sound, player.getSoundPosition()); } + + return false; + }; + //now implement also pause here: note that pause has some odd behaviour. + //Try this sequence: play stop moveforward moveback play pause + //When we press the last pause the sound restarts (??!!!!) + this.pause = function(){ + var sound = this.getSound(); + //we don't check if it's playing, as the stop must really stop anyway + //if(sound && this.isPlaying()){ + sound.stop(); + //} + return false; + }; - var elements = this.elements.image - .add(this.elements.imageContainer) - .add(this.elements.imageCanvas); + //initializing markermap and markerui + var map = new MarkerMap(); + this.getMarkerMap = function(){ + return map; + } + var mapUI = new MarkerMapDiv(); + this.getMarkersUI = function(){ + return mapUI; + } + //TODO: define setUpInterface here???? - elements.css('width', 'auto'); // for IE6 + }, - if (!height){ - height = 200; - } - var style = { - width: this.elements.wave.width(), - height: height - } - elements.css(style); - this.imageWidth = style.width; - this.imageHeight = style.height; - this.refreshImage(); - this.ruler.resize(); - return this; - }, - //sound object methods - - getSoundPosition :function(){ - //note that this.cfg.sound.position is buggy. If we did not play, calling this.cfg.sound.setPosition(p) - //stores the position, but this.cfg.position returns zero. - //otherwise (we did play at least once) this.cfg.sound.position returns the good value - //to overcome this problem, we return the ruler position, NOTE that it is in seconds - return this.ruler.pointerPos; - // var s = this.cfg.sound; - // return s ? s.position/1000 : 0; - }, - - getSoundVolume :function(){ - var s = this.cfg.sound; - return s ? s.volume : 0; - }, - - getSoundDuration :function(){ - var s = this.cfg.sound; - return s ? s.duration/1000 : 0; - }, - - _onRewind: function() { - var offset = 0; - if (this.map) { - var position = parseFloat(this.getSoundPosition()); - var idx = this.map.indexOf(position)-1; - if(idx>=0){ - var marker = this.map.get(idx); - if(marker){ - offset = marker.offset; - } - } - } - this.ruler._movePointerAndUpdateSoundPosition(offset); - return false; - }, - - _onForward: function() { - var offset = this.getSoundDuration(); - if (this.map) { - var position = parseFloat(this.getSoundPosition()); - var idx = this.map.insertionIndex(position); - if(idx>=0){ //the pointer is exactly on a marker, the index is the marker itself - //so increase by one otherwise and we wouldn't move ahead - //more specifically, increase as long as we have markers with this offset (there could be more than - //one marker at offset - var m = this.map.get(idx); - while(m && m.offset == position){ - idx++; - m = this.map.get(idx); - if(!m){ - idx=-1; - } - } - }else{ - //we are not on a pointer, get the index of the marker - //(see markermap insertionindex) - idx = -idx-1; + _setupInterface: function(isInteractive) { + + this.isInteractive = function(){ + return isInteractive; + } + + var sound = this.getSound(); + consolelog('player _setupInterface sound.readyState:'+sound.readyState); //handle also cases 0 and 2???? + + var $J = this.$J; //defined in the super constructor + + //TODO: use cssPrefix or delete cssPrefix!!!!! + //TODO: note that ts-viewer is already in the html page. Better avoid this (horrible) method and use the html + var skeleton = { + 'div.ts-viewer': { + 'div.ts-ruler': {}, + 'div.ts-wave': { + 'div.ts-image-canvas': {}, + 'div.ts-image-container': ['img.ts-image'] } - if(idx>=0){ - var marker = this.map.get(idx); - if(marker){ - offset = marker.offset; - } + }, + 'div.ts-control': { + 'div.ts-layout': { + 'div.ts-playback': ['a.ts-play', 'a.ts-pause', 'a.ts-rewind', 'a.ts-forward', 'a.ts-set-marker' //] + ,'a.ts-volume'] } - } - this.ruler._movePointerAndUpdateSoundPosition(offset); + }/*, + 'div.marker-control': ['a.set-marker']*/ + }; + var jQueryObjs = this.loadUI(this.getContainer(), skeleton); + - return false; - }, - //notified from a click event on the anchor - setVolume: function(event){ + this.getElements = function(){ + return jQueryObjs; + } + + + + var rewind = jQueryObjs.find('.ts-rewind'); + var forward = jQueryObjs.find('.ts-forward'); + var play = jQueryObjs.find('.ts-play'); + var pause = jQueryObjs.find('.ts-pause'); + var volume = jQueryObjs.find('.ts-volume'); + + + //setting events to buttons (code left untouched from olivier): + //rewind + // + //(olivier comment) IE apparently doesn't send the second mousedown on double click: + // var jump = $J.browser.msie ? 'mousedown dblclick' : 'mousedown'; + // rewind.attr('href', '#').bind(jump, this.attach(this._onRewind)) + // .click(function() { + // return false; + // }); + // //forward: + // forward.attr('href', '#').bind(jump, this.attach(this._onForward)) + // .click(function() { + // return false; + // }); + var me=this; + //attaching event to the image. Note that attaching an event to a transparent div is buggy in IE +// if($J.browser.msie){ +// +// } + consolelog('ope'); + consolelog(jQueryObjs.find('.ts-image').length); + jQueryObjs.find('.ts-image').click(function(event){ + alert('g'); + consolelog(event); +// me.setSoundPosition( me.getSoundDuration()/$J(this).width()); + }); + + + var rewind_ = this.rewind; + var forward_ = this.forward; + rewind.attr('href', '#').click(function(e){ + rewind_.apply(me); + return false; + }); + forward.attr('href', '#').click(function(e){ + forward_.apply(me); + return false; + }); + + //volume: + function setVolume(event){ var ticks = [18,26,33,40,47]; var vol = event.layerX; for(var i=0; i0 && markers[idx-1].offset == position){ + idx--; } - if(volume<0){ - volume = 0; + } + idx--; //move backward (rewind) + if(idx>=0){ + offset = markers[idx].offset; + } + this.setSoundPosition(offset); + this.getRuler().movePointer(offset) + return false; + }, + + setSoundVolume: function(volume){ + + if(typeof volume != 'number'){ //note: typeof for primitive values, instanceof for the rest + //see topic http://stackoverflow.com/questions/472418/why-is-4-not-an-instance-of-number + volume = 100; + } + if(volume<0){ + volume = 0; + }else if(volume>100){ + volume = 100; + } + var sound = this.getSound(); + // if(sound.volume == volume){ + // return; + // } + sound.setVolume(volume); + //update the anchor image: + var indices = [20,40,60,80,100,100000]; + + var volumeElm = this.getElements().find('.ts-volume'); + for(var i=0; i 100){ - volume = 100; + } + // this.elements.volume.css('backgroundPosition','0px 0px !important') + + }, + + loadMarkers: function(isInteractive_){ + //ruler.bind('markermoved',this.markerMoved,this); + + var itemId = ITEM_PUBLIC_ID; + + var player = this; + //initialize the map. + var map = this.getMarkerMap(); + var mapUI = this.getMarkersUI(); + var ruler = this.getRuler(); + map.clear(); + mapUI.clear(); + ruler.clear(); + + //building the onSuccess function + var onSuccess = function(data) { + var tabIndex = 0; + var mapuiAdd = mapUI.add; + var rulerAdd = ruler.add; + + if(data && data.result && data.result.length>0){ + var result = data.result; + //add markers to the map. No listeners associated to it (for the moment) + var mapAdd = map.add; + for(var i =0; i< result.length; i++){ + mapAdd.apply(map,[result[i]]); + } + //add markers to ruler and div + map.each(function(i,marker){ + rulerAdd.apply(ruler,[marker, i]); + mapuiAdd.apply(mapUI,[marker, i]); + }); + + tabIndex = result.length>0 ? 1 : 0; } - this.cfg.sound.setVolume(volume); - //update the anchor image: - var indices = [20,40,60,80,100,100000]; - - for(var i=0; i - * License: GNU General Public License version 2.0 - */ - -TimeSide(function($N, $J) { - - $N.Class.create("Ruler", $N.Core, { - - fullSectionDuration: 60, - sectionSubDivision: 10, - sectionSteps: [[5, 1], [10, 1], [20, 2], [30, 5], [60, 10], [120, 20], [300, 30], - [600, 60], [1800, 300], [3600, 600]], - sectionsNum: 0, - timeLabelWidth: 0, - pointerPos: 0, - layout: null, - width: null, - mouseDown: false, - pointer: null, - markers: new Array(), - duration: 0, - container: null, - waveContainer: null, - - initialize: function($super, cfg) { - $super(); - this.configure(cfg, { - viewer: [null, 'required'], - fontSize: 10, - //map: null, - sound: [null, 'required'], - soundDurationInMsec:0 - }); - this.cfg.viewer = $J(this.cfg.viewer); - this.container = this.cfg.viewer.find('.' + $N.cssPrefix + 'ruler'); - this.waveContainer = this.cfg.viewer.find('.' + $N.cssPrefix + 'image-canvas'); - - //this.duration = this.cfg.sound.duration/1000; //TODO: improve this function!! - //note that soundmanager2 returns the duration in milliseconds, while here we compute the - //layout according to the duration in seconds. Changing all functions it's a pain and it's useless' - - //initialize duration. If sound autoLoad=false, duration is zero and we must use durationEstimate - //this.duration = this.cfg.sound.duration ? this.cfg.sound.duration : this.cfg.sound.durationEstimate; - //this - this.duration = this.cfg.soundDurationInMsec/1000; - consolelog('duration - - '+this.cfg.sound.duration); - consolelog('duration -E- '+this.cfg.sound.bytesTotal); - - var imgContainer = this.cfg.viewer.find('.' + $N.cssPrefix + 'image-container'); // for IE - - this._observeMouseEvents(this.waveContainer.add(imgContainer)); - //this is a workaround: when moving the marker the first time sound.setPosition seems not to work - //after playing the first time, it works. Or after having set position explicitly, apparently - //this.cfg.sound.setPosition(0); - // this.sp = this._setPosition; - - // this.cfg.sound.whileplaying(function(){ - // sp(this.position/1000); - // }); - // this.cfg.sound.onfinish(function(){ //when it reaches the end (naturally) force pointer to be at the end - // sp(this.duration); - // }); - - //if (this.cfg.map) { - // this.cfg.map - //.observe('add', this.attach(this._onMapAdd)) - //.observe('remove', this.attach(this._onMapRemove)) - //.observe('indexchange', this.attach(this._onMapIndexChange)); - //} - - //this.cfg.soundProvider.observe('update', this.attach(this._onSoundProviderUpdate)); - //this.cfg.soundProvider.observe('play', this.attach(this._onSoundProviderPlaying)); - }, - - free: function($super) { - this.layout = null; - this.container = null; - this.waveContainer = null; - this.cfg.viewer = null; - $super(); - }, - - _computeLayout: function() { - this.width = this.waveContainer.width(); - this.debug('container width: ' + this.width); - var i, ii = this.sectionSteps.length; - this.timeLabelWidth = this._textWidth('00:00', this.cfg.fontSize); - for (i = 0; i < ii; i++) { - // this.debug('step: ' +i+' duration: '+this.sectionSteps[i][0]); - // this.debug('step: ' +i+' subdivision: '+this.sectionSteps[i][1]); - // this.debug('labelsNum: ' +i+' labelsNum (this.duration/duration): '+Math.floor(this.duration / duration)); - - var duration = this.sectionSteps[i][0]; - var subDivision = this.sectionSteps[i][1]; - var labelsNum = Math.floor(this.duration / duration); - if ((i == ii - 1) || (this.width / labelsNum > this.timeLabelWidth * 2)) { - this.fullSectionDuration = duration; - this.sectionSubDivision = subDivision; - this.sectionsNum = Math.floor(this.duration / this.fullSectionDuration); - this.debug('(in _computeLayout) this.fullSectionDuration: ' + this.fullSectionDuration); - this.debug('(in _computeLayout) sectionsNum: ' +this.sectionsNum); - this.debug('(in _computeLayout) duration: ' +this.duration); - break; - } +var Ruler = TimesideArray.extend({ + //init constructor: soundDuration is IN SECONDS!!! (float) + init: function(viewer, soundDuration, isInteractive){ + this._super(); + var cssPref = this.cssPrefix; + + this.isInteractive = function(){ + return isInteractive; + }; + + this.getSoundDuration= function(){ + return soundDuration; + }; + + var waveContainer = viewer.find('.' + cssPref + 'image-canvas'); + this.debug( 'WAVECONTAINER?? LENGTH='+ waveContainer.length); + this.getWaveContainer =function(){ + return waveContainer; + }; + //ts-image-canvas has width=0. Why was not the case in old code? + //BECAUSE IN OLD CODE ts-image-canvas has style="width..height" defined, and not HERE!!!! + this.getContainerWidth =function(){ + return waveContainer.width(); + }; + + + this.debug( 'init ruler: container width '+this.getContainerWidth()); + + + //private function used in resize() defined below + + + var container = viewer.find('.' + cssPref + 'ruler'); + + this.getRulerContainer = function(){ + return container; + } + + + if(!isInteractive){ //is not interactive, skip all methods assignmenets below + return; + } + + // TODO: check here + // http://stackoverflow.com/questions/3299926/ie-mousemove-bug + // div in IE to receive mouse events must have a background + // so for the moment + + + + // var mouseDown = false; + // var _onMouseDown = function(evt) { + // mouseDown = true; + // this._onMouseMove(evt); + // evt.preventDefault(); //If this method is called, the default action of the event will not be triggered. + // }; + // var _onMouseMove = function(evt) { + // if (mouseDown) { + // var pixelOffset = evt.pageX - container.offset().left; + // this._movePointerAndUpdateSoundPosition(pixelOffset / this.width * this.duration); + // //moves the pointer and fires onPointerMove + // } + // return false; + // }; + // + // var _onMouseUp= function(evt) { + // if (mouseDown) { + // mouseDown = false; + // this.debug('_onMouseUp:'+this.pointerPos+' '+this.cfg.sound.position); + // } + // return false; + // }; + // var imgContainer = viewer.find('.' + cssPref + 'image-container'); // for IE + // var element = waveContainer.add(imgContainer); //constructs a new jQuery object which is the union of the jquery objects + // + // element + // .bind('click dragstart', function() { + // return false; + // }) + // .bind('mousedown', function(evt){ + // return _onMouseDown(evt); + // }) + // .bind('mousemove', function(evt){ + // return _onMouseMove(evt); + // }) + // .bind('mouseup', function(evt){ + // return _onMouseUp(evt); + // }); + // this.$J(document) + // .bind('mousemove', function(evt){ + // return _onMouseMove(evt); + // }); + + }, + + resize : function(){ + //code copied from old implementation, still to get completely what is going on here... + var sectionSteps = [[5, 1], [10, 1], [20, 2], [30, 5], [60, 10], [120, 20], [300, 30], + [600, 60], [1800, 300], [3600, 600]]; + //old computeLayout code + var fullSectionDuration,sectionSubDivision, sectionsNum; + var width = this.getContainerWidth(); + var duration = this.getSoundDuration(); + var cssPref = this.cssPrefix;//defined in superclass + var fontSize = 10; + var mfloor = Math.floor; //instanciating once increases performances + var $J = this.$J; //reference to jQuery + //this.debug('container width: ' +" "+width); + + + var i, ii = sectionSteps.length; + var timeLabelWidth = this._textWidth('00:00', fontSize); + for (i = 0; i < ii; i++) { + var tempDuration = sectionSteps[i][0]; + var subDivision = sectionSteps[i][1]; + var labelsNum = mfloor(duration / tempDuration); + if ((i == ii - 1) || (width / labelsNum > timeLabelWidth * 2)) { + fullSectionDuration = tempDuration; + sectionSubDivision = subDivision; + sectionsNum = mfloor(duration / fullSectionDuration); + //this.debug('(in _computeLayout) this.fullSectionDuration: ' + fullSectionDuration); + //this.debug('(in _computeLayout) sectionsNum: ' +sectionsNum); + //this.debug('(in _computeLayout) sectionSubDivision: ' +sectionSubDivision); + break; } - }, + } + //old draw() code: + if (!duration) { + this.debug("Can't draw ruler with a duration of 0"); + return; + } + //this.debug("draw ruler, duration: " + duration); + + var container = this.getRulerContainer(); + var layout = container.find("."+cssPref + 'layout'); + //REDONE: if does not exists, create it + if(!layout || !(layout.length)){ + layout = $J('
') + .addClass(cssPref + 'layout') + .css({ + position: 'relative' + }) // bugs on IE when resizing + //TODO: bind doubleclick events!!!!!! + //.bind('dblclick', this.attachWithEvent(this._onDoubleClick)) + //.bind('resize', this.attachWithEvent(this.resize)) // Can loop ? + .appendTo(container); + }else{ + //remove all elements neither pointer nor marker + layout.find(':not(a.ts-pointer,a.ts-marker,a.ts-pointer>*,a.ts-marker>*)').remove(); + } + + // if (layout && layout.length){ + // layout.remove(); + // } + // layout = $J('
') + // .addClass(cssPref + 'layout') + // .css({ + // position: 'relative' + // }) // bugs on IE when resizing + // //TODO: bind doubleclick events!!!!!! + // //.bind('dblclick', this.attachWithEvent(this._onDoubleClick)) + // //.bind('resize', this.attachWithEvent(this.resize)) // Can loop ? + // .appendTo(container); - getUnitDuration: function() { - return this.sectionSubDivision; - }, + - resize: function() { - // var pointerVisible = this.pointer && this.pointer.isVisible(); - // this.debug('resizing (pointer visible: :'+pointerVisible+':'); - // alert(this.pointer.isVisible()); - this._computeLayout(); + //creating sections + //defining function maketimelabel + var makeTimeLabel = this.makeTimeLabel; - this.draw(); - if(this.pointer){ - if(!this.pointer.isVisible()){ - this.pointer.show(); - } - // } - // if (pointerVisible) { - // this.setPosition(this.cfg.soundProvider.getPosition()); - // this.setBuffering(this.cfg.soundProvider.isBuffering() && this.cfg.soundProvider.isPlaying()); - - this._movePointer(this.cfg.sound.position/1000); - this.setBuffering(this.cfg.sound.isBuffering && this.cfg.sound.playState==1); - //Note that playState = 1 may not always guarantee that sound is being heard, given buffering and autoPlay status. - //(from soundmanager2 tutorial) - } - }, - -// _setDuration: function(duration) { -// this.debug('duration setting ruler: ' + duration); -// this.duration = duration; -// this._computeLayout(); -// }, -// -// setDuration: function(durationInMillisecs) { -// var duration = durationInMillisecs ? durationInMillisecs/1000 : 60; -// if (this.duration != duration) { -// this._setDuration(duration); -// this.draw(); -// } -// }, - - _createSection: function(timeOffset, pixelWidth) { + //defining the function createSection + var _createSection = function(timeOffset, pixelWidth,timeLabelWidth) { var section = $J('
') - .addClass($N.cssPrefix + 'section') + .addClass(cssPref + 'section') .css({ - fontSize: this.cfg.fontSize + 'px', + fontSize: fontSize + 'px', fontFamily: 'monospace', width: pixelWidth, overflow: 'hidden' }) - .append($J('
').addClass($N.cssPrefix + 'canvas')); + .append($J('
').addClass(cssPref + 'canvas')); var topDiv = $J('
') - .addClass($N.cssPrefix + 'label') + .addClass(cssPref + 'label') .appendTo(section); var bottomDiv = $J('
') - .addClass($N.cssPrefix + 'lines') + .addClass(cssPref + 'lines') + .appendTo(section); var empty = $J('').css({ visibility: 'hidden' }).text(' '); - if (pixelWidth > this.timeLabelWidth) { - var text = $J('') - .text($N.Util.makeTimeLabel(timeOffset)) - .bind('mousedown selectstart', function() { + var text; + + if (pixelWidth > timeLabelWidth) { + text = $J('') + .text(makeTimeLabel(timeOffset)) + .bind('mousedown selectstart', function() { //WHY THIS? return false; }); } else { - var text = empty.clone(); + text = empty.clone(); } topDiv.append(text); bottomDiv.append(empty); return section; - }, + }; + //function defined, creating sections: + var sections = new Array(); + var currentWidth = 0; + var sectionDuration, sectionWidth; + for (i = 0; i <= sectionsNum; i++) { + if (i < sectionsNum) { + sectionDuration = fullSectionDuration; + sectionWidth = mfloor(sectionDuration / duration * width); + } else { + sectionDuration = duration - i * fullSectionDuration; + sectionWidth = width - currentWidth; + + } + var section = _createSection(i * fullSectionDuration, sectionWidth, timeLabelWidth); + if (i > 0) { + section.css({ + left: currentWidth, + top: 0, + position: 'absolute' + }); + } + section.duration = sectionDuration; + layout.append(section); + currentWidth += section.width(); + sections[i] = section; + } - _drawSectionRuler: function(section, drawFirstMark) { + //function to draw section rulers: + var _drawSectionRuler= function(section, drawFirstMark) { var j; - var jg = new jsGraphics(section.find('.' + $N.cssPrefix + 'canvas').get(0)); - jg.setColor(this.layout.find('.' + $N.cssPrefix + 'lines').css('color')); + + var jg = new jsGraphics(section.find('.' + cssPref + 'canvas').get(0)); + jg.setColor(layout.find('.' + cssPref + 'lines').css('color')); var height = section.height(); var ypos; - for (j = 0; j < section.duration; j += this.sectionSubDivision) { + for (j = 0; j < section.duration; j += sectionSubDivision) { if (j == 0) { if (drawFirstMark) { ypos = 0; @@ -200,325 +251,275 @@ TimeSide(function($N, $J) { } else { ypos = (j == section.duration / 2) ? 1/2 + 1/8 : 3/4; } - var x = j / this.duration * this.width; + //var x = j / this.duration * this.width; + var x = j / duration * width; jg.drawLine(x, height * ypos, x, height - 1); } jg.paint(); - }, - - getHeight: function() { - return this.container.find('' + $N.cssPrefix + '.section').height(); - }, - - draw: function() { - if (!this.duration) { - this.debug("Can't draw ruler with a duration of 0"); - return; - } - this.debug("draw ruler, duration: " + this.duration); - if (this.layout){ - this.layout.remove(); - } - this.layout = $J('
') - .addClass($N.cssPrefix + 'layout') - .css({ - position: 'relative' - }) // bugs on IE when resizing - .bind('dblclick', this.attachWithEvent(this._onDoubleClick)) - //.bind('resize', this.attachWithEvent(this.resize)) // Can loop ? - .appendTo(this.container); - - //this.container.html(this.layout); - - var sections = new Array(); - var currentWidth = 0; - var i; - for (i = 0; i <= this.sectionsNum; i++) { - if (i < this.sectionsNum) { - var duration = this.fullSectionDuration; - var width = Math.floor(duration / this.duration * this.width); - } else { - var duration = this.duration - i * this.fullSectionDuration; - var width = this.width - currentWidth; - - } - var section = this._createSection(i * this.fullSectionDuration, width); - if (i > 0) { - section.css({ - left: currentWidth, - top: 0, - position: 'absolute' - }); - } - section.duration = duration; - this.layout.append(section); - currentWidth += section.width(); - sections[i] = section; - } + }; + //draw section rulers + for (i = 0; i <= sectionsNum; i++) { + _drawSectionRuler(sections[i], (i > 0)); + } - for (i = 0; i <= this.sectionsNum; i++) { - this._drawSectionRuler(sections[i], (i > 0)); - } + + var pointer = undefined; + if('getPointer' in this){ + pointer = this.getPointer(); + } + if(!pointer){ + //consolelog('QUALE CHAZZO E IL CONTAINER?????? ' + $J(layout.get(0)).attr('class')); + // pointer = new RulerMarker($J(layout.get(0)),this.getWaveContainer(),'pointer', true); + // pointer.setText(this.makeTimeLabel(0)); + // + // this.debug('WELL, '); + // consolelog(pointer); + // var me = this; + // pointer.getLabel().mousedown(function(evt) { + // var lbl = $J(evt.target); + // me.markerBeingClicked = { + // 'marker':pointer, + // 'offset':evt.pageX-(lbl.offset().left+lbl.outerWidth(true)/2) + // }; + // consolelog(evt.pageX-(lbl.offset().left+lbl.outerWidth(true)/2)); + // evt.stopPropagation(); //dont notify the ruler; + // return false; + // }); + pointer = this.add(0); + this.getPointer = function(){ + return pointer; + }; + }else{ + pointer.refreshPosition(); + + } + this.each(function(i,rulermarker){ + rulermarker.refreshPosition(); + }); + + // if(!pointer){ + // this.debug("Creating pointer:"+layout); + // //this.createMarkerForRuler = function(rulerLayout,viewer,className, fontSize, optionalToolTip) + // pointer = this.createMarkerForRuler($J(layout.get(0)),waveContainer,'pointer',fontSize,'move pointer'); + // this.debug('pointerdisplay'+pointer.css('display')); + // } + + //TODO: move pointer?????? + //this._movePointer(sound.position/1000); + + + //TODO: draw markers? + // if (this.cfg.map) { + // $J(this.markers).each(function(i, m) { + // m.clear(); + // }); + // this.markers = new Array(); + // this.cfg.map.each(this.attach(function(i, m) { + // this.markers.push(this._drawMarker(m, i)); + // })); + // } + }, - this._createPointer(); - //draw markers - if (this.cfg.map) { - $J(this.markers).each(function(i, m) { - m.clear(); - }); - this.markers = new Array(); - this.cfg.map.each(this.attach(function(i, m) { - this.markers.push(this._drawMarker(m, i)); - })); - } - //this._drawMarkers(); - }, - - // _drawMarkers: function() { - // if (this.cfg.map) { - // $J(this.markers).each(function(i, m) { - // m.clear(); - // }); - // this.markers = new Array(); - // this.cfg.map.each(this.attach(function(i, m) { - // this.markers.push(this._drawMarker(m, i)); - // })); - // } - // }, - - _createPointer: function() { - if (this.pointer) { - this.pointer.clear(); - } - this.pointer = new $N.RulerMarker({ - rulerLayout: this.layout.get(0), - viewer: this.waveContainer, - fontSize: this.cfg.fontSize, - zIndex: 1000, - top:0, - className: 'pointer', - tooltip: 'Move head', - canMove: true + //overridden: Note that the pointer is NOT cleared!!!!! + clear: function(){ + var markers = this._super(); + // if('getPointer' in this){ + // markers.push(this.getPointer()); + // } + for( var i=0; i").id('#' + $N.cssPrefix + 'pointerOffset').css('zIndex','10').appendTo(tsMainLabel); - // this.pointer.label = label; - // } - // } - - this.pointer - //.setText("+") - .setText($N.Util.makeTimeLabel(0)) - .observe('move', this.attach(this._onPointerMove)); - }, - - // _setPosition: function(offset) { - // this._movePointer(offset); - //// if (this.pointer) { - //// this.pointer.show(); - //// } - // }, - - - + } + }, + //overridden + //markerObjOrOffset can be a marker object (see in markermap) or any object with the fields isEditable and offset + add: function(markerObjOrOffset, indexIfMarker){ + var soundPosition; + var isMovable; + var markerClass; + + if(typeof markerObjOrOffset == 'number'){ + soundPosition = markerObjOrOffset; + isMovable = this.isInteractive(); + markerClass='pointer'; + }else{ + soundPosition = markerObjOrOffset.offset; + isMovable = markerObjOrOffset.isEditable && this.isInteractive(); + markerClass='marker'; + } + var container = this.getRulerContainer(); + var layout = container.find("."+this.cssPrefix + 'layout'); + var $J = this.$J; + var pointer = new RulerMarker($J(layout.get(0)),this.getWaveContainer(),markerClass); + //call super constructor + //if it is a pointer, dont add it + if(markerClass != 'pointer'){ + this._super(pointer,indexIfMarker); //add at the end + //note that setText is called BEFORE move as move must have the proper label width + this.each(indexIfMarker, function(i,rulermarker){ + rulermarker.setIndex(i,i!=indexIfMarker); + //rulermarker.setIndex.apply(rulermarker, [i,i!=indexIfMarker]); //update label width only if it is not this marker added + //as for this marker we update the position below (move) + }); + this.debug('added marker at index '+indexIfMarker+' offset: '+markerObjOrOffset.offset); + }else{ + //note that setText is called BEFORE move as move must have the proper label width + pointer.setText(this.makeTimeLabel(0)); + } + //proceed with events and other stuff: move (called AFTER setText or setText) + pointer.move(this.toPixelOffset(soundPosition)); + + //pointer.setText(markerClass== 'pointer' ? this.makeTimeLabel(0) : this.length); - // setPosition: function(offset) { - // if (!this.mouseDown) { - // this._setPosition(offset); - // } - // }, - - // shiftPosition: function(delta) { - // this.setPosition(this.pointerPos + delta); - // }, - - hidePointer: function() { - if (this.pointer) - this.pointer.hide(); - }, + //if there are no events to associate, return it. + if(!isMovable){ + return pointer; + } - setBuffering: function(state) { - if (this.pointer) { - this.pointer.blink(state); - } - }, - /* - _onClick: function(evt) { - var offset = (evt.pageX - this.container.offset().left) - / this.width * this.duration; - this._setPosition(offset); - this.fire('move', {offset: offset}); - }, -*/ - _onMouseDown: function(evt) { - this.mouseDown = true; - this._onMouseMove(evt); - evt.preventDefault(); //If this method is called, the default action of the event will not be triggered. - }, + //namespace for jquery event: + var eventId = 'markerclicked'; + var doc = $J(document); + var lbl = pointer.getLabel(); + + var me = this; - - _onMouseMove: function(evt) { - if (this.mouseDown) { - var pixelOffset = evt.pageX - this.container.offset().left; - this._movePointerAndUpdateSoundPosition(pixelOffset / this.width * this.duration); - //moves the pointer and fires onPointerMove - return false; - } - }, + var ismovingpointer = false; + var setmovingpointer = function(value){ + ismovingpointer = value; + } + //TODO: this method below private, but how to let him see in the bind below??? + this.setPointerMovingFromMouse = function(value){setmovingpointer(value);} + this.isPointerMovingFromMouse = function(){ return ismovingpointer;}; + //functions to set if we are moving the pointer (for player when playing) - _onMouseUp: function(evt) { - if (this.mouseDown) { - this.mouseDown = false; - this.debug('_onMouseUp:'+this.pointerPos+' '+this.cfg.sound.position); - //this.debug("mousedup"+this.cfg.sound.position) - } - return false; - }, - //called while playing, does not update sound position - _movePointer: function(offset) { + lbl.bind('mousedown.'+eventId,function(evt) { - if (offset < 0){ - offset = 0; - }else if (offset > this.duration){ - offset = this.duration; - } - var pixelOffset = offset / this.duration * this.width; - if (this.pointer) { - this.pointer.move(pixelOffset); //does NOT fire any move method - this.pointer.setText($N.Util.makeTimeLabel(offset)); + if(markerClass=='pointer'){ + me.setPointerMovingFromMouse(true); } - this.pointerPos = offset; - this.debug('_movePointer: position set to'+offset); - }, - //called by everything else than playing, same as _movePointer but updates also the sound position accordingly - _movePointerAndUpdateSoundPosition: function(offset) { - this._movePointer(offset); - this.cfg.sound.setPosition(parseInt(1000*this.pointerPos)); - }, - - _onPointerMove: function(evt, data) { - //this.debug('_onPointerMove:'+ this.pointerPos+' '+this.cfg.sound.position); - - this.mouseDown = true; - this._movePointerAndUpdateSoundPosition(data.offset / this.width * this.duration); - if(data.finish) { - // this.fire('move', { - // offset: this.pointerPos - // }); - this.mouseDown = false; - } - return false; - }, - - _observeMouseEvents: function(element) { - if(!(CURRENT_USER_NAME)){ - return; - } - element - .bind('click dragstart', function() { - return false; - }) - .bind('mousedown', this.attachWithEvent(this._onMouseDown)) - .bind('mousemove', this.attachWithEvent(this._onMouseMove)) - .bind('mouseup', this.attachWithEvent(this._onMouseUp)); - $J(document) - .bind('mousemove', this.attachWithEvent(this._onMouseMove)); - }, - - _drawMarker: function(marker, index) { - if (marker.offset < 0){ - marker.offset = 0; - }else if (marker.offset > this.duration){ - marker.offset = this.duration; - } - - pixelOffset = marker.offset / this.duration * this.width; + var startX = evt.pageX; //lbl.position().left-container.position().left; + var startPos = lbl.position().left+lbl.width()/2; - m = new $N.RulerMarker({ - rulerLayout: this.layout.get(0), - viewer: this.waveContainer, - fontSize: this.cfg.fontSize, - className: 'marker', - index: index, - tooltip: 'Move marker', - canMove: marker.isEditable + evt.stopPropagation(); //dont notify the ruler; + var newPos = startPos; + doc.bind('mousemove.'+eventId, function(evt){ + var x = evt.pageX; + newPos = startPos+(x-startX); + pointer.move(newPos); + //update the text if pointer + if(markerClass=='pointer'){ + pointer.setText(me.makeTimeLabel(me.toSoundPosition(newPos))); + } + return false; + }); - - if(marker.isEditable){ - m.observe('move', this.attach(this._onMarkerMove)) - } - //m.observe('move', this.attach(this._onMarkerMove)) - m - //.setText(index + 1) - .move(pixelOffset) - .show(); - return m; - }, - - _onMarkerMove: function(e, data) { - if (data.finish) { - var offset = data.offset / this.width * this.duration; - this.fire('markermove', { - index: data.index, - offset: offset - }); - } - }, - - //called from markermap after we retrieved the marker index: - onMapAdd: function(marker, index){ - this.markers.splice(index, 0, this._drawMarker(marker, index)); - }, - - // _onMapAdd2: function(e, data) { - // this.markers.push(this._drawMarker(data.marker, data.index)); - // }, - - remove: function(index){ - var rulermarker = this.markers[index]; - rulermarker.clear(); - this.markers.splice(index, 1); - }, + lbl.bind('click.'+eventId, function(){ + return false; + }); //to avoid scrolling + //TODO: what happens if the user releases the mouse OUTSIDE the browser???? + var mouseup = function(evt_){ + doc.unbind('mousemove.'+eventId); + doc.unbind('mouseup.'+eventId); + evt_.stopPropagation(); + //TODO: fire event marker moved (with the class name) + var data = { + 'markerElement':pointer, + 'soundPosition': me.toSoundPosition.apply(me,[newPos]), + 'markerClass':markerClass + }; + if(markerClass=='pointer'){ + me.setPointerMovingFromMouse(false); + } + me.fire('markermoved',data); + return false; + }; + doc.bind('mouseup.'+eventId, mouseup); + //lbl.bind('mouseup.'+eventId, mouseup); + // doc.bind('mouseup.'+eventId, function(evt){ + // consolelog(newPos); + // doc.unbind('mousemove.'+eventId); + // doc.unbind('mouseup.'+eventId); + // + // //TODO: fire event marker moved (with the class name) + // var data = { + // 'markerElement':pointer, + // 'soundPosition': me.toSoundPosition.apply(me,[newPos]), + // 'markerClass':markerClass + // }; + // me.fire('markermoved',data); + // return false; + // }); + return false; + }); - //it is assured that fromIndex!=toIndex and fromIndex!=toIndex+1 (see markermap.move) - // move: function(fromIndex, toIndex){ - // var m = this.markers.splice(fromIndex,1)[0]; //remove - // this.markers.splice(toIndex,0,m); //add - // }, - - updateMarkerIndices:function(fromIndex, toIndex){ - for(var i=fromIndex; i<=toIndex; i++){ - this.markers[i].setIndex(i); - } - }, - - _onDoubleClick: function(evt) { - if (CURRENT_USER_NAME) { - var offset = (evt.pageX - this.container.offset().left) - / this.width * this.duration; - this.fire('markeradd', { - offset: offset - }); - } - } + return pointer; - // , _onSoundProviderUpdate: function(e) { - // this.debug("spupdate"); - // - // //this.setDuration(this.cfg.soundProvider.getDuration()); - // this.setPosition(this.cfg.soundProvider.getPosition()); - // this.setBuffering(this.cfg.soundProvider.isBuffering() && this.cfg.soundProvider.isPlaying()); - // } - }); - $N.notifyScriptLoad(); + }, -}); + //moves the pointer, does not notify any listener. + //soundPosition is in seconds (float) + movePointer : function(soundPosition) { + var pointer = this.getPointer(); + if (pointer) { + var pixelOffset = this.toPixelOffset(soundPosition); + //first set text, so the label width is set, then call move: + pointer.setText(this.makeTimeLabel(soundPosition)); + pointer.move(pixelOffset); //does NOT fire any move method + } + //this.debug('moving pointer: position set to '+offset); + return soundPosition; + }, + //soundPosition is in seconds (float) + toPixelOffset: function(soundPosition) { + //this.debug('sPos:' + soundPosition+ 'sDur: '+this.getSoundDuration()); + var duration = this.getSoundDuration(); + if (soundPosition < 0){ + soundPosition = 0; + }else if (soundPosition > duration){ + soundPosition = duration; + } + var width = this.getContainerWidth(); + var pixelOffset = (soundPosition / duration) * width; + return pixelOffset; + }, + + //returns the soundPosition is in seconds (float) + toSoundPosition: function(pixelOffset) { + var width = this.getContainerWidth(); + if (pixelOffset < 0){ + pixelOffset = 0; + }else if (pixelOffset > width){ + pixelOffset = width; + } + var duration = this.getSoundDuration(); + var soundPosition = (pixelOffset / width) *duration; + return soundPosition; + } +}); \ No newline at end of file diff --git a/telemeta/htdocs/timeside/src/rulermarker.js b/telemeta/htdocs/timeside/src/rulermarker.js index 3304f4ec..6e516074 100644 --- a/telemeta/htdocs/timeside/src/rulermarker.js +++ b/telemeta/htdocs/timeside/src/rulermarker.js @@ -1,273 +1,180 @@ -/** - * TimeSide - Web Audio Components - * Copyright (c) 2008-2009 Samalyse - * Author: Olivier Guilyardi - * License: GNU General Public License version 2.0 - */ - -TimeSide(function($N, $J) { - - $N.Class.create("RulerMarker", $N.Core, { - id: null, - painter: null, - visible: false, - position: 0, - label: null, - blinking: false, - nodes: null, - mouseDown: false, - blinkAnimation: null, - mouseDownOffset:0, - - initialize: function($super, cfg) { - $super(); - //sets the fields required???? see ruler.js createPointer - this.configure(cfg, { - rulerLayout: [null, 'required'], - viewer: [null, 'required'], - fontSize: 10, - zIndex: null, - className: [null, 'required'], - index: null, - tooltip: null, - canMove: false - }); - this.cfg.rulerLayout = $J(this.cfg.rulerLayout); - this.cfg.viewer = $J(this.cfg.viewer); - - this.width = this.cfg.viewer.width(); - this.painter = new jsGraphics(this.cfg.viewer.get(0)); - this._create(); - if(this.cfg.canMove){ - this._observeMouseEvents(); - } - //if it is the pointer, cfg.index is undefined - if(cfg.index !== undefined && cfg.className!='pointer'){ - this.setIndex(cfg.index); - } - - }, - - setIndex: function(index){ - this.index = index; - this.setText(index+1); - }, - - free: function($super) { - this.cfg.rulerLayout = null; - this.cfg.viewer = null; - $super(); - }, - - clear: function() { - this.painter.clear(); - $J(this.painter.cnv).remove(); - this.label.remove(); - return this; - }, - - _create: function() { - this.debug('create marker'); - var y = this.cfg.rulerLayout.find('.' + $N.cssPrefix + 'label').outerHeight(); - //added by me:================ - if(this.cfg.className == "pointer"){ - y = 0; - } - //========================== - this.label = $J('') - .css({ - display: 'block', - width: '10px', - textAlign: 'center', - position: 'absolute', - fontSize: this.cfg.fontSize + 'px', - fontFamily: 'monospace', - top: y + 'px' - }) - .attr('href', '#') - .addClass($N.cssPrefix + this.cfg.className) - .append('') - .hide(); - - if (this.cfg.tooltip){ - this.label.attr('title', this.cfg.tooltip); - } - - this.cfg.rulerLayout.append(this.label); - - var height = this.cfg.viewer.height(); - var x = 0; - this.painter.drawLine(x, 0, x, height); - x = [-4, 4, 0]; - y = [0, 0, 4]; - - this.painter.fillPolygon(x, y); - this.painter.paint(); - this.nodes = $J(this.painter.cnv).children(); - - var style = {}; - if (this.cfg.zIndex) { - style.zIndex = this.cfg.zIndex; - this.label.css(style); - } - style.backgroundColor = ''; - - this.nodes.hide().css(style).addClass($N.cssPrefix + this.cfg.className) - .each(function(i, node) { - node.originalPosition = parseInt($J(node).css('left')); - }); - - }, +var RulerMarker = TimesideClass.extend({ + + init: function(rulerLayout, viewer, className) { + this._super(); + var $J = this.$J; + var fontSize = 10; + this.getFontSize = function(){ + return fontSize; + } + var zIndex = 1000; + var tooltip = ''; + //TODO: why viewer get(0) ? more than one? check and maybe simplify + var painter = new jsGraphics(viewer.get(0)); + //from create (oldCode) + var cssPref = this.cssPrefix; + var y = rulerLayout.find('.' + cssPref + 'label').outerHeight(); + //added by me:================ + if(className == "pointer"){ + y = 0; + } + //========================== + var label = $J('') + .css({ + display: 'block', + width: '10px', + textAlign: 'center', + position: 'absolute', + fontSize: fontSize + 'px', + fontFamily: 'monospace', + top: y + 'px' + }) + .attr('href', '#') + .addClass(cssPref + className) + .append('') + //.hide(); + + if (tooltip){ + label.attr('title', tooltip); + } - setText: function(text) { - if (this.label) { - text += ''; - var labelWidth = this._textWidth(text, this.cfg.fontSize) + 10; - labelWidth += 'px'; - if (this.label.css('width') != labelWidth) { - this.label.css({ - width: labelWidth - }); - } - this.label.find('span').html(text); - } - return this; - }, + rulerLayout.append(label); + + var height = viewer.height(); + var x = 0; + painter.drawLine(x, 0, x, height); + + x = [-4, 4, 0]; + y = [0, 0, 4]; + + painter.fillPolygon(x, y); + painter.paint(); + var nodes = $J(painter.cnv).children(); + + var style = {}; + if (zIndex) { + style.zIndex = zIndex; + label.css(style); + } + style.backgroundColor = ''; + //nodes.hide(); + nodes.css(style).addClass(cssPref + className) + .each(function(i, node) { + node.originalPosition = parseInt($J(node).css('left')); + }); + + //set the index, + var index = -1; + this.setIndex = function(idx, optionalUpdateLabelWidth){ + index = idx; + this.setText(idx+1, optionalUpdateLabelWidth ? true : false); + }; + this.getIndex = function(){ + return index; + } - move: function(pixelOffset) { - if (this.position != pixelOffset) { + //end======================================================= + //creating public methods: + this.getLabel = function(){ + return label; + } + + + //CODE HERE BELOW IS EXECUTED ONLY IF THE MARKER HAS CAN MOVE IMPLEMENTED. + //Otherwise, no mouse event can call these methods + //re-implement function move + var position = 0; + var relativePosition = 0; //position in percentage of container width, set it in move and use it in refreshPosition + + var mRound = Math.round; //instantiate the functio once + + this.move = function(pixelOffset) { + var width = viewer.width(); + if (position != pixelOffset) { if (pixelOffset < 0) { pixelOffset = 0; - } else if (pixelOffset >= this.width) { - pixelOffset = this.width - 1; + } else if (pixelOffset >= width) { + pixelOffset = width - 1; } - this.nodes.each(function(i, node) { - $J(node).css('left', Math.round(node.originalPosition + pixelOffset) + 'px'); + nodes.each(function(i, node) { + $J(node).css('left', mRound(node.originalPosition + pixelOffset) + 'px'); }); - var labelWidth = this.label.width(); - var labelPixelOffset = pixelOffset - labelWidth / 2; - if (labelPixelOffset < 0) - labelPixelOffset = 0; - else if (labelPixelOffset + labelWidth > this.width) - labelPixelOffset = this.width - labelWidth; - this.label.css({ - left: Math.round(labelPixelOffset) + 'px' - }); - this.position = pixelOffset; + position = pixelOffset; + this.refreshLabelPosition(width); + //store relative position (see refreshPosition below) + relativePosition = pixelOffset == width-1 ? 1 : pixelOffset/width; } return this; - }, + }; - show: function(offset) { - if (!this.visible) { - this.nodes.show(); - this.label.show(); - this.visible = true; + this.refreshLabelPosition = function(optionalContainerWidth){ + if(!(optionalContainerWidth)){ + optionalContainerWidth = viewer.width(); } - return this; - }, - - hide: function() { - this.nodes.hide(); - this.label.hide(); - this.visible = false; - return this; - }, - - isVisible: function() { - return this.visible; - }, - - blink: function(state) { - var speed = 200; - if (this.label && this.blinking != state) { - var span = this.label.find('span'); - - span.stop(); - - function fade(on) { - if (on) { - span.animate({ - opacity: 1 - }, speed, null, - function() { - fade(false) - }); - } else { - span.animate({ - opacity: 0.4 - }, speed, null, - function() { - fade(true) - }) - } - } - - if (state) { - fade(); - } else { - span.animate({ - opacity: 1 - }, speed); - } - - this.blinking = state; + var width = optionalContainerWidth; + var pixelOffset = position; + var labelWidth = label.outerWidth(); //consider margins and padding //label.width(); + var labelPixelOffset = pixelOffset - labelWidth / 2; + if (labelPixelOffset < 0){ + labelPixelOffset = 0; + }else if (labelPixelOffset + labelWidth > width){ + labelPixelOffset = width - labelWidth; } - return this; - }, + label.css({ + left: mRound(labelPixelOffset) + 'px' + }); - _onMouseDown: function(evt) { - this.mouseDown = true; - this.mouseDownOffset = evt.pageX-(this.label.offset().left+this.label.outerWidth(true)/2); - //this._onMouseMove(evt); - return false; - }, + }; + + //function called on ruler.resize. Instead of recreating all markers, simply redraw them + this.refreshPosition = function(){ + var width = viewer.width(); + //store relativePosition: + var rp = relativePosition; + this.move(mRound(relativePosition*width)); + //reset relative position, which does not have to change + //but in move might have been rounded: + relativePosition = rp; + //last thing: resize the vertical line. + //Assumptions (having a look at the web page element with a debugger and the code above + //which uses jsgraphics): + //The line is the first item (see drawLine above) + //not only the height, but also the height of the clip property must be set + var h = viewer.height(); + $J(nodes[0]).css({ + 'height':h+'px', + 'clip': 'rect(0px 1px '+h+'px 0px)' + }); + } - _onMouseMove: function(evt) { - if (this.mouseDown) { - var offset = (evt.pageX - this.cfg.rulerLayout.offset().left)-this.mouseDownOffset; - this.debug(evt.pageX); - this.debug(this.label.outerWidth(true)); - this.debug(this.cfg.rulerLayout.offset().left); - this.move(offset); - this.fire('move', { //calls move (see above) - offset: this.position, - finish: false + this.remove = function() { + painter.clear(); + $J(painter.cnv).remove(); + label.remove(); + return this; + }; + }, + + //sets the text of the marker, if the text changes the marker width and optionalUpdateLabelPosition=true, + //re-arranges the marker position to be center-aligned with its vertical line (the one lying on the wav image) + setText: function(text, optionalUpdateLabelPosition) { + var label = this.getLabel(); + if (label) { + text += ''; + var labelWidth = this._textWidth(text, this.getFontSize()) + 10; + var oldWidth = label.width(); + if (oldWidth != labelWidth) { + label.css({ + width: labelWidth+'px' }); - return false; } - }, - - _onMouseUp: function(evt) { - if (this.mouseDown) { - this.mouseDown = false; - this.fire('move', { - index: this.index, - offset: this.position, - finish: true - }); - return false; + label.find('span').html(text); + if(oldWidth != labelWidth && optionalUpdateLabelPosition){ + consolelog('refreshing label position'); + this.refreshLabelPosition(); } - }, - - _observeMouseEvents: function() { - this.label.mousedown(this.attachWithEvent(this._onMouseDown)) - .bind('click dragstart', function() { - return false; - }); - this.cfg.rulerLayout.mousemove(this.attachWithEvent(this._onMouseMove)); - this.cfg.rulerLayout.mouseup(this.attachWithEvent(this._onMouseUp)); - $J(document).mouseup(this.attachWithEvent(this._onMouseUp)); } - - // _toString: function() { - // return ""; - // } - - - }); - - $N.notifyScriptLoad(); + return this; + } }); diff --git a/telemeta/htdocs/timeside/src/timeside.js b/telemeta/htdocs/timeside/src/timeside.js index 19055133..30f33f88 100644 --- a/telemeta/htdocs/timeside/src/timeside.js +++ b/telemeta/htdocs/timeside/src/timeside.js @@ -1,133 +1,628 @@ /** * TimeSide - Web Audio Components - * Copyright (c) 2008-2009 Samalyse - * Author: Olivier Guilyardi + * Author: Riccardo Zaccarelli and Olivier Guilyardi * License: GNU General Public License version 2.0 */ -//this global variable SEEMS to do a check on the variable jQuery, then -//simply executes the argument (which is a function) -var TimeSide = function() { - //arguments is an array-like object corresponding to the arguments passed to a function - if (arguments[0]) { - var toolkit = null; - if (typeof jQuery != 'undefined'){ - toolkit = jQuery; - } - //call arguments[0] (a function) with arguments this and jQuery - (arguments[0])(TimeSide, toolkit) - } -}; -//this is the first function instantiated. It SEEMS to check the document status and -//load synchronously all the scripts -TimeSide(function($N, $J) { - - $N.isDomLoaded = false; - $N.isLoaded = false; - $N.isLoading = false; - $N.onLoadCallbacks = []; - $N.cssPrefix = 'ts-'; - $N.debugging = true; - - $J(document).ready(function () { - $N.isDomLoaded = true; - }); +/* Simple JavaScript Inheritance + * By John Resig http://ejohn.org/ + * MIT Licensed. + * (Inspired by base2 and Prototype) + * + * In my opinion the lightest (no tons of heavy .js files to be included) and + * easiest (no days spent understanding what is going on) way to implement inhertance and OOP in + * javascript. Usages can be found below. + * Basically, + * 1) a new Class is instantiated with Class.extend(). This function takes a dictionary + * of properties/methods which will be put IN THE PROTOTYPE of the class, so that each instance will share the same properties/methods + * and the latter don't have to be created for each instance separately. + * 2) If var A = Class.extend({...}) and var B = A.extend({..}), then methods which are found in B will override the same methods in A. + * In this case, the variable this._super inside the overridden methods will refers to the super-method and can thus be called safely. + * Consequently, if a _super property/method is implemented in the extend dictionary, it WILL NOT be accessible + * to the overriding methods of B. Basically, don't use _super as a key of the argument of extend. + * 3) AFTER the prototype has been populated, the init function, if exists, is called. The latter can be seen as a class constructor in java. + * Private variable can be declared in the init function (which is used as class constructor), as well as + * relative getters and setters, if needed. Downside is that the privileged getters and setters can’t be put in the prototype, + * i.e. they are created for each instance separately. Another issue is the overhead of closures in general (basically, write as less as possible + * in the init functionm in particular if the class has to be declared several times) + * Of course, the this._super keyword of methods implemented in the init constructor does not work + * + * EXAMPLE: + * var MyClass = Class.extend({ + * init: function(optionalArray){ //constructor + * this._super(); //!!!ERROR: Class is the base class and does not have a super construcor + * var me = []; //private variable + * this.count = 6; //set the value of the public property defined below + * this.getMe = function(){ //public method + * this._super(); //!!!ERROR: methods defined in the init function don't have acces to _super + * } + * this.alert = function(){ //another public method, !!!WARNING: this will be put in the MyClass scope (NOT in the prototype) + * alert('ok'); + * } + * }, + * count:0, //public property + * alert: function(){ //public method. !!!WARNING: this method will be put in the prototype BEFORE the init is called, + * alert('no'); // so the alert defined above will be actually called + * } + * }); + * var MyClass2 = MyClass.extend({ + * init: function(){ + * this._super(); //call the super constructor + * } + * alert: function(){ //override a method + * this._super(); //call the super method, ie alerts 'no'. WARNING: However, as long as there is an alert written + * //in the init method of the superclass (see above), THAT method will be called + * } + * }); + * + */ +// +(function(){ + + var initializing = false, fnTest = /xyz/.test(function(){ + xyz; + }) ? /\b_super\b/ : /.*/; + + /*The xyz test above determines whether the browser can inspect the textual body of a function. + *If it can, you can perform an optimization by only wrapping an overridden method if it + *actually calls this._super() somewhere in its body. + *Since it requires an additional closure and function call overhead to support _super, + *it’s nice to skip that step if it isn’t needed. + */ + + // The base Class implementation (does nothing) + this.Class = function(){}; + + // Create a new Class that inherits from this class + Class.extend = function(prop) { + var _super = this.prototype; + + // Instantiate a base class (but only create the instance, + // don't run the init constructor) + initializing = true; + var prototype = new this(); + initializing = false; + + // Copy the properties over onto the new prototype + for (var name in prop) { + // Check if we're overwriting an existing function + prototype[name] = typeof prop[name] == "function" && + typeof _super[name] == "function" && fnTest.test(prop[name]) ? + (function(name, fn){ + return function() { + var tmp = this._super; - $N.domReady = function(callback) { - // simply calling jQuery.ready() *after* the DOM is loaded doesn't work reliably, - // at least with jQuery 1.2.6 - if ($N.isDomLoaded) { - callback(); - } else{ - $J(document).ready(callback); + // Add a new ._super() method that is the same method + // but on the super-class + this._super = _super[name]; + + // The method only need to be bound temporarily, so we + // remove it when we're done executing + var ret = fn.apply(this, arguments); + this._super = tmp; + + return ret; + }; + })(name, prop[name]) : + prop[name]; } - } - $N.instances = []; - $N.registerInstance = function(obj) { - $N.instances.push(obj); - } + // The dummy class constructor + function Class() { + // All construction is actually done in the init method + if ( !initializing && this.init ){ + this.init.apply(this, arguments); + } + } - $N.free = function() { - $J($N.instances).each(function(i, obj) { - obj.free(); - }); - } + // Populate our constructed prototype object + Class.prototype = prototype; + + // Enforce the constructor to be what we expect + Class.constructor = Class; + + // And make this class extendable + Class.extend = arguments.callee; - $J(window).unload($N.free); + return Class; + }; +})(); - $N.loadScriptsNum = 0; - $N.loadScriptsCallback = null; - $N.loadScripts = function(root, scripts, callback) { - if ($N.loadScriptsCallback) { - throw "Timeside loader error: concurrent script loading"; +//Defining the base TimeClass class. Player, Ruler, MarkerMap are typical implementations (see js files) +//Basically we store here static methods which must be accessible +//in several timside sub-classes +var TimesideClass = Class.extend({ + + _textWidth : function(text, fontSize) { + var ratio = 3/5; + return text.length * ratio * fontSize; + }, + + //formats (ie returns a string representation of) a time which is in the form seconds,milliseconds, eg 07.6750067 + //formatArray is an array of strings which can be: + // 'h' hours. Use 'hh' for a zero-padding to 10 (so that 6 hours is rendered as '06') + // 'm' hours. Use 'mm' for a zero-padding to 10 (so that 6 minutes is rendered as '06') + // 's' hours. Use 'ss' foar a zero-padding to 10 (so that 6 seconds is rendered as '06') + // 'D' deciseconds + // 'C' centiseconds (it will be padded to 10, so that 5 centiseconds will be rendered as '05') + // 'S' milliseconds (it will be padded to 100, so that 5 milliseconds will be rendered as '005') + // If formatArray is null or undefined or zero-length, it defaults to ['mm','ss'] + // 'h','m' and 's' will be prepended the separator ':'. For the others, the prepended separator is '.' + // Examples: + // makeTimeLabel(607,087) returns '10:07' + // makeTimeLabel(611,087,['m':'s']) returns '10:7' + // makeTimeLabel(611,087,['m':'s','C']) returns '10:7.09' + //======================================================================================== + makeTimeLabel: function(time, formatArray){ + if(!(formatArray)){ + formatArray = ['mm','ss']; } + //marker offset is in float format second.decimalPart + var pInt = parseInt; + var round = Math.round; + var factor = 60*24; + var hours = pInt(time/factor); + time-=hours*factor; + factor = 60; + var minutes = pInt(time/factor); + time-=minutes*factor; + var seconds = pInt(time); + time-=seconds; - $N.loadScriptsNum = scripts.length; - $N.loadScriptsCallback = callback; + //here below the function to format a number + //ceilAsPowerOfTen is the ceil specifiedas integer indicating the relative power of ten + //(0: return the number as it is, 1: format as "0#" and so on) + //Examples: format(6) = "6", format(6,1)= "06", format(23,1)= "23" - var head= document.getElementsByTagName('head')[0]; - for (i = 0; i < scripts.length; i++) { + //first of all, instantiate the power function once (and not inside the function or function's loop): + var mpow = Math.pow; //instantiate mpow once + var format = function(integer,ceilAsPowerOfTen){ + var n = ""+integer; + if(!(ceilAsPowerOfTen)){ + return n; + } + var zero = "0"; //instantiating once increases performances??? + for(var i=0; i< ceilAsPowerOfTen; i++){ + if(integer0){ + ret[i] = separator+ret[i]; + } + } + return ret.join(""); + }, - var script = document.createElement('script'); - script.type = 'text/javascript'; - var debug = $N.debugging ? '?rand=' + Math.random() : ''; - script.src = root + scripts[i] + debug; - head.appendChild(script); + cssPrefix : 'ts-', + $J : jQuery, + debugging : true, + debug : function(message) { + if (this.debugging && typeof console != 'undefined' && console.log) { + console.log(message); + //console.log('TimeSide.' + this.__class__.__name__ + ': ' + message); } - } + }, + //init constructor. Define the 'bind' and 'fire' (TODO: rename as 'trigger'?) methods + //we do it in the init function so that we can set a private variable storing all + //listeners. This means we have to re-write all methods + init: function(){ + + //the map for listeners. Must be declared in the init as it's private and NOT shared by all instances + //(ie, every instance has its own copy) + var listenersMap={}; + //follows jquery bind. Same as adding a listener for a key + this.bind = function(key, callback, optionalThisArgInCallback){ + if(!(callback && callback instanceof Function)){ + this.debug('cannot bind '+key+' to callback: the latter is null or not a function'); + return; + } + var keyAlreadyRegistered = (key in listenersMap); + if(!keyAlreadyRegistered){ + listenersMap[key] = []; + } + listenersMap[key].push({ + callback:callback, + optionalThisArgInCallback:optionalThisArgInCallback + }); + }; + this.unbind = function(){ + if(arguments.length>0){ + var key = arguments[0]; + if(key in listenersMap){ + delete listenersMap[key]; + } + }else{ + listenersMap={}; + } + }; + this.fire = function(key, dataArgument){ + if(!(key in listenersMap)){ + this.debug(key+' fired but no binding associated to it'); + return; + } + var callbacks = listenersMap[key]; + var len = callbacks && callbacks.length ? callbacks.length : 0; + for(var i=0; i= l){ + return; + } + startInclusive = arg[0]=== undefined ? 0 : arg[0]; + endExclusive = l; + //callback = arg[len-1]; + break; + default: + startInclusive = arg[0]=== undefined ? 0 : arg[0]; + endExclusive = arg[1]=== undefined ? l : arg[1]; + //callback = arg[len-1]; + } + callback = arg[len-1]; + if(!(callback instanceof Function)){ + this.debug('callback NOT a function!!!'); + return; + } + var me =this.toArray(); + for(var i = startInclusive; i1 && arg[0] >= this.length){ + // return; + // } + // if(len>1 && startInclusive=== undefined){ + // consolelog('oops---------------- startindex'); + // }else if(len>2 && endExclusive=== undefined){ + // consolelog('oops---------------- endIndex'); + // } + // + // var formatvar = function(array,index, correctionValue){ + // if(array.length>index && array[index]!== undefined){ + // return array[index]; + // } + // return correctionValue; + // } + // var me =this.toArray(); + // var s = len > 1 ? formatvar(arg,0,0) : 0; + // var e = len > 2 ? formatvar(arg,1,me.length) : me.length; + // //consolelog(s+' - '+e); + // for(var i = s; ifrom+1) is to-1 + move : function(from, to){ + var pInt = parseInt; + if(pInt(from)!==from || pInt(to)!==to){ + return from; + } + var me =this.toArray(); + var len = me.length; + if((from<0 || from>len)||(to<0 || to>len)){ + return from; + } + //if we moved left to right, the insertion index is actually + //newIndex-1, as we must also consider the removal of the object at index from + if(to>from){ + to--; + } + if(from != to){ + var elm = me.splice(from,1)[0]; + me.splice(to,0,elm); + } + return to; + } +}); + + +/* + * Sets a "tab look" on some elements of the page. Takes at least 3 arguments, at most 5: + * 1st argument: an array (or a jquery object) of html elements, ususally anchors, representing the tabs + * 2nd argument: an array (or a jquery object) of html elements, ususally divs, representing the containers to be shown/hidden when + * clicking the tabs. The n-th tab will set the n-th container to visible, hiding the others. So order is important. Note that if tabs + * or container are jQuery objects, the html elements inside them are sorted according to the document order. That's why tabs and + * container can be passed also as javascript arrays, so that the binding n-th tab -> n-th container can be decided by the user + * regardeless on how elements are written on the page, if already present + * 3rd argument: the selected index. If missing it defaullts to zero. + * 4th argument: selectedtab class. Applies to the selected tab after click of one tab. If missing, nothing is done + * 5th argument the unselectedtab class. Applies to all tabs not selected after click of one tab. If missing, nothing is done + * + * NOTE: that the last 2 arguments are intended for css "visual look", as the relevant css will be overridden inside the code. + * With relevant css we mean all css necessary to let tabs behave like a 'desktop application tab' (eg, (position, top, zIndex). Note also + * that very tab parent container' of every tab's visibility is set to 'visible' (the default) + * + * Examples: + * setUpPlayerTabs([jQuery('#tab1),jQuery('#tab1)], [jQuery('#div1),jQuery('#div2)], 1); + * sets the elements with id '#tab1' and '#tab2' as tab and assign the click events to them so that clicking tab_1 will show '#div_1' + * (and hide '#div2') and viceversa for '#tab2'. The selected index will be 1 (second tab '#tab2') +*/ +function setUpPlayerTabs() {//called from within controller.js once all markers have been loaded. + //this is because we need all divs to be visible to calculate size. selIndex is optional, it defaults to 0 + // + + var $J = jQuery; + var tabs_ = arguments[0]; + var divs_ = arguments[1]; //they might be ctually any content, div is a shoertand + + //converting arguments to array: tabs + var tabs=[]; + if(tabs_ instanceof $J){ + tabs_.each(function(i,elm){ + tabs.push(elm); }); + }else{ + tabs = tabs_; } + //set the overflow property of the parent tab to visible, otherwise scrollbars are displayed + //and the trick of setting position:relative+top:1px+zIndices (see css) doesnt work) + $J(tabs).each(function(i,tab){ + var t = $J(tab).attr('href','#'); + t.show(); //might be hidden + //set necessary style for the tab appearence: + var overflow = t.parent().css('overflow'); + if(overflow && overflow != 'visible'){ + t.parent().css('overflow','visible'); + } + }); + //converting arguments to array: divs + var divs=[]; + if(divs_ instanceof $J){ + divs_.each(function(i,elm){ + divs.push(elm); + }); + }else{ + divs = divs_; + } + + //reading remaing arguments (if any) + var selIndex = arguments.length>2 ? arguments[2] : 0; + var selectedTabClass = arguments.length>3 ? arguments[3] : undefined; + var unselectedTabClass = arguments.length>4 ? arguments[4] : undefined; -}); + //function to be associate to every click on the tab (see below) + var tabClicked = function(index) { + for(var i=0; i--> + + {% block extra_javascript %}{% endblock %} diff --git a/telemeta/templates/telemeta_default/mediaitem_detail.html b/telemeta/templates/telemeta_default/mediaitem_detail.html index 2cba3efc..12ddabdf 100644 --- a/telemeta/templates/telemeta_default/mediaitem_detail.html +++ b/telemeta/templates/telemeta_default/mediaitem_detail.html @@ -147,8 +147,9 @@   Loading... - - +
diff --git a/telemeta/urls.py b/telemeta/urls.py index 7a297449..a98b44dd 100644 --- a/telemeta/urls.py +++ b/telemeta/urls.py @@ -59,6 +59,7 @@ export_extensions = "|".join(web_view.list_export_extensions()) htdocs = os.path.dirname(__file__) + '/htdocs' + urlpatterns = patterns('', url(r'^$', web_view.index, name="telemeta-home"), url(r'^help$', web_view.help, name="telemeta-help"), @@ -253,5 +254,11 @@ urlpatterns = patterns('', # Not allowed url(r'/*/(?P[A-Za-z0-9._-]+)/not_allowed/$', web_view.not_allowed, name="telemeta-not-allowed"), - + + #i18n javascript + url(r'^jsi18n/(?P\S+?)/$', 'django.views.i18n.javascript_catalog', name="telemeta-js-translations") + + ) + + -- 2.39.5