diff --git a/Kieker.WebGUI/lib/kieker-1.6-SNAPSHOT_emf.jar b/Kieker.WebGUI/lib/kieker-1.6-SNAPSHOT_emf.jar index 602b5194f4809d48124453bbefdc43664cc4974d..177ced9bf680c39239073c4a3ba63fbd87e65ac1 100644 Binary files a/Kieker.WebGUI/lib/kieker-1.6-SNAPSHOT_emf.jar and b/Kieker.WebGUI/lib/kieker-1.6-SNAPSHOT_emf.jar differ diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/beans/view/CurrentAnalysisEditorBean.java b/Kieker.WebGUI/src/main/java/kieker/webgui/beans/view/CurrentAnalysisEditorBean.java index fd83efb10ddae607ccfa663525a8b2202a0d128e..9f4e7f69908be5b3e76ed830089a426460fdd00c 100644 --- a/Kieker.WebGUI/src/main/java/kieker/webgui/beans/view/CurrentAnalysisEditorBean.java +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/beans/view/CurrentAnalysisEditorBean.java @@ -57,6 +57,7 @@ import kieker.analysis.plugin.AbstractPlugin; import kieker.analysis.plugin.annotation.InputPort; import kieker.analysis.plugin.annotation.OutputPort; import kieker.analysis.plugin.annotation.Plugin; +import kieker.analysis.plugin.annotation.Property; import kieker.analysis.plugin.annotation.RepositoryPort; import kieker.analysis.plugin.filter.AbstractFilterPlugin; import kieker.analysis.plugin.reader.AbstractReaderPlugin; @@ -533,6 +534,34 @@ public final class CurrentAnalysisEditorBean { } } + public List<Property> getProperties(final Class<?> clazz) { + final ArrayList<Property> result = new ArrayList<Property>(); + + // Get the two potential annotations + final Plugin annotationPlugin = clazz.getAnnotation(Plugin.class); + final Repository annotationRepository = clazz.getAnnotation(Repository.class); + + final Property[] properties; + + // Now check which one of them is available + if ((annotationPlugin == null) || annotationPlugin.description().isEmpty()) { + if ((annotationRepository == null) || annotationRepository.description().isEmpty()) { + // None. + properties = new Property[0]; + } else { + properties = annotationRepository.configuration(); + } + } else { + properties = annotationPlugin.configuration(); + } + + for (final Property property : properties) { + result.add(property); + } + + return result; + } + /** * Searches for input ports within the given class and returns them. * diff --git a/Kieker.WebGUI/src/main/webapp/AnalysisEditor.xhtml b/Kieker.WebGUI/src/main/webapp/AnalysisEditor.xhtml index b3a205bd7b3a934f1ec70648bd6f04cbc035b92a..75e76745660ef3d730c458efa4de72833f192231 100644 --- a/Kieker.WebGUI/src/main/webapp/AnalysisEditor.xhtml +++ b/Kieker.WebGUI/src/main/webapp/AnalysisEditor.xhtml @@ -25,26 +25,34 @@ <script language="javascript" type="text/javascript" src="../js/jit.js"></script> <script language="javascript" type="text/javascript" src="../js/flowEditor.js"></script> - <script> - window.onload = function() { - document.getElementById('hidden:link').onclick(); - } - + <script> nodeClickListener = function(node, info, e) { nodeClickCommand([{name : 'ID', value : node.id}, {name : 'type', value : node.data.$nodeType}]); + // var graphNode = graph.getNode(node.id); + // graphNode.name = 'Hallo, Welt'; + // graph.refresh(); } nodeRemoveListener = function(node, info, e) { nodeRemoveCommand([{name : 'ID', value : node.id}, {name : 'type', value : node.data.$nodeType}]); } + + edgeCreateListener = function(sourceNodeID, targetNodeID, sourcePortID, targetPortID) { + + } + + edgeRemoveListener = function(sourceNodeID, targetNodeID, sourcePortID, targetPortID) { + + } + + nodeMouseListener = function(node, info, e) { + } </script> </h:head> <h:body> <h:form id="hidden" style="display:none"> - <h:commandLink id="link"> - <f:ajax event="click" listener="#{currentAnalysisEditorBean.initializeGraph()}" /> - </h:commandLink> + <p:remoteCommand autoRun="true" name="init" action="#{currentAnalysisEditorBean.initializeGraph()}" /> </h:form> <h:form id="hiddenNodeProperties" style="display:none"> <p:remoteCommand name="nodeClickCommand" action="#{currentAnalysisEditorBean.nodeClicked()}" update=":propertiesForm"/> @@ -157,6 +165,10 @@ <p:dataList value="#{currentAnalysisEditorBean.getRepositoryPorts(reader)}" var="port"> #{port.name()} </p:dataList> + <b><h:outputText value="Configuration:"/></b> + <p:dataList value="#{currentAnalysisEditorBean.getProperties(reader)}" var="property"> + #{property.name()} + </p:dataList> </p:tooltip> </ui:repeat> </p:tab> @@ -180,6 +192,10 @@ <p:dataList value="#{currentAnalysisEditorBean.getRepositoryPorts(filter)}" var="port"> #{port.name()} </p:dataList> + <b><h:outputText value="Configuration:"/></b> + <p:dataList value="#{currentAnalysisEditorBean.getProperties(filter)}" var="property"> + #{property.name()} + </p:dataList> </p:tooltip> </ui:repeat> </p:tab> @@ -190,6 +206,11 @@ <b><h:outputText value="#{repository.simpleName} (#{repository.name})"/></b> <br/> <h:outputText value="#{currentAnalysisEditorBean.getDescription(repository)}"/> + <br/><br/> + <b><h:outputText value="Configuration:"/></b> + <p:dataList value="#{currentAnalysisEditorBean.getProperties(repository)}" var="property"> + #{property.name()} + </p:dataList> </p:tooltip> </ui:repeat> </p:tab> diff --git a/Kieker.WebGUI/src/main/webapp/js/flowEditor.js b/Kieker.WebGUI/src/main/webapp/js/flowEditor.js index b411673eb0ca53b8c2d6e20bb1ff189efe263c76..de8e6511bdd91e0f42912ad6cbddb06492983bab 100644 --- a/Kieker.WebGUI/src/main/webapp/js/flowEditor.js +++ b/Kieker.WebGUI/src/main/webapp/js/flowEditor.js @@ -114,6 +114,157 @@ function GraphFlow(){ // FUNCTIONS // ///////////////////////////////////////////// + + /** + Sets the mouseCursor to a new one. If the new cursor is null, + it will be reset to the default mouse cursor. + */ + this.setMouseCursor = function(newCursor){ + if(newCursor == null){ + fd.canvas.getElement().style.cursor = ''; + }else{ + fd.canvas.getElement().style.cursor = newCursor; + } + } + + this.graphToDot = function(filename){ + var node; + var dotGraph = 'digraph structs {\nnode [shape=plaintext];\n\n' + for(var n=0; n < json.length; n++){ + node = json[n]; + if(node.data.$type == "nodeFamily"){ + dotGraph += nodeToDot(node); + dotGraph += '\n'; + } + } + dotGraph += '}'; + console.log(dotGraph); + } + + /** + Converts a single node to a node in dot language, and returns it as a string. + @param nodeFamily - the node which is to be converted + */ + this.nodeToDot = function(nodeFamily){ + var pWidth = (nodeFamily.data.$width+vertexLabelSize)/2; + var tableDef = '<TABLE BORDER="0" CELLSPACING="0" CELLBORDER="1" CELLPADDING="0">\n', + portDefA = '<TR><TD WIDTH="'+pWidth+'" FIXEDSIZE="true" HEIGHT="'+(2*vertexLabelSize)+'" PORT="', + portDefB = '">p</TD></TR>\n', + padDefA = '<TR><TD FIXEDSIZE="true" WIDTH="'+pWidth+'" HEIGHT="', + padDefB = '"></TD></TR>\n', + emptyDef = '<TD FIXEDSIZE="true" HEIGHT="1" WIDTH="'+pWidth+'"></TD>\n'; + + // this variable will contain a string, representing a node in dot-code + var dot = nodeFamily.id, + dotEdges = ''; + + dot+= ' [label=< '+tableDef; + dot+= '<TR><TD COLSPAN="2" HEIGHT="'+(2.5*vertexLabelSize)+'"></TD></TR><TR>\n'; + + var portIndex = nodeFamily.data.$jsonIndex+2, + lastPortIndex = nodeFamily.data.$jsonIndex+1+ nodeFamily.data.$portCount; + var port = json[portIndex]; + if(port.data.$type == "inputPort"){ + + dot += '<TD>'+tableDef; + var dotInput = '', + countInput = 0; + + // write the dot representation of ports into a temporary string + while(portIndex <= lastPortIndex && port.data.$type == "inputPort"){ + dotInput += portDefA+ port.id + portDefB; + countInput++; + portIndex++; + port = json[portIndex]; + } + + // apply padding if there is space above and below the ports + var padding = nodeFamily.data.$height/2 - (1.5+countInput)*vertexLabelSize; + if(padding > 0){ + dotInput = padDefA+padding+padDefB + dotInput + padDefA+padding+padDefB; + } + + // append temp string to result + dot += dotInput + '</TABLE></TD>'; + } + else{ + dot += emptyDef; + } + + if (portIndex <= lastPortIndex){ + // we get here if there were no input ports OR if we handled them all + var dotRight = '', + countRight = 0, + adja, // outgoing edges of port + adjaIter; // used to iterate through the edges + + // add repository ports + if(port.data.$type == "repositoryPort"){ + + dot += '<TD>'+tableDef; + + // write the dot representation of ports into a temporary string + while(portIndex <= lastPortIndex && port.data.$type == "repositoryPort"){ + dotRight += portDefA+ port.id + portDefB; + + // add edges + adja = port.adjacencies; + for(a = 0; a < adja.length; a++){ + adjaIter = adja[a]; + dotEdges += nodeFamily.id+":"+port.id + ":e->" + adjaIter.data.$targetFamily + ":" + adjaIter.nodeTo+":w;\n"; + } + + countRight++; + portIndex++; + port = json[portIndex]; + } + } + // add output ports + if(portIndex <= lastPortIndex && port.data.$type == "outputPort"){ + + // if we have preceding repo ports, insert a little gap to separate + // the output ports + if(json[portIndex-1].data.$type == "repositoryPort"){ + dotRight += padDefA + vertexLabelSize + padDefB; + countRight += 0.5; + } + else{ + dot += '<TD>'+tableDef; + } + + // write the dot representation of ports into a temporary string + while(portIndex <= lastPortIndex && port.data.$type == "outputPort"){ + dotRight += portDefA+ port.id + portDefB; + + // add edges + adja = port.adjacencies; + for(var a = 0; a < adja.length; a++){ + adjaIter = adja[a]; + dotEdges += nodeFamily.id+":"+port.id + ":e->" + adjaIter.data.$targetFamily + ":" + adjaIter.nodeTo+":w;\n"; + } + + countRight++; + portIndex++; + port = json[portIndex]; + } + } + // apply padding to right sideif there is space above and below the ports + var padding = nodeFamily.data.$height/2 - (1.5+countRight)*vertexLabelSize; + if(padding > 0){ + dotRight = padDefA+padding+padDefB + dotRight + padDefA+padding+padDefB; + } + dot += dotRight + '</TABLE></TD>'; + }else{ + dot += emptyDef; + } + // end of ports + + dot+= '</TR><TR><TD COLSPAN="2" HEIGHT="'+vertexLabelSize/2+'"></TD></TR></TABLE>>];\n'; + dot+= dotEdges; + + return dot; + } + /** reloads the graph, trying to restore all positions this function needs to be called whenever nodes or egdges are @@ -152,11 +303,6 @@ function GraphFlow(){ if(listener[event] == undefined){ listener[event] = []; } - /*if(listenerFunction.arguments.length != 3){ - Log.write("Invalid Listener Function: needs 3 parameters"); - return; - }*/ - // push new function to the listener table listener[event].push(listenerFunction); } @@ -319,7 +465,7 @@ function GraphFlow(){ @param inputPortIDs - an array of properties for the input ports (id, name, tooltip) @param outputPortIDs - an array of properties for the output ports (id, name, tooltip) */ - function addNode(xPosition, yPosition, nodeFamily, repositoryPorts, inputPorts, outputPorts, nodeType){ + this.addNode = function(xPosition, yPosition, nodeFamily, repositoryPorts, inputPorts, outputPorts, nodeType){ var countRepo = 0, countInput = 0, @@ -335,15 +481,16 @@ function GraphFlow(){ countOutput= outputPorts.length; } - var maxPorts = Math.max(countInput, countRepo+countOutput), + var maxPorts = Math.max(countInput, countRepo+countOutput+0.5), size = vertexLabelSize*2; - - if(maxPorts >= countInput && countRepo > 0 && countInput > 0){ - maxPorts++; + + // insert some space between repo ports and output ports, only if both are present + if(maxPorts > countInput && (countRepo == 0 || countOutput == 0)){ + maxPorts-= 0.5; } var width = (nodeFamily.name.length+4)*vertexLabelSize, - height = 1*size + + height = size + Math.max( 2*size, size/2 + maxPorts * size); // the classname may be longer than the diplayed name @@ -403,27 +550,31 @@ function GraphFlow(){ var x, y, relativeX, relativeY, loopPorts, loopType; switch(portType){ - case 0: // repository ports - var repoSpace = 0; - if(countInput == 0){ - repoSpace = 1; + case 0: // input ports + relativeX = -width/2; + relativeY = (0.75 - countInput/2 )*size; + loopPorts = inputPorts; + loopType = "inputPort"; + break; + + case 1: // repository ports + var repoSpace = 0.5; + if(countOutput == 0){ + repoSpace = 0; } - relativeX = width/2, - relativeY = ((0.5+repoSpace-countOutput-countRepo) * size)/2, + relativeX = width/2; + relativeY = (0.75 - (countOutput+countRepo+repoSpace)/2 )*size; loopPorts = repositoryPorts; loopType = "repositoryPort"; break; - case 1: // input ports - relativeX = -width/2, - relativeY = ( (1.5-countInput)*size)/2, - loopPorts = inputPorts; - loopType = "inputPort"; - break; - case 2: // output ports - relativeX = width/2, - relativeY = ((1.5 + countRepo - countOutput) * size)/2; + var repoSpace = 0.5; + if(countRepo == 0){ + repoSpace = 0; + } + relativeX = width/2; + relativeY = (0.75 - (countOutput-countRepo-repoSpace)/2 )*size; loopPorts = outputPorts; loopType = "outputPort"; break; @@ -588,7 +739,8 @@ function GraphFlow(){ var edge ={ "nodeTo": targetID, "nodeFrom": sourceID, - "data": {"$direction" : [ sourceID, targetID ] } + "data": {"$direction" : [ sourceID, targetID ], + "$targetFamily" : targetFamilyID} }; adja.push(edge); @@ -1110,7 +1262,7 @@ function init(){ // add a new listener: graph.addListener("onRightClick", function(node,info,e){ var newNode = {"id":"nuNode"+addNodeCounter, - "name":"i am node #"+addNodeCounter, + "name":"node #"+addNodeCounter, "nodeClass":"someClassAgain", "tooltip":"i am new and shiny!"}; graph.addFilter(info.getPos().x, info.getPos().y, newNode, null, @@ -1119,16 +1271,19 @@ function init(){ graph.refresh(); addNodeCounter++; }); - + /*graph.addListener("onRightClick", function(node, info, e){ + graph.graphToDot(null); + });*/ + // adds a listener that removes invalid Edges graph.addEdgeConstraints(); - graph.addListener("onRemoveNode", function(nodeID){alert("bye bye, "+ nodeID+".");}); + //graph.addListener("onRemoveNode", function(nodeID){alert("bye bye, "+ nodeID+".");}); // define graph by adding nodes var node1 = {"id":"superNode1", "name":"i am node", - "nodeClass":"someClass", + "nodeClass":"someFilter", "tooltip":"look at me, i'm a node!"}; graph.addFilter(0, -100, node1,[{"name":"repoPort", "id":"rp1", "tooltip":"repo"}, @@ -1149,15 +1304,13 @@ function init(){ // add nodes after graph creation var node2 = {"id":"superNode2", - "name":"i am node", - "nodeType":"Filter", - "nodeClass":"someOtherClass", + "name":"Super Repo", + "nodeClass":"Reposiotry", "tooltip":"look at me, i'm another node!"}; - graph.addRepository(100, 0, node2,{"name":"outputPort", "id":"op1"}); + graph.addRepository(100, 0, node2,{"name":"inputPort", "id":"ip1"}); var node3 = {"id":"superNode3", "name":"i am reader", - "nodeType":"Reader", - "nodeClass":"someOtherClass", + "nodeClass":"Reader", "tooltip":"look at me, i'm another node!"}; graph.addReader(0, 100, node3,[{"name":"repoPort", "id":"rp1"}], [{"name":"outputPort", "id":"op1"}]); // refresh graph to show the new nodes diff --git a/Kieker.WebGUI/src/main/webapp/js/jit.js b/Kieker.WebGUI/src/main/webapp/js/jit.js index ccb742b49bf747caf95c1e04b80f55a9147d3eec..02ffca3052eb09a17676669aedf4c988896c0139 100644 --- a/Kieker.WebGUI/src/main/webapp/js/jit.js +++ b/Kieker.WebGUI/src/main/webapp/js/jit.js @@ -6196,6 +6196,88 @@ var NodeHelper = { 'contains': function(npos, pos, dim) { return NodeHelper.circle.contains(npos, pos, dim); } + }, + /* + Object: NodeHelper.roundRect + */ + 'roundRect': { + /* + Method: render + + Renders a rounded rectangle into the canvas. + + Parameters: + + type - (string) Possible options are 'fill' or 'stroke'. + pos - (object) An *x*, *y* object with the position of the center of the rectangle. + width - (number) The width of the rectangle. + height - (number) The height of the rectangle. + roundness - the radius of the rounded corners. + canvas - (object) A <Canvas> instance. + + Example: + (start code js) + NodeHelper.rectangle.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas); + (end code) + */ + 'render': function(type, pos, width, height, roundness, canvas){ + width/=2; + height/=2; + var ctx = canvas.getCtx(), + rWidth = width - roundness, + rHeight = height - roundness; + x = pos.x - rWidth, + y = pos.y - rHeight; + ctx.save(); + + var pif = Math.PI/2; + ctx.beginPath(); + // upper left + ctx.arc(x , y , roundness, Math.PI, 3*pif, false); + x = pos.x + rWidth, + ctx.lineTo(x, pos.y - height); + + // upper right + ctx.arc(x , y , roundness, 3*pif, 0, false); + y = pos.y + rHeight, + ctx.lineTo(pos.x + width, y); + + // lower right + ctx.arc(x , y , roundness,0, pif, false); + x = pos.x - rWidth, + ctx.lineTo(x, pos.y + height); + + // lower left + ctx.arc(x , y , roundness, pif, Math.PI, false); + ctx.closePath(); + + ctx[type](); + ctx.restore(); + + width*=2; + height*=2; + }, + /* + Method: contains + + Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise. + + Parameters: + + npos - (object) An *x*, *y* object with the <Graph.Node> position. + pos - (object) An *x*, *y* object with the position to check. + width - (number) The width of the rendered rectangle. + height - (number) The height of the rendered rectangle. + + Example: + (start code js) + NodeHelper.rectangle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40); + (end code) + */ + 'contains': function(npos, pos, width, height){ + return Math.abs(pos.x - npos.x) <= width / 2 + && Math.abs(pos.y - npos.y) <= height / 2; + } } }; @@ -6387,7 +6469,8 @@ var EdgeHelper = { }, /* Method: contains - + + Simply copied from rectangle. Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise. Parameters: @@ -17622,6 +17705,7 @@ $jit.FlowGraph.$extend = true; var pos = node.pos.getc(true), width = node.getData('width'), height = node.getData('height'), + rounded = node.getData('dim')/4, strokeWidth = 2, fillColor = node.getData('fillColor'), posX = pos.x , @@ -17629,11 +17713,11 @@ $jit.FlowGraph.$extend = true; var ctx = canvas.getCtx(); // draw Stroke ( in the color, specified in node properties) - this.nodeHelper.rectangle.render('fill', {x: posX, y: posY}, width+2*strokeWidth, height+2*strokeWidth, canvas); + this.nodeHelper.roundRect.render('fill', {x: posX, y: posY}, width+2*strokeWidth, height+2*strokeWidth, rounded+2, canvas); // fill box ctx.fillStyle = fillColor; - this.nodeHelper.rectangle.render('fill', {x: posX, y: posY}, width, height, canvas); + this.nodeHelper.roundRect.render('fill', {x: posX, y: posY}, width, height, rounded, canvas); }, 'contains': function(node, pos){ @@ -17664,7 +17748,7 @@ $jit.FlowGraph.$extend = true; ctx = canvas.getCtx(); // draw close-button area - this.nodeHelper.square.render('fill', {x: bx, y: by}, size, canvas); + this.nodeHelper.roundRect.render('fill', {x: bx, y: by}, size*2, size*2, size/2, canvas); // draw cross var yTop = by-4*pe,