package variableEditorComponents; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.util.Observable; import java.util.Observer; import javax.swing.AbstractAction; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.event.UndoableEditEvent; import javax.swing.event.UndoableEditListener; import javax.swing.text.Document; import javax.swing.undo.CannotRedoException; import javax.swing.undo.CannotUndoException; import javax.swing.undo.UndoManager; import parser.RecursiveDescentParser; import parser.Value; import variableEditorUI.VariableEditorUIUpdateThread; import variables.Variable; import expressionConsole.ExpressionConsoleModel; /** * A JTextField which is bound to a Variable. When the user enters something in * the text field and presses the enter key, the content of the Variable is * updated with the parsed content of the text field. When the Variable is * changed from another source, the content of the text field is updated with * the new contents of the Variable. * * @author Curran Kelleher * */ public class VariableBoundTextField extends JTextField implements KeyListener, Observer, VariableEditorComponent { private static final long serialVersionUID = 7668831707610334469L; /** * The Variable to which this VariableBoundTextField is bound. */ Variable variable; /** * The parser which will be used to parse the user's input to the text field * when the enter key is pressed. */ RecursiveDescentParser parser = ExpressionConsoleModel.getInstance() .getParser(); /** * The Value that is currently being displayed in this text field. This is * checked against the actual value of the Variable periodically to decide * whether or not to update the text with the current value of the Variable. * This String is parseable into the actual value. It is generated by the * method Value.toParseableString() */ String displayedValue = ""; /** * Construct a VariableBoundTextField which is bound to the specified * Variable. * * @param variable * the variable to edit */ public VariableBoundTextField(Variable variable) { this.variable = variable; // set up listening to the update thread for updates VariableEditorUIUpdateThread.getInstance().addObserver(this); // set up the key listener addKeyListener(this); // initialize the text field updateWithCurrentVariableValue(); //add undo redo support setUpUndoRedo(); } /** * Sets up undo (ctrl z) and redo (ctrl y) * */ @SuppressWarnings("serial") private void setUpUndoRedo() { final UndoManager undo = new UndoManager(); Document doc = getDocument(); // Listen for undo and redo events doc.addUndoableEditListener(new UndoableEditListener() { public void undoableEditHappened(UndoableEditEvent evt) { undo.addEdit(evt.getEdit()); } }); // Create an undo action and add it to the text component getActionMap().put("Undo", new AbstractAction("Undo") { public void actionPerformed(ActionEvent evt) { try { if (undo.canUndo()) { undo.undo(); } } catch (CannotUndoException e) { } } }); // Bind the undo action to ctl-Z getInputMap().put(KeyStroke.getKeyStroke("control Z"), "Undo"); // Create a redo action and add it to the text component getActionMap().put("Redo", new AbstractAction("Redo") { public void actionPerformed(ActionEvent evt) { try { if (undo.canRedo()) { undo.redo(); } } catch (CannotRedoException e) { } } }); // Bind the redo action to ctl-Y getInputMap().put(KeyStroke.getKeyStroke("control Y"), "Redo"); } /** * Called when the user enters text and hits the enter key. This method * updates the contents of the variable to displayedValue, which is set to * the contents of the text field after being parsed and evaluated. * * @param text * the text in the text field when the user hit the enter key. */ protected void processInputText(String text) { // log the change for correct replay ExpressionConsoleModel.getInstance().enterExpression( variable.toString() + " = " + getText()); } /** * Responds to the enter key when it is pressed in the function field. * * @param e * the key event */ public void keyPressed(KeyEvent e) { // only act if the enter key has been pressed if (e.getKeyChar() == '\n') // update the string function variable with the new function String. processInputText(getText()); displayedValue = getText(); } public void keyTyped(KeyEvent e) { } public void keyReleased(KeyEvent e) { } /** * Get the update from the VariableEditorUIUpdateThread */ public void update(Observable o, Object arg) { if (o == VariableEditorUIUpdateThread.getInstance()) { // if this component is no longer usable, remove it as an Observer. if (!isDisplayable()) VariableEditorUIUpdateThread.getInstance().deleteObserver(this); else if (!variable.evaluate().toParseableString().equals( displayedValue)) if (!hasFocus()) updateWithCurrentVariableValue(); } else if (arg == variable)// if the update is coming directly from the // variable updateWithCurrentVariableValue(); } /** * Updates the content of the text field to reflect the current value of the * variable. * */ public void updateWithCurrentVariableValue() { Value currentValue = variable.evaluate(); displayedValue = currentValue.toParseableString(); setText(displayedValue); // so the first part of the number is displayed rather than the last setCaretPosition(0); } /** * Sets up a VariableEditorComponent such that * updateWithCurrentVariableValue() will be called in it whenever the enter * key is pressed in the text field. * * @param componentToUpdate * the VariableEditorComponent to update when the enter key is * pressed in the text field. */ public void bindToVariableEditorComponent( VariableEditorComponent componentToUpdate) { class ComponentKeyListener implements KeyListener { VariableEditorComponent componentToUpdate; public ComponentKeyListener( VariableEditorComponent componentToUpdate) { this.componentToUpdate = componentToUpdate; } public void keyTyped(KeyEvent e) { if (e.getKeyChar() == '\n') componentToUpdate.updateWithCurrentVariableValue(); } public void keyPressed(KeyEvent e) { } public void keyReleased(KeyEvent e) { } } addKeyListener(new ComponentKeyListener(componentToUpdate)); } /** * Sets this object up as a direct observer of the Variable it is editing. * Normally, it updates the variable periodically, not as a direct result of * ot's changing. Calling this method will change it's behavior such that it * always updates immediately when the variable changes, not periodically. * */ public void setUpAsDirectObserver(String explaination) { VariableEditorUIUpdateThread.getInstance().deleteObserver(this); variable.addObserver(this, explaination); } }