All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jgoodies.validation.view.ValidationComponentUtils Maven / Gradle / Ivy

Go to download

The JGoodies Validation helps you validate user input in Swing apps and report validation errors and warnings. It has been designed to work with different architectures and programming flavors.

The newest version!
/*
 * Copyright (c) 2003-2014 JGoodies Software GmbH. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  o Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 *  o Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 *  o Neither the name of JGoodies Software GmbH nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.jgoodies.validation.view;

import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.util.HashMap;
import java.util.Map;

import javax.swing.JComponent;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JViewport;
import javax.swing.LookAndFeel;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.LineBorder;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicBorders;
import javax.swing.text.JTextComponent;

import com.jgoodies.common.base.Strings;
import com.jgoodies.validation.Severity;
import com.jgoodies.validation.ValidationResult;

/**
 * Consists exclusively of static methods that provide convenience behavior for
 * operating on components that present validation data. Methods that access
 * component state utilize the {@link javax.swing.JComponent} client property
 * mechanism as a backing store.
 *
 * @author Karsten Lentzsch
 * @version $Revision: 1.20 $
 *
 * @see com.jgoodies.validation.ValidationMessage
 * @see com.jgoodies.validation.ValidationMessage#key()
 * @see com.jgoodies.validation.ValidationResult
 * @see com.jgoodies.validation.ValidationResult#subResult(Object)
 * @see com.jgoodies.validation.ValidationResult#keyMap()
 */
public final class ValidationComponentUtils {

    // Colors *****************************************************************

    private static final Color MANDATORY_FOREGROUND = new Color(70, 70, 210);

    private static final Color MANDATORY_BACKGROUND = new Color(235, 235, 255);

    private static final Color ERROR_BACKGROUND = new Color(255, 215, 215);

    private static final Color WARNING_BACKGROUND = new Color(255, 235, 205);


    // Client Property Keys **************************************************

    /**
     * The JComponent client property key for the mandatory property
     * that indicates whether a component's content is mandatory or optional.
     *
     * @see #isMandatory(JComponent)
     * @see #isMandatoryAndBlank(JComponent)
     * @see #setMandatory(JComponent, boolean)
     */
    private static final String MANDATORY_KEY = "validation.isMandatory";

    /**
     * The JComponent client property key used to associate a component
     * with a set of ValidationMessages. Before the Validation 1.4,
     * there was only one key per component. Since 1.4 multiple keys are
     * allowed.
     *
     * @see #getMessageKeys(JComponent)
     * @see #setMessageKey(JComponent, Object)
     * @see #setMessageKeys(JComponent, Object...)
     * @see com.jgoodies.validation.ValidationMessage#key()
     * @see ValidationResult#subResult(Object)
     * @see ValidationResult#keyMap()
     */
    private static final String MESSAGE_KEYS = "validation.messageKeys";

    /**
     * The JComponent client property key for the input hint text.
     * The text stored under this key is intended to be displayed if and only if
     * the component has the focus.
     *
     * @see #getInputHint(JComponent)
     * @see #setInputHint(JComponent, Object)
     */
    private static final String INPUT_HINT_KEY = "validation.inputHint";

    /**
     * The JComponent client property key for the severity property.
     * Once a component's severity state has been set by the method
     * {@link #updateComponentTreeSeverity(Container, ValidationResult)}
     * it can be used to display validation feedback, such as background
     * changes, overlay information, etc. See for example
     * {@link #updateComponentTreeSeverityBackground(Container, ValidationResult)}.
     *
     * @see #getSeverity(JComponent)
     * @see #setSeverity(JComponent, Severity)
     * @see #updateComponentTreeSeverity(Container, ValidationResult)
     * @see #updateComponentTreeSeverityBackground(Container, ValidationResult)
     */
    private static final String SEVERITY_KEY = "validation.severity";

    /**
     * The JComponent client property key used to store a component's
     * original background color. The stored background can be restored later.
     *
     * @see #getStoredBackground(JTextComponent)
     * @see #restoreBackground(JTextComponent)
     * @see #ensureCustomBackgroundStored(JTextComponent)
     * @see #setMandatoryBackground(JTextComponent)
     */
    private static final String STORED_BACKGROUND_KEY = "validation.storedBackground";

