com.jgoodies.binding.beans.DelayedPropertyChangeHandler Maven / Gradle / Ivy
Show all versions of jgoodies-binding Show documentation
/*
* Copyright (c) 2002-2015 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.beans;
import static com.jgoodies.common.base.Preconditions.checkArgument;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.Timer;
/**
* A PropertyChangeListener that is intended to handle property changes
* after a specified delay. Useful to defer changes until a stable state
* is reached. For example if you look up a persistent object for a selection
* in a list. You don't want to find and transport objects that the user
* selects temporarily; you want to get only a stable selection.
* Or if you want to validate on every key typed, you may delay the
* validation until no key has been typed for a given time.
*
* If this handler is notified about a property change it stores
* the PropertyChangeEvent it has received in {@code #propertyChange},
* and starts a Swing Timer that will call {@code #delayedPropertyChange}
* after a delay. In coalescing mode a previously started timer - if any -
* will be stopped before. In other words, the timer is restarted.
*
* TODO: Write about the recommended delay time - above the double-click time
* and somewhere below a second, e.g. 100ms to 200ms.
*
* TODO: Summarize the differences between the DelayedReadValueModel, the
* DelayedWriteValueModel, and this DelayedPropertyChangeHandler.
*
* @author Karsten Lentzsch
* @version $Revision: 1.19 $
*
* @see com.jgoodies.binding.value.DelayedReadValueModel
* @see com.jgoodies.binding.extras.DelayedWriteValueModel
* @see javax.swing.Timer
*
* @since 1.1
*/
public abstract class DelayedPropertyChangeHandler implements PropertyChangeListener {
/**
* The delay in milliseconds used as default in the no-arg constructor.
*/
public static final int DEFAULT_DELAY = 200; // ms
/**
* The Timer used to perform the delayed property change.
* Started or restarted on every property change.
*/
private final Timer timer;
/**
* If {@code true} all pending updates will be coalesced.
* In other words, an update will be fired if no updates
* have been received for this model's delay.
*/
private boolean coalesce;
/**
* Holds the most recent pending PropertyChangeEvent as stored
* when this handler is notified about a property change, i. e.
* {@code #propertyChange} is called. This event will be
* used to call {@code #delayedPropertyChange} after a delay.
*/
private PropertyChangeEvent pendingEvt;
// Instance Creation ******************************************************
/**
* Constructs a DelayedPropertyChangeHandler with a default delay.
*/
public DelayedPropertyChangeHandler() {
this(DEFAULT_DELAY);
}
/**
* Constructs a DelayedPropertyChangeHandler with the specified Timer delay
* and the coalesce disabled.
*
* @param delay the milliseconds to wait before the delayed property change
* will be performed
*
* @throws IllegalArgumentException if the delay is negative
*/
public DelayedPropertyChangeHandler(int delay) {
this(delay, false);
}
/**
* Constructs a DelayedPropertyChangeHandler with the specified Timer delay
* and the given coalesce mode.
*
* @param delay the milliseconds to wait before the delayed property change
* will be performed
* @param coalesce {@code true} to coalesce all pending changes,
* {@code false} to fire changes with the delay when an update
* has been received
*
* @throws IllegalArgumentException if the delay is negative
*
* @see #setCoalesce(boolean)
*/
public DelayedPropertyChangeHandler(int delay, boolean coalesce) {
checkArgument(delay >= 0, "The delay must not be negative.");
this.coalesce = coalesce;
this.timer = new Timer(delay, new DelayHandler());
timer.setRepeats(false);
}
// Accessors **************************************************************
/**
* Returns the delay, in milliseconds, that is used to defer value change
* notifications.
*
* @return the delay, in milliseconds, that is used to defer
* value change notifications
*
* @see #setDelay
*
* @since 1.5
*/
public final int getDelay() {
return timer.getDelay();
}
/**
* Sets the delay, in milliseconds, that is used to defer value change
* notifications.
*
* @param delay the delay, in milliseconds, that is used to defer
* value change notifications
* @see #getDelay
*
* @throws IllegalArgumentException if the delay is negative
*
* @since 1.5
*/
public final void setDelay(int delay) {
timer.setInitialDelay(delay);
timer.setDelay(delay);
}
/**
* Returns if this model coalesces all pending changes or not.
*
* @return {@code true} if all pending changes will be coalesced,
* {@code false} if pending changes are fired with a delay
* when an update has been received.
*
* @see #setCoalesce(boolean)
*/
public final boolean isCoalesce() {
return coalesce;
}
/**
* Sets if this model shall coalesce all pending changes or not.
* In this case, a change event will be fired first,
* if no updates have been received for this model's delay.
* If coalesce is {@code false}, a change event will be fired
* with this model's delay when an update has been received.
*
* The default value is {@code false}.
*
* Note that this value is not the #coalesce value
* of this model's internal Swing timer.
*
* @param b {@code true} to coalesce,
* {@code false} to fire separate changes
*/
public final void setCoalesce(boolean b) {
coalesce = b;
}
// Misc *******************************************************************
/**
* Stops a running timer. Pending events - if any - are canceled
* and won't be delivered to {@code #delayedPropertyChange}.
*
* @since 1.2
*/
public final void stop() {
timer.stop();
}
/**
* Checks and answers whether there are pending events.
*
* @return {@code true} if there are pending events, {@code false} if not.
*
* @since 2.0.4
*/
public final boolean isPending() {
return timer.isRunning();
}
/**
* This handler has been notified about a change in a bound property.
* Stores the given event, then starts a timer that'll call
* {@code #delayedPropertyChange} after this handler's delay.
* If coalescing is enabled, a previously started timer - if any -
* if stopped before. In other words, the timer is restarted.
*
* @param evt the PropertyChangeEvent describing the event source
* and the property that has changed
*/
@Override
public final void propertyChange(PropertyChangeEvent evt) {
pendingEvt = evt;
if (coalesce) {
timer.restart();
} else {
timer.start();
}
}
/**
* This method gets called after this handler's delay
* if a bound property has changed. The event is the
* pending event as stored in {@code #propertyChange}.
*
* @param evt the PropertyChangeEvent describing the event source
* and the property that has changed
*/
public abstract void delayedPropertyChange(PropertyChangeEvent evt);
// Event Handling *********************************************************
/**
* Describes the delayed action to be performed by the timer.
*/
private final class DelayHandler implements ActionListener {
/**
* An ActionEvent has been fired by the Timer after its delay.
* Invokes #delayedPropertyChange with the pending PropertyChangeEvent,
* then stops the timer.
*/
@Override
public void actionPerformed(ActionEvent e) {
delayedPropertyChange(pendingEvt);
timer.stop();
}
}
}