/*!
* 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) 2002-2013 Pentaho Corporation.. All rights reserved.
*/
package org.pentaho.reporting.designer.core.editor.report.layouting;
import org.pentaho.reporting.designer.core.ReportDesignerBoot;
import org.pentaho.reporting.designer.core.ReportDesignerContext;
import org.pentaho.reporting.designer.core.editor.ReportDocumentContext;
import org.pentaho.reporting.designer.core.editor.report.DesignerPageDrawable;
import org.pentaho.reporting.designer.core.model.ModelUtility;
import org.pentaho.reporting.designer.core.model.lineal.LinealModel;
import org.pentaho.reporting.designer.core.util.BreakPositionsList;
import org.pentaho.reporting.designer.core.util.Unit;
import org.pentaho.reporting.engine.classic.core.Element;
import org.pentaho.reporting.engine.classic.core.Section;
import org.pentaho.reporting.engine.classic.core.designtime.AttributeChange;
import org.pentaho.reporting.engine.classic.core.event.ReportModelEvent;
import org.pentaho.reporting.engine.classic.core.event.ReportModelListener;
import org.pentaho.reporting.engine.classic.core.filter.types.bands.ItemBandType;
import org.pentaho.reporting.engine.classic.core.filter.types.bands.ReportFooterType;
import org.pentaho.reporting.engine.classic.core.filter.types.bands.ReportHeaderType;
import org.pentaho.reporting.engine.classic.core.layout.model.LogicalPageBox;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderNode;
import org.pentaho.reporting.engine.classic.core.layout.output.OutputProcessorMetaData;
import org.pentaho.reporting.engine.classic.core.metadata.ElementType;
import org.pentaho.reporting.engine.classic.core.util.InstanceID;
import org.pentaho.reporting.engine.classic.core.util.geom.StrictBounds;
import org.pentaho.reporting.engine.classic.core.util.geom.StrictGeomUtility;
import org.pentaho.reporting.libraries.resourceloader.ResourceManager;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.EventListenerList;
import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
/**
* @author Thomas Morgner
*/
public abstract class AbstractElementRenderer implements ElementRenderer {
private class VisualHeightUpdateListener implements ReportModelListener {
private VisualHeightUpdateListener() {
}
public void nodeChanged( final ReportModelEvent event ) {
if ( event.getElement() != element ) {
if ( event.getParameter() instanceof AttributeChange ) {
final AttributeChange attributeChange = (AttributeChange) event.getParameter();
if ( ReportDesignerBoot.DESIGNER_NAMESPACE.equals( attributeChange.getNamespace() ) &&
ReportDesignerBoot.VISUAL_HEIGHT.equals( attributeChange.getName() ) ) {
fireChangeEvent();
}
}
}
}
}
private class SharedLayoutUpdateHandler implements ChangeListener {
private SharedLayoutUpdateHandler() {
}
public void stateChanged( final ChangeEvent e ) {
refreshLayoutFromSharedRenderer();
}
}
private final AbstractElementRenderer.SharedLayoutUpdateHandler sharedLayoutUpdateHandler;
private SharedElementRenderer sharedRenderer;
private Section element;
private ReportDocumentContext reportRenderContext;
private EventListenerList listenerList;
private Rectangle2D computedBounds;
private BreakPositionsList verticalEdgePositions;
private DesignerPageDrawable logicalPageDrawable;
private ResourceManager resourceManager;
private Map<InstanceID, Element> elementsById;
protected AbstractElementRenderer( final Section element,
final ReportDocumentContext reportRenderContext ) {
if ( element == null ) {
throw new NullPointerException();
}
if ( reportRenderContext == null ) {
throw new NullPointerException();
}
this.sharedLayoutUpdateHandler = new AbstractElementRenderer.SharedLayoutUpdateHandler();
this.sharedRenderer = reportRenderContext.getSharedRenderer();
this.sharedRenderer.addChangeListener( sharedLayoutUpdateHandler );
this.element = element;
this.reportRenderContext = reportRenderContext;
this.elementsById = new HashMap<InstanceID, Element>();
this.listenerList = new EventListenerList();
this.verticalEdgePositions = new BreakPositionsList();
this.resourceManager = reportRenderContext.getResourceManager();
reportRenderContext.getReportDefinition().addReportModelListener( new VisualHeightUpdateListener() );
final Object d = element.getAttribute( ReportDesignerBoot.DESIGNER_NAMESPACE, ReportDesignerBoot.VISUAL_HEIGHT );
if ( d instanceof Double == false ) {
if ( element.getElementType() instanceof ReportHeaderType ) {
setVisualHeight( Unit.INCH.getDotsPerUnit() * 1.5 );
} else if ( element.getElementType() instanceof ReportFooterType ) {
setVisualHeight( Unit.INCH.getDotsPerUnit() * 1.5 );
} else if ( element.getElementType() instanceof ItemBandType ) {
setVisualHeight( Unit.INCH.getDotsPerUnit() * 1.5 );
} else {
setVisualHeight( Unit.INCH.getDotsPerUnit() );
}
}
}
public void dispose() {
sharedRenderer.removeChangeListener( sharedLayoutUpdateHandler );
}
public ReportDocumentContext getReportRenderContext() {
return reportRenderContext;
}
public Section getElement() {
return element;
}
public ElementType getElementType() {
return element.getElementType();
}
public InstanceID getRepresentationId() {
return element.getObjectID();
}
public void addChangeListener( final ChangeListener changeListener ) {
listenerList.add( ChangeListener.class, changeListener );
}
public void removeChangeListener( final ChangeListener changeListener ) {
listenerList.remove( ChangeListener.class, changeListener );
}
public void fireChangeEvent() {
final ChangeEvent ce = new ChangeEvent( this );
final ChangeListener[] changeListeners = listenerList.getListeners( ChangeListener.class );
for ( int i = 0; i < changeListeners.length; i++ ) {
final ChangeListener listener = changeListeners[ i ];
listener.stateChanged( ce );
}
}
public double getVisualHeight() {
final Object d = element.getAttribute( ReportDesignerBoot.DESIGNER_NAMESPACE, ReportDesignerBoot.VISUAL_HEIGHT );
if ( d instanceof Double ) {
return (Double) d;
}
return 0;
}
public void setVisualHeight( final double visualHeight ) {
if ( visualHeight < 0 ) {
throw new IllegalArgumentException();
}
final double oldHeight = getVisualHeight();
if ( visualHeight != oldHeight ) {
this.element.setAttribute
( ReportDesignerBoot.DESIGNER_NAMESPACE, ReportDesignerBoot.VISUAL_HEIGHT, visualHeight, false );
fireChangeEvent();
}
}
public boolean isHideInLayout() {
return ModelUtility.isHideInLayoutGui( element );
}
public LinealModel getVerticalLinealModel() {
return ModelUtility.getVerticalLinealModel( element );
}
public synchronized double getLayoutHeight() {
if ( computedBounds == null || sharedRenderer.isLayoutValid() == false ) {
computedBounds = performLayouting();
}
return Math.max( computedBounds.getHeight(), getVisualHeight() );
}
public synchronized void invalidateLayout() {
// Set computedBounds to null to allow performLayouting() to recalculate them.
computedBounds = null;
}
public Rectangle2D getBounds() {
if ( computedBounds == null || sharedRenderer.isLayoutValid() == false ) {
computedBounds = performLayouting();
}
return new Rectangle2D.Double( 0, computedBounds.getY(), computedBounds.getWidth(),
Math.max( computedBounds.getHeight(), getVisualHeight() ) );
}
public StrictBounds getRootElementBounds() {
if ( logicalPageDrawable == null ) {
return new StrictBounds();
}
return (StrictBounds) logicalPageDrawable.getRootElementBounds().clone();
}
protected Rectangle2D performLayouting() {
if ( sharedRenderer.performLayouting() ) {
fireChangeEvent();
if ( computedBounds == null ) {
refreshLayoutFromSharedRenderer();
}
return computedBounds;
} else {
logicalPageDrawable = null;
fireChangeEvent();
return new Rectangle2D.Double();
}
}
private void refreshLayoutFromSharedRenderer() {
final LogicalPageBox pageBox = sharedRenderer.getPageBox();
if ( pageBox == null ) {
computedBounds = sharedRenderer.getFallbackBounds();
return;
}
elementsById.clear();
sharedRenderer.transferLocalLayout( getElement(), elementsById, verticalEdgePositions );
final OutputProcessorMetaData outputProcessorMetaData = sharedRenderer.getLayouter().getOutputProcessorMetaData();
logicalPageDrawable = new DesignerPageDrawable( pageBox, outputProcessorMetaData, resourceManager, element );
final StrictBounds bounds = logicalPageDrawable.getRootElementBounds();
computedBounds = StrictGeomUtility.createAWTRectangle( 0, bounds.getY(), pageBox.getWidth(), bounds.getHeight() );
if ( getVisualHeight() < computedBounds.getHeight() ) {
setVisualHeight( computedBounds.getHeight() );
}
}
public boolean draw( final Graphics2D graphics2D ) {
// this also computes the pagebox.
final Rectangle2D bounds1 = getBounds();
if ( logicalPageDrawable == null ) {
return false;
}
final Graphics2D graphics = (Graphics2D) graphics2D.create();
graphics.setRenderingHint( RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON );
graphics.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON );
logicalPageDrawable.draw( graphics, bounds1 );
graphics.dispose();
return true;
}
public void handleError( final ReportDesignerContext designerContext, final ReportDocumentContext reportContext ) {
if ( sharedRenderer.isMigrationError() ) {
SwingUtilities.invokeLater
( new MigrateReportTask( designerContext, reportContext, sharedRenderer.getMinimumVersionNeeded() ) );
sharedRenderer.clearMigrationError();
}
}
public BreakPositionsList getHorizontalEdgePositions() {
return sharedRenderer.getHorizontalEdgePositions();
}
public long[] getHorizontalEdgePositionKeys() {
return getHorizontalEdgePositions().getKeys();
}
public BreakPositionsList getVerticalEdgePositions() {
return verticalEdgePositions;
}
public Element[] getElementsAt( final double x, final double y, final double width, final double height ) {
if ( logicalPageDrawable == null ) {
return new Element[ 0 ];
}
final RenderNode[] nodes = logicalPageDrawable.getNodesAt( x, y, width, height, null, null );
if ( nodes.length == 0 ) {
return new Element[ 0 ];
}
final LinkedHashSet<Element> elements = new LinkedHashSet<Element>( nodes.length );
for ( int i = 0; i < nodes.length; i++ ) {
final RenderNode node = nodes[ i ];
final Element reportElement = elementsById.get( node.getInstanceId() );
if ( reportElement != null ) {
elements.add( reportElement );
}
}
return elements.toArray( new Element[ elements.size() ] );
}
public Element[] getElementsAt( final double x, final double y ) {
if ( logicalPageDrawable == null ) {
return new Element[ 0 ];
}
final RenderNode[] nodes = logicalPageDrawable.getNodesAt( x, y, null, null );
if ( nodes.length == 0 ) {
return new Element[ 0 ];
}
final LinkedHashSet<Element> elements = new LinkedHashSet<Element>( nodes.length );
for ( int i = 0; i < nodes.length; i++ ) {
final RenderNode node = nodes[ i ];
final Element reportElement = elementsById.get( node.getInstanceId() );
if ( reportElement != null ) {
elements.add( reportElement );
}
}
return elements.toArray( new Element[ elements.size() ] );
}
protected DesignerPageDrawable getLogicalPageDrawable() {
return logicalPageDrawable;
}
}