    /**
     * Holds a cached Border that is used to indicate mandatory text components.
     * It will be lazily created in method {@link #getMandatoryBorder()}.
     *
     * @see #getMandatoryBorder()
     * @see #setMandatoryBorder(JTextComponent)
     */
    private static Border mandatoryBorder;


    // A Map that holds reusable prototype text components ********************

    /**
     * Maps text component classes to prototype instances of such a class.
     * Used to get the default background color of these component types.
     *
     * @see #getDefaultBackground(JTextComponent)
     * @see #getPrototypeFor(Class)
     */
    private static final Map, JTextComponent> PROTOTYPE_COMPONENTS =
        new HashMap, JTextComponent>();


    // Instance creation ******************************************************

    private ValidationComponentUtils() {
        // Override default constructor; prevents instantiation.
    }


    // Accessing Validation Properties ****************************************

    /**
     * Returns if the component has been marked as mandatory.
     *
     * @param comp    the component to be checked
     * @return true if the component has been marked as mandatory
     *
     * @see #isMandatoryAndBlank(JComponent)
     * @see #setMandatory(JComponent, boolean)
     * @see #setMandatoryBackground(JTextComponent)
     * @see #setMandatoryBorder(JTextComponent)
     */
    public static boolean isMandatory(JComponent comp) {
        return Boolean.TRUE.equals(comp.getClientProperty(MANDATORY_KEY));
    }

    /**
     * Returns if the component is a {@link JTextComponent} with blank content
     * and has been marked as mandatory.
     *
     * @param comp  the component to be checked
     * @return true if the component's has a blank content and has been marked
     *     as mandatory
     *
     * @see #isMandatory(JComponent)
     * @see #setMandatory(JComponent, boolean)
     * @see #setMandatoryBackground(JTextComponent)
     * @see #setMandatoryBorder(JTextComponent)
     */
    public static boolean isMandatoryAndBlank(JComponent comp) {
        if (!(comp instanceof JTextComponent)) {
            return false;
        }
        JTextComponent textComponent = (JTextComponent) comp;
        return isMandatory(textComponent)
                && Strings.isBlank(textComponent.getText());
    }

    /**
     * Marks the given component as mandatory or optional.
     * The value will be stored as a client property value.
     *
     * @param comp        the component to be marked
     * @param mandatory   true for mandatory, false for optional
     *
     * @see #isMandatory(JComponent)
     * @see #isMandatoryAndBlank(JComponent)
     * @see #setMandatoryBackground(JTextComponent)
     * @see #setMandatoryBorder(JTextComponent)
     */
    public static void setMandatory(JComponent comp, boolean mandatory) {
        boolean oldMandatory = isMandatory(comp);
        if (oldMandatory != mandatory) {
            comp.putClientProperty(MANDATORY_KEY, Boolean.valueOf(mandatory));
        }
    }


    /**
     * Returns the component's {@link Severity} if it has been set before.
     * Useful for validation-aware containers that render the component's
     * validation state.
     *
     * @param comp   the component to be read
     * @return the component's {@code Severity} as set before
     *
     * @see #setSeverity(JComponent, Severity)
     * @see #updateComponentTreeSeverity(Container, ValidationResult)
     * @see #updateComponentTreeSeverityBackground(Container, ValidationResult)
     */
    public static Severity getSeverity(JComponent comp) {
        return (Severity) comp.getClientProperty(SEVERITY_KEY);
    }

    /**
     * Marks the given component with the specified severity.
     * The severity will be stored as a client property value.
     * Useful for validation-aware containers that render the component's
     * validation state once it has been set.
     *
     * @param comp      the component that shall be marked
     * @param severity  the component's severity
     *
     * @see #getSeverity(JComponent)
     * @see #updateComponentTreeSeverity(Container, ValidationResult)
     * @see #updateComponentTreeSeverityBackground(Container, ValidationResult)
     */
    public static void setSeverity(JComponent comp, Severity severity) {
        comp.putClientProperty(SEVERITY_KEY, severity);
    }


