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 "sortable" 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}