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