/*******************************************************************************
* Copyright (c) 2014 Dirk Fauth.
* 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:
* Dirk Fauth <dirk.fauth@googlemail.com> - initial API and implementation
******************************************************************************/
package org.eclipse.nebula.widgets.nattable.summaryrow;
import java.util.Collection;
import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
import org.eclipse.nebula.widgets.nattable.coordinate.Range;
import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer;
import org.eclipse.nebula.widgets.nattable.layer.AbstractLayer;
import org.eclipse.nebula.widgets.nattable.layer.CompositeLayer;
import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
import org.eclipse.nebula.widgets.nattable.layer.ILayer;
import org.eclipse.nebula.widgets.nattable.layer.ILayerListener;
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.event.ILayerEvent;
import org.eclipse.nebula.widgets.nattable.painter.layer.GridLineCellLayerPainter;
import org.eclipse.nebula.widgets.nattable.painter.layer.ILayerPainter;
/**
* This layer is a specialization of the {@link SummaryRowLayer} and is intended
* to be used in a composition below a {@link GridLayer} or a vertical
* composition like one with a column header and a body. It is horizontal
* dependent to the layer above and configured as a standalone summary row,
* which means that only the summary row is rendered.
* <p>
* A typical composition to use this layer could look like this:<br>
*
* <pre>
* +---------------+---------------+<br>
* | CompositeLayer |<br>
* |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+<br>
* | +---------------------------+ |<br>
* | | GridLayer | |<br>
* | +~~~~~~~~~~~~~~~~~~~~~~~~~~~+ |<br>
* | | Corner | ColHeader | |<br>
* | +-----------+---------------+ |<br>
* | | RowHeader | Body | |<br>
* | +-----------+---------------+ |<br>
* +-------------------------------+<br>
* | FixedSummaryRowLayer |<br>
* +-------------------------------+<br>
* </pre>
* <p>
* It would be created by the following code:
* </p>
*
* <pre>
* GridLayer gridLayer = new GridLayer(...);
* FixedSummaryRowLayer summaryRowLayer =
* new FixedSummaryRowLayer(bodyDataLayer, gridLayer, configRegistry);
*
* CompositeLayer composite = new CompositeLayer(1, 2);
* composite.setChildLayer("GRID", gridLayer, 0, 0);
* composite.setChildLayer(SUMMARY_REGION, summaryRowLayer, 0, 1);
*
* NatTable natTable = new NatTable(panel, composite);
* </pre>
* <p>
* Note that the <i>bodyDataLayer</i> needs to be accessible somehow and the
* creation of the {@link GridLayer} is not specified in detail in the above
* example.
* </p>
* <p>
* Using this layer in a composition as shown above will result in a fixed
* summary row that doesn't scroll if the viewport is scrolled. This is
* different to the typical approach of adding the {@link SummaryRowLayer} on
* top of the body {@link DataLayer} where the summary row will scroll as part
* of the body region.
* </p>
*/
public class FixedSummaryRowLayer extends SummaryRowLayer {
public static final String DEFAULT_SUMMARY_ROW_LABEL = "Summary"; //$NON-NLS-1$
protected String summaryRowLabel = DEFAULT_SUMMARY_ROW_LABEL;
/**
* The layer to which fixed summary row should be horizontally dependent.
* Typically a {@link GridLayer} or the body layer stack in case of a simple
* vertical composition.
*/
protected ILayer horizontalLayerDependency;
/**
* Flag to tell whether the horizontal dependency is a composite or not. For
* example, if the horizontal dependency is a {@link GridLayer} it contains
* a row header, which means this {@link FixedSummaryRowLayer} needs to
* handle an additional column at position 1.
*/
private boolean horizontalCompositeDependency = true;
/**
* Creates a standalone {@link FixedSummaryRowLayer} that is horizontal
* dependent to the given layer and calculates the summary values from the
* given bodyDataLayer. It will register the default configurations and
* perform smooth value updates.
*
* <p>
* <b>Note:</b><br>
* The {@link FixedSummaryRowLayer} constructor is setting a
* {@link GridLineCellLayerPainter} that is configured for clipTop to the
* given bodyDataLayer. This is necessary as otherwise the body region would
* paint over the summary row. In case you want to use a different
* {@link ILayerPainter} ensure to set it AFTER creating the
* {@link FixedSummaryRowLayer}.
* </p>
*
* @param bodyDataLayer
* The underlying layer on which this layer should be build.
* Typically the {@link DataLayer} of the body region.
* @param horizontalLayerDependency
* The layer that is above this layer in the surrounding
* composition. Typically a {@link GridLayer}.
* @param configRegistry
* The ConfigRegistry for retrieving the ISummaryProvider per
* column.
*/
public FixedSummaryRowLayer(
IUniqueIndexLayer bodyDataLayer, ILayer horizontalLayerDependency, IConfigRegistry configRegistry) {
this(bodyDataLayer, horizontalLayerDependency, configRegistry, true, true);
}
/**
* Creates a standalone {@link FixedSummaryRowLayer} that is horizontal
* dependent to the given layer and calculates the summary values from the
* given bodyDataLayer. It will perform smooth value updates.
*
* <p>
* <b>Note:</b><br>
* The {@link FixedSummaryRowLayer} constructor is setting a
* {@link GridLineCellLayerPainter} that is configured for clipTop to the
* given bodyDataLayer. This is necessary as otherwise the body region would
* paint over the summary row. In case you want to use a different
* {@link ILayerPainter} ensure to set it AFTER creating the
* {@link FixedSummaryRowLayer}.
* </p>
*
* @param bodyDataLayer
* The underlying layer on which this layer should be build.
* Typically the {@link DataLayer} of the body region.
* @param horizontalLayerDependency
* The layer that is above this layer in the surrounding
* composition. Typically a {@link GridLayer}.
* @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.
*/
public FixedSummaryRowLayer(
IUniqueIndexLayer bodyDataLayer, ILayer horizontalLayerDependency, IConfigRegistry configRegistry,
boolean autoConfigure) {
this(bodyDataLayer, horizontalLayerDependency, configRegistry, true, autoConfigure);
}
/**
* Creates a standalone {@link FixedSummaryRowLayer} that is horizontal
* dependent to the given layer and calculates the summary values from the
* given bodyDataLayer.
*
* <p>
* <b>Note:</b><br>
* The {@link FixedSummaryRowLayer} constructor is setting a
* {@link GridLineCellLayerPainter} that is configured for clipTop to the
* given bodyDataLayer. This is necessary as otherwise the body region would
* paint over the summary row. In case you want to use a different
* {@link ILayerPainter} ensure to set it AFTER creating the
* {@link FixedSummaryRowLayer}.
* </p>
*
* @param bodyDataLayer
* The underlying layer on which this layer should be build.
* Typically the {@link DataLayer} of the body region.
* <p>
* <b>Note</b>: When using a different layer than the DataLayer,
* e.g. the GlazedListsEventLayer to receive automatic updates,
* you need to ensure that the GridLineCellLayerPainter
* configured for clipping on top is set to the DataLayer for
* correct rendering of the fixed summary row.
* </p>
* @param horizontalLayerDependency
* The layer that is above this layer in the surrounding
* composition. Typically a {@link GridLayer}.
* @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.
*/
public FixedSummaryRowLayer(
IUniqueIndexLayer bodyDataLayer, ILayer horizontalLayerDependency, IConfigRegistry configRegistry,
boolean smoothUpdates, boolean autoConfigure) {
super(bodyDataLayer, configRegistry, smoothUpdates, autoConfigure);
this.horizontalLayerDependency = horizontalLayerDependency;
setStandalone(true);
// register a GridLineCellLayerPainter that is configured for clipping
// on top
if (bodyDataLayer instanceof AbstractLayer) {
((AbstractLayer) bodyDataLayer).setLayerPainter(new GridLineCellLayerPainter(false, true));
}
// if the layer we are dependent to has changed, we need to update
if (this.horizontalLayerDependency != null) {
horizontalLayerDependency.addLayerListener(new ILayerListener() {
@Override
public void handleLayerEvent(ILayerEvent event) {
FixedSummaryRowLayer.this.handleLayerEvent(event);
}
});
}
}
@Override
public Object getDataValueByPosition(int columnPosition, int rowPosition) {
if (!isBodyColumn(columnPosition)) {
return getSummaryRowLabel();
}
int columnIndex = LayerUtil.convertColumnPosition(
this.horizontalLayerDependency, columnPosition, (IUniqueIndexLayer) this.underlyingLayer);
return super.getDataValueByPosition(columnIndex, rowPosition);
}
/**
*
* @param columnPosition
* The column position that should be checked.
* @return <code>true</code> if the column at the given position is a column
* of the body, <code>false</code> if it is a column of another
* region, e.g. the row header in a grid.
*/
protected boolean isBodyColumn(int columnPosition) {
return !(this.horizontalCompositeDependency && (columnPosition == 0));
}
/**
*
* @return <code>true</code> if the horizontal dependency is itself a
* composite that has an additional column, e.g. a {@link GridLayer}
* with a row header. <code>false</code> if the horizontal
* dependency is not a composite, e.g. the body layer stack.
*/
public boolean hasHorizontalCompositeDependency() {
return this.horizontalCompositeDependency;
}
/**
* Specify if the horizontal dependency is a {@link CompositeLayer} that
* adds additional columns.
*
* @param compositeDependency
* <code>true</code> to specify that the horizontal dependency is
* itself a composite that has an additional column, e.g. a
* {@link GridLayer} with a row header. <code>false</code> if the
* horizontal dependency is not a composite, e.g. the body layer
* stack.
*/
public void setHorizontalCompositeDependency(boolean compositeDependency) {
this.horizontalCompositeDependency = compositeDependency;
}
/**
*
* @return The label that is used as data value for the horizontal dependent
* cell to the row header column.
*/
public String getSummaryRowLabel() {
return this.summaryRowLabel;
}
/**
*
* @param summaryRowLabel
* The label that should be used as data value for the horizontal
* dependent cell to the row header column.
*/
public void setSummaryRowLabel(String summaryRowLabel) {
this.summaryRowLabel = summaryRowLabel;
}
/**
* This implementation directly calls the super implementation. This is done
* to skip the column position-index transformation since it was done
* already.
*/
@Override
protected LabelStack getConfigLabelsByPositionWithoutTransformation(
int columnPosition, int rowPosition) {
return super.getConfigLabelsByPosition(columnPosition, rowPosition);
}
@Override
public LabelStack getConfigLabelsByPosition(int columnPosition, int rowPosition) {
if (!isBodyColumn(columnPosition)) {
LabelStack labelStack = this.horizontalLayerDependency.getConfigLabelsByPosition(
columnPosition, this.horizontalLayerDependency.getRowCount() - 1);
labelStack.addLabelOnTop(SummaryRowLayer.DEFAULT_SUMMARY_ROW_CONFIG_LABEL);
if (getConfigLabelAccumulator() != null) {
getConfigLabelAccumulator().accumulateConfigLabels(
labelStack, columnPosition, rowPosition);
}
return labelStack;
}
int columnIndex = LayerUtil.convertColumnPosition(
this.horizontalLayerDependency, columnPosition, (IUniqueIndexLayer) this.underlyingLayer);
return super.getConfigLabelsByPosition(columnIndex, rowPosition);
}
// Columns
@Override
public int getColumnCount() {
return this.horizontalLayerDependency.getColumnCount();
}
@Override
public int getPreferredColumnCount() {
return this.horizontalLayerDependency.getPreferredColumnCount();
}
@Override
public int getColumnIndexByPosition(int columnPosition) {
return this.horizontalLayerDependency.getColumnIndexByPosition(columnPosition);
}
@Override
public int localToUnderlyingColumnPosition(int localColumnPosition) {
return this.horizontalLayerDependency.localToUnderlyingColumnPosition(localColumnPosition);
}
@Override
public int underlyingToLocalColumnPosition(ILayer sourceUnderlyingLayer,
int underlyingColumnPosition) {
if (sourceUnderlyingLayer == this.horizontalLayerDependency) {
return underlyingColumnPosition;
}
return this.horizontalLayerDependency.underlyingToLocalColumnPosition(
sourceUnderlyingLayer, underlyingColumnPosition);
}
@Override
public Collection<Range> underlyingToLocalColumnPositions(
ILayer sourceUnderlyingLayer,
Collection<Range> underlyingColumnPositionRanges) {
if (sourceUnderlyingLayer == this.horizontalLayerDependency) {
return underlyingColumnPositionRanges;
}
return this.horizontalLayerDependency.underlyingToLocalColumnPositions(
sourceUnderlyingLayer, underlyingColumnPositionRanges);
}
// Width
@Override
public int getWidth() {
return this.horizontalLayerDependency.getWidth();
}
@Override
public int getPreferredWidth() {
return this.horizontalLayerDependency.getPreferredWidth();
}
@Override
public int getColumnWidthByPosition(int columnPosition) {
return this.horizontalLayerDependency.getColumnWidthByPosition(columnPosition);
}
// Column resize
@Override
public boolean isColumnPositionResizable(int columnPosition) {
return this.horizontalLayerDependency.isColumnPositionResizable(columnPosition);
}
// X
@Override
public int getColumnPositionByX(int x) {
return this.horizontalLayerDependency.getColumnPositionByX(x);
}
@Override
public int getStartXOfColumnPosition(int columnPosition) {
return this.horizontalLayerDependency.getStartXOfColumnPosition(columnPosition);
}
}