From 2201110b9afd000380f91ea41a818114312e04ce Mon Sep 17 00:00:00 2001 From: ellson Date: Thu, 6 Jan 2005 15:01:42 +0000 Subject: [PATCH] add lefty, dotty, lneato to graphviz2 tree --- cmd/dotty/dotty.bsh | 104 +++++ cmd/dotty/dotty.ksh | 109 +++++ cmd/dotty/dotty.lefty | 772 +++++++++++++++++++++++++++++++++++ cmd/dotty/dotty.sh | 104 +++++ cmd/dotty/dotty_draw.lefty | 763 ++++++++++++++++++++++++++++++++++ cmd/dotty/dotty_edit.lefty | 583 ++++++++++++++++++++++++++ cmd/dotty/dotty_layout.lefty | 272 ++++++++++++ cmd/dotty/dotty_ui.lefty | 434 ++++++++++++++++++++ 8 files changed, 3141 insertions(+) create mode 100755 cmd/dotty/dotty.bsh create mode 100755 cmd/dotty/dotty.ksh create mode 100644 cmd/dotty/dotty.lefty create mode 100755 cmd/dotty/dotty.sh create mode 100644 cmd/dotty/dotty_draw.lefty create mode 100644 cmd/dotty/dotty_edit.lefty create mode 100644 cmd/dotty/dotty_layout.lefty create mode 100644 cmd/dotty/dotty_ui.lefty diff --git a/cmd/dotty/dotty.bsh b/cmd/dotty/dotty.bsh new file mode 100755 index 000000000..e4a256eb0 --- /dev/null +++ b/cmd/dotty/dotty.bsh @@ -0,0 +1,104 @@ +#!/bin/sh + +FILES="" +MLEVEL="0" +LMODE="async" + +usage='echo "usage: dotty [-V] [-lm (sync|async)] [-el (0|1)] "' + +if [ "x$DOTTYOPTIONS" != "x" ]; then + set -- $DOTTYOPTIONS $* +fi + +while [ "x$1" != 'x' ]; do + case $1 in + -V) + echo "dotty version 96c (09-24-96)" + shift + ;; + -f) + shift + loadfile=$1 + shift + ;; + -lm) + shift + LMODE=$1 + if [ "x$LMODE" != 'xsync' -a "x$LMODE" != 'xasync' ]; then + $usage + exit 1 + fi + shift + ;; + -el) + shift + MLEVEL=$1 + if [ "x$MLEVEL" != 'x0' -a "x$MLEVEL" != 'x1' ]; then + $usage + exit 1 + fi + shift + ;; + -) + FILES=`echo $FILES \"$1\"` + shift + ;; + -*) + $usage + exit 1 + ;; + *) + FILES=`echo $FILES \"$1\"` + shift + ;; + esac +done + +if [ "x$DOTTYPATH" != 'x' ]; then + LEFTYPATH="$DOTTYPATH:$LEFTYPATH" +fi + +CMDS="" + +CMDS="dotty.protogt.layoutmode = '$LMODE';" + +CMDS=`echo $CMDS dotty.mlevel = $MLEVEL";"` + +if [ "x$loadfile" != 'x' ]; then + CMDS=`echo $CMDS load \("'"$loadfile"'"\)";"` +fi + +if [ "x$FILES" = 'x' ]; then + FILES=null +fi +FUNC="dotty.createviewandgraph" +for i in $FILES; do + CMDS=`echo $CMDS $FUNC \($i, "'"file"'", null, null\)";"` +done + +leftypath=`which lefty` +if [ ! -f "$leftypath" ]; then + echo "dotty: cannot locate the lefty program" + echo " make sure that your path includes" + echo " the directory containing dotty and lefty" + exit 1 +fi + +exec $leftypath -e " +load ('dotty.lefty'); +checkpath = function () { + if (tablesize (dotty) > 0) + remove ('checkpath'); + else { + echo ('dotty: cannot locate the dotty scripts'); + echo (' make sure that the environment variable LEFTYPATH'); + echo (' is set to the directory containing dotty.lefty'); + exit (); + } +}; +checkpath (); +dotty.init (); +monitorfile = dotty.monitorfile; +$CMDS +txtview ('off'); +" diff --git a/cmd/dotty/dotty.ksh b/cmd/dotty/dotty.ksh new file mode 100755 index 000000000..7ad10ddf3 --- /dev/null +++ b/cmd/dotty/dotty.ksh @@ -0,0 +1,109 @@ +#!/bin/ksh + +FILES="" +MLEVEL="0" +LMODE="async" + +function usage { + print "usage: dotty [-V] [-lm (sync|async)] [-el (0|1)] " +} + +function processoptions { + while [[ $# > 0 ]] do + case $1 in + -V) + print "dotty version 96c (09-24-96)" + shift + ;; + -f) + shift + loadfile=$1 + shift + ;; + -lm) + shift + LMODE=$1 + if [[ $LMODE != 'sync' && $LMODE != 'async' ]] then + usage + exit 1 + fi + shift + ;; + -el) + shift + MLEVEL=$1 + if [[ $MLEVEL != '0' && $MLEVEL != '1' ]] then + usage + exit 1 + fi + shift + ;; + -) + FILES=$(print $FILES "'"$1"'") + shift + ;; + -*) + usage + exit 1 + ;; + *) + FILES=$(print $FILES "'"$1"'") + shift + ;; + esac + done +} + +if [[ $DOTTYOPTIONS != '' ]] then + processoptions $DOTTYOPTIONS +fi +processoptions "$@" + +if [[ $DOTTYPATH != '' ]] then + export LEFTYPATH="$DOTTYPATH:$LEFTYPATH" +fi + +CMDS="" + +CMDS="dotty.protogt.layoutmode = '$LMODE';" + +CMDS=$(print $CMDS dotty.mlevel = $MLEVEL";") + +if [[ $loadfile != '' ]] then + CMDS=$(print $CMDS load \("'"$loadfile"'"\)";") +fi + +if [[ $FILES = '' ]] then + FILES=null +fi +FUNC="dotty.createviewandgraph" +for i in $FILES; do + CMDS=$(print $CMDS $FUNC \($i, "'"file"'", null, null\)";") +done + +leftypath=$(whence -p lefty) +if [[ $leftypath == '' ]] then + print -u2 "dotty: cannot locate the lefty program" + print -u2 " make sure that your path includes" + print -u2 " the directory containing dotty and lefty" + exit 1 +fi + +$leftypath -e " +load ('dotty.lefty'); +checkpath = function () { + if (tablesize (dotty) > 0) + remove ('checkpath'); + else { + echo ('dotty: cannot locate the dotty scripts'); + echo (' make sure that the environment variable LEFTYPATH'); + echo (' is set to the directory containing dotty.lefty'); + exit (); + } +}; +checkpath (); +dotty.init (); +monitorfile = dotty.monitorfile; +$CMDS +txtview ('off'); +" diff --git a/cmd/dotty/dotty.lefty b/cmd/dotty/dotty.lefty new file mode 100644 index 000000000..3d7b65aa0 --- /dev/null +++ b/cmd/dotty/dotty.lefty @@ -0,0 +1,772 @@ +# +# DOTTY +# +dotty = [ + 'keys' = [ + 'nid' = 'nid'; + 'eid' = 'eid'; + 'gid' = 'gid'; + 'name' = 'name'; + 'attr' = 'attr'; + 'gattr' = 'graphattr'; + 'eattr' = 'edgeattr'; + 'nattr' = 'nodeattr'; + 'edges' = 'edges'; + 'tail' = 'tail'; + 'tport' = 'tport'; + 'head' = 'head'; + 'hport' = 'hport'; + 'pos' = 'pos'; + 'size' = 'size'; + 'points' = 'points'; + 'fname' = 'fontname'; + 'fsize' = 'fontsize'; + 'fcolor' = 'fontcolor'; + 'color' = 'color'; + ]; + 'maps' = [ + 'X11' = [ + 'fontmap' = [ + 'Times-Roman' = + '-*-times-medium-r-*--%d-*-*-*-*-*-*-1'; + 'Times-Italic' = + '-*-times-medium-i-*--%d-*-*-*-*-*-*-1'; + 'Times-Bold' = + '-*-times-bold-r-*--%d-*-*-*-*-*-*-1'; + 'Courier' = + '-*-courier-bold-r-*--%d-*-*-*-*-*-*-1'; + 'Courier-Bold' = + '-*-courier-bold-r-*--%d-*-*-*-*-*-*-1'; + 'Helvetica' = + '-*-helvetica-medium-r-normal--%d-*-*-*-p-*-iso8859-1'; + 'Helvetica-Bold' = + '-*-helvetica-bold-r-normal--%d-*-*-*-p-*-iso8859-1'; + ]; + 'psfontmap' = [ + 'Times-Roman' = 'Times-Roman'; + 'Times-Italic' = 'Times-Italic'; + 'Times-Bold' = 'Times-Bold'; + 'Courier' = 'Courier'; + 'Courier-Bold' = 'Courier-Bold'; + 'Helvetica' = 'Helvetica'; + 'Helvetica-Bold' = 'Helvetica-Bold'; + ]; + ]; + 'mswin' = [ + 'fontmap' = [ + 'Times-Roman' = 'Times New Roman'; + 'Times-Italic' = 'Times New Roman Italic'; + 'Times-Bold' = 'Times New Roman Bold'; + 'Courier' = 'Courier New'; + 'Courier-Bold' = 'Courier New Bold'; + 'Helvetica' = 'Arial'; + 'Helvetica-Bold' = 'Arial Bold'; + ]; + 'psfontmap' = [ + 'Times-Roman' = 'Times New Roman'; + 'Times-Italic' = 'Times New Roman Italic'; + 'Times-Bold' = 'Times New Roman Bold'; + 'Courier' = 'Courier New'; + 'Courier-Bold' = 'Courier New Bold'; + 'Helvetica' = 'Arial'; + 'Helvetica-Bold' = 'Arial Bold'; + ]; + ]; + ]; + 'protogt' = [ + 'graph' = [ + 'graphattr' = [ + 'fontsize' = '14'; + 'fontname' = 'Times-Roman'; + 'fontcolor' = 'black'; + 'color' = 'black'; + ]; + 'nodeattr' = [ + 'shape' = 'ellipse'; + 'fontsize' = '14'; + 'fontname' = 'Times-Roman'; + 'fontcolor' = 'black'; + 'color' = 'black'; + ]; + 'edgeattr' = [ + 'fontsize' = '14'; + 'fontname' = 'Times-Roman'; + 'fontcolor' = 'black'; + 'color' = 'black'; + ]; + 'graphdict' = []; + 'nodedict' = []; + 'graphs' = []; + 'nodes' = []; + 'edges' = []; + 'maxgid' = 0; + 'maxnid' = 0; + 'maxeid' = 0; + 'type' = 'digraph'; + ]; + 'layoutmode' = 'sync'; + 'lserver' = 'dot'; + 'edgehandles' = 1; + 'noundo' = 0; + ]; + 'lservers' = []; + 'mlevel' = 0; + 'graphs' = []; + 'views' = []; + 'protovt' = [ + 'normal' = [ + 'name' = 'DOTTY'; + 'orig' = ['x' = 1; 'y' = 1;]; + 'size' = ['x' = 420; 'y' = 520;]; + 'wrect' = [ + 0 = ['x' = 0; 'y' = 0;]; + 1 = ['x' = 400; 'y' = 500;]; + ]; + 'vsize' = ['x' = 400; 'y' = 500;]; + 'w2v' = 1; + ]; + 'birdseye' = [ + 'type' = 'birdseye'; + 'name' = 'DOTTY birdseye view'; + 'orig' = ['x' = 1; 'y' = 1;]; + 'size' = ['x' = 220; 'y' = 260;]; + 'wrect' = [ + 0 = ['x' = 0; 'y' = 0;]; + 1 = ['x' = 200; 'y' = 250;]; + ]; + 'vsize' = ['x' = 200; 'y' = 250;]; + 'w2v' = 1; + ]; + ]; + 'pagesizes' = [ + '8.5x11' = ['x' = 8; 'y' = 10.5;]; + '11x17' = ['x' = 10.5; 'y' = 16.5;]; + '36x50' = ['x' = 35.5; 'y' = 49.5;]; + ]; +]; +load ('dotty_draw.lefty'); +load ('dotty_edit.lefty'); +load ('dotty_layout.lefty'); +load ('dotty_ui.lefty'); +# +# initialization functions +# +dotty.init = function () { + dotty.outlinecolor = 1; + dotty.fontmap = dotty.maps[getenv ('LEFTYWINSYS')].fontmap; + dotty.clipgt = dotty.protogt.creategraph (['noundo' = 1;]); + dotty.inited = 1; +}; +dotty.simple = function (file) { + if (dotty.inited ~= 1) + dotty.init (); + dotty.createviewandgraph (file, 'file', null, null); + txtview ('off'); +}; +# +# main operations +# +dotty.protogt.creategraph = function (protogt) { + local gt, id, gtid; + + if (~protogt) + protogt = dotty.protogt; + for (gtid = 0; dotty.graphs[gtid]; gtid = gtid + 1) + ; + gt = (dotty.graphs[gtid] = []); + if (protogt.mode ~= 'replace') { + for (id in dotty.protogt) + gt[id] = copy (dotty.protogt[id]); + } + for (id in protogt) + gt[id] = copy (protogt[id]); + gt.gtid = gtid; + gt.views = []; + gt.undoarray = ['level' = 0; 'entries' = [];]; + gt.busy = 0; + return gt; +}; +dotty.protogt.copygraph = function (ogt) { + local gt, gtid, id; + + for (gtid = 0; dotty.graphs[gtid]; gtid = gtid + 1) + ; + gt = (dotty.graphs[gtid] = []); + for (id in ogt) + gt[id] = copy (ogt[id]); + gt.gtid = gtid; + gt.views = []; + gt.undoarray = ['level' = 0; 'entries' = [];]; + gt.busy = 0; + return gt; +}; +dotty.protogt.destroygraph = function (gt) { + local vid, vlist; + + if (gt.layoutpending > 0) + gt.cancellayout (gt); + for (vid in gt.views) + vlist[vid] = gt.views[vid]; + for (vid in gt.views) + gt.destroyview (gt, vlist[vid]); + remove (gt.gtid, dotty.graphs); +}; +dotty.protogt.loadgraph = function (gt, name, type, protograph, layoutflag) { + local vid, vt, fd, graph, nid, eid, gid; + + if (gt.layoutpending > 0) + gt.cancellayout (gt); + if (~name) + if (~(name = ask ('file name:', 'file', ''))) + return; + dotty.pushbusy (gt, gt.views); + dotty.message (1, 'loading'); + if (~protograph) + protograph = dotty.protogt.graph; + if (~((fd = dotty.openio (name, type, 'r')) >= 0) | + ~(graph = readgraph (fd, protograph))) { + dotty.message (0, 'cannot load graph'); + dotty.popbusy (gt, gt.views); + return; + } + for (vid in gt.views) { + vt = gt.views[vid]; + vt.colors = []; + vt.colorn = 2; + } + gt.graph = graph; + gt.name = name; + gt.type = type; + gt.undoarray = ['level' = 0; 'entries' = [];]; + if (~(type == 'file' & name == '-')) + closeio (fd); + graph.maxgid = tablesize (graph.graphs); + graph.maxnid = tablesize (graph.nodes); + graph.maxeid = tablesize (graph.edges); + for (nid in graph.nodes) + graph.nodes[nid][dotty.keys.nid] = nid; + for (eid in graph.edges) + graph.edges[eid][dotty.keys.eid] = eid; + for (gid in graph.graphs) + graph.graphs[gid][dotty.keys.gid] = gid; + gt.unpackattr (gt); + if (layoutflag) { + dotty.message (1, 'generating layout'); + gt.layoutgraph (gt); + } + dotty.popbusy (gt, gt.views); + return gt.graph; +}; +dotty.protogt.savegraph = function (gt, name, type, savecoord) { + local fd, graph, gid, sgraph, nid, node, eid, edge, pointi, attr; + + if (~name) + if (~(name = ask ('file name:', 'file', ''))) + return; + if (savecoord) { + graph = copy (gt.graph); + for (gid in graph.graphs) { + sgraph = graph.graphs[gid]; + if (sgraph.rect) + sgraph.graphattr.bb = concat (sgraph.rect[0].x, ',', + sgraph.rect[0].y, ',', sgraph.rect[1].x, ',', + sgraph.rect[1].y); + if (sgraph.lp & tablesize (sgraph.lp) > 0) + sgraph.graphattr.lp = concat (sgraph.lp.x, ',', sgraph.lp.y); + else if (sgraph.lp) + sgraph.graphattr.lp = ""; + } + for (nid in graph.nodes) { + node = graph.nodes[nid]; + if (~node.attr.label) + node.attr.label = '\N'; + if (node.pos) + node.attr.pos = concat (node.pos.x, ',', node.pos.y); + if (node.size) { + node.attr.width = node.size.x / 72; + node.attr.height = node.size.y / 72; + } + if (node.fields) + node.attr.rects = gt.packrecordfields (gt, node.fields); + } + for (eid in graph.edges) { + edge = graph.edges[eid]; + if (edge.points) { + attr = ''; + if (edge.sp) + attr = concat ('s,', edge.sp.x, ',', edge.sp.y, ' '); + if (edge.ep) + attr = concat (attr, 'e,', edge.ep.x, ',', edge.ep.y, ' '); + for (pointi = 0; edge.points[pointi]; pointi = pointi + 1) + attr = concat (attr, ' ', edge.points[pointi].x, ',', + edge.points[pointi].y); + edge.attr.pos = attr; + } + if (edge.lp) + edge.attr.lp = concat (edge.lp.x, ',', edge.lp.y); + } + if (~graph.rect) { + graph.rect = [ + 0 = ['x' = 0; 'y' = 0; ]; 1 = ['x' = 1; 'y' = 1; ]; + ]; + for (nid in graph.nodes) { + node = graph.nodes[nid]; + if (graph.rect[1].x < node.pos.x + node.size.x / 2) + graph.rect[1].x = node.pos.x + node.size.x / 2; + if (graph.rect[1].y < node.pos.y + node.size.y / 2) + graph.rect[1].y = node.pos.y + node.size.y / 2; + } + } + graph.graphattr.bb = concat (graph.rect[0].x, ',', + graph.rect[0].y, ',', graph.rect[1].x, ',', + graph.rect[1].y); + if (graph.lp & tablesize (graph.lp) > 0) + graph.graphattr.lp = concat (graph.lp.x, ',', graph.lp.y); + } else + graph = gt.graph; + if (~((fd = dotty.openio (name, type, 'w')) >= 0) | + ~writegraph (fd, graph, 0)) { + dotty.message (0, 'cannot save graph'); + return; + } + if (~(type == 'file' & name == '-')) + closeio (fd); +}; +dotty.protogt.packrecordfields = function (gt, fields) { + local attrs, attr, fid, field; + + for (fid = 0; fields[fid]; fid = fid + 1) { + field = fields[fid]; + if (field.fields) + attr = gt.packrecordfields (gt, field.fields); + else + attr = concat (field.rect[0].x, ',', field.rect[0].y, + ',', field.rect[1].x, ',', field.rect[1].y); + if (attrs) + attrs = concat (attrs, ' ', attr); + else + attrs = attr; + } + return attrs; +}; +dotty.protogt.setgraph = function (gt, graph) { + local vid, vt, nid, eid, gid; + + if (gt.layoutpending > 0) + gt.cancellayout (gt); + for (vid in gt.views) { + vt = gt.views[vid]; + vt.colors = []; + vt.colorn = 2; + } + gt.graph = copy (graph); + gt.undoarray = ['level' = 0; 'entries' = [];]; + gt.unpackattr (gt); + gt.graph.maxgid = tablesize (graph.graphs); + gt.graph.maxnid = tablesize (graph.nodes); + gt.graph.maxeid = tablesize (graph.edges); + for (nid in gt.graph.nodes) + gt.graph.nodes[nid][dotty.keys.nid] = nid; + for (eid in gt.graph.edges) + gt.graph.edges[eid][dotty.keys.eid] = eid; + for (gid in gt.graph.graphs) + gt.graph.graphs[gid][dotty.keys.gid] = gid; + gt.unpackattr (gt); + dotty.message (1, 'generating layout'); + gt.layoutgraph (gt); + return gt.graph; +}; +dotty.protogt.erasegraph = function (gt, protogt, protovt) { + local vid, vt; + + if (gt.layoutpending > 0) + gt.cancellayout (gt); + for (vid in gt.views) { + vt = gt.views[vid]; + vt.colors = []; + vt.colorn = 2; + clear (vt.canvas); + } + if (~protogt) + protogt = dotty.protogt; + gt.graph = copy (protogt.graph); + gt.undoarray = ['level' = 0; 'entries' = [];]; +}; +dotty.protogt.layoutgraph = function (gt) { + if (gt.graph.graphattr.bb) { + gt.unpacklayout (gt, gt.graph); + gt.setviewsize (gt.views, gt.graph.rect); + gt.redrawgraph (gt, gt.views); + return; + } + if (gt.layoutmode == 'async') { + if (~gt.haveinput) { + gt.startlayout (gt); + return; + } + if (~gt.finishlayout (gt)) + return; + gt.setviewsize (gt.views, gt.graph.rect); + gt.redrawgraph (gt, gt.views); + } else { + if (~gt.startlayout (gt)) + return; + else + while (~gt.finishlayout (gt)) + ; + gt.setviewsize (gt.views, gt.graph.rect); + gt.redrawgraph (gt, gt.views); + } +}; +dotty.protogt.createview = function (gt, protovt) { + local vt, ovt, id, t; + + vt = []; + vt.colors = []; + vt.colorn = 2; + if (~protovt) + protovt = dotty.protovt.normal; + if (protovt.mode ~= 'replace') { + for (id in dotty.protovt[protovt.type]) + vt[id] = copy (dotty.protovt[protovt.type][id]); + } + for (id in protovt) + vt[id] = copy (protovt[id]); + if (~(vt.parent >= 0)) { + vt.view = createwidget (-1, [ + 'type' = 'view'; + 'name' = vt.name; + 'origin' = vt.orig; + 'size' = vt.size; + ]); + vt.scroll = createwidget (vt.view, ['type' = 'scroll';]); + } else { + vt.view = -1; + vt.scroll = createwidget (vt.parent, [ + 'type' = 'scroll'; + 'size' = vt.size; + ]); + } + vt.canvas = createwidget (vt.scroll, [ + 'type' = 'canvas'; + 'color' = [0 = protovt.bgcolor; 1 = protovt.fgcolor;]; + ]); + setwidgetattr (vt.canvas, [ + 'window' = vt.wrect; + 'viewport' = vt.vsize; + ]); + clear (vt.canvas); + dotty.views[vt.canvas] = vt; + vt.vtid = vt.canvas; + vt.gtid = gt.gtid; + gt.views[vt.vtid] = vt; + dotty.views[vt.scroll] = vt; + if (vt.view ~= -1) + dotty.views[vt.view] = vt; + if (protovt.colors & tablesize (protovt.colors) > 0) { + for (id in protovt.colors) + if (setwidgetattr (vt.canvas, ['color' = [ + protovt.colors[id] = id; + ];]) ~= 1) { + t = split (id, ' '); + if (tablesize (t) ~= 3 | setwidgetattr (vt.canvas, [ + 'color' = [protovt.colors[id] = [ + 'h' = ston (t[0]); 's' = ston (t[1]); 'v' = ston (t[2]); + ];]; + ]) ~= 1) { + dotty.message (0, + concat ('unknown color ', id, ' using #1')); + } + } + vt.colors = copy (protovt.colors); + vt.colorn = protovt.colorn; + } else if (tablesize (gt.views) > 1) { + for (id in gt.views) + if (gt.views[id] ~= vt) + break; + ovt = gt.views[id]; + for (id in ovt.colors) + if (setwidgetattr (vt.canvas, ['color' = [ + ovt.colors[id] = id; + ];]) ~= 1) { + t = split (id, ' '); + if (tablesize (t) ~= 3 | setwidgetattr (vt.canvas, [ + 'color' = [ovt.colors[id] = [ + 'h' = ston (t[0]); 's' = ston (t[1]); 'v' = ston (t[2]); + ];]; + ]) ~= 1) { + dotty.message (0, + concat ('unknown color ', id, ' using #1')); + } + } + vt.colors = copy (ovt.colors); + vt.colorn = ovt.colorn; + } + if (gt.graph.rect) + gt.setviewsize ([vt.vtid = vt;], gt.graph.rect); + gt.drawgraph (gt, [vt.vtid = vt;]); + for (id in vt.uifuncs) + if (id == 'closeview') + widgets[vt.view][id] = vt.uifuncs[id]; + else + widgets[vt.canvas][id] = vt.uifuncs[id]; + return vt; +}; +dotty.protogt.destroyview = function (gt, vt) { + destroywidget (vt.canvas); + destroywidget (vt.scroll); + if (vt.view ~= -1) { + destroywidget (vt.view); + remove (vt.view, dotty.views); + } + remove (vt.scroll, dotty.views); + remove (vt.canvas, dotty.views); + if (vt.gtid >= 0) + remove (vt.vtid, gt.views); + if (tablesize (dotty.views) == 0) + exit (); +}; +dotty.protogt.zoom = function (gt, vt, factor, pos) { + gt.setviewscale ([vt.vtid = vt;], factor); + if (pos) + gt.setviewcenter ([vt.vtid = vt;], pos); + gt.redrawgraph (gt, [vt.vtid = vt;]); +}; +dotty.protogt.findnode = function (gt, vt) { + local key, node, node1, nid; + + if (~(key = ask ('give node name or label'))) + return; + if (gt.graph.nodedict[key] >= 0) + node = gt.graph.nodes[gt.graph.nodedict[key]]; + else if (gt.graph.nodedict[ston (key)] >= 0) + node = gt.graph.nodes[gt.graph.nodedict[ston (key)]]; + else { + for (nid in gt.graph.nodes) { + node1 = gt.graph.nodes[nid]; + if (node1.attr.label == key | node1.attr.label == ston (key)) { + node = node1; + break; + } + } + } + if (~node) { + dotty.message (0, concat ('cannot find node: ', key)); + return; + } + gt.setviewcenter ([vt.vtid = vt;], node.pos); +}; +dotty.protogt.setattr = function (gt, obj) { + local kv, t, attr, value; + + if (~(kv = ask ('give attr/value, eg. color=blue'))) + return; + t = split (kv, '='); + attr = t[0]; + value = t[1]; + if ( + obj.attr == gt.graph.graphattr | + obj.attr == gt.graph.edgeattr | + obj.attr == gt.graph.nodeattr + ) { + obj.attr[attr] = value; + return; + } + if (obj.nid >= 0) { + gt.undrawnode (gt, gt.views, obj); + obj.attr[attr] = value; + gt.unpacknodeattr (gt, obj); + gt.drawnode (gt, gt.views, obj); + } else if (obj.eid >= 0) { + gt.undrawedge (gt, gt.views, obj); + obj.attr[attr] = value; + gt.unpackedgeattr (gt, obj); + gt.drawedge (gt, gt.views, obj); + } +}; +dotty.protogt.getattr = function (gt, node) { + local kv; + + if (~(kv.key = ask ('give attr name'))) + return null; + if ((kv.val = node.attr[kv.key])) + return kv; + return null; +}; +# +# utilities +# +dotty.createviewandgraph = function (name, type, protogt, protovt) { + local vt, gt; + + if (~protogt) + protogt = dotty.protogt; + if (protogt.creategraph) + gt = protogt.creategraph (protogt); + else + gt = dotty.protogt.creategraph (protogt); + vt = gt.createview (gt, protovt); + if (~protogt.graph) + protogt.graph = copy (dotty.protogt.graph); + if (name) + gt.loadgraph (gt, name, type, protogt.graph, 1); + return ['gt' = gt; 'vt' = vt;]; +}; +dotty.openio = function (name, type, mode) { + local fd; + + if (~name) + return null; + if (type == 'file') { + if (name == '-') { + if (mode == 'r' | mode == 'r+') + fd = 0; + else + fd = 1; + } else if (~((fd = openio ('file', name, mode)) >= 0)) { + dotty.message (0, concat ('cannot open file: ', name)); + return null; + } + } else if (type == 'pipe') { + if (~((fd = openio ('pipe', 'ksh', mode, + concat ("%e ", name))) >= 0)) { + dotty.message (0, concat ('cannot run command: ', name)); + return null; + } + } else + return null; + return fd; +}; +dotty.pushbusy = function (gt, views) { + local vid; + + if (gt.busy == 0) + for (vid in gt.views) + setwidgetattr (vid, ['cursor' = 'watch';]); + gt.busy = gt.busy + 1; +}; +dotty.popbusy = function (gt, views) { + local vid; + + gt.busy = gt.busy - 1; + if (gt.busy == 0) + for (vid in gt.views) + setwidgetattr (vid, ['cursor' = 'default';]); +}; +dotty.message = function (level, text) { + if (level <= dotty.mlevel) + echo ('dotty.lefty: ', text); +}; +# +# printing or saving to file +# +dotty.protogt.printorsave = function (gt, vt, otype, name, mode, ptype) { + local pr, wrect, vsize, xy, psize, canvas, pscanvas, cid, cname, t; + local graph, edgehandles, fontmap, eid, edge, nid, node, gid, sgraph; + + if (~otype) + if (~(otype = ask ('print to', 'choice', 'file|printer'))) + return; + if (otype == 'printer') { + name = '/tmp/dottyout.ps'; + if (getenv ('LEFTYWINSYS') ~= 'mswin' & ~pr) + if (~(pr = ask ('printer command', 'string', 'lpr'))) + return; + } + if (~name) + if (~(name = ask ('postscript file', 'file', 'out.ps'))) + return; + if (~ptype) + if (~(ptype = ask ('page size', 'choice', '8.5x11|11x17|36x50'))) + return; + if (~mode) + if (~(mode = ask ('mode', 'choice', 'portrait|landscape|best fit'))) + return; + wrect = copy (vt.wrect); + wrect[0].x = wrect[0].x - 1; + wrect[1].x = wrect[1].x + 1; + wrect[0].y = wrect[0].y - 1; + wrect[1].y = wrect[1].y + 1; + vsize = copy (vt.vsize); + if (vsize.x == 0) + vsize.x = 1; + if (vsize.y == 0) + vsize.y = 1; + xy = vsize.x / vsize.y; + if (mode == 'best fit') { + if (xy < 1) + mode = 'portrait'; + else + mode = 'landscape'; + } + psize = dotty.pagesizes[ptype]; + if (mode == 'portrait') { + if (xy < psize.x / psize.y) { + vsize.y = psize.y * 300; + vsize.x = vsize.y * xy; + } else { + vsize.x = psize.x * 300; + vsize.y = vsize.x / xy; + } + } else { + if (xy < psize.y / psize.x) { + vsize.y = psize.x * 300; + vsize.x = vsize.y * xy; + } else { + vsize.x = psize.y * 300; + vsize.y = vsize.x / xy; + } + } + if (~((pscanvas = createwidget (-1, [ + 'type' = 'ps'; + 'origin' = ['x' = 0; 'y' = 0;]; + 'size' = vsize; + 'mode' = mode; + 'name' = name; + ])) >= 0)) { + dotty.message (0, 'cannot open printer device'); + return; + } + for (cname in vt.colors) { + cid = vt.colors[cname]; + if (setwidgetattr (pscanvas, ['color' = [cid = cname;];]) ~= 1) { + t = split (cname, ' '); + if (tablesize (t) ~= 3 | + setwidgetattr (pscanvas, ['color' = [cid = [ + 'h' = ston (t[0]); 's' = ston (t[1]); 'v' = ston (t[2]); + ];];]) ~= 1) { + dotty.message (0, concat ('unknown color ', + cname, ' using #1')); + } + } + } + setwidgetattr (pscanvas, ['window' = wrect;]); + graph = copy (gt.graph); + canvas = vt.canvas; + vt.canvas = pscanvas; + edgehandles = gt.edgehandles; + gt.edgehandles = 0; + fontmap = dotty.maps[getenv ('LEFTYWINSYS')].psfontmap; + for (eid in graph.edges) { + edge = graph.edges[eid]; + edge.fontname = fontmap[edge.attr.fontname]; + gt.drawedge (gt, [0 = vt;], edge); + } + for (nid in graph.nodes) { + node = graph.nodes[nid]; + node.fontname = fontmap[node.attr.fontname]; + gt.drawnode (gt, [0 = vt;], node); + } + for (gid in graph.graphs) { + sgraph = graph.graphs[gid]; + sgraph.fontname = fontmap[sgraph.graphattr.fontname]; + gt.drawsgraph (gt, [0 = vt;], sgraph); + } + graph.fontname = fontmap[graph.graphattr.fontname]; + gt.drawsgraph (gt, [0 = vt;], graph); + gt.edgehandles = edgehandles; + vt.canvas = canvas; + destroywidget (pscanvas); + if (otype == 'printer' & getenv ('LEFTYWINSYS') ~= 'mswin') + system (concat (pr, ' /tmp/dottyout.ps; rm /tmp/dottyout.ps')); +}; diff --git a/cmd/dotty/dotty.sh b/cmd/dotty/dotty.sh new file mode 100755 index 000000000..e4a256eb0 --- /dev/null +++ b/cmd/dotty/dotty.sh @@ -0,0 +1,104 @@ +#!/bin/sh + +FILES="" +MLEVEL="0" +LMODE="async" + +usage='echo "usage: dotty [-V] [-lm (sync|async)] [-el (0|1)] "' + +if [ "x$DOTTYOPTIONS" != "x" ]; then + set -- $DOTTYOPTIONS $* +fi + +while [ "x$1" != 'x' ]; do + case $1 in + -V) + echo "dotty version 96c (09-24-96)" + shift + ;; + -f) + shift + loadfile=$1 + shift + ;; + -lm) + shift + LMODE=$1 + if [ "x$LMODE" != 'xsync' -a "x$LMODE" != 'xasync' ]; then + $usage + exit 1 + fi + shift + ;; + -el) + shift + MLEVEL=$1 + if [ "x$MLEVEL" != 'x0' -a "x$MLEVEL" != 'x1' ]; then + $usage + exit 1 + fi + shift + ;; + -) + FILES=`echo $FILES \"$1\"` + shift + ;; + -*) + $usage + exit 1 + ;; + *) + FILES=`echo $FILES \"$1\"` + shift + ;; + esac +done + +if [ "x$DOTTYPATH" != 'x' ]; then + LEFTYPATH="$DOTTYPATH:$LEFTYPATH" +fi + +CMDS="" + +CMDS="dotty.protogt.layoutmode = '$LMODE';" + +CMDS=`echo $CMDS dotty.mlevel = $MLEVEL";"` + +if [ "x$loadfile" != 'x' ]; then + CMDS=`echo $CMDS load \("'"$loadfile"'"\)";"` +fi + +if [ "x$FILES" = 'x' ]; then + FILES=null +fi +FUNC="dotty.createviewandgraph" +for i in $FILES; do + CMDS=`echo $CMDS $FUNC \($i, "'"file"'", null, null\)";"` +done + +leftypath=`which lefty` +if [ ! -f "$leftypath" ]; then + echo "dotty: cannot locate the lefty program" + echo " make sure that your path includes" + echo " the directory containing dotty and lefty" + exit 1 +fi + +exec $leftypath -e " +load ('dotty.lefty'); +checkpath = function () { + if (tablesize (dotty) > 0) + remove ('checkpath'); + else { + echo ('dotty: cannot locate the dotty scripts'); + echo (' make sure that the environment variable LEFTYPATH'); + echo (' is set to the directory containing dotty.lefty'); + exit (); + } +}; +checkpath (); +dotty.init (); +monitorfile = dotty.monitorfile; +$CMDS +txtview ('off'); +" diff --git a/cmd/dotty/dotty_draw.lefty b/cmd/dotty/dotty_draw.lefty new file mode 100644 index 000000000..5e0276cbb --- /dev/null +++ b/cmd/dotty/dotty_draw.lefty @@ -0,0 +1,763 @@ +# +# dotty_draw: drawing functions and data structures +# +dotty.protogt.drawgraph = function (gt, views) { + local gid, eid, nid, graph; + + graph = gt.graph; + gt.drawsgraph (gt, views, graph); + for (gid in graph.graphs) + gt.drawsgraph (gt, views, graph.graphs[gid]); + for (eid in graph.edges) + gt.drawedge (gt, views, graph.edges[eid]); + for (nid in graph.nodes) + gt.drawnode (gt, views, graph.nodes[nid]); +}; +dotty.protogt.redrawgraph = function (gt, views) { + local vid; + + for (vid in views) + clear (views[vid].canvas); + gt.drawgraph (gt, views); +}; +dotty.protogt.setviewsize = function (views, r) { + local vid, vt, w2v, scale, attr; + + for (vid in views) { + vt = views[vid]; + vt.wrect = copy (r); + if (r[1].x == 0 | r[1].y == 0) { + attr = getwidgetattr (vt.scroll, [0 = 'size';]); + vt.wrect[1] = copy (attr.size); + } + if (vt.type == 'birdseye') { + attr = getwidgetattr (vt.scroll, [0 = 'size';]); + scale.x = (vt.wrect[1].x - vt.wrect[0].x) / attr.size.x; + scale.y = (vt.wrect[1].y - vt.wrect[0].y) / attr.size.y; + if (scale.x > 1 & scale.x > scale.y) + vt.w2v = scale.x; + else if (scale.y > 1) + vt.w2v = scale.y; + else + vt.w2v = 1; + } + w2v = vt.w2v; + vt.vsize = [ + 'x' = (vt.wrect[1].x - vt.wrect[0].x) / w2v; + 'y' = (vt.wrect[1].y - vt.wrect[0].y) / w2v; + ]; + setwidgetattr (vt.canvas, [ + 'window' = vt.wrect; + 'viewport' = vt.vsize; + ]); + } +}; +dotty.protogt.setviewscale = function (views, factor) { + local vid, vt, w2v; + + for (vid in views) { + vt = views[vid]; + if ((w2v = vt.w2v * factor) < 0.01) { + dotty.message (0, 'cannot zoom any closer'); + return; + } + vt.w2v = w2v; + vt.vsize = [ + 'x' = (vt.wrect[1].x - vt.wrect[0].x) / w2v; + 'y' = (vt.wrect[1].y - vt.wrect[0].y) / w2v; + ]; + setwidgetattr (vt.canvas, ['viewport' = vt.vsize;]); + } +}; +dotty.protogt.setviewcenter = function (views, center) { + local vid, vt, pos; + + for (vid in views) { + vt = views[vid]; + pos = [ + 'x' = center.x * vt.vsize.x / (vt.wrect[1].x - vt.wrect[0].x); + 'y' = (vt.wrect[1].y - center.y) * vt.vsize.y / + (vt.wrect[1].y - vt.wrect[0].y); + ]; + setwidgetattr (vt.scroll, ['childcenter' = pos;]); + } +}; +dotty.protogt.drawsgraph = function (gt, views, sgraph) { + local vid, canvas, pos; + + sgraph.draw = 1; + if (~sgraph.rect[0] | sgraph.graphattr.style == 'invis') + return; + for (vid in views) { + canvas = views[vid].canvas; + if (~sgraph.type) # 'type' only exists on top level + box (canvas, null, sgraph.rect, ['color' = sgraph.color;]); + if (sgraph.graphattr.label) { + if (sgraph.lp.x >= 0) { + pos = sgraph.lp; + text (canvas, null, pos, sgraph.graphattr.label, + sgraph.fontname, sgraph.fontsize, 'cc', + ['color' = sgraph.fontcolor;]); + } else { + pos = ['x' = sgraph.rect[0].x; 'y' = sgraph.rect[1].y;]; + text (canvas, null, pos, sgraph.graphattr.label, + sgraph.fontname, sgraph.fontsize, 'ld', + ['color' = sgraph.fontcolor;]); + } + } + } +}; +dotty.protogt.undrawsgraph = function (gt, views, sgraph) { + local vid, canvas, pos; + + if (~sgraph.drawn) + return; + sgraph.drawn = 0; + if (~sgraph.rect[0] | sgraph.graphattr.style == 'invis') + return; + for (vid in views) { + canvas = views[vid].canvas; + if (~sgraph.type) # 'type' only exists on top level + box (canvas, null, sgraph.rect, ['color' = 0;]); + if (sgraph.graphattr.label) { + if (sgraph.lp.x >= 0) { + pos = sgraph.lp; + text (canvas, null, pos, sgraph.graphattr.label, + sgraph.fontname, sgraph.fontsize, 'cc', + ['color' = 0;]); + } else { + pos = ['x' = sgraph.rect[0].x; 'y' = sgraph.rect[1].y;]; + text (canvas, null, pos, sgraph.graphattr.label, + sgraph.fontname, sgraph.fontsize, 'ld', + ['color' = 0;]); + } + } + clearpick (canvas, sgraph); + } +}; +dotty.protogt.drawnode = function (gt, views, node) { + local vid, func, pos, size, rect; + + node.drawn = 1; + if (~node.pos) + return; + if (node.attr.style == 'invis') { + pos = node.pos; + size = node.size; + rect[0] = ['x' = pos.x - size.x / 2; 'y' = pos.y - size.y / 2;]; + rect[1] = ['x' = rect[0].x + size.x; 'y' = rect[0].y + size.y;]; + for (vid in views) + setpick (views[vid].canvas, node, rect); + return; + } + if (~(func = gt.shapefunc[node.attr.shape])) + func = gt.shapefunc['box']; + for (vid in views) + func (gt, views[vid].canvas, node); +}; +dotty.protogt.undrawnode = function (gt, views, node) { + local vid, func, pos, size, rect, color, fontcolor, outlinecolor; + + if (~node.drawn) + return; + node.drawn = 0; + if (~node.pos) + return; + if (node.attr.style == 'invis') { + pos = node.pos; + size = node.size; + rect[0] = ['x' = pos.x - size.x / 2; 'y' = pos.y - size.y / 2;]; + rect[1] = ['x' = rect[0].x + size.x; 'y' = rect[0].y + size.y;]; + for (vid in views) + clearpick (views[vid].canvas, node); + return; + } + color = node.color; + node.color = 0; + fontcolor = node.fontcolor; + node.fontcolor = 0; + outlinecolor = dotty.outlinecolor; + dotty.outlinecolor = 0; + if (~(func = gt.shapefunc[node.attr.shape])) + func = gt.shapefunc['box']; + for (vid in views) { + func (gt, views[vid].canvas, node); + clearpick (views[vid].canvas, node); + } + node.color = color; + node.fontcolor = fontcolor; + dotty.outlinecolor = outlinecolor; +}; +dotty.protogt.shapefunc.record = function (gt, canvas, node) { + local rect, pos, size; + + pos = node.pos; + size = node.size; + rect[0] = ['x' = pos.x - size.x / 2; 'y' = pos.y - size.y / 2;]; + rect[1] = ['x' = rect[0].x + size.x; 'y' = rect[0].y + size.y;]; + if (node.attr.style == 'filled') { + box (canvas, node, rect, ['color' = node.color; 'fill' = 'on';]); + box (canvas, node, rect, ['color' = dotty.outlinecolor;]); + } + gt.shapefunc.rfields (gt, canvas, node, node.fields); + setpick (canvas, node, rect); +}; +dotty.protogt.shapefunc.rfields = function (gt, canvas, node, fields) { + local fid, field, pos, label; + + for (fid in fields) { + field = fields[fid]; + if (field.fields) + gt.shapefunc.rfields (gt, canvas, node, field.fields); + else { + if (node.attr.style == 'filled') + box (canvas, null, field.rect, ['color' = dotty.outlinecolor;]); + else + box (canvas, null, field.rect, ['color' = node.color;]); + pos.x = (field.rect[1].x + field.rect[0].x) / 2; + pos.y = (field.rect[1].y + field.rect[0].y) / 2; + if (~(label = field.text) | label == '\N') + label = node.name; + text (canvas, null, pos, label, node.fontname, node.fontsize, + 'cc', ['color' = node.fontcolor;]); + } + } +}; +dotty.protogt.shapefunc.plaintext = function (gt, canvas, node) { + local pos, size, label, rect; + + pos = node.pos; + size = node.size; + if (~(label = node.attr.label) | label == '\N') + label = node.name; + rect[0] = ['x' = pos.x - size.x / 2; 'y' = pos.y - size.y / 2;]; + rect[1] = ['x' = rect[0].x + size.x; 'y' = rect[0].y + size.y;]; + setpick (canvas, node, rect); + text (canvas, null, pos, label, node.fontname, node.fontsize, + 'cc', ['color' = node.fontcolor;]); +}; +dotty.protogt.shapefunc.box = function (gt, canvas, node) { + local pos, size, label, rect; + + pos = node.pos; + size = node.size; + if (~(label = node.attr.label) | label == '\N') + label = node.name; + rect[0] = ['x' = pos.x - size.x / 2; 'y' = pos.y - size.y / 2;]; + rect[1] = ['x' = rect[0].x + size.x; 'y' = rect[0].y + size.y;]; + if (node.attr.style == 'filled') { + box (canvas, node, rect, ['color' = node.color; 'fill' = 'on';]); + box (canvas, node, rect, ['color' = dotty.outlinecolor;]); + } else + box (canvas, node, rect, ['color' = node.color;]); + text (canvas, null, pos, label, node.fontname, node.fontsize, + 'cc', ['color' = node.fontcolor;]); +}; +dotty.protogt.shapefunc.Msquare = function (gt, canvas, node) { + local pos, size, label, rect, color; + + pos = node.pos; + size = node.size; + if (~(label = node.attr.label) | label == '\N') + label = node.name; + rect[0] = ['x' = pos.x - size.x / 2; 'y' = pos.y - size.y / 2;]; + rect[1] = ['x' = rect[0].x + size.x; 'y' = rect[0].y + size.y;]; + if (node.attr.style == 'filled') { + box (canvas, node, rect, ['color' = node.color; 'fill' = 'on';]); + color = dotty.outlinecolor; + box (canvas, node, rect, ['color' = color;]); + line (canvas, null, ['x' = rect[0].x; 'y' = rect[0].y + 10;], + ['x' = rect[0].x + 10; 'y' = rect[0].y;], ['color' = color;]); + line (canvas, null, ['x' = rect[0].x; 'y' = rect[1].y - 10;], + ['x' = rect[0].x + 10; 'y' = rect[1].y;], ['color' = color;]); + line (canvas, null, ['x' = rect[1].x; 'y' = rect[0].y + 10;], + ['x' = rect[1].x - 10; 'y' = rect[0].y;], ['color' = color;]); + line (canvas, null, ['x' = rect[1].x; 'y' = rect[1].y - 10;], + ['x' = rect[1].x - 10; 'y' = rect[1].y;], ['color' = color;]); + } else { + color = node.color; + box (canvas, node, rect, ['color' = color;]); + line (canvas, null, ['x' = rect[0].x; 'y' = rect[0].y + 10;], + ['x' = rect[0].x + 10; 'y' = rect[0].y;], ['color' = color;]); + line (canvas, null, ['x' = rect[0].x; 'y' = rect[1].y - 10;], + ['x' = rect[0].x + 10; 'y' = rect[1].y;], ['color' = color;]); + line (canvas, null, ['x' = rect[1].x; 'y' = rect[0].y + 10;], + ['x' = rect[1].x - 10; 'y' = rect[0].y;], ['color' = color;]); + line (canvas, null, ['x' = rect[1].x; 'y' = rect[1].y - 10;], + ['x' = rect[1].x - 10; 'y' = rect[1].y;], ['color' = color;]); + } + text (canvas, null, pos, label, node.fontname, node.fontsize, + 'cc', ['color' = node.fontcolor;]); +}; +dotty.protogt.shapefunc.ellipse = function (gt, canvas, node) { + local pos, size, label; + + pos = node.pos; + size.x = node.size.x / 2; + size.y = node.size.y / 2; + if (~(label = node.attr.label) | label == '\N') + label = node.name; + if (node.attr.style == 'filled') { + if (node.attr.shape == 'doublecircle') { + arc (canvas, node, pos, size, ['color' = dotty.outlinecolor;]); + size.x = size.x - 4; + size.y = size.y - 4; + } + arc (canvas, node, pos, size, ['color' = node.color; 'fill' = 'on';]); + arc (canvas, node, pos, size, ['color' = dotty.outlinecolor;]); + } else { + if (node.attr.shape == 'doublecircle') { + arc (canvas, node, pos, size, ['color' = node.color;]); + size.x = size.x - 4; + size.y = size.y - 4; + } + arc (canvas, node, pos, size, ['color' = node.color;]); + } + text (canvas, null, pos, label, node.fontname, node.fontsize, + 'cc', ['color' = node.fontcolor;]); +}; +dotty.protogt.shapefunc.circle = dotty.protogt.shapefunc.ellipse; +dotty.protogt.shapefunc.doublecircle = dotty.protogt.shapefunc.ellipse; +dotty.protogt.shapefunc.diamond = function (gt, canvas, node) { + local pos, size, label, p, rect; + + pos = node.pos; + size = node.size; + if (~(label = node.attr.label) | label == '\N') + label = node.name; + p[0] = ['x' = pos.x; 'y' = pos.y + size.y / 2;]; + p[1] = ['x' = pos.x + size.x / 2; 'y' = pos.y;]; + p[2] = ['x' = pos.x; 'y' = pos.y - size.y / 2;]; + p[3] = ['x' = pos.x - size.x / 2; 'y' = pos.y;]; + p[4] = p[0]; + rect[0] = ['x' = pos.x - size.x / 2; 'y' = pos.y - size.y / 2;]; + rect[1] = ['x' = rect[0].x + size.x; 'y' = rect[0].y + size.y;]; + if (node.attr.style == 'filled') { + polygon (canvas, node, p, ['color' = node.color; 'fill' = 'on';]); + polygon (canvas, node, p, ['color' = dotty.outlinecolor;]); + } else + polygon (canvas, node, p, ['color' = node.color;]); + setpick (canvas, node, rect); + text (canvas, null, pos, label, node.fontname, node.fontsize, + 'cc', ['color' = node.fontcolor;]); +}; +dotty.protogt.shapefunc.parallelogram = function (gt, canvas, node) { + local pos, size, label, rect, color, dx, p; + + pos = node.pos; + size = node.size; + if (~(label = node.attr.label) | label == '\N') + label = node.name; + rect[0] = ['x' = pos.x - size.x / 2; 'y' = pos.y - size.y / 2;]; + rect[1] = ['x' = rect[0].x + size.x; 'y' = rect[0].y + size.y;]; + dx = (rect[1].x - rect[0].x) / 5; + p[0] = ['x' = rect[0].x; 'y' = rect[0].y;]; + p[1] = ['x' = rect[1].x - dx; 'y' = rect[0].y;]; + p[2] = ['x' = rect[1].x; 'y' = rect[1].y;]; + p[3] = ['x' = rect[0].x + dx; 'y' = rect[1].y;]; + p[4] = ['x' = rect[0].x; 'y' = rect[0].y;]; + if (node.attr.style == 'filled') { + polygon (canvas, node, p, ['color' = node.color; 'fill' = 'on';]); + polygon (canvas, node, p, ['color' = dotty.outlinecolor;]); + } else + polygon (canvas, node, p, ['color' = node.color;]); + setpick (canvas, node, rect); + text (canvas, null, pos, label, node.fontname, node.fontsize, + 'cc', ['color' = node.fontcolor;]); +}; +dotty.protogt.shapefunc.trapezium = function (gt, canvas, node) { + local pos, size, label, rect, color, dx, p; + + pos = node.pos; + size = node.size; + if (~(label = node.attr.label) | label == '\N') + label = node.name; + rect[0] = ['x' = pos.x - size.x / 2; 'y' = pos.y - size.y / 2;]; + rect[1] = ['x' = rect[0].x + size.x; 'y' = rect[0].y + size.y;]; + dx = (rect[1].x - rect[0].x) / 5; + p[0] = ['x' = rect[0].x; 'y' = rect[0].y;]; + p[1] = ['x' = rect[1].x; 'y' = rect[0].y;]; + p[2] = ['x' = rect[1].x - dx; 'y' = rect[1].y;]; + p[3] = ['x' = rect[0].x + dx; 'y' = rect[1].y;]; + p[4] = ['x' = rect[0].x; 'y' = rect[0].y;]; + if (node.attr.style == 'filled') { + polygon (canvas, node, p, ['color' = node.color; 'fill' = 'on';]); + polygon (canvas, node, p, ['color' = dotty.outlinecolor;]); + } else + polygon (canvas, node, p, ['color' = node.color;]); + setpick (canvas, node, rect); + text (canvas, null, pos, label, node.fontname, node.fontsize, + 'cc', ['color' = node.fontcolor;]); +}; +dotty.protogt.shapefunc.triangle = function (gt, canvas, node) { + local pos, size, label, rect, color, dx, dy, p; + + pos = node.pos; + size = node.size; + if (~(label = node.attr.label) | label == '\N') + label = node.name; + rect[0] = ['x' = pos.x - size.x / 2; 'y' = pos.y - size.y / 2;]; + rect[1] = ['x' = rect[0].x + size.x; 'y' = rect[0].y + size.y;]; + if (node.attr.orientation ~= -90) { + dx = size.x / 2; + dy = size.y / 4; + p[0] = ['x' = pos.x - dx; 'y' = pos.y - dy;]; + p[1] = ['x' = pos.x + dx; 'y' = pos.y - dy;]; + p[2] = ['x' = pos.x; 'y' = rect[1].y;]; + p[3] = ['x' = pos.x - dx; 'y' = pos.y - dy;]; + } else { + dx = size.x / 4; + dy = size.y / 2; + p[0] = ['x' = pos.x - dx; 'y' = pos.y - dy;]; + p[1] = ['x' = pos.x - dx; 'y' = pos.y + dy;]; + p[2] = ['x' = pos.x + dx * 2; 'y' = pos.y;]; + p[3] = ['x' = pos.x - dx; 'y' = pos.y - dy;]; + } + if (node.attr.style == 'filled') { + polygon (canvas, node, p, ['color' = node.color; 'fill' = 'on';]); + polygon (canvas, node, p, ['color' = dotty.outlinecolor;]); + } else + polygon (canvas, node, p, ['color' = node.color;]); + setpick (canvas, node, rect); + text (canvas, null, pos, label, node.fontname, node.fontsize, + 'cc', ['color' = node.fontcolor;]); +}; +dotty.protogt.movenode = function (gt, node, pos) { + local ppos, eid, edge, p, fp, lp; + + ppos = copy (node.pos); + gt.undrawnode (gt, gt.views, node); + node.pos.x = pos.x; + node.pos.y = pos.y; + if (node.attr.shape == 'record') + gt.moverecordfields (gt, node.fields, pos.x - ppos.x, pos.y - ppos.y); + for (eid in node.edges) { + edge = node.edges[eid]; + if (~edge.dir & edge.head ~= edge.tail) { + p = edge.tail.pos; + fp = edge.points[0]; + lp = edge.points[tablesize (edge.points) - 1]; + if (((p.x - fp.x) * (p.x - fp.x) + (p.y - fp.y) * (p.y - fp.y)) < + ((p.x - lp.x) * (p.x - lp.x) + (p.y - lp.y) * (p.y - lp.y))) + edge.dir = 1; + else + edge.dir = -1; + } + gt.moveedge (gt, edge, node, ppos, pos); + } + gt.drawnode (gt, gt.views, node); +}; +dotty.protogt.moverecordfields = function (gt, fields, dx, dy) { + local fid, field; + + for (fid in fields) { + field = fields[fid]; + if (field.fields) + gt.moverecordfields (gt, field.fields, dx, dy); + else { + field.rect[0].x = field.rect[0].x + dx; + field.rect[0].y = field.rect[0].y + dy; + field.rect[1].x = field.rect[1].x + dx; + field.rect[1].y = field.rect[1].y + dy; + } + } +}; +dotty.protogt.drawedge = function (gt, views, edge) { + local vid, canvas; + + edge.drawn = 1; + if (~edge.points) + return; + if (edge.attr.style == 'invis') { + if (gt.edgehandles == 0) + return; + for (vid in views) { + arc (views[vid].canvas, edge, [ + 'x' = (edge.points[1].x + edge.points[2].x) / 2; + 'y' = (edge.points[1].y + edge.points[2].y) / 2; + ], ['x' = 5; 'y' = 5;], ['color' = 1;]); + } + return; + } + for (vid in views) { + canvas = views[vid].canvas; + if (edge.attr.style == 'bold') + setgfxattr (canvas, ['width' = 3;]); + splinegon (canvas, null, edge.points, + ['color' = edge.color; 'style' = edge.attr.style;]); + if (edge.sp) + arrow (canvas, null, edge.points[0], + edge.sp, ['color' = edge.color;]); + if (edge.ep) + arrow (canvas, null, edge.points[tablesize (edge.points) - 1], + edge.ep, ['color' = edge.color;]); + if (edge.attr.style == 'bold') + setgfxattr (canvas, ['width' = 0;]); + if (edge.lp) + text (canvas, null, edge.lp, edge.attr.label, edge.fontname, + edge.fontsize, 'cc', ['color' = edge.fontcolor;]); + if (gt.edgehandles == 0) + continue; + arc (canvas, edge, [ + 'x' = (edge.points[1].x + edge.points[2].x) / 2; + 'y' = (edge.points[1].y + edge.points[2].y) / 2; + ], ['x' = 5; 'y' = 5;], ['color' = 1;]); + } +}; +dotty.protogt.undrawedge = function (gt, views, edge) { + local vid, canvas; + + if (~edge.drawn) + return; + edge.drawn = 0; + if (~edge.points) + return; + if (edge.attr.style == 'invis') { + if (gt.edgehandles == 0) + return; + for (vid in views) { + arc (views[vid].canvas, edge, [ + 'x' = (edge.points[1].x + edge.points[2].x) / 2; + 'y' = (edge.points[1].y + edge.points[2].y) / 2; + ], ['x' = 5; 'y' = 5;], ['color' = 0;]); + clearpick (views[vid].canvas, edge); + } + return; + } + for (vid in views) { + canvas = views[vid].canvas; + if (edge.attr.style == 'bold') + setgfxattr (canvas, ['width' = 3;]); + splinegon (canvas, null, edge.points, ['color' = 0;]); + if (edge.sp) + arrow (canvas, null, edge.points[0], + edge.sp, ['color' = 0;]); + if (edge.ep) + arrow (canvas, null, edge.points[tablesize (edge.points) - 1], + edge.ep, ['color' = 0;]); + if (edge.attr.style == 'bold') + setgfxattr (canvas, ['width' = 0;]); + if (edge.lp) + text (canvas, null, edge.lp, edge.attr.label, edge.fontname, + edge.fontsize, 'cc', ['color' = 0;]); + if (gt.edgehandles == 0) + continue; + arc (canvas, edge, [ + 'x' = (edge.points[1].x + edge.points[2].x) / 2; + 'y' = (edge.points[1].y + edge.points[2].y) / 2; + ], ['x' = 5; 'y' = 5;], ['color' = 0;]); + clearpick (canvas, edge); + } +}; +dotty.protogt.moveedge = function (gt, edge, node, pb, pc) { + local dx, dy, tp, hp, pid, p, pa, da, lab, lac, s, ce, se, n, x, y, dir; + + gt.undrawedge (gt, gt.views, edge); + dx = pc.x - pb.x; dy = pc.y - pb.y; + tp = edge.sp; + hp = edge.ep; + if (edge.tail == node) { + if (edge.head == node) { + for (pid in edge.points) { + p = edge.points[pid]; + p.x = p.x + dx; p.y = p.y + dy; + } + if (tp) { + tp.x = tp.x + dx; tp.y = tp.y + dy; + } + if (hp) { + hp.x = hp.x + dx; hp.y = hp.y + dy; + } + if (edge.lp) { + edge.lp.x = edge.lp.x + dx; + edge.lp.y = edge.lp.y + dy; + } + gt.drawedge (gt, gt.views, edge); + return; + } + pa = edge.head.pos; + dir = 1; + } else { + pa = edge.tail.pos; + dir = -1; + } + dir = edge.dir * dir; + da = atan (pc.y - pa.y, pc.x - pa.x) - atan (pb.y - pa.y, pb.x - pa.x); + lab = sqrt ((pb.y - pa.y) * (pb.y - pa.y) + + (pb.x - pa.x) * (pb.x - pa.x)); + lac = sqrt ((pc.y - pa.y) * (pc.y - pa.y) + + (pc.x - pa.x) * (pc.x - pa.x)); + s = lac / lab; + ce = cos (da); + se = sin (da); + n = tablesize (edge.points); + for (pid = 1; pid < n - 1; pid = pid + 1) { + p = edge.points[pid]; + x = p.x - pa.x; + y = p.y - pa.y; + p.x = pa.x + (ce * x - se * y) * s; + p.y = pa.y + (se * x + ce * y) * s; + } + if (dir == 1) { + p = edge.points[0]; + p.x = p.x + dx; p.y = p.y + dy; + if (tp) { + tp.x = tp.x + dx; tp.y = tp.y + dy; + } + } else { + p = edge.points[n - 1]; + p.x = p.x + dx; p.y = p.y + dy; + if (hp) { + hp.x = hp.x + dx; hp.y = hp.y + dy; + } + } + if (edge.lp) { + x = edge.lp.x - pa.x; + y = edge.lp.y - pa.y; + edge.lp.x = pa.x + (ce * x - se * y) * s; + edge.lp.y = pa.y + (se * x + ce * y) * s; + } + gt.drawedge (gt, gt.views, edge); +}; +dotty.protogt.getcolor = function (views, name) { + local vid, vt, color, t; + + for (vid in views) { + vt = views[vid]; + if (~(color >= 0)) { + if (~(vt.colors[name] >= 0)) + color = (vt.colors[name] = vt.colorn); + else { + color = vt.colors[name]; + break; + } + } else if (~(vt.colors[name] >= 0)) + vt.colors[name] = color; + else if (vt.colors[name] ~= color) + dotty.message (0, concat ('inconsistent color ids for ', name)); + if (setwidgetattr (vt.canvas, ['color' = [color = name;];]) ~= 1) { + t = split (name, ' '); + if (tablesize (t) ~= 3 | + setwidgetattr (vt.canvas, ['color' = [color = [ + 'h' = ston (t[0]); 's' = ston (t[1]); 'v' = ston (t[2]); + ];];]) ~= 1) { + dotty.message (0, concat ('unknown color ', name, ' using #1')); + return 1; + } + } + vt.colorn = color + 1; + } + return color; +}; +dotty.protogt.unpacksgraphattr = function (gt, sgraph) { + local attr; + + attr = sgraph.graphattr; + if (dotty.fontmap[attr.fontname]) + sgraph[dotty.keys.fname] = dotty.fontmap[attr.fontname]; + else + sgraph[dotty.keys.fname] = attr.fontname; + sgraph[dotty.keys.fsize] = ston (attr.fontsize); + sgraph[dotty.keys.fcolor] = gt.getcolor (gt.views, attr.fontcolor); + if (attr.style == 'filled' & attr.color == 'black') + sgraph[dotty.keys.color] = gt.getcolor (gt.views, 'grey'); + else + sgraph[dotty.keys.color] = gt.getcolor (gt.views, attr.color); +}; +dotty.protogt.unpacknodeattr = function (gt, node) { + local attr; + + attr = node.attr; + if (dotty.fontmap[attr.fontname]) + node[dotty.keys.fname] = dotty.fontmap[attr.fontname]; + else + node[dotty.keys.fname] = attr.fontname; + node[dotty.keys.fsize] = ston (attr.fontsize); + node[dotty.keys.fcolor] = gt.getcolor (gt.views, attr.fontcolor); + if (attr.style == 'filled' & attr.color == 'black') + node[dotty.keys.color] = gt.getcolor (gt.views, 'grey'); + else + node[dotty.keys.color] = gt.getcolor (gt.views, attr.color); +}; +dotty.protogt.unpackedgeattr = function (gt, edge) { + local attr, n; + + attr = edge.attr; + if (dotty.fontmap[attr.fontname]) + edge[dotty.keys.fname] = dotty.fontmap[attr.fontname]; + else + edge[dotty.keys.fname] = attr.fontname; + edge[dotty.keys.fsize] = ston (attr.fontsize); + edge[dotty.keys.fcolor] = gt.getcolor (gt.views, attr.fontcolor); + if (attr.style == 'filled' & attr.color == 'black') + edge[dotty.keys.color] = gt.getcolor (gt.views, 'grey'); + else + edge[dotty.keys.color] = gt.getcolor (gt.views, attr.color); + if (attr.label & attr.label ~= '' & ~edge.lp & edge.points) { + if ((n = tablesize (edge.points)) > 4) + edge.lp = [ + 'x' = edge.points[toint (n / 2)].x + 5; + 'y' = edge.points[toint (n / 2)].y + 5; + ]; + else + edge.lp = [ + 'x' = (edge.points[1].x + edge.points[2].x) / 2 + 5; + 'y' = (edge.points[1].y + edge.points[2].y) / 2 + 5; + ]; + } +}; +dotty.protogt.unpackattr = function (gt) { + local gid, sgraph, nid, node, eid, edge, graph, attr; + + graph = gt.graph; + attr = graph.graphattr; + if (dotty.fontmap[attr.fontname]) + graph[dotty.keys.fname] = dotty.fontmap[attr.fontname]; + else + graph[dotty.keys.fname] = attr.fontname; + graph[dotty.keys.fsize] = ston (attr.fontsize); + graph[dotty.keys.fcolor] = gt.getcolor (gt.views, attr.fontcolor); + if (attr.style == 'filled' & attr.color == 'black') + graph[dotty.keys.color] = gt.getcolor (gt.views, 'grey'); + else + graph[dotty.keys.color] = gt.getcolor (gt.views, attr.color); + for (gid in graph.graphdict) { + sgraph = graph.graphs[graph.graphdict[gid]]; + attr = sgraph.graphattr; + if (dotty.fontmap[attr.fontname]) + sgraph[dotty.keys.fname] = dotty.fontmap[attr.fontname]; + else + sgraph[dotty.keys.fname] = attr.fontname; + sgraph[dotty.keys.fsize] = ston (attr.fontsize); + sgraph[dotty.keys.fcolor] = gt.getcolor (gt.views, attr.fontcolor); + if (attr.style == 'filled' & attr.color == 'black') + sgraph[dotty.keys.color] = gt.getcolor (gt.views, 'grey'); + else + sgraph[dotty.keys.color] = gt.getcolor (gt.views, attr.color); + } + for (nid in graph.nodedict) { + node = graph.nodes[graph.nodedict[nid]]; + attr = node.attr; + if (dotty.fontmap[attr.fontname]) + node[dotty.keys.fname] = dotty.fontmap[attr.fontname]; + else + node[dotty.keys.fname] = attr.fontname; + node[dotty.keys.fsize] = ston (attr.fontsize); + node[dotty.keys.fcolor] = gt.getcolor (gt.views, attr.fontcolor); + if (attr.style == 'filled' & attr.color == 'black') + node[dotty.keys.color] = gt.getcolor (gt.views, 'grey'); + else + node[dotty.keys.color] = gt.getcolor (gt.views, attr.color); + } + for (eid in graph.edges) { + edge = graph.edges[eid]; + attr = edge.attr; + if (dotty.fontmap[attr.fontname]) + edge[dotty.keys.fname] = dotty.fontmap[attr.fontname]; + else + edge[dotty.keys.fname] = attr.fontname; + edge[dotty.keys.fsize] = ston (attr.fontsize); + edge[dotty.keys.fcolor] = gt.getcolor (gt.views, attr.fontcolor); + edge[dotty.keys.color] = gt.getcolor (gt.views, attr.color); + } +}; diff --git a/cmd/dotty/dotty_edit.lefty b/cmd/dotty/dotty_edit.lefty new file mode 100644 index 000000000..d49fbe086 --- /dev/null +++ b/cmd/dotty/dotty_edit.lefty @@ -0,0 +1,583 @@ +# +# dotty_edit: editing functions and data structures +# +dotty.protogt.getnodesbyattr = function (gt, key, val) { + local nid, node, nlist; + + nlist = []; + for (nid in gt.graph.nodes) { + node = gt.graph.nodes[nid]; + if (node.attr[key] == val) + nlist[nid] = node; + } + return nlist; +}; +dotty.protogt.reachablenodes = function (gt, node) { + local nlist, stack, eid, edge, i; + + stack[0] = node; + i = 1; + while (i > 0) { + node = stack[i - 1]; + i = i - 1; + nlist[node.nid] = node; + for (eid in node.edges) { + edge = node.edges[eid]; + if (~nlist[edge.head.nid]) { + nlist[edge.head.nid] = edge.head; + stack[i] = edge.head; + i = i + 1; + } + } + } + return nlist; +}; +dotty.protogt.mergegraph = function (gt, graph, show) { + local nameid, onode, pos, size, eid, eid2, tnode, hnode, oedge; + + if (~gt.noundo) + gt.startadd2undo (gt); + for (nameid in graph.nodedict) { + pos = null; + size = null; + onode = graph.nodes[graph.nodedict[nameid]]; + if (onode.pos) + pos = node.pos; + if (onode.size) + size = node.size; + if (~(gt.graph.nodedict[nameid] >= 0)) { + pos = null; + size = null; + if (onode.pos) + pos = node.pos; + if (onode.size) + size = node.size; + gt.insertnode (gt, pos, size, nameid, onode.attr, show); + } + } + for (eid in graph.edges) { + oedge = graph.edges[eid]; + tnode = gt.graph.nodes[gt.graph.nodedict[oedge.tail.name]]; + hnode = gt.graph.nodes[gt.graph.nodedict[oedge.head.name]]; + for (eid2 in tnode.edges) + if (tnode.edges[eid2].tail == tnode & + tnode.edges[eid2].head == hnode) { + oedge = null; + break; + } + if (oedge) + gt.insertedge (gt, tnode, null, hnode, null, oedge.attr, show); + } + if (~gt.noundo) + gt.endadd2undo (gt); +}; +dotty.protogt.insertsgraph = function (gt, name, attr, show) { + local gid, sgraph, aid; + + if (~gt) + return null; + gid = gt.graph.maxgid; + if (~name) { + while (gt.graph.graphdict[(name = concat ('g', gid))] >= 0) + gid = gid + 1; + } else if (gt.graph.graphdict[name]) { + dotty.message (0, concat ('graph: ', name, ' exists')); + return null; + } + gt.graph.graphdict[name] = gid; + gt.graph.maxgid = gid + 1; + gt.graph.graphs[gid] = [ + dotty.keys.gid = gid; + dotty.keys.name = name; + dotty.keys.gattr = copy (gt.graph.graphattr); + dotty.keys.nattr = copy (gt.graph.nodeattr); + dotty.keys.eattr = copy (gt.graph.edgeattr); + ]; + sgraph = gt.graph.graphs[gid]; + if (~attr) + attr = []; + if (~attr.label) + attr.label = '\N'; + for (aid in attr) + sgraph.graphattr[aid] = attr[aid]; + gt.unpacksgraphattr (gt, sgraph); + if (show) + gt.drawsgraph (gt, gt.views, sgraph); + return sgraph; +}; +dotty.protogt.removesgraph = function (gt, sgraph) { + gt.undrawsgraph (gt, gt.views, sgraph); + remove (sgraph.name, gt.graph.graphdict); + remove (sgraph.gid, gt.graph.graphs); +}; +dotty.protogt.insertnode = function (gt, pos, size, name, attr, show) { + local nid, node, aid; + + nid = gt.graph.maxnid; + if (~name) { + while (gt.graph.nodedict[(name = concat ('n', nid))] >= 0) + nid = nid + 1; + } else if (gt.graph.nodedict[name] >= 0) { + dotty.message (0, concat ('node: ', name, ' exists')); + return null; + } + gt.graph.nodedict[name] = nid; + gt.graph.maxnid = nid + 1; + gt.graph.nodes[nid] = [ + dotty.keys.nid = nid; + dotty.keys.name = name; + dotty.keys.attr = copy (gt.graph.nodeattr); + dotty.keys.edges = []; + ]; + node = gt.graph.nodes[nid]; + if (~attr) + attr = []; + if (~attr.label) + attr.label = '\N'; + for (aid in attr) + node.attr[aid] = attr[aid]; + gt.unpacknodeattr (gt, node); + if (~pos) + pos = ['x' = 10; 'y' = 10;]; + node[dotty.keys.pos] = copy (pos); + if (~size) + size = ['x' = strlen (attr.label) * 30; 'y' = 30;]; + if (size.x == 0) + size.x = 30; + node[dotty.keys.size] = copy (size); + if (show) + gt.drawnode (gt, gt.views, node); + if (~gt.noundo) { + gt.startadd2undo (gt); + gt.currundo.inserted.nodes[nid] = node; + gt.endadd2undo (gt); + } + return node; +}; +dotty.protogt.removenode = function (gt, node) { + local eid, list, edge, gid; + + if (~gt.noundo) + gt.startadd2undo (gt); + for (eid in node.edges) + list[eid] = node.edges[eid]; + for (eid in list) + gt.removeedge (gt, list[eid]); + gt.undrawnode (gt, gt.views, node); + for (gid in gt.graph.graphs) + remove (node.nid, gt.graph.graphs[gid].nodes); + remove (node.name, gt.graph.nodedict); + remove (node.nid, gt.graph.nodes); + if (~gt.noundo) { + gt.currundo.deleted.nodes[node.nid] = node; + gt.endadd2undo (gt); + } +}; +dotty.protogt.insertedge = + function (gt, nodea, porta, nodeb, portb, attr, show) { + local eid, edge, aid, tport, hport; + + if (~nodea | ~nodeb) + return null; + if (porta) + tport = porta; + if (portb) + hport = portb; + eid = gt.graph.maxeid; + while (gt.graph.edges[eid]) + eid = eid + 1; + gt.graph.maxeid = eid + 1; + gt.graph.edges[eid] = [ + dotty.keys.eid = eid; + dotty.keys.tail = nodea; + dotty.keys.tport = porta; + dotty.keys.head = nodeb; + dotty.keys.hport = portb; + dotty.keys.attr = copy (gt.graph.edgeattr); + ]; + edge = gt.graph.edges[eid]; + if (~attr) + attr = []; + for (aid in attr) + edge.attr[aid] = attr[aid]; + nodea.edges[eid] = edge; + nodeb.edges[eid] = edge; + edge[dotty.keys.points] = [ + 0 = copy (nodea.pos); + 1 = copy (nodea.pos); + 2 = copy (nodeb.pos); + 3 = copy (nodeb.pos); + ]; + gt.unpackedgeattr (gt, edge); + if (show) + gt.drawedge (gt, gt.views, edge); + if (~gt.noundo) { + gt.startadd2undo (gt); + gt.currundo.inserted.edges[eid] = edge; + gt.endadd2undo (gt); + } + return edge; +}; +dotty.protogt.removeedge = function (gt, edge) { + local head, tail; + + if (~gt.noundo) + gt.startadd2undo (gt); + if (edge.head.attr.support == 1) + head = edge.head; + if (edge.tail.attr.support == 1) + if (head ~= edge.tail) + tail = edge.tail; + gt.undrawedge (gt, gt.views, edge); + remove (edge.eid, edge.head.edges); + remove (edge.eid, edge.tail.edges); + remove (edge.eid, gt.graph.edges); + if (head & tablesize (head.edges) == 0) + gt.removenode (gt, head); + if (tail & tablesize (tail.edges) == 0) + gt.removenode (gt, tail); + if (~gt.noundo) { + gt.currundo.deleted.edges[edge.eid] = edge; + gt.endadd2undo (gt); + } +}; +dotty.protogt.swapedgeids = function (gt, edge1, edge2) { + local eid1, eid2; + + if (edge1.eid == edge2.eid) + return; + if (~gt.noundo) + gt.startadd2undo (gt); + eid1 = edge1.eid; + eid2 = edge2.eid; + gt.graph.edges[eid1] = edge2; + gt.graph.edges[eid2] = edge1; + remove (eid1, edge1.tail.edges); + remove (eid1, edge1.head.edges); + remove (eid2, edge2.tail.edges); + remove (eid2, edge2.head.edges); + edge1.tail.edges[eid2] = edge1; + edge1.head.edges[eid2] = edge1; + edge2.tail.edges[eid1] = edge2; + edge2.head.edges[eid1] = edge2; + edge1.eid = eid2; + edge2.eid = eid1; + if (~gt.noundo) { + gt.currundo.swapped.edges[eid1] = edge1; + gt.currundo.swapped.edges[eid2] = edge2; + gt.endadd2undo (gt); + } +}; +dotty.protogt.removesubtree = function (gt, obj) { + local nlist, node, head, nid, edge, eid; + + if (~gt.noundo) + gt.startadd2undo (gt); + if (obj.nid >= 0) + node = obj; + else if (obj.eid >= 0) { + node = obj.head; + gt.removeedge (gt, obj); + if (~gt.graph.nodes[node.nid]) { + if (~gt.noundo) + gt.endadd2undo (gt); + return; + } + for (eid in node.edges) { + edge = node.edges[eid]; + if (edge.head == node & edge.tail ~= node) { + if (~gt.noundo) + gt.endadd2undo (gt); + return; + } + } + } else { + dotty.message (0, 'bad object type in gt.removesubtree'); + return; + } + nlist = [node.nid = node;]; + while (node) { + for (eid in node.edges) { + head = node.edges[eid].head; + if (head ~= node) + nlist[head.nid] = head; + } + gt.removenode (gt, node); + remove (node.nid, nlist); + node = null; + for (nid in nlist) { + node = nlist[nid]; + for (eid in node.edges) { + edge = node.edges[eid]; + if (edge.head == node & edge.tail ~= node) { + node = null; + break; + } + } + if (node) + break; + } + } + if (~gt.noundo) + gt.endadd2undo (gt); +}; +dotty.protogt.removenodesbyattr = function (gt, key, val) { + local nlist, nid; + + if (~gt.noundo) + gt.startadd2undo (gt); + nlist = gt.getnodesbyattr (gt, key, val); + for (nid in nlist) + gt.removenode (gt, nlist[nid]); + if (~gt.noundo) + gt.endadd2undo (gt); +}; +dotty.protogt.removesubtreesbyattr = function (gt, key, val) { + local nlist, nid; + + if (~gt.noundo) + gt.startadd2undo (gt); + nlist = gt.getnodesbyattr (gt, key, val); + for (nid in nlist) + if (gt.graph.nodes[nid]) + gt.removesubtree (gt, nlist[nid]); + if (~gt.noundo) + gt.endadd2undo (gt); +}; +dotty.protogt.groupnodes = function (gt, nlist, gnode, pos, size, attr, + keepmulti, show) { + local nid, node, elist, eid, edge, nodea, nodeb, inlist, outlist; + + if (~nlist | tablesize (nlist) == 0) + return; + if (gnode.attr.support) { + dotty.message (0, 'cannot group nodes in a support node'); + return; + } + if (~gt.noundo) + gt.startadd2undo (gt); + if (~gnode) + gnode = gt.insertnode (gt, pos, size, null, attr, show); + inlist = []; + outlist = []; + for (nid in nlist) { + if ((node = nlist[nid]) == gnode) + continue; + elist = []; + for (eid in node.edges) + elist[eid] = node.edges[eid]; + for (eid in elist) { + edge = elist[eid]; + if (edge.head == node) { + nodea = edge.tail; + nodeb = gnode; + if (~keepmulti) { + if (inlist[nodea.nid]) + continue; + inlist[nodea.nid] = nodea; + if (nodea == gnode) + outlist[nodea.nid] = nodea; + } + } else { + nodea = gnode; + nodeb = edge.head; + if (~keepmulti) { + if (outlist[nodeb.nid]) + continue; + outlist[nodeb.nid] = nodeb; + if (nodeb == gnode) + inlist[nodeb.nid] = nodeb; + } + } + gt.insertedge (gt, nodea, null, nodeb, null, edge.attr, show); + } + gt.removenode (gt, node); + } + if (~gt.noundo) + gt.endadd2undo (gt); + return gnode; +}; +dotty.protogt.groupnodesbyattr = + function (gt, key, val, attr, keepmulti, show) { + local nlist, nid, pos, size; + + pos = null; + size = null; + nlist = gt.getnodesbyattr (gt, key, val); + if (show) + for (nid in nlist) { + pos = nlist[nid].pos; + size = nlist[nid].size; + break; + } + return gt.groupnodes (gt, nlist, null, pos, size, attr, keepmulti, show); +}; +dotty.protogt.cut = function (gt, obj, set, mode, op) { + local clipgt, list, node, nid, edge, eid, clipnode; + + clipgt = dotty.clipgt; + clipgt.graph = copy (dotty.protogt.graph); + if (obj.eid >= 0) { # it's an edge + list.edges[obj.eid] = obj; + node = obj.head; + } else if (obj.nid >= 0) { + list.nodes[obj.nid] = obj; + node = obj; + for (eid in node.edges) + list.edges[eid] = node.edges[eid]; + } else { + dotty.message (0, 'unknown object type in gt.cut'); + return; + } + if (set == 'reachable') { + list.nodes = gt.reachablenodes (gt, node); + for (nid in list.nodes) { + node = list.nodes[nid]; + for (eid in node.edges) { + edge = node.edges[eid]; + list.edges[edge.eid] = edge; + } + } + } + if (mode == 'support') { + for (eid in list.edges) { + edge = list.edges[eid]; + if (~list.nodes[edge.tail.nid]) { + list.support[edge.tail.nid] = edge.tail; + list.nodes[edge.tail.nid] = edge.tail; + } + if (~list.nodes[edge.head.nid]) { + list.support[edge.head.nid] = edge.head; + list.nodes[edge.head.nid] = edge.head; + } + } + } + for (nid = 0; nid < gt.graph.maxnid; nid = nid + 1) { + if (~list.nodes[nid]) + continue; + node = list.nodes[nid]; + clipnode = gt.insertnode (clipgt, null, null, node.name, node.attr, 0); + if (list.support[nid]) + clipnode.support = 1; + list.clipnodes[nid] = clipnode; + } + for (eid = 0; eid < gt.graph.maxeid; eid = eid + 1) { + if (~list.edges[eid]) + continue; + edge = list.edges[eid]; + if (~list.nodes[edge.tail.nid] | ~list.nodes[edge.head.nid]) + continue; + gt.insertedge (clipgt, list.clipnodes[edge.tail.nid], null, + list.clipnodes[edge.head.nid], null, edge.attr, 0); + } + if (op ~= 'cut') + return; + if (~gt.noundo) + gt.startadd2undo (gt); + for (eid in list.edges) + gt.removeedge (gt, list.edges[eid]); + for (nid in list.nodes) + if (~list.support[nid] & gt.graph.nodes[nid]) + gt.removenode (gt, list.nodes[nid]); + if (~gt.noundo) + gt.endadd2undo (gt); +}; +dotty.protogt.paste = function (gt, pos, show) { + local clipgt, offset, center, nid, node, eid, edge, nodes; + + if (~gt.noundo) + gt.startadd2undo (gt); + clipgt = dotty.clipgt; + if (clipgt.graph.rect) + center = [ + 'x' = (clipgt.graph.rect[1].x + clipgt.graph.rect[0].x) / 2; + 'y' = (clipgt.graph.rect[1].y + clipgt.graph.rect[0].y) / 2; + ]; + else + center = pos; + offset = [ + 'x' = center.x - pos.x; + 'y' = center.y - pos.y; + ]; + for (nid = 0; clipgt.graph.nodes[nid]; nid = nid + 1) { + node = clipgt.graph.nodes[nid]; + if (node.attr.label == '\N' | ~node.attr.label) + node.attr.label = node.name; + if (node.support == 1) + nodes[nid] = gt.insertnode (gt, [ + 'x' = node.pos.x - offset.x; + 'y' = node.pos.y - offset.y; + ], null, null, [ + 'support' = 1; 'shape' = 'circle'; + 'label' = ''; 'width' = 0.2; + ], show); + else + nodes[nid] = gt.insertnode (gt, [ + 'x' = node.pos.x - offset.x; + 'y' = node.pos.y - offset.y; + ], node.size, null, node.attr, show); + } + for (eid = 0; clipgt.graph.edges[eid]; eid = eid + 1) { + edge = clipgt.graph.edges[eid]; + gt.insertedge (gt, nodes[edge.tail.nid], null, + nodes[edge.head.nid], null, edge.attr, show); + } + if (~gt.noundo) + gt.endadd2undo (gt); +}; +dotty.protogt.startadd2undo = function (gt) { + if (~gt.undoarray.level) + gt.currundo = + (gt.undoarray.entries[tablesize (gt.undoarray.entries)] = []); + gt.undoarray.level = gt.undoarray.level + 1; +}; +dotty.protogt.endadd2undo = function (gt) { + gt.undoarray.level = gt.undoarray.level - 1; +}; +dotty.protogt.undo = function (gt, show) { + local entry, n, eid, edge, nid, node, edges; + + if ((n = tablesize (gt.undoarray.entries)) < 1) + return; + entry = gt.undoarray.entries[n - 1]; + remove (n - 1, gt.undoarray.entries); + remove ('currundo', gt); + gt.noundo = 1; + # hardwire nodes and edges back with the same id's as the originals + for (nid in entry.deleted.nodes) { + node = entry.deleted.nodes[nid]; + gt.graph.nodedict[node.name] = node.nid; + gt.graph.nodes[node.nid] = node; + node.edges = []; + if (show) + gt.drawnode (gt, gt.views, node); + } + for (eid in entry.deleted.edges) { + edge = entry.deleted.edges[eid]; + gt.graph.edges[edge.eid] = edge; + edge.head.edges[edge.eid] = edge; + edge.tail.edges[edge.eid] = edge; + if (show) + gt.drawedge (gt, gt.views, edge); + } + if (entry.swapped.edges) { + if (tablesize (entry.swapped.edges) == 2) { + n = 0; + for (eid in entry.swapped.edges) { + edges[n] = entry.swapped.edges[eid]; + n = n + 1; + } + gt.swapedgeids (gt, edges[0], edges[1]); + } else + dotty.message (0, 'cannot handle undoing swap of > 2 edges'); + } + for (eid in entry.inserted.edges) { + edge = entry.inserted.edges[eid]; + gt.removeedge (gt, edge); + } + for (nid in entry.inserted.nodes) { + node = entry.inserted.nodes[nid]; + gt.removenode (gt, node); + } + gt.noundo = 0; +}; diff --git a/cmd/dotty/dotty_layout.lefty b/cmd/dotty/dotty_layout.lefty new file mode 100644 index 000000000..12c0ef972 --- /dev/null +++ b/cmd/dotty/dotty_layout.lefty @@ -0,0 +1,272 @@ +# +# dotty_layout: layout functions and data structures +# +dotty.grablserver = function (lserver) { + local fd; + + if (~dotty.lservers[lserver] | tablesize (dotty.lservers[lserver]) == 0) { + if (~((fd = openio ('pipe', lserver, 'r+')) >= 0)) { + dotty.message (0, concat ('cannot start ', lserver)); + return null; + } + dotty.lservers[lserver][fd] = [ + 'fd' = fd; + 'count' = 0; + ]; + } + for (fd in dotty.lservers[lserver]) { + dotty.lservers[lserver][fd].count = + dotty.lservers[lserver][fd].count + 1; + dotty.lservers.inuse[fd] = dotty.lservers[lserver][fd]; + remove (fd, dotty.lservers[lserver]); + return fd; + } +}; +dotty.releaselserver = function (lserver, fd, state) { + if (state == 'bad' | dotty.lservers.inuse[fd].count > 40) { + closeio (fd, 'kill'); + remove (fd, dotty.lservers.inuse); + return; + } + dotty.lservers[lserver][fd] = dotty.lservers.inuse[fd]; + remove (fd, dotty.lservers.inuse); +}; +dotty.protogt.startlayout = function (gt) { + local lpt, fd; + + if (gt.layoutpending >= 1) { + lpt = dotty.layoutpending[gt.gtid]; + if (gt.layoutmode == 'async') + monitor ('off', lpt.fd); + dotty.releaselserver (gt.lserver, lpt.fd, 'bad'); + remove (gt.gtid, dotty.layoutpending); + gt.layoutpending = 0; + gt.haveinput = 0; + dotty.popbusy (gt, gt.views); + } + if (~((fd = dotty.grablserver (gt.lserver)) >= 0)) + return null; + dotty.pushbusy (gt, gt.views); + writegraph (fd, gt.graph, 1); + gt.layoutpending = 1; + dotty.layoutpending[gt.gtid] = [ + 'fd' = fd; + 'gtid' = gt.gtid; + ]; + if (gt.layoutmode == 'async') + monitor ('on', fd); + return 1; +}; +dotty.protogt.finishlayout = function (gt) { + local graph, lpt, fd; + + if (~(gt.layoutpending >= 1)) { + dotty.message (0, concat ('no layout pending for graph ', gt.gtid)); + return null; + } + lpt = dotty.layoutpending[gt.gtid]; + if (~(graph = readgraph (lpt.fd))) { + if (gt.layoutmode == 'async') + monitor ('off', lpt.fd); + dotty.releaselserver (gt.lserver, lpt.fd, 'bad'); + if (gt.layoutpending == 2) { + dotty.message (0, concat ('giving up on ', gt.lserver)); + if ((fd = openio ('file', 'dottybug.dot', 'w+')) >= 0) { + writegraph (fd, gt.graph, 0); + closeio (fd); + dotty.message (0, + concat ('graph that causes ', gt.lserver)); + dotty.message (0, + 'to fail has been saved in file dottybug.dot'); + dotty.message (0, + 'please fill out a bug report at http://www.research.att.com/~erg/graphviz/bugform.html'); + } + dotty.popbusy (gt, gt.views); + gt.layoutpending = 0; + gt.haveinput = 0; + return 1; + } + dotty.message (1, + concat ('lost connection to ', gt.lserver, ', restarting...')); + lpt.fd = dotty.grablserver (gt.lserver); + writegraph (lpt.fd, gt.graph, 1); + if (gt.layoutmode == 'async') + monitor ('on', lpt.fd); + gt.layoutpending = 2; + gt.haveinput = 0; + return null; + } + if (gt.layoutmode == 'async') + monitor ('off', lpt.fd); + dotty.releaselserver (gt.lserver, lpt.fd, null); + remove (gt.gtid, dotty.layoutpending); + gt.layoutpending = 0; + gt.haveinput = 0; + gt.unpacklayout (gt, graph); + dotty.popbusy (gt, gt.views); + return 1; +}; +dotty.protogt.cancellayout = function (gt) { + local lpt, vid; + + if (gt.layoutpending >= 1) { + lpt = dotty.layoutpending[gt.gtid]; + if (gt.layoutmode == 'async') + monitor ('off', lpt.fd); + dotty.releaselserver (gt.lserver, lpt.fd, 'bad'); + remove (gt.gtid, dotty.layoutpending); + gt.layoutpending = 0; + gt.haveinput = 0; + dotty.popbusy (gt, gt.views); + } +}; +dotty.protogt.unpacklayout = function (gt, graph2) { + local graph, gid, sgraph1, sgraph2, nid, node1, node2; + local t1, t2, t3, n2, i, j, k, l, m, eid, edge1, edge2, points; + local pa1, pa2, pb1, pb2, la, lb; + + graph = gt.graph; + for (gid in graph2.graphdict) { + if (~(sgraph1 = graph.graphs[graph.graphdict[gid]])) + continue; + sgraph2 = graph2.graphs[graph2.graphdict[gid]]; + if (sgraph2.graphattr.bb & sgraph2.graphattr.bb ~= '') { + t1 = split (sgraph2.graphattr.bb, ','); + sgraph1.rect = [ + 0 = ['x' = ston (t1[0]); 'y' = ston (t1[1]);]; + 1 = ['x' = ston (t1[2]); 'y' = ston (t1[3]);]; + ]; + } else + sgraph1.rect = []; + if (sgraph2.graphattr.lp & sgraph2.graphattr.lp ~= '') { + t1 = split (sgraph2.graphattr.lp, ','); + sgraph1.lp = ['x' = ston (t1[0]); 'y' = ston (t1[1]);]; + } else + sgraph1.lp = []; + } + for (nid in graph2.nodedict) { + if (~(node1 = graph.nodes[graph.nodedict[nid]])) + continue; + node2 = graph2.nodes[graph2.nodedict[nid]]; + t1 = split (node2.attr.pos, ','); + node1.pos = ['x' = ston (t1[0]); 'y' = ston (t1[1]);]; + node1.size.x = ston (node2.attr.width) * 72; + node1.size.y = ston (node2.attr.height) * 72; + if (node2.attr.rects) + node1.fields = parsegraphlabel (node2.attr.label, node2.attr.rects); + } + for (eid in graph2.edges) { + edge2 = graph2.edges[eid]; + if (edge2.attr.id) { + if (~(edge1 = graph.edges[ston (edge2.attr.id)])) + continue; + } else if (graph == graph2) + edge1 = edge2; + if (edge2.attr.pos) { + points = []; + remove ('sp', edge1); + remove ('ep', edge1); + t2 = split (edge2.attr.pos, ';'); + for (k = 0; t2[k]; k = k + 1) { + t3 = split (t2[k], ' '); + n2 = tablesize (t3); + j = 0; + i = 0; + t1 = split (t3[0], ','); + while (t1[0] == 's' | t1[0] == 'e') { + if (t1[0] == 's') + edge1.sp = ['x' = ston (t1[1]); 'y' = ston (t1[2]);]; + else # (t1[0] == 'e') + edge1.ep = ['x' = ston (t1[1]); 'y' = ston (t1[2]);]; + i = i + 1; + t1 = split (t3[i], ','); + } + points[k][j] = ['x' = ston (t1[0]); 'y' = ston (t1[1]);]; + i = i + 1; + j = j + 1; + while (i < n2) { + t1 = split (t3[i], ','); + points[k][j] = ['x' = ston (t1[0]); 'y' = ston (t1[1]);]; + j = j + 1; + i = i + 1; + } + } + if (k > 1) { # concentrators + l = k; + while (l > 1) { + la = tablesize (points[0]); + pa1 = points[0][0]; + pa2 = points[0][la - 1]; + for (k = 1; points[k]; k = k + 1) { + lb = tablesize (points[k]); + pb1 = points[k][0]; + pb2 = points[k][lb - 1]; + if (pa1.x == pb2.x & pa1.y == pb2.y) { + for (m = 1; m < la; m = m + 1) { + points[k][lb] = points[0][m]; + lb = lb + 1; + } + points[0] = points[l - 1]; + remove (l - 1, points); + break; + } else if (pa2.x == pb1.x & pa2.y == pb1.y) { + for (m = 1; m < lb; m = m + 1) { + points[0][la] = points[k][m]; + la = la + 1; + } + points[k] = points[l - 1]; + remove (l - 1, points); + break; + } + } + if (points[l - 1]) { + dotty.message (1, 'failed to match edge points'); + break; + } + l = l - 1; + } + } + edge1.points = points[0]; + } + if (edge2.attr.lp) { + t1 = split (edge2.attr.lp, ','); + edge1.lp = ['x' = ston (t1[0]); 'y' = ston (t1[1]);]; + } + } + t1 = split (graph2.graphattr.bb, ','); + graph.rect[0].x = ston (t1[0]); + graph.rect[0].y = ston (t1[1]); + graph.rect[1].x = ston (t1[2]); + graph.rect[1].y = ston (t1[3]); + if (graph2.graphattr.lp & graph2.graphattr.lp ~= '') { + t1 = split (graph2.graphattr.lp, ','); + graph.lp = ['x' = ston (t1[0]); 'y' = ston (t1[1]);]; + } else + graph.lp = []; + if (gt.graph ~= graph2) + return; + # strip position and size info from the attributes + for (gid in graph2.graphdict) { + sgraph2 = graph2.graphs[graph2.graphdict[gid]]; + if (sgraph2.graphattr.bb) + remove ('bb', sgraph2.graphattr); + } + for (nid in graph2.nodedict) { + node2 = graph2.nodes[graph2.nodedict[nid]]; + if (node2.attr.rects) + remove ('rects', node2.attr); + remove ('pos', node2.attr); + remove ('width', node2.attr); + remove ('height', node2.attr); + } + for (eid in graph2.edges) { + edge2 = graph2.edges[eid]; + if (edge2.attr.pos) + remove ('pos', edge2.attr); + if (edge2.attr.lp) + remove ('lp', edge2.attr); + } + remove ('bb', graph2.graphattr); + if (graph2.graphattr.lp) + remove ('lp', graph2.graphattr); +}; diff --git a/cmd/dotty/dotty_ui.lefty b/cmd/dotty/dotty_ui.lefty new file mode 100644 index 000000000..7f6f9dae2 --- /dev/null +++ b/cmd/dotty/dotty_ui.lefty @@ -0,0 +1,434 @@ +# +# dotty_ui: user interface functions and data structures +# +dotty.protogt.doaction = function (data, s) { + local vt, gt; + + vt = dotty.views[data.widget]; + gt = dotty.graphs[vt.gtid]; + if (data.obj.nid >= 0) { + if (gt.actions.node[s]) { + gt.actions.node[s] (gt, vt, data.obj, data); + return; + } + } else if (data.obj.eid >= 0) { + if (gt.actions.edge[s]) { + gt.actions.edge[s] (gt, vt, data.obj, data); + return; + } + } + if (gt.actions.general[s]) + gt.actions.general[s] (gt, vt, data); +}; +dotty.protogt.actions.general = [ + "undo" = function (gt, vt, data) { + gt.undo (gt, 1); + }; + "paste" = function (gt, vt, data) { + gt.paste (gt, data.pos, 1); + }; + "do layout" = function (gt, vt, data) { + gt.layoutgraph (gt); + }; + "cancel layout" = function (gt, vt, data) { + gt.cancellayout (gt); + }; + "redraw" = function (gt, vt, data) { + gt.redrawgraph (gt, [vt.vtid = vt;]); + }; + "new graph" = function (gt, vt, data) { + gt.erasegraph (gt, null, null); + }; + "load graph" = function (gt, vt, data) { + gt.loadgraph (gt, null, 'file', dotty.protogt.graph, 1); + }; + "reload graph" = function (gt, vt, data) { + gt.loadgraph (gt, gt.name, gt.type, gt.graph, 1); + }; + "save graph" = function (gt, vt, data) { + gt.savegraph (gt, gt.name, gt.type, 1); + }; + "save graph as" = function (gt, vt, data) { + gt.savegraph (gt, null, 'file', 1); + }; + "open view" = function (gt, vt, data) { + gt = dotty.protogt.creategraph (null); + gt.createview (gt, null); + }; + "copy view" = function (gt, vt, data) { + gt = gt.copygraph (gt); + gt.createview (gt, vt); + }; + "birdseye view" = function (gt, vt, data) { + gt.createview (gt, dotty.protovt.birdseye); + }; + "clone view" = function (gt, vt, data) { + gt.createview (gt, vt); + }; + "close view" = function (gt, vt, data) { + gt.destroyview (gt, vt); + if (tablesize (gt.views) == 0) + gt.destroygraph (gt); + }; + "set graph attr" = function (gt, vt, data) { + gt.setattr (gt, ['attr' = gt.graph.graphattr;]); + }; + "set node attr" = function (gt, vt, data) { + gt.setattr (gt, ['attr' = gt.graph.nodeattr;]); + }; + "set edge attr" = function (gt, vt, data) { + gt.setattr (gt, ['attr' = gt.graph.edgeattr;]); + }; + "zoom in" = function (gt, vt, data) { + gt.zoom (gt, vt, 0.5, data.pos); + }; + "zoom out" = function (gt, vt, data) { + gt.zoom (gt, vt, 2, data.pos); + }; + "zoom in slowly" = function (gt, vt, data) { + gt.zoom (gt, vt, 0.9, data.pos); + }; + "zoom out slowly" = function (gt, vt, data) { + gt.zoom (gt, vt, 1.1, data.pos); + }; + "find node" = function (gt, vt, data) { + gt.findnode (gt, vt); + }; + "print graph" = function (gt, vt, data) { + gt.printorsave (gt, vt, null, null, null, null); + }; + "text view" = function (gt, vt, data) { + if (dotty.txtview == 'on') + dotty.txtview = 'off'; + else + dotty.txtview = 'on'; + txtview (dotty.txtview); + }; + "quit" = function (gt, vt, data) { + exit (); + }; +]; +dotty.protogt.actions.node = [ + "cut" = function (gt, vt, obj, data) { + gt.cut (gt, obj, 'one', 'support', 'cut'); + dotty.clipgt.layoutgraph (dotty.clipgt); + }; + "Cut" = function (gt, vt, obj, data) { + gt.cut (gt, obj, 'reachable', 'support', 'cut'); + dotty.clipgt.layoutgraph (dotty.clipgt); + }; + "copy" = function (gt, vt, obj, data) { + gt.cut (gt, obj, 'one', 'support', 'copy'); + dotty.clipgt.layoutgraph (dotty.clipgt); + }; + "Copy" = function (gt, vt, obj, data) { + gt.cut (gt, obj, 'reachable', 'support', 'copy'); + dotty.clipgt.layoutgraph (dotty.clipgt); + }; + "group" = function (gt, vt, obj, data) { + local kv; + + if ((kv = gt.getattr (gt, obj))) + gt.groupnodesbyattr (gt, kv.key, kv.val, [ + 'label' = kv.val; kv.key = kv.val; + ], 1, 1); + }; + "Group" = function (gt, vt, obj, data) { + local kv; + + if ((kv = gt.getattr (gt, obj))) + gt.groupnodesbyattr (gt, kv.key, kv.val, [ + 'label' = kv.val; kv.key = kv.val; + ], 0, 1); + }; + "delete" = function (gt, vt, obj, data) { + if (obj.eid >= 0) + gt.removeedge (gt, obj); + else + gt.removenode (gt, obj); + }; + "Delete" = function (gt, vt, obj, data) { + gt.removesubtree (gt, obj); + }; + "remove" = function (gt, vt, obj, data) { + if (obj.nid >= 0) + if ((kv = gt.getattr (gt, obj))) + gt.removenodesbyattr (gt, kv.key, kv.val); + }; + "Remove" = function (gt, vt, obj, data) { + if (obj.nid >= 0) + if ((kv = gt.getattr (gt, obj))) + gt.removesubtreesbyattr (gt, kv.key, kv.val); + }; + "set attr" = function (gt, vt, obj, data) { + gt.setattr (gt, obj); + }; + "print attr" = function (gt, vt, obj, data) { + if (obj.nid >= 0) + echo ('node: ', obj.name); + dump (obj.attr); + }; +]; +dotty.protogt.actions.edge = dotty.protogt.actions.node; +dotty.protovt.normal.menus = [ + 'general' = [ + 0 = "undo"; + 1 = "paste"; + 2 = "do layout"; + 3 = "cancel layout"; + 4 = "redraw"; + 5 = "new graph"; + 6 = "load graph"; + 7 = "reload graph"; + 8 = "save graph"; + 9 = "save graph as"; + 10 = "open view"; + 11 = "copy view"; + 12 = "clone view"; + 13 = "birdseye view"; + 14 = "close view"; + 15 = "set graph attr"; + 16 = "set node attr"; + 17 = "set edge attr"; + 18 = "zoom in"; + 19 = "zoom out"; + 20 = "find node"; + 21 = "print graph"; + 22 = "text view"; + 23 = "quit"; + ]; + 'node' = [ + 0 = "cut"; + 1 = "Cut"; + 2 = "copy"; + 3 = "Copy"; + 4 = "group"; + 5 = "Group"; + 6 = "delete"; + 7 = "Delete"; + 8 = "remove"; + 9 = "Remove"; + 10 = "set attr"; + 11 = "print attr"; + ]; + 'edge' = [ + 0 = "cut"; + 1 = "Cut"; + 2 = "copy"; + 3 = "Copy"; + 4 = "delete"; + 5 = "Delete"; + 6 = "set attr"; + 7 = "print attr"; + ]; +]; +dotty.protovt.normal.keys = [ + 'general' = [ + 'u' = "undo"; + 'p' = "paste"; + 'l' = "do layout"; + 'k' = "cancel layout"; + ' ' = "redraw"; + 'L' = "reload graph"; + 's' = "save graph"; + 'Z' = "zoom in slowly"; + 'z' = "zoom out slowly"; + ]; + 'node' = [ + 'c' = "copy"; + 'C' = "Copy"; + 'g' = "group"; + 'G' = "Group"; + 'd' = "delete"; + 'D' = "Delete"; + 'r' = "remove"; + 'R' = "Remove"; + 'a' = "set attr"; + ]; + 'edge' = [ + 'c' = "copy"; + 'C' = "Copy"; + 'd' = "delete"; + 'D' = "Delete"; + 'a' = "set attr"; + ]; +]; +dotty.protovt.birdseye.menus = dotty.protovt.normal.menus; +dotty.protovt.birdseye.keys = dotty.protovt.normal.keys; +dotty.protovt.normal.uifuncs = [ + 'leftdown' = function (data) { + local gt; + + gt = dotty.graphs[dotty.views[data.widget].gtid]; + if (data.obj.nid >= 0) { + dotty.node2move = data.obj; + dotty.movewidget = data.widget; + dotty.rp2 = data.pos; + } + }; + 'leftmove' = function (data) { + local gt; + + gt = dotty.graphs[dotty.views[data.widget].gtid]; + if (dotty.node2move & + (dotty.rp2.x ~= data.pos.x | dotty.rp2.y ~= data.pos.y)) { + gt.movenode (gt, dotty.node2move, data.pos); + dotty.rp2 = data.pos; + } + }; + 'leftup' = function (data) { + local gt; + + gt = dotty.graphs[dotty.views[data.widget].gtid]; + if (dotty.node2move) { + if (dotty.movewidget == data.widget) + gt.movenode (gt, dotty.node2move, data.pos); + dotty.node2move = 0; + } else if (~data.obj) + gt.insertnode (gt, data.pos, null, null, null, 1); + }; + 'middledown' = function (data) { + if (~(data.obj.nid >= 0)) + return; + dotty.rubberband = 1; + dotty.movewidget = data.widget; + setgfxattr (data.widget, ['mode' = 'xor';]); + dotty.rp1 = data.pos; + dotty.rp2 = data.pos; + line (data.widget, null, dotty.rp1, dotty.rp2, ['color' = 1;]); + }; + 'middlemove' = function (data) { + if (dotty.rubberband ~= 1 | + (dotty.rp2.x == data.pos.x & dotty.rp2.y == data.pos.y)) + return; + line (data.widget, null, dotty.rp1, dotty.rp2, ['color' = 1;]); + dotty.rp2 = data.pos; + line (data.widget, null, dotty.rp1, dotty.rp2, ['color' = 1;]); + }; + 'middleup' = function (data) { + local gt; + + gt = dotty.graphs[dotty.views[data.widget].gtid]; + if (dotty.rubberband ~= 1) + return; + dotty.rubberband = 0; + line (dotty.movewidget, null, dotty.rp1, dotty.rp2, ['color' = 1;]); + setgfxattr (dotty.movewidget, ['mode' = 'src';]); + if (dotty.movewidget ~= data.widget | + ~(data.pobj.nid >= 0) | ~(data.obj.nid >= 0)) + return; + if (data.pobj.attr.support) + gt.groupnodes (gt, [ + data.obj.nid = data.obj; + data.pobj.nid = data.pobj; + ], data.obj, null, null, null, 1, 1); + else if (data.obj.attr.support) + gt.groupnodes (gt, [ + data.obj.nid = data.obj; + data.pobj.nid = data.pobj; + ], data.pobj, null, null, null, 1, 1); + else + gt.insertedge (gt, data.pobj, null, data.obj, null, null, 1); + }; + 'rightdown' = function (data) { + local vt, gt, menu, i; + + vt = dotty.views[data.widget]; + gt = dotty.graphs[vt.gtid]; + if (~data.obj) + menu = vt.menus.general; + else if (data.obj.nid >= 0) + menu = vt.menus.node; + else if (data.obj.eid >= 0) + menu = vt.menus.edge; + if ((i = displaymenu (data.widget, menu)) >= 0) + gt.doaction (data, menu[i]); + }; + 'keyup' = function (data) { + local vt, gt, action; + + vt = dotty.views[data.widget]; + gt = dotty.graphs[vt.gtid]; + if (data.obj.nid >= 0) { + if (vt.keys.node[data.key]) + action = vt.keys.node[data.key]; + } else if (data.obj.eid >= 0) { + if (vt.keys.edge[data.key]) + action = vt.keys.edge[data.key]; + } + if (~action) + if (vt.keys.general[data.key]) + action = vt.keys.general[data.key]; + if (action) + gt.doaction (data, action); + }; + 'redraw' = function (data) { + local vt, gt; + + vt = dotty.views[data.widget]; + gt = dotty.graphs[vt.gtid]; + gt.drawgraph (gt, [vt.vtid = vt;]); + }; + 'closeview' = function (data) { + local vt, gt; + + vt = dotty.views[data.widget]; + gt = dotty.graphs[vt.gtid]; + gt.destroyview (gt, vt); + if (tablesize (gt.views) == 0) + gt.destroygraph (gt); + }; +]; +dotty.protovt.birdseye.uifuncs = [ + 'leftdown' = function (data) { + local gt, vid; + + gt = dotty.graphs[dotty.views[data.widget].gtid]; + for (vid in gt.views) { + vt = gt.views[vid]; + if (vt.type ~= 'birdseye') + gt.setviewcenter ([vid = vt;], data.pos); + } + }; + 'leftmove' = function (data) { + local gt, vid; + + gt = dotty.graphs[dotty.views[data.widget].gtid]; + for (vid in gt.views) { + vt = gt.views[vid]; + if (vt.type ~= 'birdseye') + gt.setviewcenter ([vid = vt;], data.pos); + } + }; + 'leftup' = function (data) { + local gt, vid; + + gt = dotty.graphs[dotty.views[data.widget].gtid]; + for (vid in gt.views) { + vt = gt.views[vid]; + if (vt.type ~= 'birdseye') + gt.setviewcenter ([vid = vt;], data.pos); + } + }; + 'middledown' = dotty.protovt.normal.uifuncs.middledown; + 'middlemove' = dotty.protovt.normal.uifuncs.middlemove; + 'middleup' = dotty.protovt.normal.uifuncs.middleup; + 'rightdown' = dotty.protovt.normal.uifuncs.rightdown; + 'keyup' = dotty.protovt.normal.uifuncs.keyup; + 'redraw' = dotty.protovt.normal.uifuncs.redraw; + 'closeview' = dotty.protovt.normal.uifuncs.closeview; +]; +dotty.monitorfile = function (data) { + local gtid, gt, lpt; + + for (gtid in dotty.layoutpending) { + lpt = dotty.layoutpending[gtid]; + if (lpt.fd == data.fd) { + gt = dotty.graphs[lpt.gtid]; + gt.haveinput = 1; + gt.layoutgraph (gt); + return 1; + } + } + return 0; +}; -- 2.40.0