client/js/paper-renderer.js
changeset 159 1796e0220bef
parent 157 cdfc0b1547f3
child 160 408da84d4dc0
--- a/client/js/paper-renderer.js	Fri Apr 19 11:30:33 2013 +0200
+++ b/client/js/paper-renderer.js	Fri Apr 19 13:39:39 2013 +0200
@@ -12,6 +12,9 @@
     _MIN_SCALE: 1/20,
     _MAX_SCALE: 20,
     _AUTOSCALE_MARGIN: 50,
+    _MOUSEMOVE_RATE: 80,
+    _DOUBLETAP_DELAY: 800,
+    _DOUBLETAP_DISTANCE: 20*20,
     _USER_PLACEHOLDER : function(_renkan) {
     	return {
     		color: _renkan.options.default_user_color,
@@ -25,11 +28,8 @@
     	return "(function(a,b,c,d,e,f,h,i,j,k,l,m,n,o,p,q,r){a=document;b=a.body;c=a.location.href;j='draggable';m='text/x-iri-';d=a.createElement('div');d.innerHTML='<p_style=\"position:fixed;top:0;right:0;font:bold_18px_sans-serif;color:#fff;background:#909;padding:10px;z-index:100000;\">"
 	    + _renkan.translate("Drag items from this website, drop them in Renkan").replace(/ /g,"_")
 	    + "</p>'.replace(/_/g,String.fromCharCode(32));b.appendChild(d);e=[{r:/https?:\\/\\/[^\\/]*twitter\\.com\\//,s:'.tweet',n:'twitter'},{r:/https?:\\/\\/[^\\/]*google\\.[^\\/]+\\//,s:'.g',n:'google'},{r:/https?:\\/\\/[^\\/]*lemonde\\.fr\\//,s:'[data-vr-contentbox]',n:'lemonde'}];f=false;e.forEach(function(g){if(g.r.test(c)){f=g;}});if(f){h=function(){Array.prototype.forEach.call(a.querySelectorAll(f.s),function(i){i[j]=true;k=i.style;k.borderWidth='2px';k.borderColor='#909';k.borderStyle='solid';k.backgroundColor='rgba(200,0,180,.1)';})};window.setInterval(h,500);h();};a.addEventListener('dragstart',function(k){l=k.dataTransfer;l.setData(m+'source-uri',c);l.setData(m+'source-title',a.title);n=k.target;if(f){o=n;while(!o.attributes[j]){o=o.parentNode;if(o==b){break;}}}if(f&&o.attributes[j]){p=o.cloneNode(true);l.setData(m+'specific-site',f.n)}else{q=a.getSelection();if(q.type==='Range'||!q.type){p=q.getRangeAt(0).cloneContents();}else{p=n.cloneNode();}}r=a.createElement('div');r.appendChild(p);l.setData('text/x-iri-selected-text',r.textContent.trim());l.setData('text/x-iri-selected-html',r.innerHTML);},false);})();"
-    }
-}
-
-Rkns.Renderer.Utils = {
-	shortenText : function(_text, _maxlength) {
+    },
+    shortenText : function(_text, _maxlength) {
 		return (_text.length > _maxlength ? (_text.substr(0,_maxlength) + '…') : _text);
 	},
     drawEditBox : function(_options, _coords, _path, _xmargin, _selector) {
@@ -75,106 +75,6 @@
             top: (_options.tooltip_padding + _top)
         });
         return _path;
-    },
-    sector : function(_repr, _inR, _outR, _startAngle, _endAngle, _padding, _imgsrc, _caption) {
-        var _options = _repr.renderer.renkan.options,
-        	_startRads = _startAngle * Math.PI / 180,
-            _endRads = _endAngle * Math.PI / 180,
-            _img = new Image(),
-            _span = _endRads - _startRads,
-            _startdx = - Math.sin(_startRads),
-            _startdy = Math.cos(_startRads),
-            _startXIn = Math.cos(_startRads) * _inR + _padding * _startdx,
-            _startYIn = Math.sin(_startRads) * _inR + _padding * _startdy,
-            _startXOut = Math.cos(_startRads) * _outR + _padding * _startdx,
-            _startYOut = Math.sin(_startRads) * _outR + _padding * _startdy,
-            _enddx = - Math.sin(_endRads),
-            _enddy = Math.cos(_endRads),
-            _endXIn = Math.cos(_endRads) * _inR - _padding * _enddx,
-            _endYIn = Math.sin(_endRads) * _inR - _padding * _enddy,
-            _endXOut = Math.cos(_endRads) * _outR - _padding * _enddx,
-            _endYOut = Math.sin(_endRads) * _outR - _padding * _enddy,
-            _centerR = (_inR + _outR)/2,
-            _centerRads = (_startRads + _endRads) / 2,
-            _centerX = Math.cos(_centerRads) * _centerR,
-            _centerY = Math.sin(_centerRads) * _centerR,
-            _centerXIn = Math.cos(_centerRads) * _inR,
-            _centerXOut = Math.cos(_centerRads) * _outR,
-            _centerYIn = Math.sin(_centerRads) * _inR,
-            _centerYOut = Math.sin(_centerRads) * _outR,
-            _textX = Math.cos(_centerRads) * (_outR + 3),
-            _textY = Math.sin(_centerRads) * (_outR + _options.buttons_label_font_size) + _options.buttons_label_font_size / 2,
-            _segments = [];
-    	_repr.renderer.buttons_layer.activate();
-        var _path = new paper.Path();
-        _path.add([_startXIn, _startYIn]);
-        _path.arcTo([_centerXIn, _centerYIn], [_endXIn, _endYIn]);
-        _path.lineTo([_endXOut,  _endYOut]);
-        _path.arcTo([_centerXOut, _centerYOut], [_startXOut, _startYOut]);
-        _path.fillColor = _options.buttons_background;
-        _path.opacity = .5;
-        _path.closed = true;
-        _path.__representation = _repr;
-        var _text = new paper.PointText(_textX,_textY);
-        _text.characterStyle = {
-            fontSize: _options.buttons_label_font_size,
-            fillColor: _options.buttons_label_color
-        };
-        if (_textX > 2) {
-            _text.paragraphStyle.justification = 'left';
-        } else if (_textX < -2) {
-            _text.paragraphStyle.justification = 'right';
-        } else {
-            _text.paragraphStyle.justification = 'center';
-        }
-        _text.visible = false;
-        var _visible = false,
-            _restPos = new paper.Point(-200, -200),
-            _grp = new paper.Group([_path, _text]),
-            _delta = _grp.position,
-            _imgdelta = new paper.Point([_centerX, _centerY]),
-            _currentPos = new paper.Point(0,0);
-        _text.content = _caption;
-        _grp.visible = false;
-        _grp.position = _restPos;
-        var _res = {
-            show: function() {
-                _visible = true;
-                _grp.position = _currentPos.add(_delta);
-                _grp.visible = true;
-            },
-            moveTo: function(_point) {
-                _currentPos = _point;
-                if (_visible) {
-                    _grp.position = _point.add(_delta);
-                }
-            },
-            hide: function() {
-                _visible = false;
-                _grp.visible = false;
-                _grp.position = _restPos;
-            },
-            select: function() {
-                _path.opacity = .8;
-                _text.visible = true;
-            },
-            unselect: function() {
-                _path.opacity = .5;
-                _text.visible = false;
-            },
-            destroy: function() {
-                _grp.remove();
-            }
-        }
-        _img.onload = function() {
-            var _h = _img.height;
-            var _raster = new paper.Raster(_img);
-            _raster.position = _imgdelta.add(_grp.position).subtract(_delta);
-            _grp.addChild(_raster);
-            _repr.renderer.throttledPaperDraw();
-        }
-        _img.src = _imgsrc;
-        return _res
     }
 }
 
@@ -333,8 +233,8 @@
     }
     this.last_circle_radius = this.circle_radius;
     
-    var _text = this.model.get("title") || this.renkan.translate("(untitled)");
-	_text = Rkns.Renderer.Utils.shortenText(_text, this.options.node_label_max_length);
+    var _text = this.model.get("title") || this.renkan.translate(this.options.label_untitled_nodes) || "";
+	_text = Rkns.Renderer.shortenText(_text, this.options.node_label_max_length);
 	this.title.text(_text);
 	this.title.css({
 		left: this.paper_coords.x,
@@ -608,8 +508,8 @@
         _a += 180;
         _textdelta = _textdelta.multiply(-1);
     }
-    var _text = this.model.get("title");
-	_text = Rkns.Renderer.Utils.shortenText(_text, this.options.node_label_max_length);
+    var _text = this.model.get("title") || this.renkan.translate(this.options.label_untitled_edges) || "";
+	_text = Rkns.Renderer.shortenText(_text, this.options.node_label_max_length);
     this.text.text(_text);
     var _textpos = this.paper_coords.add(_textdelta);
     this.text.css({
@@ -804,38 +704,29 @@
 
 /* */
 
-Rkns.Renderer.NodeEditor = Rkns.Utils.inherit(Rkns.Renderer._BaseRepresentation);
+Rkns.Renderer._BaseEditor = Rkns.Utils.inherit(Rkns.Renderer._BaseRepresentation);
 
-Rkns.Renderer.NodeEditor.prototype._init = function() {
-    this.renderer.buttons_layer.activate();
-    this.type = "editor";
-    this.editor_block = new paper.Path();
-    var _pts = Rkns._(Rkns._.range(8)).map(function() {return [0,0]});
-    this.editor_block.add.apply(this.editor_block, _pts);
-    this.editor_block.strokeWidth = this.options.tooltip_border_width;
-    this.editor_block.strokeColor = this.options.tooltip_border_color;
-    this.editor_block.opacity = .8;
-    this.editor_$ = Rkns.$('<div>')
-        .appendTo(this.renderer.editor_$)
-        .css({
-            position: "absolute",
-            opacity: .8
-        })
-        .hide();
+Rkns.Renderer._BaseEditor.prototype.destroy = function() {
+    this.editor_block.remove();
+    this.editor_$.detach();
 }
 
+/* */
+
+Rkns.Renderer.NodeEditor = Rkns.Utils.inherit(Rkns.Renderer._BaseEditor);
+
 Rkns.Renderer.NodeEditor.prototype.template = Rkns._.template(
-    '<h2><span class="Rk-CloseX">&times;</span><%-translate("Edit Node")%></span></h2>'
-    + '<p><label><%-translate("Title:")%></label><input class="Rk-Edit-Title" type="text" value="<%-node.title%>"/></p>'
-    + '<p><label><%-translate("URI:")%></label><input class="Rk-Edit-URI" type="text" value="<%-node.uri%>"/><a class="Rk-Edit-Goto" href="<%-node.uri%>" target="_blank"></a></p>'
-    + '<p><label><%-translate("Description:")%></label><textarea class="Rk-Edit-Description"><%-node.description%></textarea></p>'
-    + '<p><span class="Rk-Editor-Label"><%-translate("Size:")%></span><a href="#" class="Rk-Edit-Size-Down">-</a><span class="Rk-Edit-Size-Value"><%-node.size%></span><a href="#" class="Rk-Edit-Size-Up">+</a></p>'
-    + '<div class="Rk-Editor-p"><span class="Rk-Editor-Label"><%-translate("Node color:")%></span><div class="Rk-Edit-ColorPicker-Wrapper"><span class="Rk-Edit-Color" style="background:<%-node.color%>;"><span class="Rk-Edit-ColorTip"></span></span><ul class="Rk-Edit-ColorPicker">'
-    + '<% _(Rkns.pickerColors).each(function(c) { %><li data-color="<%=c%>" style="background: <%=c%>"></li><% }); %></ul><span class="Rk-Edit-ColorPicker-Text"><%- translate("Choose color") %></span></div></div>'
+    '<h2><span class="Rk-CloseX">&times;</span><%-renkan.translate("Edit Node")%></span></h2>'
+    + '<p><label><%-renkan.translate("Title:")%></label><input class="Rk-Edit-Title" type="text" value="<%-node.title%>"/></p>'
+    + '<p><label><%-renkan.translate("URI:")%></label><input class="Rk-Edit-URI" type="text" value="<%-node.uri%>"/><a class="Rk-Edit-Goto" href="<%-node.uri%>" target="_blank"></a></p>'
+    + '<p><label><%-renkan.translate("Description:")%></label><textarea class="Rk-Edit-Description"><%-node.description%></textarea></p>'
+    + '<p><span class="Rk-Editor-Label"><%-renkan.translate("Size:")%></span><a href="#" class="Rk-Edit-Size-Down">-</a><span class="Rk-Edit-Size-Value"><%-node.size%></span><a href="#" class="Rk-Edit-Size-Up">+</a></p>'
+    + '<div class="Rk-Editor-p"><span class="Rk-Editor-Label"><%-renkan.translate("Node color:")%></span><div class="Rk-Edit-ColorPicker-Wrapper"><span class="Rk-Edit-Color" style="background:<%-node.color%>;"><span class="Rk-Edit-ColorTip"></span></span><ul class="Rk-Edit-ColorPicker">'
+    + '<% _(Rkns.pickerColors).each(function(c) { %><li data-color="<%=c%>" style="background: <%=c%>"></li><% }); %></ul><span class="Rk-Edit-ColorPicker-Text"><%- renkan.translate("Choose color") %></span></div></div>'
     + '<img class="Rk-Edit-ImgPreview" src="<%-node.image || node.image_placeholder%>" />'
-    + '<p><label><%-translate("Image URL:")%></label><input class="Rk-Edit-Image" type="text" value="<%-node.image%>"/></p>'
-    + '<p><label><%-translate("Choose Image File:")%></label><input class="Rk-Edit-Image-File" type="file" accept="image/*"/></p>'    
-    + '<% if (node.has_creator) { %><p><span class="Rk-Editor-Label"><%-translate("Created by:")%></span> <span class="Rk-UserColor" style="background:<%-node.created_by_color%>;"></span><%- Rkns.Renderer.Utils.shortenText(node.created_by_title, 25) %></p><% } %>'
+    + '<p><label><%-renkan.translate("Image URL:")%></label><input class="Rk-Edit-Image" type="text" value="<%-node.image%>"/></p>'
+    + '<p><label><%-renkan.translate("Choose Image File:")%></label><input class="Rk-Edit-Image-File" type="file" accept="image/*"/></p>'    
+    + '<% if (node.has_creator) { %><p><span class="Rk-Editor-Label"><%-renkan.translate("Created by:")%></span> <span class="Rk-UserColor" style="background:<%-node.created_by_color%>;"></span><%- Rkns.Renderer.shortenText(node.created_by_title, 25) %></p><% } %>'
 );
 
 Rkns.Renderer.NodeEditor.prototype.readOnlyTemplate = Rkns._.template(
@@ -844,7 +735,7 @@
     + '<% if (node.uri) { %><p class="Rk-Display-URI"><a href="<%-node.uri%>" target="_blank"><%-node.short_uri%></a></p><% } %>'
     + '<p><%-node.description%></p>'
     + '<% if (node.image) { %><img class="Rk-Display-ImgPreview" src="<%-node.image%>" /><% } %>'
-    + '<% if (node.has_creator) { %><p><span class="Rk-Editor-Label"><%-translate("Created by:")%></span><span class="Rk-UserColor" style="background:<%-node.created_by_color%>;"></span><%- Rkns.Renderer.Utils.shortenText(node.created_by_title, 25) %></p><% } %>'
+    + '<% if (node.has_creator) { %><p><span class="Rk-Editor-Label"><%-renkan.translate("Created by:")%></span><span class="Rk-UserColor" style="background:<%-node.created_by_color%>;"></span><%- Rkns.Renderer.shortenText(node.created_by_title, 25) %></p><% } %>'
 );
 
 Rkns.Renderer.NodeEditor.prototype.draw = function() {
@@ -859,7 +750,7 @@
             	has_creator: !!_model.get("created_by"),
                 title: _model.get("title"),
                 uri: _model.get("uri"),
-                short_uri:  Rkns.Renderer.Utils.shortenText((_model.get("uri") || "").replace(/^(https?:\/\/)?(www\.)?/,'').replace(/\/$/,''),40),
+                short_uri:  Rkns.Renderer.shortenText((_model.get("uri") || "").replace(/^(https?:\/\/)?(www\.)?/,'').replace(/\/$/,''),40),
                 description: _model.get("description"),
                 image: _model.get("image") || "",
                 image_placeholder: _image_placeholder,
@@ -868,7 +759,7 @@
                 created_by_title: _created_by.get("title"),
                 size: (_size > 0 ? "+" : "") + _size
             },
-            translate: this.renkan.translate
+            renkan: this.renkan
         }));
     this.redraw();
     var _this = this,
@@ -931,16 +822,34 @@
         	}
         });
         this.editor_$.find(".Rk-Edit-Title")[0].focus();
