package de.unisiegen.tpml.graphics.minimaltyping;
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.minimaltyping.MinimalTypingProofModel;
import de.unisiegen.tpml.core.minimaltyping.MinimalTypingProofNode;
import de.unisiegen.tpml.graphics.AbstractProofComponent;
import de.unisiegen.tpml.graphics.bigstep.BigStepComponent;
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 graphics repsentation of the
* MinimalTyping<br>
* <br>
*
* The following image shows a usual look of a part of an TypeChckerComponent.
* 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/MinimalTyping.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>MinimalTypingComponent</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(MinimalTypingProofNode)}-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.minimaltyping.MinimalTypingNodeComponent}.<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.
*
*
*
* @author Benjamin Mies
*
* @see de.unisiegen.tpml.graphics.minimaltyping.MinimalTypingView
* @see de.unisiegen.tpml.graphics.minimaltyping.MinimalTypingNodeComponent
*
*/
public class MinimalTypingComponent extends AbstractProofComponent implements Scrollable, Cloneable {
/**
*
*/
private static final long serialVersionUID = 5184580585827680414L;
/**
* Index that will be incremented during the layouting.
*/
private int index;
/**
* TreeNodeLayout will be used to do the layouting of the tree.
*/
private TreeNodeLayout treeNodeLayout;
/**
* The ProofNode where the scrollView will scroll to when
* an new node will be inserted.
*/
private ProofNode jumpNode;
/**
*
* Allocates a new <code>MinimalTypingComponent</code> o
*
* @param model the proof model for this component
*/
public MinimalTypingComponent (MinimalTypingProofModel model) {
super (model);
this.treeNodeLayout = new TreeNodeLayout (10);
this.jumpNode = null;
setLayout (null);
// initialy the tree content has changed
treeContentChanged();
}
/**
*
* Set a new spacing for this component.
*
* @param spacing the new spacing value
*/
public void setSpacing (int spacing) {
this.treeNodeLayout.setSpacing(spacing);
}
/**
* Guesses the next unprooven node in 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"); //$NON-NLS-1$
}
/**
* Recalculates the layout
*
*/
@Override
protected void relayout()
{
if (this.currentlyLayouting)
{
return;
}
this.currentlyLayouting = true;
SwingUtilities.invokeLater(new Runnable() {
@SuppressWarnings("synthetic-access")
public void run()
{
doRelayout();
}
});
}
protected void doRelayout()
{
MinimalTypingProofNode rootNode = (MinimalTypingProofNode) MinimalTypingComponent.this.proofModel.getRoot();
Point rightBottomPos = MinimalTypingComponent.this.treeNodeLayout.placeNodes(rootNode, 20, 20,
MinimalTypingComponent.this.availableWidth, MinimalTypingComponent.this.availableHeight);
// lets add some border to the space
rightBottomPos.x += 20;
rightBottomPos.y += 20;
Dimension size = new Dimension(rightBottomPos.x, rightBottomPos.y);
// set all the sizes needed by the component
setMaximumSize(size);
setMinimumSize(size);
setPreferredSize(size);
setSize(size);
MinimalTypingComponent.this.currentlyLayouting = false;
MinimalTypingComponent.this.jumpToNodeVisible();
}
/**
* Causes every {@link PrettyStringRenderer} and {@link EnvironmentRenderer} to recalculate thier layout.
*/
@Override
protected void resetLayout () {
// apply the reset on the root node
resetUserObject((MinimalTypingProofNode)this.proofModel.getRoot());
}
/**
* Checks the entire tree if every {@link MinimalTypingProofNode} contains an Userobject.
*
* @param node
*/
private void checkForUserObject (MinimalTypingProofNode node) {
if (node == null) {
return;
}
MinimalTypingNodeComponent nodeComponent = (MinimalTypingNodeComponent)node.getUserObject();
if (nodeComponent == null) {
// if the node has no userobject it may be new in the
// tree, so a new MinimalTypingNodeComponent will be created
// and added to the MinimalTypingProofNode
nodeComponent = new MinimalTypingNodeComponent (node, (MinimalTypingProofModel)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.addMinimalTypingNodeListener(new MinimalTypingNodeListener () {
public void nodeChanged (@SuppressWarnings("unused")
MinimalTypingNodeComponent pNode) {
MinimalTypingComponent.this.relayout();
}
public void requestTypeEnter (@SuppressWarnings("unused")
MinimalTypingNodeComponent pNode) {
// Nothing to do
}
@SuppressWarnings("synthetic-access")
public void requestJumpToNode (ProofNode pNode) {
MinimalTypingComponent.this.jumpNode = pNode;
}
});
}
// set the index value for the node
nodeComponent.setIndex(this.index);
++this.index;
// the MinimalTyping is a real tree, so its needed to proceed with all
// children of the node
for (int i=0; i<node.getChildCount(); i++) {
MinimalTypingProofNode 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.
* @param node the node to reset
*/
private void resetUserObject (MinimalTypingProofNode node) {
if (node == null) {
return;
}
MinimalTypingNodeComponent nodeComponent = (MinimalTypingNodeComponent)node.getUserObject();
if (nodeComponent == null) {
return;
}
nodeComponent.reset ();
for (int i=0; i<node.getChildCount (); i++) {
MinimalTypingProofNode pNode = node.getChildAt(i);
resetUserObject (pNode);
}
}
/**
* Gets called when the content of the tree has changed.
* If nodes are newly inserted or nodes got removed.<br>
* <br>
* The tree will be checked for userobject and than get
* relayouted.
*
*/
@Override
protected void treeContentChanged () {
MinimalTypingProofNode rootNode = (MinimalTypingProofNode)this.proofModel.getRoot();
// initiate the index
this.index = 1;
checkForUserObject(rootNode);
relayout ();
}
/**
* Saves the first of the newly inserted nodes for the node
* to jump to later.
*/
@Override
protected void nodesInserted (TreeModelEvent event) {
if (this.jumpNode != null) {
return;
}
Object [] children = event.getChildren();
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 a {@link MinimalTypingNodeComponent#changeNode()} to every nodes
* that have changed.
*/
@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) {
MinimalTypingProofNode proofNode = (MinimalTypingProofNode)event.getPath()[0];
MinimalTypingNodeComponent nodeComponent = (MinimalTypingNodeComponent)proofNode.getUserObject();
if (nodeComponent != null) {
nodeComponent.changeNode ();
}
}
return;
}
for (int i=0; i<children.length; i++) {
if (children[i] instanceof ProofNode) {
MinimalTypingProofNode proofNode = (MinimalTypingProofNode)children[i];
MinimalTypingNodeComponent nodeComponent = (MinimalTypingNodeComponent)proofNode.getUserObject();
if (nodeComponent != null) {
nodeComponent.changeNode ();
}
}
}
}
/**
* Removes the userobject from the {@link MinimalTypingProofNode}.
*/
@Override
protected void nodesRemoved (TreeModelEvent event) {
Object[] children = event.getChildren();
for (int i=0; i<children.length; i++) {
if (children[i] instanceof ProofNode) {
MinimalTypingProofNode proofNode = (MinimalTypingProofNode)children[i];
MinimalTypingNodeComponent nodeComponent = (MinimalTypingNodeComponent)proofNode.getUserObject();
if (nodeComponent != null) {
remove (nodeComponent);
proofNode.setUserObject(null);
}
}
}
}
/**
* Just renders the tree using the {@link TreeArrowRenderer#renderArrows(ProofNode, int, Graphics)}-Method
* @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 Viewport to the rect of the previously saved node.
*
*/
private void jumpToNodeVisible () {
if (this.jumpNode == null) {
return;
}
// get the Component nodes to evaluate the positions
// on the viewport
MinimalTypingNodeComponent node = (MinimalTypingNodeComponent)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;
}
public MinimalTypingComponent clone (){
try {
return (MinimalTypingComponent)super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
@Override
protected void forcedRelayout()
{
// TODO größe setzen...
doRelayout();
}
}