Hi

This is my first attempt at adding validation functionality to TextInput.

Comments
(0) Do I need to do some paperwork for the Apache Foundation in order to
contribute?

(1) Should the naming convention be "DoubleTextValidator" or
"TextValidatorDouble"?

(2) I dropped
    (a) the commit-or-revert behaviour on RETURN and ESCAPE
  and (b) storing a value on the Validator.
TextValidation seems sufficiently fundamental that it's worth leaving it
as simple as possible, and it's easy enough to implement
commit-or-revert behaviour with a wrapper class.

(3) Do I need to implement some kind of interaction or utility methods
for Form?

(4) At the moment I'm changing the background colour to indicate
invalidity.
Perhaps it might be better to use an icon-overlay in the lower right corner?

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,135 @@
+/*
+ * Copyright (c) 2008 VMware, Inc.
+ *
+ * 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 java.util.regex.Pattern;
+
+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.FloatRangeTextValidator;
+import pivot.wtk.text.validation.IntRangeTextValidator;
+import pivot.wtk.text.validation.RegexTextValidator;
+import pivot.wtk.text.validation.TextValidator;
+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.setTextValidator(new
FloatRangeTextValidator(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 validChanged(TextInput textInput,
+                            boolean isTextValid) {
+                        invalidLabel.setText(isTextValid ? "valid" :
"invalid");
+                    }
+                });
+
+        invalidLabel = (Label)
wtkxSerializer.getObjectByName("invalidLabel");
+
+        // standard int range model
+        textinputIntRange.setText("0");
+        textinputIntRange.setTextValidator(new IntRangeTextValidator(0,
100));
+
+        // validate using a date regex.
+        textinputDateRegex.setText("2009-09-01");
+        textinputDateRegex
+                .setTextValidator(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.setTextValidator(new TextValidator() {
+            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() {
+    }
+
+}
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,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (c) 2008 VMware, Inc.
+
+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 755889)
+++ 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.TextValidator;
 
 /**
  * A component that allows a user to enter a single line of unformatted
text.
@@ -71,6 +72,12 @@
                 listener.textKeyChanged(textInput, previousTextKey);
             }
         }
+        
+        public void validChanged(TextInput textInput, boolean
isTextValid) {
+          for (TextInputListener listener : this) {
+            listener.validChanged(textInput, isTextValid);
+          }
+        }
     }
 
     /**
@@ -132,7 +139,9 @@
     private boolean password = false;
     private String prompt = null;
     private String textKey = null;
-
+    private TextValidator textValidator = null;
+    private boolean isTextValid = true;
+    
     private NodeListener textNodeListener = new NodeListener() {
         public void parentChanged(Node node, Element previousParent) {
         }
@@ -151,6 +160,7 @@
 
            
textInputCharacterListeners.charactersInserted(TextInput.this, offset,
characterCount);
             textInputTextListeners.textChanged(TextInput.this);
+            runTextValidation();
         }
 
         public void rangeRemoved(Node node, int offset, int
characterCount) {
@@ -164,6 +174,7 @@
 
            
textInputCharacterListeners.charactersRemoved(TextInput.this, offset,
characterCount);
             textInputTextListeners.textChanged(TextInput.this);
+            runTextValidation();
         }
     };
 
@@ -191,7 +202,7 @@
         if (textNode.getCharacterCount() > maximumLength) {
             throw new IllegalArgumentException("Text length is greater
than maximum length.");
         }
-
+        
         TextNode previousTextNode = this.textNode;
 
         if (previousTextNode != textNode) {
@@ -211,6 +222,7 @@
 
             textInputListeners.textNodeChanged(this, previousTextNode);
             textInputTextListeners.textChanged(this);
+            runTextValidation();
         }
     }
 
@@ -602,6 +614,41 @@
         }
     }
 
+    public boolean isTextValid() {
+        return this.isTextValid;
+    }
+
+    /**
+     * set the validator for the text field.
+     *
+     * @exception IllegalStateException if the current text is invalid
according to the new TextValidator
+     */
+    public void setTextValidator(TextValidator 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.textValidator = validator;
+        if (!isTextValid) {
+            isTextValid = true;
+            textInputListeners.validChanged(this, isTextValid);
+        }
+    }
+
+    public TextValidator getTextValidator() {
+        return this.textValidator;
+    }
+    
+    private void runTextValidation() {
+        String s = getText();
+        boolean b = textValidator == null ? true :
textValidator.isValid(s);
+        if (b != this.isTextValid) {
+            this.isTextValid = b;
+            textInputListeners.validChanged(this, isTextValid);
+            repaint();
+        }
+    }
+    
     /**
      * Returns the text input listener list.
      */