+        
+        var _picker = _this.editor_$.find(".Rk-Edit-ColorPicker");
+        
         this.editor_$.find(".Rk-Edit-ColorPicker-Wrapper").hover(
-            function() { _this.editor_$.find(".Rk-Edit-ColorPicker").show(); },
-            function() { _this.editor_$.find(".Rk-Edit-ColorPicker").hide(); }
+            function(_e) {
+            	_e.preventDefault();
+            	_picker.show();
+        	},
+            function(_e) {
+            	_e.preventDefault();
+            	_picker.hide();
+        	}
         );
-        this.editor_$.find(".Rk-Edit-ColorPicker li").hover(
-            function() { _this.editor_$.find(".Rk-Edit-Color").css("background", $(this).attr("data-color")); },
-            function() { _this.editor_$.find(".Rk-Edit-Color").css("background", _model.get("color") || (_model.get("created_by") || Rkns.Renderer._USER_PLACEHOLDER(_this.renkan)).get("color")) }
-        ).click(function() {
+        
+        _picker.find("li").hover(
+            function(_e) {
+            	_e.preventDefault();
+            	_this.editor_$.find(".Rk-Edit-Color").css("background", $(this).attr("data-color"));
+        	},
+            function(_e) {
+            	_e.preventDefault();
+            	_this.editor_$.find(".Rk-Edit-Color").css("background", _model.get("color") || (_model.get("created_by") || Rkns.Renderer._USER_PLACEHOLDER(_this.renkan)).get("color"))
+        	}
+        ).click(function(_e) {
+        	_e.preventDefault();
             if (_this.renderer.isEditable()) {
 	            _model.set("color", $(this).attr("data-color"));
+            	_picker.hide();
 				paper.view.draw();
             } else {
             	closeEditor();
@@ -974,19 +883,14 @@
 
 Rkns.Renderer.NodeEditor.prototype.redraw = function() {
     var _coords = this.source_representation.paper_coords;
-    Rkns.Renderer.Utils.drawEditBox(this.options, _coords, this.editor_block, this.source_representation.circle_radius * .75, this.editor_$);
+    Rkns.Renderer.drawEditBox(this.options, _coords, this.editor_block, this.source_representation.circle_radius * .75, this.editor_$);
     this.editor_$.show();
     paper.view.draw();
 }
 
-Rkns.Renderer.NodeEditor.prototype.destroy = function() {
-    this.editor_block.remove();
-    this.editor_$.detach();
-}
-
 /* */
 
-Rkns.Renderer.EdgeEditor = Rkns.Utils.inherit(Rkns.Renderer._BaseRepresentation);
+Rkns.Renderer.EdgeEditor = Rkns.Utils.inherit(Rkns.Renderer._BaseEditor);
 
 Rkns.Renderer.EdgeEditor.prototype._init = function() {
     this.renderer.buttons_layer.activate();
@@ -1007,20 +911,20 @@
 }
 
 Rkns.Renderer.EdgeEditor.prototype.template = Rkns._.template(
-    '<h2><span class="Rk-CloseX">&times;</span><%-translate("Edit Edge")%></span></h2>'
-    + '<p><label><%-translate("Title:")%></label><input class="Rk-Edit-Title" type="text" value="<%-edge.title%>"/></p>'
-    + '<p><label><%-translate("URI:")%></label><input class="Rk-Edit-URI" type="text" value="<%-edge.uri%>"/><a class="Rk-Edit-Goto" href="<%-edge.uri%>" target="_blank"></a></p>'
-    + '<% if (properties.length) { %><p><label><%-translate("Choose from vocabulary:")%></label><select class="Rk-Edit-Vocabulary">'
-    + '<% _(properties).each(function(ontology) { %><option class="Rk-Edit-Vocabulary-Class" value=""><%- translate(ontology.label) %></option>'
+    '<h2><span class="Rk-CloseX">&times;</span><%-renkan.translate("Edit Edge")%></span></h2>'
+    + '<p><label><%-renkan.translate("Title:")%></label><input class="Rk-Edit-Title" type="text" value="<%-edge.title%>"/></p>'
+    + '<p><label><%-renkan.translate("URI:")%></label><input class="Rk-Edit-URI" type="text" value="<%-edge.uri%>"/><a class="Rk-Edit-Goto" href="<%-edge.uri%>" target="_blank"></a></p>'
+    + '<% if (properties.length) { %><p><label><%-renkan.translate("Choose from vocabulary:")%></label><select class="Rk-Edit-Vocabulary">'
+    + '<% _(properties).each(function(ontology) { %><option class="Rk-Edit-Vocabulary-Class" value=""><%- renkan.translate(ontology.label) %></option>'
     + '<% _(ontology.properties).each(function(property) { var uri = ontology["base-uri"] + property.uri; %><option class="Rk-Edit-Vocabulary-Property" value="<%- uri %>'
-    + '"<% if (uri === edge.uri) { %> selected<% } %>><%- translate(property.label) %></option>'
+    + '"<% if (uri === edge.uri) { %> selected<% } %>><%- renkan.translate(property.label) %></option>'
     + '<% }) %><% }) %></select></p><% } %>'
-    + '<div class="Rk-Editor-p"><span class="Rk-Editor-Label"><%-translate("Edge color:")%></span><div class="Rk-Edit-ColorPicker-Wrapper"><span class="Rk-Edit-Color" style="background:<%-edge.color%>;"><span class="Rk-Edit-ColorTip"></span></span><ul class="Rk-Edit-ColorPicker">'
-    + '<% _(Rkns.pickerColors).each(function(c) { %><li data-color="<%=c%>" style="background: <%=c%>"></li><% }); %></ul><span class="Rk-Edit-ColorPicker-Text"><%- translate("Choose color") %></span></div></div>'
-    + '<p><span class="Rk-Edit-Direction"><%- translate("Change edge direction") %></span></p>'
-    + '<p><span class="Rk-Editor-Label"><%-translate("From:")%></span><span class="Rk-UserColor" style="background:<%-edge.from_color%>;"></span><%- Rkns.Renderer.Utils.shortenText(edge.from_title, 25) %></p>'
-    + '<p><span class="Rk-Editor-Label"><%-translate("To:")%></span><span class="Rk-UserColor" style="background:<%-edge.to_color%>;"></span><%- Rkns.Renderer.Utils.shortenText(edge.to_title, 25) %></p>'
-    + '<% if (edge.has_creator) { %><p><span class="Rk-Editor-Label"><%-translate("Created by:")%></span><span class="Rk-UserColor" style="background:<%-edge.created_by_color%>;"></span><%- Rkns.Renderer.Utils.shortenText(edge.created_by_title, 25) %></p><% } %>'
+    + '<div class="Rk-Editor-p"><span class="Rk-Editor-Label"><%-renkan.translate("Edge color:")%></span><div class="Rk-Edit-ColorPicker-Wrapper"><span class="Rk-Edit-Color" style="background:<%-edge.color%>;"><span class="Rk-Edit-ColorTip"></span></span><ul class="Rk-Edit-ColorPicker">'
+    + '<% _(Rkns.pickerColors).each(function(c) { %><li data-color="<%=c%>" style="background: <%=c%>"></li><% }); %></ul><span class="Rk-Edit-ColorPicker-Text"><%- renkan.translate("Choose color") %></span></div></div>'
+    + '<p><span class="Rk-Edit-Direction"><%- renkan.translate("Change edge direction") %></span></p>'
+    + '<p><span class="Rk-Editor-Label"><%-renkan.translate("From:")%></span><span class="Rk-UserColor" style="background:<%-edge.from_color%>;"></span><%- Rkns.Renderer.shortenText(edge.from_title, 25) %></p>'
+    + '<p><span class="Rk-Editor-Label"><%-renkan.translate("To:")%></span><span class="Rk-UserColor" style="background:<%-edge.to_color%>;"></span><%- Rkns.Renderer.shortenText(edge.to_title, 25) %></p>'
+    + '<% if (edge.has_creator) { %><p><span class="Rk-Editor-Label"><%-renkan.translate("Created by:")%></span><span class="Rk-UserColor" style="background:<%-edge.created_by_color%>;"></span><%- Rkns.Renderer.shortenText(edge.created_by_title, 25) %></p><% } %>'
 );
 
 Rkns.Renderer.EdgeEditor.prototype.readOnlyTemplate = Rkns._.template(
@@ -1028,9 +932,9 @@
     + '<span class="Rk-Display-Title"><% if (edge.uri) { %><a href="<%-edge.uri%>" target="_blank"><% } %><%-edge.title%><% if (edge.uri) { %></a><% } %></span></h2>'
     + '<% if (edge.uri) { %><p class="Rk-Display-URI"><a href="<%-edge.uri%>" target="_blank"><%-edge.short_uri%></a></p><% } %>'
     + '<p><%-edge.description%></p>'
-    + '<p><span class="Rk-Editor-Label"><%-translate("From:")%></span><span class="Rk-UserColor" style="background:<%-edge.from_color%>;"></span><%- Rkns.Renderer.Utils.shortenText(edge.from_title, 25) %></p>'
-    + '<p><span class="Rk-Editor-Label"><%-translate("To:")%></span><span class="Rk-UserColor" style="background:<%-edge.to_color%>;"></span><%- Rkns.Renderer.Utils.shortenText(edge.to_title, 25) %></p>'
-    + '<% if (edge.has_creator) { %><p><span class="Rk-Editor-Label"><%-translate("Created by:")%></span><span class="Rk-UserColor" style="background:<%-edge.created_by_color%>;"></span><%- Rkns.Renderer.Utils.shortenText(edge.created_by_title, 25) %></p><% } %>'
+    + '<p><span class="Rk-Editor-Label"><%-renkan.translate("From:")%></span><span class="Rk-UserColor" style="background:<%-edge.from_color%>;"></span><%- Rkns.Renderer.shortenText(edge.from_title, 25) %></p>'
+    + '<p><span class="Rk-Editor-Label"><%-renkan.translate("To:")%></span><span class="Rk-UserColor" style="background:<%-edge.to_color%>;"></span><%- Rkns.Renderer.shortenText(edge.to_title, 25) %></p>'
+    + '<% if (edge.has_creator) { %><p><span class="Rk-Editor-Label"><%-renkan.translate("Created by:")%></span><span class="Rk-UserColor" style="background:<%-edge.created_by_color%>;"></span><%- Rkns.Renderer.shortenText(edge.created_by_title, 25) %></p><% } %>'
 );
 
 Rkns.Renderer.EdgeEditor.prototype.draw = function() {
@@ -1045,7 +949,7 @@
             	has_creator: !!_model.get("created_by"),
                 title: _model.get("title"),
                 uri: _model.get("uri"),
-                short_uri:  Rkns.Renderer.Utils.shortenText((_model.get("uri") || "").replace(/^(https?:\/\/)?(www\.)?/,'').replace(/\/$/,''),40),
+                short_uri:  Rkns.Renderer.shortenText((_model.get("uri") || "").replace(/^(https?:\/\/)?(www\.)?/,'').replace(/\/$/,''),40),
                 description: _model.get("description"),
                 color: _model.get("color") || _created_by.get("color"),
                 from_title: _from_model.get("title"),
@@ -1055,7 +959,7 @@
                 created_by_color: _created_by.get("color"),
                 created_by_title: _created_by.get("title")
             },
-            translate: this.renkan.translate,
+            renkan: this.renkan,
             properties: this.options.properties
         }));
     this.redraw();
@@ -1112,34 +1016,67 @@
 	       		closeEditor();
 	       	}
         });
