KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jdesktop > swing > TablePrintable


1 /*
2  * $Id: TablePrintable.java,v 1.1.1.1 2004/06/16 01:43:39 davidson1 Exp $
3  *
4  * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
5  * Santa Clara, California 95054, U.S.A. All rights reserved.
6  */

7
8 package org.jdesktop.swing;
9
10 import java.awt.*;
11 import java.awt.geom.*;
12 import java.awt.print.*;
13 import java.text.*;
14 import javax.swing.*;
15 import javax.swing.table.*;
16
17
18 /**
19  * An implementation of <code>Printable</code> for printing
20  * <code>JTable</code>s.
21  * <p>
22  * This implementation spreads table rows naturally in sequence
23  * across multiple pages, fitting as many rows as possible per page.
24  * The distribution of columns, on the other hand, is controlled by a
25  * printing mode parameter passed to the constructor. When
26  * <code>JTable.PRINT_MODE_NORMAL</code> is used, the implementation
27  * handles columns in a similar manner to how it handles rows, spreading them
28  * across multiple pages (in an order consistent with the table's
29  * <code>ComponentOrientation</code>).
30  * When <code>JTable.PRINT_MODE_FIT_WIDTH</code> is given, the implementation
31  * scales the output smaller if necessary, to ensure that all columns fit on
32  * the page. (Note that width and height are scaled equally, ensuring that the
33  * aspect ratio remains the same).
34  * <p>
35  * The portion of table printed on each page is headed by the
36  * appropriate section of the table's <code>JTableHeader</code>.
37  * <p>
38  * Header and footer text can be added to the output by providing
39  * <code>MessageFormat</code> instances to the constructor. The
40  * printing code requests Strings from the formats by calling
41  * their <code>format</code> method with a single parameter:
42  * an <code>Object</code> array containing a single element of type
43  * <code>Integer</code>, representing the current page number.
44  * <p>
45  * There are certain circumstances where this <code>Printable<code>
46  * cannot fit items appropriately, resulting in clipped output.
47  * These are:
48  * <ul>
49  * <li>In any mode, when the header or footer text is too wide to
50  * fit completely in the printable area. The implementation
51  * prints as much of the text as possible starting from the beginning,
52  * as determined by the table's <code>ComponentOrientation</code>.
53  * <li>In any mode, when a row is too tall to fit in the
54  * printable area. The upper most portion of the row
55  * is printed and no lower border is shown.
56  * <li>In <code>JTable.PRINT_MODE_NORMAL</code> when a column
57  * is too wide to fit in the printable area. The center of the
58  * column is printed and no left and right borders are shown.
59  * </ul>
60  * <p>
61  * It is entirely valid for a developer to wrap this <code>Printable</code>
62  * inside another in order to create complex reports and documents. They may
63  * even request that different pages be rendered into different sized
64  * printable areas. The implementation was designed to handle this by
65  * performing most of its calculations on the fly. However, providing different
66  * sizes works best when <code>JTable.PRINT_MODE_FIT_WIDTH</code> is used, or
67  * when only the printable width is changed between pages. This is because when
68  * it is printing a set of rows in <code>JTable.PRINT_MODE_NORMAL</code> and the
69  * implementation determines a need to distribute columns across pages,
70  * it assumes that all of those rows will fit on each subsequent page needed
71  * to fit the columns.
72  *
73  * @author Shannon Hickey
74  */

