| /******************************************************************************* |
| * Copyright (c) 2012, 2020 Original authors and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Original authors and others - initial API and implementation |
| * Dirk Fauth <dirk.fauth@googlemail.com> - Bug 459246 |
| ******************************************************************************/ |
| package org.eclipse.nebula.widgets.nattable.summaryrow; |
| |
| import java.util.Collection; |
| |
| import org.eclipse.nebula.widgets.nattable.command.DisposeResourcesCommand; |
| import org.eclipse.nebula.widgets.nattable.command.ILayerCommand; |
| import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry; |
| import org.eclipse.nebula.widgets.nattable.layer.AbstractLayerTransform; |
| import org.eclipse.nebula.widgets.nattable.layer.DataLayer; |
| import org.eclipse.nebula.widgets.nattable.layer.IDpiConverter; |
| import org.eclipse.nebula.widgets.nattable.layer.ILayer; |
| import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer; |
| import org.eclipse.nebula.widgets.nattable.layer.LabelStack; |
| import org.eclipse.nebula.widgets.nattable.layer.LayerUtil; |
| import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell; |
| import org.eclipse.nebula.widgets.nattable.layer.cell.LayerCell; |
| import org.eclipse.nebula.widgets.nattable.layer.command.ConfigureScalingCommand; |
| import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent; |
| import org.eclipse.nebula.widgets.nattable.layer.event.IVisualChangeEvent; |
| import org.eclipse.nebula.widgets.nattable.resize.command.MultiRowResizeCommand; |
| import org.eclipse.nebula.widgets.nattable.resize.command.RowResizeCommand; |
| import org.eclipse.nebula.widgets.nattable.resize.event.RowResizeEvent; |
| import org.eclipse.nebula.widgets.nattable.style.DisplayMode; |
| import org.eclipse.nebula.widgets.nattable.summaryrow.command.CalculateSummaryRowValuesCommand; |
| import org.eclipse.nebula.widgets.nattable.util.ArrayUtil; |
| import org.eclipse.nebula.widgets.nattable.util.CalculatedValueCache; |
| import org.eclipse.nebula.widgets.nattable.util.ICalculatedValueCache; |
| import org.eclipse.nebula.widgets.nattable.util.ICalculator; |
| |
| /** |
| * Adds a summary row at the end. Uses {@link ISummaryProvider} to calculate the |
| * summaries for all columns. |
| * <p> |
| * This layer also adds the following labels: |
| * <ol> |
| * <li>{@link SummaryRowLayer#DEFAULT_SUMMARY_COLUMN_CONFIG_LABEL_PREFIX} + |
| * column index</li> |
| * <li>{@link SummaryRowLayer#DEFAULT_SUMMARY_ROW_CONFIG_LABEL} to all cells in |
| * the row</li> |
| * </ol> |
| * |
| * Example: column with index 1 will have the |
| * DEFAULT_SUMMARY_COLUMN_CONFIG_LABEL_PREFIX + 1 label applied. Styling and |
| * {@link ISummaryProvider} can be hooked up to these labels. |
| * |
| * @see DefaultSummaryRowConfiguration |
| */ |
| public class SummaryRowLayer extends AbstractLayerTransform implements IUniqueIndexLayer { |
| |
| /** |
| * Label that gets attached to the LabelStack for every cell in the summary |
| * row. |
| */ |
| public static final String DEFAULT_SUMMARY_ROW_CONFIG_LABEL = "SummaryRow"; //$NON-NLS-1$ |
| /** |
| * Prefix of the labels that get attached to cells in the summary row. The |
| * complete label will consist of this prefix and the column index at the |
| * end of the label. This way every cell in the summary row can be accessed |
| * directly via label mechanism. |
| */ |
| public static final String DEFAULT_SUMMARY_COLUMN_CONFIG_LABEL_PREFIX = "SummaryColumn_"; //$NON-NLS-1$ |
| /** |
| * The ConfigRegistry for retrieving the ISummaryProvider per column. |
| */ |
| private final IConfigRegistry configRegistry; |
| /** |
| * The row height of the summary row. As the summary row itself is not |
| * connected to a DataLayer, this layer needs to store the height for |
| * itself. |
| * <p> |
| * Note: We are not using a SizeConfig here because the position of the |
| * summary row might change in case rows are added. |
| * </p> |
| */ |
| private int summaryRowHeight = DataLayer.DEFAULT_ROW_HEIGHT; |
| /** |
| * The value cache that contains the summary values and performs summary |
| * calculation in background processes if necessary. |
| */ |
| private ICalculatedValueCache valueCache; |
| /** |
| * Converter that is used to ensure the row height of the summary row is |
| * scaled correctly. |
| */ |
| private IDpiConverter dpiConverter; |
| |
| /** |
| * Flag to configure whether the summary row should be rendered below an |
| * underlying layer or if it should be rendered standalone. Setting this |
| * value to <code>true</code> will enable rendering of the single summary |
| * row in a composite region. |
| */ |
| private boolean standalone = false; |
| |
| /** |
| * Creates a SummaryRowLayer on top of the given underlying layer. It uses |
| * smooth value updates as default. |
| * <p> |
| * Note: This constructor will create the SummaryRowLayer by using the |
| * default configuration. The default configuration doesn't fit the needs so |
| * you usually will use your custom summary row configuration. |
| * |
| * @param underlyingDataLayer |
| * The underlying layer on which the SummaryRowLayer should be |
| * build. |
| * @param configRegistry |
| * The ConfigRegistry for retrieving the ISummaryProvider per |
| * column. |
| * |
| * @see DefaultSummaryRowConfiguration |
| */ |
| public SummaryRowLayer(IUniqueIndexLayer underlyingDataLayer, IConfigRegistry configRegistry) { |
| this(underlyingDataLayer, configRegistry, true, true); |
| } |
| |
| /** |
| * Creates a SummaryRowLayer on top of the given underlying layer. It uses |
| * smooth value updates as default. |
| * <p> |
| * Note: This constructor will create the SummaryRowLayer by using the |
| * default configuration if the autoConfig parameter is set to |
| * <code>true</code>. The default configuration doesn't fit the needs so you |
| * usually will use your custom summary row configuration. When using a |
| * custom configuration you should use this constructor setting autoConfig |
| * to <code>false</code>. Otherwise you might get strange behaviour as the |
| * default configuration will be set additionally to your configuration. |
| * |
| * @param underlyingDataLayer |
| * The underlying layer on which the SummaryRowLayer should be |
| * build. |
| * @param configRegistry |
| * The ConfigRegistry for retrieving the ISummaryProvider per |
| * column. |
| * @param autoConfigure |
| * <code>true</code> to use the DefaultSummaryRowConfiguration, |
| * <code>false</code> if a custom configuration will be set after |
| * the creation. |
| * |
| * @see DefaultSummaryRowConfiguration |
| */ |
| public SummaryRowLayer(IUniqueIndexLayer underlyingDataLayer, IConfigRegistry configRegistry, boolean autoConfigure) { |
| this(underlyingDataLayer, configRegistry, true, autoConfigure); |
| } |
| |
| /** |
| * Creates a SummaryRowLayer on top of the given underlying layer. |
| * <p> |
| * Note: This constructor will create the SummaryRowLayer by using the |
| * default configuration if the autoConfig parameter is set to |
| * <code>true</code>. The default configuration doesn't fit the needs so you |
| * usually will use your custom summary row configuration. When using a |
| * custom configuration you should use this constructor setting autoConfig |
| * to <code>false</code>. Otherwise you might get strange behaviour as the |
| * default configuration will be set additionally to your configuration. |
| * |
| * @param underlyingDataLayer |
| * The underlying layer on which the SummaryRowLayer should be |
| * build. |
| * @param configRegistry |
| * The ConfigRegistry for retrieving the ISummaryProvider per |
| * column. |
| * @param smoothUpdates |
| * <code>true</code> if the summary value updates should be |
| * performed smoothly, <code>false</code> if on re-calculation |
| * the value should be immediately shown as not calculated. |
| * @param autoConfigure |
| * <code>true</code> to use the DefaultSummaryRowConfiguration, |
| * <code>false</code> if a custom configuration will be set after |
| * the creation. |
| * |
| * @see DefaultSummaryRowConfiguration |
| */ |
| public SummaryRowLayer(IUniqueIndexLayer underlyingDataLayer, IConfigRegistry configRegistry, |
| boolean smoothUpdates, boolean autoConfigure) { |
| |
| super(underlyingDataLayer); |
| this.configRegistry = configRegistry; |
| |
| this.valueCache = new CalculatedValueCache(this, true, false, smoothUpdates); |
| |
| if (autoConfigure) { |
| addConfiguration(new DefaultSummaryRowConfiguration()); |
| } |
| } |
| |
| /** |
| * Calculates the summary for the column using the {@link ISummaryProvider} |
| * from the {@link IConfigRegistry}. In order to prevent the table from |
| * freezing (for large data sets), the summary is calculated in a separate |
| * Thread. While summary is being calculated |
| * {@link ISummaryProvider#DEFAULT_SUMMARY_VALUE} is returned. |
| * <p> |
| * NOTE: Since this is a {@link IUniqueIndexLayer} sitting close to the |
| * {@link DataLayer}, columnPosition == columnIndex |
| */ |
| @Override |
| public Object getDataValueByPosition(final int columnPosition, final int rowPosition) { |
| if (isSummaryRowPosition(rowPosition)) { |
| return calculateNewSummaryValue(columnPosition, true); |
| } |
| return super.getDataValueByPosition(columnPosition, rowPosition); |
| } |
| |
| /** |
| * Asks the local CalculatedValueCache for summary value. Is also used to |
| * trigger calculation prior printing or exporting to ensure that the values |
| * are calculated. This is necessary because usually the calculation will be |
| * done when the data value is requested the first time. |
| * |
| * @param columnPosition |
| * The column position of the requested summary value. |
| * @param calculateInBackground |
| * <code>true</code> if the summary value calculation should be |
| * processed in a background thread, <code>false</code> if the |
| * calculation should be processed in the current thread. |
| * @return The value that is calculated in the CalculatedValueCache or a |
| * temporary value if the calculation process is started in a |
| * background thread. |
| */ |
| private Object calculateNewSummaryValue(final int columnPosition, boolean calculateInBackground) { |
| |
| // as we only care about one row in the value cache, the real row |
| // position doesn't matter in fact using the real summary row |
| // position would cause issues if rows are added or removed because |
| // the value cache takes into account column and row position for |
| // caching. |
| return this.valueCache.getCalculatedValue( |
| columnPosition, |
| getSummaryRowPosition(), |
| calculateInBackground, |
| new ICalculator() { |
| |
| @Override |
| public Object executeCalculation() { |
| LabelStack labelStack = getConfigLabelsByPositionWithoutTransformation(columnPosition, getSummaryRowPosition()); |
| String[] configLabels = labelStack.toArray(ArrayUtil.STRING_TYPE_ARRAY); |
| |
| final ISummaryProvider summaryProvider = SummaryRowLayer.this.configRegistry.getConfigAttribute( |
| SummaryRowConfigAttributes.SUMMARY_PROVIDER, |
| DisplayMode.NORMAL, |
| configLabels); |
| |
| // If there is no Summary provider - skip processing |
| if (summaryProvider == ISummaryProvider.NONE || summaryProvider == null) { |
| return null; |
| } |
| |
| return summaryProvider.summarize(columnPosition); |
| } |
| }); |
| } |
| |
| /** |
| * Checks if the given row position is the position of the summary row. |
| * |
| * @param rowPosition |
| * The row position to check. |
| * @return <code>true</code> if the given row position is the summary row |
| * position. |
| * @since 1.6 |
| */ |
| public boolean isSummaryRowPosition(int rowPosition) { |
| return rowPosition == getSummaryRowPosition(); |
| } |
| |
| /** |
| * Get the position of the summary row in this layer. |
| * |
| * @return The position of the summary row. Typically |
| * <code>rowCount - 1</code>. |
| * @since 1.6 |
| */ |
| public int getSummaryRowPosition() { |
| return getRowCount() - 1; |
| } |
| |
| @Override |
| public boolean doCommand(ILayerCommand command) { |
| if (command instanceof RowResizeCommand && command.convertToTargetLayer(this)) { |
| RowResizeCommand rowResizeCommand = (RowResizeCommand) command; |
| if (isSummaryRowPosition(rowResizeCommand.getRowPosition())) { |
| if (rowResizeCommand.downScaleValue() && this.dpiConverter != null) { |
| this.summaryRowHeight = this.dpiConverter.convertDpiToPixel(rowResizeCommand.getNewHeight()); |
| } else { |
| this.summaryRowHeight = rowResizeCommand.getNewHeight(); |
| } |
| fireLayerEvent(new RowResizeEvent(this, getSummaryRowPosition())); |
| return true; |
| } |
| } else if (command instanceof MultiRowResizeCommand && command.convertToTargetLayer(this)) { |
| MultiRowResizeCommand rowResizeCommand = (MultiRowResizeCommand) command; |
| for (int row : rowResizeCommand.getRowPositionsArray()) { |
| if (isSummaryRowPosition(row)) { |
| if (rowResizeCommand.downScaleValue() && this.dpiConverter != null) { |
| this.summaryRowHeight = this.dpiConverter.convertDpiToPixel(rowResizeCommand.getRowHeight(row)); |
| } else { |
| this.summaryRowHeight = rowResizeCommand.getRowHeight(row); |
| } |
| fireLayerEvent(new RowResizeEvent(this, getSummaryRowPosition())); |
| // do not consume as additional rows might need to get |
| // updated too |
| } |
| } |
| } else if (command instanceof CalculateSummaryRowValuesCommand) { |
| for (int i = 0; i < getColumnCount(); i++) { |
| calculateNewSummaryValue(i, false); |
| } |
| // we do not return true here, as there might be other layers |
| // involved in the composition that also need to calculate |
| // the summary values immediately |
| } else if (command instanceof DisposeResourcesCommand) { |
| this.valueCache.dispose(); |
| } else if (command instanceof ConfigureScalingCommand) { |
| this.dpiConverter = ((ConfigureScalingCommand) command).getVerticalDpiConverter(); |
| } |
| return super.doCommand(command); |
| } |
| |
| @Override |
| public void handleLayerEvent(ILayerEvent event) { |
| if (event instanceof IVisualChangeEvent) { |
| clearCache(); |
| } |
| super.handleLayerEvent(event); |
| } |
| |
| /** |
| * Clear the internal cache to trigger new calculations. |
| * <p> |
| * Usually it is not necessary to call this method manually. But for certain |
| * use cases it might be useful, e.g. changing the summary provider |
| * implementation at runtime. |
| * |
| * @see CalculatedValueCache#clearCache() |
| */ |
| public void clearCache() { |
| this.valueCache.clearCache(); |
| } |
| |
| /** |
| * Clears all values in the internal cache to trigger new calculations. This |
| * will also clear all values in the cache copy and will result in rendering |
| * like there was never a summary value calculated before. |
| * <p> |
| * Usually it is not necessary to call this method manually. But for certain |
| * use cases it might be useful, e.g. changing the summary provider |
| * implementation at runtime. |
| * |
| * @see CalculatedValueCache#killCache() |
| */ |
| public void killCache() { |
| this.valueCache.killCache(); |
| } |
| |
| /** |
| * This method is a wrapper for calling |
| * {@link #getConfigLabelsByPosition(int, int)}. It is needed because |
| * sub-classes of the {@link SummaryRowLayer} might perform column |
| * position-index transformations, which would be executed again if the call |
| * for config labels is nested, e.g. in |
| * {@link #calculateNewSummaryValue(int, boolean)}. By providing and using |
| * this implementation, sub-classes that perform position-index |
| * transformations are able to implement this method by directly accessing |
| * the super implementation. |
| * |
| * @param columnPosition |
| * The column position of the cell for which the config labels |
| * are requested. If transformations are necessary, this value |
| * should be already transformed. |
| * @param rowPosition |
| * The row position of the cell for which the config labels are |
| * requested. If transformations are necessary, this value should |
| * be already transformed. |
| * @return The {@link LabelStack} for the cell at the given coordinates. |
| */ |
| protected LabelStack getConfigLabelsByPositionWithoutTransformation(int columnPosition, int rowPosition) { |
| return this.getConfigLabelsByPosition(columnPosition, rowPosition); |
| } |
| |
| @Override |
| public LabelStack getConfigLabelsByPosition(int columnPosition, int rowPosition) { |
| if (isSummaryRowPosition(rowPosition)) { |
| // create a new LabelStack that takes the config labels into account |
| LabelStack labelStack = new LabelStack(); |
| if (getConfigLabelAccumulator() != null) { |
| getConfigLabelAccumulator().accumulateConfigLabels(labelStack, columnPosition, rowPosition); |
| } |
| labelStack.addLabelOnTop(DEFAULT_SUMMARY_ROW_CONFIG_LABEL); |
| labelStack.addLabelOnTop(DEFAULT_SUMMARY_COLUMN_CONFIG_LABEL_PREFIX + columnPosition); |
| return labelStack; |
| } |
| return super.getConfigLabelsByPosition(columnPosition, rowPosition); |
| } |
| |
| @Override |
| public ILayerCell getCellByPosition(int columnPosition, int rowPosition) { |
| if (isSummaryRowPosition(rowPosition)) { |
| return new LayerCell(this, columnPosition, rowPosition); |
| } |
| return super.getCellByPosition(columnPosition, rowPosition); |
| } |
| |
| @Override |
| public int getHeight() { |
| if (this.standalone) { |
| return getRowHeightByPosition(getSummaryRowPosition()); |
| } |
| return super.getHeight() + getRowHeightByPosition(getSummaryRowPosition()); |
| } |
| |
| @Override |
| public int getPreferredHeight() { |
| if (this.standalone) { |
| return getRowHeightByPosition(getSummaryRowPosition()); |
| } |
| return super.getPreferredHeight() + getRowHeightByPosition(getSummaryRowPosition()); |
| } |
| |
| @Override |
| public int getRowCount() { |
| if (this.standalone) { |
| return 1; |
| } |
| return super.getRowCount() + 1; |
| } |
| |
| @Override |
| public int getPreferredRowCount() { |
| return getRowCount(); |
| } |
| |
| @Override |
| public int getRowIndexByPosition(int rowPosition) { |
| if (isSummaryRowPosition(rowPosition)) { |
| return rowPosition; |
| } |
| return super.getRowIndexByPosition(rowPosition); |
| } |
| |
| @Override |
| public int getRowPositionByY(int y) { |
| return LayerUtil.getRowPositionByY(this, y); |
| } |
| |
| @Override |
| public int getRowHeightByPosition(int rowPosition) { |
| if (isSummaryRowPosition(rowPosition)) { |
| if (this.dpiConverter != null) { |
| return this.dpiConverter.convertPixelToDpi(this.summaryRowHeight); |
| } |
| return this.summaryRowHeight; |
| } |
| return super.getRowHeightByPosition(rowPosition); |
| } |
| |
| @Override |
| public int getRowPositionByIndex(int rowIndex) { |
| if (rowIndex >= 0 && rowIndex < getRowCount()) { |
| return rowIndex; |
| } else { |
| return -1; |
| } |
| } |
| |
| @Override |
| public int getColumnPositionByIndex(int columnIndex) { |
| if (columnIndex >= 0 && columnIndex < getColumnCount()) { |
| return columnIndex; |
| } else { |
| return -1; |
| } |
| } |
| |
| /** |
| * |
| * @return <code>true</code> if the summary row is rendered standalone, |
| * <code>false</code> if it is rendered below the underlying layer. |
| */ |
| public boolean isStandalone() { |
| return this.standalone; |
| } |
| |
| /** |
| * Configure whether the summary row should be rendered below the underlying |
| * layer (<code>false</code>) or if it should be rendered standalone in a |
| * separate region of a composite (<code>true</code>). |
| * |
| * @param standalone |
| * Whether the summary row should be rendered standalone or below |
| * the underlying layer. |
| */ |
| public void setStandalone(boolean standalone) { |
| this.standalone = standalone; |
| } |
| |
| /** |
| * @return The {@link ICalculatedValueCache} that contains the summary |
| * values and performs summary calculation in background processes |
| * if necessary. |
| * @since 1.3 |
| */ |
| public ICalculatedValueCache getValueCache() { |
| return this.valueCache; |
| } |
| |
| /** |
| * Set the {@link ICalculatedValueCache} that should be used internally to |
| * calculate the summary values in a background thread and cache the |
| * results. |
| * <p> |
| * <b><u>Note:</u></b> By default the {@link CalculatedValueCache} is used. |
| * Be sure you know what you are doing when you are trying to exchange the |
| * implementation. |
| * </p> |
| * |
| * @param valueCache |
| * The {@link ICalculatedValueCache} that contains the summary |
| * values and performs summary calculation in background |
| * processes if necessary. |
| * @since 1.3 |
| */ |
| public void setValueCache(ICalculatedValueCache valueCache) { |
| this.valueCache = valueCache; |
| } |
| |
| @Override |
| public Collection<ILayer> getUnderlyingLayersByColumnPosition(int columnPosition) { |
| // Bug 475766 |
| // if this SummaryRowLayer is configured to be standalone, return null |
| // to ensure that possible transformations are not skipped due to layer |
| // composition |
| return this.standalone ? null : super.getUnderlyingLayersByColumnPosition(columnPosition); |
| } |
| |
| @Override |
| public Collection<ILayer> getUnderlyingLayersByRowPosition(int rowPosition) { |
| // Bug 475766 |
| // if this SummaryRowLayer is configured to be standalone, return null |
| // to ensure that possible transformations are not skipped due to layer |
| // composition |
| return this.standalone ? null : super.getUnderlyingLayersByRowPosition(rowPosition); |
| } |
| |
| @Override |
| public ILayer getUnderlyingLayerByPosition(int columnPosition, int rowPosition) { |
| // Bug 475766 |
| // if this SummaryRowLayer is configured to be standalone, return null |
| // to ensure that possible transformations are not skipped due to layer |
| // composition |
| return this.standalone ? null : super.getUnderlyingLayerByPosition(columnPosition, rowPosition); |
| } |
| |
| /** |
| * @since 1.4 |
| */ |
| @Override |
| public Collection<String> getProvidedLabels() { |
| Collection<String> labels = super.getProvidedLabels(); |
| |
| labels.add(DEFAULT_SUMMARY_ROW_CONFIG_LABEL); |
| for (int i = 0; i < getColumnCount(); i++) { |
| labels.add(DEFAULT_SUMMARY_COLUMN_CONFIG_LABEL_PREFIX + i); |
| } |
| |
| return labels; |
| } |
| } |