blob: 2de6a40d6c1ac53a4111e082101d8053f5958460 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* SVG and VML composite rendering functions.
*/
var graph = {};
/**
* Basic colors
*/
graph.colors = {};
graph.colors.black = '#000000';
graph.colors.blue = '#0000ff';
graph.colors.cyan = '#00ffff';
graph.colors.gray = '#808080'
graph.colors.lightgray = '#dcdcdc'
graph.colors.green = '#00ff00';
graph.colors.magenta = '#ff00ff';
graph.colors.orange = '#ffa500';
graph.colors.pink = '#ffc0cb';
graph.colors.purple = '#800080';
graph.colors.red = '#ff0000';
graph.colors.white = '#ffffff';
graph.colors.yellow = '#ffff00';
graph.colors.orange1 = '#ffbb00';
graph.colors.green1 = '#96d333';
graph.colors.blue1 = '#00c3c9';
graph.colors.red1 = '#d03f41';
graph.colors.yellow1 = '#fcee21';
graph.colors.magenta1 = '#c0688a';
graph.colors.cyan1 = '#d5dcf9';
graph.colors.lightgray1 = '#dcdcdc'
/**
* Default positions and sizes.
*/
var palcx = 2500;
var trashcx = 2480;
var proxcx = 20;
var proxcy = 20;
var buttoncx = 65;
var buttoncy = 30;
var curvsz = 6;
var tabsz = 2;
var fontsz = '11px';
/**
* Base path class.
*/
graph.BasePath = function() {
this.path = '';
this.x = 0;
this.y = 0;
this.pos = function(x, y) {
this.x = x;
this.y = y;
return this;
};
this.xpos = function() {
return this.x;
};
this.ypos = function() {
return this.y;
};
this.rmove = function(x, y) {
return this.move(this.x + x, this.y + y);
};
this.rline = function(x, y) {
return this.line(this.x + x, this.y + y);
};
this.rcurve = function(x1, y1, x, y) {
return this.curve(this.x + x1, this.y + y1, this.x + x1 + x, this.y + y1 + y);
};
this.str = function() {
return this.path;
};
};
/**
* SVG rendering functions.
*/
graph.svgns='http://www.w3.org/2000/svg';
/**
* Make an SVG graph.
*/
graph.mkgraph = function(pos, cvalue, cadd, cdelete) {
// Create a div element to host the graph
var div = document.createElement('div');
div.id = 'svgdiv';
div.style.position = 'absolute';
div.style.left = ui.pixpos(pos.xpos());
div.style.top = ui.pixpos(pos.ypos());
div.style.overflow = 'hidden';
document.body.appendChild(div);
// Create SVG element
var svg = document.createElementNS(graph.svgns, 'svg');
svg.style.height = ui.pixpos(5000);
svg.style.width = ui.pixpos(5000);
div.appendChild(svg);
// Track element dragging and selection
graph.dragging = null;
graph.selected = null;
cvalue.disabled = true;
cdelete.disabled = true;
/**
* Find the first draggable element in a hierarchy of elements.
*/
function draggable(n) {
if (n == div || n == svg || n == null)
return null;
if (n.nodeName == 'g' && !isNil(n.id) && n.id != '')
return n;
return draggable(n.parentNode);
}
/**
* Handle a mouse down event.
*/
div.onmousedown = function(e) {
// Find draggable component
graph.dragging = draggable(e.target);
graph.selected = graph.dragging;
if (graph.dragging == null) {
// Reset current selection
cvalue.value = '';
cvalue.disabled = true;
cdelete.disabled = true;
// Trigger component select event
svg.oncompselect('');
return true;
}
// Clone component from the palette
var compos = scdl.composite(svg.compos);
if (graph.dragging.id.substring(0, 8) == 'palette:') {
graph.dragging = graph.clonepalette(graph.dragging, compos);
graph.selected = graph.dragging;
// Move into the editing area and hide the palette
var gpos = graph.relpos(graph.dragging);
graph.move(graph.dragging, graph.mkpath().move(gpos.xpos() + palcx, gpos.ypos()));
div.style.left = ui.pixpos(palcx * -1);
}
// Cut wire to component
if (graph.dragging.parentNode != svg)
setElement(compos, graph.sortcompos(graph.cutwire(graph.dragging, compos, svg)));
// Bring component to the top
graph.bringtotop(graph.dragging, svg);
// Remember current mouse position
var pos = typeof e.touches != "undefined" ? e.touches[0] : e;
graph.dragX = pos.screenX;
graph.dragY = pos.screenY;
// Update the component name and property value fields
cvalue.value = graph.hasproperty(graph.selected.comp)? graph.property(graph.selected.comp) : graph.selected.id;
cvalue.disabled = false;
cdelete.disabled = false;
// Trigger component select event
svg.oncompselect(graph.selected.id);
if (e.preventDefault)
e.preventDefault();
else
e.returnValue = false;
return true;
};
// Support touch devices
div.ontouchstart = div.onmousedown;
/**
* Handle a mouse up event.
*/
div.onmouseup = function(e) {
if (graph.dragging == null)
return true;
if (graph.dragging.parentNode == svg && graph.dragging.id.substring(0, 8) != 'palette:') {
var gpos = graph.relpos(graph.dragging);
if (gpos.xpos() >= trashcx) {
// If component close enough to editing area, move it there
if (gpos.xpos() < palcx)
graph.move(graph.dragging, graph.mkpath().move(palcx, gpos.ypos()));
// Add new dragged component to the composite
if (isNil(graph.dragging.compos)) {
var compos = scdl.composite(svg.compos);
setElement(compos, graph.sortcompos(graph.addcomp(graph.dragging.comp, compos)));
graph.dragging.compos = svg.compos;
}
// Update component position
setElement(graph.dragging.comp, graph.movecomp(graph.dragging.comp, graph.abspos(graph.dragging, svg)));
// Wire component to neighboring reference
if (!isNil(graph.dragging.svcpos)) {
var compos = scdl.composite(svg.compos);
setElement(compos, graph.sortcompos(graph.clonerefs(graph.wire(graph.dragging, compos, svg))));
}
} else {
// Discard component dragged out of composite
svg.removeChild(graph.dragging);
if (!isNil(graph.dragging.compos)) {
var compos = scdl.composite(svg.compos);
setElement(compos, graph.sortcompos(graph.clonerefs(graph.gcollect(graph.removecomp(graph.dragging.comp, compos)))));
}
// Reset current selection
graph.selected = null;
cvalue.value = '';
cvalue.disabled = true;
cdelete.disabled = true;
// Trigger component select event
svg.oncompselect('');
}
}
// Forget current dragged component
graph.dragging = null;
// Refresh the composite
graph.refresh(svg);
// Trigger composite change event
svg.oncomposchange(false);
return true;
};
// Support touch devices
div.ontouchend = div.onmouseup;
// Handle a mouse click event.
div.onclick = function(e) {
if (graph.dragging == null && (e.target == div || e.target == svg)) {
// Dismiss the palette
if (ui.numpos(div.style.left) != (palcx * -1))
div.style.left = ui.pixpos(palcx * -1);
}
return true;
}
/**
* Handle a mouse move event.
*/
window.onmousemove = function(e) {
if (graph.dragging == null)
return true;
// Calculate new position of dragged element
var gpos = graph.relpos(graph.dragging);
var pos = typeof e.touches != "undefined" ? e.touches[0] : e;
var newX = gpos.xpos() + (pos.screenX - graph.dragX);
var newY = gpos.ypos() + (pos.screenY - graph.dragY);
if (newX >= 0)
graph.dragX = pos.screenX;
else
newX = 0;
if (newY >= 0)
graph.dragY = pos.screenY;
else
newY = 0;
// Move the dragged element
graph.move(graph.dragging, graph.mkpath().move(newX, newY));
return true;
};
// Support touch devices
div.ontouchmove = window.onmousemove;
/**
* Handle field on change events.
*/
cvalue.onchange = function() {
if (graph.selected == null)
return false;
// Change component name and refactor references to it
function changename() {
var compos = scdl.composite(svg.compos);
cvalue.value = graph.ucid(cvalue.value, compos);
graph.selected.id = cvalue.value;
setElement(compos, graph.sortcompos(graph.renamecomp(graph.selected.comp, compos, cvalue.value)));
// Trigger component select event
svg.oncompselect(graph.selected.id);
// Refresh the composite
graph.refresh(svg);
// Trigger composite change event
svg.oncomposchange(true);
return false;
}
// Change the component property value
function changeprop() {
graph.setproperty(graph.selected.comp, cvalue.value);
cvalue.value = graph.property(graph.selected.comp);
cvalue.disabled = !graph.hasproperty(graph.selected.comp);
// Refresh the composite
graph.refresh(svg);
// Trigger composite change event
svg.oncomposchange(true);
return false;
}
return graph.hasproperty(graph.selected.comp)? changeprop() : changename();
};
// Handle delete event
cdelete.onclick = function() {
if (graph.selected == null)
return false;
if (graph.selected.id.substring(0, 8) != 'palette:' && !isNil(graph.selected.compos)) {
// Remove selected component
var compos = scdl.composite(svg.compos);
setElement(compos, graph.sortcompos(graph.clonerefs(graph.gcollect(graph.removecomp(graph.selected.comp, compos)))));
// Reset current selection
graph.selected = null;
cvalue.value = '';
cvalue.disabled = true;
cdelete.disabled = true;
// Refresh the composite
graph.refresh(svg);
// Trigger component select event
svg.oncompselect('');
// Trigger composite change event
svg.oncomposchange(true);
}
return false;
};
// Handle add event
cadd.onclick = function() {
// Show the palette
div.style.left = ui.pixpos(0);
return false;
};
// Create a hidden SVG element to help compute the width
// of component and reference titles
graph.titlewidthsvg = document.createElementNS(graph.svgns, 'svg');
graph.titlewidthsvg.style.visibility = 'hidden';
graph.titlewidthsvg.style.height = ui.pixpos(0);
graph.titlewidthsvg.style.width = ui.pixpos(0);
div.appendChild(graph.titlewidthsvg);
return svg;
};
/**
* Make a path.
*/
graph.mkpath = function() {
function Path() {
this.BasePath = graph.BasePath;
this.BasePath();
this.clone = function() {
return graph.mkpath().pos(this.xpos(), this.ypos());
};
this.move = function(x, y) {
this.path += 'M' + x + ',' + y + ' ';
return this.pos(x, y);
};
this.line = function(x, y) {
this.path += 'L' + x + ',' + y + ' ';
return this.pos(x, y);
};
this.curve = function(x1, y1, x, y) {
this.path += 'Q' + x1 + ',' + y1 + ' ' + x + ',' + y + ' ';
return this.pos(x, y);
};
this.end = function() {
this.path += 'Z';
return this;
};
}
return new Path();
};
/**
* Return an element representing a title.
*/
graph.mktitle = function(t, style) {
var title = document.createElementNS(graph.svgns, 'text');
title.setAttribute('x', 5);
title.setAttribute('y', 15);
title.setAttribute('text-anchor', 'start');
if (style != '')
title.style.cssText = style;
if (fontsz != '')
title.style.fontSize = fontsz;
title.style.cursor = 'default';
title.appendChild(document.createTextNode(t));
return title;
};
/**
* Return an element representing the title of a component.
*/
graph.comptitle = function(comp) {
return graph.mktitle(graph.title(comp), graph.compstyle(comp));
};
/**
* Return the width of the title of a component.
*/
graph.comptitlewidth = function(comp) {
var title = graph.comptitle(comp);
graph.titlewidthsvg.appendChild(title);
var width = title.getBBox().width + 2;
graph.titlewidthsvg.removeChild(title);
return width;
};
/**
* Return an element representing the title of a reference.
*/
graph.reftitle = function(ref) {
return graph.mktitle(graph.title(ref), graph.refstyle(ref));
};
/**
* Return the width of the title of a reference.
*/
graph.reftitlewidth = function(ref) {
var title = graph.reftitle(ref);
graph.titlewidthsvg.appendChild(title);
var width = title.getBBox().width;
graph.titlewidthsvg.removeChild(title);
return width;
};
/**
* Return an element representing the value of a property.
*/
graph.proptitle = function(comp) {
var title = graph.mktitle(graph.propertytitle(comp), graph.propstyle(comp));
title.setAttribute('x', graph.comptitlewidth(comp) + 7);
return title;
};
/**
* Return the width of the title of a property.
*/
graph.proptitlewidth = function(comp) {
var title = graph.proptitle(comp);
graph.titlewidthsvg.appendChild(title);
var width = title.getBBox().width + 4;
graph.titlewidthsvg.removeChild(title);
return width;
};
/**
* Return a node representing a component.
*/
graph.compnode = function(comp, cassoc, pos) {
// Make the component and property title elements
var title = graph.comptitle(comp);
var prop = graph.proptitle(comp);
// Compute the path of the component shape
var path = graph.comppath(comp, cassoc);
var d = path.str();
// Create the main component shape
var shape = document.createElementNS(graph.svgns, 'path');
shape.setAttribute('d', d);
shape.setAttribute('fill', graph.color(comp));
shape.setAttribute('fill-opacity', '0.60');
// Create an overlay contour shape
var contour = document.createElementNS(graph.svgns, 'path');
contour.setAttribute('d', d);
contour.setAttribute('fill', 'none');
contour.setAttribute('stroke', graph.colors.gray);
contour.setAttribute('stroke-width', '3');
contour.setAttribute('stroke-opacity', '0.20');
contour.setAttribute('transform', 'translate(1,1)');
// Create a group and add the component and contour shapes to it.
var g = document.createElementNS(graph.svgns, 'g');
g.id = scdl.name(comp);
g.setAttribute('transform', 'translate(' + pos.xpos() + ',' + pos.ypos() + ')');
g.appendChild(shape);
g.appendChild(contour);
g.appendChild(title);
g.appendChild(prop);
// Store the component and the positions of its services
// and references in the component shape
g.comp = comp;
g.refpos = reverse(path.refpos);
g.svcpos = reverse(path.svcpos);
return g;
};
/**
* Return a graphical group.
*/
graph.mkgroup = function(pos) {
var g = document.createElementNS(graph.svgns, 'g');
g.setAttribute('transform', 'translate(' + pos.xpos() + ',' + pos.ypos() + ')');
return g;
};
/**
* Return a node representing a button.
*/
graph.mkbutton = function(t, pos) {
// Make the button title
var title = graph.mktitle(t, '');
// Compute the path of the button shape
var path = graph.buttonpath().str();
// Create the main button shape
var shape = document.createElementNS(graph.svgns, 'path');
shape.setAttribute('d', path);
shape.setAttribute('fill', graph.colors.lightgray);
shape.setAttribute('fill-opacity', '0.60');
// Create an overlay contour shape
var contour = document.createElementNS(graph.svgns, 'path');
contour.setAttribute('d', path);
contour.setAttribute('fill', 'none');
contour.setAttribute('stroke', graph.colors.gray);
contour.setAttribute('stroke-width', '3');
contour.setAttribute('stroke-opacity', '0.20');
contour.setAttribute('transform', 'translate(1,1)');
// Create a group and add the button and contour shapes to it
var g = document.createElementNS(graph.svgns, 'g');
g.setAttribute('transform', 'translate(' + pos.xpos() + ',' + pos.ypos() + ')');
g.appendChild(shape);
g.appendChild(contour);
g.appendChild(title);
return g;
};
/**
* Return the relative position of a node.
*/
graph.relpos = function(e) {
var pmatrix = e.parentNode.getCTM();
var matrix = e.getCTM();
var curX = pmatrix != null? (Number(matrix.e) - Number(pmatrix.e)): Number(matrix.e);
var curY = pmatrix != null? (Number(matrix.f) - Number(pmatrix.f)): Number(matrix.f);
return graph.mkpath().move(curX, curY);
};
/**
* Move a node.
*/
graph.move = function(e, pos) {
e.setAttribute('transform', 'translate(' + pos.xpos() + ',' + pos.ypos() + ')');
};
/**
* Return the absolute position of a component node.
*/
graph.abspos = function(e, g) {
if (e == g)
return graph.mkpath();
var gpos = graph.relpos(e);
var pgpos = graph.abspos(e.parentNode, g);
return graph.mkpath().move(gpos.xpos() + pgpos.xpos(), gpos.ypos() + pgpos.ypos());
};
/**
* Bring a component node to the top.
*/
graph.bringtotop = function(n, g) {
if (n == g)
return null;
graph.move(n, graph.abspos(n, g));
g.appendChild(n);
}
/**
* Return the title of a SCDL element.
*/
graph.title = function(e) {
var t = scdl.title(e);
if (t != null) {
if (t == 'gt')
return '>'
if (t == 'lt')
return '<';
if (t.indexOf('{propval}') != -1)
return '';
if (t.indexOf('{compname}') == -1)
return t;
return t.replace('{compname}', scdl.name(e));
}
return scdl.name(e);
};
/**
* Return the display style of an SCDL component or reference.
*/
graph.compstyle = graph.refstyle = function(e) {
var s = scdl.style(e);
return isNil(s)? '' : s;
};
/**
* Return the property value of a SCDL component.
*/
graph.property = function(e) {
var p = scdl.properties(e);
if (isNil(p))
return '';
if (scdl.visible(car(p)) == 'false')
return '';
var pv = scdl.propertyValue(car(p));
return pv;
};
/**
* Return the title of a property of a SCDL component.
*/
graph.propertytitle = function(e) {
var pv = graph.property(e);
var t = scdl.title(e);
if (t.indexOf('{propval}') == -1)
return pv;
return t[0] == ' '? t.substr(1).replace('{propval}', pv) : t.replace('{propval}', pv);
};
/**
* Return true if a SCDL component has a property.
*/
graph.hasproperty = function(e) {
var p = scdl.properties(e);
if (isNil(p))
return false;
if (scdl.visible(car(p)) == 'false')
return false;
return true;
};
/**
* Return the display style of the property of an SCDL component.
*/
graph.propstyle = function(e) {
var p = scdl.properties(e);
if (isNil(p))
return '';
if (scdl.visible(car(p)) == 'false')
return '';
var s = scdl.style(car(p));
return isNil(s)? '' : s;
};
/**
* Change the property value of a SCDL component.
*/
graph.setproperty = function(e, value) {
var p = scdl.properties(e);
if (isNil(p))
return '';
if (scdl.visible(car(p)) == 'false')
return '';
var name = scdl.name(car(p));
setElement(car(p), mklist(element, "'property", mklist(attribute, "'name", name != null? name : "property"), value));
return value;
};
/**
* Return the color of a SCDL component.
*/
graph.color = function(comp) {
return memo(comp, 'color', function() {
var c = scdl.color(comp);
return c == null? graph.colors.blue1 : graph.colors[c];
});
};
/**
* Return the services on the top side of a component.
*/
graph.tsvcs = function(comp) {
return memo(comp, 'tsvcs', function() {
var svcs = scdl.services(comp);
var l = filter(function(s) { return scdl.align(s) == 'top' && scdl.visible(s) != 'false'; }, svcs);
if (isNil(l))
return mklist();
return mklist(car(l));
});
};
/**
* Return the services on the left side of a component.
*/
graph.lsvcs = function(comp) {
return memo(comp, 'lsvcs', function() {
var svcs = scdl.services(comp);
if (isNil(svcs))
return mklist(mklist("'element","'service","'attribute","'name",scdl.name(comp)));
var l = filter(function(s) {
var a = scdl.align(s);
var v = scdl.visible(s);
return (a == null || a == 'left') && v != 'false';
}, svcs);
if (isNil(l))
return mklist();
if (!isNil(graph.tsvcs(comp)))
return mklist();
return mklist(car(l));
});
};
/**
* Return the references on the bottom side of a component.
*/
graph.brefs = function(comp) {
return memo(comp, 'brefs', function() {
return filter(function(r) { return scdl.align(r) == 'bottom' && scdl.visible(r) != 'false'; }, scdl.references(comp));
});
};
/**
* Return the references on the right side of a component.
*/
graph.rrefs = function(comp) {
return memo(comp, 'rrefs', function() {
return filter(function(r) {
var a = scdl.align(r);
var v = scdl.visible(r);
return (a == null || a == 'right') && v != 'false';
}, scdl.references(comp));
});
};
/**
* Return the height of a reference on the right side of a component.
*/
graph.rrefheight = function(ref, cassoc) {
return memo(ref, 'rheight', function() {
var target = assoc(scdl.target(ref), cassoc);
if (isNil(target))
return tabsz * 10;
return graph.compclosureheight(cadr(target), cassoc);
});
};
/**
* Return the height of a reference on the bottom side of a component.
*/
graph.brefheight = function(ref, cassoc) {
return memo(ref, 'bheight', function() {
var target = assoc(scdl.target(ref), cassoc);
if (isNil(target))
return 0;
return graph.compclosureheight(cadr(target), cassoc);
});
};
/**
* Return the total height of the references on the right side of a component.
*/
graph.rrefsheight = function(refs, cassoc) {
if (isNil(refs))
return 0;
return graph.rrefheight(car(refs), cassoc) + graph.rrefsheight(cdr(refs), cassoc);
};
/**
* Return the max height of the references on the bottom side of a component.
*/
graph.brefsheight = function(refs, cassoc) {
if (isNil(refs))
return 0;
return Math.max(graph.brefheight(car(refs), cassoc), graph.brefsheight(cdr(refs), cassoc));
};
/**
* Return the height of a component node.
*/
graph.compheight = function(comp, cassoc) {
return memo(comp, 'height', function() {
var lsvcs = graph.lsvcs(comp);
var lsvcsh = Math.max(1, length(lsvcs)) * (tabsz * 10) + (tabsz * 4);
var rrefs = graph.rrefs(comp);
var rrefsh = graph.rrefsheight(rrefs, cassoc) + (tabsz * 4);
var height = Math.max(lsvcsh, rrefsh);
if (!isNil(graph.brefs(comp)))
height = Math.max(height, (tabsz * 10) + (tabsz * 4) + (tabsz * 2));
return height;
});
};
/**
* Return the height of a component and the components wired to its bottom side.
*/
graph.compclosureheight = function(comp, cassoc) {
return memo(comp, 'closureheight', function() {
var brefs = graph.brefs(comp);
var height = graph.compheight(comp, cassoc) + graph.brefsheight(brefs, cassoc);
return height;
});
};
/**
* Return the width of a reference on the bottom side of a component.
*/
graph.brefwidth = function(ref, cassoc) {
return memo(ref, 'width', function() {
var target = assoc(scdl.target(ref), cassoc);
if (isNil(target))
return tabsz * 10;
return graph.compclosurewidth(cadr(target), cassoc);
});
};
/**
* Return the total width of the references on the bottom side of a component.
*/
graph.brefswidth = function(refs, cassoc) {
if (isNil(refs))
return 0;
return graph.brefwidth(car(refs), cassoc) + graph.brefswidth(cdr(refs), cassoc);
};
/**
* Return the max width of the references on the right side of a component.
*/
graph.rrefswidth = function(refs, cassoc) {
if (isNil(refs))
return 0;
return Math.max(graph.brefwidth(car(refs), cassoc), graph.rrefswidth(cdr(refs), cassoc));
};
/**
* Return the width of a component.
*/
graph.compwidth = function(comp, cassoc) {
return memo(comp, 'width', function() {
var twidth = graph.comptitlewidth(comp) + graph.proptitlewidth(comp) + (tabsz * 8);
var tsvcs = graph.tsvcs(comp);
var tsvcsw = Math.max(1, length(tsvcs)) * (tabsz * 10) + (tabsz * 4);
var brefs = graph.brefs(comp);
var brefsw = graph.brefswidth(brefs, cassoc) + (tabsz * 4);
var width = Math.max(twidth, Math.max(tsvcsw, brefsw));
return width;
});
};
/**
* Return the width of a component and all the components wired to its right side.
*/
graph.compclosurewidth = function(comp, cassoc) {
return memo(comp, 'closurewidth', function() {
var rrefs = graph.rrefs(comp);
var width = graph.compwidth(comp, cassoc) + graph.rrefswidth(rrefs, cassoc);
return width;
});
};
/**
* Return a path representing a reference positioned to the right of a component.
*/
graph.rrefpath = function(ref, cassoc, path) {
var height = graph.rrefheight(ref, cassoc);
// Record reference position in the path
var xpos = path.xpos();
var ypos = path.ypos();
//path.refpos = cons(mklist(ref, graph.mkpath().move(xpos, ypos + (tabsz * 6))), path.refpos);
path.refpos = cons(mklist(ref, graph.mkpath().move(xpos, ypos + (tabsz * 5))), path.refpos);
// Compute the reference path
return path.rline(0,tabsz).rline(0,tabsz * 2).rcurve(0,tabsz,-tabsz,0).rcurve(-tabsz,0,0,-tabsz).rcurve(0,-tabsz,-tabsz,0).rcurve(-tabsz,0,0,tabsz).rline(0,tabsz * 4).rcurve(0,tabsz,tabsz,0).rcurve(tabsz,0,0,-tabsz).rcurve(0,-tabsz,tabsz,0).rcurve(tabsz,0,0,tabsz).line(path.xpos(),ypos + height);
};
/**
* Return a path representing a reference positioned at the bottom of a component.
*/
graph.brefpath = function(ref, cassoc, path) {
var width = graph.brefwidth(ref, cassoc);
// Record reference position in the path
var xpos = path.xpos();
var ypos = path.ypos();
path.refpos = cons(mklist(ref, graph.mkpath().move(xpos - width + tabsz * 5, ypos)), path.refpos);
// Compute the reference path
return path.line(xpos - width + (tabsz * 10),path.ypos()).rline(-tabsz,0).rline(-(tabsz *2),0).rcurve(-tabsz,0,0,-tabsz).rcurve(0,-tabsz,tabsz,0).rcurve(tabsz,0,0,-tabsz).rcurve(0,-tabsz,-tabsz,0).rline(-(tabsz * 4),0).rcurve(-tabsz,0,0,tabsz).rcurve(0,tabsz,tabsz,0).rcurve(tabsz,0,0,tabsz).rcurve(0,tabsz,-tabsz,0).line(xpos - width,path.ypos());
};
/**
* Return a path representing a service positioned to the left of a component.
*/
graph.lsvcpath = function(svc, cassoc, path) {
var height = tabsz * 10;
// Record service position in the path
var xpos = path.xpos();
var ypos = path.ypos();
path.svcpos = cons(mklist(svc, graph.mkpath().move(xpos, ypos - (tabsz * 6))), path.svcpos);
// Compute the service path
return path.rline(0,-tabsz).rline(0, -(tabsz * 2)).rcurve(0,-tabsz,-tabsz,0).rcurve(-tabsz,0,0,tabsz).rcurve(0,tabsz,-tabsz,0).rcurve(-tabsz,0,0,-tabsz).rline(0,-(tabsz * 4)).rcurve(0,-tabsz,tabsz,0).rcurve(tabsz,0,0,tabsz).rcurve(0,tabsz,tabsz,0).rcurve(tabsz,0,0,-tabsz).line(path.xpos(), ypos - height);
};
/**
* Return a path representing a service positioned at the top of a component.
*/
graph.tsvcpath = function(svc, cassoc, path) {
var width = tabsz * 10;
// Record service position in the path
var xpos = path.xpos();
var ypos = path.ypos();
path.svcpos = cons(mklist(svc, graph.mkpath().move(xpos + (tabsz * 5), ypos)), path.svcpos);
// Compute the service path
return path.rline(tabsz,0).rline(tabsz * 2,0).rcurve(tabsz,0,0,-tabsz).rcurve(0,-tabsz,-tabsz,0).rcurve(-tabsz,0,0,-tabsz).rcurve(0,-tabsz,tabsz,0).rline(tabsz * 4,0).rcurve(tabsz,0,0,tabsz).rcurve(0,tabsz,-tabsz,0).rcurve(-tabsz,0,0,tabsz).rcurve(0,tabsz,tabsz,0).line(xpos + width,path.ypos());
};
/**
* Return a path representing a component node.
*/
graph.comppath = function(comp, cassoc) {
// Calculate the width and height of the component node
var width = graph.compwidth(comp, cassoc);
var height = graph.compheight(comp, cassoc);
/**
* Apply a path rendering function to a list of services or references.
*/
function renderpath(x, f, cassoc, path) {
if (isNil(x))
return path;
return renderpath(cdr(x), f, cassoc, f(car(x), cassoc, path));
}
var path = graph.mkpath().move(curvsz,0);
// Store the positions of services and references in the path
path.refpos = mklist();
path.svcpos = mklist();
// Render the services on the top side of the component
var tsvcs = graph.tsvcs(comp);
path = renderpath(tsvcs, graph.tsvcpath, cassoc, path);
// Render the references on the right side of the component
var rrefs = graph.rrefs(comp);
path = path.line(width - curvsz,path.ypos()).rcurve(curvsz,0,0,curvsz);
path = renderpath(rrefs, graph.rrefpath, cassoc, path);
// Render the references on the bottom side of the component
var brefs = reverse(graph.brefs(comp));
var boffset = curvsz + graph.brefswidth(brefs, cassoc);
path = path.line(path.xpos(),height - curvsz).rcurve(0,curvsz,curvsz * -1,0).line(boffset, path.ypos());
path = renderpath(brefs, graph.brefpath, cassoc, path);
// Render the services on the left side of the component
var lsvcs = graph.lsvcs(comp);
var loffset = curvsz + (length(lsvcs) * (tabsz * 10));
path = path.line(curvsz,path.ypos()).rcurve(curvsz * -1,0,0,curvsz * -1).line(path.xpos(), loffset);
path = renderpath(lsvcs, graph.lsvcpath, cassoc, path);
// Close the component node path
path = path.line(0,curvsz).rcurve(0,curvsz * -1,curvsz,0);
return path.end();
};
/**
* Return a path representing a button node.
*/
graph.buttonpath = function(t) {
var path = graph.mkpath().move(curvsz,0);
path = path.line(buttoncx - curvsz,path.ypos()).rcurve(curvsz,0,0,curvsz);
path = path.line(path.xpos(),buttoncy - curvsz).rcurve(0,curvsz,-curvsz,0).line(curvsz, path.ypos());
path = path.line(curvsz,path.ypos()).rcurve(-curvsz,0,0,-curvsz).line(path.xpos(), curvsz);
path = path.line(0,curvsz).rcurve(0,-curvsz,curvsz,0);
return path.end();
};
/**
* Render a SCDL composite into a list of component nodes.
*/
graph.composite = function(compos, pos) {
var name = scdl.name(scdl.composite(compos));
var comps = scdl.components(compos);
var cassoc = scdl.nameToElementAssoc(comps);
var proms = scdl.promotions(compos);
/**
* Render a component.
*/
function rendercomp(comp, cassoc, pos) {
/**
* Render the references on the right side of a component.
*/
function renderrrefs(refs, cassoc, pos, gcomp) {
/**
* Render a reference on the right side of a component.
*/
function renderrref(ref, cassoc, pos, gcomp) {
var target = assoc(scdl.target(ref), cassoc);
if (isNil(target))
return null;
// Render the component target of the reference
return rendercomp(cadr(target), cassoc, pos);
}
/**
* Move the rendering cursor down below a reference.
*/
function rendermove(ref, cassoc, pos) {
return pos.clone().rmove(0, graph.rrefheight(ref, cassoc));
}
if (isNil(refs))
return mklist();
// Return list of (ref, comp rendering) pairs
var grefcomp = renderrref(car(refs), cassoc, pos, gcomp);
return cons(mklist(car(refs), grefcomp), renderrrefs(cdr(refs), cassoc, rendermove(car(refs), cassoc, pos), gcomp));
}
/**
* Render the references on the bottom side of a component.
*/
function renderbrefs(refs, cassoc, pos, gcomp) {
/**
* Render a reference on the bottom side of a component.
*/
function renderbref(ref, cassoc, pos, gcomp) {
var target = assoc(scdl.target(ref), cassoc);
if (isNil(target))
return null;
// Render the component target of the reference
return rendercomp(cadr(target), cassoc, pos);
}
/**
* Move the rendering cursor to the right of a reference.
*/
function rendermove(ref, cassoc, pos) {
return pos.clone().rmove(graph.brefwidth(ref, cassoc), 0);
}
if (isNil(refs))
return mklist();
// Return list of (ref, comp rendering) pairs
var grefcomp = renderbref(car(refs), cassoc, pos, gcomp);
return cons(mklist(car(refs), grefcomp), renderbrefs(cdr(refs), cassoc, rendermove(car(refs), cassoc, pos), gcomp));
}
// Compute the component shape
var gcomp = graph.compnode(comp, cassoc, pos);
// Render the components wired to the component references
var rrefs = graph.rrefs(comp);
var rpos = graph.mkpath().rmove(graph.compwidth(comp, cassoc), 0);
var grrefs = renderrrefs(rrefs, cassoc, rpos, gcomp);
var brefs = graph.brefs(comp);
var bpos = graph.mkpath().rmove(0 , graph.compheight(comp, cassoc));
var gbrefs = renderbrefs(brefs, cassoc, bpos, gcomp);
// Store list of (ref, pos, component rendering) triplets in the component
function refposgcomp(refpos, grefs) {
if (isNil(refpos))
return mklist();
// Append component rendering to component
var gref = cadr(car(grefs));
if (gref != null)
appendNodes(mklist(gref), gcomp);
return cons(mklist(car(car(refpos)), cadr(car(refpos)), gref), refposgcomp(cdr(refpos), cdr(grefs)));
}
gcomp.refpos = refposgcomp(gcomp.refpos, append(grrefs, gbrefs));
return gcomp;
}
/**
* Render a list of promoted service components.
*/
function renderproms(svcs, cassoc, pos) {
/**
* Return the component promoted by a service.
*/
function promcomp(svc, cassoc) {
var c = assoc(scdl.promote(svc), cassoc);
if (isNil(c))
return mklist();
return cadr(c);
}
/**
* Return the position of a component.
*/
function comppos(comp, pos) {
var x = scdl.x(comp);
var y = scdl.y(comp);
return graph.mkpath().move(
x != null? Number(x) + palcx : pos.xpos(),
y != null? Number(y) : (isNil(graph.tsvcs(comp))? pos.ypos() : pos.ypos() + (tabsz * 4)));
}
/**
* Move the rendering cursor down below a component.
*/
function rendermove(comp, cassoc, pos) {
return pos.clone().rmove(0, graph.compclosureheight(comp, cassoc) + Math.max((tabsz * 2), 8));
}
if (isNil(svcs))
return mklist();
// Render the first promoted component in the list
// then recurse to render the rest of the list
var comp = promcomp(car(svcs), cassoc);
if (isNil(comp))
return renderproms(cdr(svcs), cassoc, rendermove(car(svcs), cassoc, pos));
var cpos = comppos(comp, pos);
return cons(rendercomp(comp, cassoc, cpos), renderproms(cdr(svcs), cassoc, rendermove(comp, cassoc, cpos)));
}
// Render the promoted service components
var rproms = renderproms(proms, cassoc, pos.clone().rmove(tabsz * 4, tabsz * 4));
if (name == 'palette') {
// Prefix ids of palette component elements with 'palette:'
return map(function(r) { r.id = 'palette:' + r.id; return r; }, rproms);
} else {
// Link app component elements to the containing composite
return map(function(r) { r.compos = compos; return r; }, rproms);
}
};
/**
* Return a component unique id.
*/
graph.ucid = function(prefix, compos) {
// Build an assoc list keyed by component name
var comps = map(function(c) { return mklist(scdl.name(c), c); }, namedElementChildren("'component", compos));
/**
* Find a free component id.
*/
function ucid(p, id) {
if (isNil(assoc(p + id, comps)))
return p + id;
return ucid(p, id + 1);
}
if (isNil(assoc(prefix, comps)))
return prefix;
return ucid(prefix == ''? 'comp' : prefix, 2);
};
/**
* Clone a palette component node.
*/
graph.clonepalette = function(e, compos) {
// Clone the SCDL component and give it a unique name
var comp = append(mklist(element, "'component", mklist(attribute, "'name", graph.ucid(scdl.name(e.comp), compos))),
filter(function(c) { return !(isAttribute(c) && attributeName(c) == "'name")}, elementChildren(e.comp)));
var x = '<composite xmlns:t="http://tuscany.apache.org/xmlns/sca/1.1">' + writeXML(mklist(comp), false) + '</composite>';
var compos = readXML(mklist(x));
comp = car(scdl.components(compos));
// Make a component node
var gcomp = graph.compnode(comp, mklist(), graph.mkpath());
graph.move(gcomp, graph.relpos(e));
e.parentNode.appendChild(gcomp);
return gcomp;
};
/**
* Move a SCDL component to the given position.
*/
graph.movecomp = function(comp, pos) {
if (isNil(pos))
return append(mklist(element, "'component"),
filter(function(e) { return !(isAttribute(e) && (attributeName(e) == "'t:x" || attributeName(e) == "'t:y")); }, elementChildren(comp)));
return append(mklist(element, "'component", mklist(attribute, "'t:x", '' + (pos.xpos() - palcx)), mklist(attribute, "'t:y", '' + pos.ypos())),
filter(function(e) { return !(isAttribute(e) && (attributeName(e) == "'t:x" || attributeName(e) == "'t:y")); }, elementChildren(comp)));
};
/**
* Sort elements of a composite.
*/
graph.sortcompos = function(compos) {
return append(mklist(element, "'composite"), elementChildren(compos).sort(function(a, b) {
// Sort attributes, place them at the top
var aa = isAttribute(a);
var ba = isAttribute(b);
if (aa && !ba) return -1;
if (!aa && ba) return 1;
if (aa && ba) {
var aan = attributeName(a);
var ban = attributeName(b);
if (aan < ban) return -1;
if (aan > ban) return 1;
return 0;
}
// Sort elements, place services before components
var aen = elementName(a);
var ben = elementName(b);
if (aen == "'service" && ben == "'component") return -1;
if (aen == "'component" && ben == "'service") return 1;
var an = scdl.name(a);
var bn = scdl.name(b);
if (an < bn) return -1;
if (an > bn) return 1;
return 0;
}));
}
/**
* Add a component to a SCDL composite.
*/
graph.addcomp = function(comp, compos) {
var name = scdl.name(comp);
var prom = mklist(element, "'service", mklist(attribute, "'name", name), mklist(attribute, "'promote", name));
return append(mklist(element, "'composite"), append(elementChildren(compos), mklist(prom, comp)));
};
/**
* Remove a component from a SCDL composite.
*/
graph.removecomp = function(comp, compos) {
var name = scdl.name(comp);
return append(mklist(element, "'composite"),
filter(function(c) { return !(isElement(c) && scdl.name(c) == name); }, elementChildren(compos)));
};
/**
* Garbage collect components not referenced or promoted.
*/
graph.gcollect = function(compos) {
// List the promoted components
var proms = map(function(s) { return mklist(scdl.promote(s), true); }, scdl.promotions(mklist(compos)));
// List the referenced components
var refs = reduce(function(a, comp) {
return append(a,
map(function(ref) { return mklist(scdl.target(ref), true); }, filter(function(ref) { return scdl.target(ref) != null; }, scdl.references(comp))));
}, mklist(), scdl.components(mklist(compos)));
// Filter out the unused components
var used = append(proms, refs);
return append(mklist(element, "'composite"),
filter(function(c) { return !(isElement(c) && elementName(c) == "'component" && isNil(assoc(scdl.name(c), used))); }, elementChildren(compos)));
}
/**
* Clone and cleanup clonable references.
*/
graph.clonerefs = function(compos) {
return append(mklist(element, "'composite"),
map(function(c) {
if (elementName(c) != "'component")
return c;
// If the references are clonable
var refs = scdl.references(c);
if (isNil(refs))
return c;
if (scdl.clonable(car(refs)) != 'true')
return c;
// Filter out the unwired references and add a fresh unwired
// reference at the end of the list
var cc = append(
filter(function(e) { return !(elementName(e) == "'reference" && scdl.target(e) == null); }, elementChildren(c)),
mklist(mklist(element, "'reference", mklist(attribute, "'name", scdl.name(car(refs))), mklist(attribute, "'t:clonable", "true"))));
return append(mklist(element, "'component"), cc);
}, elementChildren(compos)));
}
/**
* Rename a component.
*/
graph.renamecomp = function(comp, compos, name) {
/**
* Refactor references to a component.
*/
function refactorrefs(refs, oname, nname) {
if (isNil(refs))
return true;
var ref = car(refs);
if (scdl.target(ref) != oname)
return refactorrefs(cdr(refs), oname, nname);
// Change the reference's target attribute
setElement(ref, append(mklist(element, "'reference"),
append(filter(function(e) { return !(isAttribute(e) && attributeName(e) == "'target"); }, elementChildren(ref)),
mklist(mklist(attribute, "'target", nname)))));
return refactorrefs(cdr(refs), oname, nname);
}
// Refactor all the references to the renamed component
var oname = scdl.name(comp);
map(function(c) { return refactorrefs(scdl.references(c), oname, name); }, namedElementChildren("'component", compos));
// Rename the SCDL promoted service and component
var proms = filter(function(s) { return scdl.name(s) == oname }, scdl.services(compos));
if (!isNil(proms))
setElement(car(proms), mklist(element, "'service", mklist(attribute, "'name", name), mklist(attribute, "'promote", name)));
setElement(comp, append(mklist(element, "'component"),
cons(mklist(attribute, "'name", name),
filter(function(e) { return !(isAttribute(e) && attributeName(e) == "'name"); }, elementChildren(comp)))));
return append(mklist(element, "'composite"), elementChildren(compos));
};
/**
* Cut the wire to a component node and make that node a
* top level component node.
*/
graph.cutwire = function(node, compos, g) {
/**
* Find the reference wired to a node and cut its wire.
*/
function cutref(refs, node) {
if (isNil(refs))
return true;
var ref = car(refs);
if (caddr(ref) == node) {
setlist(ref, mklist(car(ref), cadr(ref), null));
setElement(car(ref),
append(mklist(element, "'reference"),
filter(function(e) { return !(isAttribute(e) && attributeName(e) == "'target"); }, elementChildren(car(ref)))));
}
return cutref(cdr(refs), node);
}
// Cut any reference wire, if found
cutref(node.parentNode.refpos, node);
// Make the component node a top level node.
node.compos = g.compos;
// Update the SCDL composite, add a promote element for
// that component
var comp = node.comp;
var name = scdl.name(comp);
var prom = mklist(element, "'service", mklist(attribute, "'name", name), mklist(attribute, "'promote", name));
return append(mklist(element, "'composite"),
append(mklist(prom), filter(function(c) { return !(isElement(c) && elementName(c) == "'service" && scdl.name(c) == name); }, elementChildren(compos))));
}
/**
* Wire a component to the closest neighbor reference.
*/
graph.wire = function(n, compos, g) {
// Compute position of the component's service node
var spos = cadr(car(n.svcpos));
var aspos = graph.abspos(n, g).rmove(spos.xpos(), spos.ypos());
/**
* Find closest unwired reference node among all the references
* of all the components.
*/
function closecomprefs(nodes, spos, cref) {
/**
* Find the closest unwired reference node among all the
* references of a node.
*/
function closerefs(npos, refs, spos, cref) {
if (isNil(refs))
return cref;
var fdist = cadddr(cref);
var ref = car(refs);
// Skip wired reference
if (!isNil(filter(function(n) { return isAttribute(n) && attributeName(n) == "'target"; }, car(ref))))
return closerefs(npos, cdr(refs), spos, cref);
// Compute distance between service node and reference node
var rpos = cadr(ref).clone().rmove(npos.xpos(), npos.ypos());
var dx = Math.pow(rpos.xpos() - spos.xpos(), 2);
var dy = Math.pow(rpos.ypos() - spos.ypos(), 2);
// Check for proximity threshold
var rdist = (dx < (proxcx * proxcx) && dy < (proxcy * proxcy))? Math.sqrt(dx + dy) : 25000000;
// Go through all the references in the component
return closerefs(npos, cdr(refs), spos, fdist < rdist? cref : mklist(car(ref), cadr(ref), caddr(ref), rdist));
}
if (isNil(nodes))
return cref;
// Skip non-component nodes
var node = car(nodes);
if (isNil(node.comp))
return closecomprefs(cdr(nodes), spos, cref);
// Compute the component absolute position
var npos = graph.abspos(node, g);
// Go through all the components and their references
return closecomprefs(append(nodeList(node.childNodes), cdr(nodes)), spos, closerefs(npos, node.refpos, spos, cref));
}
// Find closest reference node
var cref = closecomprefs(nodeList(g.childNodes), aspos, mklist(null, graph.mkpath(), null, 25000000));
if (car(cref) == null)
return compos;
if (cadddr(cref) == 25000000)
return compos;
// Wire component to that reference, un-promote it, and
// update the SCDL reference and composite
setElement(n.comp, graph.movecomp(graph.dragging.comp, null));
n.compos = null;
setElement(car(cref), append(mklist(element, "'reference", mklist(attribute, "'target", scdl.name(n.comp))), elementChildren(car(cref))));
var name = scdl.name(n.comp);
return append(mklist(element, "'composite"),
filter(function(c) { return !(isElement(c) && elementName(c) == "'service" && scdl.name(c) == name); }, elementChildren(compos)));
}
/**
* Display a list of graphical nodes.
*/
graph.display = function(nodes, g) {
// Append the nodes to the graphical canvas
appendNodes(nodes, g);
return nodes;
};
/**
* Hide a graph.
*/
graph.hide = function(g) {
// Remove nodes from the graph
map(function(n) { if (!isNil(n.comp) && n.id.substr(0, 8) != 'palette:') { g.removeChild(n); } return n; }, nodeList(g.childNodes));
return g;
};
/**
* Refresh a graph.
*/
graph.refresh = function(g) {
// Remove nodes and redisplay the composite associated with the graph
map(function(n) { if (!isNil(n.comp) && n.id.substr(0, 8) != 'palette:') { g.removeChild(n); } return n; }, nodeList(g.childNodes));
graph.display(graph.composite(g.compos, graph.mkpath().move(palcx,0)), g);
return g;
};
/**
* Display and enable editing of a composite and the graphical
* nodes that represent it.
*/
graph.edit = function(appname, compos, nodes, onchange, onselect, g) {
// Store the appname and composite in the graphical canvas
g.appname = appname;
g.compos = compos;
// Sort the composite elements now to allow for change detection later
var scompos = scdl.composite(g.compos);
setElement(scompos, graph.sortcompos(scompos));
// Store event listeners
g.oncomposchange = onchange;
g.oncompselect = onselect;
// Display the composite nodes
return graph.display(nodes, g);
};