org.archiviststoolkit.swing.ATBasicComponentFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.archiviststoolkit.swing.ATBasicComponentFactory.java

Source

/**
 * Archivists' Toolkit(TM) Copyright  2005-2007 Regents of the University of California, New York University, & Five Colleges, Inc.
 * All rights reserved.
 *
 * This software is free. You can redistribute it and / or modify it under the terms of the Educational Community License (ECL)
 * version 1.0 (http://www.opensource.org/licenses/ecl1.php)
 *
 * This software 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 ECL license for more details about permissions and limitations.
 *
 *
 * Archivists' Toolkit(TM)
 * http://www.archiviststoolkit.org
 * info@archiviststoolkit.org
 *
 * @author Lee Mandell
 * Date: Jan 11, 2006
 * Time: 1:52:15 PM
 */

package org.archiviststoolkit.swing;

import com.inet.jortho.SpellChecker;
import com.jgoodies.validation.formatter.EmptyNumberFormatter;
import com.jgoodies.validation.formatter.EmptyDateFormatter;
import com.jgoodies.binding.PresentationModel;
import com.jgoodies.binding.value.ValueModel;
import com.jgoodies.binding.value.ComponentValueModel;
import com.jgoodies.binding.list.SelectionInList;
import com.jgoodies.binding.adapter.BasicComponentFactory;
import com.jgoodies.binding.adapter.Bindings;

import javax.swing.*;
import javax.swing.text.NumberFormatter;
import javax.swing.text.DefaultFormatterFactory;
import javax.swing.text.JTextComponent;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Vector;
import java.awt.*;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;

import org.archiviststoolkit.structure.ATFieldInfo;
import org.archiviststoolkit.model.LookupListItems;
import org.archiviststoolkit.ApplicationFrame;
import org.archiviststoolkit.util.LookupListUtils;
import org.archiviststoolkit.util.ATDateUtils;

public class ATBasicComponentFactory extends BasicComponentFactory {

    public static JFormattedTextField createIntegerField(PresentationModel detailsModel, String fieldName) {
        return createIntegerField(detailsModel, fieldName, false, false);
    }

    public static JFormattedTextField createBufferedIntegerField(PresentationModel detailsModel, String fieldName) {
        return createIntegerField(detailsModel, fieldName, false, true);
    }

    public static JFormattedTextField createBufferedIntegerField(PresentationModel detailsModel, String fieldName,
            boolean useGrouping) {
        return createIntegerField(detailsModel, fieldName, useGrouping, true);
    }

    public static JFormattedTextField createIntegerField(PresentationModel detailsModel, String fieldName,
            boolean useGrouping) {
        return createIntegerField(detailsModel, fieldName, useGrouping, false);
    }

    public static JFormattedTextField createIntegerField(PresentationModel detailsModel, String fieldName,
            boolean useGrouping, boolean buffered) {
        NumberFormat nf = NumberFormat.getIntegerInstance();
        nf.setGroupingUsed(useGrouping);
        JFormattedTextField returnField;
        if (buffered) {
            returnField = BasicComponentFactory.createIntegerField(detailsModel.getBufferedModel(fieldName), nf);
        } else {
            returnField = BasicComponentFactory.createIntegerField(detailsModel.getModel(fieldName), nf);
        }
        returnField.addFocusListener(
                new ATFormattedTextFocusListener(returnField, "Invalid entry. This field requires an integer."));
        return returnField;
    }

    /**
     * Method to create a formated textfield which only accepts integers with a certain range
     * @param detailsModel The details model
     * @param fieldName The field name to bound to
     * @param min The minimum value allowed
     * @param max The maximum value allowed
     * @return
     */
    public static JFormattedTextField createIntegerField(PresentationModel detailsModel, String fieldName, int min,
            int max) {
        NumberFormat nf = NumberFormat.getIntegerInstance();
        JFormattedTextField returnField;
        returnField = BasicComponentFactory.createIntegerField(detailsModel.getModel(fieldName), nf);
        returnField.addFocusListener(new ATFormattedTextFocusListener(returnField,
                "Invalid entry. This field requires an integer between " + min + " and " + max, min, max));
        return returnField;
    }

