0001: /*
0002: * $Id: JXList.java,v 1.51 2007/01/25 13:19:00 kleopatra Exp $
0003: *
0004: * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
0005: * Santa Clara, California 95054, U.S.A. All rights reserved.
0006: *
0007: * This library is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU Lesser General Public
0009: * License as published by the Free Software Foundation; either
0010: * version 2.1 of the License, or (at your option) any later version.
0011: *
0012: * This library is distributed in the hope that it will be useful,
0013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0015: * Lesser General Public License for more details.
0016: *
0017: * You should have received a copy of the GNU Lesser General Public
0018: * License along with this library; if not, write to the Free Software
0019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
0020: */
0021:
0022: package org.jdesktop.swingx;
0023:
0024: import java.awt.Component;
0025: import java.awt.Cursor;
0026: import java.awt.Point;
0027: import java.awt.Rectangle;
0028: import java.awt.event.ActionEvent;
0029: import java.util.Collections;
0030: import java.util.Comparator;
0031: import java.util.List;
0032: import java.util.Vector;
0033: import java.util.regex.Matcher;
0034: import java.util.regex.Pattern;
0035:
0036: import javax.swing.AbstractListModel;
0037: import javax.swing.Action;
0038: import javax.swing.DefaultListCellRenderer;
0039: import javax.swing.JComponent;
0040: import javax.swing.JList;
0041: import javax.swing.KeyStroke;
0042: import javax.swing.ListCellRenderer;
0043: import javax.swing.ListModel;
0044: import javax.swing.ListSelectionModel;
0045: import javax.swing.event.ChangeEvent;
0046: import javax.swing.event.ChangeListener;
0047: import javax.swing.event.ListDataEvent;
0048: import javax.swing.event.ListDataListener;
0049:
0050: import org.jdesktop.swingx.decorator.ComponentAdapter;
0051: import org.jdesktop.swingx.decorator.DefaultSelectionMapper;
0052: import org.jdesktop.swingx.decorator.FilterPipeline;
0053: import org.jdesktop.swingx.decorator.Highlighter;
0054: import org.jdesktop.swingx.decorator.HighlighterPipeline;
0055: import org.jdesktop.swingx.decorator.PipelineEvent;
0056: import org.jdesktop.swingx.decorator.PipelineListener;
0057: import org.jdesktop.swingx.decorator.SelectionMapper;
0058: import org.jdesktop.swingx.decorator.SortController;
0059: import org.jdesktop.swingx.decorator.SortKey;
0060: import org.jdesktop.swingx.decorator.SortOrder;
0061: import org.jdesktop.swingx.renderer.DefaultListRenderer;
0062:
0063: /**
0064: * JXList
0065: *
0066: * Enabled Rollover/LinkModel handling. Enabled Highlighter support.
0067: *
0068: * Added experimental support for filtering/sorting. This feature is disabled by
0069: * default because it has side-effects which might break "normal" expectations
0070: * when using a JList: if enabled all row coordinates (including those returned
0071: * by the selection) are in view coordinates. Furthermore, the model returned
0072: * from getModel() is a wrapper around the actual data.
0073: *
0074: *
0075: *
0076: * @author Ramesh Gupta
0077: * @author Jeanette Winzenburg
0078: */
0079: public class JXList extends JList {
0080: public static final String EXECUTE_BUTTON_ACTIONCOMMAND = "executeButtonAction";
0081:
0082: /** The pipeline holding the filters. */
0083: protected FilterPipeline filters;
0084:
0085: /**
0086: * The pipeline holding the highlighters.
0087: */
0088: protected HighlighterPipeline highlighters;
0089:
0090: /** listening to changeEvents from highlighterPipeline. */
0091: private ChangeListener highlighterChangeListener;
0092:
0093: /** The ComponentAdapter for model data access. */
0094: protected ComponentAdapter dataAdapter;
0095:
0096: /**
0097: * Mouse/Motion/Listener keeping track of mouse moved in cell coordinates.
0098: */
0099: private RolloverProducer rolloverProducer;
0100:
0101: /**
0102: * RolloverController: listens to cell over events and repaints
0103: * entered/exited rows.
0104: */
0105: private ListRolloverController linkController;
0106:
0107: /** A wrapper around the default renderer enabling decoration. */
0108: private DelegatingRenderer delegatingRenderer;
0109:
0110: private WrappingListModel wrappingModel;
0111:
0112: private PipelineListener pipelineListener;
0113:
0114: private boolean filterEnabled;
0115:
0116: private SelectionMapper selectionMapper;
0117:
0118: private Searchable searchable;
0119:
0120: private Comparator comparator;
0121:
0122: /**
0123: * Constructs a <code>JXList</code> with an empty model and filters disabled.
0124: *
0125: */
0126: public JXList() {
0127: this (false);
0128: }
0129:
0130: /**
0131: * Constructs a <code>JXList</code> that displays the elements in the
0132: * specified, non-<code>null</code> model and filters disabled.
0133: *
0134: * @param dataModel the data model for this list
0135: * @exception IllegalArgumentException if <code>dataModel</code>
0136: * is <code>null</code>
0137: */
0138: public JXList(ListModel dataModel) {
0139: this (dataModel, false);
0140: }
0141:
0142: /**
0143: * Constructs a <code>JXList</code> that displays the elements in
0144: * the specified array and filters disabled.
0145: *
0146: * @param listData the array of Objects to be loaded into the data model
0147: * @throws IllegalArgumentException if <code>listData</code>
0148: * is <code>null</code>
0149: */
0150: public JXList(Object[] listData) {
0151: this (listData, false);
0152: }
0153:
0154: /**
0155: * Constructs a <code>JXList</code> that displays the elements in
0156: * the specified <code>Vector</code> and filtes disabled.
0157: *
0158: * @param listData the <code>Vector</code> to be loaded into the
0159: * data model
0160: * @throws IllegalArgumentException if <code>listData</code>
0161: * is <code>null</code>
0162: */
0163: public JXList(Vector<?> listData) {
0164: this (listData, false);
0165: }
0166:
0167: /**
0168: * Constructs a <code>JXList</code> with an empty model and
0169: * filterEnabled property.
0170: *
0171: * @param filterEnabled <code>boolean</code> to determine if
0172: * filtering/sorting is enabled
0173: */
0174: public JXList(boolean filterEnabled) {
0175: init(filterEnabled);
0176: }
0177:
0178: /**
0179: * Constructs a <code>JXList</code> with the specified model and
0180: * filterEnabled property.
0181: *
0182: * @param dataModel the data model for this list
0183: * @param filterEnabled <code>boolean</code> to determine if
0184: * filtering/sorting is enabled
0185: * @throws IllegalArgumentException if <code>dataModel</code>
0186: * is <code>null</code>
0187: */
0188: public JXList(ListModel dataModel, boolean filterEnabled) {
0189: super (dataModel);
0190: init(filterEnabled);
0191: }
0192:
0193: /**
0194: * Constructs a <code>JXList</code> that displays the elements in
0195: * the specified array and filterEnabled property.
0196: *
0197: * @param listData the array of Objects to be loaded into the data model
0198: * @param filterEnabled <code>boolean</code> to determine if filtering/sorting
0199: * is enabled
0200: * @throws IllegalArgumentException if <code>listData</code>
0201: * is <code>null</code>
0202: */
0203: public JXList(Object[] listData, boolean filterEnabled) {
0204: super (listData);
0205: if (listData == null)
0206: throw new IllegalArgumentException(
0207: "listData must not be null");
0208: init(filterEnabled);
0209: }
0210:
0211: /**
0212: * Constructs a <code>JXList</code> that displays the elements in
0213: * the specified <code>Vector</code> and filtersEnabled property.
0214: *
0215: * @param listData the <code>Vector</code> to be loaded into the
0216: * data model
0217: * @param filterEnabled <code>boolean</code> to determine if filtering/sorting
0218: * is enabled
0219: * @throws IllegalArgumentException if <code>listData</code> is <code>null</code>
0220: */
0221: public JXList(Vector<?> listData, boolean filterEnabled) {
0222: super (listData);
0223: if (listData == null)
0224: throw new IllegalArgumentException(
0225: "listData must not be null");
0226: init(filterEnabled);
0227: }
0228:
0229: private void init(boolean filterEnabled) {
0230: setFilterEnabled(filterEnabled);
0231:
0232: Action findAction = createFindAction();
0233: getActionMap().put("find", findAction);
0234:
0235: KeyStroke findStroke = SearchFactory.getInstance()
0236: .getSearchAccelerator();
0237: getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
0238: findStroke, "find");
0239:
0240: }
0241:
0242: private Action createFindAction() {
0243: Action findAction = new UIAction("find") {
0244:
0245: public void actionPerformed(ActionEvent e) {
0246: doFind();
0247:
0248: }
0249:
0250: };
0251: return findAction;
0252: }
0253:
0254: protected void doFind() {
0255: SearchFactory.getInstance()
0256: .showFindInput(this , getSearchable());
0257:
0258: }
0259:
0260: /**
0261: *
0262: * @return a not-null Searchable for this editor.
0263: */
0264: public Searchable getSearchable() {
0265: if (searchable == null) {
0266: searchable = new ListSearchable();
0267: }
0268: return searchable;
0269: }
0270:
0271: /**
0272: * sets the Searchable for this editor. If null, a default
0273: * searchable will be used.
0274: *
0275: * @param searchable
0276: */
0277: public void setSearchable(Searchable searchable) {
0278: this .searchable = searchable;
0279: }
0280:
0281: public class ListSearchable extends AbstractSearchable {
0282:
0283: @Override
0284: protected void findMatchAndUpdateState(Pattern pattern,
0285: int startRow, boolean backwards) {
0286: SearchResult searchResult = null;
0287: if (backwards) {
0288: for (int index = startRow; index >= 0
0289: && searchResult == null; index--) {
0290: searchResult = findMatchAt(pattern, index);
0291: }
0292: } else {
0293: for (int index = startRow; index < getSize()
0294: && searchResult == null; index++) {
0295: searchResult = findMatchAt(pattern, index);
0296: }
0297: }
0298: updateState(searchResult);
0299: }
0300:
0301: @Override
0302: protected SearchResult findExtendedMatch(Pattern pattern,
0303: int row) {
0304:
0305: return findMatchAt(pattern, row);
0306: }
0307:
0308: /**
0309: * Matches the cell content at row/col against the given Pattern.
0310: * Returns an appropriate SearchResult if matching or null if no
0311: * matching
0312: *
0313: * @param pattern
0314: * @param row a valid row index in view coordinates
0315: * @return <code>SearchResult</code> if matched otherwise null
0316: */
0317: protected SearchResult findMatchAt(Pattern pattern, int row) {
0318: Object value = getElementAt(row);
0319: if (value != null) {
0320: Matcher matcher = pattern.matcher(value.toString());
0321: if (matcher.find()) {
0322: return createSearchResult(matcher, row, -1);
0323: }
0324: }
0325: return null;
0326: }
0327:
0328: @Override
0329: protected int getSize() {
0330: return getElementCount();
0331: }
0332:
0333: @Override
0334: protected void moveMatchMarker() {
0335: setSelectedIndex(lastSearchResult.foundRow);
0336: if (lastSearchResult.foundRow >= 0) {
0337: ensureIndexIsVisible(lastSearchResult.foundRow);
0338: }
0339:
0340: }
0341:
0342: }
0343:
0344: /**
0345: * Property to enable/disable rollover support. This can be enabled to show
0346: * "live" rollover behaviour, f.i. the cursor over LinkModel cells. Default
0347: * is disabled.
0348: *
0349: * @param rolloverEnabled
0350: */
0351: public void setRolloverEnabled(boolean rolloverEnabled) {
0352: boolean old = isRolloverEnabled();
0353: if (rolloverEnabled == old)
0354: return;
0355: if (rolloverEnabled) {
0356: rolloverProducer = createRolloverProducer();
0357: addMouseListener(rolloverProducer);
0358: addMouseMotionListener(rolloverProducer);
0359: getLinkController().install(this );
0360: } else {
0361: removeMouseListener(rolloverProducer);
0362: removeMouseMotionListener(rolloverProducer);
0363: rolloverProducer = null;
0364: getLinkController().release();
0365: }
0366: firePropertyChange("rolloverEnabled", old, isRolloverEnabled());
0367: }
0368:
0369: protected ListRolloverController getLinkController() {
0370: if (linkController == null) {
0371: linkController = createLinkController();
0372: }
0373: return linkController;
0374: }
0375:
0376: protected ListRolloverController createLinkController() {
0377: return new ListRolloverController();
0378: }
0379:
0380: /**
0381: * creates and returns the RolloverProducer to use with this tree.
0382: *
0383: * @return <code>RolloverProducer</code> to use with this tree
0384: */
0385: protected RolloverProducer createRolloverProducer() {
0386: RolloverProducer r = new RolloverProducer() {
0387: protected void updateRolloverPoint(JComponent component,
0388: Point mousePoint) {
0389: JList list = (JList) component;
0390: int row = list.locationToIndex(mousePoint);
0391: if (row >= 0) {
0392: Rectangle cellBounds = list.getCellBounds(row, row);
0393: if (!cellBounds.contains(mousePoint)) {
0394: row = -1;
0395: }
0396: }
0397: int col = row < 0 ? -1 : 0;
0398: rollover.x = col;
0399: rollover.y = row;
0400: }
0401:
0402: };
0403: return r;
0404: }
0405:
0406: /**
0407: * returns the rolloverEnabled property.
0408: *
0409: * TODO: why doesn't this just return rolloverEnabled???
0410: *
0411: * @return true if rollover is enabled
0412: */
0413: public boolean isRolloverEnabled() {
0414: return rolloverProducer != null;
0415: }
0416:
0417: /**
0418: * listens to rollover properties. Repaints effected component regions.
0419: * Updates link cursor.
0420: *
0421: * @author Jeanette Winzenburg
0422: */
0423: public static class ListRolloverController<T extends JList> extends
0424: RolloverController<T> {
0425:
0426: private Cursor oldCursor;
0427:
0428: // --------------------------------- JList rollover
0429:
0430: protected void rollover(Point oldLocation, Point newLocation) {
0431: setRolloverCursor(newLocation);
0432: // JW: partial repaints incomplete
0433: component.repaint();
0434: }
0435:
0436: /**
0437: * something weird: cursor in JList behaves different from JTable?
0438: *
0439: * @param location
0440: */
0441: private void setRolloverCursor(Point location) {
0442: if (hasRollover(location)) {
0443: oldCursor = component.getCursor();
0444: component.setCursor(Cursor
0445: .getPredefinedCursor(Cursor.HAND_CURSOR));
0446: } else {
0447: component.setCursor(oldCursor);
0448: oldCursor = null;
0449: }
0450:
0451: }
0452:
0453: protected RolloverRenderer getRolloverRenderer(Point location,
0454: boolean prepare) {
0455: ListCellRenderer renderer = component.getCellRenderer();
0456: RolloverRenderer rollover = renderer instanceof RolloverRenderer ? (RolloverRenderer) renderer
0457: : null;
0458: if ((rollover != null) && !rollover.isEnabled()) {
0459: rollover = null;
0460: }
0461: if ((rollover != null) && prepare) {
0462: Object element = component.getModel().getElementAt(
0463: location.y);
0464: renderer.getListCellRendererComponent(component,
0465: element, location.y, false, true);
0466: }
0467: return rollover;
0468: }
0469:
0470: @Override
0471: protected Point getFocusedCell() {
0472: int leadRow = component.getLeadSelectionIndex();
0473: if (leadRow < 0)
0474: return null;
0475: return new Point(0, leadRow);
0476: }
0477:
0478: }
0479:
0480: //--------------------- public sort api
0481: // /**
0482: // * Returns the sortable property.
0483: // * Here: same as filterEnabled.
0484: // * @return true if the table is sortable.
0485: // */
0486: // public boolean isSortable() {
0487: // return isFilterEnabled();
0488: // }
0489: /**
0490: * Removes the interactive sorter.
0491: *
0492: */
0493: public void resetSortOrder() {
0494: SortController controller = getSortController();
0495: if (controller != null) {
0496: controller.setSortKeys(null);
0497: }
0498: }
0499:
0500: /**
0501: *
0502: * Toggles the sort order of the items.
0503: * <p>
0504: * The exact behaviour is defined by the SortController's
0505: * toggleSortOrder implementation. Typically a unsorted
0506: * column is sorted in ascending order, a sorted column's
0507: * order is reversed.
0508: * <p>
0509: * PENDING: where to get the comparator from?
0510: * <p>
0511: *
0512: *
0513: */
0514: public void toggleSortOrder() {
0515: SortController controller = getSortController();
0516: if (controller != null) {
0517: controller.toggleSortOrder(0, getComparator());
0518: }
0519: }
0520:
0521: /**
0522: * Sorts the list using SortOrder.
0523: *
0524: *
0525: * Respects the JXList's sortable and comparator
0526: * properties: routes the comparator to the SortController
0527: * and does nothing if !isFilterEnabled().
0528: * <p>
0529: *
0530: * @param sortOrder the sort order to use. If null or SortOrder.UNSORTED,
0531: * this method has the same effect as resetSortOrder();
0532: *
0533: */
0534: public void setSortOrder(SortOrder sortOrder) {
0535: if ((sortOrder == null) || !sortOrder.isSorted()) {
0536: resetSortOrder();
0537: return;
0538: }
0539: SortController sortController = getSortController();
0540: if (sortController != null) {
0541: SortKey sortKey = new SortKey(sortOrder, 0, getComparator());
0542: sortController.setSortKeys(Collections
0543: .singletonList(sortKey));
0544: }
0545: }
0546:
0547: /**
0548: * Returns the SortOrder.
0549: *
0550: * @return the interactive sorter's SortOrder
0551: * or SortOrder.UNSORTED
0552: */
0553: public SortOrder getSortOrder() {
0554: SortController sortController = getSortController();
0555: if (sortController == null)
0556: return SortOrder.UNSORTED;
0557: SortKey sortKey = SortKey.getFirstSortKeyForColumn(
0558: sortController.getSortKeys(), 0);
0559: return sortKey != null ? sortKey.getSortOrder()
0560: : SortOrder.UNSORTED;
0561: }
0562:
0563: /**
0564: *
0565: * @return the comparator used.
0566: * @see #setComparator(Comparator)
0567: */
0568: public Comparator getComparator() {
0569: return comparator;
0570: }
0571:
0572: /**
0573: * Sets the comparator used. As a side-effect, the
0574: * current sort might be updated. The exact behaviour
0575: * is defined in #updateSortAfterComparatorChange.
0576: *
0577: * @param comparator the comparator to use.
0578: */
0579: public void setComparator(Comparator comparator) {
0580: Comparator old = getComparator();
0581: this .comparator = comparator;
0582: updateSortAfterComparatorChange();
0583: firePropertyChange("comparator", old, getComparator());
0584: }
0585:
0586: /**
0587: * Updates sort after comparator has changed.
0588: * Here: sets the current sortOrder with the new comparator.
0589: *
0590: */
0591: protected void updateSortAfterComparatorChange() {
0592: setSortOrder(getSortOrder());
0593:
0594: }
0595:
0596: /**
0597: * returns the currently active SortController. Will be null if
0598: * !isFilterEnabled().
0599: * @return the currently active <code>SortController</code> may be null
0600: */
0601: protected SortController getSortController() {
0602: // // this check is for the sake of the very first call after instantiation
0603: // doesn't apply for JXList? need to test for filterEnabled?
0604: //if (filters == null) return null;
0605: if (!isFilterEnabled())
0606: return null;
0607: return getFilters().getSortController();
0608: }
0609:
0610: // ---------------------------- filters
0611:
0612: /**
0613: * returns the element at the given index. The index is in view coordinates
0614: * which might differ from model coordinates if filtering is enabled and
0615: * filters/sorters are active.
0616: *
0617: * @param viewIndex the index in view coordinates
0618: * @return the element at the index
0619: * @throws IndexOutOfBoundsException if viewIndex < 0 or viewIndex >=
0620: * getElementCount()
0621: */
0622: public Object getElementAt(int viewIndex) {
0623: return getModel().getElementAt(viewIndex);
0624: }
0625:
0626: /**
0627: * Returns the number of elements in this list in view
0628: * coordinates. If filters are active this number might be
0629: * less than the number of elements in the underlying model.
0630: *
0631: * @return number of elements in this list in view coordinates
0632: */
0633: public int getElementCount() {
0634: return getModel().getSize();
0635: }
0636:
0637: /**
0638: * Convert row index from view coordinates to model coordinates accounting
0639: * for the presence of sorters and filters.
0640: *
0641: * @param viewIndex index in view coordinates
0642: * @return index in model coordinates
0643: * @throws IndexOutOfBoundsException if viewIndex < 0 or viewIndex >= getElementCount()
0644: */
0645: public int convertIndexToModel(int viewIndex) {
0646: return isFilterEnabled() ? getFilters().convertRowIndexToModel(
0647: viewIndex) : viewIndex;
0648: }
0649:
0650: /**
0651: * Convert index from model coordinates to view coordinates accounting
0652: * for the presence of sorters and filters.
0653: *
0654: * PENDING Filter guards against out of range - should not?
0655: *
0656: * @param modelIndex index in model coordinates
0657: * @return index in view coordinates if the model index maps to a view coordinate
0658: * or -1 if not contained in the view.
0659: *
0660: */
0661: public int convertIndexToView(int modelIndex) {
0662: return isFilterEnabled() ? getFilters().convertRowIndexToView(
0663: modelIndex) : modelIndex;
0664: }
0665:
0666: /**
0667: * returns the underlying model. If !isFilterEnabled this will be the same
0668: * as getModel().
0669: *
0670: * @return the underlying model
0671: */
0672: public ListModel getWrappedModel() {
0673: return isFilterEnabled() ? wrappingModel.getModel()
0674: : getModel();
0675: }
0676:
0677: /**
0678: * Enables/disables filtering support. If enabled all row indices -
0679: * including the selection - are in view coordinates and getModel returns a
0680: * wrapper around the underlying model.
0681: *
0682: * Note: as an implementation side-effect calling this method clears the
0683: * selection (done in super.setModel).
0684: *
0685: * PENDING: cleanup state transitions!! - currently this can be safely
0686: * applied once only to enable. Internal state is inconsistent if trying to
0687: * disable again. As a temporary emergency measure, this will throw a
0688: * IllegalStateException.
0689: *
0690: * see Issue #2-swinglabs.
0691: *
0692: * @param enabled
0693: * @throws IllegalStateException if trying to disable again.
0694: */
0695: public void setFilterEnabled(boolean enabled) {
0696: boolean old = isFilterEnabled();
0697: if (old == enabled)
0698: return;
0699: if (old == true)
0700: throw new IllegalStateException(
0701: "must not reset filterEnabled");
0702: // JW: filterEnabled must be set before calling super.setModel!
0703: filterEnabled = enabled;
0704: if (!old) {
0705: wrappingModel = new WrappingListModel(getModel());
0706: super .setModel(wrappingModel);
0707: } else {
0708: ListModel model = wrappingModel.getModel();
0709: wrappingModel = null;
0710: super .setModel(model);
0711: }
0712:
0713: }
0714:
0715: /**
0716: *
0717: * @return a <boolean> indicating if filtering is enabled.
0718: * @see #setFilterEnabled(boolean)
0719: */
0720: public boolean isFilterEnabled() {
0721: return filterEnabled;
0722: }
0723:
0724: /**
0725: * Overridden to update selectionMapper
0726: */
0727: @Override
0728: public void setSelectionModel(ListSelectionModel newModel) {
0729: super .setSelectionModel(newModel);
0730: getSelectionMapper().setViewSelectionModel(getSelectionModel());
0731: }
0732:
0733: /**
0734: * set's the underlying data model. Note that if isFilterEnabled you must
0735: * call getWrappedModel to access the model given here. In this case
0736: * getModel returns a wrapper around the data!
0737: *
0738: *
0739: *
0740: */
0741: @Override
0742: public void setModel(ListModel model) {
0743: if (isFilterEnabled()) {
0744: wrappingModel.setModel(model);
0745: } else {
0746: super .setModel(model);
0747: }
0748: }
0749:
0750: /**
0751: * widened access for testing...
0752: * @return the selection mapper
0753: */
0754: protected SelectionMapper getSelectionMapper() {
0755: if (selectionMapper == null) {
0756: selectionMapper = new DefaultSelectionMapper(filters,
0757: getSelectionModel());
0758: }
0759: return selectionMapper;
0760: }
0761:
0762: /**
0763: *
0764: * @return the <code>FilterPipeline</code> assigned to this list, or
0765: * null if !isFiltersEnabled().
0766: */
0767: public FilterPipeline getFilters() {
0768: if ((filters == null) && isFilterEnabled()) {
0769: setFilters(null);
0770: }
0771: return filters;
0772: }
0773:
0774: /** Sets the FilterPipeline for filtering the items of this list, maybe null
0775: * to remove all previously applied filters.
0776: *
0777: * Note: the current "interactive" sortState is preserved (by
0778: * internally copying the old sortKeys to the new pipeline, if any).
0779: *
0780: * PRE: isFilterEnabled()
0781: *
0782: * @param pipeline the <code>FilterPipeline</code> to use, null removes
0783: * all filters.
0784: * @throws IllegalStateException if !isFilterEnabled()
0785: */
0786: public void setFilters(FilterPipeline pipeline) {
0787: if (!isFilterEnabled())
0788: throw new IllegalStateException(
0789: "filters not enabled - not allowed to set filters");
0790:
0791: FilterPipeline old = filters;
0792: List<? extends SortKey> sortKeys = null;
0793: if (old != null) {
0794: old.removePipelineListener(pipelineListener);
0795: sortKeys = old.getSortController().getSortKeys();
0796: }
0797: if (pipeline == null) {
0798: pipeline = new FilterPipeline();
0799: }
0800: filters = pipeline;
0801: filters.getSortController().setSortKeys(sortKeys);
0802: // JW: first assign to prevent (short?) illegal internal state
0803: // #173-swingx
0804: use(filters);
0805: getSelectionMapper().setFilters(filters);
0806:
0807: }
0808:
0809: /**
0810: * setModel() and setFilters() may be called in either order.
0811: *
0812: * @param pipeline
0813: */
0814: private void use(FilterPipeline pipeline) {
0815: if (pipeline != null) {
0816: // check JW: adding listener multiple times (after setModel)?
0817: if (initialUse(pipeline)) {
0818: pipeline
0819: .addPipelineListener(getFilterPipelineListener());
0820: pipeline.assign(getComponentAdapter());
0821: } else {
0822: pipeline.flush();
0823: }
0824: }
0825: }
0826:
0827: /**
0828: * @return true is not yet used in this JXTable, false otherwise
0829: */
0830: private boolean initialUse(FilterPipeline pipeline) {
0831: if (pipelineListener == null)
0832: return true;
0833: PipelineListener[] l = pipeline.getPipelineListeners();
0834: for (int i = 0; i < l.length; i++) {
0835: if (pipelineListener.equals(l[i]))
0836: return false;
0837: }
0838: return true;
0839: }
0840:
0841: /** returns the listener for changes in filters. */
0842: protected PipelineListener getFilterPipelineListener() {
0843: if (pipelineListener == null) {
0844: pipelineListener = createPipelineListener();
0845: }
0846: return pipelineListener;
0847: }
0848:
0849: /** creates the listener for changes in filters. */
0850: protected PipelineListener createPipelineListener() {
0851: PipelineListener l = new PipelineListener() {
0852: public void contentsChanged(PipelineEvent e) {
0853: updateOnFilterContentChanged();
0854: }
0855: };
0856: return l;
0857: }
0858:
0859: /**
0860: * method called on change notification from filterpipeline.
0861: */
0862: protected void updateOnFilterContentChanged() {
0863: // make the wrapper listen to the pipeline?
0864: if (wrappingModel != null) {
0865: wrappingModel.updateOnFilterContentChanged();
0866: }
0867: revalidate();
0868: repaint();
0869: }
0870:
0871: private class WrappingListModel extends AbstractListModel {
0872:
0873: private ListModel delegate;
0874:
0875: private ListDataListener listDataListener;
0876:
0877: public WrappingListModel(ListModel model) {
0878: setModel(model);
0879: }
0880:
0881: public void updateOnFilterContentChanged() {
0882: fireContentsChanged(this , -1, -1);
0883:
0884: }
0885:
0886: public void setModel(ListModel model) {
0887: ListModel old = this .getModel();
0888: if (old != null) {
0889: old.removeListDataListener(listDataListener);
0890: }
0891: this .delegate = model;
0892: delegate.addListDataListener(getListDataListener());
0893: fireContentsChanged(this , -1, -1);
0894: }
0895:
0896: private ListDataListener getListDataListener() {
0897: if (listDataListener == null) {
0898: listDataListener = createListDataListener();
0899: }
0900: return listDataListener;
0901: }
0902:
0903: private ListDataListener createListDataListener() {
0904: ListDataListener l = new ListDataListener() {
0905:
0906: public void intervalAdded(ListDataEvent e) {
0907: contentsChanged(e);
0908:
0909: }
0910:
0911: public void intervalRemoved(ListDataEvent e) {
0912: contentsChanged(e);
0913:
0914: }
0915:
0916: public void contentsChanged(ListDataEvent e) {
0917: boolean wasEnabled = getSelectionMapper()
0918: .isEnabled();
0919: getSelectionMapper().setEnabled(false);
0920: try {
0921: fireContentsChanged(this , -1, -1);
0922: updateSelection(e);
0923: } finally {
0924: getSelectionMapper().setEnabled(wasEnabled);
0925: }
0926: getFilters().flush();
0927: }
0928:
0929: };
0930: return l;
0931: }
0932:
0933: protected void updateSelection(ListDataEvent e) {
0934: if (e.getType() == ListDataEvent.INTERVAL_REMOVED) {
0935: getSelectionMapper().removeIndexInterval(e.getIndex0(),
0936: e.getIndex1());
0937: } else if (e.getType() == ListDataEvent.INTERVAL_ADDED) {
0938:
0939: int minIndex = Math.min(e.getIndex0(), e.getIndex1());
0940: int maxIndex = Math.max(e.getIndex0(), e.getIndex1());
0941: int length = maxIndex - minIndex + 1;
0942: getSelectionMapper().insertIndexInterval(minIndex,
0943: length, true);
0944: } else {
0945: getSelectionMapper().clearModelSelection();
0946: }
0947:
0948: }
0949:
0950: public ListModel getModel() {
0951: return delegate;
0952: }
0953:
0954: public int getSize() {
0955: return getFilters().getOutputSize();
0956: }
0957:
0958: public Object getElementAt(int index) {
0959: return getFilters().getValueAt(index, 0);
0960: }
0961:
0962: }
0963:
0964: // ---------------------------- uniform data model
0965:
0966: protected ComponentAdapter getComponentAdapter() {
0967: if (dataAdapter == null) {
0968: dataAdapter = new ListAdapter(this );
0969: }
0970: return dataAdapter;
0971: }
0972:
0973: protected static class ListAdapter extends ComponentAdapter {
0974: private final JXList list;
0975:
0976: /**
0977: * Constructs a <code>ListDataAdapter</code> for the specified target
0978: * component.
0979: *
0980: * @param component
0981: * the target component
0982: */
0983: public ListAdapter(JXList component) {
0984: super (component);
0985: list = component;
0986: }
0987:
0988: /**
0989: * Typesafe accessor for the target component.
0990: *
0991: * @return the target component as a {@link org.jdesktop.swingx.JXList}
0992: */
0993: public JXList getList() {
0994: return list;
0995: }
0996:
0997: /**
0998: * {@inheritDoc}
0999: */
1000: public boolean hasFocus() {
1001: /** TODO: Think through printing implications */
1002: return list.isFocusOwner()
1003: && (row == list.getLeadSelectionIndex());
1004: }
1005:
1006: @Override
1007: public int getRowCount() {
1008: return list.getWrappedModel().getSize();
1009: }
1010:
1011: /**
1012: * {@inheritDoc} <p>
1013: * Overridden to return value at implicit view coordinates.
1014: */
1015: @Override
1016: public Object getValue() {
1017: return list.getElementAt(row);
1018: }
1019:
1020: /**
1021: * {@inheritDoc}
1022: */
1023: public Object getValueAt(int row, int column) {
1024: return list.getWrappedModel().getElementAt(row);
1025: }
1026:
1027: public Object getFilteredValueAt(int row, int column) {
1028: return list.getElementAt(row);
1029: }
1030:
1031: public void setValueAt(Object aValue, int row, int column) {
1032: throw new UnsupportedOperationException(
1033: "Method getFilteredValueAt() not yet implemented.");
1034: }
1035:
1036: public boolean isCellEditable(int row, int column) {
1037: return false;
1038: }
1039:
1040: /**
1041: * {@inheritDoc}
1042: */
1043: public boolean isSelected() {
1044: /** TODO: Think through printing implications */
1045: return list.isSelectedIndex(row);
1046: }
1047:
1048: public String getColumnName(int columnIndex) {
1049: return "Column_" + columnIndex;
1050: }
1051:
1052: public String getColumnIdentifier(int columnIndex) {
1053: return null;
1054: }
1055:
1056: }
1057:
1058: // ------------------------------ renderers
1059:
1060: /**
1061: * @return the HighlighterPipeline assigned to the list.
1062: * @see #setHighlighters(HighlighterPipeline)
1063: */
1064: public HighlighterPipeline getHighlighters() {
1065: return highlighters;
1066: }
1067:
1068: /**
1069: * Assigns a HighlighterPipeline to the list. This is a bound property.
1070: *
1071: * @param pipeline the HighlighterPipeline to use for renderer
1072: * decoration, maybe null to remove all Highlighters.
1073: */
1074: public void setHighlighters(HighlighterPipeline pipeline) {
1075: HighlighterPipeline old = getHighlighters();
1076: if (old != null) {
1077: old.removeChangeListener(getHighlighterChangeListener());
1078: }
1079: highlighters = pipeline;
1080: if (highlighters != null) {
1081: highlighters
1082: .addChangeListener(getHighlighterChangeListener());
1083: }
1084: firePropertyChange("highlighters", old, getHighlighters());
1085: }
1086:
1087: /**
1088: * Sets the <code>Highlighter</code>s to the list, replacing any old settings.
1089: * May be null to remove all highlighters.<p>
1090: *
1091: *
1092: * @param highlighters the highlighters to use for renderer decoration.
1093: * @see #getHighlighters()
1094: * @see #addHighlighter(Highlighter)
1095: * @see #removeHighlighter(Highlighter)
1096: *
1097: */
1098: public void setHighlighters(Highlighter... highlighters) {
1099: HighlighterPipeline pipeline = null;
1100: if ((highlighters != null) && (highlighters.length > 0)
1101: && (highlighters[0] != null)) {
1102: pipeline = new HighlighterPipeline(highlighters);
1103: }
1104: setHighlighters(pipeline);
1105: }
1106:
1107: /**
1108: * Adds a Highlighter.
1109: *
1110: * If the HighlighterPipeline returned from getHighlighters() is null, creates
1111: * and sets a new pipeline containing the given Highlighter. Else, appends
1112: * the Highlighter to the end of the pipeline.
1113: *
1114: * @param highlighter the Highlighter to add - must not be null.
1115: * @throws NullPointerException if highlighter is null.
1116: */
1117: public void addHighlighter(Highlighter highlighter) {
1118: HighlighterPipeline pipeline = getHighlighters();
1119: if (pipeline == null) {
1120: setHighlighters(new HighlighterPipeline(
1121: new Highlighter[] { highlighter }));
1122: } else {
1123: pipeline.addHighlighter(highlighter);
1124: }
1125: }
1126:
1127: /**
1128: * Removes the Highlighter.
1129: *
1130: * Does nothing if the HighlighterPipeline is null or does not contain
1131: * the given Highlighter.
1132: *
1133: * @param highlighter the Highlighter to remove.
1134: */
1135: public void removeHighlighter(Highlighter highlighter) {
1136: if ((getHighlighters() == null))
1137: return;
1138: getHighlighters().removeHighlighter(highlighter);
1139: }
1140:
1141: /**
1142: * returns the ChangeListener to use with highlighters. Creates one if
1143: * necessary.
1144: *
1145: * @return != null
1146: */
1147: protected ChangeListener getHighlighterChangeListener() {
1148: if (highlighterChangeListener == null) {
1149: highlighterChangeListener = new ChangeListener() {
1150:
1151: public void stateChanged(ChangeEvent e) {
1152: repaint();
1153: }
1154:
1155: };
1156: }
1157: return highlighterChangeListener;
1158: }
1159:
1160: private DelegatingRenderer getDelegatingRenderer() {
1161: if (delegatingRenderer == null) {
1162: // only called once... to get hold of the default?
1163: delegatingRenderer = new DelegatingRenderer(
1164: createDefaultCellRenderer());
1165: }
1166: return delegatingRenderer;
1167: }
1168:
1169: /**
1170: * Creates and returns the default cell renderer to use. Subclasses
1171: * may override to use a different type. Here: returns a <code>DefaultListRenderer</code>.
1172: *
1173: * @return the default cell renderer to use with this list.
1174: */
1175: protected ListCellRenderer createDefaultCellRenderer() {
1176: return new DefaultListRenderer();
1177: }
1178:
1179: @Override
1180: public ListCellRenderer getCellRenderer() {
1181: return getDelegatingRenderer();
1182: }
1183:
1184: @Override
1185: public void setCellRenderer(ListCellRenderer renderer) {
1186: // JW: Pending - probably fires propertyChangeEvent with wrong newValue?
1187: // how about fixedCellWidths?
1188: // need to test!!
1189: getDelegatingRenderer().setDelegateRenderer(renderer);
1190: super .setCellRenderer(delegatingRenderer);
1191: }
1192:
1193: public class DelegatingRenderer implements ListCellRenderer,
1194: RolloverRenderer {
1195:
1196: private ListCellRenderer delegateRenderer;
1197:
1198: public DelegatingRenderer(ListCellRenderer delegate) {
1199: setDelegateRenderer(delegate);
1200: }
1201:
1202: public void setDelegateRenderer(ListCellRenderer delegate) {
1203: if (delegate == null) {
1204: delegate = new DefaultListCellRenderer();
1205: }
1206: delegateRenderer = delegate;
1207: }
1208:
1209: public ListCellRenderer getDelegateRenderer() {
1210: return delegateRenderer;
1211: }
1212:
1213: public boolean isEnabled() {
1214: return (delegateRenderer instanceof RolloverRenderer)
1215: && ((RolloverRenderer) delegateRenderer)
1216: .isEnabled();
1217: }
1218:
1219: public void doClick() {
1220: if (isEnabled()) {
1221: ((RolloverRenderer) delegateRenderer).doClick();
1222: }
1223: }
1224:
1225: public Component getListCellRendererComponent(JList list,
1226: Object value, int index, boolean isSelected,
1227: boolean cellHasFocus) {
1228: Component comp = null;
1229:
1230: comp = delegateRenderer.getListCellRendererComponent(list,
1231: value, index, isSelected, cellHasFocus);
1232: if (highlighters != null) {
1233: ComponentAdapter adapter = getComponentAdapter();
1234: adapter.column = 0;
1235: adapter.row = index;
1236: comp = highlighters.apply(comp, adapter);
1237: }
1238: return comp;
1239: }
1240:
1241: public void updateUI() {
1242: updateRendererUI(delegateRenderer);
1243: }
1244:
1245: private void updateRendererUI(ListCellRenderer renderer) {
1246: if (renderer instanceof JComponent) {
1247: ((JComponent) renderer).updateUI();
1248: } else if (renderer != null) {
1249: Component comp = renderer.getListCellRendererComponent(
1250: JXList.this , null, -1, false, false);
1251: if (comp instanceof JComponent) {
1252: ((JComponent) comp).updateUI();
1253: }
1254: }
1255:
1256: }
1257:
1258: }
1259:
1260: // --------------------------- updateUI
1261:
1262: @Override
1263: public void updateUI() {
1264: super .updateUI();
1265: updateRendererUI();
1266: }
1267:
1268: private void updateRendererUI() {
1269: if (delegatingRenderer != null) {
1270: delegatingRenderer.updateUI();
1271: } else {
1272: ListCellRenderer renderer = getCellRenderer();
1273: if (renderer instanceof JComponent) {
1274: ((JComponent) renderer).updateUI();
1275: }
1276: }
1277: }
1278:
1279: }
|