/*
This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your option) any
later version.
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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along
with this program; if not, see http://www.gnu.org/licenses or write to the Free
Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
*/
package com.servoy.j2db.printing;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.swing.RepaintManager;
import com.servoy.j2db.IApplication;
import com.servoy.j2db.dataprocessing.IRecordInternal;
import com.servoy.j2db.persistence.Part;
import com.servoy.j2db.smart.dataui.DataRenderer;
import com.servoy.j2db.util.Debug;
import com.servoy.j2db.util.PersistHelper;
import com.servoy.j2db.util.RendererParentWrapper;
import com.servoy.j2db.util.Utils;
/**
* Represents one page which is printable
*
* @author jblok
*/
public class PageDefinition implements Printable
{
private final IApplication application;
private final List<DataRendererDefinition> panels;
private final Dimension size;
private int heightLeft;
private int nextYPosition = 0;
private int nextXPosition = 0;
private int skipOnY = 0;
private int insetIndex;
private final IPrintInfo printInfo;
private final RendererParentWrapper renderParent;
PageDefinition(IApplication app, IPrintInfo pp, RendererParentWrapper renderParent, Dimension d)
{
application = app;
printInfo = pp;
panels = new ArrayList<DataRendererDefinition>();
size = d;
heightLeft = size.height;
this.renderParent = renderParent;
}
public int getHeightLeft()
{
return heightLeft;
}
public int print(Graphics g, PageFormat pf, int pageNumber)
{
int retval;
try
{
application.getRuntimeProperties().put("isPrinting", Boolean.TRUE); //$NON-NLS-1$
application.getRuntimeProperties().put("printGraphics", g); //$NON-NLS-1$
//set util flag for use in printing
application.getScriptEngine().getJSApplication().setDidLastPrintPreviewPrint(true);
//start actual printing
retval = printPage(g, pf);
}
finally
{
application.getRuntimeProperties().put("printGraphics", null); //$NON-NLS-1$
application.getRuntimeProperties().put("isPrinting", null); //$NON-NLS-1$
}
return retval;
}
int printPage(Graphics g, PageFormat pf)
{
Graphics2D graphics2D = (Graphics2D)g;
double f = printInfo.getZoomFactor();
double imx = pf.getImageableX() * (1 / f);
double imy = pf.getImageableY() * (1 / f);
double imw = pf.getImageableWidth() * (1 / f);
double imh = pf.getImageableHeight() * (1 / f);
Rectangle saveClip = graphics2D.getClipBounds();
Rectangle newClip = new Rectangle((int)imx, (int)imy, (int)imw, (int)imh);
AffineTransform at = new AffineTransform();
boolean isPrinting = Utils.getAsBoolean(application.getRuntimeProperties().get("isPrinting")); //$NON-NLS-1$
if (isPrinting)
{
at.scale(f, f);
if (f < 1)
{
newClip = new Rectangle((int)(pf.getImageableX() * f), (int)(pf.getImageableY() * f), (int)(pf.getImageableWidth() * (1 / f)),
(int)(pf.getImageableHeight() * (1 / f)));
}
else
{
newClip = new Rectangle((int)(pf.getImageableX() * (1 / f)), (int)(pf.getImageableY() * (1 / f)), (int)(pf.getImageableWidth() * f),
(int)(pf.getImageableHeight() * f));
}
}
at.translate((int)imx, (int)imy);
Rectangle result = newClip.intersection(saveClip);
graphics2D.setClip(result);//keep within im.
AffineTransform save = graphics2D.getTransform();
graphics2D.transform(at);//do transformation
RepaintManager currentManager = RepaintManager.currentManager(renderParent.getParent());
boolean isDoubleBufferingEnabled = currentManager.isDoubleBufferingEnabled();
Object oldPrintState = application.getRuntimeProperties().get("isPrinting"); //$NON-NLS-1$
try
{
System.setProperty("component.isprinting", "true"); //done for Kunststoff LAF (disables bg shading) //$NON-NLS-1$ //$NON-NLS-2$
application.getRuntimeProperties().put("isPrinting", Boolean.TRUE); // show high resolution images in print preview also (with zoom), not only when really printing (main idea is to get the closest look possible to the real printed material) //$NON-NLS-1$
currentManager.setDoubleBufferingEnabled(false);
//-------start rendering------------------------------------
render(renderParent, graphics2D);
//----------end rendering------------------------------------
}
catch (Exception e)
{
Debug.error(e);
}
finally
{
application.getRuntimeProperties().put("isPrinting", oldPrintState); //$NON-NLS-1$
currentManager.setDoubleBufferingEnabled(isDoubleBufferingEnabled);
System.setProperty("component.isprinting", ""); //done for Kunststoff LAF (disables bg shading) //$NON-NLS-1$//$NON-NLS-2$
graphics2D.setTransform(save);
graphics2D.setClip(saveClip);
printInfo.flushCachedData();
}
return Printable.PAGE_EXISTS;
}
private void render(RendererParentWrapper c, Graphics2D graphics2D)
{
Rectangle clip = graphics2D.getClipBounds();
boolean didClip = false;
graphics2D.setColor(Color.black);
Iterator<DataRendererDefinition> it = panels.iterator();
while (it.hasNext())
{
DataRendererDefinition drd = it.next();
DataRenderer partpane = drd.getDataRenderer();
if (partpane == null) continue;//is empty body
int Y = drd.getYlocation() - drd.getStartYOrgin();
int X = drd.getXlocation();
try
{
if (drd.getPart() != null && drd.getPart().getPartType() != Part.LEADING_GRAND_SUMMARY &&
drd.getPart().getPartType() != Part.TRAILING_GRAND_SUMMARY)//setting the state in grand summary clears the grand sum
{
IRecordInternal state = drd.getState();
if (state instanceof PageNumberState)
{
((PageNumberState)state).initPagePositionAndSize(this);
}
}
if (drd.getFullSize().height != drd.getSize().height || drd.getStartYOrgin() != 0)
{
Rectangle newClip = new Rectangle(clip);//clone
//System.out.println("Y "+Y+" ,Ylocation "+drd.getYlocation());
newClip.y = Math.max(Y, drd.getYlocation());
newClip.height = drd.getSize().height;//Math.min(drd.getSize().height,clip.height);//only allow smaller
graphics2D.setClip(newClip.intersection(clip));
didClip = true;
}
else
{
didClip = false;
}
//System.out.println("drd "+drd+" didClip "+didClip);
//translate and scale does effect clipping !!!!!!!!
graphics2D.translate(X, Y);//set the right position (the component paint ALWAY fom its 0,0 position, becouse of the absulut positioning inside it)
//only needed to get it painted (position is irrelevant)
partpane.setBounds(0, 0, drd.getSize().width, drd.getFullSize().height);
//needed for correct drawing
drd.tempAddToParent(c, true, false, false);//this does also fill the renderer again with record data, must do also to make the summaries work
//draw the pane
drd.printAll(graphics2D);
}
finally
{
//needed for correct drawing
drd.tempRemoveFromParent(c);//remove imm.
//correct translate back
graphics2D.translate(-X, -Y);
//restore
if (didClip) graphics2D.setClip(clip);
}
}
}
private boolean normalPanelsAdded = false;
boolean areNormalPanelsAdded()
{
return normalPanelsAdded;
}
// see if panel can be added aside or it will be added below; we must recalculate the panel's height
// for each of these scenarios, because the location of the panel may determine certain "grow" width and height
// fields to be cut by the page margins - and then they grow downwards to show their data
void seeWhereThePanelWillBeAdded(DataRendererDefinition panel)
{
if (nextXPosition != 0)
{
// see if it fits aside; if it does then add it and compute new nextXPosition/skipNextYPosition;
// if it doesn't fit aside calculate the next x=0 location where it will be added
Dimension panelSize = panel.getSize();
if (nextXPosition + panelSize.width > size.width)
{
// try to see if the width that is too large is not determined by growing fields;
// maybe, knowing an available width, the panel will be able to change it's size to fit
Dimension newSize = panel.computeNewWidthForXLocation(renderParent, nextXPosition, false);
if ((nextXPosition + newSize.width > size.width) || (newSize.height > heightLeft))
{
// it really does not fit aside... so move it below
nextXPosition = 0;
nextYPosition += skipOnY;
heightLeft -= skipOnY;
skipOnY = 0;
}
else
{
// will be put aside; so tell the panel to use the new size
panel.computeNewWidthForXLocation(renderParent, nextXPosition, true);
}
} // else it fits without trying to resize it
}
if (nextXPosition == 0)
{
// it will be added below the currently used position (it was not added aside...)
// calculate it's height & more (manage changes produced by growing field limitation to page width)
panel.computeNewWidthForXLocation(renderParent, 0, true);
}
}
void addPanel(DataRendererDefinition panel)
{
normalPanelsAdded = true;
panels.add(insetIndex, panel);
insetIndex++;
panel.setXlocation(nextXPosition);
panel.setYlocation(nextYPosition);
// calculate the coordinates where the next panel should be added aside (if it fits)
Dimension panelSize = panel.getSize();
nextXPosition += panelSize.width;
skipOnY = Math.max(skipOnY, panelSize.height);
}
private boolean hasHeader = false;
void addHeaderPanel(DataRendererDefinition panel)
{
panels.add(0, panel);
heightLeft -= panel.getSize().height;
panel.setYlocation(nextYPosition);
nextYPosition += panel.getSize().height;
hasHeader = true;
insetIndex++;
}
void addFooterPanel(DataRendererDefinition panel)
{
panels.add(panel);
heightLeft -= panel.getSize().height;
panel.setYlocation(size.height - panel.getSize().height);
}
@Override
public String toString()
{
StringBuffer sb = new StringBuffer();
sb.append("----start page---- "); //$NON-NLS-1$
sb.append("[" + PersistHelper.createDimensionString(size) + "]"); //$NON-NLS-1$ //$NON-NLS-2$
sb.append("\n"); //$NON-NLS-1$
for (int i = 0; i < panels.size(); i++)
{
sb.append(panels.get(i));
sb.append("\n"); //$NON-NLS-1$
}
sb.append("----end page----\n"); //$NON-NLS-1$
return sb.toString();
}
public void toXML(Writer w) throws IOException
{
w.write("<PAGE>"); //$NON-NLS-1$
w.write("<PARTS count=\"" + panels.size() + "\" >"); //$NON-NLS-1$ //$NON-NLS-2$
for (int i = 0; i < panels.size(); i++)
{
DataRendererDefinition drd = panels.get(i);
IRecordInternal state = drd.getState();
if (state instanceof PageNumberState)
{
((PageNumberState)state).initPagePositionAndSize(this);
}
DataRenderer partpane = drd.getDataRenderer();
partpane.getDataAdapterList().setRecord(state, true); //fill with data
drd.toXML(w);
}
w.write("</PARTS>"); //$NON-NLS-1$
w.write("</PAGE>"); //$NON-NLS-1$
}
List<DataRendererDefinition> getPanels()
{
return panels;
}
void handleSink()
{
heightLeft -= skipOnY;
if (heightLeft > 0)
{
for (int i = panels.size() - 1; i >= 0; i--)
{
DataRendererDefinition drd = panels.get(i);
Part part = drd.getPart();
if (part != null)
{
if (part.getPartType() == Part.FOOTER || part.getPartType() == Part.TITLE_FOOTER)
{
continue;
}
if (part.getSinkWhenLast())
{
drd.setYlocation(drd.getYlocation() + heightLeft); //sink
heightLeft = 0;
}
}
break;
}
}
}
}