/* 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.Dimension; import java.awt.print.PageFormat; import java.awt.print.Pageable; import java.awt.print.Printable; import java.io.IOException; import java.io.Writer; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import com.servoy.j2db.IApplication; import com.servoy.j2db.persistence.Part; import com.servoy.j2db.util.Debug; import com.servoy.j2db.util.RendererParentWrapper; import com.servoy.j2db.util.SafeArrayList; /** * The object holding all the pages and being pageble * * @author jblok */ public class PageList implements Pageable { private final IApplication application; private final List pages; private final DataRendererDefinition[] dataRendererDefinitions;//default static renderers for all pages private PageDefinition currentPageDefinition; private final Dimension size; private final IPrintInfo printInfo; private final RendererParentWrapper renderParent; private final Map occurrenceBreak; //part->count private boolean restartPageNumbers; private int normalPageBodyAreaHeight; public PageList(IApplication app, IPrintInfo pp, RendererParentWrapper renderParent) { application = app; printInfo = pp; pages = new ArrayList(); size = new Dimension((int)(printInfo.getPageFormat().getImageableWidth() * (1 / printInfo.getZoomFactor())), (int)(printInfo.getPageFormat().getImageableHeight() * (1 / printInfo.getZoomFactor()))); normalPageBodyAreaHeight = size.height; dataRendererDefinitions = new DataRendererDefinition[11]; this.renderParent = renderParent; occurrenceBreak = new HashMap(); } public void setNonRepeatingPart(int type, DataRendererDefinition rendererDef) { if (rendererDef.getPart() != null && rendererDef.getPart().getPartType() != Part.BODY) { rendererDef.setFixedSize(new Dimension(size.width, rendererDef.getSize().height)); //set to page width for background and such } dataRendererDefinitions[type] = rendererDef; // update normalPageBodyAreaHeight if necessary if (type == Part.HEADER) { normalPageBodyAreaHeight = size.height - rendererDef.getSize().height; if (dataRendererDefinitions[Part.FOOTER] != null) { normalPageBodyAreaHeight -= dataRendererDefinitions[Part.FOOTER].getSize().getHeight(); } } else if (type == Part.FOOTER) { normalPageBodyAreaHeight = size.height - rendererDef.getSize().height; if (dataRendererDefinitions[Part.HEADER] != null) { normalPageBodyAreaHeight -= dataRendererDefinitions[Part.HEADER].getSize().getHeight(); } } } //pageble implementation public PageFormat getPageFormat(int pageIndex) { return printInfo.getPageFormat(); } public Printable getPrintable(int pageIndex) { return getPage(pageIndex); } public int getPageSectionLength(PageDefinition pd) { int idx = pages.indexOf(pd); // find the section of the given page int previousRestartIndex = 0; int nextRestartIndex = pages.size(); for (int i = idx; i > previousRestartIndex; i--) { Integer in = (Integer)restartList.get(i); if (in != null) { previousRestartIndex = i; } } for (int i = idx + 1; i < nextRestartIndex; i++) { Integer in = (Integer)restartList.get(i); if (in != null) { nextRestartIndex = i; } } return nextRestartIndex - previousRestartIndex; } public int getNumberOfPages() { return pages.size(); } public int getPageNumber(PageDefinition pd) { int idx = pages.indexOf(pd); for (int i = idx; i >= 0; i--) { Integer in = (Integer)restartList.get(i); if (in != null) { return (idx - i) + 1; } } return idx + 1; } private final List restartList = new SafeArrayList(); public void restartPageNumber(PageDefinition pd) { restartList.set(pages.indexOf(pd), new Integer(0)); } public PageDefinition getPage(int p) { if (pages.size() != 0) { return (PageDefinition)pages.get(p); } else { return null; } } //do smart adding to corect page(s) etc public void addPanel(DataRendererDefinition rendererDef) { addPanel(rendererDef, true); } public void addPanel(DataRendererDefinition rendererDef, boolean firstPageOfRenderer) { if (currentPageDefinition == null) createNewPageDefinition(); if (rendererDef.getDataRenderer() == null) return;//is empty body if (rendererDef.getPart() != null && rendererDef.getPart().getPartType() != Part.BODY) { rendererDef.setFixedSize(new Dimension(size.width, rendererDef.getSize().height)); //set to page width for background and such } if ((currentPageDefinition.areNormalPanelsAdded() || pages.size() == 0) && rendererDef.getPageBreakBefore()) createNewPageDefinition(); // first page could leave very little space left after adding header/titleheader/footer/grandLeadingSummary so allow it to show even without normal panel added currentPageDefinition.seeWhereThePanelWillBeAdded(rendererDef); int left = currentPageDefinition.getHeightLeft(); int panelHeigth = rendererDef.getFullSize().height; if (left > 5 && (panelHeigth <= left || rendererDef.getAllowBreakAcrossPageBounds()))//check if leftover to big enough { DataRendererDefinition rendererDefClone = null; int oldYOrgin = rendererDef.getStartYOrgin(); if (panelHeigth - oldYOrgin > left) { if (!rendererDef.getDiscardRemainderAfterBreak()) { try { rendererDefClone = (DataRendererDefinition)rendererDef.clone(); } catch (Exception e) { Debug.error(e); } } int newLeft = rendererDef.getPreferedBreak(renderParent, left, normalPageBodyAreaHeight); if (newLeft <= 0) { Debug.warn("No nice page break location found so as not to split form components in two..."); //$NON-NLS-1$ } else { left = newLeft; } rendererDef.setFixedSize(new Dimension(rendererDef.getSize().width, left)); } currentPageDefinition.addPanel(rendererDef); if (!restartPageNumbers && firstPageOfRenderer && rendererDef.getRestartPageNumber()) { restartPageNumbers = true; // after all panels are added to a page definition, we will remember if that page resets the page numbers or not } if ((panelHeigth - oldYOrgin) > left) { createNewPageDefinition(); if (rendererDefClone != null) { int newStartYOrgin = oldYOrgin + left; rendererDefClone.setStartYOrgin(newStartYOrgin); rendererDefClone.setFixedSize(new Dimension(rendererDef.getSize().width, panelHeigth - newStartYOrgin)); addPanel(rendererDefClone, false); } } else { doOccurrenceBreak(rendererDef); } } else { // first page could leave very little space left after adding header/titleheader/footer/grandLeadingSummary so allow it to show even without normal panel added (else we might force this panel in a few pixels of space just so that we have normal panels added...) if (currentPageDefinition.areNormalPanelsAdded() || pages.size() == 0) { createNewPageDefinition(); } if (currentPageDefinition.getHeightLeft() > 5 && (panelHeigth <= currentPageDefinition.getHeightLeft() || rendererDef.getAllowBreakAcrossPageBounds())) // check this in order to avoid stack overflow { // normal case addPanel(rendererDef); } else { // not normal case; page height is < 5 pixels? strange currentPageDefinition.addPanel(rendererDef); if (!restartPageNumbers && firstPageOfRenderer && rendererDef.getRestartPageNumber()) { restartPageNumbers = true; // after all panels are added to a page definition, we will remember if that page resets the page numbers or not } } doOccurrenceBreak(rendererDef); } } private void doOccurrenceBreak(DataRendererDefinition rendererDef) { //do page break occurence int pbo = rendererDef.getPageBreakAfterOccurrence(); if (pbo > 0) { Part part = rendererDef.getPart(); if (part != null) { Integer count = (Integer)occurrenceBreak.get(part); if (count == null) count = new Integer(1);//already one is added if (count.intValue() == pbo) { createNewPageDefinition(); count = new Integer(0); } occurrenceBreak.put(part, new Integer(count.intValue() + 1)); } } } //create a new page well initialized with static partrenders private void createNewPageDefinition() { if (currentPageDefinition != null) { addCurrentPageDefinition(); currentPageDefinition = null; } if (currentPageDefinition == null) { try { currentPageDefinition = new PageDefinition(application, printInfo, renderParent, size); // Debug.trace("Creating page "+pages.size()); if (pages.size() == 0) { if (dataRendererDefinitions[Part.TITLE_HEADER] != null) { currentPageDefinition.addHeaderPanel(dataRendererDefinitions[Part.TITLE_HEADER]); } else { if (dataRendererDefinitions[Part.HEADER] != null) currentPageDefinition.addHeaderPanel((DataRendererDefinition)dataRendererDefinitions[Part.HEADER].clone()); } if (dataRendererDefinitions[Part.TITLE_FOOTER] != null) { currentPageDefinition.addFooterPanel(dataRendererDefinitions[Part.TITLE_FOOTER]); } else { if (dataRendererDefinitions[Part.FOOTER] != null) currentPageDefinition.addFooterPanel((DataRendererDefinition)dataRendererDefinitions[Part.FOOTER].clone()); } if (dataRendererDefinitions[Part.LEADING_GRAND_SUMMARY] != null) { addPanel(dataRendererDefinitions[Part.LEADING_GRAND_SUMMARY]); // allow it to fill more pages if needed; add it after header/footer as it might create new pages itself and the current page def. should be ready for that } if (pages.size() == 0 && // this means that LEADING_GRAND_SUMMARY didn't occupy more then one page ((dataRendererDefinitions[Part.TITLE_HEADER] != null && dataRendererDefinitions[Part.TITLE_HEADER].getPageBreakAfterOccurrence() == 1) || (dataRendererDefinitions[Part.TITLE_FOOTER] != null && dataRendererDefinitions[Part.TITLE_FOOTER].getPageBreakAfterOccurrence() == 1))) { // first page is not meant to contain any more data in this case... createNewPageDefinition(); } } else { if (dataRendererDefinitions[Part.HEADER] != null) currentPageDefinition.addHeaderPanel((DataRendererDefinition)dataRendererDefinitions[Part.HEADER].clone()); if (dataRendererDefinitions[Part.FOOTER] != null) currentPageDefinition.addFooterPanel((DataRendererDefinition)dataRendererDefinitions[Part.FOOTER].clone()); } } catch (CloneNotSupportedException e) { Debug.error(e); } } } //must be called after being done with processing chain public void finish() { if (currentPageDefinition == null) createNewPageDefinition(); if (dataRendererDefinitions[Part.TRAILING_GRAND_SUMMARY] != null) { addPanel(dataRendererDefinitions[Part.TRAILING_GRAND_SUMMARY]); } if (currentPageDefinition.areNormalPanelsAdded()) { addCurrentPageDefinition(); } currentPageDefinition = null; for (int i = 0; i < pages.size(); i++) { ((PageDefinition)pages.get(i)).handleSink(); } } private void addCurrentPageDefinition() { pages.add(currentPageDefinition); // not see it it has any "reset page numbers" part if (restartPageNumbers) { restartPageNumber(currentPageDefinition); restartPageNumbers = false; } } @Override public String toString() { StringBuffer sb = new StringBuffer(); sb.append("----#start list#----\n"); //$NON-NLS-1$ for (int i = 0; i < pages.size(); i++) { sb.append("\n"); //$NON-NLS-1$ sb.append("page "); //$NON-NLS-1$ sb.append(i); sb.append("\n"); //$NON-NLS-1$ sb.append(pages.get(i)); } sb.append("----#end list#----\n"); //$NON-NLS-1$ return sb.toString(); } public void toXML(Writer w) throws IOException { w.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"); //$NON-NLS-1$ w.write("<SERVOYREPORT version=\"1\" >"); //$NON-NLS-1$ w.write("<PAGES count=\"" + pages.size() + "\" >"); //$NON-NLS-1$ //$NON-NLS-2$ for (int i = 0; i < pages.size(); i++) { ((PageDefinition)pages.get(i)).toXML(w); } w.write("</PAGES>"); //$NON-NLS-1$ w.write("</SERVOYREPORT>"); //$NON-NLS-1$ } }