/*
* Created on 23.04.2009
*
*/
package org.hdesktop.swingx.event;
import java.awt.Component;
import java.awt.KeyboardFocusManager;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JComponent;
import org.hdesktop.beans.AbstractBean;
import org.hdesktop.swingx.SwingXUtilities;
import org.hdesktop.swingx.util.Contract;
/**
* An convenience class which maps focusEvents received
* from a container hierarchy to a bound read-only property. Registered
* PropertyChangeListeners are notified if the focus is transfered into/out of
* the hierarchy of a given root.
* <p>
*
* F.i, client code which wants to get notified if focus enters/exits the hierarchy below
* panel would install the compound focus listener like:
*
* <pre>
* <code>
* // add some components inside
* panel.add(new JTextField("something to .... focus"));
* panel.add(new JXDatePicker(new Date()));
* JComboBox combo = new JComboBox(new Object[] {"dooooooooo", 1, 2, 3, 4 });
* combo.setEditable(true);
* panel.add(new JButton("something else to ... focus"));
* panel.add(combo);
* panel.setBorder(new TitledBorder("has focus dispatcher"));
* // register the compound dispatcher
* CompoundFocusListener report = new CompoundFocusListener(panel);
* PropertyChangeListener l = new PropertyChangeListener() {
*
* public void propertyChange(PropertyChangeEvent evt) {
* // do something useful here
*
* }};
* report.addPropertyChangeListener(l);
*
* </code>
* </pre>
*
* PENDING JW: change of current instance of KeyboardFocusManager?
*
*/
public class CompoundFocusListener extends AbstractBean {
/** the root of the component hierarchy.
* PENDING JW: weak reference and auto-release listener?
*/
private JComponent root;
/** PropertyChangeListener registered with the current keyboardFocusManager. */
private PropertyChangeListener managerListener;
private boolean focused;
/**
* Instantiates a CompoundFocusListener on the component hierarchy below the given
* component.
*
* @param root the root of a component hierarchy
* @throws NullPointerException if the root is null
*/
public CompoundFocusListener(JComponent root) {
this.root = Contract.asNotNull(root, "root must not be null");
KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
addManagerListener(manager);
permanentFocusOwnerChanged(manager.getPermanentFocusOwner());
}
/**
* Return true if the root or any of its descendants is focused. This is a
* read-only bound property, that is property change event is fired if focus
* is transfered into/out of root's hierarchy.
*
* @return a boolean indicating whether or not any component in the
* container hierarchy below root is permanent focus owner.
*/
public boolean isFocused() {
return focused;
}
/**
* Releases all listeners and internal references.<p>
*
* <b>Note</b>: this instance must not be used after calling this method.
*
*/
public void release() {
removeManagerListener(KeyboardFocusManager.getCurrentKeyboardFocusManager());
removeAllListeners();
this.root = null;
}
/**
* Removes all property change listeners which are registered with this instance.
*/
private void removeAllListeners() {
for (PropertyChangeListener l : getPropertyChangeListeners()) {
removePropertyChangeListener(l);
}
}
/**
* Updates focused property depending on whether or not the given component
* is below the root's hierarchy. <p>
*
* Note: Does nothing if the component is null. This might not be entirely correct,
* but property change events from the focus manager come in pairs, with only
* one of the new/old value not-null.
*
* @param focusOwner the component with is the current focusOwner.
*/
protected void permanentFocusOwnerChanged(Component focusOwner) {
if (focusOwner == null) return;
setFocused(SwingXUtilities.isDescendingFrom(focusOwner, root));
}
private void setFocused(boolean focused) {
boolean old = isFocused();
this.focused = focused;
firePropertyChange("focused", old, isFocused());
}
/**
* Adds all listeners to the given KeyboardFocusManager. <p>
*
* @param manager the KeyboardFocusManager to add internal listeners to.
* @see #removeManagerListener(KeyboardFocusManager)
*/
private void addManagerListener(KeyboardFocusManager manager) {
manager.addPropertyChangeListener("permanentFocusOwner", getManagerListener());
}
/**
* Removes all listeners this instance has installed from the given KeyboardFocusManager.<p>
*
* @param manager the KeyboardFocusManager to remove internal listeners from.
* @see #addManagerListener(KeyboardFocusManager)
*/
private void removeManagerListener(KeyboardFocusManager manager) {
manager.removePropertyChangeListener("permanentFocusOwner", getManagerListener());
}
/**
* Lazily creates and returns a property change listener to be registered on the
* KeyboardFocusManager.
*
* @return a property change listener to be registered on the KeyboardFocusManager.
*/
private PropertyChangeListener getManagerListener() {
if (managerListener == null) {
managerListener = new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if ("permanentFocusOwner".equals(evt.getPropertyName())) {
permanentFocusOwnerChanged((Component) evt.getNewValue());
}
}};
}
return managerListener;
}
}