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

src.gov.nasa.worldwind.util.tree.ScrollFrame Maven / Gradle / Ivy

Go to download

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

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

package gov.nasa.worldwind.util.tree;

import com.jogamp.opengl.util.awt.TextRenderer;
import com.jogamp.opengl.util.texture.*;
import gov.nasa.worldwind.*;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.event.SelectEvent;
import gov.nasa.worldwind.pick.PickSupport;
import gov.nasa.worldwind.render.*;
import gov.nasa.worldwind.util.*;

import javax.media.opengl.*;
import javax.media.opengl.glu.GLU;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.net.URL;
import java.nio.DoubleBuffer;
import java.util.*;
import java.util.List;

/**
 * A frame that can scroll its contents. The frame can be interactively resized by dragging the border, and be moved by
 * dragging the frame or title bar. The frame can be minimized. The frame displays scroll bars if the size of the
 * content exceeds the size of the frame, and optionally displays a title bar with a text string and an icon.
 * 

* The frame renders its contents into a texture, and then draws the texture when the frame is rendered. This provides * good performance for content that is expensive to draw, and changes infrequently. If the frame is sized so large that * the visible portion of the contents cannot be rendered into a single texture, then the contents will be drawn * directly when the frame is rendered. * * @author pabercrombie * @version $Id: ScrollFrame.java 1171 2013-02-11 21:45:02Z dcollins $ */ public class ScrollFrame extends DragControl implements PreRenderable, Renderable { /** Default dimension of tiles in the backing texture. */ protected static final int DEFAULT_TEXTURE_TILE_DIMENSION = 512; /** Default height of the frame title bar. */ protected static final int DEFAULT_TITLE_BAR_HEIGHT = 25; /** Default size of the minimize button. */ protected static final int DEFAULT_BUTTON_SIZE = 18; /** Default width of the scroll bars. */ protected static final int DEFAULT_SCROLL_BAR_SIZE = 15; /** Default width of the frame border. */ protected static final int DEFAULT_FRAME_BORDER_WIDTH = 3; /** Default width of the pickable frame border. */ protected static final int DEFAULT_FRAME_BORDER_PICK_WIDTH = 10; /** Default width of lines used to draw the frame. */ protected static final int DEFAULT_LINE_WIDTH = 1; /** Default delay (in milliseconds) between frame when the frame is animating. */ protected static final int DEFAULT_ANIMATION_DELAY = 5; /** Default size of the maximized frame. */ protected static final Size DEFAULT_MAXIMIZED_SIZE = Size.fromPixels(265, 300); /** Attributes to use when the frame is not highlighted. */ protected FrameAttributes normalAttributes; /** Attributes to use when the frame is highlighted. */ protected FrameAttributes highlightAttributes; /** Active attributes, either normal or highlight. */ protected FrameAttributes activeAttributes = new BasicFrameAttributes(); // re-determined each frame /** * The full frame title. This title may be displayed in a truncated form if the frame is too small to accommodate * the full title. */ protected String frameTitle; /** The contents of the frame. */ protected Scrollable contents; /** Indicates the location of the upper left corner of the frame. */ protected Offset screenLocation; /** Indicates whether or not to draw a title bar in the frame. Default is true. */ protected boolean drawTitleBar = true; /** Indicates whether or not the user can resize the frame by dragging the edge. Default is true. */ protected boolean enableResize = true; /** Indicates whether or not the user can move the frame by dragging with the mouse. Default is true. */ protected boolean enableMove = true; /** The height, in pixels, of the frame title bar. */ protected int titleBarHeight = DEFAULT_TITLE_BAR_HEIGHT; /** The size, in pixels, of the frame's minimize button. */ protected int buttonSize = DEFAULT_BUTTON_SIZE; /** The width of the frame scroll bar. */ protected int scrollBarSize = DEFAULT_SCROLL_BAR_SIZE; /** The width of the frame border. */ protected int frameBorder = DEFAULT_FRAME_BORDER_WIDTH; /** The width of lines used to draw the frame. */ protected int frameLineWidth = DEFAULT_LINE_WIDTH; /** Support for setting up and restoring OpenGL state during rendering. */ protected OGLStackHandler BEogsh = new OGLStackHandler(); /** Support for setting up and restoring picking state, and resolving the picked object. */ protected PickSupport pickSupport = new PickSupport(); /** Scroll bar to control vertical scrolling. */ protected ScrollBar verticalScrollBar; /** Scroll bar to control horizontal scrolling. */ protected ScrollBar horizontalScrollBar; /** Indicates whether or not the frame is minimized. */ protected boolean minimized = false; /** The size of the maximized frame. */ protected Size maximizedSize = DEFAULT_MAXIMIZED_SIZE; /** The size of the minimized frame. */ protected Size minimizedSize; /** The size of the active frame, minimized or maximized. */ protected Size activeSize; /** The maximum size of the frame. This is a constraint applied to the frame's size. */ protected Size maxSize; /** Image source for the icon drawn in the upper left corner of the frame. */ protected Object iconImageSource; /** Texture for the icon displayed in the frame title bar. Loaded from {@link #iconImageSource}. */ protected BasicWWTexture texture; /** An animation to play when the frame is minimized or maximized. */ protected Animation minimizeAnimation; /** The active animation that is currently playing. */ protected Animation animation; /** Delay in milliseconds between frames of an animation. */ protected int animationDelay = DEFAULT_ANIMATION_DELAY; // UI controls /** HotSpot to handle user input on the minimize button. */ protected HotSpot minimizeButton; /** Control to handle resizing the frame. */ protected FrameResizeControl frameResizeControl; /** Width of the pickable frame border. */ protected int borderPickWidth = DEFAULT_FRAME_BORDER_PICK_WIDTH; /** The frame geometry vertices passed to OpenGL. */ protected DoubleBuffer vertexBuffer; /** Support class used to render to an offscreen texture. */ protected OGLRenderToTextureSupport rttSupport = new OGLRenderToTextureSupport(); protected List tiles = new ArrayList(); /** * Indicates whether the contents should be rendered into a texture and cached, or rendered directly. The frame * renders into a texture if the frame size can be accommodated by a single texture. If the size is too large to * render using a single texture, the contents are drawn directly. */ protected boolean renderToTexture; /** Cache key used to locate the rendering texture in the DrawContext's texture cache. */ protected final Object textureCacheKey = new Object(); /** * Map that associates logical tiles in the scrollable content with allocated tiles in the texture used to render * the content. */ protected Map textureTileMap = new HashMap(); /** List to manage sub-tiles of the rendering texture. */ protected List textureTiles = new ArrayList(); /** Dimension of the texture used to render the scrollable content. Must be a power of two. */ protected int textureDimension; /** * Dimension of a sub-tile in the rendering texture. Also the dimension of logical tiles in the scrollable content * that are rendered into the texture tiles. */ protected int textureTileDimension = DEFAULT_TEXTURE_TILE_DIMENSION; // Frame title fields. These fields are recomputed when the frame size changes, the title text changes, or the // title font changes. /** * Frame title that is actually drawn in the title bar. If the frame is too narrow to display the entire title, the * title will be truncated. This title must be regenerated if the frame size or the title font change. */ protected String shortTitle; /** Width of the frame title area. */ protected int frameTitleWidth; /** Font used to generate {@link #shortTitle}. */ protected Font shortFrameTitleFont; /** Text bounds of the {@link #shortTitle}. */ protected Rectangle2D shortTitleBounds; // Computed each frame protected long frameNumber = -1; /** Indicates that the frame must be regenerated because the size or attributes have changed. */ protected boolean mustRecomputeFrameGeometry = true; /** * Indicates the location of the upper left corner of the frame, in AWT coordinates (origin at the upper left corner * of the screen. */ protected Point2D awtScreenPoint; /** Bounds of the full frame. */ protected Rectangle frameBounds; /** Bounds of the frame inside the frame border. */ protected Rectangle innerBounds; /** Bounds of the content part of the frame. */ protected Rectangle contentBounds; protected Rectangle scrollContentBounds; /** Bounds of the pickable area. */ protected Rectangle pickBounds; /** * Total size of the frame content. This size may exceed the size of the frame, in which case scroll bars will be * displayed. */ protected Dimension contentSize; /** Size of the frame. */ protected Dimension frameSize; /** Indicates whether or not the frame is highlighted. */ protected boolean highlighted; /** Indicates whether or not the vertical scroll bar must be drawn. */ protected boolean showVerticalScrollbar; /** Indicates whether or not the horizontal scroll bar must be drawn. */ protected boolean showHorizontalScrollbar; /** The attributes used if attributes are not specified. */ protected static final FrameAttributes defaultAttributes; static { defaultAttributes = new BasicFrameAttributes(); } /** Create a new scroll frame. */ public ScrollFrame() { super(null); this.initializeUIControls(); } /** * Create a scroll frame with a position. * * @param x x coordinate of the upper left corner of the frame, in AWT screen coordinates (origin upper left corner * of the screen). * @param y y coordinate of the upper left corner of the frame, in AWT screen coordinates (origin upper left corner * of the screen). */ public ScrollFrame(int x, int y) { this(new Offset((double) x, (double) y, AVKey.PIXELS, AVKey.INSET_PIXELS)); } /** * Create a scroll positioned with an Offset. * * @param screenLocation initial location of the upper left corner of the frame. */ public ScrollFrame(Offset screenLocation) { super(null); this.setScreenLocation(screenLocation); this.initializeUIControls(); } /** * Indicates the frame contents. * * @return the contents of the frame. */ public Scrollable getContents() { return contents; } /** * Specifies the frame contents. * * @param contents new frame contents. */ public void setContents(Scrollable contents) { this.contents = contents; } /** * Indicates if the frame is minimized. If the frame is not minimized, it is either maximized or animating toward a * maximized state. * * @return {@code true} if the frame is minimized. */ public boolean isMinimized() { return this.minimized; } /** * Sets the frame to its minimized or maximized state. If the frame is not already in the desired state it will * animate to the desired state. * * @param minimized {@code true} if the frame must be minimized. {@code false} if the frame must not be minimized. */ public void setMinimized(boolean minimized) { if (minimized != this.isMinimized()) { this.minimized = minimized; if (this.minimizeAnimation != null) { this.animation = this.minimizeAnimation; this.animation.reset(); } } } /** * Indicates whether or not the frame is highlighted. * * @return {@code true} if the frame is highlighted, otherwise {@code false}. */ public boolean isHighlighted() { return this.highlighted; } /** * Sets the frame its highlighted or not highlighted state. * * @param highlighted {@code true} if the frame is now highlighted. */ public void setHighlighted(boolean highlighted) { if (this.highlighted != highlighted) { this.highlighted = highlighted; this.contents.setHighlighted(highlighted); } } /** * Get the title of the tree frame. * * @return The frame title. * * @see #setFrameTitle(String) */ public String getFrameTitle() { return this.frameTitle; } /** * Set the title of the tree frame. * * @param frameTitle New frame title. * * @see #getFrameTitle() */ public void setFrameTitle(String frameTitle) { this.frameTitle = frameTitle; // Invalidate the computed title for display. It will be regenerated the next time the frame is rendered. this.shortTitle = null; } /** * Get the size of tree frame. The size determines the size of the frame when the frame is maximized. * * @return the size of the tree frame. * * @see #getMinimizedSize() */ public Size getSize() { return this.maximizedSize; } /** * Set the size of the frame. The size determines the size of the frame when the frame is maximized. * * @param size New size. * * @see #setMinimizedSize(gov.nasa.worldwind.render.Size) */ public void setSize(Size size) { if (size == null) { String message = Logging.getMessage("nullValue.SizeIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.maximizedSize = size; this.mustRecomputeFrameGeometry = true; if (!this.isAnimating()) this.forceTileUpdate(); } /** * Indicates the size of the minimized tree frame. This size is used when the tree is minimized or animating. * * @return the size of the minimized frame. {@code null} indicates that there is no minimized size, in which case * the normal maximized frame size is used in the minimized state. */ public Size getMinimizedSize() { return this.minimizedSize; } /** * Specifies the size of the minimized tree frame. This size is used when the tree is minimized or animating. An * animation can use this field to manipulate the size of the frame as the animation runs without interfering with * the frame's normal maximized size. * * @param size the size of the minimized frame. Set {@code null} to use the same size in maximized and minimized * states. */ public void setMinimizedSize(Size size) { this.minimizedSize = size; this.mustRecomputeFrameGeometry = true; if (!this.isAnimating()) this.forceTileUpdate(); } /** * Indicates the maximum size of frame. This size is applied as a constraint to the tree's normal size (indicated by * {@link #getSize()}). * * @return the maximum size of the frame. {@code null} indicates no maximum. */ public Size getMaxSize() { return this.maxSize; } /** * Specifies the maximum size of the frame. This size is applied as a constraint to the tree's normal size * (indicated by {@link #getSize()}). * * @param size the maximum size of the minimized frame. Set {@code null} for no maximum. */ public void setMaxSize(Size size) { this.maxSize = size; this.mustRecomputeFrameGeometry = true; if (!this.isAnimating()) this.forceTileUpdate(); } /** * Return the amount of screen that space that the frame is currently using. The frame size may change due to a * window resize, or an animation. * * @return The size of the frame on screen, in pixels. This method will return null until the frame has been * rendered at least once. */ public Dimension getCurrentSize() { return this.frameSize; } /** * Indicates the height, in pixels, of the frame title bar. * * @return height of the title bar. * * @see #isDrawTitleBar() */ public int getTitleBarHeight() { return this.titleBarHeight; } /** * Specifies the height, in pixels, of the frame title bar. * * @param titleBarHeight new height, in pixels. */ public void setTitleBarHeight(int titleBarHeight) { if (titleBarHeight < 0) { String message = Logging.getMessage("generic.InvalidHeight", titleBarHeight); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.titleBarHeight = titleBarHeight; } /** * Does the frame have a title bar? * * @return True if the frame will draw a title bar. */ public boolean isDrawTitleBar() { return this.drawTitleBar; } /** * Set whether the frame has a title bar. * * @param drawTitleBar True if the frame will draw a title bar. * * @see #setTitleBarHeight(int) */ public void setDrawTitleBar(boolean drawTitleBar) { this.drawTitleBar = drawTitleBar; } /** * Indicates whether or not the user can resize the frame by dragging the frame border. * * @return {@code true} if the user can resize the frame by dragging. */ public boolean isEnableResizeControl() { return this.enableResize; } /** * Specifies whether the user can resize the frame by dragging the border. * * @param enable {@code true} to allow the user to resize the frame by dragging the border. */ public void setEnableResizeControl(boolean enable) { this.enableResize = enable; } /** * Specifies whether the user can move the frame by dragging the title bar. * * @return {@code true} if the user can allowed to move the frame by dragging the title bar. */ public boolean isEnableMove() { return this.enableMove; } /** * Specifies whether the user can move the frame by dragging the title bar. * * @param enable {@code true} if the user is allowed to move the frame by dragging. */ public void setEnableMove(boolean enable) { this.enableMove = enable; } /** * Get the animation that is played when the tree frame is minimized. * * @return Animation played when the frame is minimized. * * @see #setMinimizeAnimation(Animation) */ public Animation getMinimizeAnimation() { return minimizeAnimation; } /** * Set the animation that is played when the tree frame is minimized. * * @param minimizeAnimation New minimize animation. * * @see #getMinimizeAnimation() */ public void setMinimizeAnimation(Animation minimizeAnimation) { this.minimizeAnimation = minimizeAnimation; } /** * Get the image source for the frame icon. * * @return The icon image source, or null if no image source has been set. * * @see #setIconImageSource(Object) */ public Object getIconImageSource() { return this.iconImageSource; } /** * Set the image source of the frame icon. This icon is drawn in the upper right hand corner of the tree frame. * * @param imageSource New image source. May be a String, URL, or BufferedImage. */ public void setIconImageSource(Object imageSource) { this.iconImageSource = imageSource; } /** * Get the bounds of the tree frame. * * @param dc Draw context * * @return The bounds of the tree frame on screen, in screen coordinates (origin at upper left). */ public Rectangle getBounds(DrawContext dc) { this.updateBounds(dc); return new Rectangle((int) this.awtScreenPoint.getX(), (int) this.awtScreenPoint.getY(), this.frameSize.width, this.frameSize.height); } /** * Get the location of the upper left corner of the tree, measured in screen coordinates with the origin at the * upper left corner of the screen. * * @return Screen location, measured in pixels from the upper left corner of the screen. */ public Offset getScreenLocation() { return this.screenLocation; } /** * Set the location of the upper left corner of the tree, measured in screen coordinates with the origin at the * upper left corner of the screen. * * @param screenLocation New screen location. */ public void setScreenLocation(Offset screenLocation) { this.screenLocation = screenLocation; } /** * Get the location of the upper left corner of the frame, measured from the upper left corner of the screen. * * @return The location of the upper left corner of the frame. This method will return null until the has been * rendered. */ protected Point2D getScreenPoint() { return this.awtScreenPoint; } /** * Indicates the frame attributes used to draw the frame when it is not highlighted. * * @return normal frame attributes. */ public FrameAttributes getAttributes() { return this.normalAttributes; } /** * Specifies the frame attributes used to draw the frame when it is not highlighted. * * @param attributes new attributes bundle for normal state. */ public void setAttributes(FrameAttributes attributes) { if (attributes == null) { String msg = Logging.getMessage("nullValue.AttributesIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } this.normalAttributes = attributes; } /** * Indicates the frame attributes used to draw the frame when it is highlighted. * * @return highlight frame attributes. */ public FrameAttributes getHighlightAttributes() { return this.highlightAttributes; } /** * Specifies the frame attributes used to draw the frame when it is highlighted. * * @param attributes new attributes bundle for highlight state. */ public void setHighlightAttributes(FrameAttributes attributes) { if (attributes == null) { String msg = Logging.getMessage("nullValue.AttributesIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } this.highlightAttributes = attributes; } //**************************************************************// //******************** Tile Updating *************************// //**************************************************************// /** * Build the list of ContentTile that represents the logical tiles in the frame contents. * * @param rows Number of rows of tiles in the contents. * @param columns Number of columns of tiles. */ protected void assembleTiles(int rows, int columns) { this.tiles.clear(); for (int i = 0; i < rows; i++) { for (int j = 0; j < columns; j++) { ContentTile newTile = new ContentTile(i, j); this.tiles.add(newTile); } } } /** * Indicates whether or not any of the the rendered tiles must be updated. * * @param dc Current draw context. * * @return {@code true} if any of the tiles need to be updated. */ protected boolean mustUpdateTiles(DrawContext dc) { // Tiles are not visible if the frame is minimized, so no reason to update if (this.isMinimized()) return false; // Make sure that our texture is available. If the texture has been evicted it will need to be regenerated. if (dc.getTextureCache().getTexture(this.textureCacheKey) == null) return true; if (tiles.isEmpty()) return true; long contentUpdateTime = this.contents.getUpdateTime(); for (ContentTile tile : this.tiles) { if (this.mustUpdateTile(tile, contentUpdateTime)) return true; } return false; } /** * Determine if a tile in the content layout needs to be updated. * * @param tile Tile to test. * @param contentUpdateTime Time at which the content was last updated. * * @return {@code true} if the tile needs to be updated. Always returns {@code false} if the tile is not visible in * the current frame bounds. */ protected boolean mustUpdateTile(ContentTile tile, long contentUpdateTime) { Rectangle tileBounds = this.getContentTileBounds(tile.row, tile.column); if (this.contentBounds.intersects(tileBounds)) { if (tile.updateTime != contentUpdateTime) return true; TextureTile textureTile = this.getTextureTile(tile); if (textureTile == null) return true; } return false; } /** * Update content tiles that have been rendered to a texture. * * @param dc Current draw context. */ protected void updateTiles(DrawContext dc) { // The OpenGL framebuffer object extension used by RenderToTextureSupport works only for texture formats // GL_RGB and GL_RGBA. this.rttSupport.setEnableFramebufferObject(true); Texture texture = dc.getTextureCache().getTexture(this.textureCacheKey); // Determine how large of a texture is required to render the full frame bounds. int dim = this.computeTileTextureDimension(this.contentBounds.getSize(), this.contentSize); // Check the texture dimension against the maximum supported by the hardware int maxTexture = dc.getGLRuntimeCapabilities().getMaxTextureSize(); this.renderToTexture = (dim <= maxTexture); // If the frame is too big to render into a texture don't bother building tiles if (!this.renderToTexture) { return; } // If we don't have a texture, or if we need a different size of texture, allocate a new one if (texture == null || this.textureDimension != dim) { texture = this.createTileTexture(dc, dim, dim); dc.getTextureCache().put(this.textureCacheKey, texture); this.textureDimension = dim; int numTiles = dim / this.textureTileDimension; // Create entries for the sub-tiles in the texture. Each sub-tile will be used to render a piece of the // frame contents. this.textureTiles.clear(); for (int i = 0; i < numTiles; i++) { for (int j = 0; j < numTiles; j++) { this.textureTiles.add(new TextureTile(i, j)); } } // Clear the texture tile map. Any previous tile allocations are now invalid. this.textureTileMap.clear(); } if (texture == null) // This should never happen, but we check anyway. { Logging.logger().warning(Logging.getMessage("nullValue.TextureIsNull")); return; } int rows = (int) Math.ceil((double) this.contentSize.height / this.textureTileDimension); int columns = (int) Math.ceil((double) this.contentSize.width / this.textureTileDimension); if (tiles.size() != rows * columns) { this.assembleTiles(rows, columns); } long contentUpdateTime = this.contents.getUpdateTime(); // Update each tile that needs updating for (ContentTile tile : this.tiles) { if (this.mustUpdateTile(tile, contentUpdateTime)) { TextureTile textureTile = this.getTextureTile(tile); if (textureTile == null) { textureTile = this.allocateTextureTile(tile); } int x = textureTile.column * this.textureTileDimension; int y = textureTile.row * this.textureTileDimension; Rectangle tileBounds = new Rectangle(x, y, this.textureTileDimension, this.textureTileDimension); this.rttSupport.beginRendering(dc, tileBounds.x, tileBounds.y, tileBounds.width, tileBounds.height); try { this.updateTile(dc, tile, tileBounds); tile.updateTime = contentUpdateTime; textureTile.lastUsed = dc.getFrameTimeStamp(); } finally { this.rttSupport.endRendering(dc); } } } } /** * Get the texture tile allocated for a ContentTile in the frame content. * * @param tile ContentTile for which to get a texture tile. * * @return TextureTile allocated for the given ContentTile, or {@code null} if no TextureTile has been allocated. */ protected TextureTile getTextureTile(ContentTile tile) { TextureTile textureTile = this.textureTileMap.get(tile); if (textureTile != null && textureTile.currentTile.equals(tile)) { return textureTile; } return null; } /** * Allocate a texture tile for a ContentTile. There are a limited number of texture tiles. If no tiles are free, the * least recently used texture tile will be reallocated. * * @param tile ScrollableTile for which to allocate a texture tile. * * @return TextureTile allocated for the ScrollableTile. */ protected TextureTile allocateTextureTile(ContentTile tile) { // Sort the list of texture tiles so that we can find the one that is the least recently used. TextureTile[] timeOrderedEntries = new TextureTile[this.textureTiles.size()]; Arrays.sort(this.textureTiles.toArray(timeOrderedEntries)); // Grab the least recently used tile TextureTile textureTile = timeOrderedEntries[0]; textureTile.currentTile = tile; this.textureTileMap.put(tile, textureTile); return textureTile; } /** * Draws the current list of ScrollableTiles into the texture tiles. The tiles are updated when necessary. * * @param dc the draw context the tile relates to. * @param tile the tile to update. A new texture tile will be allocated for the tile, if the tile does not * have a texture. * @param tileBounds bounds of the tile being updated, within the larger texture. */ protected void updateTile(DrawContext dc, ContentTile tile, Rectangle tileBounds) { int x = tileBounds.x - tile.column * this.textureTileDimension; int y = tileBounds.y - this.contentSize.height + this.textureTileDimension * (tile.row + 1); Rectangle scrollBounds = new Rectangle(x, y, this.contentBounds.width, this.textureTileDimension); try { Texture texture = dc.getTextureCache().getTexture(this.textureCacheKey); this.rttSupport.setColorTarget(dc, texture); this.rttSupport.clear(dc, new Color(0, 0, 0, 0)); // Set all texture pixels to transparent black. this.contents.renderScrollable(dc, scrollBounds.getLocation(), scrollBounds.getSize(), tileBounds); } finally { this.rttSupport.setColorTarget(dc, null); } } /** Force all tiles to update on the next frame. */ protected void forceTileUpdate() { for (ContentTile tile : tiles) { tile.updateTime = -1; } } /** * Returns a new tile texture with the specified width and height. *

* The returned texture's internal format is RGBA8. * * @param width the texture's width, in pixels. * @param height the texture's height, in pixels. * * @return a new texture with the specified width and height. */ protected Texture createTileTexture(DrawContext dc, int width, int height) { GL gl = dc.getGL(); TextureData td = new TextureData( gl.getGLProfile(), // GL profile GL.GL_RGBA8, // internal format width, height, // dimension 0, // border GL.GL_RGBA, // pixel format GL.GL_UNSIGNED_BYTE, // pixel type false, // mipmap false, false, // dataIsCompressed, mustFlipVertically null, null) // buffer, flusher { /** * Overridden to return a non-zero size. TextureData does not compute an estimated memory size if the buffer * is null. Therefore we override getEstimatedMemorySize() to return the appropriate size in bytes of a * texture with the common pixel formats. */ @Override public int getEstimatedMemorySize() { int sizeInBytes = OGLUtil.estimateTextureMemorySize(this.getInternalFormat(), this.getWidth(), this.getHeight(), this.getMipmap()); if (sizeInBytes > 0) return sizeInBytes; return super.getEstimatedMemorySize(); } }; Texture t = TextureIO.newTexture(td); t.bind(gl); return t; } /** * Compute the dimension of a texture large enough to represent the amount of the contents visible in the frame. * * @param frameSize Size of the frame content area. * @param contentSize Size of the frame content. * * @return Dimension of a texture large enough to render the full frame content area. This method always returns a * power of two dimension. */ protected int computeTileTextureDimension(Dimension frameSize, Dimension contentSize) { int width = Math.min(frameSize.width, contentSize.width); int height = Math.min(frameSize.height, contentSize.height); int area = width * height; return WWMath.powerOfTwoCeiling((int) Math.sqrt(area) + this.textureTileDimension); } /** {@inheritDoc} */ public void preRender(DrawContext dc) { Offset screenLocation = this.getScreenLocation(); if (screenLocation == null) return; this.stepAnimation(dc); this.updateBounds(dc); // Highlight the frame if the pick point is within the frame's pickable bounds. Point pickPoint = dc.getPickPoint(); if (pickPoint != null) { int glY = dc.getView().getViewport().height - pickPoint.y; this.setHighlighted(this.pickBounds.contains(new Point(pickPoint.x, glY))); } this.determineActiveAttributes(); if (this.intersectsFrustum(dc) && this.mustUpdateTiles(dc)) { try { this.beginDrawing(dc); this.updateTiles(dc); } finally { this.endDrawing(dc); } } } /** {@inheritDoc} */ public void render(DrawContext dc) { Offset screenLocation = this.getScreenLocation(); if (screenLocation == null || this.frameBounds == null) return; if (this.mustRecomputeFrameGeometry) { this.computeFrameGeometry(); this.mustRecomputeFrameGeometry = false; } if (this.intersectsFrustum(dc)) { try { this.beginDrawing(dc); // While the tree is animated toward a minimized state, draw it as if it were maximized, // with the contents and scroll bars if (this.isDrawMinimized()) this.drawMinimized(dc); else this.drawMaximized(dc); } finally { this.endDrawing(dc); } } } /** Initialize controls to resizing the frame, minimizing the frame, etc. */ protected void initializeUIControls() { this.minimizeAnimation = new WindowShadeAnimation(this); this.frameResizeControl = new FrameResizeControl(this); this.minimizeButton = new TreeHotSpot(this) { @Override public void selected(SelectEvent event) { if (event == null || this.isConsumed(event)) return; if (event.isLeftClick()) { ScrollFrame.this.setMinimized(!ScrollFrame.this.isMinimized()); event.consume(); } else { super.selected(event); } } }; this.verticalScrollBar = new ScrollBar(this, AVKey.VERTICAL); this.horizontalScrollBar = new ScrollBar(this, AVKey.HORIZONTAL); } /** * Determines whether the frame intersects the view frustum. * * @param dc the current draw context. * * @return {@code true} If the frame intersects the frustum, otherwise {@code false}. */ protected boolean intersectsFrustum(DrawContext dc) { if (dc.isPickingMode()) return dc.getPickFrustums().intersectsAny(this.pickBounds); else return dc.getView().getViewport().intersects(this.frameBounds); } /** * Increment the active animation. Has no effect if there is no active animation. This method must be called once * per frame. * * @param dc Current draw context. */ protected void stepAnimation(DrawContext dc) { if (this.isAnimating()) { this.animation.step(); if (this.animation.hasNext()) dc.setRedrawRequested(this.animationDelay); else this.animation = null; } } /** * Get the bounds of a tile in the frame content. * * @param row Row of the tile to get the bounds of. * @param column Column of the tile to get the bounds of. * * @return Bounds of the desired tile, relative to the lower left corner of {#link contentBounds}. */ protected Rectangle getContentTileBounds(int row, int column) { int xScroll = this.horizontalScrollBar.getValue(); int yScroll = this.verticalScrollBar.getValue(); int tileScreenX = (int) this.contentBounds.getMinX() + this.textureTileDimension * column - xScroll; int tileScreenY = (int) this.contentBounds.getMaxY() - this.textureTileDimension * (row + 1) + yScroll; return new Rectangle(tileScreenX, tileScreenY, this.textureTileDimension, this.textureTileDimension); } /** * Compute the bounds of the content frame, and the extents of the scroll range. Calling this method updates the * frame and scroll model to match the size of the scrollable content. Bounds are only computed once per frame. * Multiple calls in the same frame will not change the computed bounds. * * @param dc Current draw context. */ public void updateBounds(DrawContext dc) { if (dc.getFrameTimeStamp() == this.frameNumber) return; this.determineSize(); Rectangle viewport = dc.getView().getViewport(); Dimension contentSize = null; Dimension previousFrameSize = this.frameSize; Size size = this.getActiveSize(); // If the frame size is relative to the content size, compute the content size and then set the frame size. if (this.isRelativeSize(size)) { // Pass null for the frame bounds because the frame size depends on the content size. contentSize = this.contents.getSize(dc, null); Dimension frameSizeForContentSize = this.computeFrameRectForContentRect(contentSize); this.frameSize = size.compute(frameSizeForContentSize.width, frameSizeForContentSize.height, viewport.width, viewport.height); } else { // Otherwise just compute the frame size. The content size will be computed after the frame size has been // determined. this.frameSize = size.compute(0, 0, viewport.width, viewport.height); } // Apply the maximum size constraint if (this.getMaxSize() != null) { Dimension max = this.getMaxSize().compute(this.frameSize.width, this.frameSize.height, viewport.width, viewport.height); this.frameSize.width = Math.min(this.frameSize.width, max.width); this.frameSize.height = Math.min(this.frameSize.height, max.height); } // If the frame size has changed, the frame geometry must be regenerated if (!this.frameSize.equals(previousFrameSize)) this.mustRecomputeFrameGeometry = true; // Compute point in OpenGL coordinates Point2D upperLeft = this.screenLocation.computeOffset(viewport.width, viewport.height, 1.0, 1.0); this.awtScreenPoint = new Point((int) upperLeft.getX(), (int) (viewport.height - upperLeft.getY())); this.frameBounds = new Rectangle((int) upperLeft.getX(), (int) upperLeft.getY() - this.frameSize.height, this.frameSize.width, this.frameSize.height); // Compute the pickable screen extent as the frame extent, plus the width of the frame's pickable outline. // This extent is used during picking to ensure that the frame's outline is pickable when it exceeds the // frame's screen extent. this.pickBounds = new Rectangle( this.frameBounds.x - this.borderPickWidth / 2, this.frameBounds.y - this.borderPickWidth / 2, this.frameBounds.width + this.borderPickWidth, this.frameBounds.height + this.borderPickWidth); this.innerBounds = new Rectangle((int) upperLeft.getX() + this.frameBorder, (int) upperLeft.getY() - frameSize.height + this.frameBorder, frameSize.width - this.frameBorder * 2, frameSize.height - this.frameBorder * 2); // If the content size has yet not been computed, compute it now. if (contentSize == null) { // Compute the bounds as if both scroll bars are visible. This saves us from having to compute the size // multiple times if scroll bars are required. If scroll bars are not required it may leave a little bit of // extra padding on the edges of the frame. contentSize = this.contents.getSize(dc, this.computeBounds(true, true).getSize()); } // Computing the bounds of the content area of the frame requires computing the bounds with no scroll bars, // and determining if scroll bars are required given the size of the scrollable content. // Try laying out the frame without scroll bars this.contentBounds = this.computeBounds(false, false); // Determine if we need a vertical scroll bar boolean showVerticalScrollbar = this.mustShowVerticalScrollbar(contentSize); // If we need a vertical scroll bar, recompute the bounds because the scrollbar consumes horizontal space if (showVerticalScrollbar) { this.contentBounds = this.computeBounds(true, false); } // Determine if we need a horizontal scroll bar boolean showHorizontalScrollbar = this.mustShowHorizontalScrollbar(contentSize); // If we need a horizontal scroll bar, recompute the bounds because the horizontal scroll bar consumes vertical // space and a vertical scroll bar may now be required if (showHorizontalScrollbar && !showVerticalScrollbar) { this.contentBounds = this.computeBounds(showVerticalScrollbar, showHorizontalScrollbar); // Determine if we now need a vertical scroll bar showVerticalScrollbar = this.mustShowVerticalScrollbar(contentSize); } // Final bounds computation now that all the scroll bars are established this.contentBounds = this.computeBounds(showVerticalScrollbar, showHorizontalScrollbar); // If the scroll bars were visible and are now hidden, reset the scroll position to zero. Otherwise, the // scroll bar will already be scrolled to a certain position when it reappears, which is probably not what // the user expects. if (this.showVerticalScrollbar && !showVerticalScrollbar) this.verticalScrollBar.setValue(0); if (this.showHorizontalScrollbar && !showHorizontalScrollbar) this.horizontalScrollBar.setValue(0); this.showVerticalScrollbar = showVerticalScrollbar; this.showHorizontalScrollbar = showHorizontalScrollbar; // Set scroll bar ranges this.verticalScrollBar.setMaxValue(contentSize.height); this.verticalScrollBar.setExtent(this.contentBounds.height); this.horizontalScrollBar.setMaxValue(contentSize.width); this.horizontalScrollBar.setExtent(this.contentBounds.width); // Compute the bounds of the content based on the scroll position this.scrollContentBounds = new Rectangle(this.contentBounds); this.scrollContentBounds.x -= this.horizontalScrollBar.getValue(); this.scrollContentBounds.y += this.verticalScrollBar.getValue(); this.scrollContentBounds.y = this.scrollContentBounds.y - (contentSize.height - this.contentBounds.height); this.contentSize = contentSize; this.frameNumber = dc.getFrameTimeStamp(); } /** * Compute the size of the frame rectangle required to accommodate a given content size without displaying scroll * bars. * * @param contentSize Size of the frame content. * * @return Frame size required to display the content without scrollbars. */ protected Dimension computeFrameRectForContentRect(Dimension contentSize) { int frameWidth = contentSize.width + this.frameBorder * 2 + 4 * this.frameLineWidth + this.scrollBarSize; int frameHeight = contentSize.height + this.frameBorder * 2 + this.getTitleBarHeight() + 2 * this.frameLineWidth; return new Dimension(frameWidth, frameHeight); } /** * Determines if the frame size is relative to the size of the scrollable content (as opposed to an absolute pixel * value, or a fraction of the viewport). * * @param size Size to test. * * @return {@code true} if the absolute size of {@code size} depends on the size of the frame content. */ // TODO try to eliminate this dependence on size modes. This would break if an app subclassed Size and implemented // TODO different modes. protected boolean isRelativeSize(Size size) { String heightMode = size.getHeightMode(); String widthMode = size.getWidthMode(); return Size.NATIVE_DIMENSION.equals(heightMode) || Size.MAINTAIN_ASPECT_RATIO.equals(heightMode) || Size.NATIVE_DIMENSION.equals(widthMode) || Size.MAINTAIN_ASPECT_RATIO.equals(widthMode); } /** * Determine if the vertical scrollbar should be displayed. * * @param contentSize The total size, in pixels, of the scrollable content. * * @return {@code true} if the vertical scrollbar should be displayed, otherwise {@code false}. */ protected boolean mustShowVerticalScrollbar(Dimension contentSize) { // If the frame is not minimized or in the middle of an animation, compare the content size to the visible // bounds. if ((!this.isMinimized() && !this.isAnimating())) { return contentSize.height > this.contentBounds.height; } else { // Otherwise, return the previous scrollbar setting, do not recompute it. While the frame is animating, we want // the scrollbar decision to be based on its maximized size. If the frame would have scrollbars when maximized will // have scrollbars while it animates, but a frame that would not have scrollbars when maximized will not have // scrollbars while animating. return this.showVerticalScrollbar; } } /** * Determine if the horizontal scrollbar should be displayed. * * @param contentSize The total size, in pixels, of the scrollable content. * * @return {@code true} if the horizontal scrollbar should be displayed, otherwise {@code false}. */ protected boolean mustShowHorizontalScrollbar(Dimension contentSize) { // Show a scroll bar if the content is large enough to require a scroll bar, and there is enough space to // draw the scroll bar. return contentSize.width > this.contentBounds.width && this.innerBounds.height > this.titleBarHeight + this.scrollBarSize; } /** * Determines if the frame is currently animating. * * @return {@code true} if an animation is in progress, otherwise {@code false}. */ protected boolean isAnimating() { return this.animation != null; } /** * Compute the content bounds, taking into account the frame size and the presence of scroll bars. * * @param showVerticalScrollBar True if the frame will have a vertical scroll bar. A vertical scroll bar will make * the content frame narrower. * @param showHorizontalScrollBar True if the frame will have a horizontal scroll bar. A horizontal scroll bar will * make the content frame shorter. * * @return The bounds of the content frame. */ protected Rectangle computeBounds(boolean showVerticalScrollBar, boolean showHorizontalScrollBar) { int hScrollBarSize = (showHorizontalScrollBar ? this.scrollBarSize : 0); int vScrollBarSize = (showVerticalScrollBar ? this.scrollBarSize : 0); int titleBarHeight = this.isDrawTitleBar() ? this.titleBarHeight : 0; int inset = 2 * this.frameLineWidth; return new Rectangle(this.innerBounds.x + inset, this.innerBounds.y + hScrollBarSize + inset, this.innerBounds.width - vScrollBarSize - inset * 2, this.innerBounds.height - titleBarHeight - hScrollBarSize - inset); } /** Updates the frame's screen-coordinate geometry in {@link #vertexBuffer} according to the current screen bounds. */ protected void computeFrameGeometry() { if (this.frameBounds == null) return; FrameAttributes attributes = this.getActiveAttributes(); this.vertexBuffer = FrameFactory.createShapeBuffer(AVKey.SHAPE_RECTANGLE, this.frameBounds.width, this.frameBounds.height, attributes.getCornerRadius(), this.vertexBuffer); } /** * Get the smallest dimension that the frame can draw itself. This user is not allowed to resize the frame to be * smaller than this dimension. * * @return The frame's minimum size. */ protected Dimension getMinimumSize() { // Reserve enough space to draw the border, both scroll bars, and the title bar int minWidth = this.frameBorder * 2 + this.scrollBarSize * 3; // left scroll arrow + right + vertical scroll bar int minHeight = this.frameBorder * 2 + this.scrollBarSize * 3 + this.titleBarHeight; // Up arrow + down arrow + horizontal scroll bar return new Dimension(minWidth, minHeight); } /** * Determines if the frame should draw in its minimized form. * * @return {@code true} if the frame should draw minimized, otherwise {@code false}. */ protected boolean isDrawMinimized() { // Draw minimized when the frame is minimized, but not while animating toward the minimized state return this.isMinimized() && !this.isAnimating(); } /** * Draw the frame in its maximized state. * * @param dc Current draw context. */ protected void drawMaximized(DrawContext dc) { this.drawFrame(dc); // Draw the contents using the cached texture, if we've rendered to a texture. Otherwise, just draw the // contents directly. Always draw the contents directly in picking mode because unique pick colors can't be // cached in a texture. if (this.renderToTexture && !dc.isPickingMode()) { this.drawContentTiles(dc); } else { this.drawContentDirect(dc); } } /** * Draw the frame contents directly (not using previously generated tiles). * * @param dc Current draw context. */ protected void drawContentDirect(DrawContext dc) { GL gl = dc.getGL(); try { gl.glEnable(GL.GL_SCISSOR_TEST); gl.glScissor(this.contentBounds.x, this.contentBounds.y - 1, this.contentBounds.width + 1, this.contentBounds.height); this.contents.renderScrollable(dc, this.scrollContentBounds.getLocation(), this.scrollContentBounds.getSize(), this.contentBounds); } finally { gl.glDisable(GL.GL_SCISSOR_TEST); } } /** * Draw the frame contents using previously build texture tiles. * * @param dc Current draw context. */ protected void drawContentTiles(DrawContext dc) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. try { gl.glPolygonMode(GL2.GL_FRONT, GL2.GL_FILL); gl.glEnable(GL.GL_TEXTURE_2D); // Set up blending with pre-multiplied colors OGLUtil.applyBlending(gl, true); Texture texture = dc.getTextureCache().getTexture(this.textureCacheKey); if (texture == null) return; texture.bind(gl); for (ContentTile tile : tiles) { TextureTile textureTile = this.getTextureTile(tile); if (textureTile == null) { continue; } int tileX = textureTile.column * this.textureTileDimension; int tileY = textureTile.row * this.textureTileDimension; Rectangle tileScreenBounds = this.getContentTileBounds(tile.row, tile.column); Rectangle clippedTileBounds = tileScreenBounds.intersection(this.contentBounds); // If the tile is not visible in the content area, don't bother drawing it. if (clippedTileBounds.isEmpty()) { continue; } Rectangle subTileBounds = new Rectangle(tileX + clippedTileBounds.x - tileScreenBounds.x, tileY + clippedTileBounds.y - tileScreenBounds.y, clippedTileBounds.width, clippedTileBounds.height); gl.glPushMatrix(); try { gl.glTranslated(clippedTileBounds.x, clippedTileBounds.y, 0.0f); gl.glColor4f(1, 1, 1, (float) this.getActiveAttributes().getForegroundOpacity()); TextureCoords texCoords = texture.getSubImageTexCoords((int) subTileBounds.getMinX(), (int) subTileBounds.getMinY(), (int) subTileBounds.getMaxX(), (int) subTileBounds.getMaxY()); gl.glScaled(subTileBounds.width, subTileBounds.height, 1d); dc.drawUnitQuad(texCoords); } finally { gl.glPopMatrix(); } } } finally { gl.glDisable(GL.GL_TEXTURE_2D); gl.glBindTexture(GL.GL_TEXTURE_2D, 0); } } /** * Draw the frame in its minimized state. * * @param dc Current draw context. */ protected void drawMinimized(DrawContext dc) { this.drawFrame(dc); } /** * Draw the frame, scroll bars, and title bar. * * @param dc Current draw context. */ protected void drawFrame(DrawContext dc) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. OGLStackHandler oglStack = new OGLStackHandler(); try { oglStack.pushModelviewIdentity(gl); FrameAttributes attributes = this.getActiveAttributes(); gl.glTranslated(this.frameBounds.x, this.frameBounds.y, 0.0); boolean drawHorizontalScrollbar = this.showHorizontalScrollbar; boolean drawVerticalScrollbar = this.showVerticalScrollbar; if (!dc.isPickingMode()) { Color[] color = attributes.getBackgroundColor(); try { gl.glEnable(GL.GL_LINE_SMOOTH); OGLUtil.applyColor(gl, color[0], 1.0, false); gl.glLineWidth(this.frameLineWidth); FrameFactory.drawBuffer(dc, GL.GL_LINE_STRIP, this.vertexBuffer); } finally { gl.glDisable(GL.GL_LINE_SMOOTH); } gl.glLoadIdentity(); gl.glTranslated(this.innerBounds.x, this.innerBounds.y, 0.0); // Translate back inner frame TreeUtil.drawRectWithGradient(gl, new Rectangle(0, 0, this.innerBounds.width, this.innerBounds.height), color[0], color[1], attributes.getBackgroundOpacity(), AVKey.VERTICAL); } else { int frameHeight = this.frameBounds.height; int frameWidth = this.frameBounds.width; // Draw draggable frame TreeUtil.drawPickableRect(dc, this.pickSupport, this, new Rectangle(0, 0, frameWidth, frameHeight)); if (this.isEnableResizeControl() && !this.isDrawMinimized()) { Color color = dc.getUniquePickColor(); int colorCode = color.getRGB(); this.pickSupport.addPickableObject(colorCode, this.frameResizeControl); gl.glColor3ub((byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue()); // Draw the resize control as a pickable line on the frame border gl.glLineWidth(this.borderPickWidth); FrameFactory.drawBuffer(dc, GL.GL_LINE_STRIP, this.vertexBuffer); } gl.glLoadIdentity(); gl.glTranslated(this.innerBounds.x, this.innerBounds.y, 0.0); // Translate back inner frame // If both scroll bars are visible, draw the empty square in the lower right hand corner as a part of // the resize control, pickable area. if (drawVerticalScrollbar && drawHorizontalScrollbar && !this.isDrawMinimized()) { gl.glRecti(this.innerBounds.width - this.scrollBarSize, 0, this.innerBounds.width, this.scrollBarSize); } } if (!this.isDrawMinimized()) this.drawScrollBars(dc); // Draw title bar if (this.isDrawTitleBar()) { gl.glTranslated(0, this.innerBounds.height - this.titleBarHeight, 0); this.drawTitleBar(dc); } // Draw a thin border outlining the filled rectangle that is the frame background. if (!dc.isPickingMode()) { gl.glLoadIdentity(); int minX = (int) this.innerBounds.getMinX(); int minY = (int) this.innerBounds.getMinY(); int maxX = (int) this.innerBounds.getMaxX(); int maxY = (int) this.innerBounds.getMaxY(); OGLUtil.applyColor(gl, attributes.getForegroundColor(), false); // Do not draw the outline on the edges with scroll bars because the scrollbar draws its own border. On // some devices the scroll bar border draws next to the frame border, resulting in a double width border. gl.glBegin(GL2.GL_LINE_STRIP); try { if (!drawVerticalScrollbar) gl.glVertex2f(maxX, minY + 0.5f); gl.glVertex2f(maxX, maxY); gl.glVertex2f(minX + 0.5f, maxY); gl.glVertex2f(minX + 0.5f, minY + 0.5f); if (!drawHorizontalScrollbar) gl.glVertex2f(maxX, minY + 0.5f); } finally { gl.glEnd(); } } } finally { oglStack.pop(gl); } } /** * Draw visible scroll bars for the frame. Scroll bars that are not visible will not be drawn. * * @param dc Current draw context. */ protected void drawScrollBars(DrawContext dc) { // Draw a vertical scroll bar if the tree extends beyond the visible bounds if (this.showVerticalScrollbar) { int x1 = this.innerBounds.width - this.scrollBarSize; int y1 = 1; if (this.showHorizontalScrollbar) y1 += this.scrollBarSize; Rectangle scrollBarBounds = new Rectangle(x1, y1, this.scrollBarSize, this.contentBounds.height + 1); this.verticalScrollBar.setBounds(scrollBarBounds); this.verticalScrollBar.render(dc); } // Draw a horizontal scroll bar if the tree extends beyond the visible bounds if (this.showHorizontalScrollbar) { int x1 = 1; int y1 = 1; int width = this.innerBounds.width - 1; if (this.showVerticalScrollbar) width -= this.scrollBarSize; Rectangle scrollBarBounds = new Rectangle(x1, y1, width, this.scrollBarSize); this.horizontalScrollBar.setBounds(scrollBarBounds); this.horizontalScrollBar.render(dc); } } /** * Draw the title bar. * * @param dc Draw context */ protected void drawTitleBar(DrawContext dc) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. FrameAttributes attributes = this.getActiveAttributes(); if (!dc.isPickingMode()) { // Draw title bar as a rectangle with gradient Color[] color = attributes.getTitleBarColor(); TreeUtil.drawRectWithGradient(gl, new Rectangle(0, 0, this.innerBounds.width, this.getTitleBarHeight()), color[0], color[1], attributes.getBackgroundOpacity(), AVKey.VERTICAL); OGLUtil.applyColor(gl, attributes.getForegroundColor(), 1.0, false); if (!this.isDrawMinimized()) { // Draw a line to separate the title bar from the frame gl.glBegin(GL2.GL_LINES); try { gl.glVertex2f(0, 0); gl.glVertex2f(this.innerBounds.width, 0); } finally { gl.glEnd(); } } Point drawPoint = new Point(0, 0); this.drawIcon(dc, drawPoint); this.drawTitleText(dc, drawPoint); } this.drawMinimizeButton(dc); } /** * Draw an icon in the upper left corner of the title bar. This method takes a point relative to lower left corner * of the title bar. This point is modified to indicate how much horizontal space is consumed by the icon. * * @param dc Draw context * @param drawPoint Point at which to draw the icon. This point is relative to the lower left corner of the title * bar. This point will be modified to indicate how much horizontal space was consumed by drawing * the icon. After drawing the icon, the x value with point to the first available space to the * right of the icon. */ protected void drawIcon(DrawContext dc, Point drawPoint) { // This method is never called during picked, so picking mode is not handled here GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. FrameAttributes attributes = this.getActiveAttributes(); int iconSpace = attributes.getIconSpace(); // Draw icon in upper left corner BasicWWTexture texture = this.getTexture(); if (texture == null) { drawPoint.x += iconSpace; return; } OGLStackHandler oglStack = new OGLStackHandler(); try { if (texture.bind(dc)) { gl.glEnable(GL.GL_TEXTURE_2D); Dimension iconSize = attributes.getIconSize(); oglStack.pushModelview(gl); gl.glColor4d(1.0, 1.0, 1.0, 1.0); double vertAdjust = (this.titleBarHeight - iconSize.height) / 2; TextureCoords texCoords = texture.getTexCoords(); gl.glTranslated(drawPoint.x + iconSpace, drawPoint.y + vertAdjust + 1, 1.0); gl.glScaled((double) iconSize.width, (double) iconSize.height, 1d); dc.drawUnitQuad(texCoords); drawPoint.x += iconSize.getWidth() + iconSpace * 2; } } finally { gl.glDisable(GL.GL_TEXTURE_2D); gl.glBindTexture(GL.GL_TEXTURE_2D, 0); oglStack.pop(gl); } } /** * Draw text in the frame title. * * @param dc Draw context * @param drawPoint Point at which to draw text. This point is relative to the lower left corner of the title bar. */ protected void drawTitleText(DrawContext dc, Point drawPoint) { // This method is never called during picked, so picking mode is not handled here GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. FrameAttributes attributes = this.getActiveAttributes(); String frameTitle = this.getFrameTitle(); if (frameTitle == null) return; TextRenderer textRenderer = OGLTextRenderer.getOrCreateTextRenderer(dc.getTextRendererCache(), attributes.getFont()); // Determine if the shortened frame title needs to be regenerated. If so, generate it now. int titleAreaWidth = this.innerBounds.width - this.buttonSize - drawPoint.x - attributes.getIconSpace(); if (this.mustGenerateShortTitle(attributes.getFont(), titleAreaWidth)) { this.generateShortTitle(dc, frameTitle, titleAreaWidth, "..."); } if (this.shortTitle == null) return; try { textRenderer.begin3DRendering(); OGLUtil.applyColor(gl, attributes.getTextColor(), 1.0, false); double vertAdjust = (this.titleBarHeight - Math.abs(this.shortTitleBounds.getY())) / 2; textRenderer.draw(this.shortTitle, drawPoint.x, (int) (drawPoint.y + vertAdjust) + 1); } finally { textRenderer.end3DRendering(); } } /** * Determine if a the shortened frame title needs to be regenerated. * * @param titleFont Title font. * @param titleAreaWidth Width in pixels of the frame title area. * * @return {@code true} if the shortened title needs to be regenerated, otherwise {@code false}. */ protected boolean mustGenerateShortTitle(Font titleFont, int titleAreaWidth) { return this.shortTitle == null || !titleFont.equals(this.shortFrameTitleFont) || titleAreaWidth != this.frameTitleWidth; } /** * Generate a shortened version of the frame title that will fit in the frame title area. If the frame is wide * enough to accommodate the full title, then the short title will be the same as the full title. The method sets * the fields {@link #shortTitle}, {@link #shortFrameTitleFont}, {@link #frameTitleWidth}. * * @param dc Current draw context. * @param frameTitle Full frame title. * @param width Width in pixels of the frame title area. * @param cutOff String to append to title to indicate that text has been cut off due to a small frame. For * example, if the cut off string is "...", the string "Frame Title" might be shortened to "Frame * T...". */ protected void generateShortTitle(DrawContext dc, String frameTitle, int width, String cutOff) { Font font = this.getActiveAttributes().getFont(); // Keep track of the font and width used to generate the title so that we can invalidate the generated // title if the font or size change. this.shortFrameTitleFont = font; this.frameTitleWidth = width; if (frameTitle == null) { this.shortTitle = null; return; } TextRenderer textRenderer = OGLTextRenderer.getOrCreateTextRenderer(dc.getTextRendererCache(), font); // Check to see if the frame is wide enough to display the entire title Rectangle2D size = textRenderer.getBounds(frameTitle); if (size.getWidth() < width) { this.shortTitle = frameTitle; // No truncation required this.shortTitleBounds = size; return; } // Check to see if the frame is too small to display even the continuation Rectangle2D ellipseSize = textRenderer.getBounds(cutOff); if (width < ellipseSize.getWidth()) { this.shortTitle = null; // Frame too small this.shortTitleBounds = null; return; } // Starting at the end of the string, remove characters until the string fits StringBuilder sb = new StringBuilder(); for (int i = 0; i < frameTitle.length(); i++) { sb.append(frameTitle.charAt(i)); Rectangle2D bounds = textRenderer.getBounds(sb); if (bounds.getWidth() + ellipseSize.getWidth() > width) { sb.deleteCharAt(sb.length() - 1); sb.append("..."); break; } } // Make sure that the computed string contains at least one character of the original title. If not, don't // show any text. if (sb.length() > cutOff.length()) { this.shortTitle = sb.toString(); this.shortTitleBounds = textRenderer.getBounds(sb); } else { this.shortTitle = null; this.shortTitleBounds = null; } } /** * Draw the minimize/maximize button in the frame title bar. * * @param dc Current draw context. */ protected void drawMinimizeButton(DrawContext dc) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. OGLStackHandler oglStack = new OGLStackHandler(); try { oglStack.pushModelviewIdentity(gl); int x = (int) this.innerBounds.getMaxX() - this.getActiveAttributes().getIconSpace() - this.buttonSize; int y = (int) this.innerBounds.getMaxY() - (this.titleBarHeight - this.buttonSize) / 2 - this.buttonSize; gl.glTranslated(x, y, 0.0); if (!dc.isPickingMode()) { Color color = this.getActiveAttributes().getMinimizeButtonColor(); FrameAttributes attributes = this.getActiveAttributes(); OGLUtil.applyColor(gl, color, attributes.getForegroundOpacity(), false); gl.glRectf(0, 0, buttonSize, buttonSize); OGLUtil.applyColor(gl, attributes.getForegroundColor(), false); gl.glBegin(GL2.GL_LINE_LOOP); try { gl.glVertex2f(0f, 0f); gl.glVertex2f(0.5f, buttonSize + 0.5f); gl.glVertex2f(buttonSize, buttonSize + 0.5f); gl.glVertex2f(buttonSize, 0); } finally { gl.glEnd(); } gl.glBegin(GL2.GL_LINES); try { // Draw a horizontal line. If the frame is maximized, this will be a minus sign. If the tree is // minimized, this will be part of a plus sign. gl.glVertex2f(buttonSize / 4f, buttonSize / 2f); gl.glVertex2f(buttonSize - buttonSize / 4f, buttonSize / 2f); // Draw a vertical line to complete the plus sign if the frame is minimized. if (this.isMinimized()) { gl.glVertex2f(buttonSize / 2f, buttonSize / 4f); gl.glVertex2f(buttonSize / 2f, buttonSize - buttonSize / 4f); } } finally { gl.glEnd(); } } else { Color color = dc.getUniquePickColor(); int colorCode = color.getRGB(); this.pickSupport.addPickableObject(colorCode, this.minimizeButton, null, false); gl.glColor3ub((byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue()); gl.glScaled(buttonSize, buttonSize, 1d); dc.drawUnitQuad(); } } finally { oglStack.pop(gl); } } protected void beginDrawing(DrawContext dc) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. GLU glu = dc.getGLU(); this.BEogsh.pushAttrib(gl, GL2.GL_DEPTH_BUFFER_BIT | GL2.GL_COLOR_BUFFER_BIT | GL2.GL_ENABLE_BIT | GL2.GL_CURRENT_BIT | GL2.GL_POLYGON_BIT // For polygon mode | GL2.GL_LINE_BIT // For line width | GL2.GL_TRANSFORM_BIT | GL2.GL_SCISSOR_BIT); gl.glEnable(GL.GL_BLEND); gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA); gl.glDisable(GL.GL_DEPTH_TEST); // Load a parallel projection with xy dimensions (viewportWidth, viewportHeight) // into the GL projection matrix. this.BEogsh.pushProjectionIdentity(gl); java.awt.Rectangle viewport = dc.getView().getViewport(); glu.gluOrtho2D(0d, viewport.width, 0d, viewport.height); this.BEogsh.pushModelviewIdentity(gl); if (dc.isPickingMode()) { this.pickSupport.clearPickList(); this.pickSupport.beginPicking(dc); } } protected void endDrawing(DrawContext dc) { if (dc.isPickingMode()) { this.pickSupport.endPicking(dc); this.pickSupport.resolvePick(dc, dc.getPickPoint(), dc.getCurrentLayer()); } GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. this.BEogsh.pop(gl); } /** * Get the currently active frame attributes. * * @return attributes that are active for this frame. */ protected FrameAttributes getActiveAttributes() { return this.activeAttributes; } /** Determines which attributes -- normal, highlight or default -- to use each frame. */ protected void determineActiveAttributes() { if (this.isHighlighted()) { if (this.getHighlightAttributes() != null) this.activeAttributes.copy(this.getHighlightAttributes()); else { // If no highlight attributes have been specified we will use the normal attributes. if (this.getAttributes() != null) this.activeAttributes.copy(this.getAttributes()); else this.activeAttributes.copy(defaultAttributes); } } else if (this.getAttributes() != null) { this.activeAttributes.copy(this.getAttributes()); } else { this.activeAttributes.copy(defaultAttributes); } this.determineScrollbarAttributes(); } /** Update the attributes of the scroll bars to match the frame's highlight state. */ protected void determineScrollbarAttributes() { this.verticalScrollBar.setLineColor(this.activeAttributes.getForegroundColor()); this.verticalScrollBar.setOpacity(this.activeAttributes.getBackgroundOpacity()); this.horizontalScrollBar.setLineColor(this.activeAttributes.getForegroundColor()); this.horizontalScrollBar.setOpacity(this.activeAttributes.getBackgroundOpacity()); Color[] scrollBarColor = this.activeAttributes.getScrollBarColor(); this.horizontalScrollBar.setKnobColor(scrollBarColor[0], scrollBarColor[1]); this.verticalScrollBar.setKnobColor(scrollBarColor[0], scrollBarColor[1]); } /** * Indicates the size that applies to the frame for this frame, either the maximumed or the minimized size. * * @return the frame size for the duration of this frame. */ protected Size getActiveSize() { return this.activeSize; } /** Determine the frame size to use for the current frame. */ protected void determineSize() { // Use the minimized size if the frame is minimized or animating to or from the minimized state. if ((this.isMinimized() || this.isAnimating()) && this.minimizedSize != null) { this.activeSize = this.minimizedSize; } else { this.activeSize = this.maximizedSize; } } /** * Get the texture loaded for the icon image source. If the texture has not been loaded, this method will attempt to * load it in the background. * * @return The icon texture, or no image source has been set, or if the icon has not been loaded yet. */ protected BasicWWTexture getTexture() { if (this.texture != null) return this.texture; else return this.initializeTexture(); } /** * Create and initialize the texture from the image source. If the image is not in memory this method will request * that it be loaded and return null. * * @return The texture, or null if the texture is not yet available. */ protected BasicWWTexture initializeTexture() { Object imageSource = this.getIconImageSource(); if (imageSource instanceof String || imageSource instanceof URL) { URL imageURL = WorldWind.getDataFileStore().requestFile(imageSource.toString()); if (imageURL != null) { this.texture = new BasicWWTexture(imageURL, true); } } else if (imageSource != null) { this.texture = new BasicWWTexture(imageSource, true); return this.texture; } return null; } /** * Get a reference to one of the frame's scroll bars. * * @param direction Determines which scroll bar to get. Either {@link AVKey#VERTICAL} or {@link AVKey#HORIZONTAL}. * * @return The horizontal or vertical scroll bar. */ public ScrollBar getScrollBar(String direction) { if (AVKey.VERTICAL.equals(direction)) return this.verticalScrollBar; else return this.horizontalScrollBar; } @Override protected void beginDrag(Point point) { if (this.isEnableMove()) { Point2D location = this.awtScreenPoint; this.dragRefPoint = new Point((int) location.getX() - point.x, (int) location.getY() - point.y); } } public void drag(Point point) { if (this.isEnableMove()) { double x = point.x + this.dragRefPoint.x; double y = point.y + this.dragRefPoint.y; this.setScreenLocation(new Offset(x, y, AVKey.PIXELS, AVKey.INSET_PIXELS)); } } @Override public void selected(SelectEvent event) { if (event == null || this.isConsumed(event)) return; super.selected(event); // Minimize the frame if the title bar was double clicked. Rectangle titleBarBounds = new Rectangle((int) this.awtScreenPoint.getX() + this.frameBorder, (int) this.awtScreenPoint.getY() + this.frameBorder * 2, this.innerBounds.width, this.titleBarHeight); if (event.isLeftDoubleClick()) { Point pickPoint = event.getPickPoint(); if (pickPoint != null && titleBarBounds.contains(event.getPickPoint())) { this.setMinimized(!this.isMinimized()); event.consume(); } } } @Override public void mouseWheelMoved(MouseWheelEvent e) { if (e == null || e.isConsumed()) return; // Java on Mac OS X implements support for horizontal scrolling by sending a Shift+ScrollWheel event. This is // not the case for Java for other platforms, so we handle the scrolling logic for Mac OS X if (Configuration.isMacOS()) { this.doScrollMacOS(e); } else { this.doScroll(e); } e.consume(); // Fire a property change to trigger a repaint this.firePropertyChange(AVKey.REPAINT, null, this); } /** * Handle a mouse wheel event. * * @param e Mouse event that triggered the scroll. */ protected void doScroll(MouseWheelEvent e) { // Determine whether to scroll horizontally or vertically by giving priority to the vertical scroll bar. Scroll // vertically if only both scroll bars are active or only the vertical scroll bar is active. Scroll horizontally // if only the horizontal scroll bar is active. if (this.showVerticalScrollbar) { this.verticalScrollBar.scroll(e.getUnitsToScroll() * this.getMouseWheelScrollUnit(AVKey.VERTICAL)); } else if (this.showHorizontalScrollbar) { this.horizontalScrollBar.scroll(e.getUnitsToScroll() * this.getMouseWheelScrollUnit(AVKey.HORIZONTAL)); } } /** * Handle a mouse wheel event on Mac OS X. This method correctly handles the Magic Mouse and Magic Trackpad devices, * which support horizontal scrolling. * * @param e Mouse event that triggered the scroll. */ protected void doScrollMacOS(MouseWheelEvent e) { // On Mac OS X, Java always scrolls horizontally when the Shift key is down. This policy is used to support the // Magic Mouse and Magic Trackpad devices. When the user scroll horizontally on either of these devices, Java // automatically sends a Shift+ScrollWheel event, regardless of whether the Shift key is actually down. See // Radar #4631846 in // http://developer.apple.com/library/mac/#releasenotes/Java/JavaLeopardRN/ResolvedIssues/ResolvedIssues.html. if (e.isShiftDown()) { this.horizontalScrollBar.scroll(e.getUnitsToScroll() * this.getMouseWheelScrollUnit(AVKey.HORIZONTAL)); } // If the Shift key is not down, Java Mac OS X implements the standard scrolling policy used by Java on other // operating systems. else { this.doScroll(e); } } /** * Get the scroll unit that the mouse wheel scrolls by. * * @param direction Direction of scroll. * * @return The scroll unit that will be applied when the mouse wheel is moved. */ protected int getMouseWheelScrollUnit(String direction) { return (int) (this.getScrollBar(direction).getBlockIncrement() * 0.25); } /** * A tile in the frame content. This class represents one tile in the frame contents, and the time at which that * tile was last drawn to texture tile. */ class ContentTile { /** Row in the frame content. */ int row; /** Column in the frame content. */ int column; /** Time at which this content tile was last drawn to a texture tile. */ long updateTime; /** * Create new content tile. * * @param row Row of this tile in the frame content. * @param column Column of this tile in the frame content. */ ContentTile(int row, int column) { this.row = row; this.column = column; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || this.getClass() != o.getClass()) return false; ContentTile that = (ContentTile) o; if (this.row != that.row) return false; //noinspection RedundantIfStatement if (this.column != that.column) return false; return true; } @Override public int hashCode() { int result; result = this.row; result = 31 * result + this.column; return result; } } /** A region of the backing texture used to render one tile of the scrollable content. */ class TextureTile implements Comparable { /** Row of this tile in the frame's backing texture. */ int row; /** Column of this tile in the frame's backing texture. */ int column; /** The content tile currently rendered in this texture tile. */ ContentTile currentTile; /** The last time that this tile was accessed. Used to implement an LRU tile replacement scheme. */ long lastUsed; /** * Create a new texture tile. * * @param row Row of the tile in the frame's backing texture. * @param column Column of the tile in the frame's backing texture. */ TextureTile(int row, int column) { this.row = row; this.column = column; } /** * Compare two TextureTiles by the time that the tiles were last accessed. * * @param that Tile to compare with. * * @return -1 if this tile was accessed less recently than that tile, 0 if the access times are the same, or 1 * if this tile was accessed more recently. */ public int compareTo(TextureTile that) { if (that == null) { String msg = Logging.getMessage("nullValue.CacheEntryIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } return this.lastUsed < that.lastUsed ? -1 : this.lastUsed == that.lastUsed ? 0 : 1; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy