blob: 6c5293e04672999041b76d6d55b7c583b7c92e9a [file] [log] [blame]
/*******************************************************************************
* 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.edit.editor.TextCellEditor;
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.TextMatchingMode;
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.layer.cell.ILayerCell;
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<>();
private boolean regexFilterActive = false;
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)));
// 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) {
private static final String ACTIVATE_REGEX_MENU_ID = "ACTIVATE_REGEX";
private static final String DEACTIVATE_REGEX_MENU_ID = "DEACTIVATE_REGEX";
@Override
protected PopupMenuBuilder createCornerMenu(NatTable natTable) {
return super.createCornerMenu(natTable)
.withStateManagerMenuItemProvider();
}
@Override
protected PopupMenuBuilder createColumnHeaderMenu(NatTable natTable) {
return super.createColumnHeaderMenu(natTable)
.withMenuItemProvider(ACTIVATE_REGEX_MENU_ID, new IMenuItemProvider() {
@Override
public void addMenuItem(NatTable natTable, Menu popupMenu) {
MenuItem excludeRow = new MenuItem(popupMenu, SWT.PUSH);
excludeRow.setText("Activate RegEx filter");
excludeRow.setEnabled(true);
excludeRow.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
_6037_MixedFilterRowExample.this.regexFilterActive = true;
natTable.getConfigRegistry().registerConfigAttribute(
FilterRowConfigAttributes.TEXT_MATCHING_MODE,
TextMatchingMode.REGULAR_EXPRESSION,
DisplayMode.NORMAL,
FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX + 0);
}
});
}
})
.withVisibleState(ACTIVATE_REGEX_MENU_ID, new IMenuItemState() {
@Override
public boolean isActive(NatEventData natEventData) {
int columnPosition = natEventData.getColumnPosition();
int columnIndex = natTable.getColumnIndexByPosition(columnPosition);
return columnIndex == 0 && !_6037_MixedFilterRowExample.this.regexFilterActive;
}
})
.withMenuItemProvider(DEACTIVATE_REGEX_MENU_ID, new IMenuItemProvider() {
@Override
public void addMenuItem(NatTable natTable, Menu popupMenu) {
MenuItem excludeRow = new MenuItem(popupMenu, SWT.PUSH);
excludeRow.setText("Deactivate RegEx filter");
excludeRow.setEnabled(true);
excludeRow.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
_6037_MixedFilterRowExample.this.regexFilterActive = false;
}
});
}
})
.withVisibleState(DEACTIVATE_REGEX_MENU_ID, new IMenuItemState() {
@Override
public boolean isActive(NatEventData natEventData) {
int columnPosition = natEventData.getColumnPosition();
int columnIndex = natTable.getColumnIndexByPosition(columnPosition);
return columnIndex == 0 && _6037_MixedFilterRowExample.this.regexFilterActive;
}
});
};
});
// 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(configRegistry),
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(configRegistry),
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 TextCellEditor(),
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);
configRegistry.registerConfigAttribute(
FilterRowConfigAttributes.TEXT_DELIMITER, "[&\\|]"); //$NON-NLS-1$
}
}
/**
* 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*$).+";
private IConfigRegistry configRegistry;
public CustomFilterRowRegularExpressionConverter(IConfigRegistry configRegistry) {
this.configRegistry = configRegistry;
}
@Override
public Object displayToCanonicalValue(ILayerCell cell, IConfigRegistry configRegistry, Object displayValue) {
if (displayValue != null) {
switch (displayValue.toString()) {
case EMPTY_LITERAL:
this.configRegistry.registerConfigAttribute(
FilterRowConfigAttributes.TEXT_MATCHING_MODE,
TextMatchingMode.REGULAR_EXPRESSION,
DisplayMode.NORMAL,
FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX + cell.getColumnIndex());
return EMPTY_REGEX;
case NOT_EMPTY_LITERAL:
this.configRegistry.registerConfigAttribute(
FilterRowConfigAttributes.TEXT_MATCHING_MODE,
TextMatchingMode.REGULAR_EXPRESSION,
DisplayMode.NORMAL,
FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX + cell.getColumnIndex());
return NOT_EMPTY_REGEX;
default:
if (!_6037_MixedFilterRowExample.this.regexFilterActive) {
// only switch to CONTAINS if RegEx filtering is not
// activated
this.configRegistry.registerConfigAttribute(
FilterRowConfigAttributes.TEXT_MATCHING_MODE,
TextMatchingMode.CONTAINS,
DisplayMode.NORMAL,
FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX + cell.getColumnIndex());
}
return super.displayToCanonicalValue(displayValue);
}
}
return displayValue;
}
@Override
public Object canonicalToDisplayValue(ILayerCell cell, IConfigRegistry configRegistry, 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));
}
}
}