Files
secondo/Javagui/viewer/TreeViewer.java
2026-01-23 17:03:45 +08:00

1432 lines
41 KiB
Java

//This file is part of SECONDO.
//Copyright (C) 2004, University in Hagen, Department of Computer Science,
//Database Systems for New Applications.
//SECONDO is free software; you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation; either version 2 of the License, or
//(at your option) any later version.
//SECONDO 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
//GNU General Public License for more details.
//You should have received a copy of the GNU General Public License
//along with SECONDO; if not, write to the Free Software
//Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package viewer;
import java.awt.geom.*;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import sj.lang.ListExpr;
import java.util.*;
import gui.SecondoObject;
import gui.Environment;
import java.io.*;
import tools.Reporter;
interface PNode{
void computeBounds(Graphics2D g);
Rectangle2D getBounds();
void paint(Graphics2D g);
boolean saveAsLatex(File f);
}
/** This class provides a datastructure for representing a Tree.
* Using the paint method we can draw the tree on a graphic context.
*/
class TreeNode implements PNode{
/** Reads the Label of this Node from a nested list atom
*
**/
private boolean readLabel(ListExpr LE){
int type = LE.atomType();
if(type==ListExpr.INT_ATOM){
Label = ""+LE.intValue();
return true;
}
if(type==ListExpr.SYMBOL_ATOM){
Label = LE.symbolValue();
return true;
}
if(type==ListExpr.REAL_ATOM){
Label = ""+LE.realValue();
return true;
}
if(type== ListExpr.STRING_ATOM){
Label = LE.stringValue();
return true;
}
if(type==ListExpr.TEXT_ATOM){
Label = LE.textValue();
return true;
}
return false;
}
/** reads this (sub) tree from a nested list.
* The return value descibes the sucess.
* If the reading is unsuccessful, the node will be
* a single leaf labeled with "Error".
*/
public boolean readFrom(ListExpr LE){
if(LE.atomType()!=ListExpr.NO_ATOM){ // a leaf
if(!readLabel(LE)){
Reporter.debug("Error in reading Label 1");
Label = "Error";
sons = null;
return false;
}
return true;
}
// now we have a list descibing a tree in the from (root subtree_1 .. subtree_n)
// empty lists are not allowed here
if(LE.listLength()==0){
Reporter.debug("Empty lists are not allowed as trees");
Label = "Error";
sons = null;
return false;
}
ListExpr Root = LE.first(); // the root must be only a label
if(!readLabel(Root)){
Reporter.debug("The root must be an atom");
Label = "Error";
sons = null;
return false;
}
ListExpr Sons;
if(LE.listLength()==2){ // sons are wrapped in a list
Sons = LE.second();
} else{ // without a enclosing list
Sons = LE.rest();
}
if(Sons.atomType()!=ListExpr.NO_ATOM){ // a single leaf
sons = new TreeNode[1];
sons[0] = new TreeNode("",null);
sons[0].readFrom(Sons);
}else{ // a list of sons
if(Sons.isEmpty()){ // non sons in list
sons = null;
return true;
} else{ // a proper list of sons
sons = new TreeNode[Sons.listLength()];
for(int i=0;i<sons.length;i++){
sons[i] = new TreeNode("",null);
if(!sons[i].readFrom(Sons.first())){ // error in reading son
sons = null;
Label = "Error";
return false;
}
Sons = Sons.rest();
}
}
}
return true;
}
boolean readFromPMType(ListExpr value){
if(value.listLength()!=2){
Reporter.debug("wrong list length");
return false;
}
ListExpr subtype = value.first();
String st = null;
if(subtype.atomType()!=ListExpr.SYMBOL_ATOM){ // an usual submove
Reporter.debug("Invalid list for the submove" + subtype );
return false;
}
st = subtype.symbolValue();
if(TreeViewer.isPMType(subtype)){ // the total move
value = value.second();
if(value.listLength()!=2){ // Instant SubMove
Reporter.debug("Invalid Length");
return false;
}
Double time = viewer.hoese.LEUtils.readInstant(value.first());
if(time==null){
Reporter.debug("invalid representation of the start time");
return false;
} else {
Label = st + " " + viewer.hoese.LEUtils.convertTimeToString(time.doubleValue());
System.out.println("MyLabel = " + Label);
sons = new TreeNode[1];
sons[0] = new TreeNode("",null);
return sons[0].readFromPMType(value.second());
}
}
if(st.equals("linear")){
Label = "L";
sons = null;
return true;
}
if(st.equals("period")){
System.out.println("Period found");
value = value.second();
int len = value.listLength();
if(len!=2 && len!=4){ // (reps submove)
Reporter.debug("period:invalid length "+len);
return false;
}
ListExpr r = value.first();
if(r.atomType()!=ListExpr.INT_ATOM){
Reporter.debug("invalid type for repetitions");
return false;
}
Label = "P["+r.intValue()+"]";
sons = new TreeNode[1];
sons[0] = new TreeNode("",null);
if(len==2){
return sons[0].readFromPMType(value.second());
} else{
return sons[0].readFromPMType(value.fourth());
}
}
if(st.equals("composite")){
value = value.second();
int len = value.listLength();
Label = "C";
sons = new TreeNode[len];
for(int i=0;i<len;i++){
sons[i] = new TreeNode("",null);
if(!sons[i].readFromPMType(value.first())){
return false;
}
value = value.rest();
}
return true;
}
return false;
}
/** Sets the Label and the Sons of this tree. **/
TreeNode(String Label, TreeNode[] Sons){
this.Label = Label;
this.sons=Sons;
if(!arrowDone)
computeArrow();
}
/** This function fixes the form of the arrow for connections.
**/
void computeArrow(){
GeneralPath gparrow = new GeneralPath();
gparrow.moveTo(0,0);
gparrow.lineTo(5,-1);
gparrow.lineTo(3,0);
gparrow.lineTo(5,1);
gparrow.lineTo(0,0);
arrow = new Area(gparrow);
arrowDone = true;
}
/** This function computes the bounding box required for the
* whole tree in the given context.
* The bounding box will start at (0,0); only width and height
* are computed.
**/
public void computeBounds(Graphics2D g){
// the initial is the bounding box of the label
StringTokenizer ST = new StringTokenizer(Label,"\n");
Rectangle2D nodebounds = new Rectangle2D.Double();
int lineno = 0;
while(ST.hasMoreTokens()){
String Line = ST.nextToken();
Rectangle2D currentBounds = g.getFontMetrics().getStringBounds(Line,g);
currentBounds.setRect(0,0,-currentBounds.getX()+currentBounds.getWidth(),
-currentBounds.getY()+currentBounds.getHeight());
if(lineno==0)
nodebounds=currentBounds;
else{
nodebounds.setRect(0,0,Math.max(nodebounds.getWidth(),currentBounds.getWidth()),
nodebounds.getHeight()+currentBounds.getHeight());
}
lineno++;
}
// we add a border of 5 pixels
nodebounds.setRect(0,0,-nodebounds.getX()+nodebounds.getWidth()+10,
-nodebounds.getY()+nodebounds.getHeight());
if(sons==null || sons.length==0)
bounds = nodebounds;
else{
// compute the bounds of all sons
for(int i=0;i<sons.length;i++)
sons[i].computeBounds(g);
// build the union of the bounds of the sons when drawing left to right
bounds = sons[0].getBounds();
for(int i=1;i<sons.length;i++){ // compute bounds of the subtree
Rectangle2D nextBound = sons[i].getBounds();
bounds.setRect(0,0,
bounds.getWidth()+nodesep+nextBound.getWidth(),
Math.max(bounds.getHeight(),nextBound.getHeight()));
}
// store the width of the sons
sonswidth = bounds.getWidth();
// add the own bound
bounds.setRect(0,0,
Math.max(bounds.getWidth(),nodebounds.getWidth()), // maximum of own and son bounds
bounds.getHeight()+levelsep+nodebounds.getHeight());
// bounds of sons + line + own bound
}
}
/** returns the bounds computed at the last run of computeBounds */
public Rectangle2D getBounds(){
return bounds;
}
/**
Paints this tree on the given Graphics object
The tree is painted from (0,0). The size depends on the
used fontsize.
*/
public void paint(Graphics2D g){
paint(g,0,0);
}
/**
* Paints this tree from the given position.
* The bounds of the tree has to been computed already.
*/
private void paint(Graphics g1,double deltax, double deltay){
Graphics2D g = (Graphics2D) g1;
computeBounds( g);
// compute the bounds for this node and move it to (0,0)
StringTokenizer ST = new StringTokenizer(Label,"\n");
Rectangle2D nodeBounds = new Rectangle2D.Double();
int lineno = 0;
while(ST.hasMoreTokens()){
String Line = ST.nextToken();
Rectangle2D currentBounds = g.getFontMetrics().getStringBounds(Line,g);
currentBounds.setRect(0,0,-currentBounds.getX()+currentBounds.getWidth(),
-currentBounds.getY()+currentBounds.getHeight());
if(lineno==0)
nodeBounds=currentBounds;
else{
nodeBounds.setRect(0,0,Math.max(nodeBounds.getWidth(),currentBounds.getWidth()),
nodeBounds.getHeight()+currentBounds.getHeight());
}
lineno++;
}
// we add a border of 5 pixels
nodeBounds.setRect(0,0,-nodeBounds.getX()+nodeBounds.getWidth()+10,
-nodeBounds.getY()+nodeBounds.getHeight());
// first paint the subtrees
double sdx=deltax;
double nextLevel = deltay + levelsep+nodeBounds.getHeight();
// paint itself
double xpos = (bounds.getWidth()/2 - nodeBounds.getWidth()/2+deltax);
double ypos = deltay;
// draw the bounding rectangle of this node itself
nodeBounds.setRect(xpos,ypos,nodeBounds.getWidth(),nodeBounds.getHeight());
g.draw(nodeBounds);
// draw the label
ST = new StringTokenizer(Label,"\n");
double lypos = ypos;
double lxpos;
while(ST.hasMoreTokens()){
String Line = ST.nextToken();
Rectangle2D currentBounds = g.getFontMetrics().getStringBounds(Line,g);
currentBounds.setRect(0,0,-currentBounds.getX()+currentBounds.getWidth(),
-currentBounds.getY()+currentBounds.getHeight());
lxpos = (nodeBounds.getWidth()-currentBounds.getWidth())/2+xpos;
g.drawString(Line,(int)lxpos,(int)(lypos-7+currentBounds.getHeight()));
lypos = lypos+currentBounds.getHeight();
}
//g.drawString(Label,(int)xpos+5,(int)(ypos-7+nodeBounds.getHeight()));
// compute the position for connections
xpos = bounds.getWidth()/2;
ypos = deltay+nodeBounds.getHeight();
// paint the sons and connections to it
double adddeltax = 0.0;
if(sons!=null){
if(sonswidth<nodeBounds.getWidth()){
adddeltax = (nodeBounds.getWidth()-sonswidth)/(2.0);
sdx += adddeltax;
}
}
if(sons!=null){
for(int i=0;i<sons.length;i++){
// paint son
sons[i].paint(g,sdx,nextLevel);
// paint connection
double x1 = (nodeBounds.getX()+nodeBounds.getWidth()/2);
double y1 = (nodeBounds.getY()+nodeBounds.getHeight()+0.0);
double x2 = sdx+sons[i].getBounds().getWidth()/2;
double y2 = nextLevel;
Line2D.Double L = new Line2D.Double(x1,y1,x2,y2);
g.draw(L);
// transform the arrow to be in the correct angle at the end of the connection
AffineTransform aat = new AffineTransform();
aat.setToTranslation(x2,y2);
// normalize
double dx = x1-x2;
double dy = y1-y2;
double len = Math.sqrt(dx*dx+dy*dy); // the length
dx = dx / len;
dy = dy / len;
AffineTransform Rot = new AffineTransform(dx,dy,-dy,dx,0,0);
aat.concatenate(Rot);
Shape S = aat.createTransformedShape(arrow);
// paint the arrow
g.fill(S);
// compute the x position for the next son
sdx = sdx + (sons[i].getBounds().getWidth())+nodesep;
}
}
}
/** Save this tree to F in LaTex format.
* The output goes wrong when label contain any latex
* commands
**/
public boolean saveAsLatex(File F){
if(F.exists())
return false;
try{
PrintWriter out = new PrintWriter(new FileWriter(F));
// print latex header
out.println("\\documentclass{article}");
out.println("\\usepackage{pst-tree,pst-eps}");
out.println("\\pagestyle{empty}");
out.println("\\begin{document}");
out.println("\\thispagestyle{empty}");
out.println("\\TeXtoEPS%");
printRecTo(out);
out.println("\\endTeXtoEPS");
out.println("\\end{document}");
out.close();
}catch(Exception e){
Reporter.debug(e);
return false;
}
return true;
}
/** Supports saveAsLatex **/
private void printRecTo(PrintWriter out) {
// this is a tree
if(sons!=null)
out.println("{\\pstree[levelsep=1cm,treesep=0.5cm,edge=\\ncline{->}]%");
// save label as root
out.println("{ \\Tr{\\psframebox[framearc=0.5]{"+Label+"}}}%");
// perform all sons
if(sons!=null){
out.println("{%");
for(int i=0;i<sons.length;i++){
if(sons[i]!=null){
sons[i].printRecTo(out);
}
}
out.println("}}%"); // close sons and this tree
}
}
String Label;
TreeNode[] sons;
double sonswidth =0;
Rectangle2D bounds=null; // bounds for this node and all subtrees
static int nodesep = 10; // distance between nodes
static int levelsep = 20; // distance between levels
static Shape arrow;
static boolean arrowDone= false;
}
class BTreeNode implements PNode{
private String[] labels;
private BTreeNode[] sons;
private boolean isLeaf;
boolean defined;
boolean empty;
Rectangle2D bounds;
private static final int vDist = 30;
private static final int hDist = 5;
private static final int nodeHeight = 15;
public boolean saveAsLatex(File f){
Reporter.showError("not implemented for a btree");
return false;
}
public BTreeNode(){
defined = false;
labels = null;
sons = null;
bounds = null;
}
public int getHeight(){
if(!defined){
return 0;
} else {
int h = 0;
BTreeNode n = this;
while(!n.isLeaf){
h++;
n = n.sons[0];
}
return h;
}
}
private int getWidth(Graphics2D g){
return getWidth(determineEntryWidth(g.getFontMetrics()));
}
private int getWidth(int entryWidth){
if(isLeaf){
int sep = 10;
int w = (entryWidth + sep )* labels.length;
return w;
} else {
int x1 = 0;
for(int i=0;i<sons.length;i++){
int sw = sons[i].getWidth(entryWidth);
x1 += hDist + sw;
}
x1 -= hDist;
return x1;
}
}
public Rectangle2D getBounds(){
return bounds;
}
public void computeBounds(Graphics2D g){
if(empty){
bounds = new Rectangle2D.Double(0,0,10,10);
return;
}
int h = getHeight();
h = h*(nodeHeight+vDist) + nodeHeight;
bounds = new Rectangle2D.Double(0,0,getWidth(g),h);
}
/** The list must be formated as (l1, l2, ...l_n ( s_1 ... s_n+1)
* where l1...l_n are of type string or symbol or int
*/
public boolean readFrom(ListExpr list,int level){
if(list.atomType()!=ListExpr.NO_ATOM){
System.out.println(" not a list");
return false;
}
int len = list.listLength();
if(len ==0 && level==0){
// an empty tree
defined = true;
empty = true;
return true;
}
empty = false;
if(len!=1 && len != 2){
System.out.println("wrong length len = " + len);
return false;
}
int len1 = list.first().listLength();
isLeaf = len==1;
int len2 = 0;
if(!isLeaf){
len2 = list.second().listLength();
if(len2<1){
return false; // sons must be inside a list
}
sons = new BTreeNode[len2];
if(len1 != len2-1){
System.out.println("wrong length len1 = " + len1 + " len2=" + len2);
return false;
}
}
if(len1 < 0){ // error not an list for the entries
return false;
}
// read the entries
labels = new String[len1];
ListExpr rest = list.first();
ListExpr first;
for(int i=0;i<len1; i++){
first = rest.first();
rest = rest.rest();
switch(first.atomType()){
case ListExpr.INT_ATOM : labels[i] = "" + first.intValue();break;
case ListExpr.STRING_ATOM : labels[i] = first.stringValue(); break;
case ListExpr.SYMBOL_ATOM : labels[i] = first.symbolValue(); break;
default: labels = null; sons = null; return false;
}
}
if(isLeaf){
defined = true;
return true;
} else {
rest = list.second();
for(int i=0;i<len2;i++){
first = rest.first();
rest = rest.rest();
sons[i] = new BTreeNode();
if(!sons[i].readFrom(first,level+1)){
labels = null;
sons = null;
return false;
}
}
defined = true;
return true;
}
}
private int determineEntryWidth(FontMetrics fm){
int last = isLeaf?labels.length:labels.length-1;
int max = 0;
for(int i=0;i<last;i++){
int d = fm.stringWidth(labels[i]);
if(d>max){
max = d;
}
}
if(!isLeaf){
for(int i=0;i<sons.length;i++){
int d = sons[i].determineEntryWidth(fm);
if(d>max){
max = d;
}
}
}
return max;
}
public void paint(Graphics2D g){
if(!defined){
g.drawString("not defined",10,10);
return;
} else if(empty){
g.drawRect(0,0,10,10);
g.drawLine(0,0,10,10);
g.drawLine(0,10,10,0);
} else {
int entryWidth = determineEntryWidth(g.getFontMetrics());
paintRec(g, 0,0, entryWidth);
}
}
private static void drawStringAt(Graphics2D g, String s, int x, int y){
int w = g.getFontMetrics().stringWidth(s);
g.drawString(s, x - w/2 , y);
}
int paintRec(Graphics2D g, int x, int y, int entryWidth){
if(isLeaf){
int sep = 10;
int w = (entryWidth + sep )* labels.length;
// draw a rectangle
g.drawRect(x,y, w, nodeHeight);
int xl = 0;
int xs = 0;
// draw the entries and separators
for(int i=0; i < labels.length;i++){
xl = x + i * (entryWidth+sep) + sep/2 + entryWidth/2;
drawStringAt(g,labels[i], xl , y + nodeHeight -2);
if(i < labels.length-1){
xs = x + (i+1) *(entryWidth+sep) ;
g.drawLine(xs, y , xs, y + nodeHeight);
}
}
return w;
} else {
int sep = 14;
int w = (entryWidth + sep)* labels.length + sep -sep/3;
// paint sons, store middles
Vector v = new Vector();
int x1 = x;
for(int i=0;i<sons.length;i++){
int sw = sons[i].paintRec(g,x1, y + nodeHeight + vDist, entryWidth);
v.add(new Integer(x1 + sw/2));
x1 += hDist + sw;
}
x1 -= hDist;
// paint box
int xs = (x+x1)/2 - w / 2;
g.drawRect(xs,y, w, nodeHeight);
// paint labels , separators and connections to the sons
// paint first separator:
int y1 = y+nodeHeight;
g.drawLine(xs+sep/2,y, xs+sep/2,y + nodeHeight);
g.drawLine(xs+sep/4,y1,
((Integer)v.get(0)).intValue(),y+nodeHeight+vDist );
for(int i=0; i < labels.length;i++){
int xl = xs + sep + entryWidth/2 + i*(entryWidth + sep);
drawStringAt(g,labels[i], xl, y+nodeHeight -2);
int x2 = xs + (i+1)*(entryWidth+sep) + sep /2;
g.drawLine(x2-sep/3, y , x2-sep/3, y + nodeHeight);
if(i<labels.length-1){
g.drawLine(x2+sep/3, y , x2+sep/3, y + nodeHeight);
}
g.drawLine(x2, y1 , ((Integer)v.get(i+1)).intValue(), y+nodeHeight+vDist);
}
return x1-x;
}
}
}
enum NodeType{Pointer, Object, IndirectObject, Operator,Unknown };
class OpTreeNode implements PNode{
public OpTreeNode(){
bounds = new Rectangle2D.Double(0,0,100,100);
sons = new Vector<OpTreeNode>();
label = "--";
}
public void computeBounds(Graphics2D g) {
bounds = new Rectangle2D.Double(0,0, getWidth(g)+10, getHeight(g)+10);
}
int getWidth(Graphics2D g){
int myWidth = g.getFontMetrics().stringWidth(label) + 20;
int sonWidth = 0;
for(int i=0;i<sons.size();i++){
sonWidth += sons.get(i).getWidth(g) + nodeSep;
}
return Math.max(myWidth, sonWidth);
}
int getHeight(Graphics2D g){
if(sons.size()==0){
return nodeHeight;
}
int sh = 0;
for(int i=0;i<sons.size();i++){
sh = Math.max(sh,sons.get(i).getHeight(g));
}
return sh + vSep + nodeHeight;
}
public Rectangle2D getBounds(){
return bounds;
}
public void paint(Graphics2D g) {
paint(g,0,0);
}
private int paint(Graphics2D g, int x, int y){
int w = g.getFontMetrics().stringWidth(label) + 20;
int dy = (nodeHeight - g.getFontMetrics().getHeight()) ;
if(sons.size()==0){
g.drawRect(x,y,w,nodeHeight);
g.drawString(label, x+10, y+nodeHeight - dy);
return w;
}
int sw = 0;
Vector<Integer> sws = new Vector<Integer>();
// draw Subtrees
for(int i=0; i < sons.size();i++){
int k = sons.get(i).paint(g, sw + x , y + nodeHeight + vSep);
sws.add(new Integer(k));
sw = sw + k + nodeSep;
}
sw-=nodeSep;
// paint node itself
int ux;
int uy;
int dx = Math.max(0, sw - w)/2;
g.drawRect(x+dx,y,w,nodeHeight);
g.drawString(label, x+dx+10, y+nodeHeight-dy);
ux = x + dx + w/2;
uy = y + nodeHeight;
// draw connections to sons
int sy = y + nodeHeight + vSep;
int startX = x;
for(int i=0;i<sws.size();i++){
int sx = startX + sws.get(i).intValue() / 2;
g.drawLine(ux,uy,sx,sy);
startX = startX + sws.get(i).intValue() + nodeSep;
}
return Math.max(sw,w);
}
public boolean saveAsLatex(File f){
return false;
}
public boolean readFromString(String treeString){
ListExpr treeList = new ListExpr();
if(treeList.readFromString(treeString)!=0){
return false;
}
return readFromList(treeList,true);
}
private boolean isSymbol(ListExpr sym, String v){
if(sym.atomType()!=ListExpr.SYMBOL_ATOM){
return false;
} else {
return sym.symbolValue().equals(v);
}
}
public boolean readFromList(ListExpr treeList, boolean isRoot){
ListExpr value;
if(isRoot){
if(treeList.listLength()!=2){
return false;
}
if(!isSymbol(treeList.first(),"optree")){
return false;
}
value = treeList.second();
} else {
value = treeList;
}
if(value.listLength()!=2){
return false;
}
if(!readLabel(value.first())){
return false;
}
ListExpr sons = value.second();
while(!sons.isEmpty()){
OpTreeNode otn = new OpTreeNode();
if(!otn.readFromList(sons.first(),false)){
return false;
}
this.sons.add(otn);
sons = sons.rest();
}
return true;
}
private NodeType getNodeType(ListExpr t){
if(isSymbol(t,"Pointer")) return NodeType.Pointer;
if(isSymbol(t,"Object")) return NodeType.Object;
if(isSymbol(t,"IndirectObject")) return NodeType.IndirectObject;
if(isSymbol(t,"Operator")) return NodeType.Operator;
return NodeType.Unknown;
}
private String getProp(ListExpr props, String name){
while(!props.isEmpty()){
ListExpr prop = props.first();
props = props.rest();
if( (prop.listLength()==2) &&
(isSymbol(prop.first(),name))){
if(prop.second().isEmpty()){
return "--";
}
return ""+prop.second();
}
}
return "?"+name+"?";
}
private boolean readLabel(ListExpr root){
if(root.listLength()!=2){
return false;
}
NodeType nodeType = getNodeType(root.first());
if(nodeType==NodeType.Unknown){
return false;
}
ListExpr prop = root.second();
switch(nodeType){
case Pointer: label = "Pointer"; break;
case Object: label = getProp(prop,"symbol");
if(label.equals("--")){
label = getProp(prop,"typeExpr");
String isConst = getProp(prop,"isConstant");
if(isConst.trim().equals("TRUE")){
label = "const " + label;
}
}
break;
case IndirectObject: label = "indirectObject"; break;
case Operator: label = getProp(prop,"opName"); break;
default : label = "--";
}
return true;
}
private Rectangle2D.Double bounds;
private Vector<OpTreeNode> sons;
private String label;
private static final int nodeSep = 10;
private static final int nodeHeight = 20;
private static final int vSep = 20;
}
/** This class provides a panel drawing a single tree.
* They are possibilities for magnifying or moving the drawing.
*/
class TreePainterPanel extends JPanel{
TreePainterPanel(){
super();
// add a mouselistener for magnifying the picture
addMouseListener(new MouseAdapter(){
public void mouseClicked(MouseEvent evt){
int x = evt.getX();
int y = evt.getY();
int Bt = evt.getButton();
Dimension D = getSize();
if(Bt==MouseEvent.BUTTON1){ // left button = zoom in
double sc = scalefactor;
tmpAT.setTransform(scalefactor,0,0,scalefactor,
-x*scalefactor+D.getWidth()/2,
-y*scalefactor+D.getHeight()/2);
AT.preConcatenate(tmpAT);
repaint();
}
if(Bt==MouseEvent.BUTTON3){ // right button = zoom out
double sc = 1/scalefactor;
tmpAT.setTransform(sc,0,0,sc,
-x*sc+D.getWidth()/2,
-y*sc+D.getHeight()/2);
AT.preConcatenate(tmpAT);
repaint();
}
if(Bt==MouseEvent.BUTTON2){ // middle button = go to the first view
firstPaint=true;
repaint();
}
}
});
// change the background to be white
setOpaque(true);
setBackground(Color.WHITE);
}
/**
Fits the tree to the available size
*/
public void fit(){
firstPaint=true;
repaint();
}
/** Zooms the current drawing by scale factor around the middle of the screen.
*/
public void zoomIn(){
double sc = scalefactor;
Dimension D = getSize();
double x = D.getWidth()/2;
double y = D.getHeight()/2;
tmpAT.setTransform(scalefactor,0,0,scalefactor,
-x*scalefactor+D.getWidth()/2,
-y*scalefactor+D.getHeight()/2);
AT.preConcatenate(tmpAT);
repaint();
}
/** Zooms out the current drawing */
public void zoomOut(){
double sc = 1/scalefactor;
Dimension D = getSize();
double x = D.getWidth()/2;
double y = D.getHeight()/2;
tmpAT.setTransform(sc,0,0,sc,
-x*sc+D.getWidth()/2,
-y*sc+D.getHeight()/2);
AT.preConcatenate(tmpAT);
repaint();
}
/** paints the contained tree **/
public void paint(Graphics g1){
Graphics2D g = (Graphics2D) g1;
g.setBackground(Color.WHITE);
super.paint(g1);
if(Tree==null){
String S1 = "Secondo";
String S2 = "FernUni Hagen";
Rectangle2D BB = g.getFontMetrics().getStringBounds(S1,g);
BB.setRect(0,0,-BB.getX()+BB.getWidth(),(-BB.getY()+BB.getHeight()));
double sf = Math.min( (getSize().getWidth()-20)/BB.getWidth(),
((getSize().getHeight()/2)-20)/BB.getHeight());
AffineTransform OldTransform = g.getTransform();
AffineTransform A1 = new AffineTransform();
A1.setToIdentity();
A1.setToScale(sf,sf);
g.transform(A1);
Color OldColor = g.getColor();
g.setColor(Color.RED);
g.drawString(S1,(int)(10/sf), (int)(getSize().getHeight() / (3*sf) ));
g.setColor(Color.BLUE);
g.setTransform(OldTransform);
BB = g.getFontMetrics().getStringBounds(S2,g);
BB.setRect(0,0,-BB.getX()+BB.getWidth(),(-BB.getY()+BB.getHeight()));
g.rotate(0.5);
g.scale(sf/2,sf/2);
g.drawString(S2,(int)(20/sf+BB.getWidth()),
(int)(-BB.getHeight()+getSize().getHeight() / (1.5*sf) ));
g.setColor(OldColor);
return;
}
g.setColor(Color.BLACK);
// in the first paint we center the tree, possible fitting to the window
if(firstPaint){
Dimension D = getSize();
Tree.computeBounds(g);
TreeBounds=Tree.getBounds();
if(D!=null){
if(fit){ // move to center and fit to window
double sc = Math.min((D.getWidth()-30)/TreeBounds.getWidth(),
(D.getHeight()-20)/TreeBounds.getHeight());
double x = TreeBounds.getWidth()/2;
double y = TreeBounds.getHeight()/2;
AT.setTransform(sc,0,0,sc,
-x*sc+D.getWidth()/2,
-y*sc+D.getHeight()/2);
}
else{ // move to center
AT.setToTranslation( (D.getWidth()-TreeBounds.getWidth())/2,
(D.getHeight()-TreeBounds.getHeight())/2);
}
firstPaint=false;
}
}
g.setTransform(AT);
// set borderwidth to 1 undepending on the zoom factor
g.setStroke(new BasicStroke((float) (1/AT.getScaleX())));
if(Tree!=null){
Tree.paint(g);
}
}
/* move the drawing , values are in pixels*/
void moveXY(int X,int Y){
AffineTransform A = new AffineTransform();
A.setToTranslation(X,Y);
AT.preConcatenate(A);
repaint();
}
// sets the tree to paint
void setTree(PNode Tree){
this.Tree=Tree;
AT.setToIdentity();
firstPaint=true;
repaint();
}
// the used affine transformation
AffineTransform AT=new AffineTransform();
// temporal affine transformation to avoid creation of new objects
AffineTransform tmpAT = new AffineTransform();
// scale for one single click
double scalefactor = 1.5;
// the Tree to paint
PNode Tree=null;
// the bounding box of this tree
Rectangle2D TreeBounds=null;
// paint without manual zoomung
boolean firstPaint=true;
// fit to window
boolean fit=true;
// Dimension for last paint
Dimension lastDim = null;
}
/** This class is the Viewer for Trees */
public class TreeViewer extends SecondoViewer{
private void down(){
int d = (int) (TPP.getSize().getHeight() / 3);
TPP.moveXY(0,-d);
}
private void up(){
int d = (int) (TPP.getSize().getHeight() / 3);
TPP.moveXY(0,d);
}
private void left(){
int d = (int) (TPP.getSize().getWidth()/3);
TPP.moveXY(d,0);
}
private void right(){
int d = (int) (TPP.getSize().getWidth()/3);
TPP.moveXY(-d,0);
}
private static void addKeyListenerRec(KeyListener KL, Component C){
C.addKeyListener(KL);
if(C instanceof Container){
Container F = (Container) C;
Component[] Comps = F.getComponents();
for(int i=0;i<Comps.length;i++)
addKeyListenerRec(KL,Comps[i]);
}
}
/** Creates a new Instance of this Viewer **/
public TreeViewer(){
setLayout(new BorderLayout());
add(ChoiceBox,BorderLayout.NORTH);
add(TPP,BorderLayout.CENTER);
JPanel ControlPanel = new JPanel();
ControlPanel.add(SaveBtn);
ControlPanel.add(EPSButton);
ControlPanel.add(FitBtn);
ControlPanel.add(ZoomInBtn);
ControlPanel.add(ZoomOutBtn);
ControlPanel.add(new JLabel(" ")); // distance
ControlPanel.add(LeftBtn);
ControlPanel.add(UpBtn);
ControlPanel.add(DownBtn);
ControlPanel.add(RightBtn);
add(ControlPanel,BorderLayout.SOUTH);
ActionListener AL = new ActionListener(){
public void actionPerformed(ActionEvent evt){
Object o = evt.getSource();
if(o.equals(DownBtn)){
down();
}else if(o.equals(UpBtn)){
up();
}else if(o.equals(LeftBtn)){
left();
}else if(o.equals(RightBtn)){
right();
}else if(o.equals(FitBtn)){
TPP.fit();
}else if(o.equals(ZoomInBtn)){
TPP.zoomIn();
}else if(o.equals(ZoomOutBtn)){
TPP.zoomOut();
}
}
};
SaveBtn.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent evt){
if(TPP.Tree==null){
Reporter.showError("no tree present");
}
JFileChooser FC = new JFileChooser();
if(FC.showSaveDialog(null)==JFileChooser.APPROVE_OPTION){
File F = FC.getSelectedFile();
if(F.exists()){
Reporter.showError("File "+ F.getName()+" alreday exists");
return;
}
if(!TPP.Tree.saveAsLatex(F)){
Reporter.showError("error occured");
} else{
Reporter.showInfo("tree stored");
}
}
}
});
EPSButton.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent evt){
JFileChooser FC = new JFileChooser();
if(FC.showSaveDialog(null)==JFileChooser.APPROVE_OPTION){
File f = FC.getSelectedFile();
if( f.exists() &&
(Reporter.showQuestion("File exists\n Overwrite it ?")!=Reporter.YES)){
return;
}
if(!extern.psexport.PSCreator.export(TPP,f)){
Reporter.showError("Error in eps export");
}
}
}
});
UpBtn.addActionListener(AL);
DownBtn.addActionListener(AL);
LeftBtn.addActionListener(AL);
RightBtn.addActionListener(AL);
FitBtn.addActionListener(AL);
ZoomInBtn.addActionListener(AL);
ZoomOutBtn.addActionListener(AL);
ChoiceBox.addItemListener(new ItemListener(){
public void itemStateChanged(ItemEvent evt){
int i = ChoiceBox.getSelectedIndex();
if(i<0){
TPP.setTree(null);
return;
}
TPP.setTree((PNode)Trees.get(i));
}
});
KeyListener KL = new KeyAdapter(){
public void keyPressed(KeyEvent evt){
if(evt.getKeyCode()==KeyEvent.VK_L && (evt.getModifiers() & KeyEvent.CTRL_MASK)>0){
TPP.repaint();
} else
if(evt.getKeyCode()==KeyEvent.VK_F && (evt.getModifiers() & KeyEvent.CTRL_MASK)>0){
TPP.fit();
} else
if(evt.getKeyCode()==KeyEvent.VK_PLUS && (evt.getModifiers() & KeyEvent.CTRL_MASK)>0){
TPP.zoomIn();
} else
if(evt.getKeyCode()==KeyEvent.VK_MINUS && (evt.getModifiers() & KeyEvent.CTRL_MASK)>0){
TPP.zoomOut();
} else
if (evt.getKeyCode()==KeyEvent.VK_LEFT){
int d = (int) TPP.getSize().getWidth()/3;
TPP.moveXY(-d,0);
} else
if (evt.getKeyCode()==KeyEvent.VK_RIGHT){
int d = (int) TPP.getSize().getWidth()/3;
TPP.moveXY(d,0);
} else
if (evt.getKeyCode()==KeyEvent.VK_UP){
int d = (int) TPP.getSize().getHeight()/3;
TPP.moveXY(0,-d);
} else
if (evt.getKeyCode()==KeyEvent.VK_DOWN){
int d = (int) TPP.getSize().getWidth()/3;
TPP.moveXY(0,d);
}
}
};
addKeyListenerRec(KL,this);
}
/** gets the name of this Viewer **/
public String getName(){ return "TreeViewer";}
/** add an object to this viewer **/
public boolean addObject(SecondoObject o){
if(!canDisplay(o)){
return false;
}
ListExpr type = o.toListExpr().first();
ListExpr LE = o.toListExpr().second(); // we need only the value
PNode pN;
if(type.atomType()==ListExpr.SYMBOL_ATOM &&
(type.symbolValue().equals("tree") || isPMType(type))){
TreeNode N = new TreeNode("",null);
if(isPMType(type)){
if(!N.readFromPMType(o.toListExpr())){
return false;
}
} else if(!N.readFrom(LE)){ // error in valueList
return false;
}
pN = N;
} else if(type.atomType()==ListExpr.SYMBOL_ATOM &&
(type.symbolValue().equals("text") )){
OpTreeNode op = new OpTreeNode();
if(!op.readFromString(LE.textValue())){
return false;
}
pN = op;
} else { // b-tree
BTreeNode bN = new BTreeNode();
if(!bN.readFrom(LE,0)){
return false;
}
pN = bN;
}
// values are correct
Trees.add(pN);
SObjects.add(o);
ChoiceBox.addItem(o.getName());
ChoiceBox.setSelectedIndex(Trees.size()-1);
requestFocus();
return true;
}
/** returns true if the type represents a periodic moving type **/
static boolean isPMType(ListExpr list){
if(list.atomType()!=ListExpr.SYMBOL_ATOM){
return false;
}
String v = list.symbolValue();
return v.equals("pmpoint") ||
v.equals("pmreal"); // extend if needed
}
/** removes an given object */
public void removeObject(SecondoObject o){
int index = SObjects.indexOf(o);
if(index < 0)
return;
SObjects.removeElementAt(index);
Trees.removeElementAt(index);
ChoiceBox.removeItemAt(index);
if(SObjects.size()==0){
TPP.setTree(null);
}
}
public void removeAll(){
SObjects.clear();
Trees.clear();
ChoiceBox.removeAll();
TPP.setTree(null);
}
public boolean canDisplay(SecondoObject o){
ListExpr LE = o.toListExpr();
if(LE.listLength()!=2){ // not an secondo object (type value)
return false;
}
ListExpr value = LE.second();
LE = LE.first(); // get the type
// check for typed btree (btree (rel (...)) Attrname )
if(LE.atomType()==ListExpr.NO_ATOM){
if( LE.listLength()==3 &&
LE.first().atomType()==ListExpr.SYMBOL_ATOM &&
LE.first().symbolValue().equals("btree")){
return true;
}
}
if(LE.atomType()!=ListExpr.SYMBOL_ATOM){
return false;
}
if(!LE.symbolValue().equals("tree") && !isPMType(LE) && !LE.symbolValue().equals("btree") && !LE.symbolValue().equals("text")){
return false;
}
if(LE.symbolValue().equals("text")){
return value.textValue().startsWith("(optree");
}
return true;
}
public boolean isDisplayed(SecondoObject o){
return SObjects.indexOf(o)>=0;
}
public boolean selectObject(SecondoObject o){
int index = SObjects.indexOf(o);
if(index <0)
return false;
ChoiceBox.setSelectedIndex(index);
return true;
}
public MenuVector getMenuVector(){
return null;
}
public double getDisplayQuality(SecondoObject o){
if(canDisplay(o))
return 0.8;
return 0;
}
private JComboBox ChoiceBox = new JComboBox();
private JButton SaveBtn= new JButton("LaTeX");
private JButton EPSButton = new JButton("EPS");
private JButton UpBtn=new JButton("^");
private JButton DownBtn = new JButton("v");
private JButton LeftBtn = new JButton("<");;
private JButton RightBtn = new JButton(">");
private JButton ZoomInBtn = new JButton("Zoom In");
private JButton ZoomOutBtn = new JButton("Zoom Out");
private JButton FitBtn = new JButton("Fit");
private Vector SObjects = new Vector(10);
private Vector Trees = new Vector(10);
TreePainterPanel TPP = new TreePainterPanel();
}