/******************************************************************************* * 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 ******************************************************************************/ /* * @(#)AbstractCompositeFigure.java 1.0.2 2009-04-16 * * Copyright (c) 2007-2009 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 java.io.IOException; import org.jhotdraw.util.*; import java.awt.*; import java.awt.geom.*; import java.io.Serializable; import java.util.*; import javax.swing.event.*; import org.jhotdraw.geom.*; import org.jhotdraw.xml.DOMInput; import org.jhotdraw.xml.DOMOutput; import static org.jhotdraw.draw.AttributeKeys.*; /** * AbstractCompositeFigure. * * @author Werner Randelshofer * @version 1.0.2 2009-04-16 Guard against infinity in method setBounds. * <br>1.0.1 2008-03-30 Made basicRemove method non-final. * <br>1.0 July 17, 2007 Created. */ public abstract class AbstractCompositeFigure extends AbstractFigure implements CompositeFigure { /** * A Layouter determines how the children of the CompositeFigure * are laid out graphically. */ protected Layouter layouter; /** * The children that this figure is composed of * * @see #add * @see #removeChild */ protected ArrayList<Figure> children = new ArrayList<Figure>(); /** * Cached draw cachedBounds. */ protected transient Rectangle2D.Double cachedDrawingArea; /** * Cached layout cachedBounds. */ protected transient Rectangle2D.Double cachedBounds; /** * Handles figure changes in the children. */ protected EventHandler eventHandler; protected class EventHandler extends FigureAdapter implements UndoableEditListener, Serializable { @Override public void figureRequestRemove(FigureEvent e) { remove(e.getFigure()); } @Override public void figureChanged(FigureEvent e) { invalidate(); fireFigureChanged(e.getInvalidatedArea()); } @Override public void areaInvalidated(FigureEvent e) { fireAreaInvalidated(e); } public void undoableEditHappened(UndoableEditEvent e) { fireUndoableEditHappened(e.getEdit()); } @Override public void attributeChanged(FigureEvent e) { invalidate(); } @Override public void figureAdded(FigureEvent e) { invalidate(); } @Override public void figureRemoved(FigureEvent e) { invalidate(); } } public AbstractCompositeFigure() { eventHandler = createEventHandler(); } @Override public Collection<Handle> createHandles(int detailLevel) { LinkedList<Handle> handles = new LinkedList<Handle>(); if (detailLevel == 0) { handles.add(new BoundsOutlineHandle(this, true, false)); TransformHandleKit.addScaleMoveTransformHandles(this, handles); } return handles; } protected EventHandler createEventHandler() { return new EventHandler(); } public boolean add(Figure figure) { add(getChildCount(), figure); return true; } public void add(int index, Figure figure) { basicAdd(index, figure); if (getDrawing() != null) { figure.addNotify(getDrawing()); } fireFigureAdded(figure, index); invalidate(); } public void addAll(Collection<? extends Figure> figures) { addAll(getChildCount(), figures); } public final void addAll(int index, Collection<? extends Figure> figures) { for (Figure f : figures) { basicAdd(index++, f); if (getDrawing() != null) { f.addNotify(getDrawing()); } fireFigureAdded(f, index); } invalidate(); } public void basicAdd(Figure figure) { basicAdd(getChildCount(), figure); } public void basicAddAll(int index, Collection<? extends Figure> newFigures) { for (Figure f : newFigures) { basicAdd(index++, f); } } public void addNotify(Drawing drawing) { super.addNotify(drawing); for (Figure child : getChildren()) { child.addNotify(drawing); } } public void removeNotify(Drawing drawing) { super.removeNotify(drawing); // Copy children collection to avoid concurrent modification exception for (Figure child : new LinkedList<Figure>(getChildren())) { child.removeNotify(drawing); } } public boolean remove(final Figure figure) { int index = children.indexOf(figure); if (index == -1) { return false; } else { basicRemoveChild(index); if (getDrawing() != null) { figure.removeNotify(getDrawing()); } fireFigureRemoved(figure, index); return true; } } public Figure removeChild(int index) { Figure removed = basicRemoveChild(index); if (getDrawing() != null) { removed.removeNotify(getDrawing()); } return removed; } /** * Removes all specified children. * * @see #add */ public void removeAll(Collection<? extends Figure> figures) { for (Figure f : figures) { remove(f); } } /** * Removes all children. * * @see #add */ public void removeAllChildren() { willChange(); for (Figure f : new LinkedList<Figure>(getChildren())) { if (getDrawing() != null) { f.removeNotify(getDrawing()); } int index = basicRemove(f); } changed(); } /** * Removes all children. * * @see #add */ public void basicRemoveAllChildren() { for (Figure f : new LinkedList<Figure>(getChildren())) { basicRemove(f); } } /** * Removes all children. * * @see #add */ public void basicRemoveAll(Collection<? extends Figure> figures) { for (Figure f : figures) { basicRemove(f); } } /** * Sends a figure to the back of the composite figure. * * @param figure that is part of this composite figure */ public synchronized void sendToBack(Figure figure) { if (basicRemove(figure) != -1) { basicAdd(0, figure); fireAreaInvalidated(figure.getDrawingArea()); } } /** * Sends a figure to the front of the drawing. * * @param figure that is part of the drawing */ public synchronized void bringToFront(Figure figure) { if (basicRemove(figure) != -1) { basicAdd(figure); fireAreaInvalidated(figure.getDrawingArea()); } } /** * Transforms the figure. */ public void transform(AffineTransform tx) { for (Figure f : getChildren()) { f.transform(tx); } invalidate(); //invalidate(); } @Override public void setBounds(Point2D.Double anchor, Point2D.Double lead) { Rectangle2D.Double oldBounds = getBounds(); Rectangle2D.Double newBounds = new Rectangle2D.Double( Math.min(anchor.x, lead.x), Math.min(anchor.y, lead.y), Math.abs(anchor.x - lead.x), Math.abs(anchor.y - lead.y)); double sx = newBounds.width / oldBounds.width; double sy = newBounds.height / oldBounds.height; AffineTransform tx = new AffineTransform(); tx.translate(-oldBounds.x, -oldBounds.y); if (!Double.isNaN(sx) && !Double.isNaN(sy) && !Double.isInfinite(sx) && !Double.isInfinite(sy) && (sx != 1d || sy != 1d) && !(sx < 0.0001) && !(sy < 0.0001)) { transform(tx); tx.setToIdentity(); tx.scale(sx, sy); transform(tx); tx.setToIdentity(); } tx.translate(newBounds.x, newBounds.y); transform(tx); } /** * Returns an iterator to iterate in * Z-order front to back over the children. */ public java.util.List<Figure> getChildrenFrontToBack() { return children.size() == 0 ? new LinkedList<Figure>() : new ReversedList<Figure>(getChildren()); } public <T> void setAttribute(AttributeKey<T> key, T value) { for (Figure child : getChildren()) { child.setAttribute(key, value); } invalidate(); } public <T> T getAttribute(AttributeKey<T> name) { return null; } public Map<AttributeKey, Object> getAttributes() { return new HashMap<AttributeKey, Object>(); } public Object getAttributesRestoreData() { LinkedList<Object> data = new LinkedList<Object>(); for (Figure child : getChildren()) { data.add(child.getAttributesRestoreData()); } return data; } public void restoreAttributesTo(Object newData) { @SuppressWarnings("unchecked") Iterator<Object> data = ((LinkedList<Object>) newData).iterator(); for (Figure child : getChildren()) { child.restoreAttributesTo(data.next()); } } public boolean contains(Figure f) { return children.contains(f); } public boolean contains(Point2D.Double p) { if (TRANSFORM.get(this) != null) { try { p = (Point2D.Double) TRANSFORM.get(this).inverseTransform(p, new Point2D.Double()); } catch (NoninvertibleTransformException ex) { InternalError error = new InternalError(ex.getMessage()); error.initCause(ex); throw error; } } ; if (getDrawingArea().contains(p)) { for (Figure child : getChildrenFrontToBack()) { if (child.isVisible() && child.contains(p)) { return true; } } } return false; } public Figure findFigureInside(Point2D.Double p) { if (getDrawingArea().contains(p)) { Figure found = null; for (Figure child : getChildrenFrontToBack()) { if (child.isVisible()) { found = child.findFigureInside(p); if (found != null) { return found; } } } } return null; } public Figure findChild(Point2D.Double p) { if (getBounds().contains(p)) { Figure found = null; for (Figure child : getChildrenFrontToBack()) { if (child.isVisible() && child.contains(p)) { return child; } } } return null; } public int findChildIndex(Point2D.Double p) { Figure child = findChild(p); return (child == null) ? -1 : children.indexOf(child); } /** * Get a Layouter object which encapsulated a layout * algorithm for this figure. Typically, a Layouter * accesses the child components of this figure and arranges * their graphical presentation. * * * @return layout strategy used by this figure */ public Layouter getLayouter() { return layouter; } /** * A layout algorithm is used to define how the child components * should be laid out in relation to each other. The task for * layouting the child components for presentation is delegated * to a Layouter which can be plugged in at runtime. */ public void layout() { if (getLayouter() != null) { Rectangle2D.Double bounds = getBounds(); Point2D.Double p = new Point2D.Double(bounds.x, bounds.y); Rectangle2D.Double r = getLayouter().layout( this, p, p); setBounds(new Point2D.Double(r.x, r.y), new Point2D.Double(r.x + r.width, r.y + r.height)); invalidate(); } } /** * Set a Layouter object which encapsulated a layout * algorithm for this figure. Typically, a Layouter * accesses the child components of this figure and arranges * their graphical presentation. It is a good idea to set * the Layouter in the protected initialize() method * so it can be recreated if a GraphicalCompositeFigure is * read and restored from a StorableInput stream. * * * @param newLayouter encapsulation of a layout algorithm. */ public void setLayouter(Layouter newLayouter) { this.layouter = newLayouter; } @Override public Dimension2DDouble getPreferredSize() { if (this.layouter != null) { Rectangle2D.Double r = layouter.calculateLayout(this, getStartPoint(), getEndPoint()); return new Dimension2DDouble(r.width, r.height); } else { return super.getPreferredSize(); } } public void draw(Graphics2D g) { Rectangle2D clipBounds = g.getClipBounds(); if (clipBounds != null) { for (Figure child : getChildren()) { if (child.isVisible() && child.getDrawingArea().intersects(clipBounds)) { child.draw(g); } } } else { for (Figure child : getChildren()) { if (child.isVisible()) { child.draw(g); } } } } @Override public Collection<Figure> getDecomposition() { LinkedList<Figure> list = new LinkedList<Figure>(); list.add(this); list.addAll(getChildren()); return list; } public void read(DOMInput in) throws IOException { in.openElement("children"); for (int i = 0; i < in.getElementCount(); i++) { basicAdd((Figure) in.readObject(i)); } in.closeElement(); } public void write(DOMOutput out) throws IOException { out.openElement("children"); for (Figure child : getChildren()) { out.writeObject(child); } out.closeElement(); } public void restoreTransformTo(Object geometry) { LinkedList list = (LinkedList) geometry; Iterator i = list.iterator(); for (Figure child : getChildren()) { child.restoreTransformTo(i.next()); } invalidate(); } public Object getTransformRestoreData() { LinkedList<Object> list = new LinkedList<Object>(); for (Figure child : getChildren()) { list.add(child.getTransformRestoreData()); } return list; } @Override protected void validate() { super.validate(); layout(); } public void basicAdd(int index, Figure figure) { children.add(index, figure); figure.addFigureListener(eventHandler); } public Figure basicRemoveChild(int index) { Figure figure = children.remove(index); figure.removeFigureListener(eventHandler); invalidate(); return figure; } public java.util.List<Figure> getChildren() { return Collections.unmodifiableList(children); } public int getChildCount() { return children.size(); } public Figure getChild(int index) { return children.get(index); } @Override public AbstractCompositeFigure clone() { AbstractCompositeFigure that = (AbstractCompositeFigure) super.clone(); that.children = new ArrayList<Figure>(); that.eventHandler = that.createEventHandler(); for (Figure thisChild : this.children) { Figure thatChild = (Figure) thisChild.clone(); that.children.add(thatChild); thatChild.addFigureListener(that.eventHandler); } return that; } @Override protected void invalidate() { cachedBounds = null; cachedDrawingArea = null; } public int basicRemove(Figure child) { int index = children.indexOf(child); if (index != -1) { basicRemoveChild(index); } return index; } public int indexOf(Figure child) { return children.indexOf(child); } public Rectangle2D.Double getDrawingArea() { if (cachedDrawingArea == null) { if (getChildCount() == 0) { cachedDrawingArea = new Rectangle2D.Double(); } else { for (Figure f : children) { if (cachedDrawingArea == null || cachedDrawingArea.isEmpty()) { cachedDrawingArea = f.getDrawingArea(); } else { cachedDrawingArea.add(f.getDrawingArea()); } } } } return (Rectangle2D.Double) cachedDrawingArea.clone(); } public Rectangle2D.Double getBounds() { if (cachedBounds == null) { if (getChildCount() == 0) { cachedBounds = new Rectangle2D.Double(); } else { for (Figure f : children) { if (cachedBounds == null || cachedBounds.isEmpty()) { cachedBounds = f.getBounds(); } else { cachedBounds.add(f.getBounds()); } } } } return (Rectangle2D.Double) cachedBounds.clone(); } /** * Notify all listenerList that have registered interest for * notification on this event type. */ protected void fireFigureAdded(Figure f, int zIndex) { CompositeFigureEvent 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] == CompositeFigureListener.class) { // Lazily create the event: if (event == null) { event = new CompositeFigureEvent(this, f, f.getDrawingArea(), zIndex); } ((CompositeFigureListener) listeners[i + 1]).figureAdded(event); } } } /** * Notify all listenerList that have registered interest for * notification on this event type. */ protected void fireFigureRemoved(Figure f, int zIndex) { CompositeFigureEvent 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] == CompositeFigureListener.class) { // Lazily create the event: if (event == null) { event = new CompositeFigureEvent(this, f, f.getDrawingArea(), zIndex); } ((CompositeFigureListener) listeners[i + 1]).figureRemoved(event); } } } public void removeCompositeFigureListener(CompositeFigureListener listener) { listenerList.remove(CompositeFigureListener.class, listener); } public void addCompositeFigureListener(CompositeFigureListener listener) { listenerList.add(CompositeFigureListener.class, listener); } }