+        
+        var _picker = _this.editor_$.find(".Rk-Edit-ColorPicker");
+        
         this.editor_$.find(".Rk-Edit-ColorPicker-Wrapper").hover(
-            function() { _this.editor_$.find(".Rk-Edit-ColorPicker").show(); },
-            function() { _this.editor_$.find(".Rk-Edit-ColorPicker").hide(); }
+            function(_e) {
+            	_e.preventDefault();
+            	_picker.show();
+        	},
+            function(_e) {
+            	_e.preventDefault();
+            	_picker.hide();
+        	}
         );
-        this.editor_$.find(".Rk-Edit-ColorPicker li").hover(
-            function() { _this.editor_$.find(".Rk-Edit-Color").css("background", $(this).attr("data-color")); },
-            function() { _this.editor_$.find(".Rk-Edit-Color").css("background", _model.get("color") || (_model.get("created_by") || Rkns.Renderer._USER_PLACEHOLDER(this.renkan)).get("color")); }
-        ).click(function() {
-			if (_this.renderer.isEditable()) {
-            	_model.set("color", $(this).attr("data-color"));
-            	paper.view.draw();
+        
+        _picker.find("li").hover(
+            function(_e) {
+            	_e.preventDefault();
+            	_this.editor_$.find(".Rk-Edit-Color").css("background", $(this).attr("data-color"));
+        	},
+            function(_e) {
+            	_e.preventDefault();
+            	_this.editor_$.find(".Rk-Edit-Color").css("background", _model.get("color") || (_model.get("created_by") || Rkns.Renderer._USER_PLACEHOLDER(_this.renkan)).get("color"))
+        	}
+        ).click(function(_e) {
+        	_e.preventDefault();
+            if (_this.renderer.isEditable()) {
+	            _model.set("color", $(this).attr("data-color"));
+            	_picker.hide();
+				paper.view.draw();
             } else {
-	       		closeEditor();
-	       	}
+            	closeEditor();
+            }
         });
     }
 }
 
 Rkns.Renderer.EdgeEditor.prototype.redraw = function() {
     var _coords = this.source_representation.paper_coords;
-    Rkns.Renderer.Utils.drawEditBox(this.options, _coords, this.editor_block, 5, this.editor_$);
+    Rkns.Renderer.drawEditBox(this.options, _coords, this.editor_block, 5, this.editor_$);
     this.editor_$.show();
     paper.view.draw();
 }
 
