All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.gov.nasa.worldwind.render.AbstractAnnotation Maven / Gradle / Ivy

Go to download

World Wind is a collection of components that interactively display 3D geographic information within Java applications or applets.

There is a newer version: 2.0.0-986
Show newest version
/*
 * Copyright (C) 2012 United States Government as represented by the Administrator of the
 * National Aeronautics and Space Administration.
 * All Rights Reserved.
 */
package gov.nasa.worldwind.render;

import com.jogamp.opengl.util.awt.TextRenderer;
import gov.nasa.worldwind.WorldWindow;
import gov.nasa.worldwind.avlist.*;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.pick.PickSupport;
import gov.nasa.worldwind.util.*;

import javax.media.opengl.*;
import java.awt.*;

/**
 * An {@link Annotation} represent a text label and its rendering attributes. Annotations must be attached either to a
 * globe Position ({@link GlobeAnnotation}) or a viewport Point (ScreenAnnotation). 

*

 * GlobeAnnotation ga = new  GlobeAnnotation("Lat-Lon zero", Position.fromDegrees(0, 0, 0)));
 * ScreenAnnotation sa = new ScreenAnnotation("Message...", new Point(10,10));
 * 
*

Each Annotation refers to an {@link AnnotationAttributes} object which defines how the text will be rendered. *

Rendering attributes allow to set:
  • the size of the bounding rectangle into which the text will be * displayed
  • its frame shape, border color, width and stippling pattern
  • the text font, size, style and * color
  • the background color or image
  • how much an annotation scales and fades with distance
  • *
*
 * ga.getAttributes().setTextColor(Color.WHITE);
 * ga.getAttributes().setFont(Font.decode("Arial-BOLD-24");
 * ...
 * 
*

Annotations are usually handled by an {@link gov.nasa.worldwind.layers.AnnotationLayer}. Although they also * implement the {@link Renderable} interface and thus can be handled by a {@link gov.nasa.worldwind.layers.RenderableLayer} * too.

*

 * AnnotationLayer layer = new AnnotationLayer();
 * layer.addAnnotation(new GlobeAnnotation("Text...", Position.fromDegrees(0, 0, 0)));
 * 
*

Each Annotation starts its life with a fresh attribute set that can be altered to produce the desired effect. * However, AnnotationAttributes can be set and shared between annotations allowing to control the * rendering attributes of many annotations from a single AnnotationAttributes object.

*

 * AnnotationAttributes attr = new AnnotationAttributes();
 * attr.setTextColor(Color.WHITE);
 * attr.setFont(Font.decode("Arial-BOLD-24");
 * ga.setAttributes(attr);
 * 
*

In the above example changing the text color of the attributes set will affect all annotations referring it. * However, changing the text color of one of those annotations will also affect all others since it will in fact change * the common attributes set.

To use an attributes object only as default values for a series of annotations use: *

*
 * ga.getAttributes().setDefaults(attr);
 * 
*

which can also be done in the Annotation constructor:

*

 * GlobeAnnotation ga = new GlobeAnnotation(text, position, attr);
 * 
*

Finer control over attributes inheritance can be achieved using default or fallback attributes set.

Most * attributes can be set to a 'use default' value which is minus one for numeric values and null for * attributes referring objects (colors, dimensions, insets..). In such a case the value of an attribute will be that of * the default attribute set. New annotations have all their attributes set to use default values.

*

* Each AnnotationAttributes object points to a default static attributes set which is the fallback source * for attributes with null or -1 values. This default attributes set can be set to any * attributes object other than the static one. *

*

 * AnnotationAttributes geoFeature = new AnnotationAttributes();
 * geoFeature.setFrameShape(AVKey.SHAPE_ELLIPSE);
 * geoFeature.setInsets(new Insets(12, 12, 12, 12));
 *
 * AnnotationAttributes waterBody = new AnnotationAttributes();
 * waterBody.setTextColor(Color.BLUE);
 * waterBoby.setDefaults(geoFeature);
 *
 * AnnotationAttributes mountain = new AnnotationAttributes();
 * mountain.setTextColor(Color.GREEN);
 * mountain.setDefaults(geoFeature);
 *
 * layer.addAnnotation(new GlobeAnnotation("Spirit Lake", Position.fromDegrees(46.26, -122.15), waterBody);
 * layer.addAnnotation(new GlobeAnnotation("Mt St-Helens", Position.fromDegrees(46.20, -122.19), mountain);
 * 
*

* In the above example all geographic features have an ellipse shape, water bodies and mountains use that attributes * set has defaults and have their own text colors. They are in turn used as defaults by the two annotations. Mount * Saint Helens attributes could be changed without affecting other mountains. However, changes on the geoFeatures * attributes would affect all mountains and lakes. *

* Background images are specified by setting the Annotation attribute {@link gov.nasa.worldwind.render.AnnotationAttributes#setImageSource(Object)}. * The source can be either a path to a valid image file, or a {@link java.awt.image.BufferedImage}. By default, * background images are aligned with the annotation as follows: the image's upper left corner is aligned with the * annotation's upper left corner, and the image's lower right corner is aligned with a point (imageWidth, * imageHeight) pixels right and down from the annotation's upper left corner. Thus the background image * coordinate system has its origin at the annotation's upper left corner, has the +X axis pointing to the right, and * has the +Y axis pointing down. Units are in image pixels, where one image pixel corresponds to one screen pixel. The * background image may be translated or scaled by setting the attributes {@link gov.nasa.worldwind.render.AnnotationAttributes#setImageOffset(java.awt.Point)} * and {@link gov.nasa.worldwind.render.AnnotationAttributes#setImageScale(double)}, respectively. The offset attribute * defines an offset right and down in background image coordinates. The scale attribute is unitless, and defines the * background image's magnification or minification factor relative to the annotation. For example, a scale of * 0.5 indicates the image should be 1/2 its original size relative to the annotation, while a scale of * 2.0 indicates the image should be 2x its original size. *

* Warning: For compatibility across the myriad of graphics hardware, background images must have * power-of-two dimensions. Non-power-of-two images are handled inconsistently by graphics hardware. Not all hardware * supports them, and many that do lack full support for the features available when using power-of-two images. Proper * conversion from a non-power-of-two image to a power-of-two image depends on the image's intended use. However, the * following two step solution works for most applications:

  1. Create a transparent power-of-two image larger than * the original image. The utility method {@link gov.nasa.worldwind.util.WWMath#powerOfTwoCeiling(int)} is useful for * computing power-of-two dimensions:

    int newWidth = WWMath.powerOfTwoCeiling(originalWidth);
    int * newHeight = WWMath.powerOfTwoCeiling(originalHeight);

  2. Copy the original image contents * into the empty power-of-two image. Any pixels not covered by the original image are left completely transparent: *

    BufferedImage newImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB);
    * Graphics2D g2d = newImage.createGraphics();
    try
    {
      g2d.drawImage(originalImage, 0, 0, * null);
    }
    finally
    {
      g2d.dispose();
    }

When used as an * Annotation background image, the new power-of-two image appears identical to its original non-power-of-two * counterpart, except that the new image is now compatible with most graphics hardware. * * @author Patrick Murris * @version $Id: AbstractAnnotation.java 1171 2013-02-11 21:45:02Z dcollins $ * @see AnnotationAttributes * @see AnnotationRenderer */ public abstract class AbstractAnnotation extends AVListImpl implements Annotation { protected boolean alwaysOnTop; protected boolean pickEnabled; protected String text; protected AnnotationAttributes attributes; // Child annotation properties. protected java.util.List childList; protected AnnotationLayoutManager layoutManager; // Picking components. protected PickSupport pickSupport; protected Object delegateOwner; // Properties used or computed in each rendering pass. protected static java.nio.DoubleBuffer vertexBuffer; protected java.util.Map wrappedTextMap; protected java.util.Map textBoundsMap; protected double minActiveAltitude = -Double.MAX_VALUE; protected double maxActiveAltitude = Double.MAX_VALUE; protected AbstractAnnotation() { this.alwaysOnTop = false; this.pickEnabled = true; this.attributes = new AnnotationAttributes(); this.childList = new java.util.ArrayList(); this.layoutManager = new AnnotationNullLayout(); // Cached text computations. this.wrappedTextMap = new java.util.HashMap(); this.textBoundsMap = new java.util.HashMap(); } public boolean isAlwaysOnTop() { return alwaysOnTop; } public void setAlwaysOnTop(boolean alwaysOnTop) { this.alwaysOnTop = alwaysOnTop; } public boolean isPickEnabled() { return this.pickEnabled; } public void setPickEnabled(boolean enable) { this.pickEnabled = enable; } public String getText() { return this.text; } public void setText(String text) { if (text == null) { String message = Logging.getMessage("nullValue.StringIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.text = text; } public AnnotationAttributes getAttributes() { return this.attributes; } public void setAttributes(AnnotationAttributes attributes) { if (attributes == null) { String message = Logging.getMessage("nullValue.AnnotationAttributesIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.attributes = attributes; } public double getMinActiveAltitude() { return this.minActiveAltitude; } public void setMinActiveAltitude(double minActiveAltitude) { this.minActiveAltitude = minActiveAltitude; } public double getMaxActiveAltitude() { return maxActiveAltitude; } public void setMaxActiveAltitude(double maxActiveAltitude) { this.maxActiveAltitude = maxActiveAltitude; } public java.util.List getChildren() { return java.util.Collections.unmodifiableList(this.childList); } public void addChild(Annotation annotation) { if (annotation == null) { String message = Logging.getMessage("nullValue.AnnotationIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.childList.add(annotation); } public boolean removeChild(Annotation annotation) { if (annotation == null) { String message = Logging.getMessage("nullValue.AnnotationIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } return this.childList.remove(annotation); } public void removeAllChildren() { this.childList.clear(); } public AnnotationLayoutManager getLayout() { return this.layoutManager; } public void setLayout(AnnotationLayoutManager layoutManager) { if (layoutManager == null) { String message = Logging.getMessage("nullValue.LayoutIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.layoutManager = layoutManager; } public PickSupport getPickSupport() { return this.pickSupport; } public void setPickSupport(PickSupport pickSupport) { if (pickSupport == null) { String message = Logging.getMessage("nullValue.PickSupportIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.pickSupport = pickSupport; } public Object getDelegateOwner() { return delegateOwner; } public void setDelegateOwner(Object delegateOwner) { this.delegateOwner = delegateOwner; } /** * Render the annotation. Called as a Renderable. * * @param dc the current DrawContext. */ public void render(DrawContext dc) { if (dc == null) { String message = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (!this.getAttributes().isVisible()) return; dc.getAnnotationRenderer().render(dc, this, null, dc.getCurrentLayer()); } /** * Pick at the annotation. Called as a Pickable. * * @param dc the current DrawContext. * @param pickPoint the screen coordinate point. */ public void pick(DrawContext dc, java.awt.Point pickPoint) { if (dc == null) { String message = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (!this.getAttributes().isVisible()) return; if (!this.isPickEnabled()) return; dc.getAnnotationRenderer().pick(dc, this, null, pickPoint, null); } public void dispose() { } public java.awt.Dimension getPreferredSize(DrawContext dc) { if (dc == null) { String message = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } // Clamp the caller specified size. java.awt.Dimension size = new java.awt.Dimension(this.getAttributes().getSize()); if (size.width < 1) size.width = 1; if (size.height < 0) size.height = 0; // Compute the size of this annotation's inset region. java.awt.Rectangle insetBounds = this.computeInsetBounds(size.width, size.height); java.awt.Dimension insetSize = new java.awt.Dimension(insetBounds.width, insetBounds.height); // Wrap the text to fit inside the annotation's inset bounds. Then adjust the inset bounds to the wrapped // text, depending on the annotation's attributes. insetSize = this.adjustSizeToText(dc, insetSize.width, insetSize.height); // Adjust the inset bounds to the child annotations. insetSize = this.adjustSizeToChildren(dc, insetSize.width, insetSize.height); java.awt.Insets insets = this.getAttributes().getInsets(); return new java.awt.Dimension( insetSize.width + (insets.left + insets.right), insetSize.height + (insets.top + insets.bottom)); } public void renderNow(DrawContext dc) { if (dc == null) { String message = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (!this.getAttributes().isVisible()) return; if (dc.isPickingMode() && !this.isPickEnabled()) return; this.doRenderNow(dc); } public void draw(DrawContext dc, int width, int height, double opacity, Position pickPosition) { if (dc == null) { String message = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } double finalOpacity = opacity * this.computeOpacity(dc); this.doDraw(dc, width, height, finalOpacity, pickPosition); } protected void drawTopLevelAnnotation(DrawContext dc, int x, int y, int width, int height, double scale, double opacity, Position pickPosition) { if (dc == null) { String message = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } OGLStackHandler stackHandler = new OGLStackHandler(); this.beginDraw(dc, stackHandler); try { this.applyScreenTransform(dc, x, y, width, height, scale); this.draw(dc, width, height, opacity, pickPosition); } finally { this.endDraw(dc, stackHandler); } } @SuppressWarnings({"UnusedDeclaration"}) protected void applyScreenTransform(DrawContext dc, int x, int y, int width, int height, double scale) { double finalScale = scale * this.computeScale(dc); java.awt.Point offset = this.getAttributes().getDrawOffset(); GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. gl.glTranslated(x, y, 0); gl.glScaled(finalScale, finalScale, 1); gl.glTranslated(offset.x, offset.y, 0); gl.glTranslated(-width / 2, 0, 0); } @SuppressWarnings({"UnusedDeclaration"}) protected double computeScale(DrawContext dc) { double scale = this.attributes.getScale(); // Factor in highlight scale. if (this.attributes.isHighlighted()) { scale *= this.attributes.getHighlightScale(); } return scale; } @SuppressWarnings({"UnusedDeclaration"}) protected double computeOpacity(DrawContext dc) { double opacity = this.attributes.getOpacity(); // Remove transparency if highlighted. if (this.attributes.isHighlighted()) { opacity = 1; } return opacity; } //**************************************************************// //******************** Rendering *****************************// //**************************************************************// protected abstract void doRenderNow(DrawContext dc); protected abstract Rectangle computeBounds(DrawContext dc); /** * Get the annotation bounding {@link java.awt.Rectangle} using OGL coordinates - bottom-left corner x and y * relative to the {@link WorldWindow} bottom-left corner, and the annotation callout width and height. *

* The annotation offset from it's reference point is factored in such that the callout leader shape and reference * point are included in the bounding rectangle. * * @param dc the current DrawContext. * * @return the annotation bounding {@link java.awt.Rectangle} using OGL viewport coordinates. * * @throws IllegalArgumentException if dc is null. */ public java.awt.Rectangle getBounds(DrawContext dc) { if (dc == null) { String message = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (dc.getView().getViewport() == null) return null; return this.computeBounds(dc); } /** * Draws an annotation with the given dimensions and opacity. Current GL state has ortho identity model view active * with origin at the screen point. * * @param dc current DrawContext. * @param width annotation callout width * @param height annotation callout height * @param opacity opacity to apply * @param pickPosition Position that will be associated with any PickedObject produced * during picking. */ protected void doDraw(DrawContext dc, int width, int height, double opacity, Position pickPosition) { if (!this.getAttributes().isVisible()) return; // If this annotation is not pickable, then do not draw any of its contents. However this annotation's children // may be pickable, so we still process them. if (!dc.isPickingMode() || this.isPickEnabled()) { this.drawContent(dc, width, height, opacity, pickPosition); } this.drawChildren(dc, width, height, opacity, pickPosition); } protected void drawContent(DrawContext dc, int width, int height, double opacity, Position pickPosition) { this.drawBackground(dc, width, height, opacity, pickPosition); this.drawBackgroundImage(dc, width, height, opacity, pickPosition); this.drawBorder(dc, width, height, opacity, pickPosition); this.drawText(dc, width, height, opacity, pickPosition); } protected void beginDraw(DrawContext dc, OGLStackHandler stackHandler) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. stackHandler.pushModelviewIdentity(gl); } protected void endDraw(DrawContext dc, OGLStackHandler stackHandler) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. stackHandler.pop(gl); } //**************************************************************// //******************** Background Rendering ******************// //**************************************************************// protected void drawBackground(DrawContext dc, int width, int height, double opacity, Position pickPosition) { if (dc.isPickingMode()) { this.bindPickableObject(dc, pickPosition); } this.applyColor(dc, this.getAttributes().getBackgroundColor(), opacity, true); this.drawCallout(dc, GL.GL_TRIANGLE_FAN, width, height, false); } //**************************************************************// //******************** Background Image Rendering ************// //**************************************************************// protected void drawBackgroundImage(DrawContext dc, int width, int height, double opacity, Position pickPosition) { if (dc.isPickingMode()) return; WWTexture texture = this.getAttributes().getBackgroundTexture(dc); if (texture == null) return; this.doDrawBackgroundTexture(dc, width, height, opacity, pickPosition, texture); } @SuppressWarnings({"UnusedDeclaration"}) protected void doDrawBackgroundTexture(DrawContext dc, int width, int height, double opacity, Position pickPosition, WWTexture texture) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. // Save the current texture matrix state, and configure the texture matrix with the identity matrix. gl.glMatrixMode(GL2.GL_TEXTURE); gl.glPushMatrix(); gl.glLoadIdentity(); try { if (texture.bind(dc)) { // Enable OGL texture application. gl.glEnable(GL.GL_TEXTURE_2D); // Apply texture transform state. This must be done before the texture state is applied, because // applying the texture's internal transform potentially loads the texture, and sets the texture // parameter's we need to override in applyBackgroundTextureState(). this.transformImageCoordsToBackgroundImageCoords(dc, texture); this.transformBackgroundImageCoordsToAnnotationCoords(dc, width, height, texture); // Apply texture parameter and environment state. this.applyBackgroundTextureState(dc, width, height, opacity, texture); // Draw the annotaiton callout with the background texture enabled. this.drawCallout(dc, GL.GL_TRIANGLE_FAN, width, height, true); } } finally { // Restore the previous texture matrix and the matrix mode. gl.glPopMatrix(); gl.glMatrixMode(GL2.GL_MODELVIEW); // Restore the previous texture state. We do this to avoid pushing and popping the texture attribute // bit, which is expensive. We disabling textures and bind texture id 0. We don't set the texture coord // pointer to 0 because the client vertex array state is pushed and popped in drawCallout. gl.glDisable(GL.GL_TEXTURE_2D); gl.glBindTexture(GL.GL_TEXTURE_2D, 0); } } @SuppressWarnings({"UnusedDeclaration"}) protected void applyBackgroundTextureState(DrawContext dc, int width, int height, double opacity, WWTexture texture) { GL gl = dc.getGL(); // Apply texture wrap state. String imageRepeat = this.getAttributes().getImageRepeat(); int sWrap = (imageRepeat.equals(AVKey.REPEAT_X) || imageRepeat.equals(AVKey.REPEAT_XY)) ? GL.GL_REPEAT : GL2.GL_CLAMP_TO_BORDER; int tWrap = (imageRepeat.equals(AVKey.REPEAT_Y) || imageRepeat.equals(AVKey.REPEAT_XY)) ? GL.GL_REPEAT : GL2.GL_CLAMP_TO_BORDER; gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, sWrap); gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, tWrap); // Apply blending and color state. double imageOpacity = opacity * this.getAttributes().getImageOpacity(); this.applyColor(dc, java.awt.Color.WHITE, imageOpacity, true); } /** * Transforms texture coordinates from standard GL image coordinates to Annotation background image coordinates. In * standard GL image coordinates (0, 0) maps to the image's lower left corner, and (1, 1) * maps to the image's upper right corner. In Annotation background image coordinates (0, 0) maps to * the image's upper left corner, and (imageWidth, imageHeight) maps to the image's lower right corner. * This assumes the current OGL matrix mode is GL_TEXTURE. * * @param dc the DrawContext to receive the texture coordinate transform. * @param texture the texture to transform from standard GL image coordinates to Annotation background image * coordinates. */ protected void transformImageCoordsToBackgroundImageCoords(DrawContext dc, WWTexture texture) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. // Apply texture's internal transform state. This ensures we start with standard GL coordinates: the origin is // in the texture's lower left corner, and the Y axis points up. texture.applyInternalTransform(dc); // Transform standard coordiantes to Annotation background image coordinates: (0, 0) maps to the texture's upper // left corner, and (textureWidth, textureHeight) maps to the texture's lower right corner. gl.glScaled(1d, -1d, 1d); gl.glTranslated(0d, -1d, 0d); gl.glScaled(1d / (double) texture.getWidth(dc), 1d / (double) texture.getHeight(dc), 1d); } /** * Transforms texture coordinates from Annotation background image coordinates to Annotation geometry coordinates * (in screen pixels), and applies the Annotation's image scale and image offset attributes. In Annotation * background image coordinates (0, 0) maps to the image's upper left corner, and (imageWidth, * imageHeight) maps to the image's lower right corner. In Annotation geometry coordinates (0, * 0) maps to the Annotation geometry's lower left corner (ignoring any leader geometry), and (width, * height) maps to the Annotation's upper right corner in window coordinates (screen pixels). This assumes * the current OGL matrix mode is GL_TEXTURE. * * @param dc the DrawContext to receive the texture coordinate transform. * @param width the Annotation's width, in window coordinates (screen pixels). * @param height the Annotation's height, in window coordinates (screen pixels). * @param texture the texture to transform from Annotation background image coordinates to Annotation geometry * coordinates. */ @SuppressWarnings({"UnusedDeclaration"}) protected void transformBackgroundImageCoordsToAnnotationCoords(DrawContext dc, int width, int height, WWTexture texture) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. // Apply the Annotation's image scale. The scale is applied inversely because texture coordinates and the // texture's size on the Annotation are inversely related. double imageScale = this.getAttributes().getImageScale(); gl.glScaled(1.0 / imageScale, 1.0 / imageScale, 1); // Apply the Annotation's image offset in screen pixels. The offset is applied inversely because texture // coordinates and the texture's position on the Annotation are inversely related. java.awt.Point imageOffset = this.getAttributes().getImageOffset(); if (imageOffset != null) { gl.glTranslated(-imageOffset.x, -imageOffset.y, 0); } // Transform the Annotation background image origin to the Annotation geometry coordinate origin: (0, 0) maps to // the Annotation's lower left corner (igoring any leader geometry), and the Y axis points up. gl.glScaled(1, -1, 1); gl.glTranslated(0, -height, 0); } //**************************************************************// //******************** Border Rendering **********************// //**************************************************************// @SuppressWarnings({"UnusedDeclaration"}) protected void drawBorder(DrawContext dc, int width, int height, double opacity, Position pickPosition) { if (this.getAttributes().getBorderWidth() <= 0) return; GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. // Apply line smoothing state. if (dc.isPickingMode()) { gl.glDisable(GL.GL_LINE_SMOOTH); } else { gl.glEnable(GL.GL_LINE_SMOOTH); gl.glHint(GL.GL_LINE_SMOOTH_HINT, this.getAttributes().getAntiAliasHint()); } // Apply line stipple state. if (dc.isPickingMode() || (this.getAttributes().getBorderStippleFactor() <= 0)) { gl.glDisable(GL2.GL_LINE_STIPPLE); } else { gl.glEnable(GL2.GL_LINE_STIPPLE); gl.glLineStipple( this.getAttributes().getBorderStippleFactor(), this.getAttributes().getBorderStipplePattern()); } // Apply line width state. gl.glLineWidth((float) this.getAttributes().getBorderWidth()); // Apply blending and color state. this.applyColor(dc, this.getAttributes().getBorderColor(), opacity, false); this.drawCallout(dc, GL.GL_LINE_STRIP, width, height, false); } //**************************************************************// //******************** Text Rendering ************************// //**************************************************************// protected void drawText(DrawContext dc, int width, int height, double opacity, Position pickPosition) { AnnotationAttributes attribs = this.getAttributes(); String text = this.getText(); if (text == null || text.length() == 0) return; java.awt.Rectangle insetBounds = this.computeInsetBounds(width, height); // If we're in picking mode and the pick point does not intersect the annotation's inset bounds in screen space, // then exit. if (dc.isPickingMode()) { if (dc.getPickPoint() == null) return; java.awt.Rectangle screenInsetBounds = this.transformByModelview(dc, insetBounds); java.awt.Point glPickPoint = this.glPointFromAWTPoint(dc, dc.getPickPoint()); if (!screenInsetBounds.contains(glPickPoint)) return; } // Wrap the text to the annotation's inset bounds. String wrappedText = this.getWrappedText(dc, insetBounds.width, insetBounds.height, text, attribs.getFont(), attribs.getTextAlign()); java.awt.Rectangle wrappedTextBounds = this.getTextBounds(dc, wrappedText, attribs.getFont(), attribs.getTextAlign()); int baselineOffset = (int) (wrappedTextBounds.y / 6.0); // TODO: why is baseline offset computed this way? int x = insetBounds.x; int y = insetBounds.y + baselineOffset + 2; // TODO: why does this y-coordinate have an additional +2? // Adjust the text x-coordinate according to the text alignment property. if (attribs.getTextAlign().equals(AVKey.CENTER)) { x = (int) insetBounds.getCenterX(); } else if (attribs.getTextAlign().equals(AVKey.RIGHT)) { x = (int) insetBounds.getMaxX(); } // Adjust the text y-coordinate to fit inside the annotation's inset region. if (insetBounds.height > 0) { y += insetBounds.height; } else { y += wrappedTextBounds.height; } int lineHeight = (int) wrappedTextBounds.getMinY(); Object pickObject = (this.delegateOwner != null) ? this.delegateOwner : this; this.drawText(dc, x, y, lineHeight, opacity, pickObject, pickPosition, wrappedText); } protected void drawText(DrawContext dc, int x, int y, int lineHeight, double opacity, Object pickObject, Position pickPosition, String text) { boolean isHTML = MultiLineTextRenderer.containsHTML(text); if (isHTML) { this.drawHTML(dc, x, y, lineHeight, opacity, pickObject, pickPosition, text); } else { this.drawPlainText(dc, x, y, lineHeight, opacity, pickObject, pickPosition, text); } } protected void drawPlainText(DrawContext dc, int x, int y, int lineHeight, double opacity, Object pickObject, Position pickPosition, String text) { AnnotationAttributes attribs = this.getAttributes(); MultiLineTextRenderer mltr = this.getMultiLineTextRenderer(dc, attribs.getFont(), attribs.getTextAlign()); java.awt.Color textColor = this.modulateColorOpacity(attribs.getTextColor(), opacity); java.awt.Color backColor = this.modulateColorOpacity(attribs.getBackgroundColor(), opacity); mltr.setTextColor(textColor); mltr.setBackColor(backColor); if (dc.isPickingMode()) { mltr.pick(text, x, y, lineHeight, dc, this.pickSupport, pickObject, pickPosition); } else { mltr.getTextRenderer().begin3DRendering(); try { mltr.draw(text, x, y, lineHeight, attribs.getEffect()); } finally { mltr.getTextRenderer().end3DRendering(); } } } @SuppressWarnings({"UnusedDeclaration"}) protected void drawHTML(DrawContext dc, int x, int y, int lineHeight, double opacity, Object pickObject, Position pickPosition, String text) { AnnotationAttributes attribs = this.getAttributes(); MultiLineTextRenderer mltr = this.getMultiLineTextRenderer(dc, attribs.getFont(), attribs.getTextAlign()); java.awt.Color textColor = this.modulateColorOpacity(attribs.getTextColor(), opacity); java.awt.Color backColor = this.modulateColorOpacity(attribs.getBackgroundColor(), opacity); mltr.setTextColor(textColor); mltr.setBackColor(backColor); if (dc.isPickingMode()) { mltr.pickHTML(text, x, y, dc.getTextRendererCache(), dc, this.pickSupport, pickObject, pickPosition); } else { mltr.drawHTML(text, x, y, dc.getTextRendererCache()); } } //**************************************************************// //******************** Recursive Child Rendering *************// //**************************************************************// protected void drawChildren(DrawContext dc, int width, int height, double opacity, Position pickPosition) { if (this.childList.isEmpty()) return; java.awt.Rectangle insetBounds = this.computeInsetBounds(width, height); this.beginDrawChildren(dc, insetBounds); try { this.doDrawChildren(dc, insetBounds, opacity, pickPosition); } finally { this.endDrawChildren(dc); } } @SuppressWarnings({"UnusedDeclaration"}) protected void doDrawChildren(DrawContext dc, java.awt.Rectangle bounds, double opacity, Position pickPosition) { this.layoutManager.setPickSupport(this.pickSupport); this.layoutManager.drawAnnotations(dc, bounds, this.childList, opacity, pickPosition); } @SuppressWarnings({"UnusedDeclaration"}) protected void beginDrawChildren(DrawContext dc, java.awt.Rectangle bounds) { this.layoutManager.beginDrawAnnotations(dc, bounds); } protected void endDrawChildren(DrawContext dc) { this.layoutManager.endDrawAnnotations(dc); } //**************************************************************// //******************** Rendering Support *********************// //**************************************************************// protected void bindPickableObject(DrawContext dc, Position position) { java.awt.Color color = dc.getUniquePickColor(); int colorCode = color.getRGB(); Object object = (this.delegateOwner != null) ? this.delegateOwner : this; this.pickSupport.addPickableObject(colorCode, object, position, false); GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. gl.glColor3ub((byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue()); } protected void drawCallout(DrawContext dc, int mode, int width, int height, boolean useTexCoords) { String shape = this.getAttributes().getFrameShape(); if (shape == null) return; java.awt.Point offset = this.getAttributes().getDrawOffset(); java.awt.Point leaderOffset = new java.awt.Point((width / 2) - offset.x, -offset.y); int leaderGapWidth = this.getAttributes().getLeaderGapWidth(); int cornerRadius = this.getAttributes().getCornerRadius(); java.nio.DoubleBuffer buffer = vertexBuffer; if (this.getAttributes().getLeader().equals(AVKey.SHAPE_TRIANGLE)) { buffer = FrameFactory.createShapeWithLeaderBuffer(shape, width, height, leaderOffset, leaderGapWidth, cornerRadius, buffer); } else { buffer = FrameFactory.createShapeBuffer(shape, width, height, cornerRadius, buffer); } if (buffer != null) vertexBuffer = buffer; if (buffer == null) return; int count = buffer.remaining() / 2; if (useTexCoords) { FrameFactory.drawBuffer(dc, mode, count, buffer, buffer); } else { FrameFactory.drawBuffer(dc, mode, count, buffer); } } protected void applyColor(DrawContext dc, java.awt.Color color, double opacity, boolean premultiplyColors) { if (dc.isPickingMode()) return; double finalOpacity = opacity * (color.getAlpha() / 255.0); GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. gl.glEnable(GL.GL_BLEND); OGLUtil.applyBlending(gl, premultiplyColors); OGLUtil.applyColor(gl, color, finalOpacity, premultiplyColors); } protected java.awt.Color modulateColorOpacity(java.awt.Color color, double opacity) { float[] compArray = new float[4]; color.getRGBComponents(compArray); compArray[3] *= (float) opacity; return new java.awt.Color(compArray[0], compArray[1], compArray[2], compArray[3]); } protected java.awt.Rectangle transformByModelview(DrawContext dc, java.awt.Rectangle rectangle) { double[] compArray = new double[16]; GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. gl.glGetDoublev(GL2.GL_MODELVIEW_MATRIX, compArray, 0); Matrix modelview = Matrix.fromArray(compArray, 0, false); Vec4 origin = new Vec4(rectangle.x, rectangle.y, 1); Vec4 size = new Vec4(rectangle.width, rectangle.height, 0); origin = origin.transformBy4(modelview); size = size.transformBy3(modelview); return new java.awt.Rectangle((int) origin.x, (int) origin.y, (int) size.x, (int) size.y); } protected java.awt.Point glPointFromAWTPoint(DrawContext dc, java.awt.Point awtPoint) { if (dc.getView() == null || dc.getView().getViewport() == null) return null; java.awt.Rectangle viewport = dc.getView().getViewport(); return new java.awt.Point(awtPoint.x, viewport.height - awtPoint.y - 1); } //**************************************************************// //******************** Text Utilities ************************// //**************************************************************// protected TextRenderer getTextRenderer(DrawContext dc, java.awt.Font font) { return OGLTextRenderer.getOrCreateTextRenderer(dc.getTextRendererCache(), font); } protected MultiLineTextRenderer getMultiLineTextRenderer(DrawContext dc, java.awt.Font font, String align) { TextRenderer tr = this.getTextRenderer(dc, font); MultiLineTextRenderer mltr = new MultiLineTextRenderer(tr); // Tighten lines together a bit // mltr.setLineSpacing(-2); mltr.setTextAlign(align); return mltr; } protected String getWrappedText(DrawContext dc, int width, int height, String text, java.awt.Font font, String align) { Object key = new TextCacheKey(width, height, text, font, align); String wrappedText = this.wrappedTextMap.get(key); if (wrappedText == null) { wrappedText = this.wrapText(dc, width, height, text, font, align); this.wrappedTextMap.put(key, wrappedText); } return wrappedText; } protected java.awt.Rectangle getTextBounds(DrawContext dc, String text, java.awt.Font font, String align) { Object key = new TextCacheKey(0, 0, text, font, align); java.awt.Rectangle bounds = this.textBoundsMap.get(key); if (bounds == null) { bounds = this.computeTextBounds(dc, text, font, align); this.textBoundsMap.put(key, bounds); } return new java.awt.Rectangle(bounds); } protected String wrapText(DrawContext dc, int width, int height, String text, java.awt.Font font, String align) { if (text.length() > 0) { MultiLineTextRenderer mltr = this.getMultiLineTextRenderer(dc, font, align); if (MultiLineTextRenderer.containsHTML(text)) { text = MultiLineTextRenderer.processLineBreaksHTML(text); text = mltr.wrapHTML(text, width, height, dc.getTextRendererCache()); } else { text = mltr.wrap(text, width, height); } } return text; } protected java.awt.Rectangle computeTextBounds(DrawContext dc, String text, java.awt.Font font, String align) { if (text.length() > 0) { MultiLineTextRenderer mltr = this.getMultiLineTextRenderer(dc, font, align); if (MultiLineTextRenderer.containsHTML(text)) { return mltr.getBoundsHTML(text, dc.getTextRendererCache()); } else { return mltr.getBounds(text); } } else { return new java.awt.Rectangle(); } } protected static class TextCacheKey { private final int width; private final int height; private final String text; private final java.awt.Font font; private final String align; public TextCacheKey(int width, int height, String text, java.awt.Font font, String align) { this.width = width; this.height = height; this.text = text; this.font = font; this.align = align; } public boolean equals(Object o) { if (this == o) return true; if (o == null || this.getClass() != o.getClass()) return false; TextCacheKey that = (TextCacheKey) o; return (this.width == that.width) && (this.height == that.height) && (this.align.equals(that.align)) && (this.text != null ? this.text.equals(that.text) : that.text == null) && (this.font != null ? this.font.equals(that.font) : that.font == null); } public int hashCode() { int result = this.width; result = 31 * result + this.height; result = 31 * result + (this.text != null ? this.text.hashCode() : 0); result = 31 * result + (this.font != null ? this.font.hashCode() : 0); result = 31 * result + (this.align != null ? this.align.hashCode() : 0); return result; } } //**************************************************************// //******************** Bound Computations *********************// //**************************************************************// protected java.awt.Rectangle computeInsetBounds(int width, int height) { // TODO: factor in border width? java.awt.Insets insets = this.getAttributes().getInsets(); int insetWidth = width - (insets.left + insets.right); int insetHeight = height - (insets.bottom + insets.top); if (insetWidth < 0) insetWidth = 0; if (insetHeight < 0 && height > 0) insetHeight = 1; else if (insetHeight < 0) insetHeight = 0; return new java.awt.Rectangle(insets.left, insets.bottom, insetWidth, insetHeight); } protected java.awt.Rectangle computeFreeBounds(DrawContext dc, int width, int height) { AnnotationAttributes attribs = this.getAttributes(); // Start with the inset bounds. java.awt.Rectangle bounds = computeInsetBounds(width, height); // Adjust the free bounds by the text bounds. String wrappedText = this.getWrappedText(dc, width, height, this.getText(), attribs.getFont(), attribs.getTextAlign()); java.awt.Rectangle textBounds = this.getTextBounds(dc, wrappedText, attribs.getFont(), attribs.getTextAlign()); bounds.height -= textBounds.height; return bounds; } protected java.awt.Dimension adjustSizeToText(DrawContext dc, int width, int height) { AnnotationAttributes attribs = this.getAttributes(); String text = this.getWrappedText(dc, width, height, this.getText(), attribs.getFont(), attribs.getTextAlign()); java.awt.Rectangle textBounds = this.getTextBounds(dc, text, attribs.getFont(), attribs.getTextAlign()); // If the attributes specify to fit the annotation to the wrapped text width, then set the inset width to // the wrapped text width. if (attribs.getAdjustWidthToText().equals(AVKey.SIZE_FIT_TEXT) && text.length() > 0) { width = textBounds.width; } // If the inset height is less than or equal to zero, then override the inset height with the the wrapped // text height. if (height <= 0) { height = textBounds.height; } return new java.awt.Dimension(width, height); } protected java.awt.Dimension adjustSizeToChildren(DrawContext dc, int width, int height) { if (this.layoutManager != null) { java.awt.Dimension preferredSize = this.layoutManager.getPreferredSize(dc, this.childList); if (preferredSize != null) { if (width < preferredSize.width) width = preferredSize.width; if (height < preferredSize.height) height = preferredSize.height; } } return new java.awt.Dimension(width, height); } protected Rectangle computeBoundingRectangle(Rectangle rect, int px, int py) { if (rect.contains(px, py)) return rect; int dx = 0, dy = 0, dw = 0, dh = 0; if (px < rect.x) { dx = px - rect.x; dw = -dx; } else if (px > rect.x + rect.width - 1) { dw = px - (rect.x + rect.width - 1); } if (py < rect.y) { dy = py - rect.y; dh = -dy; } else if (py > rect.y + rect.height - 1) { dh = py - (rect.y + rect.height - 1); } rect.setBounds(rect.x + dx, rect.y + dy, rect.width + dw, rect.height + dh); return rect; } //**************************************************************// //******************** Restorable State **********************// //**************************************************************// /** * Returns an XML state document String describing the public attributes of this AbstractAnnotation. * * @return XML state document string describing this AbstractAnnotation. */ public String getRestorableState() { RestorableSupport restorableSupport = null; // This should never be the case, but we check to be thorough. if (this.attributes != null) { // Allow AnnotationAttributes to define it's restorable state, if any. String attributesStateInXml = this.attributes.getRestorableState(); if (attributesStateInXml != null) { try { restorableSupport = RestorableSupport.parse(attributesStateInXml); } catch (Exception e) { // Parsing the document specified by the superclass failed. String message = Logging.getMessage("generic.ExceptionAttemptingToParseStateXml", attributesStateInXml); Logging.logger().severe(message); } } } // Create our own state document from scratch. if (restorableSupport == null) restorableSupport = RestorableSupport.newRestorableSupport(); // Creating a new RestorableSupport failed. RestorableSupport logged the problem, so just return null. if (restorableSupport == null) return null; // Escape the text property when saving it to preserve markup characters. if (this.text != null) restorableSupport.addStateValueAsString("text", this.text, true); restorableSupport.addStateValueAsBoolean("alwaysOnTop", this.alwaysOnTop); return restorableSupport.getStateAsXml(); } /** * Restores publicly settable attribute values found in the specified XML state document String. The document * specified by stateInXml must be a well formed XML document String, or this will throw an * IllegalArgumentException. Unknown structures in stateInXml are benign, because they will simply be * ignored. * * @param stateInXml an XML document String describing an AbstractAnnotation. * * @throws IllegalArgumentException If stateInXml is null, or if stateInXml 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); } AnnotationAttributes attribs = this.attributes; // Annotation's attributes should not be null. Therefore we assign it a new one as a fallback. if (attribs == null) attribs = new AnnotationAttributes(); // Restore any AnnotationAttributes state found in "stateInXml". attribs.restoreState(stateInXml); setAttributes(attribs); // No special processing is required to restore the escaped text property. String textState = restorableSupport.getStateValueAsString("text"); if (textState != null) setText(textState); Boolean booleanState = restorableSupport.getStateValueAsBoolean("alwaysOnTop"); if (booleanState != null) setAlwaysOnTop(booleanState); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy