package de.unisiegen.tpml.graphics.subtyping; 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.languages.Language; import de.unisiegen.tpml.core.subtyping.SubTypingModel; import de.unisiegen.tpml.core.subtyping.SubTypingProofModel; import de.unisiegen.tpml.core.subtyping.SubTypingProofNode; import de.unisiegen.tpml.core.subtypingrec.DefaultSubType; import de.unisiegen.tpml.core.subtypingrec.RecSubTypingProofNode; import de.unisiegen.tpml.core.typechecker.SeenTypes; import de.unisiegen.tpml.graphics.Messages; import de.unisiegen.tpml.graphics.components.LabelComponent; import de.unisiegen.tpml.graphics.components.MenuButton; import de.unisiegen.tpml.graphics.components.MenuButtonListener; import de.unisiegen.tpml.graphics.components.MenuEnterTypeItem; 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.NailSymbolComponent; import de.unisiegen.tpml.graphics.components.RulesMenu; import de.unisiegen.tpml.graphics.components.TypeComponent; import de.unisiegen.tpml.graphics.outline.listener.OutlineMouseListener; import de.unisiegen.tpml.graphics.renderer.AbstractRenderer; import de.unisiegen.tpml.graphics.renderer.PrettyStringToHTML; import de.unisiegen.tpml.graphics.smallstep.SmallStepComponent; import de.unisiegen.tpml.graphics.tree.TreeNodeComponent; import de.unisiegen.tpml.graphics.typeinference.TypeInferenceComponent; /** * Graphical representation of a {@link SubTypingProofNode }.<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 SubTyping 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/SubTypingnode.png" /><br> * <br> * This node is actualy build using 4 components. The following scheme * illustrates the layouting of the single components.<br> * <img src="../../../../../../images/SubTypingnode_scheme.png" /><br> * <br> * The first rectangle represents the {@link #indexLabel} The second rectanle * represents the entire {@link #typeComponent}. The last element in the first * row is the {@link #typeComponent2}. * If the node is not completly evaluated only the four dots are drawn.<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}.That could be used to give the user * the possibilty to enter a type by himself.<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 SubTypingComponent} 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 * @author michael * * @see de.unisiegen.tpml.graphics.subtyping.SubTypingView * @see de.unisiegen.tpml.graphics.subtyping.SubTypingComponent * @see de.unisiegen.tpml.graphics.tree.TreeNodeComponent */ public class SubTypingNodeComponent extends JComponent implements TreeNodeComponent { /** * */ private static final long serialVersionUID = -6671706090992083026L; /** * The Model this node can work with to guess, enter type or do Coresyntax * transaltion */ private SubTypingModel proofModel; /** * The Origin {@link SubTypingProofNode} this node represents. */ private SubTypingProofNode proofNode; /** * The calculated dimension for this node in pixels. */ private Dimension dimension; /** * The spacing that should be set free between to components within the node. */ private int spacing; /** * The label containing the <i>(x)</i> text at the beginning. */ private JLabel indexLabel; /** * The label containing the A and the Nail needed for RecSubTypingNodes */ private JLabel subTypingRecA; /** * The Component drawing the label subTypingRecA */ private LabelComponent label; /** * The nail Symbol of the A */ private NailSymbolComponent nail; /** * The sub type sign "<:" */ private JLabel subType; /** * The label component containing the subtype sign */ private LabelComponent lcSubType; /** * The {@link TypeComponent} containing the left type of this node * */ private TypeComponent typeComponent; /** * The {@link TypeComponent} containing the right type of this node * */ private TypeComponent typeComponent2; /** * The {@link MenuButton} the user can use to do the actions. */ private MenuButton ruleButton; /** * The {@link JLabel} showing the resulting type of this node, once the node * has been evaluated. */ private JLabel typeLabel; /** * The {@link JLabel} showing the information about the rule, once the rule * has been evaluated. */ private JLabel ruleLabel; /** * containing the rules it may contain submenus if to many (set in TOMANY) * rules are in the popupmenu */ private JPopupMenu menu; /** * The Manager for teh RulesMenus */ private RulesMenu rulesMenu = new RulesMenu(); /** * Constructor for a SubTypingNodeComponent<br> * <br> * All elements needed within the node will be created and added to the * component. Some of them will be hidden at first ( the {@link #ruleLabel}) * because they are not needed but they are always there.<br> * * @param node The origin ProofNode * @param model The model */ public SubTypingNodeComponent ( SubTypingProofNode node, SubTypingModel model ) { super ( ); this.proofNode = node; this.proofModel = model; this.dimension = new Dimension ( 0, 0 ); this.spacing = 10; this.indexLabel = new JLabel ( ); this.indexLabel.addMouseListener ( new OutlineMouseListener ( this ) ); add ( this.indexLabel ); this.subTypingRecA = new JLabel ( ); this.label = new LabelComponent ( ); add ( this.label ); this.nail = new NailSymbolComponent(); add(this.nail); this.typeComponent = new TypeComponent ( ); this.typeComponent.setText ( "" ); //$NON-NLS-1$ this.typeComponent.addMouseListener ( new OutlineMouseListener ( this ) ); this.subType = new JLabel ( "<:" ); //$NON-NLS-1$ this.lcSubType = new LabelComponent ( ); add ( this.lcSubType ); add ( this.typeComponent ); this.typeComponent2 = new TypeComponent ( ); this.typeComponent2.setText ( "" ); //$NON-NLS-1$ this.typeComponent2.addMouseListener ( new OutlineMouseListener ( this ) ); add ( this.typeComponent2 ); changeNode ( ); /* * Create both, the ruleButton for selecting the rule and the label, that * will be displayed later */ this.ruleButton = new MenuButton ( ); add ( this.ruleButton ); this.ruleButton.setVisible ( true ); this.ruleLabel = new JLabel ( ); add ( this.ruleLabel ); this.ruleLabel.setVisible ( false ); this.typeLabel = new JLabel ( ); add ( this.typeLabel ); this.typeLabel.setText ( " <: " ); //$NON-NLS-1$ this.typeLabel.addMouseListener ( new OutlineMouseListener ( this ) ); /* * Create the PopupMenu for the menu button */ menu = new JPopupMenu ( ); ProofRule[] rules = this.proofModel.getRules ( ); if ( rules.length > 0 ) { int group = rules[0].getGroup ( ); for ( ProofRule r : rules ) { if ( r.getGroup ( ) != group ) { menu.addSeparator ( ); } menu.add ( new MenuRuleItem ( r ) ); group = r.getGroup ( ); } } menu.addSeparator ( ); menu.add ( new MenuEnterTypeItem ( ) ); menu.add ( new MenuGuessItem ( ) ); menu.add ( new MenuGuessTreeItem ( ) ); this.ruleButton.setMenu ( menu ); /* * Connect the handling of the ruleButton */ this.ruleButton.addMenuButtonListener ( new MenuButtonListener ( ) { public void menuClosed ( @SuppressWarnings ( "unused" ) MenuButton button ) { // Nothing to do } public void menuItemActivated ( @SuppressWarnings ( "unused" ) 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 ( ) { @SuppressWarnings ( "synthetic-access" ) public void run ( ) { // handle the menu action SubTypingNodeComponent.this.handleMenuActivated ( source ); // wait for the repaint before resetting the cursor SwingUtilities.invokeLater ( new Runnable ( ) { public void run ( ) { // reset the cursor toplevel.setCursor ( cursor ); } } ); } } ); } } ); } /** * Causes the types recalculate their layout. */ public void reset ( ) { this.typeComponent.reset ( ); this.typeComponent2.reset ( ); } /** * Sets the index that will be displayed in front of the node * * @param index The index */ public void setIndex ( int index ) { this.indexLabel.setText ( "(" + index + ")" ); //$NON-NLS-1$ //$NON-NLS-2$ } /** * Causes the SubTypingNodeComponent to updated theire expression and * environment. */ public void changeNode ( ) { this.subTypingRecA.setText ( "" ); //$NON-NLS-1$ this.label.setLabel ( this.subTypingRecA ); // if the node is RecSubTypingNode we have to render the A and set the Tooltip if ( this.proofNode instanceof RecSubTypingProofNode ) { this.subTypingRecA.setText ( "A" ); //$NON-NLS-1$ this.label.setLabel ( this.subTypingRecA ); RecSubTypingProofNode node = ( RecSubTypingProofNode ) this.proofNode; //build the tooltip String tooltip = "<html>{"; //$NON-NLS-1$ SeenTypes < DefaultSubType > seenTypes = node.getSeenTypes ( ); for ( DefaultSubType s : seenTypes ) { tooltip += "<font color=\"#FF0000\"> (</font>"; //$NON-NLS-1$ tooltip += PrettyStringToHTML.toHTMLString ( s.toPrettyString ( ) ); tooltip += "<font color=\"#FF0000\">) </font>"; //$NON-NLS-1$ } tooltip += "}<html>"; //$NON-NLS-1$ this.label.setToolTipText ( tooltip ); } this.typeComponent.setType ( this.proofNode.getLeft ( ) ); this.lcSubType.setLabel ( this.subType ); this.typeComponent2.setType ( this.proofNode.getRight ( ) ); } /** * Places all elements one after another.<br> * <br> * First the label, the expression and the "::" will be placed, if the node is * already prooven the {@link #typeLabel} will be placed aswell. The * {@link #dimension} will be rescaled with every item that is placed and with * all items, the height of the dimension will set to the current maximum.<br> * <br> * When all item of the top row are placed the {@link #ruleButton} or * {@link #ruleLabel} will be placed depending whether * the node is evaluated, it is not evaluated or the user previously selected * <i>Enter type</i>. * * @param pMaxWidth The maximum amount of pixels available to place the * elements. */ 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 ); Dimension labelComponentSize = new Dimension ( fm.stringWidth ( this.subTypingRecA.getText ( ) ), fm.getHeight ( ) ); this.dimension.width += labelComponentSize.width + this.spacing; Dimension lcSubtypeSize = new Dimension ( fm.stringWidth ( this.subType.getText ( ) ), fm.getHeight ( ) ); Dimension nailComponentSize = new Dimension (fm.stringWidth ( "--" ), fm.getHeight ( )); this.dimension.width += nailComponentSize.width + this.spacing; this.dimension.width += lcSubtypeSize.width + this.spacing; // there will be a bit spacing between the index label and first type this.dimension.width += this.spacing; // the index shrinkens the max size for the types maxWidth -= labelSize.width; // get the needed size for the types Dimension expSize = this.typeComponent.getNeededSize ( maxWidth ); this.dimension.width += expSize.width + this.spacing; this.dimension.height = Math.max ( expSize.height, this.dimension.height ); /*Dimension expSize2 = this.typeComponent2.getNeededSize ( maxWidth ); this.dimension.width += expSize2.width + this.spacing;*/ // get the neede size for the type this.proofNode.getRight ( ).toPrettyString ( ); this.typeLabel.setText ( "" + this.proofNode.getLeft ( ) ); //$NON-NLS-1$ //Dimension typeSize = this.typeLabel.getPreferredSize ( ) ; Dimension typeSize = this.typeComponent2.getNeededSize ( maxWidth ); boolean broke = false; if ( ( this.dimension.width + typeSize.width + this.spacing ) > maxWidth ) //passt nicht mehr { this.dimension.width = Math.max ( this.dimension.width, expSize.width ); this.dimension.height += typeSize.height; this.dimension.height += AbstractRenderer.getAbsoluteHeight ( ); broke = true; } else { this.dimension.width += typeSize.width + this.spacing; this.dimension.height = Math.max ( typeSize.height, this.dimension.height ); } // now place the components int posX = 0; this.indexLabel.setBounds ( posX, 0, labelSize.width, this.dimension.height ); posX += labelSize.width + this.spacing; if ( this.proofNode instanceof RecSubTypingProofNode ) { this.label.setBounds ( posX, 0, labelComponentSize.width, this.dimension.height ); posX += labelComponentSize.width + this.spacing; this.nail.setBounds ( posX, 0, nailComponentSize.width, this.dimension.height ); posX += nailComponentSize.width + this.spacing; } this.typeComponent.setBounds ( posX, 0, expSize.width, this.dimension.height ); int posXfront = posX; posX += expSize.width + this.spacing; this.lcSubType.setBounds ( posX, 0, lcSubtypeSize.width, this.dimension.height ); posX += lcSubtypeSize.width + this.spacing; if ( broke ) { this.typeComponent2.setBounds ( posXfront, 0 + expSize.height + AbstractRenderer.getAbsoluteHeight ( ), typeSize.width, typeSize.height ); } else { this.typeComponent2.setBounds ( posX, 0, typeSize.width, typeSize.height ); } posX += typeSize.width; /* * 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.isProven ( ) ) { // place the menu label this.ruleLabel.setText ( this.proofNode.getRule ( ).toString ( ) ); 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 eventhandling */ /** * Add the given node listener. * * @param listener the new node listener */ public void addSubTypingNodeListener ( SubTypingNodeListener listener ) { this.listenerList.add ( SubTypingNodeListener.class, listener ); } /** * * Remove the given listener. * * @param listener the node listener which should be removed */ public void removeSubTypingNodeListener ( SubTypingNodeListener listener ) { this.listenerList.remove ( SubTypingNodeListener.class, listener ); } private void fireNodeChanged ( ) { Object[] listeners = this.listenerList.getListenerList ( ); for ( int i = 0; i < listeners.length; i += 2 ) { if ( listeners[i] != SubTypingNodeListener.class ) { continue; } ( ( SubTypingNodeListener ) 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] != SubTypingNodeListener.class ) { continue; } ( ( SubTypingNodeListener ) listeners[i + 1] ).requestJumpToNode ( node ); } } /** * Handles every action, that is done via the menu of the {@link #ruleButton}.<br> * <br> * Because every item in the menu (except the Separatero :-) ) is one of our * own, the activated type of item can simply be identified by its class.<br> * * @param item */ private 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 ( Exception exc ) { // 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 text for the tooltip this.ruleButton.setToolTipText ( exc.getMessage ( ) ); } 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$ } } ); } fireNodeChanged ( ); } 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 ( ); } } /* * Implementation of the TreeNodeComponent Interface */ /** * Performs an update for the Entire Node. All elements get rearanged based on * the given maximum with, the menu items will be checked if they are still * available. */ public Dimension update ( int maxWidth ) { placeElements ( maxWidth ); //this.menuTranslateItem.setEnabled ( this.translator.containsSyntacticSugar ( this.proofNode.getExpression ( ), // true ) ); return this.dimension; } /** * Returns the number of pixels the children should be displayed indentated. */ public int getIndentationWidth ( ) { // XXX: calculate the indentation return this.indexLabel.getWidth ( ); } /** * 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 ); } /** * 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 typeComponent. * * @return The typeComponent. * @see #typeComponent */ public TypeComponent getTypeComponent ( ) { return this.typeComponent; } /** * Returns the typeComponent2. * * @return The typeComponent2. * @see #typeComponent2 */ public TypeComponent getTypeComponent2 ( ) { return this.typeComponent2; } /** * Returns the proofNode. * * @return The proofNode. * @see #proofNode */ public SubTypingProofNode getProofNode ( ) { return this.proofNode; } /** * Returns the indexLabel. * * @return The indexLabel. * @see #indexLabel */ public JLabel getIndexLabel ( ) { return this.indexLabel; } /** * Sets whether the small step view operates in advanced or beginner mode. * * @param pAdvanced * <code>true</code> to display only axiom rules in the menu. * * @see TypeInferenceComponent#setAdvanced(boolean) */ void setAdvanced(boolean pAdvanced) { ((SubTypingModel) this.proofModel).setAdvanced ( pAdvanced ); // Fill the menu with menuitems JPopupMenu newMenu = new JPopupMenu(); ProofRule[] rulesOfModel = this.proofModel.getRules(); if (rulesOfModel.length > 0) { int group = rulesOfModel[0].getGroup(); for (ProofRule r : rulesOfModel) { { if (r.getGroup() != group) { newMenu.addSeparator(); } newMenu.add(new MenuRuleItem(r)); group = r.getGroup(); } } } newMenu.addSeparator(); newMenu.add(new MenuGuessItem()); newMenu.add(new MenuGuessTreeItem());//this.rules.getMenuButton().setMenu(this.menu); //this.rules.getMenuButton().setMenu(menu); this.ruleButton.setMenu ( newMenu ); } }