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    }