org.jdesktop.swingx.JXList Maven / Gradle / Ivy
Show all versions of swingx-all Show documentation
/*
* $Id: JXList.java 4158 2012-02-03 18:29:40Z kschaefe $
*
* Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
* Santa Clara, California 95054, U.S.A. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.jdesktop.swingx;
import org.jdesktop.beans.JavaBean;
import org.jdesktop.swingx.decorator.ComponentAdapter;
import org.jdesktop.swingx.decorator.CompoundHighlighter;
import org.jdesktop.swingx.decorator.Highlighter;
import org.jdesktop.swingx.plaf.LookAndFeelAddons;
import org.jdesktop.swingx.plaf.UIAction;
import org.jdesktop.swingx.plaf.XListAddon;
import org.jdesktop.swingx.plaf.basic.core.BasicXListUI;
import org.jdesktop.swingx.renderer.AbstractRenderer;
import org.jdesktop.swingx.renderer.DefaultListRenderer;
import org.jdesktop.swingx.renderer.StringValue;
import org.jdesktop.swingx.rollover.ListRolloverController;
import org.jdesktop.swingx.rollover.ListRolloverProducer;
import org.jdesktop.swingx.rollover.RolloverProducer;
import org.jdesktop.swingx.rollover.RolloverRenderer;
import org.jdesktop.swingx.search.ListSearchable;
import org.jdesktop.swingx.search.SearchFactory;
import org.jdesktop.swingx.search.Searchable;
import org.jdesktop.swingx.sort.DefaultSortController;
import org.jdesktop.swingx.sort.ListSortController;
import org.jdesktop.swingx.sort.SortController;
import org.jdesktop.swingx.sort.StringValueRegistry;
import org.jdesktop.swingx.table.TableColumnExt;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.KeyStroke;
import javax.swing.ListCellRenderer;
import javax.swing.ListModel;
import javax.swing.RowFilter;
import javax.swing.RowSorter;
import javax.swing.SortOrder;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.ListUI;
import javax.swing.text.Position.Bias;
import java.awt.Component;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Vector;
import java.util.regex.Pattern;
/**
* Enhanced List component with support for general SwingX sorting/filtering,
* rendering, highlighting, rollover and search functionality. List specific
* enhancements include ?? PENDING JW ...
*
* Sorting and Filtering
* JXList supports sorting and filtering.
*
* Changed to use core support. Usage is very similar to J/X/Table.
* It provides api to apply a specific sort order, to toggle the sort order and to reset a sort.
* Sort sequence can be configured by setting a custom comparator.
*
*
* list.setAutoCreateRowSorter(true);
* list.setComparator(myComparator);
* list.setSortOrder(SortOrder.DESCENDING);
* list.toggleSortOder();
* list.resetSortOrder();
*
*
*
* JXList provides api to access items of the underlying model in view coordinates
* and to convert from/to model coordinates.
*
* Note: JXList needs a specific ui-delegate - BasicXListUI and subclasses - which
* is aware of model vs. view coordiate systems and which controls the synchronization of
* selection/dataModel and sorter state. SwingX comes with a subclass for Synth.
*
*
Rendering and Highlighting
*
* As all SwingX collection views, a JXList is a HighlighterClient (PENDING JW:
* formally define and implement, like in AbstractTestHighlighter), that is it
* provides consistent api to add and remove Highlighters which can visually
* decorate the rendering component.
*
*
*
*
* JXList list = new JXList(new Contributors());
* // implement a custom string representation, concated from first-, lastName
* StringValue sv = new StringValue() {
* public String getString(Object value) {
* if (value instanceof Contributor) {
* Contributor contributor = (Contributor) value;
* return contributor.lastName() + ", " + contributor.firstName();
* }
* return StringValues.TO_STRING(value);
* }
* };
* list.setCellRenderer(new DefaultListRenderer(sv);
* // highlight condition: gold merits
* HighlightPredicate predicate = new HighlightPredicate() {
* public boolean isHighlighted(Component renderer,
* ComponentAdapter adapter) {
* if (!(value instanceof Contributor)) return false;
* return ((Contributor) value).hasGold();
* }
* };
* // highlight with foreground color
* list.addHighlighter(new PainterHighlighter(predicate, goldStarPainter);
*
*
*
* Note: to support the highlighting this implementation wraps the
* ListCellRenderer set by client code with a DelegatingRenderer which applies
* the Highlighter after delegating the default configuration to the wrappee. As
* a side-effect, getCellRenderer does return the wrapper instead of the custom
* renderer. To access the latter, client code must call getWrappedCellRenderer.
*
*
*
Rollover
*
* As all SwingX collection views, a JXList supports per-cell rollover. If
* enabled, the component fires rollover events on enter/exit of a cell which by
* default is promoted to the renderer if it implements RolloverRenderer, that
* is simulates live behaviour. The rollover events can be used by client code
* as well, f.i. to decorate the rollover row using a Highlighter.
*
*
*
* JXList list = new JXList();
* list.setRolloverEnabled(true);
* list.setCellRenderer(new DefaultListRenderer());
* list.addHighlighter(new ColorHighlighter(HighlightPredicate.ROLLOVER_ROW,
* null, Color.RED);
*
*
*
*
* Search
*
* As all SwingX collection views, a JXList is searchable. A search action is
* registered in its ActionMap under the key "find". The default behaviour is to
* ask the SearchFactory to open a search component on this component. The
* default keybinding is retrieved from the SearchFactory, typically ctrl-f (or
* cmd-f for Mac). Client code can register custom actions and/or bindings as
* appropriate.
*
*
* JXList provides api to vend a renderer-controlled String representation of
* cell content. This allows the Searchable and Highlighters to use WYSIWYM
* (What-You-See-Is-What-You-Match), that is pattern matching against the actual
* string as seen by the user.
*
* @author Ramesh Gupta
* @author Jeanette Winzenburg
*/
@JavaBean
public class JXList extends JList {
/**
* UI Class ID
*/
public static final String uiClassID = "XListUI";
/**
* Registers a Addon for JXList.
*/
static {
LookAndFeelAddons.contribute(new XListAddon());
}
public static final String EXECUTE_BUTTON_ACTIONCOMMAND = "executeButtonAction";
/**
* The pipeline holding the highlighters.
*/
protected CompoundHighlighter compoundHighlighter;
/**
* listening to changeEvents from compoundHighlighter.
*/
private ChangeListener highlighterChangeListener;
/**
* The ComponentAdapter for model data access.
*/
protected ComponentAdapter dataAdapter;
/**
* Mouse/Motion/Listener keeping track of mouse moved in cell coordinates.
*/
private RolloverProducer rolloverProducer;
/**
* RolloverController: listens to cell over events and repaints
* entered/exited rows.
*/
private ListRolloverController> linkController;
/**
* A wrapper around the default renderer enabling decoration.
*/
private transient DelegatingRenderer delegatingRenderer;
private Searchable searchable;
private Comparator> comparator;
private boolean autoCreateRowSorter;
private RowSorter extends ListModel> rowSorter;
private boolean sortable;
private boolean sortsOnUpdates;
private StringValueRegistry stringValueRegistry;
private SortOrder[] sortOrderCycle;
/**
* Constructs a JXList
with an empty model and filters disabled.
*/
public JXList() {
this(false);
}
/**
* Constructs a JXList
that displays the elements in the
* specified, non-null
model and automatic creation of a RowSorter disabled.
*
* @param dataModel the data model for this list
* @throws IllegalArgumentException if dataModel
* is null
*/
public JXList(ListModel dataModel) {
this(dataModel, false);
}
/**
* Constructs a JXList
that displays the elements in
* the specified array and automatic creation of a RowSorter disabled.
*
* @param listData the array of Objects to be loaded into the data model
* @throws IllegalArgumentException if listData
* is null
*/
public JXList(E[] listData) {
this(listData, false);
}
/**
* Constructs a JXList
that displays the elements in
* the specified Vector
and automatic creation of a RowSorter disabled.
*
* @param listData the Vector
to be loaded into the
* data model
* @throws IllegalArgumentException if listData
* is null
*/
public JXList(Vector listData) {
this(listData, false);
}
/**
* Constructs a JXList
with an empty model and
* automatic creation of a RowSorter as given.
*
* @param autoCreateRowSorter boolean
to determine if
* a RowSorter should be created automatically.
*/
public JXList(boolean autoCreateRowSorter) {
init(autoCreateRowSorter);
}
/**
* Constructs a JXList
with the specified model and
* automatic creation of a RowSorter as given.
*
* @param dataModel the data model for this list
* @param autoCreateRowSorter boolean
to determine if
* a RowSorter should be created automatically.
* @throws IllegalArgumentException if dataModel
* is null
*/
public JXList(ListModel dataModel, boolean autoCreateRowSorter) {
super(dataModel);
init(autoCreateRowSorter);
}
/**
* Constructs a JXList
that displays the elements in
* the specified array and automatic creation of a RowSorter as given.
*
* @param listData the array of Objects to be loaded into the data model
* @param autoCreateRowSorter boolean
to determine if
* a RowSorter should be created automatically.
* @throws IllegalArgumentException if listData
* is null
*/
public JXList(E[] listData, boolean autoCreateRowSorter) {
super(listData);
if (listData == null)
throw new IllegalArgumentException("listData must not be null");
init(autoCreateRowSorter);
}
/**
* Constructs a JXList
that displays the elements in
* the specified Vector
and filtersEnabled property.
*
* @param listData the Vector
to be loaded into the
* data model
* @param autoCreateRowSorter boolean
to determine if
* a RowSorter should be created automatically.
* @throws IllegalArgumentException if listData
is null
*/
public JXList(Vector listData, boolean autoCreateRowSorter) {
super(listData);
if (listData == null)
throw new IllegalArgumentException("listData must not be null");
init(autoCreateRowSorter);
}
private void init(boolean autoCreateRowSorter) {
sortOrderCycle = DefaultSortController.getDefaultSortOrderCycle();
setSortable(true);
setSortsOnUpdates(true);
setAutoCreateRowSorter(autoCreateRowSorter);
Action findAction = createFindAction();
getActionMap().put("find", findAction);
KeyStroke findStroke = SearchFactory.getInstance().getSearchAccelerator();
getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(findStroke, "find");
}
private Action createFindAction() {
return new UIAction("find") {
@Override
public void actionPerformed(ActionEvent e) {
doFind();
}
};
}
/**
* Starts a search on this List's visible items. This implementation asks the
* SearchFactory to open a find widget on itself.
*/
protected void doFind() {
SearchFactory.getInstance().showFindInput(this, getSearchable());
}
/**
* Returns a Searchable for this component, guaranteed to be not null. This
* implementation lazily creates a ListSearchable if necessary.
*
* @return a not-null Searchable for this list.
* @see #setSearchable(Searchable)
* @see ListSearchable
*/
public Searchable getSearchable() {
if (searchable == null) {
searchable = new ListSearchable(this);
}
return searchable;
}
/**
* Sets the Searchable for this component. If null, a default
* Searchable will be created and used.
*
* @param searchable the Searchable to use for this component, may be null to indicate
* using the list's default searchable.
* @see #getSearchable()
*/
public void setSearchable(Searchable searchable) {
this.searchable = searchable;
}
/**
* {@inheritDoc}
*
* Overridden to cope with sorting/filtering, taking over completely.
*/
@Override
public int getNextMatch(String prefix, int startIndex, Bias bias) {
Pattern pattern = Pattern.compile("^" + prefix, Pattern.CASE_INSENSITIVE);
return getSearchable().search(pattern, startIndex, bias == Bias.Backward);
}
//--------------------- Rollover support
/**
* Sets the property to enable/disable rollover support. If enabled, the list
* fires property changes on per-cell mouse rollover state, i.e.
* when the mouse enters/leaves a list cell.
*
* This can be enabled to show "live" rollover behaviour, f.i. the cursor over a cell
* rendered by a JXHyperlink.
*
* Default value is disabled.
*
* @param rolloverEnabled a boolean indicating whether or not the rollover
* functionality should be enabled.
* @see #isRolloverEnabled()
* @see #getLinkController()
* @see #createRolloverProducer()
* @see RolloverRenderer
*/
public void setRolloverEnabled(boolean rolloverEnabled) {
boolean old = isRolloverEnabled();
if (rolloverEnabled == old)
return;
if (rolloverEnabled) {
rolloverProducer = createRolloverProducer();
rolloverProducer.install(this);
getLinkController().install(this);
} else {
rolloverProducer.release(this);
rolloverProducer = null;
getLinkController().release();
}
firePropertyChange("rolloverEnabled", old, isRolloverEnabled());
}
/**
* Returns a boolean indicating whether or not rollover support is enabled.
*
* @return a boolean indicating whether or not rollover support is enabled.
* @see #setRolloverEnabled(boolean)
*/
public boolean isRolloverEnabled() {
return rolloverProducer != null;
}
/**
* Returns the RolloverController for this component. Lazyly creates the
* controller if necessary, that is the return value is guaranteed to be
* not null.
*
* PENDING JW: rename to getRolloverController
*
* @return the RolloverController for this tree, guaranteed to be not null.
* @see #setRolloverEnabled(boolean)
* @see #createLinkController()
* @see org.jdesktop.swingx.rollover.RolloverController
*/
protected ListRolloverController> getLinkController() {
if (linkController == null) {
linkController = createLinkController();
}
return linkController;
}
/**
* Creates and returns a RolloverController appropriate for this component.
*
* @return a RolloverController appropriate for this component.
* @see #getLinkController()
* @see org.jdesktop.swingx.rollover.RolloverController
*/
protected ListRolloverController> createLinkController() {
return new ListRolloverController<>();
}
/**
* Creates and returns the RolloverProducer to use with this tree.
*
*
* @return RolloverProducer
to use with this tree
* @see #setRolloverEnabled(boolean)
*/
protected RolloverProducer createRolloverProducer() {
return new ListRolloverProducer();
}
//--------------------- public sort api
/**
* Returns {@code true} if whenever the model changes, a new
* {@code RowSorter} should be created and installed
* as the table's sorter; otherwise, returns {@code false}.
*
* @return true if a {@code RowSorter} should be created when
* the model changes
* @since 1.6
*/
public boolean getAutoCreateRowSorter() {
return autoCreateRowSorter;
}
/**
* Specifies whether a {@code RowSorter} should be created for the
* list whenever its model changes.
*
* When {@code setAutoCreateRowSorter(true)} is invoked, a {@code
* RowSorter} is immediately created and installed on the
* list. While the {@code autoCreateRowSorter} property remains
* {@code true}, every time the model is changed, a new {@code
* RowSorter} is created and set as the list's row sorter.
*
* The default value is false.
*
* @param autoCreateRowSorter whether or not a {@code RowSorter}
* should be automatically created
*/
public void setAutoCreateRowSorter(boolean autoCreateRowSorter) {
if (getAutoCreateRowSorter() == autoCreateRowSorter)
return;
boolean oldValue = getAutoCreateRowSorter();
this.autoCreateRowSorter = autoCreateRowSorter;
if (autoCreateRowSorter) {
setRowSorter(createDefaultRowSorter());
}
firePropertyChange("autoCreateRowSorter", oldValue, getAutoCreateRowSorter());
}
/**
* Creates and returns the default RowSorter. Note that this is already
* configured to the current ListModel.
*
* PENDING JW: review method signature - better expose the need for the
* model by adding a parameter?
*
* @return the default RowSorter.
*/
protected RowSorter extends ListModel> createDefaultRowSorter() {
return new ListSortController<>(getModel());
}
/**
* Returns the object responsible for sorting.
*
* @return the object responsible for sorting
* @since 1.6
*/
public RowSorter extends ListModel> getRowSorter() {
return rowSorter;
}
/**
* Sets the RowSorter
. RowSorter
is used
* to provide sorting and filtering to a JXList
.
*
* This method clears the selection and resets any variable row heights.
*
* If the underlying model of the RowSorter
differs from
* that of this JXList
undefined behavior will result.
*
* @param sorter the RowSorter
; null
turns
* sorting off
*/
public void setRowSorter(RowSorter extends ListModel> sorter) {
RowSorter extends ListModel> oldRowSorter = getRowSorter();
this.rowSorter = sorter;
configureSorterProperties();
firePropertyChange("rowSorter", oldRowSorter, sorter);
}
/**
* Propagates sort-related properties from table/columns to the sorter if it
* is of type SortController, does nothing otherwise.
*/
protected void configureSorterProperties() {
if (!getControlsSorterProperties())
return;
// configure from table properties
getSortController().setSortable(sortable);
getSortController().setSortsOnUpdates(sortsOnUpdates);
getSortController().setComparator(0, comparator);
getSortController().setSortOrderCycle(getSortOrderCycle());
getSortController().setStringValueProvider(getStringValueRegistry());
}
/**
* Sets "sortable" property indicating whether or not this list
* isSortable.
*
* Note: as of post-1.0 this property is propagated to the SortController.
* Whether or not a change triggers a re-sort is up to either the concrete controller
* implementation (the default doesn't) or client code. This behaviour is
* different from old SwingX style sorting.
*
* @param sortable boolean indicating whether or not this table supports
* sortable columns
* @see TableColumnExt#isSortable()
*/
public void setSortable(boolean sortable) {
boolean old = isSortable();
this.sortable = sortable;
if (getControlsSorterProperties()) {
getSortController().setSortable(sortable);
}
firePropertyChange("sortable", old, isSortable());
}
/**
* Returns the table's sortable property.
*
* @return true if the table is sortable.
*/
public boolean isSortable() {
return sortable;
}
/**
* If true, specifies that a sort should happen when the underlying
* model is updated (rowsUpdated
is invoked). For
* example, if this is true and the user edits an entry the
* location of that item in the view may change. The default is
* true.
*
* @param sortsOnUpdates whether or not to sort on update events
*/
public void setSortsOnUpdates(boolean sortsOnUpdates) {
boolean old = getSortsOnUpdates();
this.sortsOnUpdates = sortsOnUpdates;
if (getControlsSorterProperties()) {
getSortController().setSortsOnUpdates(sortsOnUpdates);
}
firePropertyChange("sortsOnUpdates", old, getSortsOnUpdates());
}
/**
* Returns true if a sort should happen when the underlying
* model is updated; otherwise, returns false.
*
* @return whether or not to sort when the model is updated
*/
public boolean getSortsOnUpdates() {
return sortsOnUpdates;
}
/**
* Sets the sortorder cycle used when toggle sorting this table's columns.
* This property is propagated to the SortController
* if controlsSorterProperties is true.
*
* @param cycle the sequence of zero or more not-null SortOrders to cycle through.
* @throws NullPointerException if the array or any of its elements are null
*/
public void setSortOrderCycle(SortOrder... cycle) {
SortOrder[] old = getSortOrderCycle();
if (getControlsSorterProperties()) {
getSortController().setSortOrderCycle(cycle);
}
this.sortOrderCycle = Arrays.copyOf(cycle, cycle.length);
firePropertyChange("sortOrderCycle", old, getSortOrderCycle());
}
/**
* Returns the sortOrder cycle used when toggle sorting this table's columns, guaranteed
* to be not null.
*
* @return the sort order cycle used in toggle sort, not null
*/
public SortOrder[] getSortOrderCycle() {
return Arrays.copyOf(sortOrderCycle, sortOrderCycle.length);
}
/**
* @return the comparator used.
* @see #setComparator(Comparator)
*/
public Comparator> getComparator() {
return comparator;
}
/**
* Sets the comparator to use for sorting.
*
* Note: as of post-1.0 the property is propagated to the SortController,
* if available.
* Whether or not a change triggers a re-sort is up to either the concrete controller
* implementation (the default doesn't) or client code. This behaviour is
* different from old SwingX style sorting.
*
* @param comparator the comparator to use.
*/
public void setComparator(Comparator> comparator) {
Comparator> old = getComparator();
this.comparator = comparator;
updateSortAfterComparatorChange();
firePropertyChange("comparator", old, getComparator());
}
/**
* Updates the SortController's comparator, if available. Does nothing otherwise.
*/
protected void updateSortAfterComparatorChange() {
if (getControlsSorterProperties()) {
getSortController().setComparator(0, getComparator());
}
}
//------------------------- sort: do sort/filter
/**
* Sets the filter to the sorter, if available and of type SortController.
* Does nothing otherwise.
*
*
* @param filter the filter used to determine what entries should be
* included
*/
@SuppressWarnings("unchecked")
public > void setRowFilter(RowFilter super R, ? super Integer> filter) {
if (hasSortController()) {
// all fine, because R is a ListModel (R extends ListModel)
SortController controller = (SortController) getSortController();
controller.setRowFilter(filter);
}
}
/**
* Returns the filter of the sorter, if available and of type SortController.
* Returns null otherwise.
*
* PENDING JW: generics? had to remove return type from getSortController to
* make this compilable, so probably wrong.
*
* @return the filter used in the sorter.
*/
public RowFilter, ?> getRowFilter() {
return hasSortController() ? getSortController().getRowFilter() : null;
}
/**
* Resets sorting of all columns.
* Delegates to the SortController if available, or does nothing if not.
*
* PENDING JW: method name - consistent in SortController and here.
*/
public void resetSortOrder() {
if (hasSortController())
getSortController().resetSortOrders();
}
/**
* Toggles the sort order of the list.
* Delegates to the SortController if available, or does nothing if not.
*
*
* The exact behaviour is defined by the SortController's toggleSortOrder
* implementation. Typically a unsorted list is sorted in ascending order,
* a sorted list's order is reversed.
*
*/
public void toggleSortOrder() {
if (hasSortController())
getSortController().toggleSortOrder(0);
}
/**
* Sorts the list using SortOrder.
* Delegates to the SortController if available, or does nothing if not.
*
* @param sortOrder the sort order to use.
*/
public void setSortOrder(SortOrder sortOrder) {
if (hasSortController())
getSortController().setSortOrder(0, sortOrder);
}
/**
* Returns the SortOrder.
* Delegates to the SortController if available, or returns SortOrder.UNSORTED if not.
*
* @return the current SortOrder
*/
public SortOrder getSortOrder() {
if (hasSortController())
return getSortController().getSortOrder(0);
return SortOrder.UNSORTED;
}
/**
* Returns the currently active SortController. May be null if RowSorter
* is null or not of type SortController.
*
* PENDING JW: swaying about hiding or not - currently the only way to
* make the view not configure a RowSorter of type SortController is to
* let this return null.
*
* @return the currently active SortController
may be null
*/
@SuppressWarnings("unchecked")
protected SortController extends ListModel> getSortController() {
if (hasSortController()) {
// JW: the RowSorter is always of type extends ListModel>
// so the unchecked cast is safe
return (SortController extends ListModel>) getRowSorter();
}
return null;
}
/**
* Returns a boolean indicating whether the table has a SortController.
* If true, the call to getSortController is guaranteed to return a not-null
* value.
*
* @return a boolean indicating whether the table has a SortController.
* @see #getSortController()
*/
protected boolean hasSortController() {
return getRowSorter() instanceof SortController>;
}
/**
* Returns a boolean indicating whether the table configures the sorter's
* properties. If true, guaranteed that table's and the columns' sort related
* properties are propagated to the sorter. If false, guaranteed to not
* touch the sorter's configuration.
*
* This implementation returns true if the sorter is of type SortController.
*
* Note: the synchronization is unidirection from the table to the sorter.
* Changing the sorter under the table's feet might lead to undefined
* behaviour.
*
* @return a boolean indicating whether the table configurers the sorter's
* properties.
*/
protected boolean getControlsSorterProperties() {
return hasSortController() && getAutoCreateRowSorter();
}
// ---------------------------- filters
/**
* Returns the element at the given index. The index is in view coordinates
* which might differ from model coordinates if filtering is enabled and
* filters/sorters are active.
*
* @param viewIndex the index in view coordinates
* @return the element at the index
* @throws IndexOutOfBoundsException if {@code viewIndex < 0 or viewIndex >= getElementCount()}
*/
public E getElementAt(int viewIndex) {
return getModel().getElementAt(convertIndexToModel(viewIndex));
}
/**
* Returns the value for the smallest selected cell index;
* the selected value when only a single item is selected in the
* list. When multiple items are selected, it is simply the value for the
* smallest selected index. Returns {@code null} if there is no selection.
*
* This is a convenience method that simply returns the model value for
* {@code getMinSelectionIndex}, taking into account sorting and filtering.
*
* @return the first selected value
* @see #getMinSelectionIndex
* @see #getModel
* @see #addListSelectionListener
*/
@Override
public E getSelectedValue() {
int i = getSelectedIndex();
return i == -1 ? null : getElementAt(i);
}
/**
* Selects the specified object from the list, taking into account
* sorting and filtering.
*
* @param anObject the object to select
* @param shouldScroll {@code true} if the list should scroll to display
* the selected object, if one exists; otherwise {@code false}
*/
@Override
public void setSelectedValue(Object anObject, boolean shouldScroll) {
// Note: this method is a copy of JList.setSelectedValue,
// including comments. It simply usues getElementCount() and getElementAt()
// instead of the model.
if (anObject == null)
setSelectedIndex(-1);
else if (!anObject.equals(getSelectedValue())) {
int c = getElementCount();
for (int i = 0; i < c; i++) {
if (anObject.equals(getElementAt(i))) {
setSelectedIndex(i);
if (shouldScroll)
ensureIndexIsVisible(i);
repaint(); /** FIX-ME setSelectedIndex does not redraw all the time with the basic l&f**/
return;
}
}
setSelectedIndex(-1);
}
repaint(); /** FIX-ME setSelectedIndex does not redraw all the time with the basic l&f**/
}
/**
* Returns an array of all the selected values, in increasing order based
* on their indices in the list and taking into account sourting and filtering.
*
* @return the selected values, or an empty array if nothing is selected
* @see #isSelectedIndex
* @see #getModel
* @see #addListSelectionListener
*/
@SuppressWarnings("deprecation")
@Override
public Object[] getSelectedValues() {
int[] selectedIndexes = getSelectedIndices();
Object[] selectedValues = new Object[selectedIndexes.length];
for (int i = 0; i < selectedIndexes.length; i++) {
selectedValues[i] = getElementAt(selectedIndexes[i]);
}
return selectedValues;
}
@Override
public List getSelectedValuesList() {
int[] selectedIndexes = getSelectedIndices();
List selectedValues = new ArrayList<>(selectedIndexes.length);
for (int selectedIndex : selectedIndexes) {
selectedValues.add(getElementAt(selectedIndex));
}
return selectedValues;
}
/**
* Returns the number of elements in this list in view
* coordinates. If filters are active this number might be
* less than the number of elements in the underlying model.
*
* @return number of elements in this list in view coordinates
*/
public int getElementCount() {
return getRowSorter() != null ? getRowSorter().getViewRowCount() : getModel().getSize();
}
/**
* Convert row index from view coordinates to model coordinates accounting
* for the presence of sorters and filters.
*
* @param viewIndex index in view coordinates
* @return index in model coordinates
* @throws IndexOutOfBoundsException if {@code viewIndex < 0 or viewIndex >= getElementCount()}
*/
public int convertIndexToModel(int viewIndex) {
return getRowSorter() != null ? getRowSorter().convertRowIndexToModel(viewIndex) : viewIndex;
}
/**
* Convert index from model coordinates to view coordinates accounting
* for the presence of sorters and filters.
*
* @param modelIndex index in model coordinates
* @return index in view coordinates if the model index maps to a view coordinate
* or -1 if not contained in the view.
*/
public int convertIndexToView(int modelIndex) {
return getRowSorter() != null ? getRowSorter().convertRowIndexToView(modelIndex) : modelIndex;
}
/**
* {@inheritDoc}
*
* Sets the underlying data model. Note that if isFilterEnabled you must
* call getWrappedModel to access the model given here. In this case
* getModel returns a wrapper around the data!
*
* @param model the data model for this list.
*/
@Override
public void setModel(ListModel model) {
super.setModel(model);
if (getAutoCreateRowSorter()) {
setRowSorter(createDefaultRowSorter());
}
}
// ---------------------------- uniform data model
/**
* @return the unconfigured ComponentAdapter.
*/
protected ComponentAdapter getComponentAdapter() {
if (dataAdapter == null) {
dataAdapter = new ListAdapter(this);
}
return dataAdapter;
}
/**
* Convenience to access a configured ComponentAdapter.
* Note: the column index of the configured adapter is always 0.
*
* @param index the row index in view coordinates, must be valid.
* @return the configured ComponentAdapter.
*/
protected ComponentAdapter getComponentAdapter(int index) {
ComponentAdapter adapter = getComponentAdapter();
adapter.column = 0;
adapter.row = index;
return adapter;
}
/**
* A component adapter targeted at a JXList.
*/
protected static class ListAdapter extends ComponentAdapter {
private final JXList> list;
/**
* Constructs a ListAdapter
for the specified target
* JXList.
*
* @param component the target list.
*/
public ListAdapter(JXList> component) {
super(component);
list = component;
}
/**
* Typesafe accessor for the target component.
*
* @return the target component as a {@link JXList}
*/
public JXList> getList() {
return list;
}
/**
* {@inheritDoc}
*/
@Override
public boolean hasFocus() {
/** TODO: Think through printing implications */
return list.isFocusOwner() && row == list.getLeadSelectionIndex();
}
/**
* {@inheritDoc}
*/
@Override
public int getRowCount() {
return list.getModel().getSize();
}
/**
* {@inheritDoc}
*/
@Override
public Object getValueAt(int row, int column) {
return list.getModel().getElementAt(row);
}
/**
* {@inheritDoc}
* This is implemented to query the table's StringValueRegistry for an appropriate
* StringValue and use that for getting the string representation.
*/
@Override
public String getStringAt(int row, int column) {
StringValue sv = list.getStringValueRegistry().getStringValue(row, column);
return sv.getString(getValueAt(row, column));
}
/**
* {@inheritDoc}
*/
@Override
public Rectangle getCellBounds() {
return list.getCellBounds(row, row);
}
/**
* {@inheritDoc}
*/
@Override
public boolean isCellEditable(int row, int column) {
return false;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isEditable() {
return false;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isSelected() {
/** TODO: Think through printing implications */
return list.isSelectedIndex(row);
}
/**
* {@inheritDoc}
*/
@Override
public int convertRowIndexToView(int rowModelIndex) {
return list.convertIndexToView(rowModelIndex);
}
/**
* {@inheritDoc}
*/
@Override
public int convertRowIndexToModel(int rowViewIndex) {
return list.convertIndexToModel(rowViewIndex);
}
}
// ------------------------------ renderers
/**
* Sets the Highlighter
s to the table, replacing any old settings.
* None of the given Highlighters must be null.
*
* This is a bound property.
*
* Note: as of version #1.257 the null constraint is enforced strictly. To remove
* all highlighters use this method without param.
*
* @param highlighters zero or more not null highlighters to use for renderer decoration.
* @throws NullPointerException if array is null or array contains null values.
* @see #getHighlighters()
* @see #addHighlighter(Highlighter)
* @see #removeHighlighter(Highlighter)
*/
public void setHighlighters(Highlighter... highlighters) {
Highlighter[] old = getHighlighters();
getCompoundHighlighter().setHighlighters(highlighters);
firePropertyChange("highlighters", old, getHighlighters());
}
/**
* Returns the Highlighter
s used by this table.
* Maybe empty, but guarantees to be never null.
*
* @return the Highlighters used by this table, guaranteed to never null.
* @see #setHighlighters(Highlighter[])
*/
public Highlighter[] getHighlighters() {
return getCompoundHighlighter().getHighlighters();
}
/**
* Appends a Highlighter
to the end of the list of used
* Highlighter
s. The argument must not be null.
*
*
* @param highlighter the Highlighter
to add, must not be null.
* @throws NullPointerException if Highlighter
is null.
* @see #removeHighlighter(Highlighter)
* @see #setHighlighters(Highlighter[])
*/
public void addHighlighter(Highlighter highlighter) {
Highlighter[] old = getHighlighters();
getCompoundHighlighter().addHighlighter(highlighter);
firePropertyChange("highlighters", old, getHighlighters());
}
/**
* Removes the given Highlighter.
*
* Does nothing if the Highlighter is not contained.
*
* @param highlighter the Highlighter to remove.
* @see #addHighlighter(Highlighter)
* @see #setHighlighters(Highlighter...)
*/
public void removeHighlighter(Highlighter highlighter) {
Highlighter[] old = getHighlighters();
getCompoundHighlighter().removeHighlighter(highlighter);
firePropertyChange("highlighters", old, getHighlighters());
}
/**
* Returns the CompoundHighlighter assigned to the table, null if none.
* PENDING: open up for subclasses again?.
*
* @return the CompoundHighlighter assigned to the table.
*/
protected CompoundHighlighter getCompoundHighlighter() {
if (compoundHighlighter == null) {
compoundHighlighter = new CompoundHighlighter();
compoundHighlighter.addChangeListener(getHighlighterChangeListener());
}
return compoundHighlighter;
}
/**
* Returns the ChangeListener
to use with highlighters. Lazily
* creates the listener.
*
* @return the ChangeListener for observing changes of highlighters,
* guaranteed to be not-null
*/
protected ChangeListener getHighlighterChangeListener() {
if (highlighterChangeListener == null) {
highlighterChangeListener = createHighlighterChangeListener();
}
return highlighterChangeListener;
}
/**
* Creates and returns the ChangeListener observing Highlighters.
*
* Here: repaints the table on receiving a stateChanged.
*
* @return the ChangeListener defining the reaction to changes of
* highlighters.
*/
protected ChangeListener createHighlighterChangeListener() {
return e -> repaint();
}
/**
* Returns the StringValueRegistry which defines the string representation for
* each cells. This is strictly for internal use by the table, which has the
* responsibility to keep in synch with registered renderers.
*
* Currently exposed for testing reasons, client code is recommended to not use nor override.
*
* @return the current string value registry
*/
protected StringValueRegistry getStringValueRegistry() {
if (stringValueRegistry == null) {
stringValueRegistry = createDefaultStringValueRegistry();
}
return stringValueRegistry;
}
/**
* Creates and returns the default registry for StringValues.
*
* @return the default registry for StringValues.
*/
protected StringValueRegistry createDefaultStringValueRegistry() {
return new StringValueRegistry();
}
/**
* Returns the string representation of the cell value at the given position.
*
* @param row the row index of the cell in view coordinates
* @return the string representation of the cell value as it will appear in the
* table.
*/
public String getStringAt(int row) {
// changed implementation to use StringValueRegistry
StringValue stringValue = getStringValueRegistry().getStringValue(convertIndexToModel(row), 0);
return stringValue.getString(getElementAt(row));
}
private DelegatingRenderer getDelegatingRenderer() {
if (delegatingRenderer == null) {
// only called once... to get hold of the default?
delegatingRenderer = new DelegatingRenderer();
}
return delegatingRenderer;
}
/**
* Creates and returns the default cell renderer to use. Subclasses
* may override to use a different type. Here: returns a DefaultListRenderer
.
*
* @return the default cell renderer to use with this list.
*/
protected ListCellRenderer super E> createDefaultCellRenderer() {
return new DefaultListRenderer();
}
/**
* {@inheritDoc}
*
* Overridden to return the delegating renderer which is wrapped around the
* original to support highlighting. The returned renderer is of type
* DelegatingRenderer and guaranteed to not-null
*
* @see #setCellRenderer(ListCellRenderer)
* @see DelegatingRenderer
*/
@Override
public ListCellRenderer super E> getCellRenderer() {
return getDelegatingRenderer();
}
/**
* Returns the renderer installed by client code or the default if none has
* been set.
*
* @return the wrapped renderer.
* @see #setCellRenderer(ListCellRenderer)
*/
public ListCellRenderer super E> getWrappedCellRenderer() {
return getDelegatingRenderer().getDelegateRenderer();
}
/**
* {@inheritDoc}
*
* Overridden to wrap the given renderer in a DelegatingRenderer to support
* highlighting.
*
* Note: the wrapping implies that the renderer returned from the getCellRenderer
* is not the renderer as given here, but the wrapper. To access the original,
* use getWrappedCellRenderer
.
*
* @see #getWrappedCellRenderer()
* @see #getCellRenderer()
*/
@Override
public void setCellRenderer(ListCellRenderer super E> renderer) {
// PENDING JW: super fires for very first setting
// as defaults are automagically set (by delegatingRenderer
// using this list's factory method) there is no
// easy way to _not_ force, this isn't working
// but then ... it's only the very first time around.
// Safe enough to wait for complaints ;-)
boolean forceFire = delegatingRenderer != null;
// JW: Pending - probably fires propertyChangeEvent with wrong newValue?
// how about fixedCellWidths?
// need to test!!
getDelegatingRenderer().setDelegateRenderer(renderer);
getStringValueRegistry().setStringValue(renderer instanceof StringValue ? (StringValue) renderer : null, 0);
super.setCellRenderer(delegatingRenderer);
if (forceFire)
firePropertyChange("cellRenderer", null, delegatingRenderer);
}
/**
* A decorator for the original ListCellRenderer. Needed to hook highlighters
* after messaging the delegate.
*
* PENDING JW: formally implement UIDependent?
*/
public class DelegatingRenderer implements ListCellRenderer, RolloverRenderer {
/**
* the delegate.
*/
private ListCellRenderer super E> delegateRenderer;
/**
* Instantiates a DelegatingRenderer with list's default renderer as delegate.
*/
public DelegatingRenderer() {
this(null);
}
/**
* Instantiates a DelegatingRenderer with the given delegate. If the
* delegate is null, the default is created via the list's factory method.
*
* @param delegate the delegate to use, if null the list's default is
* created and used.
*/
public DelegatingRenderer(ListCellRenderer super E> delegate) {
setDelegateRenderer(delegate);
}
/**
* Sets the delegate. If the
* delegate is null, the default is created via the list's factory method.
*
* @param delegate the delegate to use, if null the list's default is
* created and used.
*/
public void setDelegateRenderer(ListCellRenderer super E> delegate) {
if (delegate == null) {
delegate = createDefaultCellRenderer();
}
delegateRenderer = delegate;
}
/**
* Returns the delegate.
*
* @return the delegate renderer used by this renderer, guaranteed to
* not-null.
*/
public ListCellRenderer super E> getDelegateRenderer() {
return delegateRenderer;
}
/**
* Updates the ui of the delegate.
*/
public void updateUI() {
updateRendererUI(delegateRenderer);
}
/**
* @param renderer the renderer to update the ui of.
*/
private void updateRendererUI(ListCellRenderer super E> renderer) {
if (renderer == null)
return;
Component comp = null;
if (renderer instanceof AbstractRenderer) {
comp = ((AbstractRenderer) renderer).getComponentProvider().getRendererComponent(null);
} else if (renderer instanceof Component) {
comp = (Component) renderer;
} else {
try {
comp = renderer.getListCellRendererComponent(JXList.this, null, -1, false, false);
} catch (Exception e) {
// nothing to do - renderer barked on off-range row
}
}
if (comp != null) {
SwingUtilities.updateComponentTreeUI(comp);
}
}
// --------- implement ListCellRenderer
/**
* {@inheritDoc}
*
* Overridden to apply the highlighters, if any, after calling the delegate.
* The decorators are not applied if the row is invalid.
*/
@Override
public Component getListCellRendererComponent(JList extends E> list, E value, int index,
boolean isSelected, boolean cellHasFocus) {
Component comp = delegateRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (compoundHighlighter != null && index >= 0 && index < getElementCount()) {
comp = compoundHighlighter.highlight(comp, getComponentAdapter(index));
}
return comp;
}
// implement RolloverRenderer
/**
* {@inheritDoc}
*/
@Override
public boolean isEnabled() {
return delegateRenderer instanceof RolloverRenderer && ((RolloverRenderer) delegateRenderer).isEnabled();
}
/**
* {@inheritDoc}
*/
@Override
public void doClick() {
if (isEnabled()) {
((RolloverRenderer) delegateRenderer).doClick();
}
}
}
/**
* Invalidates cell size caching in the ui delegate. May do nothing if there's no
* safe (i.e. without reflection) way to message the delegate.
*
* This implementation calls the corresponding method on BasicXListUI if available,
* does nothing otherwise.
*/
public void invalidateCellSizeCache() {
if (getUI() instanceof BasicXListUI) {
((BasicXListUI) getUI()).invalidateCellSizeCache();
}
}
// --------------------------- updateUI
/**
* {@inheritDoc}
*
* Overridden to update renderer and Highlighters.
*/
@Override
public void updateUI() {
// PENDING JW: temporary during dev to quickly switch between default and custom ui
if (Objects.equals(getUIClassID(), super.getUIClassID())) {
super.updateUI();
} else {
setUI((ListUI) LookAndFeelAddons.getUI(this, ListUI.class));
}
updateRendererUI();
updateHighlighterUI();
}
@Override
public String getUIClassID() {
// PENDING JW: temporary during dev to quickly switch between default and custom ui
// return super.getUIClassID();
return uiClassID;
}
private void updateRendererUI() {
if (delegatingRenderer != null) {
delegatingRenderer.updateUI();
} else {
ListCellRenderer> renderer = getCellRenderer();
if (renderer instanceof Component) {
SwingUtilities.updateComponentTreeUI((Component) renderer);
}
}
}
/**
* Updates highlighter after updateUI
changes.
*
* @see org.jdesktop.swingx.plaf.UIDependent
*/
protected void updateHighlighterUI() {
if (compoundHighlighter == null)
return;
compoundHighlighter.updateUI();
}
}