package utility.printing; import utility.ContainerOps; import utility.Fonts; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.geom.Rectangle2D; import java.awt.print.PageFormat; import java.awt.print.Printable; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import javax.swing.JComponent; import javax.swing.JTable; import javax.swing.JTextArea; import javax.swing.RepaintManager; import javax.swing.table.JTableHeader; /** * It renders a print preview for a given container. * @author Konrad Borowiecki */ public class ContainerPageRenderer extends JComponent implements Printable, IZoomableComponent { private static final long serialVersionUID = 1L; private int currentPageIndex; private Dimension preferredSize; /**the container to print */ private Container printedContainer; private List yOrderedComponents; private List heighestInRowComponents; private List pageHeights; /**The font used for the header.*/ private Font headerFont = null; /**The text of the header.*/ private String header = null; /**The font used in case if the headerFont is not specified.*/ private Font defaultFont = Fonts.BookAntiqua.getFont(); /** This list is in relation to page index, i.e. on position equal page index * it has header of table which didn't fit on previous page, otherwise it has null.*/ private List tableHeadersOfTooLargeTables; /** This is page height without header, footer and page number (fontHeight) * currently we assume that footer is at the same line as page number. */ private double usablePageHeight = 0; private PageFormat pageFormat; private double scale; /** * this two variables are controlling position of a table header * when it is moved to next page */ private int leftBorderOfMovedTableHeader = 0; private int rightBorderOfMovedTableHeader = 0; private Color leftBorderOfMovedTableHeaderColor = Color.GRAY; private Color rightBorderOfMovedTableHeaderColor = Color.GRAY; private boolean isInitialized = false; /** Height of currently created page. */ private double pageH = 0; /** Currently taken height of the container. */ private double h = 0; private int headerHeight = 0; private int headerFreeSpace = 0; private boolean isPrintOutCenteredOnPageWidth = false; /** When the object to render is shown we must then call initPages().*/ public ContainerPageRenderer(Container container, PageFormat pageFormat) { this.printedContainer = container; //we cannot call it here since the object must be shown to use the methods // initPages(pageFormat); //just set the format as update preffered size uses it to calculate size this.pageFormat = pageFormat; updatePrefferedSize(); } public void initPages() { initPages(pageFormat); updatePrefferedSize(); } private void updatePrefferedSize() { this.preferredSize = new Dimension((int) (pageFormat.getWidth() * zoom), (int) (pageFormat.getHeight() * zoom));//new Dimension((int) pPageWidth, (int) pPageHeight); } public void resetCurrentPageIndex() { currentPageIndex = 0; } /** * Sets the header. It requires initPages() to be called. * @param header the string to be used as the header. */ public void setHeader(String header) { this.header = header; } public void setHeaderFont(Font headerFont) { this.headerFont = headerFont; } public void initPages(PageFormat pageFormat) { this.pageFormat = pageFormat; updatePrefferedSize(); List childComponents = ContainerOps.findChildComponents(printedContainer); this.yOrderedComponents = ContainerOps.orderComponentsForY(childComponents); this.heighestInRowComponents = ContainerOps.findFurthestReachingInRowChildComponents(printedContainer); this.pageHeights = new ArrayList(); this.tableHeadersOfTooLargeTables = new ArrayList(); //add first null element for the first page, as previously there was no pages thus no nonefitting tables tableHeadersOfTooLargeTables.add(null); this.currentPageIndex = 0; int containerY = printedContainer.getLocationOnScreen().y; int containerW = printedContainer.getWidth(); //width in pixels // int containerH = printedContainer.getHeight(); //height in pixels //when header is given the reserve space for it if(header != null) { //when the font of header was not specified it sets the default one if(headerFont == null) this.headerFont = defaultFont; FontMetrics naglowekFM = printedContainer.getFontMetrics(headerFont); headerHeight = naglowekFM.getHeight(); headerFreeSpace = naglowekFM.getDescent(); } FontMetrics fm = printedContainer.getFontMetrics(printedContainer.getFont()); int pageNumberHeight = fm.getHeight(); int pageNumberFreeSpace = fm.getHeight(); double pageWidth = pageFormat.getImageableWidth(); double pageHeight = pageFormat.getImageableHeight(); this.usablePageHeight = pageHeight - headerHeight - headerFreeSpace - pageNumberHeight - pageNumberFreeSpace; this.scale = 1.0; //scale only if the container width is wider then page width if(containerW > pageWidth) scale = pageWidth / containerW; // Max height for a current page. double pageMaxHTemp = usablePageHeight; // Index of page where recently a multi-line component was being squeezed in. int idxLastPage = 0; // The most recent table header component. JTableHeader tableHeader = null; for(int i = 0; i < heighestInRowComponents.size(); i++) { if(idxLastPage < pageHeights.size()) pageMaxHTemp = usablePageHeight; Component c = heighestInRowComponents.get(i); int cY = c.getLocationOnScreen().y; int cH = c.getHeight(); // System.out.println(" component h=" + cH+ "; class=" + c.getClass() + "; scale=" + scale); int spaceSize = 0; if(i == 0) spaceSize = cY - containerY; else if(i > 0) {//previous component Component pc = heighestInRowComponents.get(i - 1); spaceSize = cY - pc.getLocationOnScreen().y - pc.getHeight(); } // System.out.println("i=" + i + "; spaceSize=" + spaceSize); int counter = 0; //fill pages with all the empty space while(spaceSize > 0) { counter++; //if smaller then required space then add whatever fitts and reduce spaceSize by it and add new page if(pageH + spaceSize * scale > pageMaxHTemp){ // System.out.println("\n**************if(pageH + spaceSize * skala > pageMaxHTemp) counter=" + counter); double fittingSpaceSize = pageMaxHTemp - pageH; spaceSize -= fittingSpaceSize / scale; pageH += fittingSpaceSize; //then create a new page addNewPage(); pageMaxHTemp = usablePageHeight; } else { pageH += spaceSize * scale; spaceSize = 0; } } //remember the header of a table if(c instanceof JTableHeader) { System.out.println(" component is a table header "); tableHeader = (JTableHeader) c; } if(pageH + cH * scale <= pageMaxHTemp) pageH += cH * scale; else {//if this is a multiline component try special processing/spliting of the compoent into lines //try fitting into the page as many lines as possible if(isMultilineComponent(c)) { System.out.println("isMultilineComponent(c)"); double[] rowHeights = null; int rowCount = 0; if(c instanceof JTextArea) { System.out.println("if(c instanceof JTextArea)"); JTextArea textArea = (JTextArea) c; rowCount = textArea.getRows(); if(rowCount == 0) { //there is always at least one row but if row count is not set then it is zero rowCount = 1; String nl = "\n";//System.getProperty("line.separator"); String text = textArea.getText(); System.out.println("nl=" + nl); if(text.contains("\n")) { System.out.println("if(textArea.getText().contains(nl))"); String[] strs = text.split(nl);//("\n"); if(strs != null) rowCount = strs.length; } } rowHeights = new double[rowCount]; FontMetrics taFM = textArea.getFontMetrics(textArea.getFont()); int textAreaRowH = taFM.getHeight(); //first line and last line should be larger (respecitvly by top and bottom border size) for(int k = 0; k < rowCount; k++) { rowHeights[k] = textAreaRowH; //set first row larger by top border size if(k == 0) rowHeights[k] += textArea.getInsets().top; //set last row larger by bottom border size if(k == rowCount - 1) rowHeights[k] += textArea.getInsets().bottom; } } //fit as many rows as possible else if(c instanceof JTable) { JTable table = (JTable) c; //if(table.getRowCount() > 0)//table with zero rows has zero height rowCount = table.getRowCount(); System.out.println("rowCount=" + rowCount); rowHeights = new double[rowCount]; //with table and borders is different then other components // the border must be onther component // as its size dsoesnt influence table size for(int k = 0; k < rowCount; k++) { rowHeights[k] = table.getRowHeight(k); // if(k == 0) // rowHeights[k] += table.getInsets().top; // if(k == rowCount -1) // rowHeights[k] += table.getInsets().bottom; } } int rowIdx = 0; //it stores the initial row index at start of each iteration in the while loop int startIterationRowIdx = -1; System.out.println("rowIdx=" + rowIdx + "; rowCount=" + rowCount); while(rowIdx < rowCount) { startIterationRowIdx = rowIdx; double freeH = pageMaxHTemp - pageH; System.out.println("pageMaxHTemp=" + pageMaxHTemp + "; pageH=" + pageH + "; freeH=" + freeH + "; "); double totalRowH = 0;//total height of rows which potentially fit in the freeH //we find the row index for(int j = rowIdx; j < rowCount; j++) { double rowH = rowHeights[j] * scale; // System.out.println("rowH="+rowH); if(freeH - rowH > 0) { freeH -= rowH; rowIdx = j; totalRowH += rowH; } else break; } int goodRowIdx = rowIdx; double goodTotalRowH = totalRowH; //having potential max rows check how many will in fact fit for(int j = rowIdx; j > 0; j--) { Component comp = null; //find closest component int idx = yOrderedComponents.indexOf(c); int hIdx = heighestInRowComponents.indexOf(c); int stopIdx = 0; //znalesc index poprzedniego najwyzszego i wszystkie pomiedzy nimi moga potecjalnie byc przeszkodami if(hIdx > 0) { Component prevHighestC = heighestInRowComponents.get(hIdx - 1); stopIdx = yOrderedComponents.indexOf(prevHighestC); } double y = containerY + h + pageH;// - table.getRowHeight(j) * skala; //look through potentially conflicting components which might have size conflict with these rows for(int k = idx - 1; k > stopIdx; k--) { Component yoc = yOrderedComponents.get(k); int yocY = yoc.getLocationOnScreen().y; int yocH = yoc.getHeight(); if(yocY + yocH > y) { comp = yoc; break; } } //no conflict if(comp == null) { goodRowIdx = j; break; }//else try for other rowIdx, and change height of all rows else goodTotalRowH -= rowHeights[j];// * skala; } //no rows fit freeH - start new page if(goodRowIdx == -1) { double pageNextMaxH = usablePageHeight - tableHeader.getHeight() * scale; System.out.println("if(goodRowIdx == -1)"); addAndCutToThisIfNoFitOnNextPage(goodTotalRowH, pageNextMaxH); //if(tableHeader != null)//or c instancof JTable //{ pageMaxHTemp = usablePageHeight - tableHeader.getHeight() * scale; idxLastPage = pageHeights.size(); //tableHeadersOfTooLargeTables.set(idxLastPage, tableHeader); //} } else//some of the rows fit { System.out.println("goodRowIdx=" + goodRowIdx + "; rowIdx=" + rowIdx + ";prevRowIdx=" + startIterationRowIdx + "; pageMaxHTemp=" + pageMaxHTemp + "; goodTotalRowH=" + goodTotalRowH + "; rowCount=" + rowCount); rowIdx = goodRowIdx; pageH += goodTotalRowH; if(rowIdx != rowCount - 1) { addNewPage(); System.out.println(" ; pageHeights.size()=" + pageHeights.size()); if(tableHeader != null){ /* decrease the page size just once since it needs space for table header*/ pageMaxHTemp = usablePageHeight - tableHeader.getHeight() * scale; idxLastPage = pageHeights.size(); tableHeadersOfTooLargeTables.set(idxLastPage, tableHeader); } } else System.out.println("(rowCount-1) == goodRowIdx=" + goodRowIdx + "; rowIdx=" + rowIdx + "; rowCount=" + rowCount); } rowIdx += 1; } } else //not multiline component { addAndCut(cH, pageMaxHTemp); } } //to add the empty space that can be under all components // if(i == heighestInRowComponents.size() - 1) // { // spaceSize = containerY + containerH - cY - cH; // //if it fits add it otherwise ignore it // if(pageH + spaceSize * skala <= pageMaxHTemp) // pageH += spaceSize * skala; // System.out.println("final i=" + i + "; spaceSize=" + spaceSize); // } } if(pageH > 0) { System.out.print("endnndnd pageH=" + pageH + "; h=" + (h + pageH)); //add the final page height addNewPage(); System.out.println("; h=" + h); /* remove the last element of the header list so the pages and header lists will have the same sizes * they differ since headers have one element set at start to null since no page was before */ tableHeadersOfTooLargeTables.remove(tableHeadersOfTooLargeTables.size() - 1); } /** if there is absolutely now components then add an empty page * this way there will be date and page number */ if(pageHeights.isEmpty()) pageHeights.add(0.0); isInitialized = true; repaint(); } private void addNewPage() { //remember height of this page pageHeights.add(pageH); //remember complete height of already created pages h += pageH; //start haight of new page from zero pageH = 0; /* add empty element to list of table headers, so table have space to add its * own header, if this is the case when table rows are added and we run out of space */ tableHeadersOfTooLargeTables.add(null); } /** * If the component's height is smaller then or equal to total height of the next page then * add it to the next page otherwise start adding it with cutting to the current page. * @param componentH the height of component to be added. * @param usablePageH maximum usable height for currently created page. */ private void addAndCutToThisIfNoFitOnNextPage( double componentH, double usableNextPageH) { // System.out.println("addAndCutToThisIfNoFitOnNextPage"); //if component height is smaller then next page's usable space start adding it from the next page if(componentH * scale <= usableNextPageH) { boolean isTableHeaderPresent = false; JTableHeader tableHeader = tableHeadersOfTooLargeTables.get( tableHeadersOfTooLargeTables.size() - 1); if(tableHeader != null) isTableHeaderPresent = true; addNewPage(); if(isTableHeaderPresent) tableHeadersOfTooLargeTables.set( tableHeadersOfTooLargeTables.size() - 1, tableHeader); else /* make sure the new page is set to max space if no header was present, otherwise do not change as usableNextPageH reflects the space already taken by the header */ usableNextPageH = usablePageHeight; } addAndCut(componentH, usableNextPageH); } /** * Adds a componentH to the height of currently created page, if it doesn't it will * squeeze in as much as possible of the componentH then create a new pages and repeat * until all is in. If the componeneH is smaller then allowed percentage of * current page then add new page before calling addAndCut. * @param componentH the height of component to be added. * @param usablePageH maximum usable height for currently created page. * @param percentage integer value from 1 to 100. The function will do nothing * for a percentage value out of this range. */ private void addAndCutAddNewPageIfCurrentSpaceSmallerThen( double componentH, double usablePageH, int percentage) { // System.out.println("addAndCutAddNewPageIfCurrentSpaceSmallerThen"); if(percentage <= 100 && percentage >= 1) { double freeH = usablePageH - pageH; //if free space is smaller then allowed percentage of current page then add new page before calling addAndCut if(freeH < (percentage / 100.0) * usablePageH) { boolean isTableHeaderPresent = false; JTableHeader tableHeader = tableHeadersOfTooLargeTables.get( tableHeadersOfTooLargeTables.size() - 1); if(tableHeader != null) isTableHeaderPresent = true; addNewPage(); if(isTableHeaderPresent) tableHeadersOfTooLargeTables.set(tableHeadersOfTooLargeTables.size() - 1, tableHeader); else/* make sure the new page is set to max space if no header was present, otherwise do not change as usableNextPageH reflects the space already taken by the header */ usablePageH = usablePageHeight; } addAndCut(componentH, usablePageH); } } /** * Adds a componentH to the height of currently created page, if it doesn't it will * squeeze in as much as possible of the componentH then create a new pages and repeat * until all is in. * @param componentH the height of component to be added. * @param usablePageH maximum usable height for currently created page. */ private void addAndCut(double componentH, double usablePageH) { // System.out.println("in addAndCut ..start componentH=" + componentH + "; usablePageH=" + usablePageH + "; pageH=" + pageH); while(componentH * scale > usablePageH) { double freeH = usablePageH - pageH; componentH -= freeH / scale; pageH += freeH; boolean isTableHeaderPresent = false; JTableHeader tableHeader = tableHeadersOfTooLargeTables.get( tableHeadersOfTooLargeTables.size() - 1); if(tableHeader != null) isTableHeaderPresent = true; addNewPage(); if(isTableHeaderPresent) tableHeadersOfTooLargeTables.set( tableHeadersOfTooLargeTables.size() - 1, tableHeader); else /* make sure the new page is set to max space if no header was present, otherwise do not change as usableNextPageH reflects the space already taken by the header */ usablePageH = usablePageHeight; // System.out.println("addAndCut componentH=" + componentH + "; usablePageH=" + usablePageH + "; pageH=" + pageH + "; freeH=" + freeH); } //add the rest of compoent to current pageH pageH += componentH * scale; // System.out.println("in addAndCut ....finish componentH=" + componentH + "; usablePageH=" + usablePageH + "; pageH=" + pageH); } public List getPageHeights() { return pageHeights; } public List getTableHeadersOfToLargeTables() { return tableHeadersOfTooLargeTables; } private boolean isMultilineComponent(Component c) { if(c instanceof JTable || c instanceof JTextArea) return true; else return false; } /** * Allows to set set centering of a print out. * @param isPrintOutCenteredOnPageWidth true to center otherwise false. */ public void setIsPrintOutCenteredOnPageWidth(boolean isPrintOutCenteredOnPageWidth) { this.isPrintOutCenteredOnPageWidth = isPrintOutCenteredOnPageWidth; } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); //do not render until it is initialised if(!isInitialized)// || pageHeights.isEmpty()) return; Graphics2D g2 = (Graphics2D) g; g2.scale(zoom, zoom); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); Rectangle2D r = new Rectangle2D.Float(0, 0, (int) pageFormat.getWidth(), (int) pageFormat.getHeight()); g2.setColor(Color.white); g2.fill(r); g2.setColor(Color.black); int containerW = printedContainer.getWidth(); double pageWidth = pageFormat.getImageableWidth(); double pageHeight = pageHeights.get(currentPageIndex); double pageY = 0; for(int i = 0; i < currentPageIndex; i++) pageY += pageHeights.get(i); // System.out.println("currentPageIndex=" + currentPageIndex + "; pageY=" + pageY // + "; pageHeight=" + pageHeight + "; pageWidth=" + pageWidth); FontMetrics fm = printedContainer.getFontMetrics(printedContainer.getFont());//FontMetrics fm = g2.getFontMetrics(); int numerStronyWysokosc = fm.getHeight(); String numerStrony = "Strona: " + (currentPageIndex + 1); double tX = pageFormat.getImageableX(); //page number always in the middle int stronyX = (int) (pageWidth - fm.stringWidth(numerStrony)) / 2; //centruj wydruk jesli jego szerokosc jest mniejsza od szerokosci strony if(isPrintOutCenteredOnPageWidth && containerW < pageWidth) { tX = tX + (pageWidth - containerW) / 2; stronyX = (int) (stronyX - (pageWidth - containerW) / 2); } Calendar cal = Calendar.getInstance(); SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy HH:mm"); String obecnaDataICzas = "Data: " + sdf.format(cal.getTime()); //move graphics to printable region begining g2.translate(pageFormat.getImageableX(), pageFormat.getImageableY()); //move down by the height of page number (the same as for date) g2.translate(0, numerStronyWysokosc); //print date and time g2.drawString(obecnaDataICzas, 0, 0); //move the graphics to component begining g2.translate(tX - pageFormat.getImageableX(), 0); //paint page number in top middle g2.drawString(numerStrony, stronyX, 0); //print header if it exists if(header != null) { g2.translate(0.0, headerHeight); Font domyslnaCzcionka = g2.getFont(); g2.setFont(headerFont); int naglowkaX = (int) (-tX + pageFormat.getImageableX()); FontMetrics naglowekFM = g2.getFontMetrics(headerFont); if(isPrintOutCenteredOnPageWidth) naglowkaX = naglowkaX + (int) (pageWidth / 2 - naglowekFM.stringWidth(header) / 2); //print header g2.drawString(header, naglowkaX, 0); g2.setFont(domyslnaCzcionka); g2.translate(0.0, headerFreeSpace); } // //line marking begining of the usable page // g2.setColor(Color.RED); // g2.drawLine(0, 0, (int) pageWidth, 0); // g2.translate(0.0, usablePageHeight);//pageHeight); // //line marking end of the usable page // g2.drawLine(0, 0, (int) pageWidth, 0); // g2.translate(0.0, -usablePageHeight);//pageHeight); if(tableHeadersOfTooLargeTables.get(currentPageIndex) != null) { JTableHeader th = tableHeadersOfTooLargeTables.get(currentPageIndex); double thHeight = th.getHeight(); double thWidth = th.getWidth(); //paint header borders Color prevColor = g2.getColor(); if(leftBorderOfMovedTableHeaderColor != null) { g2.setColor(leftBorderOfMovedTableHeaderColor); Rectangle2D rect = new Rectangle2D.Double(0.0, 0.0, leftBorderOfMovedTableHeader * scale, Math.ceil(thHeight)); g2.scale(scale, scale); g2.fill(rect); g2.scale(1 / scale, 1 / scale); } g2.setColor(prevColor); double mod = leftBorderOfMovedTableHeader * scale; //((komponentSzerokosc - th.getWidth()) * skala) / 2; g2.translate(mod, 0.0); //clip table's header //g2.setClip(0, 0, (int) Math.ceil(thWidth), (int) Math.ceil(thHeight)); /* //if cliping needed alternative is to use set clip but like in PaintForeignComponentSSCCE2 // otherwise it could be painted over e.g. scroll bars int x = 0; int y = 0; int w = 200; int h = 250; Rectangle r = g2.getClipBounds(); if(r != null) { x = r.x; y = r.y; if(r.width < w) w = r.width; if(w +x > wMax) w = wMax - x; if(r.height < h) h = r.height; if(h +y > hMax) h = hMax - y; } } g2.setClip(x,y,w,h); */ // scale g2.scale(scale, scale); th.paint(g2); //paint the header at the top of the page // remove scale g2.scale(1 / scale, 1 / scale); if(rightBorderOfMovedTableHeaderColor != null) { g2.setColor(rightBorderOfMovedTableHeaderColor); g2.translate((int) Math.ceil(thWidth) * scale, 0.0); Rectangle2D rect = new Rectangle2D.Double( 0.0, 0.0, rightBorderOfMovedTableHeader * scale, Math.ceil(thHeight)); g2.scale(scale, scale); g2.fill(rect); g2.scale(1 / scale, 1 / scale); g2.translate(-(int) Math.ceil(thWidth) * scale, 0.0); } //back to original position g2.translate(-mod, 0.0); g2.translate(0.0, thHeight * scale); } //shift Graphic to line up with beginning of next page to print g2.translate(0f, -pageY); //g2.setClip(0, (int) Math.round(pageY), (int) Math.round(pageWidth),(int) Math.round(pageHeight)); g2.clipRect(0, (int) Math.round(pageY), (int) Math.round(pageWidth), (int) Math.round(pageHeight)); // scale the page so the width fits... g2.scale(scale, scale); disableDoubleBuffering(printedContainer); printedContainer.paint(g2); enableDoubleBuffering(printedContainer); g2.scale(1 / scale, 1 / scale); g2.scale(1 / zoom, 1 / zoom); } /** Lets to increase/decrease zoom level of the panel. */ private double zoom = 1.0; @Override public double getZoom() { return zoom; } @Override public void setZoom(double zoom) { this.zoom = zoom; updatePrefferedSize(); } @Override public int print(Graphics g, PageFormat pageFormat, int pageIndex) { if(pageIndex >= pageHeights.size()) return NO_SUCH_PAGE; int savedPage = currentPageIndex; currentPageIndex = pageIndex; paint(g); currentPageIndex = savedPage; return PAGE_EXISTS; } public double getScale() { return scale; } @Override public Dimension getPreferredSize() { return preferredSize; } /** * Gets page index staring from 0, for the first page. * @return index of current page, 0 for the first page. */ public int getCurrentPage() { return currentPageIndex; } public int getNumPages() { return pageHeights.size(); } public void nextPage() { if(currentPageIndex < pageHeights.size() - 1) currentPageIndex++; repaint(); } public void previousPage() { if(currentPageIndex > 0) currentPageIndex--; repaint(); } public static void disableDoubleBuffering(Component c) { RepaintManager currentManager = RepaintManager.currentManager(c); currentManager.setDoubleBufferingEnabled(false); } public static void enableDoubleBuffering(Component c) { RepaintManager currentManager = RepaintManager.currentManager(c); currentManager.setDoubleBufferingEnabled(true); } }