package de.unisiegen.tpml.graphics.bigstep ;
import java.awt.Color;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Point;
import java.text.MessageFormat;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import de.unisiegen.tpml.core.ProofGuessException;
import de.unisiegen.tpml.core.ProofNode;
import de.unisiegen.tpml.core.ProofRule;
import de.unisiegen.tpml.core.bigstep.BigStepProofModel;
import de.unisiegen.tpml.core.bigstep.BigStepProofNode;
import de.unisiegen.tpml.core.expressions.Expression;
import de.unisiegen.tpml.core.expressions.Location;
import de.unisiegen.tpml.core.languages.Language;
import de.unisiegen.tpml.core.languages.LanguageTranslator;
import de.unisiegen.tpml.graphics.Messages;
import de.unisiegen.tpml.graphics.components.CompoundExpression;
import de.unisiegen.tpml.graphics.components.MenuButton;
import de.unisiegen.tpml.graphics.components.MenuButtonListener;
import de.unisiegen.tpml.graphics.components.MenuGuessItem;
import de.unisiegen.tpml.graphics.components.MenuGuessTreeItem;
import de.unisiegen.tpml.graphics.components.MenuRuleItem;
import de.unisiegen.tpml.graphics.components.MenuTranslateItem;
import de.unisiegen.tpml.graphics.components.RulesMenu;
import de.unisiegen.tpml.graphics.outline.listener.OutlineMouseListener;
import de.unisiegen.tpml.graphics.renderer.AbstractRenderer;
import de.unisiegen.tpml.graphics.tree.TreeNodeComponent;
/**
* Graphics representation of a {@link BigStepProofNode} <br>
* <br>
* A usual look of a node may be given in the following pictur. It shows the
* second node within the tree of the BigStepper of the 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/bigstepnode.png" /><br>
* <br>
* This node is actualy build using 5 components. The following scheme
* illustrates the layouting of the single components.<br>
* <img src="../../../../../../images/bigstepnode_scheme.png" /><br>
* <br>
* The first rectangle represents the {@link #indexLabel}. The second rectanle
* represents the {@link #compoundExpression}. The third is the down-directed
* arrow this is the {@link #downArrowLabel}. The last element in the first row
* is the {@link #resultCompoundExpression} containing the resulting expression,
* this is not visible until the node is complete evaluated.<br>
* In the next row there is only one rectangle containing the rule. In the case
* of the previous image the {@link #ruleLabel} is shown, but as long as the
* node has not been evaluated with a rule there would be located the
* {@link #ruleButton}.<br>
* The bit of free space between the top and the bottom row aswell as between
* the indexLabel and the expression is given in pixels in the {@link #spacing}.
* <br>
* Within the {@link BigStepComponent} the
* {@link de.unisiegen.tpml.graphics.renderer.TreeArrowRenderer} will be used to
* draw the lines and the arrow of the tree. The TreeArrowRenderer uses
* {@link TreeNodeComponent}s to located the points where the lines and the
* arrow will be located. Therefore this component implements this interface. So
* the method {@link #getLeftArrowConnection()} returns the point marked in red
* in the scheme and the method {@link #getBottomArrowConnection()} return the
* point marked in blue. Those points are absolut positions, not relative to
* this component.<br>
* <br>
* The entire layouting or placing of the nodes of this component is done in the
* method {@link #placeElements(int)}.<br>
*
* @author marcell
* @see de.unisiegen.tpml.graphics.bigstep.BigStepView
* @see de.unisiegen.tpml.graphics.bigstep.BigStepComponent
* @see de.unisiegen.tpml.graphics.tree.TreeNodeComponent
* @see de.unisiegen.tpml.graphics.renderer.TreeArrowRenderer
*/
public class BigStepNodeComponent extends JComponent implements
TreeNodeComponent
{
/**
*
*/
private static final long serialVersionUID = - 2050381804392542081L ;
/**
* The {@link BigStepProofModel} that will get used to apply the actions on.
*/
private BigStepProofModel proofModel ;
/**
* The origin {@link BigStepProofNode}. Contains the information this node
* gives a graphics representation.
*/
private BigStepProofNode proofNode ;
/**
* Contains the information of the size of the Component. When
* {@link #placeElements(int)} is called <i>dimension</i> is filled with
* proppert values.
*/
private Dimension dimension ;
/**
* Amount of pixels that will be left free between the elements of the node.
*/
private int spacing ;
/**
* Amount of pixels that will be left free between the elements of the node.
*/
private int meshPix = 50;
/**
* Label that will contain the index at the front.
*/
private JLabel indexLabel ;
/**
* Component containing the expression and the store.
*/
private CompoundExpression < Location , Expression > compoundExpression ;
/**
* Label containing the down-directed double-arrow separating the Expression
* and the Resul-Expression.
*/
private JLabel downArrowLabel ;
/**
* Component containing the result-expression and the result-store.
*/
private CompoundExpression < Location , Expression > resultCompoundExpression ;
/**
* The Button with its DropDownMenu used to perform the userinteraction.
*/
private MenuButton ruleButton ;
/**
* Label that will be used to show the evaluated rule.
*/
private JLabel ruleLabel ;
/**
* The "Translate to core syntax" item in the context menu.
*/
private MenuTranslateItem menuTranslateItem ;
/**
* The translator will be used to determine whether the expression contains
* syntactical sugar.
*/
private LanguageTranslator translator ;
/**
* containing the rules it may contain submenus if to many (set in TOMANY)
* rules are in the popupmenu
*/
private JPopupMenu menu ;
/**
* Manages the RulesMenu
*/
private RulesMenu rm = new RulesMenu ( ) ;
/**
* @param node The node that should be represented
* @param model The model
* @param pTranslator The translator from the model
*/
public BigStepNodeComponent ( BigStepProofNode node ,
BigStepProofModel model , LanguageTranslator pTranslator )
{
super ( ) ;
this.proofNode = node ;
this.proofModel = model ;
this.translator = pTranslator ;
this.dimension = new Dimension ( 0 , 0 ) ;
this.spacing = 10 ;
/*
* Create and add the components needed to render this node
*/
this.indexLabel = new JLabel ( ) ;
this.indexLabel.addMouseListener ( new OutlineMouseListener ( this ) ) ;
add ( this.indexLabel ) ;
this.compoundExpression = new CompoundExpression < Location , Expression > ( ) ;
this.compoundExpression
.addMouseListener ( new OutlineMouseListener ( this ) ) ;
add ( this.compoundExpression ) ;
this.downArrowLabel = new JLabel ( ) ;
add ( this.downArrowLabel ) ;
this.downArrowLabel.setText ( " \u21d3 " ) ; //$NON-NLS-1$
this.resultCompoundExpression = new CompoundExpression < Location , Expression > ( ) ;
this.resultCompoundExpression.addMouseListener ( new OutlineMouseListener (
this ) ) ;
add ( this.resultCompoundExpression ) ;
this.resultCompoundExpression.setAlternativeColor ( Color.LIGHT_GRAY ) ;
this.ruleButton = new MenuButton ( ) ;
add ( this.ruleButton ) ;
this.ruleButton.setVisible ( true ) ;
this.ruleLabel = new JLabel ( ) ;
add ( this.ruleLabel ) ;
this.ruleLabel.setVisible ( false ) ;
/*
* Create the PopupMenu for the menu button
*/
// Fill the menu with menuitems
ProofRule [ ] rules = this.proofModel.getRules ( ) ;
Language lang = this.proofModel.getLanguage ( ) ;
this.menu = new JPopupMenu ( ) ;
this.menu = this.rm.getMenu ( rules , rules , lang , this , "bigstep" , false ) ;
this.menu.addSeparator ( ) ;
this.menu.add ( this.menuTranslateItem = new MenuTranslateItem ( ) ) ;
this.ruleButton.setMenu ( this.menu ) ;
/*
* Connect the handling of the ruleButton
*/
this.ruleButton.addMenuButtonListener ( new MenuButtonListener ( )
{
public void menuClosed ( MenuButton button )
{
//empty block
}
public void menuItemActivated ( MenuButton button , final JMenuItem source )
{
// setup a wait cursor for the toplevel ancestor
final Container toplevel = getTopLevelAncestor ( ) ;
final Cursor cursor = toplevel.getCursor ( ) ;
toplevel.setCursor ( new Cursor ( Cursor.WAIT_CURSOR ) ) ;
// avoid blocking the popup menu
SwingUtilities.invokeLater ( new Runnable ( )
{
public void run ( )
{
// handle the menu action
BigStepNodeComponent.this.handleMenuActivated ( source ) ;
// wait for the repaint before resetting the cursor
SwingUtilities.invokeLater ( new Runnable ( )
{
public void run ( )
{
// reset the cursor
toplevel.setCursor ( cursor ) ;
}
} ) ;
}
} ) ;
}
} ) ;
changeNode ( ) ;
}
/**
* Causes the expression and the resultexpression to recalculate their layout.
*/
public void reset ( )
{
this.compoundExpression.reset ( ) ;
this.resultCompoundExpression.reset ( ) ;
}
/**
* Adds a new {@link BigStepNodeListener} to the <i>SmallStepNodeComponent</i>
*
* @param listener The listener to be added
*/
public void addBigStepNodeListener ( BigStepNodeListener listener )
{
this.listenerList.add ( BigStepNodeListener.class , listener ) ;
}
/**
* Removes a {@link BigStepNodeListener} from the <i>SmallStepNodeComponent</i>
*
* @param listener The listener to be removed.
*/
public void removeBigStepNodeListener ( BigStepNodeListener listener )
{
this.listenerList.remove ( BigStepNodeListener.class , listener ) ;
}
/**
* Calls the {@link BigStepNodeListener#nodeChanged(BigStepNodeComponent)} of
* all listeners.
*/
private void fireNodeChanged ( )
{
Object [ ] listeners = this.listenerList.getListenerList ( ) ;
for ( int i = 0 ; i < listeners.length ; i += 2 )
{
if ( listeners [ i ] != BigStepNodeListener.class )
{
continue ;
}
( ( BigStepNodeListener ) listeners [ i + 1 ] ).nodeChanged ( this ) ;
}
}
private void fireRequestJumpToNode ( ProofNode node )
{
Object [ ] listeners = this.listenerList.getListenerList ( ) ;
for ( int i = 0 ; i < listeners.length ; i += 2 )
{
if ( listeners [ i ] != BigStepNodeListener.class )
{
continue ;
}
( ( BigStepNodeListener ) listeners [ i + 1 ] ).requestJumpToNode ( node ) ;
}
}
/**
* Handles the actions that should be done when an item from the MenuButton
* was selected.<br>
* <br>
* Tree possible actions can be done here: 1st Selection of a rule to apply to
* the node. 2nd Translation of the expression into core syntax. And 3rd Guess
* of the rules for the current node.<br>
* <br>
* If the current outermost expression does not contain any syntactical sugar,
* the translation will be done directly recersivly on the entire expresion.<br>
* If the current outermost expression does contain syntactical sugar, a
* messagebox will be shown whether the translation should be done only on the
* outermost expression or if it should be done on the entire expression.
*
* @param item
*/
public void handleMenuActivated ( JMenuItem item )
{
if ( item instanceof MenuRuleItem )
{
MenuRuleItem ruleItem = ( MenuRuleItem ) item ;
ProofRule rule = ruleItem.getRule ( ) ;
try
{
this.proofModel.prove ( rule , this.proofNode ) ;
this.ruleButton.setToolTipText ( null ) ;
}
catch ( Throwable e )
{
// revert the menu
this.rm.revertMenu ( ) ;
// when the node could not be prooven with the selected
// rule the menu button gets labeled with the given rule
// and will be displayed in red
this.ruleButton.setText ( "(" + rule.getName ( ) + ")" ) ; //$NON-NLS-1$ //$NON-NLS-2$
this.ruleButton.setTextColor ( Color.RED ) ;
// determine the error message for the tooltip
this.ruleButton.setToolTipText ( e.getMessage ( ) ) ;
}
}
else if ( item instanceof MenuTranslateItem )
{
int answer = 1 ;
if ( this.proofModel.containsSyntacticSugar ( this.proofNode , false ) )
{
String [ ] answers =
{
Messages.getString ( "NodeComponent.0" ) , Messages.getString ( "NodeComponent.1" ) , Messages.getString ( "NodeComponent.2" ) } ; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
answer = JOptionPane.showOptionDialog (
getTopLevelAncestor ( ) ,
Messages.getString ( "NodeComponent.3" ) , //$NON-NLS-1$
Messages.getString ( "NodeComponent.4" ) , //$NON-NLS-1$
JOptionPane.YES_NO_CANCEL_OPTION , JOptionPane.QUESTION_MESSAGE ,
null , answers , answers [ 0 ] ) ;
}
switch ( answer )
{
case 0 :
this.proofModel.translateToCoreSyntax ( this.proofNode , false ) ;
break ;
case 1 :
this.proofModel.translateToCoreSyntax ( this.proofNode , true ) ;
break ;
case 2 :
break ;
}
fireNodeChanged ( ) ;
}
else if ( item instanceof MenuGuessItem )
{
try
{
this.proofModel.guess ( this.proofNode ) ;
}
catch ( final ProofGuessException e )
{
fireRequestJumpToNode ( e.getNode ( ) ) ;
SwingUtilities.invokeLater ( new Runnable ( )
{
public void run ( )
{
JOptionPane
.showMessageDialog (
getTopLevelAncestor ( ) ,
MessageFormat.format ( Messages
.getString ( "NodeComponent.5" ) , e.getMessage ( ) ) , Messages.getString ( "NodeComponent.6" ) , JOptionPane.ERROR_MESSAGE ) ; //$NON-NLS-1$ //$NON-NLS-2$
}
} ) ;
}
}
else if ( item instanceof MenuGuessTreeItem )
{
try
{
this.proofModel.complete ( this.proofNode ) ;
}
catch ( final ProofGuessException e )
{
fireRequestJumpToNode ( e.getNode ( ) ) ;
SwingUtilities.invokeLater ( new Runnable ( )
{
public void run ( )
{
JOptionPane
.showMessageDialog (
getTopLevelAncestor ( ) ,
MessageFormat.format ( Messages
.getString ( "NodeComponent.7" ) , e.getMessage ( ) ) , Messages.getString ( "NodeComponent.8" ) , JOptionPane.ERROR_MESSAGE ) ; //$NON-NLS-1$ //$NON-NLS-2$
}
} ) ;
}
}
fireNodeChanged ( ) ;
}
/**
* Sets the index of the current node.
*
* @param index
*/
public void setIndex ( int index )
{
this.indexLabel.setText ( "(" + index + ")" ) ; //$NON-NLS-1$ //$NON-NLS-2$
}
/**
* Does an update on the compound expression.<br>
* Resets the expression and the environment (if there is one). That causes
* the PrettyStringRenderer to recheck the breakoints.
*/
public void changeNode ( )
{
this.compoundExpression.setExpression ( this.proofNode.getExpression ( ) ) ;
// only if memory is enabled set the store because the
// store is always valid
if ( this.proofModel.isMemoryEnabled ( ) )
{
this.compoundExpression.setEnvironment ( this.proofNode.getStore ( ) ) ;
}
else
{
this.compoundExpression.setEnvironment ( null ) ;
}
if ( this.proofNode.getResult ( ) != null )
{
this.resultCompoundExpression.setExpression ( this.proofNode.getResult ( )
.getValue ( ) ) ;
if ( this.proofModel.isMemoryEnabled ( ) )
{
this.resultCompoundExpression.setEnvironment ( this.proofNode
.getResult ( ).getStore ( ) ) ;
}
else
{
this.resultCompoundExpression.setEnvironment ( null ) ;
}
}
else
{
this.resultCompoundExpression.setExpression ( null ) ;
this.resultCompoundExpression.setEnvironment ( null ) ;
}
}
/**
* Places all elements of the current node.<br>
* Just places one element after the other. 1st the index, 2nd the compoundExpression 3rd the
* double-sidded-down-directed arrow. When the node is proven (that is if there is already an evaluated result, it
* will be placed behind the arrow.<br>
* <br>
* If the the nodes is evaluated the ruleLabel is placed with a bit spacing below the expression. If the node is
* not evaluated the menuButton is placed at the same size.<br>
* <br>
* After the placing is done the {@link #dimension} contains the needed size of this node.
*
* @param maxWidth
* The maximum width that is available for the current node.
*/
private void placeElements(int pMaxWidth)
{
int maxWidth = pMaxWidth;
// get the size for the index at the beginning: (x)
FontMetrics fm = AbstractRenderer.getTextFontMetrics();
Dimension labelSize = new Dimension(fm.stringWidth(this.indexLabel.getText()), fm.getHeight());
this.dimension.setSize(labelSize.width, labelSize.height);
// there will be a bit spacing between the index label and the expression
this.dimension.width += this.spacing;
// the index shrinkens the max size for the expression
maxWidth -= labelSize.width;
// get the needed size for the expression
Dimension expSize = this.compoundExpression.getNeededSize(maxWidth - this.dimension.width);
this.dimension.width += expSize.width;
this.dimension.height = Math.max(expSize.height, this.dimension.height);
Dimension arrowSize = this.downArrowLabel.getPreferredSize();
this.dimension.width += arrowSize.width;
this.dimension.height = Math.max(arrowSize.height, this.dimension.height);
Dimension resultSize = new Dimension(0, 0);
//get the size of the Result if there is no linebrake
resultSize = this.resultCompoundExpression.getNeededSize(maxWidth - this.dimension.width);
boolean breakNeeded = false;
// if the expression is braken the result will be meshed mesPix pixels
//break if the result dose not fit
if ((maxWidth - this.dimension.width) < resultSize.width)
{
breakNeeded = true;
}
if (!breakNeeded)
{
// if the result is higher than the hight of one line the result will be
// in the next line
if (resultSize.height > AbstractRenderer.getAbsoluteHeight())
{
breakNeeded = true;
}
}
if (breakNeeded)
{
//get the new resultsiz in the next line
resultSize = this.resultCompoundExpression.getNeededSize(maxWidth - this.meshPix);
//the hight will be lager
this.dimension.height = expSize.height + resultSize.height;
}
if (!breakNeeded)
{
this.dimension.width += resultSize.width;
this.dimension.height = Math.max(resultSize.height, this.dimension.height);
}
else
{
this.dimension.width = Math.max((resultSize.width + this.meshPix), this.dimension.width);
}
// now place the elements
int posX = 0;
this.indexLabel.setBounds(posX, 0, labelSize.width, this.dimension.height);
posX += labelSize.width + this.spacing;
this.compoundExpression.setBounds(posX, 0, expSize.width, expSize.height);
posX += expSize.width;
this.downArrowLabel.setBounds(posX, 0, arrowSize.width, expSize.height);
posX += arrowSize.width;
if (!breakNeeded)
{
this.resultCompoundExpression.setBounds(posX, 0, resultSize.width, resultSize.height);
}
else
{
this.resultCompoundExpression.setBounds(this.meshPix, (this.dimension.height - resultSize.height),
resultSize.width, resultSize.height);
}
/*
* Check whether this is node is evaluated. If it is evaluated only the Label needs to get placed, if it is not
* evaluated yet the MenuButton needs to get placed.
*/
posX = labelSize.width + this.spacing;
if (this.proofNode.getRule() != null)
{
this.ruleLabel.setText(this.proofNode.getRule().toString()); //$NON-NLS-1$ //$NON-NLS-2$
Dimension ruleLabelSize = this.ruleLabel.getPreferredSize();
this.ruleLabel.setBounds(posX, this.dimension.height + this.spacing, ruleLabelSize.width,
ruleLabelSize.height);
this.dimension.height += this.spacing + ruleLabelSize.height;
this.dimension.width = Math.max(this.dimension.width, ruleLabelSize.width + posX);
// display only the label not the button
this.ruleLabel.setVisible(true);
this.ruleButton.setVisible(false);
}
else
{
// place the menu button
Dimension buttonSize = this.ruleButton.getNeededSize();
this.ruleButton.setBounds(posX, this.dimension.height + this.spacing, buttonSize.width, buttonSize.height);
this.dimension.height += this.spacing + buttonSize.height;
this.dimension.width = Math.max(this.dimension.width, buttonSize.width + posX);
// display only the button not the label
this.ruleLabel.setVisible(false);
this.ruleButton.setVisible(true);
}
}
/*
* Implementation of the TreeNodeComponent interface
*/
public Dimension update ( int maxWidth )
{
placeElements ( maxWidth ) ;
this.menuTranslateItem.setEnabled ( this.translator.containsSyntacticSugar (
this.proofNode.getExpression ( ) , true ) ) ;
return this.dimension ;
}
/**
* Returns the point at the bottom of the node where the layout should attach the arrow.
*/
public Point getBottomArrowConnection()
{
return new Point(this.getX() + this.indexLabel.getWidth() / 2, this.getY() + (this.dimension.height / 2));
}
/**
* Returns the point at the left of the node where the layout should attach the line to its parent.
*/
public Point getLeftArrowConnection ( )
{
return new Point ( this.getX ( ) , this.getY ( ) + this.indexLabel.getY ( )
+ this.indexLabel.getHeight ( ) / 2 ) ;
}
/**
* Returns the number of pixels the children should be displayed indentated.
*/
public int getIndentationWidth ( )
{
// XXX: calculate the indentation
return this.indexLabel.getWidth ( ) ;
}
/**
* Just calls setBounds of the super class.
*/
@ Override
public void setBounds ( int x , int y , int width , int height )
{
super.setBounds ( x , y , width , height ) ;
}
/**
* Returns the indexLabel.
*
* @return The indexLabel.
* @see #indexLabel
*/
public JLabel getIndexLabel ( )
{
return this.indexLabel ;
}
/**
* Returns the compoundExpression.
*
* @return The compoundExpression.
* @see #compoundExpression
*/
public CompoundExpression < Location , Expression > getCompoundExpression ( )
{
return this.compoundExpression ;
}
/**
* Returns the resultCompoundExpression.
*
* @return The resultCompoundExpression.
* @see #resultCompoundExpression
*/
public CompoundExpression < Location , Expression > getResultCompoundExpression ( )
{
return this.resultCompoundExpression ;
}
}