/* * 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 ); } } } }