Index: wtk/src/pivot/wtk/TextInputListener.java
===================================================================
--- wtk/src/pivot/wtk/TextInputListener.java    (revision 755889)
+++ wtk/src/pivot/wtk/TextInputListener.java    (working copy)
@@ -68,4 +68,13 @@
      * @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 validChanged(TextInput textInput, boolean isTextValid);
+
 }
Index: wtk/src/pivot/wtk/skin/terra/TerraTextInputSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/terra/TerraTextInputSkin.java    (revision
755889)
+++ wtk/src/pivot/wtk/skin/terra/TerraTextInputSkin.java    (working copy)
@@ -39,8 +39,8 @@
 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;
@@ -233,6 +233,7 @@
     private Color promptColor;
     private Color backgroundColor;
     private Color disabledBackgroundColor;
+    private Color invalidTextBackgroundColor;
     private Color borderColor;
     private Color disabledBorderColor;
     private Insets padding;
@@ -256,6 +257,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 +348,9 @@
             borderColor = this.disabledBorderColor;
             bevelColor = disabledBevelColor;
         }
+        if (!textInput.isTextValid()) {
+            backgroundColor = invalidTextBackgroundColor;
+        }
 
         graphics.setStroke(new BasicStroke());
 
@@ -609,6 +614,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 +666,7 @@
         TerraTheme theme = (TerraTheme)Theme.getTheme();
         setDisabledBackgroundColor(theme.getColor(color));
     }
-
+    
     public Color getBorderColor() {
         return borderColor;
     }
@@ -1064,20 +1095,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,6 +1140,14 @@
     public void textKeyChanged(TextInput textInput, String
previousTextKey) {
         // No-op
     }
+    
+    public void validChanged(TextInput textInput, boolean isValid) {
+        repaintComponent();
+    }
+    
+    public void validValueChanged(TextInput textInput, Object validValue) {
+        // No-op
+    }
 
     // Text input character events
     public void charactersInserted(TextInput textInput, int index, int
count) {
@@ -1207,4 +1245,5 @@
 
         repaintComponent();
     }
+    
 }
Index: wtk/src/pivot/wtk/skin/terra/TerraTheme_default.json
===================================================================
--- wtk/src/pivot/wtk/skin/terra/TerraTheme_default.json    (revision
755889)
+++ wtk/src/pivot/wtk/skin/terra/TerraTheme_default.json    (working copy)
@@ -8,6 +8,7 @@
         "#2b5580",
         "#3c77b2",
         "#14538b",
-        "#b0000f"
+        "#b0000f",
+        "#ffff00"
     ]
 }
Index: wtk/src/pivot/wtk/text/validation/DecimalTextValidator.java
===================================================================
--- wtk/src/pivot/wtk/text/validation/DecimalTextValidator.java  
 (revision 0)
