001 /*
002 * $Id: JXList.java,v 1.34 2006/05/14 08:12:16 dmouse Exp $
003 *
004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005 * Santa Clara, California 95054, U.S.A. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
020 */
021
022 package org.jdesktop.swingx;
023
024 import java.awt.Component;
025 import java.awt.Cursor;
026 import java.awt.Point;
027 import java.awt.Rectangle;
028 import java.awt.event.ActionEvent;
029 import java.beans.PropertyChangeEvent;
030 import java.beans.PropertyChangeListener;
031 import java.util.List;
032 import java.util.Vector;
033 import java.util.regex.Matcher;
034 import java.util.regex.Pattern;
035
036 import javax.swing.AbstractAction;
037 import javax.swing.AbstractButton;
038 import javax.swing.AbstractListModel;
039 import javax.swing.Action;
040 import javax.swing.DefaultListCellRenderer;
041 import javax.swing.JComponent;
042 import javax.swing.JList;
043 import javax.swing.JTable;
044 import javax.swing.KeyStroke;
045 import javax.swing.ListCellRenderer;
046 import javax.swing.ListModel;
047 import javax.swing.ListSelectionModel;
048 import javax.swing.event.ChangeEvent;
049 import javax.swing.event.ChangeListener;
050 import javax.swing.event.ListDataEvent;
051 import javax.swing.event.ListDataListener;
052
053 import org.jdesktop.swingx.action.LinkAction;
054 import org.jdesktop.swingx.decorator.ComponentAdapter;
055 import org.jdesktop.swingx.decorator.FilterPipeline;
056 import org.jdesktop.swingx.decorator.HighlighterPipeline;
057 import org.jdesktop.swingx.decorator.PipelineEvent;
058 import org.jdesktop.swingx.decorator.PipelineListener;
059 import org.jdesktop.swingx.decorator.SelectionMapper;
060 import org.jdesktop.swingx.decorator.SortKey;
061
062 /**
063 * JXList
064 *
065 * Enabled Rollover/LinkModel handling. Enabled Highlighter support.
066 *
067 * Added experimental support for filtering/sorting. This feature is disabled by
068 * default because it has side-effects which might break "normal" expectations
069 * when using a JList: if enabled all row coordinates (including those returned
070 * by the selection) are in view coordinates. Furthermore, the model returned
071 * from getModel() is a wrapper around the actual data.
072 *
073 *
074 *
075 * @author Ramesh Gupta
076 * @author Jeanette Winzenburg
077 */
078 public class JXList extends JList {
079 public static final String EXECUTE_BUTTON_ACTIONCOMMAND = "executeButtonAction";
080
081 /** The pipeline holding the filters. */
082 protected FilterPipeline filters;
083
084 /**
085 * The pipeline holding the highlighters.
086 */
087 protected HighlighterPipeline highlighters;
088
089 /** listening to changeEvents from highlighterPipeline. */
090 private ChangeListener highlighterChangeListener;
091
092 /** The ComponentAdapter for model data access. */
093 protected ComponentAdapter dataAdapter;
094
095 /**
096 * Mouse/Motion/Listener keeping track of mouse moved in cell coordinates.
097 */
098 private RolloverProducer rolloverProducer;
099
100 /**
101 * RolloverController: listens to cell over events and repaints
102 * entered/exited rows.
103 */
104 private ListRolloverController linkController;
105
106 /** A wrapper around the default renderer enabling decoration. */
107 private DelegatingRenderer delegatingRenderer;
108
109 private WrappingListModel wrappingModel;
110
111 private PipelineListener pipelineListener;
112
113 private boolean filterEnabled;
114
115 private SelectionMapper selectionMapper;
116
117 private Searchable searchable;
118
119 public JXList() {
120 init();
121 }
122
123 public JXList(ListModel dataModel) {
124 super(dataModel);
125 init();
126 }
127
128 public JXList(Object[] listData) {
129 super(listData);
130 init();
131 }
132
133 public JXList(Vector listData) {
134 super(listData);
135 init();
136 }
137
138 private void init() {
139 Action findAction = createFindAction();
140 getActionMap().put("find", findAction);
141 // JW: this should be handled by the LF!
142 KeyStroke findStroke = KeyStroke.getKeyStroke("control F");
143 getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(findStroke, "find");
144
145 }
146
147 private Action createFindAction() {
148 Action findAction = new UIAction("find") {
149
150 public void actionPerformed(ActionEvent e) {
151 doFind();
152
153 }
154
155 };
156 return findAction;
157 }
158
159 protected void doFind() {
160 SearchFactory.getInstance().showFindInput(this, getSearchable());
161
162 }
163
164 /**
165 *
166 * @return a not-null Searchable for this editor.
167 */
168 public Searchable getSearchable() {
169 if (searchable == null) {
170 searchable = new ListSearchable();
171 }
172 return searchable;
173 }
174
175 /**
176 * sets the Searchable for this editor. If null, a default
177 * searchable will be used.
178 *
179 * @param searchable
180 */
181 public void setSearchable(Searchable searchable) {
182 this.searchable = searchable;
183 }
184
185
186 public class ListSearchable extends AbstractSearchable {
187
188 @Override
189 protected void findMatchAndUpdateState(Pattern pattern, int startRow, boolean backwards) {
190 SearchResult searchResult = null;
191 if (backwards) {
192 for (int index = startRow; index >= 0 && searchResult == null; index--) {
193 searchResult = findMatchAt(pattern, index);
194 }
195 } else {
196 for (int index = startRow; index < getSize() && searchResult == null; index++) {
197 searchResult = findMatchAt(pattern, index);
198 }
199 }
200 updateState(searchResult);
201 }
202
203 @Override
204 protected SearchResult findExtendedMatch(Pattern pattern, int row) {
205
206 return findMatchAt(pattern, row);
207 }
208 /**
209 * Matches the cell content at row/col against the given Pattern.
210 * Returns an appropriate SearchResult if matching or null if no
211 * matching
212 *
213 * @param pattern
214 * @param row a valid row index in view coordinates
215 * @return <code>SearchResult</code> if matched otherwise null
216 */
217 protected SearchResult findMatchAt(Pattern pattern, int row) {
218 Object value = getElementAt(row);
219 if (value != null) {
220 Matcher matcher = pattern.matcher(value.toString());
221 if (matcher.find()) {
222 return createSearchResult(matcher, row, -1);
223 }
224 }
225 return null;
226 }
227
228 @Override
229 protected int getSize() {
230 return getElementCount();
231 }
232
233 @Override
234 protected void moveMatchMarker() {
235 setSelectedIndex(lastSearchResult.foundRow);
236 if (lastSearchResult.foundRow >= 0) {
237 ensureIndexIsVisible(lastSearchResult.foundRow);
238 }
239
240 }
241
242 }
243 /**
244 * Property to enable/disable rollover support. This can be enabled to show
245 * "live" rollover behaviour, f.i. the cursor over LinkModel cells. Default
246 * is disabled.
247 *
248 * @param rolloverEnabled
249 */
250 public void setRolloverEnabled(boolean rolloverEnabled) {
251 boolean old = isRolloverEnabled();
252 if (rolloverEnabled == old)
253 return;
254 if (rolloverEnabled) {
255 rolloverProducer = createRolloverProducer();
256 addMouseListener(rolloverProducer);
257 addMouseMotionListener(rolloverProducer);
258 getLinkController().install(this);
259 } else {
260 removeMouseListener(rolloverProducer);
261 removeMouseMotionListener(rolloverProducer);
262 rolloverProducer = null;
263 getLinkController().release();
264 }
265 firePropertyChange("rolloverEnabled", old, isRolloverEnabled());
266 }
267
268
269 protected ListRolloverController getLinkController() {
270 if (linkController == null) {
271 linkController = createLinkController();
272 }
273 return linkController;
274 }
275
276 protected ListRolloverController createLinkController() {
277 return new ListRolloverController();
278 }
279
280
281 /**
282 * creates and returns the RolloverProducer to use with this tree.
283 *
284 * @return <code>RolloverProducer</code> to use with this tree
285 */
286 protected RolloverProducer createRolloverProducer() {
287 RolloverProducer r = new RolloverProducer() {
288 protected void updateRolloverPoint(JComponent component,
289 Point mousePoint) {
290 JList list = (JList) component;
291 int row = list.locationToIndex(mousePoint);
292 if (row >= 0) {
293 Rectangle cellBounds = list.getCellBounds(row, row);
294 if (!cellBounds.contains(mousePoint)) {
295 row = -1;
296 }
297 }
298 int col = row < 0 ? -1 : 0;
299 rollover.x = col;
300 rollover.y = row;
301 }
302
303 };
304 return r;
305 }
306 /**
307 * returns the rolloverEnabled property.
308 *
309 * TODO: why doesn't this just return rolloverEnabled???
310 *
311 * @return true if rollover is enabled
312 */
313 public boolean isRolloverEnabled() {
314 return rolloverProducer != null;
315 }
316
317 /**
318 * listens to rollover properties. Repaints effected component regions.
319 * Updates link cursor.
320 *
321 * @author Jeanette Winzenburg
322 */
323 public static class ListRolloverController<T extends JList> extends
324 RolloverController<T> {
325
326 private Cursor oldCursor;
327
328 // --------------------------------- JList rollover
329
330 protected void rollover(Point oldLocation, Point newLocation) {
331 setRolloverCursor(newLocation);
332 // JW: partial repaints incomplete
333 component.repaint();
334 }
335
336 /**
337 * something weird: cursor in JList behaves different from JTable?
338 *
339 * @param location
340 */
341 private void setRolloverCursor(Point location) {
342 if (hasRollover(location)) {
343 oldCursor = component.getCursor();
344 component.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
345 } else {
346 component.setCursor(oldCursor);
347 oldCursor = null;
348 }
349
350 }
351
352 protected RolloverRenderer getRolloverRenderer(Point location,
353 boolean prepare) {
354 ListCellRenderer renderer = component.getCellRenderer();
355 RolloverRenderer rollover = renderer instanceof RolloverRenderer
356 ? (RolloverRenderer) renderer : null;
357 if ((rollover != null) && !rollover.isEnabled()) {
358 rollover = null;
359 }
360 if ((rollover != null) && prepare) {
361 Object element = component.getModel().getElementAt(location.y);
362 renderer.getListCellRendererComponent(component, element,
363 location.y, false, true);
364 }
365 return rollover;
366 }
367
368 @Override
369 protected Point getFocusedCell() {
370 int leadRow = component.getLeadSelectionIndex();
371 if (leadRow < 0)
372 return null;
373 return new Point(0, leadRow);
374 }
375
376 }
377
378
379 // ---------------------------- filters
380
381 /**
382 * returns the element at the given index. The index is in view coordinates
383 * which might differ from model coordinates if filtering is enabled and
384 * filters/sorters are active.
385 *
386 * @param viewIndex the index in view coordinates
387 * @return the element at the index
388 * @throws IndexOutOfBoundsException if viewIndex < 0 or viewIndex >=
389 * getElementCount()
390 */
391 public Object getElementAt(int viewIndex) {
392 return getModel().getElementAt(viewIndex);
393 }
394
395 /**
396 * Returns the number of elements in this list in view
397 * coordinates. If filters are active this number might be
398 * less than the number of elements in the underlying model.
399 *
400 * @return number of elements in this list in view coordinates
401 */
402 public int getElementCount() {
403 return getModel().getSize();
404 }
405
406 /**
407 * Convert row index from view coordinates to model coordinates accounting
408 * for the presence of sorters and filters.
409 *
410 * @param viewIndex index in view coordinates
411 * @return index in model coordinates
412 * @throws IndexOutOfBoundsException if viewIndex < 0 or viewIndex >= getElementCount()
413 */
414 public int convertIndexToModel(int viewIndex) {
415 return isFilterEnabled() ? getFilters().convertRowIndexToModel(
416 viewIndex) : viewIndex;
417 }
418
419 /**
420 * Convert index from model coordinates to view coordinates accounting
421 * for the presence of sorters and filters.
422 *
423 * PENDING Filter guards against out of range - should not?
424 *
425 * @param modelIndex index in model coordinates
426 * @return index in view coordinates if the model index maps to a view coordinate
427 * or -1 if not contained in the view.
428 *
429 */
430 public int convertIndexToView(int modelIndex) {
431 return isFilterEnabled() ? getFilters().convertRowIndexToView(
432 modelIndex) : modelIndex;
433 }
434
435 /**
436 * returns the underlying model. If !isFilterEnabled this will be the same
437 * as getModel().
438 *
439 * @return the underlying model
440 */
441 public ListModel getWrappedModel() {
442 return isFilterEnabled() ? wrappingModel.getModel() : getModel();
443 }
444
445 /**
446 * Enables/disables filtering support. If enabled all row indices -
447 * including the selection - are in view coordinates and getModel returns a
448 * wrapper around the underlying model.
449 *
450 * Note: as an implementation side-effect calling this method clears the
451 * selection (done in super.setModel).
452 *
453 * PENDING: cleanup state transitions!! - currently this can be safely
454 * applied once only to enable. Internal state is inconsistent if trying to
455 * disable again.
456 *
457 * see Issue #2-swinglabs.
458 *
459 * @param enabled
460 */
461 public void setFilterEnabled(boolean enabled) {
462 boolean old = isFilterEnabled();
463 if (old == enabled)
464 return;
465 filterEnabled = enabled;
466 if (!old) {
467 wrappingModel = new WrappingListModel(getModel());
468 super.setModel(wrappingModel);
469 } else {
470 ListModel model = wrappingModel.getModel();
471 wrappingModel = null;
472 super.setModel(model);
473 }
474
475 }
476
477 public boolean isFilterEnabled() {
478 return filterEnabled;
479 }
480
481 /**
482 * Overridden to update selectionMapper
483 */
484 @Override
485 public void setSelectionModel(ListSelectionModel newModel) {
486 super.setSelectionModel(newModel);
487 getSelectionMapper().setViewSelectionModel(getSelectionModel());
488 }
489
490 /**
491 * set's the underlying data model. Note that if isFilterEnabled you must
492 * call getWrappedModel to access the model given here. In this case
493 * getModel returns a wrapper around the data!
494 *
495 *
496 *
497 */
498 @Override
499 public void setModel(ListModel model) {
500 if (isFilterEnabled()) {
501 wrappingModel.setModel(model);
502 } else {
503 super.setModel(model);
504 }
505 }
506
507 /**
508 * widened access for testing...
509 * @return the selection mapper
510 */
511 protected SelectionMapper getSelectionMapper() {
512 if (selectionMapper == null) {
513 selectionMapper = new SelectionMapper(filters, getSelectionModel());
514 }
515 return selectionMapper;
516 }
517
518 public FilterPipeline getFilters() {
519 if ((filters == null) && isFilterEnabled()) {
520 setFilters(null);
521 }
522 return filters;
523 }
524
525 /** Sets the FilterPipeline for filtering table rows.
526 * PRE: isFilterEnabled()
527 *
528 * @param pipeline the filterPipeline to use.
529 * @throws IllegalStateException if !isFilterEnabled()
530 */
531 public void setFilters(FilterPipeline pipeline) {
532 if (!isFilterEnabled()) throw
533 new IllegalStateException("filters not enabled - not allowed to set filters");
534
535 FilterPipeline old = filters;
536 List<? extends SortKey> sortKeys = null;
537 if (old != null) {
538 old.removePipelineListener(pipelineListener);
539 sortKeys = old.getSortController().getSortKeys();
540 }
541 if (pipeline == null) {
542 pipeline = new FilterPipeline();
543 }
544 filters = pipeline;
545 filters.getSortController().setSortKeys(sortKeys);
546 // JW: first assign to prevent (short?) illegal internal state
547 // #173-swingx
548 use(filters);
549 getSelectionMapper().setFilters(filters);
550
551 }
552
553 /**
554 * setModel() and setFilters() may be called in either order.
555 *
556 * @param pipeline
557 */
558 private void use(FilterPipeline pipeline) {
559 if (pipeline != null) {
560 // check JW: adding listener multiple times (after setModel)?
561 if (initialUse(pipeline)) {
562 pipeline.addPipelineListener(getFilterPipelineListener());
563 pipeline.assign(getComponentAdapter());
564 } else {
565 pipeline.flush();
566 }
567 }
568 }
569
570 /**
571 * @return true is not yet used in this JXTable, false otherwise
572 */
573 private boolean initialUse(FilterPipeline pipeline) {
574 if (pipelineListener == null)
575 return true;
576 PipelineListener[] l = pipeline.getPipelineListeners();
577 for (int i = 0; i < l.length; i++) {
578 if (pipelineListener.equals(l[i]))
579 return false;
580 }
581 return true;
582 }
583
584 /** returns the listener for changes in filters. */
585 protected PipelineListener getFilterPipelineListener() {
586 if (pipelineListener == null) {
587 pipelineListener = createPipelineListener();
588 }
589 return pipelineListener;
590 }
591
592 /** creates the listener for changes in filters. */
593 protected PipelineListener createPipelineListener() {
594 PipelineListener l = new PipelineListener() {
595 public void contentsChanged(PipelineEvent e) {
596 updateOnFilterContentChanged();
597 }
598 };
599 return l;
600 }
601
602 /**
603 * method called on change notification from filterpipeline.
604 */
605 protected void updateOnFilterContentChanged() {
606 // make the wrapper listen to the pipeline?
607 if (wrappingModel != null) {
608 wrappingModel.updateOnFilterContentChanged();
609 }
610 revalidate();
611 repaint();
612 }
613
614 private class WrappingListModel extends AbstractListModel {
615
616 private ListModel delegate;
617
618 private ListDataListener listDataListener;
619
620 public WrappingListModel(ListModel model) {
621 setModel(model);
622 }
623
624 public void updateOnFilterContentChanged() {
625 fireContentsChanged(this, -1, -1);
626
627 }
628
629 public void setModel(ListModel model) {
630 ListModel old = this.getModel();
631 if (old != null) {
632 old.removeListDataListener(listDataListener);
633 }
634 this.delegate = model;
635 delegate.addListDataListener(getListDataListener());
636 fireContentsChanged(this, -1, -1);
637 }
638
639 private ListDataListener getListDataListener() {
640 if (listDataListener == null) {
641 listDataListener = createListDataListener();
642 }
643 return listDataListener;
644 }
645
646 private ListDataListener createListDataListener() {
647 ListDataListener l = new ListDataListener() {
648
649 public void intervalAdded(ListDataEvent e) {
650 contentsChanged(e);
651
652 }
653
654 public void intervalRemoved(ListDataEvent e) {
655 contentsChanged(e);
656
657 }
658
659 public void contentsChanged(ListDataEvent e) {
660 getSelectionMapper().lock();
661 fireContentsChanged(this, -1, -1);
662 updateSelection(e);
663 getFilters().flush();
664
665 }
666
667 };
668 return l;
669 }
670
671 protected void updateSelection(ListDataEvent e) {
672 if (e.getType() == ListDataEvent.INTERVAL_REMOVED) {
673 getSelectionMapper()
674 .removeIndexInterval(e.getIndex0(), e.getIndex1());
675 } else if (e.getType() == ListDataEvent.INTERVAL_ADDED) {
676
677 int minIndex = Math.min(e.getIndex0(), e.getIndex1());
678 int maxIndex = Math.max(e.getIndex0(), e.getIndex1());
679 int length = maxIndex - minIndex + 1;
680 getSelectionMapper().insertIndexInterval(minIndex, length, true);
681 } else {
682 getSelectionMapper().clearModelSelection();
683 }
684
685 }
686
687 public ListModel getModel() {
688 return delegate;
689 }
690
691 public int getSize() {
692 return getFilters().getOutputSize();
693 }
694
695 public Object getElementAt(int index) {
696 return getFilters().getValueAt(index, 0);
697 }
698
699 }
700
701 // ---------------------------- uniform data model
702
703 protected ComponentAdapter getComponentAdapter() {
704 if (dataAdapter == null) {
705 dataAdapter = new ListAdapter(this);
706 }
707 return dataAdapter;
708 }
709
710 protected static class ListAdapter extends ComponentAdapter {
711 private final JXList list;
712
713 /**
714 * Constructs a <code>ListDataAdapter</code> for the specified target
715 * component.
716 *
717 * @param component
718 * the target component
719 */
720 public ListAdapter(JXList component) {
721 super(component);
722 list = component;
723 }
724
725 /**
726 * Typesafe accessor for the target component.
727 *
728 * @return the target component as a {@link org.jdesktop.swingx.JXList}
729 */
730 public JXList getList() {
731 return list;
732 }
733
734 /**
735 * {@inheritDoc}
736 */
737 public boolean hasFocus() {
738 /** TODO: Think through printing implications */
739 return list.isFocusOwner() && (row == list.getLeadSelectionIndex());
740 }
741
742 @Override
743 public int getRowCount() {
744 return list.getWrappedModel().getSize();
745 }
746
747 /**
748 * {@inheritDoc}
749 */
750 public Object getValueAt(int row, int column) {
751 return list.getWrappedModel().getElementAt(row);
752 }
753
754 public Object getFilteredValueAt(int row, int column) {
755 return list.getElementAt(row);
756 }
757
758 public void setValueAt(Object aValue, int row, int column) {
759 throw new UnsupportedOperationException(
760 "Method getFilteredValueAt() not yet implemented.");
761 }
762
763 public boolean isCellEditable(int row, int column) {
764 return false;
765 }
766
767 /**
768 * {@inheritDoc}
769 */
770 public boolean isSelected() {
771 /** TODO: Think through printing implications */
772 return list.isSelectedIndex(row);
773 }
774
775 public String getColumnName(int columnIndex) {
776 return "Column_" + columnIndex;
777 }
778
779 public String getColumnIdentifier(int columnIndex) {
780 return null;
781 }
782
783 }
784
785 // ------------------------------ renderers
786
787 public HighlighterPipeline getHighlighters() {
788 return highlighters;
789 }
790
791 /** Assigns a HighlighterPipeline to the table. */
792 public void setHighlighters(HighlighterPipeline pipeline) {
793 HighlighterPipeline old = getHighlighters();
794 if (old != null) {
795 old.removeChangeListener(getHighlighterChangeListener());
796 }
797 highlighters = pipeline;
798 if (highlighters != null) {
799 highlighters.addChangeListener(getHighlighterChangeListener());
800 }
801 firePropertyChange("highlighters", old, getHighlighters());
802 }
803
804 private ChangeListener getHighlighterChangeListener() {
805 if (highlighterChangeListener == null) {
806 highlighterChangeListener = new ChangeListener() {
807
808 public void stateChanged(ChangeEvent e) {
809 repaint();
810 }
811
812 };
813 }
814 return highlighterChangeListener;
815 }
816
817 private DelegatingRenderer getDelegatingRenderer() {
818 if (delegatingRenderer == null) {
819 // only called once... to get hold of the default?
820 delegatingRenderer = new DelegatingRenderer(super.getCellRenderer());
821 }
822 return delegatingRenderer;
823 }
824
825 @Override
826 public ListCellRenderer getCellRenderer() {
827 return getDelegatingRenderer();
828 }
829
830 @Override
831 public void setCellRenderer(ListCellRenderer renderer) {
832 // JW: Pending - probably fires propertyChangeEvent with wrong newValue?
833 // how about fixedCellWidths?
834 // need to test!!
835 getDelegatingRenderer().setDelegateRenderer(renderer);
836 super.setCellRenderer(delegatingRenderer);
837 }
838
839 private class DelegatingRenderer implements ListCellRenderer, RolloverRenderer {
840
841 private ListCellRenderer delegateRenderer;
842
843 public DelegatingRenderer(ListCellRenderer delegate) {
844 setDelegateRenderer(delegate);
845 }
846
847 public void setDelegateRenderer(ListCellRenderer delegate) {
848 if (delegate == null) {
849 delegate = new DefaultListCellRenderer();
850 }
851 delegateRenderer = delegate;
852 }
853
854 public boolean isEnabled() {
855 return (delegateRenderer instanceof RolloverRenderer) &&
856 ((RolloverRenderer) delegateRenderer).isEnabled();
857 }
858
859 public void doClick() {
860 if (isEnabled()) {
861 ((RolloverRenderer) delegateRenderer).doClick();
862 }
863 }
864 public Component getListCellRendererComponent(JList list, Object value,
865 int index, boolean isSelected, boolean cellHasFocus) {
866 Component comp = null;
867
868 comp = delegateRenderer.getListCellRendererComponent(list,
869 value, index, isSelected, cellHasFocus);
870 if (highlighters != null) {
871 ComponentAdapter adapter = getComponentAdapter();
872 adapter.column = 0;
873 adapter.row = index;
874 comp = highlighters.apply(comp, adapter);
875 }
876 return comp;
877 }
878
879
880 public void updateUI() {
881 updateRendererUI(delegateRenderer);
882 }
883
884 private void updateRendererUI(ListCellRenderer renderer) {
885 if (renderer instanceof JComponent) {
886 ((JComponent) renderer).updateUI();
887 } else if (renderer != null) {
888 Component comp = renderer.getListCellRendererComponent(
889 JXList.this, null, -1, false, false);
890 if (comp instanceof JComponent) {
891 ((JComponent) comp).updateUI();
892 }
893 }
894
895 }
896
897 }
898
899 // --------------------------- updateUI
900
901 @Override
902 public void updateUI() {
903 super.updateUI();
904 updateRendererUI();
905 }
906
907 private void updateRendererUI() {
908 if (delegatingRenderer != null) {
909 delegatingRenderer.updateUI();
910 } else {
911 ListCellRenderer renderer = getCellRenderer();
912 if (renderer instanceof JComponent) {
913 ((JComponent) renderer).updateUI();
914 }
915 }
916 }
917
918 }