Files
gzz-mirror/Java/flob/LevelView.zob
2026-09-14 20:19:29 -04:00

493 lines
16 KiB
Java

/*
LevelRaster.zob
*
* Copyright (c) 2000, Ted Nelson and Tuomas Lukka
*
* You may use and distribute under the terms of either the GNU Lesser
* General Public License, either version 2 of the license or,
* at your choice, any later version. Alternatively, you may use and
* distribute under the terms of the XPL.
*
* See the LICENSE.lgpl and LICENSE.xpl files for the specific terms of
* the licenses.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the README
* file for more details.
*
*/
/*
* Written by Benjamin Fallenstein
*/
package org.gzigzag;
import java.awt.*;
import java.util.*;
/** A raster for multiple-level structures.
* Interprets two dimensions as many-to-many relationship. Also interprets
* third dimension as one-to-one relationships which are shown the same way
* as the many-to-many relationships. For many-to-many relationships with
* link cells, shows the link cells.
*/
/* Some XXX'es:
- fix the damn occupiedbreadth bug!!!
- should move the parentlinks in the right place
- primitive actions for leveled visualization (including move)
- loose linking so that new links can be created? or: new link creates
new target, too? or maybe: in loose mode, links w/o target are shown as
that, links w/o target? yeah, way to go. (anyways, should new link also
create new target or not? pbly yes, eh?)
- gaps that shrink; think depthwise gapping through again
*/
public class LevelView implements FlobView, ZOb {
public static final String rcsid = "$Id$";
public static boolean dbg = false ;
static final void p(String s) { if(dbg) System.out.println(s); }
static final void pa(String s) { System.out.println(s); }
STRUCTPARAMS {
/** The size factor applied to cells. */
float initmul
= (float)1.6;
/** How many recursion levels shall we render (on both sides of the
* root)? */
int maxdepth
= 4;
/** The breadth distance between neigbouring cells. */
int bgap
= 15;
/** The depth distance between a cell and a link */
int dgap
= 10;
/** The factor of enlargement of cells as they move away
depthwise from the accursed cell. Element 0 is
horizontal, element 1 is vertical. */
float shrink
= (float)0.7;
/** The enlargement factor applied to link cells. Think linkshrink! */
float linkshrink
= (float)0.25;
/** Should the tree be shown "depthhorizontal", ie. depth rightwards? */
boolean depthhorizontal
= false;
/** Should the headcell be used on both dimensions?
* Normally on target dim, tailcell is used. */
boolean bothhead
= false;
/** Are links without targets interpreted as targets?
* If two elements are given, the first is used for upstream- and the
* second for downstream connections. */
boolean[] loose // 1..2
= new boolean[] { false, false };
}
INITIALIZE {
if(loose.length == 1)
loose = new boolean[] { loose[0], loose[0] };
}
DimVector[] dims;
Coordinates treegap; // gap converted to a Coordinates instance
Coordinates linkgap; // same with linkshrink applied
Coordinates[] cellsizes;
boolean looseup, loosedown; // XXX ???
// A "direction" in the ZZ space
static class DimVector {
String dim;
int dir;
DimVector rev; // The reverse vector
DimVector(String dim, int dir) {
this.dim = dim; this.dir = dir; rev = new DimVector(this);
}
DimVector(DimVector rev) { dim=rev.dim; dir=-rev.dir; this.rev=rev; }
ZZCell get(ZZCell c) { return c.s(dim, dir); }
ZZCell end(ZZCell c) { return c.h(dim, dir); }
ZZCell end(ZZCell c, boolean ensuremove) {
return c.h(dim, dir, ensuremove);
}
ZZCell[] rank(ZZCell c, boolean includeThis) {
return c.readRank(dim, dir, includeThis);
}
}
class Coordinates {
public int breadth;
public int depth;
public int x() { return depthhorizontal ? depth : breadth; }
public int y() { return depthhorizontal ? breadth : depth; }
public void set(int setx, int sety) {
if(depthhorizontal) {
depth = setx; breadth = sety;
} else {
breadth = setx; depth = sety;
}
}
Coordinates(Dimension d) { set(d.width, d.height); }
Coordinates(int b, int d) { breadth = b; depth = d; }
Coordinates(int i, int j, boolean treecoords) {
if(treecoords) { breadth = i; depth = j; }
else set(i, j);
}
Coordinates(Coordinates c, float fract) {
breadth = (int) (c.breadth * fract);
depth = (int) (c.depth * fract);
}
}
DimVector[] directeddims(boolean upwards) {
return new DimVector[] {
upwards ? dims[1].rev : dims[0],
upwards ? dims[0].rev : dims[1],
upwards ? dims[2].rev : dims[2]
};
}
// STEP 1: Tree generation. This may be overridden
// Note that "children" are cells away from root, and "parents" are
// cells towards root. Only the links to the parents are drawn.
CellInfo connectCell(ZZCell c, ZZCell linkc, ZZCell parentc,
int steps, boolean upwards) {
CellInfo r = new CellInfo(c, linkc, parentc, steps, upwards);
boolean more = steps < maxdepth - 1;
DimVector[] ds = directeddims(upwards);
DimVector linkdim = ds[0];
DimVector targetdim = ds[1];
DimVector tunneldim = ds[2];
ZZCell target, parent;
if((target=tunneldim.get(c)) != null && more)
r.childinfo.addElement(connectCell(target, null, c,
steps+1, upwards));
if((parent=tunneldim.rev.get(c)) != null)
r.parentlinks.addElement(parent);
ZZCell[] links = linkdim.rank(c, true);
for(int i=0; more && i<links.length; i++) {
target = targetdim.end(links[i], !loose[upwards ? 0 : 1]);
if(target == null) continue;
if(target == links[i])
r.childinfo.addElement(connectCell(target, null, c,
steps+1, upwards));
else
r.childinfo.addElement(connectCell(target, links[i], c,
steps+1, upwards));
}
if((parent=linkdim.rev.end(c)) != null && parent != c)
r.parentlinks.addElement(parent);
ZZCell[] parentlinks = targetdim.rev.rank(c, false);
for(int i=0; i<parentlinks.length; i++)
if(loose[upwards?1:0] || linkdim.rev.get(parentlinks[i]) != null)
r.parentlinks.addElement(parentlinks[i]);
return r;
}
class CellInfo {
// Stuff set in step 1 (tree generation)
ZZCell c, linkc, parentc; // Linkcell CAN be null
Vector childinfo = new Vector();
Vector parentlinks = new Vector(); // can include linkless parents
int steps; // from root
boolean upwards;
// Stuff set in step 2 (size & breadth computing)
Coordinates size;
int occupiedbreadth = 0;
CellInfo(ZZCell c, ZZCell linkc, ZZCell parentc, int steps,
boolean upwards) {
this.c = c; this.linkc = linkc; this.parentc = parentc;
this.steps = steps; this.upwards = upwards;
}
// STEP 2: Compute the size of cell and breadth of tree part
public int computesize() {
size = cellsizes[steps];
for(Enumeration e = childinfo.elements(); e.hasMoreElements();)
occupiedbreadth += ((CellInfo) e.nextElement()).computesize()
+ treegap.breadth;
if(occupiedbreadth < size.breadth)
occupiedbreadth = size.breadth;
return occupiedbreadth;
}
// STEP 3: Put the cell and its parentlinks into a continuum
public void putall(Continuum co, int bstart, int dstart) {
int bcoord=bstart, dcoord=dstart;
if(upwards) dcoord -= size.depth;
Coordinates pos = new Coordinates(
bcoord + occupiedbreadth/2 - size.breadth/2,
dcoord);
boolean newlypositioned = co.put(c, pos, size, steps, false);
if(parentc != null) {
if(upwards) co.putlink(c, linkc, parentc);
else co.putlink(parentc, linkc, c);
}
if(!newlypositioned && parentc != null) return;
int childdcoord = dcoord + treegap.depth * (upwards ? -1 : 1);
if(!upwards) childdcoord += size.depth;
bcoord += treegap.breadth / 2;
for(Enumeration e = childinfo.elements(); e.hasMoreElements();) {
CellInfo ci = (CellInfo)e.nextElement();
ci.putall(co, bcoord, childdcoord);
bcoord += ci.occupiedbreadth + treegap.breadth;
}
if(parentc == null) return;
int bmiddle = pos.breadth + size.breadth/2;
Coordinates linksize = new Coordinates(size, linkshrink);
int plnkbreadth = linkgap.breadth + linksize.breadth;
bcoord = bmiddle - (int)(plnkbreadth * parentlinks.size() / 2);
bcoord += linkgap.breadth / 2;
dcoord += linkgap.depth * (upwards ? 1 : -2);
dcoord += size.depth * (upwards ? 1 : 0);
for(Enumeration e = parentlinks.elements(); e.hasMoreElements();) {
Coordinates lpos = new Coordinates(bcoord, dcoord);
ZZCell plinkc = (ZZCell)e.nextElement();
if(co.put(plinkc, lpos, linksize, steps, true))
co.putsmalllink(c, plinkc, upwards);
bcoord += linksize.breadth + linkgap.breadth;
}
}
}
class Continuum {
class Link { ZZCell fromc, linkc, toc; }
// Information used for rastering
Hashtable posns = new Hashtable(); // POSitionNS
Hashtable sizes = new Hashtable();
Hashtable depths = new Hashtable();
Vector links = new Vector();
Hashtable linksmalls = new Hashtable();
// Information used only while generating the continuum
Hashtable smalls = new Hashtable();
Hashtable linkbigs = new Hashtable();
Coordinates getpos(ZZCell c) { return (Coordinates)posns.get(c); }
Coordinates getsize(ZZCell c) { return (Coordinates)sizes.get(c); }
int getdepth(ZZCell c) { return ((Integer)depths.get(c)).intValue(); }
// Return false if the cell has already been positioned
public boolean put(ZZCell c, Coordinates pos, Coordinates size,
int steps, boolean small) {
if(posns.containsKey(c) && (small || !smalls.containsKey(c)))
return false;
posns.put(c, pos);
sizes.put(c, size);
depths.put(c, new Integer(steps+1));
if(small) smalls.put(c, c);
return true;
}
public void putsmalllink(ZZCell fromc, ZZCell linkc, boolean up) {
if(linkbigs.containsKey(linkc)) return;
Link link = new Link(); link.linkc = linkc;
if(up) { link.fromc = fromc; link.toc = linkc; }
else { link.fromc = linkc; link.toc = fromc; }
linksmalls.put(linkc, link);
}
public void putlink(ZZCell fromc, ZZCell linkc, ZZCell toc) {
Link link = new Link();
link.fromc = fromc; link.linkc = linkc; link.toc = toc;
links.addElement(link);
if(linkc != null && linksmalls.containsKey(linkc))
linksmalls.remove(linkc);
if(linkc != null) linkbigs.put(linkc, link);
}
// STEP 4: Position link cells
public void positionlinkcells() {
for(Enumeration e = links.elements(); e.hasMoreElements();) {
Link link = (Link)e.nextElement();
if(link.linkc == null) continue; // only draw a line
int fdepth = getdepth(link.fromc);
int tdepth = getdepth(link.toc);
int depth = fdepth > tdepth ? fdepth : tdepth;
Coordinates size = new Coordinates(cellsizes[depth-1],
linkshrink);
Coordinates fpos = getpos(link.fromc);
Coordinates fsize = getsize(link.fromc);
Coordinates tpos = getpos(link.toc);
Coordinates tsize = getsize(link.toc);
Coordinates pos = new Coordinates(
(fpos.breadth + (fsize.breadth/2) +
tpos.breadth + (tsize.breadth/2) - size.breadth) / 2,
(fpos.depth + fsize.depth + tpos.depth - size.depth) / 2);
boolean succ = put(link.linkc, pos, size, depth, false);
p((succ ? "S" : "Uns")+"uccessfully positioned link cell: "+
link.linkc.getText());
}
}
// STEP 5: Raster the continuum into a flob set
public void raster(FlobSet into, FlobFactory fact, ZZCell view) {
for(Enumeration e = posns.keys(); e.hasMoreElements();) {
ZZCell c = (ZZCell)e.nextElement();
Coordinates pos = getpos(c);
Coordinates size = getsize(c);
int depth = getdepth(c);
fact.makeFlob(into, c, c, 1, pos.x(), pos.y(), depth,
size.x(), size.y());
}
for(Enumeration e = linksmalls.elements(); e.hasMoreElements();) {
Link link = (Link)e.nextElement();
putlink(link.fromc, null, link.toc);
}
for(Enumeration e = links.elements(); e.hasMoreElements();) {
Link link = (Link)e.nextElement();
int fdepth = getdepth(link.fromc);
int tdepth = getdepth(link.toc);
int depth = fdepth > tdepth ? fdepth : tdepth;
Coordinates fpos = getpos(link.fromc);
Coordinates fsize = getsize(link.fromc);
Coordinates tpos = getpos(link.toc);
Coordinates tsize = getsize(link.toc);
Coordinates from = new Coordinates(
fpos.breadth + fsize.breadth/2, fpos.depth + fsize.depth);
Coordinates to = new Coordinates(
tpos.breadth + tsize.breadth/2, tpos.depth);
into.add(new LineDecor(from.x(), from.y(), to.x(), to.y(),
Color.red, depth));
}
}
}
public void initdims(String[] dims) {
this.dims = new DimVector[] {
new DimVector(dims[0], 1),
new DimVector(dims[1], bothhead ? -1 : 1),
new DimVector(dims[2], 1),
};
}
public ZZCell look(ZZCell c, int x, int y) {
Coordinates co = new Coordinates(x, y, false);
DimVector d=null, t=null;
if(co.depth == 0)
return null;
else if(co.depth < 0) {
d = dims[0];
t = dims[3];
} else {
d = dims[1].rev;
t = dims[3].rev;
}
if(co.breadth < 0)
return d.get(c);
else if(co.breadth > 0)
return d.end(c, true);
else {
if(t.get(c) != null) return t.get(c);
ZZCell[] rank = d.rank(c, false);
if(rank.length == 0) return null;
return rank[rank.length / 2];
}
}
public void raster(FlobSet into, FlobFactory fact,
ZZCell view, String[] dims, ZZCell c) {
Dimension cs = fact.getSize(null, initmul);
cellsizes = new Coordinates[maxdepth];
cellsizes[0] = new Coordinates(cs);
for(int i=1; i<maxdepth; i++)
cellsizes[i] = new Coordinates(cellsizes[i-1], shrink);
treegap = new Coordinates(bgap, cellsizes[0].depth + dgap*4);
linkgap = new Coordinates(bgap/4, dgap);
Coordinates fs = new Coordinates(into.getSize());
p("Prepared canvas size");
initdims(dims);
DimVector linkdim = this.dims[0], targetdim = this.dims[1],
tunneldim = this.dims[2];
p("Prepared DimVectors");
CellInfo uproot, downroot;
boolean islink;
// CALL 1: Tree generation. This may be overridden
p("Starting LevelRaster call one");
if(linkdim.rev.get(c) != null && targetdim.get(c) != null) {
islink = true;
uproot = connectCell(linkdim.rev.end(c), null, c, 1, true);
downroot = connectCell(targetdim.end(c), null, c, 1, false);
} else {
islink = false;
uproot = connectCell(c, null, null, 0, true);
downroot = connectCell(c, null, null, 0, false);
}
// CALL 2: Compute the size of cell and breadth of tree part
p("Starting LevelRaster call two");
uproot.computesize(); downroot.computesize();
// CALL 3: Put the cell and its parentlinks into a continuum
p("Starting LevelRaster call three");
Continuum co = new Continuum();
int upbstart = (fs.breadth - uproot.occupiedbreadth) / 2;
int downbstart = (fs.breadth - downroot.occupiedbreadth) / 2;
int updstart = (fs.depth + cellsizes[0].depth) / 2;
int downdstart = (fs.depth - cellsizes[0].depth) / 2;
if(islink) {
co.put(c, new Coordinates((fs.breadth - cellsizes[0].breadth) / 2,
downdstart), cellsizes[0],
0, false);
updstart -= treegap.depth/4*3; //+ cellsizes[0].depth;
downdstart += treegap.depth/4*3;
}
downroot.putall(co, downbstart, downdstart);
uproot.putall(co, upbstart, updstart);
// CALL 4: Position link cells
p("Starting LevelRaster call four");
co.positionlinkcells();
// CALL 5: Raster the continuum into a flob set
p("Starting LevelRaster call five");
co.raster(into, fact, view);
}
}
// vim: set syntax=java :