src.gov.nasa.worldwind.render.AbstractBrowserBalloon Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of worldwindx Show documentation
Show all versions of worldwindx Show documentation
World Wind is a collection of components that interactively display 3D geographic information within Java applications or applets.
/*
* 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.layers.Layer;
import gov.nasa.worldwind.pick.*;
import gov.nasa.worldwind.util.*;
import gov.nasa.worldwind.util.webview.*;
import javax.media.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 1171 2013-02-11 21:45:02Z dcollins $
*/
public abstract class AbstractBrowserBalloon extends AbstractBalloon implements OrderedRenderable, 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()
{
}
}
/**
* 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 calculate the balloon's active attributes and points. */
protected long frameTimeStamp = -1;
/** Identifies the frame used to calculate the balloon's geometry. */
protected long geomTimeStamp = -1;
/** 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 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 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;
/** Used to order the balloon as an ordered renderable. */
protected double eyeDistance;
/** The balloon geometry vertices passed to OpenGL. */
protected FrameGeometryInfo frameInfo;
/** 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 AbstractBrowserBalloon(String text)
{
super(text);
}
/**
* Computes and stores the balloon's model-coordinate and screen-coordinate points.
*
* @param dc the current draw context.
*/
protected abstract void computeBalloonPoints(DrawContext dc);
protected abstract void setupDepthTest(DrawContext dc);
/**
* 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 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);
}
// Update the balloon's active attributes and points if that hasn't already been done this frame.
this.updateRenderStateIfNeeded(dc);
// Return the balloon's screen extent computed in updateRenderStateIfNeeded. This may be null.
return this.screenExtent;
}
public double getDistanceFromEye()
{
return this.eyeDistance;
}
public void pick(DrawContext dc, Point pickPoint)
{
// This method is called only when ordered renderables are being drawn.
// Arg checked within call to render.
if (!this.isPickEnabled())
return;
this.pickSupport.clearPickList();
try
{
this.pickSupport.beginPicking(dc);
this.render(dc);
}
finally
{
this.pickSupport.endPicking(dc);
this.pickSupport.resolvePick(dc, pickPoint, this.pickLayer);
}
}
public void render(DrawContext dc)
{
// This render method is called three times during frame generation. It's first called as a Renderable during
// picking. It's called again during normal rendering. And it's called a third time as an OrderedRenderable. The
// first two calls determine whether to add the placemark and its optional line to the ordered renderable list
// during pick and render. The third call just draws the ordered renderable.
if (dc == null)
{
String message = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (!this.isVisible())
return;
if (dc.isOrderedRenderingMode())
this.drawOrderedRenderable(dc);
else
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)
{
// Update the balloon's active attributes and points if that hasn't already been done this frame.
this.updateRenderStateIfNeeded(dc);
// 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 || this.screenRect == null)
return;
// Re-use geometry already calculated this frame.
if (dc.getFrameTimeStamp() != this.geomTimeStamp)
{
// Recompute this balloon's geometry only when an attribute change requires us to.
if (this.mustRegenerateGeometry())
this.computeGeometry();
this.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);
this.webViewTimeStamp = dc.getFrameTimeStamp();
}
if (this.intersectsFrustum(dc))
dc.addOrderedRenderable(this);
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)
{
// Re-use rendering state values already calculated this frame.
if (dc.getFrameTimeStamp() != this.frameTimeStamp)
{
this.updateRenderState(dc);
this.frameTimeStamp = dc.getFrameTimeStamp();
}
}
protected void updateRenderState(DrawContext dc)
{
this.determineActiveAttributes();
if (this.getActiveAttributes() == null)
return;
this.determineWebViewContentSize();
this.computeBalloonPoints(dc);
}
/**
* 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()
{
if (this.frameInfo == null)
return true;
if (!this.screenRect.getSize().equals(this.frameInfo.size) || !this.screenOffset.equals(this.frameInfo.offset))
return true;
BalloonAttributes activeAttrs = this.getActiveAttributes();
return !activeAttrs.getBalloonShape().equals(this.frameInfo.balloonShape)
|| !activeAttrs.getLeaderShape().equals(this.frameInfo.leaderShape)
|| activeAttrs.getLeaderWidth() != this.frameInfo.leaderWidth
|| activeAttrs.getCornerRadius() != this.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()
{
if (this.screenRect == null)
return;
BalloonAttributes activeAttrs = this.getActiveAttributes();
if (this.frameInfo == null)
this.frameInfo = new FrameGeometryInfo();
// Regenerate the frame's vertex buffer.
this.frameInfo.vertexBuffer = this.createFrameVertices();
// Update the current attributes associated with FrameInfo's vertex buffer.
this.frameInfo.size = this.screenRect.getSize();
this.frameInfo.offset = this.screenOffset;
this.frameInfo.balloonShape = activeAttrs.getBalloonShape();
this.frameInfo.leaderShape = activeAttrs.getLeaderShape();
this.frameInfo.leaderWidth = activeAttrs.getLeaderWidth();
this.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()
{
BalloonAttributes activeAttrs = this.getActiveAttributes();
if (AVKey.SHAPE_NONE.equals(activeAttrs.getBalloonShape()))
return this.makeDefaultFrameVertices();
else if (AVKey.SHAPE_ELLIPSE.equals(activeAttrs.getBalloonShape()))
return this.makeEllipseFrameVertices();
else // Default to AVKey.SHAPE_RECTANGLE
return this.makeRectangleFrameVertices();
}
protected FloatBuffer makeDefaultFrameVertices()
{
BalloonAttributes activeAttrs = this.getActiveAttributes();
GeometryBuilder gb = new GeometryBuilder();
int x = this.webViewRect.x - this.screenRect.x;
int y = this.webViewRect.y - this.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, this.webViewRect.width, this.webViewRect.height,
this.screenOffset.x, this.screenOffset.y, activeAttrs.getLeaderWidth());
}
else // Default to AVKey.SHAPE_NONE
{
return gb.makeRectangle(x, y, this.webViewRect.width, this.webViewRect.height);
}
}
protected FloatBuffer makeEllipseFrameVertices()
{
BalloonAttributes activeAttrs = this.getActiveAttributes();
GeometryBuilder gb = new GeometryBuilder();
int x = this.screenRect.width / 2;
int y = this.screenRect.height / 2;
int majorRadius = this.screenRect.width / 2;
int minorRadius = this.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()
{
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, this.screenRect.width, this.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, this.screenRect.width, this.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)
{
// 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(this.screenPickExtent);
return dc.getView().getViewport().intersects(this.screenExtent);
}
protected void drawOrderedRenderable(DrawContext dc)
{
this.beginDrawing(dc);
try
{
this.doDrawOrderedRenderable(dc);
}
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)
{
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(this.screenRect.x, this.screenRect.y, 0);
if (!dc.isDeepPickingEnabled())
this.setupDepthTest(dc);
// Draw the balloon frame geometry. This draws the WebView as a texture applied to the balloon frame's interior.
this.drawFrame(dc);
if (this.isDrawTitleBar(dc))
this.drawTitleBar(dc);
if (this.isDrawResizeControl(dc))
this.drawResizeControl(dc);
if (this.isDrawBrowserControls(dc))
this.drawBrowserControls(dc);
// 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);
}
@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)
{
if (this.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, this.frameInfo.vertexBuffer);
if (this.isDrawInterior(dc))
{
this.prepareToDrawInterior(dc);
this.drawFrameInterior(dc);
}
if (this.isDrawOutline(dc))
{
this.prepareToDrawOutline(dc);
this.drawFrameOutline(dc);
}
}
/**
* 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)
{
if (this.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))
{
// 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, this.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, this.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)
{
if (this.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, this.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)
{
// Attempt to create the balloon's WebView.
if (this.webView == null)
{
this.makeWebView(dc, this.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(this.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)
{
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 / this.webViewRect.width, 1f / this.webViewRect.height, 1f);
gl.glTranslatef(this.screenRect.x - this.webViewRect.x, this.screenRect.y - this.webViewRect.y, 0f);
// Restore the matrix mode.
gl.glMatrixMode(GL2.GL_MODELVIEW);
return true;
}
protected void drawWebViewLinks(DrawContext dc)
{
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(this.webViewRect.x, this.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 = this.webViewRect.x - this.screenRect.x;
int y = this.webViewRect.y - this.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)
{
if (this.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(this.screenRect.x,
dc.getView().getViewport().height - this.screenRect.y - this.screenRect.height,
this.screenRect.width, this.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, this.frameInfo.vertexBuffer);
gl.glDrawArrays(GL.GL_LINE_LOOP, 0, this.frameInfo.vertexBuffer.remaining() / 2);
}
protected void drawBrowserControls(DrawContext dc)
{
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);
}
catch (Exception e)
{
Logging.logger().severe(Logging.getMessage("generic.ExceptionWhileRenderingBrowserControl", control));
}
}
}
protected void drawBrowserControl(DrawContext dc, BrowserControl control)
{
WWTexture texture = control.getTexture();
if (texture == null)
return;
Point2D offset = control.getOffset().computeOffset(this.screenRect.width, this.screenRect.height, 1d, 1d);
Dimension size = control.getSize().compute(texture.getWidth(dc), texture.getHeight(dc),
this.screenRect.width, this.screenRect.height);
Rectangle rect = new Rectangle(this.screenRect.x + (int) offset.getX(), this.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)
{
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 = this.webViewRect.x - this.screenRect.x;
int y = this.webViewRect.y - this.screenRect.y;
gl.glBegin(GL2.GL_LINES);
try
{
gl.glVertex2i(x, y + this.webViewRect.height);
gl.glVertex2i(x + this.webViewRect.width, y + this.webViewRect.height);
}
finally
{
gl.glEnd();
}
}
protected void drawLinks(DrawContext dc)
{
this.drawWebViewLinks(dc);
}
/** {@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;
x -= this.webViewRect.x;
y -= this.webViewRect.y;
return new Point(x, y);
}
}