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.beans;
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 PropertyChangeListener that is intended to handle property changes
042: * after a specified delay. Useful to defer changes until a stable state
043: * is reached. For example if you look up a persistent object for a selection
044: * in a list. You don't want to find and transport objects that the user
045: * selects temporarily; you want to get only a stable selection.
046: * Or if you want to validate on every key typed, you may delay the
047: * validation until no key has been typed for a given time.<p>
048: *
049: * If this handler is notified about a property change it stores
050: * the PropertyChangeEvent it has received in <code>#propertyChange</code>,
051: * and starts a Swing Timer that will call <code>#delayedPropertyChange</code>
052: * after a delay. In coalescing mode a previously started timer - if any -
053: * will be stopped before. In other words, the timer is restarted.<p>
054: *
055: * TODO: Write about the recommended delay time - above the double-click time
056: * and somewhere below a second, e.g. 100ms to 200ms.<p>
057: *
058: * TODO: Summarize the differences between the DelayedReadValueModel, the
059: * DelayedWriteValueModel, and this DelayedPropertyChangeHandler.
060: *
061: * @author Karsten Lentzsch
062: * @version $Revision: 1.10 $
063: *
064: * @see com.jgoodies.binding.value.DelayedReadValueModel
065: * @see com.jgoodies.binding.extras.DelayedWriteValueModel
066: * @see javax.swing.Timer
067: *
068: * @since 1.1
069: */
070: public abstract class DelayedPropertyChangeHandler implements
071: PropertyChangeListener {
072:
073: /**
074: * The delay in milliseconds used as default in the no-arg constructor.
075: */
076: public static final int DEFAULT_DELAY = 200; // ms
077:
078: /**
079: * The Timer used to perform the delayed property change.
080: * Started or restarted on every property change.
081: */
082: private final Timer timer;
083:
084: /**
085: * If <code>true</code> all pending updates will be coalesced.
086: * In other words, an update will be fired if no updates
087: * have been received for this model's delay.
088: */
089: private boolean coalesce;
090:
091: /**
092: * Holds the most recent pending PropertyChangeEvent as stored
093: * when this handler is notified about a property change, i. e.
094: * <code>#propertyChange</code> is called. This event will be
095: * used to call <code>#delayedPropertyChange</code> after a delay.
096: */
097: private PropertyChangeEvent pendingEvt;
098:
099: // Instance Creation ******************************************************
100:
101: /**
102: * Constructs a DelayedPropertyChangeHandler with a default delay.
103: */
104: public DelayedPropertyChangeHandler() {
105: this (DEFAULT_DELAY);
106: }
107:
108: /**
109: * Constructs a DelayedPropertyChangeHandler with the specified Timer delay
110: * and the coalesce disabled.
111: *
112: * @param delay the milliseconds to wait before the delayed property change
113: * will be performed
114: *
115: * @throws IllegalArgumentException if the delay is negative
116: */
117: public DelayedPropertyChangeHandler(int delay) {
118: this (delay, false);
119: }
120:
121: /**
122: * Constructs a DelayedPropertyChangeHandler with the specified Timer delay
123: * and the given coalesce mode.
124: *
125: * @param delay the milliseconds to wait before the delayed property change
126: * will be performed
127: * @param coalesce <code>true</code> to coalesce all pending changes,
128: * <code>false</code> to fire changes with the delay when an update
129: * has been received
130: *
131: * @throws IllegalArgumentException if the delay is negative
132: *
133: * @see #setCoalesce(boolean)
134: */
135: public DelayedPropertyChangeHandler(int delay, boolean coalesce) {
136: this .coalesce = coalesce;
137: this .timer = new Timer(delay, new DelayHandler());
138: timer.setRepeats(false);
139: }
140:
141: // Accessors **************************************************************
142:
143: /**
144: * Returns the delay, in milliseconds, that is used to defer value change
145: * notifications.
146: *
147: * @return the delay, in milliseconds, that is used to defer
148: * value change notifications
149: *
150: * @see #setDelay
151: *
152: * @since 1.5
153: */
154: public int getDelay() {
155: return timer.getDelay();
156: }
157:
158: /**
159: * Sets the delay, in milliseconds, that is used to defer value change
160: * notifications.
161: *
162: * @param delay the delay, in milliseconds, that is used to defer
163: * value change notifications
164: * @see #getDelay
165: *
166: * @since 1.5
167: */
168: public void setDelay(int delay) {
169: timer.setInitialDelay(delay);
170: timer.setDelay(delay);
171: }
172:
173: /**
174: * Returns if this model coalesces all pending changes or not.
175: *
176: * @return <code>true</code> if all pending changes will be coalesced,
177: * <code>false</code> if pending changes are fired with a delay
178: * when an update has been received.
179: *
180: * @see #setCoalesce(boolean)
181: */
182: public boolean isCoalesce() {
183: return coalesce;
184: }
185:
186: /**
187: * Sets if this model shall coalesce all pending changes or not.
188: * In this case, a change event will be fired first,
189: * if no updates have been received for this model's delay.
190: * If coalesce is <code>false</code>, a change event will be fired
191: * with this model's delay when an update has been received.<p>
192: *
193: * The default value is <code>false</code>.<p>
194: *
195: * Note that this value is not the #coalesce value
196: * of this model's internal Swing timer.
197: *
198: * @param b <code>true</code> to coalesce,
199: * <code>false</code> to fire separate changes
200: */
201: public void setCoalesce(boolean b) {
202: coalesce = b;
203: }
204:
205: // Misc *******************************************************************
206:
207: /**
208: * Stops a running timer - if any. The last pending event
209: * won't be delivered to <code>#delayedPropertyChange</code>.
210: *
211: * @since 1.2
212: */
213: public final void stop() {
214: timer.stop();
215: }
216:
217: /**
218: * This handler has been notified about a change in a bound property.
219: * Stores the given event, then starts a timer that'll call
220: * <code>#delayedPropertyChange</code> after this handler's delay.
221: * If coalescing is enabled, a previously started timer - if any -
222: * if stopped before. In other words, the timer is restarted.
223: *
224: * @param evt the PropertyChangeEvent describing the event source
225: * and the property that has changed
226: */
227: public final void propertyChange(PropertyChangeEvent evt) {
228: pendingEvt = evt;
229: if (coalesce) {
230: timer.restart();
231: } else {
232: timer.start();
233: }
234: }
235:
236: /**
237: * This method gets called after this handler's delay
238: * if a bound property has changed. The event is the
239: * pending event as stored in <code>#propertyChange</code>.<p>
240: *
241: * This method is invoked only if this handler hasn't received
242: * subsequent property changes. In other words, it is called only
243: * if the observed bound property is stable during the delay time.
244: *
245: * @param evt the PropertyChangeEvent describing the event source
246: * and the property that has changed
247: */
248: public abstract void delayedPropertyChange(PropertyChangeEvent evt);
249:
250: // Event Handling *********************************************************
251:
252: /**
253: * Describes the delayed action to be performed by the timer.
254: */
255: private final class DelayHandler implements ActionListener {
256:
257: /**
258: * An ActionEvent has been fired by the Timer after its delay.
259: * Invokes #delayedPropertyChange with the pending PropertyChangeEvent,
260: * then stops the timer.
261: */
262: public void actionPerformed(ActionEvent e) {
263: delayedPropertyChange(pendingEvt);
264: timer.stop();
265: }
266: }
267:
268: }
|