package jsetool.gui; import java.awt.*; import java.awt.event.MouseEvent; import java.io.*; import java.io.Externalizable; import java.util.ArrayList; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.event.MouseInputListener; /** * @author Mike Lawson * * @since Oct 24, 2004 * * The DrawingPane class is a generic pane on which DrawingObjects are drawn and * managed. This class provides the basics for a drawing pane, including * primitives to handle dragging around on the pane. * * */ public class DrawingPane extends JPanel implements MouseInputListener, Externalizable { protected double displayHeight; protected double displayWidth; // This is the object that is currently being dragged. protected Drawable draggingObject; protected boolean isDragging = true; // This is the object that is current set as selected protected Drawable selectedObject; private ArrayList graphicsComponents = new ArrayList(); protected Dimension area = new Dimension(0, 0); /** * Constructs an empty DrawingPane */ public DrawingPane() { addMouseListener(this); addMouseMotionListener(this); setBackground(Color.WHITE); } /** * Constructs a drawing pane with the given width and height * * @param width * @param height */ public DrawingPane(double width, double height) { super(); this.displayWidth = width; this.displayHeight = height; setPreferredSize(new Dimension((int) displayWidth, (int) displayHeight)); } /** * Adds the given drawing object to the set of known graphics objects. * * @param obj */ public void addDrawingObject(Drawable obj) { graphicsComponents.add(obj); } /** * Returns an Iterator over the graphics components. * * @return Iterator */ public ArrayList getGraphicsComponents() { return graphicsComponents; } /** * Returns the current selected object on the pane. * * @return Drawable */ public Drawable getSelectedObject() { return selectedObject; } // // MouseListener implementation // /** * Called when a user clicks on the drawing pane itself. Subclass may extend * this without a specific call to super(). * * @param e * the mouse event that generated this call back. */ public void mouseClicked(MouseEvent e) { // Select the object no matter what. setSelectedObject(e); // Take actions based on which button is pressed. switch (e.getButton()) { case MouseEvent.BUTTON1: // If double-clicked, show the object's editor. if (selectedObject != null && e.getClickCount() >= 2) { selectedObject.showEditor(e); } break; case MouseEvent.BUTTON3: JPopupMenu popup = getPopupMenu(e); if (popup != null) { popup.show(this, e.getX(), e.getY()); } break; default: break; } // switch on button } /** * Subclasses may return a popup menu if desired. * * @param e * @return JPopupMenu */ protected JPopupMenu getPopupMenu(MouseEvent e) { // Default action is to not show a popup. return null; } /** * Called when a user drags the mouse on the drawing pane. * * If there is a drawing object selected, it will be told to move to the mouse * event. * * Subclass MUST call super() if the extend this method! * * @param e * the mouse event that generated this callback. */ public void mouseDragged(MouseEvent e) { // Don't propagate dragged events if dragging is disabled. if (isDragging == false) { return; } if (draggingObject != null) { draggingObject.mouseDragged(e); repaint(); } } /** * Called when the mouse enters the drawing pane. Subclass may extend this * without a specific call to super(). * * @param e * the mouse event that generated this callback. */ public void mouseEntered(MouseEvent e) { } /** * Called when the mouse exists the drawing pane. Subclass may extend this * without a specific call to super(). * * @param e * the mouse event that generated this call back. */ public void mouseExited(MouseEvent e) { } /** * Called when a user moves the mouse on the drawing pane (but not dragging * it). * * If there is a drawing object underneath the mouse, it will be highlighted. * * Subclass MUST call super() if the extend this method! * * @param e * the mouse event that generated this callback. */ public void mouseMoved(MouseEvent e) { for (int i = 0; i < graphicsComponents.size(); i++) { Drawable mad = (Drawable) graphicsComponents.get(i); mad.setHighlight(mad.isMouseOn(e)); } repaint(); } /** * Called when a user presses the mouse on the drawing pane. If there is a * drawing object underneath the mouse press, it will be selected. * * Subclass MUST call super() if the extend this method! * * @param e * the mouse event that generated this callback. */ public void mousePressed(MouseEvent e) { // The selected object should be the one that the user has also // decided to drag. setSelectedObject(e); draggingObject = getObjectMouseIsOn(e); setSelectedObject(draggingObject); if (draggingObject != null && isDragging == true) draggingObject.mousePressed(e); } /** * Called when a user releases the mouse on the drawing pane. * * If a drawing object had previously been selected, it's mouse released * method will be called. * * Subclass MUST call super() if the extend this method! * * @param e * the mouse event that generated this callback. */ public void mouseReleased(MouseEvent e) { if ((draggingObject != null) && (isDragging == true)) { draggingObject.mouseReleased(e); } draggingObject = null; // Reconfigure the preferred size in case the action should // cause a scroll pane to recompute it's display. try { reconfigurePreferredSize(); } catch (IndexOutOfBoundsException iobe) { // Do nothing here, this just means that there isn't anything // on the drawing yet. } // Repaint the display to reflect any changes from a // mouse drag or whatever. repaint(); } /** * Paints this drawing pane on the given graphics context * * @param g * A graphics object. */ public void paint(Graphics g) { Graphics2D graphics2d = (Graphics2D) g; graphics2d.clearRect(0, 0, getWidth(), getHeight()); graphics2d.setPaint(getBackground()); graphics2d.fillRect(0, 0, getWidth(), getHeight()); drawGraphicComponents(graphics2d); } /** * Removes the given drawing object from the set of known objects. * * @param obj * */ public void removeDrawingObject(Drawable obj) { graphicsComponents.remove(obj); reconfigurePreferredSize(); } /** * @param cursor * The cursor to be set. */ public void setCursor(Cursor cursor) { super.setCursor(cursor); } /** * Called to disable dragging based on mouse-press/mouse-release events. * * @param isDragging */ public void setIsDragging(boolean isDragging) { this.isDragging = isDragging; } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { area = (Dimension)in.readObject(); displayHeight = in.readDouble(); displayWidth = in.readDouble(); graphicsComponents = (ArrayList)in.readObject(); } public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(area); out.writeDouble(displayHeight); out.writeDouble(displayWidth); out.writeObject(graphicsComponents); } /** * Subclasses may override this method to configure itself anyway it feels. * Called from the constructor. * */ protected void configureDisplay() { } /** * Draws all known graphics components on the given 2D graphic context. * * @param graphics2d */ protected void drawGraphicComponents(Graphics2D graphics2d) { for (int i = 0; i < graphicsComponents.size(); i++) { Drawable drawingObject = (Drawable) graphicsComponents.get(i); drawingObject.draw(graphics2d); } } /** * Returns the drawing object on which the mouse is currently located. * * @param me * The mouse event that caused the callback. * * @return DrawingObject - the object on which the mouse is located or null if * none. */ protected Drawable getObjectMouseIsOn(MouseEvent me) { for (int i = 0; i < graphicsComponents.size(); i++) { Drawable mad = (Drawable) graphicsComponents.get(i); if (mad.isMouseOn(me)) { return mad; } } return null; } /** * Sets the currently selected object. * * @param e * the mouse event that caused the object to be selected. */ protected void setSelectedObject(MouseEvent e) { if (selectedObject != null) { selectedObject.setSelected(false, e); } setSelectedObject(getObjectMouseIsOn(e)); if (selectedObject != null) { selectedObject.setSelected(true, e); } } /** * Sets the selected object on the pane. * * @param selectedObject */ private void setSelectedObject(Drawable selectedObject) { this.selectedObject = selectedObject; } /** * This method reconfigures the current preferred size based on the objects on * the drawing pane. This allows a scroll pane to correctly show scroll bars. */ protected void reconfigurePreferredSize() { final int BORDER_SIZE = 25; // Find the rightmost object. Drawable rightMost = getRightMostObject(); // Find the bottommost object. Drawable bottomMost = getBottomMostObject(); boolean changed = false; // If the bottom right corner of the rightmost // drawing component is outside of the current preferred // area size, expand the area size horizontally. If // it is well within the bounds of the area, shrink horizontally. if (rightMost != null) { double rightMostX = rightMost.getX() + rightMost.getWidth(); if (rightMostX > area.width - BORDER_SIZE) { area.width = (int) (rightMostX + BORDER_SIZE); changed = true; } else if (rightMostX < area.width - BORDER_SIZE) { area.width = (int) (rightMostX); changed = true; } } // Ditto for the Y dimensions. if (bottomMost != null) { double bottomMostY = bottomMost.getY() + bottomMost.getHeight(); if (bottomMostY > area.height - BORDER_SIZE) { area.height = (int) (bottomMostY + BORDER_SIZE); changed = true; } else if (bottomMostY < area.height - BORDER_SIZE) { area.height = (int) (bottomMostY); changed = true; } } if (changed) { setPreferredSize(area); // Be sure to call revalidate so any enclosing JScrollPanes // know to recompute their displays. revalidate(); } } /** * Returns the object who's bottom-right corner is the farthest to the bottom * * @return Drawable */ private Drawable getBottomMostObject() { if (graphicsComponents.size() == 0) return null; Drawable bottomMost = (Drawable) graphicsComponents.get(0); for (int i = 0; i < graphicsComponents.size(); i++) { Drawable drawable = (Drawable) graphicsComponents.get(i); if ((drawable.getY() + drawable.getHeight()) > (bottomMost.getY() + bottomMost.getHeight())) { bottomMost = drawable; } } return bottomMost; } /** * Returns the object who's bottom-right-corner is the farthest to the right. * * @return Drawable */ private Drawable getRightMostObject() { if (graphicsComponents.size() == 0) return null; Drawable rightMost = (Drawable) graphicsComponents.get(0); for (int i = 0; i < graphicsComponents.size(); i++) { Drawable drawable = (Drawable) graphicsComponents.get(i); if ((drawable.getX() + drawable.getWidth()) > (rightMost.getX() + rightMost.getWidth())) { rightMost = drawable; } } return rightMost; } }