001 /*
002 * $Id: CompoundPainter.java 3264 2009-02-19 18:26:31Z kschaefe $
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.Graphics2D;
025 import java.awt.geom.AffineTransform;
026
027 /**
028 * <p>A {@link Painter} implementation composed of an array of <code>Painter</code>s.
029 * <code>CompoundPainter</code> provides a means for combining several individual
030 * <code>Painter</code>s, or groups of them, into one logical unit. Each of the
031 * <code>Painter</code>s are executed in order. BufferedImageOp filter effects can
032 * be applied to them together as a whole. The entire set of painting operations
033 * may be cached together.</p>
034 *
035 * <p></p>
036 *
037 * <p>For example, if I want to create a CompoundPainter that started with a blue
038 * background, had pinstripes on it running at a 45 degree angle, and those
039 * pinstripes appeared to "fade in" from left to right, I would write the following:
040 * <pre><code>
041 * Color blue = new Color(0x417DDD);
042 * Color translucent = new Color(blue.getRed(), blue.getGreen(), blue.getBlue(), 0);
043 * panel.setBackground(blue);
044 * panel.setForeground(Color.LIGHT_GRAY);
045 * GradientPaint blueToTranslucent = new GradientPaint(
046 * new Point2D.Double(.4, 0),
047 * blue,
048 * new Point2D.Double(1, 0),
049 * translucent);
050 * MattePainter veil = new MattePainter(blueToTranslucent);
051 * veil.setPaintStretched(true);
052 * Painter pinstripes = new PinstripePainter(45);
053 * Painter backgroundPainter = new RectanglePainter(this.getBackground(), null);
054 * Painter p = new CompoundPainter(backgroundPainter, pinstripes, veil);
055 * panel.setBackgroundPainter(p);
056 * </code></pre></p>
057 *
058 * @author rbair
059 */
060 public class CompoundPainter<T> extends AbstractPainter<T> {
061 private Painter[] painters = new Painter[0];
062 private AffineTransform transform;
063 private boolean clipPreserved = false;
064
065 private boolean checkForDirtyChildPainters = true;
066
067 /** Creates a new instance of CompoundPainter */
068 public CompoundPainter() {
069 }
070
071 /**
072 * Convenience constructor for creating a CompoundPainter for an array
073 * of painters. A defensive copy of the given array is made, so that future
074 * modification to the array does not result in changes to the CompoundPainter.
075 *
076 * @param painters array of painters, which will be painted in order
077 */
078 public CompoundPainter(Painter... painters) {
079 this.painters = new Painter[painters == null ? 0 : painters.length];
080 if (painters != null) {
081 System.arraycopy(painters, 0, this.painters, 0, painters.length);
082 }
083 }
084
085 /**
086 * Sets the array of Painters to use. These painters will be executed in
087 * order. A null value will be treated as an empty array. To prevent unexpected
088 * behavior all values in provided array are copied to internally held array.
089 * Any changes to the original array will not be reflected.
090 *
091 * @param painters array of painters, which will be painted in order
092 */
093 public void setPainters(Painter... painters) {
094 Painter[] old = getPainters();
095 this.painters = new Painter[painters == null ? 0 : painters.length];
096 if (painters != null) {
097 System.arraycopy(painters, 0, this.painters, 0, this.painters.length);
098 }
099 setDirty(true);
100 firePropertyChange("painters", old, getPainters());
101 }
102
103 /**
104 * Gets the array of painters used by this CompoundPainter
105 * @return a defensive copy of the painters used by this CompoundPainter.
106 * This will never be null.
107 */
108 public final Painter[] getPainters() {
109 Painter[] results = new Painter[painters.length];
110 System.arraycopy(painters, 0, results, 0, results.length);
111 return results;
112 }
113
114
115 /**
116 * Indicates if the clip produced by any painter is left set once it finishes painting.
117 * Normally the clip will be reset between each painter. Setting clipPreserved to
118 * true can be used to let one painter mask other painters that come after it.
119 * @return if the clip should be preserved
120 * @see #setClipPreserved(boolean)
121 */
122 public boolean isClipPreserved() {
123 return clipPreserved;
124 }
125
126 /**
127 * Sets if the clip should be preserved.
128 * Normally the clip will be reset between each painter. Setting clipPreserved to
129 * true can be used to let one painter mask other painters that come after it.
130 *
131 * @param shouldRestoreState new value of the clipPreserved property
132 * @see #isClipPreserved()
133 */
134 public void setClipPreserved(boolean shouldRestoreState) {
135 boolean oldShouldRestoreState = isClipPreserved();
136 this.clipPreserved = shouldRestoreState;
137 setDirty(true);
138 firePropertyChange("clipPreserved",oldShouldRestoreState,shouldRestoreState);
139 }
140
141 /**
142 * Gets the current transform applied to all painters in this CompoundPainter. May be null.
143 * @return the current AffineTransform
144 */
145 public AffineTransform getTransform() {
146 return transform;
147 }
148
149 /**
150 * Set a transform to be applied to all painters contained in this CompoundPainter
151 * @param transform a new AffineTransform
152 */
153 public void setTransform(AffineTransform transform) {
154 AffineTransform old = getTransform();
155 this.transform = transform;
156 setDirty(true);
157 firePropertyChange("transform",old,transform);
158 }
159
160 /**
161 * <p>Iterates over all child <code>Painter</code>s and gives them a chance
162 * to validate themselves. If any of the child painters are dirty, then
163 * this <code>CompoundPainter</code> marks itself as dirty.</p>
164 *
165 * {@inheritDoc}
166 */
167 @Override
168 protected void validate(T object) {
169 boolean dirty = false;
170 for (Painter p : painters) {
171 if (p instanceof AbstractPainter) {
172 AbstractPainter ap = (AbstractPainter)p;
173 ap.validate(object);
174 if (ap.isDirty()) {
175 dirty = true;
176 break;
177 }
178 }
179 }
180 clearLocalCacheOnly = true;
181 setDirty(dirty); //super will call clear cache
182 clearLocalCacheOnly = false;
183 }
184
185 //indicates whether the local cache should be cleared only, as opposed to the
186 //cache's of all of the children. This is needed to optimize the caching strategy
187 //when, during validate, the CompoundPainter is marked as dirty
188 private boolean clearLocalCacheOnly = false;
189
190 /**
191 * Used by {@link #isDirty()} to check if the child <code>Painter</code>s
192 * should be checked for their <code>dirty</code> flag as part of
193 * processing.<br>
194 * Default value is: <code>true</code><br>
195 * This should be set to </code>false</code> if the cacheable state
196 * of the child <code>Painter</code>s are different from each other. This
197 * will allow the cacheable == <code>true</code> <code>Painter</code>s to
198 * keep their cached image during regular repaints. In this case,
199 * client code should call {@link #clearCache()} manually when the cacheable
200 * <code>Painter</code>s should be updated.
201 *
202 *
203 * @see #isDirty()
204 */
205 public boolean isCheckingDirtyChildPainters() {
206 return checkForDirtyChildPainters;
207 }
208 /**
209 * Set the flag used by {@link #isDirty()} to check if the
210 * child <code>Painter</code>s should be checked for their
211 * <code>dirty</code> flag as part of processing.
212 *
213 * @see #isCheckingDirtyChildPainters()
214 * @see #isDirty()
215 */
216 public void setCheckingDirtyChildPainters(boolean b) {
217 boolean old = isCheckingDirtyChildPainters();
218 this.checkForDirtyChildPainters = b;
219 firePropertyChange("checkingDirtyChildPainters",old, isCheckingDirtyChildPainters());
220 }
221
222 /**
223 * <p>This <code>CompoundPainter</code> is dirty if it, or (optionally)
224 * any of its children, are dirty. If the super implementation returns
225 * <code>true</code>, we return <code>true</code>. Otherwise, if
226 * {@link #isCheckingDirtyChildPainters()} is <code>true</code>, we iterate
227 * over all child <code>Painter</code>s and query them to see
228 * if they are dirty. If so, then <code>true</code> is returned.
229 * Otherwise, we return <code>false</code>.</p>
230 *
231 * {@inheritDoc}
232 * {@see #isCheckingDirtyChildPainters()}
233 */
234 @Override
235 protected boolean isDirty() {
236 boolean dirty = super.isDirty();
237 if (dirty) {
238 return true;
239 }
240 else if (isCheckingDirtyChildPainters()) {
241 for (Painter p : painters) {
242 if (p instanceof AbstractPainter) {
243 AbstractPainter ap = (AbstractPainter)p;
244 if (ap.isDirty()) {
245 return true;
246 }
247 }
248 }
249 }
250 return false;
251 }
252
253 /**
254 * <p>Clears the cache of this <code>Painter</code>, and all child
255 * <code>Painters</code>. This is done to ensure that resources
256 * are collected, even if clearCache is called by some framework
257 * or other code that doesn't realize this is a CompoundPainter.</p>
258 *
259 * <p>Call #clearLocalCache if you only want to clear the cache of this
260 * <code>CompoundPainter</code>
261 *
262 * {@inheritDoc}
263 */
264 @Override
265 public void clearCache() {
266 if (!clearLocalCacheOnly) {
267 for (Painter p : painters) {
268 if (p instanceof AbstractPainter) {
269 AbstractPainter ap = (AbstractPainter)p;
270 ap.clearCache();
271 }
272 }
273 }
274 super.clearCache();
275 }
276
277 /**
278 * <p>Clears the cache of this painter only, and not of any of the children.</p>
279 */
280 public void clearLocalCache() {
281 super.clearCache();
282 }
283
284 /**
285 * {@inheritDoc}
286 */
287 @Override
288 protected void doPaint(Graphics2D g, T component, int width, int height) {
289 for (Painter p : getPainters()) {
290 Graphics2D temp = (Graphics2D) g.create();
291
292 try {
293 p.paint(temp, component, width, height);
294 if(isClipPreserved()) {
295 g.setClip(temp.getClip());
296 }
297 } finally {
298 temp.dispose();
299 }
300 }
301 }
302
303 /**
304 * {@inheritDoc}
305 */
306 @Override
307 protected void configureGraphics(Graphics2D g) {
308 //applies the transform
309 AffineTransform tx = getTransform();
310 if (tx != null) {
311 g.setTransform(tx);
312 }
313 }
314
315 /**
316 * {@inheritDoc}
317 */
318 @Override
319 protected boolean shouldUseCache() {
320 return (isCacheable() && painters != null && painters.length > 0) || super.shouldUseCache();
321 }
322 }