/* * @(#)Figure.java * * Copyright (c) 1996-2010 The authors and contributors of JHotDraw. * You may not use, copy or modify this file, except in compliance with the * accompanying license terms. */ package org.jhotdraw.draw; import org.jhotdraw.geom.Dimension2DDouble; import javax.annotation.Nullable; import org.jhotdraw.draw.tool.Tool; import org.jhotdraw.draw.connector.Connector; import org.jhotdraw.draw.handle.Handle; import org.jhotdraw.draw.event.FigureListener; import java.awt.*; import java.awt.geom.*; import java.awt.event.*; import java.beans.PropertyChangeListener; import java.util.*; import javax.swing.*; import java.io.*; /** * A <em>figure</em> is a graphical element of a {@link Drawing}. A figure * can be only in one drawing at a time. * <p> * {@code Figure} provides the following functionality: * <ul> * <li>{@code Figure} knows its bounds and it can draw itself.</li> * * <li>Figures can have an open ended set of attributes. An attribute is * identified by an {@link AttributeKey}.</li> * * <li>A figure can have {@link org.jhotdraw.draw.connector.Connector}s that define how to locate a * connection point on the figure.</li> * * <li>A figure can create a set of {@link Handle}s which can interactively * manipulate aspects of the figure.</li> * * <li>A figure can return a set of actions associated with a specific * point on the figure.</li> * * <li>A figure can be composed of other figures. If this is the case, * the object implementing the {@code Figure} interface usually also * implements the {@link CompositeFigure} interface.</li> * * <li>A figure can create a clone of itself.</li> * </ul> * Note that there are some restrictions too. Here are some things that a * figure is not allowed to do: * <ul> * <li>A figure may not access {@code DrawingView}, {@code DrawingEditor} or * {@code Tool}. The drawing framework is built on the assumption that a * drawing can be rendered at any time without the need for the creation of * views and editing tools.</li> * </ul> * * * <hr> * <b>Design Patterns</b> * * <p><em>Framework</em><br> * The following interfaces define the contracts of a framework for structured * drawing editors:<br> * Contract: {@link Drawing}, {@link Figure}, {@link DrawingView}, * {@link DrawingEditor}, {@link org.jhotdraw.draw.handle.Handle} and * {@link org.jhotdraw.draw.tool.Tool}. * * <p><em>Composite</em><br> * Composite figures can be composed of other figures.<br> * Component: {@link Figure}; Composite: {@link CompositeFigure}. * * <p><em>Framework</em><br> * Two figures can be connected using a connection figure. The location of * the start or end point of the connection is handled by a connector object * at each connected figure.<br> * Contract: {@link org.jhotdraw.draw.Figure}, * {@link ConnectionFigure}, * {@link org.jhotdraw.draw.connector.Connector}, * {@link org.jhotdraw.draw.tool.ConnectionTool}. * * <p><em>Decorator</em><br> * Decorated figures can be adorned with another figure.<br> * Component: {@link DecoratedFigure}; Decorator: {@link Figure}. * * <p><em>Observer</em><br> * State changes of figures can be observed by other objects. Specifically * {@code CompositeFigure} observes area invalidations of its child figures. And * {@code DrawingView} observers area invalidations of its drawing object.<br> * Subject: {@link Figure}; Observer: * {@link org.jhotdraw.draw.event.FigureListener}; Event: {@link org.jhotdraw.draw.event.FigureEvent}; Concrete Observer: * {@link CompositeFigure}, {@link DrawingView}. * * <p><em>Prototype</em><br> * The creation tool creates new figures by cloning a prototype figure object. * That's the reason why {@code Figure} extends the {@code Cloneable} interface. * <br> * Prototype: {@link Figure}; Client: {@link org.jhotdraw.draw.tool.CreationTool}. * * <p><em>Strategy</em><br> * The location of the start and end points of a connection figure are determined * by {@code Connector}s which are owned by the connected figures.<br> * Context: {@link Figure}, {@link ConnectionFigure}; * Strategy: {@link org.jhotdraw.draw.connector.Connector}. * * <p><em>Strategy</em><br> * {@code Locator} encapsulates a strategy for locating a point on a * {@code Figure}.<br> * Strategy: {@link org.jhotdraw.draw.locator.Locator}; Context: {@link Figure}. * <hr> * * @author Werner Randelshofer * @version $Id$ */ public interface Figure extends Cloneable, Serializable { // PROPERTIES /** The name of the "connectable" property. */ public static final String CONNECTABLE_PROPERTY="connectable"; /** The name of the "removable" property. */ public static final String REMOVABLE_PROPERTY="removable"; /** The name of the "selectable" property. */ public static final String SELECTABLE_PROPERTY="selectable"; /** The name of the "transformable" property. */ public static final String TRANSFORMABLE_PROPERTY="transformable"; // DRAWING /** * Draws the figure. * * @param g The Graphics2D to draw to. */ public void draw(Graphics2D g); /** * Gets the layer number of the figure. * The layer is used to determine the z-ordering of a figure inside of a * drawing. Figures with a higher layer number are drawn after figures * with a lower number. * The z-order of figures within the same layer is determined by the * sequence the figures were added to a drawing. Figures added later to * a drawn after figures which have been added before. * If a figure changes its layer, it must fire a * <code>FigureListener.figureChanged</code> event to * its figure listeners. */ public int getLayer(); /** * A Figure is only drawn by a CompositeFigure, if it is visible. * Layouter's should ignore invisible figures too. */ public boolean isVisible(); // BOUNDS /** * Sets the logical and untransformed bounds of the figure. * <p> * This is used by Tool's which create a new Figure and by Tool's which * connect a Figure to another Figure. * <p> * This is a basic operation which does not fire events. Use the following * code sequence, if you need event firing: * <pre> * figure.willChange(); * figure.setBounds(...); * figure.changed(); * </pre> * * * @param start the start point of the bounds * @param end the end point of the bounds * @see #getBounds */ public void setBounds(Point2D.Double start, Point2D.Double end); /** * Returns the untransformed logical start point of the bounds. * * * * @see #setBounds */ public Point2D.Double getStartPoint(); /** * Returns the untransformed logical end point of the bounds. * * * * @see #setBounds */ public Point2D.Double getEndPoint(); /** * Returns the untransformed logical bounds of the figure as a Rectangle. * <p> * The bounds are used by Handle objects for adjusting the * figure and for aligning the figure on a grid. */ public Rectangle2D.Double getBounds(); /** * Returns the drawing area of the figure as a Rectangle. * <p> * The drawing area is used to inform {@link DrawingView} about the * area that is needed to draw this figure. * <p> * The drawing area needs to be large enough, to take line width, line caps * and other decorations into account that exceed the bounds of the Figure. */ public Rectangle2D.Double getDrawingArea(); /** * Returns the drawing area of the figure as a Rectangle. * <p> * The drawing area is used to inform {@link DrawingView} about the * area that is needed to draw this figure. * <p> * The drawing area needs to be large enough, to take line width, line caps * and other decorations into account that exceed the bounds of the Figure. */ public Rectangle2D.Double getDrawingArea(double factor); /** * The preferred size is used by Layouter to determine the preferred * size of a Figure. For most Figure's this is the same as the * dimensions returned by getBounds. */ public Dimension2DDouble getPreferredSize(); /** * Checks if a point is contained by the figure. * <p> * This is used for hit testing by Tool's. */ public boolean contains(Point2D.Double p); // TRANSFORMING /** * Gets data which can be used to restore the transformation of the figure * without loss of precision, after a transform has been applied to it. * * @see #transform(AffineTransform) */ public Object getTransformRestoreData(); /** * Restores the transform of the figure to a previously stored state. */ public void restoreTransformTo(Object restoreData); /** * Transforms the shape of the Figure. Transformations using double * precision arithmethics are inherently lossy operations. Therefore it is * recommended to use getTransformRestoreData() restoreTransformTo() to * provide lossless undo/redo functionality. * <p> * This is a basic operation which does not fire events. Use the following * code sequence, if you need event firing: * <pre> * figure.willChange(); * figure.transform(...); * figure.changed(); * </pre> * * * @param tx The transformation. * @see #getTransformRestoreData * @see #restoreTransformTo */ public void transform(AffineTransform tx); // ATTRIBUTES /** * Sets an attribute on the figure and calls {@code attributeChanged} * on all registered {@code FigureListener}s if the attribute value * has changed. * <p> * For efficiency reasons, the drawing is not automatically repainted. * If you want the drawing to be repainted when the attribute is changed, * you can either use {@code key.set(figure, value); } or * <pre> * figure.willChange(); * figure.set(...); * figure.changed(); * </pre> * * @see AttributeKey#set */ public <T> void set(AttributeKey<T> key, @Nullable T value); /** * Gets an attribute from the Figure. * * @see AttributeKey#get * * @return Returns the attribute value. If the Figure does not have an * attribute with the specified key, returns key.getDefaultValue(). */ @Nullable public <T> T get(AttributeKey<T> key); /** * Returns a view to all attributes of this figure. * By convention, an unmodifiable map is returned. */ public Map<AttributeKey<?>, Object> getAttributes(); /** * Gets data which can be used to restore the attributes of the figure * after a set has been applied to it. */ public Object getAttributesRestoreData(); /** * Restores the attributes of the figure to a previously stored state. */ public void restoreAttributesTo(Object restoreData); // EDITING /** * Returns true, if the user may select this figure. * If this operation returns false, Tool's should not select this * figure on behalf of the user. * <p> * Please note, that even if this method returns false, the Figure * may become part of a selection for other reasons. For example, * if the Figure is part of a GroupFigure, then the Figure is * indirectly part of the selection, when the user selects the * GroupFigure. */ public boolean isSelectable(); /** * Returns true, if the user may remove this figure. * If this operation returns false, Tool's should not remove this * figure on behalf of the user. * <p> * Please note, that even if this method returns false, the Figure * may be removed from the Drawing for other reasons. For example, * if the Figure is used to display a warning message, the Figure * can be removed from the Drawing, when the warning message is * no longer relevant. */ public boolean isRemovable(); /** * Returns true, if the user may transform this figure. * If this operation returns false, Tool's should not transform this * figure on behalf of the user. * <p> * Please note, that even if this method returns false, the Figure * may be transformed for other reasons. For example, if the Figure takes * part in an animation. * * @see #transform */ public boolean isTransformable(); /** * Creates handles used to manipulate the figure. * * @param detailLevel The detail level of the handles. Usually this is 0 for * bounding box handles and 1 for point handles. The value -1 is used * by the SelectAreaTracker and the HandleTracker to highlight figures, over which the mouse * pointer is hovering. * @return a Collection of handles * @see Handle */ public Collection<Handle> createHandles(int detailLevel); /** * Returns a cursor for the specified location. */ public Cursor getCursor(Point2D.Double p); /** * Returns a collection of Action's for the specified location on the figure. * * <p>The collection may contain null entries. These entries are used * interpreted as separators in the popup menu. * <p>Actions can use the property Figure.ACTION_SUBMENU to specify a * submenu. */ public Collection<Action> getActions(Point2D.Double p); /** * Returns a specialized tool for the specified location. * <p>Returns null, if no specialized tool is available. */ @Nullable public Tool getTool(Point2D.Double p); /** * Returns a tooltip for the specified location on the figure. */ @Nullable public String getToolTipText(Point2D.Double p); // CONNECTING /** * Returns true if this Figure can be connected to a {@link ConnectionFigure}. */ public boolean isConnectable(); /** * Gets a connector for this figure at the given location. * A figure can have different connectors at different locations. * * @param p the location of the connector. * @param prototype The prototype used to create a connection or null if * unknown. This allows for specific connectors for different * connection figures. */ @Nullable public Connector findConnector(Point2D.Double p, @Nullable ConnectionFigure prototype); /** * Gets a compatible connector. * If the provided connector is part of this figure, return the connector. * If the provided connector is part of another figure, return a connector * with the same semantics for this figure. * Returns null, if no compatible connector is available. */ @Nullable public Connector findCompatibleConnector(Connector c, boolean isStartConnector); /** * Returns all connectors of this Figure for the specified prototype of * a ConnectionFigure. * <p> * This is used by connection tools and connection handles * to visualize the connectors when the user is about to * create a ConnectionFigure to this Figure. * * @param prototype The prototype used to create a connection or null if * unknown. This allows for specific connectors for different * connection figures. */ public Collection<Connector> getConnectors(@Nullable ConnectionFigure prototype); // COMPOSITE FIGURES /** * Checks whether the given figure is contained in this figure. * A figure includes itself. */ public boolean includes(Figure figure); /** * Finds the innermost figure at the specified location. * <p> * In case of a {@code CompositeFigure}, this method descends into its * children and into its children's children until the innermost figure is * found. * <p> * This functionality is implemented using the <em>Chain of * Responsibility</em> design pattern. A figure which is not composed * of other figures returns itself if the point is contained by the figure. * Composed figures pass the method call down to their children. * * @param p A location on the drawing. * @return Returns the innermost figure at the location, or null if the * location is not contained in a figure. */ @Nullable public Figure findFigureInside(Point2D.Double p); /** * Returns a decompositon of a figure into its parts. * A figure is considered as a part of itself. */ public Collection<Figure> getDecomposition(); // CLONING /** * Returns a clone of the figure, with clones of all aggregated figures, * such as children and decorators. The cloned figure does not clone * the list of FigureListeners from its original. */ public Figure clone(); /** * After cloning a collection of figures, the ConnectionFigures contained * in this collection still connect to the original figures instead of * to the clones. * Using This operation and providing a map, which maps from the original * collection of figures to the new collection, connections can be remapped * to the new figures. */ public void remap(Map<Figure, Figure> oldToNew, boolean disconnectIfNotInMap); // EVENT HANDLING /** * Informs a figure, that it has been added to a drawing. * The figure must inform all FigureListeners that it has been added. */ public void addNotify(Drawing d); /** * Informs a figure, that it has been removed from a drawing. * The figure must inform all FigureListeners that it has been removed. */ public void removeNotify(Drawing d); /** * Informs that the figure is about to change its visual representation * (for example, its shape, or its color). * <p> * Note: <code>willChange</code> and <code>changed</code> are typically used * as pairs before and after invoking one or multiple basic-methods on * the Figure. * * @see #changed */ public void willChange(); /** * Informs that a Figure changed its visual representation and needs to * be redrawn. * <p> * This fires a <code>FigureListener.figureChanged</code> * event for the current display bounds of the figure. * <p> * Note: <code>willChange</code> and <code>changed</code> are typically used * as pairs before and after invoking one or multiple basic-methods on * the Figure. * * @see #willChange */ public void changed(); /** * Fires a <code>FigureListener.figureRequestRemove</code> event. */ public void requestRemove(); /** * Handles a drop. * * @param p The location of the mouse event. * @param droppedFigures The dropped figures. * @param view The drawing view which is the source of the mouse event. * @return Returns true, if the figures should snap back to the location * they were dragged from. */ public boolean handleDrop(Point2D.Double p, Collection<Figure> droppedFigures, DrawingView view); /** * Handles a mouse click. * * @param p The location of the mouse event. * @param evt The mouse event. * @param view The drawing view which is the source of the mouse event. * * @return Returns true, if the event was consumed. */ public boolean handleMouseClick(Point2D.Double p, MouseEvent evt, DrawingView view); /** * Adds a listener for FigureEvent's. */ public void addFigureListener(FigureListener l); /** * Removes a listener for FigureEvent's. */ public void removeFigureListener(FigureListener l); /** Adds a {@code PropertyChangeListener} which can optionally be wrapped * into a {@code WeakPropertyChangeListener}. * @param listener */ public void addPropertyChangeListener(PropertyChangeListener listener); /** Removes a {@code PropertyChangeListener}. If the listener was added * wrapped into a {@code WeakPropertyChangeListener}, the * {@code WeakPropertyChangeListener} is removed. * * @param listener */ public void removePropertyChangeListener(PropertyChangeListener listener); }