package animation; import java.awt.Rectangle; import javax.swing.JTree; import javax.swing.SwingUtilities; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.MutableTreeNode; import javax.swing.tree.TreePath; import lib.TextLayoutHelper; /** * represents a line of pseudocode * @author kolja * */ public class PseudoCodeNode extends DefaultMutableTreeNode { public static enum CodeAction { SKIP, STOP, CONTINUE } private static final long serialVersionUID = 1L; private static int nextNodeId = 0; private final int nodeId; private AnimationController controller; private JTree tree; private CodeLine code; private boolean selected; private boolean breakPoint; public PseudoCodeNode( String description, String[] vars, JTree tree, CodeLine line ) { super( TextLayoutHelper.setupPseudoCode( description, vars ) ); synchronized( PseudoCodeNode.class ) { nodeId = nextNodeId++; } selected = false; this.tree = tree; breakPoint = false; code = line; } public int getId() { return nodeId; } public void setController( AnimationController c ) { if( children != null ) { for( Object ch : children ) { ((PseudoCodeNode)ch).setController( c ); } } controller = c; } @Override public void add( MutableTreeNode node ) { ((PseudoCodeNode)node).setController( controller ); super.add( node ); } /** * * @return the tree that this node belongs to */ public JTree getTree() { return tree; } /** * checks if this node should be highlighted * @return true if it should, false otherwise */ public boolean isSelected() { return selected; } /** * checks if one of the subnodes of this node is selected. * @return true if one is, false otherwise */ public boolean hasSelectedSubnode() { if( children != null ) { for( Object ch : children ) { if( ((PseudoCodeNode)ch).isSelected() || ((PseudoCodeNode)ch).hasSelectedSubnode() ) return true; } } return false; } private void expandToRoot() { if( parent != null ) ((PseudoCodeNode)parent).expandToRoot(); tree.expandPath( new TreePath( this.getPath() ) ); } /** * highlight this line of pseudocode. * should be called when the line is entered, as it triggers breakpoints * @param selected whether to select or deselect this line * @return false iff a breakpoint was reached and the node was set to be selected. */ public CodeAction setSelected( boolean selected ) { if( selected && breakPoint ) controller.setContinuous( false ); this.selected = selected; if( selected ) { if( tree != null ) { TreePath path = new TreePath( getPath() ); Rectangle bounds = tree.getPathBounds(path); if( bounds!= null ) { bounds.height = (int) (tree.getVisibleRect().height - tree.getVisibleRect().getHeight() / 2); bounds.x = 0; SwingUtilities.invokeLater( new Runnable() { @Override public void run() { tree.scrollRectToVisible(bounds); } }); } } if( controller == null || controller.getStepOption() != 1 || breakPoint ) { SwingUtilities.invokeLater( new Runnable() { @Override public void run() { expandToRoot(); } }); } } else { if( controller == null || controller.getStepOption() != 1 ) { SwingUtilities.invokeLater( new Runnable() { @Override public void run() { tree.collapsePath( new TreePath( getPath() ) ); } }); } } if( breakPoint && selected ) return CodeAction.STOP; // Breakpoint if( controller != null && controller.getStepOption() == 1 && !tree.isVisible( new TreePath( this.getPath() ) ) ) return CodeAction.SKIP; // Step would be to detailed return CodeAction.CONTINUE; // Normal } /** * set a breakpoint at this line of code * @param breakPoint whether there should be a breakpoint or node */ public void setBreakPoint( boolean breakPoint ) { this.breakPoint = breakPoint; } /** * check if there is a breakpoint set at this line of code * @return true, iff there is a breakpoint */ public boolean hasBreakPoint() { return breakPoint; } public ControlFlow forwardStep( Memory m ) { ControlFlow cf = code.runForward( m ); cf.setJumpBack( this ); return cf; } public ControlFlow emptyForwardStep( Memory m ) { code.actions.push( (Memory mem) -> {} ); // add empty reverse function ControlFlow cf = new ControlFlow( ControlFlow.STEP_OVER ); cf.setJumpBack( this ); return cf; } public void backwardStep( Memory m ) { code.runBackward( m ); } public String getDebugOutput( Memory m ) { if( parent == null ) return ""; return ((PseudoCodeNode)parent).getDebugOutput( m ); } }