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.value;
032:
033: import java.awt.event.ActionEvent;
034: import java.awt.event.ActionListener;
035: import java.beans.PropertyChangeEvent;
036: import java.beans.PropertyChangeListener;
037:
038: import javax.swing.Timer;
039:
040: /**
041: * A ValueModel that deferres updates and read-access for a specified delay.
042: * Useful to coalesce frequent changes. For example if a heavy computation
043: * shall be performed only for a "stable" selection after a series of
044: * quick selection changes.<p>
045: *
046: * Wraps a given subject ValueModel and observes subject value changes
047: * and forwards them to listeners of this model after a delay. If the subject
048: * value changes, a Swing Timer is used to delay the change notification.
049: * A previously started timer - if any - will be stopped before.
050: * Reading this model's value returns:
051: * a) the subject value if there's no pending update, or
052: * b) this model's old value that will be updated after the delay.
053: * If a value is set to this model, it immediately updates the subject value.<p>
054: *
055: * TODO: Describe how and when listeners get notified about the delayed change.<p>
056: *
057: * TODO: Write about the recommended delay time - above the double-click time
058: * and somewhere below a second, e.g. 100ms to 200ms.<p>
059: *
060: * TODO: Write about a slightly different commit handling. The current
061: * implementation defers the commit until the value is stable for the
062: * specified delay; it's a DelayUntilStableForXXXmsValueModel. Another
063: * feature is to delay for a specified time but ensure that some commits
064: * and change notifications happen. The latter is a CoalescingWriteValueModel.<p>
065: *
066: * TODO: Summarize the differences between the DelayedReadValueModel, the
067: * DelayedWriteValueModel, and the DelayedPropertyChangeHandler.
068: *
069: * @author Karsten Lentzsch
070: * @version $Revision: 1.8 $
071: *
072: * @see javax.swing.Timer
073: *
074: * @since 1.1
075: */
076: public final class DelayedReadValueModel extends AbstractValueModel {
077:
078: /**
079: * Refers to the underlying subject ValueModel.
080: */
081: private final ValueModel subject;
082:
083: /**
084: * The Timer used to perform the delayed commit.
085: */
086: private final Timer timer;
087:
088: /**
089: * If <code>true</code> all pending updates will be coalesced.
090: * In other words, an update will be fired if no updates
091: * have been received for this model's delay.
092: */
093: private boolean coalesce;
094:
095: /**
096: * Holds this model's old value that is returned in <code>getValue</code>
097: * during a pending change. most recent old value. It is set in
098: * <code>#fireDelayedValueChange</code>.
099: */
100: private Object oldValue;
101:
102: /**
103: * Holds the most recent pending PropertyChangeEvent as provided
104: * from the subject change notification that this model deferres.
105: * <code>#fireDelayedValueChange</code>.
106: */
107: private PropertyChangeEvent pendingEvt;
108:
109: // Instance Creation ******************************************************
110:
111: /**
112: * Constructs a DelayedReadValueModel for the given subject ValueModel
113: * and the specified Timer delay in milliseconds with coalescing disabled.
114: *
115: * @param subject the underlying (or wrapped) ValueModel
116: * @param delay the milliseconds to wait before a change
117: * shall be committed
118: *
119: * @throws IllegalArgumentException if the delay is negative
120: */
121: public DelayedReadValueModel(ValueModel subject, int delay) {
122: this (subject, delay, false);
123: }
124:
125: /**
126: * Constructs a DelayedReadValueModel for the given subject ValueModel
127: * and the specified Timer delay in milliseconds using the given
128: * coalesce mode.
129: *
130: * @param subject the underlying (or wrapped) ValueModel
131: * @param delay the milliseconds to wait before a change
132: * shall be committed
133: * @param coalesce <code>true</code> to coalesce all pending changes,
134: * <code>false</code> to fire changes with the delay when an update
135: * has been received
136: *
137: * @throws IllegalArgumentException if the delay is negative
138: *
139: * @see #setCoalesce(boolean)
140: */
141: public DelayedReadValueModel(ValueModel subject, int delay,
142: boolean coalesce) {
143: this .subject = subject;
144: this .coalesce = coalesce;
145: this .timer = new Timer(delay, new ValueUpdateListener());
146: timer.setRepeats(false);
147: subject.addValueChangeListener(new SubjectValueChangeHandler());
148: oldValue = subject.getValue();
149: }
150:
151: // ValueModel Implementation ******************************************
152:
153: /**
154: * Returns the subject's value or in case of a pending commit,
155: * the pending new value.
156: *
157: * @return the subject's current or future value.
158: */
159: public Object getValue() {
160: return hasPendingChange() ? oldValue : subject.getValue();
161: }
162:
163: /**
164: * Sets the given new value immediately as the subject's new value.
165: * Note that change notifications from the subject are deferred
166: * by this model. Therefore listeners registered with this model
167: * will be notified after this model's delay.
168: *
169: * @param newValue the value to set
170: */
171: public void setValue(Object newValue) {
172: subject.setValue(newValue);
173: }
174:
175: // Accessors **************************************************************
176:
177: /**
178: * Returns the delay, in milliseconds, that is used to defer value change
179: * notifications.
180: *
181: * @return the delay, in milliseconds, that is used to defer
182: * value change notifications
183: *
184: * @see #setDelay
185: */
186: public int getDelay() {
187: return timer.getDelay();
188: }
189:
190: /**
191: * Sets the delay, in milliseconds, that is used to defer value change
192: * notifications.
193: *
194: * @param delay the delay, in milliseconds, that is used to defer
195: * value change notifications
196: * @see #getDelay
197: */
198: public void setDelay(int delay) {
199: timer.setInitialDelay(delay);
200: timer.setDelay(delay);
201: }
202:
203: /**
204: * Returns if this model coalesces all pending changes or not.
205: *
206: * @return <code>true</code> if all pending changes will be coalesced,
207: * <code>false</code> if pending changes are fired with a delay
208: * when an update has been received.
209: *
210: * @see #setCoalesce(boolean)
211: */
212: public boolean isCoalesce() {
213: return coalesce;
214: }
215:
216: /**
217: * Sets if this model shall coalesce all pending changes or not.
218: * In this case, a change event will be fired first,
219: * if no updates have been received for this model's delay.
220: * If coalesce is <code>false</code>, a change event will be fired
221: * with this model's delay when an update has been received.<p>
222: *
223: * The default value is <code>false</code>.<p>
224: *
225: * Note that this value is not the #coalesce value
226: * of this model's internal Swing timer.
227: *
228: * @param b <code>true</code> to coalesce,
229: * <code>false</code> to fire separate changes
230: */
231: public void setCoalesce(boolean b) {
232: coalesce = b;
233: }
234:
235: // Misc *******************************************************************
236:
237: /**
238: * Stops a running timer - if any. The last pending change
239: * won't be performed by the <code>ValueUpdateListener</code>.
240: *
241: * @since 1.2
242: */
243: public void stop() {
244: timer.stop();
245: }
246:
247: /**
248: * Sets the given new value after this model's delay.
249: * Does nothing if the new value and the latest pending value are the same.
250: *
251: * @param evt the PropertyChangeEvent to be fired after this model's delay
252: */
253: private void fireDelayedValueChange(PropertyChangeEvent evt) {
254: pendingEvt = evt;
255: if (coalesce) {
256: timer.restart();
257: } else {
258: timer.start();
259: }
260: }
261:
262: private boolean hasPendingChange() {
263: return timer.isRunning();
264: }
265:
266: // Event Handling *****************************************************
267:
268: /**
269: * Describes the delayed action to be performed by the timer.
270: */
271: private final class ValueUpdateListener implements ActionListener {
272:
273: /**
274: * An ActionEvent has been fired by the Timer after its delay.
275: * Fires the pending PropertyChangeEvent, stops the timer,
276: * and updates this model's oldValue.<p>
277: *
278: * TODO: Consider stopping the timer before firing the change,
279: * because the change handling may take some time.
280: */
281: public void actionPerformed(ActionEvent e) {
282: fireValueChange(pendingEvt.getOldValue(), pendingEvt
283: .getNewValue(), true);
284: stop();
285: oldValue = pendingEvt.getNewValue() != null ? pendingEvt
286: .getNewValue() : subject.getValue();
287: }
288: }
289:
290: /**
291: * Forwards value changes in the subject to listeners of this model.
292: */
293: private final class SubjectValueChangeHandler implements
294: PropertyChangeListener {
295:
296: public void propertyChange(PropertyChangeEvent evt) {
297: fireDelayedValueChange(evt);
298: }
299: }
300:
301: }
|