001: /*
002: * Copyright (c) 2002-2007 JGoodies Karsten Lentzsch. All Rights Reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * o Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * o Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * o Neither the name of JGoodies Karsten Lentzsch nor the names of
015: * its contributors may be used to endorse or promote products derived
016: * from this software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020: * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
021: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
022: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
025: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
026: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
027: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030:
031: package com.jgoodies.binding.adapter;
032:
033: import java.beans.PropertyChangeEvent;
034: import java.beans.PropertyChangeListener;
035:
036: import javax.swing.SpinnerModel;
037: import javax.swing.event.ChangeEvent;
038: import javax.swing.event.ChangeListener;
039:
040: import com.jgoodies.binding.PresentationModel;
041: import com.jgoodies.binding.beans.BeanAdapter;
042: import com.jgoodies.binding.value.ValueModel;
043:
044: /**
045: * Synchronizes a SpinnerModel with a ValueModel.
046: *
047: * @author Karsten Lentzsch
048: * @version $Revision: 1.9 $
049: *
050: * @see SpinnerModel
051: * @see ValueModel
052: *
053: * @since 1.1
054: */
055: public final class SpinnerToValueModelConnector {
056:
057: /**
058: * Holds a SpinnerModel instance that is used to read and set the
059: * spinner value.
060: */
061: private final SpinnerModel spinnerModel;
062:
063: /**
064: * Holds the underlying ValueModel that is used to read values, to update
065: * the spinner model and to write values if the spinner changes.
066: */
067: private final ValueModel valueModel;
068:
069: /**
070: * An optional value that will be set to update the spinner model
071: * if the value model's value is <code>null</code>.
072: */
073: private final Object defaultValue;
074:
075: /**
076: * Implements PropertyChangeListener and ChangeListener and
077: * is used to update both the SpinnerModel and the ValueModel.
078: */
079: private final UpdateHandler updateHandler;
080:
081: // Instance Creation ******************************************************
082:
083: /**
084: * Constructs a SpinnerToValueModelConnector that establishes a
085: * Synchronization between the SpinnerModel and ValueModel.
086: * This constructor does not synchronize the SpinnerModel and ValueModel now.
087: * You may update the spinner model or value model using
088: * <code>#updateSpinnerModel</code> or <code>#updateValueModel</code>.<p>
089: *
090: * In case you don't need the connector instance, you better use
091: * the static method {@link #connect(SpinnerModel, ValueModel, Object)}.
092: * This constructor may confuse developers if you just use
093: * the side effects performed in the constructor; this is because it is
094: * quite unconventional to instantiate an object that you never use.
095: *
096: * @param spinnerModel the SpinnerModel to be synchronized
097: * @param valueModel the ValueModel to be synchronized
098: * @param defaultValue the value that will be used to update
099: * the spinnerModel, if the valueModel's value is <code>null</code>
100: *
101: * @throws NullPointerException
102: * if the spinnerModel, valueModel or defaultValue is <code>null</code>
103: */
104: public SpinnerToValueModelConnector(SpinnerModel spinnerModel,
105: ValueModel valueModel, Object defaultValue) {
106:
107: if (spinnerModel == null)
108: throw new NullPointerException(
109: "The spinner model must not be null.");
110: if (valueModel == null)
111: throw new NullPointerException(
112: "The value model must not be null.");
113: if (defaultValue == null)
114: throw new NullPointerException(
115: "The default value must not be null.");
116: this .spinnerModel = spinnerModel;
117: this .valueModel = valueModel;
118: this .defaultValue = defaultValue;
119: this .updateHandler = new UpdateHandler();
120: spinnerModel.addChangeListener(updateHandler);
121: valueModel.addValueChangeListener(updateHandler);
122: }
123:
124: /**
125: * Establishes a synchronization between the SpinnerModel and ValueModel.
126: * This method does not synchronize the SpinnerModel and ValueModel now.
127: * You may update the spinner model or value model using
128: * <code>#updateSpinnerModel</code> or <code>#updateValueModel</code>.
129: *
130: * @param spinnerModel the SpinnerModel to be synchronized
131: * @param valueModel the ValueModel to be synchronized
132: * @param defaultValue the value used if the valueModel's value is <code>null</code>
133: *
134: * @throws NullPointerException
135: * if the spinnerModel or valueModel is <code>null</code>
136: */
137: public static void connect(SpinnerModel spinnerModel,
138: ValueModel valueModel, Object defaultValue) {
139: new SpinnerToValueModelConnector(spinnerModel, valueModel,
140: defaultValue);
141: }
142:
143: // Synchronization ********************************************************
144:
145: /**
146: * Sets the subject value as spinner value.
147: */
148: public void updateSpinnerModel() {
149: Object value = valueModel.getValue();
150: Object valueWithDefault = value != null ? value : defaultValue;
151: setSpinnerModelValueSilently(valueWithDefault);
152: }
153:
154: /**
155: * Sets the spinner value as value model's value.
156: */
157: public void updateValueModel() {
158: setValueModelValueSilently(spinnerModel.getValue());
159: }
160:
161: /**
162: * Sets the spinner model's value without notifying the subject of changes.
163: * Invoked by the subject change listener.
164: *
165: * @param newValue the value to be set in the spinner model
166: */
167: private void setSpinnerModelValueSilently(Object newValue) {
168: spinnerModel.removeChangeListener(updateHandler);
169: spinnerModel.setValue(newValue);
170: spinnerModel.addChangeListener(updateHandler);
171: }
172:
173: /**
174: * Reads the current value from the spinner model and sets it as new
175: * value of the subject. Removes the value change listener before the
176: * subject value is set and adds it after the new value has been set.
177: *
178: * @param newValue the value to be set in the value model
179: */
180: private void setValueModelValueSilently(Object newValue) {
181: valueModel.removeValueChangeListener(updateHandler);
182: valueModel.setValue(newValue);
183: valueModel.addValueChangeListener(updateHandler);
184: }
185:
186: // Misc *******************************************************************
187:
188: /**
189: * Removes the internal listener from the SpinnerModel and ValueModel.
190: * This connector must not be used after calling <code>#release</code>.<p>
191: *
192: * To avoid memory leaks it is recommended to invoke this method,
193: * if the ValueModel lives much longer than the text component.
194: * Instead of releasing this connector, you typically make the ValueModel
195: * obsolete by releasing the PresentationModel or BeanAdapter that has
196: * created the ValueModel.<p>
197: *
198: * As an alternative you may use ValueModels that in turn use
199: * event listener lists implemented using <code>WeakReference</code>.
200: *
201: * @see PresentationModel#release()
202: * @see BeanAdapter#release()
203: * @see java.lang.ref.WeakReference
204: */
205: public void release() {
206: spinnerModel.removeChangeListener(updateHandler);
207: valueModel.removeValueChangeListener(updateHandler);
208: }
209:
210: // Event Handling Class ***************************************************
211:
212: /**
213: * Registered with both the SpinnerModel and the ValueModel.
214: * Used to update the spinner if the value changes, and vice versa.
215: */
216: private final class UpdateHandler implements
217: PropertyChangeListener, ChangeListener {
218:
219: /**
220: * The valueModel's value has changed; update the spinner model.
221: *
222: * @param evt the event to handle
223: */
224: public void propertyChange(PropertyChangeEvent evt) {
225: updateSpinnerModel();
226: }
227:
228: /**
229: * The spinner value has changed; update the valueModel and
230: * notify all listeners about the state change.
231: *
232: * @param evt the change event
233: */
234: public void stateChanged(ChangeEvent evt) {
235: updateValueModel();
236: }
237:
238: }
239:
240: }
|