    /**
     * Returns the message key that has been set to associate the given
     * component with a set of ValidationMessages.
     *
     * @param comp  the component to be requested
     * @return the component's validation association key
     *
     * @see #setMessageKey(JComponent, Object)
     * @see com.jgoodies.validation.ValidationMessage
     * @see com.jgoodies.validation.ValidationMessage#key()
     * @see ValidationResult#subResult(Object)
     * @see ValidationResult#keyMap()
     */
    public static Object[] getMessageKeys(JComponent comp) {
        return (Object[]) comp.getClientProperty(MESSAGE_KEYS);
    }

    /**
     * Associates the given component with the specified message key.
     * That in turn will associate the component with all ValidationMessages
     * that share this key. The latter can be checked by comparing this key
     * with the key provided by a ValidationMessage.
     *
     * @param comp         the component that shall be associated with the key
     * @param messageKey   the key to be set
     *
     * @see #getMessageKeys(JComponent)
     * @see com.jgoodies.validation.ValidationMessage
     * @see com.jgoodies.validation.ValidationMessage#key()
     * @see ValidationResult#subResult(Object)
     * @see ValidationResult#keyMap()
     */
    public static void setMessageKey(JComponent comp, Object messageKey) {
        Object[] keyArray = messageKey == null ? null : new Object[]{messageKey};
        setMessageKeys(comp, keyArray);
    }


