gov.nasa.worldwind.render.AbstractBrowserBalloon Maven / Gradle / Ivy
The 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 gov.nasa.worldwind.*;
import gov.nasa.worldwind.avlist.*;
import gov.nasa.worldwind.event.SelectEvent;
import gov.nasa.worldwind.globes.GlobeStateKey;
import gov.nasa.worldwind.layers.Layer;
import gov.nasa.worldwind.pick.*;
import gov.nasa.worldwind.util.*;
import gov.nasa.worldwind.util.webview.*;
import com.jogamp.opengl.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.beans.PropertyChangeEvent;
import java.net.URL;
import java.nio.FloatBuffer;
import java.util.*;
import java.util.List;
/**
* A {@link gov.nasa.worldwind.render.Balloon}
that displays HTML, JavaScript, and Flash content using the
* system's native browser. The balloon's HTML content is specified by calling setText
with an HTML
* formatted string. A browser balloon resolves relative URLs
in the HTML content by consulting its
* resource resolver. The resource resolver converts a relative URL
to an absolute URL
that
* the browser can load. The resource resolver is specified by calling setResourceResolver
, and may be one
* of the following: a {@link gov.nasa.worldwind.util.webview.WebResourceResolver}
, a {@link
* java.net.URL}
, or a String
containing a valid URL description. If a browser balloon's resource
* resolver is null
or is an unrecognized type, the browser interprets relative URLs
as
* unresolved references.
*
* Browser Controls
*
* Browser balloons display three default browser controls that enable users to navigate the browser's history back and
* forward, and to close the balloon. When the user selects one of these controls, a SelectEvent
is
* generated with the PickedObject's
AVKey.ACTION
value set to one of
* AVKey.CLOSE
, AVKey.BACK
, or AVKey.FORWARD
. These controls may be enabled or
* disabled by calling setDrawBrowserControls
(they are enabled by default), and may be customized by
* adding or removing controls from the browser balloon. See getBrowserControls
,
* addBrowserControl
, and removeBrowserControl
.
*
* Resize Control
*
* Browser balloons provide a default resize control that is activated by dragging the balloon's border. When the user
* drags the border, a SelectEvent
is generated with the PickedObject's AVKey.ACTION
value set
* to AVKey.RESIZE
. The PickedObject's
AVKey.BOUNDS
value holds the Balloon's
* screen bounds in AWT coordinates (origin at the upper left corner) as a java.awt.Rectangle
. The resize
* control may be enabled or disabled by calling setDrawResizeControl
(it is enabled by default).
*
* Balloon Size
*
* The browser balloon's screen width and height are specified as a {@link gov.nasa.worldwind.render.Size}
* object in its BalloonAttributes
. This size may configured in one of the following three modes:
* - Explicit size in pixels.
- Fraction of the
WorldWindow
size. - Fit to the balloon's
* HTML content.
The balloon's width and height may be configured independently, enabling any combination of
* these three modes. The balloon's width and height are limited by its maximum size, which is also specified as a
* Size
object in its BalloonAttributes
. If the maximum size is null
, the
* balloon's width and height are unlimited. The space provided for the balloon's HTML content equal to the balloon's
* screen width and height minus the balloon's insets, also specified in its BalloonAttributes
*
* The balloon's width or height (or both) may be configured to fit to the balloon's HTML content by configuring its
* BalloonAttributes
with a Size
who's width or height mode is
* Size.NATIVE_DIMENSION
or Size.MAINTAIN_ASPECT_RATIO
. When configured in this mode, the
* browser balloon's size always fits the HTML content specified at construction or by calling setText
. If
* a user action causes the balloon to navigate to another page, the balloon continues to fit to its current HTML
* content.
*
* The balloon frame's corner radius and leader width specified in its BalloonAttributes
are limited by the
* balloon's size. The corner radius is first limited by the balloon's width and height, then the leader width is
* limited by the balloon's width and height minus space taken by rounded corners. For example, if the corner radius is
* 100 and the width and height are 50 and 100, the actual corner radius used is 25 - half of the rectangle's smallest
* dimension. Similarly, if the leader is attached to the rectangle's bottom, its width is limited by the rectangle's
* width minus any space used by the balloon's rounded corners.
*
* Hiding the balloon
*
* The balloon can be made visible or invisible by calling {@link #setVisible(boolean) setVisible}. The balloon's {@code
* visibilityAction} determines what happens to the native web browser when the balloon is invisible. The balloon can
* either release its native web browser, preventing the native browser from consuming system resources while the
* balloon is invisible, or it can retain the native browser. Possible values of {@code visibilityAction} are: -
* AVKey.VISIBILITY_ACTION_RELEASE (Default) — Release the native web browser when this balloon is invisible. The
* browser will be recreated when the balloon becomes visible again. All browser state (navigation history, page scroll
* position, etc) will be lost.
- AVKey.VISIBILITY_ACTION_RETAIN — Do not release the native browser when
* this balloon is invisible. This action will retain all browser state while this balloon is invisible, but the browser
* will continue to consume system resources. Dynamic content on the page (such as animations and Flash video) will
* continue to play while the balloon is invisible.
*
* @author dcollins
* @version $Id: AbstractBrowserBalloon.java 2148 2014-07-14 16:27:49Z tgaskins $
*/
public abstract class AbstractBrowserBalloon extends AbstractBalloon implements HotSpot, Disposable
{
public static class BrowserControl extends AVListImpl
{
protected static final Color DEFAULT_COLOR = new Color(255, 255, 255, 153);
protected static final Color DEFAULT_HIGHLIGHT_COLOR = new Color(255, 255, 255, 255);
protected boolean visible = true;
protected Offset offset;
protected Size size;
protected Object imageSource;
protected Color color;
protected Color highlightColor;
protected WWTexture texture;
public BrowserControl(String action, Offset offset, Object imageSource)
{
if (offset == null)
{
String message = Logging.getMessage("nullValue.OffsetIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (WWUtil.isEmpty(imageSource))
{
String message = Logging.getMessage("nullValue.ImageSource");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.setAction(action);
this.offset = offset;
this.size = new Size(Size.NATIVE_DIMENSION, 0d, null, Size.NATIVE_DIMENSION, 0d, null);
this.imageSource = imageSource;
}
public BrowserControl(String action, Offset offset, Size size, Object imageSource)
{
if (offset == null)
{
String message = Logging.getMessage("nullValue.OffsetIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (size == null)
{
String message = Logging.getMessage("nullValue.SizeIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (WWUtil.isEmpty(imageSource))
{
String message = Logging.getMessage("nullValue.ImageSource");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.setValue(AVKey.ACTION, action);
this.offset = offset;
this.size = size;
this.imageSource = imageSource;
}
public boolean isVisible()
{
return this.visible;
}
public void setVisible(boolean visible)
{
this.visible = visible;
}
public String getAction()
{
return this.getStringValue(AVKey.ACTION);
}
public void setAction(String action)
{
this.setValue(AVKey.ACTION, action);
}
public Offset getOffset()
{
return this.offset;
}
public void setOffset(Offset offset)
{
if (offset == null)
{
String message = Logging.getMessage("nullValue.OffsetIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.offset = offset;
}
public Size getSize()
{
return size;
}
public void setSize(Size size)
{
if (size == null)
{
String message = Logging.getMessage("nullValue.SizeIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.size = size;
}
public Color getColor()
{
return color;
}
public void setColor(Color color)
{
this.color = color;
}
public Color getHighlightColor()
{
return highlightColor;
}
public void setHighlightColor(Color highlightColor)
{
this.highlightColor = highlightColor;
}
public Object getImageSource()
{
return imageSource;
}
public void setImageSource(Object imageSource)
{
if (WWUtil.isEmpty(imageSource))
{
String message = Logging.getMessage("nullValue.ImageSource");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.imageSource = imageSource;
this.texture = null; // Force a texture to be re-created with the new image source.
}
protected WWTexture getTexture()
{
if (this.texture == null && this.getImageSource() != null)
{
this.texture = new BasicWWTexture(this.getImageSource(), true);
}
return this.texture;
}
}
/**
* Holds the vertex data and the defining properties of a balloon's frame and leader geometry. The
* vertexBuffer
represents the screen-coordinate vertices of the balloon's frame. The
* size
, offset
, balloonShape
, leaderShape
,
* leaderWidth
, and cornerRadius
are the frame geometry's defining properties. These are
* used to determine when the frame geometry is invalid and must be recomputed.
*/
protected static class FrameGeometryInfo
{
protected FloatBuffer vertexBuffer;
protected Dimension size;
protected Point offset;
protected String balloonShape;
protected String leaderShape;
protected int leaderWidth;
protected int cornerRadius;
public FrameGeometryInfo()
{
}
}
protected class OrderedBrowserBalloon implements OrderedRenderable
{
/** The location and size of the balloon's content frame in the viewport (on the screen). */
protected Rectangle screenRect;
/** The extent of the balloon's geometry in the viewport (on the screen). */
protected Rectangle screenExtent;
/**
* The extend of the balloon's pickable geometry in the viewport (on the screen). Includes this balloon's outline
* where it exceeds the screen extent.
*/
protected Rectangle screenPickExtent;
/** The location and size of the WebView's content frame in the viewport (on the screen). */
protected Rectangle webViewRect;
/** The balloon geometry vertices passed to OpenGL. */
protected FrameGeometryInfo frameInfo;
/** Used to order the balloon as an ordered renderable. */
protected double eyeDistance;
/** Identifies the frame used to calculate the balloon's geometry. */
protected long geomTimeStamp = -1;
/** Identifies the frame used to calculate the balloon's active attributes and points. */
protected long frameTimeStamp = -1;
@Override
public double getDistanceFromEye()
{
return this.eyeDistance;
}
@Override
public void pick(DrawContext dc, Point pickPoint)
{
AbstractBrowserBalloon.this.pick(dc, pickPoint, this);
}
@Override
public void render(DrawContext dc)
{
AbstractBrowserBalloon.this.drawOrderedRenderable(dc, this);
}
}
/**
* The browser balloon's default native size: 400x300. This default size is used when a balloon's size is configured
* to use a native dimension, but the WebView either has not been created or its content size is not known. This
* default chosen to minimize the popping effect when the balloon's size switches from the default to the WebView's
* content size.
*/
protected static final Dimension DEFAULT_NATIVE_SIZE = new Dimension(400, 300);
/**
* The default outline pick width in pixels. The default is 10 pixels, the maximum OpenGL line width supported by
* most graphics cards.
*/
protected static final int DEFAULT_OUTLINE_PICK_WIDTH = 10;
/**
* The class name of the default {@link gov.nasa.worldwind.util.webview.WebViewFactory}
used to create
* the balloon's internal WebView
. This factory is used when the configuration does not specify a
* WebView factory.
*/
protected static final String DEFAULT_WEB_VIEW_FACTORY = BasicWebViewFactory.class.getName();
/** The number of slices used to display a balloon frame as an ellipse: 64. */
protected static final int FRAME_GEOMETRY_ELLIPSE_SLICES = 64;
/** The number of slices used to display each of a rectangular balloon frame's rounded corners: 16. */
protected static final int FRAME_GEOMETRY_RECTANGLE_CORNER_SLICES = 16;
/**
* Returns a list containing the browser balloon's three default browser controls, configured as follows:
*
* Control Action Offset Size Image Source
* Close AVKey.CLOSE
(30, 25) pixels inset from the balloon's upper right
* corner Image source's native size in pixels (16x16) images/browser-close-16x16.gif
* Back AVKey.BACK
(15, 25) pixels inset from the balloon's upper left
* corner Image source's native size in pixels (16x16) images/browser-back-16x16.gif
* Forward AVKey.FORWARD
(35, 25) pixels inset from the balloon's upper left
* corner Image source's native size in pixels (16x16) images/browser-forward-16x16.gif
*
*
* @return a list containing the browser balloon's default browser controls.
*/
protected static List createDefaultBrowserControls()
{
return Arrays.asList(
new BrowserControl(AVKey.CLOSE, new Offset(30.0, 25.0, AVKey.INSET_PIXELS, AVKey.INSET_PIXELS),
"images/browser-close-16x16.gif"),
new BrowserControl(AVKey.BACK, new Offset(15.0, 25.0, AVKey.PIXELS, AVKey.INSET_PIXELS),
"images/browser-back-16x16.gif"),
new BrowserControl(AVKey.FORWARD, new Offset(35.0, 25.0, AVKey.PIXELS, AVKey.INSET_PIXELS),
"images/browser-forward-16x16.gif")
);
}
/** Action that will occur when the balloon is made invisible. */
protected String visibilityAction = AVKey.VISIBILITY_ACTION_RELEASE;
protected boolean drawTitleBar = true;
protected boolean drawBrowserControls = true;
protected boolean drawResizeControl = true;
/**
* The line width used to draw the the balloon's outline during picking. Initially set to {@link
* #DEFAULT_OUTLINE_PICK_WIDTH}
.
*/
protected int outlinePickWidth = DEFAULT_OUTLINE_PICK_WIDTH;
protected List browserControls = new ArrayList(createDefaultBrowserControls());
/**
* Indicates the object used to resolve relative resource paths in this browser balloon's HTML content. May be one
* of the following: {@link gov.nasa.worldwind.util.webview.WebResourceResolver}
, {@link
* java.net.URL}
, {@link String}
containing a valid URL description, or null
to
* specify that relative paths should be interpreted as unresolved references. Initially null
.
*/
protected Object resourceResolver;
/** Identifies the time when the balloon text was updated. Initially -1. */
protected long textUpdateTime = -1;
/**
* Denotes whether or not an attempt at WebView creation failed. When true
the balloon does not perform
* subsequent attempts to create the WebView. Initially false
.
*/
protected boolean webViewCreationFailed;
/** Interface for interacting with the operating system's web browser control. Initially null
. */
protected WebView webView;
/** Identifies the frame used to update the WebView's state. */
protected long webViewTimeStamp = -1;
/** The location of the balloon's content frame relative to the balloon's screen point in the viewport. */
protected Point screenOffset;
/**
* The size of the WebView's HTML content size, in pixels. This is the size that the WebView can be displayed at
* without the need for scroll bars. May be null
or (0, 0)
, indicating that the WebView's
* HTML content size is unknown. Initially null
.
*/
protected Dimension webViewContentSize;
/** The layer active during the most recent pick pass. */
protected Layer pickLayer;
/** The screen coordinate of the last SelectEvent
sent to this balloon's select
method. */
protected Point lastPickPoint;
/** Support for setting up and restoring picking state, and resolving the picked object. */
protected PickSupport pickSupport = new PickSupport();
/** Support for setting up and restoring OpenGL state during rendering. */
protected OGLStackHandler osh = new OGLStackHandler();
protected long screenBalloonPickFrame;
protected long screenBalloonRenderFrame;
protected HashMap
orderedRenderables = new HashMap(1);
protected AbstractBrowserBalloon(String text)
{
super(text);
}
protected abstract OrderedBrowserBalloon createOrderedRenderable();
/**
* Computes and stores the balloon's model-coordinate and screen-coordinate points.
*
* @param dc the current draw context.
*/
protected abstract void computeBalloonPoints(DrawContext dc, OrderedBrowserBalloon obb);
protected abstract void setupDepthTest(DrawContext dc, OrderedBrowserBalloon obb);
/**
* Disposes the balloon's internal {@link gov.nasa.worldwind.util.webview.WebView}
. This does nothing
* if the balloon is already disposed.
*/
public void dispose()
{
this.disposeWebView();
}
public boolean isDrawTitleBar()
{
return this.drawTitleBar;
}
public void setDrawTitleBar(boolean draw)
{
this.drawTitleBar = draw;
}
public boolean isDrawBrowserControls()
{
return this.drawBrowserControls;
}
public void setDrawBrowserControls(boolean draw)
{
this.drawBrowserControls = draw;
}
public boolean isDrawResizeControl()
{
return this.drawResizeControl;
}
public void setDrawResizeControl(boolean draw)
{
this.drawResizeControl = draw;
}
/**
* {@inheritDoc}
*
* When this balloon is set to invisible, the {@code visibilityAction} determines what happens to the native web
* browser that backs the balloon. By default, the browser resources are released when the balloon is not visible.
*
* @see #setVisibilityAction(String)
*/
@Override
public void setVisible(boolean visible)
{
super.setVisible(visible);
// If the balloon is not visible and the visibility action indicates to release the browser, dispose of the web
// view to release native resources.
if (!this.isVisible() && AVKey.VISIBILITY_ACTION_RELEASE.equals(this.getVisibilityAction()))
{
this.disposeWebView();
}
}
/**
* Indicates the outline line width (in pixels) used during picking. A larger width than normal typically makes the
* outline easier to pick.
*
* @return the outline line width (in pixels) used during picking.
*
* @see #setOutlinePickWidth(int)
*/
public int getOutlinePickWidth()
{
return this.outlinePickWidth;
}
/**
* Specifies the outline line width (in pixels) to use during picking. The specified width
must be zero
* or a positive integer. Specifying a pick width of zero effectively disables the picking of the balloon's outline
* and its resize control. A larger width than normal typically makes the outline easier to pick.
*
* When the the balloon's resize control is enabled, the outline becomes the resize control and is drawn in the
* specified width
. Therefore this value also controls the balloon's resize control width. If the
* resize control is disabled by calling {@link #setDrawResizeControl(boolean)}
with a value of
* false
, this has no effect on the balloon's resize control until it is enabled.
*
* @param width the outline line width (in pixels) to use during picking.
*
* @throws IllegalArgumentException if width
is less than zero.
* @see #getOutlinePickWidth()
* @see #setDrawResizeControl(boolean)
*/
public void setOutlinePickWidth(int width)
{
if (width < 0)
{
String message = Logging.getMessage("Geom.WidthIsNegative", width);
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.outlinePickWidth = width;
}
public Iterable getBrowserControls()
{
return this.browserControls;
}
public void addBrowserControl(BrowserControl browserControl)
{
if (browserControl == null)
{
String message = Logging.getMessage("nullValue.BrowserControlIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.browserControls.add(browserControl);
}
public BrowserControl addBrowserControl(String action, Offset offset, Object imageSource)
{
if (offset == null)
{
String message = Logging.getMessage("nullValue.OffsetIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (WWUtil.isEmpty(imageSource))
{
String message = Logging.getMessage("nullValue.ImageSource");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
BrowserControl browserControl = new BrowserControl(action, offset, imageSource);
this.addBrowserControl(browserControl);
return browserControl;
}
public BrowserControl addBrowserControl(String action, Offset offset, Size size, Object imageSource)
{
if (offset == null)
{
String message = Logging.getMessage("nullValue.OffsetIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (size == null)
{
String message = Logging.getMessage("nullValue.SizeIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (WWUtil.isEmpty(imageSource))
{
String message = Logging.getMessage("nullValue.ImageSource");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
BrowserControl browserControl = new BrowserControl(action, offset, size, imageSource);
this.addBrowserControl(browserControl);
return browserControl;
}
public void addAllBrowserControls(Iterable extends BrowserControl> iterable)
{
if (iterable == null)
{
String message = Logging.getMessage("nullValue.IterableIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
for (BrowserControl browserControl : iterable)
{
if (browserControl != null)
{
this.browserControls.add(browserControl);
}
}
}
public void removeBrowserControl(BrowserControl browserControl)
{
if (browserControl == null)
{
String message = Logging.getMessage("nullValue.BrowserControlIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.browserControls.remove(browserControl);
}
public void removeAllBrowserControls()
{
this.browserControls.clear();
}
/**
* Indicates the object used to resolve relative resource paths in this browser balloon's HTML content.
*
* @return the object used to resolve relative resource paths in HTML content. One of the following: {@link
* gov.nasa.worldwind.util.webview.WebResourceResolver}
, {@link java.net.URL}
,
* {@link String}
containing a valid URL description, or null
to indicate that
* relative paths are interpreted as unresolved references.
*
* @see #setResourceResolver(Object)
*/
public Object getResourceResolver()
{
return this.resourceResolver;
}
/**
* Specifies a the object to use when resolving relative resource paths in this browser balloon's HTML content. The
* resourceResolver
may be one of the following:
*
* - a
{@link gov.nasa.worldwind.util.webview.WebResourceResolver}
- a
{@link
* java.net.URL}
- a
{@link String}
containing a valid URL description
*
* If the resourceResolver
is null
or is not one of the recognized types, this browser
* balloon interprets relative resource paths as unresolved references.
*
* @param resourceResolver the object to use when resolving relative resource paths in HTML content. May be one of
* the following: {@link gov.nasa.worldwind.util.webview.WebResourceResolver}
,
* {@link java.net.URL}
, {@link String}
containing a valid URL
* description, or null
to specify that relative paths should be interpreted as
* unresolved references.
*
* @see #getResourceResolver()
*/
public void setResourceResolver(Object resourceResolver)
{
this.resourceResolver = resourceResolver;
// Setting a new resource resolver may change how the WebView content is rendered. Set the textUpdate time to
// ensure that the WebView content will be reset on the next frame.
this.textUpdateTime = -1;
}
/**
* Indicates the the action that occurs when the BrowserBalloon is set to invisible. See {@link
* #setVisibilityAction(String) setVisibilityAction} for a description of the possible actions.
*
* @return A string that indicates the action that will occur when the balloon is set to invisible.
*
* @see #setVisibilityAction(String)
* @see #setVisible(boolean)
*/
public String getVisibilityAction()
{
return visibilityAction;
}
/**
* Specifies the action that occurs when this balloon is set to invisible. Possible actions are: -
* AVKey.VISIBILITY_ACTION_RELEASE (Default) — Release the native web browser when the balloon is invisible.
* The browser will be recreated when the balloon becomes visible. Note that all browser state (navigation history,
* page scroll position, etc) will be lost.
- AVKey.VISIBILITY_ACTION_RETAIN — Do not release the
* native browser when this balloon is invisible. This action will retain all browser state while the balloon is
* invisible, but the browser will continue to consume system resources while is is invisible. Dynamic content on
* the page (for example, Flash video) will continue to play.
*
* @param visibilityAction Either {@link AVKey#VISIBILITY_ACTION_RELEASE} or {@link AVKey#VISIBILITY_ACTION_RETAIN}.
*/
public void setVisibilityAction(String visibilityAction)
{
if (visibilityAction == null)
{
String message = Logging.getMessage("nullValue.StringIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.visibilityAction = visibilityAction;
}
/** Navigate the browser to the previous page in the browsing history. Has no effect if there is previous page. */
public void goBack()
{
if (this.webView != null)
this.webView.goBack();
}
/** Navigate the browser to the next page in the browsing history. Has no effect if there is no next page. */
public void goForward()
{
if (this.webView != null)
this.webView.goForward();
}
/**
* {@inheritDoc}
*
* Overridden to suppress AVKey.REPAINT
property change events sent by the balloon's internal
* {@link gov.nasa.worldwind.util.webview.WebView}
when isVisible
returns
* false
.
*/
@Override
public void propertyChange(PropertyChangeEvent propertyChangeEvent)
{
if (!this.isVisible() && propertyChangeEvent != null
&& AVKey.REPAINT.equals(propertyChangeEvent.getPropertyName()))
{
return;
}
super.propertyChange(propertyChangeEvent);
}
/** {@inheritDoc} */
public Rectangle getBounds(DrawContext dc)
{
if (dc == null)
{
String message = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
OrderedBrowserBalloon obb = this.orderedRenderables.get(dc.getGlobe().getGlobeStateKey());
if (obb == null)
obb = this.createOrderedRenderable();
// Update the balloon's active attributes and points if that hasn't already been done this frame.
this.updateRenderStateIfNeeded(dc, obb);
// Return the balloon's screen extent computed in updateRenderStateIfNeeded. This may be null.
return obb.screenExtent;
}
public void pick(DrawContext dc, Point pickPoint, OrderedBrowserBalloon obb)
{
// This method is called only when ordered renderables are being drawn.
if (!this.isPickEnabled())
return;
this.pickSupport.clearPickList();
try
{
this.pickSupport.beginPicking(dc);
this.drawOrderedRenderable(dc, obb);
}
finally
{
this.pickSupport.endPicking(dc);
this.pickSupport.resolvePick(dc, pickPoint, this.pickLayer);
}
}
public void render(DrawContext dc)
{
// This render method is called twice during frame generation. It's first called as a {@link Renderable}
// during Renderable
picking. It's called again during normal rendering. These two calls determine
// whether to add the placemark and its optional line to the ordered renderable list during pick and render.
if (dc == null)
{
String message = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (!this.isVisible())
return;
this.makeOrderedRenderable(dc);
}
/**
* Updates the balloon's per-frame rendering state, and determines whether to queue an ordered renderable for the
* balloon. This queues an ordered renderable if the balloon intersects the current viewing frustum, and if the
* balloon's internal rendering state can be computed. This updates the balloon's rendering state by calling
* updateRenderStateIfNeeded
, and updates its geometry by calling computeGeometry
.
*
* BrowserBalloon separates render state updates from geometry updates for two reasons: - Geometry may be
* updated based on different conditions.
- Rendering state potentially needs to be updated in
* getBounds.
*
* @param dc the current draw context.
*/
protected void makeOrderedRenderable(DrawContext dc)
{
// Prevent screen balloons from drawing more than once per frame for 2D continuous globes.
if (this instanceof ScreenBrowserBalloon && dc.isContinuous2DGlobe())
{
if (dc.isPickingMode() && this.screenBalloonPickFrame == dc.getFrameTimeStamp())
return;
if (!dc.isPickingMode() && this.screenBalloonRenderFrame == dc.getFrameTimeStamp())
return;
}
OrderedBrowserBalloon obb = this.orderedRenderables.get(dc.getGlobe().getGlobeStateKey());
if (obb == null)
{
obb = this.createOrderedRenderable();
this.orderedRenderables.put(dc.getGlobe().getGlobeStateKey(), obb);
}
// Update the balloon's active attributes and points if that hasn't already been done this frame.
this.updateRenderStateIfNeeded(dc, obb);
// Exit immediately if either the balloon's active attributes or its screen rectangle are null. In either case
// we cannot compute the balloon's geometry nor can we determine where to render the balloon.
if (this.getActiveAttributes() == null || obb.screenRect == null)
return;
// Re-use geometry already calculated this frame.
if (dc.getFrameTimeStamp() != obb.geomTimeStamp)
{
// Recompute this balloon's geometry only when an attribute change requires us to.
if (this.mustRegenerateGeometry(obb))
this.computeGeometry(obb);
obb.geomTimeStamp = dc.getFrameTimeStamp();
}
// Update the balloon's WebView to be current with the BrowserBalloon's properties. This must be done after
// updating the render state; this balloon's active attributes are applied to the WebView. Re-use WebView state
// already calculated this frame.
if (dc.getFrameTimeStamp() != this.webViewTimeStamp)
{
this.updateWebView(dc, obb);
this.webViewTimeStamp = dc.getFrameTimeStamp();
}
if (this.intersectsFrustum(dc, obb))
{
dc.addOrderedRenderable(obb);
if (dc.isPickingMode())
this.screenBalloonPickFrame = dc.getFrameTimeStamp();
else
this.screenBalloonRenderFrame = dc.getFrameTimeStamp();
}
if (dc.isPickingMode())
this.pickLayer = dc.getCurrentLayer();
}
/**
* Update the balloon's active attributes and points, if that hasn't already been done this frame. This updates the
* balloon's rendering state as follows: - Computes the balloon's active attributes by calling
*
determineActiveAttributes
and stores the result in activeAttributes
. - Computes
* the balloon's model-coordinate and screen-coordinate points by calling
computeBalloonPoints
.
*
*
* @param dc the current draw context.
*/
protected void updateRenderStateIfNeeded(DrawContext dc, OrderedBrowserBalloon obb)
{
// Re-use rendering state values already calculated this frame.
if (dc.getFrameTimeStamp() != obb.frameTimeStamp)
{
this.updateRenderState(dc, obb);
obb.frameTimeStamp = dc.getFrameTimeStamp();
}
}
protected void updateRenderState(DrawContext dc, OrderedBrowserBalloon obb)
{
this.determineActiveAttributes();
if (this.getActiveAttributes() == null)
return;
this.determineWebViewContentSize();
this.computeBalloonPoints(dc, obb);
}
/**
* Computes the size of this balloon's frame in the viewport (on the screen). If this balloon's maximum size is not
* null
, the returned size is no larger than the maximum size.
*
* @param dc the current draw context.
* @param activeAttrs the attributes used to compute the balloon's size.
*
* @return this balloon frame's screen size, in pixels.
*/
protected Dimension computeSize(DrawContext dc, BalloonAttributes activeAttrs)
{
// Determine the balloon's current native size. If the WebView's content size is non-null and nonzero, then use
// that as the basis for the balloon's native size. Otherwise use a default native size. This handles the case
// where the balloon's size is computed either before the WebView is created or before the WebView's content
// size is known. The default native size is chosen to minimize the popping effect when the balloon's size
// switches from the default to the WebView's content size. If the balloon's size is not configured to use a
// native dimension, this size is ignored.
Dimension nativeSize;
if (this.webViewContentSize != null && this.webViewContentSize.width != 0
&& this.webViewContentSize.height != 0)
{
// Convert the WebView's content size to a balloon frame size.
nativeSize = this.computeFrameRectForWebViewRect(activeAttrs,
new Rectangle(this.webViewContentSize)).getSize();
}
else
{
nativeSize = DEFAULT_NATIVE_SIZE;
}
Dimension size = activeAttrs.getSize().compute(nativeSize.width, nativeSize.height,
dc.getView().getViewport().width, dc.getView().getViewport().height);
if (activeAttrs.getMaximumSize() != null)
{
Dimension maxSize = activeAttrs.getMaximumSize().compute(nativeSize.width, nativeSize.height,
dc.getView().getViewport().width, dc.getView().getViewport().height);
if (size.width > maxSize.width)
size.width = maxSize.width;
if (size.height > maxSize.height)
size.height = maxSize.height;
}
return size;
}
@SuppressWarnings({"UnusedDeclaration"})
protected Point computeOffset(DrawContext dc, BalloonAttributes activeAttrs, int width, int height)
{
Point2D.Double offset = activeAttrs.getOffset().computeOffset(width, height, 1d, 1d);
return new Point((int) offset.getX(), (int) offset.getY());
}
/**
* Indicates whether this balloon's screen-coordinate geometry must be recomputed as a result of a balloon attribute
* changing.
*
* @return true
if this balloon's geometry must be recomputed, otherwise false
.
*/
protected boolean mustRegenerateGeometry(OrderedBrowserBalloon obb)
{
if (obb.frameInfo == null)
return true;
if (!obb.screenRect.getSize().equals(obb.frameInfo.size) || !this.screenOffset.equals(obb.frameInfo.offset))
return true;
BalloonAttributes activeAttrs = this.getActiveAttributes();
return !activeAttrs.getBalloonShape().equals(obb.frameInfo.balloonShape)
|| !activeAttrs.getLeaderShape().equals(obb.frameInfo.leaderShape)
|| activeAttrs.getLeaderWidth() != obb.frameInfo.leaderWidth
|| activeAttrs.getCornerRadius() != obb.frameInfo.cornerRadius;
}
/**
* Updates the balloon's screen-coordinate geometry in frameInfo
according to the current screen
* bounds, screen offset, and active attributes.
*/
protected void computeGeometry(OrderedBrowserBalloon obb)
{
if (obb.screenRect == null)
return;
BalloonAttributes activeAttrs = this.getActiveAttributes();
if (obb.frameInfo == null)
obb.frameInfo = new FrameGeometryInfo();
// Regenerate the frame's vertex buffer.
obb.frameInfo.vertexBuffer = this.createFrameVertices(obb);
// Update the current attributes associated with FrameInfo's vertex buffer.
obb.frameInfo.size = obb.screenRect.getSize();
obb.frameInfo.offset = this.screenOffset;
obb.frameInfo.balloonShape = activeAttrs.getBalloonShape();
obb.frameInfo.leaderShape = activeAttrs.getLeaderShape();
obb.frameInfo.leaderWidth = activeAttrs.getLeaderWidth();
obb.frameInfo.cornerRadius = activeAttrs.getCornerRadius();
}
/**
* Creates the balloon's frame vertex buffer according to the active attributes.
*
* @return a buffer containing the frame's x and y locations.
*/
protected FloatBuffer createFrameVertices(OrderedBrowserBalloon obb)
{
BalloonAttributes activeAttrs = this.getActiveAttributes();
if (AVKey.SHAPE_NONE.equals(activeAttrs.getBalloonShape()))
return this.makeDefaultFrameVertices(obb);
else if (AVKey.SHAPE_ELLIPSE.equals(activeAttrs.getBalloonShape()))
return this.makeEllipseFrameVertices(obb);
else // Default to AVKey.SHAPE_RECTANGLE
return this.makeRectangleFrameVertices(obb);
}
protected FloatBuffer makeDefaultFrameVertices(OrderedBrowserBalloon obb)
{
BalloonAttributes activeAttrs = this.getActiveAttributes();
GeometryBuilder gb = new GeometryBuilder();
int x = obb.webViewRect.x - obb.screenRect.x;
int y = obb.webViewRect.y - obb.screenRect.y;
// Return a rectangle that represents the WebView's screen rectangle.
if (AVKey.SHAPE_TRIANGLE.equals(activeAttrs.getLeaderShape()))
{
// The balloon's leader location is equivalent to its screen offset because the screen offset specifies the
// location of the screen reference point relative to the frame, and the leader points from the frame to the
// screen reference point.
return gb.makeRectangleWithLeader(x, y, obb.webViewRect.width, obb.webViewRect.height,
this.screenOffset.x, this.screenOffset.y, activeAttrs.getLeaderWidth());
}
else // Default to AVKey.SHAPE_NONE
{
return gb.makeRectangle(x, y, obb.webViewRect.width, obb.webViewRect.height);
}
}
protected FloatBuffer makeEllipseFrameVertices(OrderedBrowserBalloon obb)
{
BalloonAttributes activeAttrs = this.getActiveAttributes();
GeometryBuilder gb = new GeometryBuilder();
int x = obb.screenRect.width / 2;
int y = obb.screenRect.height / 2;
int majorRadius = obb.screenRect.width / 2;
int minorRadius = obb.screenRect.height / 2;
// Return an ellipse centered at the balloon's center and with major and minor axes equal to the balloon's
// width and height, respectively. We use integer coordinates for the center and the radii to ensure that
// these vertices align image texels exactly with screen pixels when used as texture coordinates.
if (AVKey.SHAPE_TRIANGLE.equals(activeAttrs.getLeaderShape()))
{
// The balloon's leader location is equivalent to its screen offset because the screen offset specifies the
// location of the screen reference point relative to the frame, and the leader points from the frame to the
// screen reference point.
return gb.makeEllipseWithLeader(x, y, majorRadius, minorRadius, FRAME_GEOMETRY_ELLIPSE_SLICES,
this.screenOffset.x, this.screenOffset.y, activeAttrs.getLeaderWidth());
}
else // Default to AVKey.SHAPE_NONE
{
return gb.makeEllipse(x, y, majorRadius, minorRadius, FRAME_GEOMETRY_ELLIPSE_SLICES);
}
}
protected FloatBuffer makeRectangleFrameVertices(OrderedBrowserBalloon obb)
{
BalloonAttributes activeAttrs = this.getActiveAttributes();
GeometryBuilder gb = new GeometryBuilder();
// Return a rectangle that represents the balloon's screen rectangle, with optional rounded corners.
if (AVKey.SHAPE_TRIANGLE.equals(activeAttrs.getLeaderShape()))
{
// The balloon's leader location is equivalent to its screen offset because the screen offset specifies the
// location of the screen reference point relative to the frame, and the leader points from the frame to the
// screen reference point.
return gb.makeRectangleWithLeader(0, 0, obb.screenRect.width, obb.screenRect.height,
activeAttrs.getCornerRadius(), FRAME_GEOMETRY_RECTANGLE_CORNER_SLICES, this.screenOffset.x,
this.screenOffset.y, activeAttrs.getLeaderWidth());
}
else // Default to AVKey.SHAPE_NONE
{
return gb.makeRectangle(0, 0, obb.screenRect.width, obb.screenRect.height, activeAttrs.getCornerRadius(),
FRAME_GEOMETRY_RECTANGLE_CORNER_SLICES);
}
}
/**
* Determines whether the balloon intersects the view frustum.
*
* @param dc the current draw context.
*
* @return true
If the balloon intersects the frustum, otherwise false
.
*/
protected boolean intersectsFrustum(DrawContext dc, OrderedBrowserBalloon obb)
{
// During picking, use the balloon's pickable screen extent. This extent includes this balloon's outline where
// it exceeds the screen extent.
if (dc.isPickingMode())
return dc.getPickFrustums().intersectsAny(obb.screenPickExtent);
return dc.getView().getViewport().intersects(obb.screenExtent);
}
protected void drawOrderedRenderable(DrawContext dc, OrderedBrowserBalloon obb)
{
this.beginDrawing(dc);
try
{
this.doDrawOrderedRenderable(dc, obb);
}
finally
{
this.endDrawing(dc);
}
}
protected void beginDrawing(DrawContext dc)
{
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
int attrMask =
GL2.GL_COLOR_BUFFER_BIT // For alpha enable, blend enable, alpha func, blend func.
| GL2.GL_CURRENT_BIT // For current color
| GL2.GL_DEPTH_BUFFER_BIT // For depth test enable/disable, depth func, depth mask.
| GL2.GL_LINE_BIT // For line smooth enable, line stipple enable, line width, line stipple factor,
// line stipple pattern.
| GL2.GL_POLYGON_BIT // For polygon mode.
| GL2.GL_VIEWPORT_BIT; // For depth range.
this.osh.clear(); // Reset the stack handler's internal state.
this.osh.pushAttrib(gl, attrMask);
this.osh.pushClientAttrib(gl, GL2.GL_CLIENT_VERTEX_ARRAY_BIT); // For vertex array enable, pointers.
this.osh.pushProjectionIdentity(gl);
// The browser balloon is drawn using a parallel projection sized to fit the viewport.
gl.glOrtho(0d, dc.getView().getViewport().width, 0d, dc.getView().getViewport().height, -1d, 1d);
this.osh.pushTextureIdentity(gl);
this.osh.pushModelviewIdentity(gl);
gl.glEnableClientState(GL2.GL_VERTEX_ARRAY); // All drawing uses vertex arrays.
if (!dc.isPickingMode())
{
gl.glEnable(GL.GL_BLEND); // Enable interior and outline alpha blending when not picking.
OGLUtil.applyBlending(gl, false);
}
}
protected void endDrawing(DrawContext dc)
{
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
this.osh.pop(gl);
}
protected void doDrawOrderedRenderable(DrawContext dc, OrderedBrowserBalloon obb)
{
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
if (dc.isPickingMode())
{
// Set up the pick color used during interior and outline rendering.
Color pickColor = dc.getUniquePickColor();
this.pickSupport.addPickableObject(this.createPickedObject(dc, pickColor));
gl.glColor3ub((byte) pickColor.getRed(), (byte) pickColor.getGreen(), (byte) pickColor.getBlue());
}
// Translate to the balloon's screen origin. Use integer coordinates to ensure that the WebView texels are
// aligned exactly with screen pixels.
gl.glTranslatef(obb.screenRect.x, obb.screenRect.y, 0);
if (!dc.isDeepPickingEnabled())
this.setupDepthTest(dc, obb);
// Draw the balloon frame geometry. This draws the WebView as a texture applied to the balloon frame's interior.
this.drawFrame(dc, obb);
if (this.isDrawTitleBar(dc))
this.drawTitleBar(dc, obb);
if (this.isDrawResizeControl(dc))
this.drawResizeControl(dc, obb);
if (this.isDrawBrowserControls(dc))
this.drawBrowserControls(dc, obb);
// We draw the links last to ensure that their picked objects are on top. We do this to ensure that link picking
// is consistent with mouse events sent to the WebView. Currently, all select events that occur in this balloon
// are send to the WebView. We want link pick areas to be on top to ensure that the application has a chance to
// veto any link click select events before they are sent to the WebView.
if (this.isDrawLinks(dc))
this.drawLinks(dc, obb);
}
@SuppressWarnings({"UnusedDeclaration"})
protected PickedObject createPickedObject(DrawContext dc, Color pickColor)
{
PickedObject po = new PickedObject(pickColor.getRGB(),
this.getDelegateOwner() != null ? this.getDelegateOwner() : this);
// Attach the balloon to the picked object's AVList under the key HOT_SPOT. The application can then find that
// the balloon is a HotSpot by looking in the picked object's AVList. This is critical when the delegate owner
// is not null because the balloon is no longer the picked object. This would otherwise prevent the application
// from interacting with the balloon via the HotSpot interface.
po.setValue(AVKey.HOT_SPOT, this);
return po;
}
@SuppressWarnings({"UnusedDeclaration"})
protected PickedObject createLinkPickedObject(DrawContext dc, Color pickColor, AVList linkParams)
{
PickedObject po = new PickedObject(pickColor.getRGB(), this);
// Apply all of the link parameters to the picked object. This provides the application with the link's URL,
// mime type, and target.
po.setValues(linkParams);
// Attach the balloon's context to the picked object to provide context for link clicked events. This supports
// KML features that specify links with relative paths or fragments to the parent KML root.
po.setValue(AVKey.CONTEXT, this.getValue(AVKey.CONTEXT));
return po;
}
@SuppressWarnings({"UnusedDeclaration"})
protected boolean isDrawInterior(DrawContext dc)
{
return this.getActiveAttributes().isDrawInterior() && this.getActiveAttributes().getInteriorOpacity() > 0;
}
@SuppressWarnings({"UnusedDeclaration"})
protected boolean isDrawOutline(DrawContext dc)
{
return this.getActiveAttributes().isDrawOutline() && this.getActiveAttributes().getOutlineOpacity() > 0;
}
protected boolean isDrawTitleBar(DrawContext dc)
{
return this.isDrawTitleBar() && this.isDrawInterior(dc) && !dc.isPickingMode();
}
protected boolean isDrawResizeControl(DrawContext dc)
{
// There is no visible control so only proceed in picking mode.
return this.isDrawResizeControl() && (this.isDrawInterior(dc) || this.isDrawOutline(dc)) && dc.isPickingMode();
}
protected boolean isDrawBrowserControls(DrawContext dc)
{
return this.isDrawBrowserControls() && this.isDrawInterior(dc);
}
protected boolean isDrawLinks(DrawContext dc)
{
return this.isDrawInterior(dc) && dc.isPickingMode();
}
protected void drawFrame(DrawContext dc, OrderedBrowserBalloon obb)
{
if (obb.frameInfo.vertexBuffer == null) // This should never happen, but we check anyway.
return;
// Bind the balloon's vertex buffer as source of GL vertex coordinates. This buffer is used by both interior
// and outline rendering. We bind it once here to avoid loading the buffer twice.
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
gl.glVertexPointer(2, GL.GL_FLOAT, 0, obb.frameInfo.vertexBuffer);
if (this.isDrawInterior(dc))
{
this.prepareToDrawInterior(dc);
this.drawFrameInterior(dc, obb);
}
if (this.isDrawOutline(dc))
{
this.prepareToDrawOutline(dc);
this.drawFrameOutline(dc, obb);
}
}
/**
* Draws this browser balloon's interior geometry in screen-coordinates, with the WebView's
texture
* representation applied as an OpenGL decal. OpenGL's texture decal mode uses the texture color where the texture's
* alpha is 1, and uses the balloon's background color where it's 0. The texture's internal format must be RGBA to
* work correctly, and this assumes that the WebView's texture format is RGBA.
*
* If the WebView's
texture cannot be created or cannot be applied for any reason, this displays the
* balloon's interior geometry in the balloon's background color without applying the WebView's
* texture.
*
* If the specified draw context is in picking mode, this draws the balloon's interior geometry in the current color
* and does not apply the WebView's
texture.
*
* @param dc the current draw context.
*/
protected void drawFrameInterior(DrawContext dc, OrderedBrowserBalloon obb)
{
if (obb.frameInfo.vertexBuffer == null) // This should never happen, but we check anyway.
return;
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
boolean textureApplied = false;
try
{
// Bind the WebView's texture representation as the current texture source if we're in normal rendering
// mode. This also configures the texture matrix to transform texture coordinates from the balloon's vertex
// coordinates to the WebView's screen rectangle. For this reason we use the balloon's vertex coordinates as
// its texture coordinates.
if (!dc.isPickingMode() && this.bindWebViewTexture(dc, obb))
{
// The WebView's texture is successfully bound. Enable GL texturing and set up the texture
// environment to apply the texture in decal mode. Decal mode uses the texture color where the
// texture's alpha is 1, and uses the balloon's background color where it's 0. The texture's
// internal format must be RGBA to work correctly, and we assume that the WebView's texture format
// is RGBA.
gl.glEnable(GL.GL_TEXTURE_2D);
gl.glEnableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
gl.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_DECAL);
gl.glTexCoordPointer(2, GL.GL_FLOAT, 0, obb.frameInfo.vertexBuffer);
// Denote that the texture has been applied and that we need to restore the default texture state.
textureApplied = true;
}
// Draw the balloon's geometry as a triangle fan to display the interior. The balloon's vertices are
// represented by (x,y) pairs in screen coordinates. The number of vertices to draw is computed by dividing
// the number of coordinates by 2, because each vertex has exactly two coordinates: x and y.
gl.glDrawArrays(GL.GL_TRIANGLE_FAN, 0, obb.frameInfo.vertexBuffer.remaining() / 2);
}
finally
{
// Restore the previous texture state and client array state. We do this to avoid pushing and popping the
// texture attribute bit, which is expensive. We disable textures, disable texture coordinate arrays, bind
// texture id 0, set the default texture environment mode, and and set the texture coord pointer to 0.
if (textureApplied)
{
gl.glDisable(GL.GL_TEXTURE_2D);
gl.glDisableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
gl.glBindTexture(GL.GL_TEXTURE_2D, 0);
gl.glTexEnvf(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_MODULATE);
gl.glTexCoordPointer(2, GL.GL_FLOAT, 0, null);
}
}
}
protected void drawFrameOutline(DrawContext dc, OrderedBrowserBalloon obb)
{
if (obb.frameInfo.vertexBuffer == null) // This should never happen, but we check anyway.
return;
// Draw the balloon's geometry as a line loop to display the outline. The balloon's vertices are in screen
// coordinates.
dc.getGL().glDrawArrays(GL.GL_LINE_LOOP, 0, obb.frameInfo.vertexBuffer.remaining() / 2);
}
protected void prepareToDrawInterior(DrawContext dc)
{
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
if (!dc.isPickingMode())
{
// Apply the balloon's background color and opacity if we're in normal rendering mode.
Color color = this.getActiveAttributes().getInteriorMaterial().getDiffuse();
double opacity = this.getActiveAttributes().getInteriorOpacity();
gl.glColor4ub((byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue(),
(byte) (opacity < 1 ? (int) (opacity * 255 + 0.5) : 255));
}
}
protected void prepareToDrawOutline(DrawContext dc)
{
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
if (!dc.isPickingMode())
{
// Apply the balloon's outline color and opacity and apply the balloon's normal outline width if we're in
// normal rendering mode.
Color color = this.getActiveAttributes().getOutlineMaterial().getDiffuse();
double opacity = this.getActiveAttributes().getOutlineOpacity();
gl.glColor4ub((byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue(),
(byte) (opacity < 1 ? (int) (opacity * 255 + 0.5) : 255));
// Apply line smoothing if we're in normal rendering mode.
if (this.getActiveAttributes().isEnableAntialiasing())
{
gl.glEnable(GL.GL_LINE_SMOOTH);
}
if (this.getActiveAttributes().getOutlineStippleFactor() > 0)
{
gl.glEnable(GL2.GL_LINE_STIPPLE);
gl.glLineStipple(this.getActiveAttributes().getOutlineStippleFactor(),
this.getActiveAttributes().getOutlineStipplePattern());
}
}
// Apply the balloon's outline width. Use the outline pick width if we're in picking mode and the pick width is
// greater than the normal line width. Otherwise use the normal line width.
if (dc.isPickingMode())
gl.glLineWidth((float) this.computeOutlinePickWidth());
else
gl.glLineWidth((float) this.getActiveAttributes().getOutlineWidth());
}
protected Rectangle computeFramePickRect(Rectangle frameRect)
{
double outlinePickWidth = this.computeOutlinePickWidth();
return new Rectangle(
frameRect.x - (int) outlinePickWidth / 2,
frameRect.y - (int) outlinePickWidth / 2,
frameRect.width + (int) outlinePickWidth,
frameRect.height + (int) outlinePickWidth);
}
/**
* Computes the line width to use during picking (in pixels). Returns the larger of this balloon's outline width and
* its outlinePickWidth
.
*
* @return the line width to use during picking, in pixels.
*/
protected double computeOutlinePickWidth()
{
return Math.max(this.getActiveAttributes().getOutlineWidth(), this.getOutlinePickWidth());
}
protected void updateWebView(DrawContext dc, OrderedBrowserBalloon obb)
{
// Attempt to create the balloon's WebView.
if (this.webView == null)
{
this.makeWebView(dc, obb.webViewRect.getSize());
// Exit immediately if WebView creation failed.
if (this.webView == null)
return;
}
// The WebView's frame size and background color can change each frame. Synchronize the WebView's background
// color and frame size with the desired values before attempting to use the WebView's texture. The WebView
// avoids doing unnecessary work when the same frame size or background color is specified.
this.webView.setFrameSize(obb.webViewRect.getSize());
this.webView.setBackgroundColor(this.getActiveAttributes().getInteriorMaterial().getDiffuse());
// Update the WebView's text content each time the balloon's decoded string changes. We update the text even if
// the user has navigated to a page other than the balloon's text content. This ensures that any changes the
// application makes to the decoded text are reflected in the browser balloon's content. If we ignore those
// changes when the user navigates to another page, the application cannot retain control over the balloon's
// content.
if (this.getTextDecoder().getLastUpdateTime() != this.textUpdateTime)
{
this.setWebViewContent();
this.textUpdateTime = this.getTextDecoder().getLastUpdateTime();
}
}
protected void setWebViewContent()
{
String text = this.getTextDecoder().getDecodedText();
Object resourceResolver = this.getResourceResolver();
if (resourceResolver instanceof WebResourceResolver)
{
this.webView.setHTMLString(text, (WebResourceResolver) resourceResolver);
}
else if (resourceResolver instanceof URL)
{
this.webView.setHTMLString(text, (URL) resourceResolver);
}
else if (resourceResolver instanceof String)
{
// If the string is not a valid URL, then makeURL returns null and the WebView treats any relative paths as
// unresolved references.
URL url = WWIO.makeURL((String) resourceResolver);
if (url == null)
{
Logging.logger().warning(Logging.getMessage("generic.URIInvalid", resourceResolver));
}
this.webView.setHTMLString(text, url);
}
else
{
if (resourceResolver != null)
{
Logging.logger().warning(Logging.getMessage("generic.UnrecognizedResourceResolver", resourceResolver));
}
this.webView.setHTMLString(text);
}
}
protected void makeWebView(DrawContext dc, Dimension frameSize)
{
if (this.webView != null || this.webViewCreationFailed)
return;
try
{
// Attempt to get the WebViewFactory class name from configuration. Fall back on the BrowserBalloon's
// default factory if the configuration does not specify a one.
String className = Configuration.getStringValue(AVKey.WEB_VIEW_FACTORY, DEFAULT_WEB_VIEW_FACTORY);
WebViewFactory factory = (WebViewFactory) WorldWind.createComponent(className);
this.webView = factory.createWebView(frameSize);
}
catch (Throwable t)
{
String message = Logging.getMessage("WebView.ExceptionCreatingWebView", t);
Logging.logger().severe(message);
dc.addRenderingException(t);
// Set flag to prevent retrying the web view creation. We assume that if this fails once it will continue to
// fail.
this.webViewCreationFailed = true;
}
// Configure the balloon to forward the WebView's property change events to its listeners.
if (this.webView != null)
this.webView.addPropertyChangeListener(this);
}
protected void disposeWebView()
{
if (this.webView == null)
return;
this.webView.removePropertyChangeListener(this);
this.webView.dispose();
this.webView = null;
this.textUpdateTime = -1;
this.webViewContentSize = null;
}
protected void determineWebViewContentSize()
{
// Update the WebView's HTML content size when the WebView is non-null and has not navigated to a another page
// (indicated by a non-null URL). The latter case indicates that the WebView is not displaying this balloon's
// text. We avoid updating the content size in this case to ensure that the balloon's size always fits the
// balloon's text, and not content the user navigates to. Fitting the balloon's size to the WebView's current
// content causes the balloon to abruptly change size as the user navigates. Note that the content size may be
// null or (0, 0), indicating that the WebView does not know its content size. The balloon handles this by
// falling back to a default content size.
if (this.webView != null && this.webView.getContentURL() == null)
{
this.webViewContentSize = this.webView.getContentSize();
}
}
protected Rectangle computeWebViewRectForFrameRect(BalloonAttributes activeAttrs, Rectangle frameRect)
{
// Compute the WebView rectangle as an inset of the balloon's screen rectangle, given the current inset values.
Insets insets = activeAttrs.getInsets();
return new Rectangle(
frameRect.x + insets.left,
frameRect.y + insets.bottom,
frameRect.width - (insets.left + insets.right),
frameRect.height - (insets.bottom + insets.top));
}
protected Rectangle computeFrameRectForWebViewRect(BalloonAttributes activeAttrs, Rectangle webViewRect)
{
Insets insets = activeAttrs.getInsets();
return new Rectangle(
webViewRect.x - insets.left,
webViewRect.y - insets.bottom,
webViewRect.width + (insets.left + insets.right),
webViewRect.height + (insets.bottom + insets.top));
}
protected boolean bindWebViewTexture(DrawContext dc, OrderedBrowserBalloon obb)
{
if (this.webView == null)
return false;
WWTexture texture = this.webView.getTextureRepresentation(dc);
if (texture == null)
return false;
if (!texture.bind(dc))
return false;
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
// Set up the texture matrix to transform texture coordinates from the balloon's screen space vertex
// coordinates into WebView texture space. This places the WebView's texture in the WebView's screen
// rectangle. Use integer coordinates when possible to ensure that the image texels are aligned
// exactly with screen pixels. This transforms texture coordinates such that
// (webViewRect.getMinX(), webViewRect.getMinY()) maps to (0, 0) - the texture's lower left corner,
// and (webViewRect.getMaxX(), webViewRect.getMaxY()) maps to (1, 1) - the texture's upper right
// corner. Since texture coordinates are generated relative to the screenRect origin and webViewRect
// is in screen coordinates, we translate the texture coordinates by the offset from the screenRect
// origin to the webViewRect origin.
texture.applyInternalTransform(dc);
gl.glMatrixMode(GL2.GL_TEXTURE);
gl.glScalef(1f / obb.webViewRect.width, 1f / obb.webViewRect.height, 1f);
gl.glTranslatef(obb.screenRect.x - obb.webViewRect.x, obb.screenRect.y - obb.webViewRect.y, 0f);
// Restore the matrix mode.
gl.glMatrixMode(GL2.GL_MODELVIEW);
return true;
}
protected void drawWebViewLinks(DrawContext dc, OrderedBrowserBalloon obb)
{
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
if (this.webView == null)
return;
Iterable links = this.webView.getLinks();
if (links == null)
return;
for (AVList linkParams : links)
{
// This should never happen, but we check anyway.
if (linkParams == null)
continue;
// Ignore any links that have no bounds or no rectangles; they cannot be drawn.
if (linkParams.getValue(AVKey.BOUNDS) == null || linkParams.getValue(AVKey.RECTANGLES) == null)
continue;
// Translate the bounds from WebView coordinates to World Window screen coordinates.
Rectangle bounds = new Rectangle((Rectangle) linkParams.getValue(AVKey.BOUNDS));
bounds.translate(obb.webViewRect.x, obb.webViewRect.y);
// Ignore link rectangles that do not intersect any of the current pick rectangles.
if (!dc.getPickFrustums().intersectsAny(bounds))
continue;
Color pickColor = dc.getUniquePickColor();
gl.glColor3ub((byte) pickColor.getRed(), (byte) pickColor.getGreen(), (byte) pickColor.getBlue());
this.pickSupport.addPickableObject(this.createLinkPickedObject(dc, pickColor, linkParams));
int x = obb.webViewRect.x - obb.screenRect.x;
int y = obb.webViewRect.y - obb.screenRect.y;
gl.glBegin(GL2.GL_QUADS);
try
{
for (Rectangle rect : (Rectangle[]) linkParams.getValue(AVKey.RECTANGLES))
{
// This should never happen, but we check anyway.
if (rect == null)
continue;
gl.glVertex2i(x + rect.x, y + rect.y);
gl.glVertex2i(x + rect.x + rect.width, y + rect.y);
gl.glVertex2i(x + rect.x + rect.width, y + rect.y + rect.height);
gl.glVertex2i(x + rect.x, y + rect.y + rect.height);
}
}
finally
{
gl.glEnd();
}
}
}
/**
* Draw pickable regions for the resize controls. A pickable region is drawn along the frame outline.
*
* @param dc Draw context.
*/
protected void drawResizeControl(DrawContext dc, OrderedBrowserBalloon obb)
{
if (obb.frameInfo.vertexBuffer == null) // This should never happen, but we check anyway.
return;
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
// Compute the screen rectangle in AWT coordinates (origin top left).
Rectangle awtScreenRect = new Rectangle(obb.screenRect.x,
dc.getView().getViewport().height - obb.screenRect.y - obb.screenRect.height,
obb.screenRect.width, obb.screenRect.height);
Color color = dc.getUniquePickColor();
gl.glColor3ub((byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue());
// Set ACTION of PickedObject to RESIZE. Attach current bounds to the picked object so that the resize
// controller will have enough information to interpret mouse drag events.
PickedObject po = new PickedObject(color.getRGB(), this);
po.setValue(AVKey.ACTION, AVKey.RESIZE);
po.setValue(AVKey.BOUNDS, awtScreenRect);
this.pickSupport.addPickableObject(po);
gl.glLineWidth((float) this.computeOutlinePickWidth());
gl.glVertexPointer(2, GL.GL_FLOAT, 0, obb.frameInfo.vertexBuffer);
gl.glDrawArrays(GL.GL_LINE_LOOP, 0, obb.frameInfo.vertexBuffer.remaining() / 2);
}
protected void drawBrowserControls(DrawContext dc, OrderedBrowserBalloon obb)
{
for (BrowserControl control : this.getBrowserControls())
{
if (control == null) // This should never happen, but we check anyway.
continue;
if (!control.isVisible())
continue;
try
{
this.drawBrowserControl(dc, control, obb);
}
catch (Exception e)
{
Logging.logger().severe(Logging.getMessage("generic.ExceptionWhileRenderingBrowserControl", control));
}
}
}
protected void drawBrowserControl(DrawContext dc, BrowserControl control, OrderedBrowserBalloon obb)
{
WWTexture texture = control.getTexture();
if (texture == null)
return;
Point2D offset = control.getOffset().computeOffset(obb.screenRect.width, obb.screenRect.height, 1d, 1d);
Dimension size = control.getSize().compute(texture.getWidth(dc), texture.getHeight(dc),
obb.screenRect.width, obb.screenRect.height);
Rectangle rect = new Rectangle(obb.screenRect.x + (int) offset.getX(), obb.screenRect.y + (int) offset.getY(),
size.width, size.height);
if (rect.isEmpty())
return;
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
OGLStackHandler ogsh = new OGLStackHandler();
ogsh.pushTextureIdentity(gl);
ogsh.pushModelviewIdentity(gl);
try
{
gl.glTranslated(rect.x, rect.y, 0);
gl.glScaled(rect.width, rect.height, 1);
if (dc.isPickingMode())
{
Color pickColor = dc.getUniquePickColor();
gl.glColor3ub((byte) pickColor.getRed(), (byte) pickColor.getGreen(), (byte) pickColor.getBlue());
// Populate the picked object's AVList with all key-value pairs attached to the BrowserControl. This
// typically includes the ACTION key used to represent the action associated with the control. We attach
// the balloon as the picked object, because the application's balloon controller depends on the balloon
// to execute the appropriate action.
PickedObject po = new PickedObject(pickColor.getRGB(), this);
po.setValues(control);
this.pickSupport.addPickableObject(po);
dc.drawUnitQuad();
}
else
{
// Determine the control's active color: either the highlight color or the normal color, depending on
// whether a pick point is over the control.
Color color;
if (dc.getPickFrustums().intersectsAny(rect))
{
color = control.getHighlightColor() != null ? control.getHighlightColor()
: BrowserControl.DEFAULT_HIGHLIGHT_COLOR;
}
else
{
color = control.getColor() != null ? control.getColor() : BrowserControl.DEFAULT_COLOR;
}
float[] compArray = new float[4];
color.getRGBComponents(compArray);
// Multiply the color's opacity by the balloon's interior opacity so that controls maintain the same
// relative opacity to the balloon's interior.
float alpha = compArray[3] * (float) this.getActiveAttributes().getInteriorOpacity();
if (alpha > 1f)
alpha = 1f;
// Apply the control's color and enable blending in premultiplied alpha mode. We must enable use the
// correct blending function for premultiplied alpha colors, because textures loaded by JOGL contain
// premultiplied alpha colors.
OGLUtil.applyBlending(gl, true);
gl.glColor4f(compArray[0] * alpha, compArray[1] * alpha, compArray[2] * alpha, alpha);
gl.glEnable(GL.GL_TEXTURE_2D);
if (texture.bind(dc))
{
dc.drawUnitQuad(texture.getTexCoords());
}
}
}
finally
{
ogsh.pop(gl);
// Restore the previous texture state. We do this to avoid pushing and popping the texture attribute bit,
// which is expensive. We disable textures and bind texture id 0.
gl.glDisable(GL.GL_TEXTURE_2D);
gl.glBindTexture(GL.GL_TEXTURE_2D, 0);
}
}
protected void drawTitleBar(DrawContext dc, OrderedBrowserBalloon obb)
{
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
// Apply the balloon's outline color, but use the interior opacity.
Color color = this.getActiveAttributes().getOutlineMaterial().getDiffuse();
double opacity = this.getActiveAttributes().getInteriorOpacity();
gl.glColor4ub((byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue(),
(byte) (opacity < 1 ? (int) (opacity * 255 + 0.5) : 255));
// Disable line smoothing and specify a line with of 1.0. This ensures that the title separator line appears
// sharp and thin.
gl.glDisable(GL.GL_LINE_SMOOTH);
gl.glLineWidth(1f);
int x = obb.webViewRect.x - obb.screenRect.x;
int y = obb.webViewRect.y - obb.screenRect.y;
gl.glBegin(GL2.GL_LINES);
try
{
gl.glVertex2i(x, y + obb.webViewRect.height);
gl.glVertex2i(x + obb.webViewRect.width, y + obb.webViewRect.height);
}
finally
{
gl.glEnd();
}
}
protected void drawLinks(DrawContext dc, OrderedBrowserBalloon obb)
{
this.drawWebViewLinks(dc, obb);
}
/** {@inheritDoc} */
public void setActive(boolean active)
{
if (this.webView != null)
this.webView.setActive(active);
}
/** {@inheritDoc} */
public boolean isActive()
{
return (this.webView != null) && this.webView.isActive();
}
/**
* Forwards the MouseEvent
associated with the specified event
to the balloon's internal
* WebView
. This does not consume the event, because the {@link
* gov.nasa.worldwind.event.InputHandler}
implements the policy for consuming or forwarding mouse clicked
* events to other objects.
*
* @param event The event to handle.
*/
public void selected(SelectEvent event)
{
if (event == null || event.isConsumed())
return;
this.handleSelectEvent(event);
}
/**
* Forwards the key typed event to the balloon's internal {@link gov.nasa.worldwind.util.webview.WebView}
* and consumes the event. This consumes the event so the {@link gov.nasa.worldwind.View}
doesn't
* respond to it.
*
* @param event The event to forward.
*/
public void keyTyped(KeyEvent event)
{
if (event == null || event.isConsumed())
return;
this.handleKeyEvent(event);
event.consume(); // Consume the event so the View doesn't respond to it.
}
/**
* Forwards the key pressed event to the balloon's internal {@link gov.nasa.worldwind.util.webview.WebView}
* and consumes the event. This consumes the event so the {@link gov.nasa.worldwind.View}
doesn't
* respond to it. The
*
* @param event The event to forward.
*/
public void keyPressed(KeyEvent event)
{
if (event == null || event.isConsumed())
return;
this.handleKeyEvent(event);
event.consume(); // Consume the event so the View doesn't respond to it.
}
/**
* Forwards the key released event to the balloon's internal {@link gov.nasa.worldwind.util.webview.WebView}
* and consumes the event. This consumes the event so the {@link gov.nasa.worldwind.View}
doesn't
* respond to it.
*
* @param event The event to forward.
*/
public void keyReleased(KeyEvent event)
{
if (event == null || event.isConsumed())
return;
this.handleKeyEvent(event);
event.consume(); // Consume the event so the View doesn't respond to it.
}
/**
* Does nothing; BrowserBalloon handles mouse clicked events in selected
.
*
* @param event The event to handle.
*/
public void mouseClicked(MouseEvent event)
{
}
/**
* Does nothing; BrowserBalloon handles mouse pressed events in selected
.
*
* @param event The event to handle.
*/
public void mousePressed(MouseEvent event)
{
}
/**
* Does nothing; BrowserBalloon handles mouse released events in selected
.
*
* @param event The event to handle.
*/
public void mouseReleased(MouseEvent event)
{
}
/**
* Does nothing; BrowserBalloon does not handle mouse entered events.
*
* @param event The event to handle.
*/
public void mouseEntered(MouseEvent event)
{
}
/**
* Does nothing; BrowserBalloon does not handle mouse exited events.
*
* @param event The event to handle.
*/
public void mouseExited(MouseEvent event)
{
}
/**
* Does nothing; BrowserBalloon handles mouse dragged events in selected
.
*
* @param event The event to handle.
*/
public void mouseDragged(MouseEvent event)
{
}
/**
* Forwards the mouse moved event to the balloon's internal {@link gov.nasa.worldwind.util.webview.WebView}
.
* This does not consume the event, because the {@link gov.nasa.worldwind.event.InputHandler}
* implements the policy for consuming or forwarding mouse moved events to other objects.
*
* Unlike mouse clicked, mouse pressed, and mouse dragged events, mouse move events cannot be forwarded to the
* WebView via SelectEvents in selected
, because mouse movement events are not selection events.
*
* @param event The event to forward.
*/
public void mouseMoved(MouseEvent event)
{
if (event == null || event.isConsumed())
return;
this.handleMouseEvent(event);
}
/**
* Forwards the mouse wheel event to the balloon's internal {@link gov.nasa.worldwind.util.webview.WebView}
* and consumes the event. This consumes the event so the {@link gov.nasa.worldwind.View}
doesn't
* respond to it.
*
* Unlike mouse clicked, mouse pressed, and mouse dragged events, mouse wheel events cannot be forwarded to the
* WebView via SelectEvents in selected
, because mouse wheel events are not selection events.
*
* @param event The event to forward.
*/
public void mouseWheelMoved(MouseWheelEvent event)
{
if (event == null || event.isConsumed())
return;
this.handleMouseEvent(event);
event.consume(); // Consume the event so the View doesn't respond to it.
}
/**
* Returns a null
Cursor, indicating the default cursor should be used when the BrowserBalloon is
* active. The Cursor is set by the {@link gov.nasa.worldwind.util.webview.WebView}
in response to
* mouse moved events.
*
* @return A null
Cursor.
*/
public Cursor getCursor()
{
return null;
}
/**
* Sends the specified SelectEvent
to the balloon's internal WebView
. The event's
* pickPoint
is converted from AWT coordinates to the WebView's local coordinate system.
*
* @param event the event to send.
*/
protected void handleSelectEvent(SelectEvent event)
{
if (this.webView == null)
return;
// Ignore box selection events. These currently have no mapping to a WebView internal event.
if (event.isBoxSelect())
return;
// Convert the mouse event's screen point to the WebView's local coordinate system. Note that we send the mouse
// event to the WebView even when its screen point is outside the WebView's bounding rectangle. This gives the
// WebView a chance to change its state or the cursor's state when the cursor it exits the WebView.
Point pickPoint = event.getPickPoint();
// The SelectEvent's pick point is null if its a drag end event. In this case, use pick point of the last
// SelectEvent we received, which should be a drag event with a non-null pick point.
if (pickPoint == null)
pickPoint = this.lastPickPoint;
// If the last SelectEvent's pick point is null and the current SelectEvent's pick point is null, then we cannot
// send this event to the WebView.
if (pickPoint == null)
return;
Point webViewPoint = this.convertToWebView(event.getSource(), pickPoint);
if (event.isLeftPress() || event.isRightPress())
{
int modifiers = event.isLeftPress() ? MouseEvent.BUTTON1_DOWN_MASK : MouseEvent.BUTTON3_DOWN_MASK;
this.webView.sendEvent(
new MouseEvent((Component) event.getSource(), MouseEvent.MOUSE_PRESSED,
System.currentTimeMillis(), modifiers, // when, modifiers.
webViewPoint.x, webViewPoint.y, 0, // x, y, clickCount.
event.isRightPress(), // isPopupTrigger.
event.isRightPress() ? MouseEvent.BUTTON3 : MouseEvent.BUTTON1));
}
else if (event.isLeftClick() || event.isRightClick() || event.isLeftDoubleClick())
{
int clickCount = event.isLeftDoubleClick() ? 2 : 1;
int modifiers = (event.isLeftClick() || event.isLeftDoubleClick()) ? MouseEvent.BUTTON1_DOWN_MASK
: MouseEvent.BUTTON3_DOWN_MASK;
this.webView.sendEvent(
new MouseEvent((Component) event.getSource(), MouseEvent.MOUSE_RELEASED,
System.currentTimeMillis(), 0, // when, modifiers.
webViewPoint.x, webViewPoint.y, clickCount, // x, y, clickCount.
false, // isPopupTrigger.
event.isRightClick() ? MouseEvent.BUTTON3 : MouseEvent.BUTTON1));
this.webView.sendEvent(
new MouseEvent((Component) event.getSource(), MouseEvent.MOUSE_CLICKED,
System.currentTimeMillis(), modifiers, // when, modifiers.
webViewPoint.x, webViewPoint.y, clickCount, // x, y, clickCount
false, // isPopupTrigger.
event.isRightClick() ? MouseEvent.BUTTON3 : MouseEvent.BUTTON1));
}
else if (event.isDrag())
{
this.webView.sendEvent(
new MouseEvent((Component) event.getSource(), MouseEvent.MOUSE_DRAGGED,
System.currentTimeMillis(), MouseEvent.BUTTON1_DOWN_MASK, // when, modifiers.
webViewPoint.x, webViewPoint.y, 0, // x, y, clickCount.
false, // isPopupTrigger.
MouseEvent.BUTTON1));
}
else if (event.isDragEnd())
{
this.webView.sendEvent(
new MouseEvent((Component) event.getSource(), MouseEvent.MOUSE_RELEASED,
System.currentTimeMillis(), 0, // when, modifiers.
webViewPoint.x, webViewPoint.y, 0, // x, y, clickCount.
false, // isPopupTrigger.
MouseEvent.BUTTON1));
}
this.lastPickPoint = event.getPickPoint();
// Consume the SelectEvent now that it has been passed on to the WebView as a mouse event. We avoid consuming
// left press events, since doing so prevents the WorldWindow from gaining focus.
if (!event.isLeftPress())
event.consume();
}
/**
* Sends the specified KeyEvent
to the balloon's internal WebView
.
*
* This does nothing if the balloon's internal WebView
is uninitialized.
*
* @param event the event to send.
*/
protected void handleKeyEvent(KeyEvent event)
{
if (this.webView != null)
this.webView.sendEvent(event);
}
/**
* Sends the specified MouseEvent
to the balloon's internal WebView
. The event's point is
* converted from AWT coordinates to the WebView's local coordinate system.
*
* This does nothing if the balloon's internal WebView
is uninitialized.
*
* @param event the event to send.
*/
protected void handleMouseEvent(MouseEvent event)
{
if (this.webView == null)
return;
// Convert the mouse event's screen point to the WebView's local coordinate system. Note that we send the mouse
// event to the WebView even when its screen point is outside the WebView's bounding rectangle. This gives the
// WebView a chance to change its state or the cursor's state when the cursor it exits the WebView.
Point webViewPoint = this.convertToWebView(event.getSource(), event.getPoint());
// Send a copy of the mouse event using the point in the WebView's local coordinate system.
if (event instanceof MouseWheelEvent)
{
this.webView.sendEvent(
new MouseWheelEvent((Component) event.getSource(), event.getID(), event.getWhen(), event.getModifiers(),
webViewPoint.x, webViewPoint.y, event.getClickCount(), event.isPopupTrigger(),
((MouseWheelEvent) event).getScrollType(), ((MouseWheelEvent) event).getScrollAmount(),
((MouseWheelEvent) event).getWheelRotation()));
}
else
{
this.webView.sendEvent(
new MouseEvent((Component) event.getSource(), event.getID(), event.getWhen(), event.getModifiers(),
webViewPoint.x, webViewPoint.y, event.getClickCount(), event.isPopupTrigger(), event.getButton()));
}
}
/**
* Converts the specified screen point from AWT coordinates to local WebView coordinates.
*
* @param point The point to convert.
* @param context the component who's coordinate system the point is in.
*
* @return A new Point
in the WebView's local coordinate system.
*/
protected Point convertToWebView(Object context, Point point)
{
int x = point.x;
int y = point.y;
// Translate AWT coordinates to OpenGL screen coordinates by moving the Y origin from the upper left corner to
// the lower left corner and flipping the direction of the Y axis.
if (context instanceof Component)
y = ((Component) context).getHeight() - point.y;
// Find the ordered renderable that contains the point.
Rectangle rect = null;
for (OrderedBrowserBalloon obb : this.orderedRenderables.values())
{
rect = obb.webViewRect;
if (x >= rect.x && x <= rect.x && y >= rect.y && y <= rect.y)
break;
}
if (rect != null)
{
x -= rect.x;
y -= rect.y;
}
return new Point(x, y);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy