/*
* @(#)JComponentPopup.java
*
* Copyright (c) 2010 The authors and contributors of JHotDraw.
*
* You may not use, copy or modify this file, except in compliance with the
* accompanying license terms.
*/
package org.jhotdraw.gui;
import java.awt.AWTEvent;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.awt.event.MouseEvent;
import java.security.AccessControlException;
import javax.swing.JLayeredPane;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
/**
* This is an extension of the Swing {@code JPopupMenu} which can be used to
* display a {@code JComponent} in a popup menu.
* <p>
* Unlike {@code JPopupMenu}, the popup will stay open if the {@code JComponent}
* opens a popup menu of its own.
*
* @author Werner Randelshofer
* @version $Id$
*/
public class JComponentPopup extends JPopupMenu {
private static final long serialVersionUID = 1L;
/** Wether we are permitted to listen on AWT events. */
private boolean isAWTEventListenerPermitted = true;
private class Handler implements AWTEventListener {
@Override
public void eventDispatched(AWTEvent ev) {
if (!(ev instanceof MouseEvent) || !(ev.getSource() instanceof Component)) {
// We are interested in MouseEvents only
return;
}
MouseEvent me = (MouseEvent) ev;
Component src = (Component) ev.getSource();
Component invoker = JComponentPopup.this.getInvoker();
if (ev.getID() == MouseEvent.MOUSE_PRESSED) {
// Close popup if the mouse press occured on a component which is
// not descending from this popup menu, but has the same
// window ancestor.
if (!SwingUtilities.isDescendingFrom(src, JComponentPopup.this)
&& SwingUtilities.getWindowAncestor(src)
== SwingUtilities.getWindowAncestor(invoker)) {
JLayeredPane srcLP = (JLayeredPane) SwingUtilities.getAncestorOfClass(JLayeredPane.class, src);
Component srcLPChild = src;
while (srcLPChild.getParent() != srcLP) {
srcLPChild = srcLPChild.getParent();
}
if (srcLP.getLayer(srcLPChild) < JLayeredPane.POPUP_LAYER) {
JComponentPopup.this.setVisible(false);
}
}
} else if (ev.getID() == MouseEvent.MOUSE_CLICKED) {
// Close popup if a double click occured on the popup component.
if (me.getClickCount() == 2
&& SwingUtilities.isDescendingFrom(src, JComponentPopup.this)) {
JComponentPopup.this.setVisible(false);
}
}
}
};
private Handler handler = new Handler();
public JComponentPopup() {
setLightWeightPopupEnabled(false);
}
@Override
public void menuSelectionChanged(boolean isIncluded) {
if (isAWTEventListenerPermitted) {
// Don't let the MenuSelectionManager hide this popup.
return;
} else {
// Since we are not allowed to use an AWTEventListener we
// grab the current AWT Event ourselves (hoping that this method
// invocation is associated to it) and try to decide whether
// we want to close the popup.
//
// This will prevent undesired closing of the popup component when
// a combo box is opened on the popup component.
// After this happened though, menuSelectionChanged is not invoked
// anymore and we lose the ability to close the popup component.
AWTEvent evt = EventQueue.getCurrentEvent();
if (evt != null && evt.getSource() instanceof Component) {
Component src = (Component) evt.getSource();
Component invoker = getInvoker();
if (!SwingUtilities.isDescendingFrom(src, JComponentPopup.this)
&& SwingUtilities.getWindowAncestor(src)
== SwingUtilities.getWindowAncestor(invoker)) {
JLayeredPane srcLP = (JLayeredPane) SwingUtilities.getAncestorOfClass(JLayeredPane.class, src);
Component srcLPChild = src;
while (srcLPChild.getParent() != srcLP) {
srcLPChild = srcLPChild.getParent();
}
if (srcLP.getLayer(srcLPChild) < JLayeredPane.POPUP_LAYER) {
JComponentPopup.this.setVisible(false);
}
}
} else {
super.menuSelectionChanged(isIncluded);
}
}
}
@Override
public void setVisible(boolean newValue) {
// Attach/detach AWTEventListener on "visible" property change.
if (isVisible() != newValue) {
if (isAWTEventListenerPermitted) {
try {
if (newValue) {
Toolkit.getDefaultToolkit().addAWTEventListener(handler, AWTEvent.MOUSE_EVENT_MASK);
} else {
Toolkit.getDefaultToolkit().removeAWTEventListener(handler);
}
} catch (AccessControlException e) {
// Unsigned Applets are not allowed to use an AWTEventListener.
isAWTEventListenerPermitted = false;
}
}
super.setVisible(newValue);
}
}
}