/*
* @(#)AbstractFigure.java
*
* Project: JHotdraw - a GUI framework for technical drawings
* http://www.jhotdraw.org
* http://jhotdraw.sourceforge.net
* Copyright: ?by the original author(s) and all contributors
* License: Lesser GNU Public License (LGPL)
* http://www.opensource.org/licenses/lgpl-license.html
*/
package research;
import research.util.Geom;
import research.util.ColorMap;
import java.awt.*;
import java.util.*;
import java.io.*;
import research.store.StorableOutput;
import research.store.StorableInput;
import research.connector.ChopBoxConnector;
import research.connector.Connector;
import javax.swing.*;
/**
* AbstractFigure provides default implementations for
* the Figure interface.
*
* <hr>
* <b>Design Patterns</b><P>
* <img src="images/red-ball-small.gif" width=6 height=6 alt=" o ">
* <b><a href=../pattlets/sld036.htm>Template Method</a></b><br>
* Template Methods implement default and invariant behavior for
* figure subclasses.
* <hr>
*
* @see research.Figure
* @see research.Handle
*
* @version <$CURRENT_VERSION$>
*/
public abstract class AbstractFigure extends AttributeFigure {
/**
* The listeners for a figure's changes.
* @see #invalidate
* @see #changed
* @see #willChange
*/
protected transient FigureChangeListener fListener;
protected Connector[] connector = null;
/*
* Serialization support.
*/
private static final long serialVersionUID = -10857585979273442L;
private int abstractFigureSerializedDataVersion = 1;
public ActionMap getActionMap(){
return null;
}
public void setAttribute(String name, Object value){
this.willChange();
super._setAttribute(name, value);
this.changed();
}
/**
* Moves the figure by the given offset.
*/
public void moveBy(int dx, int dy) {
willChange();
basicMoveBy(dx, dy);
changed();
}
/**
* Moves the figure. This is the
* method that subclassers override. Clients usually
* call displayBox.
* @see #moveBy
*/
protected abstract void basicMoveBy(int dx, int dy);
/**
* Changes the display box of a figure. Clients usually
* call this method. It changes the display box
* and announces the corresponding change.
* @param origin the new origin
* @param corner the new corner
* @see #getDisplayBox
*/
public void setDisplayBox(Point origin, Point corner) {
willChange();
basicDisplayBox(origin, corner);
changed();
}
/**
* Sets the display box of a figure. This is the
* method that subclassers override. Clients usually
* call displayBox.
* @see #getDisplayBox
*/
public abstract void basicDisplayBox(Point origin, Point corner);
/**
* Gets the display box of a figure.
*/
public abstract Rectangle getDisplayBox();
/**
* Gets the display box of a figure.
*/
public abstract Rectangle getDisplayBox(Rectangle r);
/**
* Returns the handles of a Figure that can be used
* to manipulate some of its attributes.
* @return a Vector of handles
*/
public abstract Vector handles();
/**
* Returns an Enumeration of the figures contained in this figure.
* @see CompositeFigure
*/
public FigureEnumeration getFigures() {
Vector figures = new Vector(1);
figures.addElement(this);
return new FigureEnumerator(figures);
}
/**
* Gets the size of the figure. A convenience method.
*/
public Dimension size() {
Rectangle rect = getDisplayBox();
return new Dimension(rect.width, rect.height);
}
/**
* Checks if the figure is empty. The default implementation returns
* true if the width or height of its display box is < 3
* @see Figure#isEmpty
*/
public boolean isEmpty() {
Dimension size = size();
return (size.width < 3) || (size.height < 3);
}
/**
* Returns the figure that contains the given point.
* In contrast to containsPoint it returns its
* innermost figure that contains the point.
*
* @see #containsPoint
*/
public Figure findFigureInside(int x, int y) {
if (containsPoint(x, y)) {
return this;
}
return null;
}
/**
* Checks if a point is inside the figure.
*/
public boolean containsPoint(int x, int y) {
return getDisplayBox().contains(x, y);
}
/**
* Changes the display box of a figure. This is a
* convenience method. Implementors should only
* have to override _setDisplayBox
* @see #getDisplayBox
*/
public void setDisplayBox(Rectangle r) {
setDisplayBox(new Point(r.x, r.y), new Point(r.x+r.width, r.y+r.height));
}
/**
* Checks whether the given figure is contained in this figure.
*/
public boolean includes(Figure figure) {
return figure == this;
}
/**
* Draws the figure in the given graphics. Draw is a template
* method calling drawBackground followed by drawFrame.
*/
public void draw(Graphics g) {
//added by zhangwei
boolean visible = FigureHelper.isVisible(this);
if (!visible)
return;
Color fill = getFillColor();
Color oldColor = g.getColor();
if (!ColorMap.isTransparent(fill)) {
g.setColor(fill);
drawBackground(g);
}
Color frame = getFrameColor();
if (!ColorMap.isTransparent(frame)) {
g.setColor(frame);
drawFrame(g);
}
g.setColor(oldColor);
drawContent(g);
boolean connectorVisible = FigureHelper.isConnectorVisible(this);
if(connectorVisible){
Connector[] array = this.getConnectors();
for(int i = 0; i < array.length; i++){
array[i].draw(g);
}
}
}
/**
* Draws the background of the figure.
* @see #draw
*/
protected void drawBackground(Graphics g) {
}
/**
* Draws the frame of the figure.
* @see #draw
*/
protected void drawFrame(Graphics g) {
}
/**
*
*/
protected void drawContent(Graphics g) {
}
/**
* Gets the fill color of a figure. This is a convenience
* method.
* @see #getAttribute
*/
protected Color getFillColor() {
return (Color) getAttribute("fillColor");
}
/**
* Gets the frame color of a figure. This is a convenience
* method.
* @see #getAttribute
*/
protected Color getFrameColor() {
return (Color) getAttribute("frameColor");
}
protected Color getTextColor() {
return (Color) getAttribute("textColor");
}
/**
* Decomposes a figure into its parts. It returns a Vector
* that contains itself.
* @return an Enumeration for a Vector with itself as the
* only element.
*/
public FigureEnumeration decompose() {
Vector figures = new Vector(1);
figures.addElement(this);
return new FigureEnumerator(figures);
}
/**
* Sets the Figure's container and registers the container
* as a figure change listener. A figure's container can be
* any kind of FigureChangeListener. A figure is not restricted
* to have a single container.
*/
public void addToContainer(FigureChangeListener c) {
addFigureChangeListener(c);
invalidate();
}
/**
* Removes a figure from the given container and unregisters
* it as a change listener.
*/
public void removeFromContainer(FigureChangeListener c) {
invalidate();
removeFigureChangeListener(c);
}
/**
* Adds a listener for this figure.
*/
public void addFigureChangeListener(FigureChangeListener l) {
fListener = FigureChangeEventMulticaster.add(fListener, l);
}
/**
* Removes a listener for this figure.
*/
public void removeFigureChangeListener(FigureChangeListener l) {
fListener = FigureChangeEventMulticaster.remove(fListener, l);
}
/**
* Gets the figure's listners.
*/
public FigureChangeListener listener() {
return fListener;
}
/**
* A figure is released from the drawing. You never call this
* method directly. Release notifies its listeners.
* @see Figure#release
*/
public void release() {
if (fListener != null) {
fListener.figureRemoved(new FigureChangeEvent(this));
}
}
/**
* Invalidates the figure. This method informs the listeners
* that the figure's current display box is invalid and should be
* refreshed.
*/
public void invalidate() {
if (fListener != null) {
Rectangle r = getDisplayBox();
r.grow(Handle.HANDLESIZE/2 + 1, Handle.HANDLESIZE/2 + 1);
fListener.figureInvalidated(new FigureChangeEvent(this, r));
}
}
/**
* Informes that a figure is about to change such that its
* display box is affected.
* Here is an example of how it is used together with changed()
* <pre>
* public void move(int x, int y) {
* willChange();
* // change the figure's location
* changed();
* }
* </pre>
* @see #invalidate
* @see #changed
*/
public void willChange() {
invalidate();
}
/**
* Informes that a figure has changed its display box.
* This method also triggers an update call for its
* registered observers.
* @see #invalidate
* @see #willChange
*
*/
public void changed() {
invalidate();
if (fListener != null) {
fListener.figureChanged(new FigureChangeEvent(this));
}
}
/**
* Gets the center of a figure. A convenice
* method that is rarely overridden.
*/
public Point center() {
return Geom.center(getDisplayBox());
}
/**
* Returns the connection inset. The connection inset
* defines the area where the display box of a
* figure can't be connected. By default the entire
* display box can be connected.
*
*/
public Insets connectionInsets() {
return new Insets(0, 0, 0, 0);
}
/**
* Returns the Figures connector for the specified location.
* By default a ChopBoxConnector is returned.
*/
public Connector connectorAt(int x, int y) {
Connector[] array = getConnectors();
Connector rv = null;
for(int i = 0; i < array.length; i++){
if(array[i].containsPoint(x,y)){
rv = array[i];
break;
}
}
return rv;
}
public Connector[] getConnectors(){
if(connector == null){
connector = new Connector[1];
connector[0] = new ChopBoxConnector(this);
}
return connector;
}
/**
* Returns the locator used to located connected text.
*/
public Locator connectedTextLocator(Figure text) {
return RelativeLocator.center();
}
public void write(StorableOutput dw) {
super.write(dw);
Connector[] array = getConnectors();
int connectorNum = 0;
if(array != null){
connectorNum = array.length;
}
dw.writeInt(connectorNum);
for(int i = 0; i < connectorNum; i++){
dw.writeStorable(array[i]);
}
}
public void read(StorableInput dr) throws IOException {
super.read(dr);
int connectorNum = dr.readInt();
connector = new Connector[connectorNum];
for(int i = 0; i < connectorNum; i++){
connector[i] = (Connector) dr.readStorable();
}
}
}