package jsetool.gui; import java.awt.*; import java.awt.event.MouseEvent; import java.awt.font.FontRenderContext; import java.awt.geom.Rectangle2D; import java.io.*; import java.util.ArrayList; import javax.swing.JOptionPane; /** * @author Mike Lawson * * @since Oct 23, 2004 * * The LabeledState is a simply a state symbol that has a label on it. * */ public class LabeledState extends Rectangle2D.Double implements Drawable, Externalizable { /** The default height */ public static final int DEFAULT_HEIGHT = 75; /** The default width */ public static final int DEFAULT_WIDTH = 150; protected boolean objectIsSelected; protected String stateLabel; private boolean beingDragged = false; private transient Stroke beingDraggedStroke = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[] { 5, 5 }, 0); private double deltaX; private double deltaY; private Paint highlightPaint; private boolean isEditable = true; private boolean isHighlighted = false; private Paint normalPaint; private ArrayList associatedDrawables = new ArrayList(); private boolean toBeDeleted; private boolean isExplored = false; /** * Constructs a LabeledState with no label. */ public LabeledState() { this(""); } /** * Constructs a new GlobalState view object for the given ExplorationState * * @param label */ public LabeledState(String label) { setStateLabel(label); setStateColors(); } /** * Associates the given drawable object with this one * * @param drawable */ public void addAssociatedDrawable(Drawable drawable) { associatedDrawables.add(drawable); } /** * Breaks the association to the given drawable object. * * @param drawable */ public void removeAssociatedDrawable(Drawable drawable) { associatedDrawables.remove(drawable); } /** * Returns all associated drawable objects * * @return ArrayList */ public ArrayList getAssociatedDrawable() { return associatedDrawables; } /** * Deletes this state and all associated edges. * */ public void objectIsBeingDeleted() { // Set this object to be deleted toBeDeleted = true; // Set associated objects to be deleted. ArrayList cloneOfAssociatedDrawables = (ArrayList) associatedDrawables.clone(); for (int i = 0; i < cloneOfAssociatedDrawables.size(); i++) { Drawable d = (Drawable) cloneOfAssociatedDrawables.get(i); d.objectIsBeingDeleted(); } } /** * Externalizable implementation. */ public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { stateLabel = (String) in.readObject(); isEditable = in.readBoolean(); isExplored = in.readBoolean(); double x = in.readDouble(); double y = in.readDouble(); double w = in.readDouble(); double h = in.readDouble(); associatedDrawables = (ArrayList) in.readObject(); setRect(x, y, w, h); setStateColors(); } /** * Externalizable implementation. */ public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(stateLabel); out.writeBoolean(isEditable); out.writeBoolean(isExplored); out.writeDouble(getX()); out.writeDouble(getY()); out.writeDouble(getWidth()); out.writeDouble(getHeight()); out.writeObject(associatedDrawables); } /** * Returns true if objectIsBeingDeleted has been called. * * @return boolean */ public boolean toBeDeleted() { return toBeDeleted; } /** * Draws this object on the given graphics context. * * @param g */ public void draw(Graphics g) { Graphics2D g2d = (Graphics2D) g; drawInternal(g2d); } /** * Returns true if this object has been set to be highlighted * * @return boolean */ public boolean getIsHighlighted() { return isHighlighted; } /** * Gets the state label * * @return String * */ public String getStateLabel() { return stateLabel; } /** * Returns true if the given mouse event is on top of this object. * * @param me * @return boolean true if the mouse is on top of this object. */ public boolean isMouseOn(MouseEvent me) { return contains(me.getX(), me.getY()); } /** * Called by the encapsulating drawing pane whenever the mouse is pressed on * this object. * * @param me * the event that caused the callback. */ public void mousePressed(MouseEvent me) { deltaX = Math.abs(getX() - me.getX()); deltaY = Math.abs(getY() - me.getY()); beingDragged = true; } /** * Called by the encapsulating drawing pane whenever the mouse is released off * of this object. * * @param me * the event that caused the callback. */ public void mouseReleased(MouseEvent me) { deltaX = 0; deltaY = 0; beingDragged = false; } /** * Moves this global state to the coordinates in the given mouse event * * @param me * */ public void mouseDragged(MouseEvent me) { double newX = me.getX() - deltaX; double newY = me.getY() - deltaY; // Don't let the user drag the object so that it is out of // positive bounds of the display. Stop the drag // at either the left side or the top. Otherwise, // we'll have to define a new origin and transform all the // objects on the display. if (newX < 0) newX = 0; if (newY < 0) newY = 0; this.setFrame(newX, newY , getWidth(), getHeight()); } /** * Sets whether this object is editable or not, based on the value. * * Implementations should cache this value and take appropriate actions when * showEditor is called (if ever). * * An example is whether an object will let you change its label. * * @param isEditable */ public void setEditable(boolean isEditable) { this.isEditable = isEditable; } /** * Turns on or off the highlight for this global state. * * If this global state has any "next" states, they are highlighted as well. * * @param doHighlight * */ public void setHighlight(boolean doHighlight) { isHighlighted = doHighlight; } /** * Sets the paint for the normal color to that given. * * @param paint */ public void setNormalPaint(Color paint) { normalPaint = paint; } /** * Called by the encapsulating drawing pane whenever the mouse is clicked on * the object. This object should set itself as "selected." * * @param me * the event that caused the callback. * */ public void setSelected(boolean isSelected, MouseEvent me) { objectIsSelected = isSelected; } /** * Sets the label of the state. * * @param label * */ public void setStateLabel(String label) { stateLabel = label; } /** * Called when the user has selected to edit something about the Drawable * object, e.g. change it's label. * * @param me * the event that caused the callback */ public void showEditor(MouseEvent me) { if (isEditable == true) { promptForStateLabel(); } } /** * Draws squares to highlight this object * * @param g2d */ protected void drawHighlightSquares(Graphics2D g2d) { double x = getX(); double y = getY(); double w = getWidth(); double h = getHeight(); g2d.setPaint(Color.ORANGE); g2d.fill(new Rectangle.Double(x - 3, y - 3, 6, 6)); g2d.fill(new Rectangle.Double(x + w / 2 - 3, y - 3, 6, 6)); g2d.fill(new Rectangle.Double(x + w - 3, y - 3, 6, 6)); g2d.fill(new Rectangle.Double(x - 3, y + h / 2 - 3, 6, 6)); g2d.fill(new Rectangle.Double(x + w - 3, y + h / 2 - 3, 6, 6)); g2d.fill(new Rectangle.Double(x - 3, y + h - 3, 6, 6)); g2d.fill(new Rectangle.Double(x + w / 2 - 3, y + h - 3, 6, 6)); g2d.fill(new Rectangle.Double(x + w - 3, y + h - 3, 6, 6)); } /** * An internal method to render this state object on the given graphics * context. * * @param g2d */ protected void drawInternal(Graphics2D g2d) { g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // Determine the size of the rendered state label. // This can only be done with a valid Graphics object, // so we must do it here each time as the graphics context // may change dynamically. Font font = new Font("Serif", Font.PLAIN, 20); g2d.setFont(font); FontRenderContext frc = g2d.getFontRenderContext(); Rectangle2D textBounds = g2d.getFont().getStringBounds(stateLabel, frc); // Enlarge the state if the string is bigger than the global state. // This means that the user will have to drag and drop to be able // to read the display. if (textBounds.getWidth() > this.getWidth() - 20) { this.setFrame(getX(), getY(), textBounds.getWidth() + 10, getHeight()); } // Draw the rectangle, if the rectangle is being dragged // draw it using a different stroke so it looks cool. g2d.setPaint(getPaintColor()); if (beingDragged == true) { // In order to draw JUST this state with a different stroke // we need to duplicate the original graphics context unique for this // object only. If we don't, the ENTIRE display will be drawn in the // new stroke for the duration of the drag, which is kind of entertaining // but could disturb users. Graphics2D foo = (Graphics2D) g2d.create(); foo.setStroke(beingDraggedStroke); foo.draw(this); } else { g2d.draw(this); } if (objectIsSelected) { drawHighlightSquares(g2d); } // Compute the X and Y where the label string will be drawn. double stringX = getX() + getWidth() / 2 - textBounds.getWidth() / 2; double stringY = getY() + getHeight() / 2; g2d.setPaint(getPaintColor()); g2d.drawString(stateLabel, (float) stringX, (float) stringY); } /** * Returns either the normal paint color or the highlight paint color, based * on the internal flag. Subclasses may override this to provide unique colors * as necessary. * * @return Paint */ protected Paint getPaintColor() { if (isHighlighted) { return highlightPaint; } else { return normalPaint; } } /** * Sets up the colors that this state will be drawn. * */ protected void setStateColors() { highlightPaint = Color.BLUE; setNormalPaint(Color.BLACK); } /** * Prompts the user for a state label. */ private void promptForStateLabel() { String temp = JOptionPane.showInputDialog(null, "Enter new label for state", stateLabel); if (temp != null) { stateLabel = temp.trim(); } } /* * (non-Javadoc) * * @see jsetool.gui.Drawable#setExplored(boolean) */ public void setExplored(boolean b) { isExplored = b; } /** * @return */ public boolean isExplored() { return isExplored; } public String toString() { return stateLabel; } }