author | malenkov |
Wed, 30 Apr 2014 19:28:05 +0400 | |
changeset 24544 | c0133e7c7162 |
parent 5506 | 202f599c92aa |
permissions | -rw-r--r-- |
2 | 1 |
/* |
5506 | 2 |
* Copyright (c) 2003, 2008, Oracle and/or its affiliates. All rights reserved. |
2 | 3 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
4 |
* |
|
5 |
* This code is free software; you can redistribute it and/or modify it |
|
6 |
* under the terms of the GNU General Public License version 2 only, as |
|
5506 | 7 |
* published by the Free Software Foundation. Oracle designates this |
2 | 8 |
* particular file as subject to the "Classpath" exception as provided |
5506 | 9 |
* by Oracle in the LICENSE file that accompanied this code. |
2 | 10 |
* |
11 |
* This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 |
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 |
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 |
* version 2 for more details (a copy is included in the LICENSE file that |
|
15 |
* accompanied this code). |
|
16 |
* |
|
17 |
* You should have received a copy of the GNU General Public License version |
|
18 |
* 2 along with this work; if not, write to the Free Software Foundation, |
|
19 |
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 |
* |
|
5506 | 21 |
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
22 |
* or visit www.oracle.com if you need additional information or have any |
|
23 |
* questions. |
|
2 | 24 |
*/ |
25 |
||
26 |
package javax.swing; |
|
27 |
||
28 |
import javax.swing.table.*; |
|
29 |
import java.awt.*; |
|
30 |
import java.awt.print.*; |
|
31 |
import java.awt.geom.*; |
|
32 |
import java.text.MessageFormat; |
|
33 |
||
34 |
/** |
|
35 |
* An implementation of <code>Printable</code> for printing |
|
36 |
* <code>JTable</code>s. |
|
37 |
* <p> |
|
38 |
* This implementation spreads table rows naturally in sequence |
|
39 |
* across multiple pages, fitting as many rows as possible per page. |
|
40 |
* The distribution of columns, on the other hand, is controlled by a |
|
41 |
* printing mode parameter passed to the constructor. When |
|
42 |
* <code>JTable.PrintMode.NORMAL</code> is used, the implementation |
|
43 |
* handles columns in a similar manner to how it handles rows, spreading them |
|
44 |
* across multiple pages (in an order consistent with the table's |
|
45 |
* <code>ComponentOrientation</code>). |
|
46 |
* When <code>JTable.PrintMode.FIT_WIDTH</code> is given, the implementation |
|
47 |
* scales the output smaller if necessary, to ensure that all columns fit on |
|
48 |
* the page. (Note that width and height are scaled equally, ensuring that the |
|
49 |
* aspect ratio remains the same). |
|
50 |
* <p> |
|
51 |
* The portion of table printed on each page is headed by the |
|
52 |
* appropriate section of the table's <code>JTableHeader</code>. |
|
53 |
* <p> |
|
54 |
* Header and footer text can be added to the output by providing |
|
55 |
* <code>MessageFormat</code> instances to the constructor. The |
|
56 |
* printing code requests Strings from the formats by calling |
|
57 |
* their <code>format</code> method with a single parameter: |
|
58 |
* an <code>Object</code> array containing a single element of type |
|
59 |
* <code>Integer</code>, representing the current page number. |
|
60 |
* <p> |
|
61 |
* There are certain circumstances where this <code>Printable</code> |
|
62 |
* cannot fit items appropriately, resulting in clipped output. |
|
63 |
* These are: |
|
64 |
* <ul> |
|
65 |
* <li>In any mode, when the header or footer text is too wide to |
|
66 |
* fit completely in the printable area. The implementation |
|
67 |
* prints as much of the text as possible starting from the beginning, |
|
68 |
* as determined by the table's <code>ComponentOrientation</code>. |
|
69 |
* <li>In any mode, when a row is too tall to fit in the |
|
70 |
* printable area. The upper most portion of the row |
|
71 |
* is printed and no lower border is shown. |
|
72 |
* <li>In <code>JTable.PrintMode.NORMAL</code> when a column |
|
73 |
* is too wide to fit in the printable area. The center of the |
|
74 |
* column is printed and no left and right borders are shown. |
|
75 |
* </ul> |
|
76 |
* <p> |
|
77 |
* It is entirely valid for a developer to wrap this <code>Printable</code> |
|
78 |
* inside another in order to create complex reports and documents. They may |
|
79 |
* even request that different pages be rendered into different sized |
|
80 |
* printable areas. The implementation was designed to handle this by |
|
81 |
* performing most of its calculations on the fly. However, providing different |
|
82 |
* sizes works best when <code>JTable.PrintMode.FIT_WIDTH</code> is used, or |
|
83 |
* when only the printable width is changed between pages. This is because when |
|
84 |
* it is printing a set of rows in <code>JTable.PrintMode.NORMAL</code> and the |
|
85 |
* implementation determines a need to distribute columns across pages, |
|
86 |
* it assumes that all of those rows will fit on each subsequent page needed |
|
87 |
* to fit the columns. |
|
88 |
* <p> |
|
89 |
* It is the responsibility of the developer to ensure that the table is not |
|
90 |
* modified in any way after this <code>Printable</code> is created (invalid |
|
91 |
* modifications include changes in: size, renderers, or underlying data). |
|
92 |
* The behavior of this <code>Printable</code> is undefined if the table is |
|
93 |
* changed at any time after creation. |
|
94 |
* |
|
95 |
* @author Shannon Hickey |
|
96 |
*/ |
|
97 |
class TablePrintable implements Printable { |
|
98 |
||
99 |
/** The table to print. */ |
|
100 |
private JTable table; |
|
101 |
||
102 |
/** For quick reference to the table's header. */ |
|
103 |
private JTableHeader header; |
|
104 |
||
105 |
/** For quick reference to the table's column model. */ |
|
106 |
private TableColumnModel colModel; |
|
107 |
||
108 |
/** To save multiple calculations of total column width. */ |
|
109 |
private int totalColWidth; |
|
110 |
||
111 |
/** The printing mode of this printable. */ |
|
112 |
private JTable.PrintMode printMode; |
|
113 |
||
114 |
/** Provides the header text for the table. */ |
|
115 |
private MessageFormat headerFormat; |
|
116 |
||
117 |
/** Provides the footer text for the table. */ |
|
118 |
private MessageFormat footerFormat; |
|
119 |
||
120 |
/** The most recent page index asked to print. */ |
|
121 |
private int last = -1; |
|
122 |
||
123 |
/** The next row to print. */ |
|
124 |
private int row = 0; |
|
125 |
||
126 |
/** The next column to print. */ |
|
127 |
private int col = 0; |
|
128 |
||
129 |
/** Used to store an area of the table to be printed. */ |
|
130 |
private final Rectangle clip = new Rectangle(0, 0, 0, 0); |
|
131 |
||
132 |
/** Used to store an area of the table's header to be printed. */ |
|
133 |
private final Rectangle hclip = new Rectangle(0, 0, 0, 0); |
|
134 |
||
135 |
/** Saves the creation of multiple rectangles. */ |
|
136 |
private final Rectangle tempRect = new Rectangle(0, 0, 0, 0); |
|
137 |
||
138 |
/** Vertical space to leave between table and header/footer text. */ |
|
139 |
private static final int H_F_SPACE = 8; |
|
140 |
||
141 |
/** Font size for the header text. */ |
|
142 |
private static final float HEADER_FONT_SIZE = 18.0f; |
|
143 |
||
144 |
/** Font size for the footer text. */ |
|
145 |
private static final float FOOTER_FONT_SIZE = 12.0f; |
|
146 |
||
147 |
/** The font to use in rendering header text. */ |
|
148 |
private Font headerFont; |
|
149 |
||
150 |
/** The font to use in rendering footer text. */ |
|
151 |
private Font footerFont; |
|
152 |
||
153 |
/** |
|
154 |
* Create a new <code>TablePrintable</code> for the given |
|
155 |
* <code>JTable</code>. Header and footer text can be specified using the |
|
156 |
* two <code>MessageFormat</code> parameters. When called upon to provide |
|
157 |
* a String, each format is given the current page number. |
|
158 |
* |
|
159 |
* @param table the table to print |
|
160 |
* @param printMode the printing mode for this printable |
|
161 |
* @param headerFormat a <code>MessageFormat</code> specifying the text to |
|
162 |
* be used in printing a header, or null for none |
|
163 |
* @param footerFormat a <code>MessageFormat</code> specifying the text to |
|
164 |
* be used in printing a footer, or null for none |
|
165 |
* @throws IllegalArgumentException if passed an invalid print mode |
|
166 |
*/ |
|
167 |
public TablePrintable(JTable table, |
|
168 |
JTable.PrintMode printMode, |
|
169 |
MessageFormat headerFormat, |
|
170 |
MessageFormat footerFormat) { |
|
171 |
||
172 |
this.table = table; |
|
173 |
||
174 |
header = table.getTableHeader(); |
|
175 |
colModel = table.getColumnModel(); |
|
176 |
totalColWidth = colModel.getTotalColumnWidth(); |
|
177 |
||
178 |
if (header != null) { |
|
179 |
// the header clip height can be set once since it's unchanging |
|
180 |
hclip.height = header.getHeight(); |
|
181 |
} |
|
182 |
||
183 |
this.printMode = printMode; |
|
184 |
||
185 |
this.headerFormat = headerFormat; |
|
186 |
this.footerFormat = footerFormat; |
|
187 |
||
188 |
// derive the header and footer font from the table's font |
|
189 |
headerFont = table.getFont().deriveFont(Font.BOLD, |
|
190 |
HEADER_FONT_SIZE); |
|
191 |
footerFont = table.getFont().deriveFont(Font.PLAIN, |
|
192 |
FOOTER_FONT_SIZE); |
|
193 |
} |
|
194 |
||
195 |
/** |
|
196 |
* Prints the specified page of the table into the given {@link Graphics} |
|
197 |
* context, in the specified format. |
|
198 |
* |
|
199 |
* @param graphics the context into which the page is drawn |
|
200 |
* @param pageFormat the size and orientation of the page being drawn |
|
201 |
* @param pageIndex the zero based index of the page to be drawn |
|
202 |
* @return PAGE_EXISTS if the page is rendered successfully, or |
|
203 |
* NO_SUCH_PAGE if a non-existent page index is specified |
|
204 |
* @throws PrinterException if an error causes printing to be aborted |
|
205 |
*/ |
|
206 |
public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) |
|
207 |
throws PrinterException { |
|
208 |
||
209 |
// for easy access to these values |
|
210 |
final int imgWidth = (int)pageFormat.getImageableWidth(); |
|
211 |
final int imgHeight = (int)pageFormat.getImageableHeight(); |
|
212 |
||
213 |
if (imgWidth <= 0) { |
|
214 |
throw new PrinterException("Width of printable area is too small."); |
|
215 |
} |
|
216 |
||
217 |
// to pass the page number when formatting the header and footer text |
|
438
2ae294e4518c
6613529: Avoid duplicate object creation within JDK packages
dav
parents:
2
diff
changeset
|
218 |
Object[] pageNumber = new Object[]{Integer.valueOf(pageIndex + 1)}; |
2 | 219 |
|
220 |
// fetch the formatted header text, if any |
|
221 |
String headerText = null; |
|
222 |
if (headerFormat != null) { |
|
223 |
headerText = headerFormat.format(pageNumber); |
|
224 |
} |
|
225 |
||
226 |
// fetch the formatted footer text, if any |
|
227 |
String footerText = null; |
|
228 |
if (footerFormat != null) { |
|
229 |
footerText = footerFormat.format(pageNumber); |
|
230 |
} |
|
231 |
||
232 |
// to store the bounds of the header and footer text |
|
233 |
Rectangle2D hRect = null; |
|
234 |
Rectangle2D fRect = null; |
|
235 |
||
236 |
// the amount of vertical space needed for the header and footer text |
|
237 |
int headerTextSpace = 0; |
|
238 |
int footerTextSpace = 0; |
|
239 |
||
240 |
// the amount of vertical space available for printing the table |
|
241 |
int availableSpace = imgHeight; |
|
242 |
||
243 |
// if there's header text, find out how much space is needed for it |
|
244 |
// and subtract that from the available space |
|
245 |
if (headerText != null) { |
|
246 |
graphics.setFont(headerFont); |
|
247 |
hRect = graphics.getFontMetrics().getStringBounds(headerText, |
|
248 |
graphics); |
|
249 |
||
250 |
headerTextSpace = (int)Math.ceil(hRect.getHeight()); |
|
251 |
availableSpace -= headerTextSpace + H_F_SPACE; |
|
252 |
} |
|
253 |
||
254 |
// if there's footer text, find out how much space is needed for it |
|
255 |
// and subtract that from the available space |
|
256 |
if (footerText != null) { |
|
257 |
graphics.setFont(footerFont); |
|
258 |
fRect = graphics.getFontMetrics().getStringBounds(footerText, |
|
259 |
graphics); |
|
260 |
||
261 |
footerTextSpace = (int)Math.ceil(fRect.getHeight()); |
|
262 |
availableSpace -= footerTextSpace + H_F_SPACE; |
|
263 |
} |
|
264 |
||
265 |
if (availableSpace <= 0) { |
|
266 |
throw new PrinterException("Height of printable area is too small."); |
|
267 |
} |
|
268 |
||
269 |
// depending on the print mode, we may need a scale factor to |
|
270 |
// fit the table's entire width on the page |
|
271 |
double sf = 1.0D; |
|
272 |
if (printMode == JTable.PrintMode.FIT_WIDTH && |
|
273 |
totalColWidth > imgWidth) { |
|
274 |
||
275 |
// if not, we would have thrown an acception previously |
|
276 |
assert imgWidth > 0; |
|
277 |
||
278 |
// it must be, according to the if-condition, since imgWidth > 0 |
|
279 |
assert totalColWidth > 1; |
|
280 |
||
281 |
sf = (double)imgWidth / (double)totalColWidth; |
|
282 |
} |
|
283 |
||
284 |
// dictated by the previous two assertions |
|
285 |
assert sf > 0; |
|
286 |
||
287 |
// This is in a loop for two reasons: |
|
288 |
// First, it allows us to catch up in case we're called starting |
|
289 |
// with a non-zero pageIndex. Second, we know that we can be called |
|
290 |
// for the same page multiple times. The condition of this while |
|
291 |
// loop acts as a check, ensuring that we don't attempt to do the |
|
292 |
// calculations again when we are called subsequent times for the |
|
293 |
// same page. |
|
294 |
while (last < pageIndex) { |
|
295 |
// if we are finished all columns in all rows |
|
296 |
if (row >= table.getRowCount() && col == 0) { |
|
297 |
return NO_SUCH_PAGE; |
|
298 |
} |
|
299 |
||
300 |
// rather than multiplying every row and column by the scale factor |
|
301 |
// in findNextClip, just pass a width and height that have already |
|
302 |
// been divided by it |
|
303 |
int scaledWidth = (int)(imgWidth / sf); |
|
304 |
int scaledHeight = (int)((availableSpace - hclip.height) / sf); |
|
305 |
||
306 |
// calculate the area of the table to be printed for this page |
|
307 |
findNextClip(scaledWidth, scaledHeight); |
|
308 |
||
309 |
last++; |
|
310 |
} |
|
311 |
||
312 |
// create a copy of the graphics so we don't affect the one given to us |
|
313 |
Graphics2D g2d = (Graphics2D)graphics.create(); |
|
314 |
||
315 |
// translate into the co-ordinate system of the pageFormat |
|
316 |
g2d.translate(pageFormat.getImageableX(), pageFormat.getImageableY()); |
|
317 |
||
318 |
// to save and store the transform |
|
319 |
AffineTransform oldTrans; |
|
320 |
||
321 |
// if there's footer text, print it at the bottom of the imageable area |
|
322 |
if (footerText != null) { |
|
323 |
oldTrans = g2d.getTransform(); |
|
324 |
||
325 |
g2d.translate(0, imgHeight - footerTextSpace); |
|
326 |
||
327 |
printText(g2d, footerText, fRect, footerFont, imgWidth); |
|
328 |
||
329 |
g2d.setTransform(oldTrans); |
|
330 |
} |
|
331 |
||
332 |
// if there's header text, print it at the top of the imageable area |
|
333 |
// and then translate downwards |
|
334 |
if (headerText != null) { |
|
335 |
printText(g2d, headerText, hRect, headerFont, imgWidth); |
|
336 |
||
337 |
g2d.translate(0, headerTextSpace + H_F_SPACE); |
|
338 |
} |
|
339 |
||
340 |
// constrain the table output to the available space |
|
341 |
tempRect.x = 0; |
|
342 |
tempRect.y = 0; |
|
343 |
tempRect.width = imgWidth; |
|
344 |
tempRect.height = availableSpace; |
|
345 |
g2d.clip(tempRect); |
|
346 |
||
347 |
// if we have a scale factor, scale the graphics object to fit |
|
348 |
// the entire width |
|
349 |
if (sf != 1.0D) { |
|
350 |
g2d.scale(sf, sf); |
|
351 |
||
352 |
// otherwise, ensure that the current portion of the table is |
|
353 |
// centered horizontally |
|
354 |
} else { |
|
355 |
int diff = (imgWidth - clip.width) / 2; |
|
356 |
g2d.translate(diff, 0); |
|
357 |
} |
|
358 |
||
359 |
// store the old transform and clip for later restoration |
|
360 |
oldTrans = g2d.getTransform(); |
|
361 |
Shape oldClip = g2d.getClip(); |
|
362 |
||
363 |
// if there's a table header, print the current section and |
|
364 |
// then translate downwards |
|
365 |
if (header != null) { |
|
366 |
hclip.x = clip.x; |
|
367 |
hclip.width = clip.width; |
|
368 |
||
369 |
g2d.translate(-hclip.x, 0); |
|
370 |
g2d.clip(hclip); |
|
371 |
header.print(g2d); |
|
372 |
||
373 |
// restore the original transform and clip |
|
374 |
g2d.setTransform(oldTrans); |
|
375 |
g2d.setClip(oldClip); |
|
376 |
||
377 |
// translate downwards |
|
378 |
g2d.translate(0, hclip.height); |
|
379 |
} |
|
380 |
||
381 |
// print the current section of the table |
|
382 |
g2d.translate(-clip.x, -clip.y); |
|
383 |
g2d.clip(clip); |
|
384 |
table.print(g2d); |
|
385 |
||
386 |
// restore the original transform and clip |
|
387 |
g2d.setTransform(oldTrans); |
|
388 |
g2d.setClip(oldClip); |
|
389 |
||
390 |
// draw a box around the table |
|
391 |
g2d.setColor(Color.BLACK); |
|
392 |
g2d.drawRect(0, 0, clip.width, hclip.height + clip.height); |
|
393 |
||
394 |
// dispose the graphics copy |
|
395 |
g2d.dispose(); |
|
396 |
||
397 |
return PAGE_EXISTS; |
|
398 |
} |
|
399 |
||
400 |
/** |
|
401 |
* A helper method that encapsulates common code for rendering the |
|
402 |
* header and footer text. |
|
403 |
* |
|
404 |
* @param g2d the graphics to draw into |
|
405 |
* @param text the text to draw, non null |
|
406 |
* @param rect the bounding rectangle for this text, |
|
407 |
* as calculated at the given font, non null |
|
408 |
* @param font the font to draw the text in, non null |
|
409 |
* @param imgWidth the width of the area to draw into |
|
410 |
*/ |
|
411 |
private void printText(Graphics2D g2d, |
|
412 |
String text, |
|
413 |
Rectangle2D rect, |
|
414 |
Font font, |
|
415 |
int imgWidth) { |
|
416 |
||
417 |
int tx; |
|
418 |
||
419 |
// if the text is small enough to fit, center it |
|
420 |
if (rect.getWidth() < imgWidth) { |
|
421 |
tx = (int)((imgWidth - rect.getWidth()) / 2); |
|
422 |
||
423 |
// otherwise, if the table is LTR, ensure the left side of |
|
424 |
// the text shows; the right can be clipped |
|
425 |
} else if (table.getComponentOrientation().isLeftToRight()) { |
|
426 |
tx = 0; |
|
427 |
||
428 |
// otherwise, ensure the right side of the text shows |
|
429 |
} else { |
|
430 |
tx = -(int)(Math.ceil(rect.getWidth()) - imgWidth); |
|
431 |
} |
|
432 |
||
433 |
int ty = (int)Math.ceil(Math.abs(rect.getY())); |
|
434 |
g2d.setColor(Color.BLACK); |
|
435 |
g2d.setFont(font); |
|
436 |
g2d.drawString(text, tx, ty); |
|
437 |
} |
|
438 |
||
439 |
/** |
|
440 |
* Calculate the area of the table to be printed for |
|
441 |
* the next page. This should only be called if there |
|
442 |
* are rows and columns left to print. |
|
443 |
* |
|
444 |
* To avoid an infinite loop in printing, this will |
|
445 |
* always put at least one cell on each page. |
|
446 |
* |
|
447 |
* @param pw the width of the area to print in |
|
448 |
* @param ph the height of the area to print in |
|
449 |
*/ |
|
450 |
private void findNextClip(int pw, int ph) { |
|
451 |
final boolean ltr = table.getComponentOrientation().isLeftToRight(); |
|
452 |
||
453 |
// if we're ready to start a new set of rows |
|
454 |
if (col == 0) { |
|
455 |
if (ltr) { |
|
456 |
// adjust clip to the left of the first column |
|
457 |
clip.x = 0; |
|
458 |
} else { |
|
459 |
// adjust clip to the right of the first column |
|
460 |
clip.x = totalColWidth; |
|
461 |
} |
|
462 |
||
463 |
// adjust clip to the top of the next set of rows |
|
464 |
clip.y += clip.height; |
|
465 |
||
466 |
// adjust clip width and height to be zero |
|
467 |
clip.width = 0; |
|
468 |
clip.height = 0; |
|
469 |
||
470 |
// fit as many rows as possible, and at least one |
|
471 |
int rowCount = table.getRowCount(); |
|
472 |
int rowHeight = table.getRowHeight(row); |
|
473 |
do { |
|
474 |
clip.height += rowHeight; |
|
475 |
||
476 |
if (++row >= rowCount) { |
|
477 |
break; |
|
478 |
} |
|
479 |
||
480 |
rowHeight = table.getRowHeight(row); |
|
481 |
} while (clip.height + rowHeight <= ph); |
|
482 |
} |
|
483 |
||
484 |
// we can short-circuit for JTable.PrintMode.FIT_WIDTH since |
|
485 |
// we'll always fit all columns on the page |
|
486 |
if (printMode == JTable.PrintMode.FIT_WIDTH) { |
|
487 |
clip.x = 0; |
|
488 |
clip.width = totalColWidth; |
|
489 |
return; |
|
490 |
} |
|
491 |
||
492 |
if (ltr) { |
|
493 |
// adjust clip to the left of the next set of columns |
|
494 |
clip.x += clip.width; |
|
495 |
} |
|
496 |
||
497 |
// adjust clip width to be zero |
|
498 |
clip.width = 0; |
|
499 |
||
500 |
// fit as many columns as possible, and at least one |
|
501 |
int colCount = table.getColumnCount(); |
|
502 |
int colWidth = colModel.getColumn(col).getWidth(); |
|
503 |
do { |
|
504 |
clip.width += colWidth; |
|
505 |
if (!ltr) { |
|
506 |
clip.x -= colWidth; |
|
507 |
} |
|
508 |
||
509 |
if (++col >= colCount) { |
|
510 |
// reset col to 0 to indicate we're finished all columns |
|
511 |
col = 0; |
|
512 |
||
513 |
break; |
|
514 |
} |
|
515 |
||
516 |
colWidth = colModel.getColumn(col).getWidth(); |
|
517 |
} while (clip.width + colWidth <= pw); |
|
518 |
||
519 |
} |
|
520 |
||
521 |
} |