Bug 581005 - Support mixed filter row

Signed-off-by: Dirk Fauth <dirk.fauth@googlemail.com>

Change-Id: I340f4c82b0fcb1c72a1b4711afcff7899a9270bd
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 e7a5d13..ee9cb59 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
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2012, 2021 Original authors and others.
+ * Copyright (c) 2012, 2022 Original authors and others.
  *
  * This program and the accompanying materials are made
  * available under the terms of the Eclipse Public License 2.0
@@ -179,7 +179,7 @@ public int getRowCount() {
 
     @Override
     public void setDataValue(int columnIndex, int rowIndex, Object newValue) {
-        if (ObjectUtils.isNotNull(newValue)) {
+        if (newValue != null && newValue.toString().length() > 0) {
             this.filterIndexToObjectMap.put(columnIndex, newValue);
         } else {
             this.filterIndexToObjectMap.remove(columnIndex);
diff --git a/org.eclipse.nebula.widgets.nattable.examples/src/org/eclipse/nebula/widgets/nattable/examples/_600_GlazedLists/_603_Filter/_6037_MixedFilterRowExample.java b/org.eclipse.nebula.widgets.nattable.examples/src/org/eclipse/nebula/widgets/nattable/examples/_600_GlazedLists/_603_Filter/_6037_MixedFilterRowExample.java
new file mode 100644
index 0000000..5c7ded6
--- /dev/null
+++ b/org.eclipse.nebula.widgets.nattable.examples/src/org/eclipse/nebula/widgets/nattable/examples/_600_GlazedLists/_603_Filter/_6037_MixedFilterRowExample.java
@@ -0,0 +1,943 @@
+/*******************************************************************************
+ * Copyright (c) 2022 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
+ *******************************************************************************/
+package org.eclipse.nebula.widgets.nattable.examples._600_GlazedLists._603_Filter;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.eclipse.jface.bindings.keys.KeyStroke;
+import org.eclipse.jface.bindings.keys.ParseException;
+import org.eclipse.jface.fieldassist.SimpleContentProposalProvider;
+import org.eclipse.jface.fieldassist.TextContentAdapter;
+import org.eclipse.jface.layout.GridDataFactory;
+import org.eclipse.nebula.widgets.nattable.NatTable;
+import org.eclipse.nebula.widgets.nattable.config.AbstractRegistryConfiguration;
+import org.eclipse.nebula.widgets.nattable.config.AbstractUiBindingConfiguration;
+import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes;
+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.config.IEditableRule;
+import org.eclipse.nebula.widgets.nattable.data.ExtendedReflectiveColumnPropertyAccessor;
+import org.eclipse.nebula.widgets.nattable.data.IColumnAccessor;
+import org.eclipse.nebula.widgets.nattable.data.IColumnPropertyAccessor;
+import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
+import org.eclipse.nebula.widgets.nattable.data.IRowIdAccessor;
+import org.eclipse.nebula.widgets.nattable.data.ListDataProvider;
+import org.eclipse.nebula.widgets.nattable.data.convert.DefaultDisplayConverter;
+import org.eclipse.nebula.widgets.nattable.data.convert.DefaultIntegerDisplayConverter;
+import org.eclipse.nebula.widgets.nattable.data.convert.DisplayConverter;
+import org.eclipse.nebula.widgets.nattable.dataset.person.DataModelConstants;
+import org.eclipse.nebula.widgets.nattable.dataset.person.Person.Gender;
+import org.eclipse.nebula.widgets.nattable.dataset.person.PersonService;
+import org.eclipse.nebula.widgets.nattable.dataset.person.PersonWithAddress;
+import org.eclipse.nebula.widgets.nattable.edit.EditConfigAttributes;
+import org.eclipse.nebula.widgets.nattable.edit.editor.ComboBoxCellEditor;
+import org.eclipse.nebula.widgets.nattable.edit.editor.ICellEditor;
+import org.eclipse.nebula.widgets.nattable.edit.editor.IComboBoxDataProvider;
+import org.eclipse.nebula.widgets.nattable.examples.AbstractNatExample;
+import org.eclipse.nebula.widgets.nattable.examples.runner.StandaloneNatExampleRunner;
+import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsEventLayer;
+import org.eclipse.nebula.widgets.nattable.extension.glazedlists.filterrow.ComboBoxFilterRowHeaderComposite;
+import org.eclipse.nebula.widgets.nattable.extension.glazedlists.filterrow.ComboBoxGlazedListsFilterStrategy;
+import org.eclipse.nebula.widgets.nattable.extension.glazedlists.filterrow.GlazedListsFilterRowComboBoxDataProvider;
+import org.eclipse.nebula.widgets.nattable.filterrow.FilterRowDataLayer;
+import org.eclipse.nebula.widgets.nattable.filterrow.FilterRowDataProvider;
+import org.eclipse.nebula.widgets.nattable.filterrow.FilterRowPainter;
+import org.eclipse.nebula.widgets.nattable.filterrow.FilterRowRegularExpressionConverter;
+import org.eclipse.nebula.widgets.nattable.filterrow.FilterRowTextCellEditor;
+import org.eclipse.nebula.widgets.nattable.filterrow.IFilterStrategy;
+import org.eclipse.nebula.widgets.nattable.filterrow.combobox.ComboBoxFilterIconPainter;
+import org.eclipse.nebula.widgets.nattable.filterrow.combobox.ComboBoxFilterRowConfiguration;
+import org.eclipse.nebula.widgets.nattable.filterrow.combobox.FilterRowComboBoxCellEditor;
+import org.eclipse.nebula.widgets.nattable.filterrow.combobox.FilterRowComboBoxDataProvider;
+import org.eclipse.nebula.widgets.nattable.filterrow.config.FilterRowConfigAttributes;
+import org.eclipse.nebula.widgets.nattable.freeze.CompositeFreezeLayer;
+import org.eclipse.nebula.widgets.nattable.freeze.FreezeLayer;
+import org.eclipse.nebula.widgets.nattable.grid.GridRegion;
+import org.eclipse.nebula.widgets.nattable.grid.data.DefaultColumnHeaderDataProvider;
+import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider;
+import org.eclipse.nebula.widgets.nattable.grid.data.DefaultRowHeaderDataProvider;
+import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;
+import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer;
+import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultColumnHeaderDataLayer;
+import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultRowHeaderDataLayer;
+import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer;
+import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer;
+import org.eclipse.nebula.widgets.nattable.layer.AbstractLayerTransform;
+import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
+import org.eclipse.nebula.widgets.nattable.layer.ILayer;
+import org.eclipse.nebula.widgets.nattable.layer.LabelStack;
+import org.eclipse.nebula.widgets.nattable.layer.cell.ColumnLabelAccumulator;
+import org.eclipse.nebula.widgets.nattable.layer.cell.IConfigLabelAccumulator;
+import org.eclipse.nebula.widgets.nattable.painter.cell.decorator.PaddingDecorator;
+import org.eclipse.nebula.widgets.nattable.persistence.command.DisplayPersistenceDialogCommandHandler;
+import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;
+import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes;
+import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
+import org.eclipse.nebula.widgets.nattable.style.Style;
+import org.eclipse.nebula.widgets.nattable.style.theme.IThemeExtension;
+import org.eclipse.nebula.widgets.nattable.style.theme.ModernNatTableThemeConfiguration;
+import org.eclipse.nebula.widgets.nattable.style.theme.ThemeConfiguration;
+import org.eclipse.nebula.widgets.nattable.ui.NatEventData;
+import org.eclipse.nebula.widgets.nattable.ui.binding.UiBindingRegistry;
+import org.eclipse.nebula.widgets.nattable.ui.matcher.MouseEventMatcher;
+import org.eclipse.nebula.widgets.nattable.ui.menu.HeaderMenuConfiguration;
+import org.eclipse.nebula.widgets.nattable.ui.menu.IMenuItemProvider;
+import org.eclipse.nebula.widgets.nattable.ui.menu.IMenuItemState;
+import org.eclipse.nebula.widgets.nattable.ui.menu.MenuItemProviders;
+import org.eclipse.nebula.widgets.nattable.ui.menu.PopupMenuAction;
+import org.eclipse.nebula.widgets.nattable.ui.menu.PopupMenuBuilder;
+import org.eclipse.nebula.widgets.nattable.util.GUIHelper;
+import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.MenuItem;
+
+import ca.odell.glazedlists.EventList;
+import ca.odell.glazedlists.FilterList;
+import ca.odell.glazedlists.GlazedLists;
+import ca.odell.glazedlists.SortedList;
+import ca.odell.glazedlists.TransformedList;
+import ca.odell.glazedlists.matchers.CompositeMatcherEditor;
+import ca.odell.glazedlists.matchers.Matcher;
+import ca.odell.glazedlists.matchers.MatcherEditor;
+
+/**
+ * Example showing how to add the filter row to the layer composition of a grid
+ * that contains Excel like filters, text filters and combobox filters.
+ */
+public class _6037_MixedFilterRowExample extends AbstractNatExample {
+
+    private static final String EXCLUDE_LABEL = "EXCLUDE";
+
+    private ArrayList<Serializable> filterExcludes = new ArrayList<>();
+
+    public static void main(String[] args) throws Exception {
+        StandaloneNatExampleRunner.run(new _6037_MixedFilterRowExample());
+    }
+
+    @Override
+    public String getDescription() {
+        return "This example shows the usage of the filter row within a grid that has"
+                + " Excel-like multi-select combobox filters, free text filters and"
+                + " single-selection combobox filters in the filter row";
+    }
+
+    @Override
+    public Control createExampleControl(Composite parent) {
+        Composite container = new Composite(parent, SWT.NONE);
+        container.setLayout(new GridLayout());
+
+        // create a new ConfigRegistry which will be needed for GlazedLists
+        // handling
+        ConfigRegistry configRegistry = new ConfigRegistry();
+
+        // property names of the Person class
+        String[] propertyNames = { "firstName", "lastName", "gender",
+                "married", "birthday", "address.street", "address.housenumber",
+                "address.postalCode", "address.city" };
+
+        // mapping from property to label, needed for column header labels
+        Map<String, String> propertyToLabelMap = new HashMap<>();
+        propertyToLabelMap.put("firstName", "Firstname");
+        propertyToLabelMap.put("lastName", "Lastname");
+        propertyToLabelMap.put("gender", "Gender");
+        propertyToLabelMap.put("married", "Married");
+        propertyToLabelMap.put("birthday", "Birthday");
+        propertyToLabelMap.put("address.street", "Street");
+        propertyToLabelMap.put("address.housenumber", "Housenumber");
+        propertyToLabelMap.put("address.postalCode", "Postal Code");
+        propertyToLabelMap.put("address.city", "City");
+
+        IColumnPropertyAccessor<PersonWithAddress> columnPropertyAccessor =
+                new ExtendedReflectiveColumnPropertyAccessor<>(propertyNames);
+
+        IRowIdAccessor<PersonWithAddress> rowIdAccessor = new IRowIdAccessor<PersonWithAddress>() {
+
+            @Override
+            public Serializable getRowId(PersonWithAddress rowObject) {
+                return rowObject.getId();
+            }
+        };
+
+        final BodyLayerStack<PersonWithAddress> bodyLayerStack =
+                new BodyLayerStack<>(
+                        PersonService.getPersonsWithAddress(50),
+                        columnPropertyAccessor);
+
+        // add some null and empty values to verify the correct handling
+        bodyLayerStack.getBodyDataLayer().setDataValue(0, 3, "");
+        bodyLayerStack.getBodyDataLayer().setDataValue(0, 5, null);
+        bodyLayerStack.getBodyDataLayer().setDataValue(1, 2, "");
+        bodyLayerStack.getBodyDataLayer().setDataValue(1, 6, null);
+
+        // build the column header layer
+        IDataProvider columnHeaderDataProvider =
+                new DefaultColumnHeaderDataProvider(propertyNames, propertyToLabelMap);
+        DataLayer columnHeaderDataLayer =
+                new DefaultColumnHeaderDataLayer(columnHeaderDataProvider);
+        ILayer columnHeaderLayer =
+                new ColumnHeaderLayer(
+                        columnHeaderDataLayer,
+                        bodyLayerStack,
+                        bodyLayerStack.getSelectionLayer());
+
+        // Create a customized GlazedListsFilterRowComboBoxDataProvider that
+        // distincts the empty string and null from the collected values. This
+        // way null and "" entries in the collection are treated the same way
+        // and there is only a single "empty" entry in the dropdown.
+        GlazedListsFilterRowComboBoxDataProvider<PersonWithAddress> filterRowComboBoxDataProvider =
+                new GlazedListsFilterRowComboBoxDataProvider<PersonWithAddress>(
+                        bodyLayerStack.getGlazedListsEventLayer(),
+                        bodyLayerStack.getSortedList(),
+                        columnPropertyAccessor) {
+
+                    @Override
+                    protected List<?> collectValues(int columnIndex) {
+                        List<?> result = super.collectValues(columnIndex);
+                        result = result.stream()
+                                .map(x -> (x instanceof String && ((String) x).isEmpty()) ? null : x)
+                                .distinct()
+                                .collect(Collectors.toList());
+
+                        return result;
+                    }
+                };
+
+        ComboBoxGlazedListsWithExcludeFilterStrategy<PersonWithAddress> filterStrategy =
+                new ComboBoxGlazedListsWithExcludeFilterStrategy<>(
+                        filterRowComboBoxDataProvider,
+                        bodyLayerStack.getFilterList(),
+                        columnPropertyAccessor,
+                        configRegistry);
+
+        // create the ComboBoxFilterRowHeaderComposite without the default
+        // configuration
+        ComboBoxFilterRowHeaderComposite<PersonWithAddress> filterRowHeaderLayer =
+                new ComboBoxFilterRowHeaderComposite<>(
+                        filterStrategy,
+                        filterRowComboBoxDataProvider,
+                        columnHeaderLayer,
+                        columnHeaderDataProvider,
+                        configRegistry,
+                        false);
+
+        // add a default ComboBoxFilterRowConfiguration with an updated editor
+        // that shows a filter icon if a filter is applied
+        IComboBoxDataProvider comboBoxDataProvider = filterRowHeaderLayer.getComboBoxDataProvider();
+        FilterRowComboBoxCellEditor filterEditor = new FilterRowComboBoxCellEditor(comboBoxDataProvider, 10);
+        filterEditor.setShowDropdownFilter(true);
+        filterRowHeaderLayer.addConfiguration(
+                new ComboBoxFilterRowConfiguration(
+                        filterEditor,
+                        new ComboBoxFilterIconPainter(comboBoxDataProvider, GUIHelper.getImage("filter"), null)));
+
+        // add the specialized configuration to the
+        // ComboBoxFilterRowHeaderComposite
+        filterRowHeaderLayer.addConfiguration(new FilterRowConfiguration());
+
+        // build the row header layer
+        IDataProvider rowHeaderDataProvider =
+                new DefaultRowHeaderDataProvider(bodyLayerStack.getBodyDataProvider());
+        DataLayer rowHeaderDataLayer =
+                new DefaultRowHeaderDataLayer(rowHeaderDataProvider);
+        ILayer rowHeaderLayer =
+                new RowHeaderLayer(
+                        rowHeaderDataLayer,
+                        bodyLayerStack,
+                        bodyLayerStack.getSelectionLayer());
+
+        // build the corner layer
+        IDataProvider cornerDataProvider =
+                new DefaultCornerDataProvider(
+                        columnHeaderDataProvider,
+                        rowHeaderDataProvider);
+        DataLayer cornerDataLayer =
+                new DataLayer(cornerDataProvider);
+        ILayer cornerLayer =
+                new CornerLayer(
+                        cornerDataLayer,
+                        rowHeaderLayer,
+                        filterRowHeaderLayer);
+
+        // build the grid layer
+        GridLayer gridLayer =
+                new GridLayer(
+                        bodyLayerStack,
+                        filterRowHeaderLayer,
+                        rowHeaderLayer,
+                        cornerLayer);
+
+        // turn the auto configuration off as we want to add our header menu
+        // configuration
+        NatTable natTable = new NatTable(container, gridLayer, false);
+
+        // as the autoconfiguration of the NatTable is turned off, we have to
+        // add the DefaultNatTableStyleConfiguration and the ConfigRegistry
+        // manually
+        natTable.setConfigRegistry(configRegistry);
+        natTable.addConfiguration(new DefaultNatTableStyleConfiguration());
+
+        // edit configuration
+        natTable.addConfiguration(new EditConfiguration());
+
+        // header menu configuration
+        natTable.addConfiguration(new HeaderMenuConfiguration(natTable) {
+            @Override
+            protected PopupMenuBuilder createCornerMenu(NatTable natTable) {
+                return super.createCornerMenu(natTable)
+                        .withStateManagerMenuItemProvider();
+            }
+        });
+
+        // body menu configuration
+        natTable.addConfiguration(new BodyMenuConfiguration<PersonWithAddress>(
+                natTable,
+                bodyLayerStack,
+                rowIdAccessor,
+                filterStrategy,
+                filterRowHeaderLayer.getFilterRowDataLayer().getFilterRowDataProvider()));
+
+        natTable.configure();
+
+        // The painter instances in a theme configuration are created on demand
+        // to avoid unnecessary painter instances in memory. To change the
+        // default filter row cell painter with the one for the excel like
+        // filter row, we get the painter from the ConfigRegistry after
+        // natTable#configure() and override createPainterInstances() of the
+        // theme configuration.
+        ModernNatTableThemeConfiguration themeConfiguration = new ModernNatTableThemeConfiguration() {
+            @Override
+            public void createPainterInstances() {
+                super.createPainterInstances();
+                this.filterRowCellPainter = configRegistry.getConfigAttribute(
+                        CellConfigAttributes.CELL_PAINTER,
+                        DisplayMode.NORMAL,
+                        GridRegion.FILTER_ROW);
+            }
+        };
+
+        // configure the filter exclude support
+        configureFilterExcludes(rowIdAccessor, filterStrategy, bodyLayerStack, themeConfiguration);
+
+        natTable.setTheme(themeConfiguration);
+
+        natTable.registerCommandHandler(
+                new DisplayPersistenceDialogCommandHandler(natTable));
+
+        GridDataFactory.fillDefaults().grab(true, true).applyTo(natTable);
+
+        return container;
+    }
+
+    /**
+     * This method is used to configure the filter exclude support. This means
+     * it creates and applies an exclude {@link Matcher}, configures an
+     * {@link IConfigLabelAccumulator} and registers the styling via
+     * {@link IThemeExtension}.
+     *
+     * @param rowIdAccessor
+     * @param filterStrategy
+     * @param bodyLayerStack
+     * @param themeConfiguration
+     */
+    private void configureFilterExcludes(
+            IRowIdAccessor<PersonWithAddress> rowIdAccessor,
+            ComboBoxGlazedListsWithExcludeFilterStrategy<PersonWithAddress> filterStrategy,
+            BodyLayerStack<PersonWithAddress> bodyLayerStack,
+            ThemeConfiguration themeConfiguration) {
+
+        // register the Matcher to the
+        // ComboBoxGlazedListsWithExcludeFilterStrategy
+        Matcher<PersonWithAddress> idMatcher = new Matcher<PersonWithAddress>() {
+            @Override
+            public boolean matches(PersonWithAddress item) {
+                return _6037_MixedFilterRowExample.this.filterExcludes.contains(rowIdAccessor.getRowId(item));
+            }
+        };
+        filterStrategy.addExcludeFilter(idMatcher);
+
+        // register the IConfigLabelAccumulator to the body DataLayer
+        bodyLayerStack.getBodyDataLayer().setConfigLabelAccumulator(new IConfigLabelAccumulator() {
+
+            @Override
+            public void accumulateConfigLabels(LabelStack configLabels, int columnPosition, int rowPosition) {
+                if (idMatcher.matches(bodyLayerStack.getBodyDataProvider().getRowObject(rowPosition))) {
+                    configLabels.add(EXCLUDE_LABEL);
+                }
+            }
+        });
+
+        // extend the ThemeConfiguration to add styling for the EXCLUDE_LABEL
+        themeConfiguration.addThemeExtension(new IThemeExtension() {
+
+            @Override
+            public void unregisterStyles(IConfigRegistry configRegistry) {
+                configRegistry.unregisterConfigAttribute(
+                        CellConfigAttributes.CELL_STYLE,
+                        DisplayMode.NORMAL,
+                        EXCLUDE_LABEL);
+            }
+
+            @Override
+            public void registerStyles(IConfigRegistry configRegistry) {
+                Style style = new Style();
+                style.setAttributeValue(CellStyleAttributes.BACKGROUND_COLOR, GUIHelper.COLOR_WIDGET_LIGHT_SHADOW);
+                configRegistry.registerConfigAttribute(
+                        CellConfigAttributes.CELL_STYLE,
+                        style,
+                        DisplayMode.NORMAL,
+                        EXCLUDE_LABEL);
+            }
+        });
+    }
+
+    /**
+     * Always encapsulate the body layer stack in an AbstractLayerTransform to
+     * ensure that the index transformations are performed in later commands.
+     *
+     * @param <T>
+     */
+    class BodyLayerStack<T> extends AbstractLayerTransform {
+
+        private final SortedList<T> sortedList;
+        private final FilterList<T> filterList;
+
+        private final ListDataProvider<T> bodyDataProvider;
+        private final DataLayer bodyDataLayer;
+        private final GlazedListsEventLayer<T> glazedListsEventLayer;
+
+        private final SelectionLayer selectionLayer;
+
+        public BodyLayerStack(List<T> values, IColumnPropertyAccessor<T> columnPropertyAccessor) {
+            // wrapping of the list to show into GlazedLists
+            // see http://publicobject.com/glazedlists/ for further information
+            EventList<T> eventList = GlazedLists.eventList(values);
+            TransformedList<T, T> rowObjectsGlazedList = GlazedLists.threadSafeList(eventList);
+
+            // use the SortedList constructor with 'null' for the Comparator
+            // because the Comparator will be set by configuration
+            this.sortedList = new SortedList<>(rowObjectsGlazedList, null);
+            // wrap the SortedList with the FilterList
+            this.filterList = new FilterList<>(this.sortedList);
+
+            this.bodyDataProvider =
+                    new ListDataProvider<>(this.filterList, columnPropertyAccessor);
+            this.bodyDataLayer = new DataLayer(getBodyDataProvider());
+            this.bodyDataLayer.setConfigLabelAccumulator(new ColumnLabelAccumulator());
+
+            // layer for event handling of GlazedLists and PropertyChanges
+            this.glazedListsEventLayer =
+                    new GlazedListsEventLayer<>(this.bodyDataLayer, this.filterList);
+
+            this.selectionLayer = new SelectionLayer(getGlazedListsEventLayer());
+            ViewportLayer viewportLayer = new ViewportLayer(getSelectionLayer());
+
+            FreezeLayer freezeLayer = new FreezeLayer(this.selectionLayer);
+            CompositeFreezeLayer compositeFreezeLayer =
+                    new CompositeFreezeLayer(freezeLayer, viewportLayer, this.selectionLayer);
+
+            setUnderlyingLayer(compositeFreezeLayer);
+        }
+
+        public SelectionLayer getSelectionLayer() {
+            return this.selectionLayer;
+        }
+
+        public SortedList<T> getSortedList() {
+            return this.sortedList;
+        }
+
+        public FilterList<T> getFilterList() {
+            return this.filterList;
+        }
+
+        public ListDataProvider<T> getBodyDataProvider() {
+            return this.bodyDataProvider;
+        }
+
+        public DataLayer getBodyDataLayer() {
+            return this.bodyDataLayer;
+        }
+
+        public GlazedListsEventLayer<T> getGlazedListsEventLayer() {
+            return this.glazedListsEventLayer;
+        }
+    }
+
+    /**
+     * Specialized {@link ComboBoxGlazedListsFilterStrategy} that can be used to
+     * exclude items from filtering. This means you can register a
+     * {@link Matcher} that avoids that matching items get filtered by the
+     * filterrow.
+     *
+     * @param <T>
+     */
+    class ComboBoxGlazedListsWithExcludeFilterStrategy<T> extends ComboBoxGlazedListsFilterStrategy<T> {
+
+        private final CompositeMatcherEditor<T> compositeMatcherEditor;
+
+        protected Map<Matcher<T>, MatcherEditor<T>> excludeMatcherEditor = new HashMap<>();
+
+        public ComboBoxGlazedListsWithExcludeFilterStrategy(
+                FilterRowComboBoxDataProvider<T> comboBoxDataProvider,
+                FilterList<T> filterList,
+                IColumnAccessor<T> columnAccessor,
+                IConfigRegistry configRegistry) {
+            super(comboBoxDataProvider, filterList, columnAccessor, configRegistry);
+
+            // The default MatcherEditor is created and stored as member in the
+            // DefaultGlazedListsFilterStrategy. That MatcherEditor is used for
+            // the default filter operations. To exclude entries from filtering,
+            // we create another CompositeMatcherEditor with an OR mode and set
+            // that one on the FilterList.
+            this.compositeMatcherEditor = new CompositeMatcherEditor<>();
+            this.compositeMatcherEditor.setMode(CompositeMatcherEditor.OR);
+
+            this.compositeMatcherEditor.getMatcherEditors().add(getMatcherEditor());
+
+            this.filterList.setMatcherEditor(this.compositeMatcherEditor);
+        }
+
+        /**
+         * Add a exclude filter to this filter strategy which will always be
+         * applied additionally to any other filter to exclude from filtering.
+         *
+         * @param matcher
+         *            the exclude filter to add
+         */
+        public void addExcludeFilter(final Matcher<T> matcher) {
+            // create a new MatcherEditor
+            MatcherEditor<T> matcherEditor = GlazedLists.fixedMatcherEditor(matcher);
+            addExcludeFilter(matcherEditor);
+        }
+
+        /**
+         * Add a exclude filter to this filter strategy which will always be
+         * applied additionally to any other filter to exclude items from
+         * filtering.
+         *
+         * @param matcherEditor
+         *            the exclude filter to add
+         */
+        public void addExcludeFilter(final MatcherEditor<T> matcherEditor) {
+            // add the new MatcherEditor to the CompositeMatcherEditor
+            this.filterLock.writeLock().lock();
+            try {
+                this.compositeMatcherEditor.getMatcherEditors().add(matcherEditor);
+            } finally {
+                this.filterLock.writeLock().unlock();
+            }
+
+            this.excludeMatcherEditor.put(matcherEditor.getMatcher(), matcherEditor);
+        }
+
+        /**
+         * Remove the exclude filter from this filter strategy.
+         *
+         * @param matcher
+         *            the filter to remove
+         */
+        public void removeExcludeFilter(final Matcher<T> matcher) {
+            MatcherEditor<T> removed = this.excludeMatcherEditor.remove(matcher);
+            if (removed != null) {
+                this.filterLock.writeLock().lock();
+                try {
+                    this.compositeMatcherEditor.getMatcherEditors().remove(removed);
+                } finally {
+                    this.filterLock.writeLock().unlock();
+                }
+            }
+        }
+
+        /**
+         * Remove the exclude filter from this filter strategy.
+         *
+         * @param matcherEditor
+         *            the filter to remove
+         */
+        public void removeExcludeFilter(final MatcherEditor<T> matcherEditor) {
+            removeExcludeFilter(matcherEditor.getMatcher());
+        }
+
+        /**
+         * Removes all applied exclude filters from this filter strategy.
+         */
+        public void clearExcludeFilter() {
+            Collection<MatcherEditor<T>> excludeMatcher = this.excludeMatcherEditor.values();
+            if (!excludeMatcher.isEmpty()) {
+                this.filterLock.writeLock().lock();
+                try {
+                    this.compositeMatcherEditor.getMatcherEditors().removeAll(excludeMatcher);
+                } finally {
+                    this.filterLock.writeLock().unlock();
+                }
+                this.excludeMatcherEditor.clear();
+            }
+        }
+    }
+
+    /**
+     * The configuration to enable editing of {@link PersonWithAddress} objects.
+     */
+    class EditConfiguration extends AbstractRegistryConfiguration {
+
+        @Override
+        public void configureRegistry(IConfigRegistry configRegistry) {
+            configRegistry.registerConfigAttribute(
+                    EditConfigAttributes.CELL_EDITABLE_RULE,
+                    IEditableRule.ALWAYS_EDITABLE);
+
+            configRegistry.registerConfigAttribute(
+                    CellConfigAttributes.DISPLAY_CONVERTER,
+                    new DefaultIntegerDisplayConverter(),
+                    DisplayMode.NORMAL,
+                    ColumnLabelAccumulator.COLUMN_LABEL_PREFIX
+                            + DataModelConstants.HOUSENUMBER_COLUMN_POSITION);
+
+            configRegistry.registerConfigAttribute(
+                    CellConfigAttributes.DISPLAY_CONVERTER,
+                    new DisplayConverter() {
+
+                        @Override
+                        public Object canonicalToDisplayValue(Object canonicalValue) {
+                            return (canonicalValue != null) ? canonicalValue.toString() : "";
+                        }
+
+                        @Override
+                        public Object displayToCanonicalValue(Object displayValue) {
+                            try {
+                                return Gender.valueOf(displayValue.toString());
+                            } catch (IllegalArgumentException e) {
+                                return null;
+                            }
+                        }
+
+                    },
+                    DisplayMode.NORMAL,
+                    ColumnLabelAccumulator.COLUMN_LABEL_PREFIX
+                            + DataModelConstants.GENDER_COLUMN_POSITION);
+        }
+    }
+
+    /**
+     * The configuration to specialize the combobox filter row to mix it with
+     * default filters like free text.
+     */
+    class FilterRowConfiguration extends AbstractRegistryConfiguration {
+
+        @Override
+        public void configureRegistry(IConfigRegistry configRegistry) {
+
+            // #####
+            // Free Edit Text Filter for Firstname column that supports
+            // wildcards
+            // #####
+
+            // register the FilterRowTextCellEditor in the first column which
+            // immediately commits on key press
+            FilterRowTextCellEditor editor = new FilterRowTextCellEditor();
+            SimpleContentProposalProvider contentProposalProvider =
+                    new SimpleContentProposalProvider(
+                            CustomFilterRowRegularExpressionConverter.EMPTY_LITERAL,
+                            CustomFilterRowRegularExpressionConverter.NOT_EMPTY_LITERAL);
+            contentProposalProvider.setFiltering(true);
+            KeyStroke keystroke = null;
+            try {
+                keystroke = KeyStroke.getInstance("Ctrl+Space");
+            } catch (ParseException e) {
+                // should not happen as the string is correct
+            }
+            char[] autoActivationChars = ("<").toCharArray();
+
+            editor.enableContentProposal(
+                    new TextContentAdapter(),
+                    contentProposalProvider,
+                    keystroke,
+                    autoActivationChars);
+
+            configRegistry.registerConfigAttribute(
+                    EditConfigAttributes.CELL_EDITOR,
+                    editor,
+                    DisplayMode.NORMAL,
+                    FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX
+                            + DataModelConstants.FIRSTNAME_COLUMN_POSITION);
+
+            // register the FilterRowPainter in the first column to visualize
+            // the free edit filter field as the ComboBoxFilterRowConfiguration
+            // registers the ComboBoxFilterIconPainter as default
+            configRegistry.registerConfigAttribute(
+                    CellConfigAttributes.CELL_PAINTER,
+                    new PaddingDecorator(new FilterRowPainter(), 0, 0, 0, 5),
+                    DisplayMode.NORMAL,
+                    FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX
+                            + DataModelConstants.FIRSTNAME_COLUMN_POSITION);
+
+            // register the FilterRowRegularExpressionConverter in the first
+            // column that converts simple expressions like wildcards to valid
+            // regular expressions
+            configRegistry.registerConfigAttribute(
+                    CellConfigAttributes.DISPLAY_CONVERTER,
+                    new CustomFilterRowRegularExpressionConverter(),
+                    DisplayMode.NORMAL,
+                    FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX
+                            + DataModelConstants.FIRSTNAME_COLUMN_POSITION);
+
+            configRegistry.registerConfigAttribute(
+                    FilterRowConfigAttributes.FILTER_DISPLAY_CONVERTER,
+                    new DefaultDisplayConverter(),
+                    DisplayMode.NORMAL,
+                    FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX
+                            + DataModelConstants.FIRSTNAME_COLUMN_POSITION);
+
+            // #####
+            // Fixed value single-selection combobox filter for Gender column
+            // #####
+
+            // register a combo box cell editor for the gender column in the
+            // filter row the label is set automatically to the value of
+            // FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX + column
+            // position
+            ICellEditor comboBoxCellEditor = new ComboBoxCellEditor(Arrays.asList(
+                    CustomFilterRowRegularExpressionConverter.EMPTY_REGEX,
+                    CustomFilterRowRegularExpressionConverter.NOT_EMPTY_REGEX,
+                    Gender.FEMALE.toString(),
+                    Gender.MALE.toString()));
+            configRegistry.registerConfigAttribute(
+                    EditConfigAttributes.CELL_EDITOR,
+                    comboBoxCellEditor,
+                    DisplayMode.NORMAL,
+                    FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX
+                            + DataModelConstants.GENDER_COLUMN_POSITION);
+
+            configRegistry.registerConfigAttribute(
+                    CellConfigAttributes.CELL_PAINTER,
+                    new PaddingDecorator(new FilterRowPainter(), 0, 0, 0, 5),
+                    DisplayMode.NORMAL,
+                    FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX
+                            + DataModelConstants.GENDER_COLUMN_POSITION);
+
+            configRegistry.registerConfigAttribute(
+                    CellConfigAttributes.DISPLAY_CONVERTER,
+                    new CustomFilterRowRegularExpressionConverter(),
+                    DisplayMode.NORMAL,
+                    FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX
+                            + DataModelConstants.GENDER_COLUMN_POSITION);
+
+            // #####
+            // Free Edit Text Filter for Housenumber column that supports
+            // expressions like greater, lesser, equals
+            // #####
+
+            configRegistry.registerConfigAttribute(
+                    EditConfigAttributes.CELL_EDITOR,
+                    new FilterRowTextCellEditor(),
+                    DisplayMode.NORMAL,
+                    FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX
+                            + DataModelConstants.HOUSENUMBER_COLUMN_POSITION);
+
+            configRegistry.registerConfigAttribute(
+                    CellConfigAttributes.CELL_PAINTER,
+                    new PaddingDecorator(new FilterRowPainter(), 0, 0, 0, 5),
+                    DisplayMode.NORMAL,
+                    FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX
+                            + DataModelConstants.HOUSENUMBER_COLUMN_POSITION);
+
+            configRegistry.registerConfigAttribute(
+                    FilterRowConfigAttributes.FILTER_DISPLAY_CONVERTER,
+                    new DefaultIntegerDisplayConverter(),
+                    DisplayMode.NORMAL,
+                    FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX
+                            + DataModelConstants.HOUSENUMBER_COLUMN_POSITION);
+
+            // need to register the DefaultDisplayConverter for the housenumber
+            // column as we register a custom display converter to show a label
+            // for the empty entry in the multi-select combobox filter
+            configRegistry.registerConfigAttribute(
+                    CellConfigAttributes.DISPLAY_CONVERTER,
+                    new DefaultDisplayConverter(),
+                    DisplayMode.NORMAL,
+                    FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX
+                            + DataModelConstants.HOUSENUMBER_COLUMN_POSITION);
+
+            // register a display converter on the filter row in general that
+            // shows a value for an empty entry in the dropdown
+            configRegistry.registerConfigAttribute(
+                    CellConfigAttributes.DISPLAY_CONVERTER,
+                    new DefaultDisplayConverter() {
+                        @Override
+                        public Object canonicalToDisplayValue(Object sourceValue) {
+                            return (sourceValue != null && !sourceValue.toString().isEmpty())
+                                    ? sourceValue.toString()
+                                    : CustomFilterRowRegularExpressionConverter.EMPTY_LITERAL;
+                        }
+                    },
+                    DisplayMode.NORMAL,
+                    GridRegion.FILTER_ROW);
+        }
+    }
+
+    /**
+     * Specialization of the {@link FilterRowRegularExpressionConverter} that
+     * additionally parses special literals to corresponding regular
+     * expressions.
+     */
+    class CustomFilterRowRegularExpressionConverter extends FilterRowRegularExpressionConverter {
+
+        static final String EMPTY_LITERAL = "<empty>";
+        static final String EMPTY_REGEX = "^$";
+        static final String NOT_EMPTY_LITERAL = "<not_empty>";
+        static final String NOT_EMPTY_REGEX = "^(?!\\s*$).+";
+
+        @Override
+        public Object displayToCanonicalValue(Object displayValue) {
+            if (displayValue != null) {
+                switch (displayValue.toString()) {
+                    case EMPTY_LITERAL:
+                        return EMPTY_REGEX;
+                    case NOT_EMPTY_LITERAL:
+                        return NOT_EMPTY_REGEX;
+                    default:
+                        return super.displayToCanonicalValue(displayValue);
+                }
+            }
+            return displayValue;
+        }
+
+        @Override
+        public Object canonicalToDisplayValue(Object canonicalValue) {
+            if (canonicalValue != null) {
+                switch (canonicalValue.toString()) {
+                    case EMPTY_REGEX:
+                        return EMPTY_LITERAL;
+                    case NOT_EMPTY_REGEX:
+                        return NOT_EMPTY_LITERAL;
+                    default:
+                        return super.canonicalToDisplayValue(canonicalValue);
+                }
+            }
+            return canonicalValue;
+        }
+    }
+
+    /**
+     * Menu configuration that adds a menu on the body with menu items to
+     * exclude/include items from filtering.
+     *
+     * @param <T>
+     */
+    class BodyMenuConfiguration<T> extends AbstractUiBindingConfiguration {
+
+        private static final String EXCLUDE_MENU_ID = "exclude";
+        private static final String INCLUDE_MENU_ID = "include";
+
+        private final Menu bodyMenu;
+
+        private BodyMenuConfiguration(
+                NatTable natTable,
+                BodyLayerStack<T> bodyLayerStack,
+                IRowIdAccessor<T> rowIdAccessor,
+                IFilterStrategy<T> filterStrategy,
+                FilterRowDataProvider<T> filterRowDataProvider) {
+
+            this.bodyMenu = new PopupMenuBuilder(natTable)
+                    .withMenuItemProvider(EXCLUDE_MENU_ID, new IMenuItemProvider() {
+
+                        @Override
+                        public void addMenuItem(NatTable natTable, Menu popupMenu) {
+                            MenuItem excludeRow = new MenuItem(popupMenu, SWT.PUSH);
+                            excludeRow.setText("Exclude from filter");
+                            excludeRow.setEnabled(true);
+
+                            excludeRow.addSelectionListener(new SelectionAdapter() {
+                                @Override
+                                public void widgetSelected(SelectionEvent event) {
+                                    int rowPosition = MenuItemProviders.getNatEventData(event).getRowPosition();
+                                    int rowIndex = natTable.getRowIndexByPosition(rowPosition);
+                                    T rowObject = bodyLayerStack.getBodyDataProvider().getRowObject(rowIndex);
+                                    Serializable rowId = rowIdAccessor.getRowId(rowObject);
+                                    _6037_MixedFilterRowExample.this.filterExcludes.add(rowId);
+                                    natTable.refresh(false);
+                                }
+                            });
+                        }
+                    })
+                    .withVisibleState(EXCLUDE_MENU_ID, new IMenuItemState() {
+
+                        @Override
+                        public boolean isActive(NatEventData natEventData) {
+                            int rowPosition = natEventData.getRowPosition();
+                            int rowIndex = natTable.getRowIndexByPosition(rowPosition);
+                            T rowObject = bodyLayerStack.getBodyDataProvider().getRowObject(rowIndex);
+                            Serializable rowId = rowIdAccessor.getRowId(rowObject);
+                            return !_6037_MixedFilterRowExample.this.filterExcludes.contains(rowId);
+                        }
+                    })
+                    .withMenuItemProvider(INCLUDE_MENU_ID, new IMenuItemProvider() {
+
+                        @Override
+                        public void addMenuItem(NatTable natTable, Menu popupMenu) {
+                            MenuItem excludeRow = new MenuItem(popupMenu, SWT.PUSH);
+                            excludeRow.setText("Include to filter");
+                            excludeRow.setEnabled(true);
+
+                            excludeRow.addSelectionListener(new SelectionAdapter() {
+                                @Override
+                                public void widgetSelected(SelectionEvent event) {
+                                    int rowPosition = MenuItemProviders.getNatEventData(event).getRowPosition();
+                                    int rowIndex = natTable.getRowIndexByPosition(rowPosition);
+                                    T rowObject = bodyLayerStack.getBodyDataProvider().getRowObject(rowIndex);
+                                    Serializable rowId = rowIdAccessor.getRowId(rowObject);
+                                    _6037_MixedFilterRowExample.this.filterExcludes.remove(rowId);
+                                    filterStrategy.applyFilter(filterRowDataProvider.getFilterIndexToObjectMap());
+                                }
+                            });
+                        }
+                    })
+                    .withVisibleState(INCLUDE_MENU_ID, new IMenuItemState() {
+
+                        @Override
+                        public boolean isActive(NatEventData natEventData) {
+                            int rowPosition = natEventData.getRowPosition();
+                            int rowIndex = natTable.getRowIndexByPosition(rowPosition);
+                            T rowObject = bodyLayerStack.getBodyDataProvider().getRowObject(rowIndex);
+                            Serializable rowId = rowIdAccessor.getRowId(rowObject);
+                            return _6037_MixedFilterRowExample.this.filterExcludes.contains(rowId);
+                        }
+                    })
+                    .build();
+        }
+
+        @Override
+        public void configureUiBindings(UiBindingRegistry uiBindingRegistry) {
+            uiBindingRegistry.registerMouseDownBinding(
+                    new MouseEventMatcher(
+                            SWT.NONE,
+                            GridRegion.BODY,
+                            MouseEventMatcher.RIGHT_BUTTON),
+                    new PopupMenuAction(this.bodyMenu));
+        }
+    }
+}
\ No newline at end of file
diff --git a/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/ComboBoxFilterRowHeaderComposite.java b/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/ComboBoxFilterRowHeaderComposite.java
index fb3393b..b7d333c 100644
--- a/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/ComboBoxFilterRowHeaderComposite.java
+++ b/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/ComboBoxFilterRowHeaderComposite.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2013, 2020 Dirk Fauth and others.
+ * Copyright (c) 2013, 2022 Dirk Fauth and others.
  *
  * This program and the accompanying materials are made
  * available under the terms of the Eclipse Public License 2.0
@@ -23,10 +23,13 @@
 import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
 import org.eclipse.nebula.widgets.nattable.data.IColumnAccessor;
 import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
+import org.eclipse.nebula.widgets.nattable.edit.EditConfigAttributes;
 import org.eclipse.nebula.widgets.nattable.edit.EditConstants;
+import org.eclipse.nebula.widgets.nattable.edit.editor.ICellEditor;
 import org.eclipse.nebula.widgets.nattable.filterrow.FilterRowDataLayer;
 import org.eclipse.nebula.widgets.nattable.filterrow.FilterRowDataProvider;
 import org.eclipse.nebula.widgets.nattable.filterrow.combobox.ComboBoxFilterRowConfiguration;
+import org.eclipse.nebula.widgets.nattable.filterrow.combobox.FilterRowComboBoxCellEditor;
 import org.eclipse.nebula.widgets.nattable.filterrow.combobox.FilterRowComboBoxDataProvider;
 import org.eclipse.nebula.widgets.nattable.filterrow.combobox.FilterRowComboUpdateEvent;
 import org.eclipse.nebula.widgets.nattable.filterrow.combobox.IFilterRowComboUpdateListener;
@@ -37,6 +40,7 @@
 import org.eclipse.nebula.widgets.nattable.layer.CompositeLayer;
 import org.eclipse.nebula.widgets.nattable.layer.ILayer;
 import org.eclipse.nebula.widgets.nattable.layer.event.RowStructuralRefreshEvent;
+import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
 
 import ca.odell.glazedlists.FilterList;
 import ca.odell.glazedlists.matchers.CompositeMatcherEditor;
@@ -73,6 +77,12 @@ public class ComboBoxFilterRowHeaderComposite<T> extends CompositeLayer implemen
     protected final ComboBoxGlazedListsFilterStrategy<T> filterStrategy;
 
     /**
+     * The {@link IConfigRegistry} needed to retrieve various configurations,
+     * e.g. the filter cell editor per column.
+     */
+    private IConfigRegistry configRegistry;
+
+    /**
      * Creates a new ComboBoxFilterRowHeaderComposite based on the given
      * informations. Using this constructor will create the
      * FilterRowComboBoxDataProvider needed for filtering and add the default
@@ -602,6 +612,8 @@ public ComboBoxFilterRowHeaderComposite(
                         columnHeaderDataProvider,
                         configRegistry);
 
+        this.configRegistry = configRegistry;
+
         setAllValuesSelected();
 
         setChildLayer(GridRegion.FILTER_ROW, this.filterRowDataLayer, 0, 1);
@@ -696,6 +708,39 @@ public void setAllValuesSelected() {
     }
 
     @Override
+    public Object getDataValueByPosition(int compositeColumnPosition, int compositeRowPosition) {
+        Object filterValue = super.getDataValueByPosition(compositeColumnPosition, compositeRowPosition);
+
+        // The SELECT_ALL_ITEMS_VALUE is set to the FilterRowDataProvider by
+        // setAllValueSelected(). It is actually ignored in the
+        // ComboBoxGlazedListsFilterStrategy as it technically means "no filter"
+        // and results in having all entries checked in the
+        // FilterRowComboBoxCellEditor. For other filter editors this
+        // replacement is not done and causes incorrect visualization as it
+        // shows the value and implies an active filter. As the
+        // SELECT_ALL_ITEMS_VALUE is propagated in the constructor at a time
+        // where the editors are not yet configured, we remove the value
+        // reactively on accessing it for a consistent view.
+
+        if (compositeRowPosition == 1
+                && !isFilterRowComboBoxCellEditor(compositeColumnPosition)
+                && EditConstants.SELECT_ALL_ITEMS_VALUE.equals(filterValue)) {
+            this.filterRowDataLayer.getFilterRowDataProvider().getFilterIndexToObjectMap().remove(compositeColumnPosition);
+            filterValue = null;
+        }
+
+        return filterValue;
+    }
+
+    private boolean isFilterRowComboBoxCellEditor(int column) {
+        ICellEditor cellEditor = this.configRegistry.getConfigAttribute(
+                EditConfigAttributes.CELL_EDITOR,
+                DisplayMode.NORMAL,
+                this.filterRowDataLayer.getConfigLabelsByPosition(column, 0));
+        return (cellEditor instanceof FilterRowComboBoxCellEditor);
+    }
+
+    @Override
     public int getHeight() {
         if (this.filterRowVisible) {
             return super.getHeight();
@@ -726,11 +771,13 @@ public boolean doCommand(ILayerCommand command) {
         else if (command instanceof ClearFilterCommand
                 && command.convertToTargetLayer(this)) {
             int columnPosition = ((ClearFilterCommand) command).getColumnPosition();
-            this.filterRowDataLayer.setDataValueByPosition(
-                    columnPosition,
-                    0,
-                    getComboBoxDataProvider().getValues(columnPosition, 0));
-            handled = true;
+            if (isFilterRowComboBoxCellEditor(columnPosition)) {
+                this.filterRowDataLayer.setDataValueByPosition(
+                        columnPosition,
+                        0,
+                        getComboBoxDataProvider().getValues(columnPosition, 0));
+                handled = true;
+            }
         } else if (command instanceof ClearAllFiltersCommand) {
             setAllValuesSelected();
             handled = true;
diff --git a/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/ComboBoxGlazedListsFilterStrategy.java b/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/ComboBoxGlazedListsFilterStrategy.java
index 59eb93c..1e87c88 100644
--- a/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/ComboBoxGlazedListsFilterStrategy.java
+++ b/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/ComboBoxGlazedListsFilterStrategy.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2013, 2020 Dirk Fauth and others.
+ * Copyright (c) 2013, 2022 Dirk Fauth and others.
  *
  * This program and the accompanying materials are made
  * available under the terms of the Eclipse Public License 2.0
@@ -28,8 +28,14 @@
 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.EditConfigAttributes;
 import org.eclipse.nebula.widgets.nattable.edit.EditConstants;
+import org.eclipse.nebula.widgets.nattable.edit.editor.ICellEditor;
+import org.eclipse.nebula.widgets.nattable.filterrow.FilterRowDataLayer;
+import org.eclipse.nebula.widgets.nattable.filterrow.combobox.FilterRowComboBoxCellEditor;
 import org.eclipse.nebula.widgets.nattable.filterrow.combobox.FilterRowComboBoxDataProvider;
+import org.eclipse.nebula.widgets.nattable.grid.GridRegion;
+import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
 
 import ca.odell.glazedlists.FilterList;
 import ca.odell.glazedlists.GlazedLists;
@@ -141,9 +147,19 @@ public void applyFilter(Map<Integer, Object> filterIndexToObjectMap) {
         for (Iterator<Map.Entry<Integer, Object>> it = newIndexToObjectMap.entrySet().iterator(); it.hasNext();) {
             Entry<Integer, Object> entry = it.next();
             Object filterObject = entry.getValue();
+
+            // Check if the filter editor is the combobox and only handle the
+            // collection case with that type of editor. Note that ignoring the
+            // SELECT_ALL_ITEMS_VALUE is needed in any case.
+            ICellEditor cellEditor = this.configRegistry.getConfigAttribute(
+                    EditConfigAttributes.CELL_EDITOR,
+                    DisplayMode.NORMAL,
+                    FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX + entry.getKey(), GridRegion.FILTER_ROW);
+            boolean isCombo = (cellEditor instanceof FilterRowComboBoxCellEditor);
+
             if (EditConstants.SELECT_ALL_ITEMS_VALUE.equals(filterObject)) {
                 it.remove();
-            } else {
+            } else if (isCombo) {
                 List<?> dataProviderList = this.comboBoxDataProvider.getValues(entry.getKey(), 0);
 
                 // selecting all is transported as String to support lazy
diff --git a/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/DefaultGlazedListsFilterStrategy.java b/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/DefaultGlazedListsFilterStrategy.java
index 848b249..e2e0897 100644
--- a/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/DefaultGlazedListsFilterStrategy.java
+++ b/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/DefaultGlazedListsFilterStrategy.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2012, 2020 Original authors and others.
+ * Copyright (c) 2012, 2022 Original authors and others.
  *
  * This program and the accompanying materials are made
  * available under the terms of the Eclipse Public License 2.0
@@ -500,7 +500,8 @@ protected boolean matcherEditorEqual(final MatcherEditor<T> first, final Matcher
                 ThresholdMatcherEditor<?, ?> firstThreshold = (ThresholdMatcherEditor<?, ?>) first;
                 ThresholdMatcherEditor<?, ?> secondThreshold = (ThresholdMatcherEditor<?, ?>) second;
 
-                result = firstThreshold.getThreshold().equals(secondThreshold.getThreshold())
+                result = (firstThreshold.getThreshold() != null && secondThreshold.getThreshold() != null)
+                        && firstThreshold.getThreshold().equals(secondThreshold.getThreshold())
                         && firstThreshold.getComparator().equals(secondThreshold.getComparator())
                         // MatchOperation is not visible and must be a
                         // references instance, so the 'equals' is not needed