JXList.java
上传用户:zhengdagz
上传日期:2014-03-06
资源大小:1956k
文件大小:28k
源码类别:

xml/soap/webservice

开发平台:

Java

  1. /*
  2.  * $Id: JXList.java,v 1.22 2005/10/14 14:29:50 kleopatra Exp $
  3.  *
  4.  * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
  5.  * Santa Clara, California 95054, U.S.A. All rights reserved.
  6.  *
  7.  * This library is free software; you can redistribute it and/or
  8.  * modify it under the terms of the GNU Lesser General Public
  9.  * License as published by the Free Software Foundation; either
  10.  * version 2.1 of the License, or (at your option) any later version.
  11.  * 
  12.  * This library is distributed in the hope that it will be useful,
  13.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  15.  * Lesser General Public License for more details.
  16.  * 
  17.  * You should have received a copy of the GNU Lesser General Public
  18.  * License along with this library; if not, write to the Free Software
  19.  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  20.  */
  21. package org.jdesktop.swingx;
  22. import java.awt.Component;
  23. import java.awt.Cursor;
  24. import java.awt.Point;
  25. import java.awt.Rectangle;
  26. import java.awt.event.ActionEvent;
  27. import java.awt.event.ActionListener;
  28. import java.beans.PropertyChangeEvent;
  29. import java.beans.PropertyChangeListener;
  30. import java.util.Vector;
  31. import java.util.regex.Matcher;
  32. import java.util.regex.Pattern;
  33. import javax.swing.AbstractButton;
  34. import javax.swing.AbstractListModel;
  35. import javax.swing.Action;
  36. import javax.swing.DefaultListCellRenderer;
  37. import javax.swing.JComponent;
  38. import javax.swing.JList;
  39. import javax.swing.KeyStroke;
  40. import javax.swing.ListCellRenderer;
  41. import javax.swing.ListModel;
  42. import javax.swing.event.ChangeEvent;
  43. import javax.swing.event.ChangeListener;
  44. import javax.swing.event.ListDataEvent;
  45. import javax.swing.event.ListDataListener;
  46. import org.jdesktop.swingx.decorator.ComponentAdapter;
  47. import org.jdesktop.swingx.decorator.FilterPipeline;
  48. import org.jdesktop.swingx.decorator.HighlighterPipeline;
  49. import org.jdesktop.swingx.decorator.PipelineEvent;
  50. import org.jdesktop.swingx.decorator.PipelineListener;
  51. import org.jdesktop.swingx.decorator.Selection;
  52. import org.jdesktop.swingx.decorator.Sorter;
  53. /**
  54.  * JXList
  55.  * 
  56.  * Enabled Rollover/LinkModel handling. Enabled Highlighter support.
  57.  * 
  58.  * Added experimental support for filtering/sorting. This feature is disabled by
  59.  * default because it has side-effects which might break "normal" expectations
  60.  * when using a JList: if enabled all row coordinates (including those returned
  61.  * by the selection) are in view coordinates. Furthermore, the model returned
  62.  * from getModel() is a wrapper around the actual data.
  63.  * 
  64.  * 
  65.  * 
  66.  * @author Ramesh Gupta
  67.  * @author Jeanette Winzenburg
  68.  */
  69. public class JXList extends JList {
  70.     /** The pipeline holding the filters. */
  71.     protected FilterPipeline filters;
  72.     /**
  73.      * The pipeline holding the highlighters.
  74.      */
  75.     protected HighlighterPipeline highlighters;
  76.     /** listening to changeEvents from highlighterPipeline. */
  77.     private ChangeListener highlighterChangeListener;
  78.     /** The ComponentAdapter for model data access. */
  79.     protected ComponentAdapter dataAdapter;
  80.     /**
  81.      * Mouse/Motion/Listener keeping track of mouse moved in cell coordinates.
  82.      */
  83.     private RolloverProducer rolloverProducer;
  84.     /**
  85.      * RolloverController: listens to cell over events and repaints
  86.      * entered/exited rows.
  87.      */
  88.     private LinkController linkController;
  89.     /** A wrapper around the default renderer enabling decoration. */
  90.     private DelegatingRenderer delegatingRenderer;
  91.     private WrappingListModel wrappingModel;
  92.     private PipelineListener pipelineListener;
  93.     private boolean filterEnabled;
  94.     private Selection selection;
  95.     private Searchable searchable;
  96.     public JXList() {
  97.         init();
  98.     }
  99.     public JXList(ListModel dataModel) {
  100.         super(dataModel);
  101.         init();
  102.     }
  103.     public JXList(Object[] listData) {
  104.         super(listData);
  105.         init();
  106.     }
  107.     public JXList(Vector listData) {
  108.         super(listData);
  109.         init();
  110.     }
  111.     private void init() {
  112.         Action findAction = createFindAction();
  113.         getActionMap().put("find", findAction);
  114.         // JW: this should be handled by the LF!
  115.         KeyStroke findStroke = KeyStroke.getKeyStroke("control F");
  116.         getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(findStroke, "find");
  117.         
  118.     }
  119.     private Action createFindAction() {
  120.         Action findAction = new UIAction("find") {
  121.             public void actionPerformed(ActionEvent e) {
  122.                 doFind();
  123.                 
  124.             }
  125.             
  126.         };
  127.         return findAction;
  128.     }
  129.     protected void doFind() {
  130.         SearchFactory.getInstance().showFindInput(this, getSearchable());
  131.         
  132.     }
  133.     /**
  134.      * 
  135.      * @returns a not-null Searchable for this editor.  
  136.      */
  137.     public Searchable getSearchable() {
  138.         if (searchable == null) {
  139.             searchable = new ListSearchable();
  140.         }
  141.         return searchable;
  142.     }
  143.     /**
  144.      * sets the Searchable for this editor. If null, a default 
  145.      * searchable will be used.
  146.      * 
  147.      * @param searchable
  148.      */
  149.     public void setSearchable(Searchable searchable) {
  150.         this.searchable = searchable;
  151.     }
  152.     
  153.     public class ListSearchable extends AbstractSearchable {
  154.         @Override
  155.         protected void findMatchAndUpdateState(Pattern pattern, int startRow, boolean backwards) {
  156.             SearchResult searchResult = null;
  157.             if (backwards) {
  158.                 for (int index = startRow; index >= 0 && searchResult == null; index--) {
  159.                     searchResult = findMatchAt(pattern, index);
  160.                 }
  161.             } else {
  162.                 for (int index = startRow; index < getSize() && searchResult == null; index++) {
  163.                     searchResult = findMatchAt(pattern, index);
  164.                 }
  165.             }
  166.             updateState(searchResult);
  167.         }
  168.         @Override
  169.         protected SearchResult findExtendedMatch(Pattern pattern, int row) {
  170.             
  171.             return findMatchAt(pattern, row);
  172.         }
  173.         /**
  174.          * Matches the cell content at row/col against the given Pattern.
  175.          * Returns an appropriate SearchResult if matching or null if no
  176.          * matching
  177.          * 
  178.          * @param pattern 
  179.          * @param row a valid row index in view coordinates
  180.          * @param column a valid column index in view coordinates
  181.          * @return
  182.          */
  183.         protected SearchResult findMatchAt(Pattern pattern, int row) {
  184.             Object value = getElementAt(row);
  185.             if (value != null) {
  186.                 Matcher matcher = pattern.matcher(value.toString());
  187.                 if (matcher.find()) {
  188.                     return createSearchResult(matcher, row, -1);
  189.                 }
  190.             }
  191.             return null;
  192.         }
  193.         
  194.         @Override
  195.         protected int getSize() {
  196.             return getElementCount();
  197.         }
  198.         @Override
  199.         protected void moveMatchMarker() {
  200.           setSelectedIndex(lastSearchResult.foundRow);
  201.           if (lastSearchResult.foundRow >= 0) {
  202.               ensureIndexIsVisible(lastSearchResult.foundRow);
  203.           }
  204.             
  205.         }
  206.     }
  207.     /**
  208.      * Property to enable/disable rollover support. This can be enabled to show
  209.      * "live" rollover behaviour, f.i. the cursor over LinkModel cells. Default
  210.      * is disabled.
  211.      * 
  212.      * @param rolloverEnabled
  213.      */
  214.     public void setRolloverEnabled(boolean rolloverEnabled) {
  215.         boolean old = isRolloverEnabled();
  216.         if (rolloverEnabled == old)
  217.             return;
  218.         if (rolloverEnabled) {
  219.             rolloverProducer = createRolloverProducer();
  220.             addMouseListener(rolloverProducer);
  221.             addMouseMotionListener(rolloverProducer);
  222.             linkController = new LinkController();
  223.             addPropertyChangeListener(linkController);
  224.         } else {
  225.             removeMouseListener(rolloverProducer);
  226.             removeMouseMotionListener(rolloverProducer);
  227.             rolloverProducer = null;
  228.             removePropertyChangeListener(linkController);
  229.             linkController = null;
  230.         }
  231.         firePropertyChange("rolloverEnabled", old, isRolloverEnabled());
  232.     }
  233.     /**
  234.      * creates and returns the RolloverProducer to use with this tree.
  235.      * 
  236.      * @return
  237.      */
  238.     protected RolloverProducer createRolloverProducer() {
  239.         RolloverProducer r = new RolloverProducer() {
  240.             protected void updateRolloverPoint(JComponent component,
  241.                     Point mousePoint) {
  242.                 JXList list = (JXList) component;
  243.                 int row = list.locationToIndex(mousePoint);
  244.                 if (row >= 0) {
  245.                     Rectangle cellBounds = list.getCellBounds(row, row);
  246.                     if (!cellBounds.contains(mousePoint)) {
  247.                         row = -1;
  248.                     }
  249.                 }
  250.                 int col = row < 0 ? -1 : 0;
  251.                 rollover.x = col;
  252.                 rollover.y = row;
  253.             }
  254.         };
  255.         return r;
  256.     }
  257.     /**
  258.      * returns the rolloverEnabled property.
  259.      * 
  260.      * @return
  261.      */
  262.     public boolean isRolloverEnabled() {
  263.         return rolloverProducer != null;
  264.     }
  265.     public void setLinkVisitor(ActionListener linkVisitor) {
  266.         if (linkVisitor != null) {
  267.             setRolloverEnabled(true);
  268.             getDelegatingRenderer().setLinkVisitor(linkVisitor);
  269.         } else {
  270.             // JW: think - need to revert?
  271.         }
  272.     }
  273.     /**
  274.      * listens to rollover properties. 
  275.      * Repaints effected component regions.
  276.      * Updates link cursor.
  277.      * 
  278.      * @author Jeanette Winzenburg
  279.      */
  280.     public class LinkController implements PropertyChangeListener {
  281.         private Cursor oldCursor;
  282.         public void propertyChange(PropertyChangeEvent evt) {
  283.             if (RolloverProducer.ROLLOVER_KEY.equals(evt.getPropertyName())) {
  284.                    rollover((JXList) evt.getSource(), (Point) evt.getOldValue(),
  285.                             (Point) evt.getOldValue());
  286.             } else if (RolloverProducer.CLICKED_KEY.equals(evt.getPropertyName())) {
  287.                     click((JXList) evt.getSource(), (Point) evt.getOldValue(),
  288.                             (Point) evt.getNewValue());
  289.             }
  290.         }
  291.         
  292. //    --------------------------------- JList rollover
  293.         
  294.         private void rollover(JXList list, Point oldLocation, Point newLocation) {
  295.             setLinkCursor(list, newLocation);
  296.             // JW: partial repaints incomplete
  297.             list.repaint();
  298.         }
  299.         private void click(JXList list, Point oldLocation, Point newLocation) {
  300.             if (!isLinkElement(list, newLocation)) return;
  301.             ListCellRenderer renderer = list.getCellRenderer();
  302.             // PENDING: JW - don't ask the model, ask the list!
  303.             Component comp = renderer.getListCellRendererComponent(list, list.getModel().getElementAt(newLocation.y), newLocation.y, false, true);
  304.             if (comp instanceof AbstractButton) {
  305.                 // this is fishy - needs to be removed as soon as JList is editable
  306.                 ((AbstractButton) comp).doClick();
  307.                 list.repaint();
  308.             }
  309.         }
  310.         
  311.         /**
  312.          * something weird: cursor in JList behaves different from JTable?
  313.          * @param list
  314.          * @param location
  315.          */
  316.         private void setLinkCursor(JXList list, Point location) {
  317.             if (isLinkElement(list, location)) {
  318.                     oldCursor = list.getCursor();
  319.                     list.setCursor(Cursor
  320.                             .getPredefinedCursor(Cursor.HAND_CURSOR));
  321.             } else {
  322.                     list.setCursor(oldCursor);
  323.                     oldCursor = null;
  324.             }
  325.         }
  326.         private boolean isLinkElement(JXList list, Point location) {
  327.             if (location == null || location.y < 0) return false;
  328.             // PENDING: JW - don't ask the model, ask the list!
  329.             return (list.getModel().getElementAt(location.y) instanceof LinkModel);
  330.         }
  331.         
  332.     }
  333.    
  334.     // ---------------------------- filters
  335.     /**
  336.      * returns the element at the given index. The index is
  337.      * in view coordinates which might differ from model 
  338.      * coordinates if filtering is enabled and filters/sorters
  339.      * are active.
  340.      * 
  341.      * @param viewIndex the index in view coordinates
  342.      * @return the element at the index
  343.      * @throws IndexOutOfBoundsException 
  344.      *          if viewIndex < 0 or viewIndex >= getElementCount()
  345.      */
  346.     public Object getElementAt(int viewIndex) {
  347.         return getModel().getElementAt(viewIndex);
  348.     }
  349.     /**
  350.      * Returns the number of elements in this list in view 
  351.      * coordinates. If filters are active this number might be
  352.      * less than the number of elements in the underlying model.
  353.      * 
  354.      * @return
  355.      */
  356.     public int getElementCount() {
  357.         return getModel().getSize();
  358.     }
  359.     /**
  360.      * Convert row index from view coordinates to model coordinates accounting
  361.      * for the presence of sorters and filters.
  362.      * 
  363.      * @param viewIndex index in view coordinates
  364.      * @return index in model coordinates
  365.      * @throws IndexOutOfBoundsException if viewIndex < 0 or viewIndex >= getElementCount() 
  366.      */
  367.     public int convertIndexToModel(int viewIndex) {
  368.         return isFilterEnabled() ? getFilters().convertRowIndexToModel(
  369.                 viewIndex) : viewIndex;
  370.     }
  371.     /**
  372.      * Convert index from model coordinates to view coordinates accounting
  373.      * for the presence of sorters and filters.
  374.      * 
  375.      * PENDING Filter guards against out of range - should not? 
  376.      * 
  377.      * @param modelIndex index in model coordinates
  378.      * @return index in view coordinates if the model index maps to a view coordinate
  379.      *          or -1 if not contained in the view.
  380.      * 
  381.      */
  382.     public int convertIndexToView(int modelIndex) {
  383.         return isFilterEnabled() ? getFilters().convertRowIndexToView(
  384.                 modelIndex) : modelIndex;
  385.     }
  386.     /**
  387.      * returns the underlying model. If !isFilterEnabled this will be the same
  388.      * as getModel().
  389.      * 
  390.      * @return
  391.      */
  392.     public ListModel getWrappedModel() {
  393.         return isFilterEnabled() ? wrappingModel.getModel() : getModel();
  394.     }
  395.     /**
  396.      * Enables/disables filtering support. If enabled all row indices -
  397.      * including the selection - are in view coordinates and getModel returns a
  398.      * wrapper around the underlying model.
  399.      * 
  400.      * Note: as an implementation side-effect calling this method clears the
  401.      * selection (done in super.setModel).
  402.      * 
  403.      * PENDING: cleanup state transitions!! - currently this can be safely
  404.      * applied once only to enable. Internal state is inconsistent if trying to
  405.      * disable again.
  406.      * 
  407.      * see Issue #2-swinglabs.
  408.      * 
  409.      * @param enabled
  410.      */
  411.     public void setFilterEnabled(boolean enabled) {
  412.         boolean old = isFilterEnabled();
  413.         if (old == enabled)
  414.             return;
  415.         filterEnabled = enabled;
  416.         if (!old) {
  417.             wrappingModel = new WrappingListModel(getModel());
  418.             super.setModel(wrappingModel);
  419.         } else {
  420.             ListModel model = wrappingModel.getModel();
  421.             wrappingModel = null;
  422.             super.setModel(model);
  423.         }
  424.     }
  425.     public boolean isFilterEnabled() {
  426.         return filterEnabled;
  427.     }
  428.     /**
  429.      * set's the underlying data model. Note that if isFilterEnabled you must
  430.      * call getWrappedModel to access the model given here. In this case
  431.      * getModel returns a wrapper around the data!
  432.      * 
  433.      * 
  434.      * 
  435.      */
  436.     public void setModel(ListModel model) {
  437.         if (isFilterEnabled()) {
  438.             wrappingModel.setModel(model);
  439.         } else {
  440.             super.setModel(model);
  441.         }
  442.     }
  443.     private Selection getSelection() {
  444.         if (selection == null) {
  445.             selection = new Selection(filters, getSelectionModel());
  446.         }
  447.         return selection;
  448.     }
  449.     public FilterPipeline getFilters() {
  450.         if ((filters == null) && isFilterEnabled()) {
  451.             setFilters(null);
  452.         }
  453.         return filters;
  454.     }
  455.     /** Sets the FilterPipeline for filtering table rows. 
  456.      *  PRE: isFilterEnabled()
  457.      * 
  458.      * @param pipeline the filterPipeline to use.
  459.      * @throws IllegalStateException if !isFilterEnabled()
  460.      */
  461.     public void setFilters(FilterPipeline pipeline) {
  462.         if (!isFilterEnabled()) throw
  463.             new IllegalStateException("filters not enabled - not allowed to set filters");
  464.         FilterPipeline old = filters;
  465.         Sorter sorter = null;
  466.         if (old != null) {
  467.             old.removePipelineListener(pipelineListener);
  468.             sorter = old.getSorter();
  469.         }
  470.         if (pipeline == null) {
  471.             pipeline = new FilterPipeline();
  472.         }
  473.         filters = pipeline;
  474.         filters.setSorter(sorter);
  475.         use(filters);
  476.         getSelection().setFilters(filters);
  477.     }
  478.     /**
  479.      * setModel() and setFilters() may be called in either order.
  480.      * 
  481.      * @param pipeline
  482.      */
  483.     private void use(FilterPipeline pipeline) {
  484.         if (pipeline != null) {
  485.             // check JW: adding listener multiple times (after setModel)?
  486.             if (initialUse(pipeline)) {
  487.                 pipeline.addPipelineListener(getFilterPipelineListener());
  488.                 pipeline.assign(getComponentAdapter());
  489.             } else {
  490.                 pipeline.flush();
  491.             }
  492.         }
  493.     }
  494.     /**
  495.      * @return true is not yet used in this JXTable, false otherwise
  496.      */
  497.     private boolean initialUse(FilterPipeline pipeline) {
  498.         if (pipelineListener == null)
  499.             return true;
  500.         PipelineListener[] l = pipeline.getPipelineListeners();
  501.         for (int i = 0; i < l.length; i++) {
  502.             if (pipelineListener.equals(l[i]))
  503.                 return false;
  504.         }
  505.         return true;
  506.     }
  507.     /** returns the listener for changes in filters. */
  508.     protected PipelineListener getFilterPipelineListener() {
  509.         if (pipelineListener == null) {
  510.             pipelineListener = createPipelineListener();
  511.         }
  512.         return pipelineListener;
  513.     }
  514.     /** creates the listener for changes in filters. */
  515.     protected PipelineListener createPipelineListener() {
  516.         PipelineListener l = new PipelineListener() {
  517.             public void contentsChanged(PipelineEvent e) {
  518.                 updateOnFilterContentChanged();
  519.             }
  520.         };
  521.         return l;
  522.     }
  523.     /**
  524.      * method called on change notification from filterpipeline.
  525.      */
  526.     protected void updateOnFilterContentChanged() {
  527.         // make the wrapper listen to the pipeline?
  528.         if (wrappingModel != null) {
  529.             wrappingModel.updateOnFilterContentChanged();
  530.         }
  531.         revalidate();
  532.         repaint();
  533.     }
  534.     private class WrappingListModel extends AbstractListModel {
  535.         private ListModel delegate;
  536.         private ListDataListener listDataListener;
  537.         public WrappingListModel(ListModel model) {
  538.             setModel(model);
  539.         }
  540.         public void updateOnFilterContentChanged() {
  541.             fireContentsChanged(this, -1, -1);
  542.         }
  543.         public void setModel(ListModel model) {
  544.             ListModel old = this.getModel();
  545.             if (old != null) {
  546.                 old.removeListDataListener(listDataListener);
  547.             }
  548.             this.delegate = model;
  549.             delegate.addListDataListener(getListDataListener());
  550.             fireContentsChanged(this, -1, -1);
  551.         }
  552.         private ListDataListener getListDataListener() {
  553.             if (listDataListener == null) {
  554.                 listDataListener = createListDataListener();
  555.             }
  556.             return listDataListener;
  557.         }
  558.         private ListDataListener createListDataListener() {
  559.             ListDataListener l = new ListDataListener() {
  560.                 public void intervalAdded(ListDataEvent e) {
  561.                     contentsChanged(e);
  562.                 }
  563.                 public void intervalRemoved(ListDataEvent e) {
  564.                     contentsChanged(e);
  565.                 }
  566.                 public void contentsChanged(ListDataEvent e) {
  567.                     getSelection().lock();
  568.                     fireContentsChanged(this, -1, -1);
  569.                     updateSelection(e);
  570.                     getFilters().flush();
  571.                 }
  572.             };
  573.             return l;
  574.         }
  575.         protected void updateSelection(ListDataEvent e) {
  576.             if (e.getType() == ListDataEvent.INTERVAL_REMOVED) {
  577.                 getSelection()
  578.                         .removeIndexInterval(e.getIndex0(), e.getIndex1());
  579.             } else if (e.getType() == ListDataEvent.INTERVAL_ADDED) {
  580.                 int minIndex = Math.min(e.getIndex0(), e.getIndex1());
  581.                 int maxIndex = Math.max(e.getIndex0(), e.getIndex1());
  582.                 int length = maxIndex - minIndex + 1;
  583.                 getSelection().insertIndexInterval(minIndex, length, true);
  584.             } else {
  585.                 getSelection().clearModelSelection();
  586.             }
  587.         }
  588.         public ListModel getModel() {
  589.             return delegate;
  590.         }
  591.         public int getSize() {
  592.             return getFilters().getOutputSize();
  593.         }
  594.         public Object getElementAt(int index) {
  595.             return getFilters().getValueAt(index, 0);
  596.         }
  597.     }
  598.     // ---------------------------- uniform data model
  599.     protected ComponentAdapter getComponentAdapter() {
  600.         if (dataAdapter == null) {
  601.             dataAdapter = new ListAdapter(this);
  602.         }
  603.         return dataAdapter;
  604.     }
  605.     protected static class ListAdapter extends ComponentAdapter {
  606.         private final JXList list;
  607.         /**
  608.          * Constructs a <code>ListDataAdapter</code> for the specified target
  609.          * component.
  610.          * 
  611.          * @param component
  612.          *            the target component
  613.          */
  614.         public ListAdapter(JXList component) {
  615.             super(component);
  616.             list = component;
  617.         }
  618.         /**
  619.          * Typesafe accessor for the target component.
  620.          * 
  621.          * @return the target component as a {@link org.jdesktop.swingx.JXList}
  622.          */
  623.         public JXList getList() {
  624.             return list;
  625.         }
  626.         /**
  627.          * {@inheritDoc}
  628.          */
  629.         public boolean hasFocus() {
  630.             /** @todo Think through printing implications */
  631.             return list.isFocusOwner() && (row == list.getLeadSelectionIndex());
  632.         }
  633.         public int getRowCount() {
  634.             return list.getWrappedModel().getSize();
  635.         }
  636.         /**
  637.          * {@inheritDoc}
  638.          */
  639.         public Object getValueAt(int row, int column) {
  640.             return list.getWrappedModel().getElementAt(row);
  641.         }
  642.         public Object getFilteredValueAt(int row, int column) {
  643.             return list.getElementAt(row);
  644.         }
  645.         public void setValueAt(Object aValue, int row, int column) {
  646.             throw new UnsupportedOperationException(
  647.                     "Method getFilteredValueAt() not yet implemented.");
  648.         }
  649.         public boolean isCellEditable(int row, int column) {
  650.             return false;
  651.         }
  652.         /**
  653.          * {@inheritDoc}
  654.          */
  655.         public boolean isSelected() {
  656.             /** @todo Think through printing implications */
  657.             return list.isSelectedIndex(row);
  658.         }
  659.         public String getColumnName(int columnIndex) {
  660.             return "Column_" + columnIndex;
  661.         }
  662.         public String getColumnIdentifier(int columnIndex) {
  663.             return null;
  664.         }
  665.     }
  666.     // ------------------------------ renderers
  667.     public HighlighterPipeline getHighlighters() {
  668.         return highlighters;
  669.     }
  670.     /** Assigns a HighlighterPipeline to the table. */
  671.     public void setHighlighters(HighlighterPipeline pipeline) {
  672.         HighlighterPipeline old = getHighlighters();
  673.         if (old != null) {
  674.             old.removeChangeListener(getHighlighterChangeListener());
  675.         }
  676.         highlighters = pipeline;
  677.         if (highlighters != null) {
  678.             highlighters.addChangeListener(getHighlighterChangeListener());
  679.         }
  680.         firePropertyChange("highlighters", old, getHighlighters());
  681.     }
  682.     private ChangeListener getHighlighterChangeListener() {
  683.         if (highlighterChangeListener == null) {
  684.             highlighterChangeListener = new ChangeListener() {
  685.                 public void stateChanged(ChangeEvent e) {
  686.                     repaint();
  687.                 }
  688.             };
  689.         }
  690.         return highlighterChangeListener;
  691.     }
  692.     private DelegatingRenderer getDelegatingRenderer() {
  693.         if (delegatingRenderer == null) {
  694.             // only called once... to get hold of the default?
  695.             delegatingRenderer = new DelegatingRenderer(super.getCellRenderer());
  696.         }
  697.         return delegatingRenderer;
  698.     }
  699.     public ListCellRenderer getCellRenderer() {
  700.         return getDelegatingRenderer();
  701.     }
  702.     public void setCellRenderer(ListCellRenderer renderer) {
  703.         // JW: Pending - probably fires propertyChangeEvent with wrong newValue?
  704.         // how about fixedCellWidths?
  705.         // need to test!!
  706.         getDelegatingRenderer().setDelegateRenderer(renderer);
  707.         super.setCellRenderer(delegatingRenderer);
  708.     }
  709.     private class DelegatingRenderer implements ListCellRenderer {
  710.         private LinkRenderer linkRenderer;
  711.         private ListCellRenderer delegateRenderer;
  712.         public DelegatingRenderer(ListCellRenderer delegate) {
  713.             setDelegateRenderer(delegate);
  714.         }
  715.         public void setDelegateRenderer(ListCellRenderer delegate) {
  716.             if (delegate == null) {
  717.                 delegate = new DefaultListCellRenderer();
  718.             }
  719.             delegateRenderer = delegate;
  720.         }
  721.         public Component getListCellRendererComponent(JList list, Object value,
  722.                 int index, boolean isSelected, boolean cellHasFocus) {
  723.             Component comp = null;
  724.             if (value instanceof LinkModel) {
  725.                 comp = getLinkRenderer().getListCellRendererComponent(list,
  726.                         value, index, isSelected, cellHasFocus);
  727.             } else {
  728.                 comp = delegateRenderer.getListCellRendererComponent(list,
  729.                         value, index, isSelected, cellHasFocus);
  730.             }
  731.             if (highlighters != null) {
  732.                 ComponentAdapter adapter = getComponentAdapter();
  733.                 adapter.column = 0;
  734.                 adapter.row = index;
  735.                 comp = highlighters.apply(comp, adapter);
  736.             }
  737.             return comp;
  738.         }
  739.         private LinkRenderer getLinkRenderer() {
  740.             if (linkRenderer == null) {
  741.                 linkRenderer = new LinkRenderer();
  742.             }
  743.             return linkRenderer;
  744.         }
  745.         public void setLinkVisitor(ActionListener linkVisitor) {
  746.             getLinkRenderer().setVisitingDelegate(linkVisitor);
  747.         }
  748.         public void updateUI() {
  749.             updateRendererUI(linkRenderer);
  750.             updateRendererUI(delegateRenderer);
  751.         }
  752.         private void updateRendererUI(ListCellRenderer renderer) {
  753.             if (renderer instanceof JComponent) {
  754.                 ((JComponent) renderer).updateUI();
  755.             } else if (renderer != null) {
  756.                 Component comp = renderer.getListCellRendererComponent(
  757.                         JXList.this, null, -1, false, false);
  758.                 if (comp instanceof JComponent) {
  759.                     ((JComponent) comp).updateUI();
  760.                 }
  761.             }
  762.         }
  763.     }
  764.     // --------------------------- updateUI
  765.     public void updateUI() {
  766.         super.updateUI();
  767.         updateRendererUI();
  768.     }
  769.     private void updateRendererUI() {
  770.         if (delegatingRenderer != null) {
  771.             delegatingRenderer.updateUI();
  772.         } else {
  773.             ListCellRenderer renderer = getCellRenderer();
  774.             if (renderer instanceof JComponent) {
  775.                 ((JComponent) renderer).updateUI();
  776.             }
  777.         }
  778.     }
  779. }