blob: 9359f8e9035cadf00427fe461c47d7f00d812b6e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012, 2023 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
******************************************************************************/
package org.eclipse.nebula.widgets.nattable.hideshow;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import org.eclipse.collections.api.map.primitive.IntIntMap;
import org.eclipse.collections.api.map.primitive.MutableIntIntMap;
import org.eclipse.collections.impl.factory.primitive.IntIntMaps;
import org.eclipse.nebula.widgets.nattable.command.ILayerCommand;
import org.eclipse.nebula.widgets.nattable.coordinate.PositionUtil;
import org.eclipse.nebula.widgets.nattable.coordinate.Range;
import org.eclipse.nebula.widgets.nattable.layer.AbstractLayerTransform;
import org.eclipse.nebula.widgets.nattable.layer.ILayer;
import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer;
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.SpanningLayerCell;
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.IStructuralChangeEvent;
import org.eclipse.nebula.widgets.nattable.layer.event.VisualRefreshEvent;
import org.eclipse.nebula.widgets.nattable.reorder.event.RowReorderEvent;
/**
* Abstract implementation for row hide/show operations.
*/
public abstract class AbstractRowHideShowLayer extends AbstractLayerTransform implements IUniqueIndexLayer {
private MutableIntIntMap cachedVisibleRowIndexPositionMapping;
private MutableIntIntMap cachedVisibleRowPositionIndexMapping;
private MutableIntIntMap cachedHiddenRowIndexPositionMapping;
private MutableIntIntMap startYCache = IntIntMaps.mutable.empty();
/**
* Constructor.
*
* @param underlyingLayer
* The underlying layer.
*/
public AbstractRowHideShowLayer(IUniqueIndexLayer underlyingLayer) {
super(underlyingLayer);
}
/**
* @return The underlying layer.
* @since 2.0
*/
@Override
protected IUniqueIndexLayer getUnderlyingLayer() {
return (IUniqueIndexLayer) super.getUnderlyingLayer();
}
@Override
public void handleLayerEvent(ILayerEvent event) {
if (hasHiddenRows() && event instanceof RowReorderEvent) {
// we need to convert the before positions in the event BEFORE the
// local states are changed, otherwise we are not able to convert
// the before positions as the changed layer states would return
// incorrect values
RowReorderEvent reorderEvent = (RowReorderEvent) event;
int[] fromPositions = reorderEvent.getBeforeFromRowIndexes().stream()
.mapToInt(Integer::intValue)
.map(this::getRowPositionByIndex)
.filter(pos -> pos >= 0)
.toArray();
Collection<Range> fromRanges = PositionUtil.getRanges(fromPositions);
int pos = -1;
if (!isRowIndexHidden(reorderEvent.getBeforeToRowIndex())) {
pos = getRowPositionByIndex(reorderEvent.getBeforeToRowIndex());
} else {
int i = 1;
while (pos < 0) {
int next = reorderEvent.getBeforeToRowPosition() + i;
if (next >= this.underlyingLayer.getColumnCount()) {
break;
}
pos = underlyingToLocalRowPosition(this.underlyingLayer, next);
i++;
}
if (pos >= 0) {
reorderEvent.setBeforeToRowIndex(getRowIndexByPosition(pos));
}
}
if (pos >= 0) {
reorderEvent.setConvertedBeforePositions(this, fromRanges, pos);
}
}
if (event instanceof IStructuralChangeEvent) {
IStructuralChangeEvent structuralChangeEvent = (IStructuralChangeEvent) event;
if (structuralChangeEvent.isVerticalStructureChanged()) {
// vertical structure has changed, update cached row information
invalidateCache();
}
} else if (event instanceof VisualRefreshEvent) {
// visual change, e.g. font change, the startYCache needs to be
// cleared in order to re-render correctly
this.startYCache = IntIntMaps.mutable.empty();
}
super.handleLayerEvent(event);
}
@Override
public boolean doCommand(ILayerCommand command) {
if (command instanceof ConfigureScalingCommand) {
invalidateCache();
}
return super.doCommand(command);
}
// Horizontal features
// Columns
@Override
public int getColumnPositionByIndex(int columnIndex) {
return getUnderlyingLayer().getColumnPositionByIndex(columnIndex);
}
// Vertical features
// Rows
@Override
public int getRowCount() {
if (!hasHiddenRows()) {
return super.getRowCount();
}
return getCachedVisibleRowIndexPositionMapping().size();
}
@Override
public int getRowIndexByPosition(int rowPosition) {
if (rowPosition < 0 || rowPosition >= getRowCount()) {
return -1;
}
if (!hasHiddenRows()) {
return super.getRowIndexByPosition(rowPosition);
}
return getCachedVisibleRowPositionIndexMapping().getIfAbsent(rowPosition, -1);
}
@Override
public int getRowPositionByIndex(int rowIndex) {
if (!hasHiddenRows()) {
return getUnderlyingLayer().getRowPositionByIndex(rowIndex);
}
return getCachedVisibleRowIndexPositionMapping().getIfAbsent(rowIndex, -1);
}
/**
* Get the local row positions for the given row indexes.
*
* @param rowIndexes
* The row indexes for which the local row positions are
* requested.
* @return The local row positions for the given row indexes.
*/
public Collection<Integer> getRowPositionsByIndexes(Collection<Integer> rowIndexes) {
Collection<Integer> rowPositions = new HashSet<>();
for (int rowIndex : rowIndexes) {
rowPositions.add(getRowPositionByIndex(rowIndex));
}
return rowPositions;
}
/**
* Get the local row positions for the given row indexes.
*
* @param rowIndexes
* The row indexes for which the local row positions are
* requested.
* @return The local row positions for the given row indexes.
*
* @since 2.0
*/
public int[] getRowPositionsByIndexes(int... rowIndexes) {
return (rowIndexes != null && rowIndexes.length > 0)
? Arrays.stream(rowIndexes).map(this::getRowPositionByIndex).toArray()
: new int[0];
}
@Override
public int localToUnderlyingRowPosition(int localRowPosition) {
if (localRowPosition < 0 || localRowPosition >= getRowCount()) {
return -1;
}
if (!hasHiddenRows()) {
return localRowPosition;
}
int rowIndex = getRowIndexByPosition(localRowPosition);
return getUnderlyingLayer().getRowPositionByIndex(rowIndex);
}
@Override
public int underlyingToLocalRowPosition(ILayer sourceUnderlyingLayer, int underlyingRowPosition) {
if (!hasHiddenRows()) {
return underlyingRowPosition;
}
int rowIndex = getUnderlyingLayer().getRowIndexByPosition(underlyingRowPosition);
int rowPosition = getRowPositionByIndex(rowIndex);
if (rowPosition >= 0) {
return rowPosition;
} else {
return getCachedHiddenRowIndexPositionMapping().getIfAbsent(rowIndex, -1);
}
}
@Override
public Collection<Range> underlyingToLocalRowPositions(
ILayer sourceUnderlyingLayer, Collection<Range> underlyingRowPositionRanges) {
Collection<Range> localRowPositionRanges = new ArrayList<>(underlyingRowPositionRanges.size());
for (Range underlyingRowPositionRange : underlyingRowPositionRanges) {
int startRowPosition = getAdjustedUnderlyingToLocalStartPosition(
sourceUnderlyingLayer,
underlyingRowPositionRange.start,
underlyingRowPositionRange.end);
int endRowPosition = getAdjustedUnderlyingToLocalEndPosition(
sourceUnderlyingLayer,
underlyingRowPositionRange.end,
underlyingRowPositionRange.start);
// teichstaedt: fixes the problem that ranges where added even if
// the corresponding startPosition weren't found in the underlying
// layer. Without that fix a bunch of ranges of kind Range [-1, 180]
// which causes strange behaviour in Freeze- and other Layers were
// returned.
if (startRowPosition > -1) {
localRowPositionRanges.add(new Range(startRowPosition, endRowPosition));
}
}
return localRowPositionRanges;
}
private int getAdjustedUnderlyingToLocalStartPosition(
ILayer sourceUnderlyingLayer,
int startUnderlyingPosition,
int endUnderlyingPosition) {
int localStartRowPosition = underlyingToLocalRowPosition(sourceUnderlyingLayer, startUnderlyingPosition);
int offset = 0;
while (localStartRowPosition < 0
&& (startUnderlyingPosition + offset < endUnderlyingPosition)) {
localStartRowPosition =
underlyingToLocalRowPosition(sourceUnderlyingLayer, startUnderlyingPosition + offset++);
}
return localStartRowPosition;
}
private int getAdjustedUnderlyingToLocalEndPosition(
ILayer sourceUnderlyingLayer,
int endUnderlyingPosition,
int startUnderlyingPosition) {
int localEndRowPosition = underlyingToLocalRowPosition(sourceUnderlyingLayer, endUnderlyingPosition - 1);
int offset = 0;
while (localEndRowPosition < 0
&& (endUnderlyingPosition - offset > startUnderlyingPosition)) {
localEndRowPosition =
underlyingToLocalRowPosition(sourceUnderlyingLayer, endUnderlyingPosition - offset++);
}
return localEndRowPosition + 1;
}
// Height
@Override
public int getHeight() {
if (getRowCount() == 0) {
return 0;
}
int lastRowPosition = getRowCount() - 1;
return getStartYOfRowPosition(lastRowPosition) + getRowHeightByPosition(lastRowPosition);
}
// Y
@Override
public int getRowPositionByY(int y) {
return LayerUtil.getRowPositionByY(this, y);
}
@Override
public int getStartYOfRowPosition(int localRowPosition) {
int cachedStartY = this.startYCache.getIfAbsent(localRowPosition, -1);
if (cachedStartY != -1) {
return cachedStartY;
}
IUniqueIndexLayer underlyingLayer = getUnderlyingLayer();
int underlyingPosition = localToUnderlyingRowPosition(localRowPosition);
if (underlyingPosition < 0) {
return -1;
}
int underlyingStartY = underlyingLayer.getStartYOfRowPosition(underlyingPosition);
if (underlyingStartY < 0) {
return -1;
}
for (int hiddenIndex : getHiddenRowIndexesArray()) {
int hiddenPosition = underlyingLayer.getRowPositionByIndex(hiddenIndex);
// if the hidden position is -1, it is hidden in the underlying
// layer therefore the underlying layer should handle the
// positioning
if (hiddenPosition >= 0 && hiddenPosition <= underlyingPosition) {
underlyingStartY -= underlyingLayer.getRowHeightByPosition(hiddenPosition);
}
}
this.startYCache.put(localRowPosition, underlyingStartY);
return underlyingStartY;
}
// Hide/show
/**
* Will check if the row at the specified index is hidden or not. Checks
* this layer and also the sublayers for the visibility.
*
* @param rowIndex
* The row index of the row whose visibility state should be
* checked.
* @return <code>true</code> if the row at the specified index is hidden,
* <code>false</code> if it is visible.
*/
public abstract boolean isRowIndexHidden(int rowIndex);
/**
* Will collect and return all indexes of the rows that are hidden in this
* layer.
* <p>
* <b>Note:</b> It is not intended that it also collects the row indexes of
* underlying layers. This would cause issues on calculating positions, as
* every layer is responsible for those calculations itself.
* </p>
* <p>
* Since 2.0 it is recommended to use {@link #getHiddenRowIndexesArray()} to
* avoid unnecessary autoboxing operations.
* </p>
*
* @return Collection of all row indexes that are hidden in this layer.
*/
public abstract Collection<Integer> getHiddenRowIndexes();
/**
* Will collect and return all indexes of the rows that are hidden in this
* layer.
* <p>
* <b>Note:</b> It is not intended that it also collects the row indexes of
* underlying layers. This would cause issues on calculating positions, as
* every layer is responsible for those calculations itself.
* </p>
*
* @return All row indexes that are hidden in this layer.
*
* @since 2.0
*/
public abstract int[] getHiddenRowIndexesArray();
/**
* Check if this layer actively hides rows.
*
* @return <code>true</code> if rows are hidden by this layer,
* <code>false</code> if not.
*
* @since 2.0
*/
public abstract boolean hasHiddenRows();
@Override
public ILayerCell getCellByPosition(int columnPosition, int rowPosition) {
ILayerCell cell = super.getCellByPosition(columnPosition, rowPosition);
if (cell != null && cell.isSpannedCell()) {
// the spanning needs to be updated to reflect the
// hiding accordingly
int underlyingColumnPosition = localToUnderlyingColumnPosition(columnPosition);
int underlyingRowPosition = localToUnderlyingRowPosition(rowPosition);
ILayerCell underlyingCell = this.underlyingLayer.getCellByPosition(underlyingColumnPosition, underlyingRowPosition);
boolean rowSpanUpdated = false;
int rowSpan = underlyingCell.getRowSpan();
for (int row = 0; row < underlyingCell.getRowSpan(); row++) {
int rowIndex = this.underlyingLayer.getRowIndexByPosition(underlyingCell.getOriginRowPosition() + row);
if (isRowIndexHidden(rowIndex)) {
rowSpan--;
rowSpanUpdated = true;
}
}
if (rowSpanUpdated) {
cell = new SpanningLayerCell(cell, cell.getColumnSpan(), rowSpan);
}
}
return cell;
}
// Cache
/**
* Invalidate the cache to ensure that information is rebuild.
*/
protected synchronized void invalidateCache() {
this.cachedVisibleRowIndexPositionMapping = null;
this.cachedVisibleRowPositionIndexMapping = null;
this.cachedHiddenRowIndexPositionMapping = null;
this.startYCache = IntIntMaps.mutable.empty();
}
private synchronized IntIntMap getCachedVisibleRowIndexPositionMapping() {
if (this.cachedVisibleRowIndexPositionMapping == null) {
cacheVisibleRowIndexes();
}
return this.cachedVisibleRowIndexPositionMapping;
}
private synchronized IntIntMap getCachedVisibleRowPositionIndexMapping() {
if (this.cachedVisibleRowPositionIndexMapping == null) {
cacheVisibleRowIndexes();
}
return this.cachedVisibleRowPositionIndexMapping;
}
private synchronized IntIntMap getCachedHiddenRowIndexPositionMapping() {
if (this.cachedHiddenRowIndexPositionMapping == null) {
cacheVisibleRowIndexes();
}
return this.cachedHiddenRowIndexPositionMapping;
}
/**
* Build up the row caches.
*/
protected synchronized void cacheVisibleRowIndexes() {
this.cachedVisibleRowIndexPositionMapping = IntIntMaps.mutable.empty();
this.cachedVisibleRowPositionIndexMapping = IntIntMaps.mutable.empty();
this.cachedHiddenRowIndexPositionMapping = IntIntMaps.mutable.empty();
this.startYCache = IntIntMaps.mutable.empty();
// only build up a cache if it is necessary
if (hasHiddenRows()) {
ILayer underlyingLayer = getUnderlyingLayer();
int rowPosition = 0;
for (int parentRowPosition = 0; parentRowPosition < underlyingLayer.getRowCount(); parentRowPosition++) {
int rowIndex = underlyingLayer.getRowIndexByPosition(parentRowPosition);
if (!isRowIndexHidden(rowIndex)) {
this.cachedVisibleRowIndexPositionMapping.put(rowIndex, rowPosition);
this.cachedVisibleRowPositionIndexMapping.put(rowPosition, rowIndex);
rowPosition++;
} else {
this.cachedHiddenRowIndexPositionMapping.put(rowIndex, rowPosition);
}
}
}
}
}