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