001/*
002 * $Id: JXList.java 4158 2012-02-03 18:29:40Z kschaefe $
003 *
004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005 * Santa Clara, California 95054, U.S.A. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 * 
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015 * Lesser General Public License for more details.
016 * 
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020 */
021
022package org.jdesktop.swingx;
023
024import java.awt.Component;
025import java.awt.Rectangle;
026import java.awt.event.ActionEvent;
027import java.util.Arrays;
028import java.util.Comparator;
029import java.util.Vector;
030import java.util.logging.Logger;
031import java.util.regex.Pattern;
032
033import javax.swing.Action;
034import javax.swing.JComponent;
035import javax.swing.JList;
036import javax.swing.KeyStroke;
037import javax.swing.ListCellRenderer;
038import javax.swing.ListModel;
039import javax.swing.RowFilter;
040import javax.swing.RowSorter;
041import javax.swing.SortOrder;
042import javax.swing.SwingUtilities;
043import javax.swing.event.ChangeEvent;
044import javax.swing.event.ChangeListener;
045import javax.swing.plaf.ListUI;
046import javax.swing.text.Position.Bias;
047
048import org.jdesktop.beans.JavaBean;
049import org.jdesktop.swingx.decorator.ComponentAdapter;
050import org.jdesktop.swingx.decorator.CompoundHighlighter;
051import org.jdesktop.swingx.decorator.Highlighter;
052import org.jdesktop.swingx.plaf.LookAndFeelAddons;
053import org.jdesktop.swingx.plaf.UIAction;
054import org.jdesktop.swingx.plaf.XListAddon;
055import org.jdesktop.swingx.plaf.basic.core.BasicXListUI;
056import org.jdesktop.swingx.renderer.AbstractRenderer;
057import org.jdesktop.swingx.renderer.DefaultListRenderer;
058import org.jdesktop.swingx.renderer.StringValue;
059import org.jdesktop.swingx.rollover.ListRolloverController;
060import org.jdesktop.swingx.rollover.ListRolloverProducer;
061import org.jdesktop.swingx.rollover.RolloverProducer;
062import org.jdesktop.swingx.rollover.RolloverRenderer;
063import org.jdesktop.swingx.search.ListSearchable;
064import org.jdesktop.swingx.search.SearchFactory;
065import org.jdesktop.swingx.search.Searchable;
066import org.jdesktop.swingx.sort.DefaultSortController;
067import org.jdesktop.swingx.sort.ListSortController;
068import org.jdesktop.swingx.sort.SortController;
069import org.jdesktop.swingx.sort.StringValueRegistry;
070import org.jdesktop.swingx.table.TableColumnExt;
071
072/**
073 * Enhanced List component with support for general SwingX sorting/filtering,
074 * rendering, highlighting, rollover and search functionality. List specific
075 * enhancements include ?? PENDING JW ...
076 * 
077 * <h2>Sorting and Filtering</h2>
078 * JXList supports sorting and filtering. 
079 * 
080 * Changed to use core support. Usage is very similar to J/X/Table.
081 * It provides api to apply a specific sort order, to toggle the sort order and to reset a sort.
082 * Sort sequence can be configured by setting a custom comparator.
083 * 
084 * <pre><code>
085 * list.setAutoCreateRowSorter(true);
086 * list.setComparator(myComparator);
087 * list.setSortOrder(SortOrder.DESCENDING);
088 * list.toggleSortOder();
089 * list.resetSortOrder();
090 * </code></pre>
091 * 
092 * <p>
093 * JXList provides api to access items of the underlying model in view coordinates
094 * and to convert from/to model coordinates. 
095 * 
096 * <b>Note</b>: JXList needs a specific ui-delegate - BasicXListUI and subclasses - which
097 * is aware of model vs. view coordiate systems and which controls the synchronization of 
098 * selection/dataModel and sorter state. SwingX comes with a subclass for Synth. 
099 *  
100 * <h2>Rendering and Highlighting</h2>
101 * 
102 * As all SwingX collection views, a JXList is a HighlighterClient (PENDING JW:
103 * formally define and implement, like in AbstractTestHighlighter), that is it
104 * provides consistent api to add and remove Highlighters which can visually
105 * decorate the rendering component.
106 * <p>
107 * 
108 * <pre><code>
109 * 
110 * JXList list = new JXList(new Contributors());
111 * // implement a custom string representation, concated from first-, lastName
112 * StringValue sv = new StringValue() {
113 *     public String getString(Object value) {
114 *        if (value instanceof Contributor) {
115 *           Contributor contributor = (Contributor) value;
116 *           return contributor.lastName() + ", " + contributor.firstName(); 
117 *        }
118 *        return StringValues.TO_STRING(value);
119 *     }
120 * };
121 * list.setCellRenderer(new DefaultListRenderer(sv); 
122 * // highlight condition: gold merits
123 * HighlightPredicate predicate = new HighlightPredicate() {
124 *    public boolean isHighlighted(Component renderer,
125 *                     ComponentAdapter adapter) {
126 *       if (!(value instanceof Contributor)) return false;              
127 *       return ((Contributor) value).hasGold();
128 *    }
129 * };
130 * // highlight with foreground color 
131 * list.addHighlighter(new PainterHighlighter(predicate, goldStarPainter);      
132 * 
133 * </code></pre>
134 * 
135 * <i>Note:</i> to support the highlighting this implementation wraps the
136 * ListCellRenderer set by client code with a DelegatingRenderer which applies
137 * the Highlighter after delegating the default configuration to the wrappee. As
138 * a side-effect, getCellRenderer does return the wrapper instead of the custom
139 * renderer. To access the latter, client code must call getWrappedCellRenderer.
140 * <p>
141 * 
142 * <h2>Rollover</h2>
143 * 
144 * As all SwingX collection views, a JXList supports per-cell rollover. If
145 * enabled, the component fires rollover events on enter/exit of a cell which by
146 * default is promoted to the renderer if it implements RolloverRenderer, that
147 * is simulates live behaviour. The rollover events can be used by client code
148 * as well, f.i. to decorate the rollover row using a Highlighter.
149 * 
150 * <pre><code>
151 * 
152 * JXList list = new JXList();
153 * list.setRolloverEnabled(true);
154 * list.setCellRenderer(new DefaultListRenderer());
155 * list.addHighlighter(new ColorHighlighter(HighlightPredicate.ROLLOVER_ROW, 
156 *      null, Color.RED);      
157 * 
158 * </code></pre>
159 * 
160 * 
161 * <h2>Search</h2>
162 * 
163 * As all SwingX collection views, a JXList is searchable. A search action is
164 * registered in its ActionMap under the key "find". The default behaviour is to
165 * ask the SearchFactory to open a search component on this component. The
166 * default keybinding is retrieved from the SearchFactory, typically ctrl-f (or
167 * cmd-f for Mac). Client code can register custom actions and/or bindings as
168 * appropriate.
169 * <p>
170 * 
171 * JXList provides api to vend a renderer-controlled String representation of
172 * cell content. This allows the Searchable and Highlighters to use WYSIWYM
173 * (What-You-See-Is-What-You-Match), that is pattern matching against the actual
174 * string as seen by the user.
175 * 
176 * 
177 * @author Ramesh Gupta
178 * @author Jeanette Winzenburg
179 */
180@JavaBean
181public class JXList extends JList {
182    @SuppressWarnings("all")
183    private static final Logger LOG = Logger.getLogger(JXList.class.getName());
184    
185    /**
186     * UI Class ID
187     */
188    public final static String uiClassID = "XListUI";
189    
190    /**
191     * Registers a Addon for JXList.
192     */
193    static {
194        LookAndFeelAddons.contribute(new XListAddon());
195    }
196
197    
198
199    public static final String EXECUTE_BUTTON_ACTIONCOMMAND = "executeButtonAction";
200
201    /**
202     * The pipeline holding the highlighters.
203     */
204    protected CompoundHighlighter compoundHighlighter;
205
206    /** listening to changeEvents from compoundHighlighter. */
207    private ChangeListener highlighterChangeListener;
208
209    /** The ComponentAdapter for model data access. */
210    protected ComponentAdapter dataAdapter;
211
212    /**
213     * Mouse/Motion/Listener keeping track of mouse moved in cell coordinates.
214     */
215    private RolloverProducer rolloverProducer;
216
217    /**
218     * RolloverController: listens to cell over events and repaints
219     * entered/exited rows.
220     */
221    private ListRolloverController<JXList> linkController;
222
223    /** A wrapper around the default renderer enabling decoration. */
224    private transient DelegatingRenderer delegatingRenderer;
225
226    private Searchable searchable;
227
228    private Comparator<?> comparator;
229
230    private boolean autoCreateRowSorter;
231
232    private RowSorter<? extends ListModel> rowSorter;
233
234    private boolean sortable;
235
236    private boolean sortsOnUpdates;
237
238    private StringValueRegistry stringValueRegistry;
239
240    private SortOrder[] sortOrderCycle;
241
242    /**
243    * Constructs a <code>JXList</code> with an empty model and filters disabled.
244    *
245    */                                           
246    public JXList() {
247        this(false);
248    }
249
250    /**
251     * Constructs a <code>JXList</code> that displays the elements in the
252     * specified, non-<code>null</code> model and automatic creation of a RowSorter disabled.
253     *
254     * @param dataModel   the data model for this list
255     * @exception IllegalArgumentException   if <code>dataModel</code>
256     *                                           is <code>null</code>
257     */                                           
258    public JXList(ListModel dataModel) {
259        this(dataModel, false);
260    }
261
262    /**
263     * Constructs a <code>JXList</code> that displays the elements in
264     * the specified array and automatic creation of a RowSorter disabled.
265     *
266     * @param  listData  the array of Objects to be loaded into the data model
267     * @throws IllegalArgumentException   if <code>listData</code>
268     *                                          is <code>null</code>
269     */
270    public JXList(Object[] listData) {
271        this(listData, false);
272    }
273
274    /**
275     * Constructs a <code>JXList</code> that displays the elements in
276     * the specified <code>Vector</code> and automatic creation of a RowSorter disabled.
277     *
278     * @param  listData  the <code>Vector</code> to be loaded into the
279     *          data model
280     * @throws IllegalArgumentException   if <code>listData</code>
281     *                                          is <code>null</code>
282     */
283    public JXList(Vector<?> listData) {
284        this(listData, false);
285    }
286
287
288    /**
289     * Constructs a <code>JXList</code> with an empty model and
290     * automatic creation of a RowSorter as given.
291     * 
292     * @param autoCreateRowSorter <code>boolean</code> to determine if 
293     *  a RowSorter should be created automatically.
294     */
295    public JXList(boolean autoCreateRowSorter) {
296        init(autoCreateRowSorter);
297    }
298
299    /**
300     * Constructs a <code>JXList</code> with the specified model and
301     * automatic creation of a RowSorter as given.
302     * 
303     * @param dataModel   the data model for this list
304     * @param autoCreateRowSorter <code>boolean</code> to determine if 
305     *  a RowSorter should be created automatically.
306     * @throws IllegalArgumentException   if <code>dataModel</code>
307     *                                          is <code>null</code>
308     */
309    public JXList(ListModel dataModel, boolean autoCreateRowSorter) {
310        super(dataModel);
311        init(autoCreateRowSorter);
312    }
313
314    /**
315     * Constructs a <code>JXList</code> that displays the elements in
316     * the specified array and automatic creation of a RowSorter as given.
317     *
318     * @param  listData  the array of Objects to be loaded into the data model
319     * @param autoCreateRowSorter <code>boolean</code> to determine if 
320     *  a RowSorter should be created automatically.
321     * @throws IllegalArgumentException   if <code>listData</code>
322     *                                          is <code>null</code>
323     */
324    public JXList(Object[] listData, boolean autoCreateRowSorter) {
325        super(listData);
326        if (listData == null) 
327           throw new IllegalArgumentException("listData must not be null");
328        init(autoCreateRowSorter);
329    }
330
331    /**
332     * Constructs a <code>JXList</code> that displays the elements in
333     * the specified <code>Vector</code> and filtersEnabled property.
334     *
335     * @param  listData  the <code>Vector</code> to be loaded into the
336     *          data model
337     * @param autoCreateRowSorter <code>boolean</code> to determine if 
338     *  a RowSorter should be created automatically.
339     * @throws IllegalArgumentException if <code>listData</code> is <code>null</code>
340     */
341    public JXList(Vector<?> listData, boolean autoCreateRowSorter) {
342        super(listData);
343        if (listData == null) 
344           throw new IllegalArgumentException("listData must not be null");
345        init(autoCreateRowSorter);
346    }
347
348
349    private void init(boolean autoCreateRowSorter) {
350        sortOrderCycle = DefaultSortController.getDefaultSortOrderCycle();
351        setSortable(true);
352        setSortsOnUpdates(true);
353        setAutoCreateRowSorter(autoCreateRowSorter);
354        Action findAction = createFindAction();
355        getActionMap().put("find", findAction);
356        
357        KeyStroke findStroke = SearchFactory.getInstance().getSearchAccelerator();
358        getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(findStroke, "find");
359    }
360
361    private Action createFindAction() {
362        return new UIAction("find") {
363            @Override
364            public void actionPerformed(ActionEvent e) {
365                doFind();
366            }
367        };
368    }
369
370    /** 
371     * Starts a search on this List's visible items. This implementation asks the
372     * SearchFactory to open a find widget on itself.
373     */
374    protected void doFind() {
375        SearchFactory.getInstance().showFindInput(this, getSearchable());
376    }
377
378    /**
379     * Returns a Searchable for this component, guaranteed to be not null. This 
380     * implementation lazily creates a ListSearchable if necessary.
381     *  
382     * @return a not-null Searchable for this list.
383     * 
384     * @see #setSearchable(Searchable)
385     * @see org.jdesktop.swingx.search.ListSearchable
386     */
387    public Searchable getSearchable() {
388        if (searchable == null) {
389            searchable = new ListSearchable(this);
390        }
391        return searchable;
392    }
393
394    /**
395     * Sets the Searchable for this component. If null, a default 
396     * Searchable will be created and used.
397     * 
398     * @param searchable the Searchable to use for this component, may be null to indicate
399     *   using the list's default searchable.
400     * @see #getSearchable()
401     */
402    public void setSearchable(Searchable searchable) {
403        this.searchable = searchable;
404    }
405    
406    
407    /**
408     * {@inheritDoc} <p>
409     * 
410     * Overridden to cope with sorting/filtering, taking over completely.
411     */
412    @Override
413    public int getNextMatch(String prefix, int startIndex, Bias bias) {
414        Pattern pattern = Pattern.compile("^" + prefix, Pattern.CASE_INSENSITIVE);
415        return getSearchable().search(pattern, startIndex, bias ==Bias.Backward);
416    }
417//--------------------- Rollover support
418
419    /**
420     * Sets the property to enable/disable rollover support. If enabled, the list
421     * fires property changes on per-cell mouse rollover state, i.e. 
422     * when the mouse enters/leaves a list cell. <p>
423     * 
424     * This can be enabled to show "live" rollover behaviour, f.i. the cursor over a cell 
425     * rendered by a JXHyperlink.<p>
426     * 
427     * Default value is disabled.
428     * 
429     * @param rolloverEnabled a boolean indicating whether or not the rollover
430     *   functionality should be enabled.
431     * 
432     * @see #isRolloverEnabled()
433     * @see #getLinkController()
434     * @see #createRolloverProducer()
435     * @see org.jdesktop.swingx.rollover.RolloverRenderer  
436     *    
437     */
438    public void setRolloverEnabled(boolean rolloverEnabled) {
439        boolean old = isRolloverEnabled();
440        if (rolloverEnabled == old)
441            return;
442        if (rolloverEnabled) {
443            rolloverProducer = createRolloverProducer();
444            rolloverProducer.install(this);
445            getLinkController().install(this);
446        } else {
447            rolloverProducer.release(this);
448            rolloverProducer = null;
449            getLinkController().release();
450        }
451        firePropertyChange("rolloverEnabled", old, isRolloverEnabled());
452    }
453
454    /**
455     * Returns a boolean indicating whether or not rollover support is enabled. 
456     *
457     * @return a boolean indicating whether or not rollover support is enabled. 
458     * 
459     * @see #setRolloverEnabled(boolean)
460     */
461    public boolean isRolloverEnabled() {
462        return rolloverProducer != null;
463    }
464    
465    /**
466     * Returns the RolloverController for this component. Lazyly creates the 
467     * controller if necessary, that is the return value is guaranteed to be 
468     * not null. <p>
469     * 
470     * PENDING JW: rename to getRolloverController
471     * 
472     * @return the RolloverController for this tree, guaranteed to be not null.
473     * 
474     * @see #setRolloverEnabled(boolean)
475     * @see #createLinkController()
476     * @see org.jdesktop.swingx.rollover.RolloverController
477     */
478    protected ListRolloverController<JXList> getLinkController() {
479        if (linkController == null) {
480            linkController = createLinkController();
481        }
482        return linkController;
483    }
484
485    /**
486     * Creates and returns a RolloverController appropriate for this component.
487     * 
488     * @return a RolloverController appropriate for this component.
489     * 
490     * @see #getLinkController()
491     * @see org.jdesktop.swingx.rollover.RolloverController
492     */
493    protected ListRolloverController<JXList> createLinkController() {
494        return new ListRolloverController<JXList>();
495    }
496
497
498    /**
499     * Creates and returns the RolloverProducer to use with this tree.
500     * <p>
501     * 
502     * @return <code>RolloverProducer</code> to use with this tree
503     * 
504     * @see #setRolloverEnabled(boolean)
505     */
506    protected RolloverProducer createRolloverProducer() {
507        return new ListRolloverProducer();
508    }
509
510    //--------------------- public sort api
511    
512    /**
513     * Returns {@code true} if whenever the model changes, a new
514     * {@code RowSorter} should be created and installed
515     * as the table's sorter; otherwise, returns {@code false}. 
516     *
517     * @return true if a {@code RowSorter} should be created when
518     *         the model changes
519     * @since 1.6
520     */
521    public boolean getAutoCreateRowSorter() {
522        return autoCreateRowSorter;
523    }
524
525    /**
526     * Specifies whether a {@code RowSorter} should be created for the
527     * list whenever its model changes.
528     * <p>
529     * When {@code setAutoCreateRowSorter(true)} is invoked, a {@code
530     * RowSorter} is immediately created and installed on the
531     * list.  While the {@code autoCreateRowSorter} property remains
532     * {@code true}, every time the model is changed, a new {@code
533     * RowSorter} is created and set as the list's row sorter.<p>
534     * 
535     * The default value is false.
536     *
537     * @param autoCreateRowSorter whether or not a {@code RowSorter}
538     *        should be automatically created
539     * @beaninfo
540     *        bound: true
541     *    preferred: true
542     *  description: Whether or not to turn on sorting by default.
543     */
544    public void setAutoCreateRowSorter(boolean autoCreateRowSorter) {
545        if (getAutoCreateRowSorter() == autoCreateRowSorter) return;
546        boolean oldValue = getAutoCreateRowSorter();
547        this.autoCreateRowSorter = autoCreateRowSorter;
548        if (autoCreateRowSorter) {
549            setRowSorter(createDefaultRowSorter());
550        }
551        firePropertyChange("autoCreateRowSorter", oldValue,
552                           getAutoCreateRowSorter());
553    }
554
555    /**
556     * Creates and returns the default RowSorter. Note that this is already
557     * configured to the current ListModel.
558     * 
559     * PENDING JW: review method signature - better expose the need for the
560     * model by adding a parameter? 
561     * 
562     * @return the default RowSorter.
563     */
564    protected RowSorter<? extends ListModel> createDefaultRowSorter() {
565        return new ListSortController<ListModel>(getModel());
566    }
567    /**
568     * Returns the object responsible for sorting.
569     *
570     * @return the object responsible for sorting
571     * @since 1.6
572     */
573    public RowSorter<? extends ListModel> getRowSorter() {
574        return rowSorter;
575    }
576
577    /**
578     * Sets the <code>RowSorter</code>.  <code>RowSorter</code> is used
579     * to provide sorting and filtering to a <code>JXList</code>.
580     * <p>
581     * This method clears the selection and resets any variable row heights.
582     * <p>
583     * If the underlying model of the <code>RowSorter</code> differs from
584     * that of this <code>JXList</code> undefined behavior will result.
585     *
586     * @param sorter the <code>RowSorter</code>; <code>null</code> turns
587     *        sorting off
588     */
589    public void setRowSorter(RowSorter<? extends ListModel> sorter) {
590        RowSorter<? extends ListModel> oldRowSorter = getRowSorter();
591        this.rowSorter = sorter;
592        configureSorterProperties();
593        firePropertyChange("rowSorter", oldRowSorter, sorter);
594    }
595
596    /**
597     * Propagates sort-related properties from table/columns to the sorter if it
598     * is of type SortController, does nothing otherwise.
599     * 
600     */
601    protected void configureSorterProperties() {
602        if (!getControlsSorterProperties()) return;
603        // configure from table properties
604        getSortController().setSortable(sortable);
605        getSortController().setSortsOnUpdates(sortsOnUpdates);
606        getSortController().setComparator(0, comparator);
607        getSortController().setSortOrderCycle(getSortOrderCycle());
608        getSortController().setStringValueProvider(getStringValueRegistry());
609    }
610
611    /**
612     * Sets &quot;sortable&quot; property indicating whether or not this list
613     * isSortable. 
614     * 
615     * <b>Note</b>: as of post-1.0 this property is propagated to the SortController. 
616     * Whether or not a change triggers a re-sort is up to either the concrete controller 
617     * implementation (the default doesn't) or client code. This behaviour is
618     * different from old SwingX style sorting.
619     * 
620     * @see TableColumnExt#isSortable()
621     * @param sortable boolean indicating whether or not this table supports
622     *        sortable columns
623     */
624    public void setSortable(boolean sortable) {
625        boolean old = isSortable();
626        this.sortable = sortable;
627        if (getControlsSorterProperties()) {
628            getSortController().setSortable(sortable);
629        }
630        firePropertyChange("sortable", old, isSortable());
631    }
632
633    /**
634     * Returns the table's sortable property.<p>
635     * 
636     * @return true if the table is sortable.
637     */
638    public boolean isSortable() {
639        return sortable;
640    }
641
642    /**
643     * If true, specifies that a sort should happen when the underlying
644     * model is updated (<code>rowsUpdated</code> is invoked).  For
645     * example, if this is true and the user edits an entry the
646     * location of that item in the view may change.  The default is
647     * true.
648     *
649     * @param sortsOnUpdates whether or not to sort on update events
650     */
651    public void setSortsOnUpdates(boolean sortsOnUpdates) {
652        boolean old = getSortsOnUpdates();
653        this.sortsOnUpdates = sortsOnUpdates;
654        if (getControlsSorterProperties()) {
655            getSortController().setSortsOnUpdates(sortsOnUpdates);
656        }
657        firePropertyChange("sortsOnUpdates", old, getSortsOnUpdates());
658    }
659    
660    /**
661     * Returns true if  a sort should happen when the underlying
662     * model is updated; otherwise, returns false.
663     *
664     * @return whether or not to sort when the model is updated
665     */
666    public boolean getSortsOnUpdates() {
667        return sortsOnUpdates;
668    }
669
670    /**
671     * Sets the sortorder cycle used when toggle sorting this table's columns. 
672     * This property is propagated to the SortController
673     * if controlsSorterProperties is true. 
674     * 
675     * @param cycle the sequence of zero or more not-null SortOrders to cycle through.
676     * @throws NullPointerException if the array or any of its elements are null
677     * 
678     */
679    public void setSortOrderCycle(SortOrder... cycle) {
680        SortOrder[] old = getSortOrderCycle();
681        if (getControlsSorterProperties()) {
682            getSortController().setSortOrderCycle(cycle);
683        }
684        this.sortOrderCycle = Arrays.copyOf(cycle, cycle.length);
685        firePropertyChange("sortOrderCycle", old, getSortOrderCycle());
686    }
687    
688    /**
689     * Returns the sortOrder cycle used when toggle sorting this table's columns, guaranteed
690     * to be not null.
691     *   
692     * @return the sort order cycle used in toggle sort, not null 
693     */
694    public SortOrder[] getSortOrderCycle() {
695        return Arrays.copyOf(sortOrderCycle, sortOrderCycle.length);
696    }
697
698    /**
699     * 
700     * @return the comparator used.
701     * @see #setComparator(Comparator)
702     */
703    public Comparator<?> getComparator() {
704        return comparator;
705    }
706    
707    /**
708     * Sets the comparator to use for sorting.<p>
709     *  
710     * <b>Note</b>: as of post-1.0 the property is propagated to the SortController,
711     * if available.
712     * Whether or not a change triggers a re-sort is up to either the concrete controller 
713     * implementation (the default doesn't) or client code. This behaviour is
714     * different from old SwingX style sorting.
715     * 
716     * @param comparator the comparator to use.
717     */
718    public void setComparator(Comparator<?> comparator) {
719        Comparator<?> old = getComparator();
720        this.comparator = comparator;
721        updateSortAfterComparatorChange();
722        firePropertyChange("comparator", old, getComparator());
723    }
724    
725    /**
726     * Updates the SortController's comparator, if available. Does nothing otherwise. 
727     *
728     */
729    protected void updateSortAfterComparatorChange() {
730        if (getControlsSorterProperties()) {
731            getSortController().setComparator(0, getComparator());
732        }
733    }
734
735//------------------------- sort: do sort/filter
736    
737    /**
738     * Sets the filter to the sorter, if available and of type SortController.
739     * Does nothing otherwise.
740     * <p>
741     *
742     * @param filter the filter used to determine what entries should be
743     *        included
744     */
745    @SuppressWarnings("unchecked")
746    public <R extends ListModel> void setRowFilter(RowFilter<? super R, ? super Integer> filter) {
747        if (hasSortController()) {
748            // all fine, because R is a ListModel (R extends ListModel)
749            SortController<R> controller = (SortController<R>) getSortController();
750            controller.setRowFilter(filter);
751        }
752    }
753    
754    /**
755     * Returns the filter of the sorter, if available and of type SortController.
756     * Returns null otherwise.<p>
757     * 
758     * PENDING JW: generics? had to remove return type from getSortController to 
759     * make this compilable, so probably wrong. 
760     * 
761     * @return the filter used in the sorter.
762     */
763    @SuppressWarnings("unchecked")
764    public RowFilter<?, ?> getRowFilter() {
765        return hasSortController() ? getSortController().getRowFilter() : null;
766    }
767    
768    /**
769     * Resets sorting of all columns.
770     * Delegates to the SortController if available, or does nothing if not.<p>
771     * 
772     * PENDING JW: method name - consistent in SortController and here.
773     * 
774     */
775    public void resetSortOrder() {
776        if (hasSortController()) 
777            getSortController().resetSortOrders();
778    }
779
780    /**
781     * 
782     * Toggles the sort order of the list.
783     * Delegates to the SortController if available, or does nothing if not.<p>
784     * 
785     * <p>
786     * The exact behaviour is defined by the SortController's toggleSortOrder
787     * implementation. Typically a unsorted list is sorted in ascending order,
788     * a sorted list's order is reversed.
789     * <p>
790     * 
791     * 
792     */
793    public void toggleSortOrder() {
794        if (hasSortController())
795            getSortController().toggleSortOrder(0);
796    }
797
798    /**
799     * Sorts the list using SortOrder.
800     * Delegates to the SortController if available, or does nothing if not.<p>
801     * 
802     * @param sortOrder the sort order to use.
803     * 
804     */
805    public void setSortOrder(SortOrder sortOrder) {
806        if (hasSortController())
807            getSortController().setSortOrder(0, sortOrder);
808    }
809
810
811    /**
812     * Returns the SortOrder. 
813     * Delegates to the SortController if available, or returns SortOrder.UNSORTED if not.<p>
814     * 
815     * @return the current SortOrder
816     */
817    public SortOrder getSortOrder() {
818        if (hasSortController())
819            return getSortController().getSortOrder(0);
820        return SortOrder.UNSORTED;
821    }
822
823
824    /**
825     * Returns the currently active SortController. May be null if RowSorter
826     * is null or not of type SortController.<p>
827     * 
828     * PENDING JW: swaying about hiding or not - currently the only way to
829     * make the view not configure a RowSorter of type SortController is to 
830     * let this return null. 
831     * 
832     * @return the currently active <code>SortController</code> may be null
833     */
834    @SuppressWarnings("unchecked")
835    protected SortController<? extends ListModel> getSortController() {
836        if (hasSortController()) {
837            // JW: the RowSorter is always of type <? extends ListModel>
838            // so the unchecked cast is safe
839            return (SortController<? extends ListModel>) getRowSorter();
840        }
841        return null;
842    }
843
844    /**
845     * Returns a boolean indicating whether the table has a SortController.
846     * If true, the call to getSortController is guaranteed to return a not-null
847     * value.
848     * 
849     * @return a boolean indicating whether the table has a SortController.
850     * 
851     * @see #getSortController()
852     */
853    protected boolean hasSortController() {
854        return getRowSorter() instanceof SortController<?>;
855    }
856    
857    /**
858     * Returns a boolean indicating whether the table configures the sorter's
859     * properties. If true, guaranteed that table's and the columns' sort related 
860     * properties are propagated to the sorter. If false, guaranteed to not
861     * touch the sorter's configuration.<p>
862     * 
863     * This implementation returns true if the sorter is of type SortController.
864     * 
865     * Note: the synchronization is unidirection from the table to the sorter. 
866     * Changing the sorter under the table's feet might lead to undefined
867     * behaviour.
868     * 
869     * @return a boolean indicating whether the table configurers the sorter's
870     *  properties.
871     */
872    protected boolean getControlsSorterProperties() {
873        return hasSortController() && getAutoCreateRowSorter();
874    }
875    
876    // ---------------------------- filters
877
878    /**
879     * Returns the element at the given index. The index is in view coordinates
880     * which might differ from model coordinates if filtering is enabled and
881     * filters/sorters are active.
882     * 
883     * @param viewIndex the index in view coordinates
884     * @return the element at the index
885     * @throws IndexOutOfBoundsException if viewIndex < 0 or viewIndex >=
886     *         getElementCount()
887     */
888    public Object getElementAt(int viewIndex) {
889        return getModel().getElementAt(convertIndexToModel(viewIndex));
890    }
891
892    /**
893     * Returns the value for the smallest selected cell index;
894     * <i>the selected value</i> when only a single item is selected in the
895     * list. When multiple items are selected, it is simply the value for the
896     * smallest selected index. Returns {@code null} if there is no selection.
897     * <p>
898     * This is a convenience method that simply returns the model value for
899     * {@code getMinSelectionIndex}, taking into account sorting and filtering.
900     *
901     * @return the first selected value
902     * @see #getMinSelectionIndex
903     * @see #getModel
904     * @see #addListSelectionListener
905     */
906    @Override
907    public Object getSelectedValue() {
908        int i = getSelectedIndex();
909        return (i == -1) ? null : getElementAt(i);
910    }
911
912    /**
913     * Selects the specified object from the list, taking into account
914     * sorting and filtering.
915     *
916     * @param anObject      the object to select
917     * @param shouldScroll  {@code true} if the list should scroll to display
918     *                      the selected object, if one exists; otherwise {@code false}
919     */
920    @Override
921    public void setSelectedValue(Object anObject,boolean shouldScroll) {
922        // Note: this method is a copy of JList.setSelectedValue,
923        // including comments. It simply usues getElementCount() and getElementAt()
924        // instead of the model.
925        if(anObject == null)
926            setSelectedIndex(-1);
927        else if(!anObject.equals(getSelectedValue())) {
928            int i,c;
929            for(i=0,c=getElementCount();i<c;i++)
930                if(anObject.equals(getElementAt(i))){
931                    setSelectedIndex(i);
932                    if(shouldScroll)
933                        ensureIndexIsVisible(i);
934                    repaint();  /** FIX-ME setSelectedIndex does not redraw all the time with the basic l&f**/
935                    return;
936                }
937            setSelectedIndex(-1);
938        }
939        repaint(); /** FIX-ME setSelectedIndex does not redraw all the time with the basic l&f**/
940    }
941
942    /**
943     * Returns an array of all the selected values, in increasing order based
944     * on their indices in the list and taking into account sourting and filtering.
945     *
946     * @return the selected values, or an empty array if nothing is selected
947     * @see #isSelectedIndex
948     * @see #getModel
949     * @see #addListSelectionListener
950     */
951    @Override
952    public Object[] getSelectedValues() {
953        int[] selectedIndexes = getSelectedIndices();
954        Object[] selectedValues = new Object[selectedIndexes.length];
955        for (int i = 0; i < selectedIndexes.length; i++) {
956            selectedValues[i] = getElementAt(selectedIndexes[i]);
957        }
958        return selectedValues;
959    }
960
961    /**     * Returns the number of elements in this list in view 
962     * coordinates. If filters are active this number might be
963     * less than the number of elements in the underlying model.
964     * 
965     * @return number of elements in this list in view coordinates
966     */
967    public int getElementCount() {
968        return getRowSorter() != null ? 
969                getRowSorter().getViewRowCount(): getModel().getSize();
970    }
971
972    /**
973     * Convert row index from view coordinates to model coordinates accounting
974     * for the presence of sorters and filters.
975     * 
976     * @param viewIndex index in view coordinates
977     * @return index in model coordinates
978     * @throws IndexOutOfBoundsException if viewIndex < 0 or viewIndex >= getElementCount() 
979     */
980    public int convertIndexToModel(int viewIndex) {
981        return getRowSorter() != null ? 
982                getRowSorter().convertRowIndexToModel(viewIndex):viewIndex;
983    }
984
985    /**
986     * Convert index from model coordinates to view coordinates accounting
987     * for the presence of sorters and filters.
988     * 
989     * @param modelIndex index in model coordinates
990     * @return index in view coordinates if the model index maps to a view coordinate
991     *          or -1 if not contained in the view.
992     * 
993     */
994    public int convertIndexToView(int modelIndex) {
995        return getRowSorter() != null 
996            ? getRowSorter().convertRowIndexToView(modelIndex) : modelIndex;
997    }
998
999    /**
1000     * {@inheritDoc} <p>
1001     * 
1002     * Sets the underlying data model. Note that if isFilterEnabled you must
1003     * call getWrappedModel to access the model given here. In this case
1004     * getModel returns a wrapper around the data!
1005     * 
1006     * @param model the data model for this list.
1007     * 
1008     */
1009    @Override
1010    public void setModel(ListModel model) {
1011        super.setModel(model);
1012        if (getAutoCreateRowSorter()) {
1013            setRowSorter(createDefaultRowSorter());
1014        }
1015    }
1016
1017
1018    // ---------------------------- uniform data model
1019
1020    /**
1021     * @return the unconfigured ComponentAdapter.
1022     */
1023    protected ComponentAdapter getComponentAdapter() {
1024        if (dataAdapter == null) {
1025            dataAdapter = new ListAdapter(this);
1026        }
1027        return dataAdapter;
1028    }
1029
1030    /**
1031     * Convenience to access a configured ComponentAdapter.
1032     * Note: the column index of the configured adapter is always 0.
1033     * 
1034     * @param index the row index in view coordinates, must be valid.
1035     * @return the configured ComponentAdapter.
1036     */
1037    protected ComponentAdapter getComponentAdapter(int index) {
1038        ComponentAdapter adapter = getComponentAdapter();
1039        adapter.column = 0;
1040        adapter.row = index;
1041        return adapter;
1042    }
1043    
1044    /**
1045     * A component adapter targeted at a JXList.
1046     */
1047    protected static class ListAdapter extends ComponentAdapter {
1048        private final JXList list;
1049
1050        /**
1051         * Constructs a <code>ListAdapter</code> for the specified target
1052         * JXList.
1053         * 
1054         * @param component  the target list.
1055         */
1056        public ListAdapter(JXList component) {
1057            super(component);
1058            list = component;
1059        }
1060
1061        /**
1062         * Typesafe accessor for the target component.
1063         * 
1064         * @return the target component as a {@link org.jdesktop.swingx.JXList}
1065         */
1066        public JXList getList() {
1067            return list;
1068        }
1069
1070        /**
1071         * {@inheritDoc}
1072         */
1073        @Override
1074        public boolean hasFocus() {
1075            /** TODO: Think through printing implications */
1076            return list.isFocusOwner() && (row == list.getLeadSelectionIndex());
1077        }
1078
1079        /**
1080         * {@inheritDoc}
1081         */
1082        @Override
1083        public int getRowCount() {
1084            return list.getModel().getSize();
1085        }
1086
1087        /**
1088         * {@inheritDoc}
1089         */
1090        @Override
1091        public Object getValueAt(int row, int column) {
1092            return list.getModel().getElementAt(row);
1093        }
1094
1095        /**
1096         * {@inheritDoc}
1097         * This is implemented to query the table's StringValueRegistry for an appropriate
1098         * StringValue and use that for getting the string representation.
1099         */
1100        @Override
1101        public String getStringAt(int row, int column) {
1102            StringValue sv = list.getStringValueRegistry().getStringValue(row, column);
1103            return sv.getString(getValueAt(row, column));
1104        }
1105
1106        /**
1107         * {@inheritDoc}
1108         */
1109        @Override
1110        public Rectangle getCellBounds() {
1111            return list.getCellBounds(row, row);
1112        }
1113        
1114        /**
1115         * {@inheritDoc}
1116         */
1117        @Override
1118        public boolean isCellEditable(int row, int column) {
1119            return false;
1120        }
1121
1122        /**
1123         * {@inheritDoc}
1124         */
1125        @Override
1126        public boolean isEditable() {
1127            return false;
1128        }
1129        
1130        /**
1131         * {@inheritDoc}
1132         */
1133        @Override
1134        public boolean isSelected() {
1135            /** TODO: Think through printing implications */
1136            return list.isSelectedIndex(row);
1137        }
1138        
1139        /**
1140         * {@inheritDoc}
1141         */
1142        @Override
1143        public int convertRowIndexToView(int rowModelIndex) {
1144            return list.convertIndexToView(rowModelIndex);
1145        }
1146        
1147        /**
1148         * {@inheritDoc}
1149         */
1150        @Override
1151        public int convertRowIndexToModel(int rowViewIndex) {
1152            return list.convertIndexToModel(rowViewIndex);
1153        }
1154    }
1155
1156    // ------------------------------ renderers
1157
1158
1159    
1160    /**
1161     * Sets the <code>Highlighter</code>s to the table, replacing any old settings.
1162     * None of the given Highlighters must be null.<p>
1163     * 
1164     * This is a bound property. <p> 
1165     * 
1166     * Note: as of version #1.257 the null constraint is enforced strictly. To remove
1167     * all highlighters use this method without param.
1168     * 
1169     * @param highlighters zero or more not null highlighters to use for renderer decoration.
1170     * @throws NullPointerException if array is null or array contains null values.
1171     * 
1172     * @see #getHighlighters()
1173     * @see #addHighlighter(Highlighter)
1174     * @see #removeHighlighter(Highlighter)
1175     * 
1176     */
1177    public void setHighlighters(Highlighter... highlighters) {
1178        Highlighter[] old = getHighlighters();
1179        getCompoundHighlighter().setHighlighters(highlighters);
1180        firePropertyChange("highlighters", old, getHighlighters());
1181    }
1182
1183    /**
1184     * Returns the <code>Highlighter</code>s used by this table.
1185     * Maybe empty, but guarantees to be never null.
1186     * 
1187     * @return the Highlighters used by this table, guaranteed to never null.
1188     * @see #setHighlighters(Highlighter[])
1189     */
1190    public Highlighter[] getHighlighters() {
1191        return getCompoundHighlighter().getHighlighters();
1192    }
1193    /**
1194     * Appends a <code>Highlighter</code> to the end of the list of used
1195     * <code>Highlighter</code>s. The argument must not be null. 
1196     * <p>
1197     * 
1198     * @param highlighter the <code>Highlighter</code> to add, must not be null.
1199     * @throws NullPointerException if <code>Highlighter</code> is null.
1200     * 
1201     * @see #removeHighlighter(Highlighter)
1202     * @see #setHighlighters(Highlighter[])
1203     */
1204    public void addHighlighter(Highlighter highlighter) {
1205        Highlighter[] old = getHighlighters();
1206        getCompoundHighlighter().addHighlighter(highlighter);
1207        firePropertyChange("highlighters", old, getHighlighters());
1208    }
1209
1210    /**
1211     * Removes the given Highlighter. <p>
1212     * 
1213     * Does nothing if the Highlighter is not contained.
1214     * 
1215     * @param highlighter the Highlighter to remove.
1216     * @see #addHighlighter(Highlighter)
1217     * @see #setHighlighters(Highlighter...)
1218     */
1219    public void removeHighlighter(Highlighter highlighter) {
1220        Highlighter[] old = getHighlighters();
1221        getCompoundHighlighter().removeHighlighter(highlighter);
1222        firePropertyChange("highlighters", old, getHighlighters());
1223    }
1224    
1225    /**
1226     * Returns the CompoundHighlighter assigned to the table, null if none.
1227     * PENDING: open up for subclasses again?.
1228     * 
1229     * @return the CompoundHighlighter assigned to the table.
1230     */
1231    protected CompoundHighlighter getCompoundHighlighter() {
1232        if (compoundHighlighter == null) {
1233            compoundHighlighter = new CompoundHighlighter();
1234            compoundHighlighter.addChangeListener(getHighlighterChangeListener());
1235        }
1236        return compoundHighlighter;
1237    }
1238
1239    /**
1240     * Returns the <code>ChangeListener</code> to use with highlighters. Lazily 
1241     * creates the listener.
1242     * 
1243     * @return the ChangeListener for observing changes of highlighters, 
1244     *   guaranteed to be <code>not-null</code>
1245     */
1246    protected ChangeListener getHighlighterChangeListener() {
1247        if (highlighterChangeListener == null) {
1248            highlighterChangeListener = createHighlighterChangeListener();
1249        }
1250        return highlighterChangeListener;
1251    }
1252
1253    /**
1254     * Creates and returns the ChangeListener observing Highlighters.
1255     * <p>
1256     * Here: repaints the table on receiving a stateChanged.
1257     * 
1258     * @return the ChangeListener defining the reaction to changes of
1259     *         highlighters.
1260     */
1261    protected ChangeListener createHighlighterChangeListener() {
1262        return new ChangeListener() {
1263            @Override
1264            public void stateChanged(ChangeEvent e) {
1265                repaint();
1266            }
1267        };
1268    }
1269
1270    /**
1271     * Returns the StringValueRegistry which defines the string representation for
1272     * each cells. This is strictly for internal use by the table, which has the 
1273     * responsibility to keep in synch with registered renderers.<p>
1274     * 
1275     * Currently exposed for testing reasons, client code is recommended to not use nor override.
1276     * 
1277     * @return the current string value registry
1278     */
1279    protected StringValueRegistry getStringValueRegistry() {
1280        if (stringValueRegistry == null) {
1281            stringValueRegistry = createDefaultStringValueRegistry();
1282        }
1283        return stringValueRegistry;
1284    }
1285
1286    /**
1287     * Creates and returns the default registry for StringValues.<p>
1288     * 
1289     * @return the default registry for StringValues.
1290     */
1291    protected StringValueRegistry createDefaultStringValueRegistry() {
1292        return new StringValueRegistry();
1293    }
1294    
1295    
1296    
1297    /**
1298     * Returns the string representation of the cell value at the given position. 
1299     * 
1300     * @param row the row index of the cell in view coordinates
1301     * @return the string representation of the cell value as it will appear in the 
1302     *   table. 
1303     */
1304    public String getStringAt(int row) {
1305        // changed implementation to use StringValueRegistry
1306        StringValue stringValue = getStringValueRegistry().getStringValue(
1307                convertIndexToModel(row), 0);
1308        return stringValue.getString(getElementAt(row));
1309    }
1310
1311    private DelegatingRenderer getDelegatingRenderer() {
1312        if (delegatingRenderer == null) {
1313            // only called once... to get hold of the default?
1314            delegatingRenderer = new DelegatingRenderer();
1315        }
1316        return delegatingRenderer;
1317    }
1318
1319    /**
1320     * Creates and returns the default cell renderer to use. Subclasses
1321     * may override to use a different type. Here: returns a <code>DefaultListRenderer</code>.
1322     * 
1323     * @return the default cell renderer to use with this list.
1324     */
1325    protected ListCellRenderer createDefaultCellRenderer() {
1326        return new DefaultListRenderer();
1327    }
1328
1329    /**
1330     * {@inheritDoc} <p>
1331     * 
1332     * Overridden to return the delegating renderer which is wrapped around the
1333     * original to support highlighting. The returned renderer is of type 
1334     * DelegatingRenderer and guaranteed to not-null<p>
1335     * 
1336     * @see #setCellRenderer(ListCellRenderer)
1337     * @see DelegatingRenderer
1338     */
1339    @Override
1340    public ListCellRenderer getCellRenderer() {
1341        return getDelegatingRenderer();
1342    }
1343
1344    /**
1345     * Returns the renderer installed by client code or the default if none has
1346     * been set.
1347     * 
1348     * @return the wrapped renderer.
1349     * @see #setCellRenderer(ListCellRenderer)
1350     */
1351    public ListCellRenderer getWrappedCellRenderer() {
1352        return getDelegatingRenderer().getDelegateRenderer();
1353    }
1354    
1355    /**
1356     * {@inheritDoc} <p>
1357     * 
1358     * Overridden to wrap the given renderer in a DelegatingRenderer to support
1359     * highlighting. <p>
1360     * 
1361     * Note: the wrapping implies that the renderer returned from the getCellRenderer
1362     * is <b>not</b> the renderer as given here, but the wrapper. To access the original,
1363     * use <code>getWrappedCellRenderer</code>.
1364     * 
1365     * @see #getWrappedCellRenderer()
1366     * @see #getCellRenderer()
1367     * 
1368     */
1369    @Override
1370    public void setCellRenderer(ListCellRenderer renderer) {
1371        // PENDING JW: super fires for very first setting
1372        // as defaults are automagically set (by delegatingRenderer
1373        // using this list's factory method) there is no
1374        // easy way to _not_ force, this isn't working
1375        // but then ... it's only the very first time around. 
1376        // Safe enough to wait for complaints ;-)
1377        boolean forceFire = (delegatingRenderer != null) ;
1378        // JW: Pending - probably fires propertyChangeEvent with wrong newValue?
1379        // how about fixedCellWidths?
1380        // need to test!!
1381        getDelegatingRenderer().setDelegateRenderer(renderer);
1382        getStringValueRegistry().setStringValue(
1383                renderer instanceof StringValue ? (StringValue) renderer: null, 
1384                        0);
1385        super.setCellRenderer(delegatingRenderer);
1386        if (forceFire)
1387           firePropertyChange("cellRenderer", null, delegatingRenderer);
1388    }
1389
1390    /**
1391     * A decorator for the original ListCellRenderer. Needed to hook highlighters
1392     * after messaging the delegate.<p>
1393     * 
1394     * PENDING JW: formally implement UIDependent?
1395     */
1396    public class DelegatingRenderer implements ListCellRenderer, RolloverRenderer {
1397        /** the delegate. */
1398        private ListCellRenderer delegateRenderer;
1399
1400        /**
1401         * Instantiates a DelegatingRenderer with list's default renderer as delegate.
1402         */
1403        public DelegatingRenderer() {
1404            this(null);
1405        }
1406        
1407        /**
1408         * Instantiates a DelegatingRenderer with the given delegate. If the
1409         * delegate is null, the default is created via the list's factory method.
1410         * 
1411         * @param delegate the delegate to use, if null the list's default is
1412         *   created and used.
1413         */
1414        public DelegatingRenderer(ListCellRenderer delegate) {
1415            setDelegateRenderer(delegate);
1416        }
1417
1418        /**
1419         * Sets the delegate. If the
1420         * delegate is null, the default is created via the list's factory method.
1421         * 
1422         * @param delegate the delegate to use, if null the list's default is
1423         *   created and used.
1424         */
1425        public void setDelegateRenderer(ListCellRenderer delegate) {
1426            if (delegate == null) {
1427                delegate = createDefaultCellRenderer();
1428            }
1429            delegateRenderer = delegate;
1430        }
1431
1432        /**
1433         * Returns the delegate.
1434         * 
1435         * @return the delegate renderer used by this renderer, guaranteed to
1436         *   not-null.
1437         */
1438        public ListCellRenderer getDelegateRenderer() {
1439            return delegateRenderer;
1440        }
1441
1442        /**
1443         * Updates the ui of the delegate.
1444         */
1445         public void updateUI() {
1446             updateRendererUI(delegateRenderer);
1447         }
1448
1449         /**
1450          * 
1451          * @param renderer the renderer to update the ui of.
1452          */
1453         private void updateRendererUI(ListCellRenderer renderer) {
1454             if (renderer == null) return;
1455             Component comp = null;
1456             if (renderer instanceof AbstractRenderer) {
1457                 comp = ((AbstractRenderer) renderer).getComponentProvider().getRendererComponent(null);
1458             } else if (renderer instanceof Component) {
1459                 comp = (Component) renderer;
1460             } else {
1461                 try {
1462                     comp = renderer.getListCellRendererComponent(
1463                             JXList.this, null, -1, false, false);
1464                } catch (Exception e) {
1465                    // nothing to do - renderer barked on off-range row
1466                }
1467             }
1468             if (comp != null) {
1469                 SwingUtilities.updateComponentTreeUI(comp);
1470             }
1471
1472         }
1473         
1474         // --------- implement ListCellRenderer
1475        /**
1476         * {@inheritDoc} <p>
1477         * 
1478         * Overridden to apply the highlighters, if any, after calling the delegate.
1479         * The decorators are not applied if the row is invalid.
1480         */
1481       @Override
1482    public Component getListCellRendererComponent(JList list, Object value,
1483                int index, boolean isSelected, boolean cellHasFocus) {
1484            Component comp = delegateRenderer.getListCellRendererComponent(list, value, index,
1485                    isSelected, cellHasFocus);
1486            if ((compoundHighlighter != null) && (index >= 0) && (index < getElementCount())) {
1487                comp = compoundHighlighter.highlight(comp, getComponentAdapter(index));
1488            }
1489            return comp;
1490        }
1491
1492
1493        // implement RolloverRenderer
1494        
1495        /**
1496         * {@inheritDoc}
1497         * 
1498         */
1499        @Override
1500        public boolean isEnabled() {
1501            return (delegateRenderer instanceof RolloverRenderer) && 
1502               ((RolloverRenderer) delegateRenderer).isEnabled();
1503        }
1504        
1505        /**
1506         * {@inheritDoc}
1507         */
1508        @Override
1509        public void doClick() {
1510            if (isEnabled()) {
1511                ((RolloverRenderer) delegateRenderer).doClick();
1512            }
1513        }
1514        
1515    }
1516
1517    /**
1518     * Invalidates cell size caching in the ui delegate. May do nothing if there's no
1519     * safe (i.e. without reflection) way to message the delegate. <p>
1520     * 
1521     * This implementation calls the corresponding method on BasicXListUI if available,
1522     * does nothing otherwise.
1523     * 
1524     */
1525    public void invalidateCellSizeCache() {
1526        if (getUI() instanceof BasicXListUI) {
1527            ((BasicXListUI) getUI()).invalidateCellSizeCache();
1528        }
1529    }
1530    
1531    // --------------------------- updateUI
1532
1533    
1534    /**
1535     * {@inheritDoc} <p>
1536     * 
1537     * Overridden to update renderer and Highlighters.
1538     */
1539    @Override
1540    public void updateUI() {
1541        // PENDING JW: temporary during dev to quickly switch between default and custom ui
1542        if (getUIClassID() == super.getUIClassID()) {
1543            super.updateUI();
1544        } else {    
1545            setUI((ListUI) LookAndFeelAddons.getUI(this, ListUI.class));
1546        }
1547        updateRendererUI();
1548        updateHighlighterUI();
1549    }
1550
1551    @Override
1552    public String getUIClassID() {
1553        // PENDING JW: temporary during dev to quickly switch between default and custom ui
1554//        return super.getUIClassID();
1555        return uiClassID;
1556    }
1557
1558    private void updateRendererUI() {
1559        if (delegatingRenderer != null) {
1560            delegatingRenderer.updateUI();
1561        } else {
1562            ListCellRenderer renderer = getCellRenderer();
1563            if (renderer instanceof Component) {
1564                SwingUtilities.updateComponentTreeUI((Component) renderer);
1565            }
1566        }
1567    }
1568
1569    /**
1570     * Updates highlighter after <code>updateUI</code> changes.
1571     * 
1572     * @see org.jdesktop.swingx.plaf.UIDependent
1573     */
1574    protected void updateHighlighterUI() {
1575        if (compoundHighlighter == null) return;
1576        compoundHighlighter.updateUI();
1577    }
1578
1579}