baseList, E element);
} libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/migrationkit/ 0000755 0001750 0001750 00000000000 12106516376 026643 5 ustar gregoa gregoa libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/migrationkit/AbstractFilterList.java0000644 0001750 0001750 00000016200 12106516376 033252 0 ustar gregoa gregoa /* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.migrationkit;
// the core Glazed Lists packages
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.FilterList;
import ca.odell.glazedlists.TransformedList;
import ca.odell.glazedlists.event.ListEvent;
import ca.odell.glazedlists.matchers.AbstractMatcherEditor;
import ca.odell.glazedlists.matchers.Matcher;
import ca.odell.glazedlists.matchers.MatcherEditor;
/**
* An {@link EventList} that shows a subset of the elements of a source
* {@link EventList}. This subset is composed of all elements of the source
* {@link EventList} that match the filter.
*
* The filter can be static or dynamic. Changing the behaviour of the filter
* will change which elements of the source list are included.
*
*
Extending classes define the filter by implementing the method
* {@link #filterMatches(Object)}.
*
*
Extending classes must call {@link #handleFilterChanged()} when the filter
* has changed in order to update the subset of included elements. This method
* must also be called at the end of the extending class's constructor.
*
*
Warning: This class
* breaks the contract required by {@link java.util.List}. See {@link EventList}
* for an example.
*
*
Warning: This class is
* thread ready but not thread safe. See {@link EventList} for an example
* of thread safe code.
*
*
* | EventList Overview |
* | Writable: | yes |
* | Concurrency: | thread ready, not thread safe |
* | Performance: | reads: O(log N), writes O(log N), filter changes O(N) |
* | Memory: | 0 to 26 bytes per element |
* | Unit Tests: | N/A |
* | Issues: | N/A |
*
*
* @deprecated This class uses inheritance when composition is preferrable. By replacing
* the overriding method {@link #filterMatches(Object)} with a {@link Matcher} or
* {@link MatcherEditor}, logic can be reused. That approach is far more flexible
* and powerful than the static filtering required by AbstractFilterList.
*
* @since 2004
* @author Jesse Wilson
*/
public abstract class AbstractFilterList extends TransformedList {
/** implement Matcher's requirements in one quick inner class */
private PrivateMatcherEditor editor = null;
/**
* Creates a {@link AbstractFilterList} that includes a subset of the specified
* source {@link EventList}.
*
* Extending classes must call handleFilterChanged().
*/
protected AbstractFilterList(EventList source) {
super(new FilterList(source));
// listen for changes to the source list
this.source.addListEventListener(this);
}
/**
* Handles a clearing of the filter. That is, the filter list will act as
* a passthrough and not discriminate any of the elements of the wrapped
* source list.
*/
protected void handleFilterCleared() {
if(editor == null) {
editor = new PrivateMatcherEditor();
FilterList filterList = (FilterList)super.source;
filterList.setMatcherEditor(editor);
} else {
editor.fireCleared();
}
}
/**
* Handles a relaxing or widening of the filter. This may change the
* contents of this {@link EventList} as filtered elements are unfiltered
* due to the relaxation of the filter.
*
*
Warning: This method is
* thread ready but not thread safe. See {@link EventList} for an example
* of thread safe code.
*/
protected final void handleFilterRelaxed() {
editor.fireRelaxed();
}
/**
* Handles a constraining or narrowing of the filter. This may change the
* contents of this {@link EventList} as elements are further filtered due
* to the constraining of the filter.
*
*
Warning: This method is
* thread ready but not thread safe. See {@link EventList} for an example
* of thread safe code.
*/
protected final void handleFilterConstrained() {
editor.fireConstrained();
}
/**
* Handles changes to the behavior of the filter. This may change the contents
* of this {@link EventList} as elements are filtered and unfiltered.
*
*
Warning: This method is
* thread ready but not thread safe. See {@link EventList} for an example
* of thread safe code.
*/
protected final void handleFilterChanged() {
if(editor == null) {
editor = new PrivateMatcherEditor();
FilterList filterList = (FilterList)super.source;
filterList.setMatcherEditor(editor);
} else {
editor.fireChanged();
}
}
/**
* Tests if the specified item from the source {@link EventList} is matched by
* the current filter.
*
* @return true for elements that match the filter and shall be
* included in this {@link EventList} or false for elements that
* shall not be included in this {@link EventList}.
*/
public abstract boolean filterMatches(Object element);
/** {@inheritDoc} */
@Override
public void listChanged(ListEvent listChanges) {
// just pass on the changes
updates.forwardEvent(listChanges);
}
/** {@inheritDoc} */
@Override
protected boolean isWritable() {
return true;
}
/**
* The MatcherEditor within the {@link AbstractFilterList} is a simple way for
* it to fit into the new {@link Matcher}s micro framework.
*/
private class PrivateMatcherEditor extends AbstractMatcherEditor implements Matcher {
/**
* This MatcherEditor's Matcher is itself.
*/
public PrivateMatcherEditor() {
fireChanged();
}
/** {@inheritDoc} */
public boolean matches(Object item) {
return filterMatches(item);
}
public void fireCleared() { fireMatchAll(); }
public void fireRelaxed() { fireRelaxed(this); }
public void fireConstrained() { fireConstrained(this); }
public void fireChanged() { fireChanged(this); }
}
/** {@inheritDoc} */
@Override
public void dispose() {
FilterList filteredSource = (FilterList)source;
super.dispose();
filteredSource.dispose();
}
}
libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/migrationkit/swing/ 0000755 0001750 0001750 00000000000 12106516376 027772 5 ustar gregoa gregoa ././@LongLink 0000000 0000000 0000000 00000000147 00000000000 011567 L ustar root root libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/migrationkit/swing/TextFilterList.java libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/migrationkit/swing/TextFilterList.ja0000644 0001750 0001750 00000021477 12106516376 033247 0 ustar gregoa gregoa /* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.migrationkit.swing;
// the core Glazed Lists packages
import ca.odell.glazedlists.*;
import ca.odell.glazedlists.event.ListEvent;
import ca.odell.glazedlists.swing.TextComponentMatcherEditor;
import javax.swing.*;
import javax.swing.event.DocumentListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
* An {@link EventList} that shows only elements that contain a filter text string.
* The {@link TextFilterList} uses a {@link JTextField} to allow the user to edit
* the filter text. As this filter text is edited, the contents of the
* {@link TextFilterList} are changed to reflect the elements that match the text.
*
*
The {@link TextFilterList} either requires that a {@link TextFilterator} be
* specified in its constructor, or that every object in the source list implements
* the {@link TextFilterable} interface. These are used to specify the {@link String}s
* to search for each element.
*
*
The {@link TextFilterList} initially refilters the list after each change in
* the {@link JTextField}. If this live filtering does not have adequate performance,
* it can be turned off. In this case, the list will refiltered by pressing
* ENTER in the {@link JTextField} and on every action to the {@link ActionListener}.
* This {@link ActionListener} will be returned from the method {@link #getFilterActionListener()}
* and can be used to refilter in response to a button click.
*
*
Warning: This class
* breaks the contract required by {@link java.util.List}. See {@link EventList}
* for an example.
*
*
* | EventList Overview |
* | Writable: | yes |
* | Concurrency: | thread ready, not thread safe |
* | Performance: | reads: O(log N), writes O(log N), filter changes O(N) |
* | Memory: | O(N) |
* | Unit Tests: | N/A |
* | Issues: | N/A |
*
*
* @deprecated This class uses inheritance when composition is preferrable. Instead
* of TextFilterList, use {@link FilterList} and {@link TextComponentMatcherEditor}.
*
* @author Jesse Wilson
*/
public class TextFilterList extends TransformedList {
/** the text component matcher editor does all the real work */
private TextComponentMatcherEditor matcherEditor;
/** the field where the filter strings are edited */
private JTextField filterEdit = null;
/**
* Creates a {@link TextFilterList} that filters the specified {@link EventList}
* of elements that implement the {@link TextFilterable} interface.
*/
public TextFilterList(EventList source) {
this(source, (TextFilterator)null, new JTextField(""));
}
/**
* Creates a {@link TextFilterList} that filters the specified {@link EventList}
* of elements using the specified {@link TextFilterator} to get the
* {@link String}s to search.
*/
public TextFilterList(EventList source, TextFilterator filterator) {
this(source, filterator, new JTextField(""));
}
/**
* Creates a {@link TextFilterList} that filters the specified {@link EventList}
* of elements using the JavaBeans property names specified to get the
* {@link String}s to search.
*
* Note that the classes which will be obfuscated may not work with
* reflection. In this case, implement a {@link TextFilterator} manually.
*
* @param propertyNames an array of property names in the JavaBeans format.
* For example, if your list contains Objects with the methods getFirstName(),
* setFirstName(String), getAge(), setAge(Integer), then this array should
* contain the two strings "firstName" and "age". This format is specified
* by the JavaBeans {@link java.beans.PropertyDescriptor}.
*/
public TextFilterList(EventList source, String[] propertyNames) {
this(source, GlazedLists.textFilterator(propertyNames), new JTextField(""));
}
/**
* Creates a {@link TextFilterList} that filters the specified {@link EventList}
* of elements using the JavaBeans property names specified to get the
* {@link String}s to search.
*
*
Note that the classes which will be obfuscated may not work with
* reflection. In this case, implement a {@link TextFilterator} manually.
*
* @param propertyNames an array of property names in the JavaBeans format.
* For example, if your list contains Objects with the methods getFirstName(),
* setFirstName(String), getAge(), setAge(Integer), then this array should
* contain the two strings "firstName" and "age". This format is specified
* by the JavaBeans {@link java.beans.PropertyDescriptor}.
* @param filterEdit a text field for typing in the filter text.
*/
public TextFilterList(EventList source, String[] propertyNames, JTextField filterEdit) {
this(source, GlazedLists.textFilterator(propertyNames), filterEdit);
}
/**
* Creates a {@link TextFilterList} that filters the specified {@link EventList}
* of elements using the specified {@link TextFilterator} to get the
* {@link String}s to search.
*
* @param filterEdit a text field for typing in the filter text.
*/
public TextFilterList(EventList source, TextFilterator filterator, JTextField filterEdit) {
super(new FilterList(source));
this.matcherEditor = new TextComponentMatcherEditor(filterEdit, filterator);
this.filterEdit = filterEdit;
((FilterList)this.source).setMatcherEditor(matcherEditor);
// handle changes
this.source.addListEventListener(this);
}
/**
* Gets the {@link JTextField} used to edit the filter search {@link String}.
*/
public JTextField getFilterEdit() {
return filterEdit;
}
/**
* Sets the {@link JTextField} used to edit the filter search {@link String}.
*/
public void setFilterEdit(JTextField filterEdit) {
if(filterEdit == this.filterEdit) return;
// clean up the old matcher editor
boolean live = matcherEditor.isLive();
TextFilterator textFilterator = matcherEditor.getFilterator();
matcherEditor.dispose();
// prepare the new matcher editor
this.matcherEditor = new TextComponentMatcherEditor(filterEdit, textFilterator, live);
((FilterList)source).setMatcherEditor(matcherEditor);
}
/**
* Directs this filter to respond to changes to the {@link JTextField} as they are
* made. This uses a {@link DocumentListener} and every time the
* {@link JTextField} is modified, the list is refiltered.
*
*
To avoid the processing overhead of filtering for each keystroke, use
* a not-live filter edit and trigger the {@link ActionListener} using a
* button or by pressing ENTER in the {@link JTextField}.
*/
public void setLive(boolean live) {
matcherEditor.setLive(live);
}
/**
* Gets an {@link ActionListener} that refilters the list when it is fired. This
* listener can be used to filter when the user presses a button.
*/
public ActionListener getFilterActionListener() {
return new FilterActionListener();
}
/**
* Implement the {@link ActionListener} interface for text filter updates. When
* the user clicks a button (supplied by external code), this
* {@link ActionListener} can be used to update the filter in response.
*/
private class FilterActionListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
matcherEditor.setFilterText(filterEdit.getText().split("[ \t]"));
}
}
/** {@inheritDoc} */
@Override
public void listChanged(ListEvent listChanges) {
// just pass on the changes
updates.forwardEvent(listChanges);
}
/** {@inheritDoc} */
@Override
protected boolean isWritable() {
return true;
}
/** {@inheritDoc} */
@Override
public void dispose() {
FilterList filteredSource = (FilterList)source;
super.dispose();
filteredSource.dispose();
matcherEditor.dispose();
}
} libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/Sequencers.java 0000644 0001750 0001750 00000005512 12106516400 027111 0 ustar gregoa gregoa /* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists;
import ca.odell.glazedlists.impl.GlazedListsImpl;
import java.util.Calendar;
import java.util.Date;
/**
* A factory for creating Sequencers.
*
* @author James Lemieux
*/
public final class Sequencers {
/**
* A dummy constructor to prevent instantiation of this class
*/
private Sequencers() {
throw new UnsupportedOperationException();
}
// Sequencers // // // // // // // // // // // // // // // // // // // // //
public static SequenceList.Sequencer monthSequencer() {
return new MonthSequencer();
}
/**
* This Sequencer produces a sequence of {@link Date} objects normalized
* to the first millisecond of each month.
*/
private static final class MonthSequencer implements SequenceList.Sequencer {
/** A shared Calendar; it is assumed this Sequencer is only access from a single Thread. */
private final Calendar cal = Calendar.getInstance();
/**
* The previous month in the sequence. For example:
*
*
* - previous(February 15, 2006 3:21:22.234) returns February 1, 2006 0:00:00.000
*
- previous(February 1, 2006 0:00:00.000) returns January 1, 2006 0:00:00.000
*
- previous(January 1, 2006 0:00:00.000) returns December 1, 2005 0:00:00.000
*
*/
public Date previous(Date date) {
if (date == null)
throw new IllegalArgumentException("date may not be null");
cal.setTime(date);
// if cal is on the month boundary, rollback to the previous month
if (GlazedListsImpl.isMonthStart(cal))
cal.add(Calendar.MONTH, -1);
// normalize the Date to the first millisecond of the month
return GlazedListsImpl.getMonthStart(cal);
}
/**
* The next month in the sequence. For example:
*
*
* - next(November 15, 2005 3:21:22.234) returns December 1, 2005 0:00:00.000
*
- next(December 1, 2005 0:00:00.000) returns January 1, 2006 0:00:00.000
*
- next(January 1, 2006 0:00:00.000) returns February 1, 2006 0:00:00.000
*
*/
public Date next(Date date) {
if (date == null)
throw new IllegalArgumentException("date may not be null");
cal.setTime(date);
cal.add(Calendar.MONTH, 1);
// normalize the Date to the first millisecond of the month
return GlazedListsImpl.getMonthStart(cal);
}
}
} libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/ThresholdList.java 0000644 0001750 0001750 00000032313 12106516400 027563 0 ustar gregoa gregoa /* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists;
// to use standard collections
import java.util.Comparator;
/**
* An {@link EventList} that shows a range of the elements of the source
* {@link EventList}. Each element in the source {@link EventList} is assigned
* an integer value via an {@link Evaluator}. This integer is used
* to determine whether the element fits in the {@link ThresholdList}s range.
*
* By modifying the upper and lower thresholds in the range, the list can
* be filtered in a simple and powerful way.
*
*
The {@link ThresholdList} lends itself to use with a slider widget for
* manipulating one of the range's endpoints.
*
*
One use case for {@link ThresholdList} is in a media player application.
* By creating a {@link Evaluator} for a song's bitrate, the user could
* limit results to MP3 files between 192 and 320kbps.
*
*
Note that the elements in the {@link ThresholdList} will be presented in
* order sorted by their {@link Evaluator} value.
*
*
This {@link EventList} supports all write operations.
*
*
Warning: This class
* breaks the contract required by {@link java.util.List}. See {@link EventList}
* for an example.
*
*
Warning: This class is
* thread ready but not thread safe. See {@link EventList} for an example
* of thread safe code.
*
*
* | EventList Overview |
* | Writable: | yes |
* | Concurrency: | thread ready, not thread safe |
* | Performance: | reads: O(log N), writes O(log N), change threshold O(log N) |
* | Memory: | 72 bytes per element |
* | Unit Tests: | N/A |
* | Issues: |
* 47
* 137
* 217
* 218
* 246
* 277
* |
*
*
* @author Kevin Maltby
*/
public final class ThresholdList extends RangeList {
/** the lower bound to use to define list containment */
private int lowerThreshold = Integer.MIN_VALUE;
/** the upper bound to use to define list containment */
private int upperThreshold = Integer.MAX_VALUE;
/** the evaluator to use to compare Objects against the threshold */
private Evaluator evaluator = null;
/** a sorted view of the source makes threshold operations really fast */
private final SortedList sortedSource;
/**
* Creates a {@link ThresholdList} that provides range-filtering based on the
* specified {@link EventList} based on the specified integer JavaBean property.
*/
public ThresholdList(EventList source, String propertyName) {
this(source, (Evaluator) GlazedLists.thresholdEvaluator(propertyName));
}
/**
* Creates a {@link ThresholdList} that provides range-filtering on the
* specified {@link EventList} using the specified {@link Evaluator}.
*/
public ThresholdList(EventList source, Evaluator evaluator) {
this(new SortedList(source, new ThresholdComparator(evaluator)), evaluator);
}
private ThresholdList(SortedList sortedSource, Evaluator evaluator) {
super(sortedSource);
this.sortedSource = sortedSource;
this.evaluator = evaluator;
}
/**
* Sets the lower threshold for this list to be the result of calling
* {@link Evaluator#evaluate(Object) evaluate()} on the given object.
*
* This list can be used programmatically rather than hooking it up to
* a UI component. Calling this method directly while this list
* is connected to a particular widget could result in errors.
*
*
Warning: This method is
* thread ready but not thread safe. See {@link EventList} for an example
* of thread safe code.
*/
public void setLowerThreshold(E object) {
setLowerThreshold(evaluator.evaluate(object));
}
/**
* Sets the lower threshold for this list.
*
*
This list can be used programmatically rather than hooking it up to
* a UI component. Calling this method directly while this list
* is connected to a particular widget could result in errors.
*
*
Warning: This method is
* thread ready but not thread safe. See {@link EventList} for an example
* of thread safe code.
*/
public void setLowerThreshold(int lowerThreshold) {
this.lowerThreshold = lowerThreshold;
adjustRange();
}
/**
* Gets the lower threshold for this list
*/
public int getLowerThreshold() {
return lowerThreshold;
}
/**
* Sets the upper threshold for this list to be the result of calling
* {@link Evaluator#evaluate(Object) evaluate()} on the given object.
*
*
This list can be used programmatically rather than hooking it up to
* a UI component. Calling this method directly while this list
* is connected to a particular widget could result in errors.
*
*
Warning: This method is
* thread ready but not thread safe. See {@link EventList} for an example
* of thread safe code.
*/
public void setUpperThreshold(E object) {
setUpperThreshold(evaluator.evaluate(object));
}
/**
* Sets the upper threshold for this list.
*
*
Warning: This method is
* thread ready but not thread safe. See {@link EventList} for an example
* of thread safe code.
*/
public void setUpperThreshold(int upperThreshold) {
this.upperThreshold = upperThreshold;
adjustRange();
}
/**
* Gets the upper threshold for this list
*/
public int getUpperThreshold() {
return upperThreshold;
}
/**
* A convenience method to allow access to the {@link Evaluator}
* that was provided on construction.
*/
public Evaluator getEvaluator() {
return evaluator;
}
/** {@inheritDoc} */
@Override
public boolean contains(Object object) {
// Fast fail if the object isn't within the thresholds
// Note: this technically breaks the contract for contains.
// evaluator.evaluate(object) may throw a ClassCastException
if(!withinRange((E)object)) return false;
return source.contains(object);
}
/** {@inheritDoc} */
@Override
public int indexOf(Object object) {
// Fast fail if the object isn't within the thresholds
// Note: this technically breaks the contract for indexOf.
// evaluator.evaluate(object) may throw a ClassCastException
if(!withinRange((E)object)) return -1;
return source.indexOf(object);
}
/** {@inheritDoc} */
@Override
public int lastIndexOf(Object object) {
// Fast fail if the object isn't within the thresholds
// Note: this technically breaks the contract for lastIndexOf.
// evaluator.evaluate(object) may throw a ClassCastException
if(!withinRange((E)object)) return -1;
return source.lastIndexOf(object);
}
/**
* Test if the specified object is within the range of this {@link ThresholdList}.
*/
private boolean withinRange(E object) {
int objectEvaluation = evaluator.evaluate(object);
return objectEvaluation >= lowerThreshold && objectEvaluation <= upperThreshold;
}
/** {@inheritDoc} */
@Override
public void setRange(int startIndex, int endIndex) {
// this implementation is slightly inconsistent with the superclass
// because the super treats endIndex as exclusive wheras we treat
// endIndex as inclusive
this.lowerThreshold = sourceIndexToThreshold(startIndex);
this.upperThreshold = sourceIndexToThreshold(endIndex);
adjustRange();
}
/** {@inheritDoc} */
@Override
public void setTailRange(int startIndex, int endIndex) {
// this implementation is slightly inconsistent with the superclass
// because the super treats endIndex as exclusive wheras we treat
// endIndex as inclusive
this.lowerThreshold = sourceIndexToThreshold(source.size() - startIndex);
this.upperThreshold = sourceIndexToThreshold(source.size() - endIndex);
adjustRange();
}
/**
* Given an index into the source {@link EventList}, get the
* threshold value for that index.
*/
private int sourceIndexToThreshold(int sourceIndex) {
if(sourceIndex < 0) {
return Integer.MIN_VALUE;
} else if(sourceIndex < source.size()) {
return evaluator.evaluate(source.get(sourceIndex));
} else {
return Integer.MIN_VALUE;
}
}
/** {@inheritDoc} */
@Override
public int getStartIndex() {
return sortedSource.sortIndex(new Integer(lowerThreshold));
}
/** {@inheritDoc} */
@Override
public int getEndIndex() {
// search for the upperThreshold value
int index = sortedSource.lastSortIndex(new Integer(upperThreshold));
// if the upperThreshold exists in the sortedSource, convert the exclusive index to an inclusive index
if (index < sortedSource.size() && evaluator.evaluate(sortedSource.get(index)) == upperThreshold)
index++;
return index;
}
/** {@inheritDoc} */
@Override
public void dispose() {
sortedSource.dispose();
super.dispose();
}
/**
* Provide an integer value for a given {@link Object} in a
* {@link ThresholdList}.
*/
public interface Evaluator {
/**
* Returns an integer value for an {@link Object} to be used to
* compare that object against a threshold. This value is
* not relative to any other object unlike a {@link Comparator}.
*/
public int evaluate(E object);
}
/**
* A ThresholdComparator is a simple helper class that wraps
* an {@link Evaluator} with a Comparator to
* be used for sorting of the ThresholdList.
*/
static final class ThresholdComparator implements Comparator {
/** the underlying evaluator */
private Evaluator evaluator = null;
/**
* Creates a new ThresholdComparator
*/
ThresholdComparator(Evaluator evaluator) {
this.evaluator = evaluator;
}
/**
* Compares two Objects, and compares them using the result
* given when each Object is evaluated using the underlying
* {@link Evaluator}.
*
* This method is dual-mode as in the case of the Objects passed being
* Integers, it returns the value of
* ((Integer)alpha).intValue() - ((Integer)beta).intValue().
* This is necessary so that a threshold value can be compared against an
* Object, and vice versa. This can cause problems however
* if the underlying {@link Evaluator} were to return the negation
* of an Integer.
*/
public int compare(E alpha, E beta) {
int alphaValue;
if(alpha instanceof Integer) alphaValue = ((Integer)alpha).intValue();
else alphaValue = evaluator.evaluate(alpha);
int betaValue;
if(beta instanceof Integer) betaValue = ((Integer)beta).intValue();
else betaValue = evaluator.evaluate(beta);
if(alphaValue > betaValue) return 1;
else if(alphaValue < betaValue) return -1;
else return 0;
}
/** {@inheritDoc} */
@Override
public boolean equals(Object o) {
if(this == o) return true;
if(o == null || getClass() != o.getClass()) return false;
final ThresholdComparator that = (ThresholdComparator) o;
if(!evaluator.equals(that.evaluator)) return false;
return true;
}
/** {@inheritDoc} */
@Override
public int hashCode() {
return evaluator.hashCode();
}
}
} libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/TransformedList.java 0000644 0001750 0001750 00000014261 12106516400 030115 0 ustar gregoa gregoa /* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists;
// the Glazed Lists' change objects
import ca.odell.glazedlists.event.ListEvent;
import ca.odell.glazedlists.event.ListEventListener;
import java.util.Collection;
/**
* A convenience class for {@link EventList}s that decorate another {@link EventList}.
* Extending classes transform their source {@link EventList} by modifying the
* order, visibility and value of its elements.
*
*
Extending classes may implement the method {@link #getSourceIndex(int)} to
* translate between indices of this and indices of the source.
*
*
Extending classes may implement the method {@link #isWritable()} to make the
* source writable via this API.
*
*
Extending classes must explicitly call {@link #addListEventListener(ListEventListener)}
* to receive change notifications from the source {@link EventList}.
*
*
Warning: This class is
* thread ready but not thread safe. See {@link EventList} for an example
* of thread safe code.
*
* @author Jesse Wilson
*/
public abstract class TransformedList extends AbstractEventList implements ListEventListener {
/** the event list to transform */
protected EventList source;
/**
* Creates a {@link TransformedList} to transform the specified {@link EventList}.
*
* @param source the {@link EventList} to transform
*/
protected TransformedList(EventList source) {
super(source.getPublisher());
this.source = source;
readWriteLock = source.getReadWriteLock();
}
/**
* Gets the index in the source {@link EventList} that corresponds to the
* specified index. More formally, returns the index such that
*
this.get(i) == source.get(getSourceIndex(i)) for all
* legal values of i.
*/
protected int getSourceIndex(int mutationIndex) {
return mutationIndex;
}
/**
* Gets whether the source {@link EventList} is writable via this API.
*
* Extending classes must override this method in order to make themselves
* writable.
*/
protected abstract boolean isWritable();
/** {@inheritDoc} */
public abstract void listChanged(ListEvent listChanges);
/** {@inheritDoc} */
@Override
public void add(int index, E value) {
if(!isWritable()) throw new IllegalStateException("Non-writable List cannot be modified");
if(index < 0 || index > size()) throw new IndexOutOfBoundsException("Cannot add at " + index + " on list of size " + size());
final int sourceIndex = index < size() ? getSourceIndex(index) : source.size();
source.add(sourceIndex, (S) value);
}
/** {@inheritDoc} */
@Override
public boolean addAll(int index, Collection extends E> values) {
// nest changes and let the other methods compose the event
updates.beginEvent(true);
try {
return super.addAll(index, values);
} finally {
updates.commitEvent();
}
}
/** {@inheritDoc} */
@Override
public void clear() {
// nest changes and let the other methods compose the event
updates.beginEvent(true);
try {
super.clear();
} finally {
updates.commitEvent();
}
}
/** {@inheritDoc} */
@Override
public E get(int index) {
if(index < 0 || index >= size()) throw new IndexOutOfBoundsException("Cannot get at " + index + " on list of size " + size());
return (E) source.get(getSourceIndex(index));
}
/** {@inheritDoc} */
@Override
public E remove(int index) {
if(!isWritable()) throw new IllegalStateException("Non-writable List cannot be modified");
if(index < 0 || index >= size()) throw new IndexOutOfBoundsException("Cannot remove at " + index + " on list of size " + size());
return (E) source.remove(getSourceIndex(index));
}
/** {@inheritDoc} */
@Override
public boolean removeAll(Collection> collection) {
// nest changes and let the other methods compose the event
updates.beginEvent(true);
try {
return super.removeAll(collection);
} finally {
updates.commitEvent();
}
}
/** {@inheritDoc} */
@Override
public boolean retainAll(Collection> values) {
// nest changes and let the other methods compose the event
updates.beginEvent(true);
try {
return super.retainAll(values);
} finally {
updates.commitEvent();
}
}
/** {@inheritDoc} */
@Override
public E set(int index, E value) {
if(!isWritable()) throw new IllegalStateException("List " + this.getClass().getName() + " cannot be modified in the current state");
if(index < 0 || index >= size()) throw new IndexOutOfBoundsException("Cannot set at " + index + " on list of size " + size());
return (E) source.set(getSourceIndex(index), (S) value);
}
/** {@inheritDoc} */
@Override
public int size() {
return source.size();
}
/**
* Releases the resources consumed by this {@link TransformedList} so that it
* may eventually be garbage collected.
*
*
A {@link TransformedList} will be garbage collected without a call to
* {@link #dispose()}, but not before its source {@link EventList} is garbage
* collected. By calling {@link #dispose()}, you allow the {@link TransformedList}
* to be garbage collected before its source {@link EventList}. This is
* necessary for situations where a {@link TransformedList} is short-lived but
* its source {@link EventList} is long-lived.
*
*
Warning: It is an error
* to call any method on a {@link TransformedList} after it has been disposed.
*/
public void dispose() {
source.removeListEventListener(this);
}
} libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/TextFilterable.java 0000644 0001750 0001750 00000002016 12106516400 027706 0 ustar gregoa gregoa /* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists;
import java.util.List;
/**
* An item that can be compared to a list of filters to see if it matches.
*
* @see Glazed Lists Tutorial
*
* @author Jesse Wilson
*/
public interface TextFilterable {
/**
* Gets this object as a list of Strings. These Strings
* should contain all object information so that it can be compared
* to the filter set.
*
* @param baseList a list that the implementor shall add their filter
* strings to via baseList.add(). This may be a non-empty
* List and it is an error to call any method other than add().
*/
public void getFilterStrings(List baseList);
} libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/RangeList.java 0000644 0001750 0001750 00000023605 12106516400 026667 0 ustar gregoa gregoa /* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists;
import ca.odell.glazedlists.event.ListEvent;
/**
* This {@link EventList} shows values from a continuous range of indices from
* a source {@link EventList}. It can be used to limit the length of a list to
* a desired size.
*
* Warning: This class is
* thread ready but not thread safe. See {@link EventList} for an example
* of thread safe code.
*
*
* | EventList Overview |
* | Writable: | yes |
* | Concurrency: | thread ready, not thread safe |
* | Performance: | reads: O(1), writes O(1), change range O(1) |
* | Memory: | 0 bytes per element |
* | Unit Tests: | N/A |
* | Issues: |
* 238
* 278
* |
*
*
* @author Jesse Wilson
*/
public class RangeList extends TransformedList {
/** the user-specified range of the source list to include */
private int desiredStart = 0;
private int desiredEnd = -1;
/** the first index in this list, inclusive */
private int currentStartIndex;
/** the last index in this list, exclusive */
private int currentEndIndex;
/**
* Create a new {@link RangeList} that limits the specified {@link EventList}
* to a desired range.
*/
public RangeList(EventList source) {
super(source);
currentStartIndex = 0;
currentEndIndex = source.size();
source.addListEventListener(this);
}
/** {@inheritDoc} */
@Override
public final void listChanged(ListEvent listChanges) {
// This EventList handles changes to the source EventList using a
// two-phase approach:
// 1. The change event is iterated and the current bound indices are
// offset to reflect the change. Each change event within the
// range of indices is forwarded.
// 2. In the second phase, during setRange(), the current indices
// are adjusted back to their previously set 'desired values'
// if possible.
updates.beginEvent(true);
// propagate the event and offset local indices
while(listChanges.next()) {
int changeType = listChanges.getType();
int changeIndex = listChanges.getIndex();
E oldValue = listChanges.getOldValue();
E newValue = listChanges.getNewValue();
if(changeType == ListEvent.DELETE) {
if(changeIndex < currentStartIndex) {
currentStartIndex--;
currentEndIndex--;
} else if(changeIndex < currentEndIndex) {
currentEndIndex--;
updates.elementDeleted(changeIndex - currentStartIndex, oldValue);
}
} else if(changeType == ListEvent.INSERT) {
if(changeIndex < currentStartIndex) {
currentStartIndex++;
currentEndIndex++;
} else if(changeIndex < currentEndIndex) {
currentEndIndex++;
updates.elementInserted(changeIndex - currentStartIndex, newValue);
}
} else if(changeType == ListEvent.UPDATE) {
if(changeIndex >= currentStartIndex && changeIndex < currentEndIndex) {
updates.elementUpdated(changeIndex - currentStartIndex, oldValue, newValue);
}
}
}
// adjust the displayed range to accomodate for the source changes
adjustRange();
updates.commitEvent();
}
/**
* Set the range of values displayed by this {@link RangeList}.
*
* @param startIndex the first index of the source {@link EventList} to show, inclusive
* @param endIndex the last index of the source {@link EventList} to show, exclusive
*
* @deprecated 2/15/2006 use {@link #setHeadRange(int, int)} instead. The
* introduction of {@link #setMiddleRange(int, int)} caused us to want a
* consistent naming scheme for all set*Range methods.
*/
public void setRange(int startIndex, int endIndex) {
this.setHeadRange(startIndex, endIndex);
}
/**
* Set the range of values displayed by this {@link RangeList}.
*
* @param startIndex the first index of the source {@link EventList} to show, inclusive
* @param endIndex the last index of the source {@link EventList} to show, exclusive
*/
public void setHeadRange(int startIndex, int endIndex) {
this.desiredStart = startIndex;
this.desiredEnd = endIndex;
adjustRange();
}
/**
* Set the range to include the specified indices, the startIndex offset from the
* front of the source {@link EventList} and the endIndex offset from the end
* of the source {@link EventList}.
*
* For example, to include everything but the first element, use
* RangeList.setMiddleRange(1, 0);.
*
*
For example, to include everything but the last element, use
* RangeList.setMiddleRange(0, 1);.
*/
public void setMiddleRange(int startIndex, int endIndex) {
this.desiredStart = startIndex;
this.desiredEnd = -endIndex - 1;
adjustRange();
}
/**
* Set the range to include the specified indices, offset from the end of
* the source {@link EventList}. For example, to show the last five values, use:
* RangeList.setTailRange(5, 0);
*
*
To include the 3rd last and 2nd last values, use:
* RangeList.setTailRange(3, 1);.
*/
public void setTailRange(int startIndex, int endIndex) {
this.desiredStart = -startIndex - 1;
this.desiredEnd = -endIndex - 1;
adjustRange();
}
/**
* Adjust the range of the {@link RangeList} in response to changes in the
* source list or the desired start and end indices.
*/
protected final void adjustRange() {
updates.beginEvent(true);
// get the new range
int desiredStartIndex = getStartIndex();
int desiredEndIndex = getEndIndex();
// normalize the range so start index is the smallest index and end index is the largest
if (desiredEndIndex < desiredStartIndex) {
int temp = desiredEndIndex;
desiredEndIndex = desiredStartIndex;
desiredStartIndex = temp;
}
// insert before the beginning
if(desiredStartIndex < currentStartIndex) {
updates.addInsert(0, currentStartIndex - desiredStartIndex - 1);
// delete thru to the new beginning
} else if(currentStartIndex < desiredStartIndex && currentStartIndex < currentEndIndex) {
int deleteThru = Math.min(desiredStartIndex, currentEndIndex);
for(int i = currentStartIndex; i < deleteThru; i++) {
updates.elementDeleted(0, source.get(i));
}
}
currentStartIndex = desiredStartIndex;
// delete past the end
if(desiredEndIndex < currentEndIndex) {
for(int i = desiredEndIndex; i < currentEndIndex; i++) {
updates.elementDeleted(desiredEndIndex - currentStartIndex, source.get(i));
}
// insert thru to the new end
} else if(currentEndIndex < desiredEndIndex && desiredStartIndex < desiredEndIndex) {
int insertFrom = Math.max(currentEndIndex, currentStartIndex);
updates.addInsert(insertFrom - currentStartIndex, desiredEndIndex - currentStartIndex - 1);
}
currentEndIndex = desiredEndIndex;
updates.commitEvent();
}
/** {@inheritDoc} */
@Override
public final int size() {
return currentEndIndex - currentStartIndex;
}
/** {@inheritDoc} */
@Override
protected final int getSourceIndex(int mutationIndex) {
return mutationIndex + currentStartIndex;
}
/** {@inheritDoc} */
@Override
protected final boolean isWritable() {
return true;
}
/**
* Get the first index of the source {@link EventList}
* that is presented in this {@link RangeList}.
*/
public int getStartIndex() {
// translate the positive or negative desired values to indices
int desiredStartIndex = desiredStart >= 0 ? desiredStart : source.size() + desiredStart + 1;
// adjust the start index to the size of the list
if(desiredStartIndex < 0) return 0;
if(desiredStartIndex > source.size()) return source.size();
return desiredStartIndex;
}
/**
* Get the first index of the source {@link EventList}
* that is beyond the range of this {@link RangeList}.
*/
public int getEndIndex() {
// translate the positive or negative desired values to indices
int desiredEndIndex = desiredEnd >= 0 ? desiredEnd : source.size() + desiredEnd + 1;
// adjust the end index to the size of the list
int desiredStartIndex = getStartIndex();
if(desiredEndIndex < desiredStartIndex) return desiredStartIndex;
if(desiredEndIndex > source.size()) return source.size();
return desiredEndIndex;
}
} libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/gui/ 0000755 0001750 0001750 00000000000 12106516376 024726 5 ustar gregoa gregoa ././@LongLink 0000000 0000000 0000000 00000000150 00000000000 011561 L ustar root root libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/gui/AbstractTableComparatorChooser.java libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/gui/AbstractTableComparatorChooser.j0000644 0001750 0001750 00000034433 12106516376 033176 0 ustar gregoa gregoa /* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.gui;
import ca.odell.glazedlists.SortedList;
import ca.odell.glazedlists.impl.gui.MouseKeyboardSortingStrategy;
import ca.odell.glazedlists.impl.gui.MouseOnlySortingStrategy;
import ca.odell.glazedlists.impl.gui.SortingState;
import ca.odell.glazedlists.impl.gui.MouseOnlySortingStrategyWithUndo;
import ca.odell.glazedlists.impl.sort.TableColumnComparator;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Comparator;
import java.util.List;
/**
* A TableComparatorChooser is a tool that allows the user to sort a table
* widget by clicking on the table's headers. It requires that the table has a
* SortedList as a source as the sorting on that list is used.
*
* @author Kevin Maltby
*/
public abstract class AbstractTableComparatorChooser {
/**
* Emulate the sorting behaviour of Windows Explorer and Mac OS X Finder.
*
* Single clicks toggles between forward and reverse. If multiple comparators
* are available for a particular column, they will be cycled in order.
*
*
At most one column can be sorted at a time.
*/
public static final Object SINGLE_COLUMN = new MouseOnlySortingStrategy(false);
/**
* Sort multiple columns without use of the keyboard. Single clicks cycle
* through comparators, double clicks clear all secondary sorts before
* performing the normal behaviour.
*
*
This is the original sorting strategy provided by Glazed Lists, with a
* limitation that it is impossible to clear a sort order that is already in
* place. It's designed to be used with multiple columns and multiple comparators
* per column.
*
*
The overall behaviour is as follows:
*
*
Click: sort this column. If it's already sorted, reverse the sort order.
* If its already reversed, sort using the column's next comparator in forward
* order. If there are no more comparators, go to the first comparator. If there
* are multiple sort columns, sort this column after those columns.
*
* Double click: like a single click, but clear all sorting columns first.
*/
public static final Object MULTIPLE_COLUMN_MOUSE = new MouseOnlySortingStrategy(true);
/**
* Sort multiple columns without use of the keyboard. Single clicks cycle
* through comparators. Single click on the last comparator of the primary
* sort column will clear the entire sort (for all columns).
*
* This is an improvement over the original sorting strategy provided by
* Glazed Lists, since it gives a reasonable mechanism for clearing a sort
* order that is already in place. It's designed to be used with multiple
* columns and multiple comparators per column.
*
*
The overall behaviour is as follows:
*
*
Click: sort this column. If it's already sorted, reverse the sort order.
* If its already reversed, sort using the column's next comparator in forward
* order. If there are no more comparators, clear ALL column comparators.
* If there are multiple sort columns, sort this column after those columns.
*/
public static final Object MULTIPLE_COLUMN_MOUSE_WITH_UNDO = new MouseOnlySortingStrategyWithUndo();
/**
* Emulate the sorting behaviour of SUN's TableSorter, by Philip Milne et. al.
*
* This is not a direct adaptation since we choose to support potentially
* many Comparators per column, whereas TableSorter is limited to one.
*
*
For reference, this is TableSorter's behaviour, copied shamelessly
* from that project's source file:
*
*
Mouse-click: Clears the sorting gui of all other columns and advances
* the sorting gui of that column through three values:
* {NOT_SORTED, ASCENDING, DESCENDING} (then back to NOT_SORTED again).
*
* SHIFT-mouse-click: Clears the sorting gui of all other columns and
* cycles the sorting gui of the column through the same three values,
* in the opposite order: {NOT_SORTED, DESCENDING, ASCENDING}.
*
* CONTROL-mouse-click and CONTROL-SHIFT-mouse-click: as above except that the
* changes to the column do not cancel the statuses of columns that are
* already sorting - giving a way to initiate a compound sort.
*
* @see Table tutorial
*/
public static final Object MULTIPLE_COLUMN_KEYBOARD = new MouseKeyboardSortingStrategy();
/** the sorted list to choose the comparators for */
protected SortedList sortedList;
/** the columns to sort over */
private TableFormat super E> tableFormat;
/** the potentially foreign comparator associated with the sorted list */
protected Comparator super E> sortedListComparator = null;
/** manage which columns are sorted and in which order */
protected SortingState sortingState;
/**
* Create a {@link AbstractTableComparatorChooser} that sorts the specified
* {@link SortedList} over the specified columns.
*/
protected AbstractTableComparatorChooser(SortedList sortedList, TableFormat super E> tableFormat) {
this.sortedList = sortedList;
this.sortingState = createSortingState();
this.setTableFormat(tableFormat);
this.sortingState.addPropertyChangeListener(new SortingStateListener());
}
/**
* Returns the object which models the current sorting state of all columns
* in the table.
*/
protected SortingState createSortingState() {
return new SortingState(this);
}
/**
* Handle changes to the sorting state by applying the new comparator
* to the {@link SortedList}.
*/
private class SortingStateListener implements PropertyChangeListener {
public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
rebuildComparator();
}
}
/**
* Updates the comparator in use and applies it to the table.
*/
protected void rebuildComparator() {
final Comparator rebuiltComparator = sortingState.buildComparator();
// select the new comparator
sortedList.getReadWriteLock().writeLock().lock();
try {
sortedListComparator = rebuiltComparator;
sortedList.setComparator(rebuiltComparator);
} finally {
sortedList.getReadWriteLock().writeLock().unlock();
}
}
/**
* Adjusts the TableFormat this comparator chooser uses when selecting
* comparators. Calling this method will clear any active sorting.
*/
protected void setTableFormat(TableFormat super E> tableFormat) {
this.tableFormat = tableFormat;
// handle a change in the layout of our columns
sortingState.rebuildColumns(tableFormat);
}
/**
* Gets the list of comparators for the specified column. The user is
* free to add comparators to this list or clear the list if the specified
* column cannot be sorted.
*/
public List getComparatorsForColumn(int column) {
return sortingState.getColumns().get(column).getComparators();
}
/**
* Get the columns that the TableComparatorChooser is sorting by.
*
* @return a List of Integers. The first Integer is the primary sorting column,
* the second is the secondary, etc. This list may be empty but never null.
*/
public List getSortingColumns() {
return sortingState.getSortingColumnIndexes();
}
/**
* Gets the index comparator in use for the specified column. This comparator
* may be retrieved using {@link #getComparatorsForColumn(int)}.
*
* @return the comparator index for the specified column, or -1 if that column
* is not being used to sort.
*/
public int getColumnComparatorIndex(int column) {
return sortingState.getColumns().get(column).getComparatorIndex();
}
/**
* Gets whether the comparator in use for the specified column is reverse.
*/
public boolean isColumnReverse(int column) {
return sortingState.getColumns().get(column).isReverse();
}
/**
* Append the comparator specified by the column, comparator index and reverse
* parameters to the end of the sequence of comparators this
* {@link AbstractTableComparatorChooser} is sorting the {@link SortedList}
* by.
*
* Append implies that if this {@link AbstractTableComparatorChooser}
* is already sorting that list by another column, this comparator will only
* be used to break ties from that {@link Comparator}. If the table is already
* sorting by the specified column, it will be silently discarded.
*
*
Suppose we're currently not sorting the table, this method will cause
* the table to be sorted by the column specified. If we are sorting the table
* by some column c, this will sort by that column first and the column
* specified here second.
*
*
If this {@link AbstractTableComparatorChooser} doesn't support multiple
* column sort, this will replace the current {@link Comparator} rather than
* appending to it.
*
* @param column the column to sort by
* @param comparatorIndex the comparator to use, specify 0 for the
* default comparator.
* @param reverse whether to reverse the specified comparator.
*/
public void appendComparator(int column, int comparatorIndex, boolean reverse) {
sortingState.appendComparator(column, comparatorIndex, reverse);
sortingState.fireSortingChanged();
}
/**
* Clear all sorting state and set the {@link SortedList} to use its
* natural order.
*/
public void clearComparator() {
sortingState.clearComparators();
sortingState.fireSortingChanged();
}
/**
* Examines the current {@link Comparator} of the SortedList and
* adds icons to the table header renderers in response.
*
*
To do this, clicks are injected into each of the
* corresponding ColumnClickTrackers.
*/
protected void redetectComparator(Comparator super E> currentComparator) {
sortedListComparator = currentComparator;
sortingState.detectStateFromComparator(currentComparator);
}
/**
* Gets the sorting style currently applied to the specified column.
*/
protected int getSortingStyle(int column) {
return sortingState.getColumns().get(column).getSortingStyle();
}
/**
* Creates a {@link Comparator} that can compare list elements
* given a {@link Comparator} that can compare column values for the specified
* column. This returns a {@link Comparator} that extracts the table values for
* the specified column and then delegates the actual comparison to the specified
* comparator.
*/
public Comparator createComparatorForElement(Comparator comparatorForColumn, int column) {
return new TableColumnComparator(tableFormat, column, comparatorForColumn);
}
/**
* Encode the current sorting state as a {@link String}. This specially formatted
* {@link String} is ideal for persistence using any preferences API. The
* state of this {@link AbstractTableComparatorChooser} can be restored
* by passing the return value of this method to {@link #fromString(String)}.
*/
@Override
public String toString() {
return sortingState.toString();
}
/**
* This class is capable of representing its own state with a String, to
* persist sorting state externally. The format uses zero or more column specifications,
* separated by commas. Here are some valid examples:
*
*
| String Representation | Description |
* "column 3" | Sort using the column at index 3, using that column's first comparator, in forward order |
* "column 3 reversed" | Sort using the column at index 3, using that column's first comparator, in reverse order |
* "column 3, column 1" | Sort using the column at index 3, using that column's first comparator, in forward order
* then by the column at index 1, using that column's first comparator, in forward order. |
* "column 3 comparator 2" | Sort using the column at index 3, using that column's comparator at index 2, in forward order |
* "column 3 comparator 2 reversed" | Sort using the column at index 3, using that column's comparator at index 2, in reverse order |
* "column 3 reversed, column 1 comparator 2, column 5 comparator 1 reversed, column 0" | Sort using the column at index 3, using that column's first comparator, in reverse order
* then by the column at index 1, using that column's comparator at index 2, in forward order
* then by the column at index 5, using that column's comparator at index 1, in reverse order
* then by the column at index 0, using that column's first comparator, in forward order. |
*
*
* More formally, the grammar for this String representation is as follows:
*
<COLUMN> = column <COLUMN INDEX> (comparator <COMPARATOR INDEX>)? (reversed)?
*
<COMPARATOR SPEC> = ( <COLUMN> (, <COLUMN>)* )?
*/
public void fromString(String stringEncoded) {
sortingState.fromString(stringEncoded);
sortingState.fireSortingChanged();
}
public void dispose() {
// null out references to potentially long lived objects
this.sortedList = null;
this.tableFormat = null;
this.sortedListComparator = null;
}
} libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/gui/CheckableTableFormat.java 0000644 0001750 0001750 00000001276 12106516376 031561 0 ustar gregoa gregoa /* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.gui;
/**
* Specifies how to check table elements.
*
* @author Jesse Wilson
*/
public interface CheckableTableFormat extends TableFormat {
/**
* Sets the specified object as checked.
*/
public void setChecked(E baseObject, boolean checked);
/**
* Gets whether the specified object is checked.
*/
public boolean getChecked(E baseObject);
} libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/gui/AdvancedTableFormat.java 0000644 0001750 0001750 00000003257 12106516376 031426 0 ustar gregoa gregoa /* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.gui;
import java.util.Comparator;
/**
* Allows the ability to specify column class information in addition to the standard
* {@link TableFormat} information.
*
* This class can be used as an alternative to the simple {@link TableFormat}
* class to provide column class information that is used to determine what cell renderer
* and/or editor should be used for a column. If no custom renderers or editors are
* required, it is sufficient to implement {@link TableFormat} only.
*
* @author Rob Eden
* @author Jesse Wilson
*
* @see WritableTableFormat
* @see TableFormat
*/
public interface AdvancedTableFormat extends TableFormat {
/**
* Returns the most specific superclass for all the cell values in the column. This
* is used by the table to set up a default renderer and editor for the column.
*
* @param column The index of the column being edited.
*/
public Class getColumnClass(int column);
/**
* Returns the default {@link Comparator} to use for the specified column.
* This {@link Comparator} may be used to determine how a table will be sorted.
*
* @see ca.odell.glazedlists.GlazedLists
*
* @return the {@link Comparator} to use or null for an unsortable
* column.
*/
public Comparator getColumnComparator(int column);
}
libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/gui/WritableTableFormat.java 0000644 0001750 0001750 00000004232 12106516376 031464 0 ustar gregoa gregoa /* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.gui;
/**
* Specifies how to edit the elements of table.
*
* This class can be used as an alternative to the simple {@link TableFormat}
* class to provide editable cells. The
* {@link ca.odell.glazedlists.swing.DefaultEventTableModel EventTableModel} detects if a
* class implements {@link WritableTableFormat} for modifying the table. If a table
* is not editable at all, it is sufficient to implement {@link TableFormat}
* only.
*
* @author Jesse Wilson
*
* @see AdvancedTableFormat
* @see TableFormat
*/
public interface WritableTableFormat extends TableFormat {
/**
* For editing fields. This returns true if the specified Object in the
* specified column can be edited by the user.
*
* @param baseObject the Object to test as editable or not. This will be
* an element from the source list.
* @param column the column to test.
* @return true if the object and column are editable, false otherwise.
* @since 2004-August-27, as a replacement for isColumnEditable(int).
*/
public boolean isEditable(E baseObject, int column);
/**
* Sets the specified field of the base object to the edited value. When
* a column of a table is edited, this method is called so that the user
* can specify how to modify the base object for each column.
*
* @param baseObject the Object to be edited. This will be the original
* Object from the source list.
* @param editedValue the newly constructed value for the edited column
* @param column the column which has been edited
* @return the revised Object, or null if the revision shall be discarded.
* If not null, the EventTableModel will set() this revised value in
* the list and overwrite the previous value.
*/
public E setColumnValue(E baseObject, Object editedValue, int column);
} libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/gui/TableFormat.java 0000644 0001750 0001750 00000002052 12106516376 027770 0 ustar gregoa gregoa /* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.gui;
/**
* Specifies how a set of records are rendered in a table.
*
* @see ca.odell.glazedlists.GlazedLists#tableFormat(Class,String[],String[])
*
* @author Jesse Wilson
*/
public interface TableFormat {
/**
* The number of columns to display.
*/
public int getColumnCount();
/**
* Gets the title of the specified column.
*/
public String getColumnName(int column);
/**
* Gets the value of the specified field for the specified object. This
* is the value that will be passed to the editor and renderer for the
* column. If you have defined a custom renderer, you may choose to return
* simply the baseObject.
*/
public Object getColumnValue(E baseObject, int column);
} libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/io/ 0000755 0001750 0001750 00000000000 12106516400 024535 5 ustar gregoa gregoa libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/io/FileList.java 0000644 0001750 0001750 00000011326 12106516400 027116 0 ustar gregoa gregoa /* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.io;
// the core Glazed Lists packages
import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.TransformedList;
import ca.odell.glazedlists.event.ListEvent;
import ca.odell.glazedlists.impl.io.Bufferlo;
import ca.odell.glazedlists.impl.io.ListEventToBytes;
import ca.odell.glazedlists.impl.pmap.Chunk;
import ca.odell.glazedlists.impl.pmap.PersistentMap;
import ca.odell.glazedlists.util.concurrent.ReadWriteLock;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* An {@link EventList} that is persisted to disk.
*
* Warning: This
* class is a technology preview and is subject to API changes.
*
*
* | EventList Overview |
* | Writable: | yes |
* | Concurrency: | Requires {@link ReadWriteLock} for every access, even for single-threaded use |
* | Performance: | N/A |
* | Memory: | O(N) |
* | Unit Tests: | N/A |
* | Issues: | N/A |
*
*
* @author Jesse Wilson
*/
public final class FileList extends TransformedList {
/** the destination file, just for user convenience */
private File file = null;
/** how bytes are encoded and decoded */
private ByteCoder byteCoder;
/** the underlying storage of ListEvents */
private PersistentMap storage = null;
/** the ID of the next update to write to disc */
private int nextUpdateId = 81;
/** whether this list can be modified */
private boolean writable = true;
/**
* Create a {@link FileList} that stores its data in the specified file.
*/
public FileList(File file, ByteCoder byteCoder) throws IOException {
super(new BasicEventList());
this.file = file;
this.byteCoder = byteCoder;
// store the updates in a persistent map
storage = new PersistentMap(file);
// sequence the updates
SortedMap sequentialUpdates = new TreeMap();
for(Iterator k = storage.keySet().iterator(); k.hasNext(); ) {
Integer key = (Integer)k.next();
Bufferlo valueBuffer = ((Chunk)storage.get(key)).getValue();
sequentialUpdates.put(key, valueBuffer);
}
// replay all the updates from the file
for(Iterator u = sequentialUpdates.keySet().iterator(); u.hasNext(); ) {
Integer key = (Integer)u.next();
Bufferlo update = (Bufferlo)sequentialUpdates.get(key);
ListEventToBytes.toListEvent(update, this, byteCoder);
}
// prepare the next update id to use
if(!sequentialUpdates.isEmpty()) {
nextUpdateId = ((Integer)sequentialUpdates.lastKey()).intValue() + 1;
}
// now that we're up-to-date, listen for further events
source.addListEventListener(this);
}
/** {@inheritDoc} */
@Override
public boolean isWritable() {
return writable;
}
/** {@inheritDoc} */
@Override
public void listChanged(ListEvent listChanges) {
// write the change to disc
try {
ListEvent listChangesCopy = listChanges.copy();
Bufferlo listChangesBytes = ListEventToBytes.toBytes(listChangesCopy, byteCoder);
storage.put(new Integer(nextUpdateId), new Chunk(listChangesBytes));
nextUpdateId++;
} catch(IOException e) {
throw new IllegalStateException(e.getMessage());
}
// forward the event to interested listeners
updates.forwardEvent(listChanges);
}
/**
* Closes this FileList so that it consumes no disc resources. The list may
* continue to be read until it is {@link #dispose() disposed}.
*/
public void close() {
if(storage != null) storage.close();
storage = null;
writable = false;
}
/** {@inheritDoc} */
@Override
public void dispose() {
close();
super.dispose();
}
}
libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/io/ListPeer.java 0000644 0001750 0001750 00000011552 12106516400 027133 0 ustar gregoa gregoa /* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.io;
// the core Glazed Lists packages
import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.impl.rbp.Peer;
import ca.odell.glazedlists.impl.rbp.ResourceStatus;
import java.io.IOException;
/**
* A {@link ListPeer} manages the network resources for publishing and subscribing
* to {@link NetworkList}s.
*
* A {@link ListPeer} must have its {@link #start()} method complete successfully
* before it can be used to {@link #publish(EventList,String,ByteCoder) publish()} and
* {@link #subscribe(String,int,String,ByteCoder) subscribe()} {@link EventList}s.
*
*
When a {@link ListPeer} is started, it listens for incoming connections on
* the specified port. When it is stopped, all active {@link NetworkList}s are
* {@link NetworkList#disconnect() disconnected}, and the port is closed.
*
*
To bring and individual {@link NetworkList} online or offline, use its
* {@link NetworkList#disconnect() disconnect()} and {@link NetworkList#connect() connect()}
* methods.
*
* @author Jesse Wilson
*/
public class ListPeer {
/** the peer manages the actual resources */
private Peer peer;
/**
* Creates a new ListPeer that binds to the specified port.
*/
public ListPeer(int listenPort) {
this.peer = new Peer(listenPort);
}
/**
* Starts the peer. This binds to the listen port and allows connections to
* be sent and received.
*
* @throws IOException if the listen port cannot be binded to. This will be due
* to the port being in use or as a consequence of insufficient privileges.
*/
public void start() throws IOException {
peer.start();
}
/**
* Stops the peer. This disconnects all active {@link EventList}s and releases
* the listen port.
*/
public void stop() {
peer.stop();
}
/**
* Prints the full state of this ListPeer.
*/
void print() {
peer.print();
}
/**
* Publish the specified EventList with the specified name.
*
* @param source the {@link EventList} to publish. Each change to this {@link EventList}
* will be immediately published to all subscribers.
* @param path the address that the {@link EventList} shall be published under.
* The path must start with a slash character. This must be unique among
* all {@link EventList}s published on this {@link ListPeer}.
* @param byteCoder a helper that can convert the elements of the {@link EventList}
* into binary network-transmittable form. Some general purpose {@link ByteCoder}s
* are available in the {@link ca.odell.glazedlists.GlazedLists GlazedLists}
* factory class.
* @return a simple decorator of the published {@link EventList}
* with additional methods to bring the list offline. This list is writable.
*/
public NetworkList publish(EventList source, String path, ByteCoder byteCoder) {
NetworkList published = new NetworkList(source, byteCoder);
ResourceStatus resourceStatus = peer.publish(published.getResource(), path);
published.setResourceStatus(resourceStatus);
published.setWritable(true);
return published;
}
/**
* Subscribe to the EventList with the specified name.
*
* @param host the network hostname of the machine serving the original copy
* of the data of interest. Together with the port, this should map
* to a proper {@link java.net.InetSocketAddress InetSocketAddress}.
* @param port the port the {@link EventList} of interest is being published on.
* @param path the address that the {@link EventList} is published under.
* @param byteCoder a helper that can convert the binary data from the network
* into list elements for this {@link EventList}. Some general purpose {@link ByteCoder}s
* are available in the {@link ca.odell.glazedlists.GlazedLists GlazedLists}
* factory class.
* @return the {@link EventList} that gets its data from the specified remote
* source. This {@link EventList} will contain no data until the connection
* completes. This list is not writable.
*/
public NetworkList subscribe(String host, int port, String path, ByteCoder byteCoder) {
NetworkList subscribed = new NetworkList(new BasicEventList(), byteCoder);
ResourceStatus resourceStatus = peer.subscribe(subscribed.getResource(), host, port, path);
subscribed.setResourceStatus(resourceStatus);
return subscribed;
}
}
libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/io/ByteCoder.java 0000644 0001750 0001750 00000002127 12106516400 027262 0 ustar gregoa gregoa /* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.io;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* An utility interface for converting Objects to bytes for storage or network
* transport. For some common, general-purpose {@link ByteCoder}s, see the
* {@link ca.odell.glazedlists.GlazedLists GlazedLists} factory class.
*
* @author Jesse Wilson
*/
public interface ByteCoder {
/**
* Encode the specified Object over the specified {@link OutputStream}.
*/
public void encode(Object source, OutputStream target) throws IOException;
/**
* Decode the Object from the specified {@link InputStream}. The stream should contain
* exactly one Object and no further bytes before the end of the stream.
*/
public Object decode(InputStream source) throws IOException;
} libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/io/NetworkList.java 0000644 0001750 0001750 00000025543 12106516400 027676 0 ustar gregoa gregoa /* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.io;
// the core Glazed Lists packages
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.TransformedList;
import ca.odell.glazedlists.event.ListEvent;
import ca.odell.glazedlists.impl.io.Bufferlo;
import ca.odell.glazedlists.impl.io.ListEventToBytes;
import ca.odell.glazedlists.impl.rbp.Resource;
import ca.odell.glazedlists.impl.rbp.ResourceListener;
import ca.odell.glazedlists.impl.rbp.ResourceStatus;
import ca.odell.glazedlists.impl.rbp.ResourceStatusListener;
import ca.odell.glazedlists.util.concurrent.ReadWriteLock;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* An {@link EventList} that is either published to the network or subscribed from
* the network. Since list elements must be transmitted over the network, each
* {@link NetworkList} requires a {@link ByteCoder} to convert {@link Object}s to
* and from bytes.
*
* To instantiate a {@link NetworkList}, use the
* {@link ListPeer#subscribe(String,int,String,ByteCoder) subscribe()}
* and {@link ListPeer#publish(EventList,String,ByteCoder) publish()} methods
* of a started {@link ListPeer}.
*
*
{@link NetworkList}s may be taken offline and brought back online with the
* {@link #connect()} and {@link #disconnect()} methods. This allows an application
* to use a {@link NetworkList} in spite of an unreliable network connection.
*
*
As a consequence of imperfect networks, {@link NetworkList}s may sometimes go
* offline on their own. Some causes of this include the server program shutting
* down or crashing, the local network connection becoming unavailable, or a
* problem with the physical link, such as an unplugged cable.
*
*
{@link NetworkList}s use a subset of HTTP/1.1 for transport, including
* chunked encoding. This protocol brings its own set of advantages:
*
HTTP is a standard well-understood protocol
* Clients may be served even if they are behind NAT or Firewalls
* The connection could be proxied by a standard HTTP proxy server, if necessary
* In theory, the served port could be shared with another HTTP daemon such as Tomcat
*
* And HTTP brings some disadvantages also:
*
A persistent connection must be held, even if updates are infrequent
* It cannot be differentiated from web traffic for analysis
*
* Warning: The protocol used by
* this version of {@link NetworkList} will be incompatible with future versions.
* Eventually the protocol will be finalized but the current protocol is a work
* in progress.
*
*
Warning: This class
* breaks the contract required by {@link java.util.List}. See {@link EventList}
* for an example.
*
*
* | EventList Overview |
* | Writable: | yes |
* | Concurrency: | Requires {@link ReadWriteLock} for every access, even for single-threaded use |
* | Performance: | N/A |
* | Memory: | O(N) |
* | Unit Tests: | N/A |
* | Issues: | N/A |
*
*
* @author Jesse Wilson
*/
public final class NetworkList extends TransformedList {
/** listeners to resource changes */
private List resourceListeners = new ArrayList();
/** listeners to status changes */
private List statusListeners = new ArrayList();
/** how bytes are encoded and decoded */
private ByteCoder byteCoder;
/** who manages this resource's connection */
private ResourceStatus resourceStatus = null;
/** whether this NetworkList is writable via its own API */
private boolean writable = false;
/** implementations of ResourceStatusListener and Resource */
private PrivateInterfaces privateInterfaces = new PrivateInterfaces();
/**
* Create a {@link NetworkList} that brings the specified source online.
*/
NetworkList(EventList source, ByteCoder byteCoder) {
super(source);
this.byteCoder = byteCoder;
source.addListEventListener(this);
}
/**
* Sets the ResourceStatus to delegate connection information requests to.
*/
void setResourceStatus(ResourceStatus resourceStatus) {
if(this.resourceStatus != null) throw new IllegalStateException();
this.resourceStatus = resourceStatus;
resourceStatus.addResourceStatusListener(privateInterfaces);
}
/**
* Set the NetworkList as writable.
*/
void setWritable(boolean writable) {
this.writable = writable;
}
/** {@inheritDoc} */
@Override
public boolean isWritable() {
return writable;
}
/**
* Gets the {@link Resource} that is the peer of this NetworkList.
*/
Resource getResource() {
return privateInterfaces;
}
/** {@inheritDoc} */
@Override
public void listChanged(ListEvent listChanges) {
// notify resource listeners
try {
ListEvent listChangesCopy = listChanges.copy();
Bufferlo listChangesBytes = ListEventToBytes.toBytes(listChangesCopy, byteCoder);
for(int r = 0; r < resourceListeners.size(); r++) {
ResourceListener listener = resourceListeners.get(r);
listener.resourceUpdated(privateInterfaces, listChangesBytes.duplicate());
}
} catch(IOException e) {
throw new IllegalStateException(e.getMessage());
}
// forward the event
updates.forwardEvent(listChanges);
}
/**
* Returns true if this resource is on the network. For published lists, this
* requires that the list is being served. For subscribed lists, this requires
* that a connection to the server has been established.
*/
public boolean isConnected() {
return resourceStatus.isConnected();
}
/**
* Attempts to bring this {@link NetworkList} online. When the connection attempt
* is successful (or when it fails), all {@link ResourceStatusListener}s will be
* notified.
*/
public void connect() {
resourceStatus.connect();
}
/**
* Attempts to take this {@link NetworkList} offline. When the {@link NetworkList}
* is fully disconnected, all {@link ResourceStatusListener}s will be notified.
*/
public void disconnect() {
resourceStatus.disconnect();
}
/**
* Implementations of all private interfaces.
*/
private class PrivateInterfaces implements Resource, ResourceStatusListener {
/**
* Called each time a resource becomes connected.
*/
public void resourceConnected(ResourceStatus resource) {
for(Iterator i = statusListeners.iterator(); i.hasNext(); ) {
NetworkListStatusListener listener = (NetworkListStatusListener)i.next();
listener.connected(NetworkList.this);
}
}
/**
* Called each time a resource's disconnected status changes. This method may
* be called for each attempt it makes to reconnect to the network.
*/
public void resourceDisconnected(ResourceStatus resource, Exception cause) {
for(Iterator i = statusListeners.iterator(); i.hasNext(); ) {
NetworkListStatusListener listener = (NetworkListStatusListener)i.next();
listener.disconnected(NetworkList.this, cause);
}
}
/** {@inheritDoc} */
public Bufferlo toSnapshot() {
getReadWriteLock().writeLock().lock();
try {
return ListEventToBytes.toBytes(NetworkList.this, byteCoder);
} catch(IOException e) {
throw new IllegalStateException(e.getMessage());
} finally {
getReadWriteLock().writeLock().unlock();
}
}
/** {@inheritDoc} */
public void fromSnapshot(Bufferlo snapshot) {
applyCodedEvent(snapshot);
}
/** {@inheritDoc} */
private void applyCodedEvent(Bufferlo data) {
getReadWriteLock().writeLock().lock();
try {
updates.beginEvent(true);
ListEventToBytes.toListEvent(data, source, byteCoder);
updates.commitEvent();
} catch(IOException e) {
throw new IllegalStateException(e.getMessage());
} finally {
getReadWriteLock().writeLock().unlock();
}
}
/** {@inheritDoc} */
public void update(Bufferlo delta) {
applyCodedEvent(delta);
}
/** {@inheritDoc} */
public void addResourceListener(ResourceListener listener) {
resourceListeners.add(listener);
}
/** {@inheritDoc} */
public void removeResourceListener(ResourceListener listener) {
for(int r = 0; r < resourceListeners.size(); r++) {
if(resourceListeners.get(r) == listener) {
resourceListeners.remove(r);
return;
}
}
}
/** {@inheritDoc} */
public ReadWriteLock getReadWriteLock() {
return NetworkList.this.getReadWriteLock();
}
@Override
public String toString() {
return NetworkList.this.toString();
}
}
/**
* Registers the specified listener to receive events about the status of this
* {@link NetworkList}.
*/
public void addStatusListener(NetworkListStatusListener listener) {
statusListeners.add(listener);
}
/**
* Deregisters the specified listener from receiving events about the status of
* this {@link NetworkList}.
*/
public void removeStatusListener(NetworkListStatusListener listener) {
statusListeners.remove(listener);
}
/** {@inheritDoc} */
@Override
public void dispose() {
resourceStatus.removeResourceStatusListener(privateInterfaces);
disconnect();
super.dispose();
}
}
libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/io/GlazedListsIO.java 0000644 0001750 0001750 00000003423 12106516400 030057 0 ustar gregoa gregoa /* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.io;
import ca.odell.glazedlists.impl.io.BeanXMLByteCoder;
import ca.odell.glazedlists.impl.io.SerializableByteCoder;
/**
* A factory for creating all sorts of objects to be used with Glazed Lists.
*
* @author Jesse Wilson
*/
public final class GlazedListsIO {
/**
* A dummy constructor to prevent instantiation of this class
*/
private GlazedListsIO() {
throw new UnsupportedOperationException();
}
// ByteCoders // // // // // // // // // // // // // // // // // // // // //
/** Provide Singleton access for all ByteCoders with no internal state */
private static ByteCoder serializableByteCoder = new SerializableByteCoder();
private static ByteCoder beanXMLByteCoder = new BeanXMLByteCoder();
/**
* Creates a {@link ByteCoder} that encodes {@link java.io.Serializable Serializable}
* Objects using an {@link java.io.ObjectOutputStream}.
*/
public static ByteCoder serializableByteCoder() {
if(serializableByteCoder == null) serializableByteCoder = new SerializableByteCoder();
return serializableByteCoder;
}
/**
* Creates a {@link ByteCoder} that uses {@link java.beans.XMLEncoder XMLEncoder} and
* {@link java.beans.XMLDecoder XMLDecoder} classes from java.beans. Encoded
* Objects must be JavaBeans.
*/
public static ByteCoder beanXMLByteCoder() {
if(beanXMLByteCoder == null) beanXMLByteCoder = new BeanXMLByteCoder();
return beanXMLByteCoder;
}
} libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/io/CachingList.java 0000644 0001750 0001750 00000024773 12106516400 027605 0 ustar gregoa gregoa /* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.io;
// the Glazed Lists' change objects
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.TransformedList;
import ca.odell.glazedlists.event.ListEvent;
import ca.odell.glazedlists.impl.adt.AgedNode;
import ca.odell.glazedlists.impl.adt.AgedNodeComparator;
import ca.odell.glazedlists.impl.adt.SparseList;
import ca.odell.glazedlists.impl.adt.SparseListNode;
import ca.odell.glazedlists.impl.adt.barcode2.Element;
import ca.odell.glazedlists.impl.adt.barcode2.SimpleTree;
import ca.odell.glazedlists.util.concurrent.Lock;
import ca.odell.glazedlists.util.concurrent.ReadWriteLock;
// For the execution of the performance test
/**
* An {@link ca.odell.glazedlists.EventList} that caches elements from its source {@link ca.odell.glazedlists.EventList}.
*
* It is useful in cases when the {@link #get(int)} method of an
* {@link ca.odell.glazedlists.EventList} is expensive. It can also be used when there are too many
* elements to keep in memory simultaneously. For caching to be effective, object
* access must be clustered.
*
* This {@link ca.odell.glazedlists.EventList} caches the most recently requested n elements.
*
*
By overriding the {@link #preFetch(int)} method, you can modify this
* CachingList to do predictive lookups for higher performance.
*
*
* | EventList Overview |
* | Writable: | yes |
* | Concurrency: | thread ready, not thread safe |
* | Performance: | reads: O(log N), writes O(log N) |
* | Memory: | O(N) |
* | Unit Tests: | N/A |
* | Issues: |
* 22
* 32
* 43
* 262
* |
*
*
*
*
* @author Kevin Maltby
*/
public class CachingList extends TransformedList {
/** The cache is implemented using a tree-based cache */
private SimpleTree cache;
/** The model of the source list for scalability with minimal memory footprint */
private SparseList indexTree;
/** The count of cache hits and cache misses for testing cache success */
private int cacheHits;
private int cacheMisses;
/** The number of elements in the cache */
private int currentSize = 0;
private int maxSize = 0;
/** The last inspected size of the source list */
private int lastKnownSize = 0;
/**
* Creates a {@link CachingList} that caches elements from the specified source
* {@link ca.odell.glazedlists.EventList}.
*
* @param source The source list to use to get values from
* @param maxSize The maximum size of the cache
*/
public CachingList(EventList source, int maxSize) {
super(source);
readWriteLock = new CacheLock(readWriteLock);
this.maxSize = maxSize;
cache = new SimpleTree(new AgedNodeComparator());
indexTree = new SparseList();
indexTree.addNulls(0, source.size());
source.addListEventListener(this);
lastKnownSize = source.size();
}
/** {@inheritDoc} */
@Override
public final int size() {
return source.size();
}
/** {@inheritDoc} */
@Override
public final Object get(int index) {
if(index >= size()) throw new IndexOutOfBoundsException("cannot get from tree of size " + size() + " at " + index);
preFetch(index);
return fetch(index, true);
}
/**
* Fetches a particular element.
*
* This might seem redundant with the existence of {@link #get(int)}.
* However, the goals of the methods are different. This method exists
* to be called by {@link #get(int)} or {@link #preFetch(int)}.
* This distinction allows users overriding this class a means of entry
* retrieval which does not implicitly execute a pre-fetch. This is
* particularly key for users overriding {@link #preFetch(int)}
*
* @param index The index of the value to retrieve
* @param recordHitsOrMisses Whether to increment the hit/miss counters
* (this should always be false when called from
* {@link #preFetch(int)}).
*
* @return The value associated with the given index,
* or null if the index is not found.
*/
protected final Object fetch(int index, boolean recordHitsOrMisses) {
// attempt to get the element from the cache
Object value = null;
Element cacheNode = (Element)indexTree.get(index);
// The value is cached, return cached value
if(cacheNode != null) {
if(recordHitsOrMisses) cacheHits ++;;
AgedNode agedNode = (AgedNode)cacheNode.get();
value = agedNode.getValue();
cache.remove(cacheNode);
SparseListNode indexNode = agedNode.getIndexNode();
indexNode.setValue(cache.addInSortedOrder((byte)1, agedNode, 1));
// The value is not cached, lookup from source and cache
} else {
if(recordHitsOrMisses) cacheMisses++;
// Make room in the cache if it is full
if(currentSize >= maxSize) {
Element oldestInCache = cache.get(0);
cache.remove(oldestInCache);
AgedNode oldAgedNode = (AgedNode)oldestInCache.get();
SparseListNode oldIndexNode = oldAgedNode.getIndexNode();
indexTree.set(oldIndexNode.getIndex(), null);
currentSize--;
}
// Cache the value
value = source.get(index);
indexTree.set(index, Boolean.TRUE);
SparseListNode indexNode = indexTree.getNode(index);
AgedNode agedNode = new AgedNode(indexNode, value);
indexNode.setValue(cache.addInSortedOrder((byte)1, agedNode, 1));
currentSize++;
}
return value;
}
/**
* Pre-fetches a set of data given the index that was directly requested.
*
*
Each application that wishes to take advantage of pre-fetching should
* implement this method in a way which best fits their particular use
* cases. As such, no default pre-fetch behaviour could really be defined,
* and thus this method is empty by default.
*
*
Because pre-fetching can modify the cache, child classes of CachingList
* should use careful consideration of locking when implementing this method.
*
* @param index The index that was requested from the cache
*/
protected void preFetch(int index) {
}
/** {@inheritDoc} */
@Override
protected boolean isWritable() {
return true;
}
/**
* Gets the total number of times that this list has fetched its result
* from the cache rather than from the source list.
*
* @return The number of times that this cache provided the result
*/
public final int getCacheHits() {
return cacheHits;
}
/**
* Gets the total number of times that this list has fetched its result
* from the source list rather than the cache.
*
* @return The number of times that this cache couldn't provide the result
*/
public final int getCacheMisses() {
return cacheMisses;
}
/**
* Gets the ratio of cache hits to cache misses. This is a number between
* 0 and 1, where 0 means the cache is unused and 1 means the cache was
* used exclusively.
*/
public final float getCacheHitRatio() {
if(cacheHits + cacheMisses == 0) return 0.0F;
return (float)cacheHits / (float)(cacheHits + cacheMisses);
}
/** {@inheritDoc} */
@Override
public final void listChanged(ListEvent listChanges) {
updates.beginEvent();
while(listChanges.next()) {
// get the current change info
int index = listChanges.getIndex();
int changeType = listChanges.getType();
// Lookup the cache entry for this index if possible
Element cacheNode = null;
if(index < lastKnownSize) {
cacheNode = (Element)indexTree.get(index);
}
// An INSERT causes the indexes of cached values to be offset.
if(changeType == ListEvent.INSERT) {
indexTree.add(index, null);
// A DELETE causes an entry to be removed and/or the index values to be offset.
} else if(changeType == ListEvent.DELETE) {
if(cacheNode != null) {
cache.remove(cacheNode);
currentSize--;
}
indexTree.remove(index);
// An UPDATE causes an existing entry to be removed
} else if(changeType == ListEvent.UPDATE) {
if(cacheNode != null) {
cache.remove(cacheNode);
currentSize--;
}
}
updates.addChange(changeType, index);
}
lastKnownSize = source.size();
updates.commitEvent();
}
/**
* A special lock to prevent deadlock in CachingList.
*/
private static class CacheLock implements ReadWriteLock {
/** The lock this CacheLock decorates */
private ReadWriteLock sourceLock;
/**
* Creates a new lock for CachingList.
*/
public CacheLock(ReadWriteLock sourceLock) {
this.sourceLock = sourceLock;
}
/**
* Since reads are write ops on caches, return the lock used for writing.
*/
public Lock readLock() {
return writeLock();
}
/**
* Return the lock used for writing.
*/
public Lock writeLock() {
return sourceLock.writeLock();
}
}
} libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/io/NetworkListStatusListener.java 0000644 0001750 0001750 00000001614 12106516400 032601 0 ustar gregoa gregoa /* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.io;
// NIO is used for BRP
import java.util.EventListener;
/**
* Listens to the current status of a {@link NetworkList}.
*
* @author Jesse Wilson
*/
public interface NetworkListStatusListener extends EventListener {
/**
* Called each time a {@link NetworkList} becomes connected.
*/
public void connected(NetworkList list);
/**
* Called each time a {@link NetworkList}'s connection status changes. This
* method may be called for each attempt it makes to reconnect to the network.
*/
public void disconnected(NetworkList list, Exception reason);
} libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/BasicEventList.java 0000644 0001750 0001750 00000031205 12106516400 027651 0 ustar gregoa gregoa /* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists;
import ca.odell.glazedlists.event.ListEventAssembler;
import ca.odell.glazedlists.event.ListEventListener;
import ca.odell.glazedlists.event.ListEventPublisher;
import ca.odell.glazedlists.impl.SerializedReadWriteLock;
import ca.odell.glazedlists.util.concurrent.LockFactory;
import ca.odell.glazedlists.util.concurrent.ReadWriteLock;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OptionalDataException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.RandomAccess;
/**
* An {@link EventList} that wraps any simple {@link List}, such as {@link ArrayList}
* or {@link LinkedList}.
*
*
Unlike most {@link EventList}s, this class is {@link Serializable}. When
* {@link BasicEventList} is serialized, all of its elements are serialized
* and all of its listeners that implement {@link Serializable}. Upon
* deserialization, the new copy uses a different {@link #getReadWriteLock() lock}
* than its source {@link BasicEventList}.
*
*
* | EventList Overview |
* | Writable: | yes |
* | Concurrency: | thread ready, not thread safe |
* | Performance: | reads: O(1), writes O(1) amortized |
* | Memory: | O(N) |
* | Unit Tests: | N/A |
* | Issues: | N/A |
*
*
* @author Jesse Wilson
*/
public final class BasicEventList extends AbstractEventList implements Serializable, RandomAccess {
/** For versioning as a {@link Serializable} */
private static final long serialVersionUID = 4883958173323072345L;
/** the underlying data list */
private List data;
/**
* Creates a {@link BasicEventList}.
*/
public BasicEventList() {
this(LockFactory.DEFAULT.createReadWriteLock());
}
/**
* Creates a {@link BasicEventList} that uses the specified {@link ReadWriteLock}
* for concurrent access.
*/
public BasicEventList(ReadWriteLock readWriteLock) {
this(null, readWriteLock);
}
/**
* Creates an empty {@link BasicEventList} with the given
* initialCapacity.
*/
public BasicEventList(int initalCapacity) {
this(initalCapacity, null, LockFactory.DEFAULT.createReadWriteLock());
}
/**
* Creates a {@link BasicEventList} using the specified
* {@link ListEventPublisher} and {@link ReadWriteLock}.
*
* @since 2006-June-12
*/
public BasicEventList(ListEventPublisher publisher, ReadWriteLock readWriteLock) {
this(10, publisher, readWriteLock);
}
/**
* Creates a {@link BasicEventList} using the specified initial capacity,
* {@link ListEventPublisher} and {@link ReadWriteLock}.
*
* @since 2007-April-19
*/
public BasicEventList(int initialCapacity, ListEventPublisher publisher, ReadWriteLock readWriteLock) {
super(publisher);
this.data = new ArrayList(initialCapacity);
this.readWriteLock = (readWriteLock == null) ? LockFactory.DEFAULT.createReadWriteLock() : readWriteLock;
}
/**
* Creates a {@link BasicEventList} that uses the specified {@link List} as
* the underlying implementation.
*
* Warning: all editing to
* the specified {@link List} must be done through via this
* {@link BasicEventList} interface. Otherwise this {@link BasicEventList} will
* become out of sync and operations will fail.
*
* @deprecated As of 2005/03/06, this constructor has been declared unsafe
* because the source list is exposed. This allows it to be modified without
* the required events being fired. This constructor has been replaced by
* the factory method {@link GlazedLists#eventList(Collection)}.
*/
public BasicEventList(List list) {
super(null);
this.data = list;
this.readWriteLock = LockFactory.DEFAULT.createReadWriteLock();
}
/** {@inheritDoc} */
@Override
public void add(int index, E element) {
// create the change event
updates.beginEvent();
updates.elementInserted(index, element);
// do the actual add
data.add(index, element);
// fire the event
updates.commitEvent();
}
/** {@inheritDoc} */
@Override
public boolean add(E element) {
// create the change event
updates.beginEvent();
updates.elementInserted(size(), element);
// do the actual add
boolean result = data.add(element);
// fire the event
updates.commitEvent();
return result;
}
/** {@inheritDoc} */
@Override
public boolean addAll(Collection extends E> collection) {
return addAll(size(), collection);
}
/** {@inheritDoc} */
@Override
public boolean addAll(int index, Collection extends E> collection) {
// don't do an add of an empty set
if(collection.size() == 0) return false;
// create the change event
updates.beginEvent();
for(Iterator extends E> i = collection.iterator(); i.hasNext(); ) {
E value = i.next();
updates.elementInserted(index, value);
data.add(index, value);
index++;
}
// fire the event
updates.commitEvent();
return !collection.isEmpty();
}
/** {@inheritDoc} */
@Override
public E remove(int index) {
// create the change event
updates.beginEvent();
// do the actual remove
E removed = data.remove(index);
// fire the event
updates.elementDeleted(index, removed);
updates.commitEvent();
return removed;
}
/** {@inheritDoc} */
@Override
public boolean remove(Object element) {
int index = data.indexOf(element);
if(index == -1) return false;
remove(index);
return true;
}
/** {@inheritDoc} */
@Override
public void clear() {
// don't do a clear on an empty set
if(isEmpty()) return;
// create the change event
updates.beginEvent();
for(int i = 0, size = size(); i < size; i++) {
updates.elementDeleted(0, get(i));
}
// do the actual clear
data.clear();
// fire the event
updates.commitEvent();
}
/** {@inheritDoc} */
@Override
public E set(int index, E element) {
// create the change event
updates.beginEvent();
// do the actual set
E previous = data.set(index, element);
// fire the event
updates.elementUpdated(index, previous);
updates.commitEvent();
return previous;
}
/** {@inheritDoc} */
@Override
public E get(int index) {
return data.get(index);
}
/** {@inheritDoc} */
@Override
public int size() {
return data.size();
}
/** {@inheritDoc} */
@Override
public boolean removeAll(Collection> collection) {
boolean changed = false;
updates.beginEvent();
for(Iterator i = collection.iterator(); i.hasNext(); ) {
Object value = i.next();
int index = -1;
while((index = indexOf(value)) != -1) {
E removed = data.remove(index);
updates.elementDeleted(index, removed);
changed = true;
}
}
updates.commitEvent();
return changed;
}
/** {@inheritDoc} */
@Override
public boolean retainAll(Collection> collection) {
boolean changed = false;
updates.beginEvent();
int index = 0;
while(index < data.size()) {
if(collection.contains(data.get(index))) {
index++;
} else {
E removed = data.remove(index);
updates.elementDeleted(index, removed);
changed = true;
}
}
updates.commitEvent();
return changed;
}
/**
* This method does nothing. It is not necessary to dispose a BasicEventList.
*/
public void dispose() { }
/**
* Although {@link EventList}s are not in general, {@link BasicEventList} is
* {@link Serializable}. All of the {@link ListEventListener}s that are themselves
* {@link Serializable} will be serialized, but others will not. Note that there
* is no easy way to access the {@link ListEventListener}s of
* an {@link EventList}, particularly after it has been serialized.
*
* As of October 3, 2005, this is the wire format of serialized
* {@link BasicEventList}s:
*
An Object[] containing each of the list's elements
* A ListEventListener[] containing only the
* listeners that themselves implement {@link Serializable}. Those that
* do not will not be serialized. Note that {@link TransformedList}s
* such as {@link FilterList} are not {@link Serializable} and will not
* be serialized.
*
* As of March 4, 2007, the wire format was extended to include:
*
the ListEventPublisher
* the ReadWriteLock represented as a {@link SerializedReadWriteLock}
* The motivation for this is documented here.
* Serialization streams with the old format are still readable. Serialization streams with
* the new format are not downwards-compatible.
*/
private void writeObject(ObjectOutputStream out) throws IOException {
// 1. The elements to write
E[] elements = (E[])data.toArray(new Object[data.size()]);
// 2. The Listeners to write
List> serializableListeners = new ArrayList>(1);
for(Iterator> i = updates.getListEventListeners().iterator(); i.hasNext(); ) {
ListEventListener listener = i.next();
if(!(listener instanceof Serializable)) continue;
serializableListeners.add(listener);
}
ListEventListener[] listeners = serializableListeners.toArray(new ListEventListener[serializableListeners.size()]);
// 3. Write the elements, listeners, publisher and lock
out.writeObject(elements);
out.writeObject(listeners);
out.writeObject(getPublisher());
out.writeObject(getReadWriteLock());
}
/**
* Peer method to {@link #writeObject(ObjectOutputStream)}. Note that this
* is functionally equivalent to a constructor and should validate that
* everything is in place including locks, etc.
*/
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
// 1. Read in the elements
final E[] elements = (E[]) in.readObject();
// 2. Read in the listeners
final ListEventListener[] listeners = (ListEventListener[]) in.readObject();
// 3. Try to read the ListEventPublisher and ReadWriteLock according to the new wire format
try {
this.publisher = (ListEventPublisher) in.readObject();
this.updates = new ListEventAssembler(this, publisher);
this.readWriteLock = (ReadWriteLock) in.readObject();
} catch (OptionalDataException e) {
if (e.eof)
// reading old serialization stream without publisher and lock
this.readWriteLock = LockFactory.DEFAULT.createReadWriteLock();
else throw e;
}
// 4. Populate the EventList data
this.data = new ArrayList(elements.length);
this.data.addAll(Arrays.asList(elements));
// 5. Populate the listeners
for(int i = 0; i < listeners.length; i++) {
this.updates.addListEventListener(listeners[i]);
}
}
} libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/swing/ 0000755 0001750 0001750 00000000000 12106516356 025267 5 ustar gregoa gregoa libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/swing/AutoCompleteSupport.java 0000644 0001750 0001750 00000340075 12106516356 032141 0 ustar gregoa gregoa /* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.swing;
import ca.odell.glazedlists.*;
import ca.odell.glazedlists.event.ListEvent;
import ca.odell.glazedlists.gui.TableFormat;
import ca.odell.glazedlists.impl.GlazedListsImpl;
import ca.odell.glazedlists.impl.filter.SearchTerm;
import ca.odell.glazedlists.impl.filter.TextMatcher;
import ca.odell.glazedlists.impl.swing.ComboBoxPopupLocationFix;
import ca.odell.glazedlists.matchers.Matcher;
import ca.odell.glazedlists.matchers.Matchers;
import ca.odell.glazedlists.matchers.TextMatcherEditor;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.Method;
import java.text.Format;
import java.text.ParsePosition;
import java.util.Comparator;
import java.util.List;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicComboBoxEditor;
import javax.swing.plaf.basic.ComboPopup;
import javax.swing.text.*;
/**
* This class {@link #install}s support for filtering and autocompletion into
* a standard {@link JComboBox}. It also acts as a factory class for
* {@link #createTableCellEditor creating autocompleting table cell editors}.
*
* All autocompletion behaviour provided is meant to mimic the functionality
* of the Firefox address field. To be explicit, the following is a list of
* expected behaviours which are installed:
*
*
Typing into the ComboBox Editor
*
* - typing any value into the editor when the popup is invisible causes
* the popup to appear and its contents to be filtered according to the
* editor's text. It also autocompletes to the first item that is
* prefixed with the editor's text and selects that item within the popup.
* - typing any value into the editor when the popup is visible causes
* the popup to be refiltered according to the editor's text and
* reselects an appropriate autocompletion item.
* - typing the down or up arrow keys in the editor when the popup is
* invisible causes the popup to appear and its contents to be filtered
* according to the editor's text. It also autocompletes to the first
* item that is prefixed with the editor's text and selects that item
* within the popup.
* - typing the up arrow key when the popup is visible and the selected
* element is the first element causes the autocompletion to be cleared
* and the popup's selection to be removed.
* - typing the up arrow key when no selection exists causes the last
* element of the popup to become selected and used for autocompletion
* - typing the down arrow key when the popup is visible and the selected
* element is the last element causes the autocompletion to be cleared
* and the popup's selection to be removed
* - typing the down arrow key when no selection exists causes the first
* element of the popup to become selected and used for autocompletion
* - typing the delete key while in strict mode will select the prior
* text rather than delete it. Attempting to delete all text results in
* a beep unless the autcomplete support has been configured to not beep.
*
*
* Clicking on the Arrow Button
*
* - clicking the arrow button when the popup is invisible causes the
* popup to appear and its contents to be shown unfiltered
*
- clicking the arrow button when the popup is visible causes the popup
* to be hidden
*
*
* Sizing the ComboBox Popup
*
* - the popup is always at least as wide as the
* autocompleting {@link JComboBox}, but may be wider to accomodate a
* {@link JComboBox#getPrototypeDisplayValue() prototype display value}
* if a non-null prototype display value exists
*
- as items are filtered in the ComboBoxModel, the popup height is
* adjusted to display between 0 and {@link JComboBox#getMaximumRowCount()}
* rows before scrolling the popup
*
*
* JComboBox ActionEvents
* A single {@link ActionEvent} is fired from the JComboBox in these situations:
*
* - the user hits the enter key
*
- the selected item within the popup is changed (which can happen due
* to a mouse click, a change in the autocompletion term, or using the
* arrow keys)
*
- the JComboBox loses focus and contains a value that does not appear
* in the ComboBoxModel
*
*
* Null Values in the ComboBoxModel
* null values located in the ComboBoxModel are considered
* identical to empty Strings ("") for the purposes of locating autocompletion
* terms.
*
* ComboBoxEditor Focus
*
* - the text in the ComboBoxEditor is selected if
* {@link #getSelectsTextOnFocusGain()} returns true
*
- the JPopupMenu is hidden when the ComboBoxEditor loses focus if
* {@link #getHidesPopupOnFocusLost()} returns true
*
*
* Extracting String Values
* Each value in the ComboBoxModel must be converted to a String for many
* reasons: filtering, setting into the ComboBoxEditor, displaying in the
* renderer, etc. By default, JComboBox relies on {@link Object#toString()}
* to map elements to their String equivalents. Sometimes, however, toString()
* is not a reliable or desirable mechanism to use. To deal with this problem,
* AutoCompleteSupport provides an install method that takes a {@link Format}
* object which is used to do all converting back and forth between Strings and
* ComboBoxModel objects.
*
* In order to achieve all of the autocompletion and filtering behaviour,
* the following occurs when {@link #install} is called:
*
*
* - the JComboBox will be made editable
*
- the JComboBox will have a custom ComboBoxModel installed on it
* containing the given items
*
- the ComboBoxEditor will be wrapped with functionality and set back
* into the JComboBox as the editor
*
- the JTextField which is the editor component for the JComboBox
* will have a DocumentFilter installed on its backing Document
*
*
* The strategy of this support class is to alter all of the objects which
* influence the behaviour of the JComboBox in one single context. With that
* achieved, it greatly reduces the cross-functional communication required to
* customize the behaviour of JComboBox for filtering and autocompletion.
*
* Warning: This class must be
* mutated from the Swing Event Dispatch Thread. Failure to do so will result in
* an {@link IllegalStateException} thrown from any one of:
*
*
* - {@link #install(JComboBox, EventList)}
*
- {@link #install(JComboBox, EventList, TextFilterator)}
*
- {@link #install(JComboBox, EventList, TextFilterator, Format)}
*
- {@link #isInstalled()}
*
- {@link #uninstall()}
*
- {@link #setCorrectsCase(boolean)}
*
- {@link #setStrict(boolean)}
*
- {@link #setBeepOnStrictViolation(boolean)}
*
- {@link #setSelectsTextOnFocusGain(boolean)}
*
- {@link #setHidesPopupOnFocusLost(boolean)}
*
- {@link #setFilterMode(int)}
*
- {@link #setFirstItem(Object)}
*
- {@link #removeFirstItem()}
*
*
* @author James Lemieux
*/
public final class AutoCompleteSupport {
private static final ParsePosition PARSE_POSITION = new ParsePosition(0);
private static final Class[] VALUE_OF_SIGNATURE = {String.class};
/** Marker object for indicating "not found". */
private static final Object NOT_FOUND = new Object();
//
// These member variables control behaviour of the autocompletion support
//
/**
* true if user-specified text is converted into the same case as
* the autocompletion term. false will leave user specified text
* unaltered.
*/
private boolean correctsCase = true;
/**
* false if the user can specify values that do not appear in the
* ComboBoxModel; true otherwise.
*/
private boolean strict = false;
/**
* true indicates a beep sound should be played to the user to
* indicate their error when attempting to violate the {@link #strict}
* setting; false indicates we should not beep.
*/
private boolean beepOnStrictViolation = true;
/**
* true if the text in the combobox editor is selected when the
* editor gains focus; false otherwise.
*/
private boolean selectsTextOnFocusGain = true;
/**
* true if the {@link #popupMenu} should always
* be hidden when the {@link #comboBoxEditor} loses focus; false
* if the default behaviour should be preserved. This exists to provide a
* reasonable alternative to the strange default behaviour in JComboBox in
* which the tab key will advance focus to the next focusable component and
* leave the JPopupMenu visible.
*/
private boolean hidesPopupOnFocusLost = true;
//
// These are member variables for convenience
//
/** The comboBox being decorated with autocomplete functionality. */
private JComboBox comboBox;
/** The popup menu of the decorated comboBox. */
private JPopupMenu popupMenu;
/** The popup that wraps the popupMenu of the decorated comboBox. */
private ComboPopup popup;
/** The arrow button that invokes the popup. */
private JButton arrowButton;
/** The model backing the comboBox. */
private final AutoCompleteComboBoxModel comboBoxModel;
/** The custom renderer installed on the comboBox or null if one is not required. */
private final ListCellRenderer renderer;
/** The EventList which holds the items present in the comboBoxModel. */
private final EventList items;
/** The FilterList which filters the items present in the comboBoxModel. */
private final FilterList filteredItems;
/** A single-element EventList for storing the optional first element, typically used to represent "no selection". */
private final EventList firstItem;
/** The CompositeList which is the union of firstItem and filteredItems to produce all filtered items available in the comboBoxModel. */
private final CompositeList allItemsFiltered;
/** The CompositeList which is the union of firstItem and items to produce all unfiltered items available in the comboBoxModel. */
private final CompositeList allItemsUnfiltered;
/** The Format capable of producing Strings from ComboBoxModel elements and vice versa. */
private final Format format;
/** The MatcherEditor driving the FilterList behind the comboBoxModel. */
private final TextMatcherEditor filterMatcherEditor;
/**
* The custom ComboBoxEditor that does NOT assume that the text value can
* be computed using Object.toString(). (i.e. the default ComboBoxEditor
* *does* assume that, but we decorate it and remove that assumption)
*/
private FormatComboBoxEditor comboBoxEditor;
/** The textfield which acts as the editor of the comboBox. */
private JTextField comboBoxEditorComponent;
/** The Document backing the comboBoxEditorComponent. */
private AbstractDocument document;
/** A DocumentFilter that controls edits to the document. */
private final AutoCompleteFilter documentFilter = new AutoCompleteFilter();
/** The last prefix specified by the user. */
private String prefix = "";
/** The Matcher that decides if a ComboBoxModel element is filtered out. */
private Matcher filterMatcher = Matchers.trueMatcher();
/** true while processing a text change to the {@link #comboBoxEditorComponent}; false otherwise. */
private boolean isFiltering = false;
/** Controls the selection behavior of the JComboBox when it is used in a JTable DefaultCellEditor. */
private final boolean isTableCellEditor;
//
// These listeners work together to enforce different aspects of the autocompletion behaviour
//
/**
* The MouseListener which is installed on the {@link #arrowButton} and
* is responsible for clearing the filter and then showing / hiding the
* {@link #popup}.
*/
private ArrowButtonMouseListener arrowButtonMouseListener;
/**
* A listener which reacts to changes in the ComboBoxModel by
* resizing the popup appropriately to accomodate the new data.
*/
private final ListDataListener listDataHandler = new ListDataHandler();
/**
* We ensure the popup menu is sized correctly each time it is shown.
* Namely, we respect the prototype display value of the combo box, if
* it has one. Regardless of the width of the combo box, we attempt to
* size the popup to accomodate the width of the prototype display value.
*/
private final PopupMenuListener popupSizerHandler = new PopupSizer();
/**
* An unfortunately necessary fixer for a misplaced popup.
*/
private ComboBoxPopupLocationFix popupLocationFix;
/**
* We ensure that selecting an item from the popup via the mouse never
* attempts to autocomplete for fear that we will replace the user's
* newly selected item and the item will effectively be unselectable.
*/
private final MouseListener popupMouseHandler = new PopupMouseHandler();
/** Handles the special case of the backspace key in strict mode and the enter key. */
private final KeyListener strictModeBackspaceHandler = new AutoCompleteKeyHandler();
/** Handles selecting the text in the comboBoxEditorComponent when it gains focus. */
private final FocusListener selectTextOnFocusGainHandler = new ComboBoxEditorFocusHandler();
//
// These listeners watch for invalid changes to the JComboBox which break our autocompletion
//
/**
* Watches for changes of the Document which backs comboBoxEditorComponent and uninstalls
* our DocumentFilter from the old Document and reinstalls it on the new.
*/
private final DocumentWatcher documentWatcher = new DocumentWatcher();
/** Watches for changes of the ComboBoxModel and reports them as violations. */
private final ModelWatcher modelWatcher = new ModelWatcher();
/** Watches for changes of the ComboBoxUI and reinstalls the autocompletion support. */
private final UIWatcher uiWatcher = new UIWatcher();
//
// These booleans control when certain changes are to be respected and when they aren't
//
/** true indicates document changes should not be post processed
* (i.e. just commit changes to the Document and do not cause any side-effects). */
private boolean doNotPostProcessDocumentChanges = false;
/** true indicates attempts to filter the ComboBoxModel should be ignored. */
private boolean doNotFilter = false;
/** true indicates attempts to change the document should be ignored. */
private boolean doNotChangeDocument = false;
/** true indicates attempts to select an autocompletion term should be ignored. */
private boolean doNotAutoComplete = false;
/** true indicates attempts to toggle the state of the popup should be ignored.
* In general, the only time we should toggle the state of a popup is due to a users keystroke
* (and not programmatically setting the selected item, for example).
*
* When the JComboBox is used within a TableCellEditor, this value is ALWAYS false, since
* we MUST accept keystrokes, even when they are passed second hand to the JComboBox after
* it has been installed as the cell editor (as opposed to typed into the JComboBox directly)
*/
private boolean doNotTogglePopup;
/** true indicates attempts to clear the filter when hiding the popup should be ignored.
* This is because sometimes we hide and reshow a popup in rapid succession and we want to avoid
* the work to unfiltering/refiltering it.
*/
private boolean doNotClearFilterOnPopupHide = false;
//
// Values present before {@link #install} executes - and are restored when {@link @uninstall} executes
//
/** The original setting of the editable field on the comboBox. */
private final boolean originalComboBoxEditable;
/** The original model installed on the comboBox. */
private ComboBoxModel originalModel;
/** The original ListCellRenderer installed on the comboBox. */
private ListCellRenderer originalRenderer;
//
// Values present before {@link #decorateCurrentUI} executes - and are restored when {@link @undecorateOriginalUI} executes
//
/** The original Actions associated with the up and down arrow keys. */
private Action originalSelectNextAction;
private Action originalSelectPreviousAction;
private Action originalSelectNext2Action;
private Action originalSelectPrevious2Action;
private Action originalAquaSelectNextAction;
private Action originalAquaSelectPreviousAction;
/**
* This private constructor creates an AutoCompleteSupport object which adds
* autocompletion functionality to the given comboBox. In
* particular, a custom {@link ComboBoxModel} is installed behind the
* comboBox containing the given items. The
* filterator is consulted in order to extract searchable
* text from each of the items. Non-null format
* objects are used to convert ComboBoxModel elements to Strings and back
* again for various functions like filtering, editing, and rendering.
*
* @param comboBox the {@link JComboBox} to decorate with autocompletion
* @param items the objects to display in the comboBox
* @param filterator extracts searchable text strings from each item
* @param format converts combobox elements into strings and vice versa
*/
private AutoCompleteSupport(JComboBox comboBox, EventList items, TextFilterator super E> filterator, Format format) {
this.comboBox = comboBox;
this.originalComboBoxEditable = comboBox.isEditable();
this.originalModel = comboBox.getModel();
this.items = items;
this.format = format;
// only build a custom renderer if the user specified their own Format but has not installed a custom renderer of their own
final boolean defaultRendererInstalled = comboBox.getRenderer() instanceof UIResource;
this.renderer = format != null && defaultRendererInstalled ? new StringFunctionRenderer() : null;
// is this combo box a TableCellEditor?
this.isTableCellEditor = Boolean.TRUE.equals(comboBox.getClientProperty("JComboBox.isTableCellEditor"));
this.doNotTogglePopup = !isTableCellEditor;
// lock the items list for reading since we want to prevent writes
// from occurring until we fully initialize this AutoCompleteSupport
items.getReadWriteLock().readLock().lock();
try {
// build the ComboBoxModel capable of filtering its values
this.filterMatcherEditor = new TextMatcherEditor(filterator == null ? new DefaultTextFilterator() : filterator);
this.filterMatcherEditor.setMode(TextMatcherEditor.STARTS_WITH);
this.filteredItems = new FilterList(items, this.filterMatcherEditor);
this.firstItem = new BasicEventList(items.getPublisher(), items.getReadWriteLock());
// the ComboBoxModel always contains the firstItem and a filtered view of all other items
this.allItemsFiltered = new CompositeList(items.getPublisher(), items.getReadWriteLock());
this.allItemsFiltered.addMemberList(this.firstItem);
this.allItemsFiltered.addMemberList(this.filteredItems);
this.comboBoxModel = new AutoCompleteComboBoxModel(this.allItemsFiltered);
// we need an unfiltered view in order to try to locate autocompletion terms
this.allItemsUnfiltered = new CompositeList(items.getPublisher(), items.getReadWriteLock());
this.allItemsUnfiltered.addMemberList(this.firstItem);
this.allItemsUnfiltered.addMemberList(this.items);
} finally {
items.getReadWriteLock().readLock().unlock();
}
// customize the comboBox
this.comboBox.setModel(this.comboBoxModel);
this.comboBox.setEditable(true);
decorateCurrentUI();
// react to changes made to the key parts of JComboBox which affect autocompletion
this.comboBox.addPropertyChangeListener("UI", this.uiWatcher);
this.comboBox.addPropertyChangeListener("model", this.modelWatcher);
this.comboBoxEditorComponent.addPropertyChangeListener("document", this.documentWatcher);
}
/**
* A convenience method to ensure {@link AutoCompleteSupport} is being
* accessed from the Event Dispatch Thread.
*/
private static void checkAccessThread() {
if (!SwingUtilities.isEventDispatchThread())
throw new IllegalStateException("AutoCompleteSupport must be accessed from the Swing Event Dispatch Thread, but was called on Thread \"" + Thread.currentThread().getName() + "\"");
}
/**
* A convenience method to unregister and return all {@link ActionListener}s
* currently installed on the given comboBox. This is the only
* technique we can rely on to prevent the comboBox from
* broadcasting {@link ActionEvent}s at inappropriate times.
*
* This method is the logical inverse of {@link #registerAllActionListeners}.
*/
private static ActionListener[] unregisterAllActionListeners(JComboBox comboBox) {
final ActionListener[] listeners = comboBox.getActionListeners();
for (int i = 0; i < listeners.length; i++)
comboBox.removeActionListener(listeners[i]);
return listeners;
}
/**
* A convenience method to register all of the given listeners
* with the given comboBox.
*
* This method is the logical inverse of {@link #unregisterAllActionListeners}.
*/
private static void registerAllActionListeners(JComboBox comboBox, ActionListener[] listeners) {
for (int i = 0; i < listeners.length; i++)
comboBox.addActionListener(listeners[i]);
}
/**
* A convenience method to search through the given JComboBox for the
* JButton which toggles the popup up open and closed.
*/
private static JButton findArrowButton(JComboBox c) {
for (int i = 0, n = c.getComponentCount(); i < n; i++) {
final Component comp = c.getComponent(i);
if (comp instanceof JButton)
return (JButton) comp;
}
return null;
}
/**
* Decorate all necessary areas of the current UI to install autocompletion
* support. This method is called in the constructor and when the comboBox's
* UI delegate is changed.
*/
private void decorateCurrentUI() {
// record some original settings of comboBox
this.originalRenderer = comboBox.getRenderer();
this.popupMenu = (JPopupMenu) comboBox.getUI().getAccessibleChild(comboBox, 0);
this.popup = (ComboPopup) popupMenu;
this.arrowButton = findArrowButton(comboBox);
// if an arrow button was found, decorate the ComboPopup's MouseListener
// with logic that unfilters the ComboBoxModel when the arrow button is pressed
if (this.arrowButton != null) {
this.arrowButton.removeMouseListener(popup.getMouseListener());
this.arrowButtonMouseListener = new ArrowButtonMouseListener(popup.getMouseListener());
this.arrowButton.addMouseListener(arrowButtonMouseListener);
}
// start listening for model changes (due to filtering) so we can resize the popup vertically
this.comboBox.getModel().addListDataListener(listDataHandler);
// calculate the popup's width according to the prototype value, if one exists
this.popupMenu.addPopupMenuListener(popupSizerHandler);
// fix the popup's location
this.popupLocationFix = ComboBoxPopupLocationFix.install(this.comboBox);
// start suppressing autocompletion when selecting values from the popup with the mouse
this.popup.getList().addMouseListener(popupMouseHandler);
// record the original Up/Down arrow key Actions
final ActionMap actionMap = comboBox.getActionMap();
this.originalSelectNextAction = actionMap.get("selectNext");
this.originalSelectPreviousAction = actionMap.get("selectPrevious");
this.originalSelectNext2Action = actionMap.get("selectNext2");
this.originalSelectPrevious2Action = actionMap.get("selectPrevious2");
this.originalAquaSelectNextAction = actionMap.get("aquaSelectNext");
this.originalAquaSelectPreviousAction = actionMap.get("aquaSelectPrevious");
final Action upAction = new MoveAction(-1);
final Action downAction = new MoveAction(1);
// install custom actions for the arrow keys in all non-Apple L&Fs
actionMap.put("selectPrevious", upAction);
actionMap.put("selectNext", downAction);
actionMap.put("selectPrevious2", upAction);
actionMap.put("selectNext2", downAction);
// install custom actions for the arrow keys in the Apple Aqua L&F
actionMap.put("aquaSelectPrevious", upAction);
actionMap.put("aquaSelectNext", downAction);
// install a custom ComboBoxEditor that decorates the existing one, but uses the
// convertToString(...) method to produce text for the editor component (rather than .toString())
this.comboBoxEditor = new FormatComboBoxEditor(comboBox.getEditor());
this.comboBox.setEditor(comboBoxEditor);
// add a DocumentFilter to the Document backing the editor JTextField
this.comboBoxEditorComponent = (JTextField) comboBox.getEditor().getEditorComponent();
this.document = (AbstractDocument) comboBoxEditorComponent.getDocument();
this.document.setDocumentFilter(documentFilter);
// install a custom renderer on the combobox, if we have built one
if (this.renderer != null)
comboBox.setRenderer(renderer);
// add a KeyListener to the ComboBoxEditor which handles the special case of backspace when in strict mode
this.comboBoxEditorComponent.addKeyListener(strictModeBackspaceHandler);
// add a FocusListener to the ComboBoxEditor which selects all text when focus is gained
this.comboBoxEditorComponent.addFocusListener(selectTextOnFocusGainHandler);
}
/**
* Remove all customizations installed to various areas of the current UI
* in order to uninstall autocompletion support. This method is invoked
* after the comboBox's UI delegate is changed.
*/
private void undecorateOriginalUI() {
// if an arrow button was found, remove our custom MouseListener and
// reinstall the normal popup MouseListener
if (this.arrowButton != null) {
this.arrowButton.removeMouseListener(arrowButtonMouseListener);
this.arrowButton.addMouseListener(arrowButtonMouseListener.getDecorated());
}
// stop listening for model changes
this.comboBox.getModel().removeListDataListener(listDataHandler);
// remove the DocumentFilter from the Document backing the editor JTextField
this.document.setDocumentFilter(null);
// restore the original ComboBoxEditor if our custom ComboBoxEditor is still installed
if (this.comboBox.getEditor() == comboBoxEditor)
this.comboBox.setEditor(comboBoxEditor.getDelegate());
// stop adjusting the popup's width according to the prototype value
this.popupMenu.removePopupMenuListener(popupSizerHandler);
// stop fixing the combobox's popup location
this.popupLocationFix.uninstall();
// stop suppressing autocompletion when selecting values from the popup with the mouse
this.popup.getList().removeMouseListener(popupMouseHandler);
final ActionMap actionMap = comboBox.getActionMap();
// restore the original actions for the arrow keys in all non-Apple L&Fs
actionMap.put("selectPrevious", originalSelectPreviousAction);
actionMap.put("selectNext", originalSelectNextAction);
actionMap.put("selectPrevious2", originalSelectPrevious2Action);
actionMap.put("selectNext2", originalSelectNext2Action);
// restore the original actions for the arrow keys in the Apple Aqua L&F
actionMap.put("aquaSelectPrevious", originalAquaSelectPreviousAction);
actionMap.put("aquaSelectNext", originalAquaSelectNextAction);
// remove the KeyListener from the ComboBoxEditor which handles the special case of backspace when in strict mode
this.comboBoxEditorComponent.removeKeyListener(strictModeBackspaceHandler);
// remove the FocusListener from the ComboBoxEditor which selects all text when focus is gained
this.comboBoxEditorComponent.removeFocusListener(selectTextOnFocusGainHandler);
// remove the custom renderer if it is still installed
if (this.comboBox.getRenderer() == renderer)
this.comboBox.setRenderer(originalRenderer);
// erase some original settings of comboBox
this.originalRenderer = null;
this.comboBoxEditor = null;
this.comboBoxEditorComponent = null;
this.document = null;
this.popupMenu = null;
this.popup = null;
this.arrowButton = null;
}
/**
* Installs support for autocompletion into the comboBox and
* returns the support object that is actually providing those facilities.
* The support object is returned so that the caller may invoke
* {@link #uninstall} at some later time to remove the autocompletion
* features.
*
* This method assumes that the items can be converted into
* reasonable String representations via {@link Object#toString()}.
*
*
The following must be true in order to successfully install support
* for autocompletion on a {@link JComboBox}:
*
*
* - The JComboBox must use a {@link JTextField} as its editor component
*
- The JTextField must use an {@link AbstractDocument} as its model
*
*
* @param comboBox the {@link JComboBox} to decorate with autocompletion
* @param items the objects to display in the comboBox
* @return an instance of the support class providing autocomplete features
* @throws IllegalStateException if this method is called from any Thread
* other than the Swing Event Dispatch Thread
*/
public static AutoCompleteSupport install(JComboBox comboBox, EventList items) {
return install(comboBox, items, null);
}
/**
* Installs support for autocompletion into the comboBox and
* returns the support object that is actually providing those facilities.
* The support object is returned so that the caller may invoke
* {@link #uninstall} at some later time to remove the autocompletion
* features.
*
* This method assumes that the items can be converted into
* reasonable String representations via {@link Object#toString()}.
*
*
The filterator will be used to extract searchable text
* strings from each of the items. A null
* filterator implies the item's toString() method should be used when
* filtering it.
*
*
The following must be true in order to successfully install support
* for autocompletion on a {@link JComboBox}:
*
*
* - The JComboBox must use a {@link JTextField} as its editor component
*
- The JTextField must use an {@link AbstractDocument} as its model
*
*
* @param comboBox the {@link JComboBox} to decorate with autocompletion
* @param items the objects to display in the comboBox
* @param filterator extracts searchable text strings from each item;
* null implies the item's toString() method should be
* used when filtering it
* @return an instance of the support class providing autocomplete features
* @throws IllegalStateException if this method is called from any Thread
* other than the Swing Event Dispatch Thread
*/
public static AutoCompleteSupport install(JComboBox comboBox, EventList items, TextFilterator super E> filterator) {
return install(comboBox, items, filterator, null);
}
/**
* Installs support for autocompletion into the comboBox and
* returns the support object that is actually providing those facilities.
* The support object is returned so that the caller may invoke
* {@link #uninstall} at some later time to remove the autocompletion
* features.
*
* This method uses the given format to convert the
* given items into Strings and back again. In other words,
* this method does NOT rely on {@link Object#toString()}
* to produce a reasonable String representation of each item. Likewise,
* it does not rely on the existence of a valueOf(String) method for
* creating items out of Strings as is the default behaviour of JComboBox.
*
*
It can be assumed that the only methods called on the given format are:
*
* - {@link Format#format(Object)}
*
- {@link Format#parseObject(String, ParsePosition)}
*
*
* As a convenience, this method will install a custom
* {@link ListCellRenderer} on the comboBox that displays the
* String value returned by the format. Though this is only
* done if the given format is not null and if
* the comboBox does not already use a custom renderer.
*
*
The filterator will be used to extract searchable text
* strings from each of the items. A null
* filterator implies one of two default strategies will be used. If the
* format is not null then the String value returned from the
* format object will be used when filtering a given item.
* Otherwise, the item's toString() method will be used when it is filtered.
*
*
The following must be true in order to successfully install support
* for autocompletion on a {@link JComboBox}:
*
*
* - The JComboBox must use a {@link JTextField} as its editor component
*
- The JTextField must use an {@link AbstractDocument} as its model
*
*
* @param comboBox the {@link JComboBox} to decorate with autocompletion
* @param items the objects to display in the comboBox
* @param filterator extracts searchable text strings from each item. If the
* format is not null then the String value returned from
* the format object will be used when filtering a given
* item. Otherwise, the item's toString() method will be used when it
* is filtered.
* @param format a Format object capable of converting items
* into Strings and back. null indicates the standard
* JComboBox methods of converting are acceptable.
* @return an instance of the support class providing autocomplete features
* @throws IllegalStateException if this method is called from any Thread
* other than the Swing Event Dispatch Thread
*/
public static AutoCompleteSupport install(JComboBox comboBox, EventList items, TextFilterator super E> filterator, Format format) {
checkAccessThread();
final Component editorComponent = comboBox.getEditor().getEditorComponent();
if (!(editorComponent instanceof JTextField))
throw new IllegalArgumentException("comboBox must use a JTextField as its editor component");
if (!(((JTextField) editorComponent).getDocument() instanceof AbstractDocument))
throw new IllegalArgumentException("comboBox must use a JTextField backed by an AbstractDocument as its editor component");
if (comboBox.getModel().getClass() == AutoCompleteSupport.AutoCompleteComboBoxModel.class)
throw new IllegalArgumentException("comboBox is already configured for autocompletion");
return new AutoCompleteSupport(comboBox, items, filterator, format);
}
/**
* This method is used to report environmental invariants which are
* violated when the user adjusts the combo box in a way that is
* incompatible with the requirements for autocompletion. A message can be
* specified which will be included in the {@link IllegalStateException}
* that is throw out of this method after the autocompletion support is
* uninstalled.
*
* @param message a message to the programmer explaining the environmental
* invariant that was violated
*/
private void throwIllegalStateException(String message) {
final String exceptionMsg = message + "\n" +
"In order for AutoCompleteSupport to continue to " +
"work, the following invariants must be maintained after " +
"AutoCompleteSupport.install() has been called:\n" +
"* the ComboBoxModel may not be removed\n" +
"* the AbstractDocument behind the JTextField can be changed but must be changed to some subclass of AbstractDocument\n" +
"* the DocumentFilter on the AbstractDocument behind the JTextField may not be removed\n";
uninstall();
throw new IllegalStateException(exceptionMsg);
}
/**
* A convenience method to produce a String from the given
* comboBoxElement.
*/
private String convertToString(Object comboBoxElement) {
if (comboBoxElement == NOT_FOUND)
return "NOT_FOUND";
if (format != null)
return format.format(comboBoxElement);
return comboBoxElement == null ? "" : comboBoxElement.toString();
}
/**
* Returns the autocompleting {@link JComboBox} or null if
* {@link AutoCompleteSupport} has been {@link #uninstall}ed.
*/
public JComboBox getComboBox() {
return this.comboBox;
}
/**
* Returns the {@link TextFilterator} that extracts searchable strings from
* each item in the {@link ComboBoxModel}.
*/
public TextFilterator super E> getTextFilterator() {
return this.filterMatcherEditor.getFilterator();
}
/**
* Returns the filtered {@link EventList} of items which backs the
* {@link ComboBoxModel} of the autocompleting {@link JComboBox}.
*/
public EventList getItemList() {
return this.filteredItems;
}
/**
* Returns true if user specified strings are converted to the
* case of the autocompletion term they match; false otherwise.
*/
public boolean getCorrectsCase() {
return correctsCase;
}
/**
* If correctCase is true, user specified strings
* will be converted to the case of the element they match. Otherwise
* they will be left unaltered.
*
* Note: this flag only has meeting when strict mode is turned off.
* When strict mode is on, case is corrected regardless of this setting.
*
* @see #setStrict(boolean)
*
* @throws IllegalStateException if this method is called from any Thread
* other than the Swing Event Dispatch Thread
*/
public void setCorrectsCase(boolean correctCase) {
checkAccessThread();
this.correctsCase = correctCase;
}
/**
* Returns true if the user is able to specify values which do not
* appear in the popup list of suggestions; false otherwise.
*/
public boolean isStrict() {
return strict;
}
/**
* If strict is false, the user can specify values
* not appearing within the ComboBoxModel. If it is true each
* keystroke must continue to match some value in the ComboBoxModel or it
* will be discarded.
*
*
Note: When strict mode is enabled, all user input is corrected to the
* case of the autocompletion term, regardless of the correctsCase setting.
*
* @see #setCorrectsCase(boolean)
*
* @throws IllegalStateException if this method is called from any Thread
* other than the Swing Event Dispatch Thread
*/
public void setStrict(boolean strict) {
checkAccessThread();
if (this.strict == strict) return;
this.strict = strict;
// if strict mode was just turned on, ensure the comboBox contains a
// value from the ComboBoxModel (i.e. start being strict!)
if (strict) {
final String currentText = comboBoxEditorComponent.getText();
Object currentItem = findAutoCompleteTerm(currentText);
String currentItemText = convertToString(currentItem);
boolean itemMatches = currentItem == comboBox.getSelectedItem();
boolean textMatches = GlazedListsImpl.equal(currentItemText, currentText);
// select the first element if no autocompletion term could be found
if (currentItem == NOT_FOUND && !allItemsUnfiltered.isEmpty()) {
currentItem = allItemsUnfiltered.get(0);
currentItemText = convertToString(currentItem);
itemMatches = currentItem == comboBox.getSelectedItem();
textMatches = GlazedListsImpl.equal(currentItemText, currentText);
}
// return all elements to the ComboBoxModel
applyFilter("");
doNotPostProcessDocumentChanges = true;
try {
// adjust the editor's text, if necessary
if (!textMatches)
comboBoxEditorComponent.setText(currentItemText);
// adjust the model's selected item, if necessary
if (!itemMatches || comboBox.getSelectedIndex() == -1)
comboBox.setSelectedItem(currentItem);
} finally {
doNotPostProcessDocumentChanges = false;
}
}
}
/**
* Returns true if a beep sound is played when the user attempts
* to violate the strict invariant; false if no beep sound is
* played. This setting is only respected if {@link #isStrict()} returns
* true.
*
* @see #setStrict(boolean)
*/
public boolean getBeepOnStrictViolation() {
return beepOnStrictViolation;
}
/**
* Sets the policy for indicating strict-mode violations to the user by way
* of a beep sound.
*
* @param beepOnStrictViolation true if a beep sound should be
* played when the user attempts to violate the strict invariant;
* false if no beep sound should be played
*
* @throws IllegalStateException if this method is called from any Thread
* other than the Swing Event Dispatch Thread
*/
public void setBeepOnStrictViolation(boolean beepOnStrictViolation) {
checkAccessThread();
this.beepOnStrictViolation = beepOnStrictViolation;
}
/**
* Returns true if the combo box editor text is selected when it
* gains focus; false otherwise.
*/
public boolean getSelectsTextOnFocusGain() {
return selectsTextOnFocusGain;
}
/**
* If selectsTextOnFocusGain is true, all text in the
* editor is selected when the combo box editor gains focus. If it is
* false the selection state of the editor is not effected by
* focus changes.
*
* @throws IllegalStateException if this method is called from any Thread
* other than the Swing Event Dispatch Thread
*/
public void setSelectsTextOnFocusGain(boolean selectsTextOnFocusGain) {
checkAccessThread();
this.selectsTextOnFocusGain = selectsTextOnFocusGain;
}
/**
* Returns true if the popup menu is hidden whenever the combo
* box editor loses focus; false otherwise.
*/
public boolean getHidesPopupOnFocusLost() {
return hidesPopupOnFocusLost;
}
/**
* If hidesPopupOnFocusLost is true, then the popup
* menu of the combo box is always hidden whenever the
* combo box editor loses focus. If it is false the default
* behaviour is preserved. In practice this means that if focus is lost
* because of a MouseEvent, the behaviour is reasonable, but if focus is
* lost because of a KeyEvent (e.g. tabbing to the next focusable component)
* then the popup menu remains visible.
*
* @throws IllegalStateException if this method is called from any Thread
* other than the Swing Event Dispatch Thread
*/
public void setHidesPopupOnFocusLost(boolean hidesPopupOnFocusLost) {
checkAccessThread();
this.hidesPopupOnFocusLost = hidesPopupOnFocusLost;
}
/**
* Returns the manner in which the contents of the {@link ComboBoxModel}
* are filtered. This method will return one of
* {@link TextMatcherEditor#CONTAINS} or {@link TextMatcherEditor#STARTS_WITH}.
*
*
{@link TextMatcherEditor#CONTAINS} indicates elements of the
* {@link ComboBoxModel} are matched when they contain the text entered by
* the user.
*
*
{@link TextMatcherEditor#STARTS_WITH} indicates elements of the
* {@link ComboBoxModel} are matched when they start with the text entered
* by the user.
*
*
In both modes, autocompletion only occurs when a given item starts
* with user-specified text. The filter mode only affects the filtering
* aspect of autocomplete support.
*/
public int getFilterMode() {
return filterMatcherEditor.getMode();
}
/**
* Sets the manner in which the contents of the {@link ComboBoxModel} are
* filtered. The given mode must be one of
* {@link TextMatcherEditor#CONTAINS} or {@link TextMatcherEditor#STARTS_WITH}.
*
* @throws IllegalStateException if this method is called from any Thread
* other than the Swing Event Dispatch Thread
*
* @see #getFilterMode()
*/
public void setFilterMode(int mode) {
checkAccessThread();
// adjust the MatcherEditor that filters the AutoCompleteComboBoxModel to respect the given mode
// but ONLY adjust the contents of the model, avoid changing the text in the JComboBox's textfield
doNotChangeDocument = true;
try {
filterMatcherEditor.setMode(mode);
} finally {
doNotChangeDocument = false;
}
}
/**
* Sets the manner in which the contents of the {@link ComboBoxModel} are
* filtered and autocompletion terms are matched. The given strategy must be one of
* {@link TextMatcherEditor#IDENTICAL_STRATEGY} or {@link TextMatcherEditor#NORMALIZED_STRATEGY}
* or the Unicode strategy of the ICU4J extension.
*
* @throws IllegalStateException if this method is called from any Thread
* other than the Swing Event Dispatch Thread
*
* @see #getTextMatchingStrategy()
*/
public void setTextMatchingStrategy(Object strategy) {
checkAccessThread();
// adjust the MatcherEditor that filters the AutoCompleteComboBoxModel to respect the given strategy
// but ONLY adjust the contents of the model, avoid changing the text in the JComboBox's textfield
doNotChangeDocument = true;
try {
filterMatcherEditor.setStrategy(strategy);
// do we need to update the filterMatcher here?
} finally {
doNotChangeDocument = false;
}
}
/**
* Returns the manner in which the contents of the {@link ComboBoxModel} are
* filtered and autocompletion terms are matched. The returned strategy is one of
* {@link TextMatcherEditor#IDENTICAL_STRATEGY} or {@link TextMatcherEditor#NORMALIZED_STRATEGY}
* or the Unicode strategy of the ICU4J extension.
*/
public Object getTextMatchingStrategy() {
return filterMatcherEditor.getStrategy();
}
/**
* This method set a single optional value to be used as the first element
* in the {@link ComboBoxModel}. This value typically represents
* "no selection" or "blank". This value is always present and is not
* filtered away during autocompletion.
*
* @param item the first value to present in the {@link ComboBoxModel}
*/
public void setFirstItem(E item) {
checkAccessThread();
doNotChangeDocument = true;
firstItem.getReadWriteLock().writeLock().lock();
try {
if (firstItem.isEmpty())
firstItem.add(item);
else
firstItem.set(0, item);
} finally {
firstItem.getReadWriteLock().writeLock().unlock();
doNotChangeDocument = false;
}
}
/**
* Returns the optional single value used as the first element in the
* {@link ComboBoxModel} or null if no first item has been set.
*
* @return the special first value presented in the {@link ComboBoxModel}
* or null if no first item has been set
*/
public E getFirstItem() {
firstItem.getReadWriteLock().readLock().lock();
try {
return firstItem.isEmpty() ? null : firstItem.get(0);
} finally {
firstItem.getReadWriteLock().readLock().unlock();
}
}
/**
* Removes and returns the optional single value used as the first element
* in the {@link ComboBoxModel} or null if no first item has been
* set.
*
* @return the special first value presented in the {@link ComboBoxModel}
* or null if no first item has been set
*/
public E removeFirstItem() {
checkAccessThread();
doNotChangeDocument = true;
firstItem.getReadWriteLock().writeLock().lock();
try {
return firstItem.isEmpty() ? null : firstItem.remove(0);
} finally {
firstItem.getReadWriteLock().writeLock().unlock();
doNotChangeDocument = false;
}
}
/**
* Returns true if this autocomplete support instance is currently
* installed and altering the behaviour of the combo box; false if
* it has been {@link #uninstall}ed.
*
* @throws IllegalStateException if this method is called from any Thread
* other than the Swing Event Dispatch Thread
*/
public boolean isInstalled() {
checkAccessThread();
return comboBox != null;
}
/**
* This method removes autocompletion support from the {@link JComboBox}
* it was installed on. This method is useful when the {@link EventList} of
* items that backs the combo box must outlive the combo box itself.
* Calling this method will return the combo box to its original state
* before autocompletion was installed, and it will be available for
* garbage collection independently of the {@link EventList} of items.
*
* @throws IllegalStateException if this method is called from any Thread
* other than the Swing Event Dispatch Thread
*/
public void uninstall() {
checkAccessThread();
if (this.comboBox == null)
throw new IllegalStateException("This AutoCompleteSupport has already been uninstalled");
items.getReadWriteLock().readLock().lock();
try {
// 1. stop listening for changes
this.comboBox.removePropertyChangeListener("UI", this.uiWatcher);
this.comboBox.removePropertyChangeListener("model", this.modelWatcher);
this.comboBoxEditorComponent.removePropertyChangeListener("document", this.documentWatcher);
// 2. undecorate the original UI components
this.undecorateOriginalUI();
// 3. restore the original model to the JComboBox
this.comboBox.setModel(originalModel);
this.originalModel = null;
// 4. restore the original editable flag to the JComboBox
this.comboBox.setEditable(originalComboBoxEditable);
// 5. dispose of our ComboBoxModel
this.comboBoxModel.dispose();
// 6. dispose of our EventLists so that they are severed from the given items EventList
this.allItemsFiltered.dispose();
this.allItemsUnfiltered.dispose();
this.filteredItems.dispose();
// null out the comboBox to indicate that this support class is uninstalled
this.comboBox = null;
} finally {
items.getReadWriteLock().readLock().unlock();
}
}
/**
* This method updates the value which filters the items in the
* ComboBoxModel.
*
* @param newFilter the new value by which to filter the item
*/
private void applyFilter(String newFilter) {
// break out early if we're flagged to ignore filter updates for the time being
if (doNotFilter) return;
// ignore attempts to change the text in the combo box editor while
// the filtering is taking place
doNotChangeDocument = true;
final ActionListener[] listeners = unregisterAllActionListeners(comboBox);
isFiltering = true;
try {
filterMatcherEditor.setFilterText(new String[] {newFilter});
} finally {
isFiltering = false;
registerAllActionListeners(comboBox, listeners);
doNotChangeDocument = false;
}
}
/**
* This method updates the {@link #prefix} to be the current value in the
* ComboBoxEditor.
*/
private void updateFilter() {
prefix = comboBoxEditorComponent.getText();
if (prefix.length() == 0)
filterMatcher = Matchers.trueMatcher();
else
filterMatcher = new TextMatcher(new SearchTerm[] {new SearchTerm(prefix)}, GlazedLists.toStringTextFilterator(), TextMatcherEditor.STARTS_WITH, getTextMatchingStrategy());
}
/**
* A small convenience method to try showing the ComboBoxPopup.
*/
private void togglePopup() {
// break out early if we're flagged to ignore attempts to toggle the popup state
if (doNotTogglePopup) return;
if (comboBoxModel.getSize() == 0)
comboBox.hidePopup();
else if (comboBox.isShowing() && !comboBox.isPopupVisible() && comboBoxEditorComponent.hasFocus())
comboBox.showPopup();
}
/**
* Performs a linear scan of ALL ITEMS, regardless of the filtering state
* of the ComboBoxModel, to locate the autocomplete term. If an exact
* match of the given value can be found, then the item is
* returned. If an exact match cannot be found, the first term that
* starts with the given value is returned.
*
* If no exact or partial match can be located, null is
* returned.
*/
private Object findAutoCompleteTerm(String value) {
// determine if our value is empty
final boolean prefixIsEmpty = "".equals(value);
final Matcher valueMatcher = new TextMatcher(new SearchTerm[] {new SearchTerm(value)}, GlazedLists.toStringTextFilterator(), TextMatcherEditor.STARTS_WITH, getTextMatchingStrategy());
Object partialMatchItem = NOT_FOUND;
// search the list of ALL UNFILTERED items for an autocompletion term for the given value
for (int i = 0, n = allItemsUnfiltered.size(); i < n; i++) {
final E item = allItemsUnfiltered.get(i);
final String itemString = convertToString(item);
// if we have an exact match, return the given value immediately
if (value.equals(itemString))
return item;
// if we have not yet located a partial match, check the current itemString for a partial match
// (to be returned if an exact match cannot be found)
if (partialMatchItem == NOT_FOUND) {
if (prefixIsEmpty ? "".equals(itemString) : valueMatcher.matches(itemString))
partialMatchItem = item;
}
}
return partialMatchItem;
}
/**
* This special version of EventComboBoxModel simply marks a flag to
* indicate the items in the ComboBoxModel should not be filtered as a
* side-effect of setting the selected item. It also marks another flag
* to indicate that the selected item is being explicitly set, and thus
* autocompletion should not execute and possibly overwrite the
* programmer's specified value.
*/
private class AutoCompleteComboBoxModel extends EventComboBoxModel {
public AutoCompleteComboBoxModel(EventList source) {
super(source);
}
/**
* Overridden because AutoCompleteSupport needs absolute control over
* when a JComboBox's ActionListeners are notified.
*/
@Override
public void setSelectedItem(Object selected) {
doNotFilter = true;
doNotAutoComplete = true;
// remove all ActionListeners from the JComboBox since setting the selected item
// would normally notify them, but in normal autocompletion behaviour, we don't want that
final ActionListener[] listeners = unregisterAllActionListeners(comboBox);
try {
super.setSelectedItem(selected);
if (comboBoxEditorComponent != null) {
// remove any text selection that might exist when an item is selected
final int caretPos = comboBoxEditorComponent.getCaretPosition();
comboBoxEditorComponent.select(caretPos, caretPos);
}
} finally {
// reinstall the ActionListeners we removed
registerAllActionListeners(comboBox, listeners);
doNotFilter = false;
doNotAutoComplete = false;
}
}
/**
* Overridden because ListEvents produce ListDataEvents from this
* ComboBoxModel, which notify the BasicComboBoxUI of the data change,
* which in turn tries to set the text of the ComboBoxEditor to match
* the text of the selected item. We don't want that. AutoCompleteSupport
* is the ultimate authority on the text value in the ComboBoxEditor.
* We override this method to set doNotChangeDocument to ensure that
* attempts to change the ComboBoxEditor's Document are ignored and
* our control is absolute.
*/
@Override
public void listChanged(ListEvent listChanges) {
doNotChangeDocument = true;
try {
super.listChanged(listChanges);
} finally {
doNotChangeDocument = false;
}
}
}
/**
* This class is the crux of the entire solution. This custom DocumentFilter
* controls all edits which are attempted against the Document of the
* ComboBoxEditor component. It is our hook to either control when to respect
* edits as well as the side-effects the edit has on autocompletion and
* filtering.
*/
private class AutoCompleteFilter extends DocumentFilter {
@Override
public void replace(FilterBypass filterBypass, int offset, int length, String string, AttributeSet attributeSet) throws BadLocationException {
if (doNotChangeDocument) return;
// collect rollback information before performing the replace
final String valueBeforeEdit = comboBoxEditorComponent.getText();
final int selectionStart = comboBoxEditorComponent.getSelectionStart();
final int selectionEnd = comboBoxEditorComponent.getSelectionEnd();
// this short-circuit corrects the PlasticLookAndFeel behaviour. Hitting the enter key in Plastic
// will cause the popup to reopen because the Plastic ComboBoxEditor forwards on unnecessary updates
// to the document, including ones where the text isn't really changing
final boolean isReplacingAllText = offset == 0 && document.getLength() == length;
if (isReplacingAllText && valueBeforeEdit.equals(string)) return;
super.replace(filterBypass, offset, length, string, attributeSet);
postProcessDocumentChange(filterBypass, attributeSet, valueBeforeEdit, selectionStart, selectionEnd, true);
}
@Override
public void insertString(FilterBypass filterBypass, int offset, String string, AttributeSet attributeSet) throws BadLocationException {
if (doNotChangeDocument) return;
// collect rollback information before performing the insert
final String valueBeforeEdit = comboBoxEditorComponent.getText();
final int selectionStart = comboBoxEditorComponent.getSelectionStart();
final int selectionEnd = comboBoxEditorComponent.getSelectionEnd();
super.insertString(filterBypass, offset, string, attributeSet);
postProcessDocumentChange(filterBypass, attributeSet, valueBeforeEdit, selectionStart, selectionEnd, true);
}
@Override
public void remove(FilterBypass filterBypass, int offset, int length) throws BadLocationException {
if (doNotChangeDocument) return;
// collect rollback information before performing the remove
final String valueBeforeEdit = comboBoxEditorComponent.getText();
final int selectionStart = comboBoxEditorComponent.getSelectionStart();
final int selectionEnd = comboBoxEditorComponent.getSelectionEnd();
super.remove(filterBypass, offset, length);
postProcessDocumentChange(filterBypass, null, valueBeforeEdit, selectionStart, selectionEnd, isStrict());
}
/**
* This method generically post processes changes to the ComboBox
* editor's Document. The generic algorithm, regardless of the type of
* change, is as follows:
*
*
* - save the prefix as the user has entered it
*
- filter the combo box items against the prefix
*
- update the text in the combo box editor with an autocomplete suggestion
*
- try to show the popup, if possible
*
*/
private void postProcessDocumentChange(FilterBypass filterBypass, AttributeSet attributeSet, String valueBeforeEdit, int selectionStart, int selectionEnd, boolean allowPartialAutoCompletionTerm) throws BadLocationException {
// break out early if we're flagged to not post process the Document change
if (doNotPostProcessDocumentChanges) return;
final String valueAfterEdit = comboBoxEditorComponent.getText();
// if an autocomplete term could not be found and we're in strict mode, rollback the edit
if (isStrict() && (findAutoCompleteTerm(valueAfterEdit) == NOT_FOUND) && !allItemsUnfiltered.isEmpty()) {
// indicate the error to the user
if (getBeepOnStrictViolation())
UIManager.getLookAndFeel().provideErrorFeedback(comboBoxEditorComponent);
// rollback the edit
doNotPostProcessDocumentChanges = true;
try {
comboBoxEditorComponent.setText(valueBeforeEdit);
} finally {
doNotPostProcessDocumentChanges = false;
}
// restore the selection as it existed
comboBoxEditorComponent.select(selectionStart, selectionEnd);
// do not continue post processing changes
return;
}
// record the selection before post processing the Document change
// (we'll use this to decide whether to broadcast an ActionEvent when choosing the next selected index)
final Object selectedItemBeforeEdit = comboBox.getSelectedItem();
updateFilter();
applyFilter(prefix);
selectAutoCompleteTerm(filterBypass, attributeSet, selectedItemBeforeEdit, allowPartialAutoCompletionTerm);
togglePopup();
}
/**
* This method will attempt to locate a reasonable autocomplete item
* from all combo box items and select it. It will also populate the
* combo box editor with the remaining text which matches the
* autocomplete item and select it. If the selection changes and the
* JComboBox is not a Table Cell Editor, an ActionEvent will be
* broadcast from the combo box.
*/
private void selectAutoCompleteTerm(FilterBypass filterBypass, AttributeSet attributeSet, Object selectedItemBeforeEdit, boolean allowPartialAutoCompletionTerm) throws BadLocationException {
// break out early if we're flagged to ignore attempts to autocomplete
if (doNotAutoComplete) return;
// determine if our prefix is empty (in which case we cannot use our filterMatcher to locate an autocompletion term)
final boolean prefixIsEmpty = "".equals(prefix);
// record the original caret position in case we don't want to disturb the text (occurs when an exact autocomplete term match is found)
final int originalCaretPosition = comboBoxEditorComponent.getCaretPosition();
// a flag to indicate whether a partial match or exact match exists on the autocomplete term
boolean autoCompleteTermIsExactMatch = false;
// search the combobox model for a value that starts with our prefix (called an autocompletion term)
for (int i = 0, n = comboBoxModel.getSize(); i < n; i++) {
String itemString = convertToString(comboBoxModel.getElementAt(i));
// if itemString does not match the prefix, continue searching for an autocompletion term
if (prefixIsEmpty ? !"".equals(itemString) : !filterMatcher.matches(itemString))
continue;
// record the index and value that are our "best" autocomplete terms so far
int matchIndex = i;
String matchString = itemString;
// search for an *exact* match in the remainder of the ComboBoxModel
// before settling for the partial match we have just found
for (int j = i; j < n; j++) {
itemString = convertToString(comboBoxModel.getElementAt(j));
// if we've located an exact match, use its index and value rather than the partial match
if (prefix.equals(itemString)) {
matchIndex = j;
matchString = itemString;
autoCompleteTermIsExactMatch = true;
break;
}
}
// if partial autocompletion terms are not allowed, and we only have a partial term, bail early
if (!allowPartialAutoCompletionTerm && !prefix.equals(itemString))
return;
// either keep the user's prefix or replace it with the itemString's prefix
// depending on whether we correct the case
if (getCorrectsCase() || isStrict()) {
filterBypass.replace(0, prefix.length(), matchString, attributeSet);
} else {
final String itemSuffix = matchString.substring(prefix.length());
filterBypass.insertString(prefix.length(), itemSuffix, attributeSet);
}
// select the autocompletion term
final boolean silently = isTableCellEditor || GlazedListsImpl.equal(selectedItemBeforeEdit, matchString);
selectItem(matchIndex, silently);
if (autoCompleteTermIsExactMatch) {
// if the term matched the original text exactly, return the caret to its original location
comboBoxEditorComponent.setCaretPosition(originalCaretPosition);
} else {
// select the text after the prefix but before the end of the text (it represents the autocomplete text)
comboBoxEditorComponent.select(prefix.length(), document.getLength());
}
return;
}
// reset the selection since we couldn't find the prefix in the model
// (this has the side-effect of scrolling the popup to the top)
final boolean silently = isTableCellEditor || selectedItemBeforeEdit == null;
selectItem(-1, silently);
}
/**
* Select the item at the given index. If
* silent is true, the JComboBox will not
* broadcast an ActionEvent.
*/
private void selectItem(int index, boolean silently) {
final Object valueToSelect = index == -1 ? null : comboBoxModel.getElementAt(index);
// if nothing is changing about the selection, return immediately
if (GlazedListsImpl.equal(comboBoxModel.getSelectedItem(), valueToSelect))
return;
doNotChangeDocument = true;
try {
if (silently)
comboBoxModel.setSelectedItem(valueToSelect);
else
comboBox.setSelectedItem(valueToSelect);
} finally {
doNotChangeDocument = false;
}
}
}
/**
* Select the item at the given index. This method behaves
* differently in strict mode vs. non-strict mode.
*
* In strict mode, the selected index must always be valid, so using the
* down arrow key on the last item or the up arrow key on the first item
* simply wraps the selection to the opposite end of the model.
*
*
In non-strict mode, the selected index can be -1 (no selection), so we
* allow -1 to mean "adjust the value of the ComboBoxEditor to be the user's
* text" and only wrap to the end of the model when -2 is reached. In short,
* -1 is interpreted as "clear the selected item".
* -2 is interpreted as "the last element".
*/
private void selectPossibleValue(int index) {
if (isStrict()) {
// wrap the index from past the start to the end of the model
if (index < 0)
index = comboBox.getModel().getSize()-1;
// wrap the index from past the end to the start of the model
if (index > comboBox.getModel().getSize()-1)
index = 0;
} else {
// wrap the index from past the start to the end of the model
if (index == -2)
index = comboBox.getModel().getSize()-1;
}
// check if the index is within a valid range
final boolean validIndex = index >= 0 && index < comboBox.getModel().getSize();
// if the index isn't valid, select nothing
if (!validIndex)
index = -1;
// adjust only the value in the comboBoxEditorComponent, but leave the comboBoxModel unchanged
doNotPostProcessDocumentChanges = true;
try {
// select the index
if (isTableCellEditor) {
// while operating as a TableCellEditor, no ActionListeners must be notified
// when using the arrow keys to adjust the selection
final ActionListener[] listeners = unregisterAllActionListeners(comboBox);
try {
comboBox.setSelectedIndex(index);
} finally {
registerAllActionListeners(comboBox, listeners);
}
} else {
comboBox.setSelectedIndex(index);
}
// if the original index wasn't valid, we've cleared the selection
// and must set the user's prefix into the editor
if (!validIndex) {
comboBoxEditorComponent.setText(prefix);
// don't bother unfiltering the popup since we'll redisplay the popup immediately
doNotClearFilterOnPopupHide = true;
try {
comboBox.hidePopup();
} finally {
doNotClearFilterOnPopupHide = false;
}
comboBox.showPopup();
}
} finally {
doNotPostProcessDocumentChanges = false;
}
// if the comboBoxEditorComponent's values begins with the user's prefix, highlight the remainder of the value
final String newSelection = comboBoxEditorComponent.getText();
if (filterMatcher.matches(newSelection))
comboBoxEditorComponent.select(prefix.length(), newSelection.length());
}
/**
* The action invoked by hitting the up or down arrow key.
*/
private class MoveAction extends AbstractAction {
private final int offset;
public MoveAction(int offset) {
this.offset = offset;
}
public void actionPerformed(ActionEvent e) {
if (comboBox.isShowing()) {
if (comboBox.isPopupVisible()) {
selectPossibleValue(comboBox.getSelectedIndex() + offset);
} else {
applyFilter(prefix);
comboBox.showPopup();
}
}
}
}
/**
* This class listens to the ComboBoxModel and redraws the popup if it
* must grow or shrink to accomodate the latest list of items.
*/
private class ListDataHandler implements ListDataListener {
private int previousItemCount = -1;
private final Runnable checkStrictModeInvariantRunnable = new CheckStrictModeInvariantRunnable();
public void contentsChanged(ListDataEvent e) {
final int newItemCount = comboBox.getItemCount();
// if the size of the model didn't change, the popup size won't change
if (previousItemCount != newItemCount) {
final int maxPopupItemCount = comboBox.getMaximumRowCount();
// if the popup is showing, check if it must be resized
if (popupMenu.isShowing()) {
if (comboBox.isShowing()) {
// if either the previous or new item count is less than the max,
// hide and show the popup to recalculate its new height
if (newItemCount < maxPopupItemCount || previousItemCount < maxPopupItemCount) {
// don't bother unfiltering the popup since we'll redisplay the popup immediately
doNotClearFilterOnPopupHide = true;
try {
comboBox.hidePopup();
} finally {
doNotClearFilterOnPopupHide = false;
}
comboBox.showPopup();
}
} else {
// if the comboBox is not showing, simply hide the popup to avoid:
// "java.awt.IllegalComponentStateException: component must be showing on the screen to determine its location"
// this case can occur when the comboBox is used as a TableCellEditor
// and is uninstalled (removed from the component hierarchy) before
// receiving this callback
comboBox.hidePopup();
}
}
previousItemCount = newItemCount;
}
// if the comboBoxModel was changed and it wasn't due to the filter changing
// (i.e. !isFiltering) and it wasn't because the user selected a new
// selectedItem (i.e. !userSelectedNewItem) then those changes may have
// invalidated the invariant that strict mode places on the text in the
// JTextField, so we must either:
//
// a) locate the text within the model (proving that the strict mode invariant still holds)
// b) set the text to that of the first element in the model (to reestablish the invariant)
final boolean userSelectedNewItem = e.getIndex0() == -1 || e.getIndex1() == -1;
if (isStrict() && !userSelectedNewItem && !isFiltering) {
// notice that instead of doing the work directly, we post a Runnable here
// to check the strict mode invariant and repair it if it is broken. That's
// important. It's necessary because we must let the current ListEvent
// finish its dispatching before we attempt to change the filter of the
// filteredItems list by setting new text into the comboBoxEditorComponent
SwingUtilities.invokeLater(checkStrictModeInvariantRunnable);
}
}
public void intervalAdded(ListDataEvent e) { contentsChanged(e); }
public void intervalRemoved(ListDataEvent e) { contentsChanged(e); }
private class CheckStrictModeInvariantRunnable implements Runnable {
public void run() {
final JTextField editor = comboBoxEditorComponent;
if (editor != null) {
final String currentText = editor.getText();
final Object item = findAutoCompleteTerm(currentText);
String itemText = convertToString(item);
// if we did not find the same autocomplete term
if (!currentText.equals(itemText)) {
// select the first item if we could not find an autocomplete term with the currentText
if (item == NOT_FOUND && !allItemsUnfiltered.isEmpty())
itemText = convertToString(allItemsUnfiltered.get(0));
// set the new strict value text into the editor component
editor.setText(itemText);
}
}
}
}
}
/**
* This class sizes the popup menu of the combo box immediately before
* it is shown on the screen. In particular, it will adjust the width
* of the popup to accomodate a prototype display value if the combo
* box contains one.
*/
private class PopupSizer implements PopupMenuListener {
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
// if the combo box does not contain a prototype display value, skip our sizing logic
final Object prototypeValue = comboBox.getPrototypeDisplayValue();
if (prototypeValue == null) return;
final JComponent popupComponent = (JComponent) e.getSource();
// attempt to extract the JScrollPane that scrolls the popup
if (popupComponent.getComponent(0) instanceof JScrollPane) {
final JScrollPane scroller = (JScrollPane) popupComponent.getComponent(0);
// fetch the existing preferred size of the scroller, and we'll check if it is large enough
final Dimension scrollerSize = scroller.getPreferredSize();
// calculates the preferred size of the renderer's component for the prototype value
final Dimension prototypeSize = getPrototypeSize(prototypeValue);
// add to the preferred width, the width of the vertical scrollbar, when it is visible
prototypeSize.width += scroller.getVerticalScrollBar().getPreferredSize().width;
// adjust the preferred width of the scroller, if necessary
if (prototypeSize.width > scrollerSize.width) {
scrollerSize.width = prototypeSize.width;
// set the new size of the scroller
scroller.setMaximumSize(scrollerSize);
scroller.setPreferredSize(scrollerSize);
scroller.setMinimumSize(scrollerSize);
}
}
}
private Dimension getPrototypeSize(Object prototypeValue) {
// get the renderer responsible for drawing the prototype value
ListCellRenderer renderer = comboBox.getRenderer();
if (renderer == null)
renderer = new DefaultListCellRenderer();
// get the component from the renderer
final Component comp = renderer.getListCellRendererComponent(popup.getList(), prototypeValue, -1, false, false);
// determine the preferred size of the component
comp.setFont(comboBox.getFont());
return comp.getPreferredSize();
}
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
if (doNotClearFilterOnPopupHide) return;
// the popup menu is being hidden, so clear the filter to return the ComboBoxModel to its unfiltered state
applyFilter("");
}
public void popupMenuCanceled(PopupMenuEvent e) {}
}
/**
* When the user selects a value from the popup with the mouse, we want to
* honour their selection *without* attempting to autocomplete it to a new
* term. Otherwise, it is possible that selections which are prefixes for
* values that appear higher in the ComboBoxModel cannot be selected by the
* mouse since they can always be successfully autocompleted to another
* term.
*/
private class PopupMouseHandler extends MouseAdapter {
@Override
public void mousePressed(MouseEvent e) {
doNotAutoComplete = true;
}
@Override
public void mouseReleased(MouseEvent e) {
doNotAutoComplete = false;
}
}
/**
* When the user clicks on the arrow button, we always clear the
* filtering from the model to emulate Firefox style autocompletion.
*/
private class ArrowButtonMouseListener implements MouseListener {
private final MouseListener decorated;
public ArrowButtonMouseListener(MouseListener decorated) {
this.decorated = decorated;
}
public void mousePressed(MouseEvent e) {
// clear the filter if we're about to hide or show the popup
// by clicking on the arrow button (this is EXPLICITLY different
// than using the up/down arrow keys to show the popup)
applyFilter("");
decorated.mousePressed(e);
}
public MouseListener getDecorated() { return decorated; }
public void mouseClicked(MouseEvent e) { decorated.mouseClicked(e); }
public void mouseReleased(MouseEvent e) { decorated.mouseReleased(e); }
public void mouseEntered(MouseEvent e) { decorated.mouseEntered(e); }
public void mouseExited(MouseEvent e) { decorated.mouseExited(e); }
}
/**
* This KeyListener handles the case when the user hits the backspace key
* and the {@link AutoCompleteSupport} is strict. Normally backspace would
* delete the selected text, if it existed, or delete the character
* immediately preceding the cursor. In strict mode the ComboBoxEditor must
* always contain a value from the ComboBoxModel, so the backspace key
* NEVER alters the Document. Rather, it alters the
* text selection to include one more character to the left. This is a nice
* compromise, since the editor continues to retain a valid value from the
* ComboBoxModel, but the user may type a key at any point to replace the
* selection with another valid entry.
*
* This KeyListener also makes up for a bug in normal JComboBox when
* handling the enter key. Specifically, hitting enter in an stock
* JComboBox that is editable produces TWO ActionEvents.
* When the enter key is detected we actually unregister all
* ActionListeners, process the keystroke as normal, then reregister the
* listeners and broadcast an event to them, producing a single ActionEvent.
*/
private class AutoCompleteKeyHandler extends KeyAdapter {
private ActionListener[] actionListeners;
@Override
public void keyPressed(KeyEvent e) {
if (!isTableCellEditor)
doNotTogglePopup = false;
// this KeyHandler performs ALL processing of the ENTER key otherwise multiple
// ActionEvents are fired to ActionListeners by the default JComboBox processing.
// To control processing of the enter key, we set a flag to avoid changing the
// editor's Document in any way, and also unregister the ActionListeners temporarily.
if (e.getKeyChar() == KeyEvent.VK_ENTER) {
doNotChangeDocument = true;
this.actionListeners = unregisterAllActionListeners(comboBox);
}
// make sure this backspace key does not modify our comboBoxEditorComponent's Document
if (isTrigger(e))
doNotChangeDocument = true;
}
@Override
public void keyTyped(KeyEvent e) {
if (isTrigger(e)) {
// if no content exists in the comboBoxEditorComponent, bail early
if (comboBoxEditorComponent.getText().length() == 0) return;
// calculate the current beginning of the selection
int selectionStart = Math.min(comboBoxEditorComponent.getSelectionStart(), comboBoxEditorComponent.getSelectionEnd());
// if we cannot extend the selection to the left, indicate the error
if (selectionStart == 0) {
if (getBeepOnStrictViolation())
UIManager.getLookAndFeel().provideErrorFeedback(comboBoxEditorComponent);
return;
}
// add one character to the left of the selection
selectionStart--;
// select the text from the end of the Document to the new selectionStart
// (which positions the caret at the selectionStart)
comboBoxEditorComponent.setCaretPosition(comboBoxEditorComponent.getText().length());
comboBoxEditorComponent.moveCaretPosition(selectionStart);
}
}
@Override
public void keyReleased(KeyEvent e) {
// resume the ability to modify our comboBoxEditorComponent's Document
if (isTrigger(e))
doNotChangeDocument = false;
// keyPressed(e) has disabled the JComboBox's normal processing of the enter key
// so now it is time to perform our own processing. We reattach all ActionListeners
// and simulate exactly ONE ActionEvent in the JComboBox and then reenable Document changes.
if (e.getKeyChar() == KeyEvent.VK_ENTER) {
updateFilter();
// reregister all ActionListeners and then notify them due to the ENTER key
// Note: We *must* check for a null ActionListener[]. The reason
// is that it is possible to receive a keyReleased() callback
// *without* a corresponding keyPressed() callback! It occurs
// when focus is transferred away from the ComboBoxEditor and
// then the ENTER key transfers focus back to the ComboBoxEditor.
if (actionListeners != null) {
registerAllActionListeners(comboBox, actionListeners);
comboBox.actionPerformed(new ActionEvent(e.getSource(), e.getID(), null));
}
// null out our own reference to the ActionListeners
actionListeners = null;
// reenable Document changes once more
doNotChangeDocument = false;
}
if (!isTableCellEditor)
doNotTogglePopup = true;
}
private boolean isTrigger(KeyEvent e) {
return isStrict() && e.getKeyChar() == KeyEvent.VK_BACK_SPACE;
}
}
/**
* To emulate Firefox behaviour, all text in the ComboBoxEditor is selected
* from beginning to end when the ComboBoxEditor gains focus if the value
* returned from {@link AutoCompleteSupport#getSelectsTextOnFocusGain()}
* allows this behaviour. In addition, the JPopupMenu is hidden when the
* ComboBoxEditor loses focus if the value returned from
* {@link AutoCompleteSupport#getHidesPopupOnFocusLost()} allows this
* behaviour.
*/
private class ComboBoxEditorFocusHandler extends FocusAdapter {
@Override
public void focusGained(FocusEvent e) {
if (getSelectsTextOnFocusGain())
comboBoxEditorComponent.select(0, comboBoxEditorComponent.getText().length());
}
@Override
public void focusLost(FocusEvent e) {
if (comboBox.isPopupVisible() && getHidesPopupOnFocusLost())
comboBox.setPopupVisible(false);
}
}
/**
* Watch for a change of the ComboBoxUI and reinstall the necessary
* behaviour customizations.
*/
private class UIWatcher implements PropertyChangeListener {
public void propertyChange(PropertyChangeEvent evt) {
undecorateOriginalUI();
decorateCurrentUI();
}
}
/**
* Watch for a change of the ComboBoxModel and report it as a violation.
*/
private class ModelWatcher implements PropertyChangeListener {
public void propertyChange(PropertyChangeEvent evt) {
throwIllegalStateException("The ComboBoxModel cannot be changed. It was changed to: " + evt.getNewValue());
}
}
/**
* Watch the Document behind the editor component in case it changes. If a
* new Document is swapped in, uninstall our DocumentFilter from the old
* Document and install it on the new.
*/
private class DocumentWatcher implements PropertyChangeListener {
public void propertyChange(PropertyChangeEvent evt) {
final Document newDocument = (Document) evt.getNewValue();
if (!(newDocument instanceof AbstractDocument))
throwIllegalStateException("The Document behind the JTextField was changed to no longer be an AbstractDocument. It was changed to: " + newDocument);
// remove our DocumentFilter from the old document
document.setDocumentFilter(null);
// update the document we track internally
document = (AbstractDocument) newDocument;
// add our DocumentFilter to the new Document
document.setDocumentFilter(documentFilter);
}
}
/**
* A custom renderer which honours the custom Format given by the user when
* they invoked the install method.
*/
private class StringFunctionRenderer extends DefaultListCellRenderer {
@Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
String string = convertToString(value);
// JLabels require some text before they can correctly determine their height, so we convert "" to " "
if (string.length() == 0)
string = " ";
return super.getListCellRendererComponent(list, string, index, isSelected, cellHasFocus);
}
}
/**
* A decorated version of the ComboBoxEditor that does NOT assume that
* Object.toString() is the proper way to convert values from the
* ComboBoxModel into Strings for the ComboBoxEditor's component. It uses
* convertToString(E) instead.
*
* We implement the UIResource interface here so that changes in the UI
* delegate of the JComboBox will *replace* this ComboBoxEditor with one
* that is correct for the new L&F. We will then react to the change of UI
* delegate by installing a new FormatComboBoxEditor overtop of the
* UI Delegate's default ComboBoxEditor.
*/
private class FormatComboBoxEditor implements ComboBoxEditor, UIResource {
/** This is the ComboBoxEditor installed by the current UI Delegate of the JComboBox. */
private final ComboBoxEditor delegate;
private Object oldValue;
public FormatComboBoxEditor(ComboBoxEditor delegate) {
this.delegate = delegate;
}
public ComboBoxEditor getDelegate() {
return delegate;
}
/**
* BasicComboBoxEditor defines this method to call:
*
* editor.setText(anObject.toString());
*
* we intercept and replace it with our own String conversion logic
* to remain consistent throughout.
*/
public void setItem(Object anObject) {
oldValue = anObject;
((JTextField) getEditorComponent()).setText(convertToString(anObject));
}
/**
* BasicComboBoxEditor defines this method to use reflection to try
* finding a method called valueOf(String) in order to return the
* item. We attempt to find a user-supplied Format before
* resorting to the valueOf(String) call.
*/
public Object getItem() {
final String oldValueString = convertToString(oldValue);
final String currentString = ((JTextField) getEditorComponent()).getText();
// if the String value in the editor matches the String version of
// the last item that was set in the editor, return the item
if (GlazedListsImpl.equal(oldValueString, currentString))
return oldValue;
// if the user specified a Format, use it
if (format != null)
return format.parseObject(currentString, PARSE_POSITION);
// otherwise, use the default algorithm from BasicComboBoxEditor to produce a value
if (oldValue != null && !(oldValue instanceof String)) {
try {
final Method method = oldValue.getClass().getMethod("valueOf", VALUE_OF_SIGNATURE);
return method.invoke(oldValue, new Object[] {currentString});
} catch (Exception ex) {
// fail silently and return the current string
}
}
return currentString;
}
public Component getEditorComponent() { return delegate.getEditorComponent(); }
public void selectAll() { delegate.selectAll(); }
public void addActionListener(ActionListener l) { delegate.addActionListener(l); }
public void removeActionListener(ActionListener l) { delegate.removeActionListener(l); }
}
/**
* This default implementation of the TextFilterator interface uses the
* same strategy for producing Strings from ComboBoxModel objects as the
* renderer and editor.
*/
class DefaultTextFilterator implements TextFilterator {
public void getFilterStrings(List baseList, E element) {
baseList.add(convertToString(element));
}
}
/**
* This extension of DefaultCellEditor exists solely to provide a handle to
* the AutoCompleteSupport object that is providing autocompletion
* capabilities to the JComboBox.
*/
public static class AutoCompleteCellEditor extends DefaultCellEditor {
private final AutoCompleteSupport autoCompleteSupport;
/**
* Construct a TableCellEditor using the JComboBox supplied by the
* given autoCompleteSupport. Specifically, the JComboBox
* is retrieved using {@link AutoCompleteSupport#getComboBox()}.
*/
public AutoCompleteCellEditor(AutoCompleteSupport autoCompleteSupport) {
super(autoCompleteSupport.getComboBox());
this.autoCompleteSupport = autoCompleteSupport;
}
/**
* Returns the AutoCompleteSupport object that controls the
* autocompletion behaviour for the JComboBox.
*/
public AutoCompleteSupport getAutoCompleteSupport() {
return autoCompleteSupport;
}
}
/**
* This factory method creates and returns a {@link AutoCompleteCellEditor}
* which adapts an autocompleting {@link JComboBox} for use as a Table
* Cell Editor. The values within the table column are used as
* autocompletion terms within the {@link ComboBoxModel}.
*
* This version of createTableCellEditor assumes that the
* values stored in the TableModel at the given columnIndex
* are all {@link Comparable}, and that the natural ordering defined by
* those {@link Comparable} values also determines which are duplicates
* (and thus can safely be removed) and which are unique (and thus must
* remain in the {@link ComboBoxModel}).
*
*
Note that this factory method is only appropriate for use when the
* values in the {@link ComboBoxModel} should be the unique set of values
* in a table column. If some other list of values will be used then
* {@link #createTableCellEditor(EventList)} is the appropriate factory
* method to use.
*
*
If the appearance or function of the autocompleting {@link JComboBox}
* is to be customized, it can be retrieved using
* {@link AutoCompleteCellEditor#getComponent()}.
*
* @param tableFormat specifies how each row object within a table is
* broken apart into column values
* @param tableData the {@link EventList} backing the TableModel
* @param columnIndex the index of the column for which to return a
* {@link AutoCompleteCellEditor}
* @return a {@link AutoCompleteCellEditor} which contains an autocompleting
* combobox whose contents remain consistent with the data in the
* table column at the given columnIndex
*/
public static AutoCompleteCellEditor createTableCellEditor(TableFormat tableFormat, EventList tableData, int columnIndex) {
return createTableCellEditor(GlazedLists.comparableComparator(), tableFormat, tableData, columnIndex);
}
/**
* This factory method creates and returns a {@link AutoCompleteCellEditor}
* which adapts an autocompleting {@link JComboBox} for use as a Table
* Cell Editor. The values within the table column are used as
* autocompletion terms within the {@link ComboBoxModel}.
*
* This version of createTableCellEditor makes no
* assumption about the values stored in the TableModel at the given
* columnIndex. Instead, it uses the given
* uniqueComparator to determine which values are duplicates
* (and thus can safely be removed) and which are unique (and thus must
* remain in the {@link ComboBoxModel}).
*
*
Note that this factory method is only appropriate for use when the
* values in the {@link ComboBoxModel} should be the unique set of values
* in a table column. If some other list of values will be used then
* {@link #createTableCellEditor(EventList)} is the appropriate factory
* method to use.
*
*
If the appearance or function of the autocompleting {@link JComboBox}
* is to be customized, it can be retrieved using
* {@link AutoCompleteCellEditor#getComponent()}.
*
* @param uniqueComparator the {@link Comparator} that strips away
* duplicate elements from the {@link ComboBoxModel}
* @param tableFormat specifies how each row object within a table is
* broken apart into column values
* @param tableData the {@link EventList} backing the TableModel
* @param columnIndex the index of the column for which to return a
* {@link AutoCompleteCellEditor}
* @return a {@link AutoCompleteCellEditor} which contains an autocompleting
* combobox whose contents remain consistent with the data in the
* table column at the given columnIndex
*/
public static AutoCompleteCellEditor createTableCellEditor(Comparator uniqueComparator, TableFormat tableFormat, EventList tableData, int columnIndex) {
// use a function to extract all values for the column
final FunctionList.Function columnValueFunction = new TableColumnValueFunction(tableFormat, columnIndex);
final FunctionList allColumnValues = new FunctionList(tableData, columnValueFunction);
// narrow the list to just unique values within the column
final EventList uniqueColumnValues = new UniqueList(allColumnValues, uniqueComparator);
return createTableCellEditor(uniqueColumnValues);
}
/**
* This factory method creates and returns a {@link AutoCompleteCellEditor}
* which adapts an autocompleting {@link JComboBox} for use as a Table
* Cell Editor. The values within the source are used as
* autocompletion terms within the {@link ComboBoxModel}.
*
* If the appearance or function of the autocompleting {@link JComboBox}
* is to be customized, it can be retrieved using
* {@link AutoCompleteCellEditor#getComponent()}.
*
* @param source the source of data for the JComboBox within the table cell editor
* @return a {@link AutoCompleteCellEditor} which contains an autocompleting
* combobox whose model contents are determined by the given source
*/
public static AutoCompleteCellEditor createTableCellEditor(EventList source) {
// build a special JComboBox used only in Table Cell Editors
final JComboBox comboBox = new TableCellComboBox();
comboBox.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);
// install autocompletion support on the special JComboBox
final AutoCompleteSupport autoCompleteSupport = AutoCompleteSupport.install(comboBox, source);
autoCompleteSupport.setSelectsTextOnFocusGain(false);
// create an AutoCompleteCellEditor using the AutoCompleteSupport object
final AutoCompleteCellEditor cellEditor = new AutoCompleteCellEditor(autoCompleteSupport);
cellEditor.setClickCountToStart(2);
return cellEditor;
}
/**
* This customized JComboBox is only used when creating an autocompleting
* {@link DefaultCellEditor}. It customizes the behaviour of a JComboBox to
* make it more appropriate for use as a TableCellEditor. Specifically it
* adds the following:
*
*
* - key presses which start table cell edits are also respected by the
* JTextField
*
- the next focusable component for the JTextField is set to be the
* JTable when editing begins so that focus returns to the table when
* editing stops
*
*/
private static final class TableCellComboBox extends JComboBox implements FocusListener {
public TableCellComboBox() {
// use a customized ComboBoxEditor within this special JComboBox
setEditor(new TableCellComboBoxEditor());
// replace the UI Delegate's FocusListener with our own in both
// the JComboBox and its ComboBoxEditor
replaceUIDelegateFocusListener(getEditor().getEditorComponent(), this);
replaceUIDelegateFocusListener(this, this);
}
/**
* This method is a complete hack, but is necessary to achieve the
* desired behaviour when using an autocompleting JComboBox in a
* TableCellEditor.
*
* The problem is that when cell editing begins due to a keystroke,
* ideally the ComboBoxPopup should be displayed in a filtered state.
* But, the FocusListener installed by BasicComboBoxUI actually hides
* the ComboBoxPopup due to some phantom focusLost event we receive.
*
* To solve the problem, we rip out the FocusListener installed by
* the BasicComboBoxUI and replace it with our own that does NOT hide
* the popup when this JComboBox loses focus. That's with us since
* losing focus implies we are committing or cancelling the cell edit
* anyway, so the entire editor is about to be removed.
*/
private static void replaceUIDelegateFocusListener(Component c, FocusListener replacement) {
// remove all FocusListeners that appear to be installed by the UIdelegate
final FocusListener[] focusListeners = c.getFocusListeners();
for (int i = 0; i < focusListeners.length; i++)
if (focusListeners[i].getClass().getName().indexOf("ComboBoxUI") != -1)
c.removeFocusListener(focusListeners[i]);
c.addFocusListener(replacement);
}
/**
* Repaint and request focus if editable.
*/
public void focusGained(FocusEvent e) {
final ComboBoxEditor currentEditor = getEditor();
if (currentEditor != null && currentEditor.getEditorComponent() != e.getSource()) {
repaint();
if (isEditable()) {
currentEditor.getEditorComponent().requestFocus();
}
}
}
/**
* BasicComboBoxUI.Handler.focusLost screws up the installation of this
* JComboBox as a TableCellEditor by hiding the ComboBoxPopup on the
* first keystroke and represent the reason why we must tear out the
* FocusListener and replace it with one of our own.
*/
public void focusLost(FocusEvent e) {
final ComboBoxEditor currentEditor = getEditor();
if (!e.isTemporary() && currentEditor != null && currentEditor.getEditorComponent() == e.getSource()) {
final Object currentItem = currentEditor.getItem();
if (currentItem != null && !currentItem.equals(getSelectedItem())) {
fireActionPerformed(currentEditor);
}
}
repaint();
}
private void fireActionPerformed(ComboBoxEditor source) {
actionPerformed(new ActionEvent(source, 0, "", EventQueue.getMostRecentEventTime(), 0));
}
/**
* This method is called by Swing when the JComboBox is installed as a
* TableCellEditor. It gives the component a chance to process the
* KeyEvent. For example, a JTextField will honour the keystroke and
* add the letter to its Document.
*
* Editable JComboBoxes don't provide that expected behaviour out of
* the box, so we override this method with logic that gives the editor
* component of the JComboBox a chance to respond to the keystroke that
* initiated the cell edit.
*/
@Override
protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) {
// let the textfield have a crack at processing the KeyEvent
final TableCellTextField tableCellTextField = (TableCellTextField) getEditor().getEditorComponent();
tableCellTextField.processKeyBinding(ks, e, condition, pressed);
// ensure the text field has focus if it is still processing key strokes
// (I've seen bad behaviour on windows textfield has no cursor, yet continues to process keystrokes
// - this helps to ensure that the textfield actually has focus and thus the cursor)
if (!tableCellTextField.hasFocus())
tableCellTextField.requestFocus();
// now let the JComboBox react (important for arrow keys to work as expected)
return super.processKeyBinding(ks, e, condition, pressed);
}
/**
* This method is called by Swing when installing this JComboBox as a
* TableCellEditor. It ensures that focus will return to the JTable
* when the cell edit is complete.
*
* We override this method to ensure that if the JTextField acting
* as the editor of the JComboBox has focus when the cell edit is
* complete, focus is returned to the JTable in that case as well.
*/
@Override
public void setNextFocusableComponent(Component aComponent) {
super.setNextFocusableComponent(aComponent);
// set the next focusable component for the editor as well
((JComponent) getEditor().getEditorComponent()).setNextFocusableComponent(aComponent);
}
/**
* A custom BasicComboBoxEditor that builds a custom JTextField with
* an extra capability: a public implementation of
* {@link TableCellTextField#processKeyBinding}
*/
private static final class TableCellComboBoxEditor extends BasicComboBoxEditor {
public TableCellComboBoxEditor() {
// replace the super's editor with a JTextField of our own design
editor = new TableCellTextField();
}
}
/**
* This custom JTextField exists solely to make
* {@link #processKeyBinding} a public method so that it can be called
* from {@link TableCellComboBox#processKeyBinding}.
*
* This custom JTextField is only used when creating an autocompleting
* TableCellEditor via {@link AutoCompleteSupport#createTableCellEditor}.
*/
private static class TableCellTextField extends JTextField {
public TableCellTextField() {
super("", 9);
}
/**
* {@inheritDoc}
*/
@Override
public void setText(String newText) {
// workaround for bug 4530952
if (!equalsText(newText)) {
super.setText(newText);
}
}
private boolean equalsText(String newText) {
final String currentText = getText();
return (currentText == null) ? newText == null : currentText.equals(newText);
}
/**
* {@inheritDoc}
*/
@Override
public void setBorder(Border b) {
// NOP, we want no border
}
/**
* We override this method to make it public so that it can be
* called from {@link TableCellComboBox#processKeyBinding}.
*
*
This allows the keystroke which begins a table cell edit to
* also contribute a character to this JTextField, thus mimicing
* the behaviour of normal editable JTextField table cell editors.
*/
@Override
public boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) {
return super.processKeyBinding(ks, e, condition, pressed);
}
}
}
/**
* This function uses a TableFormat and columnIndex to extract all of the
* values that are displayed in the given table column. These values are
* used as autocompletion terms when editing a cell within that column.
*/
private static final class TableColumnValueFunction implements FunctionList.Function {
private final TableFormat tableFormat;
private final int columnIndex;
public TableColumnValueFunction(TableFormat tableFormat, int columnIndex) {
this.tableFormat = tableFormat;
this.columnIndex = columnIndex;
}
public Object evaluate(E sourceValue) {
return tableFormat.getColumnValue(sourceValue, columnIndex);
}
}
} libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/swing/EventListModel.java 0000644 0001750 0001750 00000010574 12106516356 031037 0 ustar gregoa gregoa /* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.swing;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.TransformedList;
import javax.swing.JList;
import javax.swing.ListModel;
import javax.swing.SwingUtilities;
/**
* An EventListModel adapts an EventList to the ListModel interface making it
* appropriate for use with a {@link JList}. Each element of the list
* corresponds to an element in the {@link ListModel}.
*
* The EventListModel class is not thread-safe. Unless
* otherwise noted, all methods are only safe to be called from the event
* dispatch thread. To do this programmatically, use
* {@link SwingUtilities#invokeAndWait(Runnable)}.
*
* @see Bug 14
* @see Bug 146
* @see Bug 177
* @see Bug 228
* @see SwingUtilities#invokeAndWait(Runnable)
*
* @deprecated Use {@link DefaultEventListModel} instead. This class will be removed in the GL
* 2.0 release. The wrapping of the source list with an EDT safe list has been
* determined to be undesirable (it is better for the user to provide their own EDT
* safe list).
*
* @author Jesse Wilson
* @author Holger Brands
*/
public class EventListModel extends DefaultEventListModel {
/** indicates, if source list has to be disposed */
private boolean disposeSource;
/**
* Creates a new model that contains all objects located in the given
* source and reacts to any changes in the given
* source.
*/
public EventListModel(EventList source) {
super(createProxyList(source));
disposeSource = (this.source != source);
}
/**
* Releases the resources consumed by this {@link EventListModel} so that it
* may eventually be garbage collected.
*
* An {@link EventListModel} will be garbage collected without a call to
* {@link #dispose()}, but not before its source {@link EventList} is garbage
* collected. By calling {@link #dispose()}, you allow the {@link EventListModel}
* to be garbage collected before its source {@link EventList}. This is
* necessary for situations where an {@link EventListModel} is short-lived but
* its source {@link EventList} is long-lived.
*
*
Warning: It is an error
* to call any method on an {@link EventListModel} after it has been disposed.
* As such, this {@link EventListModel} should be detached from its
* corresponding Component before it is disposed.
*/
@Override
public void dispose() {
if (disposeSource) source.dispose();
super.dispose();
}
/**
* while holding a read lock, this method wraps the given source list with a swing thread
* proxy list.
*/
private static EventList createProxyList(EventList source) {
// lock the source list for reading since we want to prevent writes
// from occurring until we fully initialize this EventTableModel
EventList result = source;
source.getReadWriteLock().readLock().lock();
try {
final TransformedList decorated = createSwingThreadProxyList(source);
// if the create method actually returned a decorated form of the source,
// record it so it may later be disposed
if (decorated != null && decorated != source) {
result = decorated;
}
} finally {
source.getReadWriteLock().readLock().unlock();
}
return result;
}
/** wraps the given source list with a swing thread proxy list, if necessary */
private static TransformedList createSwingThreadProxyList(EventList source) {
return GlazedListsSwing.isSwingThreadProxyList(source) ? null : GlazedListsSwing.swingThreadProxyList(source);
}
} libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/swing/AdvancedTableModel.java 0000644 0001750 0001750 00000004731 12106516356 031575 0 ustar gregoa gregoa /* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.swing;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.gui.TableFormat;
import javax.swing.table.TableModel;
/**
* AdvancedTableModel is the extended interface intended to be implemented by
* Glazed Lists table models. It provides additional methods for managing the
* {@link TableFormat} and disposing, for example.
*
* @author Holger Brands
*/
public interface AdvancedTableModel extends TableModel {
/**
* Gets the {@link TableFormat} used by this table model.
*/
TableFormat super E> getTableFormat();
/**
* Sets the {@link TableFormat} that will extract column data from each
* element. This has some very important consequences. Any cell selections
* will be lost - this is due to the fact that the TableFormats may have
* different numbers of columns, and JTable has no event to specify columns
* changing without rows.
*/
void setTableFormat(TableFormat super E> tableFormat);
/**
* Retrieves the value at the specified location from the table.
*
* This may be used by renderers to paint the cells of a row differently
* based on the entire value for that row.
*
* @see #getValueAt(int,int)
*/
E getElementAt(int index);
/**
* Releases the resources consumed by this {@link AdvancedTableModel} so that it
* may eventually be garbage collected.
*
*
An {@link AdvancedTableModel} will be garbage collected without a call to
* {@link #dispose()}, but not before its source {@link EventList} is garbage
* collected. By calling {@link #dispose()}, you allow the {@link AdvancedTableModel}
* to be garbage collected before its source {@link EventList}. This is
* necessary for situations where an {@link AdvancedTableModel} is short-lived but
* its source {@link EventList} is long-lived.
*
*
Warning: It is an error
* to call any method on an {@link AdvancedTableModel} after it has been disposed.
* As such, this {@link AdvancedTableModel} should be detached from its
* corresponding Component before it is disposed.
*/
void dispose();
}
libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/swing/MutableListDataEvent.java 0000644 0001750 0001750 00000003703 12106516356 032156 0 ustar gregoa gregoa /* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.swing;
// Swing toolkit stuff for displaying widgets
import javax.swing.event.ListDataEvent;
/**
* The mutable list data event is a list data event that can be rewritten
* for performance gains. The class is completely re-implemented and it only
* extends ListDataEvent to fit the ListDataListener interface.
*
* @author Jesse Wilson
*/
final class MutableListDataEvent extends ListDataEvent {
/** what the change is, currently */
private int index0;
private int index1;
private int type;
/**
* Creates a new mutable data event that always uses the specified object
* as its source.
*/
public MutableListDataEvent(Object source) {
super(source, CONTENTS_CHANGED, 0, 0);
}
/**
* Sets the start and end range for this event. The values are inclusive.
*/
public void setRange(int index0, int index1) {
this.index0 = index0;
this.index1 = index1;
}
/**
* Sets the type of change. This value must be either CONTENTS_CHANGED,
* INTERVAL_ADDED, or INTERVAL REMOVED.
*/
public void setType(int type) {
this.type = type;
}
/**
* Accessors for the change information do not use any information
* in the parent class.
*/
@Override
public int getIndex0() {
return index0;
}
@Override
public int getIndex1() {
return index1;
}
@Override
public int getType() {
return type;
}
/**
* Gets this event as a String for debugging.
*/
@Override
public String toString() {
return "" + type + "[" + index0 + "," + index1 + "]";
}
} libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/swing/SortableRenderer.java 0000644 0001750 0001750 00000001762 12106516356 031402 0 ustar gregoa gregoa /* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.swing;
import javax.swing.*;
/**
* This interface is intended to be implemented by custom TableCellRenderers
* installed on the JTableHeader of a sortable JTable. The custom renderer
* need only implement this interface and it will be notified of the
* appropriate sorting icon immediately before the renderer is asked to provide
* a component.
*
* @author James Lemieux
*/
public interface SortableRenderer {
/**
* Sets the icon to display in order to indicate sorting direction or
* null if no sorting is taking place.
*
* @param sortIcon the Icon indicating the sort direction or
* null if there is not sorting
*/
public void setSortIcon(Icon sortIcon);
} libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/swing/GlazedListsSwing.java 0000644 0001750 0001750 00000034643 12106516356 031401 0 ustar gregoa gregoa /* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.swing;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.GlazedLists;
import ca.odell.glazedlists.ThresholdList;
import ca.odell.glazedlists.TransformedList;
import ca.odell.glazedlists.gui.TableFormat;
import ca.odell.glazedlists.impl.swing.LowerThresholdRangeModel;
import ca.odell.glazedlists.impl.swing.SwingThreadProxyEventList;
import ca.odell.glazedlists.impl.swing.UpperThresholdRangeModel;
import javax.swing.BoundedRangeModel;
import javax.swing.SwingUtilities;
/**
* A factory for creating all sorts of objects to be used with Glazed Lists.
*
* @author Jesse Wilson
*/
public final class GlazedListsSwing {
/**
* A dummy constructor to prevent instantiation of this class
*/
private GlazedListsSwing() {
throw new UnsupportedOperationException();
}
// EventLists // // // // // // // // // // // // // // // // // // // // //
/**
* Wraps the source in an {@link EventList} that fires all of its update
* events from the Swing event dispatch thread.
*/
public static TransformedList swingThreadProxyList(EventList source) {
return new SwingThreadProxyEventList(source);
}
/**
* Returns true iff list is an {@link EventList} that fires
* all of its update events from the Swing event dispatch thread.
*/
public static boolean isSwingThreadProxyList(EventList list) {
return list instanceof SwingThreadProxyEventList;
}
// ThresholdRangeModels // // // // // // // // // // // // // // // // //
/**
* Creates a model that manipulates the lower bound of the specified
* ThresholdList. The ThresholdList linked to this model type will contain
* a range of Objects between the results of getValue() and getMaximum()
* on the BoundedRangeModel.
*/
public static BoundedRangeModel lowerRangeModel(ThresholdList target) {
return new LowerThresholdRangeModel(target);
}
/**
* Creates a model that manipulates the upper bound of the specified
* ThresholdList. The ThresholdList linked to this model type will contain
* a range of Objects between the results of getMinimum() and getValue()
* on the BoundedRangeModel.
*/
public static BoundedRangeModel upperRangeModel(ThresholdList target) {
return new UpperThresholdRangeModel(target);
}
// TableModel convenience creators
/**
* Creates a new table model that extracts column data from the given
* source using the the given tableFormat.
*
* The returned table model is not thread-safe. Unless otherwise
* noted, all methods are only safe to be called from the event dispatch thread.
* To do this programmatically, use {@link SwingUtilities#invokeAndWait(Runnable)} and
* wrap the source list (or some part of the source list's pipeline) using
* {@link GlazedListsSwing#swingThreadProxyList(EventList)}.
*
* @param source the EventList that provides the row objects
* @param tableFormat the object responsible for extracting column data
* from the row objects
*/
public static AdvancedTableModel eventTableModel(EventList source, TableFormat super E> tableFormat) {
return new DefaultEventTableModel(source, tableFormat);
}
/**
* Creates a new table model that extracts column data from the given source
* using the the given tableFormat. While holding a read lock,
* this method wraps the source list using
* {@link GlazedListsSwing#swingThreadProxyList(EventList)}.
*
* The returned table model is not thread-safe. Unless otherwise noted, all
* methods are only safe to be called from the event dispatch thread.
*
*
* @param source the EventList that provides the row objects
* @param tableFormat the object responsible for extracting column data from the row objects
*/
public static AdvancedTableModel eventTableModelWithThreadProxyList(EventList source, TableFormat super E> tableFormat) {
final EventList proxySource = createSwingThreadProxyList(source);
return new DefaultEventTableModel(proxySource, true, tableFormat);
}
/**
* Creates a new table model that renders the specified list with an automatically
* generated {@link TableFormat}. It uses JavaBeans and reflection to create
* a {@link TableFormat} as specified.
*
* Note that classes that will be obfuscated may not work with
* reflection. In this case, implement a {@link TableFormat} manually.
*
* The returned table model is not thread-safe. Unless otherwise
* noted, all methods are only safe to be called from the event dispatch thread.
* To do this programmatically, use {@link SwingUtilities#invokeAndWait(Runnable)} and
* wrap the source list (or some part of the source list's pipeline) using
* {@link GlazedListsSwing#swingThreadProxyList(EventList)}.
*
* @param source the EventList that provides the row objects
* @param propertyNames an array of property names in the JavaBeans format.
* For example, if your list contains Objects with the methods getFirstName(),
* setFirstName(String), getAge(), setAge(Integer), then this array should
* contain the two strings "firstName" and "age". This format is specified
* by the JavaBeans {@link java.beans.PropertyDescriptor}.
* @param columnLabels the corresponding column names for the listed property
* names. For example, if your columns are "firstName" and "age", then
* your labels might be "First Name" and "Age".
* @param writable an array of booleans specifying which of the columns in
* your table are writable.
*
*/
public static AdvancedTableModel eventTableModel(EventList source, String[] propertyNames, String[] columnLabels, boolean[] writable) {
return eventTableModel(source, GlazedLists.tableFormat(propertyNames, columnLabels, writable));
}
/**
* Creates a new table model that renders the specified list with an automatically
* generated {@link TableFormat}. It uses JavaBeans and reflection to create
* a {@link TableFormat} as specified. While holding a read lock,
* this method wraps the source list using
* {@link GlazedListsSwing#swingThreadProxyList(EventList)}.
*
* Note that classes that will be obfuscated may not work with
* reflection. In this case, implement a {@link TableFormat} manually.
*
* The returned table model is not thread-safe. Unless otherwise
* noted, all methods are only safe to be called from the event dispatch thread.
*
* @param source the EventList that provides the row objects
* @param propertyNames an array of property names in the JavaBeans format.
* For example, if your list contains Objects with the methods getFirstName(),
* setFirstName(String), getAge(), setAge(Integer), then this array should
* contain the two strings "firstName" and "age". This format is specified
* by the JavaBeans {@link java.beans.PropertyDescriptor}.
* @param columnLabels the corresponding column names for the listed property
* names. For example, if your columns are "firstName" and "age", then
* your labels might be "First Name" and "Age".
* @param writable an array of booleans specifying which of the columns in
* your table are writable.
*
*/
public static AdvancedTableModel eventTableModelWithThreadProxyList(EventList source, String[] propertyNames, String[] columnLabels, boolean[] writable) {
return eventTableModelWithThreadProxyList(source, GlazedLists.tableFormat(propertyNames, columnLabels, writable));
}
// ListSelectionModel convenience creators
/**
* Creates a new selection model that also presents a list of the selection.
*
* The {@link AdvancedListSelectionModel} listens to this {@link EventList} in order
* to adjust selection when the {@link EventList} is modified. For example,
* when an element is added to the {@link EventList}, this may offset the
* selection of the following elements.
*
* The returned selection model is not thread-safe. Unless otherwise
* noted, all methods are only safe to be called from the event dispatch thread.
* To do this programmatically, use {@link SwingUtilities#invokeAndWait(Runnable)} and
* wrap the source list (or some part of the source list's pipeline) using
* {@link GlazedListsSwing#swingThreadProxyList(EventList)}.
*
* @param source the {@link EventList} whose selection will be managed. This should
* be the same {@link EventList} passed to the constructor of your
* {@link AdvancedTableModel} or {@link EventListModel}.
*/
public static AdvancedListSelectionModel eventSelectionModel(EventList source) {
return new DefaultEventSelectionModel(source);
}
/**
* Creates a new selection model that also presents a list of the selection.
* While holding a read lock, it wraps the source list using
* {@link GlazedListsSwing#swingThreadProxyList(EventList)}. The
* {@link AdvancedListSelectionModel} listens to this {@link EventList} in order to adjust
* selection when the {@link EventList} is modified. For example, when an element is added to
* the {@link EventList}, this may offset the selection of the following elements.
*
* The returned selection model is not thread-safe. Unless otherwise noted,
* all methods are only safe to be called from the event dispatch thread.
*
*
* @param source the {@link EventList} whose selection will be managed. This should be the
* same {@link EventList} passed to the constructor of your
* {@link AdvancedTableModel} or {@link EventListModel}.
*/
public static AdvancedListSelectionModel eventSelectionModelWithThreadProxyList(EventList source) {
final EventList proxySource = createSwingThreadProxyList(source);
return new DefaultEventSelectionModel(proxySource, true);
}
// EventListModel convenience creators
/**
* Creates a new list model that contains all objects located in the given
* source and reacts to any changes in the given source.
*
* The returned selection model is not thread-safe. Unless otherwise
* noted, all methods are only safe to be called from the event dispatch thread.
* To do this programmatically, use {@link SwingUtilities#invokeAndWait(Runnable)} and
* wrap the source list (or some part of the source list's pipeline) using
* {@link GlazedListsSwing#swingThreadProxyList(EventList)}.
*
*
* @param source the EventList that provides the elements
*/
public static DefaultEventListModel eventListModel(EventList source) {
return new DefaultEventListModel(source);
}
/**
* Creates a new list model that contains all objects located in the given
* source and reacts to any changes in the given source.
* While holding a read lock, it wraps the source list using
* {@link GlazedListsSwing#swingThreadProxyList(EventList)}.
*
* The returned selection model is not thread-safe. Unless otherwise
* noted, all methods are only safe to be called from the event dispatch thread.
*
*
* @param source the EventList that provides the elements
*/
public static DefaultEventListModel eventListModelWithThreadProxyList(EventList source) {
final EventList proxySource = createSwingThreadProxyList(source);
return new DefaultEventListModel(proxySource, true);
}
// EventComboBoxModel convenience creators
/**
* Creates a new combobox model that contains all objects located in the given
* source and reacts to any changes in the given source.
*
* The returned combobox model is not thread-safe. Unless otherwise
* noted, all methods are only safe to be called from the event dispatch thread.
* To do this programmatically, use {@link SwingUtilities#invokeAndWait(Runnable)} and
* wrap the source list (or some part of the source list's pipeline) using
* {@link GlazedListsSwing#swingThreadProxyList(EventList)}.
*
*
* @param source the EventList that provides the elements
*/
public static DefaultEventComboBoxModel eventComboBoxModel(EventList source) {
return new DefaultEventComboBoxModel(source);
}
/**
* Creates a new combobox model that contains all objects located in the given
* source and reacts to any changes in the given source.
* While holding a read lock, it wraps the source list using
* {@link GlazedListsSwing#swingThreadProxyList(EventList)}.
*
* The returned combobox model is not thread-safe. Unless otherwise
* noted, all methods are only safe to be called from the event dispatch thread.
*
*
* @param source the EventList that provides the elements
*/
public static DefaultEventComboBoxModel eventComboBoxModelWithThreadProxyList(EventList source) {
final EventList proxySource = createSwingThreadProxyList(source);
return new DefaultEventComboBoxModel(proxySource, true);
}
/** Helper method to create a SwingThreadProxyList with read locks. */
private static EventList createSwingThreadProxyList(EventList source) {
final EventList result;
source.getReadWriteLock().readLock().lock();
try {
result = GlazedListsSwing.swingThreadProxyList(source);
} finally {
source.getReadWriteLock().readLock().unlock();
}
return result;
}
} ././@LongLink 0000000 0000000 0000000 00000000146 00000000000 011566 L ustar root root libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/swing/DefaultEventSelectionModel.java libglazedlists-java-1.9.0+dfsg.orig/source/ca/odell/glazedlists/swing/DefaultEventSelectionModel.jav0000644 0001750 0001750 00000040632 12106516356 033213 0 ustar gregoa gregoa /* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.swing;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.ListSelection;
import ca.odell.glazedlists.matchers.Matcher;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
/**
* An {@link DefaultEventSelectionModel} is a class that performs two simulaneous
* services. It is a {@link ListSelectionModel} to provide selection tracking for a
* {@link JTable}. It is also a {@link EventList} that contains the table's selection.
*
* As elements are selected or deselected, the {@link EventList} aspect of this
* {@link DefaultEventSelectionModel} changes. Changes to that {@link List} will change the
* source {@link EventList}. To modify only the selection, use the
* {@link ListSelectionModel}'s methods.
*
*
Alongside MULTIPLE_INTERVAL_SELECTION, this {@link ListSelectionModel}
* supports an additional selection mode.
* MULTIPLE_INTERVAL_SELECTION_DEFENSIVE is a new selection mode.
* It is identical to MULTIPLE_INTERVAL_SELECTION in every way but
* one. When a row is inserted immediately before a selected row in the
* MULTIPLE_INTERVAL_SELECTION mode, it becomes selected. But in
* the MULTIPLE_INTERVAL_SELECTION_DEFENSIVE mode, it does not
* become selected. To set this mode, use
* {@link #setSelectionMode(int) setSelectionMode(ListSelection.MULTIPLE_INTERVAL_SELECTION_DEFENSIVE)}.
*
*
{@link DefaultEventSelectionModel} is not thread-safe. Unless otherwise
* noted, all methods are only safe to be called from the event dispatch thread.
* To do this programmatically, use {@link SwingUtilities#invokeAndWait(Runnable)} and
* wrap the source list (or some part of the source list's pipeline) using
* GlazedListsSwing#swingThreadProxyList(EventList).
*
* @see Bug 39
* @see Bug 61
* @see Bug 76
* @see Bug 108
* @see Bug 110
* @see Bug 112
* @see Bug 222 *
*
* @author Jesse Wilson
*/
public final class DefaultEventSelectionModel implements AdvancedListSelectionModel {
/** the event lists that provide an event list view of the selection */
private ListSelection listSelection;
/** the source event list. */
private EventList source;
/** indicator to dispose source list */
private boolean disposeSource;
/** whether the user can modify the selection */
private boolean enabled = true;
/** listeners to notify when the selection changes */
private final List listeners = new ArrayList();
/** whether there are a series of changes on the way */
private boolean valueIsAdjusting = false;
private int fullChangeStart = -1;
private int fullChangeFinish = -1;
/**
* Creates a new selection model that also presents a list of the selection.
*
* The {@link DefaultEventSelectionModel} listens to this {@link EventList} in order
* to adjust selection when the {@link EventList} is modified. For example,
* when an element is added to the {@link EventList}, this may offset the
* selection of the following elements.
*
* @param source the {@link EventList} whose selection will be managed. This should
* be the same {@link EventList} passed to the constructor of your
* {@link DefaultEventTableModel} or {@link DefaultEventListModel}.
*/
public DefaultEventSelectionModel(EventList source) {
this(source, false);
}
/**
* Creates a new selection model that also presents a list of the selection. The
* {@link DefaultEventSelectionModel} listens to this {@link EventList} in order to adjust
* selection when the {@link EventList} is modified. For example, when an element is added to
* the {@link EventList}, this may offset the selection of the following elements.
*
* @param source the {@link EventList} whose selection will be managed. This should be the
* same {@link EventList} passed to the constructor of your
* {@link DefaultEventTableModel} or {@link DefaultEventListModel}.
* @param diposeSource true if the source list should be disposed when disposing
* this model, false otherwise
*/
protected DefaultEventSelectionModel(EventList source, boolean disposeSource) {
// lock the source list for reading since we want to prevent writes
// from occurring until we fully initialize this EventSelectionModel
source.getReadWriteLock().readLock().lock();
try {
this.source = source;
// build a list for reading the selection
this.listSelection = new ListSelection(source);
this.listSelection.addSelectionListener(new SwingSelectionListener());
} finally {
source.getReadWriteLock().readLock().unlock();
}
this.disposeSource = disposeSource;
}
/**
* {@inheritDoc}
*/
public EventList getSelected() {
source.getReadWriteLock().readLock().lock();
try {
return listSelection.getSelected();
} finally {
source.getReadWriteLock().readLock().unlock();
}
}
/**
* {@inheritDoc}
*/
public EventList getTogglingSelected() {
source.getReadWriteLock().readLock().lock();
try {
return listSelection.getTogglingSelected();
} finally {
source.getReadWriteLock().readLock().unlock();
}
}
/**
* {@inheritDoc}
*/
public EventList