package ui;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.util.EventObject;
import java.util.Vector;
import java.util.HashMap;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JTextField;
import expressions.Constant;
import expressions.Expression;
import expressions.Identifier;
import expressions.Projection;
import typing.MonoType;
import typing.ProofNode;
import typing.ProofTree;
import typing.Rule;
import ui.beans.MenuButton;
import ui.beans.MenuButtonListener;
/**
* Implementation of the TypeCheckerComponent.
* @author marcell
*
*/
public class TypeCheckerComponent extends JComponent {
/**
* The Node item represents a single TypeJudgement line.
* Containing of the position where to render the Judgement.
*
* @author marcell
*
*/
private class NodeItem {
/**
* The x position where the Judgement should be drawn to
*/
public int x;
/**
* The y position where the Judgement should be drawn to
*/
public int y;
/**
* The x position of the parent where the vertical line of
* the Tree begins.
*/
public int parentX;
/**
* The y position of the parent where the vertical line of
* the Tree begins.
*/
public int parentY;
/**
* True when the Judgement has a parent.
*/
public boolean hasParent;
/**
* The node from the proof tree that should be rendered.
*/
public ProofNode node;
}
/**
* The Tree model that is used to display the expression tree
*/
private ProofTree model;
/**
* The number of pixels the tree will be indented every childstep
*/
private int indentionDepth;
/**
* Counter that is needed to mark the Judgements
*/
private int ruleId;
/**
* Helper variable that is used to decide which ComboBox
* from the vector of comboboxes should be set when needed
*/
private int whichComboBox;
/**
* The font that is used to render the judgement
*/
private Font expressionFont;
private FontMetrics expressionFM;
/**
*
*/
private Font ruleFont;
private FontMetrics ruleFM;
/**
*
*/
private Vector<MenuButton> menuButtons;
/**
*
*/
private Vector<ProofNode> selectionRelation;
/**
*
*/
private HashMap<String, Rule> rules;
private Dimension selectionSize;
private Dimension maxSize;
private enum ActionType {
ActionTypeTranslateToCoresyntax,
ActionTypeEnterType,
}
private class RuleMenuItem extends JMenuItem {
private Rule rule;
public RuleMenuItem (Rule rule) {
super (rule.getName());
this.rule = rule;
}
public Rule 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 ActionTypeTranslateToCoresyntax:
return "Translate to coresyntax";
case ActionTypeEnterType:
return "Enter type";
}
return "narf";
}
public ActionType getActionType () {
return type;
}
}
public TypeCheckerComponent () {
super ();
this.setLayout(null);
this.indentionDepth = 25;
Font font = new JComboBox ().getFont();
this.expressionFont = font.deriveFont(font.getSize2D() * 1.3f);
this.expressionFM = getFontMetrics (this.expressionFont);
this.ruleFont = new JComboBox().getFont();
this.ruleFM = getFontMetrics (this.ruleFont);
this.menuButtons = new Vector<MenuButton>();
this.selectionRelation = new Vector<ProofNode> ();
this.rules = new HashMap<String, Rule>();
this.maxSize = new Dimension ();
FontMetrics fm = getFontMetrics (font);
this.selectionSize = new Dimension (fm.stringWidth("DAS IST NE RULE"), fm.getHeight());
for (Rule r : Rule.getAllRules()) {
rules.put(r.getName(), r);
}
}
public void setModel(ProofTree model) {
this.model = model;
checkNumberOfNeededSelections();
repaint ();
}
public void setIndentionDepth(int depth) {
this.indentionDepth = depth;
}
private boolean isLeaf (Expression exp) {
return ( (exp instanceof Constant)
|| (exp instanceof Identifier)
|| (exp instanceof Projection));
}
public void handleButtonChanged (MenuButton button, JMenuItem item) {
if (item instanceof RuleMenuItem) {
RuleMenuItem ruleItem = (RuleMenuItem)item;
Rule rule = ruleItem.getRule();
if (rule == null)
return;
int p = this.menuButtons.indexOf(button);
if (p == -1)
return;
ProofNode proofNode = this.selectionRelation.elementAt(p);
fireEvent (proofNode, rule);
}
else if (item instanceof ActionMenuItem) {
ActionMenuItem actionItem = (ActionMenuItem)item;
switch (actionItem.getActionType()) {
case ActionTypeEnterType:
handleEnterType (button);
break;
case ActionTypeTranslateToCoresyntax:
handleTranslteToCoreSyntax(button);
break;
}
}
}
private void handleEnterType (MenuButton button) {
button.setVisible(false);
int p = this.menuButtons.indexOf(button);
if (p == -1)
return;
ProofNode proofNode = this.selectionRelation.elementAt(p);
FontMetrics fm = getFontMetrics (new JTextField().getFont());
TypeEnterGUI gui = new TypeEnterGUI (proofNode);
gui.addTypeEnterListener(new TypeEnterListener() {
public void typeAccepted (JComponent gui, String typeString, ProofNode node, MonoType type) {
fireGuess(node, type);
remove (gui);
}
public void typeRejected (JComponent gui) {
remove (gui);
repaint ();
}
});
add(gui);
gui.setBounds(button.getX(), button.getY(), 250, fm.getHeight() + 8);
gui.setVisible(true);
gui.setFocus();
}
private void handleTranslteToCoreSyntax(MenuButton button) {
}
private Vector<Expression> tempExpressions;
/**
* Checks a ProofNode (with its childnodes) to decide how many
* JComboBoxes are needed to allow the user to interact.
*
* @param node The node that should be checked
* @param currentlyNeeded The number of selections that are needed to
* the current stage.
* @return Return the number of selections that are needed for this
* branch of the ProofTree.
*/
private int checkNextNode (ProofNode node, int currentlyNeeded) {
// every node that is currently not marked with a rule has
// still needs to get evaluated by the user so we need a
// selection box here.
if (node.getRule() == null) {
currentlyNeeded++;
tempExpressions.add(node.getJudgement().getExpression());
}
else {
for (int i=0; i<node.getChildCount(); i++) {
ProofNode child = (ProofNode)node.getChildAt(i);
currentlyNeeded = checkNextNode (child, currentlyNeeded);
}
}
return currentlyNeeded;
}
/**
* Initiates the recursive check how much selection JComboBoxes are
* needed to allow the user to interact. <br>
*
*/
private void checkNumberOfNeededSelections() {
if (model == null)
return;
tempExpressions = new Vector<Expression>();
int selectionCount = checkNextNode ((ProofNode)model.getRoot(), 0);
for (MenuButton b : menuButtons) {
this.remove(b);
}
this.menuButtons.clear ();
this.selectionRelation.clear ();
for (int i=0; i<selectionCount; i++) {
MenuButton menuButton = new MenuButton();
this.menuButtons.add(menuButton);
menuButton.setVisible(false);
JPopupMenu menu = new JPopupMenu();
JMenuItem item;
for (Rule r : Rule.getAllRules()) {
item = new RuleMenuItem (r);
item.setFont(item.getFont().deriveFont(Font.PLAIN));
menu.add(item);
}
Expression exp = tempExpressions.elementAt(i);
menu.addSeparator();
item = new ActionMenuItem(ActionType.ActionTypeTranslateToCoresyntax);
item.setEnabled(exp.containsSyntacticSugar());
menu.add(item);
item = new ActionMenuItem(ActionType.ActionTypeEnterType);
menu.add(item);
menuButton.setMenu(menu);
menuButton.addMenuButtonListener(new MenuButtonListener () {
public void menuItemActivated (MenuButton button, JMenuItem item) {
handleButtonChanged (button, item);
}
public void menuClosed (MenuButton button) {
}
});
}
}
/**
* Renders a ProofNode encapsulates within the NodeItem with the
* Environment, the Expression and the resulting Type.
*
* @param g2d The Rendering context used to draw the text and the
* TreeLines
* @param node The NodeItem containing the positions the parentPosition
* and the ProofNode that should be rendered.
* @return The vertical position where the TreeNode ends on the screen.
*/
public int renderTreeNode (Graphics2D g2d, NodeItem node, String sindention) {
g2d.setFont(this.expressionFont);
// build the judgement for this line in the way
// (<ID>) [<ENVIRONMENT>] |> <EXPRESSION> :: <TYPE>
String judgement = "(" + this.ruleId + ") " +
node.node.getJudgement().getEnvironment() + " \u22b3 " +
node.node.getJudgement().getExpression().toString() +
" : : ";
if (node.node.isFinished()) {
judgement += node.node.getJudgement().getType() + " ";
}
int identifierSize = this.expressionFM.stringWidth ("(" + this.ruleId + ")");
// Draw the two orthogonal lines from the front of this judgement
// line to the bottom of the leading parent judgement when a parent is
// is present.
if (node.hasParent) {
int nx = node.x - 2;
int ny = node.y - this.expressionFM.getDescent();
g2d.drawLine(node.parentX, node.parentY, node.parentX, ny);
g2d.drawLine(node.parentX, ny, nx, ny);
Polygon poly = new Polygon ();
poly.addPoint (node.parentX, node.parentY);
poly.addPoint (node.parentX + this.expressionFM.getAscent()/2, node.parentY + this.expressionFM.getAscent());
poly.addPoint (node.parentX, node.parentY + this.expressionFM.getAscent() / 2);
poly.addPoint (node.parentX - this.expressionFM.getAscent()/2, node.parentY + this.expressionFM.getAscent());
g2d.fill (poly);
}
// find the position in the center of the (<ID>) below the judgement
int parentX = node.x + identifierSize / 2;
int parentY = node.y + 5;
// render the judgement
g2d.drawString(judgement, node.x, node.y);
// find the max size behind the judgement line
int size = node.x + this.expressionFM.stringWidth(judgement);
if (size > this.maxSize.width) {
this.maxSize.width = size;
}
// check wether we have to put a combobox
Expression exp = node.node.getJudgement().getExpression();
boolean idConst = isLeaf (exp);
// (exp instanceof Constant) || (exp instanceof Identifier);
if (node.node.getRule() == null) {
// find the correct combobox with the
MenuButton button = this.menuButtons.elementAt(this.whichComboBox);
if (!button.isVisible()) {
this.add(button);
this.selectionRelation.add(node.node);
button.setVisible(true);
}
this.whichComboBox++;
this.selectionSize.width = button.getPreferredSize().width;
this.selectionSize.height = button.getPreferredSize().height;
// check whether this judgement is based on CONST or ID
// if so, the combobox will be placed behind the judgement line.
// if not, the combobox will be placed below.
if (idConst) {
int boxX = node.x + this.expressionFM.stringWidth(judgement);
int boxY = node.y;
boxY -= selectionSize.height - this.expressionFM.getDescent();
button.setBounds(boxX, boxY, selectionSize.width, selectionSize.height);
}
else {
int boxX = node.x + identifierSize;
int boxY = node.y + this.expressionFM.getDescent() + selectionSize.height / 2;
button.setBounds(boxX, boxY, selectionSize.width, selectionSize.height);
node.y += selectionSize.height + selectionSize.height / 2 + this.expressionFM.getDescent();
}
if (button.getX() + button.getWidth() > this.maxSize.width) {
this.maxSize.width = button.getX() + button.getWidth();
}
}
else {
g2d.setFont(this.ruleFont);
g2d.setColor(new Color (255, 0, 0));
int ruleX = 0;
int ruleY = 0;
if (idConst) {
ruleX = node.x + this.expressionFM.stringWidth(judgement);
ruleY = node.y;
}
else {
ruleX = node.x + identifierSize;
ruleY = node.y + this.ruleFM.getHeight () + selectionSize.height / 2;
node.y = ruleY;
}
// find the max size behind the evaluated rule
size = ruleX + this.ruleFM.stringWidth(node.node.getRule ().toString());
if (size > this.maxSize.width) {
this.maxSize.width = size;
}
g2d.drawString(node.node.getRule().toString(), ruleX, ruleY);
g2d.setColor(new Color (0, 0, 0));
}
// increase the judgment id
this.ruleId++;
// continue with the child judgements.
node.y += this.expressionFM.getHeight() + 15;
for (int i=0; i<node.node.getChildCount(); i++) {
// create the NodeItem that is used to draw
NodeItem newNode = new NodeItem();
newNode.hasParent = true;
newNode.node = node.node.getChildAt(i);
newNode.x = node.x + this.indentionDepth;
newNode.y = node.y;
newNode.parentX = parentX;
newNode.parentY = parentY;
// proceed with the children
node.y = renderTreeNode (g2d, newNode, sindention + " ");
}
return (node.y);
}
/**
* Reimplemented function from the JComponent.<br><br>
*
* Clears the background white and renders the TypingTree
* @param g The graphics context that will be used to render
* the Tree
*/
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D)g.create();
g2d.setColor(Color.white);
g2d.fillRect(0, 0, getWidth(), getHeight());
g2d.setColor(Color.black);
if (model != null) {
NodeItem newNode = new NodeItem();
newNode.hasParent = false;
newNode.node = (ProofNode)model.getRoot();
newNode.x = 25;
newNode.y = 25;
this.ruleId = 1;
this.whichComboBox = 0;
this.maxSize.width = 0;
this.maxSize.height = renderTreeNode (g2d, newNode, "");
}
setPreferredSize(this.maxSize);
}
public void addTypeCheckerEventListener (TypeCheckerEventListener listener) {
listenerList.add(TypeCheckerEventListener.class, listener);
}
public void fireEvent(ProofNode node, Rule rule) {
Object[] listeners = listenerList.getListenerList();
for (int i = listeners.length-2; i>=0; i-=2) {
if (listeners[i]==TypeCheckerEventListener.class) {
// Lazily create the event:
((TypeCheckerEventListener)listeners[i+1]).applyRule(new EventObject(this), node, rule);
}
}
}
public void fireGuess (ProofNode node, MonoType type) {
Object[] listeners = listenerList.getListenerList();
for (int i = listeners.length-2; i>=0; i-=2) {
if (listeners[i]==TypeCheckerEventListener.class) {
// Lazily create the event:
((TypeCheckerEventListener)listeners[i+1]).guessType(new EventObject(this), node, type);
}
}
}
}