# # 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')); };