/*
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.geom.AffineTransform;
import java.awt.print.PageFormat;
import java.awt.print.Pageable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import javax.swing.BorderFactory;
import javax.swing.JPanel;
import com.servoy.j2db.FormController;
import com.servoy.j2db.FormManager;
import com.servoy.j2db.IApplication;
import com.servoy.j2db.component.ComponentFactory;
import com.servoy.j2db.dataprocessing.FoundSet;
import com.servoy.j2db.dataprocessing.FoundSetManager;
import com.servoy.j2db.dataprocessing.IFoundSetInternal;
import com.servoy.j2db.dataprocessing.IRecordInternal;
import com.servoy.j2db.dataprocessing.SortColumn;
import com.servoy.j2db.persistence.Form;
import com.servoy.j2db.persistence.Part;
import com.servoy.j2db.persistence.RepositoryException;
import com.servoy.j2db.persistence.Table;
import com.servoy.j2db.query.QuerySelect;
import com.servoy.j2db.smart.dataui.DataRenderer;
import com.servoy.j2db.smart.dataui.DataRendererFactory;
import com.servoy.j2db.ui.PropertyCopy;
import com.servoy.j2db.util.Debug;
import com.servoy.j2db.util.RendererParentWrapper;
/**
* this class renders the pages in the printpreview and suplies pageble for real printing
*
* @author jblok
*/
public class FormPreviewPanel extends JPanel implements IPrintInfo
{
private final IApplication application;
private final FormController controllerBeingPreviewed;
private IFoundSetInternal formData;//NOTE this can be different than form model from formBeingPreviewed
private Map<Part, DataRenderer> part_panels;
private PageFormat currentPageFormat;
private static float zoomFactor;
private int pageNumber;//current page number
private final double factor;//scale factor (based on form printscale)
private Dimension orgWidth;
//the list of all pages
private PageList plist;
//root of processing tree, a chain is builded and processed to add (in nested loops) all renderes to pages
private PartNode root;
private RendererParentWrapper renderParent;
public FormPreviewPanel(IApplication app, FormController formController, IFoundSetInternal formData)
{
super(false);
application = app;
controllerBeingPreviewed = formController;
this.formData = formData;
currentPageFormat = controllerBeingPreviewed.getPageFormat();
if (currentPageFormat == null)
{
currentPageFormat = application.getPageFormat();
}
factor = 100d / controllerBeingPreviewed.getForm().getPaperPrintScale();
zoomFactor = (float)factor;
setOpaque(true);
setBackground(Color.white);
//set size of this panel
orgWidth = new Dimension((int)(currentPageFormat.getWidth() * (1 / factor)), (int)(currentPageFormat.getHeight() * (1 / factor)));
applySize();
setBorder(BorderFactory.createMatteBorder(1, 1, 2, 2, Color.black));
setLayout(null);
}
public void destroy()
{
formData = null;
root = null;
if (part_panels != null)
{
List<Runnable> invokeLaterRunnables = new ArrayList<Runnable>();
Iterator<DataRenderer> it = part_panels.values().iterator();
while (it.hasNext())
{
it.next().notifyVisible(false, invokeLaterRunnables);
}
//we ignore invokeLaterRunnables since we are in printing
it = part_panels.values().iterator();
while (it.hasNext())
{
it.next().destroy();
}
part_panels = null;
}
if (renderParent != null) renderParent.destroy();
}
//build the chain and fill the renderers,returns number of pages
public int process() throws Exception
{
//clear
root = null;
//set size of this panel
orgWidth = new Dimension((int)(currentPageFormat.getWidth() * (1 / factor)), (int)(currentPageFormat.getHeight() * (1 / factor)));
applySize();
part_panels = createPartPanels();
Form form = controllerBeingPreviewed.getForm();
int w = form.getWidth();//otherwise you cannot print multiple columns (int) (application.getPageFormat().getImageableWidth()*(1/factor));
try
{
application.getRuntimeProperties().put("isPrinting", Boolean.TRUE); //$NON-NLS-1$
Map componentsUsingSliding = application.getDataRenderFactory().completeRenderers(application, form, controllerBeingPreviewed.getScriptExecuter(),
part_panels, w, true, null, null);
PropertyCopy.copyExistingPrintableProperties(application, controllerBeingPreviewed, part_panels);
Iterator<DataRenderer> panels = part_panels.values().iterator();
while (panels.hasNext())
{
DataRenderer panel = panels.next();
panel.setComponentsUsingSliding(componentsUsingSliding);
DataRendererFactory.addSpringsBetweenComponents(application, panel);
}
Debug.trace("usesSliding " + (componentsUsingSliding.size() != 0)); //$NON-NLS-1$
}
finally
{
application.getRuntimeProperties().put("isPrinting", null); //$NON-NLS-1$
}
//create list
renderParent = application.getPrintingRendererParent();
plist = new PageList(application, this, renderParent);
PartNode node = null;
//create the chain based on the sort,LAST node must be the body part (is virtal added if not present)
Part body = null;
FormController fp = ((FormManager)application.getFormManager()).leaseFormPanel(controllerBeingPreviewed.getName());
if (fp != null && !fp.isShowingData())
{
// List lst = fp.getFormModel().getLastSearchColumns();
if (fp.wantEmptyFoundSet())
{
if (fp.getFormModel() != null) fp.getFormModel().clear();
}
else
{
fp.loadAllRecords();
}
// fp.getFormModel().sort(lst);
}
List<SortColumn> sortColumns = ((FoundSet)formData).getLastSortColumns();
if (formData.getSize() != 0)
{
if (sortColumns != null)
{
Set<String> consumed = new HashSet<String>();
for (int i = 0; i < sortColumns.size(); i++)
{
SortColumn sc = sortColumns.get(i);
Iterator<Part> it = part_panels.keySet().iterator();
while (it.hasNext())
{
Part part = it.next();
DataRenderer dr = part_panels.get(part);
if (part.getPartType() == Part.BODY)
{
body = part;
continue;
}
if (part.getPartType() != Part.LEADING_SUBSUMMARY && part.getPartType() != Part.TRAILING_SUBSUMMARY)
{
IRecordInternal state = new PageNumberState(formData, plist);
plist.setNonRepeatingPart(part.getPartType(), new DataRendererDefinition(this, renderParent, part, dr, state));
continue;
}
boolean match = false;
int inlineCount = 0;
List<SortColumn> partSortColumns = new ArrayList<SortColumn>();
SortColumn lastMatch = sc;
String groupByDataproviders = part.getGroupbyDataProviderIDs() != null ? part.getGroupbyDataProviderIDs() : "";
StringTokenizer tk = new StringTokenizer("" + groupByDataproviders.toLowerCase(), ", "); //$NON-NLS-1$ //$NON-NLS-2$
int tokenCount = tk.countTokens();
String[] ids = new String[tokenCount];
for (; inlineCount < tokenCount; inlineCount++)
{
String id = tk.nextToken();
ids[inlineCount] = id;
if (lastMatch.getDataProviderID().equals(id))
{
partSortColumns.add(lastMatch);
if ((i + inlineCount + 1) < sortColumns.size())
{
lastMatch = sortColumns.get(i + inlineCount + 1);
if (part.getPartType() == Part.LEADING_SUBSUMMARY && consumed.contains(lastMatch))
{
break;
}
}
else
{
break;
}
}
else
{
break;
}
}
if (tokenCount > 0 && partSortColumns.size() == tokenCount) //did all match?
{
match = true;
if (part.getPartType() == Part.LEADING_SUBSUMMARY)
{
for (String element : ids)
{
consumed.add(element);
}
}
}
if (match)
{
SortColumn[] array = new SortColumn[partSortColumns.size()];
partSortColumns.toArray(array);
if (root == null)//create root
{
root = new PartNode(this, part, dr, renderParent, array);
node = root;
}
else
{
if (!tryToPlaceInExistingNodes(part, dr, array))
{
PartNode newNode = new PartNode(this, part, dr, renderParent, array);
node.setChild(newNode);
node = newNode;
}
}
}
}
}
PartNode newNode = null;
if (body == null)
{
newNode = new PartNode(this, null, null, renderParent, null);//a virtual body (when no body is placed in the parts)
}
else
{
newNode = new PartNode(this, body, part_panels.get(body), renderParent, null);//the body
}
if (node != null)
{
node.setChild(newNode);
}
else
{
root = newNode;
}
}
else
//no sort...
{
if (body == null)//search for body
{
Iterator<Part> it = part_panels.keySet().iterator();
while (it.hasNext())
{
Part part = it.next();
DataRenderer dr = part_panels.get(part);
IRecordInternal state = new PageNumberState(formData, plist);
if (part.getPartType() == Part.BODY)
{
body = part;
continue;
}
if (part.getPartType() != Part.LEADING_SUBSUMMARY && part.getPartType() != Part.TRAILING_SUBSUMMARY)
{
plist.setNonRepeatingPart(part.getPartType(), new DataRendererDefinition(this, renderParent, part, dr, state));
continue;
}
}
}
if (body == null)
{
root = new PartNode(this, null, null, renderParent, null);//a virtual body (when no body is placed in the parts)
}
else
//if (body != null)
{
root = new PartNode(this, body, part_panels.get(body), renderParent, null);//the body
}
}
}
try
{
application.getRuntimeProperties().put("isPrinting", Boolean.TRUE); //$NON-NLS-1$
long t1 = System.currentTimeMillis();
//fill the renderers with data
if (root != null)
{
//dump chain
Debug.trace("Root " + root); //$NON-NLS-1$
QuerySelect sqlString = ((FoundSet)formData).getSqlSelect();
Table table = formData.getSQLSheet().getTable();
FoundSet fs = (FoundSet)((FoundSetManager)application.getFoundSetManager()).getNewFoundSet(table, null, sortColumns);
fs.browseAll(sqlString);
long t3 = System.currentTimeMillis();
List<DataRendererDefinition> childRetval = root.process(this, fs, table, sqlString);
long t4 = System.currentTimeMillis();
if (Debug.tracing())
{
Debug.trace("Database queries took " + ((t4 - t3) / 1000f) + " second"); //$NON-NLS-1$ //$NON-NLS-2$
}
if (childRetval != null)
{
for (int i = 0; i < childRetval.size(); i++)
{
plist.addPanel(childRetval.get(i));
}
}
}
plist.finish();
long t2 = System.currentTimeMillis();
int pageCount = plist.getNumberOfPages();
//dump
if (Debug.tracing())
{
Debug.trace(plist);
Debug.trace("Generated " + pageCount / ((t2 - t1) / 1000f) + " printable pages per second"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
finally
{
application.getRuntimeProperties().put("isPrinting", null); //$NON-NLS-1$
}
renderParent.removeAll();
return plist.getNumberOfPages();
}
private boolean tryToPlaceInExistingNodes(Part p, DataRenderer r, SortColumn[] scs)
{
PartNode node = root;
while (node != null)
{
if (p.getPartType() == Part.TRAILING_SUBSUMMARY && node.isLeading() && Arrays.equals(node.getSortColumns(), scs))
{
try
{
node.setSecondPartAsTrailingRenderer(p, r);
}
catch (RepositoryException e)
{
Debug.error(e);
}
return true;
}
node = node.getChild();
}
return false;
}
private Map<Part, DataRenderer> createPartPanels() throws RepositoryException
{
try
{
application.getRuntimeProperties().put("isPrinting", Boolean.TRUE); //$NON-NLS-1$
part_panels = new LinkedHashMap<Part, DataRenderer>();
Iterator<Part> it = controllerBeingPreviewed.getForm().getParts();
while (it.hasNext())
{
Part part = it.next();
// printing is always swing
DataRenderer partpane = (DataRenderer)application.getDataRenderFactory().getEmptyDataRenderer(
ComponentFactory.getWebID(controllerBeingPreviewed.getForm(), part), part.toString(), application, false);
part_panels.put(part, partpane);
}
return part_panels;
}
finally
{
application.getRuntimeProperties().put("isPrinting", null); //$NON-NLS-1$
}
}
public IApplication getApplication()
{
return application;
}
public void showPage(int page)
{
this.pageNumber = page;
applySize();
revalidate();
application.invokeLater(new Runnable()
{
public void run()
{
repaint();
}
});
}
public void zoom(float a_zoomFactor)
{
zoomFactor = a_zoomFactor;
applySize();
if (getParent() != null)
{
invalidate();
getParent().validate();
}
}
private void applySize()
{
Dimension d = new Dimension(orgWidth);
d.width *= zoomFactor;
d.height *= zoomFactor;
setPreferredSize(d);
setSize(d);
}
@Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
AffineTransform save = ((Graphics2D)g).getTransform();
AffineTransform at = (AffineTransform)save.clone();
try
{
at.scale(zoomFactor, zoomFactor);//set zoom
((Graphics2D)g).setTransform(at);
drawMargins(g, currentPageFormat);
if (plist != null)
{
PageDefinition page = plist.getPage(pageNumber);
if (page != null) page.printPage(g, currentPageFormat);
}
}
finally
{
((Graphics2D)g).setTransform(save);
if (renderParent != null) renderParent.removeAll();
}
}
public Pageable getPageable()
{
return plist;
}
public PageFormat getPageFormat()
{
return currentPageFormat;
}
public void setPageFormat(PageFormat pf)
{
currentPageFormat = pf;
// push this new format to the controller so that the user does not have to
// overrule the page format again for this form in this ssession.
controllerBeingPreviewed.setPageFormat(pf);
}
public double getZoomFactor()
{
return factor;
}
private void drawMargins(Graphics g, PageFormat pf)
{
double imx = pf.getImageableX() * (1 / factor);
double imy = pf.getImageableY() * (1 / factor);
double imw = pf.getImageableWidth() * (1 / factor);
double imh = pf.getImageableHeight() * (1 / factor);
double h = pf.getHeight() * (1 / factor);
double w = pf.getWidth() * (1 / factor);
g.setColor(Color.lightGray);
g.drawLine((int)imx - 1, 0, (int)imx - 1, (int)h); //left margin
g.drawLine((int)Math.round(imx + imw), 0, (int)Math.round(imx + imw), (int)h); //rigth margin
g.drawLine(0, (int)imy - 1, (int)w, (int)imy - 1); //top margin
g.drawLine(0, (int)Math.round(imy + imh), (int)w, (int)Math.round(imy + imh)); //bottom margin
}
/**
*
*/
public void flushCachedData()//removePrintedStatesFromFoundSets()
{
if (root != null) root.removePrintedStatesFromFoundSets();
}
}