001: /*
002: * TablePrinter.java
003: *
004: * This file is part of SQL Workbench/J, http://www.sql-workbench.net
005: *
006: * Copyright 2002-2008, Thomas Kellerer
007: * No part of this code maybe reused without the permission of the author
008: *
009: * To contact the author please send an email to: support@sql-workbench.net
010: *
011: */
012: package workbench.print;
013:
014: import java.awt.Color;
015: import java.awt.Font;
016: import java.awt.FontMetrics;
017: import java.awt.Graphics;
018: import java.awt.Graphics2D;
019: import java.awt.Rectangle;
020: import java.awt.geom.AffineTransform;
021: import java.awt.geom.Rectangle2D;
022: import java.awt.print.PageFormat;
023: import java.awt.print.Pageable;
024: import java.awt.print.Printable;
025: import java.awt.print.PrinterException;
026: import java.awt.print.PrinterJob;
027: import java.util.HashMap;
028: import java.util.Map;
029: import javax.swing.Icon;
030: import javax.swing.RepaintManager;
031: import javax.swing.SwingConstants;
032: import javax.swing.SwingUtilities;
033: import javax.swing.table.TableColumn;
034: import javax.swing.table.TableColumnModel;
035: import workbench.gui.components.WbTable;
036: import workbench.resource.ResourceMgr;
037: import workbench.resource.Settings;
038:
039: /**
040: * Prints the content of a Table.
041: * Usage:
042: <pre>
043: PrinterJob job = PrinterJob.getPrintJob();
044: PageFormat format = job.defaultPage();
045: Font f = new Font("Courier New", Font.PLAIN, 10);
046: TablePrinter printer = new TablePrinter(theTable, format, printerFont);
047: printer.startPrint();
048: </pre>
049: * The printout will be started in a separate thread on the default printer.
050: *
051: * @author support@sql-workbench.et
052: */
053: public class TablePrinter implements Printable, Pageable {
054: private PageFormat format;
055: protected WbTable table;
056: private int pageCount = -1;
057:
058: private Font printFont;
059: private String[] colHeaders;
060: private String headerText = null;
061: private TablePrintPage[] pages = null;
062:
063: private int pagesAcross = 0;
064: private int pagesDown = 0;
065: private int lineSpacing = 2;
066: private int colSpacing = 4;
067:
068: public TablePrinter(WbTable toPrint) {
069: PageFormat pformat = Settings.getInstance().getPageFormat();
070: Font printerFont = Settings.getInstance().getPrinterFont();
071: init(toPrint, pformat, printerFont);
072: }
073:
074: protected void init(WbTable toPrint, PageFormat aFormat, Font aFont) {
075: this .table = toPrint;
076: this .printFont = aFont;
077: this .format = aFormat;
078: String header = this .table.getPrintHeader();
079: if (header != null) {
080: setHeaderText(header);
081: }
082: calculatePages();
083: }
084:
085: public void setHeaderText(String aText) {
086: this .headerText = aText;
087: }
088:
089: public void setFont(Font aFont) {
090: this .printFont = aFont;
091: this .calculatePages();
092: }
093:
094: public Font getFont() {
095: return this .printFont;
096: }
097:
098: public void startPrint() {
099: final PrinterJob pj = PrinterJob.getPrinterJob();
100: if (this .format == null) {
101: this .setPageFormat(pj.defaultPage());
102: }
103: pj.setPrintable(this , this .format);
104: pj.setPageable(this );
105:
106: Thread pt = new Thread() {
107: public void run() {
108: try {
109: if (pj.printDialog()) {
110: RepaintManager.currentManager(table)
111: .setDoubleBufferingEnabled(false);
112: pj.print();
113: RepaintManager.currentManager(table)
114: .setDoubleBufferingEnabled(true);
115: }
116: } catch (Exception e) {
117: }
118: }
119: };
120: pt.setDaemon(true);
121: pt.setName("Print Thread");
122: pt.start();
123: }
124:
125: public int getPagesAcross() {
126: return this .pagesAcross;
127: }
128:
129: public int getPreviousVerticalPage(int index) {
130: if (index < 1)
131: return -1;
132: if (index >= this .pageCount)
133: return -1;
134: if (this .pagesAcross == 1) {
135: return index - 1;
136: } else {
137: int pageDown = this .pages[index].getPageNumberDown();
138: for (int i = index; i > 0; i--) {
139: int pd = this .pages[i].getPageNumberDown();
140: if (pd < pageDown)
141: return i;
142: }
143: return -1;
144: }
145: }
146:
147: public int getNextVerticalPage(int index) {
148: if (index < 0)
149: return -1;
150: if (index >= this .pageCount - 1)
151: return -1;
152: if (this .pagesAcross == 1) {
153: return index + 1;
154: } else {
155: int pageDown = this .pages[index].getPageNumberDown();
156: for (int i = index; i < this .pageCount; i++) {
157: int pd = this .pages[i].getPageNumberDown();
158: if (pd > pageDown)
159: return i;
160: }
161: return -1;
162: }
163: }
164:
165: public int getNextHorizontalPage(int index) {
166: if (this .pagesAcross == 1)
167: return -1;
168: if (index < 0)
169: return -1;
170: if (index >= this .pageCount - 1)
171: return -1;
172: int currentAcross = this .pages[index].getPageNumberAcross();
173: if (currentAcross == this .pagesAcross)
174: return -1;
175: return index + 1;
176: }
177:
178: public int getPreviousHorizontalPage(int index) {
179: if (this .pagesAcross == 1)
180: return -1;
181: if (index < 1)
182: return -1;
183: int currentAcross = this .pages[index].getPageNumberAcross();
184: if (currentAcross == 1)
185: return -1;
186: return index - 1;
187: }
188:
189: public PageFormat getPageFormat() {
190: return this .format;
191: }
192:
193: public void setPageFormat(PageFormat aFormat) {
194: this .format = aFormat;
195: if (this .format == null) {
196: this .format = PrinterJob.getPrinterJob().defaultPage();
197: }
198: this .calculatePages();
199: }
200:
201: private void calculatePages() {
202: if (this .format == null)
203: return;
204:
205: int pageWidth = (int) format.getImageableWidth();
206: int pageHeight = (int) format.getImageableHeight();
207:
208: if (this .printFont == null) {
209: this .printFont = table.getFont();
210: }
211:
212: FontMetrics fm = this .table.getFontMetrics(this .printFont);
213:
214: int lineHeight = fm.getHeight() + this .lineSpacing;
215:
216: pageHeight -= (lineHeight + 10); // reserve one row for the column headers
217:
218: int rowsPerPage = (pageHeight / lineHeight);
219:
220: if (this .headerText != null) {
221: rowsPerPage--;
222: }
223: rowsPerPage--; // one line for the page information
224:
225: TableColumnModel colModel = table.getColumnModel();
226: int colCount = colModel.getColumnCount();
227:
228: int rowCount = table.getRowCount();
229: pagesDown = (int) Math.ceil((double) rowCount
230: / (double) rowsPerPage);
231:
232: int currentPageWidth = 0;
233: int[] width = new int[colCount]; // stores the width for each column
234:
235: // the key to the map is the horizontal page number
236: // the value will be the column were that page starts
237: Map<Integer, Integer> horizontalBrakeColumns = new HashMap<Integer, Integer>();
238:
239: // First page always starts at column 0
240: horizontalBrakeColumns.put(new Integer(0), new Integer(0));
241:
242: this .colHeaders = new String[colCount];
243: this .pagesAcross = 1;
244:
245: Rectangle paintIconR = new Rectangle();
246: Rectangle paintTextR = new Rectangle();
247: Rectangle paintViewR = new Rectangle();
248:
249: // TODO: horizontal pages do not work when a column exceeds the horizontal space
250: for (int col = 0; col < colCount; col++) {
251: TableColumn column = colModel.getColumn(col);
252: String title = (String) column.getIdentifier();
253:
254: width[col] = column.getWidth();
255:
256: paintViewR.x = 0;
257: paintViewR.y = 0;
258: paintViewR.width = width[col];
259: paintViewR.height = lineHeight;
260:
261: paintIconR.x = paintIconR.y = paintIconR.width = paintIconR.height = 0;
262: paintTextR.x = paintTextR.y = paintTextR.width = paintTextR.height = 0;
263:
264: this .colHeaders[col] = SwingUtilities.layoutCompoundLabel(
265: fm, title, (Icon) null, SwingConstants.TOP,
266: SwingConstants.LEFT, SwingConstants.TOP,
267: SwingConstants.RIGHT, paintViewR, paintIconR,
268: paintTextR, 0);
269:
270: if ((currentPageWidth + width[col] + colSpacing) >= pageWidth) {
271: horizontalBrakeColumns.put(new Integer(pagesAcross),
272: new Integer(col));
273: pagesAcross++;
274: currentPageWidth = 0;
275: }
276: currentPageWidth += (width[col] + colSpacing);
277: }
278:
279: int currentPage = 0;
280: this .pageCount = pagesDown * pagesAcross;
281: this .pages = new TablePrintPage[this .pageCount];
282:
283: int startRow = 0;
284:
285: for (int pd = 0; pd < pagesDown; pd++) {
286: for (int pa = 0; pa < pagesAcross; pa++) {
287: int startCol = horizontalBrakeColumns.get(pa);
288: int endCol = 0;
289:
290: if (pa + 1 >= pagesAcross) {
291: endCol = colCount - 1;
292: } else {
293: endCol = horizontalBrakeColumns.get(pa + 1) - 1;
294: }
295: int endRow = startRow + rowsPerPage;
296: if (endRow >= rowCount) {
297: endRow = rowCount - 1;
298: }
299: TablePrintPage p = new TablePrintPage(this .table,
300: startRow, endRow, startCol, endCol);
301: p.setPageIndex(currentPage + 1);
302: if (pagesAcross > 1) {
303: p.setPageNumberDown(pd + 1);
304: p.setPageNumberAcross(pa + 1);
305: }
306: p.setSpacing(lineSpacing, colSpacing);
307: p.setColumnHeaders(this .colHeaders);
308: p.setColumnWidths(width);
309: p.setFont(this .printFont);
310: this .pages[currentPage] = p;
311: currentPage++;
312: }
313: startRow += rowsPerPage + 1;
314: }
315: }
316:
317: public int print(Graphics g, PageFormat pageFormat, int pageIndex)
318: throws PrinterException {
319: Graphics2D pg = (Graphics2D) g;
320: if (pageIndex >= this .pageCount)
321: return NO_SUCH_PAGE;
322:
323: double startx = pageFormat.getImageableX();
324: double starty = pageFormat.getImageableY();
325:
326: int wPage = (int) pageFormat.getImageableWidth();
327: int hPage = (int) pageFormat.getImageableHeight();
328:
329: pg.setClip((int) startx, (int) starty, wPage, hPage);
330: pg.translate(startx, starty);
331: AffineTransform oldTransform = pg.getTransform();
332:
333: pg.setColor(Color.BLACK);
334: pg.setFont(this .printFont);
335: TablePrintPage currentPage = this .pages[pageIndex];
336:
337: StringBuilder footer = new StringBuilder(100);
338: if (pagesAcross > 1) {
339: footer.append(ResourceMgr.getFormattedString(
340: "TxtPageFooterHor", currentPage
341: .getPageNumberAcross(), this .pagesAcross,
342: currentPage.getPageNumberDown(), this .pagesDown));
343: } else {
344: footer.append(ResourceMgr.getFormattedString(
345: "TxtPageFooterNormal", currentPage.getPageIndex(),
346: this .pageCount));
347: }
348:
349: FontMetrics fm = pg.getFontMetrics(this .printFont);
350: Rectangle2D bounds = fm.getStringBounds(footer.toString(), pg);
351: double len = bounds.getWidth();
352:
353: pg.drawString(footer.toString(), (int) ((wPage - len) / 2),
354: hPage - fm.getDescent());
355:
356: if (this .headerText != null) {
357: bounds = fm.getStringBounds(this .headerText, pg);
358: len = bounds.getWidth();
359: pg.drawString(this .headerText, 0, fm.getAscent());
360: pg.translate(0, lineSpacing + fm.getAscent() + 5);
361: }
362: currentPage.print(pg);
363:
364: pg.setTransform(oldTransform);
365: pg.setClip(null);
366:
367: return PAGE_EXISTS;
368: }
369:
370: public int getNumberOfPages() {
371: return this .pageCount;
372: }
373:
374: public PageFormat getPageFormat(int pageIndex)
375: throws IndexOutOfBoundsException {
376: return this .format;
377: }
378:
379: public Printable getPrintable(int pageIndex)
380: throws IndexOutOfBoundsException {
381: return this;
382: }
383:
384: }
|