com.jgoodies.validation.view.ValidationComponentUtils Maven / Gradle / Ivy
Show all versions of jgoodies-validation Show documentation
/*
* 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