    /**
     * Associates the given component with the specified message keys.
     * That in turn will associate the component with all ValidationMessages
     * that share these keys. The latter can be checked by comparing the
     * given (and stored) keys with the key provided by a ValidationMessage.

* * Since version 2.0.1 this method uses varargs instead of an Object array. * * @param comp the component that shall be associated with the keys * @param messageKeys the keys to be set * * @see #getMessageKeys(JComponent) * @see com.jgoodies.validation.ValidationMessage * @see com.jgoodies.validation.ValidationMessage#key() * @see ValidationResult#subResult(Object) * @see ValidationResult#keyMap() * * @since 1.4 */ public static void setMessageKeys(JComponent comp, Object... messageKeys) { comp.putClientProperty(MESSAGE_KEYS, messageKeys); } /** * Returns the component's input hint that is stored in a client property. * Useful to indicate the format of valid data to the user. * The input hint object can be a plain {@code String} or a * compound object, for example that is able to localize the input hint * for the active {@link java.util.Locale}.

* * To make use of this information an editor should register a * listener with the focus management. Whenever the focused component * changes, the mechanism can request the input hint for the focus owner * using this service and can display the result hint in the user interface. * * @param comp the component to be requested * @return the component's input hint * * @see #setInputHint(JComponent, Object) */ public static Object getInputHint(JComponent comp) { return comp.getClientProperty(INPUT_HINT_KEY); } /** * Sets the input hint for the given component. This hint can be later * retrieved to indicate to the user the format of valid data for the * focused component. * * @param comp the component to set a hint for * @param hint the input hint to be associated with the component * * @see #getInputHint(JComponent) */ public static void setInputHint(JComponent comp, Object hint) { comp.putClientProperty(INPUT_HINT_KEY, hint); } /** * Checks and answers if the specified component is associated with an * error message in the given validation result. As a prerequisite, * the component must have an association key set. That can * be done using {@link #setMessageKey(JComponent, Object)} or * {@link #setMessageKeys(JComponent, Object[])}.

* * Note: This method may become slow if invoked for larger * validation results and multiple components. In this case, * it is recommended to use {@link ValidationResult#keyMap()} instead. * The latter iterates once over the validation result and can be used later * to request the severity for multiple components in almost linear time. * * @param comp used to get the association key from * @param result used to lookup the validation messages from * @return true if the given component is associated with an error message * @throws NullPointerException if the component or validation result * is {@code null} * * @see #hasWarning(JComponent, ValidationResult) * @see #getMessageKeys(JComponent) */ public static boolean hasError(JComponent comp, ValidationResult result) { return result.subResult(getMessageKeys(comp)).hasErrors(); } /** * Checks and answers if the specified component is associated with a * warning message in the given validation result. As a prerequisite, * the component must have a message key set. That can * be done using {@link #setMessageKey(JComponent, Object)} or * {@link #setMessageKeys(JComponent, Object[])}.

* * Note: This method may become slow if invoked for larger * validation results and multiple components. In this case, * it is recommended to use {@link ValidationResult#keyMap()} instead. * The latter iterates once over the validation result and can be used later * to request the severity for multiple components in almost linear time. * * @param comp used to get the association key from * @param result used to lookup the validation messages from * @return true if the given component is associated with a warning message * @throws NullPointerException if the component or validation result * is {@code null} * * @see #hasError(JComponent, ValidationResult) * @see #getMessageKeys(JComponent) */ public static boolean hasWarning(JComponent comp, ValidationResult result) { return result.subResult(getMessageKeys(comp)).hasWarnings(); } /** * Returns a default background color that can be used as the component * background for components with mandatory content. Typically this * color will be used with instances of {@link JTextComponent}.

* * Note: The component background colors are managed * by the look&feel implementation. Many l&fs will honor a * custom background color. However, some l&fs may ignore custom * background colors. It is recommended to check the * appearance in all l&fs available in an application. * * @return a background color useful for components with mandatory content * * @see #getMandatoryForeground() */ public static Color getMandatoryBackground() { return MANDATORY_BACKGROUND; } /** * Returns a default foreground color that can be used as the component * foreground for components with mandatory content. Typically this * color will be used with instances of {@link JTextComponent}.

* * Note: The component foreground and border colors are * managed by the look&feel implementation. Many l&fs will honor a * custom foreground color and custom border configuration. However, some * l&fs may ignore these custom settings. It is recommended to check * the appearance in all l&fs available in an application. * * @return a foreground color useful for components with mandatory content * * @see #getMandatoryBackground() * @see #getMandatoryBorder() */ public static Color getMandatoryForeground() { return MANDATORY_FOREGROUND; } /** * Sets the text component's background to a color that shall indicate * that the component's content is mandatory.

* * Note: The component background colors are * managed by the look&feel implementation. Many l&fs will honor a * custom foreground color and custom border configuration. However, some * l&fs may ignore these custom settings. It is recommended to check * the appearance in all l&fs available in an application. * * @param comp the text component that shall get a new background * * @see #setMandatoryBorder(JTextComponent) * @see #setErrorBackground(JTextComponent) * @see #setWarningBackground(JTextComponent) */ public static void setMandatoryBackground(JTextComponent comp) { comp.setBackground(MANDATORY_BACKGROUND); } /** * Returns the error background color used to mark components * that have an associated validation error. * * @return the error background color * * @see #getWarningBackground() * @see #setErrorBackground(JTextComponent) * @see #updateComponentTreeSeverityBackground(Container, ValidationResult) * * @since 1.0.2 */ public static Color getErrorBackground() { return ERROR_BACKGROUND; } /** * Sets the text component's background to a color that shall indicate * that the component's content has is invalid with error severity.

* * Note: The component background colors are * managed by the look&feel implementation. Many l&fs will honor a * custom foreground color and custom border configuration. However, some * l&fs may ignore these custom settings. It is recommended to check * the appearance in all l&fs available in an application. * * @param comp the text component that shall get a new background * * @see #setMandatoryBackground(JTextComponent) * @see #setWarningBackground(JTextComponent) */ public static void setErrorBackground(JTextComponent comp) { comp.setBackground(ERROR_BACKGROUND); } /** * Returns the warning background color used to mark components * that have an associated validation warning. * * @return the warning background color * * @see #getErrorBackground() * @see #setWarningBackground(JTextComponent) * @see #updateComponentTreeSeverityBackground(Container, ValidationResult) * * @since 1.0.2 */ public static Color getWarningBackground() { return WARNING_BACKGROUND; } /** * Sets the text component's background to a color that shall indicate * that the component's content is invalid with warning severity.

* * Note: The component background colors are * managed by the look&feel implementation. Many l&fs will honor a * custom foreground color and custom border configuration. However, some * l&fs may ignore these custom settings. It is recommended to check * the appearance in all l&fs available in an application. * * @param comp the text component that shall get a new background * * @see #setMandatoryBackground(JTextComponent) * @see #setErrorBackground(JTextComponent) */ public static void setWarningBackground(JTextComponent comp) { comp.setBackground(WARNING_BACKGROUND); } // Managing Borders ******************************************************* /** * Sets the text component's border to use a new border that shall indicate * that the component's content is mandatory.

* * Note: The component foreground and border colors are * managed by the look&feel implementation. Many l&fs will honor a * custom foreground color and custom border configuration. However, some * l&fs may ignore these custom settings. It is recommended to check * the appearance in all l&fs available in an application. * * @param comp the component that gets a new border * * @see #setMandatoryBackground(JTextComponent) * @see #getMandatoryBorder() */ public static void setMandatoryBorder(JTextComponent comp) { Container parent = comp.getParent(); if (parent instanceof JViewport) { Container grandpa = parent.getParent(); if (grandpa instanceof JScrollPane) { ((JScrollPane) grandpa).setBorder(getMandatoryBorder()); return; } } comp.setBorder(getMandatoryBorder()); } /** * Lazily creates and returns a {@link Border} instance that is used * to indicate that a component's content is mandatory. * * @return a {@code Border} that is used to indicate that * a component's content is mandatory */ public static Border getMandatoryBorder() { if (mandatoryBorder == null) { mandatoryBorder = new CompoundBorder(new LineBorder( getMandatoryForeground()), new BasicBorders.MarginBorder()); } return mandatoryBorder; } // Predefined Component Tree Updates ************************************** /** * Traverses a component tree and sets mandatory backgrounds * to text components that have been marked as mandatory * with {@link #setMandatory(JComponent, boolean)} before. * The iteration starts at the given container. * * @param container the component tree root * * @see #setMandatory(JComponent, boolean) * @see #setMandatoryBackground(JTextComponent) */ public static void updateComponentTreeMandatoryBackground(Container container) { visitComponentTree(container, null, new MandatoryBackgroundVisitor()); } /** * Traverses a component tree and sets mandatory backgrounds * to text components that have blank content and have been marked * as mandatory with {@link #setMandatory(JComponent, boolean)} before. * The iteration starts at the given container. * * @param container the component tree root * * @see #setMandatory(JComponent, boolean) * @see #setMandatoryBackground(JTextComponent) */ public static void updateComponentTreeMandatoryAndBlankBackground(Container container) { visitComponentTree(container, null, new MandatoryAndBlankBackgroundVisitor()); } /** * Traverses a component tree and sets mandatory borders * to text components that have been marked as mandatory * with {@link #setMandatory(JComponent, boolean)} before. * The iteration starts at the given container. * * @param container the component tree root * * @see #setMandatory(JComponent, boolean) * @see #setMandatoryBorder(JTextComponent) */ public static void updateComponentTreeMandatoryBorder(Container container) { visitComponentTree(container, null, new MandatoryBorderVisitor()); } /** * Traverses a component tree and sets the text component backgrounds * according to the severity of an associated validation result - if any. * The iteration starts at the given container.

* * The message keys used to associate components with validation messages * should be set using {@link #setMessageKey(JComponent, Object)} before * you call this method. * * @param container the component tree root * @param result the validation result used to lookup the severities * * @see #setMandatory(JComponent, boolean) * @see #setMessageKey(JComponent, Object) * @see #setMandatoryBackground(JTextComponent) * @see #setErrorBackground(JTextComponent) * @see #setWarningBackground(JTextComponent) * * @since 1.0.2 */ public static void updateComponentTreeSeverityBackground( Container container, ValidationResult result) { visitComponentTree(container, result.keyMap(), new SeverityBackgroundVisitor()); } /** * Traverses a component tree and sets the severity for all text components. * The iteration starts at the given container. If a validation result is * associated with a component, the result's severity is set. Otherwise * the severity is set to {@code null}. The severity is set using * {@link #setSeverity(JComponent, Severity)}.

* * Before you use this method, associate components with validation * messages using {@link #setMessageKey(JComponent, Object)}. * * @param container the component tree root * @param result the validation result that provides the associated messages * * @see #setSeverity(JComponent, Severity) */ public static void updateComponentTreeSeverity(Container container, ValidationResult result) { visitComponentTree(container, result.keyMap(), new SeverityVisitor()); } // Visiting Text Components in a Component Tree *************************** /** * Traverses the component tree starting at the given container and invokes * the given visitor's {@code #visit} method on each instance of * {@link JTextComponent}. Useful to perform custom component tree updates * that are not already provided by the {@code #updateComponentTreeXXX} * methods.

* * The arguments passed to the #visit method are the visited component and * its associated validation subresult. This subresult is requested from * the specified {@code keyMap} using the component's message key.

* * Before you use this method, associate text component with validation * messages using {@link #setMessageKey(JComponent, Object)}. * * @param container the component tree root * @param keyMap maps messages keys to associated validation results * @param visitor the visitor that is applied to all text components * * @see #setMessageKey(JComponent, Object) * @see Visitor */ public static void visitComponentTree( Container container, Map keyMap, Visitor visitor) { int componentCount = container.getComponentCount(); for (int i = 0; i < componentCount; i++) { Component child = container.getComponent(i); if (child instanceof JTextComponent) { JComponent component = (JComponent) child; visitor.visit(component, keyMap); } else if (child instanceof Container) { visitComponentTree((Container) child, keyMap, visitor); } } } // Helper Code ************************************************************ /** * Returns the ValidationResult associated with the given component * using the specified validation result key map, * or {@code null} if the component has no message key set, * or {@code ValidationResult.EMPTY} if the key map contains * no result for the component. * * @param comp the component may be marked with a validation message keys * @param keyMap maps validation message keys to ValidationResults * @return the ValidationResult associated with the given component * as provided by the specified validation key map * or {@code null} if the component has no message key set, * or {@code ValidationResult.EMPTY} if no result is associated * with the component * * @since 1.4 */ public static ValidationResult getAssociatedResult( JComponent comp, Map keyMap) { Object[] messageKeys = getMessageKeys(comp); if (messageKeys == null || keyMap == null) { return null; } if (messageKeys.length == 1) { ValidationResult result = keyMap.get(messageKeys[0]); return result == null ? ValidationResult.EMPTY : result; // already unmodifiable } ValidationResult result = null; for (Object element : messageKeys) { ValidationResult subResult = keyMap.get(element); if (subResult != null) { if (result == null) { result = new ValidationResult(); } result.addAllFrom(subResult); } } return result == null ? ValidationResult.EMPTY : ValidationResult.unmodifiableResult(result); } /** * Returns a default background color that is requested from an instance * of a prototype component of the same type as the given component. * If such a component cannot be created, a JTextField is used. * The prototype's enabled and editable state is then set to the state * of the given component. Finally the prototype's background is returned. * * @param component the component to get the default background for * @return the background color of a prototype text component that has * the same state as the given component * * @see #restoreBackground(JTextComponent) */ private static Color getDefaultBackground(JTextComponent component) { JTextComponent prototype = getPrototypeFor(component.getClass()); prototype.setEnabled(component.isEnabled()); prototype.setEditable(component.isEditable()); return prototype.getBackground(); } private static LookAndFeel cachedLookAndFeel; /** * Lazily creates and returns a prototype text component instance * of the given text component class. The prototype components are * stored per the current Look&Feel. If the L&F these components * have been created for has changed, the prototype component cache is * cleared. In other words the cachec prototype components are valid * as long as the current L&F is the one used to create them. * * @param prototypeClass the class of the prototype to be returned * @return the lazily created prototype component */ private static JTextComponent getPrototypeFor(Class prototypeClass) { if (UIManager.getLookAndFeel() != cachedLookAndFeel) { PROTOTYPE_COMPONENTS.clear(); cachedLookAndFeel = UIManager.getLookAndFeel(); } JTextComponent prototype = PROTOTYPE_COMPONENTS.get(prototypeClass); if (prototype == null) { try { prototype = prototypeClass.newInstance(); } catch (Exception e) { prototype = new JTextField(); } PROTOTYPE_COMPONENTS.put(prototypeClass, prototype); } return prototype; } /** * Returns the background color that has been previously stored for * the given component, or {@code null} if none. * * @param comp the component to be requested * @return the background color that has been previously stored for * the given component, or {@code null} if none. * * @see #ensureCustomBackgroundStored(JTextComponent) * @see #restoreBackground(JTextComponent) */ private static Color getStoredBackground(JTextComponent comp) { return (Color) comp.getClientProperty(STORED_BACKGROUND_KEY); } /** * Ensures that a text component's custom background - if any - * is stored as a client property. Used to store the background once only. * * @param comp the component to be requested * * @see #getStoredBackground(JTextComponent) * @see #restoreBackground(JTextComponent) */ private static void ensureCustomBackgroundStored(JTextComponent comp) { if (getStoredBackground(comp) != null) { return; } Color background = comp.getBackground(); if ( background == null || background instanceof UIResource || background == WARNING_BACKGROUND || background == ERROR_BACKGROUND) { return; } comp.putClientProperty(STORED_BACKGROUND_KEY, background); } /** * Looks up and restores the text component's previously stored (original) * background color. * * @param comp the component that shall get its original background color * * @see #getStoredBackground(JTextComponent) * @see #ensureCustomBackgroundStored(JTextComponent) */ private static void restoreBackground(JTextComponent comp) { Color storedBackground = getStoredBackground(comp); comp.setBackground(storedBackground == null ? getDefaultBackground(comp) : storedBackground); } // Visitor Definition and Predefined Visitor Implementations ************** /** * Describes visitors that visit a component tree. * Visitor implementations are used to mark components, * to change component background, to associate components * with additional information; things that are not already * provided by the {@code #updateComponentTreeXXX} methods * and this class' predefined Visitor implementations. */ public interface Visitor { /** * Visits the given component using the specified key map, that maps * message keys to associated validation subresults. * Typically an implementation will operate on the component state. * * @param component the component to be visited * @param keyMap maps messages keys to associated validation results */ void visit(JComponent component, Map keyMap); } /** * A validation visitor that sets the background color of JTextComponents * to mark mandatory components. */ private static final class MandatoryBackgroundVisitor implements Visitor { /** * Sets the mandatory background to text components that have been marked * as mandatory. * * @param component the component to be visited * @param keyMap ignored */ @Override public void visit(JComponent component, Map keyMap) { if (component instanceof JTextComponent && isMandatory(component)) { setMandatoryBackground((JTextComponent) component); } } } /** * A validation visitor that sets the background color of JTextComponents * to indicate if mandatory components have a blank text or not. */ private static final class MandatoryAndBlankBackgroundVisitor implements Visitor { /** * Sets the mandatory background to text components that have been marked * as mandatory if the content is blank, otherwise the original * background is restored. * * @param component the component to be visited * @param keyMap ignored */ @Override public void visit(JComponent component, Map keyMap) { JTextComponent textChild = (JTextComponent) component; if (isMandatoryAndBlank(textChild)) { setMandatoryBackground(textChild); } else { restoreBackground(textChild); } } } /** * A validation visitor that sets a mandatory border for JTextComponents * that have been marked as mandatory. */ private static final class MandatoryBorderVisitor implements Visitor { /** * Sets the mandatory border to text components that have been marked * as mandatory. * * @param component the component to be visited * @param keyMap ignored */ @Override public void visit(JComponent component, Map keyMap) { if (component instanceof JTextComponent && isMandatory(component)) { setMandatoryBorder((JTextComponent) component); } } } /** * A validation visitor that sets the background color of JTextComponents * according to the severity of an associated validation result - if any. */ private static final class SeverityBackgroundVisitor implements Visitor { /** * Sets the component background according to the associated * validation result: default, error, warning. * * @param component the component to be visited * @param keyMap maps messages keys to associated validation results */ @Override public void visit(JComponent component, Map keyMap) { Object messageKeys = getMessageKeys(component); if (messageKeys == null) { return; } JTextComponent textChild = (JTextComponent) component; ensureCustomBackgroundStored(textChild); ValidationResult result = getAssociatedResult(component, keyMap); if (result == null || result.isEmpty()) { restoreBackground(textChild); } else if (result.hasErrors()) { setErrorBackground(textChild); } else if (result.hasWarnings()) { setWarningBackground(textChild); } } } /** * A validation visitor that sets each component's severity * according to its associated validation result or to {@code null} * if no message key is set for the component. */ private static final class SeverityVisitor implements Visitor { /** * Sets the component's severity according to its associated * validation result, or {@code null} if the component * has no message key set. * * @param component the component to be visited * @param keyMap maps messages keys to associated validation results */ @Override public void visit(JComponent component, Map keyMap) { ValidationResult result = getAssociatedResult(component, keyMap); Severity severity = result == null ? null : result.getSeverity(); setSeverity(component, severity); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy