| /******************************************************************************* |
| * 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; |
| } |
| } |