-Rkns.Renderer.EdgeEditor.prototype.destroy = function() {
-    this.editor_block.remove();
-    this.editor_$.detach();
+/* */
+
+Rkns.Renderer._BaseEditor.prototype._init = function() {
+    this.renderer.buttons_layer.activate();
+    this.type = "editor";
+    this.editor_block = new paper.Path();
+    var _pts = Rkns._(Rkns._.range(8)).map(function() {return [0,0]});
+    this.editor_block.add.apply(this.editor_block, _pts);
+    this.editor_block.strokeWidth = this.options.tooltip_border_width;
+    this.editor_block.strokeColor = this.options.tooltip_border_color;
+    this.editor_block.opacity = .8;
+    this.editor_$ = Rkns.$('<div>')
+        .appendTo(this.renderer.editor_$)
+        .css({
+            position: "absolute",
+            opacity: .8
+        })
+        .hide();
 }
 
 /* */
@@ -1152,13 +1089,13 @@
 		if (this.sector) {
 			this.sector.destroy();
 		}
-		this.sector = Rkns.Renderer.Utils.sector(
+		this.sector = this.renderer.drawSector(
 			this, 1 + sectorInner,
 			Rkns.Renderer._NODE_BUTTON_WIDTH + sectorInner,
 			this.startAngle,
 			this.endAngle,
 			1,
-			this.options.static_url + this.imageFile,
+			this.imageName,
 			this.renkan.translate(this.text)
 		);
 		this.lastSectorInner = sectorInner;
@@ -1174,7 +1111,7 @@
     this.lastSectorInner = 0;
     this.startAngle = -135;
     this.endAngle = -45;
-    this.imageFile = 'img/edit.png';
+    this.imageName = "edit";
     this.text = "Edit";
 }
 
@@ -1193,7 +1130,7 @@
     this.lastSectorInner = 0;
     this.startAngle = 0;
     this.endAngle = 90;
-    this.imageFile = 'img/remove.png';
+    this.imageName = "remove";
     this.text = "Remove";
 }
 
@@ -1215,7 +1152,7 @@
     this.lastSectorInner = 0;
     this.startAngle = 90;
     this.endAngle = 180;
-    this.imageFile = 'img/link.png';
+    this.imageName = "link";
     this.text = "Link to another node";
 }
 
@@ -1241,7 +1178,7 @@
     this.lastSectorInner = 0;
     this.startAngle = -45;
     this.endAngle = 0;
-    this.imageFile = 'img/enlarge.png';
+    this.imageName = "enlarge";
     this.text = "Enlarge";
 }
 
@@ -1262,7 +1199,7 @@
     this.lastSectorInner = 0;
     this.startAngle = -180;
     this.endAngle = -135;
-    this.imageFile = 'img/shrink.png';
+    this.imageName = "shrink";
     this.text = "Shrink";
 }
 
@@ -1280,7 +1217,7 @@
 
 Rkns.Renderer.EdgeEditButton.prototype._init = function() {
     this.type = "Edge-edit-button";
-    this.sector = Rkns.Renderer.Utils.sector(this, Rkns.Renderer._EDGE_BUTTON_INNER, Rkns.Renderer._EDGE_BUTTON_OUTER, - 90, 90, 1, this.options.static_url+'img/edit.png', this.renkan.translate("Edit"));
+    this.sector = this.renderer.drawSector(this, Rkns.Renderer._EDGE_BUTTON_INNER, Rkns.Renderer._EDGE_BUTTON_OUTER, -270, -90, 1, "edit", this.renkan.translate("Edit"));
 }
 
 Rkns.Renderer.EdgeEditButton.prototype.mouseup = function() {
@@ -1295,7 +1232,7 @@
 
 Rkns.Renderer.EdgeRemoveButton.prototype._init = function() {
     this.type = "Edge-remove-button";
-    this.sector = Rkns.Renderer.Utils.sector(this, Rkns.Renderer._EDGE_BUTTON_INNER, Rkns.Renderer._EDGE_BUTTON_OUTER, - 270, -90, 1, this.options.static_url+'img/remove.png', this.renkan.translate("Remove"));
+    this.sector = this.renderer.drawSector(this, Rkns.Renderer._EDGE_BUTTON_INNER, Rkns.Renderer._EDGE_BUTTON_OUTER, -90, 90, 1, "remove", this.renkan.translate("Remove"));
 }
 
 Rkns.Renderer.EdgeRemoveButton.prototype.mouseup = function() {
@@ -1385,7 +1322,22 @@
     var _this = this,
         _allowScroll = true,
         _originalScale,
-        _zooming = false;
+        _zooming = false,
+        _lastTapDate,
+        _lastTapX,
+        _lastTapY;
+    
+    this.imageCache = {};
+    
+    ['edit', 'remove', 'link', 'enlarge', 'shrink'].forEach(function(imgname) {
+    	var img = new Image();
+    	img.src = _renkan.options.static_url + 'img/' + imgname + '.png';
+    	_this.imageCache[imgname] = img;
+    });
+    
+    var throttledMouseMove = _.throttle(function(_event, _isTouch) {
+    	_this.onMouseMove(_event, _isTouch);
+    }, Rkns.Renderer._MOUSEMOVE_RATE);
     
     this.canvas_$.on({
     	mousedown: function(_event) {
@@ -1394,7 +1346,7 @@
     	},
     	mousemove: function(_event) {
     		_event.preventDefault();
-        	_this.onMouseMove(_event, false);
+        	throttledMouseMove(_event, false);
 	    },
 	    mouseup: function(_event) {
     		_event.preventDefault();
@@ -1408,12 +1360,26 @@
 	    },
 	    touchstart: function(_event) {
 	    	_event.preventDefault();
-	    	_originalScale = _this.scale;
-	    	_zooming = false;
-	    	_this.onMouseDown(_event.originalEvent.touches[0], true);
+	    	var _touches = _event.originalEvent.touches[0];
+    		if (
+    			_renkan.options.allow_double_click
+    			&& new Date() - _lastTap < Rkns.Renderer._DOUBLETAP_DELAY
+    			&& ( Math.pow(_lastTapX - _touches.pageX, 2) + Math.pow(_lastTapY - _touches.pageY, 2) < Rkns.Renderer._DOUBLETAP_DISTANCE )
+			) {
+    			_lastTap = 0;
+    			_this.onDoubleClick(_touches);
+    		} else {
+    			_lastTap = new Date();
+    			_lastTapX = _touches.pageX;
+    			_lastTapY = _touches.pageY;
+		    	_originalScale = _this.scale;
+		    	_zooming = false;
+		    	_this.onMouseDown(_touches, true);
+    		}
 	    },
 	    touchmove: function(_event) {
 	    	_event.preventDefault();
+    		_lastTap = 0;
 	    	if (_event.originalEvent.touches.length == 1) {
 	    		_this.onMouseMove(_event.originalEvent.touches[0], true);
 	    	} else {
@@ -1441,7 +1407,9 @@
 	    },
 	    dblclick: function(_event) {
     		_event.preventDefault();
-	        _this.onDoubleClick(_event);
+    		if (_renkan.options.allow_double_click) {
+    			_this.onDoubleClick(_event);
+    		}
 	    },
 	    mouseleave: function(_event) {
     		_event.preventDefault();
@@ -1697,6 +1665,110 @@
     + '</div></div>'
 );
 
+Rkns.Renderer.Scene.prototype.drawSector = function(_repr, _inR, _outR, _startAngle, _endAngle, _padding, _imgname, _caption) {
+    var _options = this.renkan.options,
+    	_startRads = _startAngle * Math.PI / 180,
+        _endRads = _endAngle * Math.PI / 180,
+        _img = this.imageCache[_imgname],
+        _span = _endRads - _startRads,
+        _startdx = - Math.sin(_startRads),
+        _startdy = Math.cos(_startRads),
+        _startXIn = Math.cos(_startRads) * _inR + _padding * _startdx,
+        _startYIn = Math.sin(_startRads) * _inR + _padding * _startdy,
+        _startXOut = Math.cos(_startRads) * _outR + _padding * _startdx,
+        _startYOut = Math.sin(_startRads) * _outR + _padding * _startdy,
+        _enddx = - Math.sin(_endRads),
+        _enddy = Math.cos(_endRads),
+        _endXIn = Math.cos(_endRads) * _inR - _padding * _enddx,
+        _endYIn = Math.sin(_endRads) * _inR - _padding * _enddy,
+        _endXOut = Math.cos(_endRads) * _outR - _padding * _enddx,
+        _endYOut = Math.sin(_endRads) * _outR - _padding * _enddy,
+        _centerR = (_inR + _outR)/2,
+        _centerRads = (_startRads + _endRads) / 2,
+        _centerX = Math.cos(_centerRads) * _centerR,
+        _centerY = Math.sin(_centerRads) * _centerR,
+        _centerXIn = Math.cos(_centerRads) * _inR,
+        _centerXOut = Math.cos(_centerRads) * _outR,
+        _centerYIn = Math.sin(_centerRads) * _inR,
+        _centerYOut = Math.sin(_centerRads) * _outR,
+        _textX = Math.cos(_centerRads) * (_outR + 3),
+        _textY = Math.sin(_centerRads) * (_outR + _options.buttons_label_font_size) + _options.buttons_label_font_size / 2,
+        _segments = [];
+	this.buttons_layer.activate();
+    var _path = new paper.Path();
+    _path.add([_startXIn, _startYIn]);
+    _path.arcTo([_centerXIn, _centerYIn], [_endXIn, _endYIn]);
+    _path.lineTo([_endXOut,  _endYOut]);
+    _path.arcTo([_centerXOut, _centerYOut], [_startXOut, _startYOut]);
+    _path.fillColor = _options.buttons_background;
+    _path.opacity = .5;
+    _path.closed = true;
+    _path.__representation = _repr;
+    var _text = new paper.PointText(_textX,_textY);
+    _text.characterStyle = {
+        fontSize: _options.buttons_label_font_size,
+        fillColor: _options.buttons_label_color
+    };
+    if (_textX > 2) {
+        _text.paragraphStyle.justification = 'left';
+    } else if (_textX < -2) {
+        _text.paragraphStyle.justification = 'right';
+    } else {
+        _text.paragraphStyle.justification = 'center';
+    }
+    _text.visible = false;
+    var _visible = false,
+        _restPos = new paper.Point(-200, -200),
+        _grp = new paper.Group([_path, _text]),
+        _delta = _grp.position,
+        _imgdelta = new paper.Point([_centerX, _centerY]),
+        _currentPos = new paper.Point(0,0);
+    _text.content = _caption;
+    _grp.visible = false;
+    _grp.position = _restPos;
+    var _res = {
+        show: function() {
+            _visible = true;
+            _grp.position = _currentPos.add(_delta);
+            _grp.visible = true;
+        },
+        moveTo: function(_point) {
+            _currentPos = _point;
+            if (_visible) {
+                _grp.position = _point.add(_delta);
+            }
+        },
+        hide: function() {
+            _visible = false;
+            _grp.visible = false;
+            _grp.position = _restPos;
+        },
+        select: function() {
+            _path.opacity = .8;
+            _text.visible = true;
+        },
+        unselect: function() {
+            _path.opacity = .5;
+            _text.visible = false;
+        },
+        destroy: function() {
+            _grp.remove();
+        }
+    }
+    function showImage() {
+    	var _raster = new paper.Raster(_img);
+	    _raster.position = _imgdelta.add(_grp.position).subtract(_delta);
+	   	_grp.addChild(_raster);
+    }
+    if (_img.width) {
+    	showImage();
+    } else {
+    	Rkns.$(_img).on("load",showImage);
+    }
+    
+    return _res
+}
+
 Rkns.Renderer.Scene.prototype.addToBundles = function(_edgeRepr) {
     var _bundle = Rkns._(this.bundles).find(function(_bundle) {
         return ( 
@@ -2135,6 +2207,9 @@
 	    		newNode.description = snippet.find("[data-description]").attr("data-description") || newNode.description;
 	    	}
 	}
+	if (!newNode.title) {
+		newNode.title = this.renkan.translate("Dragged resource");
+	}
 	var fields = ["title", "description", "uri", "image"];
 	for (var i = 0; i < fields.length; i++) {
 		var f = fields[i];
@@ -2145,8 +2220,7 @@
 			newNode[f] = undefined;
 		}
 	}
-	if (newNode.title || newNode.description || newNode.uri) {
-		var _off = this.canvas_$.offset(),
+	var _off = this.canvas_$.offset(),
         _point = new paper.Point([
             _event.pageX - _off.left,
             _event.pageY - _off.top
@@ -2156,7 +2230,7 @@
             id: Rkns.Utils.getUID('node'),
             created_by: this.renkan.current_user,
             uri: newNode.uri || "",
-            title: newNode.title || this.renkan.translate("Dragged resource"),
+            title: newNode.title || "",
             description: newNode.description || "",
             image: newNode.image || "",
             color: newNode.color || undefined,
@@ -2165,10 +2239,9 @@
                 y: _coords.y
             }
         };
-    	var _node = this.renkan.project.addNode(_nodedata),
-    		_repr = this.getRepresentationByModel(_node);
-    	if (_event.type === "drop") {
-        	_repr.openEditor();
-        }
-	}
+	var _node = this.renkan.project.addNode(_nodedata),
+		_repr = this.getRepresentationByModel(_node);
+	if (_event.type === "drop") {
+    	_repr.openEditor();
+    }
 }