    public static JFormattedTextField createUnboundIntegerField() {
        return createUnboundIntegerField(true);
    }

    public static JFormattedTextField createUnboundIntegerField(boolean useGrouping) {
        NumberFormat nf = NumberFormat.getIntegerInstance();
        nf.setGroupingUsed(useGrouping);
        NumberFormatter numberFormatter = new EmptyNumberFormatter(nf);
        numberFormatter.setValueClass(Integer.class);
        JFormattedTextField returnField = new JFormattedTextField(numberFormatter);
        returnField.addFocusListener(new FocusAdapter() {
            public void focusLost(FocusEvent e) {
                Component component = e.getComponent();
                if (component instanceof JFormattedTextField) {
                    if (!((JFormattedTextField) component).isEditValid()) {
                        JOptionPane.showMessageDialog(null, "Invalid entry. This field requires an integer.");
                    }
                }

            }
        });
        return returnField;
    }

    /**
     * Creates and returns a formatted text field that is bound
     * to the Date value of the given ValueModel.
     * The JFormattedTextField is configured with an AbstractFormatter
     * that uses two different DateFormats to edit and display the Date.
     * A <code>SHORT</code> DateFormat with strict checking is used to edit
     * (parse) a date; the DateFormatter's default DateFormat is used to
     * display (format) a date. In both cases <code>null</code> Dates are
     * mapped to the empty String.
     *
     * @param valueModel the model that holds the value to be edited
     * @return a formatted text field for Date instances that is bound
     *         to the given value model
     * @throws NullPointerException if the valueModel is <code>null</code>
     */
    public static JFormattedTextField createDateField(ValueModel valueModel) {
        JFormattedTextField.AbstractFormatter defaultFormatter = new EmptyDateFormatter(
                ApplicationFrame.applicationDateFormat);
        JFormattedTextField.AbstractFormatter displayFormatter = new EmptyDateFormatter(
                ApplicationFrame.applicationDateFormat);
        DefaultFormatterFactory formatterFactory = new DefaultFormatterFactory(defaultFormatter, displayFormatter);

        JFormattedTextField returnField = createFormattedTextField(valueModel, formatterFactory);
        returnField.addFocusListener(new ATFormattedTextFocusListener(returnField,
                "Invalid date format. Dates must be entered with the format\n"
                        + ApplicationFrame.applicationDateFormat.toPattern(),
                true));
        return returnField;
    }

    public static JFormattedTextField createUnboundDateField() {
        JFormattedTextField.AbstractFormatter defaultFormatter = new EmptyDateFormatter(
                ApplicationFrame.applicationDateFormat);
        JFormattedTextField.AbstractFormatter displayFormatter = new EmptyDateFormatter(
                ApplicationFrame.applicationDateFormat);
        DefaultFormatterFactory formatterFactory = new DefaultFormatterFactory(defaultFormatter, displayFormatter);

        JFormattedTextField returnField = new JFormattedTextField(formatterFactory);
        returnField.addFocusListener(new FocusAdapter() {
            public void focusLost(FocusEvent e) {
                Component component = e.getComponent();
                if (component instanceof JFormattedTextField) {
                    if (!((JFormattedTextField) component).isEditValid()) {
                        JOptionPane.showMessageDialog(null,
                                "Invalid date format. Dates must be entered with the format\n"
                                        + ApplicationFrame.applicationDateFormat.toPattern());
                    } else {
                        ATDateUtils.isValidDatabaseDate((JFormattedTextField) component);
                    }
                }

            }
        });
        return returnField;
    }

    /**
     * This create a textfeild that is used to input iso dates. The only thing this does
     * from a regular textfield is that it does checking of the date format on focus lost
     * valid formats are yyyy, yyyy-mm, yyyy-mm-dd in the date format being used by the AT
     * @param valueModel The vlaue model to bind to
     * @return  The JTextfield
     */
    public static JTextField createISODateField(ValueModel valueModel) {
        JTextField textField = new JTextField();
        Bindings.bind(textField, valueModel, true);
        textField.addFocusListener(new ISODateFocusListener(textField));
        return textField;
    }

