/*
* (c) Copyright 2010-2011 AgileBirds
*
* This file is part of OpenFlexo.
*
* OpenFlexo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* OpenFlexo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenFlexo. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.openflexo.fge.shapes;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.util.List;
import java.util.Vector;
import java.util.logging.Logger;
import org.openflexo.fge.ShapeGraphicalRepresentation;
import org.openflexo.fge.cp.ControlPoint;
import org.openflexo.fge.cp.ShapeResizingControlPoint;
import org.openflexo.fge.geom.FGELine;
import org.openflexo.fge.geom.FGEPoint;
import org.openflexo.fge.geom.FGEShape;
import org.openflexo.fge.geom.area.FGEArea;
import org.openflexo.fge.geom.area.FGEEmptyArea;
import org.openflexo.fge.geom.area.FGEHalfBand;
import org.openflexo.fge.geom.area.FGEHalfLine;
import org.openflexo.fge.graphics.BackgroundStyle;
import org.openflexo.fge.graphics.FGEShapeGraphics;
import org.openflexo.fge.graphics.ForegroundStyle;
import org.openflexo.kvc.KVCObject;
import org.openflexo.xmlcode.XMLSerializable;
public abstract class Shape extends KVCObject implements XMLSerializable, Cloneable {
private static final Logger logger = Logger.getLogger(Shape.class.getPackage().getName());
private transient ShapeGraphicalRepresentation graphicalRepresentation;
private transient Vector<ControlPoint> _controlPoints = null;
public static final FGEPoint CENTER = new FGEPoint(0.5, 0.5);
public static final FGEPoint NORTH_EAST = new FGEPoint(1, 0);
public static final FGEPoint SOUTH_EAST = new FGEPoint(1, 1);
public static final FGEPoint SOUTH_WEST = new FGEPoint(0, 1);
public static final FGEPoint NORTH_WEST = new FGEPoint(0, 0);
public static final FGEPoint NORTH = new FGEPoint(0.5, 0);
public static final FGEPoint EAST = new FGEPoint(1, 0.5);
public static final FGEPoint SOUTH = new FGEPoint(0.5, 1);
public static final FGEPoint WEST = new FGEPoint(0, 0.5);
public static enum ShapeType {
RECTANGLE, SQUARE, RECTANGULAROCTOGON, POLYGON, TRIANGLE, LOSANGE, OVAL, CIRCLE, STAR, ARC, CUSTOM_POLYGON/*
* ,
* CUSTOM_COMPLEX_SHAPE
*/
}
// *******************************************************************************
// * Constructor *
// *******************************************************************************
public Shape(ShapeGraphicalRepresentation aGraphicalRepresentation) {
super();
graphicalRepresentation = aGraphicalRepresentation;
}
public static Shape makeShape(ShapeType type, ShapeGraphicalRepresentation aGraphicalRepresentation) {
if (type == ShapeType.SQUARE) {
return new Square(aGraphicalRepresentation);
} else if (type == ShapeType.RECTANGLE) {
return new Rectangle(aGraphicalRepresentation);
} else if (type == ShapeType.TRIANGLE) {
return new Triangle(aGraphicalRepresentation);
} else if (type == ShapeType.LOSANGE) {
return new Losange(aGraphicalRepresentation);
} else if (type == ShapeType.RECTANGULAROCTOGON) {
return new RectangularOctogon(aGraphicalRepresentation);
} else if (type == ShapeType.POLYGON) {
return new RegularPolygon(aGraphicalRepresentation);
} else if (type == ShapeType.CUSTOM_POLYGON) {
return new Polygon(aGraphicalRepresentation);
} else if (type == ShapeType.OVAL) {
return new Oval(aGraphicalRepresentation);
} else if (type == ShapeType.CIRCLE) {
return new Circle(aGraphicalRepresentation);
} else if (type == ShapeType.STAR) {
return new Star(aGraphicalRepresentation);
} else if (type == ShapeType.ARC) {
return new Arc(aGraphicalRepresentation);
}
return null;
}
// *******************************************************************************
// * Methods *
// *******************************************************************************
public void setPaintAttributes(FGEShapeGraphics g) {
// Background
if (getGraphicalRepresentation().getIsSelected()) {
if (getGraphicalRepresentation().getHasSelectedBackground()) {
g.setDefaultBackground(getGraphicalRepresentation().getSelectedBackground());
} else if (getGraphicalRepresentation().getHasFocusedBackground()) {
g.setDefaultBackground(getGraphicalRepresentation().getFocusedBackground());
} else {
g.setDefaultBackground(getGraphicalRepresentation().getBackground());
}
} else if (getGraphicalRepresentation().getIsFocused() && getGraphicalRepresentation().getHasFocusedBackground()) {
g.setDefaultBackground(getGraphicalRepresentation().getFocusedBackground());
} else {
g.setDefaultBackground(getGraphicalRepresentation().getBackground());
}
// Foreground
if (getGraphicalRepresentation().getIsSelected()) {
if (getGraphicalRepresentation().getHasSelectedForeground()) {
g.setDefaultForeground(getGraphicalRepresentation().getSelectedForeground());
} else if (getGraphicalRepresentation().getHasFocusedForeground()) {
g.setDefaultForeground(getGraphicalRepresentation().getFocusedForeground());
} else {
g.setDefaultForeground(getGraphicalRepresentation().getForeground());
}
} else if (getGraphicalRepresentation().getIsFocused() && getGraphicalRepresentation().getHasFocusedForeground()) {
g.setDefaultForeground(getGraphicalRepresentation().getFocusedForeground());
} else {
g.setDefaultForeground(getGraphicalRepresentation().getForeground());
}
// Text
g.setDefaultTextStyle(getGraphicalRepresentation().getTextStyle());
}
/**
* Must be overriden when shape requires it
*
* @return
*/
public boolean areDimensionConstrained() {
return false;
}
public abstract ShapeType getShapeType();
/**
* Return geometric shape of this shape
*
* @return
*/
public abstract FGEShape<?> getShape();
/**
* Return outline for geometric shape of this shape (this is the shape itself, but NOT filled)
*
* @return
*/
public final FGEShape getOutline() {
FGEShape<?> outline = (FGEShape<?>) getShape().clone();
outline.setIsFilled(false);
return outline;
}
public final ShapeGraphicalRepresentation getGraphicalRepresentation() {
return graphicalRepresentation;
}
public final void setGraphicalRepresentation(ShapeGraphicalRepresentation aGR) {
if (aGR != graphicalRepresentation) {
// logger.info("Shape "+this+" changed GR from "+graphicalRepresentation+" to "+aGR);
graphicalRepresentation = aGR;
updateShape();
}
}
public List<ControlPoint> getControlPoints() {
if (_controlPoints == null) {
rebuildControlPoints();
}
return _controlPoints;
}
public List<ControlPoint> rebuildControlPoints() {
// logger.info("For Shape "+this+" rebuildControlPoints()");
if (_controlPoints != null) {
_controlPoints.clear();
} else {
_controlPoints = new Vector<ControlPoint>();
}
if (getGraphicalRepresentation() == null) {
return _controlPoints;
}
if (getShape() == null) {
updateShape();
}
if (getShape().getControlPoints() != null) {
for (FGEPoint pt : getShape().getControlPoints()) {
_controlPoints.add(new ShapeResizingControlPoint(getGraphicalRepresentation(), pt, null));
}
}
return _controlPoints;
}
/**
* Return nearest point located on outline, asserting aPoint is related to shape coordinates, and normalized to shape
*
* @param aPoint
* @return
*/
public FGEPoint nearestOutlinePoint(FGEPoint aPoint) {
return getShape().nearestOutlinePoint(aPoint);
}
/**
* Return flag indicating if position represented is located inside shape, asserting aPoint is related to shape coordinates, and
* normalized to shape
*
* @param aPoint
* @return
*/
public boolean isPointInsideShape(FGEPoint aPoint) {
return getShape().containsPoint(aPoint);
}
/**
* Compute point where supplied line intersects with shape outline trying to minimize distance from "from" point
*
* Returns null if no intersection was found
*
* @param aLine
* @param from
* @return
*/
public final FGEPoint outlineIntersect(FGELine line, FGEPoint from) {
FGEArea intersection = getShape().intersect(line);
return intersection.getNearestPoint(from);
}
/**
* Compute point where a line formed by current shape's center and "from" point intersects with shape outline trying to minimize
* distance from "from" point This implementation provide simplified computation with outer bounds (relative coordinates (0,0)-(1,1))
* and must be overriden when required
*
* Returns null if no intersection was found
*
* @param aLine
* @param from
* @return
*/
public final FGEPoint outlineIntersect(FGEPoint from) {
FGELine line = new FGELine(new FGEPoint(0.5, 0.5), from);
return outlineIntersect(line, from);
}
public FGEArea getAllowedHorizontalConnectorLocationFromEast() {
FGEHalfLine north = new FGEHalfLine(1, 0, 2, 0);
FGEHalfLine south = new FGEHalfLine(1, 1, 2, 1);
return new FGEHalfBand(north, south);
}
public FGEArea getAllowedHorizontalConnectorLocationFromWest2() {
double maxY = Double.NEGATIVE_INFINITY;
double minY = Double.POSITIVE_INFINITY;
for (ControlPoint cp : getControlPoints()) {
FGEPoint p = cp.getPoint();
FGEHalfLine hl = new FGEHalfLine(p.x, p.y, p.x - 1, p.y);
FGEArea inters = getShape().intersect(hl);
System.out.println("inters=" + inters);
if (inters instanceof FGEPoint || inters instanceof FGEEmptyArea) {
// Consider this point
if (p.y > maxY) {
maxY = p.y;
}
if (p.y < minY) {
minY = p.y;
}
}
}
FGEHalfLine north = new FGEHalfLine(0, minY, -1, minY);
FGEHalfLine south = new FGEHalfLine(0, maxY, -1, maxY);
/*
* FGEHalfLine north = new FGEHalfLine(0,0,-1,0); FGEHalfLine south =
* new FGEHalfLine(0,1,-1,1);
*/
if (north.overlap(south)) {
System.out.println("Return a " + north.intersect(south));
return north.intersect(south);
}
return new FGEHalfBand(north, south);
}
public FGEArea getAllowedHorizontalConnectorLocationFromWest() {
FGEHalfLine north = new FGEHalfLine(0, 0, -1, 0);
FGEHalfLine south = new FGEHalfLine(0, 1, -1, 1);
return new FGEHalfBand(north, south);
}
public FGEArea getAllowedVerticalConnectorLocationFromNorth() {
FGEHalfLine east = new FGEHalfLine(1, 0, 1, -1);
FGEHalfLine west = new FGEHalfLine(0, 0, 0, -1);
return new FGEHalfBand(east, west);
}
public FGEArea getAllowedVerticalConnectorLocationFromSouth() {
FGEHalfLine east = new FGEHalfLine(1, 1, 1, 2);
FGEHalfLine west = new FGEHalfLine(0, 1, 0, 2);
return new FGEHalfBand(east, west);
}
public final void paintShadow(FGEShapeGraphics g) {
double deep = getGraphicalRepresentation().getShadowStyle().getShadowDepth();
int blur = getGraphicalRepresentation().getShadowStyle().getShadowBlur();
double viewWidth = getGraphicalRepresentation().getViewWidth(1.0);
double viewHeight = getGraphicalRepresentation().getViewHeight(1.0);
AffineTransform shadowTranslation = AffineTransform.getTranslateInstance(deep / viewWidth, deep / viewHeight);
int darkness = getGraphicalRepresentation().getShadowStyle().getShadowDarkness();
Graphics2D oldGraphics = g.cloneGraphics();
Area clipArea = new Area(new java.awt.Rectangle(0, 0, getGraphicalRepresentation().getViewWidth(g.getScale()),
getGraphicalRepresentation().getViewHeight(g.getScale())));
Area a = new Area(getGraphicalRepresentation().getShape().getShape());
a.transform(getGraphicalRepresentation().convertNormalizedPointToViewCoordinatesAT(g.getScale()));
clipArea.subtract(a);
g.getGraphics().clip(clipArea);
Color shadowColor = new Color(darkness, darkness, darkness);
ForegroundStyle foreground = ForegroundStyle.makeStyle(shadowColor);
foreground.setUseTransparency(true);
foreground.setTransparencyLevel(0.5f);
BackgroundStyle background = BackgroundStyle.makeColoredBackground(shadowColor);
background.setUseTransparency(true);
background.setTransparencyLevel(0.5f);
g.setDefaultForeground(foreground);
g.setDefaultBackground(background);
for (int i = blur - 1; i >= 0; i--) {
float transparency = 0.4f - i * 0.4f / blur;
foreground.setTransparencyLevel(transparency);
background.setTransparencyLevel(transparency);
AffineTransform at = AffineTransform.getScaleInstance((i + 1 + viewWidth) / viewWidth, (i + 1 + viewHeight) / viewHeight);
at.concatenate(shadowTranslation);
getShape().transform(at).paint(g);
}
g.releaseClonedGraphics(oldGraphics);
}
// @Override
public final void paintShape(FGEShapeGraphics g) {
setPaintAttributes(g);
getShape().paint(g);
// drawLabel(g);
}
// Override when required
public void notifyObjectResized() {
}
@Override
public Shape clone() {
try {
Shape returned = (Shape) super.clone();
returned._controlPoints = null;
returned.graphicalRepresentation = null;
returned.updateShape();
returned.rebuildControlPoints();
return returned;
} catch (CloneNotSupportedException e) {
// cannot happen since we are clonable
e.printStackTrace();
return null;
}
}
public abstract void updateShape();
@Override
public boolean equals(Object object) {
if (object instanceof Shape && getShape() != null) {
return getShape().equals(((Shape) object).getShape())
&& areDimensionConstrained() == ((Shape) object).areDimensionConstrained();
}
return super.equals(object);
}
@Override
public int hashCode() {
if (getShape() != null) {
return getShape().toString().hashCode();
}
return super.hashCode();
}
}