Hi Apache ICLA sent to apache org.
Applied changes from comments. Regards, Noel Grandin. Index: wtk-test/src/pivot/wtk/test/TextInputValidatorTest.java =================================================================== --- wtk-test/src/pivot/wtk/test/TextInputValidatorTest.java (revision 0) +++ wtk-test/src/pivot/wtk/test/TextInputValidatorTest.java (revision 0) @@ -0,0 +1,134 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package pivot.wtk.test; + +import pivot.collections.Dictionary; +import pivot.wtk.Application; +import pivot.wtk.Component; +import pivot.wtk.DesktopApplicationContext; +import pivot.wtk.Display; +import pivot.wtk.Label; +import pivot.wtk.TextInput; +import pivot.wtk.TextInputListener; +import pivot.wtk.Window; +import pivot.wtk.text.TextNode; +import pivot.wtk.text.validation.FloatRangeValidator; +import pivot.wtk.text.validation.IntRangeValidator; +import pivot.wtk.text.validation.RegexTextValidator; +import pivot.wtk.text.validation.Validator; +import pivot.wtkx.WTKXSerializer; + +public class TextInputValidatorTest implements Application { + private Window window = null; + private TextInput textinputFloatRange = null; + private Label invalidLabel = null; + private TextInput textinputIntRange = null; + private TextInput textinputDateRegex = null; + private TextInput textinputCustomBoolean = null; + + public void startup(Display display, Dictionary<String, String> properties) + throws Exception { + WTKXSerializer wtkxSerializer = new WTKXSerializer(); + window = new Window((Component) wtkxSerializer.readObject(getClass() + .getResource("textInputValidator_test.wtkx"))); + textinputFloatRange = (TextInput) wtkxSerializer + .getObjectByName("textinputFloatRange"); + textinputIntRange = (TextInput) wtkxSerializer + .getObjectByName("textinputIntRange"); + textinputDateRegex = (TextInput) wtkxSerializer + .getObjectByName("textinputDateRegex"); + textinputCustomBoolean = (TextInput) wtkxSerializer + .getObjectByName("textinputCustomBoolean"); + + // standard float range model + textinputFloatRange.setText("0.5"); + textinputFloatRange.setValidator(new FloatRangeValidator(0.3f, + 2000f)); + + // test the listener by updating a label + textinputFloatRange.getTextInputListeners().add( + new TextInputListener() { + public void maximumLengthChanged(TextInput textInput, + int previousMaximumLength) { + } + + public void passwordChanged(TextInput textInput) { + } + + public void promptChanged(TextInput textInput, + String previousPrompt) { + } + + public void textKeyChanged(TextInput textInput, + String previousTextKey) { + } + + public void textNodeChanged(TextInput textInput, + TextNode previousTextNode) { + } + + public void textSizeChanged(TextInput textInput, + int previousTextSize) { + } + + public void textValidChanged(TextInput textInput, + boolean isTextValid) { + invalidLabel.setText(isTextValid ? "valid" : "invalid"); + } + public void textValidatorChanged(TextInput textInput, + Validator validator) { + } + }); + + invalidLabel = (Label) wtkxSerializer.getObjectByName("invalidLabel"); + + // standard int range model + textinputIntRange.setText("0"); + textinputIntRange.setValidator(new IntRangeValidator(0, 100)); + + // validate using a date regex. + textinputDateRegex.setText("2009-09-01"); + textinputDateRegex + .setValidator(new RegexTextValidator( + "(19|20)\\d\\d[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])")); + + // creating a custom model that only accepts "true" or "false" + textinputCustomBoolean.setText("true"); + textinputCustomBoolean.setValidator(new Validator() { + public boolean isValid(String s) { + return "true".equals(s) || "false".equals(s); + } + }); + + window.setTitle("Text Input Validator Test"); + window.setMaximized(true); + window.open(display); + } + + public boolean shutdown(boolean optional) { + window.close(); + return true; + } + + public void resume() { + } + + public void suspend() { + } + + public static void main(String[] args) throws Exception { + DesktopApplicationContext + .main(new String[] { TextInputValidatorTest.class.getName() }); + } +} Index: wtk-test/src/pivot/wtk/test/textInputValidator_test.wtkx =================================================================== --- wtk-test/src/pivot/wtk/test/textInputValidator_test.wtkx (revision 0) +++ wtk-test/src/pivot/wtk/test/textInputValidator_test.wtkx (revision 0) @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +--> + +<TablePane styles="{padding:6, verticalSpacing:8}" + xmlns:wtkx="http://pivot-toolkit.org/wtkx/2008" xmlns="pivot.wtk"> + <columns> + <TablePane.Column width="-1"/> + <TablePane.Column width="-1"/> + <TablePane.Column width="-1"/> + </columns> + <rows> + <TablePane.Row height="-1"> + <Label text="float range 0.3-2000"/> + <TextInput wtkx:id="textinputFloatRange"/> + <Label wtkx:id="invalidLabel"/> + </TablePane.Row> + <TablePane.Row height="-1"> + <Label text="int range 0-100"/> + <TextInput wtkx:id="textinputIntRange"/> + </TablePane.Row> + <TablePane.Row height="-1"> + <Label text="date regex"/> + <TextInput wtkx:id="textinputDateRegex"/> + </TablePane.Row> + <TablePane.Row height="-1"> + <Label text="custom boolean"/> + <TextInput wtkx:id="textinputCustomBoolean"/> + </TablePane.Row> + </rows> +</TablePane> Index: wtk/src/pivot/wtk/TextInput.java =================================================================== --- wtk/src/pivot/wtk/TextInput.java (revision 756338) +++ wtk/src/pivot/wtk/TextInput.java (working copy) @@ -23,6 +23,7 @@ import pivot.wtk.text.Node; import pivot.wtk.text.NodeListener; import pivot.wtk.text.TextNode; +import pivot.wtk.text.validation.Validator; /** * A component that allows a user to enter a single line of unformatted text. @@ -71,6 +72,18 @@ listener.textKeyChanged(textInput, previousTextKey); } } + + public void textValidChanged(TextInput textInput, boolean isTextValid) { + for (TextInputListener listener : this) { + listener.textValidChanged(textInput, isTextValid); + } + } + + public void textValidatorChanged(TextInput textInput, Validator validator) { + for (TextInputListener listener : this) { + listener.textValidatorChanged(textInput, validator); + } + } } /** @@ -132,7 +145,9 @@ private boolean password = false; private String prompt = null; private String textKey = null; - + private Validator validator = null; + private boolean textValid = true; + private NodeListener textNodeListener = new NodeListener() { public void parentChanged(Node node, Element previousParent) { } @@ -151,6 +166,7 @@ textInputCharacterListeners.charactersInserted(TextInput.this, offset, characterCount); textInputTextListeners.textChanged(TextInput.this); + validateText(); } public void rangeRemoved(Node node, int offset, int characterCount) { @@ -164,6 +180,7 @@ textInputCharacterListeners.charactersRemoved(TextInput.this, offset, characterCount); textInputTextListeners.textChanged(TextInput.this); + validateText(); } }; @@ -191,7 +208,7 @@ if (textNode.getCharacterCount() > maximumLength) { throw new IllegalArgumentException("Text length is greater than maximum length."); } - + TextNode previousTextNode = this.textNode; if (previousTextNode != textNode) { @@ -211,6 +228,7 @@ textInputListeners.textNodeChanged(this, previousTextNode); textInputTextListeners.textChanged(this); + validateText(); } } @@ -603,6 +621,42 @@ } } + public boolean isTextValid() { + return this.textValid; + } + + /** + * set the validator for the text field. + * + * @exception IllegalStateException if the current text is invalid according to the new TextValidator + */ + public void setValidator(Validator validator) { + if (validator != null && !validator.isValid(getText())) { + throw new IllegalStateException( + "cannot set a TextValidator (" + validator + ") that results in the current text (" + getText() + ") being invalid"); + } + this.validator = validator; + textInputListeners.textValidatorChanged(this, this.validator); + if (!textValid) { + textValid = true; + textInputListeners.textValidChanged(this, textValid); + } + } + + public Validator getValidator() { + return this.validator; + } + + private void validateText() { + String text = getText(); + boolean b = validator == null ? true : validator.isValid(text); + if (b != this.textValid) { + this.textValid = b; + textInputListeners.textValidChanged(this, textValid); + repaint(); + } + } + /** * Returns the text input listener list. */ Index: wtk/src/pivot/wtk/TextInputListener.java =================================================================== --- wtk/src/pivot/wtk/TextInputListener.java (revision 756338) +++ wtk/src/pivot/wtk/TextInputListener.java (working copy) @@ -16,6 +16,7 @@ package pivot.wtk; import pivot.wtk.text.TextNode; +import pivot.wtk.text.validation.Validator; /** * Text input listener interface. @@ -68,4 +69,17 @@ * @param previousTextKey */ public void textKeyChanged(TextInput textInput, String previousTextKey); + + /** + * Called when the text changes validity. + * + * @param textInput + * @param isTextValid is the text currently valid + */ + public void textValidChanged(TextInput textInput, boolean isTextValid); + + /** + * Called when the validator changes.. + */ + public void textValidatorChanged(TextInput textInput, Validator validator); } Index: wtk/src/pivot/wtk/skin/terra/TerraTextInputSkin.java =================================================================== --- wtk/src/pivot/wtk/skin/terra/TerraTextInputSkin.java (revision 756338) +++ wtk/src/pivot/wtk/skin/terra/TerraTextInputSkin.java (working copy) @@ -39,12 +39,13 @@ import pivot.wtk.Mouse; import pivot.wtk.Platform; import pivot.wtk.TextInput; -import pivot.wtk.TextInputListener; import pivot.wtk.TextInputCharacterListener; +import pivot.wtk.TextInputListener; import pivot.wtk.TextInputSelectionListener; import pivot.wtk.Theme; import pivot.wtk.skin.ComponentSkin; import pivot.wtk.text.TextNode; +import pivot.wtk.text.validation.Validator; /** * Text input skin. @@ -233,6 +234,7 @@ private Color promptColor; private Color backgroundColor; private Color disabledBackgroundColor; + private Color invalidTextBackgroundColor; private Color borderColor; private Color disabledBorderColor; private Insets padding; @@ -256,6 +258,7 @@ disabledColor = theme.getColor(7); backgroundColor = theme.getColor(11); disabledBackgroundColor = theme.getColor(10); + invalidTextBackgroundColor = theme.getColor(25); borderColor = theme.getColor(7); disabledBorderColor = theme.getColor(7); padding = new Insets(2); @@ -346,6 +349,9 @@ borderColor = this.disabledBorderColor; bevelColor = disabledBevelColor; } + if (!textInput.isTextValid()) { + backgroundColor = invalidTextBackgroundColor; + } graphics.setStroke(new BasicStroke()); @@ -609,6 +615,32 @@ setBackgroundColor(theme.getColor(color)); } + public Color getInvalidTextBackgroundColor() { + return invalidTextBackgroundColor; + } + + public void setInvalidTextBackgroundColor(Color color) { + if (invalidTextBackgroundColor == null) { + throw new IllegalArgumentException("invalidTextBackgroundColor is null."); + } + + this.invalidTextBackgroundColor = color; + repaintComponent(); + } + + public final void setInvalidTextBackgroundColor(String color) { + if (color == null) { + throw new IllegalArgumentException("invalidTextBackgroundColor is null."); + } + + setInvalidTextBackgroundColor(decodeColor(color)); + } + + public final void setInvalidTextBackgroundColor(int color) { + TerraTheme theme = (TerraTheme)Theme.getTheme(); + setInvalidTextBackgroundColor(theme.getColor(color)); + } + public Color getDisabledBackgroundColor() { return disabledBackgroundColor; } @@ -635,7 +667,7 @@ TerraTheme theme = (TerraTheme)Theme.getTheme(); setDisabledBackgroundColor(theme.getColor(color)); } - + public Color getBorderColor() { return borderColor; } @@ -1064,20 +1096,19 @@ public void focusedChanged(Component component, boolean temporary) { super.focusedChanged(component, temporary); - TextInput textInput = (TextInput)getComponent(); + TextInput textInput = (TextInput) getComponent(); TextNode textNode = textInput.getTextNode(); if (component.isFocused()) { showCaret(textInput.getSelectionLength() == 0); - if (!temporary - && Mouse.getCapturer() != component) { + if (!temporary && Mouse.getCapturer() != component) { textInput.setSelection(0, textNode.getCharacterCount()); } } else { if (!temporary) { textInput.setSelection(textInput.getSelectionStart() - + textInput.getSelectionLength(), 0); + + textInput.getSelectionLength(), 0); } showCaret(false); @@ -1110,7 +1141,15 @@ public void textKeyChanged(TextInput textInput, String previousTextKey) { // No-op } - + + public void textValidChanged(TextInput textInput, boolean isValid) { + repaintComponent(); + } + + public void textValidatorChanged(TextInput textInput, Validator validator) { + // No-op + } + // Text input character events public void charactersInserted(TextInput textInput, int index, int count) { updateSelection(); @@ -1207,4 +1246,5 @@ repaintComponent(); } + } Index: wtk/src/pivot/wtk/skin/terra/TerraTheme_default.json =================================================================== --- wtk/src/pivot/wtk/skin/terra/TerraTheme_default.json (revision 756338) +++ wtk/src/pivot/wtk/skin/terra/TerraTheme_default.json (working copy) @@ -9,6 +9,7 @@ "#2b5580", "#3c77b2", "#14538b", - "#b0000f" + "#b0000f", + "#ffff00" ] } Index: wtk/src/pivot/wtk/text/validation/DecimalValidator.java =================================================================== --- wtk/src/pivot/wtk/text/validation/DecimalValidator.java (revision 0) +++ wtk/src/pivot/wtk/text/validation/DecimalValidator.java (revision 0) @@ -0,0 +1,36 @@ +package pivot.wtk.text.validation; + +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.text.ParseException; + +/** + * + * @author Noel Grandin + */ +public abstract class DecimalValidator extends FormattedValidator<NumberFormat> +{ + protected DecimalValidator(DecimalFormat format) + { + super(format); + } + + protected DecimalValidator() + { + super(NumberFormat.getInstance()); + } + + /** helper for textToObject */ + protected final Number parse(String text) + { + try + { + return format.parse(text); + } + catch (ParseException ex) + { + // this should never happen + throw new RuntimeException(ex); + } + } +} \ No newline at end of file Index: wtk/src/pivot/wtk/text/validation/DoubleRangeValidator.java =================================================================== --- wtk/src/pivot/wtk/text/validation/DoubleRangeValidator.java (revision 0) +++ wtk/src/pivot/wtk/text/validation/DoubleRangeValidator.java (revision 0) @@ -0,0 +1,25 @@ +package pivot.wtk.text.validation; + +/** + * + * @author Noel Grandin + */ +public class DoubleRangeValidator extends DoubleValidator +{ + private final double minValue, maxValue; + + public DoubleRangeValidator(double minValue, double maxValue) + { + this.minValue = minValue; + this.maxValue = maxValue; + } + + @Override + public boolean isValid(String text) + { + if (!super.isValid(text)) + return false; + final double f = textToObject(text); + return f >= minValue && f <= maxValue; + } +} \ No newline at end of file Index: wtk/src/pivot/wtk/text/validation/DoubleValidator.java =================================================================== --- wtk/src/pivot/wtk/text/validation/DoubleValidator.java (revision 0) +++ wtk/src/pivot/wtk/text/validation/DoubleValidator.java (revision 0) @@ -0,0 +1,17 @@ +package pivot.wtk.text.validation; + +/** + * + * @author Noel Grandin + */ +public class DoubleValidator extends DecimalValidator +{ + public DoubleValidator() + { + } + + protected final Double textToObject(String text) + { + return parse(text).doubleValue(); + } +} \ No newline at end of file Index: wtk/src/pivot/wtk/text/validation/FloatRangeValidator.java =================================================================== --- wtk/src/pivot/wtk/text/validation/FloatRangeValidator.java (revision 0) +++ wtk/src/pivot/wtk/text/validation/FloatRangeValidator.java (revision 0) @@ -0,0 +1,25 @@ +package pivot.wtk.text.validation; + +/** + * + * @author Noel Grandin + */ +public class FloatRangeValidator extends FloatValidator +{ + private final float minValue, maxValue; + + public FloatRangeValidator(float minValue, float maxValue) + { + this.minValue = minValue; + this.maxValue = maxValue; + } + + @Override + public boolean isValid(String text) + { + if (!super.isValid(text)) + return false; + final float f = textToObject(text); + return f >= minValue && f <= maxValue; + } +} \ No newline at end of file Index: wtk/src/pivot/wtk/text/validation/FloatValidator.java =================================================================== --- wtk/src/pivot/wtk/text/validation/FloatValidator.java (revision 0) +++ wtk/src/pivot/wtk/text/validation/FloatValidator.java (revision 0) @@ -0,0 +1,17 @@ +package pivot.wtk.text.validation; + +/** + * + * @author Noel Grandin + */ +public class FloatValidator extends DecimalValidator +{ + public FloatValidator() + { + } + + protected final Float textToObject(String text) + { + return parse(text).floatValue(); + } +} \ No newline at end of file Index: wtk/src/pivot/wtk/text/validation/FormattedValidator.java =================================================================== --- wtk/src/pivot/wtk/text/validation/FormattedValidator.java (revision 0) +++ wtk/src/pivot/wtk/text/validation/FormattedValidator.java (revision 0) @@ -0,0 +1,26 @@ +package pivot.wtk.text.validation; + +import java.text.ParsePosition; + +/** + * + * @author Noel Grandin + */ +public abstract class FormattedValidator<TFormat extends java.text.Format> implements Validator +{ + protected final TFormat format; + + protected FormattedValidator(TFormat format) + { + this.format = format; + } + + public boolean isValid(String text) + { + final ParsePosition pos = new ParsePosition(0); + Object obj = format.parseObject(text, pos); + // the text is only valid is we successfully parsed ALL of it. Don't want trailing bits of + // not-valid text. + return obj != null && pos.getErrorIndex() == -1 && pos.getIndex() == text.length(); + } +} \ No newline at end of file Index: wtk/src/pivot/wtk/text/validation/IntRangeValidator.java =================================================================== --- wtk/src/pivot/wtk/text/validation/IntRangeValidator.java (revision 0) +++ wtk/src/pivot/wtk/text/validation/IntRangeValidator.java (revision 0) @@ -0,0 +1,25 @@ +package pivot.wtk.text.validation; + +/** + * + * @author Noel Grandin + */ +public class IntRangeValidator extends IntValidator +{ + private final int minValue, maxValue; + + public IntRangeValidator(int minValue, int maxValue) + { + this.minValue = minValue; + this.maxValue = maxValue; + } + + @Override + public boolean isValid(String text) + { + if (!super.isValid(text)) + return false; + final int i = textToObject(text); + return i >= minValue && i <= maxValue; + } +} \ No newline at end of file Index: wtk/src/pivot/wtk/text/validation/IntValidator.java =================================================================== --- wtk/src/pivot/wtk/text/validation/IntValidator.java (revision 0) +++ wtk/src/pivot/wtk/text/validation/IntValidator.java (revision 0) @@ -0,0 +1,18 @@ +package pivot.wtk.text.validation; + +/** + * + * @author Noel Grandin + */ +public class IntValidator extends DecimalValidator +{ + public IntValidator() + { + format.setParseIntegerOnly(true); + } + + protected final Integer textToObject(String text) + { + return parse(text).intValue(); + } +} \ No newline at end of file Index: wtk/src/pivot/wtk/text/validation/RegexTextValidator.java =================================================================== --- wtk/src/pivot/wtk/text/validation/RegexTextValidator.java (revision 0) +++ wtk/src/pivot/wtk/text/validation/RegexTextValidator.java (revision 0) @@ -0,0 +1,25 @@ +package pivot.wtk.text.validation; + +import java.util.regex.Pattern; + +/** + * + * @author Noel Grandin + */ +public class RegexTextValidator implements Validator { + + private final Pattern p; + + public RegexTextValidator(Pattern p) { + this.p = p; + } + + public RegexTextValidator(String regexPattern) { + this.p = Pattern.compile(regexPattern); + } + + public boolean isValid(String text) { + return p.matcher(text).matches(); + } + +} Index: wtk/src/pivot/wtk/text/validation/Validator.java =================================================================== --- wtk/src/pivot/wtk/text/validation/Validator.java (revision 0) +++ wtk/src/pivot/wtk/text/validation/Validator.java (revision 0) @@ -0,0 +1,11 @@ +package pivot.wtk.text.validation; + +/** + * Validation interface for TextInput widget. + * + * @author Noel Grandin + */ +public interface Validator { + /** Is the text value valid? */ + boolean isValid(String text); +} \ No newline at end of file