    //   public static JFormattedTextField getSQLDateField() {
    //      DateFormatter dateFormatter =
    //            new DateFormatter();
    //      dateFormatter.setValueClass(Date.class);
    //      return new JFormattedTextField(dateFormatter);
    //   }
    //

    public static JFormattedTextField createDoubleField(PresentationModel detailsModel, String fieldName) {
        return createDoubleField(detailsModel, fieldName, false);
    }

    public static JFormattedTextField createDoubleField(PresentationModel detailsModel, String fieldName,
            boolean useGrouping) {
        NumberFormat nf = NumberFormat.getInstance();
        nf.setGroupingUsed(useGrouping);
        NumberFormatter numberFormatter = new EmptyNumberFormatter(nf, null);
        numberFormatter.setValueClass(Double.class);
        JFormattedTextField returnField = BasicComponentFactory
                .createFormattedTextField(detailsModel.getModel(fieldName), numberFormatter);
        returnField.addFocusListener(
                new ATFormattedTextFocusListener(returnField, "Invalid entry. This field requires a number."));
        return returnField;
    }

    /**
     * Method to return a text field which has been formated to accept currency
     *
     * @param detailsModel The model to bind to
     * @param fieldName The name of the field in the model to bind to
     * @param useGrouping
     * @return The textfeild formatted to accept currency
     */
    public static JFormattedTextField createCurrencyField(PresentationModel detailsModel, String fieldName,
            boolean useGrouping) {
        NumberFormat nf = NumberFormat.getInstance();
        nf.setGroupingUsed(useGrouping);
        nf.setMaximumFractionDigits(2);
        NumberFormatter numberFormatter = new EmptyNumberFormatter(nf, null);
        numberFormatter.setValueClass(Double.class);
        JFormattedTextField returnField = BasicComponentFactory
                .createFormattedTextField(detailsModel.getModel(fieldName), numberFormatter);
        returnField.addFocusListener(
                new ATFormattedTextFocusListener(returnField, "Invalid entry. This field requires a number."));
        return returnField;
    }

    public static JFormattedTextField createUnboundDoubleField() {
        return createUnboundDoubleField(true);
    }

    public static JFormattedTextField createUnboundDoubleField(boolean useGrouping) {
        NumberFormat nf = NumberFormat.getInstance();
        nf.setGroupingUsed(useGrouping);
        NumberFormatter numberFormatter = new EmptyNumberFormatter(nf, null);
        numberFormatter.setValueClass(Double.class);
        JFormattedTextField returnField = new JFormattedTextField(numberFormatter);
        returnField.addFocusListener(new FocusAdapter() {
            public void focusLost(FocusEvent e) {
                Component component = e.getComponent();
                if (component instanceof JFormattedTextField) {
                    if (!((JFormattedTextField) component).isEditValid()) {
                        JOptionPane.showMessageDialog(null, "Invalid entry. This field requires a number.");
                    }
                }

            }
        });
        return returnField;
    }

    public static JCheckBox createCheckBox(PresentationModel detailsModel, String fieldName, Class clazz) {
        return createCheckBox(detailsModel, fieldName, clazz, false);
    }

    public static JCheckBox createCheckBox(PresentationModel detailsModel, String fieldName, Class clazz,
            boolean buffered) {
        ATFieldInfo fieldInfo = ATFieldInfo.getFieldInfo(clazz.getName() + "." + fieldName);
        JCheckBox checkBox;
        if (fieldInfo == null) {
            if (buffered) {
                checkBox = BasicComponentFactory.createCheckBox(detailsModel.getBufferedModel(fieldName),
                        fieldName);
            } else {
                checkBox = BasicComponentFactory.createCheckBox(detailsModel.getModel(fieldName), fieldName);
            }
        } else {
            if (buffered) {
                checkBox = BasicComponentFactory.createCheckBox(detailsModel.getBufferedModel(fieldName),
                        fieldInfo.getFieldLabel());
            } else {
                checkBox = BasicComponentFactory.createCheckBox(detailsModel.getModel(fieldName),
                        fieldInfo.getFieldLabel());
            }
            checkBox.setToolTipText(fieldInfo.getPopupHelpText());
        }
        checkBox.addFocusListener(new ATCheckBoxListener(checkBox));
        return checkBox;
    }

