/*******************************************************************************
* 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
******************************************************************************/
/*
* @(#)AttributeKeys.java 1.5.1 2009-03-29
*
* Copyright (c) 1996-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 org.jhotdraw.util.ResourceBundleUtil;
import java.awt.*;
import java.awt.geom.*;
import java.util.*;
import org.jhotdraw.geom.*;
/**
* Defines AttributeKeys used by the Figures in this package as well as some
* helper methods.
* <p>
* If you are developing an applications that uses a different set or an
* extended set of attributes, it is best, to create a new AttributeKeys class,
* and to define all needed AttributeKeys as static variables in there.
*
* @author Werner Randelshofer
* @version 1.5.1 2009-03-29 Method getStroke does not attempt to create a
* dashed line, if all dashes are 0.
* <br>1.8 2008-05-28 Added LAYOUT_ALIGNMENT.
* <br>1.4 2007-12-16 Added CANVAS_FILL_COLOR.
* <br>1.3 2006-12-09 Streamlined to better support SVG.
* <br>1.2 2006-07-09 Stroke dash factor added.
* <br>1.1 2006-06-07 Changed all values to double.
* <br>1.0 23. 3. 2006 Created.
*/
public class AttributeKeys {
private final static ResourceBundleUtil labels = ResourceBundleUtil.getBundle("org.jhotdraw.draw.Labels");
/**
* Canvas fill color. The value of this attribute is a Color object.
* This attribute is used by a Drawing object to specify the fill color
* of the drawing. The default value is white.
*/
public final static AttributeKey<Color> CANVAS_FILL_COLOR = new AttributeKey<Color>("canvasFillColor", Color.class, Color.white, true, labels);
/**
* Canvas fill opacity. The value of this attribute is a Double object.
* This is a value between 0 and 1 whereas 0 is translucent and 1 is fully opaque.
*/
public final static AttributeKey<Double> CANVAS_FILL_OPACITY = new AttributeKey<Double>("canvasFillOpacity", Double.class, 1d, false, labels);
/**
* The width of the canvas. The value of this attribute is a Double object.
* This is a value between 1 and Double.MAX_VALUE. If the value is null, the
* width is dynamically adapted to the content of the drawing.
*/
public final static AttributeKey<Double> CANVAS_WIDTH = new AttributeKey<Double>("canvasWidth", Double.class, null, true, labels);
/**
* The height of the canvas. The value of this attribute is a Double object.
* This is a value between 1 and Double.MAX_VALUE. If the value is null, the
* height is dynamically adapted to the content of the drawing.
*/
public final static AttributeKey<Double> CANVAS_HEIGHT = new AttributeKey<Double>("canvasHeight", Double.class, null, true, labels);
/**
* Figure fill color. The value of this attribute is a Color object.
*/
public final static AttributeKey<Color> FILL_COLOR = new AttributeKey<Color>("fillColor", Color.class, Color.white, true, labels);
/**
* Close BezierFigure. The value of this attribute is a Boolean object.
*/
public final static AttributeKey<Boolean> CLOSED = new AttributeKey<Boolean>("closed", Boolean.class, false, false, labels);
/**
* Fill BezierFigure. The value of this attribute is a Boolean object.
*/
public final static AttributeKey<Boolean> FILL_OPEN_PATH = new AttributeKey<Boolean>("fillOpenPath", Boolean.class, false, false, labels);
public static enum WindingRule {
/**
* If WINDING_RULE is set to this value, an even-odd winding rule
* is used for determining the interior of a path.
*/
EVEN_ODD,
/**
* If WINDING_RULE is set to this value, a non-zero winding rule
* is used for determining the interior of a path.
*/
NON_ZERO
}
/**
* Fill under stroke. The value of this attribute is a Boolean object.
*/
public final static AttributeKey<WindingRule> WINDING_RULE = new AttributeKey<WindingRule>("windingRule", WindingRule.class, WindingRule.EVEN_ODD, false, labels);
public static enum Underfill {
/**
* If FILL_UNDER_STROKE is set to this value, the area under the
* stroke will not be filled.
*/
NONE,
/**
* If FILL_UNDER_STROKE is set to this value, the area under the stroke
* is filled to the center of the stroke. This is the default behavior
* of Graphics2D.fill(Shape), Graphics2D.draw(Shape) when using the
* same shape object.
*/
CENTER,
/**
* If FILL_UNDER_STROKE is set to this value, the area under the
* stroke will be filled.
*/
FULL
}
/**
* Fill under stroke. The value of this attribute is a Boolean object.
*/
public final static AttributeKey<Underfill> FILL_UNDER_STROKE = new AttributeKey<Underfill>("fillUnderStroke", Underfill.class, Underfill.CENTER, false, labels);
/**
* Stroke color. The value of this attribute is a Color object.
*/
public final static AttributeKey<Color> STROKE_COLOR = new AttributeKey<Color>("strokeColor", Color.class, Color.black, true, labels);
/**
* Stroke width. A double used to construct a BasicStroke or the
* outline of a DoubleStroke.
*/
public final static AttributeKey<Double> STROKE_WIDTH = new AttributeKey<Double>("strokeWidth", Double.class, 1d, false, labels);
/**
* Factor for the stroke inner width. This is a double. The default value
* is 2.
*
* @deprecated This is not flexible enough. Lets replace this with a
* STROKE_STRIPES_ARRAY<Double[]> and a IS_STROKE_STRIPES_FACTOR.
*/
public final static AttributeKey<Double> STROKE_INNER_WIDTH_FACTOR = new AttributeKey<Double>("innerStrokeWidthFactor", Double.class, 2d, false, labels);
/**
* Stroke join. One of the BasicStroke.JOIN_... values used to
* construct a BasicStroke.
*/
public final static AttributeKey<Integer> STROKE_JOIN = new AttributeKey<Integer>("strokeJoin", Integer.class, BasicStroke.JOIN_MITER, false, labels);
/**
* Stroke join. One of the BasicStroke.CAP_... values used to
* construct a BasicStroke.
*/
public final static AttributeKey<Integer> STROKE_CAP = new AttributeKey<Integer>("strokeCap", Integer.class, BasicStroke.CAP_BUTT, false, labels);
/**
* Stroke miter limit factor. A double multiplied by total stroke width,
* used to construct the miter limit of a BasicStroke.
*/
public final static AttributeKey<Double> STROKE_MITER_LIMIT = new AttributeKey<Double>("strokeMiterLimitFactor", Double.class, 3d, false, labels);
/**
* A boolean used to indicate whether STROKE_MITER_LIMIT is a factor of
* STROKE_WIDTH, or whether it represents an absolute value.
*/
public final static AttributeKey<Boolean> IS_STROKE_MITER_LIMIT_FACTOR = new AttributeKey<Boolean>("isStrokeMiterLimitFactor", Boolean.class, true, false, labels);
/**
* An array of doubles used to specify the dash pattern in
* a BasicStroke;
*/
public final static AttributeKey<double[]> STROKE_DASHES = new AttributeKey<double[]>("strokeDashes", double[].class, null, true, labels);
/**
* A double used to specify the starting phase of the stroke dashes.
*/
public final static AttributeKey<Double> STROKE_DASH_PHASE = new AttributeKey<Double>("strokeDashPhase", Double.class, 0d, false, labels);
/**
* A boolean used to indicate whether STROKE_DASHES and STROKE_DASH_PHASE
* shall be interpreted as factors of STROKE_WIDTH, or whether they are
* absolute values.
*/
public final static AttributeKey<Boolean> IS_STROKE_DASH_FACTOR = new AttributeKey<Boolean>("isStrokeDashFactor", Boolean.class, true, false, labels);
public static enum StrokeType {
/**
* If STROKE_TYPE is set to this value, a BasicStroke instance is used
* for stroking.
*/
BASIC,
/**
* If STROKE_TYPE is set to this value, a DoubleStroke instance is used
* for stroking.
* @deprecated This is not flexible enough. Lets replace this with
* STRIPED. for example to support for striped strokes.
*/
DOUBLE
}
/**
* Stroke type. The value of this attribute is either VALUE_STROKE_TYPE_BASIC
* or VALUE_STROKE_TYPE_DOUBLE.
* FIXME - Type should be an enumeration.
*/
public final static AttributeKey<StrokeType> STROKE_TYPE = new AttributeKey<StrokeType>("strokeType", StrokeType.class, StrokeType.BASIC, false, labels);
public static enum StrokePlacement {
/**
* If STROKE_PLACEMENT is set to this value, the stroke is centered
* on the path.
*/
CENTER,
/**
* If STROKE_PLACEMENT is set to this value, the stroke is placed
* inside of a closed path.
*/
INSIDE,
/**
* If STROKE_PLACEMENT is set to this value, the stroke is placed
* outside of a closed path.
*/
OUTSIDE
}
/**
* Stroke placement. The value is either StrokePlacement.CENTER,
* StrokePlacement.INSIDE or StrokePlacement.OUTSIDE.
* This only has effect for closed paths. On open paths, the stroke
* is always centered on the path.
* <p>
* The default value is StrokePlacement.CENTER.
*/
public final static AttributeKey<StrokePlacement> STROKE_PLACEMENT = new AttributeKey<StrokePlacement>("strokePlacement", StrokePlacement.class, StrokePlacement.CENTER, false, labels);
/**
* The value of this attribute is a String object, which is used to
* display the text of the figure.
*/
public final static AttributeKey<String> TEXT = new AttributeKey<String>("text", String.class, null, true, labels);
/**
* Text color. The value of this attribute is a Color object.
*/
public final static AttributeKey<Color> TEXT_COLOR = new AttributeKey<Color>("textColor", Color.class, Color.BLACK, false, labels);
/**
* Text shadow color. The value of this attribute is a Color object.
*/
public final static AttributeKey<Color> TEXT_SHADOW_COLOR = new AttributeKey<Color>("textShadowColor", Color.class, null, true, labels);
/**
* Text shadow offset. The value of this attribute is a Dimension2DDouble object.
*/
public final static AttributeKey<Dimension2DDouble> TEXT_SHADOW_OFFSET = new AttributeKey<Dimension2DDouble>("textShadowOffset", Dimension2DDouble.class, new Dimension2DDouble(1d, 1d), false, labels);
public static enum Alignment {
/** align on the left or the top */
LEADING,
/** align on the right or the bottom */
TRAILING,
/** align in the center */
CENTER,
/** stretch to fill horizontally, or vertically */
BLOCK,
}
/**
* Text alignment. The value of this attribute is a Alignment enum.
*/
public final static AttributeKey<Alignment> TEXT_ALIGNMENT = new AttributeKey<Alignment>("textAlignment", Alignment.class, Alignment.LEADING, false, labels);
/**
* The value of this attribute is a Font object, which is used as a prototype
* to create the font for the text.
*/
public final static AttributeKey<Font> FONT_FACE = new AttributeKey<Font>("fontFace", Font.class, new Font("VERDANA", Font.PLAIN, 10), false, labels);
/**
* The value of this attribute is a double object.
*/
public final static AttributeKey<Double> FONT_SIZE = new AttributeKey<Double>("fontSize", Double.class, 12d, false, labels);
/**
* The value of this attribute is a Boolean object.
*/
public final static AttributeKey<Boolean> FONT_BOLD = new AttributeKey<Boolean>("fontBold", Boolean.class, false, false, labels);
/**
* The value of this attribute is a Boolean object.
*/
public final static AttributeKey<Boolean> FONT_ITALIC = new AttributeKey<Boolean>("fontItalic", Boolean.class, false, false, labels);
/**
* The value of this attribute is a Boolean object.
*/
public final static AttributeKey<Boolean> FONT_UNDERLINE = new AttributeKey<Boolean>("fontUnderlined", Boolean.class, false, false, labels);
/**
* The value of this attribute is a Liner object.
*/
public final static AttributeKey<Liner> BEZIER_PATH_LAYOUTER = new AttributeKey<Liner>("bezierPathLayouter", Liner.class, null, true, labels);
public static final AttributeKey<LineDecoration> END_DECORATION = new AttributeKey<LineDecoration>("endDecoration", LineDecoration.class, null, true, labels);
public static final AttributeKey<LineDecoration> START_DECORATION = new AttributeKey<LineDecoration>("startDecoration", LineDecoration.class, null, true, labels);
/**
* The value of this attribute is a Insets2D.Double object.
*/
public static final AttributeKey<Insets2D.Double> DECORATOR_INSETS = new AttributeKey<Insets2D.Double>("decoratorInsets", Insets2D.Double.class, new Insets2D.Double(), false, labels);
/**
* The value of this attribute is a Insets2D.Double object.
* <p>
* This attribute can be set on a CompositeFigure, which uses
* a Layouter to lay out its children.
* <p>
* The insets are used to determine the insets between the bounds
* of the CompositeFigure and its children.
*/
public final static AttributeKey<Insets2D.Double> LAYOUT_INSETS = new AttributeKey<Insets2D.Double>("borderInsets", Insets2D.Double.class, new Insets2D.Double(), false, labels);
/**
* The value of this attribute is a Alignment object.
* <p>
* This attribute can be set on a CompositeFigure, which uses
* a Layouter to lay out its children.
* <p>
* The insets are used to determine the default alignment of
* the children of the CompositeFigure.
*/
public final static AttributeKey<Alignment> COMPOSITE_ALIGNMENT = new AttributeKey<Alignment>("layoutAlignment", Alignment.class, Alignment.BLOCK, false, labels);
/**
* The value of this attribute is a Alignment object.
* <p>
* This attribute can be set on a child of a CompositeFigure, which uses
* a Layouter to lay out its children.
* <p>
* Layouters should use this attribute, to determine the default alignment
* of the child figures contained in the CompositeFigure which they lay out.
*/
public final static AttributeKey<Alignment> CHILD_ALIGNMENT = new AttributeKey<Alignment>("layoutAlignment", Alignment.class, null, true, labels);
/**
* Specifies the transform of a Figure.
*/
public final static AttributeKey<AffineTransform> TRANSFORM = new AttributeKey<AffineTransform>("transform", AffineTransform.class, null, true, labels);
public static enum Orientation {
NORTH,
NORTH_EAST,
EAST,
SOUTH_EAST,
SOUTH,
SOUTH_WEST,
WEST,
NORTH_WEST
}
/**
* Specifies the orientation of a Figure.
*/
public final static AttributeKey<Orientation> ORIENTATION = new AttributeKey<Orientation>("orientation", Orientation.class, Orientation.NORTH, false, labels);
/**
* A set with all attributes defined by this class.
*/
public final static Set<AttributeKey> supportedAttributes;
public final static Map<String, AttributeKey> supportedAttributeMap;
static {
HashSet<AttributeKey> as = new HashSet<AttributeKey>();
as.addAll(Arrays.asList(new AttributeKey[]{
FILL_COLOR,
FILL_UNDER_STROKE,
STROKE_COLOR,
STROKE_WIDTH,
STROKE_INNER_WIDTH_FACTOR,
STROKE_JOIN,
STROKE_CAP,
STROKE_MITER_LIMIT,
STROKE_DASHES,
STROKE_DASH_PHASE,
STROKE_TYPE,
STROKE_PLACEMENT,
TEXT,
TEXT_COLOR,
TEXT_SHADOW_COLOR,
TEXT_SHADOW_OFFSET,
TRANSFORM,
FONT_FACE,
FONT_SIZE,
FONT_BOLD,
FONT_ITALIC,
FONT_UNDERLINE,
BEZIER_PATH_LAYOUTER,
END_DECORATION,
START_DECORATION,
DECORATOR_INSETS,
ORIENTATION,
WINDING_RULE,}));
supportedAttributes = Collections.unmodifiableSet(as);
HashMap<String, AttributeKey> am = new HashMap<String, AttributeKey>();
for (AttributeKey a : as) {
am.put(a.getKey(), a);
}
supportedAttributeMap = Collections.unmodifiableMap(am);
}
/**
* Convenience method for computing the total stroke width from the
* STROKE_WIDTH, STROKE_INNER_WIDTH and STROKE_TYPE attributes.
*/
public static double getStrokeTotalWidth(Figure f) {
switch (STROKE_TYPE.get(f)) {
case BASIC:
default:
return STROKE_WIDTH.get(f);
// break; not reached
case DOUBLE:
return STROKE_WIDTH.get(f) * (1d + STROKE_INNER_WIDTH_FACTOR.get(f));
// break; not reached
}
}
/**
* Convenience method for computing the total stroke miter limit from the
* STROKE_MITER_LIMIT, and IS_STROKE_MITER_LIMIT factor.
*/
public static double getStrokeTotalMiterLimit(Figure f) {
if (IS_STROKE_MITER_LIMIT_FACTOR.get(f)) {
return STROKE_MITER_LIMIT.get(f) * STROKE_WIDTH.get(f);
} else {
return STROKE_MITER_LIMIT.get(f);
}
}
public static Stroke getStroke(Figure f) {
double strokeWidth = STROKE_WIDTH.get(f);
float miterLimit = (float) getStrokeTotalMiterLimit(f);
double dashFactor = IS_STROKE_DASH_FACTOR.get(f) ? strokeWidth : 1d;
double dashPhase = STROKE_DASH_PHASE.get(f);
double[] ddashes = STROKE_DASHES.get(f);
float[] dashes = null;
boolean isAllZeroes = true;
if (ddashes != null) {
dashes = new float[ddashes.length];
double dashSize = 0f;
for (int i = 0; i < dashes.length; i++) {
dashes[i] = Math.max(0f, (float) (ddashes[i] * dashFactor));
dashSize += dashes[i];
if (isAllZeroes && dashes[i] != 0) {
isAllZeroes = false;
}
}
if (dashes.length % 2 == 1) {
dashSize *= 2;
}
if (dashPhase < 0) {
dashPhase = dashSize + dashPhase % dashSize;
}
}
if (isAllZeroes) {
// don't draw dashes, if all values are 0.
dashes = null;
}
switch (STROKE_TYPE.get(f)) {
case BASIC:
default:
return new BasicStroke((float) strokeWidth,
STROKE_CAP.get(f),
STROKE_JOIN.get(f),
miterLimit,
dashes, Math.max(0, (float) (dashPhase * dashFactor)));
//not reached
case DOUBLE:
return new DoubleStroke(
(float) (STROKE_INNER_WIDTH_FACTOR.get(f) * strokeWidth),
(float) strokeWidth,
STROKE_CAP.get(f),
STROKE_JOIN.get(f),
miterLimit,
dashes, Math.max(0, (float) (dashPhase * dashFactor)));
//not reached
}
}
/**
* Returns a stroke which is useful for hit-testing.
* The stroke reflects the stroke width, but not the stroke dashes
* attribute.
* @param f
* @return A stroke suited for creating a shape for hit testing.
*/
public static Stroke getHitStroke(Figure f) {
double strokeWidth = Math.max(1, STROKE_WIDTH.get(f));
float miterLimit = (float) getStrokeTotalMiterLimit(f);
double dashFactor = IS_STROKE_DASH_FACTOR.get(f) ? strokeWidth : 1d;
switch (STROKE_TYPE.get(f)) {
case BASIC:
default:
return new BasicStroke((float) strokeWidth,
STROKE_CAP.get(f),
STROKE_JOIN.get(f),
miterLimit,
null, Math.max(0, (float) (STROKE_DASH_PHASE.get(f) * dashFactor)));
//not reached
case DOUBLE:
return new DoubleStroke(
(float) (STROKE_INNER_WIDTH_FACTOR.get(f) * strokeWidth),
(float) strokeWidth,
STROKE_CAP.get(f),
STROKE_JOIN.get(f),
miterLimit,
null, Math.max(0, (float) (STROKE_DASH_PHASE.get(f).floatValue() * dashFactor)));
//not reached
}
}
public static Font getFont(Figure f) {
Font prototype = FONT_FACE.get(f);
if (prototype == null) {
return null;
}
if (getFontStyle(f) != Font.PLAIN) {
return prototype.deriveFont(getFontStyle(f), FONT_SIZE.get(f).floatValue());
} else {
return prototype.deriveFont(FONT_SIZE.get(f).floatValue());
}
}
public static int getFontStyle(Figure f) {
int style = Font.PLAIN;
if (FONT_BOLD.get(f)) {
style |= Font.BOLD;
}
if (FONT_ITALIC.get(f)) {
style |= Font.ITALIC;
}
return style;
}
/**
* Returns the distance, that a Rectangle needs to grow (or shrink) to
* fill its shape as specified by the FILL_UNDER_STROKE and STROKE_POSITION
* attributes of a figure.
* The value returned is the number of units that need to be grown (or shrunk)
* perpendicular to a stroke on an outline of the shape.
*/
public static double getPerpendicularFillGrowth(Figure f) {
double grow;
double strokeWidth = AttributeKeys.getStrokeTotalWidth(f);
StrokePlacement placement = STROKE_PLACEMENT.get(f);
switch (FILL_UNDER_STROKE.get(f)) {
case FULL:
switch (placement) {
case INSIDE:
grow = 0f;
break;
case OUTSIDE:
grow = strokeWidth;
break;
case CENTER:
default:
grow = strokeWidth / 2d;
break;
}
break;
case NONE:
switch (placement) {
case INSIDE:
grow = -strokeWidth;
break;
case OUTSIDE:
grow = 0f;
break;
case CENTER:
default:
grow = strokeWidth / -2d;
break;
}
break;
case CENTER:
default:
switch (placement) {
case INSIDE:
grow = strokeWidth / -2d;
break;
case OUTSIDE:
grow = strokeWidth / 2d;
break;
case CENTER:
default:
grow = 0d;
break;
}
break;
}
return grow;
}
/**
* Returns the distance, that a Rectangle needs to grow (or shrink) to
* draw (aka stroke) its shape as specified by the FILL_UNDER_STROKE and
* STROKE_POSITION attributes of a figure.
* The value returned is the number of units that need to be grown (or shrunk)
* perpendicular to a stroke on an outline of the shape.
*/
public static double getPerpendicularDrawGrowth(Figure f) {
double grow;
double strokeWidth = AttributeKeys.getStrokeTotalWidth(f);
switch (STROKE_PLACEMENT.get(f)) {
case INSIDE:
grow = strokeWidth / -2d;
break;
case OUTSIDE:
grow = strokeWidth / 2d;
break;
case CENTER:
default:
grow = 0f;
break;
}
return grow;
}
/**
* Returns the distance, that a Rectangle needs to grow (or shrink) to
* make hit detections on a shape as specified by the FILL_UNDER_STROKE and STROKE_POSITION
* attributes of a figure.
* The value returned is the number of units that need to be grown (or shrunk)
* perpendicular to a stroke on an outline of the shape.
*/
public static double getPerpendicularHitGrowth(Figure f) {
double grow;
if (STROKE_COLOR.get(f) == null) {
grow = getPerpendicularFillGrowth(f);
} else {
double strokeWidth = AttributeKeys.getStrokeTotalWidth(f);
grow = getPerpendicularDrawGrowth(f) + strokeWidth / 2d;
}
return grow;
}
}