Bug 581620 - Reapply combobox filter states on different collections
Signed-off-by: Dirk Fauth <dirk.fauth@googlemail.com>
Change-Id: Iadc6059ca0037819055dbbdcfdcdee2bf07e4c31
diff --git a/org.eclipse.nebula.widgets.nattable.core/.settings/.api_filters b/org.eclipse.nebula.widgets.nattable.core/.settings/.api_filters
index afddbc7..5fb9e5e 100644
--- a/org.eclipse.nebula.widgets.nattable.core/.settings/.api_filters
+++ b/org.eclipse.nebula.widgets.nattable.core/.settings/.api_filters
@@ -15,6 +15,18 @@
<message_argument value="COMMA_REPLACEMENT"/>
</message_arguments>
</filter>
+ <filter id="336658481">
+ <message_arguments>
+ <message_argument value="org.eclipse.nebula.widgets.nattable.filterrow.FilterRowDataProvider"/>
+ <message_argument value="EMPTY_REPLACEMENT"/>
+ </message_arguments>
+ </filter>
+ <filter id="336658481">
+ <message_arguments>
+ <message_argument value="org.eclipse.nebula.widgets.nattable.filterrow.FilterRowDataProvider"/>
+ <message_argument value="NULL_REPLACEMENT"/>
+ </message_arguments>
+ </filter>
</resource>
<resource path="src/org/eclipse/nebula/widgets/nattable/filterrow/combobox/ComboBoxFilterRowConfiguration.java" type="org.eclipse.nebula.widgets.nattable.filterrow.combobox.ComboBoxFilterRowConfiguration">
<filter id="336658481">
diff --git a/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/filterrow/FilterRowDataProvider.java b/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/filterrow/FilterRowDataProvider.java
index 6eb8382..7493d31 100644
--- a/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/filterrow/FilterRowDataProvider.java
+++ b/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/filterrow/FilterRowDataProvider.java
@@ -13,9 +13,11 @@
package org.eclipse.nebula.widgets.nattable.filterrow;
import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
import java.util.Properties;
@@ -23,6 +25,8 @@
import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
import org.eclipse.nebula.widgets.nattable.data.convert.IDisplayConverter;
+import org.eclipse.nebula.widgets.nattable.edit.EditConstants;
+import org.eclipse.nebula.widgets.nattable.filterrow.combobox.FilterRowComboBoxDataProvider;
import org.eclipse.nebula.widgets.nattable.filterrow.event.FilterAppliedEvent;
import org.eclipse.nebula.widgets.nattable.layer.ILayer;
import org.eclipse.nebula.widgets.nattable.layer.cell.LayerCell;
@@ -66,6 +70,26 @@
public static final String COMMA_REPLACEMENT = "°#°"; //$NON-NLS-1$
/**
+ * Replacement for the null value that is used for persisting collection
+ * values in case of combobox filters. Needed for the inverted persistence
+ * in case there are null values in the collection that need to be
+ * persisted.
+ *
+ * @since 2.1
+ */
+ public static final String NULL_REPLACEMENT = "°null°"; //$NON-NLS-1$
+
+ /**
+ * Replacement for an empty String value that is used for persisting
+ * collection values in case of combobox filters. Needed for the inverted
+ * persistence in case there are empty String values in the collection that
+ * need to be persisted.
+ *
+ * @since 2.1
+ */
+ public static final String EMPTY_REPLACEMENT = "°empty°"; //$NON-NLS-1$
+
+ /**
* The prefix String that will be used to mark that the following filter
* value in the persisted state is a collection.
*/
@@ -105,6 +129,29 @@
private Map<Integer, Object> filterIndexToObjectMap = new HashMap<>();
/**
+ * Flag to configure how filter collections are persisted. By default the
+ * values in the collection are persisted as is. In case of Excel like
+ * filters, it can be more feasible to store which values are NOT selected,
+ * to be able to load the filter even for different values in the filter
+ * list.
+ *
+ * @see #filterRowComboBoxDataProvider
+ *
+ * @since 2.1
+ */
+ private boolean invertCollectionPersistence = false;
+
+ /**
+ * The FilterRowComboBoxDataProvider that is needed to be able to support
+ * inverted persistence of filter collections.
+ *
+ * @see FilterRowDataProvider#invertCollectionPersistence
+ *
+ * @since 2.1
+ */
+ private FilterRowComboBoxDataProvider<T> filterRowComboBoxDataProvider;
+
+ /**
*
* @param filterStrategy
* The {@link IFilterStrategy} to which the set filter value
@@ -285,16 +332,43 @@
StringBuilder builder = new StringBuilder(collectionSpec);
builder.append("["); //$NON-NLS-1$
Collection<?> filterCollection = (Collection<?>) filterValue;
- for (Iterator<?> iterator = filterCollection.iterator(); iterator.hasNext();) {
- Object filterObject = iterator.next();
- String displayValue = (String) converter.canonicalToDisplayValue(
- new LayerCell(null, columnIndex, 0),
- this.configRegistry,
- filterObject);
- displayValue = displayValue.replace(IPersistable.VALUE_SEPARATOR, COMMA_REPLACEMENT);
- builder.append(displayValue);
- if (iterator.hasNext()) {
- builder.append(IPersistable.VALUE_SEPARATOR);
+
+ if (!this.invertCollectionPersistence) {
+ for (Iterator<?> iterator = filterCollection.iterator(); iterator.hasNext();) {
+ Object filterObject = iterator.next();
+ String displayValue = (String) converter.canonicalToDisplayValue(
+ new LayerCell(null, columnIndex, 0),
+ this.configRegistry,
+ filterObject);
+ displayValue = displayValue.replace(IPersistable.VALUE_SEPARATOR, COMMA_REPLACEMENT);
+ builder.append(displayValue);
+ if (iterator.hasNext()) {
+ builder.append(IPersistable.VALUE_SEPARATOR);
+ }
+ }
+ } else {
+ List<?> allValues = new ArrayList<>(this.filterRowComboBoxDataProvider.getAllValues(columnIndex));
+ allValues.removeAll(filterCollection);
+
+ for (Iterator<?> iterator = allValues.iterator(); iterator.hasNext();) {
+ Object filterObject = iterator.next();
+ if (filterObject == null) {
+ builder.append(NULL_REPLACEMENT);
+ } else {
+ String displayValue = (String) converter.canonicalToDisplayValue(
+ new LayerCell(null, columnIndex, 0),
+ this.configRegistry,
+ filterObject);
+ displayValue = displayValue.replace(IPersistable.VALUE_SEPARATOR, COMMA_REPLACEMENT);
+ if (displayValue.isEmpty()) {
+ builder.append(EMPTY_REPLACEMENT);
+ } else {
+ builder.append(displayValue);
+ }
+ }
+ if (iterator.hasNext()) {
+ builder.append(IPersistable.VALUE_SEPARATOR);
+ }
}
}
@@ -345,13 +419,31 @@
// also get rid of the collection marks
filterText = filterText.substring(indexEndCollSpec + 2, filterText.length() - 1);
- String[] filterSplit = filterText.split(IPersistable.VALUE_SEPARATOR);
- for (String filterString : filterSplit) {
- filterString = filterString.replace(COMMA_REPLACEMENT, IPersistable.VALUE_SEPARATOR);
- filterCollection.add(converter.displayToCanonicalValue(
- new LayerCell(null, columnIndex, 0),
- this.configRegistry,
- filterString));
+ if (!filterText.isEmpty()) {
+ String[] filterSplit = filterText.split(IPersistable.VALUE_SEPARATOR);
+ for (String filterString : filterSplit) {
+ filterString = filterString.replace(COMMA_REPLACEMENT, IPersistable.VALUE_SEPARATOR);
+ if (NULL_REPLACEMENT.equals(filterString)) {
+ filterCollection.add(null);
+ } else if (EMPTY_REPLACEMENT.equals(filterString)) {
+ filterCollection.add(""); //$NON-NLS-1$
+ } else {
+ filterCollection.add(converter.displayToCanonicalValue(
+ new LayerCell(null, columnIndex, 0),
+ this.configRegistry,
+ filterString));
+ }
+ }
+ }
+
+ if (this.invertCollectionPersistence) {
+ if (filterCollection.isEmpty()) {
+ return EditConstants.SELECT_ALL_ITEMS_VALUE;
+ }
+
+ List<?> allValues = new ArrayList<>(this.filterRowComboBoxDataProvider.getAllValues(columnIndex));
+ allValues.removeAll(filterCollection);
+ return allValues;
}
return filterCollection;
@@ -383,4 +475,35 @@
return this.filterStrategy;
}
+ /**
+ *
+ * @return <code>true</code> if filter collections are persisted in an
+ * inverted way, which means the values that are <b>NOT</b> selected
+ * in the combo are persisted. By default this configuration is set
+ * to <code>false</code> which means values in the collection are
+ * persisted as is.
+ *
+ * @since 2.1
+ */
+ public boolean isInvertCollectionPersistence() {
+ return this.invertCollectionPersistence;
+ }
+
+ /**
+ *
+ * @param invertCollectionPersistence
+ * <code>true</code> if filter collections should be persisted in
+ * an inverted way, which means the values that are <b>NOT</b>
+ * selected in the combo are persisted.
+ *
+ * @since 2.1
+ */
+ public void setInvertCollectionPersistence(boolean invertCollectionPersistence, FilterRowComboBoxDataProvider<T> comboBoxDataProvider) {
+ if (invertCollectionPersistence && comboBoxDataProvider == null) {
+ throw new IllegalArgumentException("Can only invert the collection persistence if the FilterRowComboBoxDataProvider is provided"); //$NON-NLS-1$
+ }
+ this.invertCollectionPersistence = invertCollectionPersistence;
+ this.filterRowComboBoxDataProvider = comboBoxDataProvider;
+ }
+
}
diff --git a/org.eclipse.nebula.widgets.nattable.extension.glazedlists.test/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/FilterRowDataProviderTest.java b/org.eclipse.nebula.widgets.nattable.extension.glazedlists.test/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/FilterRowDataProviderTest.java
index bc82a8d..0dcc01d 100644
--- a/org.eclipse.nebula.widgets.nattable.extension.glazedlists.test/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/FilterRowDataProviderTest.java
+++ b/org.eclipse.nebula.widgets.nattable.extension.glazedlists.test/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/FilterRowDataProviderTest.java
@@ -25,25 +25,30 @@
import org.eclipse.nebula.widgets.nattable.config.ConfigRegistry;
import org.eclipse.nebula.widgets.nattable.config.DefaultNatTableStyleConfiguration;
import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
+import org.eclipse.nebula.widgets.nattable.data.ListDataProvider;
import org.eclipse.nebula.widgets.nattable.data.ReflectiveColumnPropertyAccessor;
import org.eclipse.nebula.widgets.nattable.data.convert.ContextualDisplayConverter;
import org.eclipse.nebula.widgets.nattable.data.convert.DefaultDoubleDisplayConverter;
import org.eclipse.nebula.widgets.nattable.dataset.fixture.data.RowDataFixture;
import org.eclipse.nebula.widgets.nattable.dataset.fixture.data.RowDataListFixture;
+import org.eclipse.nebula.widgets.nattable.edit.EditConstants;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.fixture.DataLayerFixture;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.fixture.LayerListenerFixture;
import org.eclipse.nebula.widgets.nattable.filterrow.FilterRowDataLayer;
import org.eclipse.nebula.widgets.nattable.filterrow.FilterRowDataProvider;
import org.eclipse.nebula.widgets.nattable.filterrow.TextMatchingMode;
+import org.eclipse.nebula.widgets.nattable.filterrow.combobox.FilterRowComboBoxDataProvider;
import org.eclipse.nebula.widgets.nattable.filterrow.config.DefaultFilterRowConfiguration;
import org.eclipse.nebula.widgets.nattable.filterrow.config.FilterRowConfigAttributes;
import org.eclipse.nebula.widgets.nattable.filterrow.event.FilterAppliedEvent;
+import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
import org.eclipse.nebula.widgets.nattable.persistence.IPersistable;
import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.FilterList;
import ca.odell.glazedlists.GlazedLists;
@@ -51,9 +56,13 @@
private FilterRowDataProvider<RowDataFixture> dataProvider;
private DataLayerFixture columnHeaderLayer;
+ private EventList<RowDataFixture> baseList;
private FilterList<RowDataFixture> filterList;
private ConfigRegistry configRegistry;
+ private ReflectiveColumnPropertyAccessor<RowDataFixture> columnAccessor =
+ new ReflectiveColumnPropertyAccessor<>(RowDataListFixture.getPropertyNames());
+
@BeforeEach
public void setup() {
this.columnHeaderLayer = new DataLayerFixture(10, 2, 100, 50);
@@ -62,12 +71,13 @@
new DefaultNatTableStyleConfiguration().configureRegistry(this.configRegistry);
new DefaultFilterRowConfiguration().configureRegistry(this.configRegistry);
- this.filterList = new FilterList<>(GlazedLists.eventList(RowDataListFixture.getList()));
+ this.baseList = GlazedLists.eventList(RowDataListFixture.getList());
+ this.filterList = new FilterList<>(this.baseList);
this.dataProvider = new FilterRowDataProvider<>(
new DefaultGlazedListsFilterStrategy<>(
this.filterList,
- new ReflectiveColumnPropertyAccessor<RowDataFixture>(RowDataListFixture.getPropertyNames()),
+ this.columnAccessor,
this.configRegistry),
this.columnHeaderLayer,
this.columnHeaderLayer.getDataProvider(),
@@ -412,4 +422,151 @@
assertEquals("foo", this.dataProvider.getDataValue(1, 1));
assertEquals("testValue", this.dataProvider.getDataValue(2, 1));
}
+
+ @Test
+ public void shouldInvertFilterCollectionPersistence() {
+ // enable inverted combobox filter persistence
+ FilterRowComboBoxDataProvider<RowDataFixture> cbdp =
+ new FilterRowComboBoxDataProvider<>(
+ new DataLayer(new ListDataProvider<>(this.filterList, this.columnAccessor)),
+ this.baseList,
+ this.columnAccessor);
+ this.dataProvider.setInvertCollectionPersistence(true, cbdp);
+
+ // set filter to filter out AAA and aaa
+ this.dataProvider.setDataValue(2, 1, new ArrayList<>(Arrays.asList("A-", "AA", "B", "B-", "BB", "C", "a", "aa")));
+
+ // save state
+ Properties properties = new Properties();
+ this.dataProvider.saveState("prefix", properties);
+ String persistedProperty = properties.getProperty("prefix" + FilterRowDataLayer.PERSISTENCE_KEY_FILTER_ROW_TOKENS);
+
+ String expectedPersistedCollection = "2:" + FilterRowDataProvider.FILTER_COLLECTION_PREFIX + ArrayList.class.getName() + ")["
+ + "AAA" + IPersistable.VALUE_SEPARATOR
+ + "aaa"
+ + "]";
+
+ assertEquals(expectedPersistedCollection + "|", persistedProperty);
+
+ // reset state
+ this.dataProvider.clearAllFilters();
+
+ assertNull(this.dataProvider.getDataValue(2, 1));
+
+ // load state
+ this.dataProvider.loadState("prefix", properties);
+
+ assertEquals(new ArrayList<>(Arrays.asList("A-", "AA", "B", "B-", "BB", "C", "a", "aa")), this.dataProvider.getDataValue(2, 1));
+ }
+
+ @Test
+ public void shouldInvertAllSelectedFilterCollectionPersistence() {
+ // enable inverted combobox filter persistence
+ FilterRowComboBoxDataProvider<RowDataFixture> cbdp =
+ new FilterRowComboBoxDataProvider<>(
+ new DataLayer(new ListDataProvider<>(this.filterList, this.columnAccessor)),
+ this.baseList,
+ this.columnAccessor);
+ this.dataProvider.setInvertCollectionPersistence(true, cbdp);
+
+ // set filter to select all, which means nothing is filtered
+ this.dataProvider.setDataValue(2, 1, new ArrayList<>(Arrays.asList("A-", "AA", "AAA", "B", "B-", "BB", "C", "a", "aa", "aaa")));
+
+ // save state
+ Properties properties = new Properties();
+ this.dataProvider.saveState("prefix", properties);
+ String persistedProperty = properties.getProperty("prefix" + FilterRowDataLayer.PERSISTENCE_KEY_FILTER_ROW_TOKENS);
+
+ String expectedPersistedCollection = "2:" + FilterRowDataProvider.FILTER_COLLECTION_PREFIX + ArrayList.class.getName() + ")[]";
+
+ assertEquals(expectedPersistedCollection + "|", persistedProperty);
+
+ // reset state
+ this.dataProvider.clearAllFilters();
+
+ assertNull(this.dataProvider.getDataValue(2, 1));
+
+ // load state
+ this.dataProvider.loadState("prefix", properties);
+
+ assertEquals(EditConstants.SELECT_ALL_ITEMS_VALUE, this.dataProvider.getDataValue(2, 1));
+ }
+
+ @Test
+ public void shouldInvertFilterCollectionNullValuePersistence() {
+ // enable inverted combobox filter persistence
+ FilterRowComboBoxDataProvider<RowDataFixture> cbdp =
+ new FilterRowComboBoxDataProvider<>(
+ new DataLayer(new ListDataProvider<>(this.filterList, this.columnAccessor)),
+ this.baseList,
+ this.columnAccessor);
+ this.dataProvider.setInvertCollectionPersistence(true, cbdp);
+
+ this.filterList.get(0).setRating(null);
+
+ assertEquals(Arrays.asList(null, "A-", "AA", "AAA", "B", "B-", "BB", "C", "aa", "aaa"), cbdp.getAllValues(2));
+
+ // set filter to filter out null values
+ this.dataProvider.setDataValue(2, 1, new ArrayList<>(Arrays.asList("A-", "AA", "AAA", "B", "B-", "BB", "C", "aa", "aaa")));
+
+ // save state
+ Properties properties = new Properties();
+ this.dataProvider.saveState("prefix", properties);
+ String persistedProperty = properties.getProperty("prefix" + FilterRowDataLayer.PERSISTENCE_KEY_FILTER_ROW_TOKENS);
+
+ String expectedPersistedCollection = "2:" + FilterRowDataProvider.FILTER_COLLECTION_PREFIX + ArrayList.class.getName() + ")["
+ + FilterRowDataProvider.NULL_REPLACEMENT
+ + "]";
+
+ assertEquals(expectedPersistedCollection + "|", persistedProperty);
+
+ // reset state
+ this.dataProvider.clearAllFilters();
+
+ assertNull(this.dataProvider.getDataValue(2, 1));
+
+ // load state
+ this.dataProvider.loadState("prefix", properties);
+
+ assertEquals(new ArrayList<>(Arrays.asList("A-", "AA", "AAA", "B", "B-", "BB", "C", "aa", "aaa")), this.dataProvider.getDataValue(2, 1));
+ }
+
+ @Test
+ public void shouldInvertFilterCollectionEmptyValuePersistence() {
+ // enable inverted combobox filter persistence
+ FilterRowComboBoxDataProvider<RowDataFixture> cbdp =
+ new FilterRowComboBoxDataProvider<>(
+ new DataLayer(new ListDataProvider<>(this.filterList, this.columnAccessor)),
+ this.baseList,
+ this.columnAccessor);
+ this.dataProvider.setInvertCollectionPersistence(true, cbdp);
+
+ this.filterList.get(0).setRating("");
+
+ assertEquals(Arrays.asList("", "A-", "AA", "AAA", "B", "B-", "BB", "C", "aa", "aaa"), cbdp.getAllValues(2));
+
+ // set filter to filter out null values
+ this.dataProvider.setDataValue(2, 1, new ArrayList<>(Arrays.asList("A-", "AA", "AAA", "B", "B-", "BB", "C", "aa", "aaa")));
+
+ // save state
+ Properties properties = new Properties();
+ this.dataProvider.saveState("prefix", properties);
+ String persistedProperty = properties.getProperty("prefix" + FilterRowDataLayer.PERSISTENCE_KEY_FILTER_ROW_TOKENS);
+
+ String expectedPersistedCollection = "2:" + FilterRowDataProvider.FILTER_COLLECTION_PREFIX + ArrayList.class.getName() + ")["
+ + FilterRowDataProvider.EMPTY_REPLACEMENT
+ + "]";
+
+ assertEquals(expectedPersistedCollection + "|", persistedProperty);
+
+ // reset state
+ this.dataProvider.clearAllFilters();
+
+ assertNull(this.dataProvider.getDataValue(2, 1));
+
+ // load state
+ this.dataProvider.loadState("prefix", properties);
+
+ assertEquals(new ArrayList<>(Arrays.asList("A-", "AA", "AAA", "B", "B-", "BB", "C", "aa", "aaa")), this.dataProvider.getDataValue(2, 1));
+ }
}
\ No newline at end of file