Source code: com/pjsofts/eurobudget/gui/PrintableComponent.java
1 /*
2 * PrintableComponent.java
3 *
4 * Created on 29 janvier 2003, 16:16
5 */
6
7 package com.pjsofts.eurobudget.gui;
8
9 import com.pjsofts.eurobudget.EBConstants;
10 import com.pjsofts.eurobudget.EBPrefs;
11 import com.pjsofts.eurobudget.EBPrefsService;
12 import java.awt.*;
13 import java.awt.geom.AffineTransform;
14 import java.awt.image.BufferedImage;
15 import java.awt.print.PageFormat;
16 import java.awt.print.Pageable;
17 import java.awt.print.Printable;
18 import javax.swing.BorderFactory;
19 import javax.swing.JComponent;
20 import javax.swing.RepaintManager;
21 import javax.swing.border.BevelBorder;
22
23 /**
24 * Basic Component that is able to print or preview another component.
25 * Receive an awt Component in parameters and prints it.
26 * Could be useed as a Printable or as a JComponent (to preview the printing)
27 */
28 public class PrintableComponent extends JComponent implements Printable, Pageable {
29
30 /** Initial awt component to render */
31 private Component component = null;
32 /** Page format to use */
33 private PageFormat pageFormat = null;
34
35 // too memory consuming depending of size of component :
36 // /** cache of the raster of the component
37 // */
38 // private BufferedImage buffer = null;
39
40 /** flag: make the component fit in page width if needed */
41 private boolean autoFit = true;
42 /** if component is resized; keep its ratio */
43 private boolean keepRatio = true;
44 /** current index of page to display (starting from 0 like Print API) */
45 private int currentPage = 0;
46 /** last computed value for this */
47 private int numberOfPages = 0;
48
49 /** fit ratio */
50 private double fitRatio = 1.0d;
51 /** zoom value */
52 private double zoom = 1.0d;
53
54 /** final width for buffer to be in page (same as buffer ) */
55 private int bufferW = 0;
56 /** same as */
57 private int bufferH = 0;
58 // /** portion width, final width displayed in one portion (same as buffer ) */
59 // private double width = 0;
60 /** portion height, same as imageableHeight in one portion */
61 private int portionH = 0;
62
63 /** width of the page on screen in pixel (could be different as the one in page format) due of zoom */
64 private int pageWidth = 0;
65 private int pageHeight = 0;
66
67 /**
68 * @param autoFit true means expand or collapse component view to page width
69 *
70 */
71 public PrintableComponent(Component component, PageFormat pf, boolean autoFit) {
72 super();
73 this.component = component;
74 this.pageFormat = pf;
75 this.setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED));
76 // reuse static variables (otherwise the action print doesn't get same values as for preview)
77 autoFit = autoFit;
78 keepRatio = true;
79 currentPage = 0;
80 updateInternalData();
81 }
82
83 /** convenient constructor that take autofit value from preferences */
84 public PrintableComponent(Component component, PageFormat pf) {
85 this(component, pf, EBPrefsService.getBoolean(EBPrefs.PRINT_AUTOFIT,true));
86 }
87
88 /**
89 * throws PrinterExceptionPrints the page at the specified index into the specified Graphics context in the specified format. A PrinterJob calls the Printable interface to request that a page be rendered into the context specified by graphics. The format of the page to be drawn is specified by pageFormat. The zero based index of the requested page is specified by pageIndex. If the requested page does not exist then this method returns NO_SUCH_PAGE; otherwise PAGE_EXISTS is returned. The Graphics class or subclass implements the PrinterGraphics interface to provide additional information. If the Printable object aborts the print job then it throws a PrinterException.
90 *
91 * Parameters:
92 * graphics - the context into which the page is drawn
93 * pageFormat - the size and orientation of the page being drawn
94 * pageIndex - the zero based index of the page to be drawn
95 * Returns:
96 * PAGE_EXISTS if the page is rendered successfully or NO_SUCH_PAGE if pageIndex specifies a non-existent page.
97 * Throws:
98 * PrinterException - thrown when the print job is terminated. */
99 public int print(Graphics graphics, PageFormat pf, int pageIndex) {
100 if ( pf.equals(this.pageFormat) ) {
101 this.pageFormat = pf;
102 updateInternalData();
103 }
104 if ( pageIndex <0 || pageIndex >= getNumberOfPages() ) {
105 return Printable.NO_SUCH_PAGE;
106 }
107 double ix = pageFormat.getImageableX();
108 double iy = pageFormat.getImageableY();
109 // draw the good portion of component
110 int sx = 0;
111 int sy = (int)(portionH*currentPage);
112 int sw = (int)(bufferW*fitRatio);
113 int sh = (int)Math.min(portionH, bufferH - sy);
114 if ( EBConstants.DEBUG )
115 System.out.println("Portion: x="+sx+", y= "+sy+" width="+sw+" height="+sh);
116
117 BufferedImage tmpImage = new BufferedImage(sw,sh ,BufferedImage.TYPE_3BYTE_BGR);
118 Graphics2D og = tmpImage.createGraphics();
119 og.setBackground(Color.white);
120 og.clearRect(0,0, sw,sh);
121 og.translate(-sx,-sy);
122 og.setClip(sx,sy,sw,sh);
123 // rm.setDoubleBufferingEnabled(false);
124 component.paint(og);
125 // rm.setDoubleBufferingEnabled(isDBE);
126
127 // Paint on screen :
128 Graphics2D g2 = (Graphics2D) graphics;
129 // draw the background: blank page with margin
130 g2.setBackground(Color.white);
131 g2.clearRect(0,0, pageWidth,pageHeight);
132 g2.translate(ix*zoom, iy*zoom);
133 // recopy this computed image on screen :
134 g2.drawRenderedImage(tmpImage, AffineTransform.getScaleInstance(zoom/fitRatio, zoom/fitRatio));
135 og.dispose();
136
137 return Printable.PAGE_EXISTS;
138 }
139
140 /** Invoked by Swing to draw components.
141 *
142 * @param g the <code>Graphics</code> context in which to paint
143 * @see #paintComponent
144 * @see #paintBorder
145 * @see #paintChildren
146 * @see #getComponentGraphics
147 * @see #repaint
148 *
149 */
150 protected void paintComponent(Graphics graph) {
151 if ( EBConstants.DEBUG ) {
152 System.out.println("currentPage="+currentPage);
153 System.out.println("pageFormat="+pageFormat);
154 }
155 print(graph,pageFormat, currentPage);
156 }
157
158 /**
159 * update current number of pages and cache of original component image.
160 * compute new size of component (width, height) to fit the page.
161 * To call each time pageFormat or component changed.
162 */
163 private void updateInternalData() {
164 // The area of the printable area.
165 double ix = pageFormat.getImageableX();
166 double iy = pageFormat.getImageableY();
167 double iw = pageFormat.getImageableWidth();
168 double ih = pageFormat.getImageableHeight();
169 int pw = (int)pageFormat.getWidth();
170 int ph = (int)pageFormat.getHeight();
171 // The component
172 int compW = component.getWidth();
173 int compH = component.getHeight();
174 if ( autoFit && keepRatio ) {
175 this.bufferW = (int)iw;
176 this.bufferH = (int)(compH * bufferW / compW );
177 this.fitRatio = compW / iw;
178 this.portionH = (int)(ih*fitRatio);
179 } else {
180 this.bufferW = (int)compW;
181 this.bufferH = (int)compH;
182 this.fitRatio = 1.0d;
183 this.portionH = (int)ih;
184 }
185 // double scaleX = bufferW / compW;
186 // double scaleY = scaleX;
187
188 this.pageWidth = (int)(pw *zoom);
189 this.pageHeight = (int)(ph *zoom);
190 if ( EBConstants.DEBUG ) {
191 System.out.println("Page: ix="+ix+" iy="+iy+" iw="+iw+" ih="+ih+" pw="+pw+" ph="+ph);
192 System.out.println("Original component: compW="+compW+" compH="+compH);
193 // System.out.println("scaleX="+scaleX+" scaleY="+scaleY);
194 System.out.println("Resized component: bufferW="+bufferW+" bufferH="+bufferH);
195 System.out.println("portionH="+portionH);
196 System.out.println("Zoom="+zoom+" FitRatio="+fitRatio);
197 System.out.println("Final component on screen: width="+pageWidth+" height="+pageHeight);
198 }
199 // try to avoid !
200 //java.lang.IllegalStateException: constrain(xywh) is not supported for complex transform.
201 //at sun.java2d.SunGraphics2D.constrain(SunGraphics2D.java:295)
202 // may cause an OutOfMemoryError also cause too memory consuming !!!
203 // System.gc();
204 // BufferedImage originalImage = new BufferedImage((int)compW, (int) compH,BufferedImage.TYPE_3BYTE_BGR);
205 // Graphics2D orig2D = originalImage.createGraphics();
206 // component.paintAll(orig2D);
207 // this.buffer = new BufferedImage((int)bufferW, (int) bufferH,BufferedImage.TYPE_3BYTE_BGR);
208 // Graphics2D g2D = buffer.createGraphics();
209 // g2D.scale(scaleX,scaleY);
210 // component.paintAll(g2D);
211 // scall on the fly :
212 // g2D.drawImage(originalImage,0,0,(int)bufferW, (int)bufferH, this);
213 // to try :
214 // Graphics og = off.getGraphics();
215 // og.translate(-clipX,-clipY);
216 // og.setClip(clipX,clipY,clipW,clipH);
217 // rm.setDoubleBufferingEnabled(false);
218 // view.paint(og);
219 // rm.setDoubleBufferingEnabled(isDBE);
220 // og.dispose();
221
222 this.numberOfPages = (int)(bufferH / portionH) + 1;
223 if ( currentPage >= numberOfPages ) {
224 currentPage = 0;
225 }
226 this.setPreferredSize(new Dimension(pageWidth,pageHeight));
227 this.setSize(pageWidth, pageHeight);
228 // originalImage = null;// gc flag
229 // System.gc();
230 }
231
232 /**
233 * Returns the number of pages in the set.
234 * To enable advanced printing features,
235 * it is recommended that <code>Pageable</code>
236 * implementations return the true number of pages
237 * rather than the
238 * UNKNOWN_NUMBER_OF_PAGES constant.
239 * @return the number of pages in this <code>Pageable</code>.
240 */
241 public int getNumberOfPages() {
242 // updateInternalData should have been called once
243 return this.numberOfPages;
244 }
245
246
247 /** If the <code>preferredSize</code> has been set to a
248 * non-<code>null</code> value just returns it.
249 * If the UI delegate's <code>getPreferredSize</code>
250 * method returns a non <code>null</code> value then return that;
251 * otherwise defer to the component's layout manager.
252 *
253 * @return the value of the <code>preferredSize</code> property
254 * @see #setPreferredSize
255 * @see ComponentUI
256 *
257 */
258 // public Dimension getPreferredSize() {
259 // Dimension retValue;
260 // if ( pageFormat != null )
261 // retValue = new Dimension((int)pageFormat.getWidth(), (int)pageFormat.getHeight());
262 // else if ( this.component != null )
263 // retValue = component.getPreferredSize();
264 // else
265 // retValue = new Dimension(50, 50);
266 // //System.out.println("getPreferredSize="+retValue);
267 // return retValue;
268 // }
269 //
270 /**
271 *
272 */
273 // public Dimension getSize() {
274 // Dimension retValue;
275 // if ( pageFormat != null )
276 // retValue = new Dimension((int)pageFormat.getWidth(), (int)pageFormat.getHeight());
277 // else if ( this.component != null )
278 // retValue = component.getSize();
279 // else
280 // retValue = new Dimension(50, 50);
281 // //System.out.println("getSize="+retValue);
282 // return retValue;
283 // }
284
285 /** Returns true if this component is completely opaque.
286 * <p>
287 * An opaque component paints every pixel within its
288 * rectangular bounds. A non-opaque component paints only a subset of
289 * its pixels or none at all, allowing the pixels underneath it to
290 * "show through". Therefore, a component that does not fully paint
291 * its pixels provides a degree of transparency.
292 * <p>
293 * Subclasses that guarantee to always completely paint their contents
294 * should override this method and return true.
295 *
296 * @return true if this component is completely opaque
297 * @see #setOpaque
298 *
299 */
300 public boolean isOpaque() {
301 return true;
302 }
303
304 /** Returns the <code>PageFormat</code> of the page specified by
305 * <code>pageIndex</code>.
306 * @param pageIndex the zero based index of the page whose
307 * <code>PageFormat</code> is being requested
308 * @return the <code>PageFormat</code> describing the size and
309 * orientation.
310 * @throws IndexOutOfBoundsException if
311 * the <code>Pageable</code> does not contain the requested
312 * page.
313 *
314 */
315 public PageFormat getPageFormat(int pageIndex) throws IndexOutOfBoundsException {
316 return this.pageFormat;
317 }
318
319 /** Returns the <code>Printable</code> instance responsible for
320 * rendering the page specified by <code>pageIndex</code>.
321 * @param pageIndex the zero based index of the page whose
322 * <code>Printable</code> is being requested
323 * @return the <code>Printable</code> that renders the page.
324 * @throws IndexOutOfBoundsException if
325 * the <code>Pageable</code> does not contain the requested
326 * page.
327 *
328 */
329 public Printable getPrintable(int pageIndex) throws IndexOutOfBoundsException {
330 return this;
331 }
332
333 /** Getter for property autoFit.
334 * @return Value of property autoFit.
335 *
336 */
337 public boolean isAutoFit() {
338 return autoFit;
339 }
340
341 /** Setter for property autoFit.
342 * @param autoFit New value of property autoFit.
343 *
344 */
345 public void setAutoFit(boolean autoFit) {
346 this.autoFit = autoFit;
347 updateInternalData();
348 setCurrentPage(currentPage);
349 }
350
351 /** Getter for property pageFormat.
352 * @return Value of property pageFormat.
353 *
354 */
355 public java.awt.print.PageFormat getPageFormat() {
356 return pageFormat;
357 }
358
359 /** Setter for property pageFormat.
360 * @param pageFormat New value of property pageFormat.
361 *
362 */
363 public void setPageFormat(java.awt.print.PageFormat pageFormat) {
364 this.pageFormat = pageFormat;
365 updateInternalData();
366 setCurrentPage(currentPage);
367 }
368
369 /** Getter for property component.
370 * @return Value of property component.
371 *
372 */
373 public java.awt.Component getComponent() {
374 return component;
375 }
376
377 /** Setter for property component.
378 * @param component New value of property component.
379 *
380 */
381 public void setComponent(java.awt.Component component) {
382 this.component = component;
383 updateInternalData();
384 setCurrentPage(currentPage);
385 }
386
387 /** Getter for property currentPage.
388 * @return Value of property currentPage.
389 *
390 */
391 public int getCurrentPage() {
392 return currentPage;
393 }
394
395 /** Setter for property currentPage.
396 * @param currentPage New value of property currentPage.
397 * warning : valid indexes are from 0 to getNumberOfPages -1
398 */
399 public void setCurrentPage(int currentPage) {
400 if ( currentPage >= 0 && currentPage < this.numberOfPages ) {
401 this.currentPage = currentPage;
402 // this.invalidate();
403 // this.validate();
404 this.revalidate();
405 this.repaint();
406 }
407 }
408
409 /** Getter for property zoom.
410 * @return Value of property zoom.
411 *
412 */
413 public double getZoom() {
414 return zoom;
415 }
416
417 /** Setter for property zoom.
418 * @param zoom New value of property zoom.
419 *
420 */
421 public void setZoom(double zoom) {
422 this.zoom = zoom;
423 updateInternalData();
424 setCurrentPage(currentPage);
425 }
426
427 }