blob: a4f2e49eb5b2b1639fa5293ed8dd1f10cd879372 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2014, 2020 Dirk Fauth and others.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Dirk Fauth <dirk.fauth@googlemail.com> - initial API and implementation
* Dirk Fauth <dirk.fauth@googlemail.com> - Bug 462367
******************************************************************************/
package org.eclipse.nebula.widgets.nattable.extension.glazedlists.groupBy;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes;
import org.eclipse.nebula.widgets.nattable.config.ConfigRegistry;
import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
import org.eclipse.nebula.widgets.nattable.data.convert.ContextualDisplayConverter;
import org.eclipse.nebula.widgets.nattable.data.convert.DefaultDisplayConverter;
import org.eclipse.nebula.widgets.nattable.data.convert.IDisplayConverter;
import org.eclipse.nebula.widgets.nattable.layer.LabelStack;
import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
import org.eclipse.nebula.widgets.nattable.summaryrow.ISummaryProvider;
import org.eclipse.nebula.widgets.nattable.tree.TreeLayer;
/**
* {@link IDisplayConverter} that is used for conversion of
* {@link GroupByObject}s.
* <p>
* Should be registered for the label {@link GroupByDataLayer#GROUP_BY_OBJECT}.
* </p>
* <p>
* It only returns a converted value in case of the tree column or a groupBy
* summary is configured. For this it searches for an underlying
* {@link IDisplayConverter} based on the GroupBy value or the summary column.
* </p>
*
* @param <T>
* the type of the underlying data in the {@link GroupByDataLayer}
*
* @see GroupByDataLayer
* @see GroupByDataLayerConfiguration
*/
public class GroupByDisplayConverter<T> extends ContextualDisplayConverter {
private Object defaultSummaryValue = ISummaryProvider.DEFAULT_SUMMARY_VALUE;
protected final Map<Integer, IDisplayConverter> wrappedConverters = new HashMap<Integer, IDisplayConverter>();
protected final Map<Integer, IDisplayConverter> converterCache = new HashMap<Integer, IDisplayConverter>();
protected final GroupByDataLayer<T> groupByDataLayer;
/**
*
* @param groupByDataLayer
* The {@link GroupByDataLayer} necessary to perform index based
* or content based operations.
*/
public GroupByDisplayConverter(GroupByDataLayer<T> groupByDataLayer) {
this.groupByDataLayer = groupByDataLayer;
}
/**
*
* @param groupByDataLayer
* The {@link GroupByDataLayer} necessary to perform index based
* or content based operations.
* @param defaultSummaryValue
* The value that will be shown in case the summary value is not
* calculated yet.
*/
public GroupByDisplayConverter(GroupByDataLayer<T> groupByDataLayer, Object defaultSummaryValue) {
this.groupByDataLayer = groupByDataLayer;
this.defaultSummaryValue = defaultSummaryValue;
}
@Override
public Object canonicalToDisplayValue(ILayerCell cell, IConfigRegistry configRegistry, Object canonicalValue) {
// if the cell is a tree column cell return the data value
if (cell.getConfigLabels().hasLabel(TreeLayer.TREE_COLUMN_CELL)) {
Object displayValue = getDisplayValue(cell, configRegistry, canonicalValue);
// handle child count pattern
if (canonicalValue instanceof GroupByObject) {
GroupByObject groupByObject = (GroupByObject) canonicalValue;
String childCountPattern = configRegistry.getConfigAttribute(
GroupByConfigAttributes.GROUP_BY_CHILD_COUNT_PATTERN,
DisplayMode.NORMAL,
cell.getConfigLabels());
if (childCountPattern != null && childCountPattern.length() > 0) {
List<T> children = this.groupByDataLayer.getItemsInGroup(groupByObject);
int directChildCount = this.groupByDataLayer.getTreeRowModel().getDirectChildren(cell.getRowIndex()).size();
displayValue = String.valueOf(displayValue)
+ " " //$NON-NLS-1$
+ MessageFormat.format(childCountPattern, children.size(), directChildCount);
}
}
return displayValue;
} else if (cell.getConfigLabels().hasLabel(GroupByDataLayer.GROUP_BY_SUMMARY)) {
if (canonicalValue == null) {
return this.defaultSummaryValue;
}
return getDisplayValue(cell, configRegistry, canonicalValue);
} else {
return ""; //$NON-NLS-1$
}
}
@Override
public Object displayToCanonicalValue(ILayerCell cell, IConfigRegistry configRegistry, Object displayValue) {
// this method is called by editing features
// since the default implementation doesn't support editing of
// GroupByValues, this method simply returns the display value
return displayValue;
}
/**
* Searches for the {@link IDisplayConverter} to use for the given
* {@link ILayerCell} and converts the given canonical value to the display
* value using the found converter.
*
* @param cell
* The {@link ILayerCell} for which the display value is asked.
* @param configRegistry
* The {@link ConfigRegistry} necessary to retrieve the
* {@link IDisplayConverter}.
* @param canonicalValue
* The canonical value to convert.
* @return The canonical value converted to the display value.
*/
protected Object getDisplayValue(ILayerCell cell, IConfigRegistry configRegistry, Object canonicalValue) {
IDisplayConverter converter = null;
Object canonical = canonicalValue;
if (this.wrappedConverters.containsKey(cell.getColumnIndex())) {
converter = this.wrappedConverters.get(cell.getColumnIndex());
} else if (canonicalValue instanceof GroupByObject) {
GroupByObject groupByObject = (GroupByObject) canonicalValue;
int lastGroupingIndex = -1;
for (Map.Entry<Integer, Object> groupEntry : groupByObject.getDescriptor().entrySet()) {
lastGroupingIndex = groupEntry.getKey();
}
if (lastGroupingIndex >= 0) {
canonical = ((GroupByObject) canonicalValue).getValue();
// check if we already have a converter for that index in the
// cache
if (this.converterCache.containsKey(lastGroupingIndex)) {
converter = this.converterCache.get(lastGroupingIndex);
} else {
int rowPosition = cell.getRowPosition() + 1;
LabelStack stackBelow = this.groupByDataLayer.getConfigLabelsByPosition(lastGroupingIndex, rowPosition);
while (stackBelow.hasLabel(GroupByDataLayer.GROUP_BY_OBJECT)) {
stackBelow = this.groupByDataLayer.getConfigLabelsByPosition(lastGroupingIndex, ++rowPosition);
}
converter = configRegistry.getConfigAttribute(
CellConfigAttributes.DISPLAY_CONVERTER,
DisplayMode.NORMAL,
stackBelow);
// this way we are caching the found converters to avoid
// performance issues on searching for the correct one
// Note:
// Doing this avoids the possibility to change the converter
// at runtime, which is a rather uncommon scenario.
// In case the exchanging the converter at runtime is
// necessary you need to unregister any cached converter in
// this converter additionally
if (!this.converterCache.containsKey(lastGroupingIndex)) {
this.converterCache.put(lastGroupingIndex, converter);
}
}
}
} else {
// create a copy of the label stack to avoid finding this
// converter again
// Note: this displayConverter needs to be registered for the
// GroupByDataLayer.GROUP_BY_OBJECT label
List<String> labels = new ArrayList<>(cell.getConfigLabels());
labels.remove(GroupByDataLayer.GROUP_BY_OBJECT);
converter = configRegistry.getConfigAttribute(
CellConfigAttributes.DISPLAY_CONVERTER,
DisplayMode.NORMAL,
labels);
if (converter == this) {
// we found ourself again, so let's skip this
converter = null;
}
}
if (converter == null) {
converter = new DefaultDisplayConverter();
}
return converter.canonicalToDisplayValue(cell, configRegistry, canonical);
}
/**
* Registers a given {@link IDisplayConverter} for a column index. Only
* necessary to override the searching for a {@link IDisplayConverter} that
* is registered for the column of the grouping.
*
* @param columnIndex
* The column index for which the groupBy display converter
* should be overriden.
* @param converter
* The {@link IDisplayConverter} that should be used for
* converting the groupBy value of the given index.
*/
public void registerUnderlyingDisplayConverter(int columnIndex, IDisplayConverter converter) {
this.wrappedConverters.put(columnIndex, converter);
}
/**
* Unregister the {@link IDisplayConverter} that is registered for the given
* column index.
*
* @param columnIndex
* The index for which the registered {@link IDisplayConverter}
* should be unregistered.
*/
public void unregisterUnderlyingDisplayConverter(int columnIndex) {
this.wrappedConverters.remove(columnIndex);
}
/**
* Clear the internal converter cache. Needed in case of dynamical converter
* registry updates.
*/
public void clearConverterCache() {
this.converterCache.clear();
}
}