blob: 5455701dae0390d272e25f9c583f860c25e19555 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012, 2020 Original authors 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:
* Original authors and others - initial API and implementation
* Dirk Fauth <dirk.fauth@googlemail.com> - Added scaling
******************************************************************************/
package org.eclipse.nebula.widgets.nattable.group;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
import org.eclipse.nebula.widgets.nattable.NatTable;
import org.eclipse.nebula.widgets.nattable.grid.GridRegion;
import org.eclipse.nebula.widgets.nattable.group.config.DefaultRowGroupHeaderLayerConfiguration;
import org.eclipse.nebula.widgets.nattable.group.model.IRowGroup;
import org.eclipse.nebula.widgets.nattable.group.model.IRowGroupModel;
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.SizeConfig;
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.cell.TransformedLayerCell;
import org.eclipse.nebula.widgets.nattable.layer.command.ConfigureScalingCommandHandler;
import org.eclipse.nebula.widgets.nattable.layer.event.RowStructuralRefreshEvent;
import org.eclipse.nebula.widgets.nattable.selection.SelectRowGroupCommandHandler;
import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;
import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
import org.eclipse.nebula.widgets.nattable.style.SelectionStyleLabels;
/**
* Adds the Row grouping functionality to the row headers. Also persists the
* state of the row groups when {@link NatTable#saveState(String, Properties)}
* is invoked.
*
* Internally uses the {@link IRowGroupModel} to track the row groups.
*/
public class RowGroupHeaderLayer<T> extends AbstractLayerTransform {
private final SizeConfig columnWidthConfig = new SizeConfig(DataLayer.DEFAULT_COLUMN_WIDTH);
private final IRowGroupModel<T> model;
private final SelectionLayer selectionLayer;
private final ILayer rowHeaderLayer;
public RowGroupHeaderLayer(
ILayer rowHeaderLayer,
SelectionLayer selectionLayer,
IRowGroupModel<T> rowGroupModel) {
this(rowHeaderLayer, selectionLayer, rowGroupModel, true);
}
public RowGroupHeaderLayer(
ILayer rowHeaderLayer,
SelectionLayer selectionLayer,
IRowGroupModel<T> rowGroupModel,
boolean useDefaultConfiguration) {
super(rowHeaderLayer);
this.rowHeaderLayer = rowHeaderLayer;
this.selectionLayer = selectionLayer;
this.model = rowGroupModel;
registerCommandHandlers();
if (useDefaultConfiguration) {
addConfiguration(new DefaultRowGroupHeaderLayerConfiguration());
}
}
public IRowGroupModel<T> getModel() {
return this.model;
}
// Persistence
@Override
public void loadState(String prefix, Properties properties) {
super.loadState(prefix, properties);
this.model.loadState(prefix, properties);
fireLayerEvent(new RowStructuralRefreshEvent(this));
}
@Override
public void saveState(String prefix, Properties properties) {
super.saveState(prefix, properties);
this.model.saveState(prefix, properties);
}
// Configuration
@Override
protected void registerCommandHandlers() {
registerCommandHandler(new SelectRowGroupCommandHandler<>(this.model, this.selectionLayer, this));
registerCommandHandler(new ConfigureScalingCommandHandler(this.columnWidthConfig, null));
}
// Horizontal features
// Columns
@Override
public int getColumnCount() {
return this.rowHeaderLayer.getColumnCount() + 1;
}
@Override
public int getPreferredColumnCount() {
return this.rowHeaderLayer.getPreferredColumnCount() + 1;
}
@Override
public int getColumnIndexByPosition(int columnPosition) {
if (columnPosition == 0) {
return columnPosition;
} else {
return this.rowHeaderLayer.getColumnIndexByPosition(columnPosition - 1);
}
}
@Override
public int localToUnderlyingColumnPosition(int localColumnPosition) {
if (localColumnPosition == 0) {
return localColumnPosition;
}
return localColumnPosition - 1;
}
// Width
@Override
public int getWidth() {
return this.columnWidthConfig.getAggregateSize(1)
+ this.rowHeaderLayer.getWidth();
}
@Override
public int getPreferredWidth() {
return this.columnWidthConfig.getAggregateSize(1)
+ this.rowHeaderLayer.getPreferredWidth();
}
@Override
public int getColumnWidthByPosition(int columnPosition) {
if (columnPosition == 0) {
return this.columnWidthConfig.getSize(columnPosition);
} else {
return this.rowHeaderLayer.getColumnWidthByPosition(columnPosition - 1);
}
}
public void setColumnWidth(int columnWidth) {
this.columnWidthConfig.setSize(0, columnWidth);
}
// Column resize
@Override
public boolean isColumnPositionResizable(int columnPosition) {
if (columnPosition == 0) {
return this.columnWidthConfig.isPositionResizable(columnPosition);
} else {
return this.rowHeaderLayer.isRowPositionResizable(columnPosition - 1);
}
}
// X
@Override
public int getColumnPositionByX(int x) {
int col0Width = getColumnWidthByPosition(0);
if (x < col0Width) {
return 0;
} else {
return 1 + this.rowHeaderLayer.getColumnPositionByX(x - col0Width);
}
}
@Override
public int getStartXOfColumnPosition(int columnPosition) {
if (columnPosition == 0) {
return this.columnWidthConfig.getAggregateSize(columnPosition);
} else {
return getColumnWidthByPosition(0)
+ this.rowHeaderLayer.getStartXOfColumnPosition(columnPosition - 1);
}
}
// Cell features
/**
* If a cell belongs to a column group: column position - set to the start
* position of the group span - set to the width/size of the row group
*
* NOTE: gc.setClip() is used in the CompositeLayerPainter to ensure that
* partially visible Column group header cells are rendered properly.
*/
@Override
public ILayerCell getCellByPosition(int columnPosition, int rowPosition) {
int bodyRowIndex = getRowIndexByPosition(rowPosition);
// Row group header cell
if (RowGroupUtils.isPartOfAGroup(this.model, bodyRowIndex)) {
if (columnPosition == 0) {
return new LayerCell(
this,
columnPosition,
getStartPositionOfGroup(rowPosition),
columnPosition,
rowPosition,
1,
getRowSpan(rowPosition));
} else {
return new LayerCell(this, columnPosition, rowPosition);
}
} else {
// render row header w/ columnspan = 2
// as in this case we ask the row header layer for the cell position
// and the row header layer asks his data provider for the column
// count which should always return 1, we ask for row position 0
ILayerCell cell = this.rowHeaderLayer.getCellByPosition(0, rowPosition);
if (cell != null) {
cell = new TransformedLayerCell(cell) {
@Override
public ILayer getLayer() {
return RowGroupHeaderLayer.this;
}
@Override
public int getColumnSpan() {
return 2;
}
};
}
return cell;
}
}
/**
* Calculates the span of a cell in a Row Group. Takes into account
* collapsing and hidden rows in the group.
*
* @param rowPosition
* position of any row belonging to the group
*/
protected int getRowSpan(int rowPosition) {
int rowIndex = getRowIndexByPosition(rowPosition);
// Get the row and the group from our cache and model.
IRowGroup<T> rowGroup = RowGroupUtils.getRowGroupForRowIndex(this.model, rowIndex);
int sizeOfGroup = RowGroupUtils.sizeOfGroup(this.model, rowIndex);
if (RowGroupUtils.isCollapsed(this.model, rowGroup)) {
int sizeOfStaticRows = rowGroup.getOwnStaticMemberRows().size();
if (sizeOfStaticRows == 0) {
return 1;
} else {
sizeOfGroup = sizeOfStaticRows;
}
}
int startPositionOfGroup = getStartPositionOfGroup(rowPosition);
int endPositionOfGroup = startPositionOfGroup + sizeOfGroup;
List<Integer> rowIndexesInGroup = RowGroupUtils.getRowIndexesInGroup(this.model, rowIndex);
for (int i = startPositionOfGroup; i < endPositionOfGroup; i++) {
int index = getRowIndexByPosition(i);
if (!rowIndexesInGroup.contains(Integer.valueOf(index))) {
sizeOfGroup--;
}
}
return sizeOfGroup;
}
/**
* Figures out the start position of the group.
*
* @param selectionLayerColumnPosition
* of any column belonging to the group
* @return first position of the column group
*/
private int getStartPositionOfGroup(int rowPosition) {
int bodyRowIndex = getRowIndexByPosition(rowPosition);
int leastPossibleStartPositionOfGroup =
Math.max(0, (rowPosition - RowGroupUtils.sizeOfGroup(this.model, bodyRowIndex)));
int i = 0;
for (i = leastPossibleStartPositionOfGroup; i < rowPosition; i++) {
if (RowGroupUtils.isInTheSameGroup(
getRowIndexByPosition(i),
bodyRowIndex,
this.model)) {
break;
}
}
return i;
}
@Override
public String getDisplayModeByPosition(int columnPosition, int rowPosition) {
int rowIndex = getRowIndexByPosition(rowPosition);
if (columnPosition == 0
&& RowGroupUtils.isPartOfAGroup(this.model, rowIndex)) {
return DisplayMode.NORMAL;
} else {
return this.rowHeaderLayer.getDisplayModeByPosition(columnPosition - 1, rowPosition);
}
}
@Override
public LabelStack getConfigLabelsByPosition(int columnPosition, int rowPosition) {
int rowIndex = getRowIndexByPosition(rowPosition);
if (columnPosition == 0 && RowGroupUtils.isPartOfAGroup(this.model, rowIndex)) {
LabelStack stack = new LabelStack();
if (getConfigLabelAccumulator() != null) {
getConfigLabelAccumulator().accumulateConfigLabels(stack, columnPosition, rowPosition);
}
stack.addLabel(GridRegion.ROW_GROUP_HEADER);
IRowGroup<T> group = RowGroupUtils.getRowGroupForRowIndex(this.model, rowIndex);
if (RowGroupUtils.isCollapsed(this.model, group)) {
stack.addLabelOnTop(DefaultRowGroupHeaderLayerConfiguration.GROUP_COLLAPSED_CONFIG_TYPE);
} else {
stack.addLabelOnTop(DefaultRowGroupHeaderLayerConfiguration.GROUP_EXPANDED_CONFIG_TYPE);
}
List<Integer> selectedRowIndexes =
convertToRowIndexes(this.selectionLayer.getFullySelectedRowPositions());
if (selectedRowIndexes.contains(rowIndex)) {
stack.addLabelOnTop(SelectionStyleLabels.ROW_FULLY_SELECTED_STYLE);
}
return stack;
} else {
return this.rowHeaderLayer.getConfigLabelsByPosition(columnPosition - 1, rowPosition);
}
}
private List<Integer> convertToRowIndexes(final int[] rowPositions) {
final List<Integer> rowIndexes = new ArrayList<>(rowPositions.length);
for (final Integer rowPosition : rowPositions) {
rowIndexes.add(this.selectionLayer.getRowIndexByPosition(rowPosition));
}
return rowIndexes;
}
@Override
public Object getDataValueByPosition(int columnPosition, int rowPosition) {
int rowIndex = getRowIndexByPosition(rowPosition);
if (columnPosition == 0
&& RowGroupUtils.isPartOfAGroup(this.model, rowIndex)) {
return RowGroupUtils.getRowGroupNameForIndex(this.model, rowIndex);
} else {
return this.rowHeaderLayer.getDataValueByPosition(columnPosition - 1, rowPosition);
}
}
@Override
public LabelStack getRegionLabelsByXY(int x, int y) {
int rowIndex = getRowIndexByPosition(getRowPositionByY(y));
if (RowGroupUtils.isPartOfAGroup(this.model, rowIndex)
&& x < getColumnWidthByPosition(0)) {
return new LabelStack(GridRegion.ROW_GROUP_HEADER);
} else {
return this.rowHeaderLayer.getRegionLabelsByXY(x - getColumnWidthByPosition(0), y);
}
}
public void collapseRowGroupByIndex(int rowIndex) {
RowGroupUtils.getRowGroupForRowIndex(this.model, rowIndex).collapse();
}
public void clearAllGroups() {
this.model.clear();
}
@Override
public Collection<String> getProvidedLabels() {
Collection<String> labels = super.getProvidedLabels();
labels.add(GridRegion.ROW_GROUP_HEADER);
labels.add(DefaultRowGroupHeaderLayerConfiguration.GROUP_COLLAPSED_CONFIG_TYPE);
labels.add(DefaultRowGroupHeaderLayerConfiguration.GROUP_EXPANDED_CONFIG_TYPE);
return labels;
}
}