/*******************************************************************************
* 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
******************************************************************************/
/*
* @(#)ODGPathFigure.java 1.0 2007-07-28
*
* Copyright (c) 2007 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.samples.odg.figures;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.*;
import javax.swing.*;
import javax.swing.undo.*;
import org.jhotdraw.draw.*;
import org.jhotdraw.draw.action.*;
import org.jhotdraw.geom.*;
import org.jhotdraw.samples.odg.*;
import org.jhotdraw.samples.odg.ODGConstants;
import org.jhotdraw.undo.*;
import org.jhotdraw.util.*;
import org.jhotdraw.xml.*;
import static org.jhotdraw.samples.odg.ODGAttributeKeys.*;
/**
* ODGPath is a composite Figure which contains one or more
* ODGBezierFigures as its children.
*
* @author Werner Randelshofer
* @version 1.0 2007-07-28 Created.
*/
public class ODGPathFigure extends AbstractAttributedCompositeFigure implements ODGFigure {
/**
* This cachedPath is used for drawing.
*/
private transient GeneralPath cachedPath;
//private transient Rectangle2D.Double cachedDrawingArea;
private final static boolean DEBUG = false;
/** Creates a new instance. */
public ODGPathFigure() {
add(new ODGBezierFigure());
ODGAttributeKeys.setDefaults(this);
}
public void draw(Graphics2D g) {
double opacity = OPACITY.get(this);
opacity = Math.min(Math.max(0d, opacity), 1d);
if (opacity != 0d) {
if (opacity != 1d) {
Rectangle2D.Double drawingArea = getDrawingArea();
Rectangle2D clipBounds = g.getClipBounds();
if (clipBounds != null) {
Rectangle2D.intersect(drawingArea, clipBounds, drawingArea);
}
if (! drawingArea.isEmpty()) {
BufferedImage buf = new BufferedImage(
Math.max(1, (int) ((2 + drawingArea.width) * g.getTransform().getScaleX())),
Math.max(1, (int) ((2 + drawingArea.height) * g.getTransform().getScaleY())),
BufferedImage.TYPE_INT_ARGB);
Graphics2D gr = buf.createGraphics();
gr.scale(g.getTransform().getScaleX(), g.getTransform().getScaleY());
gr.translate((int) -drawingArea.x, (int) -drawingArea.y);
gr.setRenderingHints(g.getRenderingHints());
drawFigure(gr);
gr.dispose();
Composite savedComposite = g.getComposite();
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) opacity));
g.drawImage(buf, (int) drawingArea.x, (int) drawingArea.y,
2 + (int) drawingArea.width, 2 + (int) drawingArea.height, null);
g.setComposite(savedComposite);
}
} else {
drawFigure(g);
}
}
}
public void drawFigure(Graphics2D g) {
AffineTransform savedTransform = null;
if (TRANSFORM.get(this) != null) {
savedTransform = g.getTransform();
g.transform(TRANSFORM.get(this));
}
if (FILL_STYLE.get(this) != ODGConstants.FillStyle.NONE) {
Paint paint = ODGAttributeKeys.getFillPaint(this);
if (paint != null) {
g.setPaint(paint);
drawFill(g);
}
}
if (STROKE_STYLE.get(this) != ODGConstants.StrokeStyle.NONE) {
Paint paint = ODGAttributeKeys.getStrokePaint(this);
if (paint != null) {
g.setPaint(paint);
g.setStroke(ODGAttributeKeys.getStroke(this));
drawStroke(g);
}
}
if (TRANSFORM.get(this) != null) {
g.setTransform(savedTransform);
}
}
public void drawFill(Graphics2D g) {
boolean isClosed = CLOSED.get(getChild(0));
if (isClosed) {
g.fill(getPath());
}
}
public void drawStroke(Graphics2D g) {
g.draw(getPath());
}
public void invalidate() {
super.invalidate();
cachedPath = null;
cachedDrawingArea = null;
}
protected GeneralPath getPath() {
if (cachedPath == null) {
cachedPath = new GeneralPath();
cachedPath.setWindingRule(WINDING_RULE.get(this) == WindingRule.EVEN_ODD ?
GeneralPath.WIND_EVEN_ODD :
GeneralPath.WIND_NON_ZERO
);
for (Figure child : getChildren()) {
ODGBezierFigure b = (ODGBezierFigure) child;
cachedPath.append(b.getBezierPath(), false);
}
}
return cachedPath;
}
public Rectangle2D.Double getDrawingArea() {
if (cachedDrawingArea == null) {
double strokeTotalWidth = AttributeKeys.getStrokeTotalWidth(this);
double width = strokeTotalWidth / 2d;
if (STROKE_JOIN.get(this) == BasicStroke.JOIN_MITER) {
width *= STROKE_MITER_LIMIT.get(this);
} else if (STROKE_CAP.get(this) != BasicStroke.CAP_BUTT) {
width += strokeTotalWidth * 2;
}
GeneralPath gp = (GeneralPath) getPath();
Rectangle2D strokeRect = new Rectangle2D.Double(0,0,width,width);
if (TRANSFORM.get(this) != null) {
gp = (GeneralPath) gp.clone();
gp.transform(TRANSFORM.get(this));
strokeRect = TRANSFORM.get(this).createTransformedShape(strokeRect).getBounds2D();
}
Rectangle2D rx = gp.getBounds2D();
Rectangle2D.Double r = (rx instanceof Rectangle2D.Double) ?
(Rectangle2D.Double) rx :
new Rectangle2D.Double(rx.getX(), rx.getY(), rx.getWidth(), rx.getHeight());
Geom.grow(r, strokeRect.getWidth(), strokeRect.getHeight());
cachedDrawingArea = r;
}
return (Rectangle2D.Double) cachedDrawingArea.clone();
}
@Override final public void write(DOMOutput out) throws IOException {
throw new UnsupportedOperationException("Use ODGStorableOutput to write this Figure.");
}
@Override final public void read(DOMInput in) throws IOException {
throw new UnsupportedOperationException("Use ODGStorableInput to read this Figure.");
}
public boolean contains(Point2D.Double p) {
getPath();
if (TRANSFORM.get(this) != null) {
try {
p = (Point2D.Double) TRANSFORM.get(this).inverseTransform(p, new Point2D.Double());
} catch (NoninvertibleTransformException ex) {
ex.printStackTrace();
}
}
/*
return cachedPath.contains(p2);
*/
boolean isClosed = CLOSED.get(getChild(0));
double tolerance = Math.max(2f, AttributeKeys.getStrokeTotalWidth(this) / 2d);
if (isClosed) {
if (getPath().contains(p)) {
return true;
}
double grow = AttributeKeys.getPerpendicularHitGrowth(this) * 2d;
GrowStroke gs = new GrowStroke((float) grow,
(float) (AttributeKeys.getStrokeTotalWidth(this) *
STROKE_MITER_LIMIT.get(this))
);
if (gs.createStrokedShape(getPath()).contains(p)) {
return true;
} else {
if (isClosed) {
return false;
}
}
}
if (! isClosed) {
if (Shapes.outlineContains(getPath(), p, tolerance)) {
return true;
}
}
return false;
}
public void setBounds(Point2D.Double anchor, Point2D.Double lead) {
if (getChildCount() == 1 && ((ODGBezierFigure) getChild(0)).getNodeCount() <= 2) {
ODGBezierFigure b = (ODGBezierFigure) getChild(0);
b.setBounds(anchor, lead);
invalidate();
} else {
super.setBounds(anchor, lead);
}
}
public void transform(AffineTransform tx) {
if (TRANSFORM.get(this) != null ||
(tx.getType() & (AffineTransform.TYPE_TRANSLATION)) != tx.getType()) {
if (TRANSFORM.get(this) == null) {
TRANSFORM.basicSetClone(this, tx);
} else {
AffineTransform t = TRANSFORM.getClone(this);
t.preConcatenate(tx);
TRANSFORM.basicSet(this, t);
}
} else {
for (Figure f : getChildren()) {
f.transform(tx);
}
if (FILL_GRADIENT.get(this) != null &&
! FILL_GRADIENT.get(this).isRelativeToFigureBounds()) {
Gradient g = FILL_GRADIENT.getClone(this);
g.transform(tx);
FILL_GRADIENT.basicSet(this, g);
}
if (STROKE_GRADIENT.get(this) != null &&
! STROKE_GRADIENT.get(this).isRelativeToFigureBounds()) {
Gradient g = STROKE_GRADIENT.getClone(this);
g.transform(tx);
STROKE_GRADIENT.basicSet(this, g);
}
}
invalidate();
}
@Override
@SuppressWarnings("unchecked")
public void restoreTransformTo(Object geometry) {
invalidate();
Object[] restoreData = (Object[]) geometry;
ArrayList<BezierPath> paths = (ArrayList<BezierPath>) restoreData[0];
for (int i=0, n = getChildCount(); i < n; i++) {
getChild(i).setBezierPath(paths.get(i));
}
TRANSFORM.basicSetClone(this, (AffineTransform) restoreData[1]);
FILL_GRADIENT.basicSetClone(this, (Gradient) restoreData[2]);
STROKE_GRADIENT.basicSetClone(this, (Gradient) restoreData[3]);
}
@Override
@SuppressWarnings("unchecked")
public Object getTransformRestoreData() {
ArrayList<BezierPath> paths = new ArrayList<BezierPath>(getChildCount());
for (int i=0, n = getChildCount(); i < n; i++) {
paths.add(getChild(i).getBezierPath());
}
return new Object[] {
paths,
TRANSFORM.getClone(this),
FILL_GRADIENT.getClone(this),
STROKE_GRADIENT.getClone(this),
};
}
@Override
public <T> void setAttribute(AttributeKey<T> key, T newValue) {
super.setAttribute(key, newValue);
invalidate();
}
@Override
protected <T> void setAttributeOnChildren(AttributeKey<T> key, T newValue) {
// empty!
}
public boolean isEmpty() {
for (Figure child : getChildren()) {
ODGBezierFigure b = (ODGBezierFigure) child;
if (b.getNodeCount() > 0) {
return false;
}
}
return true;
}
@Override public Collection<Handle> createHandles(int detailLevel) {
LinkedList<Handle> handles = new LinkedList<Handle>();
switch (detailLevel % 2) {
case 0 :
handles.add(new ODGPathOutlineHandle(this));
for (Figure child : getChildren()) {
handles.addAll(((ODGBezierFigure) child).createHandles(this, detailLevel));
}
break;
case 1 :
TransformHandleKit.addTransformHandles(this, handles);
break;
default:
break;
}
return handles;
}
@Override public Collection<Action> getActions(Point2D.Double p) {
final ResourceBundleUtil labels = ResourceBundleUtil.getBundle("org.jhotdraw.samples.odg.Labels");
LinkedList<Action> actions = new LinkedList<Action>();
if (TRANSFORM.get(this) != null) {
actions.add(new AbstractAction(labels.getString("edit.removeTransform.text")) {
public void actionPerformed(ActionEvent evt) {
ResourceBundleUtil labels = ResourceBundleUtil.getBundle("org.jhotdraw.samples.odg.Labels");
ODGPathFigure.this.willChange();
fireUndoableEditHappened(
TRANSFORM.setUndoable(ODGPathFigure.this, null)
);
ODGPathFigure.this.changed();
}
});
actions.add(new AbstractAction(labels.getString("edit.flattenTransform.text")) {
public void actionPerformed(ActionEvent evt) {
// CompositeEdit edit = new CompositeEdit(labels.getString("flattenTransform"));
//TransformEdit edit = new TransformEdit(ODGPathFigure.this, )
final Object restoreData = getTransformRestoreData();
UndoableEdit edit = new AbstractUndoableEdit() {
public String getPresentationName() {
return labels.getString("flattenTransform");
}
public void undo() throws CannotUndoException {
super.undo();
willChange();
restoreTransformTo(restoreData);
changed();
}
public void redo() throws CannotRedoException {
super.redo();
willChange();
restoreTransformTo(restoreData);
flattenTransform();
changed();
}
};
willChange();
flattenTransform();
changed();
fireUndoableEditHappened(edit);
}
});
}
actions.add(new AbstractAction(labels.getString("closePath")) {
public void actionPerformed(ActionEvent evt) {
for (Figure child : getChildren()) {
ODGPathFigure.this.willChange();
getDrawing().fireUndoableEditHappened(
CLOSED.setUndoable(child, true)
);
ODGPathFigure.this.changed();
}
}
});
actions.add(new AbstractAction(labels.getString("openPath")) {
public void actionPerformed(ActionEvent evt) {
for (Figure child : getChildren()) {
ODGPathFigure.this.willChange();
getDrawing().fireUndoableEditHappened(
CLOSED.setUndoable(child, false)
);
ODGPathFigure.this.changed();
}
}
});
actions.add(new AbstractAction(labels.getString("windingRule.evenOdd")) {
public void actionPerformed(ActionEvent evt) {
ODGPathFigure.this.willChange();
getDrawing().fireUndoableEditHappened(
WINDING_RULE.setUndoable(ODGPathFigure.this, WindingRule.EVEN_ODD)
);
ODGPathFigure.this.changed();
}
});
actions.add(new AbstractAction(labels.getString("windingRule.nonZero")) {
public void actionPerformed(ActionEvent evt) {
WINDING_RULE.set(ODGPathFigure.this, WindingRule.NON_ZERO);
getDrawing().fireUndoableEditHappened(
WINDING_RULE.setUndoable(ODGPathFigure.this, WindingRule.EVEN_ODD)
);
}
});
return actions;
}
// CONNECTING
public boolean canConnect() {
return false; // ODG does not support connecting
}
public Connector findConnector(Point2D.Double p, ConnectionFigure prototype) {
return null; // ODG does not support connectors
}
public Connector findCompatibleConnector(Connector c, boolean isStartConnector) {
return null; // ODG does not support connectors
}
/**
* Handles a mouse click.
*/
@Override public boolean handleMouseClick(Point2D.Double p, MouseEvent evt, DrawingView view) {
if (evt.getClickCount() == 2 && view.getHandleDetailLevel() % 2 == 0) {
for (Figure child : getChildren()) {
ODGBezierFigure bf = (ODGBezierFigure) child;
int index = bf.getBezierPath().findSegment(p, (float) (5f / view.getScaleFactor()));
if (index != -1) {
bf.handleMouseClick(p, evt, view);
evt.consume();
return true;
}
}
}
return false;
}
@Override public void add(final int index, final Figure figure) {
super.add(index, (ODGBezierFigure) figure);
}
@Override public ODGBezierFigure getChild(int index) {
return (ODGBezierFigure) super.getChild(index);
}
public ODGPathFigure clone() {
ODGPathFigure that = (ODGPathFigure) super.clone();
return that;
}
public void flattenTransform() {
willChange();
AffineTransform tx = TRANSFORM.get(this);
if (tx != null) {
for (Figure child : getChildren()) {
((ODGBezierFigure) child).transform(tx);
((ODGBezierFigure) child).flattenTransform();
}
}
TRANSFORM.basicSet(this, null);
changed();
}
}