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

com.jgoodies.binding.adapter.TextComponentConnector Maven / Gradle / Ivy

Go to download

The JGoodies Binding library connects object properties to Swing user interface components. And it helps you represent the state and behavior of a presentation independently of the GUI components used in the interface.

There is a newer version: 2.13.0
Show newest version
/*
 * Copyright (c) 2002-2013 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.binding.adapter;

import static com.jgoodies.common.base.Preconditions.checkNotNull;
import static com.jgoodies.common.internal.Messages.MUST_NOT_BE_NULL;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import javax.swing.text.PlainDocument;

import com.jgoodies.binding.PresentationModel;
import com.jgoodies.binding.beans.BeanAdapter;
import com.jgoodies.binding.value.ValueModel;
import com.jgoodies.common.base.Objects;

/**
 * Connects a String typed ValueModel and a JTextField or JTextArea.
 * At construction time the text component content is updated
 * with the subject's contents.

* * This connector has been designed for text components that display a plain * String. In case of a JEditorPane, the binding may require more information * then the plain String, for example styles. Since this is outside the scope * of this connector, the public constructors prevent a construction for * general JTextComponents. If you want to establish a one-way binding for * a display JEditorPane, use a custom listener instead.

* * This class provides limited support for handling * subject value modifications while updating the subject. * If a Document change initiates a subject value update, the subject * will be observed and a property change fired by the subject will be * handled - if any. In most cases, the subject will notify about a * change to the text that was just set by this connector. * However, in some cases the subject may decide to modify this text, * for example to ensure upper case characters. * Since at this moment, this adapter's Document is still write-locked, * the Document update is performed later using * {@code SwingUtilities#invokeLater}.

* * Note: * Such an update will typically change the Caret position in JTextField's * and other JTextComponent's that are synchronized using this class. * Hence, the subject value modifications can be used with * commit-on-focus-lost text components, but typically not with a * commit-on-key-typed component. For the latter case, you may consider * using a custom {@code DocumentFilter}.

* * Constraints: * The ValueModel must be of type {@code String}.

* * Examples:

 * ValueModel lastNameModel = new PropertyAdapter(customer, "lastName", true);
 * JTextField lastNameField = new JTextField();
 * TextComponentConnector.connect(lastNameModel, lastNameField);
 *
 * ValueModel codeModel = new PropertyAdapter(shipment, "code", true);
 * JTextField codeField = new JTextField();
 * TextComponentConnector connector =
 *     new TextComponentConnector(codeModel, codeField);
 * connector.updateTextComponent();
 * 
* * @author Karsten Lentzsch * @version $Revision: 1.17 $ * * @see ValueModel * @see Document * @see PlainDocument * * @since 1.2 */ public final class TextComponentConnector { /** * Holds the underlying ValueModel that is used to read values, * to update the document and to write values if the document changes. */ private final ValueModel subject; /** * Refers to the text component that shall be synchronized * with the subject. */ private final JTextComponent textComponent; /** * Holds the text component's current document. * Used for the rare case where the text component fires * a PropertyChangeEvent for the "document" property * with oldValue or newValue == {@code null}. */ private Document document; private final SubjectValueChangeHandler subjectValueChangeHandler; private final DocumentListener textChangeHandler; private final PropertyChangeListener documentChangeHandler; // Instance Creation ****************************************************** /** * Constructs a TextComponentConnector that connects the specified * String-typed subject ValueModel with the given text area.

* * In case you don't need the TextComponentConnector instance, you better * use one of the static {@code #connect} methods. * This constructor may confuse developers, if you just use * the side effects performed in the constructor; this is because it is * quite unconventional to instantiate an object that you never use. * * @param subject the underlying String typed ValueModel * @param textArea the JTextArea to be synchronized with the ValueModel * * @throws NullPointerException if the subject or text area is {@code null} */ public TextComponentConnector(ValueModel subject, JTextArea textArea) { this(subject, (JTextComponent) textArea); } /** * Constructs a TextComponentConnector that connects the specified * String-typed subject ValueModel with the given text field.

* * In case you don't need the TextComponentConnector instance, you better * use one of the static {@code #connect} methods. * This constructor may confuse developers, if you just use * the side effects performed in the constructor; this is because it is * quite unconventional to instantiate an object that you never use. * * @param subject the underlying String typed ValueModel * @param textField the JTextField to be synchronized with the ValueModel * * @throws NullPointerException if the subject or text field is {@code null} */ public TextComponentConnector(ValueModel subject, JTextField textField) { this(subject, (JTextComponent) textField); } /** * Constructs a TextComponentConnector that connects the specified * String-typed subject ValueModel with the given JTextComponent.

* * In case you don't need the TextComponentConnector instance, you better * use one of the static {@code #connect} methods. * This constructor may confuse developers, if you just use * the side effects performed in the constructor; this is because it is * quite unconventional to instantiate an object that you never use. * * @param subject the underlying String typed ValueModel * @param textComponent the JTextComponent to be synchronized with the ValueModel * * @throws NullPointerException if the subject or text component is {@code null} */ private TextComponentConnector(ValueModel subject, JTextComponent textComponent) { this.subject = checkNotNull(subject, MUST_NOT_BE_NULL, "subject"); this.textComponent = checkNotNull(textComponent, MUST_NOT_BE_NULL, "text component"); this.subjectValueChangeHandler = new SubjectValueChangeHandler(); this.textChangeHandler = new TextChangeHandler(); document = textComponent.getDocument(); reregisterTextChangeHandler(null, document); subject.addValueChangeListener(subjectValueChangeHandler); documentChangeHandler = new DocumentChangeHandler(); textComponent.addPropertyChangeListener("document", documentChangeHandler); } /** * Establishes a synchronization between the specified String-typed * subject ValueModel and the given text area. Does not synchronize now. * * @param subject the underlying String typed ValueModel * @param textArea the JTextArea to be synchronized with the ValueModel * * @throws NullPointerException if the subject or text area is {@code null} */ @SuppressWarnings("unused") public static void connect(ValueModel subject, JTextArea textArea) { new TextComponentConnector(subject, textArea); } /** * Establishes a synchronization between the specified String-typed * subject ValueModel and the given text field. Does not synchronize now. * * @param subject the underlying String typed ValueModel * @param textField the JTextField to be synchronized with the ValueModel * * @throws NullPointerException if the subject or text area is {@code null} */ @SuppressWarnings("unused") public static void connect(ValueModel subject, JTextField textField) { new TextComponentConnector(subject, textField); } // Synchronization ******************************************************** /** * Reads the current text from the document * and sets it as new value of the subject. */ public void updateSubject() { setSubjectText(getDocumentText()); } /** * Reads the current value from the model and updates the text component. */ public void updateTextComponent() { setDocumentTextSilently(getSubjectText()); } /** * Returns the text contained in the document. * * @return the text contained in the document */ private String getDocumentText() { return textComponent.getText(); } /** * Sets the text component's contents without notifying the subject * about the change. Invoked by the subject change listener. * Sets the text, then sets the caret position to 0. * * @param newText the text to be set in the document */ private void setDocumentTextSilently(String newText) { textComponent.getDocument().removeDocumentListener(textChangeHandler); textComponent.setText(newText); textComponent.setCaretPosition(0); textComponent.getDocument().addDocumentListener(textChangeHandler); } /** * Returns the subject's text value. * * @return the subject's text value * @throws ClassCastException if the subject value is not a String */ private String getSubjectText() { String str = (String) subject.getValue(); return str == null ? "" : str; } /** * Sets the given text as new subject value. Since the subject may modify * this text, we cannot update silently, i.e. we cannot remove and add * the subjectValueChangeHandler before/after the update. Since this * change is invoked during a Document write operation, the document * is write-locked and so, we cannot modify the document before all * document listeners have been notified about the change.

* * Therefore we listen to subject changes and defer any document changes * using {@code SwingUtilities.invokeLater}. This mode is activated * by setting the subject change handler's {@code updateLater} to true. * * @param newText the text to be set in the subject */ private void setSubjectText(String newText) { subjectValueChangeHandler.setUpdateLater(true); try { subject.setValue(newText); } finally { subjectValueChangeHandler.setUpdateLater(false); } } private void reregisterTextChangeHandler(Document oldDocument, Document newDocument) { if (oldDocument != null) { oldDocument.removeDocumentListener(textChangeHandler); } if (newDocument != null) { newDocument.addDocumentListener(textChangeHandler); } } // Misc ******************************************************************* /** * Removes the internal listeners from the subject, text component, * and text component's document. * This connector must not be used after calling {@code #release}.

* * To avoid memory leaks it is recommended to invoke this method, * if the ValueModel lives much longer than the text component. * Instead of releasing a text connector, you typically make the ValueModel * obsolete by releasing the PresentationModel or BeanAdapter that has * created the ValueModel.

* * As an alternative you may use ValueModels that in turn use * event listener lists implemented using {@code WeakReference}. * * @see PresentationModel#release() * @see BeanAdapter#release() * @see java.lang.ref.WeakReference */ public void release() { reregisterTextChangeHandler(document, null); subject.removeValueChangeListener(subjectValueChangeHandler); textComponent.removePropertyChangeListener("document", documentChangeHandler); } // DocumentListener ******************************************************* /** * Updates the subject if the text has changed. */ private final class TextChangeHandler implements DocumentListener { /** * There was an insert into the document; update the subject. * * @param e the document event */ @Override public void insertUpdate(DocumentEvent e) { updateSubject(); } /** * A portion of the document has been removed; update the subject. * * @param e the document event */ @Override public void removeUpdate(DocumentEvent e) { updateSubject(); } /** * An attribute or set of attributes has changed; do nothing. * * @param e the document event */ @Override public void changedUpdate(DocumentEvent e) { // Do nothing on attribute changes. } } /** * Handles changes in the subject value and updates this document * - if necessary.

* * Document changes update the subject text and result in a subject * property change. Most of these changes will just reflect the * former subject change. However, in some cases the subject may * modify the text set, for example to ensure upper case characters. * This method reduces the number of document updates by checking * the old and new text. If the old and new text are equal or * both null, this method does nothing.

* * Since subject changes as a result of a document change may not * modify the write-locked document immediately, we defer the update * if necessary using {@code SwingUtilities.invokeLater}.

* * See the TextComponentConnector's JavaDoc class comment * for the limitations of the deferred document change. */ private final class SubjectValueChangeHandler implements PropertyChangeListener { private boolean updateLater; void setUpdateLater(boolean updateLater) { this.updateLater = updateLater; } /** * The subject value has changed; updates the document immediately * or later - depending on the {@code updateLater} state. * * @param evt the event to handle */ @Override public void propertyChange(PropertyChangeEvent evt) { final String oldText = getDocumentText(); final Object newValue = evt.getNewValue(); final String newText = newValue == null ? getSubjectText() : (String) newValue; if (Objects.equals(oldText, newText)) { return; } if (updateLater) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { setDocumentTextSilently(newText); } }); } else { setDocumentTextSilently(newText); } } } /** * Re-registers the text change handler after document changes. */ private final class DocumentChangeHandler implements PropertyChangeListener { @Override public void propertyChange(PropertyChangeEvent evt) { Document oldDocument = document; Document newDocument = textComponent.getDocument(); reregisterTextChangeHandler(oldDocument, newDocument); document = newDocument; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy