/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* Copyright (c) 2001 - 2013 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved.
*/
package org.pentaho.reporting.engine.classic.core.util;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Window;
import java.awt.geom.Rectangle2D;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.RepaintManager;
import javax.swing.SwingUtilities;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* A Drawable that renders a AWT-component. This only works if the AWT is not in headless mode.
*
* @author Thomas Morgner
*/
public class ComponentDrawable {
private static final Log logger = LogFactory.getLog( ComponentDrawable.class );
/**
* A runnable that executes the drawing operation on the event-dispatcher thread.
*/
private class PainterRunnable implements Runnable {
/**
* The draw-area as defined by the drawable.
*/
private Rectangle2D area;
/**
* The graphics-context to which the runnable draws to.
*/
private Graphics2D graphics;
/**
* Default constructor.
*/
protected PainterRunnable() {
}
/**
* Returns the Graphics2D to which the drawable should be rendered.
*
* @return the graphics.
*/
public Graphics2D getGraphics() {
return graphics;
}
/**
* Defines the Graphics2D to which the drawable should be rendered.
*
* @param graphics
* the graphics.
*/
public void setGraphics( final Graphics2D graphics ) {
this.graphics = graphics;
}
/**
* Returns the draw-area to which the drawable should be rendered.
*
* @return the graphics.
*/
public Rectangle2D getArea() {
return area;
}
/**
* Defines the draw-area to which the drawable should be rendered.
*
* @param area
* the graphics.
*/
public void setArea( final Rectangle2D area ) {
this.area = area;
}
/**
* Draws the drawable.
*/
public void run() {
try {
final Component component = getComponent();
if ( component instanceof Window ) {
final Window w = (Window) component;
w.validate();
} else if ( isOwnPeerConnected() ) {
final Window w = ComponentDrawable.getWindowAncestor( component );
if ( w != null ) {
w.validate();
}
} else {
peerSupply.pack();
contentPane.add( component );
}
component.setBounds( (int) area.getX(), (int) area.getY(), (int) area.getWidth(), (int) area.getHeight() );
component.validate();
component.paint( graphics );
} finally {
cleanUp();
}
}
}
/**
* A runnable that queries the preferred size. As this may involve font-computations, this has to be done on the
* EventDispatcher thread.
*/
private class PreferredSizeRunnable implements Runnable {
/**
* The return value.
*/
private Dimension retval;
/**
* Default Constructor.
*/
protected PreferredSizeRunnable() {
}
/**
* Returns the dimension that has been computed.
*
* @return the preferred size.
*/
public Dimension getRetval() {
return retval;
}
/**
* Computes the preferred size on the EventDispatcherThread.
*/
public void run() {
retval = null;
try {
final Component component = getComponent();
if ( component instanceof Window == false && isOwnPeerConnected() == false ) {
peerSupply.pack();
contentPane.add( component );
contentPane.validate();
component.validate();
} else if ( isOwnPeerConnected() ) {
retval = component.getSize();
return;
} else {
component.validate();
}
retval = component.getPreferredSize();
} finally {
cleanUp();
}
}
}
/**
* A runnable that queries the defined size. As this may involve font-computations, this has to be done on the
* EventDispatcher thread.
*/
private class DefinedSizeRunnable implements Runnable {
/**
* The return value.
*/
private Dimension retval;
/**
* Default Constructor.
*/
protected DefinedSizeRunnable() {
}
/**
* Returns the dimension that has been computed.
*
* @return the declared size.
*/
public Dimension getRetval() {
return retval;
}
/**
* Computes the declared size on the EventDispatcherThread.
*/
public void run() {
retval = null;
try {
final Component component = getComponent();
if ( component instanceof Window == false && isOwnPeerConnected() == false ) {
peerSupply.pack();
contentPane.add( component );
}
retval = component.getSize();
} finally {
cleanUp();
}
}
}
/**
* A flag indicating whether the aspect ratio should be preserved by the layouter.
*/
private boolean preserveAspectRatio;
/**
* The component that should be drawn.
*/
private Component component;
/**
* The Frame that connects the component to the native Window-System.
*/
private JFrame peerSupply;
/**
* The content pane of the frame.
*/
private JPanel contentPane;
/**
* The runnable that paints the component.
*/
private PainterRunnable runnable;
/**
* The runnable that computes the preferred size.
*/
private PreferredSizeRunnable preferredSizeRunnable;
/**
* The runnable that computes the declared size.
*/
private DefinedSizeRunnable sizeRunnable;
/**
* A flag indicating whether all paint-operations should be performed on the Event-Dispatcher thread.
*/
private boolean paintSynchronized;
/**
* A flag indicating whether components are allowed to provide their own AWT-Window.
*/
private boolean allowOwnPeer;
/**
* Default Constructor.
*/
public ComponentDrawable() {
this( new JFrame() );
}
/**
* Creates a new ComponentDrawable with the given Frame as peer-supply.
*
* @param peerSupply
* the frame that should be used as peer-source.
*/
public ComponentDrawable( final JFrame peerSupply ) {
if ( peerSupply == null ) {
throw new NullPointerException();
}
this.peerSupply = peerSupply;
this.contentPane = new JPanel();
this.contentPane.setLayout( null );
peerSupply.setContentPane( contentPane );
this.runnable = new PainterRunnable();
this.preferredSizeRunnable = new PreferredSizeRunnable();
this.sizeRunnable = new DefinedSizeRunnable();
}
/**
* Returns, whether components are allowed to provide their own AWT-Window.
*
* @return true, if foreign peers are allowed, false otherwise.
*/
public boolean isAllowOwnPeer() {
return allowOwnPeer;
}
/**
* Defines, whether components are allowed to provide their own AWT-Window.
*
* @param allowOwnPeer
* true, if components can provide their own peers, false otherwise.
*/
public void setAllowOwnPeer( final boolean allowOwnPeer ) {
this.allowOwnPeer = allowOwnPeer;
}
/**
* Returns, whether component operations will happen on the Event-Dispatcher threads. As the AWT is not synchronized,
* weird things can happen if AWT functionality is executed on user threads.
*
* @return true, if all operations will be done on the AWT-EventDispatcher thread, false if they should be done in the
* local thread.
*/
public boolean isPaintSynchronized() {
return paintSynchronized;
}
/**
* Defines, whether component operations will happen on the Event-Dispatcher threads. As the AWT is not synchronized,
* weird things can happen if AWT functionality is executed on user threads.
*
* @param paintSynchronized
* true, if all operations will be done on the AWT-EventDispatcher thread, false if they should be done in
* the local thread.
*/
public void setPaintSynchronized( final boolean paintSynchronized ) {
this.paintSynchronized = paintSynchronized;
}
/**
* A helper method that performs some cleanup and disconnects the component from the AWT and the Swing-Framework to
* avoid memory-leaks.
*/
protected final void cleanUp() {
if ( component instanceof JComponent && isOwnPeerConnected() == false ) {
final JComponent jc = (JComponent) component;
RepaintManager.currentManager( jc ).removeInvalidComponent( jc );
RepaintManager.currentManager( jc ).markCompletelyClean( jc );
}
contentPane.removeAll();
RepaintManager.currentManager( contentPane ).removeInvalidComponent( contentPane );
RepaintManager.currentManager( contentPane ).markCompletelyClean( contentPane );
peerSupply.dispose();
}
/**
* Returns the component that should be drawn.
*
* @return the component.
*/
public Component getComponent() {
return component;
}
/**
* Defines the component that should be drawn.
*
* @param component
* the component.
*/
public void setComponent( final Component component ) {
this.component = component;
prepareComponent( component );
}
/**
* Returns the preferred size of the drawable. If the drawable is aspect ratio aware, these bounds should be used to
* compute the preferred aspect ratio for this drawable.
* <p/>
* This calls {@link java.awt.Component#getPreferredSize()} on the given component.
*
* @return the preferred size.
*/
public Dimension getPreferredSize() {
if ( component == null ) {
return new Dimension( 0, 0 );
}
if ( SwingUtilities.isEventDispatchThread() || paintSynchronized == false ) {
preferredSizeRunnable.run();
return preferredSizeRunnable.getRetval();
}
try {
SwingUtilities.invokeAndWait( preferredSizeRunnable );
return preferredSizeRunnable.getRetval();
} catch ( Exception e ) {
ComponentDrawable.logger.warn( "Failed to compute the preferred size." );
}
return new Dimension( 0, 0 );
}
/**
* Returns the declared size of the drawable. If the drawable is aspect ratio aware, these bounds should be used to
* compute the declared aspect ratio for this drawable.
* <p/>
* This calls {@link java.awt.Component#getSize()} on the given component.
*
* @return the preferred size.
*/
public Dimension getSize() {
if ( component == null ) {
return new Dimension( 0, 0 );
}
if ( SwingUtilities.isEventDispatchThread() || paintSynchronized == false ) {
sizeRunnable.run();
return sizeRunnable.getRetval();
}
try {
SwingUtilities.invokeAndWait( sizeRunnable );
return sizeRunnable.getRetval();
} catch ( Exception e ) {
ComponentDrawable.logger.warn( "Failed to compute the defined size." );
}
return new Dimension( 0, 0 );
}
/**
* A private helper method that checks, whether the component provides an own peer.
*
* @return true, if the component has an own peer, false otherwise.
*/
protected final boolean isOwnPeerConnected() {
if ( allowOwnPeer == false ) {
return false;
}
final Window windowAncestor = ComponentDrawable.getWindowAncestor( component );
return ( windowAncestor != null && windowAncestor != peerSupply );
}
/**
* A private helper method that locates the Window to which the component is currently added.
*
* @param component
* the component for which the Window should be returned.
* @return the AWT-Window that is the (possibly indirect) parent of this component.
*/
protected static Window getWindowAncestor( final Component component ) {
Component parent = component.getParent();
while ( parent != null ) {
if ( parent instanceof Window ) {
return (Window) parent;
}
parent = parent.getParent();
}
return null;
}
/**
* Defines whether the layouter should preserve the aspect ratio of the component's preferred size.
*
* @param preserveAspectRatio
* true, if the aspect ratio should be preserved, false otherwise.
*/
public void setPreserveAspectRatio( final boolean preserveAspectRatio ) {
this.preserveAspectRatio = preserveAspectRatio;
}
/**
* Returns true, if this drawable will preserve an aspect ratio during the drawing.
*
* @return true, if an aspect ratio is preserved, false otherwise.
*/
public boolean isPreserveAspectRatio() {
return preserveAspectRatio;
}
/**
* Draws the component.
*
* @param g2
* the graphics device.
* @param area
* the area inside which the object should be drawn.
*/
public void draw( final Graphics2D g2, final Rectangle2D area ) {
if ( component == null ) {
return;
}
runnable.setArea( area );
runnable.setGraphics( g2 );
if ( SwingUtilities.isEventDispatchThread() || paintSynchronized == false ) {
runnable.run();
} else {
try {
SwingUtilities.invokeAndWait( runnable );
} catch ( Exception e ) {
ComponentDrawable.logger.warn( "Failed to redraw the component." );
}
}
}
/**
* Prepares the component for drawing. This recursively disables the double-buffering as this would interfere with the
* drawing.
*
* @param c
* the component that should be prepared.
*/
private void prepareComponent( final Component c ) {
if ( c instanceof JComponent ) {
final JComponent jc = (JComponent) c;
jc.setDoubleBuffered( false );
final Component[] childs = jc.getComponents();
for ( int i = 0; i < childs.length; i++ ) {
final Component child = childs[i];
prepareComponent( child );
}
}
}
}