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 }