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);
}
}