blob: e305299043efcd551db65d5b65e69c5f3c53da2c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2018, 2020 Dirk Fauth.
*
* 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:
* Dirk Fauth <dirk.fauth@googlemail.com> - initial API and implementation
******************************************************************************/
package org.eclipse.nebula.widgets.nattable.datachange;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.nebula.widgets.nattable.coordinate.PositionUtil;
import org.eclipse.nebula.widgets.nattable.coordinate.Range;
import org.eclipse.nebula.widgets.nattable.datachange.event.KeyRowInsertEvent;
import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEventHandler;
import org.eclipse.nebula.widgets.nattable.layer.event.IStructuralChangeEvent;
import org.eclipse.nebula.widgets.nattable.layer.event.RowInsertEvent;
import org.eclipse.nebula.widgets.nattable.layer.event.StructuralDiff;
import org.eclipse.nebula.widgets.nattable.layer.event.StructuralDiff.DiffTypeEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@link DataChangeHandler} to track row insert operations. Registers as
* {@link ILayerEventHandler} for the {@link RowInsertEvent}. It is intended to
* be used with a configuration that directly updates the backing data.
* Temporary data storage is not supported. It therefore is able to perform
* discard operations and will do nothing on save.
*
* @since 1.6
*/
public class RowInsertDataChangeHandler extends AbstractDataChangeHandler<RowInsertDataChange> implements ILayerEventHandler<RowInsertEvent> {
private static final Logger LOG = LoggerFactory.getLogger(RowInsertDataChangeHandler.class);
/**
*
* @param layer
* The {@link DataChangeLayer} this handler should be assigned
* to.
* @param keyHandler
* The {@link CellKeyHandler} that is used to store dataChanges
* for a specific key.
*/
public RowInsertDataChangeHandler(DataChangeLayer layer, CellKeyHandler<?> keyHandler) {
super(layer, keyHandler, new ConcurrentHashMap<>());
}
@Override
public void handleStructuralChange(IStructuralChangeEvent structuralChangeEvent) {
if (structuralChangeEvent.isVerticalStructureChanged()
&& structuralChangeEvent.getRowDiffs() != null) {
if (this.keyHandler.updateOnVerticalStructuralChange()) {
Collection<StructuralDiff> structuralDiffs = structuralChangeEvent.getRowDiffs();
handleRowDelete(structuralDiffs);
handleRowInsert(structuralDiffs);
}
}
}
/**
* Will check for events that indicate that rows have been deleted. In that
* case the cached dataChanges need to be updated because the index of the
* rows might have changed. E.g. cell with row at index 3 is changed in the
* given layer, deleting row at index 1 will cause the row at index 3 to be
* moved to index 2. Without transforming the index regarding the delete
* event, the wrong cell at the incorrect row would be shown as changed.
*
* @param rowDiffs
* The collection of {@link StructuralDiff}s to handle.
*/
@SuppressWarnings("unchecked")
private void handleRowDelete(Collection<StructuralDiff> rowDiffs) {
// for correct calculation the diffs need to be processed from lowest
// position to highest
List<StructuralDiff> diffs = new ArrayList<>(rowDiffs);
Collections.sort(diffs, (o1, o2) -> o1.getBeforePositionRange().start - o2.getBeforePositionRange().start);
List<Integer> toRemove = new ArrayList<>();
for (StructuralDiff rowDiff : diffs) {
if (rowDiff.getDiffType() != null
&& rowDiff.getDiffType().equals(DiffTypeEnum.DELETE)) {
Range beforePositionRange = rowDiff.getBeforePositionRange();
for (int i = beforePositionRange.start; i < beforePositionRange.end; i++) {
int index = i;
if (index >= 0) {
toRemove.add(index);
}
}
}
}
if (!toRemove.isEmpty()) {
// modify row indexes regarding the deleted rows
Map<Object, RowInsertDataChange> modifiedRows = new HashMap<>();
for (Map.Entry<Object, RowInsertDataChange> entry : this.dataChanges.entrySet()) {
int rowIndex = this.keyHandler.getRowIndex(entry.getKey());
if (!toRemove.contains(rowIndex)) {
// check number of removed indexes that are lower than the
// current one
int deletedBefore = 0;
for (Integer removed : toRemove) {
if (removed < rowIndex) {
deletedBefore++;
}
}
int modRow = rowIndex - deletedBefore;
if (modRow >= 0) {
Object oldKey = entry.getKey();
Object updatedKey = this.keyHandler.getKeyWithRowUpdate(oldKey, modRow);
entry.getValue().updateKey(updatedKey);
modifiedRows.put(updatedKey, entry.getValue());
}
}
}
this.dataChanges.clear();
this.dataChanges.putAll(modifiedRows);
}
}
/**
* Will check for events that indicate that rows are added. In that case the
* cached dataChanges need to be updated because the index of the rows might
* have changed. E.g. Row with index 3 is hidden in the given layer, adding
* a row at index 1 will cause the row at index 3 to be moved to index 4.
* Without transforming the index regarding the add event, the wrong row
* would be hidden.
*
* @param rowDiffs
* The collection of {@link StructuralDiff}s to handle.
*/
@SuppressWarnings("unchecked")
private void handleRowInsert(Collection<StructuralDiff> rowDiffs) {
// for correct calculation the diffs need to be processed from highest
// position to lowest
List<StructuralDiff> diffs = new ArrayList<>(rowDiffs);
Collections.sort(diffs, (o1, o2) -> o2.getBeforePositionRange().start - o1.getBeforePositionRange().start);
for (StructuralDiff rowDiff : diffs) {
if (rowDiff.getDiffType() != null
&& rowDiff.getDiffType().equals(DiffTypeEnum.ADD)) {
Range beforePositionRange = rowDiff.getBeforePositionRange();
// modify row indexes regarding the inserted rows
Map<Object, RowInsertDataChange> modifiedRows = new HashMap<>();
for (Map.Entry<Object, RowInsertDataChange> entry : this.dataChanges.entrySet()) {
int rowIndex = this.keyHandler.getRowIndex(entry.getKey());
int modRow = -1;
if (rowIndex >= beforePositionRange.start) {
modRow = rowIndex + 1;
} else {
modRow = rowIndex;
}
Object updatedKey = this.keyHandler.getKeyWithRowUpdate(entry.getKey(), modRow);
entry.getValue().updateKey(updatedKey);
modifiedRows.put(updatedKey, entry.getValue());
}
this.dataChanges.clear();
this.dataChanges.putAll(modifiedRows);
}
}
}
@Override
public boolean isColumnDirty(int columnPosition) {
return !this.dataChanges.isEmpty();
}
@Override
public boolean isRowDirty(int rowPosition) {
// we do not care about the column position,
// so we use -1 for key generation
Object key = this.keyHandler.getKey(-1, rowPosition);
if (key != null) {
return this.dataChanges.containsKey(key);
}
return false;
}
@Override
public boolean isCellDirty(int columnPosition, int rowPosition) {
return isRowDirty(rowPosition);
}
@Override
public void handleLayerEvent(RowInsertEvent event) {
if (this.handleDataUpdate) {
synchronized (this.dataChanges) {
if (event instanceof KeyRowInsertEvent) {
KeyRowInsertEvent e = (KeyRowInsertEvent) event;
List<Object> keys = new ArrayList<>(e.getKeys());
Collections.reverse(keys);
for (Object key : keys) {
// store the change locally
this.dataChanges.put(key, new RowInsertDataChange(key, e.getKeyHandler()));
// store the change in the DataChangeLayer
this.layer.addDataChange(new RowInsertDataChange(key, e.getKeyHandler()));
}
} else {
// we need to ensure that the data changes are in correct
// order to ensure that deleting them again delete in the
// correct places when discarding backwards
int[] positions = PositionUtil.getPositions(event.getRowPositionRanges());
for (int i : positions) {
Object key = this.keyHandler.getKey(-1, i);
if (key != null) {
// store the change locally
this.dataChanges.put(key, new RowInsertDataChange(key, this.keyHandler));
// store the change in the DataChangeLayer
this.layer.addDataChange(new RowInsertDataChange(key, this.keyHandler));
} else {
LOG.warn("key was null for position {}", i); //$NON-NLS-1$
}
}
}
}
}
}
@Override
public Class<RowInsertEvent> getLayerEventClass() {
return RowInsertEvent.class;
}
}