/* Copyright (C) 2001, 2006, 2007 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. */ package gov.nasa.worldwind.render; import gov.nasa.worldwind.Restorable; import gov.nasa.worldwind.util.Logging; import gov.nasa.worldwind.util.RestorableSupport; import java.awt.*; /** * {@link Annotation} attributes set. All {@link AbstractAnnotation} objects start life * referencing a new instance of this object. * <p> * This class also defines a static <b>default</b> attributes bundle containing default values for all attributes. * New <code>AnnotationAttributes</code> refer this static bundle as their default values source when an * attribute has not been set. * </p> * <p> * New <code>AnnotationAttributes</code> set have all their attributes pointing to the default values until * they are set by the application. Most attributes refer to the default value by using minus one (<code>-1</code>) * for munerics and <code>null</code> for objects. * </p> * <p> * The default attributes set can be changed for a non static one under the application control. The process * can be extended or cascaded to handle multiple levels of inheritance for default attributes. * </p> * @author Patrick Murris * @version $Id: AnnotationAttributes.java 5178 2008-04-25 21:51:20Z patrickmurris $ * @see AbstractAnnotation * @see FrameFactory * @see MultiLineTextRenderer */ public class AnnotationAttributes implements Restorable { private static final AnnotationAttributes defaults = new AnnotationAttributes(); static { defaults.setFrameShape(FrameFactory.SHAPE_RECTANGLE); defaults.setSize(new Dimension(160, 0)); defaults.setScale(1); defaults.setOpacity(1); defaults.setLeader(FrameFactory.LEADER_TRIANGLE); defaults.setCornerRadius(6); defaults.setAdjustWidthToText(Annotation.SIZE_FIT_TEXT); defaults.setDrawOffset(new Point(-10, 20)); defaults.setHighlightScale(1.2); defaults.setInsets(new Insets(6, 6, 6, 6)); defaults.setFont(Font.decode("Arial-PLAIN-12")); defaults.setTextAlign(MultiLineTextRenderer.ALIGN_LEFT); defaults.setTextColor(new Color(1f, 1f, 1f, .8f)); defaults.setBackgroundColor(new Color(0f, 0f, 0f, .4f)); defaults.setBorderColor(new Color(1f, 1f, 1f, .7f)); defaults.setBorderWidth(1); defaults.setBorderStippleFactor(0); defaults.setBorderStipplePattern((short)0xAAAA); defaults.setAntiAliasHint(Annotation.ANTIALIAS_FASTEST); defaults.setImageScale(1); defaults.setImageOffset(new Point(0, 0)); defaults.setImageOpacity(.7); defaults.setImageRepeat(Annotation.IMAGE_REPEAT_XY); defaults.setDistanceMinScale(.5); defaults.setDistanceMaxScale(2); defaults.setDistanceMinOpacity(.3); defaults.setEffect(MultiLineTextRenderer.EFFECT_NONE); } private AnnotationAttributes defaultAttributes = defaults; private String frameShape; // Use default (null) private Dimension size; // Use default (null) private double scale = -1; // Use default (-1) private double opacity = -1; // Use default (-1) private String leader; // Use default (null) private int cornerRadius = -1; // Use default (-1) private String adjustWidthToText; // Use default (null) private Point drawOffset; // Use default (null) private boolean isHighlighted = false; private boolean isVisible = true; private double highlightScale = -1; // Use default (-1) private Font font; // Use default (null) private int textAlign = -1; // Use default (-1) private Color textColor; // Use default (null) private Color backgroundColor; // Use default (null) private Color borderColor; // Use default (null) private double borderWidth = -1; // Use default (-1) private int borderStippleFactor = -1; // Use default (-1) private short borderStipplePattern = (short) 0x0000; // Use default (zero) private int antiAliasHint = -1; // Use default (-1) private Insets insets; // Use default (null) private Object imageSource; private double imageScale = -1; // Use default (-1) private Point imageOffset; // Use default (null) private double imageOpacity = -1; // Use default (-1) private String imageRepeat; // Use default (null) private double distanceMinScale = -1; // Use default (-1) private double distanceMaxScale = -1; // Use default (-1) private double distanceMinOpacity = -1; // Use default (-1) private String effect; // Use default (null) //** Public properties ********************************************************************** /** * Set the fallback default attributes set. * @param attr the default attributes set. */ public void setDefaults(AnnotationAttributes attr) { if (attr == null) { String message = Logging.getMessage("nullValue.AnnotationAttributesIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.defaultAttributes = attr; } /** * Get the callout frame shape. Can be one of {@link FrameFactory}.SHAPE_RECTANGLE (default), SHAPE_ELLIPSE * or SHAPE_NONE. * @return the callout frame shape. */ public String getFrameShape() { return this.frameShape != null ? this.frameShape : defaultAttributes.getFrameShape(); } /** * Set the callout frame shape. Can be one of {@link FrameFactory}.SHAPE_RECTANGLE (default), SHAPE_ELLIPSE * or SHAPE_NONE. Set to <code>null</code> to use the default shape. * <p> * Note that SHAPE_ELLIPSE draws an ellipse <u>inside</u> the callout bounding rectangle set by its * size (see setSize()) or its text bounding rectangle (see setAdjustWidthToText() and setSize() with height * set to zero). It is often necessary to have larger Insets dimensions (see setInsets()) to avoid having * the text drawn outside the shape border. * </p> * @param shape the callout frame shape. */ public void setFrameShape(String shape) { this.frameShape = shape; } /** * Get whether the <code>Annotation</code> is highlighted and should be drawn bigger - see setHighlightScale(). * @return true if highlighted. */ public boolean isHighlighted() { return isHighlighted; } /** * Set whether the <code>Annotation</code> is highlighted and should be drawn bigger - see setHighlightScale(). * @param highlighted true if highlighted. */ public void setHighlighted(boolean highlighted) { isHighlighted = highlighted; } /** * Get the scaling factor applied to highlighted <code>Annotations</code>. * @return the scaling factor applied to highlighted <code>Annotations</code>. */ public double getHighlightScale() { return highlightScale > 0 ? this.highlightScale : defaultAttributes.getHighlightScale(); } /** * Set the scaling factor applied to highlighted <code>Annotations</code>. Set to minus one (<code>-1</code>) * to use the default value. * @param highlightScale the scaling factor applied to highlighted <code>Annotations</code>. */ public void setHighlightScale(double highlightScale) { this.highlightScale = highlightScale; } /** * Get the annotation callout preferred total dimension in pixels. * @return the callout preferred total dimension in pixels. */ public Dimension getSize() { return this.size != null ? this.size : defaultAttributes.getSize(); } /** * Set the annotation callout preferred total dimension in pixels. * <p> * If necessary, the text will be wraped into several lines so as not to exceed the callout preferred * <code><b>width</b></code> (minus the <code>Insets</code> <code>left</code> and <code>right</code> dimensions * - see setInsets()). * However, if setAdjustWidthToText() is set to true, the final callout width will follow that of the final * text bounding rectangle. * </p> * <p> * If necessary, the text will also be truncated so as not to exceed the given <code><b>height</b></code>. * A <code>zero</code> value (default) will have the callout follow the final text bounding rectangle height * (including the <code>Insets</code> <code>top</code> and <code>bottom</code>). * </p> * Set to <code>null</code> to use the default size. * @param size the callout preferred total dimension in pixels. */ public void setSize(Dimension size) { this.size = size; } /** * Get the scaling factor applied to the annotation. Default is 1. * @return the scaling factor applied to the annotation */ public double getScale() { return this.scale >= 0 ? this.scale : defaultAttributes.getScale(); } /** * Set the scaling factor to apply to the annotation. Default is 1. * Set to minus one (<code>-1</code>) to use the default value. * @param scale the scaling factor to apply to the annotation */ public void setScale(double scale) { this.scale = scale; } /** * Get the opacity factor applied to the annotation. Default is 1. * @return the opacity factor applied to the annotation */ public double getOpacity() { return this.opacity >= 0 ? this.opacity : defaultAttributes.getOpacity(); } /** * Set the opacity factor to apply to the annotation. Default is 1. * Set to minus one (<code>-1</code>) to use the default value. * @param opacity the opacity factor to apply to the annotation */ public void setOpacity(double opacity) { this.opacity = opacity; } /** * Get the callout shape leader type. Can be one of {@link FrameFactory}.LEADER_TRIANGLE (default) or LEADER_NONE. * @return the callout shape leader type. */ public String getLeader() { return this.leader != null ? this.leader : defaultAttributes.getLeader(); } /** * Set the callout shape leader type. Can be one of {@link FrameFactory}.LEADER_TRIANGLE (default) or LEADER_NONE. * @param leader the callout shape leader type. */ public void setLeader(String leader) { this.leader = leader; } /** * Get the callout shape rounded corners radius in pixels. A value of <code>zero</code> means no rounded corners. * @return the callout shape rounded corners radius in pixels. */ public int getCornerRadius() { return this.cornerRadius >= 0 ? this.cornerRadius : defaultAttributes.getCornerRadius(); } /** * Set the callout shape rounded corners radius in pixels. A value of <code>zero</code> means no rounded corners. * Set this attribute to minus one (<code>-1</code>) to use the default value. * @param radius the callout shape rounded corners radius in pixels. */ public void setCornerRadius(int radius) { this.cornerRadius = radius; } /** * Get whether the callout width should adjust to follow the wrapped text bounding rectangle width, * which may be smaller or larger then the preferred size depending on the text. Can be one * of {@link Annotation}.SIZE_FIXED or SIZE_FIT_TEXT. * @return whether the callout width is adjusted to follow the text bounding rectangle width. */ public String getAdjustWidthToText() { return this.adjustWidthToText != null ? this.adjustWidthToText : defaultAttributes.getAdjustWidthToText(); } /** * Set whether the callout width should adjust to follow the wrapped text bounding rectangle width * which may be smaller or larger then the preferred size depending on the text. Can be one * of {@link Annotation}.SIZE_FIXED (default) or SIZE_FIT_TEXT. * Setting this attribute to <code>SIZE_FIT_TEXT</code> would have the callout drawn at its exact width (see setSize()). * @param state whether the callout width should adjust to follow the text bounding rectangle width. */ public void setAdjustWidthToText(String state) { this.adjustWidthToText = state; } /** * Get the callout displacement offset in pixels from the globe Position or screen point at which it is associated. * When the callout has a leader (see setLeader(String leader)), it will lead to the original point. * In the actual implementation, the callout is drawn above its associated point and the leader connects at * the bottom of the frame, in the middle. Positive X increases toward the right and positive Y in the up direction. * @return the callout displacement offset in pixels */ public Point getDrawOffset() { return this.drawOffset != null ? this.drawOffset : defaultAttributes.getDrawOffset(); } /** * Set the callout displacement offset in pixels from the globe Position or screen point at which it is associated. * When the callout has a leader (see setLeader(String leader)), it will lead to the original point. * In the actual implementation, the callout is drawn above its associated point and the leader connects at * the bottom of the frame, in the middle. Positive X increases toward the right and positive Y in the up direction. * Set to <code>null</code> to use the default offset. * @param offset the callout displacement offset in pixels */ public void setDrawOffset(Point offset) { this.drawOffset = offset; } /** * Get the callout <code>Insets</code> dimensions in pixels. The text is drawn inside the callout frame * while keeping a distance from the callout border defined in the Insets. * @return the callout <code>Insets</code> dimensions in pixels. */ public Insets getInsets() { return this.insets != null ? this.insets : defaultAttributes.getInsets(); } /** * Set the callout <code>Insets</code> dimensions in pixels. The text will be drawn inside the callout frame * while keeping a distance from the callout border defined in the Insets. Set to <code>null</code> to use the * default Insets. * @param insets the callout <code>Insets</code> dimensions in pixels. */ public void setInsets(Insets insets) { this.insets = insets; } /** * Get the callout border line width. A value of <code>zero</code> means no border is being drawn. * @return the callout border line width. */ public double getBorderWidth() { return this.borderWidth >= 0 ? this.borderWidth : defaultAttributes.getBorderWidth(); } /** * Set the callout border line width. A value of <code>zero</code> means no border * will is drawn. Set to minus one (<code>-1</code>) to use the default value. * @param width the callout border line width. */ public void setBorderWidth(double width) { this.borderWidth = width; } /** * Get the stipple factor used for the callout border line. A value of <code>zero</code> (default) means no pattern * is applied. * @return the stipple factor used for the callout border line. */ public int getBorderStippleFactor() { return this.borderStippleFactor >= 0 ? this.borderStippleFactor : defaultAttributes.getBorderStippleFactor(); } /** * Set the stipple factor used for the callout border line. A value of <code>zero</code> (default) means no pattern * will be applied. Set to minus one (<code>-1</code>) to use the default value. * @param factor the stipple factor used for the callout border line. */ public void setBorderStippleFactor(int factor) { this.borderStippleFactor = factor; } /** * Get the stipple pattern used for the callout border line. * @return the stipple pattern used for the callout border line. */ public short getBorderStipplePattern() { return this.borderStipplePattern != 0x0000 ? this.borderStipplePattern : defaultAttributes.getBorderStipplePattern(); } /** * Set the stipple pattern used for the callout border line. Set to <code>0x0000</code> to use the default value. * @param pattern the stipple pattern used for the callout border line. */ public void setBorderStipplePattern(short pattern) { this.borderStipplePattern = pattern; } /** * Get the <code>GL</code> antialias hint used for rendering the callout border line. Can be one of * {@link Annotation}.ANTIALIAS_DONT_CARE, ANTIALIAS_FASTEST (default) or ANTIALIAS_NICEST. * @return the <code>GL</code> antialias hint used for rendering the callout border line. */ public int getAntiAliasHint() { return this.antiAliasHint >=0 ? this.antiAliasHint : defaultAttributes.getAntiAliasHint(); } /** * Set the <code>GL</code> antialias hint used for rendering the callout border line. Can be one of * {@link Annotation}.ANTIALIAS_DONT_CARE, ANTIALIAS_FASTEST (default) or ANTIALIAS_NICEST. * Set to minus one (<code>-1</code>) to use the default value. * @param hint the <code>GL</code> antialias hint used for rendering the callout border line. */ public void setAntiAliasHint(int hint) { this.antiAliasHint = hint; } /** * Get whether the annotation is visible and should be rendered. * @return true if the annotation is visible and should be rendered. */ public boolean isVisible() { return isVisible; } /** * Set whether the annotation is visible and should be rendered. * @param visible true if the annotation is visible and should be rendered. */ public void setVisible(boolean visible) { isVisible = visible; } /** * Get the <code>Font</code> used for text rendering. * @return the <code>Font</code> used for text rendering. */ public Font getFont() { return this.font != null ? this.font : defaultAttributes.getFont(); } /** * Set the <code>Font</code> used for text rendering. Set to <code>null</code> to use the default value. * @param font the <code>Font</code> used for text rendering. */ public void setFont(Font font) { this.font = font; } /** * Get the text alignement. Can be one of {@link MultiLineTextRenderer}.ALIGN_LEFT (default), ALIGN_CENTER * or ALIGN_RIGHT. * @return align the text alignement. Can be one of MultiLineTextRenderer.ALIGN_LEFT, ALIGN_CENTER or ALIGN_RIGHT. */ public int getTextAlign() { return this.textAlign >= 0 ? this.textAlign : defaultAttributes.getTextAlign(); } /** * Set the text alignement. Can be one of {@link MultiLineTextRenderer}.ALIGN_LEFT (default), ALIGN_CENTER * or ALIGN_RIGHT. Set to <code>null</code> to use the default value. * @param align the text alignement. */ public void setTextAlign(int align) { this.textAlign = align; } /** * Get the text <code>Color</code>. * @return the text <code>Color</code>. */ public Color getTextColor() { return this.textColor != null ? this.textColor : defaultAttributes.getTextColor(); } /** * Set the text <code>Color</code>. Set to <code>null</code> to use the default value. * @param color the text <code>Color</code>. */ public void setTextColor(Color color) { this.textColor = color; } /** * Get the callout background <code>Color</code>. * @return the callout background <code>Color</code>. */ public Color getBackgroundColor() { return this.backgroundColor != null ? this.backgroundColor : defaultAttributes.getBackgroundColor(); } /** * Set the callout background <code>Color</code>. Set to <code>null</code> to use the default value. * @param color the callout background <code>Color</code>. */ public void setBackgroundColor(Color color) { this.backgroundColor = color; } /** * Get the callout border <code>Color</code>. * @return the callout border <code>Color</code>. */ public Color getBorderColor() { return this.borderColor != null ? this.borderColor : defaultAttributes.getBorderColor(); } /** * Set the callout border <code>Color</code>. Set to <code>null</code> to use the default value. * @param color the callout border <code>Color</code>. */ public void setBorderColor(Color color) { this.borderColor = color; } /** * Get the background image source. Can be a <code>String</code> providing the path to a local image, * a {@link java.awt.image.BufferedImage} or <code>null</code>. * @return the background image source. */ public Object getImageSource() { return this.imageSource; } /** * Set the background image source. Can be a <code>String</code> providing the path to a local image * or a {@link java.awt.image.BufferedImage}. Set to null for no background image rendering. * @param imageSource the background image source. */ public void setImageSource(Object imageSource) { this.imageSource = imageSource; } /** * Get the background image scaling factor. * @return the background image scaling factor. */ public double getImageScale() { return this.imageScale >= 0 ? this.imageScale : defaultAttributes.getImageScale(); } /** * Set the background image scaling factor. Set to minus one (<code>-1</code>) to use the default value. * @param scale the background image scaling factor. */ public void setImageScale(double scale) { this.imageScale = scale; } /** * Get the background image offset in pixels (before background scaling). * @return the background image offset in pixels */ public Point getImageOffset() { return this.imageOffset != null ? this.imageOffset : defaultAttributes.getImageOffset(); } /** * Set the background image offset in pixels (before background scaling). Set to <code>null</code> to use the * default value. * @param offset the background image offset in pixels */ public void setImageOffset(Point offset) { this.imageOffset = offset; } /** * Get the opacity of the background image (0 to 1). * @return the opacity of the background image (0 to 1). */ public double getImageOpacity() { return this.imageOpacity >= 0 ? this.imageOpacity : defaultAttributes.getImageOpacity(); } /** * Set the opacity of the background image (0 to 1). Set to minus one (<code>-1</code>) to use the default value. * @param opacity the opacity of the background image (0 to 1). */ public void setImageOpacity(double opacity) { this.imageOpacity = opacity; } /** * Get the repeat behavior or the background image. Can be one of {@link Annotation}.IMAGE_REPEAT_X, * IMAGE_REPEAT_Y, IMAGE_REPEAT_XY (default) or IMAGE_REPEAT_NONE. * @return the repeat behavior or the background image. */ public String getImageRepeat() { return this.imageRepeat != null ? this.imageRepeat : defaultAttributes.getImageRepeat(); } /** * Set the repeat behavior or the background image. Can be one of {@link Annotation}.IMAGE_REPEAT_X, * IMAGE_REPEAT_Y, IMAGE_REPEAT_XY (default) or IMAGE_REPEAT_NONE. Set to <code>null</code> to use * the default value. * @param repeat the repeat behavior or the background image. */ public void setImageRepeat(String repeat) { this.imageRepeat = repeat; } /** * Get the path to the image used for background image. Returns <code>null</code> if the image source is null * or a memory BufferedImage. * @return the path to the image used for background image. */ public String getPath() { return this.imageSource instanceof String ? (String) this.imageSource : null; } /** * Get the minimum scale that can be applied to an annotation when it gets farther away from the eye than the view * lookat point. * @return the minimum scale that can be applied to an annotation when it gets away from the eye */ public double getDistanceMinScale() { return this.distanceMinScale >= 0 ? this.distanceMinScale : defaultAttributes.getDistanceMinScale(); } /** * Set the minimum scale that can be applied to an annotation when it gets farther away from the eye than the view * lookat point. Set to minus one (<code>-1</code>) to use the default value. * @param scale the minimum scale that can be applied to an annotation when it gets away from the eye */ public void setDistanceMinScale(double scale) { this.distanceMinScale = scale; } /** * Get the maximum scale that can be applied to an annotation when it gets closer to the eye than the view * lookat point. * @return the maximum scale that can be applied to an annotation when it gets closer to the eye */ public double getDistanceMaxScale() { return this.distanceMaxScale >= 0 ? this.distanceMaxScale : defaultAttributes.getDistanceMaxScale(); } /** * Set the maximum scale that can be applied to an annotation when it gets closer to the eye than the view * lookat point. Set to minus one (<code>-1</code>) to use the default value. * @param scale the maximum scale that can be applied to an annotation when it gets closer to the eye */ public void setDistanceMaxScale(double scale) { this.distanceMaxScale = scale; } /** * Get the minimum opacity an annotation can have when fading away from the eye (0 to 1). * @return the minimum opacity an annotation can have when fading away from the eye. */ public double getDistanceMinOpacity() { return this.distanceMinOpacity >= 0 ? this.distanceMinOpacity : defaultAttributes.getDistanceMinOpacity(); } /** * Set the minimum opacity an annotation can have when fading away from the eye (0 to 1). Set to minus one * (<code>-1</code>) to use the default value. * @param opacity the minimum opacity an annotation can have when fading away from the eye. */ public void setDistanceMinOpacity(double opacity) { this.distanceMinOpacity = opacity; } /** * Get the effect used to decorate the text. Can be one of {@link MultiLineTextRenderer}.EFFECT_SHADOW, * EFFECT_OUTLINE or EFFECT_NONE (default). * @return the effect used for text rendering */ public String getEffect() { return this.effect != null ? this.effect : defaultAttributes.getEffect(); } /** * Set the effect used to decorate the text. Can be one of {@link MultiLineTextRenderer}.EFFECT_SHADOW, * EFFECT_OUTLINE or EFFECT_NONE (default). Set to <code>null</code> to use the default value. * @param effect the effect to use for text rendering */ public void setEffect(String effect) { this.effect = effect; } /** * Returns an XML state document String describing attributes that have been set by the application * (attributes not pointing to their default value). * * @return XML state document string describing this AnnotationAttributes. */ public String getRestorableState() { RestorableSupport restorableSupport = RestorableSupport.newRestorableSupport(); // Creating a new RestorableSupport failed. RestorableSupport logged the problem, so just return null. if (restorableSupport == null) return null; // Save application set attributes to the document root. saveAttributes(this, restorableSupport, null); // We only save this AnnotationAttributes' defaultAttributes when the application has set them to // something other than the static member "defaults". if (this.defaultAttributes != AnnotationAttributes.defaults) { RestorableSupport.StateObject defaultAttributesStateObj = restorableSupport.addStateObject("defaultAttributes"); saveAttributes(this.defaultAttributes, restorableSupport, defaultAttributesStateObj); } return restorableSupport.getStateAsXml(); } /** * Restores attribute values found in the specified XML state document String. The document specified by * <code>stateInXml</code> must be a well formed XML document String, or this will throw an * IllegalArgumentException. Unknown structures in <code>stateInXml</code> are benign, because they will * simply be ignored. * * @param stateInXml an XML document String describing an AnnotationAttributes. * @throws IllegalArgumentException If <code>stateInXml</code> is null, or if <code>stateInXml</code> is not * a well formed XML document String. */ public void restoreState(String stateInXml) { if (stateInXml == null) { String message = Logging.getMessage("nullValue.StringIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } RestorableSupport restorableSupport; try { restorableSupport = RestorableSupport.parse(stateInXml); } catch (Exception e) { // Parsing the document specified by stateInXml failed. String message = Logging.getMessage("generic.ExceptionAttemptingToParseStateXml", stateInXml); Logging.logger().severe(message); throw new IllegalArgumentException(message, e); } // Restore application set attributes from under the document root. restoreAttributes(restorableSupport, null, this); // Restore application set default attributes from under the "defaultAttributes" state element. RestorableSupport.StateObject defaultAttributesStateObj = restorableSupport.getStateObject("defaultAttributes"); if (defaultAttributesStateObj != null) { AnnotationAttributes newDefaultAttributes = this.defaultAttributes; // We do not want to write to the static member "defaults". So if this AnnotationAttributes' does not // have it's own defaultAttributes instance, we create one for it if (newDefaultAttributes == AnnotationAttributes.defaults) newDefaultAttributes = new AnnotationAttributes(); restoreAttributes(restorableSupport, defaultAttributesStateObj, newDefaultAttributes); setDefaults(newDefaultAttributes); } } /** * Save the attributes specified by <code>source</code> in the specified <code>restorableSupport</code>. * Only attributes that have been set by the application (attributes not pointing to their default value) will * be saved. If <code>context</code> is not null, attributes will be saved beneath it. Otherwise, they will * be saved at the document root. * * @param source the AnnotationAttriubutes to save. * @param restorableSupport RestorableSupport to write attribute values to. * @param context RestorableSupport.StateObject that attributes will be saved under, if not null. * @throws IllegalArgumentException If either <code>source</code> or <code>restorableSupport</code> is null. */ private static void saveAttributes(AnnotationAttributes source, RestorableSupport restorableSupport, RestorableSupport.StateObject context) { if (source == null || restorableSupport == null) throw new IllegalArgumentException(); if (source.frameShape != null) restorableSupport.addStateValueAsString(context, "frameShape", source.frameShape); restorableSupport.addStateValueAsBoolean(context, "highlighted", source.isHighlighted); if (source.highlightScale >= 0) restorableSupport.addStateValueAsDouble(context, "highlightScale", source.highlightScale); if (source.size != null) { RestorableSupport.StateObject sizeStateObj = restorableSupport.addStateObject(context, "size"); if (sizeStateObj != null) { restorableSupport.addStateValueAsDouble(sizeStateObj, "width", source.size.getWidth()); restorableSupport.addStateValueAsDouble(sizeStateObj, "height", source.size.getHeight()); } } if (source.scale >= 0) restorableSupport.addStateValueAsDouble(context, "scale", source.scale); if (source.opacity >= 0) restorableSupport.addStateValueAsDouble(context, "opacity", source.opacity); if (source.leader != null) restorableSupport.addStateValueAsString(context, "leader", source.leader); if (source.cornerRadius >= 0) restorableSupport.addStateValueAsInteger(context, "cornerRadius", source.cornerRadius); if (source.adjustWidthToText != null) restorableSupport.addStateValueAsString(context, "adjustWidthToText", source.adjustWidthToText); if (source.drawOffset != null) { RestorableSupport.StateObject drawOffsetStateObj = restorableSupport.addStateObject(context, "drawOffset"); if (drawOffsetStateObj != null) { restorableSupport.addStateValueAsDouble(drawOffsetStateObj, "x", source.drawOffset.getX()); restorableSupport.addStateValueAsDouble(drawOffsetStateObj, "y", source.drawOffset.getY()); } } if (source.insets != null) { RestorableSupport.StateObject insetsStateObj = restorableSupport.addStateObject(context, "insets"); if (insetsStateObj != null) { restorableSupport.addStateValueAsInteger(insetsStateObj, "top", source.insets.top); restorableSupport.addStateValueAsInteger(insetsStateObj, "left", source.insets.left); restorableSupport.addStateValueAsInteger(insetsStateObj, "bottom", source.insets.bottom); restorableSupport.addStateValueAsInteger(insetsStateObj, "right", source.insets.right); } } if (source.borderWidth >= 0) restorableSupport.addStateValueAsDouble(context, "borderWidth", source.borderWidth); if (source.borderStippleFactor >= 0) restorableSupport.addStateValueAsInteger(context, "borderStippleFactor", source.borderStippleFactor); if (source.borderStipplePattern != (short) 0x0000) restorableSupport.addStateValueAsInteger(context, "borderStipplePattern", source.borderStipplePattern); if (source.antiAliasHint >= 0) restorableSupport.addStateValueAsInteger(context, "antiAliasHint", source.antiAliasHint); restorableSupport.addStateValueAsBoolean(context, "visible", source.isVisible); // Save the name, style, and size of the font. These will be used to restore the font using the // constructor: new Font(name, style, size). if (source.font != null) { RestorableSupport.StateObject fontStateObj = restorableSupport.addStateObject(context, "font"); if (fontStateObj != null) { restorableSupport.addStateValueAsString(fontStateObj, "name", source.font.getName()); restorableSupport.addStateValueAsInteger(fontStateObj, "style", source.font.getStyle()); restorableSupport.addStateValueAsInteger(fontStateObj, "size", source.font.getSize()); } } if (source.textAlign >= 0) restorableSupport.addStateValueAsInteger(context, "textAlign", source.textAlign); if (source.textColor != null) { String encodedColor = RestorableSupport.encodeColor(source.textColor); if (encodedColor != null) restorableSupport.addStateValueAsString(context, "textColor", encodedColor); } if (source.backgroundColor != null) { String encodedColor = RestorableSupport.encodeColor(source.backgroundColor); if (encodedColor != null) restorableSupport.addStateValueAsString(context, "backgroundColor", encodedColor); } if (source.borderColor != null) { String encodedColor = RestorableSupport.encodeColor(source.borderColor); if (encodedColor != null) restorableSupport.addStateValueAsString(context, "borderColor", encodedColor); } // Save the imagePath property only when the imageSource property is a simple String path. If the imageSource // property is a BufferedImage (or some other object), we make no effort to save that state. We save under // the name "imagePath" to denote that it is a special case of "imageSource". if (source.getPath() != null) restorableSupport.addStateValueAsString(context, "imagePath", source.getPath(), true); if (source.imageScale >= 0) restorableSupport.addStateValueAsDouble(context, "imageScale", source.imageScale); if (source.imageOffset != null) { RestorableSupport.StateObject imageOffsetStateObj = restorableSupport.addStateObject(context, "imageOffset"); if (imageOffsetStateObj != null) { restorableSupport.addStateValueAsDouble(imageOffsetStateObj, "x", source.imageOffset.getX()); restorableSupport.addStateValueAsDouble(imageOffsetStateObj, "y", source.imageOffset.getY()); } } if (source.imageOpacity >= 0) restorableSupport.addStateValueAsDouble(context, "imageOpacity", source.imageOpacity); if (source.imageRepeat != null) restorableSupport.addStateValueAsString(context, "imageRepeat", source.imageRepeat); if (source.distanceMinScale >= 0) restorableSupport.addStateValueAsDouble(context, "distanceMinScale", source.distanceMinScale); if (source.distanceMaxScale >= 0) restorableSupport.addStateValueAsDouble(context, "distanceMaxScale", source.distanceMaxScale); if (source.distanceMinOpacity >= 0) restorableSupport.addStateValueAsDouble(context, "distanceMinOpacity", source.distanceMinOpacity); if (source.effect != null) restorableSupport.addStateValueAsString(context, "effect", source.effect); } /** * Restores the any attributes appearing in the specified <code>restorableSupport</code>. If <code>context</code> * is not null, this will search for attributes beneath it. Otherwise, this will search for attributes * beneath the document root. * * @param restorableSupport RestorableSupport to read attribute values from. * @param context RestorableSupport.StateObject under which attributes will be looked, if not null. * @param dest the AnnotationAttributes to restore. * @throws IllegalArgumentException If either <code>restorableSupport</code> or <code>dest</code> is null. */ private static void restoreAttributes(RestorableSupport restorableSupport, RestorableSupport.StateObject context, AnnotationAttributes dest) { if (restorableSupport == null || dest == null) throw new IllegalArgumentException(); String frameShapeState = restorableSupport.getStateValueAsString(context, "frameShape"); if (frameShapeState != null) dest.setFrameShape(frameShapeState); Boolean highlightedState = restorableSupport.getStateValueAsBoolean(context, "highlighted"); if (highlightedState != null) dest.setHighlighted(highlightedState); Double highlightScaleState = restorableSupport.getStateValueAsDouble(context, "highlightScale"); if (highlightScaleState != null) dest.setHighlightScale(highlightScaleState); // Restore the size property only if all parts are available. // We will not restore a partial size (for example, just the width). RestorableSupport.StateObject sizeStateObj = restorableSupport.getStateObject(context, "size"); if (sizeStateObj != null) { Double widthState = restorableSupport.getStateValueAsDouble(sizeStateObj, "width"); Double heightState = restorableSupport.getStateValueAsDouble(sizeStateObj, "height"); if (widthState != null && heightState != null) dest.setSize(new Dimension(widthState.intValue(), heightState.intValue())); } Double scaleState = restorableSupport.getStateValueAsDouble(context, "scale"); if (scaleState != null) dest.setScale(scaleState); Double opacityState = restorableSupport.getStateValueAsDouble(context, "opacity"); if (opacityState != null) dest.setOpacity(opacityState); String leaderState = restorableSupport.getStateValueAsString(context, "leader"); if (leaderState != null) dest.setLeader(leaderState); Integer cornerRadiusState = restorableSupport.getStateValueAsInteger(context, "cornerRadius"); if (cornerRadiusState != null) dest.setCornerRadius(cornerRadiusState); String adjustWidthToTextState = restorableSupport.getStateValueAsString(context, "adjustWidthToText"); if (adjustWidthToTextState != null) dest.setAdjustWidthToText(adjustWidthToTextState); // Restore the drawOffset property only if all parts are available. // We will not restore a partial drawOffset (for example, just the x value). RestorableSupport.StateObject drawOffsetStateObj = restorableSupport.getStateObject(context, "drawOffset"); if (drawOffsetStateObj != null) { Double xState = restorableSupport.getStateValueAsDouble(drawOffsetStateObj, "x"); Double yState = restorableSupport.getStateValueAsDouble(drawOffsetStateObj, "y"); if (xState != null && yState != null) dest.setDrawOffset(new Point(xState.intValue(), yState.intValue())); } // Restore the insets property only if all parts are available. // We will not restore a partial insets (for example, just the top value). RestorableSupport.StateObject insetsStateObj = restorableSupport.getStateObject(context, "insets"); if (insetsStateObj != null) { Integer topState = restorableSupport.getStateValueAsInteger(insetsStateObj, "top"); Integer leftState = restorableSupport.getStateValueAsInteger(insetsStateObj, "left"); Integer bottomState = restorableSupport.getStateValueAsInteger(insetsStateObj, "bottom"); Integer rightState = restorableSupport.getStateValueAsInteger(insetsStateObj, "right"); if (topState != null && leftState != null && bottomState != null && rightState != null) dest.setInsets(new Insets(topState, leftState, bottomState, rightState)); } Double borderWidthState = restorableSupport.getStateValueAsDouble(context, "borderWidth"); if (borderWidthState != null) dest.setBorderWidth(borderWidthState); Integer borderStippleFactorState = restorableSupport.getStateValueAsInteger(context, "borderStippleFactor"); if (borderStippleFactorState != null) dest.setBorderStippleFactor(borderStippleFactorState); Integer borderStipplePatternState = restorableSupport.getStateValueAsInteger(context, "borderStipplePattern"); if (borderStipplePatternState != null) dest.setBorderStipplePattern(borderStipplePatternState.shortValue()); Integer antiAliasHintState = restorableSupport.getStateValueAsInteger(context, "antiAliasHint"); if (antiAliasHintState != null) dest.setAntiAliasHint(antiAliasHintState); Boolean visibleState = restorableSupport.getStateValueAsBoolean(context, "visible"); if (visibleState != null) dest.setVisible(visibleState); // Restore the font property only if all parts are available. // We will not restore a partial font (for example, just the size). RestorableSupport.StateObject fontStateObj = restorableSupport.getStateObject(context, "font"); if (fontStateObj != null) { // The "font name" of toolTipFont. String nameState = restorableSupport.getStateValueAsString(fontStateObj, "name"); // The style attributes. Integer styleState = restorableSupport.getStateValueAsInteger(fontStateObj, "style"); // The simple font size. Integer sizeState = restorableSupport.getStateValueAsInteger(fontStateObj, "size"); if (nameState != null && styleState != null && sizeState != null) dest.setFont(new Font(nameState, styleState, sizeState)); } Integer textAlignState = restorableSupport.getStateValueAsInteger(context, "textAlign"); if (textAlignState != null) dest.setTextAlign(textAlignState); String textColorState = restorableSupport.getStateValueAsString(context, "textColor"); if (textColorState != null) { Color color = RestorableSupport.decodeColor(textColorState); if (color != null) dest.setTextColor(color); } String backgroundColorState = restorableSupport.getStateValueAsString(context, "backgroundColor"); if (backgroundColorState != null) { Color color = RestorableSupport.decodeColor(backgroundColorState); if (color != null) dest.setBackgroundColor(color); } String borderColorState = restorableSupport.getStateValueAsString(context, "borderColor"); if (borderColorState != null) { Color color = RestorableSupport.decodeColor(borderColorState); if (color != null) dest.setBorderColor(color); } // The imagePath property should exist only if the imageSource property was a simple String path. // If the imageSource property was a BufferedImage (or some other object), it should not exist in the // state document. We save under the name "imagePath" to denote that it is a special case of "imageSource". String imagePathState = restorableSupport.getStateValueAsString(context, "imagePath"); if (imagePathState != null) dest.setImageSource(imagePathState); Double imageScaleState = restorableSupport.getStateValueAsDouble(context, "imageScale"); if (imageScaleState != null) dest.setImageScale(imageScaleState); // Restore the imageOffset property only if all parts are available. // We will not restore a partial imageOffset (for example, just the x value). RestorableSupport.StateObject imageOffsetStateObj = restorableSupport.getStateObject(context, "imageOffset"); if (imageOffsetStateObj != null) { Double xState = restorableSupport.getStateValueAsDouble(imageOffsetStateObj, "x"); Double yState = restorableSupport.getStateValueAsDouble(imageOffsetStateObj, "y"); if (xState != null && yState != null) dest.setImageOffset(new Point(xState.intValue(), yState.intValue())); } Double imageOpacityState = restorableSupport.getStateValueAsDouble(context, "imageOpacity"); if (imageOpacityState != null) dest.setImageOpacity(imageOpacityState); String imageRepeatState = restorableSupport.getStateValueAsString(context, "imageRepeat"); if (imageRepeatState != null) dest.setImageRepeat(imageRepeatState); Double distanceMinScaleState = restorableSupport.getStateValueAsDouble(context, "distanceMinScale"); if (distanceMinScaleState != null) dest.setDistanceMinScale(distanceMinScaleState); Double distanceMaxScaleState = restorableSupport.getStateValueAsDouble(context, "distanceMaxScale"); if (distanceMaxScaleState != null) dest.setDistanceMaxScale(distanceMaxScaleState); Double distanceMinOpacityState = restorableSupport.getStateValueAsDouble(context, "distanceMinOpacity"); if (distanceMinOpacityState != null) dest.setDistanceMinOpacity(distanceMinOpacityState); String effectState = restorableSupport.getStateValueAsString(context, "effect"); if (effectState != null) dest.setEffect(effectState); } }