    //   public static JComboBox createComboBox(PresentationModel detailsModel, String fieldName, Class clazz) {
    //      ATFieldInfo fieldInfo = ATFieldInfo.getFieldInfo(clazz.getName() + "." + fieldName);
    //      Vector<String> values = null;
    //      if (fieldInfo != null) {
    //         values = LookupListUtils.getLookupListValues(fieldInfo.getLookupList());
    //      }
    //      if (values == null) {
    //         values = new Vector<String>();
    //      }
    //      return addListCellRenderer(BasicComponentFactory.createComboBox(new SelectionInList(values, detailsModel.getModel(fieldName))));
    //   }

    public static JComboBox createComboBox(PresentationModel detailsModel, String fieldName, Class clazz) {
        return createComboBox(detailsModel, fieldName, clazz, 0, false);
    }

    public static JComboBox createComboBox(PresentationModel detailsModel, String fieldName, Class clazz,
            boolean buffered) {
        return createComboBox(detailsModel, fieldName, clazz, 0, buffered);
    }

    public static JComboBox createComboBox(PresentationModel detailsModel, String fieldName, Class clazz,
            Integer maxLength) {
        return createComboBox(detailsModel, fieldName, clazz, maxLength, false);
    }

    public static JComboBox createComboBox(PresentationModel detailsModel, String fieldName, Class clazz,
            Integer maxLength, boolean buffered) {
        ATFieldInfo fieldInfo = ATFieldInfo.getFieldInfo(clazz.getName() + "." + fieldName);
        Vector<LookupListItems> values = null;
        JComboBox returnCombobox;
        if (fieldInfo != null) {
            values = LookupListUtils.getLookupListValues2(fieldInfo.getLookupList());
        }
        if (values == null) {
            values = new Vector<LookupListItems>();
        } else {

        }
        if (fieldInfo == null) {
            returnCombobox = addListCellRenderer(BasicComponentFactory.createComboBox(new SelectionInList(values)),
                    maxLength);
        } else {
            if (buffered) {
                ValueModel valueModel = detailsModel.getBufferedModel(fieldName);
                detailsModel.observeChanged(valueModel);
                returnCombobox = addListCellRenderer(
                        BasicComponentFactory
                                .createComboBox(new SelectionInList(values, ATJgoodiesBindingConverterFactory
                                        .createLookupListConverter(valueModel, fieldInfo.getLookupList()))),
                        maxLength);
            } else {
                ValueModel valueModel = detailsModel.getModel(fieldName);
                detailsModel.observeChanged(valueModel);
                returnCombobox = addListCellRenderer(
                        BasicComponentFactory
                                .createComboBox(new SelectionInList(values, ATJgoodiesBindingConverterFactory
                                        .createLookupListConverter(valueModel, fieldInfo.getLookupList()))),
                        maxLength);
            }
        }
        returnCombobox.addFocusListener(new ATComboBoxListener(returnCombobox));
        return returnCombobox;
    }

    public static JComboBox createComboBoxWithConverter(PresentationModel detailsModel, String fieldName,
            Vector values, String listName) {
        return addListCellRenderer(
                BasicComponentFactory
                        .createComboBox(new SelectionInList(values,
                                ATJgoodiesBindingConverterFactory
                                        .createLookupListConverter(detailsModel.getModel(fieldName), listName))),
                0);
        //      return createComboBox(detailsModel, fieldName, values, 0);
    }

    private static JComboBox addListCellRenderer(JComboBox comboBox, Integer maxLength) {
        comboBox.setRenderer(new DisplayListCellRenderer(maxLength));
        return comboBox;
    }

