001/* 002 * $Id: AbstractHighlighter.java 3927 2011-02-22 16:34:11Z kleopatra $ 003 * 004 * Copyright 2006 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 */ 021package org.jdesktop.swingx.decorator; 022 023import java.awt.Component; 024 025import javax.swing.event.ChangeEvent; 026import javax.swing.event.ChangeListener; 027 028import org.jdesktop.swingx.event.WeakEventListenerList; 029 030/** 031 * Abstract <code>Highlighter</code> implementation which manages change 032 * notification and supports conditional highlighting. 033 * Subclasses are required to fire ChangeEvents on internal changes which might 034 * effect the highlight. The HighlightPredicate controls whether or not 035 * a highlight should be applied for the given ComponentAdapter, 036 * subclasses must guarantee to respect its decision. 037 * <p> 038 * 039 * Concrete custom implementations should focus on a single (or few) visual 040 * attribute to highlight. This allows easy re-use by composition. F.i. a custom 041 * FontHighlighter: 042 * 043 * <pre><code> 044 * public static class FontHighlighter extends AbstractHighlighter { 045 * 046 * private Font font; 047 * 048 * public FontHighlighter(HighlightPredicate predicate, Font font) { 049 * super(predicate); 050 * setFont(font); 051 * } 052 * 053 * @Override 054 * protected Component doHighlight(Component component, 055 * ComponentAdapter adapter) { 056 * component.setFont(font); 057 * return component; 058 * } 059 * 060 * public final void setFont(Font font) { 061 * if (equals(font, this.font)) return; 062 * this.font = font; 063 * fireStateChanged(); 064 * } 065 * 066 * 067 * } 068 * 069 * </code></pre> 070 * 071 * Client code can combine the effect with a f.i. Color decoration, and use a 072 * shared HighlightPredicate to apply both for the same condition. 073 * 074 * <pre><code> 075 * HighlightPredicate predicate = new HighlightPredicate() { 076 * public boolean isHighlighted(Component renderer, ComponentAdapter adapter) { 077 * Object value = adapter.getFilteredValueAt(adapter.row, adapter.column); 078 * return (value instanceof Number) && ((Number) value).intValue() < 0; 079 * } 080 * }; 081 * table.setHighlighters( 082 * new ColorHighlighter(predicate, Color.RED, null), 083 * new FontHighlighter(predicate, myBoldFont)); 084 * </code></pre> 085 * 086 * @author Jeanette Winzenburg 087 * 088 * @see HighlightPredicate 089 * @see org.jdesktop.swingx.renderer.ComponentProvider 090 */ 091public abstract class AbstractHighlighter implements Highlighter { 092 093 /** 094 * Only one <code>ChangeEvent</code> is needed per model instance since the 095 * event's only (read-only) state is the source property. The source 096 * of events generated here is always "this". 097 */ 098 private transient ChangeEvent changeEvent; 099 100 /** The listeners waiting for model changes. */ 101 protected WeakEventListenerList listenerList = new WeakEventListenerList(); 102 103 /** the HighlightPredicate to use. */ 104 private HighlightPredicate predicate; 105 106 /** 107 * Instantiates a Highlighter with default HighlightPredicate. 108 * 109 * @see #setHighlightPredicate(HighlightPredicate) 110 */ 111 public AbstractHighlighter() { 112 this(null); 113 } 114 115 /** 116 * Instantiates a Highlighter with the given 117 * HighlightPredicate.<p> 118 * 119 * @param predicate the HighlightPredicate to use. 120 * 121 * @see #setHighlightPredicate(HighlightPredicate) 122 */ 123 public AbstractHighlighter(HighlightPredicate predicate) { 124 setHighlightPredicate(predicate); 125 } 126 127 /** 128 * Set the HighlightPredicate used to decide whether a cell should 129 * be highlighted. If null, sets the predicate to HighlightPredicate.ALWAYS. 130 * 131 * The default value is HighlightPredicate.ALWAYS. 132 * 133 * @param predicate the HighlightPredicate to use. 134 */ 135 public void setHighlightPredicate(HighlightPredicate predicate) { 136 if (predicate == null) { 137 predicate = HighlightPredicate.ALWAYS; 138 } 139 if (areEqual(predicate, getHighlightPredicate())) return; 140 this.predicate = predicate; 141 fireStateChanged(); 142 } 143 144 /** 145 * Returns the HighlightPredicate used to decide whether a cell 146 * should be highlighted. Guaranteed to be never null. 147 * 148 * @return the HighlightPredicate to use, never null. 149 */ 150 public HighlightPredicate getHighlightPredicate() { 151 return predicate; 152 } 153 154 //----------------------- implement predicate respecting highlight 155 156 /** 157 * {@inheritDoc} 158 * 159 * This calls doHighlight to apply the decoration if both HighlightPredicate 160 * isHighlighted and canHighlight return true. Returns the undecorated component otherwise. 161 * 162 * @param component the cell renderer component that is to be decorated 163 * @param adapter the ComponentAdapter for this decorate operation 164 * 165 * @see #canHighlight(Component, ComponentAdapter) 166 * @see #doHighlight(Component, ComponentAdapter) 167 * @see #getHighlightPredicate() 168 */ 169 @Override 170 public Component highlight(Component component, ComponentAdapter adapter) { 171 if (canHighlight(component, adapter) && 172 getHighlightPredicate().isHighlighted(component, adapter)) { 173 component = doHighlight(component, adapter); 174 } 175 return component; 176 } 177 178 /** 179 * Subclasses may override to further limit the highlighting based 180 * on Highlighter state, f.i. a PainterHighlighter can only be applied 181 * to PainterAware components. <p> 182 * 183 * This implementation returns true always. 184 * 185 * @param component 186 * @param adapter 187 * @return a boolean indication if the adapter can be highlighted based 188 * general state. This implementation returns true always. 189 */ 190 protected boolean canHighlight(Component component, ComponentAdapter adapter) { 191 return true; 192 } 193 194 195 /** 196 * Apply the highlights. 197 * 198 * @param component the cell renderer component that is to be decorated 199 * @param adapter the ComponentAdapter for this decorate operation 200 * 201 * @see #highlight(Component, ComponentAdapter) 202 */ 203 protected abstract Component doHighlight(Component component, 204 ComponentAdapter adapter); 205 206 207 /** 208 * Returns true if the to objects are either both null or equal 209 * each other. 210 * 211 * @param oneItem one item 212 * @param anotherItem another item 213 * @return true if both are null or equal other, false otherwise. 214 */ 215 protected boolean areEqual(Object oneItem, Object anotherItem) { 216 if ((oneItem == null) && (anotherItem == null)) return true; 217 if (anotherItem != null) { 218 return anotherItem.equals(oneItem); 219 } 220 return false; 221 } 222 223 //------------------------ implement Highlighter change notification 224 225 /** 226 * Adds a <code>ChangeListener</code>. ChangeListeners are 227 * notified after changes of any attribute. 228 * 229 * @param l the ChangeListener to add 230 * @see #removeChangeListener 231 */ 232 @Override 233 public final void addChangeListener(ChangeListener l) { 234 listenerList.add(ChangeListener.class, l); 235 } 236 237 /** 238 * Removes a <code>ChangeListener</code>e. 239 * 240 * @param l the <code>ChangeListener</code> to remove 241 * @see #addChangeListener 242 */ 243 @Override 244 public final void removeChangeListener(ChangeListener l) { 245 listenerList.remove(ChangeListener.class, l); 246 } 247 248 /** 249 * Returns an array of all the change listeners 250 * registered on this <code>Highlighter</code>. 251 * 252 * @return all of this model's <code>ChangeListener</code>s 253 * or an empty 254 * array if no change listeners are currently registered 255 * 256 * @see #addChangeListener 257 * @see #removeChangeListener 258 * 259 * @since 1.4 260 */ 261 @Override 262 public final ChangeListener[] getChangeListeners() { 263 return listenerList.getListeners(ChangeListener.class); 264 } 265 266 /** 267 * Notifies registered <code>ChangeListener</code>s about 268 * state changes.<p> 269 * 270 * Note: subclasses should be polite and implement any property 271 * setters to fire only if the property is really changed. 272 * 273 */ 274 protected final void fireStateChanged() { 275 Object[] listeners = listenerList.getListenerList(); 276 for (int i = listeners.length - 2; i >= 0; i -= 2) { 277 if (listeners[i] == ChangeListener.class) { 278 if (changeEvent == null) { 279 changeEvent = new ChangeEvent(this); 280 } 281 ((ChangeListener) listeners[i + 1]).stateChanged(changeEvent); 282 } 283 } 284 } 285 286}