001    /*
002     * $Id: JXList.java 3401 2009-07-22 18:09:08Z 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    
022    package org.jdesktop.swingx;
023    
024    import java.awt.Component;
025    import java.awt.Point;
026    import java.awt.event.ActionEvent;
027    import java.util.ArrayList;
028    import java.util.Collections;
029    import java.util.Comparator;
030    import java.util.List;
031    import java.util.Vector;
032    import java.util.logging.Logger;
033    
034    import javax.swing.AbstractListModel;
035    import javax.swing.Action;
036    import javax.swing.JComponent;
037    import javax.swing.JList;
038    import javax.swing.KeyStroke;
039    import javax.swing.ListCellRenderer;
040    import javax.swing.ListModel;
041    import javax.swing.ListSelectionModel;
042    import javax.swing.SortOrder;
043    import javax.swing.SwingUtilities;
044    import javax.swing.RowSorter.SortKey;
045    import javax.swing.event.ChangeEvent;
046    import javax.swing.event.ChangeListener;
047    import javax.swing.event.ListDataEvent;
048    import javax.swing.event.ListDataListener;
049    import javax.swing.plaf.ListUI;
050    
051    import org.jdesktop.swingx.decorator.ComponentAdapter;
052    import org.jdesktop.swingx.decorator.CompoundHighlighter;
053    import org.jdesktop.swingx.decorator.Highlighter;
054    import org.jdesktop.swingx.plaf.LookAndFeelAddons;
055    import org.jdesktop.swingx.plaf.XListAddon;
056    import org.jdesktop.swingx.renderer.AbstractRenderer;
057    import org.jdesktop.swingx.renderer.DefaultListRenderer;
058    import org.jdesktop.swingx.renderer.StringValue;
059    import org.jdesktop.swingx.renderer.StringValues;
060    import org.jdesktop.swingx.rollover.ListRolloverController;
061    import org.jdesktop.swingx.rollover.ListRolloverProducer;
062    import org.jdesktop.swingx.rollover.RolloverProducer;
063    import org.jdesktop.swingx.rollover.RolloverRenderer;
064    import org.jdesktop.swingx.search.ListSearchable;
065    import org.jdesktop.swingx.search.SearchFactory;
066    import org.jdesktop.swingx.search.Searchable;
067    import org.jdesktop.swingx.sort.SortController;
068    import org.jdesktop.swingx.sort.SortUtils;
069    
070    /**
071     * Enhanced List component with support for general SwingX sorting/filtering,
072     * rendering, highlighting, rollover and search functionality. List specific
073     * enhancements include ?? PENDING JW ...
074     * 
075     * <h2>Sorting and Filtering</h2>
076     * 
077     * <b>NOTE: sorting/filtering is currently disabled. All related methods are
078     * dis-functional, the overriden methods behave just the same as JList. </b>
079     * <p>
080     * JXList supports sorting and filtering. 
081     * 
082     * It provides api to apply a specific sort order, to toggle the sort order and to reset a sort.
083     * Sort sequence can be configured by setting a custom comparator.
084     * 
085     * <pre><code>
086     * list.setFilterEnabled(true);
087     * list.setComparator(myComparator);
088     * list.setSortOrder(SortOrder.DESCENDING);
089     * list.toggleSortOder();
090     * list.resetSortOrder();
091     * </code></pre>
092     * 
093     * <p>
094     * JXList provides api to access items of the underlying model in view coordinates
095     * and to convert from/to model coordinates.
096     * 
097     * <b>Note</b>: List sorting/filtering is disabled by
098     * default because it has side-effects which might break "normal" expectations
099     * when using a JList: if enabled all row coordinates (including those returned
100     * by the selection) are in view coordinates. Furthermore, the model returned
101     * from getModel() is a wrapper around the actual data. 
102     * 
103     * <b>Note:</b> SwingX sorting/filtering is incompatible with core sorting/filtering in 
104     * JDK 6+. Will be replaced by core functionality after switching the target jdk
105     * version from 5 to 6, we are on the move right now.
106     * 
107     * 
108     * <h2>Rendering and Highlighting</h2>
109     * 
110     * As all SwingX collection views, a JXList is a HighlighterClient (PENDING JW:
111     * formally define and implement, like in AbstractTestHighlighter), that is it
112     * provides consistent api to add and remove Highlighters which can visually
113     * decorate the rendering component.
114     * <p>
115     * 
116     * <pre><code>
117     * 
118     * JXList list = new JXList(new Contributors());
119     * // implement a custom string representation, concated from first-, lastName
120     * StringValue sv = new StringValue() {
121     *     public String getString(Object value) {
122     *        if (value instanceof Contributor) {
123     *           Contributor contributor = (Contributor) value;
124     *           return contributor.lastName() + ", " + contributor.firstName(); 
125     *        }
126     *        return StringValues.TO_STRING(value);
127     *     }
128     * };
129     * list.setCellRenderer(new DefaultListRenderer(sv); 
130     * // highlight condition: gold merits
131     * HighlightPredicate predicate = new HighlightPredicate() {
132     *    public boolean isHighlighted(Component renderer,
133     *                     ComponentAdapter adapter) {
134     *       if (!(value instanceof Contributor)) return false;              
135     *       return ((Contributor) value).hasGold();
136     *    }
137     * };
138     * // highlight with foreground color 
139     * list.addHighlighter(new PainterHighlighter(predicate, goldStarPainter);      
140     * 
141     * </code></pre>
142     * 
143     * <i>Note:</i> to support the highlighting this implementation wraps the
144     * ListCellRenderer set by client code with a DelegatingRenderer which applies
145     * the Highlighter after delegating the default configuration to the wrappee. As
146     * a side-effect, getCellRenderer does return the wrapper instead of the custom
147     * renderer. To access the latter, client code must call getWrappedCellRenderer.
148     * <p>
149     * 
150     * <h2>Rollover</h2>
151     * 
152     * As all SwingX collection views, a JXList supports per-cell rollover. If
153     * enabled, the component fires rollover events on enter/exit of a cell which by
154     * default is promoted to the renderer if it implements RolloverRenderer, that
155     * is simulates live behaviour. The rollover events can be used by client code
156     * as well, f.i. to decorate the rollover row using a Highlighter.
157     * 
158     * <pre><code>
159     * 
160     * JXList list = new JXList();
161     * list.setRolloverEnabled(true);
162     * list.setCellRenderer(new DefaultListRenderer());
163     * list.addHighlighter(new ColorHighlighter(HighlightPredicate.ROLLOVER_ROW, 
164     *      null, Color.RED);      
165     * 
166     * </code></pre>
167     * 
168     * 
169     * <h2>Search</h2>
170     * 
171     * As all SwingX collection views, a JXList is searchable. A search action is
172     * registered in its ActionMap under the key "find". The default behaviour is to
173     * ask the SearchFactory to open a search component on this component. The
174     * default keybinding is retrieved from the SearchFactory, typically ctrl-f (or
175     * cmd-f for Mac). Client code can register custom actions and/or bindings as
176     * appropriate.
177     * <p>
178     * 
179     * JXList provides api to vend a renderer-controlled String representation of
180     * cell content. This allows the Searchable and Highlighters to use WYSIWYM
181     * (What-You-See-Is-What-You-Match), that is pattern matching against the actual
182     * string as seen by the user.
183     * 
184     * 
185     * @author Ramesh Gupta
186     * @author Jeanette Winzenburg
187     */
188    public class JXList extends JList {
189        @SuppressWarnings("all")
190        private static final Logger LOG = Logger.getLogger(JXList.class.getName());
191        
192        /**
193         * UI Class ID
194         */
195        public final static String uiClassID = "XListUI";
196        
197        /**
198         * Registers a Addon for JXList.
199         */
200        // @KEEP JW- will be used if sortable/filterable again
201        static {
202            LookAndFeelAddons.contribute(new XListAddon());
203        }
204    
205        
206    
207        public static final String EXECUTE_BUTTON_ACTIONCOMMAND = "executeButtonAction";
208    
209        /**
210         * The pipeline holding the highlighters.
211         */
212        protected CompoundHighlighter compoundHighlighter;
213    
214        /** listening to changeEvents from compoundHighlighter. */
215        private ChangeListener highlighterChangeListener;
216    
217        /** The ComponentAdapter for model data access. */
218        protected ComponentAdapter dataAdapter;
219    
220        /**
221         * Mouse/Motion/Listener keeping track of mouse moved in cell coordinates.
222         */
223        private RolloverProducer rolloverProducer;
224    
225        /**
226         * RolloverController: listens to cell over events and repaints
227         * entered/exited rows.
228         */
229        private ListRolloverController<JXList> linkController;
230    
231        /** A wrapper around the default renderer enabling decoration. */
232        private DelegatingRenderer delegatingRenderer;
233    
234        private boolean filterEnabled;
235    
236        private Searchable searchable;
237    
238        private Comparator<?> comparator;
239    
240        /**
241        * Constructs a <code>JXList</code> with an empty model and filters disabled.
242        *
243        */                                           
244        public JXList() {
245            this(false);
246        }
247    
248        /**
249         * Constructs a <code>JXList</code> that displays the elements in the
250         * specified, non-<code>null</code> model and filters disabled.
251         *
252         * @param dataModel   the data model for this list
253         * @exception IllegalArgumentException   if <code>dataModel</code>
254         *                                           is <code>null</code>
255         */                                           
256        public JXList(ListModel dataModel) {
257            this(dataModel, false);
258        }
259    
260        /**
261         * Constructs a <code>JXList</code> that displays the elements in
262         * the specified array and filters disabled.
263         *
264         * @param  listData  the array of Objects to be loaded into the data model
265         * @throws IllegalArgumentException   if <code>listData</code>
266         *                                          is <code>null</code>
267         */
268        public JXList(Object[] listData) {
269            this(listData, false);
270        }
271    
272        /**
273         * Constructs a <code>JXList</code> that displays the elements in
274         * the specified <code>Vector</code> and filtes disabled.
275         *
276         * @param  listData  the <code>Vector</code> to be loaded into the
277         *          data model
278         * @throws IllegalArgumentException   if <code>listData</code>
279         *                                          is <code>null</code>
280         */
281        public JXList(Vector<?> listData) {
282            this(listData, false);
283        }
284    
285    
286        /**
287         * Constructs a <code>JXList</code> with an empty model and
288         * filterEnabled property.
289         * 
290         * @param filterEnabled <code>boolean</code> to determine if 
291         *  filtering/sorting is enabled
292         */
293        public JXList(boolean filterEnabled) {
294            init(filterEnabled);
295        }
296    
297        /**
298         * Constructs a <code>JXList</code> with the specified model and
299         * filterEnabled property.
300         * 
301         * @param dataModel   the data model for this list
302         * @param filterEnabled <code>boolean</code> to determine if 
303         *          filtering/sorting is enabled
304         * @throws IllegalArgumentException   if <code>dataModel</code>
305         *                                          is <code>null</code>
306         */
307        public JXList(ListModel dataModel, boolean filterEnabled) {
308            super(dataModel);
309            init(filterEnabled);
310        }
311    
312        /**
313         * Constructs a <code>JXList</code> that displays the elements in
314         * the specified array and filterEnabled property.
315         *
316         * @param  listData  the array of Objects to be loaded into the data model
317         * @param filterEnabled <code>boolean</code> to determine if filtering/sorting
318         *   is enabled
319         * @throws IllegalArgumentException   if <code>listData</code>
320         *                                          is <code>null</code>
321         */
322        public JXList(Object[] listData, boolean filterEnabled) {
323            super(listData);
324            if (listData == null) 
325               throw new IllegalArgumentException("listData must not be null");
326            init(filterEnabled);
327        }
328    
329        /**
330         * Constructs a <code>JXList</code> that displays the elements in
331         * the specified <code>Vector</code> and filtersEnabled property.
332         *
333         * @param  listData  the <code>Vector</code> to be loaded into the
334         *          data model
335         * @param filterEnabled <code>boolean</code> to determine if filtering/sorting
336         *   is enabled
337         * @throws IllegalArgumentException if <code>listData</code> is <code>null</code>
338         */
339        public JXList(Vector<?> listData, boolean filterEnabled) {
340            super(listData);
341            if (listData == null) 
342               throw new IllegalArgumentException("listData must not be null");
343            init(filterEnabled);
344        }
345    
346    
347        private void init(boolean filterEnabled) {
348            setFilterEnabled(filterEnabled);
349            
350            Action findAction = createFindAction();
351            getActionMap().put("find", findAction);
352            
353            KeyStroke findStroke = SearchFactory.getInstance().getSearchAccelerator();
354            getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(findStroke, "find");
355            
356        }
357    
358        private Action createFindAction() {
359            return new UIAction("find") {
360                public void actionPerformed(ActionEvent e) {
361                    doFind();
362                }
363            };
364        }
365    
366        /** 
367         * Starts a search on this List's visible items. This implementation asks the
368         * SearchFactory to open a find widget on itself.
369         */
370        protected void doFind() {
371            SearchFactory.getInstance().showFindInput(this, getSearchable());
372        }
373    
374        /**
375         * Returns a Searchable for this component, guaranteed to be not null. This 
376         * implementation lazily creates a ListSearchable if necessary.
377         *  
378         * @return a not-null Searchable for this list.
379         * 
380         * @see #setSearchable(Searchable)
381         * @see org.jdesktop.swingx.search.ListSearchable
382         */
383        public Searchable getSearchable() {
384            if (searchable == null) {
385                searchable = new ListSearchable(this);
386            }
387            return searchable;
388        }
389    
390        /**
391         * Sets the Searchable for this component. If null, a default 
392         * Searchable will be created and used.
393         * 
394         * @param searchable the Searchable to use for this component, may be null to indicate
395         *   using the list's default searchable.
396         * @see #getSearchable()
397         */
398        public void setSearchable(Searchable searchable) {
399            this.searchable = searchable;
400        }
401        
402    //--------------------- Rollover support
403        
404        /**
405         * Sets the property to enable/disable rollover support. If enabled, the list
406         * fires property changes on per-cell mouse rollover state, i.e. 
407         * when the mouse enters/leaves a list cell. <p>
408         * 
409         * This can be enabled to show "live" rollover behaviour, f.i. the cursor over a cell 
410         * rendered by a JXHyperlink.<p>
411         * 
412         * Default value is disabled.
413         * 
414         * @param rolloverEnabled a boolean indicating whether or not the rollover
415         *   functionality should be enabled.
416         * 
417         * @see #isRolloverEnabled()
418         * @see #getLinkController()
419         * @see #createRolloverProducer()
420         * @see org.jdesktop.swingx.rollover.RolloverRenderer  
421         *    
422         */
423        public void setRolloverEnabled(boolean rolloverEnabled) {
424            boolean old = isRolloverEnabled();
425            if (rolloverEnabled == old)
426                return;
427            if (rolloverEnabled) {
428                rolloverProducer = createRolloverProducer();
429                addMouseListener(rolloverProducer);
430                addMouseMotionListener(rolloverProducer);
431                getLinkController().install(this);
432            } else {
433                removeMouseListener(rolloverProducer);
434                removeMouseMotionListener(rolloverProducer);
435                rolloverProducer = null;
436                getLinkController().release();
437            }
438            firePropertyChange("rolloverEnabled", old, isRolloverEnabled());
439        }
440    
441        /**
442         * Returns a boolean indicating whether or not rollover support is enabled. 
443         *
444         * @return a boolean indicating whether or not rollover support is enabled. 
445         * 
446         * @see #setRolloverEnabled(boolean)
447         */
448        public boolean isRolloverEnabled() {
449            return rolloverProducer != null;
450        }
451        
452        /**
453         * Returns the RolloverController for this component. Lazyly creates the 
454         * controller if necessary, that is the return value is guaranteed to be 
455         * not null. <p>
456         * 
457         * PENDING JW: rename to getRolloverController
458         * 
459         * @return the RolloverController for this tree, guaranteed to be not null.
460         * 
461         * @see #setRolloverEnabled(boolean)
462         * @see #createLinkController()
463         * @see org.jdesktop.swingx.rollover.RolloverController
464         */
465        protected ListRolloverController<JXList> getLinkController() {
466            if (linkController == null) {
467                linkController = createLinkController();
468            }
469            return linkController;
470        }
471    
472        /**
473         * Creates and returns a RolloverController appropriate for this component.
474         * 
475         * @return a RolloverController appropriate for this component.
476         * 
477         * @see #getLinkController()
478         * @see org.jdesktop.swingx.rollover.RolloverController
479         */
480        protected ListRolloverController<JXList> createLinkController() {
481            return new ListRolloverController<JXList>();
482        }
483    
484    
485        /**
486         * Creates and returns the RolloverProducer to use with this tree.
487         * <p>
488         * 
489         * @return <code>RolloverProducer</code> to use with this tree
490         * 
491         * @see #setRolloverEnabled(boolean)
492         */
493        protected RolloverProducer createRolloverProducer() {
494            return new ListRolloverProducer();
495        }
496    
497        //--------------------- public sort api
498    //    /** 
499    //     * Returns the sortable property.
500    //     * Here: same as filterEnabled.
501    //     * @return true if the table is sortable. 
502    //     */
503    //    public boolean isSortable() {
504    //        return isFilterEnabled();
505    //    }
506        /**
507         * Removes the interactive sorter.
508         * 
509         */
510        public void resetSortOrder() {
511            SortController controller = getSortController();
512            if (controller != null) {
513                controller.resetSortOrders();
514            }
515        }
516    
517        /**
518         * 
519         * Toggles the sort order of the items.
520         * <p>
521         * The exact behaviour is defined by the SortController's
522         * toggleSortOrder implementation. Typically a unsorted 
523         * column is sorted in ascending order, a sorted column's
524         * order is reversed. 
525         * <p>
526         * PENDING: where to get the comparator from?
527         * <p>
528         * 
529         * 
530         */
531        public void toggleSortOrder() {
532            SortController controller = getSortController();
533            if (controller != null) {
534                controller.toggleSortOrder(0);
535            }
536        }
537    
538        /**
539         * Sorts the list using SortOrder. 
540         * 
541         * 
542         * Respects the JXList's sortable and comparator 
543         * properties: routes the comparator to the SortController
544         * and does nothing if !isFilterEnabled(). 
545         * <p>
546         * 
547         * @param sortOrder the sort order to use. If null or SortOrder.UNSORTED, 
548         *   this method has the same effect as resetSortOrder();
549         *    
550         */
551        public void setSortOrder(SortOrder sortOrder) {
552            if (!SortUtils.isSorted(sortOrder)) {
553                resetSortOrder();
554                return;
555            }
556            SortController sortController = getSortController();
557            if (sortController != null) {
558    //            sortController.setSortOrder(0, sortOrder);
559            }
560        }
561    
562    
563        /**
564         * Returns the SortOrder. 
565         * 
566         * @return the interactive sorter's SortOrder  
567         *  or SortOrder.UNSORTED 
568         */
569        public SortOrder getSortOrder() {
570            SortController sortController = getSortController();
571            if (sortController == null) return SortOrder.UNSORTED;
572            return sortController.getSortOrder(0);
573        }
574    
575        /**
576         * 
577         * @return the comparator used.
578         * @see #setComparator(Comparator)
579         */
580        public Comparator<?> getComparator() {
581            return comparator;
582        }
583        
584        /**
585         * Sets the comparator used. As a side-effect, the 
586         * current sort might be updated. The exact behaviour
587         * is defined in #updateSortAfterComparatorChange. 
588         * 
589         * @param comparator the comparator to use.
590         */
591        public void setComparator(Comparator<?> comparator) {
592            Comparator<?> old = getComparator();
593            this.comparator = comparator;
594            updateSortAfterComparatorChange();
595            firePropertyChange("comparator", old, getComparator());
596        }
597        
598        /**
599         * Updates sort after comparator has changed. 
600         * Here: sets the current sortOrder with the new comparator.
601         *
602         */
603        protected void updateSortAfterComparatorChange() {
604            setSortOrder(getSortOrder());
605            
606        }
607    
608        /**
609         * returns the currently active SortController. Will be null if
610         * !isFilterEnabled().
611         * @return the currently active <code>SortController</code> may be null
612         */
613        protected SortController getSortController() {
614            return null;
615        }
616        
617        
618        // ---------------------------- filters
619    
620        /**
621         * returns the element at the given index. The index is in view coordinates
622         * which might differ from model coordinates if filtering is enabled and
623         * filters/sorters are active.
624         * 
625         * @param viewIndex the index in view coordinates
626         * @return the element at the index
627         * @throws IndexOutOfBoundsException if viewIndex < 0 or viewIndex >=
628         *         getElementCount()
629         */
630        public Object getElementAt(int viewIndex) {
631            return getModel().getElementAt(viewIndex);
632        }
633    
634        /**
635         * Returns the number of elements in this list in view 
636         * coordinates. If filters are active this number might be
637         * less than the number of elements in the underlying model.
638         * 
639         * @return number of elements in this list in view coordinates
640         */
641        public int getElementCount() {
642            return getModel().getSize();
643        }
644    
645        /**
646         * Convert row index from view coordinates to model coordinates accounting
647         * for the presence of sorters and filters.
648         * 
649         * @param viewIndex index in view coordinates
650         * @return index in model coordinates
651         * @throws IndexOutOfBoundsException if viewIndex < 0 or viewIndex >= getElementCount() 
652         */
653        public int convertIndexToModel(int viewIndex) {
654            return viewIndex;
655        }
656    
657        /**
658         * Convert index from model coordinates to view coordinates accounting
659         * for the presence of sorters and filters.
660         * 
661         * PENDING Filter guards against out of range - should not? 
662         * 
663         * @param modelIndex index in model coordinates
664         * @return index in view coordinates if the model index maps to a view coordinate
665         *          or -1 if not contained in the view.
666         * 
667         */
668        public int convertIndexToView(int modelIndex) {
669            return modelIndex;
670        }
671    
672        /**
673         * returns the underlying model. If !isFilterEnabled this will be the same
674         * as getModel().
675         * 
676         * @return the underlying model
677         */
678        public ListModel getWrappedModel() {
679            return /* isFilterEnabled() ? wrappingModel.getModel() : */ getModel();
680        }
681    
682        /**
683         * Enables/disables filtering support. If enabled all row indices -
684         * including the selection - are in view coordinates and getModel returns a
685         * wrapper around the underlying model.<p>
686         * 
687         * <b>NOTE</b>: Currently, this method has no effect - filtering/sorting of
688         * a JXList is disabled until SwingX is fully moved to the core style 
689         * filtering/sorting. <p>
690         * 
691         * Note: as an implementation side-effect calling this method clears the
692         * selection (done in super.setModel).<p>
693         * 
694         * PENDING: cleanup state transitions!! - currently this can be safely
695         * applied once only to enable. Internal state is inconsistent if trying to
696         * disable again. As a temporary emergency measure, this will throw a 
697         * IllegalStateException. 
698         * 
699         * see Issue #2-swinglabs.
700         * 
701         * 
702         * @param enabled
703         * @throws IllegalStateException if trying to disable again.
704         */
705        public void setFilterEnabled(boolean enabled) {
706            return;
707    //        boolean old = isFilterEnabled();
708    //        if (old == enabled)
709    //            return;
710    //        if (old) 
711    //            throw new IllegalStateException("must not reset filterEnabled");
712    //        // JW: filterEnabled must be set before calling super.setModel!
713    //        filterEnabled = enabled;
714    //        wrappingModel = new WrappingListModel(getModel());
715    //        super.setModel(wrappingModel);
716    //        firePropertyChange("filterEnabled", old, isFilterEnabled());
717        }
718    
719        /**
720         * 
721         * @return a <boolean> indicating if filtering is enabled.
722         * @see #setFilterEnabled(boolean)
723         */
724        public boolean isFilterEnabled() {
725            return filterEnabled;
726        }
727    
728        /**
729         * {@inheritDoc} <p>
730         * 
731         * Overridden to update selectionMapper
732         */
733        @Override 
734        public void setSelectionModel(ListSelectionModel newModel) {
735            super.setSelectionModel(newModel);
736    //        getSelectionMapper().setViewSelectionModel(getSelectionModel());
737        }
738    
739        /**
740         * {@inheritDoc} <p>
741         * 
742         * Sets the underlying data model. Note that if isFilterEnabled you must
743         * call getWrappedModel to access the model given here. In this case
744         * getModel returns a wrapper around the data!
745         * 
746         * @param model the data model for this list.
747         * 
748         */
749        @Override
750        public void setModel(ListModel model) {
751            super.setModel(model);
752    //        if (isFilterEnabled()) {
753    //            wrappingModel.setModel(model);
754    //        } else {
755    //            super.setModel(model);
756    //        }
757        }
758    
759    
760        // ---------------------------- uniform data model
761    
762        /**
763         * @return the unconfigured ComponentAdapter.
764         */
765        protected ComponentAdapter getComponentAdapter() {
766            if (dataAdapter == null) {
767                dataAdapter = new ListAdapter(this);
768            }
769            return dataAdapter;
770        }
771    
772        /**
773         * Convenience to access a configured ComponentAdapter.
774         * Note: the column index of the configured adapter is always 0.
775         * 
776         * @param index the row index in view coordinates, must be valid.
777         * @return the configured ComponentAdapter.
778         */
779        protected ComponentAdapter getComponentAdapter(int index) {
780            ComponentAdapter adapter = getComponentAdapter();
781            adapter.column = 0;
782            adapter.row = index;
783            return adapter;
784        }
785        
786        /**
787         * A component adapter targeted at a JXList.
788         */
789        protected static class ListAdapter extends ComponentAdapter {
790            private final JXList list;
791    
792            /**
793             * Constructs a <code>ListAdapter</code> for the specified target
794             * JXList.
795             * 
796             * @param component  the target list.
797             */
798            public ListAdapter(JXList component) {
799                super(component);
800                list = component;
801            }
802    
803            /**
804             * Typesafe accessor for the target component.
805             * 
806             * @return the target component as a {@link org.jdesktop.swingx.JXList}
807             */
808            public JXList getList() {
809                return list;
810            }
811    
812            /**
813             * {@inheritDoc}
814             */
815            @Override
816            public boolean hasFocus() {
817                /** TODO: Think through printing implications */
818                return list.isFocusOwner() && (row == list.getLeadSelectionIndex());
819            }
820    
821            /**
822             * {@inheritDoc}
823             */
824            @Override
825            public int getRowCount() {
826                return list.getWrappedModel().getSize();
827            }
828    
829            /**
830             * {@inheritDoc} <p>
831             * Overridden to return value at implicit view coordinates.
832             */
833            @Override
834            public Object getValue() {
835                return list.getElementAt(row);
836            }
837    
838            /**
839             * {@inheritDoc}
840             */
841            @Override
842            public Object getValueAt(int row, int column) {
843                return list.getWrappedModel().getElementAt(row);
844            }
845    
846            /**
847             * {@inheritDoc}
848             */
849            @Override
850            public Object getFilteredValueAt(int row, int column) {
851                return list.getElementAt(row);
852            }
853    
854            
855            /**
856             * {@inheritDoc}
857             */
858            @Override
859            public String getFilteredStringAt(int row, int column) {
860                return list.getStringAt(row);
861            }
862    
863            /**
864             * {@inheritDoc}
865             */
866            @Override
867            public String getString() {
868                return list.getStringAt(row);
869            }
870    
871            /**
872             * {@inheritDoc}
873             */
874            @Override
875            public String getStringAt(int row, int column) {
876                // PENDING JW: here we are duplicating code from the list
877                // that's because list api is in view-coordinates
878                ListCellRenderer renderer = list.getDelegatingRenderer().getDelegateRenderer();
879                if (renderer instanceof StringValue) {
880                    return ((StringValue) renderer).getString(getValueAt(row, column));
881                }
882                return StringValues.TO_STRING.getString(getValueAt(row, column));
883            }
884    
885            /**
886             * {@inheritDoc}
887             */
888            @Override
889            public boolean isCellEditable(int row, int column) {
890                return false;
891            }
892    
893            /**
894             * {@inheritDoc}
895             */
896            @Override
897            public boolean isEditable() {
898                return false;
899            }
900            
901            /**
902             * {@inheritDoc}
903             */
904            @Override
905            public boolean isSelected() {
906                /** TODO: Think through printing implications */
907                return list.isSelectedIndex(row);
908            }
909    
910        }
911    
912        // ------------------------------ renderers
913    
914    
915        
916        /**
917         * Sets the <code>Highlighter</code>s to the table, replacing any old settings.
918         * None of the given Highlighters must be null.<p>
919         * 
920         * This is a bound property. <p> 
921         * 
922         * Note: as of version #1.257 the null constraint is enforced strictly. To remove
923         * all highlighters use this method without param.
924         * 
925         * @param highlighters zero or more not null highlighters to use for renderer decoration.
926         * @throws NullPointerException if array is null or array contains null values.
927         * 
928         * @see #getHighlighters()
929         * @see #addHighlighter(Highlighter)
930         * @see #removeHighlighter(Highlighter)
931         * 
932         */
933        public void setHighlighters(Highlighter... highlighters) {
934            Highlighter[] old = getHighlighters();
935            getCompoundHighlighter().setHighlighters(highlighters);
936            firePropertyChange("highlighters", old, getHighlighters());
937        }
938    
939        /**
940         * Returns the <code>Highlighter</code>s used by this table.
941         * Maybe empty, but guarantees to be never null.
942         * 
943         * @return the Highlighters used by this table, guaranteed to never null.
944         * @see #setHighlighters(Highlighter[])
945         */
946        public Highlighter[] getHighlighters() {
947            return getCompoundHighlighter().getHighlighters();
948        }
949        /**
950         * Appends a <code>Highlighter</code> to the end of the list of used
951         * <code>Highlighter</code>s. The argument must not be null. 
952         * <p>
953         * 
954         * @param highlighter the <code>Highlighter</code> to add, must not be null.
955         * @throws NullPointerException if <code>Highlighter</code> is null.
956         * 
957         * @see #removeHighlighter(Highlighter)
958         * @see #setHighlighters(Highlighter[])
959         */
960        public void addHighlighter(Highlighter highlighter) {
961            Highlighter[] old = getHighlighters();
962            getCompoundHighlighter().addHighlighter(highlighter);
963            firePropertyChange("highlighters", old, getHighlighters());
964        }
965    
966        /**
967         * Removes the given Highlighter. <p>
968         * 
969         * Does nothing if the Highlighter is not contained.
970         * 
971         * @param highlighter the Highlighter to remove.
972         * @see #addHighlighter(Highlighter)
973         * @see #setHighlighters(Highlighter...)
974         */
975        public void removeHighlighter(Highlighter highlighter) {
976            Highlighter[] old = getHighlighters();
977            getCompoundHighlighter().removeHighlighter(highlighter);
978            firePropertyChange("highlighters", old, getHighlighters());
979        }
980        
981        /**
982         * Returns the CompoundHighlighter assigned to the table, null if none.
983         * PENDING: open up for subclasses again?.
984         * 
985         * @return the CompoundHighlighter assigned to the table.
986         */
987        protected CompoundHighlighter getCompoundHighlighter() {
988            if (compoundHighlighter == null) {
989                compoundHighlighter = new CompoundHighlighter();
990                compoundHighlighter.addChangeListener(getHighlighterChangeListener());
991            }
992            return compoundHighlighter;
993        }
994    
995        /**
996         * Returns the <code>ChangeListener</code> to use with highlighters. Lazily 
997         * creates the listener.
998         * 
999         * @return the ChangeListener for observing changes of highlighters, 
1000         *   guaranteed to be <code>not-null</code>
1001         */
1002        protected ChangeListener getHighlighterChangeListener() {
1003            if (highlighterChangeListener == null) {
1004                highlighterChangeListener = createHighlighterChangeListener();
1005            }
1006            return highlighterChangeListener;
1007        }
1008    
1009        /**
1010         * Creates and returns the ChangeListener observing Highlighters.
1011         * <p>
1012         * Here: repaints the table on receiving a stateChanged.
1013         * 
1014         * @return the ChangeListener defining the reaction to changes of
1015         *         highlighters.
1016         */
1017        protected ChangeListener createHighlighterChangeListener() {
1018            return new ChangeListener() {
1019                public void stateChanged(ChangeEvent e) {
1020                    repaint();
1021                }
1022            };
1023        }
1024    
1025        
1026        /**
1027         * Returns the string representation of the cell value at the given position. 
1028         * 
1029         * @param row the row index of the cell in view coordinates
1030         * @return the string representation of the cell value as it will appear in the 
1031         *   table. 
1032         */
1033        public String getStringAt(int row) {
1034            ListCellRenderer renderer = getDelegatingRenderer().getDelegateRenderer();
1035            if (renderer instanceof StringValue) {
1036                return ((StringValue) renderer).getString(getElementAt(row));
1037            }
1038            return StringValues.TO_STRING.getString(getElementAt(row));
1039        }
1040    
1041        private DelegatingRenderer getDelegatingRenderer() {
1042            if (delegatingRenderer == null) {
1043                // only called once... to get hold of the default?
1044                delegatingRenderer = new DelegatingRenderer();
1045            }
1046            return delegatingRenderer;
1047        }
1048    
1049        /**
1050         * Creates and returns the default cell renderer to use. Subclasses
1051         * may override to use a different type. Here: returns a <code>DefaultListRenderer</code>.
1052         * 
1053         * @return the default cell renderer to use with this list.
1054         */
1055        protected ListCellRenderer createDefaultCellRenderer() {
1056            return new DefaultListRenderer();
1057        }
1058    
1059        /**
1060         * {@inheritDoc} <p>
1061         * 
1062         * Overridden to return the delegating renderer which is wrapped around the
1063         * original to support highlighting. The returned renderer is of type 
1064         * DelegatingRenderer and guaranteed to not-null<p>
1065         * 
1066         * @see #setCellRenderer(ListCellRenderer)
1067         * @see DelegatingRenderer
1068         */
1069        @Override
1070        public ListCellRenderer getCellRenderer() {
1071            return getDelegatingRenderer();
1072        }
1073    
1074        /**
1075         * Returns the renderer installed by client code or the default if none has
1076         * been set.
1077         * 
1078         * @return the wrapped renderer.
1079         * @see #setCellRenderer(ListCellRenderer)
1080         */
1081        public ListCellRenderer getWrappedCellRenderer() {
1082            return getDelegatingRenderer().getDelegateRenderer();
1083        }
1084        
1085        /**
1086         * {@inheritDoc} <p>
1087         * 
1088         * Overridden to wrap the given renderer in a DelegatingRenderer to support
1089         * highlighting. <p>
1090         * 
1091         * Note: the wrapping implies that the renderer returned from the getCellRenderer
1092         * is <b>not</b> the renderer as given here, but the wrapper. To access the original,
1093         * use <code>getWrappedCellRenderer</code>.
1094         * 
1095         * @see #getWrappedCellRenderer()
1096         * @see #getCellRenderer()
1097         * 
1098         */
1099        @Override
1100        public void setCellRenderer(ListCellRenderer renderer) {
1101            // JW: Pending - probably fires propertyChangeEvent with wrong newValue?
1102            // how about fixedCellWidths?
1103            // need to test!!
1104            getDelegatingRenderer().setDelegateRenderer(renderer);
1105            super.setCellRenderer(delegatingRenderer);
1106        }
1107    
1108        /**
1109         * A decorator for the original ListCellRenderer. Needed to hook highlighters
1110         * after messaging the delegate.<p>
1111         * 
1112         * PENDING JW: formally implement UIDependent?
1113         */
1114        public class DelegatingRenderer implements ListCellRenderer, RolloverRenderer {
1115            /** the delegate. */
1116            private ListCellRenderer delegateRenderer;
1117    
1118            /**
1119             * Instantiates a DelegatingRenderer with list's default renderer as delegate.
1120             */
1121            public DelegatingRenderer() {
1122                this(null);
1123            }
1124            
1125            /**
1126             * Instantiates a DelegatingRenderer with the given delegate. If the
1127             * delegate is null, the default is created via the list's factory method.
1128             * 
1129             * @param delegate the delegate to use, if null the list's default is
1130             *   created and used.
1131             */
1132            public DelegatingRenderer(ListCellRenderer delegate) {
1133                setDelegateRenderer(delegate);
1134            }
1135    
1136            /**
1137             * Sets the delegate. If the
1138             * delegate is null, the default is created via the list's factory method.
1139             * 
1140             * @param delegate the delegate to use, if null the list's default is
1141             *   created and used.
1142             */
1143            public void setDelegateRenderer(ListCellRenderer delegate) {
1144                if (delegate == null) {
1145                    delegate = createDefaultCellRenderer();
1146                }
1147                delegateRenderer = delegate;
1148            }
1149    
1150            /**
1151             * Returns the delegate.
1152             * 
1153             * @return the delegate renderer used by this renderer, guaranteed to
1154             *   not-null.
1155             */
1156            public ListCellRenderer getDelegateRenderer() {
1157                return delegateRenderer;
1158            }
1159    
1160            /**
1161             * Updates the ui of the delegate.
1162             */
1163             public void updateUI() {
1164                 updateRendererUI(delegateRenderer);
1165             }
1166    
1167             /**
1168              * 
1169              * @param renderer the renderer to update the ui of.
1170              */
1171             private void updateRendererUI(ListCellRenderer renderer) {
1172                 if (renderer == null) return;
1173                 Component comp = null;
1174                 if (renderer instanceof AbstractRenderer) {
1175                     comp = ((AbstractRenderer) renderer).getComponentProvider().getRendererComponent(null);
1176                 } else if (renderer instanceof Component) {
1177                     comp = (Component) renderer;
1178                 } else {
1179                     try {
1180                         comp = renderer.getListCellRendererComponent(
1181                                 JXList.this, null, -1, false, false);
1182                    } catch (Exception e) {
1183                        // nothing to do - renderer barked on off-range row
1184                    }
1185                 }
1186                 if (comp != null) {
1187                     SwingUtilities.updateComponentTreeUI(comp);
1188                 }
1189    
1190             }
1191             
1192             // --------- implement ListCellRenderer
1193            /**
1194             * {@inheritDoc} <p>
1195             * 
1196             * Overridden to apply the highlighters, if any, after calling the delegate.
1197             * The decorators are not applied if the row is invalid.
1198             */
1199           public Component getListCellRendererComponent(JList list, Object value,
1200                    int index, boolean isSelected, boolean cellHasFocus) {
1201                Component comp = delegateRenderer.getListCellRendererComponent(list, value, index,
1202                        isSelected, cellHasFocus);
1203                if ((compoundHighlighter != null) && (index >= 0) && (index < getElementCount())) {
1204                    comp = compoundHighlighter.highlight(comp, getComponentAdapter(index));
1205                }
1206                return comp;
1207            }
1208    
1209    
1210            // implement RolloverRenderer
1211            
1212            /**
1213             * {@inheritDoc}
1214             * 
1215             */
1216            public boolean isEnabled() {
1217                return (delegateRenderer instanceof RolloverRenderer) && 
1218                   ((RolloverRenderer) delegateRenderer).isEnabled();
1219            }
1220            
1221            /**
1222             * {@inheritDoc}
1223             */
1224            public void doClick() {
1225                if (isEnabled()) {
1226                    ((RolloverRenderer) delegateRenderer).doClick();
1227                }
1228            }
1229            
1230        }
1231    
1232        // --------------------------- updateUI
1233    
1234        
1235        /**
1236         * {@inheritDoc} <p>
1237         * 
1238         * Overridden to update renderer and Highlighters.
1239         */
1240        @Override
1241        public void updateUI() {
1242            // PENDING JW: temporary during dev to quickly switch between default and custom ui
1243            if (getUIClassID() == super.getUIClassID()) {
1244                super.updateUI();
1245            } else {    
1246                setUI((ListUI) LookAndFeelAddons.getUI(this, ListUI.class));
1247            }
1248            updateRendererUI();
1249            updateHighlighterUI();
1250        }
1251    
1252        @Override
1253        public String getUIClassID() {
1254            // PENDING JW: temporary during dev to quickly switch between default and custom ui
1255            return super.getUIClassID();
1256    //        return uiClassID;
1257        }
1258    
1259        private void updateRendererUI() {
1260            if (delegatingRenderer != null) {
1261                delegatingRenderer.updateUI();
1262            } else {
1263                ListCellRenderer renderer = getCellRenderer();
1264                if (renderer instanceof Component) {
1265                    SwingUtilities.updateComponentTreeUI((Component) renderer);
1266                }
1267            }
1268        }
1269    
1270        /**
1271         * Updates highlighter after <code>updateUI</code> changes.
1272         * 
1273         * @see org.jdesktop.swingx.decorator.UIDependent
1274         */
1275        protected void updateHighlighterUI() {
1276            if (compoundHighlighter == null) return;
1277            compoundHighlighter.updateUI();
1278        }
1279    
1280    }