    public static JComboBox createComboBox(PresentationModel detailsModel, String fieldName, Object[] values) {
        return createComboBox(detailsModel, fieldName, values, 0, false);
    }

    public static JComboBox createComboBox(PresentationModel detailsModel, String fieldName, Object[] values,
            boolean buffered) {
        return createComboBox(detailsModel, fieldName, values, 0, buffered);
    }

    public static JComboBox createComboBox(PresentationModel detailsModel, String fieldName, Object[] values,
            Integer maxLength, boolean buffered) {
        JComboBox returnCombobox;
        if (buffered) {
            returnCombobox = addListCellRenderer(BasicComponentFactory
                    .createComboBox(new SelectionInList(values, detailsModel.getModel(fieldName))), maxLength);
        } else {
            returnCombobox = addListCellRenderer(BasicComponentFactory.createComboBox(
                    new SelectionInList(values, detailsModel.getBufferedModel(fieldName))), maxLength);
        }
        returnCombobox.addFocusListener(new ATComboBoxListener(returnCombobox));
        return returnCombobox;
    }

    public static JComboBox createComboBox(PresentationModel detailsModel, String fieldName, ArrayList values) {
        return createComboBox(detailsModel, fieldName, values, 0);
    }

    public static JComboBox createComboBox(PresentationModel detailsModel, String fieldName, ArrayList values,
            Integer maxLength) {
        JComboBox returnCombobox = addListCellRenderer(
                BasicComponentFactory.createComboBox(new SelectionInList(values, detailsModel.getModel(fieldName))),
                maxLength);
        returnCombobox.addFocusListener(new ATComboBoxListener(returnCombobox));
        return returnCombobox;
    }

    public static JComboBox createComboBox(PresentationModel detailsModel, String fieldName, Vector values) {
        JComboBox returnCombobox = createComboBox(detailsModel, fieldName, values, 0);
        returnCombobox.addFocusListener(new ATComboBoxListener(returnCombobox));
        return returnCombobox;
    }

    public static JComboBox createComboBox(PresentationModel detailsModel, String fieldName, Vector values,
            Integer maxLength) {
        return addListCellRenderer(
                BasicComponentFactory.createComboBox(new SelectionInList(values, detailsModel.getModel(fieldName))),
                maxLength);
        //      return addListCellRenderer(BasicComponentFactory.createComboBox(
        //            new SelectionInList(values,
        //                  ATJgoodiesBindingConverterFactory.createLookupListConverter(detailsModel.getModel(fieldName), ""))), maxLength);
    }

    public static JComboBox createUnboundComboBox(Vector values) {
        return new JComboBox(new DefaultComboBoxModel(values));
    }

    public static JComboBox createUnboundComboBox(Object[] values) {
        return new JComboBox(new DefaultComboBoxModel(values));
    }

    public static JTextArea createTextArea(ValueModel valueModel) {
        return createTextArea(valueModel, true);
    }

    public static JTextArea createTextArea(ValueModel valueModel, boolean commitOnFocusLost) {
        JTextArea textArea = BasicComponentFactory.createTextArea(valueModel, commitOnFocusLost);
        textArea.addFocusListener(new ATTextFocusListener(textArea));
        return fixTabBehavior(textArea);
    }

    public static JTextArea createUnboundedTextArea() {
        return fixTabBehavior(new JTextArea());
    }

