0001: /*
0002: * Copyright (c) 2003-2007 JGoodies Karsten Lentzsch. All Rights Reserved.
0003: *
0004: * Redistribution and use in source and binary forms, with or without
0005: * modification, are permitted provided that the following conditions are met:
0006: *
0007: * o Redistributions of source code must retain the above copyright notice,
0008: * this list of conditions and the following disclaimer.
0009: *
0010: * o Redistributions in binary form must reproduce the above copyright notice,
0011: * this list of conditions and the following disclaimer in the documentation
0012: * and/or other materials provided with the distribution.
0013: *
0014: * o Neither the name of JGoodies Karsten Lentzsch nor the names of
0015: * its contributors may be used to endorse or promote products derived
0016: * from this software without specific prior written permission.
0017: *
0018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
0019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
0020: * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
0021: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
0022: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
0023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
0024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
0025: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
0026: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
0027: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
0028: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0029: */
0030:
0031: package com.jgoodies.validation.view;
0032:
0033: import java.awt.Color;
0034: import java.awt.Component;
0035: import java.awt.Container;
0036: import java.beans.PropertyChangeEvent;
0037: import java.beans.PropertyChangeListener;
0038: import java.util.HashMap;
0039: import java.util.Map;
0040:
0041: import javax.swing.*;
0042: import javax.swing.border.Border;
0043: import javax.swing.border.CompoundBorder;
0044: import javax.swing.border.LineBorder;
0045: import javax.swing.plaf.UIResource;
0046: import javax.swing.plaf.basic.BasicBorders;
0047: import javax.swing.text.JTextComponent;
0048:
0049: import com.jgoodies.validation.Severity;
0050: import com.jgoodies.validation.ValidationResult;
0051: import com.jgoodies.validation.util.ValidationUtils;
0052:
0053: /**
0054: * Consists exclusively of static methods that provide convenience behavior for
0055: * operating on components that present validation data. Methods that access
0056: * component state utilize the {@link javax.swing.JComponent} client property
0057: * mechanism as a backing store.
0058: *
0059: * @author Karsten Lentzsch
0060: * @version $Revision: 1.13 $
0061: *
0062: * @see com.jgoodies.validation.ValidationMessage
0063: * @see com.jgoodies.validation.ValidationMessage#key()
0064: * @see com.jgoodies.validation.ValidationResult
0065: * @see com.jgoodies.validation.ValidationResult#subResult(Object)
0066: * @see com.jgoodies.validation.ValidationResult#keyMap()
0067: */
0068: public final class ValidationComponentUtils {
0069:
0070: // Colors *****************************************************************
0071:
0072: private static final Color MANDATORY_FOREGROUND = new Color(70, 70,
0073: 210);
0074:
0075: private static final Color MANDATORY_BACKGROUND = new Color(235,
0076: 235, 255);
0077:
0078: private static final Color ERROR_BACKGROUND = new Color(255, 215,
0079: 215);
0080:
0081: private static final Color WARNING_BACKGROUND = new Color(255, 235,
0082: 205);
0083:
0084: // Client Property Keys **************************************************
0085:
0086: /**
0087: * The JComponent client property key for the mandatory property
0088: * that indicates whether a component's content is mandatory or optional.
0089: *
0090: * @see #isMandatory(JComponent)
0091: * @see #isMandatoryAndBlank(JComponent)
0092: * @see #setMandatory(JComponent, boolean)
0093: */
0094: private static final String MANDATORY_KEY = "validation.isMandatory";
0095:
0096: /**
0097: * The JComponent client property key used to associate a component
0098: * with a set of ValidationMessages. Before the Validation 1.4,
0099: * there was only one key per component. Since 1.4 multiple keys are
0100: * allowed.
0101: *
0102: * @see #getMessageKeys(JComponent)
0103: * @see #setMessageKey(JComponent, Object)
0104: * @see #setMessageKeys(JComponent, Object...)
0105: * @see com.jgoodies.validation.ValidationMessage#key()
0106: * @see ValidationResult#subResult(Object)
0107: * @see ValidationResult#keyMap()
0108: */
0109: private static final String MESSAGE_KEYS = "validation.messageKeys";
0110:
0111: /**
0112: * The JComponent client property key for the input hint text.
0113: * The text stored under this key is intended to be displayed if and only if
0114: * the component has the focus.
0115: *
0116: * @see #getInputHint(JComponent)
0117: * @see #setInputHint(JComponent, Object)
0118: */
0119: private static final String INPUT_HINT_KEY = "validation.inputHint";
0120:
0121: /**
0122: * The JComponent client property key for the severity property.
0123: * Once a component's severity state has been set by the method
0124: * {@link #updateComponentTreeSeverity(Container, ValidationResult)}
0125: * it can be used to display validation feedback, such as background
0126: * changes, overlay information, etc. See for example
0127: * {@link #updateComponentTreeSeverityBackground(Container, ValidationResult)}.
0128: *
0129: * @see #getSeverity(JComponent)
0130: * @see #setSeverity(JComponent, Severity)
0131: * @see #updateComponentTreeSeverity(Container, ValidationResult)
0132: * @see #updateComponentTreeSeverityBackground(Container, ValidationResult)
0133: */
0134: private static final String SEVERITY_KEY = "validation.severity";
0135:
0136: /**
0137: * The JComponent client property key used to store a component's
0138: * original background color. The stored background can be restored later.
0139: *
0140: * @see #getStoredBackground(JTextComponent)
0141: * @see #restoreBackground(JTextComponent)
0142: * @see #ensureCustomBackgroundStored(JTextComponent)
0143: * @see #setMandatoryBackground(JTextComponent)
0144: */
0145: private static final String STORED_BACKGROUND_KEY = "validation.storedBackground";
0146:
0147: /**
0148: * Holds a cached Border that is used to indicate mandatory text components.
0149: * It will be lazily created in method {@link #getMandatoryBorder()}.
0150: *
0151: * @see #getMandatoryBorder()
0152: * @see #setMandatoryBorder(JTextComponent)
0153: */
0154: private static Border mandatoryBorder;
0155:
0156: // A Map that holds reusable prototype text components ********************
0157:
0158: /**
0159: * Maps text component classes to prototype instances of such a class.
0160: * Used to get the default background color of these component types.
0161: *
0162: * @see #getDefaultBackground(JTextComponent)
0163: * @see #getPrototypeFor(Class)
0164: */
0165: private static final Map<Class<? extends JTextComponent>, JTextComponent> PROTOTYPE_COMPONENTS = new HashMap<Class<? extends JTextComponent>, JTextComponent>();
0166:
0167: // Instance creation ******************************************************
0168:
0169: private ValidationComponentUtils() {
0170: // Override default constructor; prevents instantiation.
0171: }
0172:
0173: // Accessing Validation Properties ****************************************
0174:
0175: /**
0176: * Returns if the component has been marked as mandatory.
0177: *
0178: * @param comp the component to be checked
0179: * @return true if the component has been marked as mandatory
0180: *
0181: * @see #isMandatoryAndBlank(JComponent)
0182: * @see #setMandatory(JComponent, boolean)
0183: * @see #setMandatoryBackground(JTextComponent)
0184: * @see #setMandatoryBorder(JTextComponent)
0185: */
0186: public static boolean isMandatory(JComponent comp) {
0187: return Boolean.TRUE.equals(comp
0188: .getClientProperty(MANDATORY_KEY));
0189: }
0190:
0191: /**
0192: * Returns if the component is a {@link JTextComponent} with blank content
0193: * and has been marked as mandatory.
0194: *
0195: * @param comp the component to be checked
0196: * @return true if the component's has a blank content and has been marked
0197: * as mandatory
0198: *
0199: * @see #isMandatory(JComponent)
0200: * @see #setMandatory(JComponent, boolean)
0201: * @see #setMandatoryBackground(JTextComponent)
0202: * @see #setMandatoryBorder(JTextComponent)
0203: */
0204: public static boolean isMandatoryAndBlank(JComponent comp) {
0205: if (!(comp instanceof JTextComponent)) {
0206: return false;
0207: }
0208: JTextComponent textComponent = (JTextComponent) comp;
0209: return isMandatory(textComponent)
0210: && ValidationUtils.isBlank(textComponent.getText());
0211: }
0212:
0213: /**
0214: * Marks the given component as mandatory or optional.
0215: * The value will be stored as a client property value.
0216: *
0217: * @param comp the component to be marked
0218: * @param mandatory true for mandatory, false for optional
0219: *
0220: * @see #isMandatory(JComponent)
0221: * @see #isMandatoryAndBlank(JComponent)
0222: * @see #setMandatoryBackground(JTextComponent)
0223: * @see #setMandatoryBorder(JTextComponent)
0224: */
0225: public static void setMandatory(JComponent comp, boolean mandatory) {
0226: boolean oldMandatory = isMandatory(comp);
0227: if (oldMandatory != mandatory) {
0228: comp.putClientProperty(MANDATORY_KEY, Boolean
0229: .valueOf(mandatory));
0230: }
0231: }
0232:
0233: /**
0234: * Returns the component's {@link Severity} if it has been set before.
0235: * Useful for validation-aware containers that render the component's
0236: * validation state.
0237: *
0238: * @param comp the component to be read
0239: * @return the component's <code>Severity</code> as set before
0240: *
0241: * @see #setSeverity(JComponent, Severity)
0242: * @see #updateComponentTreeSeverity(Container, ValidationResult)
0243: * @see #updateComponentTreeSeverityBackground(Container, ValidationResult)
0244: */
0245: public static Severity getSeverity(JComponent comp) {
0246: return (Severity) comp.getClientProperty(SEVERITY_KEY);
0247: }
0248:
0249: /**
0250: * Marks the given component with the specified severity.
0251: * The severity will be stored as a client property value.
0252: * Useful for validation-aware containers that render the component's
0253: * validation state once it has been set.
0254: *
0255: * @param comp the component that shall be marked
0256: * @param severity the component's severity
0257: *
0258: * @see #getSeverity(JComponent)
0259: * @see #updateComponentTreeSeverity(Container, ValidationResult)
0260: * @see #updateComponentTreeSeverityBackground(Container, ValidationResult)
0261: */
0262: public static void setSeverity(JComponent comp, Severity severity) {
0263: comp.putClientProperty(SEVERITY_KEY, severity);
0264: }
0265:
0266: /**
0267: * Returns the message key that has been set to associate the given
0268: * component with a set of ValidationMessages.
0269: *
0270: * @param comp the component to be requested
0271: * @return the component's validation association key
0272: *
0273: * @see #setMessageKey(JComponent, Object)
0274: * @see com.jgoodies.validation.ValidationMessage
0275: * @see com.jgoodies.validation.ValidationMessage#key()
0276: * @see ValidationResult#subResult(Object)
0277: * @see ValidationResult#keyMap()
0278: */
0279: public static Object[] getMessageKeys(JComponent comp) {
0280: return (Object[]) comp.getClientProperty(MESSAGE_KEYS);
0281: }
0282:
0283: /**
0284: * Associates the given component with the specified message key.
0285: * That in turn will associate the component with all ValidationMessages
0286: * that share this key. The latter can be checked by comparing this key
0287: * with the key provided by a ValidationMessage.
0288: *
0289: * @param comp the component that shall be associated with the key
0290: * @param messageKey the key to be set
0291: *
0292: * @see #getMessageKeys(JComponent)
0293: * @see com.jgoodies.validation.ValidationMessage
0294: * @see com.jgoodies.validation.ValidationMessage#key()
0295: * @see ValidationResult#subResult(Object)
0296: * @see ValidationResult#keyMap()
0297: */
0298: public static void setMessageKey(JComponent comp, Object messageKey) {
0299: Object[] keyArray = messageKey == null ? null
0300: : new Object[] { messageKey };
0301: setMessageKeys(comp, keyArray);
0302: }
0303:
0304: /**
0305: * Associates the given component with the specified message keys.
0306: * That in turn will associate the component with all ValidationMessages
0307: * that share these keys. The latter can be checked by comparing the
0308: * given (and stored) keys with the key provided by a ValidationMessage.
0309: *
0310: * @param comp the component that shall be associated with the keys
0311: * @param messageKeys the keys to be set
0312: *
0313: * @see #getMessageKeys(JComponent)
0314: * @see com.jgoodies.validation.ValidationMessage
0315: * @see com.jgoodies.validation.ValidationMessage#key()
0316: * @see ValidationResult#subResult(Object)
0317: * @see ValidationResult#keyMap()
0318: *
0319: * @since 1.4
0320: */
0321: public static void setMessageKeys(JComponent comp,
0322: Object[] messageKeys) {
0323: comp.putClientProperty(MESSAGE_KEYS, messageKeys);
0324: }
0325:
0326: /**
0327: * Returns the component's input hint that is stored in a client property.
0328: * Useful to indicate the format of valid data to the user.
0329: * The input hint object can be a plain <code>String</code> or a
0330: * compound object, for example that is able to localize the input hint
0331: * for the active {@link java.util.Locale}.<p>
0332: *
0333: * To make use of this information an editor should register a
0334: * listener with the focus management. Whenever the focused component
0335: * changes, the mechanism can request the input hint for the focus owner
0336: * using this service and can display the result hint in the user interface.
0337: *
0338: * @param comp the component to be requested
0339: * @return the component's input hint
0340: *
0341: * @see #setInputHint(JComponent, Object)
0342: */
0343: public static Object getInputHint(JComponent comp) {
0344: return comp.getClientProperty(INPUT_HINT_KEY);
0345: }
0346:
0347: /**
0348: * Sets the input hint for the given component. This hint can be later
0349: * retrieved to indicate to the user the format of valid data for the
0350: * focused component.
0351: *
0352: * @param comp the component to set a hint for
0353: * @param hint the input hint to be associated with the component
0354: *
0355: * @see #getInputHint(JComponent)
0356: */
0357: public static void setInputHint(JComponent comp, Object hint) {
0358: comp.putClientProperty(INPUT_HINT_KEY, hint);
0359: }
0360:
0361: /**
0362: * Checks and answers if the specified component is associated with an
0363: * error message in the given validation result. As a prerequisite,
0364: * the component must have an <em>association key</em> set. That can
0365: * be done using {@link #setMessageKey(JComponent, Object)} or
0366: * {@link #setMessageKeys(JComponent, Object[])}.<p>
0367: *
0368: * <strong>Note:</strong> This method may become slow if invoked for larger
0369: * validation results <em>and</em> multiple components. In this case,
0370: * it is recommended to use {@link ValidationResult#keyMap()} instead.
0371: * The latter iterates once over the validation result and can be used later
0372: * to request the severity for multiple components in almost linear time.
0373: *
0374: * @param comp used to get the association key from
0375: * @param result used to lookup the validation messages from
0376: * @return true if the given component is associated with an error message
0377: * @throws NullPointerException if the component or validation result
0378: * is <code>null</code>
0379: *
0380: * @see #hasWarning(JComponent, ValidationResult)
0381: * @see #getMessageKeys(JComponent)
0382: */
0383: public static boolean hasError(JComponent comp,
0384: ValidationResult result) {
0385: return result.subResult(getMessageKeys(comp)).hasErrors();
0386: }
0387:
0388: /**
0389: * Checks and answers if the specified component is associated with a
0390: * warning message in the given validation result. As a prerequisite,
0391: * the component must have a <em>message key</em> set. That can
0392: * be done using {@link #setMessageKey(JComponent, Object)} or
0393: * {@link #setMessageKeys(JComponent, Object[])}.<p>
0394: *
0395: * <strong>Note:</strong> This method may become slow if invoked for larger
0396: * validation results <em>and</em> multiple components. In this case,
0397: * it is recommended to use {@link ValidationResult#keyMap()} instead.
0398: * The latter iterates once over the validation result and can be used later
0399: * to request the severity for multiple components in almost linear time.
0400: *
0401: * @param comp used to get the association key from
0402: * @param result used to lookup the validation messages from
0403: * @return true if the given component is associated with a warning message
0404: * @throws NullPointerException if the component or validation result
0405: * is <code>null</code>
0406: *
0407: * @see #hasError(JComponent, ValidationResult)
0408: * @see #getMessageKeys(JComponent)
0409: */
0410: public static boolean hasWarning(JComponent comp,
0411: ValidationResult result) {
0412: return result.subResult(getMessageKeys(comp)).hasWarnings();
0413: }
0414:
0415: /**
0416: * Returns a default background color that can be used as the component
0417: * background for components with mandatory content. Typically this
0418: * color will be used with instances of {@link JTextComponent}.<p>
0419: *
0420: * <strong>Note:</strong> The component background colors are managed
0421: * by the look&feel implementation. Many l&fs will honor a
0422: * custom background color. However, some l&fs may ignore custom
0423: * background colors. It is recommended to check the
0424: * appearance in all l&fs available in an application.
0425: *
0426: * @return a background color useful for components with mandatory content
0427: *
0428: * @see #getMandatoryForeground()
0429: */
0430: public static Color getMandatoryBackground() {
0431: return MANDATORY_BACKGROUND;
0432: }
0433:
0434: /**
0435: * Returns a default foreground color that can be used as the component
0436: * foreground for components with mandatory content. Typically this
0437: * color will be used with instances of {@link JTextComponent}.<p>
0438: *
0439: * <strong>Note:</strong> The component foreground and border colors are
0440: * managed by the look&feel implementation. Many l&fs will honor a
0441: * custom foreground color and custom border configuration. However, some
0442: * l&fs may ignore these custom settings. It is recommended to check
0443: * the appearance in all l&fs available in an application.
0444: *
0445: * @return a foreground color useful for components with mandatory content
0446: *
0447: * @see #getMandatoryBackground()
0448: * @see #getMandatoryBorder()
0449: */
0450: public static Color getMandatoryForeground() {
0451: return MANDATORY_FOREGROUND;
0452: }
0453:
0454: /**
0455: * Sets the text component's background to a color that shall indicate
0456: * that the component's content is mandatory.<p>
0457: *
0458: * <strong>Note:</strong> The component background colors are
0459: * managed by the look&feel implementation. Many l&fs will honor a
0460: * custom foreground color and custom border configuration. However, some
0461: * l&fs may ignore these custom settings. It is recommended to check
0462: * the appearance in all l&fs available in an application.
0463: *
0464: * @param comp the text component that shall get a new background
0465: *
0466: * @see #setMandatoryBorder(JTextComponent)
0467: * @see #setErrorBackground(JTextComponent)
0468: * @see #setWarningBackground(JTextComponent)
0469: */
0470: public static void setMandatoryBackground(JTextComponent comp) {
0471: comp.setBackground(MANDATORY_BACKGROUND);
0472: }
0473:
0474: /**
0475: * Returns the error background color used to mark components
0476: * that have an associated validation error.
0477: *
0478: * @return the error background color
0479: *
0480: * @see #getWarningBackground()
0481: * @see #setErrorBackground(JTextComponent)
0482: * @see #updateComponentTreeSeverityBackground(Container, ValidationResult)
0483: *
0484: * @since 1.0.2
0485: */
0486: public static Color getErrorBackground() {
0487: return ERROR_BACKGROUND;
0488: }
0489:
0490: /**
0491: * Sets the text component's background to a color that shall indicate
0492: * that the component's content has is invalid with error severity.<p>
0493: *
0494: * <strong>Note:</strong> The component background colors are
0495: * managed by the look&feel implementation. Many l&fs will honor a
0496: * custom foreground color and custom border configuration. However, some
0497: * l&fs may ignore these custom settings. It is recommended to check
0498: * the appearance in all l&fs available in an application.
0499: *
0500: * @param comp the text component that shall get a new background
0501: *
0502: * @see #setMandatoryBackground(JTextComponent)
0503: * @see #setWarningBackground(JTextComponent)
0504: */
0505: public static void setErrorBackground(JTextComponent comp) {
0506: comp.setBackground(ERROR_BACKGROUND);
0507: }
0508:
0509: /**
0510: * Returns the warning background color used to mark components
0511: * that have an associated validation warning.
0512: *
0513: * @return the warning background color
0514: *
0515: * @see #getErrorBackground()
0516: * @see #setWarningBackground(JTextComponent)
0517: * @see #updateComponentTreeSeverityBackground(Container, ValidationResult)
0518: *
0519: * @since 1.0.2
0520: */
0521: public static Color getWarningBackground() {
0522: return WARNING_BACKGROUND;
0523: }
0524:
0525: /**
0526: * Sets the text component's background to a color that shall indicate
0527: * that the component's content is invalid with warning severity.<p>
0528: *
0529: * <strong>Note:</strong> The component background colors are
0530: * managed by the look&feel implementation. Many l&fs will honor a
0531: * custom foreground color and custom border configuration. However, some
0532: * l&fs may ignore these custom settings. It is recommended to check
0533: * the appearance in all l&fs available in an application.
0534: *
0535: * @param comp the text component that shall get a new background
0536: *
0537: * @see #setMandatoryBackground(JTextComponent)
0538: * @see #setErrorBackground(JTextComponent)
0539: */
0540: public static void setWarningBackground(JTextComponent comp) {
0541: comp.setBackground(WARNING_BACKGROUND);
0542: }
0543:
0544: // Managing Borders *******************************************************
0545:
0546: /**
0547: * Sets the text component's border to use a new border that shall indicate
0548: * that the component's content is mandatory.<p>
0549: *
0550: * <strong>Note:</strong> The component foreground and border colors are
0551: * managed by the look&feel implementation. Many l&fs will honor a
0552: * custom foreground color and custom border configuration. However, some
0553: * l&fs may ignore these custom settings. It is recommended to check
0554: * the appearance in all l&fs available in an application.
0555: *
0556: * @param comp the component that gets a new border
0557: *
0558: * @see #setMandatoryBackground(JTextComponent)
0559: * @see #getMandatoryBorder()
0560: */
0561: public static void setMandatoryBorder(JTextComponent comp) {
0562: Container parent = comp.getParent();
0563: if (parent instanceof JViewport) {
0564: Container grandpa = parent.getParent();
0565: if (grandpa instanceof JScrollPane) {
0566: ((JScrollPane) grandpa).setBorder(getMandatoryBorder());
0567: return;
0568: }
0569: }
0570: comp.setBorder(getMandatoryBorder());
0571: }
0572:
0573: /**
0574: * Lazily creates and returns a {@link Border} instance that is used
0575: * to indicate that a component's content is mandatory.
0576: *
0577: * @return a <code>Border</code> that is used to indicate that
0578: * a component's content is mandatory
0579: */
0580: public static Border getMandatoryBorder() {
0581: if (mandatoryBorder == null) {
0582: mandatoryBorder = new CompoundBorder(new LineBorder(
0583: getMandatoryForeground()),
0584: new BasicBorders.MarginBorder());
0585: }
0586: return mandatoryBorder;
0587: }
0588:
0589: // Predefined Component Tree Updates **************************************
0590:
0591: /**
0592: * Traverses a component tree and sets mandatory backgrounds
0593: * to text components that have been marked as mandatory
0594: * with {@link #setMandatory(JComponent, boolean)} before.
0595: * The iteration starts at the given container.
0596: *
0597: * @param container the component tree root
0598: *
0599: * @see #setMandatory(JComponent, boolean)
0600: * @see #setMandatoryBackground(JTextComponent)
0601: */
0602: public static void updateComponentTreeMandatoryBackground(
0603: Container container) {
0604: visitComponentTree(container, null,
0605: new MandatoryBackgroundVisitor());
0606: }
0607:
0608: /**
0609: * Traverses a component tree and sets mandatory backgrounds
0610: * to text components that have blank content and have been marked
0611: * as mandatory with {@link #setMandatory(JComponent, boolean)} before.
0612: * The iteration starts at the given container.
0613: *
0614: * @param container the component tree root
0615: *
0616: * @see #setMandatory(JComponent, boolean)
0617: * @see #setMandatoryBackground(JTextComponent)
0618: */
0619: public static void updateComponentTreeMandatoryAndBlankBackground(
0620: Container container) {
0621: visitComponentTree(container, null,
0622: new MandatoryAndBlankBackgroundVisitor());
0623: }
0624:
0625: /**
0626: * Traverses a component tree and sets mandatory borders
0627: * to text components that have been marked as mandatory
0628: * with {@link #setMandatory(JComponent, boolean)} before.
0629: * The iteration starts at the given container.
0630: *
0631: * @param container the component tree root
0632: *
0633: * @see #setMandatory(JComponent, boolean)
0634: * @see #setMandatoryBorder(JTextComponent)
0635: */
0636: public static void updateComponentTreeMandatoryBorder(
0637: Container container) {
0638: visitComponentTree(container, null,
0639: new MandatoryBorderVisitor());
0640: }
0641:
0642: /**
0643: * Traverses a component tree and sets the text component backgrounds
0644: * according to the severity of an associated validation result - if any.
0645: * The iteration starts at the given container.<p>
0646: *
0647: * The message keys used to associate components with validation messages
0648: * should be set using {@link #setMessageKey(JComponent, Object)} before
0649: * you call this method.
0650: *
0651: * @param container the component tree root
0652: * @param result the validation result used to lookup the severities
0653: *
0654: * @see #setMandatory(JComponent, boolean)
0655: * @see #setMessageKey(JComponent, Object)
0656: * @see #setMandatoryBackground(JTextComponent)
0657: * @see #setErrorBackground(JTextComponent)
0658: * @see #setWarningBackground(JTextComponent)
0659: *
0660: * @since 1.0.2
0661: */
0662: public static void updateComponentTreeSeverityBackground(
0663: Container container, ValidationResult result) {
0664: visitComponentTree(container, result.keyMap(),
0665: new SeverityBackgroundVisitor());
0666: }
0667:
0668: /**
0669: * Traverses a component tree and sets the severity for all text components.
0670: * The iteration starts at the given container. If a validation result is
0671: * associated with a component, the result's severity is set. Otherwise
0672: * the severity is set to <code>null</code>. The severity is set using
0673: * {@link #setSeverity(JComponent, Severity)}.<p>
0674: *
0675: * Before you use this method, associate components with validation
0676: * messages using {@link #setMessageKey(JComponent, Object)}.
0677: *
0678: * @param container the component tree root
0679: * @param result the validation result that provides the associated messages
0680: *
0681: * @see #setSeverity(JComponent, Severity)
0682: */
0683: public static void updateComponentTreeSeverity(Container container,
0684: ValidationResult result) {
0685: visitComponentTree(container, result.keyMap(),
0686: new SeverityVisitor());
0687: }
0688:
0689: // Visiting Text Components in a Component Tree ***************************
0690:
0691: /**
0692: * Traverses the component tree starting at the given container and invokes
0693: * the given visitor's <code>#visit</code> method on each instance of
0694: * {@link JTextComponent}. Useful to perform custom component tree updates
0695: * that are not already provided by the <code>#updateComponentTreeXXX</code>
0696: * methods.<p>
0697: *
0698: * The arguments passed to the #visit method are the visited component and
0699: * its associated validation subresult. This subresult is requested from
0700: * the specified <code>keyMap</code> using the component's message key.<p>
0701: *
0702: * Before you use this method, associate text component with validation
0703: * messages using {@link #setMessageKey(JComponent, Object)}.
0704: *
0705: * @param container the component tree root
0706: * @param keyMap maps messages keys to associated validation results
0707: * @param visitor the visitor that is applied to all text components
0708: *
0709: * @see #setMessageKey(JComponent, Object)
0710: * @see Visitor
0711: */
0712: public static void visitComponentTree(Container container,
0713: Map<Object, ValidationResult> keyMap, Visitor visitor) {
0714: int componentCount = container.getComponentCount();
0715: for (int i = 0; i < componentCount; i++) {
0716: Component child = container.getComponent(i);
0717: if (child instanceof JTextComponent) {
0718: JComponent component = (JComponent) child;
0719: visitor.visit(component, keyMap);
0720: } else if (child instanceof Container) {
0721: visitComponentTree((Container) child, keyMap, visitor);
0722: }
0723: }
0724: }
0725:
0726: // Helper Code ************************************************************
0727:
0728: /**
0729: * Returns the ValidationResult associated with the given component
0730: * using the specified validation result key map,
0731: * or <code>null</code> if the component has no message key set,
0732: * or <code>ValidationResult.EMPTY</code> if the key map contains
0733: * no result for the component.
0734: *
0735: * @param comp the component may be marked with a validation message keys
0736: * @param keyMap maps validation message keys to ValidationResults
0737: * @return the ValidationResult associated with the given component
0738: * as provided by the specified validation key map
0739: * or <code>null</code> if the component has no message key set,
0740: * or <code>ValidationResult.EMPTY</code> if no result is associated
0741: * with the component
0742: *
0743: * @since 1.4
0744: */
0745: public static ValidationResult getAssociatedResult(JComponent comp,
0746: Map<Object, ValidationResult> keyMap) {
0747: Object[] messageKeys = getMessageKeys(comp);
0748: if ((messageKeys == null) || (keyMap == null)) {
0749: return null;
0750: }
0751: if (messageKeys.length == 1) {
0752: ValidationResult result = keyMap.get(messageKeys[0]);
0753: return result == null ? ValidationResult.EMPTY : result; // already unmodifiable
0754: }
0755: ValidationResult result = null;
0756: for (Object element : messageKeys) {
0757: ValidationResult subResult = keyMap.get(element);
0758: if (subResult != null) {
0759: if (result == null) {
0760: result = new ValidationResult();
0761: }
0762: result.addAllFrom(subResult);
0763: }
0764: }
0765: return result == null ? ValidationResult.EMPTY
0766: : ValidationResult.unmodifiableResult(result);
0767: }
0768:
0769: /**
0770: * Returns a default background color that is requested from an instance
0771: * of a prototype component of the same type as the given component.
0772: * If such a component cannot be created, a JTextField is used.
0773: * The prototype's enabled and editable state is then set to the state
0774: * of the given component. Finally the prototype's background is returned.
0775: *
0776: * @param component the component to get the default background for
0777: * @return the background color of a prototype text component that has
0778: * the same state as the given component
0779: *
0780: * @see #restoreBackground(JTextComponent)
0781: */
0782: private static Color getDefaultBackground(JTextComponent component) {
0783: JTextComponent prototype = getPrototypeFor(component.getClass());
0784: prototype.setEnabled(component.isEnabled());
0785: prototype.setEditable(component.isEditable());
0786: return prototype.getBackground();
0787: }
0788:
0789: /**
0790: * Lazily creates and returns a prototype text component instance
0791: * of the given text component class. First ensures that the Look&Feel
0792: * change handler is registered that clears the prototype map if
0793: * the L&f changes.
0794: *
0795: * @param prototypeClass the class of the prototype to be returned
0796: * @return the lazily created prototype component
0797: */
0798: private static JTextComponent getPrototypeFor(
0799: Class<? extends JTextComponent> prototypeClass) {
0800: ensureLookAndFeelChangeHandlerRegistered();
0801: JTextComponent prototype = PROTOTYPE_COMPONENTS
0802: .get(prototypeClass);
0803: if (prototype == null) {
0804: try {
0805: prototype = prototypeClass.newInstance();
0806: } catch (Exception e) {
0807: prototype = new JTextField();
0808: }
0809: PROTOTYPE_COMPONENTS.put(prototypeClass, prototype);
0810: }
0811: return prototype;
0812: }
0813:
0814: /**
0815: * Returns the background color that has been previously stored for
0816: * the given component, or <code>null</code> if none.
0817: *
0818: * @param comp the component to be requested
0819: * @return the background color that has been previously stored for
0820: * the given component, or <code>null</code> if none.
0821: *
0822: * @see #ensureCustomBackgroundStored(JTextComponent)
0823: * @see #restoreBackground(JTextComponent)
0824: */
0825: private static Color getStoredBackground(JTextComponent comp) {
0826: return (Color) comp.getClientProperty(STORED_BACKGROUND_KEY);
0827: }
0828:
0829: /**
0830: * Ensures that a text component's custom background - if any -
0831: * is stored as a client property. Used to store the background once only.
0832: *
0833: * @param comp the component to be requested
0834: *
0835: * @see #getStoredBackground(JTextComponent)
0836: * @see #restoreBackground(JTextComponent)
0837: */
0838: private static void ensureCustomBackgroundStored(JTextComponent comp) {
0839: if (getStoredBackground(comp) != null) {
0840: return;
0841: }
0842: Color background = comp.getBackground();
0843: if ((background == null) || (background instanceof UIResource)
0844: || (background == WARNING_BACKGROUND)
0845: || (background == ERROR_BACKGROUND)) {
0846: return;
0847: }
0848: comp.putClientProperty(STORED_BACKGROUND_KEY, background);
0849: }
0850:
0851: /**
0852: * Looks up and restores the text component's previously stored (original)
0853: * background color.
0854: *
0855: * @param comp the component that shall get its original background color
0856: *
0857: * @see #getStoredBackground(JTextComponent)
0858: * @see #ensureCustomBackgroundStored(JTextComponent)
0859: */
0860: private static void restoreBackground(JTextComponent comp) {
0861: Color storedBackground = getStoredBackground(comp);
0862: comp
0863: .setBackground(storedBackground == null ? getDefaultBackground(comp)
0864: : storedBackground);
0865: }
0866:
0867: // Visitor Definition and Predefined Visitor Implementations **************
0868:
0869: /**
0870: * Describes visitors that visit a component tree.
0871: * Visitor implementations are used to mark components,
0872: * to change component background, to associate components
0873: * with additional information; things that are not already
0874: * provided by the <code>#updateComponentTreeXXX</code> methods
0875: * and this class' predefined Visitor implementations.
0876: */
0877: public static interface Visitor {
0878:
0879: /**
0880: * Visits the given component using the specified key map, that maps
0881: * message keys to associated validation subresults.
0882: * Typically an implementation will operate on the component state.
0883: *
0884: * @param component the component to be visited
0885: * @param keyMap maps messages keys to associated validation results
0886: */
0887: void visit(JComponent component,
0888: Map<Object, ValidationResult> keyMap);
0889: }
0890:
0891: /**
0892: * A validation visitor that sets the background color of JTextComponents
0893: * to mark mandatory components.
0894: */
0895: private static final class MandatoryBackgroundVisitor implements
0896: Visitor {
0897:
0898: /**
0899: * Sets the mandatory background to text components that have been marked
0900: * as mandatory.
0901: *
0902: * @param component the component to be visited
0903: * @param keyMap ignored
0904: */
0905: public void visit(JComponent component,
0906: Map<Object, ValidationResult> keyMap) {
0907: if ((component instanceof JTextComponent)
0908: && isMandatory(component)) {
0909: setMandatoryBackground((JTextComponent) component);
0910: }
0911: }
0912:
0913: }
0914:
0915: /**
0916: * A validation visitor that sets the background color of JTextComponents
0917: * to indicate if mandatory components have a blank text or not.
0918: */
0919: private static final class MandatoryAndBlankBackgroundVisitor
0920: implements Visitor {
0921:
0922: /**
0923: * Sets the mandatory background to text components that have been marked
0924: * as mandatory if the content is blank, otherwise the original
0925: * background is restored.
0926: *
0927: * @param component the component to be visited
0928: * @param keyMap ignored
0929: */
0930: public void visit(JComponent component,
0931: Map<Object, ValidationResult> keyMap) {
0932: JTextComponent textChild = (JTextComponent) component;
0933: if (isMandatoryAndBlank(textChild)) {
0934: setMandatoryBackground(textChild);
0935: } else {
0936: restoreBackground(textChild);
0937: }
0938: }
0939: }
0940:
0941: /**
0942: * A validation visitor that sets a mandatory border for JTextComponents
0943: * that have been marked as mandatory.
0944: */
0945: private static final class MandatoryBorderVisitor implements
0946: Visitor {
0947:
0948: /**
0949: * Sets the mandatory border to text components that have been marked
0950: * as mandatory.
0951: *
0952: * @param component the component to be visited
0953: * @param keyMap ignored
0954: */
0955: public void visit(JComponent component,
0956: Map<Object, ValidationResult> keyMap) {
0957: if ((component instanceof JTextComponent)
0958: && isMandatory(component)) {
0959: setMandatoryBorder((JTextComponent) component);
0960: }
0961: }
0962: }
0963:
0964: /**
0965: * A validation visitor that sets the background color of JTextComponents
0966: * according to the severity of an associated validation result - if any.
0967: */
0968: private static final class SeverityBackgroundVisitor implements
0969: Visitor {
0970:
0971: /**
0972: * Sets the component background according to the associated
0973: * validation result: default, error, warning.
0974: *
0975: * @param component the component to be visited
0976: * @param keyMap maps messages keys to associated validation results
0977: */
0978: public void visit(JComponent component,
0979: Map<Object, ValidationResult> keyMap) {
0980: Object messageKeys = getMessageKeys(component);
0981: if (messageKeys == null) {
0982: return;
0983: }
0984: JTextComponent textChild = (JTextComponent) component;
0985: ensureCustomBackgroundStored(textChild);
0986: ValidationResult result = getAssociatedResult(component,
0987: keyMap);
0988: if ((result == null) || result.isEmpty()) {
0989: restoreBackground(textChild);
0990: } else if (result.hasErrors()) {
0991: setErrorBackground(textChild);
0992: } else if (result.hasWarnings()) {
0993: setWarningBackground(textChild);
0994: }
0995: }
0996: }
0997:
0998: /**
0999: * A validation visitor that sets each component's severity
1000: * according to its associated validation result or to <code>null</code>
1001: * if no message key is set for the component.
1002: */
1003: private static final class SeverityVisitor implements Visitor {
1004:
1005: /**
1006: * Sets the component's severity according to its associated
1007: * validation result, or <code>null</code> if the component
1008: * has no message key set.
1009: *
1010: * @param component the component to be visited
1011: * @param keyMap maps messages keys to associated validation results
1012: */
1013: public void visit(JComponent component,
1014: Map<Object, ValidationResult> keyMap) {
1015: ValidationResult result = getAssociatedResult(component,
1016: keyMap);
1017: Severity severity = result == null ? null : result
1018: .getSeverity();
1019: setSeverity(component, severity);
1020: }
1021: }
1022:
1023: // Handling L&f Changes ***************************************************
1024:
1025: /**
1026: * Describes whether the <code>LookAndFeelChangeHandler</code>
1027: * has been registered with the <code>UIManager</code> or not.
1028: * It is registered lazily when the first prototype component
1029: * is requested in <code>#getPrototypeFor(Class)</code>.
1030: */
1031: private static boolean lafChangeHandlerRegistered = false;
1032:
1033: private static synchronized void ensureLookAndFeelChangeHandlerRegistered() {
1034: if (!lafChangeHandlerRegistered) {
1035: UIManager
1036: .addPropertyChangeListener(new LookAndFeelChangeHandler());
1037: lafChangeHandlerRegistered = true;
1038: }
1039: }
1040:
1041: /**
1042: * Clears the cached prototype components when the L& changes.
1043: */
1044: private static final class LookAndFeelChangeHandler implements
1045: PropertyChangeListener {
1046:
1047: /**
1048: * Clears the cached prototype components, if the UIManager has fired
1049: * any property change event. Since we need to handle look&feel
1050: * changes only, we check the event's property name to be
1051: * "lookAndFeel" or <code>null</code>. The check for null is necessary
1052: * to handle the special event where property name, old and new value
1053: * are all <code>null</code> to indicate that multiple properties
1054: * have changed.
1055: *
1056: * @param evt describes the property change
1057: */
1058: public void propertyChange(PropertyChangeEvent evt) {
1059: String propertyName = evt.getPropertyName();
1060: if ((propertyName == null)
1061: || propertyName.equals("lookAndFeel")) {
1062: PROTOTYPE_COMPONENTS.clear();
1063: }
1064: }
1065: }
1066:
1067: }
|