diff --git a/addons/web_diagram/static/src/js/diagram.js b/addons/web_diagram/static/src/js/diagram.js index cd360dcfafa..4e43960f206 100644 --- a/addons/web_diagram/static/src/js/diagram.js +++ b/addons/web_diagram/static/src/js/diagram.js @@ -111,34 +111,16 @@ openerp.web.DiagramView = openerp.web.View.extend({ this.get_diagram_info(); } }, - select_node: function (node, element) { - if (!this.selected_node) { - this.selected_node = node; - element.attr('stroke', 'red'); - return; - } - // Re-click selected node, deselect it - if (node.id === this.selected_node.id) { - this.selected_node = null; - element.attr('stroke', 'black'); - return; - } - this.add_edit_node(null, this.connector, { - act_from: this.selected_node.id, - act_to: node.id - }); - }, draw_diagram: function(result) { + var self = this; console.log(result); var res_nodes = result['nodes']; var res_edges = result['conn']; var id_to_node = {} - var edge_list = []; - var style = { "background" : 'url("grid.png")', - "edge" : "#A0A0A0", + var style = { "edge" : "#A0A0A0", "edge_label" : "#555", "text" : "#333", "outline" : "#000", @@ -148,89 +130,48 @@ openerp.web.DiagramView = openerp.web.View.extend({ "node_size_x" : 90, "node_size_y" : 60, "edge_spacing" : 100, - "edge_label_font_size" : 9 }; + "edge_label_font_size" : 10 }; + + $('#dia-canvas *').remove(); // remove previous diagram var r = new Raphael(document.getElementById("dia-canvas"), '100%','500px'); var graph = new CuteGraph(r,style); _.each(res_nodes, function(node) { - id_to_node[node.id] = new CuteNode( graph, - node.x, - node.y, - CuteGraph.wordwrap(node.name, 17), - node.shape === 'rectangle' ? 'rect' : 'circle', - node.color === 'white' ? style.white : style.gray ); - + var n = new CuteNode( graph, + node.x, + node.y, + CuteGraph.wordwrap(node.name, 17), + node.shape === 'rectangle' ? 'rect' : 'circle', + node.color === 'white' ? style.white : style.gray ); + n.id = node.id; + id_to_node[node.id] = n; }); _.each(res_edges, function(edge) { - edge_list.push( new CuteEdge( graph, + var e = new CuteEdge( graph, CuteGraph.wordwrap(edge.signal, 32), id_to_node[edge.s_id], - id_to_node[edge.d_id] )); + id_to_node[edge.d_id] ); + e.id = edge.id; }); - /* - //Custom logic - this.selected_node = null; - this.active_model = result['id_model']; - this.parent_field = result.parent_field; - var diagram = new Graph(); - var self = this; - var renderer = function(r, n) { - var shape = (n.node.shape === 'rectangle') ? 'rect' : 'ellipse'; + CuteNode.double_click_callback = function(cutenode){ + self.add_edit_node( cutenode.id, self.node ); + } - var node = r[shape](n.node.x, n.node.y).attr({ - "fill": n.node.color + CuteEdge.double_click_callback = function(cuteedge){ + self.add_edit_node(cuteedge.id,self.connector); + } + + CuteEdge.creation_callback = function(node_start, node_end){ + console.log("creating edge from:",node_start," to:",node_end); + self.add_edit_node(null, self.connector, { + act_from: node_start.id, + act_to: node_end.id }); - - var nodes = r.set(node, r.text(n.node.x, n.node.y, (n.label || n.id))) - .attr("cursor", "pointer") - .dblclick(function() { - self.add_edit_node(n.node.id, self.node); - }) - .mousedown(function () { node.moved = false; }) - .mousemove(function () { node.moved = true; }) - .click(function () { - // Ignore click from move event - if (node.moved) { return; } - self.select_node(n.node, node); - }); - - if (shape === 'rect') { - node.attr({width: "60", height: "44"}); - node.next.attr({"text-anchor": "middle", x: n.node.x + 20, y: n.node.y + 20}); - } else { - node.attr({rx: "40", ry: "20"}); - } - - return nodes; - }; - - _.each(res_nodes, function(res_node) { - diagram.addNode(res_node['name'],{node: res_node,render: renderer}); - }); - - // Id for Path(Edges) - var edge_ids = []; - - _.each(res_connectors, function(connector, index) { - edge_ids.push(index); - diagram.addEdge(connector['source'], connector['destination'], {directed : true, label: connector['signal']}); - }); - - self.$element.find('.diagram').empty(); - - var layouter = new Graph.Layout.Ordered(diagram); - var render_diagram = new Graph.Renderer.Raphael('dia-canvas', diagram, $('div#dia-canvas').width(), $('div#dia-canvas').height()); - - _.each(diagram.edges, function(edge, index) { - if(edge.connection) { - edge.connection.fg.attr({cursor: "pointer"}).dblclick(function() { - self.add_edit_node(edge_ids[index], self.connector); - }); - } - });*/ + return {label:""}; // TODO destroy edge on cancel + } }, add_edit_node: function(id, model, defaults) { diff --git a/addons/web_diagram/static/src/js/graph.js b/addons/web_diagram/static/src/js/graph.js index 11c29095a95..77a4d6c8e38 100644 --- a/addons/web_diagram/static/src/js/graph.js +++ b/addons/web_diagram/static/src/js/graph.js @@ -1,5 +1,6 @@ (function(window){ + // this serves as the end of an edge when creating a link @@ -61,7 +62,10 @@ graph.creating_edge = false; self.edge_tmp.remove(); if(graph.target_node && graph.target_node != node){ - new GraphEdge(graph,'new edge!', node,graph.target_node); + edge_prop = GraphEdge.creation_callback(node,graph.target_node); + if(edge_prop){ + new GraphEdge(graph,edge_prop.label, node,graph.target_node); + } } } conn_circle.drag(drag_move,drag_down,drag_up); @@ -289,7 +293,7 @@ for(var i = 0; i < edges.length; i++){ edges[i].label_disable(); } - set_pos(this.opos.add_new_xy(dx,dy)); + set_pos(this.opos.add_xy(dx,dy)); } var drag_up = function(){ //we re-enable the @@ -314,12 +318,22 @@ node_fig.hover(hover_in,hover_out); node_label.hover(hover_in,hover_out); + function double_click(){ + GraphNode.double_click_callback(self); + } + node_fig.dblclick(double_click); + node_label.dblclick(double_click); + this.connectors.push(new Connector(graph,this,-sx/2,0)); this.connectors.push(new Connector(graph,this,sx/2,0)); this.connectors.push(new Connector(graph,this,0,-sy/2)); this.connectors.push(new Connector(graph,this,0,sy/2)); } + GraphNode.double_click_callback = function(node){ + console.log("double click from node:",node); + } + // creates a new edge with label 'label' from start to end. start and end must implement get_pos_*, // if tmp is true, the edge is not added to the graph, used for drag edges. TODO pass graph in constructor, // replace tmp == false by graph == null @@ -329,6 +343,7 @@ var curvature = 0; // 0 = straight, != 0 curved var s,e; // positions of the start and end point of the line between start and end var mc; // position of the middle of the curve (bezier control point) + var mc1,mc2; // control points of the cubic bezier for the loop edges var elfs = graph.style.edge_label_font_size || 10 ; var label_enabled = true; this.uid = 0; // unique id used to order the curved edges @@ -342,7 +357,7 @@ var cpos = path.getTotalLength() * 0.5; var cindex = Math.abs(Math.floor(curvature)); var mod = ((cindex % 3)) * (elfs * 3.1) - (elfs * 0.5); - var verticality = Math.abs(end.get_pos().sub_new(start.get_pos()).normalize().dot_xy(0,1)); + var verticality = Math.abs(end.get_pos().sub(start.get_pos()).normalize().dot_xy(0,1)); verticality = Math.max(verticality-0.5,0)*2; var lpos = path.getPointAtLength(cpos + mod * verticality); @@ -358,12 +373,12 @@ } s = start.get_pos(); e = end.get_pos(); - mc = s.lerp_new(e,0.5); //middle of the line s->e - var se = e.sub_new(s); - se.normalize(); - se.rotate_deg(-90); - se.scale(curvature * graph.style.edge_spacing); - mc.add(se); + mc = s.lerp(e,0.5); //middle of the line s->e + var se = e.sub(s); + se = se.normalize(); + se = se.rotate_deg(-90); + se = se.scale(curvature * graph.style.edge_spacing); + mc = mc.add(se); if(start.get_bound){ var col = start.get_bound().collide_segment(s,mc); if(col.length > 0){ @@ -377,6 +392,14 @@ } } } + /* + function update_loop_pos(){ + s = start.get_pos(); + mc = s.add_new(Vec2.new_polar_deg(graph.style.edge_loop_radius,45*self.uid)); + var p = mc.normalize_new().rotate_deg(90); + mc1 = mc.add_new + */ + function make_line(){ return "M" + s.x + "," + s.y + "L" + e.x + "," + e.y ; @@ -431,12 +454,31 @@ edge_label.remove(); } + function double_click(){ + GraphEdge.double_click_callback(self); + } + edge.dblclick(double_click); + edge_label.dblclick(double_click); + this.label_enable = label_enable; this.label_disable = label_disable; this.update = update; this.remove = remove; - } + + GraphEdge.double_click_callback = function(edge){ + console.log("double click from edge:",edge); + } + + // this is the default edge creation callback. It is called before an edge is created + // It returns an object containing the properties of the edge. + // If it returns null, the edge is not created. + GraphEdge.creation_callback = function(start,end){ + var edge_prop = {}; + edge_prop.label = 'new edge!'; + return edge_prop; + } + // returns a new string with the same content as str, but with lines of maximum 'width' characters. // lines are broken on words, or into words if a word is longer than 'width' function wordwrap( str, width) { @@ -460,6 +502,7 @@ /* window.onload = function(){ + //Example var style = { "background" :'url("grid.png")', "edge" :"#A0A0A0", "edge_label" :"#555", @@ -470,15 +513,17 @@ window.onload = function(){ "white" :"#FFF", "node_size_x" : 110, "node_size_y" : 80, - "edge_spacing" : 100 }; + "edge_spacing" : 100, + "edge_label_font_size" : 10 + "edge_loop_radius": 50 }; var r = new Raphael(document.getElementById("canvas_container"),'100%','100%'); var g = new CuteGraph(r,style); - var n1 = new GraphNode(g,100,250,'Hello World','circle',colors.white); - var n2 = new GraphNode(g,400,250,'Hello Planet','rect',colors.white); - var n3 = new GraphNode(g,250,400,'Lonely Node','rect',colors.gray); - var e1 = new GraphEdge(g,'test',n1,n2); + var n1 = new CuteNode(g,100,250,'Hello World','circle',colors.white); + var n2 = new CuteNode(g,400,250,'Hello Planet','rect',colors.white); + var n3 = new CuteNode(g,250,400,'Lonely Node','rect',colors.gray); + var e1 = new CuteEdge(g,'test',n1,n2); }*/ diff --git a/addons/web_diagram/static/src/js/vec2js/vec2.js b/addons/web_diagram/static/src/js/vec2js/vec2.js index 9572060e675..e7c0371605b 100644 --- a/addons/web_diagram/static/src/js/vec2js/vec2.js +++ b/addons/web_diagram/static/src/js/vec2js/vec2.js @@ -6,24 +6,17 @@ // A Javascript 2D vector library // conventions : // method that returns a float value do not modify the vector - // method that implement operators are applied onto the calling vector eg: + // method that implement operators return a new vector with the modifications without + // modifying the calling vector or the parameters. // - // v1.add(v2); // v2 is added onto v1, v2 is modified, v1 is not - // - // but the parameters are never modified - // those methods also return the result so that the calls can be chained. - // method that implement operators are usually also available with a '_new' - // suffix. those method do not modify the calling vector and return the result - // as a new vector instead. - // - // v3 = v1.add_new(v2) // v3 is set to v1 + v2. v1 and v2 are not modified + // v3 = v1.add(v2); // v3 is set to v1 + v2, v1, v2 are not modified // // methods that take a single vector as a parameter are usually also available with // q '_xy' suffix. Those method takes two floats representing the x,y coordinates of // the vector parameter and allow you to avoid to needlessly create a vector object : // - // v1.add(new Vec2(3,4)); - // v1.add_xy(3,4); //equivalent to previous line + // v2 = v1.add(new Vec2(3,4)); + // v2 = v1.add_xy(3,4); //equivalent to previous line // // angles are in radians by default but method that takes angle as parameters // or return angle values usually have a variant with a '_deg' suffix that works in degrees @@ -102,149 +95,55 @@ Vec2.prototype.clone = function(){ return new Vec2(this.x,this.y); }; - // sets the coordinate of this vector to (0,0) - Vec2.prototype.zero = function(){ - this.x = 0; - this.y = 0; - return this; - }; - // sets the coordinates of this to be equal to the coordinates of v - Vec2.prototype.set = function(v){ - this.x = v.x; - this.y = v.y; - return this; - }; - // sets the coordinate of this to be equal to the vector (x,y) - Vec2.prototype.set_xy = function(x,y){ - this.x = x; - this.y = y; - return this; - }; - // sets this to be the sum of this and vector v - Vec2.prototype.add = function(v){ - this.x += v.x; - this.y += v.y; - return this; - }; - // sets this to be the sum of this and the vector (x,y) - Vec2.prototype.add_xy = function(x,y){ - this.x += x; - this.y += y; - return this; - }; // return the sum of this and vector v as a new vector - Vec2.prototype.add_new = function(v){ + Vec2.prototype.add = function(v){ return new Vec2(this.x+v.x,this.y+v.y); }; // return the sum of this and vector (x,y) as a new vector - Vec2.prototype.add_new_xy = function(x,y){ + Vec2.prototype.add_xy = function(x,y){ return new Vec2(this.x+x,this.y+y); }; - // sets this to be (this - v) where v is a vector and - is the vector substraction - Vec2.prototype.sub = function(v){ - this.x -= v.x; - this.y -= v.y; - return this; - }; - // sets this to be (this - (x,y)) where - is the vector substraction - Vec2.prototype.sub_xy = function(x,y){ - this.x -= x; - this.y -= y; - return this; - }; // returns (this - v) as a new vector where v is a vector and - is the vector substraction - Vec2.prototype.sub_new = function(v){ + Vec2.prototype.sub = function(v){ return new Vec2(this.x-v.x,this.y-v.y); }; // returns (this - (x,y)) as a new vector where - is vector substraction - Vec2.prototype.sub_new_xy = function(x,y){ + Vec2.prototype.sub_xy = function(x,y){ return new Vec2(this.x-x,this.y-y); }; - // sets this to be (this * v) where v is a vector and * is the by component product and - Vec2.prototype.mult = function(v){ - this.x *= v.x; - this.y *= v.y; - return this; - }; - // sets this to be (this * (x,y) ) where v is a vector and * is the by component product - Vec2.prototype.mult_xy = function(x,y){ - this.x *= x; - this.y *= y; - return this; - }; // return (this * v) as a new vector where v is a vector and * is the by component product - Vec2.prototype.mult_new = function(v){ + Vec2.prototype.mult = function(v){ return new Vec2(this.x*v.x,this.y*v.y); }; // return (this * (x,y)) as a new vector where * is the by component product - Vec2.prototype.mult_new_xy = function(x,y){ + Vec2.prototype.mult_xy = function(x,y){ return new Vec2(this.x*x,this.y*y); }; - // multiply all components of this vector by float f - Vec2.prototype.scale = function(f){ - this.x *= f; - this.y *= f; - return this; - }; // return this scaled by float f as a new fector - Vec2.prototype.scale_new = function(f){ + Vec2.prototype.scale = function(f){ return new Vec2(this.x*f, this.y*f); }; - //sets this vector to be the negative of itself - Vec2.prototype.neg = function(f){ - this.x = -this.x; - this.y = -this.y; - return this; - }; // return the negation of this vector - Vec2.prototype.neg_new = function(f){ + Vec2.prototype.neg = function(f){ return new Vec2(-this.x,-this.y); }; - // normalizes this vector - Vec2.prototype.normalize = function(){ - var len = this.len(); - if(len == 0){ - this.x = 1; - }else if(len != 1){ - this.scale(1.0/len); - } - return this; - }; // return this vector normalized as a new vector - Vec2.prototype.normalize_new = function(){ + Vec2.prototype.normalize = function(){ var len = this.len(); if(len == 0){ return new Vec2(0,1); }else if(len != 1){ - return this.scale_new(1.0/len); + return this.scale(1.0/len); } return new Vec2(this.x,this.y); }; - // sets the length of this vector to float l without changing its angle. (negative values of l will invert direction) - Vec2.prototype.set_len = function(l){ - this.normalize(); - this.scale(l); - return this; - }; // return a new vector with the same direction as this vector of length float l. (negative values of l will invert direction) - Vec2.prototype.set_len_new = function(l){ - var v = this.normalize_new(); - v.scale(l); - return v; - }; - // projects this vector onto the vector v - Vec2.prototype.project = function(v){ - var d = this.dot(v); - this.set(v); - this.normalize(); - this.scale(d); - return this; + Vec2.prototype.set_len = function(l){ + return this.normalize().scale(l); }; // return the projection of this onto the vector v as a new vector - Vec2.prototype.project_new = function(v){ - var vc = this.clone(); - vc.project(v); - return vc; + Vec2.prototype.project = function(v){ + return v.set_len(this.dot(v)); }; // return a string representation of this vector Vec2.prototype.toString = function(){ @@ -256,45 +155,25 @@ str += "]"; return str; }; - // rotate this vector counterclockwise by rad radians. + //return this vector counterclockwise rotated by rad radians as a new vector Vec2.prototype.rotate = function(rad){ var c = Math.cos(rad); var s = Math.sin(rad); var px = this.x * c - this.y *s; var py = this.x * s + this.y *c; - this.x = px; - this.y = py; - return this; - }; - //rotate this vector counterclockwise by deg degrees - Vec2.prototype.rotate_deg = function(deg){ - return this.rotate(deg * deg2rad); - }; - //return this vector counterclockwise rotated by rad radians as a new vector - Vec2.prototype.rotate_new = function(rad){ - var v = this.clone(); - return v.rotate(rad); + return new Vec2(px,py); }; //return this vector counterclockwise rotated by deg degrees as a new vector - Vec2.prototype.rotate_deg_new = function(deg){ - var v = this.clone(); - return v.rotate_deg(deg); + Vec2.prototype.rotate_deg = function(deg){ + return this.rotate(deg * deg2rad); }; //linearly interpolate this vector towards the vector v by float factor alpha. // alpha == 0 : does nothing // alpha == 1 : sets this to v Vec2.prototype.lerp = function(v,alpha){ var inv_alpha = 1 - alpha; - this.x = this.x * inv_alpha + v.x * alpha; - this.y = this.y * inv_alpha + v.y * alpha; - }; - // returns this vector lerped to v by alpha as a new vector - Vec2.prototype.lerp_new = function(v,alpha){ - var inv_alpha = 1 - alpha; - var v2 = new Vec2( this.x * inv_alpha + v.x * alpha, + return new Vec2( this.x * inv_alpha + v.x * alpha, this.y * inv_alpha + v.y * alpha ); - return v2; - }; // returns the angle between this vector and the vector (1,0) in radians Vec2.prototype.angle = function(){ @@ -359,8 +238,8 @@ // make all computations in a space where the ellipse is a circle // centered on zero var c = new Vec2(this.cx,this.cy); - a = a.sub_new(c).mult_xy(1/this.hx,1/this.hy); - b = b.sub_new(c).mult_xy(1/this.hx,1/this.hy); + a = a.sub(c).mult_xy(1/this.hx,1/this.hy); + b = b.sub(c).mult_xy(1/this.hx,1/this.hy); if(a.len_sq() < 1 && b.len_sq() < 1){ //both points inside the ellipse @@ -368,7 +247,7 @@ } // compute the roots of the intersection - var ab = b.sub_new(a); + var ab = b.sub(a); var A = (ab.x*ab.x + ab.y*ab.y); var B = 2*( ab.x*a.x + ab.y*a.y); var C = a.x*a.x + a.y*a.y - 1; @@ -383,18 +262,16 @@ var u2 = (-B - u) / (2*A); if(u1 >= 0 && u1 <= 1){ - var pos = a.clone(); - pos.add(ab.scale_new(u1)); + var pos = a.add(ab.scale(u1)); collisions.push(pos); } if(u1 != u2 && u2 >= 0 && u2 <= 1){ - var pos = a.clone(); - pos.add(ab.scale_new(u2)); + var pos = a.add(ab.scale(u2)); collisions.push(pos); } for(var i = 0; i < collisions.length; i++){ - collisions[i].mult_xy(this.hx,this.hy); - collisions[i].add_xy(this.cx,this.cy); + collisions[i] = collisions[i].mult_xy(this.hx,this.hy); + collisions[i] = collisions[i].add_xy(this.cx,this.cy); } return collisions; }; @@ -470,7 +347,7 @@ }; // returns true if the ellipse contains the position defined by the vector 'vec' BEllipse.prototype.contains_vec = function(v){ - v = v.mult_new_xy(this.hx,this.hy); + v = v.mult_xy(this.hx,this.hy); return v.len_sq() <= 1; }; // returns true if the ellipse contains the position (x,y)