001    /*
002     * $Id: HighlighterPipeline.java,v 1.12 2006/02/08 13:05:48 kleopatra 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    import java.util.ArrayList;
027    import java.util.Iterator;
028    import java.util.List;
029    
030    import javax.swing.BoundedRangeModel;
031    import javax.swing.JComponent;
032    import javax.swing.event.ChangeEvent;
033    import javax.swing.event.ChangeListener;
034    import javax.swing.event.EventListenerList;
035    import javax.swing.table.DefaultTableCellRenderer;
036    
037    import org.jdesktop.swingx.decorator.Highlighter.UIHighlighter;
038    
039    /**
040     * A class which manages the lists of highlighters.
041     *
042     * @see Highlighter
043     *
044     * @author Ramesh Gupta
045     * @author Jeanette Winzenburg
046     * 
047     */
048    public class HighlighterPipeline implements UIHighlighter {
049        protected transient ChangeEvent changeEvent = null;
050        protected EventListenerList listenerList = new EventListenerList();
051    
052        protected List<Highlighter> highlighters;
053        // JW: this is a hack to make JXTable renderers behave...
054        private final static Highlighter resetDefaultTableCellRendererHighlighter = new Highlighter(null, null, true){
055    
056            @Override
057            protected void applyBackground(Component renderer, ComponentAdapter adapter) {
058                if (!adapter.isSelected()) {
059                    // renderer.setBackground(null);
060                    Object colorMemory = ((JComponent) renderer).getClientProperty("rendererColorMemory.background");
061                    if (colorMemory instanceof ColorMemory) {
062                        renderer.setBackground(((ColorMemory) colorMemory).color);
063                    } else {
064                        ((JComponent) renderer).putClientProperty("rendererColorMemory.background", new ColorMemory(renderer.getBackground()));
065                    }
066                }
067            }
068    
069            @Override
070            protected void applyForeground(Component renderer, ComponentAdapter adapter) {
071                if (!adapter.isSelected()) {
072    //                renderer.setForeground(null);
073                    Object colorMemory = ((JComponent) renderer).getClientProperty("rendererColorMemory.foreground");
074                    if (colorMemory instanceof ColorMemory) {
075                        renderer.setForeground(((ColorMemory) colorMemory).color);
076                    } else {
077                        ((JComponent) renderer).putClientProperty("rendererColorMemory.foreground", new ColorMemory(renderer.getForeground()));
078                    }
079                }
080            }
081            
082        };
083        
084        private static class ColorMemory {
085            public ColorMemory(Color foreground) {
086                color = foreground;
087            }
088    
089            Color color;
090        }
091        
092        private ChangeListener highlighterChangeListener;
093    
094        public HighlighterPipeline() {
095            highlighters = new ArrayList<Highlighter>();
096        }
097        
098        /**
099         * 
100         * @param inList the array of highlighters to initially add to this.
101         * @throws NullPointerException if array is null of array contains null values.
102         */
103        public HighlighterPipeline(Highlighter[] inList) {
104            this();
105            for (int i = 0; i < inList.length; i++) {
106                addHighlighter(inList[i]);
107            }
108        }
109    
110        /**
111         * Appends a highlighter to the pipeline.
112         *
113         * @param hl highlighter to add
114          * @throws NullPointerException if highlighter is null.
115        */
116        public void addHighlighter(Highlighter hl) {
117            addHighlighter(hl, false);
118        }
119    
120        /**
121         * Adds a highlighter to the pipeline.
122         *
123         * PENDING: Duplicate inserts?
124         * 
125         * @param hl highlighter to add
126         * @param prepend prepend the highlighter if true; false will append
127         * @throws NullPointerException if highlighter is null.
128         */
129        public void addHighlighter(Highlighter hl, boolean prepend) {
130            if (prepend) {
131                highlighters.add(0, hl);
132            } else {
133                highlighters.add(highlighters.size(), hl);
134            }
135            updateUI(hl);
136            hl.addChangeListener(getHighlighterChangeListener());
137            fireStateChanged();
138        }
139    
140    
141        private ChangeListener getHighlighterChangeListener() {
142            if (highlighterChangeListener == null) {
143                highlighterChangeListener = new ChangeListener() {
144    
145                    public void stateChanged(ChangeEvent e) {
146                        fireStateChanged();
147                        
148                    }
149                    
150                };
151            }
152            return highlighterChangeListener;
153        }
154    
155        /**
156         * Removes a highlighter from the pipeline.
157         *
158         *  
159         * @param hl highlighter to remove
160         */
161        public void removeHighlighter(Highlighter hl) {
162            boolean success = highlighters.remove(hl);
163            if (success) {
164                // PENDING: duplicates?
165                hl.removeChangeListener(getHighlighterChangeListener());
166                fireStateChanged();
167            }
168            // should log if this didn't succeed. Maybe
169        }
170    
171        public Highlighter[] getHighlighters() {
172            return (Highlighter[])highlighters.toArray(new Highlighter[highlighters.size()]);
173        }
174    
175    
176        /**
177         * Applies all the highlighters to the components.
178         * 
179         * @throws NullPointerException if either stamp or adapter is null.
180         */
181        public Component apply(Component stamp, ComponentAdapter adapter) {
182            stamp = resetDefaultTableCellRenderer(stamp, adapter);
183            for (Iterator<Highlighter> iter = highlighters.iterator(); iter.hasNext();) {
184                stamp = iter.next().highlight(stamp, adapter);
185                
186            }
187            return stamp;
188        }
189    
190        /**
191         * This is a hack around DefaultTableCellRenderer color "memory".
192         * 
193         * The issue is that the default has internal color management 
194         * which is different from other types of renderers. The
195         * consequence of the internal color handling is that there's
196         * a color memory which must be reset somehow. The "old" hack around
197         * reset the xxColors of all types of renderers to the adapter's
198         * target XXColors, introducing #178-swingx (Highlighgters must not
199         * change any colors except those for which their color properties are
200         * explicitly set).
201         * 
202         * This hack limits the interference to renderers of type 
203         * DefaultTableCellRenderer, applying a hacking highlighter which
204         *  resets the renderers XXColors to null if unselected. Note that
205         *  both hacks loose any colors previously set by clients (in 
206         *  prepareRenderer before applying the pipeline). 
207         * 
208         * @param stamp
209         * @param adapter
210         * @return
211         */
212        private Component resetDefaultTableCellRenderer(Component stamp, ComponentAdapter adapter) {
213            //JW
214            // table renderers have different state memory as list/tree renderers
215            // without the null they don't unstamp!
216            // but... null has adversory effect on JXList f.i. - selection
217            // color is changed. This is related to #178-swingx: 
218            // highlighter background computation is weird.
219            // 
220            if (stamp instanceof DefaultTableCellRenderer) {    
221            /** @todo optimize the following bug fix */
222                stamp = resetDefaultTableCellRendererHighlighter.highlight(stamp, adapter); 
223            }
224            return stamp;
225        }
226    
227        public void updateUI() {
228            for (Highlighter highlighter : highlighters) {
229                updateUI(highlighter);
230            }
231        }   
232    
233        /**
234         * @param hl
235         */
236        private void updateUI(Highlighter hl) {
237            if (hl instanceof UIHighlighter) {
238                ((UIHighlighter) hl).updateUI();
239            }
240        }
241    
242        /**
243         * Adds a <code>ChangeListener</code>.  The change listeners are run each
244         * time any one of the Bounded Range model properties changes.
245         *
246         * @param l the ChangeListener to add
247         * @see #removeChangeListener
248         * @see BoundedRangeModel#addChangeListener
249         */
250        public void addChangeListener(ChangeListener l) {
251            listenerList.add(ChangeListener.class, l);
252        }
253        
254    
255        /**
256         * Removes a <code>ChangeListener</code>.
257         *
258         * @param l the <code>ChangeListener</code> to remove
259         * @see #addChangeListener
260         * @see BoundedRangeModel#removeChangeListener
261         */
262        public void removeChangeListener(ChangeListener l) {
263            listenerList.remove(ChangeListener.class, l);
264        }
265    
266    
267        /**
268         * Returns an array of all the change listeners
269         * registered on this <code>DefaultBoundedRangeModel</code>.
270         *
271         * @return all of this model's <code>ChangeListener</code>s 
272         *         or an empty
273         *         array if no change listeners are currently registered
274         *
275         * @see #addChangeListener
276         * @see #removeChangeListener
277         *
278         * @since 1.4
279         */
280        public ChangeListener[] getChangeListeners() {
281            return (ChangeListener[])listenerList.getListeners(
282                    ChangeListener.class);
283        }
284    
285    
286        /** 
287         * Runs each <code>ChangeListener</code>'s <code>stateChanged</code> method.
288         * 
289         * @see #setRangeProperties
290         * @see EventListenerList
291         */
292        protected void fireStateChanged() 
293        {
294            Object[] listeners = listenerList.getListenerList();
295            for (int i = listeners.length - 2; i >= 0; i -=2 ) {
296                if (listeners[i] == ChangeListener.class) {
297                    if (changeEvent == null) {
298                        changeEvent = new ChangeEvent(this);
299                    }
300                    ((ChangeListener)listeners[i+1]).stateChanged(changeEvent);
301                }          
302            }
303        }
304    
305    
306    }