001: /*
002: * $Id: AbstractPainter.java,v 1.18 2006/09/18 19:42:07 rbair Exp $
003: *
004: * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005: * Santa Clara, California 95054, U.S.A. All rights reserved.
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
020: */
021:
022: package org.jdesktop.swingx.painter;
023:
024: import java.awt.Color;
025: import java.awt.Composite;
026: import java.awt.Font;
027: import java.awt.Graphics2D;
028: import java.awt.Paint;
029: import java.awt.RenderingHints;
030: import java.awt.Shape;
031: import java.awt.Stroke;
032: import java.awt.Transparency;
033: import java.awt.geom.AffineTransform;
034: import java.awt.geom.RoundRectangle2D;
035: import java.awt.image.BufferedImage;
036: import java.awt.image.BufferedImageOp;
037: import java.lang.ref.SoftReference;
038: import java.util.HashMap;
039: import java.util.Map;
040: import javax.swing.JComponent;
041: import org.jdesktop.beans.AbstractBean;
042: import org.jdesktop.swingx.util.PaintUtils;
043: import org.jdesktop.swingx.util.Resize;
044:
045: /**
046: * <p>A convenient base class from which concrete Painter implementations may
047: * extend. It extends JavaBean and thus provides property change notification
048: * (which is crucial for the Painter implementations to be available in a
049: * GUI builder). It also saves off the Graphics2D state in its "saveState" method,
050: * and restores that state in the "restoreState" method. Sublasses simply need
051: * to extend AbstractPainter and implement the paintBackground method.
052: *
053: * <p>For example, here is the paintBackground method of BackgroundPainter:
054: * <pre><code>
055: * public void paintBackground(Graphics2D g, JComponent component) {
056: * g.setColor(component.getBackground());
057: * g.fillRect(0, 0, component.getWidth(), component.getHeight());
058: * }
059: * </code></pre>
060: *
061: * <p>AbstractPainter provides a very useful default implementation of
062: * the paint method. It:
063: * <ol>
064: * <li>Saves off the old state</li>
065: * <li>Sets any specified rendering hints</li>
066: * <li>Sets the Clip if there is one</li>
067: * <li>Sets the Composite if there is one</li>
068: * <li>Delegates to paintBackground</li>
069: * <li>Restores the original Graphics2D state</li>
070: * <ol></p>
071: *
072: * <p>Specifying rendering hints can greatly improve the visual impact of your
073: * applications. For example, by default Swing doesn't do much in the way of
074: * antialiasing (except for Fonts, but that's another story). Pinstripes don't
075: * look so good without antialiasing. So if I were going to paint pinstripes, I
076: * might do it like this:
077: * <pre><code>
078: * PinstripePainter p = new PinstripePainter();
079: * p.setAntialiasing(RenderingHints.VALUE_ANTIALIAS_ON);
080: * </code></pre></p>
081: *
082: * <p>You can read more about antialiasing and other rendering hints in the
083: * java.awt.RenderingHints documentation. <strong>By nature, changing the rendering
084: * hints may have an impact on performance. Certain hints require more
085: * computation, others require less</strong></p>
086: *
087: * @author rbair
088: */
089: public abstract class AbstractPainter<T extends JComponent> extends
090: AbstractBean implements Painter<T> {
091: //------------------------------------------------- Saved Graphics State
092: private boolean stateSaved = false;
093: private Paint oldPaint;
094: private Font oldFont;
095: private Stroke oldStroke;
096: private AffineTransform oldTransform;
097: private Composite oldComposite;
098: private Shape oldClip;
099: private Color oldBackground;
100: private Color oldColor;
101: private RenderingHints oldRenderingHints;
102:
103: //--------------------------------------------------- Instance Variables
104: /**
105: * A Shape that is used to clip the graphics area. Anything within this
106: * clip shape is included in the final output.
107: */
108: private Shape clip;
109: /**
110: * A Resize value indicating if and how the clip should be resized
111: * according to the size of the Component
112: */
113: private Resize resizeClip = Resize.BOTH;
114: /**
115: * The composite to use. By default this is a reasonable AlphaComposite,
116: * but you may want to specify a different composite
117: */
118: private Composite composite;
119: /**
120: * RenderingHints to apply when painting
121: */
122: private Map<RenderingHints.Key, Object> renderingHints;
123: /**
124: * A hint as to whether or not to attempt caching the image
125: */
126: private boolean useCache = false;
127: /**
128: * The cached image, if useCache is true
129: */
130: private SoftReference<BufferedImage> cachedImage;
131: /**
132: * The Effects to apply to the results of the paint() operation
133: */
134: private Effect[] effects = new Effect[0];
135:
136: /**
137: * Creates a new instance of AbstractPainter
138: */
139: public AbstractPainter() {
140: renderingHints = new HashMap<RenderingHints.Key, Object>();
141: }
142:
143: /**
144: * <p>Sets whether to cache the painted image with a SoftReference in a BufferedImage
145: * between calls. If true, and if the size of the component hasn't changed,
146: * then the cached image will be used rather than causing a painting operation.</p>
147: *
148: * <p>This should be considered a hint, rather than absolute. Several factors may
149: * force repainting, including low memory, different component sizes, or possibly
150: * new rendering hint settings, etc.</p>
151: *
152: * @param b whether or not to use the cache
153: */
154: public void setUseCache(boolean b) {
155: boolean old = isUseCache();
156: useCache = b;
157: firePropertyChange("useCache", old, isUseCache());
158: //if there was a cached image and I'm no longer using the cache, blow it away
159: if (cachedImage != null && !isUseCache()) {
160: cachedImage = null;
161: }
162: }
163:
164: /**
165: * @return whether or not the cache should be used
166: */
167: public boolean isUseCache() {
168: return useCache;
169: }
170:
171: /**
172: * <p>Sets the effects to apply to the results of the AbstractPainter's
173: * painting operation. Some common effects include blurs, shadows, embossing,
174: * and so forth. If the given effects is a null array, no effects will be used</p>
175: *
176: * @param effects the Effects to apply to the results of the AbstractPainter's
177: * painting operation
178: */
179: public void setEffects(Effect... effects) {
180: Effect[] old = getEffects();
181: this .effects = new Effect[effects == null ? 0 : effects.length];
182: if (effects != null) {
183: System.arraycopy(effects, 0, this .effects, 0,
184: effects.length);
185: }
186: firePropertyChange("effects", old, getEffects());
187: firePropertyChange("effects", old, getEffects());
188: }
189:
190: /**
191: * <p>A convenience method for specifying the effects to use based on
192: * BufferedImageOps. These will each be individually wrapped by an ImageEffect
193: * and then setEffects(Effect... effects) will be called with the resulting
194: * array</p>
195: *
196: * @param filters the BufferedImageOps to wrap as effects
197: */
198: public void setEffects(BufferedImageOp... filters) {
199: Effect[] effects = new Effect[filters == null ? 0
200: : filters.length];
201: if (filters != null) {
202: int index = 0;
203: for (BufferedImageOp op : filters) {
204: effects[index++] = new ImageEffect(op);
205: }
206: }
207: setEffects(effects);
208: }
209:
210: /**
211: * @return effects a defensive copy of the Effects to apply to the results
212: * of the AbstractPainter's painting operation. Will never null
213: */
214: public Effect[] getEffects() {
215: Effect[] results = new Effect[effects.length];
216: System.arraycopy(effects, 0, results, 0, results.length);
217: return results;
218: }
219:
220: /**
221: * Specifies the Shape to use for clipping the painting area. This
222: * may be null
223: *
224: * @param clip the Shape to use to clip the area. Whatever is inside this
225: * shape will be kept, everything else "clipped". May be null. If
226: * null, the clipping is not set on the graphics object
227: */
228: public void setClip(Shape clip) {
229: Shape old = getClip();
230: this .clip = clip;
231: firePropertyChange("clip", old, getClip());
232: }
233:
234: /**
235: * @return the clipping shape
236: */
237: public Shape getClip() {
238: return clip;
239: }
240:
241: /**
242: * Specifies the resize behavior of the clip. As with all other properties
243: * that rely on Resize, the value of the width/height of the shape will
244: * represent a percentage of the width/height of the component, as a value
245: * between 0 and 1
246: *
247: * @param r value indication whether/how to resize the clip. If null,
248: * Resize.NONE will be used
249: */
250: public void setResizeClip(Resize r) {
251: Resize old = getResizeClip();
252: this .resizeClip = r == null ? Resize.NONE : r;
253: firePropertyChange("resizeClip", old, getResizeClip());
254: }
255:
256: /**
257: * @return value indication whether/how to resize the clip. Will never be null
258: */
259: public Resize getResizeClip() {
260: return resizeClip;
261: }
262:
263: /**
264: * Sets the Composite to use. For example, you may specify a specific
265: * AlphaComposite so that when this Painter paints, any content in the
266: * drawing area is handled properly
267: *
268: * @param c The composite to use. If null, then no composite will be
269: * specified on the graphics object
270: */
271: public void setComposite(Composite c) {
272: Composite old = getComposite();
273: this .composite = c;
274: firePropertyChange("composite", old, getComposite());
275: }
276:
277: /**
278: * @return the composite
279: */
280: public Composite getComposite() {
281: return composite;
282: }
283:
284: /**
285: * @return the technique used for interpolating alpha values. May be one
286: * of:
287: * <ul>
288: * <li>RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED</li>
289: * <li>RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY</li>
290: * <li>RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT</li>
291: * </ul>
292: */
293: public Object getAlphaInterpolation() {
294: return renderingHints
295: .get(RenderingHints.KEY_ALPHA_INTERPOLATION);
296: }
297:
298: /**
299: * Sets the technique used for interpolating alpha values.
300: *
301: * @param alphaInterpolation
302: * May be one of:
303: * <ul>
304: * <li>RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED</li>
305: * <li>RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY</li>
306: * <li>RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT</li>
307: * </ul>
308: */
309: public void setAlphaInterpolation(Object alphaInterpolation) {
310: if (alphaInterpolation != null
311: && !RenderingHints.KEY_ALPHA_INTERPOLATION
312: .isCompatibleValue(alphaInterpolation)) {
313: throw new IllegalArgumentException(alphaInterpolation
314: + " is not an acceptable value");
315: }
316: Object old = getAlphaInterpolation();
317: renderingHints.put(RenderingHints.KEY_ALPHA_INTERPOLATION,
318: alphaInterpolation);
319: firePropertyChange("alphaInterpolation", old,
320: getAlphaInterpolation());
321: }
322:
323: /**
324: * @return whether or not to antialias
325: * May be one of:
326: * <ul>
327: * <li>RenderingHints.VALUE_ANTIALIAS_DEFAULT</li>
328: * <li>RenderingHints.VALUE_ANTIALIAS_OFF</li>
329: * <li>RenderingHints.VALUE_ANTIALIAS_ON</li>
330: * </ul>
331: */
332: public Object getAntialiasing() {
333: return renderingHints.get(RenderingHints.KEY_ANTIALIASING);
334: }
335:
336: /**
337: * Sets whether or not to antialias
338: * @param antialiasing
339: * May be one of:
340: * <ul>
341: * <li>RenderingHints.VALUE_ANTIALIAS_DEFAULT</li>
342: * <li>RenderingHints.VALUE_ANTIALIAS_OFF</li>
343: * <li>RenderingHints.VALUE_ANTIALIAS_ON</li>
344: * </ul>
345: */
346: public void setAntialiasing(Object antialiasing) {
347: if (antialiasing != null
348: && !RenderingHints.KEY_ANTIALIASING
349: .isCompatibleValue(antialiasing)) {
350: throw new IllegalArgumentException(antialiasing
351: + " is not an acceptable value");
352: }
353: Object old = getAntialiasing();
354: renderingHints.put(RenderingHints.KEY_ANTIALIASING,
355: antialiasing);
356: firePropertyChange("antialiasing", old, getAntialiasing());
357: }
358:
359: /**
360: * @return the technique to use for rendering colors
361: * May be one of:
362: * <ul>
363: * <li>RenderingHints.VALUE_COLOR_RENDER_DEFAULT</li>
364: * <li>RenderingHints.VALUE_RENDER_QUALITY</li>
365: * <li>RenderingHints.VALUE_RENDER_SPEED</li>
366: * </ul>
367: */
368: public Object getColorRendering() {
369: return renderingHints.get(RenderingHints.KEY_COLOR_RENDERING);
370: }
371:
372: /**
373: * Sets the technique to use for rendering colors
374: * @param colorRendering
375: * May be one of:
376: * <ul>
377: * <li>RenderingHints.VALUE_COLOR_RENDER_DEFAULT</li>
378: * <li>RenderingHints.VALUE_RENDER_QUALITY</li>
379: * <li>RenderingHints.VALUE_RENDER_SPEED</li>
380: * </ul>
381: */
382: public void setColorRendering(Object colorRendering) {
383: if (colorRendering != null
384: && !RenderingHints.KEY_COLOR_RENDERING
385: .isCompatibleValue(colorRendering)) {
386: throw new IllegalArgumentException(colorRendering
387: + " is not an acceptable value");
388: }
389: Object old = getColorRendering();
390: renderingHints.put(RenderingHints.KEY_COLOR_RENDERING,
391: colorRendering);
392: firePropertyChange("colorRendering", old, getColorRendering());
393: }
394:
395: /**
396: * @return whether or not to dither
397: * May be one of:
398: * <ul>
399: * <li>RenderingHints.VALUE_DITHER_DEFAULT</li>
400: * <li>RenderingHints.VALUE_DITHER_ENABLE</li>
401: * <li>RenderingHints.VALUE_DITHER_DISABLE</li>
402: * </ul>
403: */
404: public Object getDithering() {
405: return renderingHints.get(RenderingHints.KEY_DITHERING);
406: }
407:
408: /**
409: * Sets whether or not to dither
410: * @param dithering
411: * May be one of:
412: * <ul>
413: * <li>RenderingHints.VALUE_DITHER_DEFAULT</li>
414: * <li>RenderingHints.VALUE_DITHER_ENABLE</li>
415: * <li>RenderingHints.VALUE_DITHER_DISABLE</li>
416: * </ul>
417: */
418: public void setDithering(Object dithering) {
419: if (dithering != null
420: && !RenderingHints.KEY_DITHERING
421: .isCompatibleValue(dithering)) {
422: throw new IllegalArgumentException(dithering
423: + " is not an acceptable value");
424: }
425: Object old = getDithering();
426: renderingHints.put(RenderingHints.KEY_DITHERING, dithering);
427: firePropertyChange("dithering", old, getDithering());
428: }
429:
430: /**
431: * @return whether or not to use fractional metrics
432: * May be one of:
433: * <ul>
434: * <li>RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT</li>
435: * <li>RenderingHints.VALUE_FRACTIONALMETRICS_OFF</li>
436: * <li>RenderingHints.VALUE_FRACTIONALMETRICS_ON</li>
437: * </ul>
438: */
439: public Object getFractionalMetrics() {
440: return renderingHints.get(RenderingHints.KEY_FRACTIONALMETRICS);
441: }
442:
443: /**
444: * Sets whether or not to use fractional metrics
445: *
446: * @param fractionalMetrics
447: * May be one of:
448: * <ul>
449: * <li>RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT</li>
450: * <li>RenderingHints.VALUE_FRACTIONALMETRICS_OFF</li>
451: * <li>RenderingHints.VALUE_FRACTIONALMETRICS_ON</li>
452: * </ul>
453: */
454: public void setFractionalMetrics(Object fractionalMetrics) {
455: if (fractionalMetrics != null
456: && !RenderingHints.KEY_FRACTIONALMETRICS
457: .isCompatibleValue(fractionalMetrics)) {
458: throw new IllegalArgumentException(fractionalMetrics
459: + " is not an acceptable value");
460: }
461: Object old = getFractionalMetrics();
462: renderingHints.put(RenderingHints.KEY_FRACTIONALMETRICS,
463: fractionalMetrics);
464: firePropertyChange("fractionalMetrics", old,
465: getFractionalMetrics());
466: }
467:
468: /**
469: * @return the technique to use for interpolation (used esp. when scaling)
470: * May be one of:
471: * <ul>
472: * <li>RenderingHints.VALUE_INTERPOLATION_BICUBIC</li>
473: * <li>RenderingHints.VALUE_INTERPOLATION_BILINEAR</li>
474: * <li>RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR</li>
475: * </ul>
476: */
477: public Object getInterpolation() {
478: return renderingHints.get(RenderingHints.KEY_INTERPOLATION);
479: }
480:
481: /**
482: * Sets the technique to use for interpolation (used esp. when scaling)
483: * @param interpolation
484: * May be one of:
485: * <ul>
486: * <li>RenderingHints.VALUE_INTERPOLATION_BICUBIC</li>
487: * <li>RenderingHints.VALUE_INTERPOLATION_BILINEAR</li>
488: * <li>RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR</li>
489: * </ul>
490: */
491: public void setInterpolation(Object interpolation) {
492: if (interpolation != null
493: && !RenderingHints.KEY_INTERPOLATION
494: .isCompatibleValue(interpolation)) {
495: throw new IllegalArgumentException(interpolation
496: + " is not an acceptable value");
497: }
498: Object old = getInterpolation();
499: renderingHints.put(RenderingHints.KEY_INTERPOLATION,
500: interpolation);
501: firePropertyChange("interpolation", old, getInterpolation());
502: }
503:
504: /**
505: * @return a hint as to techniques to use with regards to rendering quality vs. speed
506: * May be one of:
507: * <ul>
508: * <li>RenderingHints.VALUE_RENDER_QUALITY</li>
509: * <li>RenderingHints.VALUE_RENDER_SPEED</li>
510: * <li>RenderingHints.VALUE_RENDER_DEFAULT</li>
511: * </ul>
512: */
513: public Object getRendering() {
514: return renderingHints.get(RenderingHints.KEY_RENDERING);
515: }
516:
517: /**
518: * Specifies a hint as to techniques to use with regards to rendering quality vs. speed
519: *
520: * @param rendering
521: * May be one of:
522: * <ul>
523: * <li>RenderingHints.VALUE_RENDER_QUALITY</li>
524: * <li>RenderingHints.VALUE_RENDER_SPEED</li>
525: * <li>RenderingHints.VALUE_RENDER_DEFAULT</li>
526: * </ul>
527: */
528: public void setRendering(Object rendering) {
529: if (rendering != null
530: && !RenderingHints.KEY_RENDERING
531: .isCompatibleValue(rendering)) {
532: throw new IllegalArgumentException(rendering
533: + " is not an acceptable value");
534: }
535: Object old = getRendering();
536: renderingHints.put(RenderingHints.KEY_RENDERING, rendering);
537: firePropertyChange("rendering", old, getRendering());
538: }
539:
540: /**
541: * @return technique for rendering strokes
542: * May be one of:
543: * <ul>
544: * <li>RenderingHints.VALUE_STROKE_DEFAULT</li>
545: * <li>RenderingHints.VALUE_STROKE_NORMALIZE</li>
546: * <li>RenderingHints.VALUE_STROKE_PURE</li>
547: * </ul>
548: */
549: public Object getStrokeControl() {
550: return renderingHints.get(RenderingHints.KEY_STROKE_CONTROL);
551: }
552:
553: /**
554: * Specifies a technique for rendering strokes
555: *
556: * @param strokeControl
557: * May be one of:
558: * <ul>
559: * <li>RenderingHints.VALUE_STROKE_DEFAULT</li>
560: * <li>RenderingHints.VALUE_STROKE_NORMALIZE</li>
561: * <li>RenderingHints.VALUE_STROKE_PURE</li>
562: * </ul>
563: */
564: public void setStrokeControl(Object strokeControl) {
565: if (strokeControl != null
566: && !RenderingHints.KEY_STROKE_CONTROL
567: .isCompatibleValue(strokeControl)) {
568: throw new IllegalArgumentException(strokeControl
569: + " is not an acceptable value");
570: }
571: Object old = getStrokeControl();
572: renderingHints.put(RenderingHints.KEY_STROKE_CONTROL,
573: strokeControl);
574: firePropertyChange("strokeControl", old, getStrokeControl());
575: }
576:
577: /**
578: * @return technique for anti-aliasing text.
579: * (TODO this needs to be updated for Mustang. You may use the
580: * new Mustang values, and everything will work, but support in
581: * the GUI builder and documentation need to be added once we
582: * branch for Mustang)<br/>
583: * May be one of:
584: * <ul>
585: * <li>RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT</li>
586: * <li>RenderingHints.VALUE_TEXT_ANTIALIAS_OFF</li>
587: * <li>RenderingHints.VALUE_TEXT_ANTIALIAS_ON</li>
588: * </ul>
589: */
590: public Object getTextAntialiasing() {
591: return renderingHints.get(RenderingHints.KEY_TEXT_ANTIALIASING);
592: }
593:
594: /**
595: * Sets the technique for anti-aliasing text.
596: * (TODO this needs to be updated for Mustang. You may use the
597: * new Mustang values, and everything will work, but support in
598: * the GUI builder and documentation need to be added once we
599: * branch for Mustang)<br/>
600: *
601: * @param textAntialiasing
602: * May be one of:
603: * <ul>
604: * <li>RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT</li>
605: * <li>RenderingHints.VALUE_TEXT_ANTIALIAS_OFF</li>
606: * <li>RenderingHints.VALUE_TEXT_ANTIALIAS_ON</li>
607: * </ul>
608: */
609: public void setTextAntialiasing(Object textAntialiasing) {
610: if (textAntialiasing != null
611: && !RenderingHints.KEY_TEXT_ANTIALIASING
612: .isCompatibleValue(textAntialiasing)) {
613: throw new IllegalArgumentException(textAntialiasing
614: + " is not an acceptable value");
615: }
616: Object old = getTextAntialiasing();
617: renderingHints.put(RenderingHints.KEY_TEXT_ANTIALIASING,
618: textAntialiasing);
619: firePropertyChange("textAntialiasing", old,
620: getTextAntialiasing());
621: }
622:
623: /**
624: * @return the rendering hint associated with the given key. May return null
625: */
626: public Object getRenderingHint(RenderingHints.Key key) {
627: return renderingHints.get(key);
628: }
629:
630: /**
631: * Set the given hint for the given key. This will end up firing the appropriate
632: * property change event if the key is recognized. For example, if the key is
633: * RenderingHints.KEY_ANTIALIASING, then the setAntialiasing method will be
634: * called firing an "antialiasing" property change event if necessary. If
635: * the key is not recognized, no event will be fired but the key will be saved.
636: * The key must not be null
637: *
638: * @param key cannot be null
639: * @param hint must be a hint compatible with the given key
640: */
641: public void setRenderingHint(RenderingHints.Key key, Object hint) {
642: if (key == RenderingHints.KEY_ALPHA_INTERPOLATION) {
643: setAlphaInterpolation(hint);
644: } else if (key == RenderingHints.KEY_ANTIALIASING) {
645: setAntialiasing(hint);
646: } else if (key == RenderingHints.KEY_COLOR_RENDERING) {
647: setColorRendering(hint);
648: } else if (key == RenderingHints.KEY_DITHERING) {
649: setDithering(hint);
650: } else if (key == RenderingHints.KEY_FRACTIONALMETRICS) {
651: setFractionalMetrics(hint);
652: } else if (key == RenderingHints.KEY_INTERPOLATION) {
653: setInterpolation(hint);
654: } else if (key == RenderingHints.KEY_RENDERING) {
655: setRendering(hint);
656: } else if (key == RenderingHints.KEY_STROKE_CONTROL) {
657: setStrokeControl(hint);
658: } else if (key == RenderingHints.KEY_TEXT_ANTIALIASING) {
659: setTextAntialiasing(hint);
660: } else {
661: renderingHints.put(key, hint);
662: }
663: }
664:
665: /**
666: * @return a copy of the map of rendering hints held by this class. This
667: * returned value will never be null
668: */
669: public Map<RenderingHints.Key, Object> getRenderingHints() {
670: return new HashMap<RenderingHints.Key, Object>(renderingHints);
671: }
672:
673: /**
674: * Sets the rendering hints to use. This will <strong>replace</strong> the
675: * rendering hints entirely, clearing any hints that were previously set.
676: *
677: * @param renderingHints map of hints. May be null. I null, a new Map of
678: * rendering hints will be created
679: */
680: public void setRenderingHints(
681: Map<RenderingHints.Key, Object> renderingHints) {
682: if (renderingHints != null) {
683: this .renderingHints = new HashMap<RenderingHints.Key, Object>(
684: renderingHints);
685: } else {
686: this .renderingHints = new HashMap<RenderingHints.Key, Object>();
687: }
688: firePropertyChange("renderingHints", null, getRenderingHints());
689: }
690:
691: /**
692: * Saves the state in the given Graphics2D object so that it may be
693: * restored later.
694: *
695: * @param g the Graphics2D object who's state will be saved
696: */
697: protected void saveState(Graphics2D g) {
698: oldPaint = g.getPaint();
699: oldFont = g.getFont();
700: oldStroke = g.getStroke();
701: oldTransform = g.getTransform();
702: oldComposite = g.getComposite();
703: oldClip = g.getClip();
704: oldBackground = g.getBackground();
705: oldColor = g.getColor();
706:
707: //save off the old rendering hints
708: oldRenderingHints = (RenderingHints) g.getRenderingHints()
709: .clone();
710:
711: stateSaved = true;
712: }
713:
714: /**
715: * Restores previously saved state. A call to saveState must have occured
716: * prior to calling restoreState, or an IllegalStateException will be thrown.
717: *
718: * @param g the Graphics2D object to restore previously saved state to
719: */
720: protected void restoreState(Graphics2D g) {
721: if (!stateSaved) {
722: throw new IllegalStateException(
723: "A call to saveState must occur "
724: + "prior to calling restoreState");
725: }
726:
727: g.setPaint(oldPaint);
728: g.setFont(oldFont);
729: g.setTransform(oldTransform);
730: g.setStroke(oldStroke);
731: g.setComposite(oldComposite);
732: g.setClip(oldClip);
733: g.setBackground(oldBackground);
734: g.setColor(oldColor);
735:
736: //restore the rendering hints
737: g.setRenderingHints(oldRenderingHints);
738:
739: stateSaved = false;
740: }
741:
742: /**
743: * @inheritDoc
744: */
745: public void paint(Graphics2D g, T component) {
746: saveState(g);
747:
748: configureGraphics(g, component);
749:
750: //if I am cacheing, and the cache is not null, and the image has the
751: //same dimensions as the component, then simply paint the image
752: BufferedImage image = cachedImage == null ? null : cachedImage
753: .get();
754: if (isUseCache() && image != null
755: && image.getWidth() == component.getWidth()
756: && image.getHeight() == component.getHeight()) {
757: g.drawImage(image, 0, 0, null);
758: } else {
759: Effect[] effects = getEffects();
760: if (effects.length > 0 || isUseCache()) {
761: image = PaintUtils.createCompatibleImage(component
762: .getWidth(), component.getHeight(),
763: Transparency.TRANSLUCENT);
764:
765: Graphics2D gfx = image.createGraphics();
766: configureGraphics(gfx, component);
767: paintBackground(gfx, component);
768: gfx.dispose();
769:
770: for (Effect effect : effects) {
771: image = effect.apply(image);
772: }
773:
774: g.drawImage(image, 0, 0, null);
775:
776: if (isUseCache()) {
777: cachedImage = new SoftReference<BufferedImage>(
778: image);
779: }
780: } else {
781: paintBackground(g, component);
782: }
783: }
784:
785: restoreState(g);
786: }
787:
788: /**
789: * Utility method for configuring the given Graphics2D with the rendering hints,
790: * composite, and clip
791: */
792: private void configureGraphics(Graphics2D g, T c) {
793: Map<RenderingHints.Key, Object> hints = getRenderingHints();
794: //merge these hints with the existing ones, otherwise I won't inherit
795: //any of the hints from the Graphics2D
796: for (Object key : hints.keySet()) {
797: Object value = hints.get(key);
798: if (value != null) {
799: g.setRenderingHint((RenderingHints.Key) key, hints
800: .get(key));
801: }
802: }
803:
804: if (getComposite() != null) {
805: g.setComposite(getComposite());
806: }
807: Shape clip = getClip();
808: if (clip != null) {
809: //resize the clip if necessary
810: double width = 1;
811: double height = 1;
812: Resize resizeClip = getResizeClip();
813: if (resizeClip == Resize.HORIZONTAL
814: || resizeClip == Resize.BOTH) {
815: width = c.getWidth();
816: }
817: if (resizeClip == Resize.VERTICAL
818: || resizeClip == Resize.BOTH) {
819: height = c.getHeight();
820: }
821: if (clip instanceof RoundRectangle2D) {
822: RoundRectangle2D rect = (RoundRectangle2D) clip;
823: clip = new RoundRectangle2D.Double(rect.getX(), rect
824: .getY(), width, height, rect.getArcWidth(),
825: rect.getArcHeight());
826: } else {
827: clip = AffineTransform.getScaleInstance(width, height)
828: .createTransformedShape(clip);
829: }
830: g.setClip(clip);
831: }
832: }
833:
834: /**
835: * Subclasses should implement this method and perform custom painting operations
836: * here. Common behavior, such as setting the clip and composite, saving and restoring
837: * state, is performed in the "paint" method automatically, and then delegated here.
838: *
839: * @param g The Graphics2D object in which to paint
840: * @param component The JComponent that the Painter is delegate for.
841: */
842: protected abstract void paintBackground(Graphics2D g, T component);
843: }
|