package de.juwimm.cms.gui.controls; import javax.swing.*; import java.awt.Point; import java.awt.event.*; import java.util.ArrayList; import java.util.Iterator; public class PopupComponent extends JFrame { private boolean POPUP_IS_MODAL = true; // By default, the popup is modal private boolean HIDE_ON_CLICK = true; // Determines if a click outside of the popup should hide it private boolean HIDE_ON_TIMER = false; // Determines if the popup should hide after a given amount of time private int HIDE_TIMER_DELAY = 0; private boolean FORWARD_MOUSE_MOTION_EVENTS = true; private Timer timer; private JComponent content; // The content that represents the popup private JComponent glassPane; // The glass pane that will contain the popup private ArrayList<PopupComponentListener> listeners; // A list of listeners to be informed of popup events private boolean mouseOverGlassPane; /** * Constructs a popup using the specified content, which will automatically * hide if a mouse click occurs outside of the popup area, and will behave * in a modal fashion - i.e. no other components (in the content pane) will * receive mouse events until the popup has been hidden. * @param content A component that represents the * popup content. */ public PopupComponent(JComponent content) { this(content, true, 0, false); } /** * Constructs a popup using the specified settings. * @param content The component that represents the popup * content. * @param hideOnClick If <code>true</code> then the popup * will hide when any area outside of it is clicked (rather * like a popup menu). If <code>false</code> the popup will not * hide when any area outside of it is clicked. * @param timerDelay The number of milliseconds after which the * the popup will automatically hide itself. If a value of zero is * specified, then the popup will remain displayed indefinitely. * @param isModal If <code>true</code>, mouse listener events * that occur outside the component area are forwarded to the appropriate * component in the content pane i.e other components may be interacted with whilst * the popup is displayed. If <code>false</code> then the popup * behaves rather like a modal dialog, so that no other components can receive * mouse events until the component has closed. */ public PopupComponent(JComponent content, boolean hideOnClick, int timerDelay, boolean isModal) { glassPane = new JPanel(null); glassPane.setLayout(null); glassPane.add(content); glassPane.setOpaque(false); listeners = new ArrayList<PopupComponentListener>(); setupListeners(); setContent(content); this.HIDE_ON_CLICK = hideOnClick; this.POPUP_IS_MODAL = isModal; // Check to see if a timer delay has been // specified. If so, set up the timer. if (timerDelay > 0) { HIDE_ON_TIMER = true; HIDE_TIMER_DELAY = timerDelay; timer = new Timer(HIDE_TIMER_DELAY, new ActionListener() { public void actionPerformed(ActionEvent e) { // Hide the popup if the mouse is outside // the content if (mouseOverGlassPane == true) { hidePopup(); timer.stop(); } else { timer.stop(); timer.start(); } } }); } } /** * Causes the popup to be displayed at the specified * location. * @param component The component whose cooredinate * system is to be used. * @param x The horizontal location of the popup * @param y The vertical location of the popup. */ public void showPopup(JComponent component, int x, int y) { // Get the root (JFrame probably) JComponent rootComp = SwingUtilities.getRootPane(component); // Determine where to place the popup // (Left or right, top or bottom of the mouse cursor // We want to fit the popup within the top level frame int componentMaxX = (int) rootComp.getSize().width; int componentMaxY = (int) rootComp.getSize().height; int xPosOnRoot = SwingUtilities.convertPoint(component, x, y, rootComp).x; int yPosOnRoot = SwingUtilities.convertPoint(component, x, y, rootComp).y; // Display to the right of the mouse cursor, unless // there is not enough room int deltaX = xPosOnRoot + content.getWidth() - componentMaxX; if (deltaX > 0) { xPosOnRoot -= deltaX; if (xPosOnRoot < 0) { xPosOnRoot = 0; } } int deltaY = yPosOnRoot + content.getHeight() - componentMaxY; if (deltaY > 0) { yPosOnRoot -= deltaY; if (yPosOnRoot < 0) { yPosOnRoot = 0; } } // Convert root pos back to component pos int xPos = SwingUtilities.convertPoint(rootComp, xPosOnRoot, yPosOnRoot, component).x; int yPos = SwingUtilities.convertPoint(rootComp, xPosOnRoot, yPosOnRoot, component).y; JRootPane rootPane = component.getRootPane(); rootPane.setGlassPane(glassPane); //Convert the mouse point from the invoking component coordinate // system to the glassPane coordinate system Point pt = SwingUtilities.convertPoint(component, xPos, yPos, glassPane); // Set the location of the popup in the glass pane content.setLocation(pt); // Show the glass pane. glassPane.setVisible(true); // If the popup is set to hide automatically after a specified // amount of time, then reset the timer. // if (HIDE_ON_TIMER == true) { // timer.stop(); // // timer.start(); // } } /** * Hides the popup */ public void hidePopup() { // Only hide the popup if the glass pane is // visible (i.e. if the popup is being displayed) if (glassPane.isVisible() == true) { glassPane.setVisible(false); firePopupClosedEvent(); } } /** * Sets the content that the popup displays * @param content A JComponent that represents the content to be displayed. */ public void setContent(JComponent content) { if (content == null) { throw new NullPointerException("Popup content must not be null"); } // Remove the previous popup content if (this.content != null) { glassPane.remove(this.content); } // Set this content to the new content this.content = content; // Set the size of the content this.content.setSize(this.content.getPreferredSize()); // Put the content into the glass pane glassPane.add(this.content); } /** * Adds a <code>PopupComponentListener</code>, which is informed * when the popup closes. * @param lsnr The listener to be added. */ public void addPopupComponentListener(PopupComponentListener lsnr) { listeners.add(lsnr); } /** * Removes a previously added listener. * @param lsnr The listener to be removed. */ public void removePopupComponentListener(PopupComponentListener lsnr) { listeners.remove(lsnr); } /** * Sets up listeners */ protected void setupListeners() { // Check to see if we need to close // the popup when a click outside of // it's area has been detected. Also, // we may need to propagte the events to // components below the glass pane glassPane.addMouseListener(new MouseListener() { /** * Invoked when the mouse button has been clicked (pressed * and released) on a component. */ public void mouseClicked(MouseEvent e) { // propagateMouseListenerEvent(e); } /** * Invoked when a mouse button has been pressed on a component. */ public void mousePressed(MouseEvent e) { // Might need to hide the popup if the mouse // has been pressed on the glass pane handleHidePopup(e); // propagateMouseListenerEvent(e); } /** * Invoked when a mouse button has been released on a component. */ public void mouseReleased(MouseEvent e) { // propagateMouseListenerEvent(e); } /** * Invoked when the mouse enters a component. */ public void mouseEntered(MouseEvent e) { // Don't propagate here mouseOverGlassPane = true; } /** * Invoked when the mouse exits a component. */ public void mouseExited(MouseEvent e) { // Don't propagate mouseOverGlassPane = false; } /** * Checks to see if a mouse event on the glass * pane should cause the popup to be hidden, and * if so, hides it. * @param e The mouse event. */ private void handleHidePopup(MouseEvent e) { if (HIDE_ON_CLICK == true) { hidePopup(); } } }); // glassPane.addMouseMotionListener(new MouseMotionListener() { // /** // * Invoked when a mouse button is pressed on a component and then // * dragged. <code>MOUSE_DRAGGED</code> events will continue to be // * delivered to the component where the drag originated until the // * mouse button is released (regardless of whether the mouse position // * is within the bounds of the component). // * <p/> // * Due to platform-dependent Drag&Drop implementations, // * <code>MOUSE_DRAGGED</code> events may not be delivered during a native // * Drag&Drop operation. // */ // public void mouseDragged(MouseEvent e) { // propagateMouseMotionListenerEvents(e); // } // // /** // * Invoked when the mouse cursor has been moved onto a component // * but no buttons have been pushed. // */ // public void mouseMoved(MouseEvent e) { // propagateMouseMotionListenerEvents(e); // } // }); } /** * Fires an event that informs registered listeners * that the popup has closed. */ protected void firePopupClosedEvent() { PopupComponentEvent evt = new PopupComponentEvent(this); // Just iterate through the list of listeners // and fire the events to them Iterator<PopupComponentListener> it = listeners.iterator(); while (it.hasNext()) { it.next().popupClosed(evt); } } /** * Obtains the deepest component below the glass pane * at the specified point. * @param pt The point. * @return The deepest component, or <code>null</code> if * there is no component at the specified point. */ // protected Component getDeepestComponent(Point pt) { // // Get hold of the content pane, since this contains the components // // that we want to relay mouse events to // SwingUtilities.getRoot(glassPane); // // JComponent contentPane = glassPane.getRootPane(); // // // Convert the mouse point from the glass pane coordinate system // // into the coordinate system of the content pane. // Point contentPanePt = SwingUtilities.convertPoint(glassPane, pt, contentPane); // // // Find the deepest component - i.e. the one that should get the mouse // // event. // JComponent deepestComponent = SwingUtilities.getDeepestComponentAt(contentPane, contentPanePt.x, contentPanePt.y); // // return deepestComponent; // } /** * Propagates certain mouse events, such as MOUSE_CLICKED, MOUSE_RELEASED * etc. to the deepest component. * @param e The MouseEvent to be propagated. */ // protected void propagateMouseListenerEvent(MouseEvent e) { // if (POPUP_IS_MODAL == false) { // //// Component deepestComponent = getDeepestComponent(e.getPoint()); // // if (deepestComponent != null) { // MouseListener[] mouseListeners = deepestComponent.getMouseListeners(); // // int eventID; // // // Get the event type // eventID = e.getID(); // // Point pt = e.getPoint(); // // Point convertedPt = SwingUtilities.convertPoint(glassPane, e.getPoint(), deepestComponent); // // MouseEvent evt = new MouseEvent(deepestComponent, e.getID(), System.currentTimeMillis(), e.getModifiers(), convertedPt.x, convertedPt.y, e.getClickCount(), e.isPopupTrigger(), e.getButton()); // // // Distibute the event to the component's listeners. // for (int i = 0; i < mouseListeners.length; i++) { // // // Forward the appropriate mouse event // if (eventID == MouseEvent.MOUSE_PRESSED) { // mouseListeners[i].mousePressed(evt); // } else if (eventID == MouseEvent.MOUSE_RELEASED) { // mouseListeners[i].mouseReleased(evt); // } else if (eventID == MouseEvent.MOUSE_CLICKED) { // mouseListeners[i].mouseClicked(evt); // } // // } // } // } // } // // protected void propagateMouseMotionListenerEvents(MouseEvent e) { // if (FORWARD_MOUSE_MOTION_EVENTS == true) { // // Get the correct component // Component deepestComponent = getDeepestComponent(e.getPoint()); // // if (deepestComponent != null) { // // Distribute the event to the components listeners // MouseMotionListener[] mouseMotionListeners = deepestComponent.getMouseMotionListeners(); // // // Get the event type // int eventID = e.getID(); // // Point pt = e.getPoint(); // // Point convertedPt = SwingUtilities.convertPoint(glassPane, e.getPoint(), deepestComponent); // // MouseEvent evt = new MouseEvent(deepestComponent, e.getID(), System.currentTimeMillis(), e.getModifiers(), convertedPt.x, convertedPt.y, e.getClickCount(), e.isPopupTrigger(), e.getButton()); // // for (int i = 0; i < mouseMotionListeners.length; i++) { // if (eventID == MouseEvent.MOUSE_MOVED) { // mouseMotionListeners[i].mouseMoved(e); // } else if (eventID == MouseEvent.MOUSE_DRAGGED) { // mouseMotionListeners[i].mouseDragged(e); // } // } // } // } // } public boolean popupIsDisplayed() { return glassPane.isVisible(); } }