/******************************************************************************* * 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 ******************************************************************************/ /* * @(#)GraphicalCompositeFigure.java 2.0 2006-01-14 * * Copyright (c) 1996-2006 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.util.*; import javax.swing.undo.*; import javax.swing.event.*; import static org.jhotdraw.draw.AttributeKeys.*; import org.jhotdraw.geom.*; import org.jhotdraw.xml.DOMInput; import org.jhotdraw.xml.DOMOutput; /** * The GraphicalCompositeFigure fills in the gap between a CompositeFigure * and other figures which mainly have a presentation purpose. The * GraphicalCompositeFigure can be configured with any Figure which * takes over the task for rendering the graphical presentation for * a CompositeFigure. Therefore, the GraphicalCompositeFigure manages * contained figures like a CompositeFigure does, but delegates * its graphical presentation to another (graphical) figure which * purpose it is to draw the container for all contained figures. * * The GraphicalCompositeFigure adds to the {@link CompositeFigure CompositeFigure} * by containing a presentation figure by default which can not be removed. Normally, * the {@link CompositeFigure CompositeFigure} can not be seen without containing a figure * because it has no mechanism to draw itself. It instead relies on its contained * figures to draw themselves thereby giving the {@link CompositeFigure BasicCompositeFigure} its * appearance. However, the <b>GraphicalCompositeFigure</b>'s presentation figure * can draw itself even when the <b>GraphicalCompositeFigure</b> contains no other figures. * The <b>GraphicalCompositeFigure</b> also uses a {@link Layouter Layouter} or layout * its contained figures. * * * * * * * @author Wolfram Kaiser (original code), Werner Randelshofer (this derived version) * @version 2.0 2006-01-14 Changed to support double precision coordinates. * <br>1.0 1. Dezember 2003 Derived from JHotDraw 5.4b1. */ public class GraphicalCompositeFigure extends AbstractCompositeFigure { protected HashMap<AttributeKey, Object> attributes = new HashMap<AttributeKey,Object>(); private HashSet<AttributeKey> forbiddenAttributes; /** * Figure which performs all presentation tasks for this * BasicCompositeFigure as CompositeFigures usually don't have * an own presentation but present only the sum of all its * children. */ private Figure presentationFigure; /** * Handles figure changes in the children. */ private PresentationFigureHandler presentationFigureHandler = new PresentationFigureHandler(this); private static class PresentationFigureHandler extends FigureAdapter implements UndoableEditListener { private GraphicalCompositeFigure owner; private PresentationFigureHandler(GraphicalCompositeFigure owner) { this.owner = owner; } @Override public void figureRequestRemove(FigureEvent e) { owner.remove(e.getFigure()); } @Override public void figureChanged(FigureEvent e) { if (! owner.isChanging()) { owner.willChange(); owner.fireFigureChanged(e); owner.changed(); } } @Override public void areaInvalidated(FigureEvent e) { if (! owner.isChanging()) { owner.fireAreaInvalidated(e.getInvalidatedArea()); } } public void undoableEditHappened(UndoableEditEvent e) { owner.fireUndoableEditHappened(e.getEdit()); } }; /** * Default constructor which uses nothing as presentation * figure. This constructor is needed by the Storable mechanism. */ public GraphicalCompositeFigure() { this(null); } /** * Constructor which creates a GraphicalCompositeFigure with * a given graphical figure for presenting it. * * @param newPresentationFigure figure which renders the container */ public GraphicalCompositeFigure(Figure newPresentationFigure) { super(); setPresentationFigure(newPresentationFigure); } /** * Return the logcal display area. This method is delegated to the encapsulated * presentation figure. */ public Rectangle2D.Double getBounds() { if (getPresentationFigure() == null) return super.getBounds(); return getPresentationFigure().getBounds(); } public boolean contains(Point2D.Double p) { if (getPresentationFigure() != null) { return getPresentationFigure().contains(p); } else { return super.contains(p); } } public void addNotify(Drawing drawing) { super.addNotify(drawing); if (getPresentationFigure() != null) { getPresentationFigure().addNotify(drawing); } } public void removeNotify(Drawing drawing) { super.removeNotify(drawing); if (getPresentationFigure() != null) { getPresentationFigure().removeNotify(drawing); } } /** * Return the draw area. This method is delegated to the * encapsulated presentation figure. */ public Rectangle2D.Double getDrawingArea() { Rectangle2D.Double r; if (getPresentationFigure() != null) { Rectangle2D.Double presentationBounds = getPresentationFigure().getDrawingArea(); r = super.getDrawingArea(); if (r.isEmpty()) { r = presentationBounds; } else { r.add(presentationBounds); } } else { r = super.getDrawingArea(); } return r; } /** * Moves the figure. This is the * method that subclassers override. Clients usually * call displayBox. */ public void setBounds(Point2D.Double anchor, Point2D.Double lead) { if (getLayouter() == null) { super.setBounds(anchor, lead); basicSetPresentationFigureBounds(anchor, lead); } else { Rectangle2D.Double r = getLayouter().layout(this, anchor, lead); basicSetPresentationFigureBounds(new Point2D.Double(r.getX(), r.getY()), new Point2D.Double( Math.max(lead.x, (int) r.getMaxX()), Math.max(lead.y, (int) r.getMaxY()) ) ); invalidate(); } } protected void superBasicSetBounds(Point2D.Double anchor, Point2D.Double lead) { super.setBounds(anchor, lead); } protected void basicSetPresentationFigureBounds(Point2D.Double anchor, Point2D.Double lead) { if (getPresentationFigure() != null) { getPresentationFigure().setBounds(anchor, lead); } } /** * Standard presentation method which is delegated to the encapsulated presentation figure. * The presentation figure is moved as well as all contained figures. */ public void transform(AffineTransform tx) { super.transform(tx); if (getPresentationFigure() != null) { getPresentationFigure().transform(tx); } } /** * Draw the figure. This method is delegated to the encapsulated presentation figure. */ public void draw(Graphics2D g) { drawPresentationFigure(g); super.draw(g); } protected void drawPresentationFigure(Graphics2D g) { if (getPresentationFigure() != null) { getPresentationFigure().draw(g); } } /** * Return default handles from the presentation figure. */ public Collection<Handle> createHandles(int detailLevel) { LinkedList<Handle> handles = new LinkedList<Handle>(); if (detailLevel == 0) { MoveHandle.addMoveHandles(this, handles); } return handles; //return getPresentationFigure().getHandles(); } /** * Set a figure which renders this BasicCompositeFigure. The presentation * tasks for the BasicCompositeFigure are delegated to this presentation * figure. * * * * * * @param newPresentationFigure figure takes over the presentation tasks */ public void setPresentationFigure(Figure newPresentationFigure) { if (this.presentationFigure != null) { this.presentationFigure.removeFigureListener(presentationFigureHandler); if (getDrawing() != null) { this.presentationFigure.removeNotify(getDrawing()); } } this.presentationFigure = newPresentationFigure; if (this.presentationFigure != null) { this.presentationFigure.addFigureListener(presentationFigureHandler); if (getDrawing() != null) { this.presentationFigure.addNotify(getDrawing()); } } // FIXME: We should calculate the layout here. } /** * Get a figure which renders this BasicCompositeFigure. The presentation * tasks for the BasicCompositeFigure are delegated to this presentation * figure. * * * * * * @return figure takes over the presentation tasks */ public Figure getPresentationFigure() { return presentationFigure; } public GraphicalCompositeFigure clone() { GraphicalCompositeFigure that = (GraphicalCompositeFigure) super.clone(); that.presentationFigure = (this.presentationFigure == null) ? null : (Figure) this.presentationFigure.clone(); if (that.presentationFigure != null) { that.presentationFigure.addFigureListener(that.presentationFigureHandler); } return that; } public void remap(HashMap<Figure,Figure> oldToNew, boolean disconnectIfNotInMap) { super.remap(oldToNew, disconnectIfNotInMap); if (presentationFigure != null) { presentationFigure.remap(oldToNew, disconnectIfNotInMap); } } /** * Sets an attribute of the figure. * AttributeKey name and semantics are defined by the class implementing * the figure interface. */ @Override public <T> void setAttribute(AttributeKey<T> key, T newValue) { if (forbiddenAttributes == null || ! forbiddenAttributes.contains(key)) { if (getPresentationFigure() != null) { getPresentationFigure().setAttribute(key, newValue); } super.setAttribute(key, newValue); Object oldValue = attributes.put(key, newValue); } } public void setAttributeEnabled(AttributeKey key, boolean b) { if (forbiddenAttributes == null) { forbiddenAttributes = new HashSet<AttributeKey>(); } if (b) { forbiddenAttributes.remove(key); } else { forbiddenAttributes.add(key); } } /** * Gets an attribute from the figure. */ @Override public <T> T getAttribute(AttributeKey<T> key) { if (getPresentationFigure() != null) { return key.get(getPresentationFigure()); } else { return (! attributes.containsKey(key)) ? key.getDefaultValue() : key.get(attributes); } } /** * Applies all attributes of this figure to that figure. */ @SuppressWarnings("unchecked") protected void applyAttributesTo(Figure that) { for (Map.Entry<AttributeKey, Object> entry : attributes.entrySet()) { entry.getKey().basicSet(that, entry.getValue()); } } protected void writeAttributes(DOMOutput out) throws IOException { Figure prototype = (Figure) out.getPrototype(); boolean isElementOpen = false; for (Map.Entry<AttributeKey, Object> entry : attributes.entrySet()) { AttributeKey key = entry.getKey(); if (forbiddenAttributes == null || ! forbiddenAttributes.contains(key)) { Object prototypeValue = key.get(prototype); Object attributeValue = key.get(this); if (prototypeValue != attributeValue || (prototypeValue != null && attributeValue != null && ! prototypeValue.equals(attributeValue))) { if (! isElementOpen) { out.openElement("a"); isElementOpen = true; } out.openElement(key.getKey()); out.writeObject(entry.getValue()); out.closeElement(); } } } if (isElementOpen) { out.closeElement(); } } @SuppressWarnings("unchecked") protected void readAttributes(DOMInput in) throws IOException { if (in.getElementCount("a") > 0) { in.openElement("a"); for (int i=in.getElementCount() - 1; i >= 0; i-- ) { in.openElement(i); String name = in.getTagName(); Object value = in.readObject(); AttributeKey key = getAttributeKey(name); if (key != null && key.isAssignable(value)) { if (forbiddenAttributes == null || ! forbiddenAttributes.contains(key)) { key.basicSet(this, value); } } in.closeElement(); } in.closeElement(); } } @Override public void read(DOMInput in) throws IOException { super.read(in); readAttributes(in); } @Override public void write(DOMOutput out) throws IOException { super.write(out); writeAttributes(out); } protected AttributeKey getAttributeKey(String name) { return AttributeKeys.supportedAttributeMap.get(name); } public Map<AttributeKey, Object> getAttributes() { return new HashMap<AttributeKey,Object>(attributes); } /** * This is a default implementation that chops the point at the rectangle * returned by getBounds() of the figure. * <p> * Figures which have a non-rectangular shape need to override this method. * <p> * This method takes the following attributes into account: * AttributeKeys.STROKE_COLOR, AttributeKeys.STROKE_PLACEMENT, and * AttributeKeys.StrokeTotalWidth. */ public Point2D.Double chop(Point2D.Double from) { Rectangle2D.Double r = getBounds(); if (STROKE_COLOR.get(this) != null) { double grow; switch (STROKE_PLACEMENT.get(this)) { case CENTER: default : grow = AttributeKeys.getStrokeTotalWidth(this); break; case OUTSIDE : grow = AttributeKeys.getStrokeTotalWidth(this); break; case INSIDE : grow = 0d; break; } Geom.grow(r, grow, grow); } return Geom.angleToPoint(r, Geom.pointToAngle(r, from)); } }