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:
} // }}}