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