blob: 59eb93c97f0c836ad3a6ac9725246f2027955302 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2013, 2020 Dirk Fauth and others.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Dirk Fauth <dirk.fauth@googlemail.com> - initial API and implementation
* Dirk Fauth <dirk.fauth@googlemail.com> - Bug 454503
*******************************************************************************/
package org.eclipse.nebula.widgets.nattable.extension.glazedlists.filterrow;
import static org.eclipse.nebula.widgets.nattable.filterrow.FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX;
import static org.eclipse.nebula.widgets.nattable.filterrow.config.FilterRowConfigAttributes.FILTER_DISPLAY_CONVERTER;
import static org.eclipse.nebula.widgets.nattable.style.DisplayMode.NORMAL;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;
import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
import org.eclipse.nebula.widgets.nattable.data.IColumnAccessor;
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 ca.odell.glazedlists.FilterList;
import ca.odell.glazedlists.GlazedLists;
import ca.odell.glazedlists.matchers.CompositeMatcherEditor;
import ca.odell.glazedlists.matchers.MatcherEditor;
import ca.odell.glazedlists.matchers.Matchers;
/**
* Specialisation of the DefaultGlazedListsStaticFilterStrategy that is intended
* to be used in combination with FilterRowComboBoxCellEditors that allows
* filtering via multiselect comboboxes like in Excel. As it extends
* DefaultGlazedListsStaticFilterStrategy it also supports static filters which
* allows to integrate it with the GlazedListsRowHideShowLayer.
* <p>
* The special case in here is that if nothing is selected in the filter
* combobox, then everything should be filtered.
*/
public class ComboBoxGlazedListsFilterStrategy<T> extends DefaultGlazedListsStaticFilterStrategy<T> {
/**
* The FilterRowComboBoxDataProvider needed to determine whether filters
* should applied or not. If there are no values specified for filtering of
* a column then everything should be filtered, if all possible values are
* given as filter then no filter needs to be applied.
*/
private FilterRowComboBoxDataProvider<T> comboBoxDataProvider;
/**
* A MatcherEditor that will never match anything.
*/
private MatcherEditor<T> matchNone = GlazedLists.fixedMatcherEditor(Matchers.falseMatcher());
/**
*
* @param comboBoxDataProvider
* The FilterRowComboBoxDataProvider needed to determine whether
* filters should applied or not. If there are no values
* specified for filtering of a column then everything should be
* filtered, if all possible values are given as filter then no
* filter needs to be applied.
* @param filterList
* The CompositeMatcherEditor that is used for GlazedLists
* filtering
* @param columnAccessor
* The IColumnAccessor needed to access the row data to perform
* filtering
* @param configRegistry
* The IConfigRegistry to retrieve several configurations from
*/
public ComboBoxGlazedListsFilterStrategy(
FilterRowComboBoxDataProvider<T> comboBoxDataProvider,
FilterList<T> filterList,
IColumnAccessor<T> columnAccessor,
IConfigRegistry configRegistry) {
super(filterList, columnAccessor, configRegistry);
this.comboBoxDataProvider = comboBoxDataProvider;
}
/**
* @param comboBoxDataProvider
* The FilterRowComboBoxDataProvider needed to determine whether
* filters should applied or not. If there are no values
* specified for filtering of a column then everything should be
* filtered, if all possible values are given as filter then no
* filter needs to be applied.
* @param filterList
* The FilterList that is used within the GlazedLists based
* NatTable for filtering.
* @param matcherEditor
* The CompositeMatcherEditor that should be used by this
* DefaultGlazedListsStaticFilterStrategy.
* @param columnAccessor
* The IColumnAccessor necessary to access the column data of the
* row objects in the FilterList.
* @param configRegistry
* The IConfigRegistry necessary to retrieve filter specific
* configurations.
*/
public ComboBoxGlazedListsFilterStrategy(
FilterRowComboBoxDataProvider<T> comboBoxDataProvider,
FilterList<T> filterList,
CompositeMatcherEditor<T> matcherEditor,
IColumnAccessor<T> columnAccessor,
IConfigRegistry configRegistry) {
super(filterList, matcherEditor, columnAccessor, configRegistry);
this.comboBoxDataProvider = comboBoxDataProvider;
}
@SuppressWarnings("rawtypes")
@Override
public void applyFilter(Map<Integer, Object> filterIndexToObjectMap) {
if (filterIndexToObjectMap.isEmpty()) {
this.filterLock.writeLock().lock();
try {
this.getMatcherEditor().getMatcherEditors().add(this.matchNone);
} finally {
this.filterLock.writeLock().unlock();
}
return;
}
// we need to create a new Map for applying a filter using the parent
// class otherwise we would remove the previous added pre-selected
// values
Map<Integer, Object> newIndexToObjectMap = new HashMap<>();
newIndexToObjectMap.putAll(filterIndexToObjectMap);
// remove all complete selected
for (Iterator<Map.Entry<Integer, Object>> it = newIndexToObjectMap.entrySet().iterator(); it.hasNext();) {
Entry<Integer, Object> entry = it.next();
Object filterObject = entry.getValue();
if (EditConstants.SELECT_ALL_ITEMS_VALUE.equals(filterObject)) {
it.remove();
} else {
List<?> dataProviderList = this.comboBoxDataProvider.getValues(entry.getKey(), 0);
// selecting all is transported as String to support lazy
// loading of combo box values
Collection filterCollection = (filterObject instanceof Collection) ? (Collection) filterObject : null;
if (filterCollection == null || filterCollection.isEmpty()) {
// for one column there are no items selected in the combo,
// therefore nothing matches
this.filterLock.writeLock().lock();
try {
this.getMatcherEditor().getMatcherEditors().add(this.matchNone);
} finally {
this.filterLock.writeLock().unlock();
}
return;
} else if (filterCollectionsEqual(filterCollection, dataProviderList)) {
it.remove();
}
}
}
super.applyFilter(newIndexToObjectMap);
}
/**
* {@inheritDoc}
* <p>
* This implementation is able to handle Collections and will generate a
* regular expression containing all values in the Collection.
*/
@SuppressWarnings("rawtypes")
@Override
protected String getStringFromColumnObject(final int columnIndex, final Object object) {
final IDisplayConverter displayConverter = this.configRegistry.getConfigAttribute(
FILTER_DISPLAY_CONVERTER,
NORMAL,
FILTER_ROW_COLUMN_LABEL_PREFIX + columnIndex);
if (object instanceof Collection) {
String result = ""; //$NON-NLS-1$
Collection valueCollection = (Collection) object;
for (Object value : valueCollection) {
if (result.length() > 0) {
result += "|"; //$NON-NLS-1$
}
String convertedValue = displayConverter.canonicalToDisplayValue(value).toString();
if (convertedValue.isEmpty()) {
// for an empty String add the regular expression for empty
// String
result += "^$"; //$NON-NLS-1$
} else {
result += Pattern.quote(convertedValue);
}
}
return "(" + result + ")"; //$NON-NLS-1$//$NON-NLS-2$
}
if (displayConverter != null) {
Object result = displayConverter.canonicalToDisplayValue(object);
if (result != null) {
return result.toString();
}
}
return ""; //$NON-NLS-1$
}
@SuppressWarnings("rawtypes")
protected boolean filterCollectionsEqual(Collection filter1, Collection filter2) {
if ((filter1 != null && filter2 != null)
&& filter1.size() == filter2.size()) {
if (!filter1.equals(filter2)) {
// as equality for collections take into account the order and
// the elements we perform an additional check if the same items
// regardless the order are contained in both lists
for (Object f1 : filter1) {
if (!filter2.contains(f1)) {
return false;
}
}
// as lists can contain the same element twice, we also perform
// a counter check
for (Object f2 : filter2) {
if (!filter1.contains(f2)) {
return false;
}
}
}
return true;
}
return false;
}
}