summaryrefslogtreecommitdiffstats
path: root/server/src/com/vaadin/ui/AbstractTextField.java
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/com/vaadin/ui/AbstractTextField.java')
-rw-r--r--server/src/com/vaadin/ui/AbstractTextField.java674
1 files changed, 674 insertions, 0 deletions
diff --git a/server/src/com/vaadin/ui/AbstractTextField.java b/server/src/com/vaadin/ui/AbstractTextField.java
new file mode 100644
index 0000000000..2326c07d97
--- /dev/null
+++ b/server/src/com/vaadin/ui/AbstractTextField.java
@@ -0,0 +1,674 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.util.Map;
+
+import com.vaadin.event.FieldEvents.BlurEvent;
+import com.vaadin.event.FieldEvents.BlurListener;
+import com.vaadin.event.FieldEvents.BlurNotifier;
+import com.vaadin.event.FieldEvents.FocusEvent;
+import com.vaadin.event.FieldEvents.FocusListener;
+import com.vaadin.event.FieldEvents.FocusNotifier;
+import com.vaadin.event.FieldEvents.TextChangeEvent;
+import com.vaadin.event.FieldEvents.TextChangeListener;
+import com.vaadin.event.FieldEvents.TextChangeNotifier;
+import com.vaadin.shared.ui.textfield.AbstractTextFieldState;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.Vaadin6Component;
+import com.vaadin.terminal.gwt.client.ui.textfield.VTextField;
+
+public abstract class AbstractTextField extends AbstractField<String> implements
+ BlurNotifier, FocusNotifier, TextChangeNotifier, Vaadin6Component {
+
+ /**
+ * Null representation.
+ */
+ private String nullRepresentation = "null";
+ /**
+ * Is setting to null from non-null value allowed by setting with null
+ * representation .
+ */
+ private boolean nullSettingAllowed = false;
+ /**
+ * The text content when the last messages to the server was sent. Cleared
+ * when value is changed.
+ */
+ private String lastKnownTextContent;
+
+ /**
+ * The position of the cursor when the last message to the server was sent.
+ */
+ private int lastKnownCursorPosition;
+
+ /**
+ * Flag indicating that a text change event is pending to be triggered.
+ * Cleared by {@link #setInternalValue(Object)} and when the event is fired.
+ */
+ private boolean textChangeEventPending;
+
+ private boolean isFiringTextChangeEvent = false;
+
+ private TextChangeEventMode textChangeEventMode = TextChangeEventMode.LAZY;
+
+ private final int DEFAULT_TEXTCHANGE_TIMEOUT = 400;
+
+ private int textChangeEventTimeout = DEFAULT_TEXTCHANGE_TIMEOUT;
+
+ /**
+ * Temporarily holds the new selection position. Cleared on paint.
+ */
+ private int selectionPosition = -1;
+
+ /**
+ * Temporarily holds the new selection length.
+ */
+ private int selectionLength;
+
+ /**
+ * Flag used to determine whether we are currently handling a state change
+ * triggered by a user. Used to properly fire text change event before value
+ * change event triggered by the client side.
+ */
+ private boolean changingVariables;
+
+ protected AbstractTextField() {
+ super();
+ }
+
+ @Override
+ public AbstractTextFieldState getState() {
+ return (AbstractTextFieldState) super.getState();
+ }
+
+ @Override
+ public void updateState() {
+ super.updateState();
+
+ String value = getValue();
+ if (value == null) {
+ value = getNullRepresentation();
+ }
+ getState().setText(value);
+ }
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+
+ if (selectionPosition != -1) {
+ target.addAttribute("selpos", selectionPosition);
+ target.addAttribute("sellen", selectionLength);
+ selectionPosition = -1;
+ }
+
+ if (hasListeners(TextChangeEvent.class)) {
+ target.addAttribute(VTextField.ATTR_TEXTCHANGE_EVENTMODE,
+ getTextChangeEventMode().toString());
+ target.addAttribute(VTextField.ATTR_TEXTCHANGE_TIMEOUT,
+ getTextChangeTimeout());
+ if (lastKnownTextContent != null) {
+ /*
+ * The field has be repainted for some reason (e.g. caption,
+ * size, stylename), but the value has not been changed since
+ * the last text change event. Let the client side know about
+ * the value the server side knows. Client side may then ignore
+ * the actual value, depending on its state.
+ */
+ target.addAttribute(
+ VTextField.ATTR_NO_VALUE_CHANGE_BETWEEN_PAINTS, true);
+ }
+ }
+
+ }
+
+ @Override
+ public void changeVariables(Object source, Map<String, Object> variables) {
+ changingVariables = true;
+
+ try {
+
+ if (variables.containsKey(VTextField.VAR_CURSOR)) {
+ Integer object = (Integer) variables.get(VTextField.VAR_CURSOR);
+ lastKnownCursorPosition = object.intValue();
+ }
+
+ if (variables.containsKey(VTextField.VAR_CUR_TEXT)) {
+ /*
+ * NOTE, we might want to develop this further so that on a
+ * value change event the whole text content don't need to be
+ * sent from the client to server. Just "commit" the value from
+ * currentText to the value.
+ */
+ handleInputEventTextChange(variables);
+ }
+
+ // Sets the text
+ if (variables.containsKey("text") && !isReadOnly()) {
+
+ // Only do the setting if the string representation of the value
+ // has been updated
+ String newValue = (String) variables.get("text");
+
+ // server side check for max length
+ if (getMaxLength() != -1 && newValue.length() > getMaxLength()) {
+ newValue = newValue.substring(0, getMaxLength());
+ }
+ final String oldValue = getValue();
+ if (newValue != null
+ && (oldValue == null || isNullSettingAllowed())
+ && newValue.equals(getNullRepresentation())) {
+ newValue = null;
+ }
+ if (newValue != oldValue
+ && (newValue == null || !newValue.equals(oldValue))) {
+ boolean wasModified = isModified();
+ setValue(newValue, true);
+
+ // If the modified status changes, or if we have a
+ // formatter, repaint is needed after all.
+ if (wasModified != isModified()) {
+ requestRepaint();
+ }
+ }
+ }
+ firePendingTextChangeEvent();
+
+ if (variables.containsKey(FocusEvent.EVENT_ID)) {
+ fireEvent(new FocusEvent(this));
+ }
+ if (variables.containsKey(BlurEvent.EVENT_ID)) {
+ fireEvent(new BlurEvent(this));
+ }
+ } finally {
+ changingVariables = false;
+
+ }
+
+ }
+
+ @Override
+ public Class<String> getType() {
+ return String.class;
+ }
+
+ /**
+ * Gets the null-string representation.
+ *
+ * <p>
+ * The null-valued strings are represented on the user interface by
+ * replacing the null value with this string. If the null representation is
+ * set null (not 'null' string), painting null value throws exception.
+ * </p>
+ *
+ * <p>
+ * The default value is string 'null'.
+ * </p>
+ *
+ * @return the String Textual representation for null strings.
+ * @see TextField#isNullSettingAllowed()
+ */
+ public String getNullRepresentation() {
+ return nullRepresentation;
+ }
+
+ /**
+ * Is setting nulls with null-string representation allowed.
+ *
+ * <p>
+ * If this property is true, writing null-representation string to text
+ * field always sets the field value to real null. If this property is
+ * false, null setting is not made, but the null values are maintained.
+ * Maintenance of null-values is made by only converting the textfield
+ * contents to real null, if the text field matches the null-string
+ * representation and the current value of the field is null.
+ * </p>
+ *
+ * <p>
+ * By default this setting is false
+ * </p>
+ *
+ * @return boolean Should the null-string represenation be always converted
+ * to null-values.
+ * @see TextField#getNullRepresentation()
+ */
+ public boolean isNullSettingAllowed() {
+ return nullSettingAllowed;
+ }
+
+ /**
+ * Sets the null-string representation.
+ *
+ * <p>
+ * The null-valued strings are represented on the user interface by
+ * replacing the null value with this string. If the null representation is
+ * set null (not 'null' string), painting null value throws exception.
+ * </p>
+ *
+ * <p>
+ * The default value is string 'null'
+ * </p>
+ *
+ * @param nullRepresentation
+ * Textual representation for null strings.
+ * @see TextField#setNullSettingAllowed(boolean)
+ */
+ public void setNullRepresentation(String nullRepresentation) {
+ this.nullRepresentation = nullRepresentation;
+ requestRepaint();
+ }
+
+ /**
+ * Sets the null conversion mode.
+ *
+ * <p>
+ * If this property is true, writing null-representation string to text
+ * field always sets the field value to real null. If this property is
+ * false, null setting is not made, but the null values are maintained.
+ * Maintenance of null-values is made by only converting the textfield
+ * contents to real null, if the text field matches the null-string
+ * representation and the current value of the field is null.
+ * </p>
+ *
+ * <p>
+ * By default this setting is false.
+ * </p>
+ *
+ * @param nullSettingAllowed
+ * Should the null-string representation always be converted to
+ * null-values.
+ * @see TextField#getNullRepresentation()
+ */
+ public void setNullSettingAllowed(boolean nullSettingAllowed) {
+ this.nullSettingAllowed = nullSettingAllowed;
+ requestRepaint();
+ }
+
+ @Override
+ protected boolean isEmpty() {
+ return super.isEmpty() || getValue().length() == 0;
+ }
+
+ /**
+ * Returns the maximum number of characters in the field. Value -1 is
+ * considered unlimited. Terminal may however have some technical limits.
+ *
+ * @return the maxLength
+ */
+ public int getMaxLength() {
+ return getState().getMaxLength();
+ }
+
+ /**
+ * Sets the maximum number of characters in the field. Value -1 is
+ * considered unlimited. Terminal may however have some technical limits.
+ *
+ * @param maxLength
+ * the maxLength to set
+ */
+ public void setMaxLength(int maxLength) {
+ getState().setMaxLength(maxLength);
+ requestRepaint();
+ }
+
+ /**
+ * Gets the number of columns in the editor. If the number of columns is set
+ * 0, the actual number of displayed columns is determined implicitly by the
+ * adapter.
+ *
+ * @return the number of columns in the editor.
+ */
+ public int getColumns() {
+ return getState().getColumns();
+ }
+
+ /**
+ * Sets the number of columns in the editor. If the number of columns is set
+ * 0, the actual number of displayed columns is determined implicitly by the
+ * adapter.
+ *
+ * @param columns
+ * the number of columns to set.
+ */
+ public void setColumns(int columns) {
+ if (columns < 0) {
+ columns = 0;
+ }
+ getState().setColumns(columns);
+ requestRepaint();
+ }
+
+ /**
+ * Gets the current input prompt.
+ *
+ * @see #setInputPrompt(String)
+ * @return the current input prompt, or null if not enabled
+ */
+ public String getInputPrompt() {
+ return getState().getInputPrompt();
+ }
+
+ /**
+ * Sets the input prompt - a textual prompt that is displayed when the field
+ * would otherwise be empty, to prompt the user for input.
+ *
+ * @param inputPrompt
+ */
+ public void setInputPrompt(String inputPrompt) {
+ getState().setInputPrompt(inputPrompt);
+ requestRepaint();
+ }
+
+ /* ** Text Change Events ** */
+
+ private void firePendingTextChangeEvent() {
+ if (textChangeEventPending && !isFiringTextChangeEvent) {
+ isFiringTextChangeEvent = true;
+ textChangeEventPending = false;
+ try {
+ fireEvent(new TextChangeEventImpl(this));
+ } finally {
+ isFiringTextChangeEvent = false;
+ }
+ }
+ }
+
+ @Override
+ protected void setInternalValue(String newValue) {
+ if (changingVariables && !textChangeEventPending) {
+
+ /*
+ * TODO check for possible (minor?) issue (not tested)
+ *
+ * -field with e.g. PropertyFormatter.
+ *
+ * -TextChangeListener and it changes value.
+ *
+ * -if formatter again changes the value, do we get an extra
+ * simulated text change event ?
+ */
+
+ /*
+ * Fire a "simulated" text change event before value change event if
+ * change is coming from the client side.
+ *
+ * Iff there is both value change and textChangeEvent in same
+ * variable burst, it is a text field in non immediate mode and the
+ * text change event "flushed" queued value change event. In this
+ * case textChangeEventPending flag is already on and text change
+ * event will be fired after the value change event.
+ */
+ if (newValue == null && lastKnownTextContent != null
+ && !lastKnownTextContent.equals(getNullRepresentation())) {
+ // Value was changed from something to null representation
+ lastKnownTextContent = getNullRepresentation();
+ textChangeEventPending = true;
+ } else if (newValue != null
+ && !newValue.toString().equals(lastKnownTextContent)) {
+ // Value was changed to something else than null representation
+ lastKnownTextContent = newValue.toString();
+ textChangeEventPending = true;
+ }
+ firePendingTextChangeEvent();
+ }
+
+ super.setInternalValue(newValue);
+ }
+
+ @Override
+ public void setValue(Object newValue) throws ReadOnlyException {
+ super.setValue(newValue);
+ /*
+ * Make sure w reset lastKnownTextContent field on value change. The
+ * clearing must happen here as well because TextChangeListener can
+ * revert the original value. Client must respect the value in this
+ * case. AbstractField optimizes value change if the existing value is
+ * reset. Also we need to force repaint if the flag is on.
+ */
+ if (lastKnownTextContent != null) {
+ lastKnownTextContent = null;
+ requestRepaint();
+ }
+ }
+
+ private void handleInputEventTextChange(Map<String, Object> variables) {
+ /*
+ * TODO we could vastly optimize the communication of values by using
+ * some sort of diffs instead of always sending the whole text content.
+ * Also on value change events we could use the mechanism.
+ */
+ String object = (String) variables.get(VTextField.VAR_CUR_TEXT);
+ lastKnownTextContent = object;
+ textChangeEventPending = true;
+ }
+
+ /**
+ * Sets the mode how the TextField triggers {@link TextChangeEvent}s.
+ *
+ * @param inputEventMode
+ * the new mode
+ *
+ * @see TextChangeEventMode
+ */
+ public void setTextChangeEventMode(TextChangeEventMode inputEventMode) {
+ textChangeEventMode = inputEventMode;
+ requestRepaint();
+ }
+
+ /**
+ * @return the mode used to trigger {@link TextChangeEvent}s.
+ */
+ public TextChangeEventMode getTextChangeEventMode() {
+ return textChangeEventMode;
+ }
+
+ /**
+ * Different modes how the TextField can trigger {@link TextChangeEvent}s.
+ */
+ public enum TextChangeEventMode {
+
+ /**
+ * An event is triggered on each text content change, most commonly key
+ * press events.
+ */
+ EAGER,
+ /**
+ * Each text change event in the UI causes the event to be communicated
+ * to the application after a timeout. The length of the timeout can be
+ * controlled with {@link TextField#setInputEventTimeout(int)}. Only the
+ * last input event is reported to the server side if several text
+ * change events happen during the timeout.
+ * <p>
+ * In case of a {@link ValueChangeEvent} the schedule is not kept
+ * strictly. Before a {@link ValueChangeEvent} a {@link TextChangeEvent}
+ * is triggered if the text content has changed since the previous
+ * TextChangeEvent regardless of the schedule.
+ */
+ TIMEOUT,
+ /**
+ * An event is triggered when there is a pause of text modifications.
+ * The length of the pause can be modified with
+ * {@link TextField#setInputEventTimeout(int)}. Like with the
+ * {@link #TIMEOUT} mode, an event is forced before
+ * {@link ValueChangeEvent}s, even if the user did not keep a pause
+ * while entering the text.
+ * <p>
+ * This is the default mode.
+ */
+ LAZY
+ }
+
+ @Override
+ public void addListener(TextChangeListener listener) {
+ addListener(TextChangeListener.EVENT_ID, TextChangeEvent.class,
+ listener, TextChangeListener.EVENT_METHOD);
+ }
+
+ @Override
+ public void removeListener(TextChangeListener listener) {
+ removeListener(TextChangeListener.EVENT_ID, TextChangeEvent.class,
+ listener);
+ }
+
+ /**
+ * The text change timeout modifies how often text change events are
+ * communicated to the application when {@link #getTextChangeEventMode()} is
+ * {@link TextChangeEventMode#LAZY} or {@link TextChangeEventMode#TIMEOUT}.
+ *
+ *
+ * @see #getTextChangeEventMode()
+ *
+ * @param timeout
+ * the timeout in milliseconds
+ */
+ public void setTextChangeTimeout(int timeout) {
+ textChangeEventTimeout = timeout;
+ requestRepaint();
+ }
+
+ /**
+ * Gets the timeout used to fire {@link TextChangeEvent}s when the
+ * {@link #getTextChangeEventMode()} is {@link TextChangeEventMode#LAZY} or
+ * {@link TextChangeEventMode#TIMEOUT}.
+ *
+ * @return the timeout value in milliseconds
+ */
+ public int getTextChangeTimeout() {
+ return textChangeEventTimeout;
+ }
+
+ public class TextChangeEventImpl extends TextChangeEvent {
+ private String curText;
+ private int cursorPosition;
+
+ private TextChangeEventImpl(final AbstractTextField tf) {
+ super(tf);
+ curText = tf.getCurrentTextContent();
+ cursorPosition = tf.getCursorPosition();
+ }
+
+ @Override
+ public AbstractTextField getComponent() {
+ return (AbstractTextField) super.getComponent();
+ }
+
+ @Override
+ public String getText() {
+ return curText;
+ }
+
+ @Override
+ public int getCursorPosition() {
+ return cursorPosition;
+ }
+
+ }
+
+ /**
+ * Gets the current (or the last known) text content in the field.
+ * <p>
+ * Note the text returned by this method is not necessary the same that is
+ * returned by the {@link #getValue()} method. The value is updated when the
+ * terminal fires a value change event via e.g. blurring the field or by
+ * pressing enter. The value returned by this method is updated also on
+ * {@link TextChangeEvent}s. Due to this high dependency to the terminal
+ * implementation this method is (at least at this point) not published.
+ *
+ * @return the text which is currently displayed in the field.
+ */
+ private String getCurrentTextContent() {
+ if (lastKnownTextContent != null) {
+ return lastKnownTextContent;
+ } else {
+ Object text = getValue();
+ if (text == null) {
+ return getNullRepresentation();
+ }
+ return text.toString();
+ }
+ }
+
+ /**
+ * Selects all text in the field.
+ *
+ * @since 6.4
+ */
+ public void selectAll() {
+ String text = getValue() == null ? "" : getValue().toString();
+ setSelectionRange(0, text.length());
+ }
+
+ /**
+ * Sets the range of text to be selected.
+ *
+ * As a side effect the field will become focused.
+ *
+ * @since 6.4
+ *
+ * @param pos
+ * the position of the first character to be selected
+ * @param length
+ * the number of characters to be selected
+ */
+ public void setSelectionRange(int pos, int length) {
+ selectionPosition = pos;
+ selectionLength = length;
+ focus();
+ requestRepaint();
+ }
+
+ /**
+ * Sets the cursor position in the field. As a side effect the field will
+ * become focused.
+ *
+ * @since 6.4
+ *
+ * @param pos
+ * the position for the cursor
+ * */
+ public void setCursorPosition(int pos) {
+ setSelectionRange(pos, 0);
+ lastKnownCursorPosition = pos;
+ }
+
+ /**
+ * Returns the last known cursor position of the field.
+ *
+ * <p>
+ * Note that due to the client server nature or the GWT terminal, Vaadin
+ * cannot provide the exact value of the cursor position in most situations.
+ * The value is updated only when the client side terminal communicates to
+ * TextField, like on {@link ValueChangeEvent}s and {@link TextChangeEvent}
+ * s. This may change later if a deep push integration is built to Vaadin.
+ *
+ * @return the cursor position
+ */
+ public int getCursorPosition() {
+ return lastKnownCursorPosition;
+ }
+
+ @Override
+ public void addListener(FocusListener listener) {
+ addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener,
+ FocusListener.focusMethod);
+ }
+
+ @Override
+ public void removeListener(FocusListener listener) {
+ removeListener(FocusEvent.EVENT_ID, FocusEvent.class, listener);
+ }
+
+ @Override
+ public void addListener(BlurListener listener) {
+ addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener,
+ BlurListener.blurMethod);
+ }
+
+ @Override
+ public void removeListener(BlurListener listener) {
+ removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener);
+ }
+
+}