001/* 002 * Created on 23.04.2009 003 * 004 */ 005package org.jdesktop.swingx.event; 006 007import java.awt.Component; 008import java.awt.KeyboardFocusManager; 009import java.beans.PropertyChangeEvent; 010import java.beans.PropertyChangeListener; 011 012import javax.swing.JComponent; 013 014import org.jdesktop.beans.AbstractBean; 015import org.jdesktop.swingx.SwingXUtilities; 016import org.jdesktop.swingx.util.Contract; 017 018/** 019 * An convenience class which maps focusEvents received 020 * from a container hierarchy to a bound read-only property. Registered 021 * PropertyChangeListeners are notified if the focus is transfered into/out of 022 * the hierarchy of a given root. 023 * <p> 024 * 025 * F.i, client code which wants to get notified if focus enters/exits the hierarchy below 026 * panel would install the compound focus listener like: 027 * 028 * <pre> 029 * <code> 030 * // add some components inside 031 * panel.add(new JTextField("something to .... focus")); 032 * panel.add(new JXDatePicker(new Date())); 033 * JComboBox combo = new JComboBox(new Object[] {"dooooooooo", 1, 2, 3, 4 }); 034 * combo.setEditable(true); 035 * panel.add(new JButton("something else to ... focus")); 036 * panel.add(combo); 037 * panel.setBorder(new TitledBorder("has focus dispatcher")); 038 * // register the compound dispatcher 039 * CompoundFocusListener report = new CompoundFocusListener(panel); 040 * PropertyChangeListener l = new PropertyChangeListener() { 041 * 042 * public void propertyChange(PropertyChangeEvent evt) { 043 * // do something useful here 044 * 045 * }}; 046 * report.addPropertyChangeListener(l); 047 * 048 * </code> 049 * </pre> 050 * 051 * PENDING JW: change of current instance of KeyboardFocusManager? 052 * 053 */ 054public class CompoundFocusListener extends AbstractBean { 055 056 /** the root of the component hierarchy. 057 * PENDING JW: weak reference and auto-release listener? 058 */ 059 private JComponent root; 060 /** PropertyChangeListener registered with the current keyboardFocusManager. */ 061 private PropertyChangeListener managerListener; 062 private boolean focused; 063 064 /** 065 * Instantiates a CompoundFocusListener on the component hierarchy below the given 066 * component. 067 * 068 * @param root the root of a component hierarchy 069 * @throws NullPointerException if the root is null 070 */ 071 public CompoundFocusListener(JComponent root) { 072 this.root = Contract.asNotNull(root, "root must not be null"); 073 KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); 074 addManagerListener(manager); 075 permanentFocusOwnerChanged(manager.getPermanentFocusOwner()); 076 } 077 078 079 /** 080 * Return true if the root or any of its descendants is focused. This is a 081 * read-only bound property, that is property change event is fired if focus 082 * is transfered into/out of root's hierarchy. 083 * 084 * @return a boolean indicating whether or not any component in the 085 * container hierarchy below root is permanent focus owner. 086 */ 087 public boolean isFocused() { 088 return focused; 089 } 090 091 /** 092 * Releases all listeners and internal references.<p> 093 * 094 * <b>Note</b>: this instance must not be used after calling this method. 095 * 096 */ 097 public void release() { 098 removeManagerListener(KeyboardFocusManager.getCurrentKeyboardFocusManager()); 099 removeAllListeners(); 100 this.root = null; 101 } 102 103 /** 104 * Removes all property change listeners which are registered with this instance. 105 */ 106 private void removeAllListeners() { 107 for (PropertyChangeListener l : getPropertyChangeListeners()) { 108 removePropertyChangeListener(l); 109 } 110 } 111 112 /** 113 * Updates focused property depending on whether or not the given component 114 * is below the root's hierarchy. <p> 115 * 116 * Note: Does nothing if the component is null. This might not be entirely correct, 117 * but property change events from the focus manager come in pairs, with only 118 * one of the new/old value not-null. 119 * 120 * @param focusOwner the component with is the current focusOwner. 121 */ 122 protected void permanentFocusOwnerChanged(Component focusOwner) { 123 if (focusOwner == null) return; 124 setFocused(SwingXUtilities.isDescendingFrom(focusOwner, root)); 125 } 126 127 private void setFocused(boolean focused) { 128 boolean old = isFocused(); 129 this.focused = focused; 130 firePropertyChange("focused", old, isFocused()); 131 } 132 133 134 /** 135 * Adds all listeners to the given KeyboardFocusManager. <p> 136 * 137 * @param manager the KeyboardFocusManager to add internal listeners to. 138 * @see #removeManagerListener(KeyboardFocusManager) 139 */ 140 private void addManagerListener(KeyboardFocusManager manager) { 141 manager.addPropertyChangeListener("permanentFocusOwner", getManagerListener()); 142 } 143 144 /** 145 * Removes all listeners this instance has installed from the given KeyboardFocusManager.<p> 146 * 147 * @param manager the KeyboardFocusManager to remove internal listeners from. 148 * @see #addManagerListener(KeyboardFocusManager) 149 */ 150 private void removeManagerListener(KeyboardFocusManager manager) { 151 manager.removePropertyChangeListener("permanentFocusOwner", getManagerListener()); 152 } 153 154 /** 155 * Lazily creates and returns a property change listener to be registered on the 156 * KeyboardFocusManager. 157 * 158 * @return a property change listener to be registered on the KeyboardFocusManager. 159 */ 160 private PropertyChangeListener getManagerListener() { 161 if (managerListener == null) { 162 managerListener = new PropertyChangeListener() { 163 164 @Override 165 public void propertyChange(PropertyChangeEvent evt) { 166 if ("permanentFocusOwner".equals(evt.getPropertyName())) { 167 permanentFocusOwnerChanged((Component) evt.getNewValue()); 168 } 169 170 }}; 171 } 172 return managerListener; 173 } 174 175 176}