diff options
Diffstat (limited to 'sca-cpp/trunk/hosting/server/htdocs/graph/index.html')
-rw-r--r-- | sca-cpp/trunk/hosting/server/htdocs/graph/index.html | 2178 |
1 files changed, 2178 insertions, 0 deletions
diff --git a/sca-cpp/trunk/hosting/server/htdocs/graph/index.html b/sca-cpp/trunk/hosting/server/htdocs/graph/index.html new file mode 100644 index 0000000000..6b750026b4 --- /dev/null +++ b/sca-cpp/trunk/hosting/server/htdocs/graph/index.html @@ -0,0 +1,2178 @@ +<!DOCTYPE html> +<!-- + * 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. +--> +<div id="bodydiv" class="bodydiv" style="overflow: visible;"> + +<table style="width: 100%;"> +<tr> +<td><h2><span id="appNameHeader"></span></h2></td> +<td style="vertical-align: middle; text-align: right; padding-right: 8px;"><span id="status" style="font-weight: bold; color: #808080;"></span></td> +</tr> +</table> + +<table id="compValueBackground" style="width: 2500px; position: absolute; top: 59px; left: 0px; z-index: -1;"> +<tr> +<th class="thr thl"><span style="display: inline-block; padding-top: 0px; padding-bottom: 0px; height: 24px;"></span></th> +</tr> +</table> + +<table id="compValueTable" style="width: 100%;"> +<tr> +<td class="thl thr" style="text-align: right; padding-right: 2px; vertical-align: top;"> +<span id="deleteCompButton" title="Delete a component" class="graybutton" style="font-weight: bold; font-size: 16px; color: #808080; display: inline-block; width: 24px; height: 20px; padding-top: 0px; padding-bottom: 0px; text-align: center; margin-left: 0px; margin-right: 0px;">-</span> + +<span id="copyCompButton" title="Copy a component" class="graybutton" style="font-weight: bold; font-size: 16px; color: #808080; display: inline-block; width: 24px; height: 20px; padding-top: 0px; padding-bottom: 0px; text-align: center; margin-left: 0px; margin-right: 0px;">c</span> + +<span id="addCompButton" title="Add a component" class="graybutton" style="font-weight: bold; font-size: 16px; display: inline-block; width: 24px; height: 20px; padding-top: 0px; padding-bottom: 0px; text-align: center; margin-left: 0px; margin-right: 0px;">+</span> + +<span id="playCompButton" title="View component value" class="graybutton" style="font-weight: bold; font-size: 16px; color: #808080; display: inline-block; width: 24px; height: 20px; padding-top: 0px; padding-bottom: 0px; text-align: center; margin-left: 0px; margin-right: 0px;">></span> +</td> + +<td class="thl thr" style="padding-left: 2px; padding-right: 2px; vertical-align: top; width: 100%"> +<input id="compValue" type="text" value="" title="Component value" autocapitalize="off" placeholder="Value" style="position: relative; visibility: hidden; width: 100%;"/> +</td> +</tr> +</table> + +<div id="contentdiv" style="margin-top: 4px; width: 2500px;"> +<div id="playdiv" style="position: relative; top: 0x; left: 0px; right: 0px; width: 2500px; height: 5000px; visibility: hidden"> +</div> +</div> + +<script type="text/javascript"> + +// Get the app name +var appname = ui.fragmentParams(location)['app']; +var ispalette = false; +if (isNil(appname)) { + appname = ui.fragmentParams(location)['palette']; + + // Edit a palette instead of a regular app + if (!isNil(appname)) + ispalette = true; +} + +// Set page titles +document.title = ui.windowtitle(location.hostname) + ' - ' + (isNil(config.compose)? 'Composition' : config.compose) + ' - ' + appname; +$('appNameHeader').innerHTML = '<a href=\"/' + appname + '/\" target=\"' + '_blank' + '\">' + appname + '</a>'; + +/** + * Component value field, add, delete and play buttons. + */ +var cvalue = $('compValue'); +var cadd = $('addCompButton'); +var cdelete = $('deleteCompButton'); +var ccopy = $('copyCompButton'); +var cplay = $('playCompButton'); + +// Position background divs +var cvbackground = $('compValueBackground'); +var cvtable = $('compValueTable'); +cvbackground.style.top = ui.pixpos(cvtable.offsetTop); + +/** + * Adjust component value field size. + */ +function resizeFields() { + cvalue.style.width = '0px'; + cvalue.style.width = ui.pixpos(cvalue.parentNode.clientWidth - 18); + return true; +} + +resizeFields(); +window.onresize = resizeFields; + +// Init componnent references +var editWidget = sca.component("EditWidget"); +var palettes = sca.reference(editWidget, "palettes"); +var composites = sca.reference(editWidget, ispalette? "palettes" : "composites"); + +// Setup remote log +//rconsole = sca.defun(sca.reference(editWidget, "log"), "log"); + +/** + * SVG 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.link = '#598edd'; + +graph.colors.orange1 = '#ffd666'; +graph.colors.green1 = '#bbe082'; +graph.colors.blue1 = '#66dbdf'; +graph.colors.yellow1 = '#fdf57a'; +graph.colors.cyan1 = '#e6eafb'; +graph.colors.lightgray1 = '#eaeaea' +graph.colors.pink1 = '#ffd9e0'; +graph.colors.red1 = '#d03f41'; +graph.colors.white1 = '#ffffff'; + +graph.colors.orange2 = '#ffbb00'; +graph.colors.green2 = '#96d333'; +//graph.colors.blue2 = '#0d7cc1'; +graph.colors.blue2 = '#00c3c9'; +graph.colors.red2 = '#d03f41'; +graph.colors.yellow2 = '#fcee21'; +graph.colors.magenta2 = '#c0688a'; +graph.colors.cyan2 = '#d5dcf9'; +graph.colors.lightgray2 = '#dcdcdc' +graph.colors.pink2 = '#ffc0cb'; +graph.colors.white2 = '#ffffff'; + +graph.colors.orange3 = '#ffc700'; +graph.colors.green3 = '#92e120'; +graph.colors.blue3 = '#008fd1'; +graph.colors.yellow3 = '#fdf400'; +graph.colors.cyan3 = '#b4d3fd'; +graph.colors.lightgray3 = '#e3e3e3' +graph.colors.pink3 = '#da749b'; +graph.colors.red3 = '#ed3f48'; +graph.colors.white3 = '#ffffff'; + +/** + * Default positions and sizes. + */ +graph.palcx = 2500; +graph.proxcx = 20; +graph.proxcy = 20; +graph.buttoncx = 55; +graph.buttoncy = 23; +graph.curvsz = 4; +graph.tabsz = 2; +graph.titlex = 4; +graph.titley = 11; +graph.titlew = ui.isMobile()? -2 : 0; + +/** + * SVG rendering functions. + */ + +graph.svgns='http://www.w3.org/2000/svg'; + +/** + * Make an SVG graph. + */ +graph.mkgraph = function(cdiv, pos, cvalue, cadd, ccopy, 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() + cdiv.offsetLeft); + div.style.top = ui.pixpos(pos.ypos() + cdiv.offsetTop); + cdiv.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.dragged = false; + graph.moverenderer = null; + graph.selected = null; + cvalue.disabled = true; + cvalue.style.visibility = 'hidden'; + ccopy.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 or touch start event. + */ + function onmousedown(e) { + + // Remember mouse or touch position + var pos = typeof e.touches != "undefined" ? e.touches[0] : e; + graph.downX = pos.screenX; + graph.downY = pos.screenY; + graph.moveX = pos.screenX; + graph.moveY = pos.screenY; + + // Engage the click component selection right away + // on mouse controlled devices + if (typeof e.touches == 'undefined') + onclick(e); + + // Find and remember draggable component + var dragging = draggable(e.target); + if (dragging == null || dragging != graph.selected) + return true; + graph.dragging = dragging; + graph.dragged = false; + + // Remember current drag position + graph.dragX = pos.screenX; + graph.dragY = pos.screenY; + + e.preventDefault(); + return true; + }; + + if (!ui.isMobile()) { + div.onmousedown = function(e) { + //log('onmousedown'); + var suspend = svg.suspendRedraw(10); + var r = onmousedown(e); + svg.unsuspendRedraw(suspend); + return r; + } + } else { + div.ontouchstart = function(e) { + //log('ontouchstart'); + + // Clear current move renderer if it's running + if (!isNil(graph.moverenderer)) { + clearInterval(graph.moverenderer); + graph.moverenderer = null; + } + + var suspend = svg.suspendRedraw(10); + var r = onmousedown(e); + svg.unsuspendRedraw(suspend); + return r; + } + } + + /** + * Handle a mouse up or touch end event. + */ + function onmouseup(e) { + + // Engage the click component selection now on touch devices + if (ui.isMobile()) { + if (!graph.dragged && graph.moveX == graph.downX && graph.moveY == graph.downY) + return onclick(e); + } + + // Stop here if the component was not dragged + if (graph.dragging == null) + return true; + if (!graph.dragged) { + graph.dragging = null; + return true; + } + + if (graph.dragging.parentNode == svg && graph.dragging.id.substring(0, 8) != 'palette:') { + + // Add new dragged component to the composite + if (isNil(graph.dragging.compos)) { + var compos = scdl.composite(svg.compos); + setElement(compos, graph.sortcompos(graph.addcomps(mklist(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)))); + } + + // Snap top level component position to grid + if (graph.dragging.parentNode == svg) { + var gpos = graph.relpos(graph.dragging); + setElement(graph.dragging.comp, graph.movecomp(graph.dragging.comp, graph.mkpath().pos(graph.gridsnap(gpos.xpos()), graph.gridsnap(gpos.ypos())))); + } + } + + // Forget current dragged component + graph.dragging = null; + graph.dragged = false; + + // Refresh the composite + //log('onmouseup refresh'); + var nodes = graph.refresh(svg); + + // Reselected the previously selected component + if (!isNil(graph.selected)) { + graph.selected = graph.findcompnode(scdl.name(graph.selected.comp), nodes); + graph.compselect(graph.selected, true, cvalue, ccopy, cdelete); + + // Trigger component select event + svg.oncompselect(graph.selected); + } + + // Trigger composite change event + svg.oncomposchange(false); + return true; + }; + + if (!ui.isMobile()) { + div.onmouseup = function(e) { + //log('onmouseup'); + var suspend = svg.suspendRedraw(10); + var r = onmouseup(e); + svg.unsuspendRedraw(suspend); + return r; + } + } else { + div.ontouchend = function(e) { + //log('ontouchend'); + + // Clear current move renderer if it's running + if (!isNil(graph.moverenderer)) { + clearInterval(graph.moverenderer); + graph.moverenderer = null; + } + + var suspend = svg.suspendRedraw(10); + var r = onmouseup(e); + svg.unsuspendRedraw(suspend); + return r; + } + } + + /** + * Handle a mouse or touch click event. + */ + function onclick(e) { + //log('onclick logic'); + + // Find selected component + var selected = draggable(e.target); + if (selected == null) { + if (graph.selected != null) { + + // Reset current selection + graph.compselect(graph.selected, false, cvalue, ccopy, cdelete); + graph.selected = null; + + // Trigger component select event + svg.oncompselect(null); + } + + // Dismiss the palette + if (e.target == div || e.target == svg && ui.numpos(div.style.left) != (graph.palcx * -1)) + div.style.left = ui.pixpos(graph.palcx * -1); + + return true; + } + + // Ignore multiple click events + if (selected == graph.selected) + return true; + if (selected.id.substring(0, 8) == 'palette:' && ui.numpos(div.style.left) != 0) + return true; + + // Deselect previously selected component + graph.compselect(graph.selected, false, cvalue, ccopy, cdelete); + + // Clone component from the palette + if (selected.id.substring(0, 8) == 'palette:') { + var compos = scdl.composite(svg.compos); + var comp = graph.clonepalette(selected, compos, svg); + setElement(compos, graph.sortcompos(graph.addcomps(mklist(comp), compos))); + + // Move into the editing area and hide the palette + div.style.left = ui.pixpos(graph.palcx * -1); + + // Refresh the composite + //log('onclick refresh'); + var nodes = graph.refresh(svg); + + // Reselect the previously selected component + graph.selected = graph.findcompnode(scdl.name(comp), nodes); + graph.compselect(graph.selected, true, cvalue, ccopy, cdelete); + + // Trigger component select event + svg.oncompselect(graph.selected); + + // Trigger composite change event + svg.oncomposchange(true); + + } else { + graph.selected = selected; + + // Select the component + graph.compselect(graph.selected, true, cvalue, ccopy, cdelete); + + // Trigger component select event + svg.oncompselect(graph.selected); + } + + //log('comp selected'); + + e.preventDefault(); + return true; + } + + if (!ui.isMobile()) { + div.onclick = function(e) { + //log('div onclick'); + var suspend = svg.suspendRedraw(10); + var r = onclick(e); + svg.unsuspendRedraw(suspend); + return r; + } + svg.onclick = function(e) { + //log('svg onclick'); + var suspend = svg.suspendRedraw(10); + var r = onclick(e); + svg.unsuspendRedraw(suspend); + return r; + } + } + + /** + * Handle a mouse or touch move event. + */ + function onmousemove(e) { + if (graph.dragging == null) + return true; + + // Ignore duplicate mouse move events + if (graph.moveX == graph.dragX && graph.moveY == graph.dragY) + return true; + + // Remember that the component was dragged + graph.dragged = true; + + // Cut wire to component + if (graph.dragging.parentNode != svg) { + var compos = scdl.composite(svg.compos); + setElement(compos, graph.sortcompos(graph.cutwire(graph.dragging, compos, svg))); + + // Bring component to the top + graph.bringtotop(graph.dragging, svg); + } + + // Calculate new position of dragged element + var gpos = graph.relpos(graph.dragging); + var newX = gpos.xpos() + (graph.moveX - graph.dragX); + var newY = gpos.ypos() + (graph.moveY - graph.dragY); + if (newX >= graph.palcx) + graph.dragX = graph.moveX + else + newX = graph.palcx; + if (newY >= 0) + graph.dragY = graph.moveY; + else + newY = 0; + + // Detach child elements to speedup rendering + graph.compoutline(graph.dragging, true); + + // Move the dragged element + graph.move(graph.dragging, graph.mkpath().pos(newX, newY)); + + return false; + }; + + if (!ui.isMobile()) { + window.onmousemove = function(e) { + //log('onmousemove'); + + // Remember mouse position + graph.moveX = e.screenX; + graph.moveY = e.screenY; + + var suspend = svg.suspendRedraw(10); + var r = onmousemove(e); + svg.unsuspendRedraw(suspend); + return r; + } + } else { + div.ontouchmove = function(e) { + //log('ontouchmove'); + + // Remember touch position + var pos = e.touches[0]; + if (graph.moveX == pos.screenX && graph.moveY == pos.screenY) + return true; + graph.moveX = pos.screenX; + graph.moveY = pos.screenY; + if (graph.moveX == graph.dragX && graph.moveY == graph.dragY) + return true; + + // Start async move renderer + if (graph.moverenderer == null) { + graph.moverenderer = setInterval(function() { + var suspend = svg.suspendRedraw(10); + onmousemove(e); + svg.unsuspendRedraw(suspend); + }, 10); + } + return true; + } + } + + /** + * Handle field on change events. + */ + function onvaluechange() { + if (graph.selected == null) + return false; + if (g.parentNode.style.visibility == 'hidden') + 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, false); + graph.selected.id = cvalue.value; + setElement(compos, graph.sortcompos(graph.renamecomp(graph.selected.comp, compos, cvalue.value))); + + // Refresh the composite + //log('onchangename refresh'); + var nodes = graph.refresh(svg); + + // Reselected the previously selected component + graph.selected = graph.findcompnode(scdl.name(graph.selected.comp), nodes); + graph.compselect(graph.selected, true, cvalue, ccopy, cdelete); + + // Trigger component select event + svg.oncompselect(graph.selected); + + // Trigger composite change event + svg.oncomposchange(true); + return false; + } + + // Change the component property value + function changeprop() { + graph.setproperty(graph.selected.comp, cvalue.value); + var hasprop = graph.hasproperty(graph.selected.comp); + cvalue.disabled = hasprop? false : true; + cvalue.style.visibility = hasprop? 'visible' : 'hidden'; + cvalue.value = graph.property(graph.selected.comp); + + // Refresh the composite + //log('onchangeprop refresh'); + var nodes = graph.refresh(svg); + + // Reselected the previously selected component + graph.selected = graph.findcompnode(scdl.name(graph.selected.comp), nodes); + graph.compselect(graph.selected, true, cvalue, ccopy, cdelete); + + // Trigger component select event + svg.oncompselect(graph.selected); + + // Trigger composite change event + svg.oncomposchange(true); + return false; + } + + return graph.hasproperty(graph.selected.comp)? changeprop() : changename(); + }; + + cvalue.onchange = function() { + var suspend = svg.suspendRedraw(10); + var r = onvaluechange(); + svg.unsuspendRedraw(suspend); + return r; + } + + // Handle delete event + function ondeleteclick() { + if (graph.selected == null) + return false; + if (graph.selected.id.substring(0, 8) != 'palette:') { + + // Remove selected component + var compos = scdl.composite(svg.compos); + if (isNil(graph.selected.compos)) + setElement(compos, graph.sortcompos(graph.cutwire(graph.selected, compos, svg))); + setElement(compos, graph.sortcompos(graph.clonerefs(graph.gcollect(graph.removecomp(graph.selected.comp, compos))))); + + // Reset current selection + graph.compselect(graph.selected, false, cvalue, ccopy, cdelete); + graph.selected = null; + + // Refresh the composite + //log('ondelete refresh'); + graph.refresh(svg); + + // Trigger component select event + svg.oncompselect(null); + + // Trigger composite change event + svg.oncomposchange(true); + } + return false; + }; + + cdelete.onclick = function() { + var suspend = svg.suspendRedraw(10); + var r = ondeleteclick(); + svg.unsuspendRedraw(suspend); + return r; + }; + + // Handle copy event + function oncopyclick() { + if (graph.selected == null) + return false; + if (graph.selected.id.substring(0, 8) == 'palette:') + return false; + + // Clone the selected component + var compos = scdl.composite(svg.compos); + var comps = graph.clonecomp(graph.selected, compos, svg); + setElement(compos, graph.sortcompos(graph.addcomps(comps, compos))); + + // Refresh the composite + //log('onclick refresh'); + var nodes = graph.refresh(svg); + + // Select the component clone + graph.selected = graph.findcompnode(scdl.name(car(comps)), nodes); + graph.compselect(graph.selected, true, cvalue, ccopy, cdelete); + + // Trigger component select event + svg.oncompselect(graph.selected); + + // Trigger composite change event + svg.oncomposchange(true); + + return false; + }; + + ccopy.onclick = function() { + var suspend = svg.suspendRedraw(10); + var r = oncopyclick(); + svg.unsuspendRedraw(suspend); + return r; + }; + + // 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.svgtitles = document.createElementNS(graph.svgns, 'svg'); + graph.svgtitles.style.visibility = 'hidden'; + graph.svgtitles.style.height = ui.pixpos(0); + graph.svgtitles.style.width = ui.pixpos(0); + div.appendChild(graph.svgtitles); + + return svg; +}; + +/** + * Point class. + */ +graph.Point = function(x, y) { + this.x = x; + this.y = y; +}; +graph.Point.prototype.xpos = function() { + return this.x; +}; +graph.Point.prototype.ypos = function() { + return this.y; +}; + +graph.mkpoint = function(x, y) { + return new graph.Point(x, y); +}; + +/** + * Path class. + */ +graph.Path = function() { + this.path = ''; + this.x = 0; + this.y = 0; +} +graph.Path.prototype.pos = function(x, y) { + this.x = x; + this.y = y; + return this; +}; +graph.Path.prototype.xpos = function() { + return this.x; +}; +graph.Path.prototype.ypos = function() { + return this.y; +}; +graph.Path.prototype.rmove = function(x, y) { + return this.move(this.x + x, this.y + y); +}; +graph.Path.prototype.rline = function(x, y) { + return this.line(this.x + x, this.y + y); +}; +graph.Path.prototype.rcurve = function(x1, y1, x, y) { + return this.curve(this.x + x1, this.y + y1, this.x + x1 + x, this.y + y1 + y); +}; +graph.Path.prototype.str = function() { + return this.path; +}; +graph.Path.prototype.clone = function() { + return graph.mkpath().pos(this.xpos(), this.ypos()); +}; +graph.Path.prototype.move = function(x, y) { + this.path += 'M' + x + ',' + y + ' '; + return this.pos(x, y); +}; +graph.Path.prototype.line = function(x, y) { + this.path += 'L' + x + ',' + y + ' '; + return this.pos(x, y); +}; +graph.Path.prototype.curve = function(x1, y1, x, y) { + this.path += 'Q' + x1 + ',' + y1 + ' ' + x + ',' + y + ' '; + return this.pos(x, y); +}; +graph.Path.prototype.end = function() { + this.path += 'Z'; + return this; +}; + +graph.mkpath = function() { + return new graph.Path(); +}; + +/** + * Return an element representing a title. + */ +graph.mktitle = function(t, x, y) { + var title = document.createElementNS(graph.svgns, 'text'); + title.setAttribute('x', x); + title.setAttribute('y', y); + title.setAttribute('class', 'svgtitle'); + title.setAttribute('pointer-events', 'none'); + title.appendChild(document.createTextNode(t)); + graph.svgtitles.appendChild(title); + return title; +}; + +/** + * Return an element representing the title of a component. + */ +graph.comptitle = function(comp) { + return memo(comp, 'title', function() { + var ct = graph.title(comp); + var pt = graph.propertytitle(comp); + if (ct == '' && pt == '') + return null; + return graph.mktitle((ct != '' && pt != '')? ct + ' ' + pt : ct + pt, graph.titlex, graph.titley); + }); +}; + +/** + * Return the width of the title of a component. + */ +graph.comptitlewidth = function(comp) { + var title = graph.comptitle(comp); + if (isNil(title)) + return 0; + return title.getBBox().width + graph.titlew; +}; + +/** + * Draw a component shape selection. + */ +graph.compselect = function(g, s, cvalue, ccopy, cdelete) { + if (isNil(g) || !s) { + cvalue.value = ''; + cvalue.disabled = true; + cvalue.style.visibility = 'hidden'; + ccopy.disabled = true; + cdelete.disabled = true; + if (isNil(g)) + return true; + g.shape.setAttribute('stroke', graph.colors.gray); + g.shape.setAttribute('stroke-width', '1'); + return true; + } + + cvalue.value = graph.hasproperty(g.comp)? graph.property(g.comp) : g.id; + cvalue.disabled = false; + cvalue.style.visibility = 'visible'; + ccopy.disabled = false; + cdelete.disabled = false; + + g.shape.setAttribute('stroke', graph.colors.link); + g.shape.setAttribute('stroke-width', '2'); + g.parentNode.appendChild(g); + return true; +}; + +/** + * Draw a palette shape selection. + */ +graph.paletteselect = function(g, s) { + if (isNil(g)) + return true; + if (!s) { + g.shape.setAttribute('stroke', graph.colors.gray); + g.shape.setAttribute('stroke-width', '1'); + return true; + } + + g.shape.setAttribute('stroke', graph.colors.link); + g.shape.setAttribute('stroke-width', '2'); + g.parentNode.appendChild(g); + return true; +}; + +/** + * Draw a component outline for faster rendering. + */ +graph.compoutline = function(g, s) { + if (s == (isNil(g.outlined)? false : g.outlined)) + return true; + g.outlined = s; + + if (s) { + g.shape.setAttribute('fill', 'none'); + if (!isNil(g.title)) + g.removeChild(g.title); + } else { + g.shape.setAttribute('fill', graph.color(g.comp)); + if (!isNil(g.title)) + g.appendChild(g.title); + } + + map(function(r) { + var n = caddr(r); + if (isNil(n)) + return r; + graph.compoutline(n, s); + return r; + }, g.refpos); + return true; +}; + +/** + * Return a node representing a component. + */ +graph.compnode = function(comp, cassoc, pos, parentg) { + + // Make the component title element + var title = graph.comptitle(comp); + + // Compute the path of the component shape + var path = graph.comppath(comp, cassoc); + + // Create the main component shape + var shape = document.createElementNS(graph.svgns, 'path'); + shape.setAttribute('d', path.str()); + shape.setAttribute('fill', graph.color(comp)); + //shape.setAttribute('fill-opacity', '0.6'); + shape.setAttribute('stroke', graph.colors.gray); + shape.setAttribute('stroke-width', '1'); + shape.setAttribute('pointer-events', 'visible'); + + // Create an svg group and add the shape and title to it + var g = document.createElementNS(graph.svgns, 'g'); + g.comp = comp; + g.id = scdl.name(comp); + g.setAttribute('transform', 'translate(' + pos.xpos() + ',' + pos.ypos() + ')'); + g.pos = pos.clone(); + g.appendChild(shape); + g.shape = shape; + if (!isNil(title)) { + g.appendChild(title); + g.title = title; + } + + // Store the the positions of the services and references + g.refpos = reverse(path.refpos); + g.svcpos = reverse(path.svcpos); + + // Handle onclick events + g.onclick = parentg.onclick; + + return g; +}; + +/** + * Find the node representing a component. + */ +graph.findcompnode = function(name, nodes) { + if (isNil(nodes)) + return null; + if (isNil(car(nodes).comp)) + return graph.findcompnode(name, cdr(nodes)); + if (name == scdl.name(car(nodes).comp)) + return car(nodes); + var node = graph.findcompnode(name, nodeList(car(nodes).childNodes)); + if (!isNil(node)) + return node; + return graph.findcompnode(name, cdr(nodes)); +} + +/** + * Return a graphical group. + */ +graph.mkgroup = function(pos) { + var g = document.createElementNS(graph.svgns, 'g'); + g.setAttribute('transform', 'translate(' + pos.xpos() + ',' + pos.ypos() + ')'); + g.pos = pos.clone(); + return g; +}; + +/** + * Return a node representing a button. + */ +graph.mkbutton = function(t, pos) { + + // Make the button title + var title = graph.mktitle(t, graph.titlex, graph.titley); + + // 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.lightgray1); + //shape.setAttribute('fill-opacity', '0.6'); + shape.setAttribute('stroke', graph.colors.gray); + shape.setAttribute('stroke-width', '1'); + shape.setAttribute('pointer-events', 'visible'); + + // Create a group and add the button shape to it + var g = document.createElementNS(graph.svgns, 'g'); + g.setAttribute('transform', 'translate(' + pos.xpos() + ',' + pos.ypos() + ')'); + g.pos = pos.clone(); + g.appendChild(shape); + g.appendChild(title); + + // Store the button shape in the group + g.shape = shape; + + return g; +}; + +/** + * Return the relative position of a node. + */ +graph.relpos = function(e) { + var pmatrix = e.parentNode != null? e.parentNode.getCTM() : null; + 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().pos(curX, curY); +}; + +/** + * Move a node. + */ +graph.move = function(e, pos) { + e.setAttribute('transform', 'translate(' + pos.xpos() + ',' + pos.ypos() + ')'); + e.pos = pos.clone(); +}; + +/** + * 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().pos(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 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; +}; + +/** + * 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 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(); + return mklist(car(l)); + }); +}; + +/** + * 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 graph.tabsz * 8; + 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 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)) * (graph.tabsz * 8) + (graph.tabsz * 4); + var rrefs = graph.rrefs(comp); + var rrefsh = graph.rrefsheight(rrefs, cassoc) + (graph.tabsz * 2); + return Math.max(lsvcsh, rrefsh); + }); +}; + +/** + * Return the height of a component and the components wired to its bottom side. + */ +graph.compclosureheight = function(comp, cassoc) { + return memo(comp, 'closureheight', function() { + return graph.compheight(comp, 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.rrefwidth(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 ctw = graph.comptitlewidth(comp); + var rrefsw = (isNil(graph.rrefs(comp))? 0 : (graph.tabsz * 4)); + var twidth = (graph.titlex * 2) + ctw + rrefsw; + var width = Math.max(twidth, (graph.tabsz * 8) + (graph.tabsz * 4)); + return width; + }); +}; + +/** + * Return a path representing a reference positioned to the right of a component. + */ +graph.rrefpath = function(ref, cassoc, path, maxheight) { + 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().pos(xpos, ypos + (graph.tabsz * 5))), path.refpos); + + // Compute the reference path + return path.rline(0,graph.tabsz * 2).rcurve(0,graph.tabsz,-graph.tabsz,0).rcurve(-graph.tabsz,0,0,-graph.tabsz/2.0).rcurve(0,-graph.tabsz/2.0,-graph.tabsz,0).rcurve(-graph.tabsz,0,0,graph.tabsz/2.0).rline(0,graph.tabsz * 3).rcurve(0,graph.tabsz/2.0,graph.tabsz,0).rcurve(graph.tabsz,0,0,-graph.tabsz/2.0).rcurve(0,-graph.tabsz/2.0,graph.tabsz,0).rcurve(graph.tabsz,0,0,graph.tabsz).line(path.xpos(), Math.min(ypos + height, maxheight)); +}; + +/** + * Return a path representing a service positioned to the left of a component. + */ +graph.lsvcpath = function(svc, cassoc, path, minheight) { + var height = graph.tabsz * 8; + + // Record service position in the path + var xpos = path.xpos(); + var ypos = path.ypos(); + path.svcpos = cons(mklist(svc, graph.mkpath().pos(xpos, ypos - (graph.tabsz * 6))), path.svcpos); + + // Compute the service path + return path.rline(0, -(graph.tabsz * 2)).rcurve(0,-graph.tabsz,-graph.tabsz,0).rcurve(-graph.tabsz,0,0,graph.tabsz/2.0).rcurve(0,graph.tabsz/2.0,-graph.tabsz,0).rcurve(-graph.tabsz,0,0,-graph.tabsz/2.0).rline(0,-(graph.tabsz * 3)).rcurve(0,-graph.tabsz/2.0,graph.tabsz,0).rcurve(graph.tabsz,0,0,graph.tabsz/2.0).rcurve(0,graph.tabsz/2.0,graph.tabsz,0).rcurve(graph.tabsz,0,0,-graph.tabsz).line(path.xpos(), Math.max(ypos - height, minheight)); +}; + +/** + * 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, height) { + if (isNil(x)) + return path; + return renderpath(cdr(x), f, cassoc, f(car(x), cassoc, path, height), height); + } + + var path = graph.mkpath().move(graph.curvsz,0); + + // Store the positions of services and references in the path + path.refpos = mklist(); + path.svcpos = mklist(); + + // Render the references on the right side of the component + var rrefs = graph.rrefs(comp); + path = path.line(width - graph.curvsz,path.ypos()).rcurve(graph.curvsz,0,0,graph.curvsz); + path = renderpath(rrefs, graph.rrefpath, cassoc, path, height - graph.curvsz); + + // Render the references on the bottom side of the component + var boffset = graph.curvsz; + path = path.line(path.xpos(),height - graph.curvsz).rcurve(0,graph.curvsz,graph.curvsz * -1,0).line(boffset, path.ypos()); + + // Render the services on the left side of the component + var lsvcs = graph.lsvcs(comp); + var loffset = graph.curvsz + (length(lsvcs) * (graph.tabsz * 8)); + path = path.line(graph.curvsz,path.ypos()).rcurve(graph.curvsz * -1,0,0,graph.curvsz * -1).line(path.xpos(), loffset); + path = renderpath(lsvcs, graph.lsvcpath, cassoc, path, graph.curvsz); + + // Close the component node path + path = path.line(0,graph.curvsz).rcurve(0,graph.curvsz * -1,graph.curvsz,0); + + return path.end(); +}; + +/** + * Return the position of a component. + */ +graph.comppos = function(comp, pos) { + var x = scdl.x(comp); + var y = scdl.y(comp); + return graph.mkpath().pos(x != null? Number(x) + graph.palcx : pos.xpos(), y != null? Number(y) : pos.ypos()); +}; + +/** + * Return a path representing a button node. + */ +graph.buttonpath = function(t) { + var path = graph.mkpath().move(graph.curvsz,0); + path = path.line(graph.buttoncx - graph.curvsz,path.ypos()).rcurve(graph.curvsz,0,0,graph.curvsz); + path = path.line(path.xpos(),graph.buttoncy - graph.curvsz).rcurve(0,graph.curvsz,-graph.curvsz,0).line(graph.curvsz, path.ypos()); + path = path.line(graph.curvsz,path.ypos()).rcurve(-graph.curvsz,0,0,-graph.curvsz).line(path.xpos(), graph.curvsz); + path = path.line(0,graph.curvsz).rcurve(0,-graph.curvsz,graph.curvsz,0); + return path.end(); +}; + +/** + * Render a SCDL composite into a list of component nodes. + */ +graph.composite = function(compos, pos, aspalette, g) { + var name = scdl.name(scdl.composite(compos)); + var comps = scdl.components(compos); + var cassoc = scdl.nameToElementAssoc(comps); + var proms = scdl.promotions(compos); + + // Unmemoize any memoized info about components and their references. + map(function(c) { + unmemo(c); + map(function(r) { + unmemo(r); + }, scdl.references(c)); + }, comps); + + /** + * 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)); + } + + // Compute the component shape + var gcomp = graph.compnode(comp, cassoc, pos, g); + + // 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); + + // 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) + gcomp.appendChild(gref); + return cons(mklist(car(car(refpos)), cadr(car(refpos)), gref), refposgcomp(cdr(refpos), cdr(grefs))); + } + + gcomp.refpos = refposgcomp(gcomp.refpos, grrefs); + + 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); + } + + /** + * Move the rendering cursor down below a component. + */ + function rendermove(comp, cassoc, pos) { + return pos.clone().rmove(0, graph.compclosureheight(comp, cassoc) + Math.max((graph.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 = graph.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(graph.tabsz * 4, graph.tabsz * 4)); + + if (aspalette) { + + // Prefix ids of palette component elements with 'palette:' and + // move them to the palette area + return map(function(r) { + r.id = 'palette:' + r.id; + var gpos = r.pos; + graph.move(r, graph.mkpath().pos(gpos.xpos() - graph.palcx, gpos.ypos())); + 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, compos1, compos2, clone) { + + // Build an assoc list keyed by component name + var comps = map(function(c) { return mklist(scdl.name(c), c); }, append(namedElementChildren("'component", compos1), namedElementChildren("'component", compos2))); + + if (!clone && isNil(assoc(prefix, comps))) + return prefix; + + /** + * Find a free component id. + */ + function ucid(p, id) { + if (isNil(assoc(p + id, comps))) + return p + id; + return ucid(p, id + 1); + } + + /** + * Remove trailing digits from a prefix. + */ + function untrail(p) { + if (p.length < 2 || p[p.length - 1] < '0' || p[p.length - 1] > '9') + return p; + return untrail(p.substring(0, p.length - 1)); + } + + return ucid(prefix == ''? 'comp' : (clone? untrail(prefix) : prefix), 1); +}; + +/** + * Clone a palette component node. + */ +graph.clonepalette = function(e, compos, g) { + + // Clone the SCDL component and give it a unique name + var wcomp = append(mklist(element, "'component", mklist(attribute, "'name", graph.ucid(scdl.name(e.comp), compos, compos, true))), + filter(function(c) { return !(isAttribute(c) && attributeName(c) == "'name")}, elementChildren(e.comp))); + var x = '<composite>' + writeXML(mklist(wcomp), false) + '</composite>'; + var rcompos = scdl.composite(readXML(mklist(x))); + var comp = car(scdl.components(mklist(rcompos))); + + // Update component position + setElement(comp, graph.movecomp(comp, graph.abspos(e, g).rmove(graph.palcx, 0))); + + return comp; +}; + +/** + * 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) == "'x" || attributeName(e) == "'y")); }, elementChildren(comp))); + return append(mklist(element, "'component", mklist(attribute, "'x", '' + (pos.xpos() - graph.palcx)), mklist(attribute, "'y", '' + pos.ypos())), + filter(function(e) { return !(isAttribute(e) && (attributeName(e) == "'x" || attributeName(e) == "'y")); }, elementChildren(comp))); +}; + +/** + * Align a pos along a 10pixel grid. + */ +graph.gridsnap = function(x) { + return Math.round(x / 10) * 10; +} + +/** + * Clone a component node and all the components it references. + */ +graph.clonecomp = function(e, compos, g) { + + // Write the component and the components it references to XML + function collectcomp(e) { + function collectrefs(refpos) { + if (isNil(refpos)) + return mklist(); + var r = car(refpos); + var n = caddr(r); + if (isNil(n)) + return collectrefs(cdr(refpos)); + return append(collectcomp(n), collectrefs(cdr(refpos))); + } + + return cons(e.comp, collectrefs(e.refpos)); + } + + var allcomps = collectcomp(e); + var ls = map(function(e) { return writeXML(mklist(e), false); }, allcomps); + var x = '<composite>' + writeStrings(ls) + '</composite>'; + + // Read them back from XML to clone them + var rcompos = scdl.composite(readXML(mklist(x))); + var comps = scdl.components(mklist(rcompos)); + + // Give them new unique names + map(function(e) { + + // Rename each component + var oname = scdl.name(e); + var name = graph.ucid(oname, compos, rcompos, true); + setElement(e, append(mklist(element, "'component", mklist(attribute, "'name", name)), + filter(function(c) { return !(isAttribute(c) && attributeName(c) == "'name")}, elementChildren(e)))); + + // Refactor references to the component + map(function(c) { return graph.refactorrefs(scdl.references(c), oname, name); }, comps); + }, comps); + + // Update the top component position + var comp = car(comps); + setElement(comp, graph.movecomp(comp, graph.abspos(e, g).rmove(10, 10))); + + return comps; +}; + +/** + * 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 list of components to a SCDL composite. The first + * component in the list is a promoted component. + */ +graph.addcomps = function(comps, compos) { + var comp = car(comps); + 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), cons(prom, comps))); +}; + +/** + * 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, "'clonable", "true")))); + return append(mklist(element, "'component"), cc); + + }, elementChildren(compos))); +} + +/** + * Refactor references to a component. + */ +graph.refactorrefs = function(refs, oname, nname) { + if (isNil(refs)) + return true; + var ref = car(refs); + if (scdl.target(ref) != oname) + return graph.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 graph.refactorrefs(cdr(refs), oname, nname); +}; + +/** + * Rename a component. + */ +graph.renamecomp = function(comp, compos, name) { + + // Refactor all the references to the renamed component + var oname = scdl.name(comp); + map(function(c) { return graph.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 < (graph.proxcx * graph.proxcx) && dy < (graph.proxcy * graph.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, svg) { + var suspend = svg.suspendRedraw(10); + + // Append the nodes to the graphical canvas + appendNodes(nodes, g); + + svg.unsuspendRedraw(suspend); + 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) { + //log('refresh'); + + // Remove existing 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)); + + // Redisplay the composite associated with the graph + var nodes = graph.composite(g.compos, graph.mkpath().pos(graph.palcx,0), false, g); + appendNodes(nodes, g); + return nodes; +}; + +/** + * Display and enable editing of a composite and the graphical + * nodes that represent it. + */ +graph.edit = function(appname, compos, nodes, onchange, onselect, g) { + var suspend = g.suspendRedraw(10); + + // 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; + + // Remove existing 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)); + + // Display the composite nodes + appendNodes(nodes, g); + + g.unsuspendRedraw(suspend); + return nodes; +}; + +/** + * Track the current app composite and corresponding saved XML content. + */ +var savedcomposxml = ''; +var composite; + +/** + * Track the composition graph, whether it's visible or not and the selected component. + */ +var g; +var gdiv; +var bg; +var gvisible = true; +var gcomp = null; +var cdiv = $('contentdiv'); +var pdiv = $('playdiv'); + +// Position play div inside the content div +pdiv.style.position = 'absolute'; +pdiv.style.top = cdiv.offsetTop + 'px'; + +/** + * Track the palettes. + */ +var gpalettes = new Array(); +var spalette = 'control'; +var bpalette = null; + +/** + * Return the composite in an ATOM entry. + */ +function atomcomposite(doc) { + var entry = atom.readATOMEntry(mklist(doc)); + if (isNil(entry)) + return mklist(); + var content = namedElementChild("'content", car(entry)); + if (content == null) + return mklist(); + return elementChildren(content); +} + +/** + * Get and display an app. + */ +function getapp(name, g) { + if (isNil(name)) + return false; + showStatus('Loading'); + + return composites.get(name, function(doc) { + + // Stop now if we didn't get a composite + if (doc == null) { + showStatus('No data'); + return false; + } + showStatus(defaultStatus()); + + composite = atomcomposite(doc); + if (isNil(composite)) { + + // Create a default empty composite if necessary + var x = '<composite xmlns="http://docs.oasis-open.org/ns/opencsa/sca/200912" ' + + 'targetNamespace="http://app" name="app"></composite>'; + composite = readXML(mklist(x)); + } + + // Display the composite + graph.edit(name, composite, graph.composite(composite, graph.mkpath().move(graph.palcx,0), false, g), oncomposchange, oncompselect, g); + + // Track the saved composite XML + savedcomposxml = car(writeXML(composite, false)); + return true; + }); +} + +/** + * Display a palette. Get it from the server if needed. + */ +function displaypalette(name, g, svg, palette, gpalettes) { + if (isNil(name)) + return; + if (isNil(gpalettes[name])) { + + // Get the palette from the server + palettes.get(name, function(doc) { + gpalettes[name] = graph.composite(atomcomposite(doc), graph.mkpath().move(2580,0), true, g); + graph.display(gpalettes[name], g, svg); + }); + return true; + } + graph.display(gpalettes[name], g, svg); + return true; +} + +/** + * Install a palette, including a button to select the palette, and + * the palette content. + */ +function installpalette(name, pos, g, bg, palette, gpalettes) { + var b = graph.mkbutton(name, pos); + graph.display(mklist(b), g, g); + b.onclick = function(e) { + + // Swap the selected palette + graph.paletteselect(bpalette, false); + displaypalette(spalette, bg, g, palette, gpalettes); + bpalette = b; + graph.paletteselect(b, true); + spalette = name; + return displaypalette(spalette, g, g, palette, gpalettes); + }; + + if (name != spalette) { + + // Will get the palette from the server later if needed + gpalettes[name] = null; + return true; + } + + // Display the selected palette + graph.paletteselect(b, true); + displaypalette(name, g, g, palette, gpalettes); + + return b; +} + +/** + * Save the current composite. + */ +function save(savexml) { + showStatus('Saving'); + savedcomposxml = savexml; + var entry = '<entry xmlns="http://www.w3.org/2005/Atom">' + + '<title type="text">' + appname + '</title><id>' + appname + '</id><content type="application/xml">' + + savedcomposxml + '</content></entry>'; + composites.put(appname, entry, function(e) { + if (e) { + showStatus('Local copy'); + return false; + } + showStatus('Saved'); + return false; + }); + return true; +} + +/** + * Handle a composite change event. + */ +function oncomposchange(prop) { + var newxml = car(writeXML(composite, false)); + if (savedcomposxml == newxml) + return false; + showStatus('Modified'); + + // Save property changes right away + if (prop) + return save(newxml); + + // Autosave other changes after 1 second + showStatus('Modified'); + setTimeout(function() { + if (savedcomposxml == newxml) { + showStatus('Saved'); + return false; + } + return save(newxml); + }, 1000); + return true; +} + +/** + * Return the link to a component. + */ +function complink(appname, cname) { + if (cname == '' || isNil(cname)) + return ''; + var protocol = location.protocol; + var host = location.hostname; + var port = ':' + location.port; + if (port == ':80' || port == ':443' || port == ':') + port = ''; + var link = protocol + '//' + host + port + '/' + appname + '/c/' + cname; + return link; +} + +/** + * Handle a component select event. + */ +function oncompselect(gsel) { + if (gsel == gcomp) + return true; + gcomp = gsel; + + function updateButton(b, v) { + b.style.color = v? '#000000' : '#808080'; + } + + updateButton(cdelete, !isNil(gsel)); + updateButton(ccopy, !isNil(gsel)); + updateButton(cplay, !isNil(gsel)); + return true; +} + +/** + * Show the result data of a component. + */ +function showdata(gcomp) { + if (!gvisible) + return true; + if (isNil(gcomp)) + return true; + cvalue.value = complink(appname, gcomp.id); + cplay.innerHTML = '<'; + gvisible = false; + pdiv.innerHTML = ''; + pdiv.style.visibility = 'visible'; + + // Get the component result data + var comp = sca.component(gcomp.id, appname); + comp.get('', function(doc) { + function displaydata(t, w) { + pdiv.style.width = w; + pdiv.innerHTML = t; + return true; + } + + // Stop now if we didn't get the doc + if (doc == null) + return displaydata('No content', '2500px'); + + // Format data table + if (json.isJSON(mklist(doc))) + return displaydata(ui.datatable(json.readJSON(mklist(doc))), '2500px'); + + if (atom.isATOMEntry(mklist(doc))) + return displaydata(ui.datatable(atom.readATOMEntry(mklist(doc))), '2500px'); + + if (atom.isATOMFeed(mklist(doc))) + return display(ui.datatable(atom.readATOMFeed(mklist(doc))), '2500px'); + + // Insert the doc as is in an iframe + var t = '<table class="datatable" style="width: 100%;">' + + '<tr><td class="datatdltop">' + 'value' + '</td>' + '<td class="datatdr">' + + '<iframe style="width: 100%; height: 5000px;" scrolling="no" frameborder="0" src="' + clink + '"/>' + + '</td></tr></table>' + return displaydata(t, '100%'); + }); + + setTimeout(function() { + gdiv.style.visibility = 'hidden' + }, 0); + return true; +} + +/** + * Show the composition graph. + */ +function showgraph(gcomp) { + if (gvisible) + return true; + cplay.innerHTML = '>'; + gdiv.style.visibility = 'visible' + gvisible = true; + graph.compselect(gcomp, true, cvalue, ccopy, cdelete); + setTimeout(function() { + pdiv.style.visibility = 'hidden'; + pdiv.innerHTML = ''; + }, 0); + return true; +} + +/** + * Handle play component button event. + */ +cplay.onclick = function() { + if (gcomp == null) + return false; + if (!gvisible) + return showgraph(gcomp); + return showdata(gcomp); +} + +// Create editor graph area +g = graph.mkgraph(cdiv, graph.mkpath().move(-2500,0), cvalue, cadd, ccopy, cdelete); +gdiv = g.parentNode; +bg = graph.mkgroup(graph.mkpath()); + +// Install the palettes +var pos = graph.mkpath().move(0, 0); +bpalette = installpalette('control', pos.rmove(5,2), g, bg, spalette, gpalettes); +installpalette('values', pos.rmove(0,28), g, bg, spalette, gpalettes); +installpalette('lists', pos.rmove(0, 28), g, bg, spalette, gpalettes); +installpalette('transform', pos.rmove(0, 28), g, bg, spalette, gpalettes); +installpalette('text', pos.rmove(0, 28), g, bg, spalette, gpalettes); +installpalette('http', pos.rmove(0, 28), g, bg, spalette, gpalettes); +installpalette('animation', pos.rmove(0, 28), g, bg, spalette, gpalettes); +installpalette('talk', pos.rmove(0, 28), g, bg, spalette, gpalettes); +installpalette('social', pos.rmove(0, 28), g, bg, spalette, gpalettes); +installpalette('search', pos.rmove(0, 28), g, bg, spalette, gpalettes); +installpalette('database', pos.rmove(0, 28), g, bg, spalette, gpalettes); +installpalette('logic', pos.rmove(0, 28), g, bg, spalette, gpalettes); +installpalette('math', pos.rmove(0, 28), g, bg, spalette, gpalettes); +installpalette('python', pos.rmove(0, 28), g, bg, spalette, gpalettes); + +// Get and display the current app +getapp(appname, g); + +</script> + +</div> |