/* * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI * for visualizing and manipulating spatial features with geometry and attributes. * * Copyright (C) 2003 Vivid Solutions * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * For more information, contact: * * Vivid Solutions * Suite #1A * 2328 Government Street * Victoria BC V8T 5G5 * Canada * * (250)385-6040 * www.vividsolutions.com */ package com.vividsolutions.jump.workbench.ui; import com.vividsolutions.jts.util.Assert; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import javax.swing.JTextField; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; import javax.swing.text.PlainDocument; /** * Prevents the user from entering invalid data. */ public class ValidatingTextField extends JTextField { public static final Validator LONG_VALIDATOR = new ValidatingTextField.Validator() { public boolean isValid(String text) { try { Long.parseLong(text.trim() + "0"); return true; } catch (NumberFormatException e) { return false; } } }; /** * Prevents the user from entering invalid integer. */ public static final Validator INTEGER_VALIDATOR = new ValidatingTextField.Validator() { public boolean isValid(String text) { try { //Add "0" so user can type "-" or make it blank [Jon Aquino] Integer.parseInt(text.trim() + "0"); return true; } catch (NumberFormatException e) { return false; } } }; /** * Prevents the user from entering invalid double. */ public static final Validator DOUBLE_VALIDATOR = new ValidatingTextField.Validator() { public boolean isValid(String text) { try { //Add "0" so user can type "-" or make it blank [Jon Aquino] Double.parseDouble(text.trim() + "0"); return true; } catch (NumberFormatException e) { return false; } } }; /** * Cleaner that does nothing. */ public static Cleaner DUMMY_CLEANER = new Cleaner() { public String clean(String text) { return text; } }; /** * The validators allow the user to simply enter "+", "-", or ".". If the * user doesn't go any farther, this cleaner will set the text to 0, which * is reasonable. */ public static Cleaner NUMBER_CLEANER = new Cleaner() { public String clean(String text) { try { Double.parseDouble(text.trim()); return text; } catch (NumberFormatException e) { return "0"; } } }; /** * Validator that does nothing. */ public static Validator DUMMY_VALIDATOR = new Validator() { public boolean isValid(String text) { return true; } }; private Cleaner cleaner; /** * Validator that uses dummy cleaner. */ public ValidatingTextField(String text, int columns, final Validator validator) { this(text, columns, LEFT, validator, DUMMY_CLEANER); } /** * Validator for text fields. */ public ValidatingTextField(String text, int columns, int horizontalAlignment, final Validator validator, final Cleaner cleaner) { super(text, columns); this.cleaner = cleaner; setHorizontalAlignment(horizontalAlignment); installValidationBehavior(this, validator, cleaner); // Clean the text, mainly so that parties wishing to install a // BlankCleaner need only pass "" for the text. [Jon Aquino] setText(cleaner.clean(getText())); //Bonus: workaround for how GridBagLayout shrinks components to //minimum sizes if it can't accomodate their preferred sizes. //[Jon Aquino] setMinimumSize(getPreferredSize()); } //Hopefully this will let us add validation behaviour to combo boxes. //[Jon Aquino] public static void installValidationBehavior(final JTextField textField, final Validator validator, final Cleaner cleaner) { final boolean[] validating = new boolean[] { true }; textField.setDocument(new PlainDocument() { public void insertString(int offs, String str, AttributeSet a) throws BadLocationException { if (!validating[0]) { super.insertString(offs, str, a); return; } String currentText = this.getText(0, getLength()); String beforeOffset = currentText.substring(0, offs); String afterOffset = currentText.substring(offs, currentText .length()); String proposedResult = beforeOffset + str + afterOffset; if (validator.isValid(cleaner.clean(proposedResult))) { super.insertString(offs, str, a); } } public void remove(int offs, int len) throws BadLocationException { if (!validating[0]) { super.remove(offs, len); return; } String currentText = this.getText(0, getLength()); String beforeOffset = currentText.substring(0, offs); String afterOffset = currentText.substring(len + offs, currentText.length()); String proposedResult = beforeOffset + afterOffset; if (validator.isValid(cleaner.clean(proposedResult))) { super.remove(offs, len); } } }); textField.addFocusListener(new FocusAdapter() { public void focusLost(FocusEvent e) { // Added validating flag to fix bug: on losing focus, #setText // called #remove, which cleared the text, which then failed // integer validation (being an empty string), which cancelled // the remove, causing duplicate strings to appear. // [Jon Aquino 2005-03-07] validating[0] = false; try { textField.setText(cleaner.clean(textField.getText())); } finally { validating[0] = true; } } }); } public String getText() { //Focus may not be lost yet (e.g. when syncing with scrollbar) [Jon // Aquino] return cleaner.clean(super.getText()); } public double getDouble() { return Double.parseDouble(getText().trim()); } public int getInteger() { return Integer.parseInt(getText().trim()); } public static interface Validator { public boolean isValid(String text); } public static interface Cleaner { public String clean(String text); } /** * Implements validator with a greater than threshold. */ public static class GreaterThanValidator implements Validator { private double threshold; public GreaterThanValidator(double threshold) { this.threshold = threshold; } public boolean isValid(String text) { try { return Double.parseDouble(text.trim()) > threshold; } catch (NumberFormatException e) { //Handle -, ., E [Jon Aquino 2005-03-17] return true; } } } /** * Implements validator with a less than threshold. */ public static class LessThanValidator implements Validator { private double threshold; public LessThanValidator(double threshold) { this.threshold = threshold; } public boolean isValid(String text) { try { return Double.parseDouble(text.trim()) < threshold; } catch (NumberFormatException e) { return true; } } } /** * Implements validator with a greater than or equal to threshold. */ public static class GreaterThanOrEqualValidator implements Validator { private double threshold; public GreaterThanOrEqualValidator(double threshold) { this.threshold = threshold; } public boolean isValid(String text) { try { return Double.parseDouble(text.trim()) >= threshold; } catch (NumberFormatException e) { return true; } } } /** * Implements validator with a less than or equal to threshold. */ public static class LessThanOrEqualValidator implements Validator { private double threshold; public LessThanOrEqualValidator(double threshold) { this.threshold = threshold; } public boolean isValid(String text) { try { return Double.parseDouble(text.trim()) <= threshold; } catch (NumberFormatException e) { return true; } } } /** * Leave untouched the really good stuff and the really bad stuff, but * replace the sort-of good stuff, as it's probably just transient. */ public static class NumberCleaner implements ValidatingTextField.Cleaner { private String replacement; public NumberCleaner(String replacement) { this.replacement = replacement; } public String clean(String text) { try { Double.parseDouble(text.trim()); return text.trim(); } catch (NumberFormatException e) { try { //Handle -, ., E [Jon Aquino 2004-08-04] Double.parseDouble(text.trim() + "0"); return replacement; } catch (NumberFormatException e2) { return text.trim(); } } } protected String getReplacement() { return replacement; } } public static class BlankCleaner implements ValidatingTextField.Cleaner { private String replacement; public BlankCleaner(String replacement) { this.replacement = replacement; } public String clean(String text) { return (text.trim().length() == 0) ? getReplacement() : text; } protected String getReplacement() { return replacement; } } public static class MinIntCleaner implements Cleaner { private int minimum; public MinIntCleaner(int minimum) { this.minimum = minimum; } public String clean(String text) { return "" + Math.max(minimum, Integer.parseInt(text)); } } /** * Extends CompositeValidator to validat that integers is within a set of * boundary values. */ public static class BoundedIntValidator extends CompositeValidator { public BoundedIntValidator(int min, int max) { super(new Validator[] { INTEGER_VALIDATOR, new GreaterThanOrEqualValidator(min), new LessThanOrEqualValidator(max) }); Assert.isTrue(min < max); } } public static class BoundedDoubleValidator extends CompositeValidator { public BoundedDoubleValidator(double min, boolean includeMin, double max, boolean includeMax) { super(new Validator[] { DOUBLE_VALIDATOR, includeMin ? (Validator) new GreaterThanOrEqualValidator( min) : new GreaterThanValidator(min), includeMax ? (Validator) new LessThanOrEqualValidator(max) : new LessThanValidator(max) }); Assert.isTrue(min < max); } } public static class MaxIntCleaner implements Cleaner { private int maximum; public MaxIntCleaner(int maximum) { this.maximum = maximum; } public String clean(String text) { return "" + Math.min(maximum, Integer.parseInt(text)); } } /** * Implements validator to check for more than one condition. */ public static class CompositeValidator implements Validator { private Validator[] validators; public CompositeValidator(Validator[] validators) { this.validators = validators; } public boolean isValid(String text) { for (int i = 0; i < validators.length; i++) { if (!validators[i].isValid(text)) { return false; } } return true; } } public static class CompositeCleaner implements Cleaner { private Cleaner[] cleaners; public CompositeCleaner(Cleaner[] cleaners) { this.cleaners = cleaners; } public String clean(String text) { String result = text; for (int i = 0; i < cleaners.length; i++) { result = cleaners[i].clean(result); } return result; } } }