+++ wtk/src/pivot/wtk/text/validation/DecimalTextValidator.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 DecimalTextValidator extends
FormattedTextValidator<NumberFormat>
+{
+    protected DecimalTextValidator(DecimalFormat format)
+    {
+        super(format);
+    }
+
+    protected DecimalTextValidator()
+    {
+        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/DoubleRangeTextValidator.java
===================================================================
--- wtk/src/pivot/wtk/text/validation/DoubleRangeTextValidator.java  
 (revision 0)
+++ wtk/src/pivot/wtk/text/validation/DoubleRangeTextValidator.java  
 (revision 0)
@@ -0,0 +1,25 @@
+package pivot.wtk.text.validation;
+
+/**
+ *
+ * @author Noel Grandin
+ */
+public class DoubleRangeTextValidator extends DoubleTextValidator
+{
+    private final double minValue, maxValue;
+
+    public DoubleRangeTextValidator(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/DoubleTextValidator.java
===================================================================
--- wtk/src/pivot/wtk/text/validation/DoubleTextValidator.java  
 (revision 0)
+++ wtk/src/pivot/wtk/text/validation/DoubleTextValidator.java  
 (revision 0)
@@ -0,0 +1,17 @@
+package pivot.wtk.text.validation;
+
+/**
+ *
+ * @author Noel Grandin
+ */
+public class DoubleTextValidator extends DecimalTextValidator
+{
+    public DoubleTextValidator()
+    {
+    }
+    
+    protected final Double textToObject(String text)
+    {
+        return parse(text).doubleValue();
+    }
+}
\ No newline at end of file
Index: wtk/src/pivot/wtk/text/validation/FloatRangeTextValidator.java
===================================================================
--- wtk/src/pivot/wtk/text/validation/FloatRangeTextValidator.java  
 (revision 0)
+++ wtk/src/pivot/wtk/text/validation/FloatRangeTextValidator.java  
 (revision 0)
@@ -0,0 +1,25 @@
+package pivot.wtk.text.validation;
+
+/**
+ *
+ * @author Noel Grandin
+ */
+public class FloatRangeTextValidator extends FloatTextValidator
+{
+    private final float minValue, maxValue;
+    
+    public FloatRangeTextValidator(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/FloatTextValidator.java
===================================================================
--- wtk/src/pivot/wtk/text/validation/FloatTextValidator.java  
 (revision 0)
+++ wtk/src/pivot/wtk/text/validation/FloatTextValidator.java  
 (revision 0)
@@ -0,0 +1,17 @@
+package pivot.wtk.text.validation;
+
+/**
+ *
+ * @author Noel Grandin
+ */
+public class FloatTextValidator extends DecimalTextValidator
+{
+    public FloatTextValidator()
+    {
+    }
+
+    protected final Float textToObject(String text)
+    {
+        return parse(text).floatValue();
+    }
+}
\ No newline at end of file
Index: wtk/src/pivot/wtk/text/validation/FormattedTextValidator.java
===================================================================
--- wtk/src/pivot/wtk/text/validation/FormattedTextValidator.java  
 (revision 0)
+++ wtk/src/pivot/wtk/text/validation/FormattedTextValidator.java  
 (revision 0)
@@ -0,0 +1,26 @@
+package pivot.wtk.text.validation;
+
+import java.text.ParsePosition;
+
+/**
+ *
+ * @author Noel Grandin
+ */
+public abstract class FormattedTextValidator<TFormat extends
java.text.Format> implements TextValidator
+{
+    protected final TFormat format;
+
+    protected FormattedTextValidator(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/IntRangeTextValidator.java
===================================================================
--- wtk/src/pivot/wtk/text/validation/IntRangeTextValidator.java  
 (revision 0)
+++ wtk/src/pivot/wtk/text/validation/IntRangeTextValidator.java  
 (revision 0)
@@ -0,0 +1,25 @@
+package pivot.wtk.text.validation;
+
+/**
+ *
+ * @author Noel Grandin
+ */
+public class IntRangeTextValidator extends IntTextValidator
+{
+    private final int minValue, maxValue;
+
+    public IntRangeTextValidator(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/IntTextValidator.java
===================================================================
--- wtk/src/pivot/wtk/text/validation/IntTextValidator.java    (revision 0)
+++ wtk/src/pivot/wtk/text/validation/IntTextValidator.java    (revision 0)
@@ -0,0 +1,18 @@
+package pivot.wtk.text.validation;
+
+/**
+ *
+ * @author Noel Grandin
+ */
+public class IntTextValidator extends DecimalTextValidator
+{
+    public IntTextValidator()
+    {
+        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 TextValidator {
+
+    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/TextValidator.java
===================================================================
--- wtk/src/pivot/wtk/text/validation/TextValidator.java    (revision 0)
+++ wtk/src/pivot/wtk/text/validation/TextValidator.java    (revision 0)
@@ -0,0 +1,11 @@
+package pivot.wtk.text.validation;
+
+/**
+ * Validation interface for TextInput widget.
+ *
+ * @author Noel Grandin
+ */
+public interface TextValidator {
+    /** Is the text value valid? */
+    boolean isValid(String text);
+}
\ No newline at end of file

Reply via email to