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