/*******************************************************************************
* Copyright (c) 2006-2012
* Software Technology Group, Dresden University of Technology
* DevBoost GmbH, Berlin, Amtsgericht Charlottenburg, HRB 140026
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Software Technology Group - TU Dresden, Germany;
* DevBoost GmbH - Berlin, Germany
* - initial API and implementation
******************************************************************************/
/*
* @(#)AbstractFigure.java 6.0 2000-02-13
*
* Copyright (c) 1996-2008 by the original authors of JHotDraw
* and all its contributors.
* All rights reserved.
*
* The copyright of this software is owned by the authors and
* contributors of the JHotDraw project ("the copyright holders").
* You may not use, copy or modify this software, except in
* accordance with the license agreement you entered into with
* the copyright holders. For details see accompanying license terms.
*/
package org.jhotdraw.draw;
import org.jhotdraw.beans.AbstractBean;
import org.jhotdraw.util.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.font.*;
import java.awt.geom.*;
import java.util.*;
import javax.swing.*;
import javax.swing.undo.*;
import java.io.*;
import org.jhotdraw.geom.*;
/**
* AbstractFigure provides the functionality for managing listeners
* for a Figure.
*
*
* @author Werner Randelshofer
* @version 7.0 2008-02-13 Huw Jones: Added methods to support
* Figure.isTransformable().
* <br>5.1 2007-12-19 Method invalidate only fires an areInvalidated
* event, when the Figure is part of a Drawing.
* <br>5.0 2007-07-17 Extends from AbstractBean.
* <br>4.0 2007-05-18 Removed addUndoableEditListener and
* removeUndoableEditListener, isConnectorsVisible, setConnectorsVisible
* methods due to changes in Figure interface.
* <br>3.4 2007-02-09 Method fireFigureHandlesChanged added.
* <br>3.3 Reworked.
* <br>3.2 2006-01-05 Added method getChangingDepth().
* <br>3.0 2006-01-20 Reworked for J2SE 1.5.
* <br>1.0 2003-12-01 Derived from JHotDraw 5.4b1.
*/
public abstract class AbstractFigure
extends AbstractBean
implements Figure {
protected EventListenerList listenerList = new EventListenerList();
private Drawing drawing;
private boolean isSelectable = true;
private boolean isRemovable = true;
private boolean isVisible = true;
private boolean isTransformable = true;
/**
* This variable is used to prevent endless change loops.
* We increase its value on each invocation of willChange() and
* decrease it on each invocation of changed().
*/
protected int changingDepth = 0;
/** Creates a new instance. */
public AbstractFigure() {
}
// DRAWING
// SHAPE AND BOUNDS
// ATTRIBUTES
// EDITING
// CONNECTING
// COMPOSITE FIGURES
// CLONING
// EVENT HANDLING
public void addFigureListener(FigureListener l) {
listenerList.add(FigureListener.class, l);
}
public void removeFigureListener(FigureListener l) {
listenerList.remove(FigureListener.class, l);
}
public void addNotify(Drawing d) {
this.drawing = d;
fireFigureAdded();
}
public void removeNotify(Drawing d) {
fireFigureRemoved();
this.drawing = null;
}
protected Drawing getDrawing() {
return drawing;
}
protected Object getLock() {
return (getDrawing() == null) ? this : getDrawing().getLock();
}
/**
* Notify all listenerList that have registered interest for
* notification on this event type.
*/
public void fireAreaInvalidated() {
fireAreaInvalidated(getDrawingArea());
}
/**
* Notify all listenerList that have registered interest for
* notification on this event type.
*/
protected void fireAreaInvalidated(Rectangle2D.Double invalidatedArea) {
if (listenerList.getListenerCount() > 0) {
FigureEvent event = null;
// Notify all listeners that have registered interest for
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == FigureListener.class) {
// Lazily create the event:
if (event == null) {
event = new FigureEvent(this, invalidatedArea);
}
((FigureListener) listeners[i + 1]).areaInvalidated(event);
}
}
}
}
/**
* Notify all listenerList that have registered interest for
* notification on this event type.
*/
protected void fireAreaInvalidated(FigureEvent event) {
// Notify all listeners that have registered interest for
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == FigureListener.class) {
((FigureListener) listeners[i + 1]).areaInvalidated(event);
}
}
}
/**
* Notify all listenerList that have registered interest for
* notification on this event type.
*/
protected void fireFigureRequestRemove() {
if (listenerList.getListenerCount() > 0) {
FigureEvent event = null;
// Notify all listeners that have registered interest for
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == FigureListener.class) {
// Lazily create the event:
if (event == null) {
event = new FigureEvent(this, getBounds());
}
((FigureListener) listeners[i + 1]).figureRequestRemove(event);
}
}
}
}
/**
* Notify all listenerList that have registered interest for
* notification on this event type.
*/
protected void fireFigureAdded() {
if (listenerList.getListenerCount() > 0) {
FigureEvent event = null;
// Notify all listeners that have registered interest for
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == FigureListener.class) {
// Lazily create the event:
if (event == null) {
event = new FigureEvent(this, getBounds());
}
((FigureListener) listeners[i + 1]).figureAdded(event);
}
}
}
}
/**
* Notify all listenerList that have registered interest for
* notification on this event type.
*/
protected void fireFigureRemoved() {
if (listenerList.getListenerCount() > 0) {
FigureEvent event = null;
// Notify all listeners that have registered interest for
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == FigureListener.class) {
// Lazily create the event:
if (event == null) {
event = new FigureEvent(this, getBounds());
}
((FigureListener) listeners[i + 1]).figureRemoved(event);
}
}
}
}
public void fireFigureChanged() {
fireFigureChanged(getBounds());
}
/**
* Notify all listenerList that have registered interest for
* notification on this event type.
*/
protected void fireFigureChanged(Rectangle2D.Double changedArea) {
if (listenerList.getListenerCount() > 0) {
FigureEvent event = null;
// Notify all listeners that have registered interest for
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == FigureListener.class) {
// Lazily create the event:
if (event == null) {
event = new FigureEvent(this, changedArea);
}
((FigureListener) listeners[i + 1]).figureChanged(event);
}
}
}
}
protected void fireFigureChanged(FigureEvent event) {
if (listenerList.getListenerCount() > 0) {
// Notify all listeners that have registered interest for
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == FigureListener.class) {
// Lazily create the event:
((FigureListener) listeners[i + 1]).figureChanged(event);
}
}
}
}
/**
* Notify all listenerList that have registered interest for
* notification on this event type.
*/
protected void fireAttributeChanged(AttributeKey attribute, Object oldValue, Object newValue) {
if (listenerList.getListenerCount() > 0 &&
(oldValue == null || newValue == null || !oldValue.equals(newValue))) {
FigureEvent event = null;
// Notify all listeners that have registered interest for
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == FigureListener.class) {
// Lazily create the event:
if (event == null) {
event = new FigureEvent(this, attribute, oldValue, newValue);
}
((FigureListener) listeners[i + 1]).attributeChanged(event);
}
}
}
}
/**
* Notify all listenerList that have registered interest for
* notification on this event type.
*/
protected void fireFigureHandlesChanged() {
Rectangle2D.Double changedArea = getDrawingArea();
if (listenerList.getListenerCount() > 0) {
FigureEvent event = null;
// Notify all listeners that have registered interest for
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == FigureListener.class) {
// Lazily create the event:
if (event == null) {
event = new FigureEvent(this, changedArea);
}
((FigureListener) listeners[i + 1]).figureHandlesChanged(event);
}
}
}
}
/**
* Notify all UndoableEditListener of the Drawing, to which this Figure has
* been added to. If this Figure is not part of a Drawing, the event is
* lost.
*/
protected void fireUndoableEditHappened(UndoableEdit edit) {
if (getDrawing() != null) {
getDrawing().fireUndoableEditHappened(edit);
}
}
/*
public Set createHandles() {
return new HashSet();
}
*/
@Override
public AbstractFigure clone() {
AbstractFigure that = (AbstractFigure) super.clone();
that.listenerList = new EventListenerList();
that.drawing = null; // Clones need to be explictly added to a drawing
return that;
}
public final AbstractFigure basicClone(HashMap<Figure, Figure> oldToNew) {
// XXX - Delete me
return null;
}
public void remap(Map<Figure, Figure> oldToNew, boolean disconnectIfNotInMap) {
}
public Collection<Handle> createHandles(int detailLevel) {
LinkedList<Handle> handles = new LinkedList<Handle>();
switch (detailLevel) {
case -1:
handles.add(new BoundsOutlineHandle(this,false,true));
break;
case 0:
ResizeHandleKit.addResizeHandles(this, handles);
break;
}
return handles;
}
public Cursor getCursor(Point2D.Double p) {
if (contains(p)) {
return Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
} else {
return Cursor.getDefaultCursor();
}
}
public final void setBounds(Rectangle2D.Double bounds) {
setBounds(new Point2D.Double(bounds.x, bounds.y), new Point2D.Double(bounds.x + bounds.width, bounds.y + bounds.height));
}
public void setBounds(Point2D.Double anchor, Point2D.Double lead) {
Point2D.Double oldAnchor = getStartPoint();
Point2D.Double oldLead = getEndPoint();
if (!oldAnchor.equals(anchor) || !oldLead.equals(lead)) {
willChange();
setBounds(anchor, lead);
changed();
fireUndoableEditHappened(new SetBoundsEdit(this, oldAnchor, oldLead, anchor, lead));
}
}
/**
* Checks if this figure can be connected. By default
* AbstractFigures can be connected.
*/
public boolean canConnect() {
return true;
}
/**
* Invalidates cached data of the Figure.
* This method must execute fast, because it can be called very often.
*/
protected void invalidate() {
}
protected boolean isChanging() {
return changingDepth != 0;
}
protected int getChangingDepth() {
return changingDepth;
}
/**
* Informs that a figure is about to change something that
* affects the contents of its display box.
*/
public void willChange() {
if (changingDepth == 0) {
fireAreaInvalidated();
invalidate();
}
changingDepth++;
}
protected void validate() {
}
/**
* Informs that a figure changed the area of its display box.
*/
public void changed() {
if (changingDepth == 1) {
validate();
fireFigureChanged(getDrawingArea());
} else if (changingDepth < 0) {
throw new InternalError("changed was called without a prior call to willChange.");
}
changingDepth--;
}
/**
* Returns the Figures connector for the specified location.
* By default a ChopBoxConnector is returned.
*
*
* @see ChopRectangleConnector
*/
public Connector findConnector(Point2D.Double p, ConnectionFigure prototype) {
return new ChopRectangleConnector(this);
}
public boolean includes(Figure figure) {
return figure == this;
}
public Figure findFigureInside(Point2D.Double p) {
return (contains(p)) ? this : null;
}
public Connector findCompatibleConnector(Connector c, boolean isStart) {
return new ChopRectangleConnector(this);
}
/**
* Returns a collection of actions which are presented to the user
* in a popup menu.
* <p>The collection may contain null entries. These entries are used
* interpreted as separators in the popup menu.
*/
public Collection<Action> getActions(Point2D.Double p) {
return Collections.emptyList();
}
/**
* Returns a specialized tool for the given coordinate.
* <p>Returns null, if no specialized tool is available.
*/
public Tool getTool(Point2D.Double p) {
return null;
}
/**
* Handles a mouse click.
*/
public boolean handleMouseClick(Point2D.Double p, MouseEvent evt, DrawingView view) {
return false;
}
public boolean handleDrop(Point2D.Double p, Collection<Figure> droppedFigures, DrawingView view) {
return false;
}
public Point2D.Double getEndPoint() {
Rectangle2D.Double r = getBounds();
return new Point2D.Double(r.x + r.width, r.y + r.height);
}
public Point2D.Double getStartPoint() {
Rectangle2D.Double r = getBounds();
return new Point2D.Double(r.x, r.y);
}
/*
public Rectangle2D.Double getHitBounds() {
return getBounds();
}
*/
public Dimension2DDouble getPreferredSize() {
Rectangle2D.Double r = getBounds();
return new Dimension2DDouble(r.width, r.height);
}
public boolean isSelectable() {
return isSelectable;
}
public void setSelectable(boolean newValue) {
boolean oldValue = isSelectable;
isSelectable = newValue;
firePropertyChange("selectable", oldValue, newValue);
}
public boolean isRemovable() {
return isRemovable;
}
public void setRemovable(boolean newValue) {
boolean oldValue = isRemovable;
isRemovable = newValue;
firePropertyChange("removable", oldValue, newValue);
}
public boolean isTransformable() {
return isTransformable;
}
public void setTransformable(boolean newValue) {
boolean oldValue = isTransformable;
isTransformable = newValue;
firePropertyChange("transformable", oldValue, newValue);
}
public boolean isVisible() {
return isVisible;
}
public void setVisible(boolean newValue) {
if (newValue != isVisible) {
willChange();
isVisible = newValue;
changed();
}
}
public Collection<Figure> getDecomposition() {
LinkedList<Figure> list = new LinkedList<Figure>();
list.add(this);
return list;
}
protected FontRenderContext getFontRenderContext() {
FontRenderContext frc = null;
if (frc == null) {
frc = new FontRenderContext(new AffineTransform(), Options.isTextAntialiased(), Options.isFractionalMetrics());
}
return frc;
}
public void requestRemove() {
fireFigureRequestRemove();
}
public int getLayer() {
return 0;
}
public String getToolTipText(Point2D.Double p) {
return null;
}
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append(getClass().getName().substring(getClass().getName().lastIndexOf('.') + 1));
buf.append('@');
buf.append(hashCode());
return buf.toString();
}
public Collection<Connector> getConnectors(ConnectionFigure prototype) {
LinkedList<Connector> connectors = new LinkedList<Connector>();
connectors.add(new ChopRectangleConnector(this));
return connectors;
}
}