package de.unisiegen.tpml.graphics.bigstep; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Point; import java.awt.Rectangle; import java.util.Enumeration; import javax.swing.Scrollable; import javax.swing.SwingUtilities; import javax.swing.event.TreeModelEvent; import de.unisiegen.tpml.core.ProofGuessException; import de.unisiegen.tpml.core.ProofNode; import de.unisiegen.tpml.core.bigstep.BigStepProofModel; import de.unisiegen.tpml.core.bigstep.BigStepProofNode; import de.unisiegen.tpml.graphics.AbstractProofComponent; import de.unisiegen.tpml.graphics.renderer.EnvironmentRenderer; import de.unisiegen.tpml.graphics.renderer.PrettyStringRenderer; import de.unisiegen.tpml.graphics.renderer.TreeArrowRenderer; import de.unisiegen.tpml.graphics.tree.TreeNodeLayout; /** * Implementation of the graphical representation of the BigStep-Interpreter. * <br> * The following image shows a usual look of a part of an BigStepComponent. * It contains a few nodes of the origin expression: * <code>let rec f = lambda x. if x = 0 then 1 else x * f (x-1) in f 3</code><br> * <img src="../../../../../../images/bigstep.png" /><br> * <br> * The entire placing of the nodes is done within the method * {@link #relayout()} but actualy the layouting is passed over * to the {@link de.unisiegen.tpml.graphics.tree.TreeNodeLayout} * to place the nodes. <br> * <br> * The lines and arrows of the tree are rendered using the * {@link de.unisiegen.tpml.graphics.renderer.TreeArrowRenderer}, * so all nodes within the tree implement the * {@link de.unisiegen.tpml.graphics.tree.TreeNodeComponent} interface.<br> * <br> * The nodes are not stored directly in the <i>BigStepComponent</i>, they are * stored using the <i>Userobject</i> provided by the {@link de.unisiegen.tpml.core.ProofNode}.<br> * Everytime the content of the tree changes ({@link #treeContentChanged()} is called) the * {@link #checkForUserObject(BigStepProofNode)}-method is called. This causes a recursive traversing * of the entire tree to check if every node has its corresponding * {@link de.unisiegen.tpml.graphics.bigstep.BigStepNodeComponent}.<br> * <br> * When nodes get removed only the userobject of that nodes needs to get release.<br> * When nodes get inserted, the first of them is stored in the {@link #jumpNode} so the * next time the component gets layouted the {@link #jumpToNodeVisible()}-method is called * and the scrollview of the {@link de.unisiegen.tpml.graphics.bigstep.BigStepView} * scrolls to a place the stored node gets visible. * * @see de.unisiegen.tpml.graphics.bigstep.BigStepView * @see de.unisiegen.tpml.graphics.bigstep.BigStepNodeComponent * @see de.unisiegen.tpml.graphics.tree.TreeNodeLayout * @see de.unisiegen.tpml.graphics.renderer.TreeArrowRenderer * @author marcell * */ public class BigStepComponent extends AbstractProofComponent implements Scrollable, Cloneable { /** * */ private static final long serialVersionUID = 3793854335585017325L; /** * Handles the layouting of the BigStepNodeComponents to a tree. */ private TreeNodeLayout treeNodeLayout; /** * When checking all nodes within the tree, this <i>index</i> is used to * count them top to bottom. */ private int index; /** * Contains the <i>ProofNode</i> where to scroll. <br> * When new nodes get inserted, the first of those nodes is * assigned to the jumpNode. When the layout is changing (that * is always happening when nodes get inserted) the <i>BigStepComponent</i> * scrolls itself, so that the <i>jumpNode</i> is visible. */ private ProofNode jumpNode; /** * The border around the <i>BigStepComponent</i> in pixels.<br> * This are 20 pixels per default. */ private int border; /** * Constructor.<br> * <br> * The first <i>treeContentChanged</i> is called manualy at the end of * the constructor to get started. * * @param model The model the <i>BigStepComponent</i> should visualise. */ public BigStepComponent (BigStepProofModel model) { super (model); this.availableHeight = Integer.MAX_VALUE; this.treeNodeLayout = new TreeNodeLayout (); this.border = 20; this.jumpNode = null; setLayout(null); // initialy the tree content has changed treeContentChanged(); } /** * Sets the spacing of the layout. * * @param spacing The spacing. */ public void setSpacing (int spacing) { this.treeNodeLayout.setSpacing(spacing); } /** * Guesses the first unprooven node within the tree. * * @throws IllegalStateException * @throws ProofGuessException */ public void guess () throws IllegalStateException, ProofGuessException { Enumeration<ProofNode> enumeration = this.proofModel.getRoot().postorderEnumeration(); while (enumeration.hasMoreElements()) { ProofNode node = enumeration.nextElement(); if (!node.isProven()) { this.proofModel.guess(node); return; } } throw new IllegalStateException("Unable to find next node"); } /** * Checks all nodes below the given node for an UserObject.<br> * All <i>BigStepProofNode</i>s are provided with an UserObject. * The UserObject is the <i>BigStepNodeComponent</i>, that is the actual * node the BigStepGUI works with.<br> * <br> * This function goes recursive over the tree and checks every <i>BigStepProofNode</i> * whether an userObject is already present. If none is there, a new one created.<br> * <br> * Here the nodes get there index value. * * @param node The node which tree should be checked. One should only give the rootNode here. */ private void checkForUserObject (BigStepProofNode node) { if (node == null) { return; } BigStepNodeComponent nodeComponent = (BigStepNodeComponent)node.getUserObject(); if (nodeComponent == null) { // if the node has no userobject it may be new in the // tree, so a new TypeCheckerNodeComponent will be created // and added to the TypeCheckerProofNode nodeComponent = new BigStepNodeComponent (node, (BigStepProofModel)this.proofModel, this.translator); node.setUserObject(nodeComponent); // the newly created nodeComponent is a gui-element so // it needs to get added to the gui add (nodeComponent); // when the node changes the gui needs to get updated nodeComponent.addBigStepNodeListener(new BigStepNodeListener () { public void nodeChanged (BigStepNodeComponent pNode) { BigStepComponent.this.relayout(); } public void requestJumpToNode (ProofNode pNode) { BigStepComponent.this.setJumpNode(pNode); } }); } // set the index value for the node nodeComponent.setIndex(this.index); ++this.index; // the typechecker is a real tree, so its needed to proceed with all // children of the node for (int i=0; i<node.getChildCount(); i++) { BigStepProofNode pNode = node.getChildAt(i); checkForUserObject (pNode); } } /** * Causes all userobject from all nodes to reset the layout.<br> * <br> * Resetting means that every {@link PrettyStringRenderer} and * {@link EnvironmentRenderer} recalculates their needed font sizes. */ private void resetUserObject (BigStepProofNode node) { if (node == null) { return; } BigStepNodeComponent nodeComponent = (BigStepNodeComponent)node.getUserObject(); if (nodeComponent == null) { return; } nodeComponent.reset (); for (int i=0; i<node.getChildCount (); i++) { BigStepProofNode pNode = node.getChildAt(i); resetUserObject (pNode); } } /* * Implementation of the AbstractProofComponent interface */ /** * Assigns the first, newly inserted node to the jumpNode. When * the next relayout is called, the <i>BigStepComponent</i> is able to * scroll to a propper position.<br> * <br> * Reimplementation of the {@link AbstractProofComponent#nodesInserted(TreeModelEvent)} * Method.<br> * * @param event */ @Override protected void nodesInserted(TreeModelEvent event) { if (this.jumpNode != null) { return; } Object [] children = event.getChildren(); // find the bottom and the top element that have been // inserted. when getting next to the relayout function // it gets tried to scroll this area visible if (children != null) { // only problem with this could occure when // then children[0] element isn't the topmost element // in the tree that has been inserted. at this condition // that behaviour is undefined this.jumpNode = (ProofNode)children [0]; } else { this.jumpNode = null; } } /** * Delegates the nodesChanged Events.<br> * <br> * Reimplementation of the {@link AbstractProofComponent#nodesChanged(TreeModelEvent)} * Method.<br> * <br> * The userobject of all <i>ProofNodes</i> that have changed * is informed about the change. * * @param event The TreeModelEvent */ @Override protected void nodesChanged(TreeModelEvent event) { Object[] children = event.getChildren(); if (children == null) { // if the children are null and the path only contains one element // this element is the root node. if (event.getPath().length == 1) { BigStepProofNode proofNode = (BigStepProofNode)event.getPath()[0]; BigStepNodeComponent nodeComponent = (BigStepNodeComponent)proofNode.getUserObject(); if (nodeComponent != null) { nodeComponent.changeNode (); } } return; } for (int i=0; i<children.length; i++) { if (children[i] instanceof ProofNode) { BigStepProofNode proofNode = (BigStepProofNode)children[i]; BigStepNodeComponent nodeComponent = (BigStepNodeComponent)proofNode.getUserObject(); if (nodeComponent != null) { nodeComponent.changeNode (); } } } } /** * Remove the userobjects from the nodes that have been removed.<br> * <br> * Reimplementation of the {@link AbstractProofComponent#nodesRemoved(TreeModelEvent)}. * Method.<br> * <br> * All nodes that have been removed within the origin <i>ProofTree</i> * from the <i>ProoModel</i> still need to get rid of thire useObject * that represents the graphical part of the node.<br> * <br> * All those <i>SmallStepNodeComponent</i> objects, that are * userobjects from each node, will be removed from this JComponent and * will be detached from the <i>ProofNode</i>. * * @param event The TreeModelEvent */ @Override protected void nodesRemoved(TreeModelEvent event) { Object[] children = event.getChildren(); if (children == null) { return; } for (int i=0; i<children.length; i++) { if (children[i] instanceof ProofNode) { BigStepProofNode proofNode = (BigStepProofNode)children[i]; BigStepNodeComponent nodeComponent = (BigStepNodeComponent)proofNode.getUserObject(); if (nodeComponent != null) { remove (nodeComponent); proofNode.setUserObject(null); } } } } /** * Checks if all nodes in the tree have an useobject and riggers a relayout.<br> * <br> * Reimplementation of the {@link AbstractProofComponent#treeContentChanged()} * Method.<br> */ @Override protected void treeContentChanged() { BigStepProofNode rootNode = (BigStepProofNode)this.proofModel.getRoot(); // initiate the index this.index = 1; checkForUserObject(rootNode); relayout (); } /** * Does the layouting of the tree.<br> * <br> * Reimplementation of the {@link AbstractProofComponent#relayout()} Method.<br> * <br> * The actual placement of the nodes is done by the {@link TreeNodeLayout. * The <i>TreeNodeLayout</i> returns the bottom-right-point of the entire * layout. Size position widened by the border used to determine the size * of the component.<br> * Right after setting the size of the component the jump to a possible new * node takes place. (See {@link #jumpToNodeVisible()})<br> * */ @Override protected void relayout() { if (this.currentlyLayouting) { return; } this.currentlyLayouting = true; SwingUtilities.invokeLater(new Runnable() { public void run () { doRelayout(); } }); } /** *{@inheritDoc} * */ @Override protected void forcedRelayout() { doRelayout(); } /** * dose a relayout */ protected void doRelayout() { BigStepProofNode rootNode = (BigStepProofNode)BigStepComponent.this.getProofModel().getRoot(); Point rightBottomPos = BigStepComponent.this.getTreeNodeLayout().placeNodes(rootNode, BigStepComponent.this.getThisBorder(), BigStepComponent.this.getThisBorder(), BigStepComponent.this.getAvailableWidth(), BigStepComponent.this.getAvailableHeight()); // Worzu brauchen wird das? // lets add some border to the space // rightBottomPos.x += BigStepComponent.this.border; // rightBottomPos.y += BigStepComponent.this.border; Dimension size = new Dimension (rightBottomPos.x, rightBottomPos.y); // set all the sizes needed by the component setMaximumSize (size); setMinimumSize (size); setPreferredSize (size); setSize (size); BigStepComponent.this.setCurrentlyLayouting(false); BigStepComponent.this.jumpToNodeVisible(); } /** * @return the border */ protected int getThisBorder() { return this.border; } /** * Causes every {@link PrettyStringRenderer} and * {@link EnvironmentRenderer} to recalculate thier * layout. */ @Override protected void resetLayout () { // apply the reset on the root node resetUserObject((BigStepProofNode)this.proofModel.getRoot()); } /** * Renders the decoration of the BigStepComponent.<br> * <br> * Reimplementation of {@link javax.swing.JComponent#paint(java.awt.Graphics)} * Method.<br> * <br> * Simple fills the entire background with white color and * lets the {@link TreeArrowRenderer#renderArrows(ProofNode, int, Graphics)} * render the lines and the arrows of the tree structur. * @param gc */ @Override protected void paintComponent (Graphics gc) { gc.setColor(Color.WHITE); gc.fillRect(0, 0, getWidth (), getHeight ()); gc.setColor(Color.BLACK); ProofNode rootNode = this.proofModel.getRoot(); TreeArrowRenderer.renderArrows (rootNode, this.treeNodeLayout.getSpacing (), gc); } /** * Scroll the vieport so that the first of least added nodes becomes visible.<br> */ void jumpToNodeVisible () { if (this.jumpNode == null) { return; } // get the Component nodes to evaluate the positions // on the viewport BigStepNodeComponent node = (BigStepNodeComponent)this.jumpNode.getUserObject(); if (node == null) { return; } // get the visible rect to ensure the x coordinate is in the // visible area. only vertical scolling is requested Rectangle visibleRect = this.getVisibleRect(); Rectangle rect = new Rectangle (); rect.x = visibleRect.x; rect.y = node.getY (); rect.width = 1; rect.height = node.getHeight (); this.scrollRectToVisible(rect); this.jumpNode = null; } /* * Implementation of the Scrollable interface */ public Dimension getPreferredScrollableViewportSize () { return getPreferredSize (); } public int getScrollableBlockIncrement (Rectangle visibleRect, int orientation, int direction) { // XXX: Dynamic block increment return 25; } public boolean getScrollableTracksViewportHeight () { return false; } public boolean getScrollableTracksViewportWidth () { return false; } public int getScrollableUnitIncrement (Rectangle visibleRect, int orientation, int direction) { // XXX: Dynamic unit increment return 10; } /** * @param pJumpNode the jumpNode to set */ public void setJumpNode(ProofNode pJumpNode) { this.jumpNode = pJumpNode; } /** * @return the treeNodeLayout */ public TreeNodeLayout getTreeNodeLayout() { return this.treeNodeLayout; } public BigStepComponent clone (){ try { return (BigStepComponent)super.clone(); } catch (CloneNotSupportedException e) { return null; } } }