001    /*
002     * $Id: Highlighter.java,v 1.11 2006/05/14 15:55:53 dmouse Exp $
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.decorator;
023    
024    import java.awt.Color;
025    import java.awt.Component;
026    
027    import javax.swing.BoundedRangeModel;
028    import javax.swing.event.ChangeEvent;
029    import javax.swing.event.ChangeListener;
030    import javax.swing.event.EventListenerList;
031    
032    /**
033     * <p><code>Highlighter</code> is a lightweight mechanism to modify the behavior
034     * and attributes of cell renderers such as {@link javax.swing.ListCellRenderer},
035     * {@link javax.swing.table.TableCellRenderer}, and
036     * {@link javax.swing.tree.TreeCellRenderer} in a simple layered fashion.
037     * While cell renderers are split along component lines, highlighters provide a
038     * <em>common interface</em> for decorating cell renderers.
039     * <code>Highlighter</code> achieves this by vectoring access to all component-specific
040     * state and functionality through a {@link ComponentAdapter} object.</p>
041     *
042     * <p>The primary purpose of <code>Highlighter</code> is to decorate a cell
043     * renderer in <em>controlled</em> ways, such as by applying a different color
044     * or font to it. For example, {@link AlternateRowHighlighter} highlights cell
045     * renderers with alternating background colors. In data visualization components
046     * that support multiple columns with potentially different types of data, this
047     * highlighter imparts the same background color consistently across <em>all</em>
048     * columns of the {@link ComponentAdapter#target target} component
049     * regardless of the actual cell renderer registered for any specific column.
050     * Thus, the <code>Highlighter</code> mechanism is orthogonal to the cell
051     * rendering mechanism.</p>
052     *
053     * <p>To use <code>Highlighter</code> you must first set up a
054     * {@link HighlighterPipeline} using an array of <code>Highlighter</code> objects,
055     * and then call setHighlighters() on a data visualization component, passing in
056     * the highligher pipeline. If the array of highlighters is not null and is not
057     * empty, the highlighters are applied to the selected renderer for each cell in
058     * the order they appear in the array.
059     * When it is time to render a cell, the cell renderer is primed as usual, after
060     * which, the {@link Highlighter#highlight highlight} method of the first
061     * highlighter in the {@link HighlighterPipeline} is invoked. The prepared
062     * renderer, and a suitable {@link ComponentAdapter} object is passed to the
063     * <code>highlight</code> method. The highlighter is expected to modify the
064     * renderer in controlled ways, and return the modified renderer (or a substitute)
065     * that is passed to the next highlighter, if any, in the pipeline. The renderer
066     * returned by the <code>highlight</code> method of the last highlighter in the
067     * pipeline is ultimately used to render the cell.</p>
068     *
069     * <p>The <code>Highlighter</code> mechanism enables multiple degrees of
070     * freedom. In addition to specifying the actual cell renderer class, now you
071     * can also specify the number, order, and class of highlighter objects. Using
072     * highlighters is really simple, as shown by the following example:</p>
073     *
074     * <pre>
075      Highlighter[]   highlighters = new Highlighter[] {
076          new <b>AlternateRowHighlighter</b>(Color.white,
077                                             new Color(0xF0, 0xF0, 0xE0), null),
078          new <b>PatternHighlighter</b>(null, Color.red, "^s", 0, 0)
079      };
080    
081      HighlighterPipeline highlighterPipeline = new HighlighterPipeline(highlighters);
082      JXTable table = new JXTable();
083      table.setHighlighters(highlighterPipeline);
084     * </pre>
085     *
086     * <p>The above example allocates an array of <code>Highlighter</code> and populates
087     * it with a new {@link AlternateRowHighlighter} and {@link PatternHighlighter}.
088     * The first one in this example highlights all cells in odd rows with a white
089     * background, and all cells in even rows with a silver background, but it does
090     * not specify a foreground color explicitly. The second highlighter does not
091     * specify a background color explicitly, but sets the foreground color to red
092     * <em>if certain conditions are met</em> (see {@link PatternHighlighter} for
093     * more details). In this example, if the cells in the first column of any
094     * row start with the letter 's', then all cells in that row are highlighted with
095     * a red foreground. Also, as mentioned earlier, the highlighters are applied in
096     * the order they appear in the list.</p>
097     *
098     * <p> Highlighters are mutable by default, that is all there properties can be
099     * changed dynamically. If so they fire changeEvents to registered ChangeListeners.
100     * They can be marked as immutable at instantiation time - if so, trying to mutate
101     * all properties will not have any effect, ChangeListeners are not registered and
102     * no events are fired. </p>
103     * 
104     * <p> This base class has properties background/foreground and corresponding
105     * selectionBackground/selectionForeground. It will apply those colors "absolutely", 
106     * that is without actually computing any derived color. It's up to subclasses to 
107     * implement color computation, if desired. </p>
108     *
109     * @author Ramesh Gupta
110     * @author Jeanette Winzenburg
111     * 
112     * @see ComponentAdapter
113     * @see javax.swing.ListCellRenderer
114     * @see javax.swing.table.TableCellRenderer
115     * @see javax.swing.tree.TreeCellRenderer
116     */
117    public class Highlighter {
118        /**
119         * Only one <code>ChangeEvent</code> is needed per model instance since the
120         * event's only (read-only) state is the source property.  The source
121         * of events generated here is always "this".
122         */
123        protected transient ChangeEvent changeEvent = null;
124    
125        /** The listeners waiting for model changes. */
126        protected EventListenerList listenerList = new EventListenerList();
127        
128        /** flag to indicate whether the Highlighter is mutable in every respect. */
129        protected final boolean immutable;
130        
131        /**
132         * Predefined <code>Highlighter</code> that highlights the background of
133         * each cell with a pastel green "ledger" background color, and is most
134         * effective when the {@link ComponentAdapter#target} component has
135         * horizontal gridlines in <code>Color.cyan.darker()</code> color.
136         * 
137         * @deprecated set the component's background color instead!
138         */
139        public final static Highlighter ledgerBackground =
140                    new Highlighter(new Color(0xF5, 0xFF, 0xF5), null, true);
141    
142        /**
143         * Predefined <code>Highlighter</code> that decorates the background of
144         * each cell with a pastel yellow "notepad" background color, and is most
145         * effective when the {@link ComponentAdapter#target} component has
146         * horizontal gridlines in <code>Color.cyan.darker()</code> color.
147         * 
148         * @deprecated set the component's background color instead!
149         */
150        public final static Highlighter notePadBackground =
151                    new Highlighter(new Color(0xFF, 0xFF, 0xCC), null, true);
152    
153        private Color background = null;
154        private Color foreground = null;
155        private Color selectedBackground = null;
156        private Color selectedForeground = null;
157    
158        /**
159         * Default constructor for mutable Highlighter.
160         * Initializes background, foreground, selectedBackground, and
161         * selectedForeground to null.
162         */
163        public Highlighter() {
164            this(null, null);
165        }
166    
167        /**
168         * Constructs a mutable <code>Highlighter</code> with the specified
169         * background and foreground colors, selectedBackground and 
170         * selectedForeground to null.
171         *
172         * @param cellBackground background color for the renderer, or null,
173         *          to compute a suitable background
174         * @param cellForeground foreground color for the renderer, or null,
175         *          to compute a suitable foreground
176         */
177        public Highlighter(Color cellBackground, Color cellForeground) {
178            this(cellBackground, cellForeground, false);
179        }
180    
181        public Highlighter(Color cellBackground, Color cellForeground, boolean immutable) {
182            this(cellBackground, cellForeground, null, null, immutable);
183        }
184        
185        /**
186         * Constructs a mutable <code>Highlighter</code> with the specified
187         * background and foreground colors.
188         *
189         * @param cellBackground background color for the renderer, or null,
190         *          to compute a suitable background
191         * @param cellForeground foreground color for the renderer, or null,
192         *          to compute a suitable foreground
193         */
194        public Highlighter(Color cellBackground, Color cellForeground, 
195                Color selectedBackground, Color selectedForeground) {
196            this(cellBackground, cellForeground, selectedBackground, selectedForeground, false);
197        }
198    
199        /**
200         * Constructs a <code>Highlighter</code> with the specified
201         * background and foreground colors with mutability depending on
202         * given flag.
203         *
204         * @param cellBackground background color for the renderer, or null,
205         *          to compute a suitable background
206         * @param cellForeground foreground color for the renderer, or null,
207         *          to compute a suitable foreground
208         */
209        public Highlighter(Color cellBackground, Color cellForeground, 
210                Color selectedBackground, Color selectedForeground, boolean immutable) {
211            this.immutable = immutable;
212            this.background = cellBackground; 
213            this.foreground = cellForeground; 
214            this.selectedBackground = selectedBackground;
215            this.selectedForeground = selectedForeground;
216        }
217    
218        /**
219         * Decorates the specified cell renderer component for the given component
220         * data adapter using highlighters that were previously set for the component.
221         * This method unconditionally invokes {@link #doHighlight doHighlight} with
222         * the same arguments as were passed in.
223         *
224         * @param renderer the cell renderer component that is to be decorated
225         * @param adapter the {@link ComponentAdapter} for this decorate operation
226         * @return the decorated cell renderer component
227         */
228        public Component highlight(Component renderer, ComponentAdapter adapter) {
229            return doHighlight(renderer, adapter);
230        }
231    
232        /**
233         * This is the bottleneck decorate method that all highlighters must invoke
234         * to decorate the cell renderer. This method invokes {@link #applyBackground
235         * applyBackground}, {@link #applyForeground applyForeground},
236         * to decorate the corresponding
237         * attributes of the specified component within the given adapter. <p>
238         *
239         * Subclasses which want to decorate additional properties must override
240         * this and additionally call custom applyXX methods.
241         * 
242         * @param renderer the cell renderer component that is to be decorated
243         * @param adapter the {@link ComponentAdapter} for this decorate operation
244         * @return the decorated cell renderer component
245         */
246        protected Component doHighlight(Component renderer, ComponentAdapter adapter) {
247            applyBackground(renderer, adapter);
248            applyForeground(renderer, adapter);
249            return renderer;
250        }
251    
252        /**
253         * Applies a suitable background for the renderer component within the
254         * specified adapter. <p>
255         * 
256         * This implementation calls {@link #computeBackground computeBackground}
257         * and applies the computed color to the component if the returned value is
258         * != null. Otherwise it does nothing.
259         *
260         * @param renderer the cell renderer component that is to be decorated
261         * @param adapter the {@link ComponentAdapter} for this decorate operation
262         */
263        protected void applyBackground(Component renderer, ComponentAdapter adapter) {
264            Color color = computeBackground(renderer, adapter);
265            if (color != null) {
266                renderer.setBackground(color);
267            }
268        }
269    
270        /**
271         * Applies a suitable foreground for the renderer component within the
272         * specified adapter. <p>
273         * 
274         * This implementation calls {@link #computeForeground computeForeground}
275         * and applies the computed color to the component if the returned value
276         * is != null. Otherwise it does nothing.
277         *
278         * @param renderer the cell renderer component that is to be decorated
279         * @param adapter the {@link ComponentAdapter} for this decorate operation
280         */
281        protected void applyForeground(Component renderer, ComponentAdapter adapter) {
282            Color color = computeForeground(renderer, adapter);
283            if (color != null) {
284                renderer.setForeground(color);
285            }
286        }
287    
288        /**
289         * <p>Computes a suitable background for the renderer component within the
290         * specified adapter and returns the computed color. 
291         * 
292         * <p> In this implementation the returned color depends
293         * on {@link ComponentAdapter#isSelected isSelected}: it will
294         * return computSelected/-UnselectedBackground, respectively.</p> 
295         *
296         * @param renderer the cell renderer component that is to be decorated
297         * @param adapter the {@link ComponentAdapter} for this decorate operation
298         * @return a suitable background color for the specified component and adapter
299         */
300        protected Color computeBackground(Component renderer, ComponentAdapter adapter) {
301            return adapter.isSelected() ? computeSelectedBackground(renderer, adapter) :
302                computeUnselectedBackground(renderer, adapter);
303        }
304    
305    
306    
307        /**
308         * <p>Computes a suitable unselected background for the renderer component within the
309         * specified adapter and returns the computed color. 
310         * 
311         * This implementation returns getBackground().
312         * 
313         * @param renderer
314         * @param adapter
315         * @return unselected background color
316         */
317        protected Color computeUnselectedBackground(Component renderer, ComponentAdapter adapter) {
318            return getBackground();
319        }
320    
321        /**
322         * <p>Computes a suitable selected background for the renderer component within the
323         * specified adapter and returns the computed color. 
324         * 
325         * This implementation returns getSelectedBackground().
326         * 
327         * @param renderer
328         * @param adapter
329         * @return selected background color
330         */
331        protected Color computeSelectedBackground(Component renderer, ComponentAdapter adapter) {
332            return getSelectedBackground();
333        }
334    
335        /**
336         * <p>Computes a suitable foreground for the renderer component within the
337         * specified adapter and returns the computed color. 
338         *  In this implementation the returned color depends
339         * on {@link ComponentAdapter#isSelected isSelected}: it will
340         * return computSelected/-UnselectedForeground, respectively.</p> 
341         *</p>
342         *
343         * @param renderer the cell renderer component that is to be decorated
344         * @param adapter the {@link ComponentAdapter} for this decorate operation
345         * @return a suitable foreground color for the specified component and adapter
346         */
347        protected Color computeForeground(Component renderer, ComponentAdapter adapter) {
348            return adapter.isSelected() ? computeSelectedForeground(renderer, adapter) :
349                computeUnselectedForeground(renderer, adapter);
350        }
351    
352        /**
353         * <p>Computes a suitable unselected foreground for the renderer component within the
354         * specified adapter and returns the computed color. 
355         * 
356         * This implementation returns getForeground().
357         * 
358         * @param renderer
359         * @param adapter
360         * @return unselected foreground color
361         */
362        protected Color computeUnselectedForeground(Component renderer, ComponentAdapter adapter) {
363            return getForeground();
364        }
365    
366        /**
367         * <p>Computes a suitable selected foreground for the renderer component within the
368         * specified adapter and returns the computed color. 
369         * 
370         * This implementation returns getSelectedForeground().
371         * 
372         * @param renderer
373         * @param adapter
374         * @return selected foreground color
375         */
376        protected Color computeSelectedForeground(Component renderer, ComponentAdapter adapter) {
377            return getSelectedForeground();
378        }
379    
380        /**
381         * Computes the selected background color. 
382         * 
383         * This implementation simply returns the selectedBackground property.
384         * 
385         * @deprecated this is no longer used by this implementation 
386         * @param seed initial background color; must cope with null!
387         * @return the background color for a selected cell
388         */
389        protected Color computeSelectedBackground(Color seed) {
390            // JW: first go on fixing #178-swingx - return absolute color
391            // this moves the responsibility of computation to subclasses.
392            return selectedBackground;
393        }
394    
395        /**
396         * Computes the selected foreground color. 
397         * 
398         * This implementation simply returns the selectedBackground property.
399         *
400         * @deprecated this method is longer called by this implementation
401         *          
402         * @param seed initial foreground color; must cope with null!
403         * @return the foreground color for a selected cell
404         */
405        protected Color computeSelectedForeground(Color seed) {
406            // JW: first go on fixing #178-swingx - return absolute color
407            // this moves the responsibility of computation to subclasses.
408            return selectedForeground; 
409        }
410    
411        /**
412         * Returns immutable flag: if true, none of the setXX methods have
413         * any effects, there are no listeners added and no change events fired.
414         * @return true if none of the setXX methods have any effect
415         */
416        public boolean isImmutable() {
417            return immutable;
418        }
419        /**
420         * Returns the background color of this <code>Highlighter</code>.
421         *
422         * @return the background color of this <code>Highlighter</code>,
423         *          or null, if no background color has been set
424         */
425        public Color getBackground() {
426            return background;
427        }
428    
429        /**
430         * Sets the background color of this <code>Highlighter</code> and 
431         * notifies registered ChangeListeners if this
432         * is mutable. Does nothing if immutable.
433         *  
434         * @param color the background color of this <code>Highlighter</code>,
435         *          or null, to clear any existing background color
436         */
437        public void setBackground(Color color) {
438            if (isImmutable()) return;
439            background = color;
440            fireStateChanged();
441        }
442    
443        /**
444         * Returns the foreground color of this <code>Highlighter</code>.
445         *
446         * @return the foreground color of this <code>Highlighter</code>,
447         *          or null, if no foreground color has been set
448         */
449        public Color getForeground() {
450            return foreground;
451        }
452    
453        /**
454         * Sets the foreground color of this <code>Highlighter</code> and notifies
455         * registered ChangeListeners if this is mutable. Does nothing if 
456         * immutable.
457         *
458         * @param color the foreground color of this <code>Highlighter</code>,
459         *          or null, to clear any existing foreground color
460         */
461        public void setForeground(Color color) {
462            if (isImmutable()) return;
463            foreground = color;
464            fireStateChanged();
465        }
466    
467        /**
468         * Returns the selected background color of this <code>Highlighter</code>.
469         *
470         * @return the selected background color of this <code>Highlighter</code>,
471         *          or null, if no selected background color has been set
472         */
473        public Color getSelectedBackground() {
474            return selectedBackground;
475        }
476    
477        /**
478         * Sets the selected background color of this <code>Highlighter</code>
479         * and notifies registered ChangeListeners if this is mutable. Does nothing
480         * if immutable.
481         *
482         * @param color the selected background color of this <code>Highlighter</code>,
483         *          or null, to clear any existing selected background color
484         */
485        public void setSelectedBackground(Color color) {
486            if (isImmutable()) return;
487            selectedBackground = color;
488            fireStateChanged();
489        }
490    
491        /**
492         * Returns the selected foreground color of this <code>Highlighter</code>.
493         *
494         * @return the selected foreground color of this <code>Highlighter</code>,
495         *          or null, if no selected foreground color has been set
496         */
497        public Color getSelectedForeground() {
498            return selectedForeground;
499        }
500    
501        /**
502         * Sets the selected foreground color of this <code>Highlighter</code> and
503         * notifies registered ChangeListeners if this is mutable. Does nothing if
504         * immutable.
505         *
506         * @param color the selected foreground color of this <code>Highlighter</code>,
507         *          or null, to clear any existing selected foreground color
508         */
509        public void setSelectedForeground(Color color) {
510            if (isImmutable()) return;
511            selectedForeground = color;
512            fireStateChanged();
513        }
514    
515        /**
516         * Adds a <code>ChangeListener</code> if this is mutable. ChangeListeners are
517         * notified after changes of any attribute. Does nothing if immutable. 
518         *
519         * @param l the ChangeListener to add
520         * @see #removeChangeListener
521         */
522        public void addChangeListener(ChangeListener l) {
523            if (isImmutable()) return;
524            listenerList.add(ChangeListener.class, l);
525        }
526        
527    
528        /**
529         * Removes a <code>ChangeListener</code> if this is mutable. 
530         * Does nothis if immutable.
531         *
532         * @param l the <code>ChangeListener</code> to remove
533         * @see #addChangeListener
534         */
535        public void removeChangeListener(ChangeListener l) {
536            if (isImmutable()) return;
537            listenerList.remove(ChangeListener.class, l);
538        }
539    
540    
541        /**
542         * Returns an array of all the change listeners
543         * registered on this <code>Highlighter</code>.
544         *
545         * @return all of this model's <code>ChangeListener</code>s 
546         *         or an empty
547         *         array if no change listeners are currently registered
548         *
549         * @see #addChangeListener
550         * @see #removeChangeListener
551         *
552         * @since 1.4
553         */
554        public ChangeListener[] getChangeListeners() {
555            return (ChangeListener[])listenerList.getListeners(
556                    ChangeListener.class);
557        }
558    
559    
560        /** 
561         * Runs each <code>ChangeListener</code>'s <code>stateChanged</code> method.
562         * 
563         */
564        protected void fireStateChanged() {
565            if (isImmutable()) return;
566            Object[] listeners = listenerList.getListenerList();
567            for (int i = listeners.length - 2; i >= 0; i -=2 ) {
568                if (listeners[i] == ChangeListener.class) {
569                    if (changeEvent == null) {
570                        changeEvent = new ChangeEvent(this);
571                    }
572                    ((ChangeListener)listeners[i+1]).stateChanged(changeEvent);
573                }          
574            }
575        }   
576    
577        
578        public interface UIHighlighter {
579            
580            void updateUI();
581        }
582    }