    private static JTextArea fixTabBehavior(JTextArea textArea) {
        textArea.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, null);
        textArea.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, null);

        // add support for spell checking here
        // using Jortho spell checker library
        if (ApplicationFrame.enableSpellCheck) {
            try {
                SpellChecker.register(textArea);
                SpellChecker.enableAutoSpell(textArea, ApplicationFrame.enableSpellCheckHighlight);
            } catch (Exception e) {
                // just print the stack trace and do nothing
                e.printStackTrace();
            }
        }

        return textArea;
    }

    public static JTextField createTextField(ValueModel valueModel) {
        return ATBasicComponentFactory.createTextField(valueModel, false);
    }

    /**
     * Creates and returns a text field with the content bound
     * to the given ValueModel. Text changes can be committed to the model
     * on focus lost or on every character typed.
     *
     * @param valueModel         the model that provides the text value
     * @param commitOnFocusLost  true to commit text changes on focus lost,
     *     false to commit text changes on every character typed
     * @return a text field that is bound to the given value model
     *
     * @throws NullPointerException if the valueModel is <code>null</code>
     *
     * @see #createTextField(ValueModel)
     */
    public static JTextField createTextField(ValueModel valueModel, boolean commitOnFocusLost) {
        JTextField textField = new JTextField();
        Bindings.bind(textField, valueModel, commitOnFocusLost);
        textField.addFocusListener(new ATTextFocusListener(textField));
        return textField;
    }

    public static JLabel createLabel(Class clazz, String fieldName, String defaultValue) {
        ATFieldInfo fieldInfo = ATFieldInfo.getFieldInfo(clazz.getName() + "." + fieldName);
        if (fieldInfo == null) {
            return new JLabel(defaultValue);
        } else {
            String labelText = fieldInfo.getFieldLabel();
            JLabel label;
            if (labelText.length() == 0) {
                label = new JLabel(defaultValue);
            } else {
                label = new JLabel(labelText);
            }
            label.setToolTipText(fieldInfo.getPopupHelpText());
            return label;
        }
    }

    public static JLabel createLabel(Class clazz, String fieldName) {
        return createLabel(clazz, fieldName, fieldName);
    }

    /**
     * Listens to property changes in a ComponentValueModel and
     * updates the associated component state.
     *
     * @see ComponentValueModel
     */
    private static final class ATTextFocusListener implements FocusListener {

        private String oldValue = null;
        private JTextComponent textComponent;

        public ATTextFocusListener(JTextComponent textComponent) {
            this.textComponent = textComponent;
        }

        public void focusGained(FocusEvent event) {
            oldValue = textComponent.getText();
        }

        public void focusLost(FocusEvent event) {
            if (!oldValue.equals(textComponent.getText())) {
                ApplicationFrame.getInstance().setRecordDirty();
            }
        }
    }

    /**
     * Listens to property changes in a ComponentValueModel and
     * updates the associated component state.
     *
     * @see ComponentValueModel
     */
    private static final class ATComboBoxListener implements FocusListener {

        private Object oldValue = null;
        private JComboBox comboBox;

        public ATComboBoxListener(JComboBox comboBox) {
            this.comboBox = comboBox;
        }

        public void focusGained(FocusEvent event) {
            oldValue = comboBox.getSelectedItem();
        }

        public void focusLost(FocusEvent event) {
            if (oldValue != null && !oldValue.equals(comboBox.getSelectedItem())) {
                ApplicationFrame.getInstance().setRecordDirty();
            }
        }
    }

    /**
     * Listens to property changes in a ComponentValueModel and
     * updates the associated component state.
     *
     * @see ComponentValueModel
     */
    private static final class ATCheckBoxListener implements FocusListener {

        private Object oldValue = null;
        private JCheckBox checkBox;

        public ATCheckBoxListener(JCheckBox checkBox) {
            this.checkBox = checkBox;
        }

        public void focusGained(FocusEvent event) {
            oldValue = checkBox.isSelected();
        }

        public void focusLost(FocusEvent event) {
            if (!oldValue.equals(checkBox.isSelected())) {
                ApplicationFrame.getInstance().setRecordDirty();
            }
        }
    }

    /**
     * Listens to property changes in a ComponentValueModel and
     * updates the associated component state.
     *
     * @see ComponentValueModel
     */
    private static final class ATFormattedTextFocusListener implements FocusListener {

        private String oldValue = null;
        private JFormattedTextField formattedTextField;
        private String invalidImputErrorMessage;
        private boolean isDateField = false; // default this to false;
        private boolean checkIntegerRange = false; // see weather to check the range of an integer field
        private int min = 0; // the minimum value an integer can be
        private int max = 0; // the maximum value an integer can be

        public ATFormattedTextFocusListener(JFormattedTextField formattedTextField,
                String invalidImputErrorMessage) {
            this.formattedTextField = formattedTextField;
            this.invalidImputErrorMessage = invalidImputErrorMessage;
        }

        /**
         * Constructor that allows setting that this is a date field so that the date range can be checked
         * @param formattedTextField
         * @param invalidImputErrorMessage
         * @param isDateField
         */
        public ATFormattedTextFocusListener(JFormattedTextField formattedTextField, String invalidImputErrorMessage,
                boolean isDateField) {
            this.formattedTextField = formattedTextField;
            this.invalidImputErrorMessage = invalidImputErrorMessage;
            this.isDateField = isDateField;
        }

        /**
         * Constructor that allows for setting the minimum and maximum an integer should be
         * @param formattedTextField
         * @param invalidImputErrorMessage
         * @param max
         * @param min
         */
        public ATFormattedTextFocusListener(JFormattedTextField formattedTextField, String invalidImputErrorMessage,
                int min, int max) {
            this.formattedTextField = formattedTextField;
            this.invalidImputErrorMessage = invalidImputErrorMessage;
            this.min = min;
            this.max = max;
            this.checkIntegerRange = true;
        }

        public void focusGained(FocusEvent event) {
            oldValue = formattedTextField.getText();
        }

        public void focusLost(FocusEvent event) {
            if (isDateField) { // it is a date field so must also check minimum allowed date for the database
                if (!(formattedTextField.isEditValid())) {
                    JOptionPane.showMessageDialog(null, invalidImputErrorMessage);
                } else {
                    ATDateUtils.isValidDatabaseDate(formattedTextField);
                }
                // check to see if the date has changed
                if (!oldValue.equals(formattedTextField.getText())) {
                    ApplicationFrame.getInstance().setRecordDirty();
                }
            } else if (checkIntegerRange) {
                if (!(formattedTextField.isEditValid())) {
                    JOptionPane.showMessageDialog(null, invalidImputErrorMessage);
                } else { // check to see if the entered integer is within a valid range
                    try {
                        int intValue = Integer.parseInt(formattedTextField.getText());

                        if (intValue < min || intValue > max) {
                            JOptionPane.showMessageDialog(null, invalidImputErrorMessage);
                            formattedTextField.setText("");
                        }
                    } catch (NumberFormatException nfe) {
                    } // do nothing about this is just a blank
                }

                if (!oldValue.equals(formattedTextField.getText())) {
                    ApplicationFrame.getInstance().setRecordDirty();
                }
            } else if (!(formattedTextField.isEditValid())) {
                JOptionPane.showMessageDialog(null, invalidImputErrorMessage);
            } else if (oldValue == null && formattedTextField.getText() == null) {
                //do nothing
            } else if (oldValue == null && formattedTextField.getText() != null) {
                ApplicationFrame.getInstance().setRecordDirty();
            } else {
                if (!oldValue.equals(formattedTextField.getText())) {
                    ApplicationFrame.getInstance().setRecordDirty();
                }
            }
        }
    }

    /**
    * Listens to property changes in a ComponentValueModel and
    * updates the associated component state.
     * Also check that the iso date is valid. Valid dates formats are
    * yyyy, yyyy-mm, yyyy-dd,
    * @see ComponentValueModel
    */
    private static final class ISODateFocusListener implements FocusListener {

        private String oldValue = null;
        private JTextComponent textComponent;

        public ISODateFocusListener(JTextComponent textComponent) {
            this.textComponent = textComponent;
        }

        public void focusGained(FocusEvent event) {
            oldValue = textComponent.getText();
        }

        public void focusLost(FocusEvent event) {
            String newValue = textComponent.getText();

            // check that the iso date format is valid
            if (newValue.length() > 0 && ATDateUtils.isValidISODate((JTextField) textComponent)) {
                // now check if the value was changed
                if (!oldValue.equals(newValue)) {
                    ApplicationFrame.getInstance().setRecordDirty();
                } else {
                    textComponent.setText(oldValue);
                }
            }
        }
    }

}