/*
 * @(#)JComponentPopup.java
 * 
 * Copyright (c) 2010 by the original authors of JHotDraw
 * and all its contributors.
 * All rights reserved.
 * 
 * The copyright of this software is owned by the authors and  
 * contributors of the JHotDraw project ("the copyright holders").  
 * You may not use, copy or modify this software, except in  
 * accordance with the license agreement you entered into with  
 * the copyright holders. For details see 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 {

    /** Wether we are permitted to listen on AWT events. */
    private boolean isAWTEventListenerPermitted = true;

    private class Handler implements AWTEventListener {

        
        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 (srcLPChild == null || 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);


    }

    
    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 (srcLPChild == null || srcLP.getLayer(srcLPChild) < JLayeredPane.POPUP_LAYER) {
                        JComponentPopup.this.setVisible(false);
                    }
                }
            } else {
                super.menuSelectionChanged(isIncluded);
            }
        }
    }

    
    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);

        }
    }
}
