package ui.smallstep; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Rectangle; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionAdapter; import java.util.LinkedList; import javax.swing.JComboBox; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import smallstep.SmallStepProofModel; import smallstep.SmallStepProofNode; import ui.AbstractNode; import ui.beans.MenuButton; import ui.beans.MenuButtonListener; import ui.renderer.AbstractRenderer; import ui.renderer.EnvironmentRenderer; import ui.renderer.ExpressionRenderer; import common.ProofModel; import common.ProofRule; import common.ProofRuleException; import common.ProofStep; import common.interpreters.InterpreterProofModel; import expressions.Expression; import expressions.Location; class SmallStepNode extends AbstractNode { private enum ActionType { TranslateToCoresyntax, } private class RuleBound { public Rectangle rect; public ProofRule rule; public RuleBound (ProofRule rule, Rectangle rect) { this.rule = rule; this.rect = rect; } } private class RuleMenuItem extends JMenuItem { private ProofRule rule; public RuleMenuItem (ProofRule rule) { super (rule.getName()); this.rule = rule; } public ProofRule getRule () { return rule; } }; private static class ActionMenuItem extends JMenuItem { private ActionType type; public ActionMenuItem (ActionType type) { super (ActionMenuItem.getString(type)); this.type = type; } private static String getString(ActionType type) { switch (type) { case TranslateToCoresyntax: return "Translate to coresyntax"; } return "narf"; } public ActionType getActionType () { return type; } } private static int center; private SmallStepView view; private EnvironmentRenderer envRenderer; private Dimension envSize; private ExpressionRenderer expRenderer; private Dimension expSize; private Dimension expEnvSize; private Dimension expMaxSize; private Dimension ruleSize; private Dimension ruleMaxSize; private MenuButton ruleButton; private Font ruleFont; private FontMetrics ruleFontMetrics; private ActionMenuItem translateToCoreSyntax = null; private LinkedList<RuleBound> ruleBounds; private Expression underlineExpression = null; public SmallStepNode(SmallStepView view, SmallStepProofNode proofNode) { super(); setLayout(null); this.ruleBounds = new LinkedList<RuleBound> (); this.view = view; this.proofNode = proofNode; Font fnt = new JComboBox().getFont(); FontMetrics fntMetrics = getFontMetrics(fnt); this.ruleFont = fnt; this.ruleFontMetrics = fntMetrics; this.ruleButton = new MenuButton(); add (ruleButton); this.ruleButton.addMenuButtonListener(new MenuButtonListener() { public void menuItemActivated (MenuButton button, JMenuItem item) { if (item instanceof RuleMenuItem) { evaluateRule(((RuleMenuItem)item).getRule()); } else if (item instanceof ActionMenuItem) { ActionMenuItem ami = (ActionMenuItem)item; switch (ami.getActionType()) { case TranslateToCoresyntax: translateToCoreSyntax(); break; } } setUnderlineExpression (null, true); setUnderlineExpression (null, false); } public void menuClosed (MenuButton button) { setUnderlineExpression (null, true); setUnderlineExpression (null, false); } }); this.ruleButton.addMouseMotionListener(new MouseMotionAdapter() { public void mouseMoved (MouseEvent event) { handleMouseMovedOnButton (event); } }); this.addMouseMotionListener(new MouseMotionAdapter() { public void mouseMoved (MouseEvent event) { handleMouseMoved (event); } }); } private void setUnderlineExpression (Expression expression, boolean applyToChildren) { if (this.underlineExpression != expression) { this.underlineExpression = expression; repaint(); } if (applyToChildren) { SmallStepNode child = (SmallStepNode)getFirstChild (); if (child != null) { child.setUnderlineExpression (null, true); } } else { SmallStepNode parent = (SmallStepNode)getParentNode(); if (parent != null) { parent.setUnderlineExpression (null, false); } } } private void handleMouseMoved (MouseEvent event) { for (RuleBound rb : this.ruleBounds) { if (rb.rect.contains(event.getX(), event.getY())) { ProofStep steps[] = this.proofNode.getSteps(); for (ProofStep s : steps) { if (s.getRule() == rb.rule) { setUnderlineExpression(s.getExpression(), true); setUnderlineExpression(s.getExpression(), false); repaint(); return; } } return; } } setUnderlineExpression(null, true); setUnderlineExpression(null, false); } private void handleMouseMovedOnButton (MouseEvent event) { SmallStepProofModel mod = (SmallStepProofModel)model; ProofStep[] steps = mod.remaining(this.proofNode); if (steps.length >= 1) { Expression exp = steps [0].getExpression(); setUnderlineExpression(exp, true); setUnderlineExpression(exp, false); } } public void reset () { resetRenderer(); if (this.translateToCoreSyntax != null) { this.translateToCoreSyntax.setEnabled(this.proofNode.containsSyntacticSugar()); } } private void resetRenderer() { this.expRenderer = new ExpressionRenderer (this.proofNode.getExpression()); this.envRenderer = new EnvironmentRenderer<Location, Expression> ( ((SmallStepProofNode)this.proofNode).getStore()); this.expRenderer.checkFonts(); this.expRenderer.checkAnnotationSizes(); } private void initiateButtonMenu() { JPopupMenu menu = new JPopupMenu(); /* * The axioms and meta rules are put in one list again * * * JMenu axiomRules = new JMenu ("Axioms"); * JMenu metaRules = new JMenu ("Meta"); * * menu.add(axiomRules); * menu.add(metaRules); */ ProofRule rules[] = this.model.getRules(); for (ProofRule r : rules) { menu.add(new RuleMenuItem (r)); /* * RuleMenuItem item = new RuleMenuItem (r); * if (r.isAxiom()) { * axiomRules.add(item); * } * else { * metaRules.add(item); * } */ } menu.addSeparator(); this.translateToCoreSyntax = new ActionMenuItem (ActionType.TranslateToCoresyntax); menu.add(this.translateToCoreSyntax); if (!this.proofNode.containsSyntacticSugar()) { this.translateToCoreSyntax.setEnabled(false); } this.ruleButton.setMenu(menu); } public void setModel (ProofModel model) { super.setModel(model); initiateButtonMenu(); } public void placeMenuButtons() { if (this.proofNode.isProven()) { this.ruleButton.setVisible(false); return; } Font expF = this.ruleFont.deriveFont(this.ruleFont.getSize2D()); FontMetrics expFM = getFontMetrics(expF); int heightDiv2 = this.expMaxSize.height + this.ruleMaxSize.height / 2; int posX = 0; ProofStep[] steps = this.proofNode.getSteps(); for (int i=0; i<steps.length;) { ProofRule r = steps [i].getRule(); if (r.isAxiom()) { return; } else { i++; int j=0; for (; (j+i)<steps.length; j++) { ProofRule tmpRule = steps [i+j].getRule(); if (!tmpRule.getName ().equals (r.getName())) { break; } } posX += ruleFontMetrics.stringWidth ("(" + r.getName() + ")"); if (j >= 1) { posX += expFM.stringWidth("" + (j+1)); } posX += ruleFontMetrics.getDescent(); i += j; } } this.ruleButton.setBounds(posX, heightDiv2 - this.ruleButton.getNeededHeight(), this.ruleButton.getNeededWidth(), this.ruleButton.getNeededHeight()); this.ruleButton.setVisible(true); } public Dimension getRuleSize () { return this.ruleSize; } public void prepareRuleSize() { int maxX = 0; int w = 0; boolean addExp = false; Font expF = this.ruleFont.deriveFont(this.ruleFont.getSize2D()); FontMetrics expFM = getFontMetrics(expF); ProofStep steps [] = this.proofNode.getSteps(); for (int i=0; i<steps.length;) { ProofRule r = steps [i].getRule(); if (r.isAxiom()) { w = this.ruleFontMetrics.stringWidth("(" + r.getName() + ")"); i++; } else { i++; int j=0; for (; (j + i) <steps.length; j++) { ProofRule tmpRule = steps [i+j].getRule(); if (!tmpRule.getName().equals(r.getName())) { break; } } if (j >= 1) { addExp = true; w += expFM.stringWidth("" + (j+1)); } w += this.ruleFontMetrics.stringWidth("(" + r.getName() + ")"); w += this.ruleFontMetrics.getDescent(); i += j; } if (w > maxX) maxX = w; } if (!this.proofNode.isProven()) { maxX += this.ruleButton.getNeededWidth(); } int height = ruleFontMetrics.getHeight() * 2 + 10; if (addExp) { height += expFM.getDescent() * 2; } this.ruleSize = new Dimension (maxX + 10 , height); } public Dimension getExpressionSize(int maxWidth) { return this.expSize; } public void prepareExpressionSize (int maxWidth) { if (((InterpreterProofModel)model).isMemoryEnabled()) { this.envSize = envRenderer.getNeededSize(); } else { this.envSize = new Dimension (0, 0); } this.expSize = expRenderer.getNeededSize(maxWidth - this.envSize.width - 10 - 10); this.expEnvSize = new Dimension (this.expSize); this.expEnvSize.width += this.envSize.width; if (this.envSize.height > this.expEnvSize.height) { this.expEnvSize.height = this.envSize.height; } } public int setTop (int posY) { int addHeight = this.ruleFontMetrics.getHeight (); AbstractNode aParent = getParentNode (); if (aParent != null) { SmallStepNode parent = (SmallStepNode)aParent; int height = this.expEnvSize.height; if (parent.ruleSize.height > height) { height = parent.ruleSize.height; } this.expMaxSize = new Dimension (this.expEnvSize.width, height + addHeight); parent.ruleMaxSize = new Dimension (SmallStepNode.center, height + addHeight); parent.finishSize(posY); } else { this.expMaxSize = new Dimension (this.expEnvSize.width, this.expEnvSize.height + addHeight); } AbstractNode aChild = getFirstChild(); if (aChild == null) { this.ruleMaxSize = new Dimension (SmallStepNode.center, this.ruleSize.height + addHeight); finishSize (posY + this.expMaxSize.height); } return posY + this.expMaxSize.height; } private void finishSize (int posY) { int addWidth = this.ruleFontMetrics.getHeight (); int px = 25; //SmallStepNode.center - this.ruleMaxSize.width; int py = posY - this.expMaxSize.height; int dx = SmallStepNode.center + this.expMaxSize.width + addWidth; int dy = this.expMaxSize.height + this.ruleMaxSize.height; setBounds (px, py, dx, dy); } /** * Render the content of the SmallStepNode. * <br> * The centering arrow will get rendered manualy, * the expression and maybe the memory environment will * get Rendered by using the ExpressionRenderer and the * */ public void paintComponent(Graphics g) { // now draw the expression int heightDiv2 = this.expMaxSize.height / 2; int posX = center; int posY = heightDiv2 - expSize.height / 2; expRenderer.render(posX + this.ruleFontMetrics.getHeight(), posY, this.underlineExpression, g); if (((InterpreterProofModel)model).isMemoryEnabled()) { envRenderer.render(posX+5, posY, 5, this.expEnvSize.height, AbstractRenderer.BRACE_LEFT, g); int x = envRenderer.render(posX + this.ruleFontMetrics.getHeight() + this.expSize.width + 10, posY, expSize.height, g); envRenderer.render(x, posY, 5, this.expEnvSize.height, AbstractRenderer.BRACE_RIGHT, g); } ProofStep steps [] = this.proofNode.getSteps(); if (steps.length == 0 && this.proofNode.isProven()) { return; } // draw a black arrow on the base heightDiv2 = this.expMaxSize.height + this.ruleMaxSize.height / 2; g.setColor(Color.BLACK); g.drawLine(0, heightDiv2, center, heightDiv2); g.drawLine(center, heightDiv2, center - 5, heightDiv2 - 5); g.drawLine(center, heightDiv2, center - 5, heightDiv2 + 5); posY = heightDiv2; // now draw the evaluated rules g.setFont(this.ruleFont); posX = 0; // clear the bounds this.ruleBounds.clear(); g.setColor(Color.BLACK); Font expF = this.ruleFont.deriveFont(this.ruleFont.getSize2D()); FontMetrics expFM = getFontMetrics (expF); for (int i=0; i<steps.length;) { ProofRule r = steps [i].getRule(); if (r.isAxiom()) { posY = heightDiv2 + ruleFontMetrics.getAscent(); posX = 0; int px = posX; int py = posY + 5 - ruleFontMetrics.getAscent(); String ruleString = "(" + r.getName() + ")"; g.drawString(ruleString, posX, posY + 5); posX += ruleFontMetrics.stringWidth(ruleString); int dx = posX - px; int dy = ruleFontMetrics.getHeight(); this.ruleBounds.add(new RuleBound (r, new Rectangle (px, py, dx, dy))); break; } else { i++; int j=0; for (; (j+i)<steps.length; j++) { ProofRule tmpRule = steps [i+j].getRule(); if (!tmpRule.getName().equals(r.getName())) { break; } } int px = posX; int py = posY - 5 - ruleFontMetrics.getAscent(); String ruleString = "(" + r.getName() + ")"; g.drawString(ruleString, posX, posY - 5); posX += ruleFontMetrics.stringWidth(ruleString); int dx = posX - px; int dy = ruleFontMetrics.getHeight(); if (j >= 1) { ruleString = "" + (j + 1); int tmpY = posY - this.ruleFontMetrics.getAscent() + expFM.getAscent() - expFM.getDescent() - 5; g.setFont(expF); g.drawString (ruleString, posX, tmpY); posX += expFM.stringWidth(ruleString); g.setFont(this.ruleFont); } this.ruleBounds.add(new RuleBound (r, new Rectangle (px, py, dx, dy))); posX += ruleFontMetrics.getDescent(); i += j; } } } public static void setCenter(int center) { SmallStepNode.center = center; } public static int getCenter() { return SmallStepNode.center; } private void evaluateRule (ProofRule rule) { String buttonText = this.ruleButton.getText(); try { this.ruleButton.setText(""); this.model.prove(rule, this.proofNode); this.ruleButton.setTextColor(Color.BLACK); } catch (ProofRuleException exc) { this.ruleButton.setText(buttonText); this.ruleButton.setTextColor(Color.RED); this.view.relayout(); } } private void translateToCoreSyntax () { this.ruleButton.setText(""); try { this.model.translateToCoreSyntax(this.proofNode); } catch (IllegalArgumentException exc) { } } }