/*************************************************** * * cismet GmbH, Saarbruecken, Germany * * ... and it just works. * ****************************************************/ /** * Copyright (C) 1998-2000 by University of Maryland, College Park, MD 20742, USA * All rights reserved. */ package pswing; import edu.umd.cs.piccolo.PCanvas; import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.io.Serializable; import java.util.Vector; import javax.swing.*; import javax.swing.plaf.basic.BasicComboBoxUI; import javax.swing.plaf.basic.BasicComboPopup; import javax.swing.plaf.basic.ComboPopup; /** * A ComboBox for use in Jazz. This still has an associated JPopupMenu (which is always potentially heavyweight * depending on component location relative to containing window borders.) However, this ComboBox places the PopupMenu * component of the ComboBox in the appropriate position relative to the permanent part of the ComboBox. The PopupMenu * is never transformed. * * <p/>This class was not designed for subclassing. If different behavior is required, it seems more appropriate to * subclass JComboBox directly using this class as a model.</p> * * <p>NOTE: There is currently a known bug, namely, if the ComboBox receives focus through 'tab' focus traversal and the * keyboard is used to interact with the ComboBox, there may be unexpected results.</p> * * <P><b>Warning:</b> Serialized and ZSerialized objects of this class will not be compatible with future Jazz releases. * The current serialization support is appropriate for short term storage or RMI between applications running the same * version of Jazz. A future release of Jazz will provide support for long term persistence.</P> * * @author Lance Good * @version $Revision$, $Date$ */ public class PComboBox extends JComboBox implements Serializable { //~ Instance fields -------------------------------------------------------- private MouseEvent currentEvent; private PSwing pSwing; private PSwingCanvas canvas; //~ Constructors ----------------------------------------------------------- /** * Create an empty ZComboBox. */ public PComboBox() { super(); init(); } /** * Creates a ZComboBox that takes its items from an existing ComboBoxModel. * * @param model The ComboBoxModel from which the list will be created */ public PComboBox(final ComboBoxModel model) { super(model); init(); } /** * Creates a ZComboBox that contains the elements in the specified array. * * @param items The items to populate the ZComboBox list */ public PComboBox(final Object[] items) { super(items); init(); } /** * Creates a ZComboBox that contains the elements in the specified Vector. * * @param items The items to populate the ZComboBox list */ public PComboBox(final Vector items) { super(items); init(); } //~ Methods ---------------------------------------------------------------- /** * Substitue our look and feel for the default. */ private void init() { setUI(new ZBasicComboBoxUI()); } /** * Stores the most recent mousePressed ZMouseEvent. * * @param me DOCUMENT ME! */ private void setCurrentEvent(final MouseEvent me) { currentEvent = me; } /** * Get the most recent ZMouseEvent. * * @return DOCUMENT ME! */ private MouseEvent getCurrentEvent() { return currentEvent; } /** * DOCUMENT ME! * * @param pSwing DOCUMENT ME! * @param canvas DOCUMENT ME! */ public void setEnvironment(final PSwing pSwing, final PSwingCanvas canvas) { this.pSwing = pSwing; this.canvas = canvas; } /** * DOCUMENT ME! * * @return DOCUMENT ME! * * @throws RuntimeException DOCUMENT ME! */ private Point2D getNodeLocationInFrame() { if ((pSwing == null) || (canvas == null)) { throw new RuntimeException( "PComboBox.setEnvironment( swing, pCanvas );//has to be done manually at present"); // NOI18N } final Point2D r1c = pSwing.getBounds().getOrigin(); pSwing.localToGlobal(r1c); canvas.getCamera().viewToLocal(r1c); return r1c; } //~ Inner Classes ---------------------------------------------------------- /** * The substitute look and feel - used to capture the mouse events on the arrowButton and the component itself and * to create our PopupMenu rather than the default. * * @version $Revision$, $Date$ */ class ZBasicComboBoxUI extends BasicComboBoxUI { //~ Instance fields ---------------------------------------------------- EventGrabber eg = new EventGrabber(); //~ Methods ------------------------------------------------------------ /** * Add our listener to the front of the button's list. */ @Override public void configureArrowButton() { arrowButton.addMouseListener(eg); super.configureArrowButton(); } /** * Add the listener to the front of the combo's list. */ @Override protected void installListeners() { comboBox.addMouseListener(eg); super.installListeners(); } /** * Create our Popup instead of theirs. * * @return DOCUMENT ME! */ @Override protected ComboPopup createPopup() { final ZBasicComboPopup popup = new ZBasicComboPopup(comboBox); popup.getAccessibleContext().setAccessibleParent(comboBox); return popup; } } /** * The substitute ComboPopupMenu that places itself correctly for Jazz. * * @version $Revision$, $Date$ */ class ZBasicComboPopup extends BasicComboPopup { //~ Constructors ------------------------------------------------------- /** * Creates a new ZBasicComboPopup object. * * @param combo The parent ComboBox */ public ZBasicComboPopup(final JComboBox combo) { super(combo); } //~ Methods ------------------------------------------------------------ /** * Correctly computes the bounds for the Popup in Jazz if a ZMouseEvent has been received. Otherwise, it uses * the default algorithm for placing the popup. * * @param px corresponds to the x coordinate of the popup * @param py corresponds to the y coordinate of the popup * @param pw corresponds to the width of the popup * @param ph corresponds to the height of the popup * * @return The bounds for the PopupMenu */ @Override protected Rectangle computePopupBounds(final int px, int py, final int pw, final int ph) { if (currentEvent != null) { // We need to modify the y position to reflect the true // height of the ComboBox given the Jazz transformation final Point2D pt = getNodeLocationInFrame(); final Rectangle2D bounds = new Rectangle2D.Double(pt.getX(), pt.getY(), (double)comboBox.getBounds().width, (double)comboBox.getBounds().height); // currentEvent.getPath().getCamera().localToCamera( bounds, currentEvent.getNode() ); py = (int)(bounds.getHeight() + 0.5); Rectangle absBounds; final Rectangle r = new Rectangle(px, py, pw, ph); final boolean inModalDialog = inModalDialog(); /** Workaround for modal dialogs. See also JPopupMenu.java **/ /** We don't need to worry about this in Jazz because Dialogs aren't supported in Jazz - We'll leave it in just in case ZCanvas is added to a Dialog **/ if (inModalDialog) { final Dialog dlg = getDialog(); Point p; if (dlg instanceof JDialog) { final JRootPane rp = ((JDialog)dlg).getRootPane(); p = rp.getLocationOnScreen(); absBounds = rp.getBounds(); absBounds.x = p.x; absBounds.y = p.y; } else { absBounds = dlg.getBounds(); } p = new Point(absBounds.x, absBounds.y); SwingUtilities.convertPointFromScreen(p, comboBox); absBounds.x = p.x; absBounds.y = p.y; } else { final Point p; final Dimension scrSize = Toolkit.getDefaultToolkit().getScreenSize(); absBounds = new Rectangle(); // We get the true ComboBox location on screen to calculate // where to put the Popup component p = getComboLocationOnScreen(); absBounds.x = -p.x; absBounds.y = -p.y; absBounds.width = scrSize.width; absBounds.height = scrSize.height; } final Point offset = getJazzComboOffset(); // In this case we can do a normal pull down if (SwingUtilities.isRectangleContainingRectangle(absBounds, r)) { r.x = r.x + offset.x; r.y = r.y + offset.y; return r; } else { final Rectangle r2 = new Rectangle(0, -r.height, r.width, r.height); // In this case we couldn't do pull down but we can do // pull up if (SwingUtilities.isRectangleContainingRectangle(absBounds, r2)) { r2.x = offset.x; r2.y = offset.y + r2.y; return r2; } // Here we couldn't pull-down so we'll take the better of // the two possibilities cause we're in a dialog. if (inModalDialog) { SwingUtilities.computeIntersection( absBounds.x, absBounds.y, absBounds.width, absBounds.height, r); SwingUtilities.computeIntersection( absBounds.x, absBounds.y, absBounds.width, absBounds.height, r2); if (r.height > r2.height) { r.x = r.x + offset.x; r.y = r.y + offset.y; return r; } else { r2.x = offset.x; r2.y = offset.y + r2.y; return r2; } } // We couldn't really do pull-down or up optimally so we // just go with up else { r2.x = offset.x; r2.y = offset.y + r2.y; return r2; } } } else { return super.computePopupBounds(px, py, pw, ph); } } /** * Gets the true location for the ComboBox on the screen. * * @return The true location of the ComboBox on the screen */ private Point getComboLocationOnScreen() { Point position = null; if (comboBox.isShowing()) { Point2D pt = new Point2D.Double(0.0, 0.0); Component c; // We don't want to get the offset of the Swing Component // from the SwingWrapper (in ZCanvas) so we stop when we get // to the top Swing component below the SwingWrapper for (c = comboBox; !(c.getParent().getParent() instanceof PCanvas); c = c.getParent()) { final Point location = c.getLocation(); pt.setLocation(pt.getX() + location.getX(), pt.getY() + location.getY()); } pt = getNodeLocationInFrame(); // PCamera camera = currentEvent.getPath().getTopCamera(); // camera.localToCamera( pt, currentEvent.getNode() ); position = new Point((int)(pt.getX() + 0.5), (int)(pt.getY() + 0.5)); final Point canvasOffset = c.getParent().getLocationOnScreen(); position.setLocation(position.getX() + canvasOffset.getX(), position.getY() + canvasOffset.getY()); } return position; } /** * The. * * @return The offset from the expected screen location and the actual screen location. */ private Point getJazzComboOffset() { final Point swing = comboBox.getLocationOnScreen(); final Point jazz = getComboLocationOnScreen(); jazz.setLocation(jazz.getLocation().getX() - swing.getLocation().getX(), jazz.getLocation().getY() - swing.getLocation().getY()); return jazz; } /** * Copied directly from BasicComboPopup. * * @return DOCUMENT ME! */ private Dialog getDialog() { Container parent; for (parent = comboBox.getParent(); (parent != null) && !(parent instanceof Dialog) && !(parent instanceof Window); parent = parent.getParent()) { ; } if (parent instanceof Dialog) { return (Dialog)parent; } else { return null; } } /** * Copied directly from BasicComboPopup. * * @return DOCUMENT ME! */ private boolean inModalDialog() { return (getDialog() != null); } } /** * Grabs mousePressed events to capture the appropriate node for this ComboBox. * * @version $Revision$, $Date$ */ class EventGrabber extends MouseAdapter { //~ Methods ------------------------------------------------------------ @Override public void mousePressed(final MouseEvent me) { if (me instanceof MouseEvent) { setCurrentEvent((MouseEvent)me); } } } }