From: riccardo Date: Tue, 24 May 2011 14:42:42 +0000 (+0200) Subject: using svg + raphael library X-Git-Tag: 1.1~163 X-Git-Url: https://git.parisson.com/?a=commitdiff_plain;h=18b63a6193aba7c8ccc09ffc5a7f4089e30044a1;p=telemeta.git using svg + raphael library --- diff --git a/telemeta/htdocs/timeside/js/playerLoader.js b/telemeta/htdocs/timeside/js/playerLoader.js index 1de86e3c..89a21a9e 100644 --- a/telemeta/htdocs/timeside/js/playerLoader.js +++ b/telemeta/htdocs/timeside/js/playerLoader.js @@ -56,6 +56,57 @@ function togglePlayerMaximization() { } } +//function loadPlayer_(htmlContainer, w, h, durationInMsec, soundUrl, callback){ +// var $ = jQuery; +// if(w<=0){ +// w = 360; +// } +// if(h<=0){ +// h= 130; +// } +// if(!callback || (typeof callback !== 'function')){ +// callback = function(){}; +// } +// if(!(htmlContainer instanceof $)){ +// htmlContainer = $(htmlContainer); +// } +// if(htmlContainer.length!=1){ +// throw 'invalid htmlContainer'; +// } +// var errMsg = ''; +// if(typeof durationInMsecOrAnalyzerUrl == 'number'){ +// load_player(soundUrl, durationInMsecOrAnalyzerUrl, itemId, visualizers, currentUserName); +// }else{ +// +// $.ajax({ +// url: durationInMsecOrAnalyzerUrl, //'analyze/xml', +// dataType: 'xml', +// success: function(data){ +// //populatetable +// $J.each($J(data).find('data'),function(index,element){ +// var elm = $J(element); +// tableBody.append(''+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(":"); +// //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, itemId, visualizers, currentUserName); +// }, +// error:function(){ +// errMsg = 'Error loading analyzer'; +// } +// }); +// } +//} function loadPlayer(analizerUrl, soundUrl, itemId, visualizers, currentUserName, isStaffOrSuperuser){ diff --git a/telemeta/htdocs/timeside/js/ruler.js b/telemeta/htdocs/timeside/js/ruler.js index d686ac2f..d7173f85 100644 --- a/telemeta/htdocs/timeside/js/ruler.js +++ b/telemeta/htdocs/timeside/js/ruler.js @@ -24,9 +24,10 @@ /** * Class representing the ruler (upper part) of the player. Requires jQuery - * wz_jsgraphics. + * and Raphael */ var Ruler = TimesideArray.extend({ + //init constructor: soundDuration is IN SECONDS!!! (float) init: function(viewer, soundDuration){ this._super(); @@ -42,19 +43,14 @@ var Ruler = TimesideArray.extend({ 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!!!! + //TODO: we dont need containerWiever here!!! + //better: it is usefult only for the canvas defined below. However... 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(){ @@ -63,174 +59,44 @@ var Ruler = TimesideArray.extend({ }, 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: + var duration = this.getSoundDuration(); //in seconds 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 (or children of it) nor marker (or children of it) - 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); + //build a canvas with raphael: + //setting global attributes: + var backgroundcolor = '#333'; + var lineAttr = { + 'stroke-width':1, + 'stroke':'#eeeeee' + }; + var rulerContainer = this.getRulerContainer(); + rulerContainer.css({'backgroundColor':backgroundcolor}); - //creating sections - //defining function maketimelabel - var makeTimeLabel = this.makeTimeLabel; - - //defining the function createSection - var _createSection = function(timeOffset, pixelWidth,timeLabelWidth) { - var section = $J('
') - .addClass(cssPref + 'section') - .css({ - fontSize: fontSize + 'px', - fontFamily: 'monospace', - width: pixelWidth, - overflow: 'hidden' - }) - .append($J('
').addClass(cssPref + 'canvas')); - - var topDiv = $J('
') - .addClass(cssPref + 'label') - .appendTo(section); - var bottomDiv = $J('
') - .addClass(cssPref + 'lines') - - .appendTo(section); - var empty = $J('').css({ - visibility: 'hidden' - }).text(' '); - var text; - - if (pixelWidth > timeLabelWidth) { - text = $J('') - .text(makeTimeLabel(timeOffset)) - .bind('mousedown selectstart', function() { //WHY THIS? - return false; - }); - } else { - 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; + //remove all elements not pointer or marker + rulerContainer.find(':not(a.ts-pointer,a.ts-marker,a.ts-pointer>*,a.ts-marker>*)').remove(); - } - 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; - } + //set font size (maybe this will be placed in a global or static variable) + var h = 28; //TODO: change it (global var?) + var obj = this.calculateRulerElements(rulerContainer.width(),h,duration); + consolelog(obj); - //function to draw section rulers: - var _drawSectionRuler= function(section, drawFirstMark) { - var j; - - 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 += sectionSubDivision) { - if (j == 0) { - if (drawFirstMark) { - ypos = 0; - } else { - continue; - } - } else { - ypos = (j == section.duration / 2) ? 1/2 + 1/8 : 3/4; - } - //var x = j / this.duration * this.width; - var x = j / duration * width; - jg.drawLine(x, height * ypos, x, height - 1); + var paper = Raphael(rulerContainer[0], rulerContainer.width(), h); + var path = paper.path(obj.path); + path.attr(lineAttr); + + var labels = obj.labels; + if(labels){ + var $J = this.$J; + for(var i=0; i ').html(labels[i][0]).css({'color':'white', 'display':'block','position':'absolute','top':'0', 'left':labels[i][1]+'px'}); + rulerContainer.append(span); } - jg.paint(); - }; - //draw section rulers - for (i = 0; i <= sectionsNum; i++) { - _drawSectionRuler(sections[i], (i > 0)); } - var pointer = undefined; if('getPointer' in this){ pointer = this.getPointer(); @@ -250,6 +116,70 @@ var Ruler = TimesideArray.extend({ }, + /** + * returns an object with the following properties: + * path: (string) the path of the ruler to be drawn + * labels (array) an array of arrays ['text',x,y] + */ + calculateRulerElements: function(w,h){ + var fontSize = 10; + var duration = this.getSoundDuration(); + + //the fontSize is actually a measure og height, seo we can set: + var fontMargin = 2; + + + var timeLabelWidth = this.textWidth('00:00', fontSize); + var timeLabelDuration = timeLabelWidth*duration/w; + + //determine the ticks: + var sectionDurations = [1,2,5,10,30,60,120,300,1800,3600,7200,18000,36000]; + //sectionDurations in seconds. Note that 60=1 minute, 3600=1 hour (so the maximum sectionDuration is 36000=10hours) + var i=0; + var len = sectionDurations.length; + while(isectionDurations[i]){ + i++; + } + var sectionDuration = sectionDurations[i]; + var sectionNums = parseInt(0.5+(duration/sectionDurations[i])); //ceil + var sectionWidth = w*sectionDuration/duration; + + var tickCounts = [10,5,2,1]; + i=0; + var tickCount = tickCounts[0]; + while(isectionWidth){ + i++; + } + var tickAtHalfSectionWidthHigher = i==0 || i==2; //draw tick at half section higher if ticks are even + tickCount = tickCounts[i]; + var tickWidth = sectionWidth/tickCount; + var makeTimeLabel = this.makeTimeLabel; + var h_1 = h-1; //TODO: use line tickness instead of 1 + var path = new Array(parseInt(0.5+(w/tickWidth))); + consolelog(path.length); + path[0] = ['M 0 '+h_1]; + len = path.length; + for(i=0; i < len; i+=tickCount){ + for(var j=1; j') .css({ display: 'block', - width: '10px', + width: fontSize +'px', textAlign: 'center', position: 'absolute', fontSize: fontSize + 'px', fontFamily: 'monospace', - top: y + 'px' + top: 0 }) .attr('href', '#') .addClass(cssPref + className) - .append('') - //.hide(); - + .html('0'); //the text inside the span is FUNDAMENTAL, although it will be replaced later, + //to calculate now the label position (see below *) + + if (tooltip){ label.attr('title', tooltip); } - 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(); + if(rulerDiv.css('position')!='relative'){ + rulerDiv.css({ + 'position':'relative' + }); + } + rulerDiv.append(label); + + if(className != "pointer"){ + label.css('top',rulerDiv.height()-label.outerHeight()); + } + 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){ @@ -107,17 +100,51 @@ var RulerMarker = TimesideClass.extend({ this.getLabel = function(){ return label; } - - this.getViewer = function(){ - return viewer; + + + this.getRulerWidth = function(){ + return rulerDiv.width(); } - this.getPainter = function(){ - return painter; + this.getWaveHeight = function(){ + return waveImgDiv.height(); } this.positionInPixels = 0; this.positionAsViewerRatio = 0; + var tW = 2*((fontSize - 1) >>> 1)+1; //if fontsize:10 or 9, tW:9, if fontSize:8 or 7, tW:7, and so on + + var fillColor = this.class2CanvasColor[className]; + var canvas = undefined; + if(this.isSvgSupported()){ + canvas = this.createCanvasSvg(waveImgDiv, tW); + var path = canvas.childNodes[0]; //note that $J(canvas).find('path') does not work in FF at least 3.5 + path.setAttributeNS(null,'fill',fillColor); + path.setAttributeNS(null,'stroke-width',0); + this.moveCanvas = function(pixelOffset){ + //consolelog(pixelOffset); + canvas.setAttributeNS( null, "transform", "translate("+pixelOffset+",0)"); + } + this.jQueryCanvas = $J(canvas); + }else{ + canvas = this.createCanvasVml(waveImgDiv, tW); + this.jQueryCanvas = $J(canvas.node); + var attributes = { + 'stroke-width':'0', + 'fill':fillColor + }; + canvas.attr(attributes); //Raphael method + this.moveCanvas = function(pixelOffset){ + //for some reason, coordinates inside the VML object are stored by raphael with a zoom of 10: + this.jQueryCanvas.css('left',(10*pixelOffset)+'px'); + } + //apparently, when resizing the markers loose their attributes. Therefore: + var r = this.refreshPosition; //reference to current refreshPosition + this.refreshPosition = function(){ + r.apply(this); + canvas.attr(attributes); + } + } }, //sets the text of the marker, if the text changes the marker width and optionalUpdateLabelPosition=true, @@ -143,24 +170,22 @@ var RulerMarker = TimesideClass.extend({ getNodes: function(){ - return this.$J(this.getPainter().cnv).children(); + return this.$J([]); + //return this.$J(this.getPainter().cnv).children(); }, //these methods are executed only if marker is movable (see Ruler.js) move : function(pixelOffset) { - var width = this.getViewer().width(); + var width = this.getRulerWidth(); if (this.positionInPixels != pixelOffset) { if (pixelOffset < 0) { pixelOffset = 0; } else if (pixelOffset >= width) { pixelOffset = width - 1; } - var nodes = this.getNodes(); - var $J = this.$J; - var mRound = this.mRound; - nodes.each(function(i, node) { - $J(node).css('left', mRound(node.originalPosition + pixelOffset) + 'px'); - }); + //defined in the conmstructor (it depends on wehter the current browser supports SVG or not) + this.moveCanvas(pixelOffset); + this.positionInPixels = pixelOffset; this.refreshLabelPosition(width); //store relative position (see refreshPosition below) @@ -171,7 +196,7 @@ var RulerMarker = TimesideClass.extend({ refreshLabelPosition : function(optionalContainerWidth){ if(!(optionalContainerWidth)){ - optionalContainerWidth = this.getViewer().width(); + optionalContainerWidth = this.getRulerWidth(); } var label = this.getLabel(); var width = optionalContainerWidth; @@ -191,37 +216,96 @@ var RulerMarker = TimesideClass.extend({ //function called on ruler.resize. Instead of recreating all markers, simply redraw them refreshPosition : function(){ - var width = this.getViewer().width(); + var width = this.getRulerWidth(); //store relativePosition: var rp = this.positionAsViewerRatio; this.move(this.mRound(this.positionAsViewerRatio*width)); //reset relative position, which does not have to change //but in move might have been rounded: this.positionAsViewerRatio = 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 = this.getViewer().height(); - var nodes = this.getNodes(); - var $J = this.$J; - $J(nodes[0]).css({ - 'height':h+'px', - 'clip': 'rect(0px 1px '+h+'px 0px)' - }); }, + remove : function() { - var $J = this.$J; - var painter = this.getPainter(); var label = this.getLabel(); - painter.clear(); - $J(painter.cnv).remove(); label.remove(); + this.jQueryCanvas.remove(); //defined in the constructor return this; }, + + createCanvasSvg: function(container, arrowBaseWidth){ + // + var $J = this.$J; + var svgNS = "http://www.w3.org/2000/svg"; + var d = document; + var svg = undefined; + if(container.children().length>0){ + svg = container.children().get(0); + }else{ + svg = d.createElementNS(svgNS, "svg:svg"); + container.append($J(svg)); + } + var group = d.createElementNS(svgNS, "svg:g"); + group.setAttributeNS( null, "transform", "translate(0,0)"); + + var path = d.createElementNS(svgNS, "svg:path"); + path.setAttributeNS( null, "d", this.createCanvasPath(0,arrowBaseWidth)); + + group.appendChild(path); + svg.appendChild(group); + + return group; + //return $J('').attr('fill',fillColor).attr('style','fill:'+fillColor+';strokeWidth:0'); + }, + + createCanvasVml: function(container, arrowBaseWidth){ + //for creating a vml object, we make use of raphael to avoid a pain in the ... implementing a non standard Microsoft syntax + //(which, after a glance, it's even syntactically a mess) + //unfotunately (and this is a real lack not even planned to be fixed, see raphael forums), + //raphael does not allow to wrap existing object, so we have to register in this.elementToPaperMap (see timeside.js) + //which is a map where to each container is associated a raphael paper: + var paper = this.elementToPaperMap && this.elementToPaperMap[container.get(0)]; + if(!paper){ + var obj = this.elementToPaperMap; + if(!obj){ + this.elementToPaperMap = {}; + obj = this.elementToPaperMap; + } + paper = Raphael(container.get(0),container.width(),container.height()); + obj[container.get(0)] = paper; + //paper canvas is a div with weird dimensions. You can check it by printing paper.canvas.outerHTML in IE. + //We set them to 100% so we dont have clipping regions when resizing (maximizing) + paper.canvas.style.width='100%'; + paper.canvas.style.height='100%'; + paper.canvas.width='100%'; + paper.canvas.height='100%'; + //apparently, there is also a clip style declaration. The following code trhows an error in IE7: + //paper.canvas.style.clip = 'auto'; + //however, even leaving the clip style declaration as it is, it seems to work (the div spans the whole width) + } + + + var shape = paper.path(this.createCanvasPath(0, arrowBaseWidth)); + return shape; + }, + + //w must be odd. Cause le central line must be centered. Example: + // + // xxxxx + // xxx + // x + // x + // x + // + createCanvasPath: function(x,w){ + var halfW = w >>> 1; + var h = this.$J(window).height(); + return 'M '+(x-halfW)+' 0 L '+(x)+' '+(halfW)+' L '+x+' '+h+ + ' L '+ (x+1)+' '+h+' L '+(x+1)+ ' '+(halfW)+' L '+(x+halfW+1)+' 0 z'; + }, + + //used for faster lookup mRound: Math.round }); diff --git a/telemeta/htdocs/timeside/js/timeside.js b/telemeta/htdocs/timeside/js/timeside.js index 673a39d4..adfaf363 100644 --- a/telemeta/htdocs/timeside/js/timeside.js +++ b/telemeta/htdocs/timeside/js/timeside.js @@ -154,6 +154,19 @@ //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({ + /** + * Returns whether SVG is supported. If it is the case, this property can simply return true. + * For a more clean code, one should remove this property, elementToPaperMap (see below), + * check where we call isSvgSupported (ruler.js and rulermarker.js) and eventually + * remove the vml methods in ruler.js and rulermarker.js + */ + isSvgSupported : function(){return Raphael.svg}, + /** + * Raphael unfortunately does not allow to wrap existing elements, which is a big lack not even planned to be implemented in + * future releases (see raphael forum). Therefore, we store here a map which binds html elements -> Raphael paper object + * This property can be deleted if svg is supported + */ + elementToPaperMap: {}, /** * function to calculate the text width according to a text and a given fontsize diff --git a/telemeta/templates/telemeta_default/mediaitem_detail.html b/telemeta/templates/telemeta_default/mediaitem_detail.html index f71357d1..a5221aeb 100644 --- a/telemeta/templates/telemeta_default/mediaitem_detail.html +++ b/telemeta/templates/telemeta_default/mediaitem_detail.html @@ -61,7 +61,7 @@ setTimeout(function(){ if(jQuery('#loading_span').length>0){ - playerError('SoundManager error. Try to:\n - Reload the page\n - Empty the cache (see browser preferences) and reload the page\n - Restart the browser'); + playerError('SoundManager is not responding. Try to:\n - Reload the page\n - Empty the cache (see browser preferences) and reload the page\n - Restart the browser'); } },10000); @@ -71,7 +71,7 @@ visualizers["{{v.name}}"] = "{% url telemeta-item-visualize item.public_id,v.id,"WIDTH","HEIGHT" %}"; {% endfor %} - var scripts = ["{% url telemeta-timeside "js/wz_jsgraphics.js" %}", "{% url telemeta-timeside "js/timeside.js" %}","{% url telemeta-timeside "js/playerLoader.js" %}"]; + var scripts = ["{% url telemeta-timeside "js/raphael-min.js" %}","{% url telemeta-timeside "js/wz_jsgraphics.js" %}", "{% url telemeta-timeside "js/timeside.js" %}","{% url telemeta-timeside "js/playerLoader.js" %}"]; jQuery(window).ready(function(){ //if soundmanager is ready, the callback is executed immetiately