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}