package de.unisiegen.tpml.graphics.subtyping;
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.AbstractProofModel;
import de.unisiegen.tpml.core.ProofGuessException;
import de.unisiegen.tpml.core.ProofNode;
import de.unisiegen.tpml.core.subtyping.SubTypingModel;
import de.unisiegen.tpml.core.subtyping.SubTypingNode;
import de.unisiegen.tpml.core.subtyping.SubTypingProofModel;
import de.unisiegen.tpml.core.subtyping.SubTypingProofNode;
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 graphics repsentation of the
* SubTyping<br>
* <br>
*
* The following image shows a usual look of a part of an SubTypingComponent.
* 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/SubTyping.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>SubTypingComponent</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(SubTypingProofNode)}-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.subtyping.SubTypingNodeComponent}.<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.subtyping.SubTypingView
* @see de.unisiegen.tpml.graphics.subtyping.SubTypingNodeComponent
*
*/
public class SubTypingComponent 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 {@link SubTypingComponent} for the specified
* {@link SubTypingProofModel}.
*
* @param model the model for this component
*/
public SubTypingComponent ( SubTypingModel model ) {
super ( ( AbstractProofModel ) model );
this.treeNodeLayout = new TreeNodeLayout ( 10 );
this.jumpNode = null;
setLayout ( null );
// initialy the tree content has changed
treeContentChanged ( );
}
/**
*
* Set the spacing for this component
*
* @param spacing the new spacing
*/
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()
{
SubTypingProofNode rootNode = (SubTypingProofNode) SubTypingComponent.this.proofModel.getRoot();
Point rightBottomPos = SubTypingComponent.this.treeNodeLayout.placeNodes(rootNode, 20, 20,
SubTypingComponent.this.availableWidth, SubTypingComponent.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);
SubTypingComponent.this.currentlyLayouting = false;
SubTypingComponent.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 ( ( SubTypingProofNode ) this.proofModel.getRoot ( ) );
}
/**
* Checks the entire tree if every {@link SubTypingProofNode} contains an Userobject.
*
* @param node
*/
private void checkForUserObject ( SubTypingProofNode node ) {
if ( node == null ) {
return;
}
SubTypingNodeComponent nodeComponent = ( SubTypingNodeComponent ) node.getUserObject ( );
if ( nodeComponent == null ) {
// if the node has no userobject it may be new in the
// tree, so a new SubTypingNodeComponent will be created
// and added to the SubTypingProofNode
nodeComponent = new SubTypingNodeComponent ( node, ( SubTypingModel ) this.proofModel );
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.addSubTypingNodeListener ( new SubTypingNodeListener ( ) {
public void nodeChanged ( @SuppressWarnings ( "unused" )
SubTypingNodeComponent pNode ) {
SubTypingComponent.this.relayout ( );
}
@SuppressWarnings ( "unused" )
public void requestTypeEnter ( SubTypingNodeComponent pNode ) {
//Nothing to do
}
@SuppressWarnings ( "synthetic-access" )
public void requestJumpToNode ( ProofNode pNode ) {
SubTypingComponent.this.jumpNode = pNode;
}
} );
}
// set the index value for the node
nodeComponent.setIndex ( this.index );
++this.index;
// the SubTyping is a real tree, so its needed to proceed with all
// children of the node
for ( int i = 0; i < node.getChildCount ( ); i++ ) {
SubTypingProofNode 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 proof node to reset
*/
private void resetUserObject ( SubTypingProofNode node ) {
if ( node == null ) {
return;
}
SubTypingNodeComponent nodeComponent = ( SubTypingNodeComponent ) node.getUserObject ( );
if ( nodeComponent == null ) {
return;
}
nodeComponent.reset ( );
for ( int i = 0; i < node.getChildCount ( ); i++ ) {
SubTypingProofNode 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 ( ) {
SubTypingProofNode rootNode = ( SubTypingProofNode ) 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 SubTypingNodeComponent#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 ) {
SubTypingProofNode proofNode = ( SubTypingProofNode ) event.getPath ( )[0];
SubTypingNodeComponent nodeComponent = ( SubTypingNodeComponent ) proofNode.getUserObject ( );
if ( nodeComponent != null ) {
nodeComponent.changeNode ( );
}
}
return;
}
for ( int i = 0; i < children.length; i++ ) {
if ( children[i] instanceof ProofNode ) {
SubTypingProofNode proofNode = ( SubTypingProofNode ) children[i];
SubTypingNodeComponent nodeComponent = ( SubTypingNodeComponent ) proofNode.getUserObject ( );
if ( nodeComponent != null ) {
nodeComponent.changeNode ( );
}
}
}
}
/**
* Removes the userobject from the {@link SubTypingProofNode}.
*/
@Override
protected void nodesRemoved ( TreeModelEvent event ) {
Object[] children = event.getChildren ( );
for ( int i = 0; i < children.length; i++ ) {
if ( children[i] instanceof ProofNode ) {
SubTypingProofNode proofNode = ( SubTypingProofNode ) children[i];
SubTypingNodeComponent nodeComponent = ( SubTypingNodeComponent ) 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
SubTypingNodeComponent node = ( SubTypingNodeComponent ) 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 SubTypingComponent clone (){
try {
return (SubTypingComponent)super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
@Override
protected void forcedRelayout()
{
// TODO größe setzen...
doRelayout();
}
/**
* Sets whether the small step view operates in advanced or beginner mode.
*
* @param advanced <code>true</code> to display only axiom rules in the
* menu.
* @see SubTypingComponent#setAdvanced(boolean)
*/
void setAdvanced(boolean advanced)
{
((SubTypingModel) this.proofModel).setAdvanced ( advanced );
// update all active nodes
Enumeration<ProofNode> enumeration = this.proofModel.getRoot().postorderEnumeration();
while (enumeration.hasMoreElements()) {
// tell the component belonging to this node, that we have a new advanced state
SubTypingNode node = (SubTypingNode)enumeration.nextElement();
SubTypingNodeComponent component = (SubTypingNodeComponent)node.getUserObject();
component.setAdvanced(advanced);
}
}
}