package menufix;
import java.awt.AWTEvent;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.util.HashSet;
import java.util.Iterator;
import javax.swing.JComboBox;
import javax.swing.JMenu;
import javax.swing.JPopupMenu;
import javax.swing.MenuSelectionManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.basic.BasicPopupMenuUI;
/**
* This class fixes a bug in Java 6 that causes all popup menus do not stay
* open. This occurs on {@link JMenu} and {@link JPopupMenu}, and
* {@link JComboBox} components. It is caused by a sun.awt.UngrabEvent being
* fired as soon as the popup menu appears and then immediately gets hidden. The
* class grabber class is called MouseGrabber and is inside the
* {@link BasicPopupMenuUI} class.
*
* @see BasicPopupMenuUI
* @author Chris Callendar
* @date 4-May-07
*/
public class PopupMenuFix implements ChangeListener {
private static PopupMenuFix singleton;
private static HashSet<ChangeListener> grabbers;
private PopupMenuFix() {
grabbers = new HashSet<ChangeListener>();
// @tag Creole.Java6.MouseGrabber : add our menu change listener
MenuSelectionManager mgr = MenuSelectionManager.defaultManager();
mgr.addChangeListener(this);
}
public static PopupMenuFix getGrabber() {
if (singleton == null) {
singleton = new PopupMenuFix();
}
return singleton;
}
/**
* In Java 6 the {@link BasicPopupMenuUI} adds the MouseGrabber class as a
* {@link ChangeListener} for when menus are selected. This listener then
* adds itself as an AWTEventListener which causes the popup menu to
* immediately be hidden because an sun.awt.UngrabEvent gets fired. This is
* a bug in Java 6. The workaround/hack is to remove the MouseGrabber as an
* {@link AWTEventListener} and add it back with a different mask that
* doesn't listen for Grab events.
*
* @tag Creole.Java6.MouseGrabber
*/
public static void removeMouseGrabber() {
String version = System
.getProperty("java.specification.version", "1.5");
if ("1.6".equals(version)) {
MenuSelectionManager mgr = MenuSelectionManager.defaultManager();
ChangeListener[] listeners = mgr.getChangeListeners();
grabbers = new HashSet<ChangeListener>();
for (int i = 0; i < listeners.length; i++) {
ChangeListener cl = listeners[i];
String name = cl.getClass().getName();
if (name.indexOf("MouseGrabber") != -1) {
if (cl instanceof AWTEventListener) {
grabbers.add(cl);
}
// make sure it is the last one in the list
// when the MenuSelectionManager.fireStateChanged() method
// runs
// it calls the listeners in reverse order, LAST first
mgr.removeChangeListener(cl);
mgr.addChangeListener(cl);
}
}
}
}
/**
* This method removes the MouseGrabber as an {@link AWTEventListener} and
* then adds it back without the sun.awt.SunToolkit.GRAB_EVENT_MASK. This
* code is copied from the MouseGrabber.ungrabWindow() and grabWindow()
* methods.
*/
public void stateChanged(ChangeEvent e) {
final Toolkit tk = Toolkit.getDefaultToolkit();
java.security.AccessController
.doPrivileged(new java.security.PrivilegedAction<Object>() {
public Object run() {
for (Iterator<ChangeListener> iter = grabbers.iterator(); iter
.hasNext();) {
AWTEventListener al = (AWTEventListener) iter
.next();
tk.removeAWTEventListener(al);
// removed the sun.awt.SunToolkit.GRAB_EVENT_MASK !
tk.addAWTEventListener(al,
AWTEvent.MOUSE_EVENT_MASK
| AWTEvent.MOUSE_MOTION_EVENT_MASK
| AWTEvent.MOUSE_WHEEL_EVENT_MASK
| AWTEvent.WINDOW_EVENT_MASK);
}
return null;
}
});
}
}