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

com.fs.commons.desktop.validation.ui.ValidationGroup Maven / Gradle / Ivy

There is a newer version: 0.0.9-3
Show newest version
/*
 * Copyright 2002-2016 Jalal Kiswani.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.fs.commons.desktop.validation.ui;

import java.awt.EventQueue;

import javax.swing.AbstractButton;
import javax.swing.ButtonModel;
import javax.swing.ComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;

import com.fs.commons.desktop.validation.Problem;
import com.fs.commons.desktop.validation.Validator;

/**
 * A group which holds validators that run against components. The group is
 * passed an instance of ValidationUI in its constructor. This is
 * typically something that is able to show the user that there is a problem in
 * some way, possibly enabling and disabling a dialog's OK button or similar.
 * 

* There are two main reasons validation groups exist: *

    *
  • Often a change in one component triggers a change in the validity of * another component
  • *
  • There are several levels of problem, INFO, WARNING and FATAL. The user * should see FATAL messages even if the component their currently interacting * with is offering a warning
  • *
*

* A validation group is typically associated with a single panel. For * components this library supports out-of-the-box such as * JTextFields or JComboBoxes, simply call one of the * add() methods with your component and validators. For validating * your own components or ones this class doesn't have methods for, you * implement and add * ValidationListeners. *

* The contract of how a validation group works is as follows: *

    *
  • When an appropriate change in a component occurs, its content shall be * validated
  • *
  • If either a non-fatal problem or no problem is found when validating the * component, all other validators in the group may be run as follows
  • *
      *
    • If the validator for the component the user is currently interacting with * did not produce a problem, the worst (by the definition of * Problem.isWorseThan()) found is passed to the UI as the current problem. This * may be the first problem with ProblemKind.FATAL which is encountered, based * on the order validators were added to the group.
    • *
    • If the validator for the component the user is currently interacting with * did produce a problem, then if its kind() is * ProblemKind.FATAL, the UI's setProblem() method is called with * that problem. If the problem from the component the user is currently * interacting with is ProblemKind.INFO or * ProblemKind.WARNING, then all other validators in the group are * called. If any problem of a greater severity is found, that problem will be * passed to the group's UI's setProblem() method; otherwise the problem from * the current component is used.
    • *
    • If no problem was produced by the component the user is interacting with, * and not by any other, then the UI's clearProblem() method is * invoked.
    • *
    *
*

Validator Ordering

*

* For the methods which take multiple validators or multiple enum constants * from the Validators * class, order is important. Generally it is best to pass validators in order * of how specific they are - for example, if you are validating a URL you would * likely pass * *

 * group.add(f, Validators.REQUIRE_NON_EMPTY_STRING, Validators.NO_WHITESPACE, Validators.URL_MUST_BE_VALID);
 * 
* * so that the most general check is done first (if the string is empty or not); * the most specific test, which will actually try to construct a URL object and * report any MalformedURLException should be last. *

* Validators for custom components can be added to the group by implementing * ValidationListener over a listener you attach to the component in question, * by calling add(ValidationListener). *

* Note: All methods on this class must be called from the AWT Event * Dispatch thread, or assertion errors will be thrown. Manipulating components * on other threads is not safe. *

* Swing documents are type-safe, and can be modified from other threads. In the * case that a text component validator receives an event on another thread, * validation will be scheduled for later, on the event thread. * * * @author Tim Boudreau */ public final class ValidationGroup { /** * Create a new ValidationGroup. * * @param ui * The user interface * @return A new ValidationGroup */ public static ValidationGroup create(final ValidationUI... ui) { assert EventQueue.isDispatchThread() : "Must be called on event thread"; return new ValidationGroup(ValidationGroupImpl.create(ui)); } private final ValidationGroupImpl delegate; ValidationGroup(final ValidationGroupImpl delegate) { this.delegate = delegate; } /** * Add a set of button models, e.g. radio buttons in a radio button group * * @param buttons * The button models * @param validator * A validator */ public final void add(final AbstractButton[] buttons, final Validator validator) { assert EventQueue.isDispatchThread() : "Must be called on event thread"; this.delegate.add(buttons, validator); } /** * Add a set of button models, e.g. radio buttons in a radio button group * * @param buttons * The button models * @param validator * A validator */ public void add(final ButtonModel[] buttons, final Validator validator) { assert EventQueue.isDispatchThread() : "Must be called on event thread"; this.delegate.add(buttons, validator); } /** * Add a combo box using the passed validation strategy and validator * * @param box * a combo box * @param strategy * the validation strategy that determines when validation should * run * @param validator * A validator for combo box models (e.g. from * Converters.convert() to use String validators in combo boxes) */ public void add(final JComboBox box, final ValidationStrategy strategy, final Validator validator) { assert EventQueue.isDispatchThread() : "Must be called on event thread"; this.delegate.add(box, strategy, validator); } /** * Add a combo box to be validated with the passed validation strategy using * the passed validators * * @param box * A combo box component * @param builtIns * One or more of the enums from the Validators * class which provide standard validation of many things */ public final void add(final JComboBox box, final ValidationStrategy strategy, final Validator... builtIns) { assert EventQueue.isDispatchThread() : "Must be called on event thread"; this.delegate.add(box, strategy, builtIns); } /** * Add a combo box to be validated with ValidationStrategy.DEFAULT using the * passed validator * * @param box * A text component such as a JTextField * @param validator * A validator */ public final void add(final JComboBox box, final Validator validator) { assert EventQueue.isDispatchThread() : "Must be called on event thread"; this.delegate.add(box, validator); } /** * Add a combo box to be validated with ValidationStrategy.DEFAULT using the * passed validators * * @param box * A combo box component * @param builtIns * One or more of the enums from the Validators * class which provide standard validation of many things */ public final void add(final JComboBox box, final Validator... builtIns) { assert EventQueue.isDispatchThread() : "Must be called on event thread"; this.delegate.add(box, builtIns); } /** * Add a text component to be validated with ValidationStrategy.DEFAULT * using the passed validator * * @param comp * A text component such as a JTextField * @param validator * A validator */ public void add(final JTextComponent comp, final ValidationStrategy strategy, final Validator validator) { assert EventQueue.isDispatchThread() : "Must be called on event thread"; this.delegate.add(comp, strategy, validator); } /** * Add a text control to be validated with the passed validation strategy * using the passed validators * * @param comp * A text control such as a JTextField * @param builtIns * One or more of the enums from the Validators * class which provide standard validation of many things */ public final void add(final JTextComponent comp, final ValidationStrategy strategy, final Validator... builtIns) { assert EventQueue.isDispatchThread() : "Must be called on event thread"; this.delegate.add(comp, strategy, builtIns); } /** * Add a text component to be validated with ValidationStrategy.DEFAULT * using the passed validator * * @param comp * A text component such as a JTextField * @param validator * A validator */ public final void add(final JTextComponent comp, final Validator validator) { assert EventQueue.isDispatchThread() : "Must be called on event thread"; this.delegate.add(comp, validator); } /** * Add a text component to be validated with ValidationStrategy.DEFAULT * using the passed validators * * @param comp * A text component such as a JTextField * @param builtIns * One or more of the enums from the Validators * class which provide standard validation of many things */ public final void add(final JTextComponent comp, final Validator... builtIns) { this.delegate.add(comp, builtIns); } /** * Add a validation listener. To validate custom components, implement * ValidationListener and attach it as a listener to the component, calling * ValidationListener.validate() on events that should trigger validation, * and add it to the group. */ public void add(final ValidationListener listener) { assert EventQueue.isDispatchThread() : "Must be called on event thread"; this.delegate.add(listener); } /** * Add a UI which should be called on validation of this group or any * components within it. *

* This is useful in the case that you have multiple components which are * provided separately and each want to respond to validation problems (for * example, one UI controlling a dialog's OK button, another controlling * display of error text. * * @param ui * An implementation of ValidationUI */ public final void addUI(final ValidationUI ui) { assert EventQueue.isDispatchThread() : "Must be called on event thread"; this.delegate.addUI(ui); } /** * Add a validation group whose components should be validated as a part of * this group. This is useful in the case that you have a panel with its own * validation group, and want to embed it another panel that also wants to * do its own validation. *

* Whenever there is a change in one of the child ValidationGroupc's * components, it will drive the UI(s) belonging to this group, as well as * (optionally) its own. *

* This technique is mostly useful in the case of things like abstract * customizer dialogs, where you have a ValidationUI to control the OK * button, but you don't actually anything about the component provided as a * customizer. This method makes it possible for a parent component (such as * our customizer) to be affected by validation in a child component. *

* If you are not composing together multiple panels which each have their * own validation code, ignore this method. * * @param group * The validation group * @param useUI * If true, the other validation group's UI should continue to be * updated */ public final void addValidationGroup(final ValidationGroup group, final boolean useUI) { assert EventQueue.isDispatchThread() : "Must be called on event thread"; if (group == this || group.delegate == this.delegate) { throw new IllegalArgumentException("Adding group to itself"); } this.delegate.addValidationGroup(group.delegate, useUI); } /** * Create a label which will show the current problem if any, which can be * added to a panel that uses validation * * @return A JLabel */ public final JLabel createProblemLabel() { assert EventQueue.isDispatchThread() : "Must be called on event thread"; final JLabel result = new FixedHeightLabel(); addUI(new ValidationUI() { @Override public void clearProblem() { result.setText(" "); result.setIcon(null); } @Override public void setProblem(final Problem problem) { result.setText(problem.getMessage()); result.setIcon(problem.severity().icon()); result.setForeground(problem.severity().color()); } }); return result; } /** * Disable validation and invoke a runnable. This method is useful in UIs * where a change in one component can trigger changes in another component, * and you do not want validation to be triggered because a component was * programmatically updated. *

* For example, say you have a dialog that lets you create a new Servlet * source file. As the user types the servlet name, web.xml entries are * updated to match, and these are also in fields in the same dialog. Since * the updated web.xml entries are being programmatically (and presumably * correctly) generated, those changes should not trigger a useless * validation run. Wrap such generation code in a Runnable and pass it to * this method when making programmatic changes to the contents of the UI. *

* The runnable is run synchronously, but no changes made to components * while the runnable is running will trigger validation. *

* When the last runnable exits, validateAll(null) will be called to run * validation against the entire newly updated UI. *

* This method is reentrant - a call to updateComponents can trigger another * call to updateComponents without triggering multiple calls to * validateAll() on each Runnable's exit. * * @param run * A runnable which makes changes to the contents of one or more * components in the UI which should not trigger validation */ public final void modifyComponents(final Runnable run) { assert EventQueue.isDispatchThread() : "Must be called on event thread"; this.delegate.modifyComponents(run); } /** * Remove a delegate UI which is being controlled by this validation group. * * @param ui * The UI */ public final void removeUI(final ValidationUI ui) { assert EventQueue.isDispatchThread() : "Must be called on event thread"; this.delegate.removeUI(ui); ui.clearProblem(); } /** * Remove a child validation group from this one. In the case of composing * together panels, each of which has its own validation group, this method * is useful if the child panel changes on the fly. *

* Once removed, the child validation group reverts to interacting only with * its own UI, and validation running in it will not affect the UI of this * ValidationGroup. * * @param group */ public final void removeValidationGroup(final ValidationGroup group) { assert EventQueue.isDispatchThread() : "Must be called on event thread"; this.delegate.removeValidationGroup(group.delegate); } /** * Set the component decorator used modify components appearance to show * that there is a problem with a component's content. * * @param decorator * A decorator. May not be null. */ public void setComponentDecorator(final ComponentDecorator decorator) { this.delegate.setComponentDecorator(decorator); } /** * Validate all components in this group, updating the UI with a new problem * as necessary. Generally the validation framework handles validation; * however you are using custom validators that rely on outside state, this * method can be used to manually force validation. * * @return A problem, if any */ public Problem validateAll() { assert EventQueue.isDispatchThread() : "Must be called on event thread"; return this.delegate.validateAll(null); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy