Name: cockpit/lib/wfadaptor.js
| 1: | /* |
| 2: | This file is part of CPEE. |
| 3: | |
| 4: | CPEE is free software: you can redistribute it and/or modify it under the terms |
| 5: | of the GNU General Public License as published by the Free Software Foundation, |
| 6: | either version 3 of the License, or (at your option) any later version. |
| 7: | |
| 8: | CPEE is distributed in the hope that it will be useful, but WITHOUT ANY |
| 9: | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A |
| 10: | PARTICULAR PURPOSE. See the GNU General Public License for more details. |
| 11: | |
| 12: | You should have received a copy of the GNU General Public License along with |
| 13: | CPEE (file COPYING in the main directory). If not, see |
| 14: | <http://www.gnu.org/licenses/>. |
| 15: | */ |
| 16: | |
| 17: | // TODO: changes in svg-script: |
| 18: | // 1) drawing functions |
| 19: | // 2) creation of svg-container (Bug: arrows on lines) |
| 20: | // 3) after-function to insert using namespace of description |
| 21: | |
| 22: | // WfAdaptor: |
| 23: | // Handles interaction between Illustartor and Description |
| 24: | // e.g. Event fires to Adaptor to insert Element and Illustrator and Description do it |
| 25: | function WfAdaptor(manifesto) { // Controller {{{ |
| 26: | |
| 27: | // public variables {{{ |
| 28: | this.illustrator; |
| 29: | this.description; |
| 30: | this.elements = {}; |
| 31: | // }}} |
| 32: | |
| 33: | // public variables {{{ |
| 34: | var illustrator; |
| 35: | var description; |
| 36: | // }}} |
| 37: | |
| 38: | // helper funtions |
| 39: | this.set_description = function(desc) { // public {{{ |
| 40: | this.description.set_description(desc); |
| 41: | } // }}} |
| 42: | this.get_description = function() { // public {{{ |
| 43: | return description.get_description(); |
| 44: | } // }}} |
| 45: | this.notify = function() { // public {{{ |
| 46: | } // }}} |
| 47: | this.set_svg_container = function (container) { // {{{ |
| 48: | illustrator.set_container(container); // TODO: shadowing the container element |
| 49: | } // }}} |
| 50: | |
| 51: | // initialze |
| 52: | this.illustrator = illustrator = new WfIllustrator(this); |
| 53: | this.description = description = new WfDescription(this, this.illustrator); |
| 54: | |
| 55: | manifestation = new manifesto(this); |
| 56: | this.illustrator.noarrow = manifestation.noarrow; |
| 57: | for(element in manifestation.elements) { |
| 58: | this.illustrator.elements[element] = manifestation.elements[element].illustrator; |
| 59: | this.description.elements[element] = manifestation.elements[element].description; |
| 60: | this.elements[element] = manifestation.elements[element].adaptor; |
| 61: | } |
| 62: | } // }}} |
| 63: | |
| 64: | // WfIllustrator: |
| 65: | // Is in charge of displaying the Graph. It is further able insert and remove elements with given ID's from the illsutration. |
| 66: | function WfIllustrator(wf_adaptor) { // View {{{ |
| 67: | // Variable {{{ |
| 68: | // public |
| 69: | var height = this.height = 40; |
| 70: | var width = this.width = 40; |
| 71: | var noarrow = this.noarrow = []; |
| 72: | var elements = this.elements = {}; |
| 73: | var svg = this.svg = {}; |
| 74: | this.draw = {}; |
| 75: | // private |
| 76: | var adaptor = null; |
| 77: | // }}} |
| 78: | // Generic Functions {{{ |
| 79: | this.set_container = function(con) { // {{{ |
| 80: | svg.container = con; |
| 81: | svg.container.append($X('<defs xmlns="http://www.w3.org/2000/svg">' + |
| 82: | '<marker id="arrow" viewBox="0 0 10 10" refX="33" refY="5" orient="auto" markerUnits="strokeWidth" markerWidth="4.5" makerHeight="4.5">' + |
| 83: | '<path d="m 2 2 l 6 3 l -6 3 z"/>' + |
| 84: | '</marker>' + |
| 85: | '</defs>')); |
| 86: | svg.defs = {}; |
| 87: | svg.defs['unknown'] = $X('<g xmlns="http://www.w3.org/2000/svg" class="unknown">' + |
| 88: | '<circle cx="15" cy="15" r="14" class="unkown"/>' + |
| 89: | '<text transform="translate(15,20)" class="normal">?</text>' + |
| 90: | '</g>'); |
| 91: | for(element in elements) |
| 92: | if(elements[element].svg() != false) { |
| 93: | var sym = $X('<g xmlns="http://www.w3.org/2000/svg"/>').append(elements[element].svg().children()); // append all children to symbol |
| 94: | $.each(elements[element].svg().attr('class').split(/\s+/), function(index, item) { sym.addClass(item); }); // copy all classes from the root node |
| 95: | svg.defs[element] = sym; |
| 96: | } |
| 97: | } // }}} |
| 98: | var clear = this.clear = function() { // {{{ |
| 99: | $('> :not(defs)', svg.container).each(function() {$(this).remove()}); |
| 100: | } // }}} |
| 101: | this.set_svg = function(graph) { // {{{ |
| 102: | if(graph.max.row < 1) graph.max.row = 1; |
| 103: | if(graph.max.col < 1) graph.max.col = 1; |
| 104: | svg.container.attr({'height': (graph.max.row+0.3)*height, 'width':(graph.max.col+0.65)*width}); |
| 105: | svg.container.append(graph.svg); |
| 106: | } // }}} |
| 107: | // }}} |
| 108: | // Helper Functions {{{ |
| 109: | var draw_symbol = this.draw.draw_symbol = function (tname, sym_name, id, title, row, col, group) { // {{{ |
| 110: | if(elements[sym_name] == undefined || elements[sym_name].svg == undefined) sym_name = 'unknown'; |
| 111: | var g = $X('<g class="element" element-id="' + id + '" transform="translate(' + String((col*width)-((width*0.39))) + ',' + String(row*height-((height*0.74))) + ')" xmlns="http://www.w3.org/2000/svg">' + |
| 112: | '<text class="super" transform="translate(30,8.4)">' + |
| 113: | '<tspan class="active">0</tspan>' + |
| 114: | '<tspan class="colon">,</tspan>' + |
| 115: | '<tspan class="vote">0</tspan>' + |
| 116: | '</text>' + |
| 117: | '</g>'); |
| 118: | var sym = svg.defs[sym_name].clone(); |
| 119: | sym.prepend($X('<title xmlns="http://www.w3.org/2000/svg">' + title + '</title>')); |
| 120: | sym.attr('class','activities'); |
| 121: | g.append(sym); |
| 122: | |
| 123: | // Binding events for symbol |
| 124: | for(event_name in adaptor.elements[tname]) { |
| 125: | sym.bind(event_name, {'function_call':adaptor.elements[tname][event_name]}, function(e) { e.data.function_call($(this).parents(':first').attr('element-id'),e)}); |
| 126: | if(event_name == 'mousedown') sym.bind('contextmenu', false); |
| 127: | } |
| 128: | if(group) {group.append(g);} |
| 129: | else {svg.container.children('g:first').append(g);} |
| 130: | return g; |
| 131: | } // }}} |
| 132: | var draw_border = this.draw.draw_border = function(id, p1, p2, group) { // {{{ |
| 133: | group.prepend($X('<rect element-id="' + id + '" x="' + (p1.col-0.50)*width + '" ' + |
| 134: | 'y="' + (p1.row-0.80)*height + '" ' + |
| 135: | 'width="' + ((p2.col+1.00)-p1.col)*width + '" ' + |
| 136: | 'height="' + ((p2.row+1.00)-p1.row)*height +'" ' + |
| 137: | 'class="block" rx="15" ry="15" xmlns="http://www.w3.org/2000/svg"/>')); |
| 138: | } // }}} |
| 139: | var draw_tile = this.draw.draw_tile = function(id, p1, p2, group) { // {{{ |
| 140: | group.prepend($X('<rect element-id="' + id + '" x="' + (p1.col-0.50)*width + '" ' + |
| 141: | 'y="' + (p1.row-0.80)*height + '" ' + |
| 142: | 'width="' + ((p2.col+1.00)-p1.col)*width + '" ' + |
| 143: | 'height="' + ((p2.row+1.00)-p1.row)*height +'" ' + |
| 144: | 'class="tile" rx="15" ry="15" xmlns="http://www.w3.org/2000/svg"/>')); |
| 145: | } // }}} |
| 146: | var draw_connection = this.draw.draw_connection = function(group, start, end, max_line, num_lines, arrow) { // {{{ |
| 147: | if(((end['row']-start['row']) == 0) && ((end['col']-start['col']) == 0)) return; |
| 148: | var line; |
| 149: | if (arrow) |
| 150: | line = $X('<path xmlns="http://www.w3.org/2000/svg" class="ourline" marker-end="url(#arrow)"/>'); |
| 151: | else |
| 152: | line = $X('<path xmlns="http://www.w3.org/2000/svg" class="ourline"/>'); |
| 153: | if (end['row']-start['row'] == 0 || end['col']-start['col'] == 0) { // straight line |
| 154: | line.attr("d", "M " + String(start['col']*width) + "," + String(start['row']*height-15) +" "+ |
| 155: | String(end['col']*width) + "," + String(end['row']*height-15) |
| 156: | ); |
| 157: | } else if (end['row']-start['row'] > 0) { // downwards |
| 158: | if (end['col']-start['col'] > 0) {// left - right |
| 159: | line.attr("d", "M " + String(start['col']*width) + "," + String(start['row']*height-15) +" "+ |
| 160: | String(start['col']*width+14) + "," + String((end['row']-1)*height) +" "+ // first turn of hotizontal-line going away from node |
| 161: | String(end['col']*width) + "," + String((end['row']-1)*height) +" "+ |
| 162: | String(end['col']*width) + "," + String(end['row']*height-15) |
| 163: | ); |
| 164: | } else { // right - left |
| 165: | line.attr("d", "M " + String(start['col']*width) + "," + String(start['row']*height-15) +" "+ |
| 166: | String(start['col']*width) + "," + String(end['row']*height-35) +" "+ |
| 167: | String(end['col']*width+14) + "," + String(end['row']*height-35) +" "+ // last turn of horizontal-line going into the node |
| 168: | String(end['col']*width) + "," + String(end['row']*height-15) |
| 169: | ); |
| 170: | } |
| 171: | } else if(end['row']-start['row'] < 0) { // upwards |
| 172: | if(num_lines > 1) {// ??? no idea |
| 173: | line.attr("d", "M " + String(start['col']*width) + "," + String(start['row']*height-15) +" "+ |
| 174: | String(start['col']*width) + "," + String((max_line-1)*height+5) +" "+ |
| 175: | String(end['col']*width+20) + "," + String((max_line-1)*height+5) +" "+ |
| 176: | String(end['col']*width+20) + "," + String(end['row']*height+25)+" "+ |
| 177: | String(end['col']*width) + "," + String(end['row']*height-15) |
| 178: | ); |
| 179: | } else { |
| 180: | line.attr("d", "M " + String(start['col']*width) + "," + String(start['row']*height-15) +" "+ |
| 181: | String(end['col']*width+20) + "," + String(start['row']*height-15) +" "+ |
| 182: | String(end['col']*width+20) + "," + String(end['row']*height+25)+" "+ |
| 183: | String(end['col']*width) + "," + String(end['row']*height-15) |
| 184: | ); |
| 185: | } |
| 186: | } |
| 187: | // Seems to solve injection groups-line problem, but I guess it will caus problem when collapsing elements |
| 188: | //if(group) {group.prepend(line);} |
| 189: | //else |
| 190: | {svg.container.append(line);} |
| 191: | } // }}} |
| 192: | // }}} |
| 193: | // Initialize {{{ |
| 194: | adaptor = wf_adaptor; |
| 195: | // }}} |
| 196: | } // }}} |
| 197: | |
| 198: | // WfDescription: |
| 199: | // Manages the description. Is is further able to add/remove elements from the controlflow description. |
| 200: | function WfDescription(wf_adaptor, wf_illustrator) { // Model {{{ |
| 201: | // public variables |
| 202: | var elements = this.elements = {}; |
| 203: | // private variables |
| 204: | var adaptor; |
| 205: | var illustrator; |
| 206: | var description; |
| 207: | var id_counter = {}; |
| 208: | var update_illustrator = true; |
| 209: | |
| 210: | // Generic Functions {{{ |
| 211: | this.set_description = function(desc, auto_update) { // public {{{ |
| 212: | if(auto_update != undefined) update_illustrator = auto_update; |
| 213: | if(typeof desc == "string") { |
| 214: | description = $($.parseXML(desc)); |
| 215: | } else if(desc instanceof jQuery) { |
| 216: | description = desc; |
| 217: | } else { |
| 218: | alert("WfDescription: unknown description type:\nConstructor-Name: " + desc.constructor + " / TypeOf: " + (typeof desc)); |
| 219: | description = null; |
| 220: | } |
| 221: | id_counter = {}; |
| 222: | illustrator.clear(); |
| 223: | var graph = parse(description.children('description').get(0), {'row':0,'col':0}); |
| 224: | illustrator.set_svg(graph); |
| 225: | } // }}} |
| 226: | var gd = this.get_description = function() { // public {{{ |
| 227: | var serxml = $(description.get(0).documentElement).clone(true); |
| 228: | serxml.removeAttr('svg-id'); |
| 229: | $('*[svg-id]',serxml).each(function(){ |
| 230: | $(this).removeAttr('svg-id'); |
| 231: | }); |
| 232: | return serxml.serializeXML(); |
| 233: | } // }}} |
| 234: | this.get_node_by_svg_id = function(svg_id) { // {{{ |
| 235: | return $('[svg-id = \'' + svg_id + '\']', description); |
| 236: | } // }}} |
| 237: | this.get_free_id = function() { // {{{ |
| 238: | var existing = new Array(); |
| 239: | $('*[id]', description).each(function(){existing.push($(this).attr('id'))}); |
| 240: | var id = 1; |
| 241: | while ($.inArray('a' + id,existing) != -1) { |
| 242: | id += 1; |
| 243: | } |
| 244: | return 'a' + id; |
| 245: | } // }}} |
| 246: | var update = this.update = function(svgid) { // {{{ |
| 247: | id_counter = {}; |
| 248: | if(update_illustrator){ |
| 249: | illustrator.clear(); |
| 250: | var graph = parse(description.children('description').get(0), {'row':0,'col':0}); |
| 251: | illustrator.set_svg(graph); |
| 252: | } |
| 253: | var newn = $('*[new=true]',description); |
| 254: | newn.removeAttr('new'); |
| 255: | |
| 256: | if (newn.attr('svg-id') != undefined) |
| 257: | adaptor.notify(newn.attr('svg-id')); |
| 258: | else if (svgid != undefined) |
| 259: | adaptor.notify(svgid); |
| 260: | else |
| 261: | console.info('Something went horribly wrong'); |
| 262: | } // }}} |
| 263: | // }}} |
| 264: | // Adaption functions {{{ |
| 265: | this.insert_after = function(new_node, target) { // {{{ |
| 266: | var nn; |
| 267: | if(typeof(new_node) == 'function') {nn = new_node(target);} |
| 268: | else {nn = new_node;} |
| 269: | target.after(nn); |
| 270: | nn.attr('new','true'); |
| 271: | update(); |
| 272: | } // }}} |
| 273: | this.insert_first_into = function(new_node, target, selector) { // {{{ |
| 274: | var nn; |
| 275: | if(typeof(new_node) == 'function') {nn = new_node(target);} |
| 276: | else {nn = new_node;} |
| 277: | target.prepend(nn); |
| 278: | nn.attr('new','true'); |
| 279: | update(); |
| 280: | } // }}} |
| 281: | this.insert_last_into = function(new_node, target, selector) { // {{{ |
| 282: | var nn; |
| 283: | if(typeof(new_node) == 'function') {nn = new_node(target);} |
| 284: | else {nn = new_node;} |
| 285: | target.append(nn); |
| 286: | nn.attr('new','true'); |
| 287: | update(); |
| 288: | } // }}} |
| 289: | this.remove = function(selector, target) {//{{{ |
| 290: | var svgid; |
| 291: | if(selector == undefined) { |
| 292: | svgid = target.attr('svg-id'); |
| 293: | target.remove() |
| 294: | } else { |
| 295: | svgid = $(selector, target).attr('svg-id'); |
| 296: | $(selector, target).remove(); |
| 297: | } |
| 298: | update(svgid); |
| 299: | } |
| 300: | // }}} |
| 301: | // }}} |
| 302: | // Helper Functions {{{ |
| 303: | var parse = function(root, parent_pos) { // private {{{ |
| 304: | var pos = jQuery.extend(true, {}, parent_pos); |
| 305: | var max = {'row': 0,'col': 0}; |
| 306: | var prev = [parent_pos]; // connects parent with child(s), depending on the expansion |
| 307: | var endnodes = []; |
| 308: | var root_expansion = illustrator.elements[root.tagName].expansion(root); |
| 309: | var block = {'max':{}}; // e.g. {'max':{'row':0,'col':0}, 'endpoints':[]}; |
| 310: | var collapsed = false; |
| 311: | |
| 312: | var group = $X('<g class="group" xmlns="http://www.w3.org/2000/svg"/>'); |
| 313: | |
| 314: | if(root_expansion == 'horizontal') pos.row++; |
| 315: | if(illustrator.elements[root.tagName].col_shift(root) == true && root_expansion != 'horizontal') pos.col++; |
| 316: | |
| 317: | if(root.tagName == 'description') { // First parsing {{{ |
| 318: | pos.row++; |
| 319: | max.row++; |
| 320: | $(root).attr('svg-id','description'); |
| 321: | group.attr('element-id','group-description'); |
| 322: | illustrator.draw.draw_symbol('start', 'start', 'description', 'START', pos.row, pos.col, group); |
| 323: | } // }}} |
| 324: | |
| 325: | $(root).children().each(function() { |
| 326: | var tname = this.tagName; |
| 327: | |
| 328: | // Set SVG-ID {{{ |
| 329: | if($(this).attr('id') == undefined) { |
| 330: | if(id_counter[tname] == undefined) id_counter[tname] = -1; |
| 331: | $(this).attr('svg-id', tname + '_' + (++id_counter[tname])); |
| 332: | $(this).attr('svg-label', ''); |
| 333: | } else { |
| 334: | $(this).attr('svg-id', $(this).attr('id')); |
| 335: | if ($(this).children('parameters').length > 0) { |
| 336: | $(this).attr('svg-label', $('label',$(this).children('parameters')).text().replace(/^['"]/,'').replace(/['"]$/,'')); |
| 337: | } else { |
| 338: | $(this).attr('svg-label', ''); |
| 339: | } |
| 340: | } // }}} |
| 341: | // Calculate next position {{{ |
| 342: | if($(this).attr('collapsed') == undefined || $(this).attr('collapsed') == 'false') { collapsed = false; } |
| 343: | else { collapsed = true; } |
| 344: | if(root_expansion == 'vertical') pos.row++; |
| 345: | if(root_expansion == 'horizontal') pos.col++; |
| 346: | if(illustrator.elements[tname] != undefined && illustrator.elements[tname].type == 'complex' && !collapsed) { |
| 347: | if(illustrator.elements[tname] != undefined && !illustrator.elements[tname].svg()) pos.row--; |
| 348: | // TODO: Remaining problem is the order inside the svg. Thats why the connection is above the icon |
| 349: | block = parse(this, jQuery.extend(true, {}, pos)); |
| 350: | group.append(block.svg); |
| 351: | block.svg.attr('id', 'group-' + $(this).attr('svg-id')); |
| 352: | if(illustrator.elements[tname].endnodes == 'aggregate') endnodes = []; // resets endpoints e.g. potential preceding primitive |
| 353: | } else { |
| 354: | if(illustrator.elements[tname] != undefined && illustrator.elements[tname].type == 'primitive' && illustrator.elements[tname].svg()) { // This enables "invisble" elements, by returning false in the SVG function (e.g. constraints) |
| 355: | block.max.row = pos.row; |
| 356: | block.max.col = pos.col; |
| 357: | block.endnodes = (!collapsed ? [pos] : [jQuery.extend(true, {}, pos)]); |
| 358: | block.svg = group; |
| 359: | } |
| 360: | } |
| 361: | // }}} |
| 362: | // Draw symbol {{{ |
| 363: | var sym_name = ''; |
| 364: | if(!illustrator.elements[tname]) {sym_name = 'unknown';} |
| 365: | else if(typeof illustrator.elements[tname].resolve_symbol == 'function') {sym_name = illustrator.elements[tname].resolve_symbol(this);} |
| 366: | else if(typeof illustrator.elements[tname].resolve_symbol == 'string') {sym_name = illustrator.elements[tname].resolve_symbol;} |
| 367: | else {sym_name = tname;} |
| 368: | if((illustrator.elements[tname] && illustrator.elements[tname].svg()) || sym_name == 'unknown') { |
| 369: | illustrator.draw.draw_symbol(tname, sym_name, $(this).attr('svg-id'), $(this).attr('svg-label'), pos.row, pos.col, block.svg).addClass(illustrator.elements[tname] ? illustrator.elements[tname].type : 'primitive unknown'); |
| 370: | } else { console.log("no icon "+ tname);} |
| 371: | if(illustrator.elements[tname] && illustrator.elements[tname].border) illustrator.draw.draw_border($(this).attr('svg-id'), pos, block.max, block.svg); |
| 372: | if(illustrator.elements[tname] && illustrator.elements[tname].type == 'complex') illustrator.draw.draw_tile($(this).attr('svg-id'), pos, block.max, block.svg); |
| 373: | // }}} |
| 374: | // Calculate Connection {{{ |
| 375: | if(illustrator.elements[tname] != undefined && illustrator.elements[tname].closeblock) { // Close Block if element e.g. loop |
| 376: | for(node in block.endnodes) illustrator.draw.draw_connection(group, block.endnodes[node], pos, block.max.row+1, block.endnodes.length, true); |
| 377: | } |
| 378: | if(illustrator.elements[tname] != undefined && illustrator.elements[tname].endnodes != 'this') { |
| 379: | for(i in block.endnodes) endnodes.push(block.endnodes[i]); // collects all endpoints from different childs e.g. alternatives from choose |
| 380: | } else { endnodes = [jQuery.extend(true, {}, pos)]; } // sets this element as only endpoint (aggreagte) |
| 381: | if(prev[0].row == 0 || prev[0].col == 0) { // this enforces the connection from description to the first element |
| 382: | illustrator.draw.draw_connection(group, { row: 1, col: 1 }, pos, null, null, true); |
| 383: | } else { |
| 384: | if ($.inArray(tname,noarrow) == -1) |
| 385: | for(node in prev) illustrator.draw.draw_connection(group, prev[node], pos, null, null, true); |
| 386: | else |
| 387: | for(node in prev) illustrator.draw.draw_connection(group, prev[node], pos, null, null, false); |
| 388: | } |
| 389: | // }}} |
| 390: | // Prepare next iteration {{{ |
| 391: | if(root_expansion == 'vertical') { prev = jQuery.extend(true, {}, endnodes); pos.row = block.max.row;} // covers e.g. input's for alternative, parallel_branch, ... everything with horizontal expansion |
| 392: | if(root_expansion == 'horizontal') pos.col = block.max.col; |
| 393: | if(max.row < block.max.row) max.row = block.max.row; |
| 394: | if(max.col < block.max.col) max.col = block.max.col; |
| 395: | // }}} |
| 396: | }); |
| 397: | |
| 398: | if($(root).children().length == 0) { // empty complex found |
| 399: | endnodes = [parent_pos]; |
| 400: | max.row = parent_pos.row; |
| 401: | max.col = parent_pos.col; |
| 402: | } |
| 403: | if(illustrator.elements[root.tagName].endnodes == 'this' && illustrator.elements[root.tagName].closeblock == false) {endnodes = [prev];} // closeblock == false, allows loop to close himselfe |
| 404: | return {'endnodes': endnodes, 'max':max, 'svg':group}; |
| 405: | } // }}} |
| 406: | // }}} |
| 407: | |
| 408: | // Initialze {{{ |
| 409: | adaptor = wf_adaptor; |
| 410: | illustrator = wf_illustrator; |
| 411: | // }}} |
| 412: | } // }}} |
