/*!
* 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 java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Rectangle2D;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.EventListenerList;
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;
/**
* @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;
}
}