blob: 10fe24d60aa6acd729a8fe777e996c696e2419a6 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012, 2019 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
******************************************************************************/
package org.eclipse.nebula.widgets.nattable.hideshow;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.TreeSet;
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.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;
public abstract class AbstractRowHideShowLayer extends AbstractLayerTransform implements IUniqueIndexLayer {
private Map<Integer, Integer> cachedVisibleRowIndexOrder;
private Map<Integer, Integer> cachedVisibleRowPositionOrder;
private Map<Integer, Integer> cachedHiddenRowIndexToPositionMap;
private final Map<Integer, Integer> startYCache = new HashMap<Integer, Integer>();
public AbstractRowHideShowLayer(IUniqueIndexLayer underlyingLayer) {
super(underlyingLayer);
}
@Override
public void handleLayerEvent(ILayerEvent event) {
if (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;
Collection<Integer> fromPositions = new TreeSet<Integer>();
for (int pos : reorderEvent.getBeforeFromRowIndexes()) {
fromPositions.add(getRowPositionByIndex(pos));
}
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.clear();
}
super.handleLayerEvent(event);
}
// Horizontal features
// Columns
@Override
public int getColumnPositionByIndex(int columnIndex) {
return ((IUniqueIndexLayer) getUnderlyingLayer()).getColumnPositionByIndex(columnIndex);
}
// Vertical features
// Rows
@Override
public int getRowCount() {
return getCachedVisibleRowIndexes().size();
}
@Override
public int getRowIndexByPosition(int rowPosition) {
if (rowPosition < 0 || rowPosition >= getRowCount()) {
return -1;
}
Integer rowIndex = getCachedVisibleRowPositons().get(rowPosition);
if (rowIndex != null) {
return rowIndex;
} else {
return -1;
}
}
@Override
public int getRowPositionByIndex(int rowIndex) {
final Integer position = getCachedVisibleRowIndexes().get(rowIndex);
return position != null ? position : -1;
}
public Collection<Integer> getRowPositionsByIndexes(Collection<Integer> rowIndexes) {
Collection<Integer> rowPositions = new HashSet<Integer>();
for (int rowIndex : rowIndexes) {
rowPositions.add(getRowPositionByIndex(rowIndex));
}
return rowPositions;
}
@Override
public int localToUnderlyingRowPosition(int localRowPosition) {
int rowIndex = getRowIndexByPosition(localRowPosition);
return ((IUniqueIndexLayer) getUnderlyingLayer()).getRowPositionByIndex(rowIndex);
}
@Override
public int underlyingToLocalRowPosition(ILayer sourceUnderlyingLayer, int underlyingRowPosition) {
int rowIndex = getUnderlyingLayer().getRowIndexByPosition(underlyingRowPosition);
int rowPosition = getRowPositionByIndex(rowIndex);
if (rowPosition >= 0) {
return rowPosition;
} else {
Integer hiddenRowPosition = getCachedHiddenRowIndexToPositionMap().get(rowIndex);
if (hiddenRowPosition != null) {
return hiddenRowPosition;
} else {
return -1;
}
}
}
@Override
public Collection<Range> underlyingToLocalRowPositions(
ILayer sourceUnderlyingLayer, Collection<Range> underlyingRowPositionRanges) {
Collection<Range> localRowPositionRanges =
new ArrayList<Range>(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) {
Integer cachedStartY = this.startYCache.get(localRowPosition);
if (cachedStartY != null) {
return cachedStartY;
}
IUniqueIndexLayer underlyingLayer = (IUniqueIndexLayer) getUnderlyingLayer();
int underlyingPosition = localToUnderlyingRowPosition(localRowPosition);
if (underlyingPosition < 0) {
return -1;
}
int underlyingStartY = underlyingLayer.getStartYOfRowPosition(underlyingPosition);
if (underlyingStartY < 0) {
return -1;
}
for (Integer hiddenIndex : getHiddenRowIndexes()) {
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. Note: 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.
*
* @return Collection of all row indexes that are hidden in this layer.
*/
public abstract Collection<Integer> getHiddenRowIndexes();
@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
boolean rowSpanUpdated = false;
int rowSpan = cell.getRowSpan();
for (int row = 0; row < cell.getRowSpan(); row++) {
int rowIndex = this.getRowIndexByPosition(cell.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.cachedVisibleRowIndexOrder = null;
this.cachedVisibleRowPositionOrder = null;
this.cachedHiddenRowIndexToPositionMap = null;
this.startYCache.clear();
}
private synchronized Map<Integer, Integer> getCachedVisibleRowIndexes() {
if (this.cachedVisibleRowIndexOrder == null) {
cacheVisibleRowIndexes();
}
return Collections.unmodifiableMap(this.cachedVisibleRowIndexOrder);
}
private synchronized Map<Integer, Integer> getCachedVisibleRowPositons() {
if (this.cachedVisibleRowPositionOrder == null) {
cacheVisibleRowIndexes();
}
return Collections.unmodifiableMap(this.cachedVisibleRowPositionOrder);
}
private synchronized Map<Integer, Integer> getCachedHiddenRowIndexToPositionMap() {
if (this.cachedHiddenRowIndexToPositionMap == null) {
cacheVisibleRowIndexes();
}
return Collections.unmodifiableMap(this.cachedHiddenRowIndexToPositionMap);
}
protected synchronized void cacheVisibleRowIndexes() {
this.cachedVisibleRowIndexOrder = new HashMap<Integer, Integer>();
this.cachedVisibleRowPositionOrder = new HashMap<Integer, Integer>();
this.cachedHiddenRowIndexToPositionMap = new HashMap<Integer, Integer>();
this.startYCache.clear();
ILayer underlyingLayer = getUnderlyingLayer();
int rowPosition = 0;
for (int parentRowPosition = 0; parentRowPosition < underlyingLayer.getRowCount(); parentRowPosition++) {
int rowIndex = underlyingLayer.getRowIndexByPosition(parentRowPosition);
if (!isRowIndexHidden(rowIndex)) {
this.cachedVisibleRowIndexOrder.put(rowIndex, rowPosition);
this.cachedVisibleRowPositionOrder.put(rowPosition, rowIndex);
rowPosition++;
} else {
this.cachedHiddenRowIndexToPositionMap.put(rowIndex, rowPosition);
}
}
}
}