75 class TablePrintable implements Printable {
76
77     /** The table to print. */
78     private JTable table;
79
80     /** For quick reference to the table's header. */
81     private JTableHeader header;
82
83     /** For quick reference to the table's column model. */
84     private TableColumnModel colModel;
85
86     /** To save multiple calculations of total column width. */
87     private int totalColWidth;
88
89     /** The printing mode of this printable. */
90     private int printMode;
91
92     /** Provides the header text for the table. */
93     private MessageFormat headerFormat;
94
95     /** Provides the footer text for the table. */
96     private MessageFormat footerFormat;
97
98     /** The most recent page index asked to print. */
99     private int last = -1;
100
101     /** The next row to print. */
102     private int row = 0;
103
104     /** The next column to print. */
105     private int col = 0;
106
107     /** Used to store an area of the table to be printed. */
108     private final Rectangle clip = new Rectangle(0, 0, 0, 0);
109
110     /** Used to store an area of the table's header to be printed. */
111     private final Rectangle hclip = new Rectangle(0, 0, 0, 0);
112
113     /** Saves the creation of multiple rectangles. */
114     private final Rectangle tempRect = new Rectangle(0, 0, 0, 0);
115
116     /** Vertical space to leave between table and header/footer text. */
117     private static final int H_F_SPACE = 8;
118
119     /** Font size for the header text. */
120     private static final float HEADER_FONT_SIZE = 18.0f;
121
122     /** Font size for the footer text. */
123     private static final float FOOTER_FONT_SIZE = 12.0f;
124
125     /** The font to use in rendering header text. */
126     private Font headerFont;
127
128     /** The font to use in rendering footer text. */
129     private Font footerFont;
130
131     /**
132      * Create a new <code>TablePrintable<code> for the given
133      * <code>JTable</code>. Header and footer text can be specified using the
134      * two <code>MessageFormat</code> parameters. When called upon to provide
135      * a String, each format is given the current page number.
136      *
137      * @param table the table to print
138      * @param printMode the printing mode for this printable:
139      * <code>JTable.PRINT_MODE_NORMAL</code> or
140      * <code>JTable.PRINT_MODE_FIT_WIDTH</code>
141      * @param headerFormat a <code>MessageFormat</code> specifying the text to
142      * be used in printing a header, or null for none
143      * @param footerFormat a <code>MessageFormat</code> specifying the text to
144      * be used in printing a footer, or null for none
145      * @throws IllegalArgumentException if passed an invalid print mode
146      */

147     public TablePrintable(JTable table,
148                           int printMode,
149                           MessageFormat headerFormat,
150                           MessageFormat footerFormat) {
151
152         this.table = table;
153
154         header = table.getTableHeader();
155         colModel = table.getColumnModel();
156         totalColWidth = colModel.getTotalColumnWidth();
157
158         if (header != null) {
159             // the header clip height can be set once since it's unchanging
160
hclip.height = header.getHeight();
161         }
162
163         if (printMode != JXTable.PRINT_MODE_NORMAL &&
164                 printMode != JXTable.PRINT_MODE_FIT_WIDTH) {
165
166             throw new IllegalArgumentException JavaDoc("Unknown Print Mode: " +
167                                                printMode);
168         }
169
170         this.printMode = printMode;
171
172         this.headerFormat = headerFormat;
173         this.footerFormat = footerFormat;
174
175         // derive the header and footer font from the table's font
176
headerFont = table.getFont().deriveFont(Font.BOLD,
177                                                 HEADER_FONT_SIZE);
178         footerFont = table.getFont().deriveFont(Font.PLAIN,
179                                                 FOOTER_FONT_SIZE);
180     }
181
182     /**
183      * Prints the specified page of the table into the given {@link Graphics}
184      * context, in the specified format.
185      *
186      * @param graphics the context into which the page is drawn
187      * @param pageFormat the size and orientation of the page being drawn
188      * @param pageIndex the zero based index of the page to be drawn
189      * @return PAGE_EXISTS if the page is rendered successfully, or
190      * NO_SUCH_PAGE if a non-existent page index is specified
191      * @throws PrinterException if an error causes printing to be aborted
192      */

193     public int print(Graphics graphics, PageFormat pageFormat, int pageIndex)
194                                                        throws PrinterException {
195
196         // for easy access to these values
197
final int imgWidth = (int)pageFormat.getImageableWidth();
198         final int imgHeight = (int)pageFormat.getImageableHeight();
199
200         if (imgWidth <= 0) {
201             throw new PrinterException("Width of printable area is too small.");
202         }
203
204         // to pass the page number when formatting the header and footer text
205
Object JavaDoc[] pageNumber = new Object JavaDoc[]{new Integer JavaDoc(pageIndex + 1)};
206
207         // fetch the formatted header text, if any
208
String JavaDoc headerText = null;
209         if (headerFormat != null) {
210             headerText = headerFormat.format(pageNumber);
211         }
212
213         // fetch the formatted footer text, if any
214
String JavaDoc footerText = null;
215         if (footerFormat != null) {
216             footerText = footerFormat.format(pageNumber);
217         }
218
219         // to store the bounds of the header and footer text
220
Rectangle2D hRect = null;
221         Rectangle2D fRect = null;
222
223         // the amount of vertical space needed for the header and footer text
224
int headerTextSpace = 0;
225         int footerTextSpace = 0;
226
227         // the amount of vertical space available for printing the table
228
int availableSpace = imgHeight;
229
230         // if there's header text, find out how much space is needed for it
231
// and subtract that from the available space
232
if (headerText != null) {
233             graphics.setFont(headerFont);
234             hRect = graphics.getFontMetrics().getStringBounds(headerText,
235                                                               graphics);
236
237             headerTextSpace = (int)Math.ceil(hRect.getHeight());
238             availableSpace -= headerTextSpace + H_F_SPACE;
239         }
240
241         // if there's footer text, find out how much space is needed for it
242
// and subtract that from the available space
243
if (footerText != null) {
244             graphics.setFont(footerFont);
245             fRect = graphics.getFontMetrics().getStringBounds(footerText,
246                                                               graphics);
247
248             footerTextSpace = (int)Math.ceil(fRect.getHeight());
249             availableSpace -= footerTextSpace + H_F_SPACE;
250         }
251
252         if (availableSpace <= 0) {
253             throw new PrinterException("Height of printable area is too small.");
254         }
255
256         // depending on the print mode, we may need a scale factor to
257
// fit the table's entire width on the page
258
double sf = 1.0D;
259         if (printMode == JXTable.PRINT_MODE_FIT_WIDTH &&
260                 totalColWidth > imgWidth) {
261
262             // if not, we would have thrown an acception previously
263
assert imgWidth > 0;
264
265             // it must be, according to the if-condition, since imgWidth > 0
266
assert totalColWidth > 1;
267
268             sf = (double)imgWidth / (double)totalColWidth;
269         }
270
271         // dictated by the previous two assertions
272
assert sf > 0;
273
274         // This is in a loop for two reasons:
275
// First, it allows us to catch up in case we're called starting
276
// with a non-zero pageIndex. Second, we know that we can be called
277
// for the same page multiple times. The condition of this while
278
// loop acts as a check, ensuring that we don't attempt to do the
279
// calculations again when we are called subsequent times for the
280
// same page.
281
while (last < pageIndex) {
282             // if we are finished all columns in all rows
283
if (row >= table.getRowCount() && col == 0) {
284                 return NO_SUCH_PAGE;
285             }
286
287             // rather than multiplying every row and column by the scale factor
288
// in findNextClip, just pass a width and height that have already
289
// been divided by it
290
int scaledWidth = (int)(imgWidth / sf);
291             int scaledHeight = (int)((availableSpace - hclip.height) / sf);
292
293             // calculate the area of the table to be printed for this page
294
findNextClip(scaledWidth, scaledHeight);
295
296             last++;
297         }
298
299         // translate into the co-ordinate system of the pageFormat
300
Graphics2D g2d = (Graphics2D)graphics;
301         g2d.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
302
303         // to save and store the transform
304
AffineTransform oldTrans;
305
306         // if there's footer text, print it at the bottom of the imageable area
307
if (footerText != null) {
308             oldTrans = g2d.getTransform();
309
310             g2d.translate(0, imgHeight - footerTextSpace);
311
312             printText(g2d, footerText, fRect, footerFont, imgWidth);
313
314             g2d.setTransform(oldTrans);
315         }
316
317         // if there's header text, print it at the top of the imageable area
318
// and then translate downwards
319
if (headerText != null) {
320             printText(g2d, headerText, hRect, headerFont, imgWidth);
321
322             g2d.translate(0, headerTextSpace + H_F_SPACE);
323         }
324
325         // constrain the table output to the available space
326
tempRect.x = 0;
327         tempRect.y = 0;
328         tempRect.width = imgWidth;
329         tempRect.height = availableSpace;
330         g2d.clip(tempRect);
331
332         // if we have a scale factor, scale the graphics object to fit
333
// the entire width
334
if (sf != 1.0D) {
335             g2d.scale(sf, sf);
336
337         // otherwise, ensure that the current portion of the table is
338
// centered horizontally
339
} else {
340             int diff = (imgWidth - clip.width) / 2;
341             g2d.translate(diff, 0);
342         }
343
344         // store the old transform and clip for later restoration
345
oldTrans = g2d.getTransform();
346         Shape oldClip = g2d.getClip();
347
348         // if there's a table header, print the current section and
349
// then translate downwards
350
if (header != null) {
351             hclip.x = clip.x;
352             hclip.width = clip.width;
353
354             g2d.translate(-hclip.x, 0);
355             g2d.clip(hclip);
356             header.print(g2d);
357
358             // restore the original transform and clip
359
g2d.setTransform(oldTrans);
360             g2d.setClip(oldClip);
361
362             // translate downwards
363
g2d.translate(0, hclip.height);
364         }
365
366         // print the current section of the table
367
g2d.translate(-clip.x, -clip.y);
368         g2d.clip(clip);
369         table.print(g2d);
370
371         // restore the original transform and clip
372
g2d.setTransform(oldTrans);
373         g2d.setClip(oldClip);
374
375         // draw a box around the table
376
g2d.setColor(Color.BLACK);
377         g2d.drawRect(0, 0, clip.width, hclip.height + clip.height);
378
379         return PAGE_EXISTS;
380     }
381
382     /**
383      * A helper method that encapsulates common code for rendering the
384      * header and footer text.
385      *
386      * @param g2d the graphics to draw into
387      * @param text the text to draw, non null
388      * @param rect the bounding rectangle for this text,
389      * as calculated at the given font, non null
390      * @param font the font to draw the text in, non null
391      * @param imgWidth the width of the area to draw into
392      */

393     private void printText(Graphics2D g2d,
394                            String JavaDoc text,
395                            Rectangle2D rect,
396                            Font font,
397                            int imgWidth) {
398
399             int tx;
400
401             // if the text is small enough to fit, center it
402
if (rect.getWidth() < imgWidth) {
403                 tx = (int)((imgWidth - rect.getWidth()) / 2);
404
405             // otherwise, if the table is LTR, ensure the left side of
406
// the text shows; the right can be clipped
407
} else if (table.getComponentOrientation().isLeftToRight()) {
408                 tx = 0;
409
410             // otherwise, ensure the right side of the text shows
411
} else {
412                 tx = -(int)(Math.ceil(rect.getWidth()) - imgWidth);
413             }
414
415             int ty = (int)Math.ceil(Math.abs(rect.getY()));
416             g2d.setColor(Color.BLACK);
417             g2d.setFont(font);
418             g2d.drawString(text, tx, ty);
419     }
420
421     /**
422      * Calculate the area of the table to be printed for
423      * the next page. This should only be called if there
424      * are rows and columns left to print.
425      *
426      * To avoid an infinite loop in printing, this will
427      * always put at least one cell on each page.
428      *
429      * @param pw the width of the area to print in
430      * @param ph the height of the area to print in
431      */

432     private void findNextClip(int pw, int ph) {
433         final boolean ltr = table.getComponentOrientation().isLeftToRight();
434
435         // if we're ready to start a new set of rows
436
if (col == 0) {
437             if (ltr) {
438                 // adjust clip to the left of the first column
439
clip.x = 0;
440             } else {
441                 // adjust clip to the right of the first column
442
clip.x = totalColWidth;
443             }
444
445             // adjust clip to the top of the next set of rows
446
clip.y += clip.height;
447
448             // adjust clip width and height to be zero
449
clip.width = 0;
450             clip.height = 0;
451
452             // fit as many rows as possible, and at least one
453
int rowCount = table.getRowCount();
454             int rowHeight = table.getRowHeight(row);
455             do {
456                 clip.height += rowHeight;
457
458                 if (++row >= rowCount) {
459                     break;
460                 }
461
462                 rowHeight = table.getRowHeight(row);
463             } while (clip.height + rowHeight <= ph);
464         }
465
466         // we can short-circuit for JTable.PRINT_MODE_FIT_WIDTH since
467
// we'll always fit all columns on the page
468
if (printMode == JXTable.PRINT_MODE_FIT_WIDTH) {
469             clip.x = 0;
470             clip.width = totalColWidth;
471             return;
472         }
473
474         if (ltr) {
475             // adjust clip to the left of the next set of columns
476
clip.x += clip.width;
477         }
478
479         // adjust clip width to be zero
480
clip.width = 0;
481
482         // fit as many columns as possible, and at least one
483
int colCount = table.getColumnCount();
484         int colWidth = colModel.getColumn(col).getWidth();
485         do {
486             clip.width += colWidth;
487             if (!ltr) {
488                 clip.x -= colWidth;
489             }
490
491             if (++col >= colCount) {
492                 // reset col to 0 to indicate we're finished all columns
493
col = 0;
494
495                 break;
496             }
497
498             colWidth = colModel.getColumn(col).getWidth();
499         } while (clip.width + colWidth <= pw);
500
501     }
502
503 }
504
Popular Tags