| /******************************************************************************* |
| * Copyright (c) 2014, 2020 Jonas Hugo, Markus Wahl, 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: |
| * Jonas Hugo <Jonas.Hugo@jeppesen.com>, |
| * Markus Wahl <Markus.Wahl@jeppesen.com> - initial API and implementation |
| * Dirk Fauth <dirk.fauth@googlemail.com> - Bug 453851, 446275, 447396 |
| ******************************************************************************/ |
| package org.eclipse.nebula.widgets.nattable.selection.preserve; |
| |
| import java.io.Serializable; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.concurrent.locks.ReadWriteLock; |
| import java.util.concurrent.locks.ReentrantReadWriteLock; |
| |
| import org.eclipse.nebula.widgets.nattable.coordinate.Range; |
| import org.eclipse.nebula.widgets.nattable.data.IRowDataProvider; |
| import org.eclipse.nebula.widgets.nattable.data.IRowIdAccessor; |
| import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer; |
| import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell; |
| import org.eclipse.nebula.widgets.nattable.layer.event.IStructuralChangeEvent; |
| import org.eclipse.nebula.widgets.nattable.layer.event.ResizeStructuralRefreshEvent; |
| import org.eclipse.nebula.widgets.nattable.layer.event.StructuralDiff; |
| import org.eclipse.nebula.widgets.nattable.layer.event.StructuralDiff.DiffTypeEnum; |
| import org.eclipse.nebula.widgets.nattable.reorder.event.ColumnReorderEvent; |
| import org.eclipse.nebula.widgets.nattable.resize.event.ColumnResizeEvent; |
| import org.eclipse.nebula.widgets.nattable.resize.event.RowResizeEvent; |
| import org.eclipse.nebula.widgets.nattable.selection.IMarkerSelectionModel; |
| import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer; |
| import org.eclipse.nebula.widgets.nattable.selection.SelectionUtils; |
| import org.eclipse.nebula.widgets.nattable.selection.preserve.Selections.CellPosition; |
| import org.eclipse.nebula.widgets.nattable.selection.preserve.Selections.Row; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.graphics.Rectangle; |
| |
| /** |
| * Individual cell selection model that copes with the reordering of rows. |
| * |
| * @param <T> |
| * the type of object underlying each row |
| */ |
| public class PreserveSelectionModel<T> implements IMarkerSelectionModel { |
| |
| /** |
| * Provider of cell information. |
| * |
| * @since 1.6 |
| */ |
| protected final IUniqueIndexLayer selectionLayer; |
| |
| /** |
| * Provider of underlying row objects |
| */ |
| private final IRowDataProvider<T> rowDataProvider; |
| |
| /** |
| * Provider of unique IDs for the rows |
| */ |
| private final IRowIdAccessor<T> rowIdAccessor; |
| |
| /** |
| * Whether to allow multiple selections |
| */ |
| private boolean allowMultiSelection; |
| |
| /** |
| * The selected cells |
| */ |
| private Selections<T> selections = new Selections<T>(); |
| |
| /** |
| * Lock for ensuring thread safety |
| */ |
| private final ReadWriteLock selectionsLock; |
| |
| /** |
| * Position of the selection anchor marker, expressed in row object and |
| * column position |
| */ |
| CellPosition<T> selectionAnchor; |
| |
| /** |
| * The selection anchor point calculated out of the selection anchor marker. |
| * Tracked here to reduce the number of calculations on rendering. |
| */ |
| Point selectionAnchorPoint; |
| |
| /** |
| * Position of the last selected cell marker, expressed in row object and |
| * column position |
| */ |
| CellPosition<T> lastSelectedCell; |
| |
| /** |
| * Area of the last selected region marker, expressed in row position, |
| * column position, number of column width and number of rows height |
| */ |
| Rectangle lastSelectedRegion; |
| |
| /** |
| * The row object of the origin of the last selected region |
| */ |
| T lastSelectedRegionOriginRowObject; |
| |
| /** |
| * Creates a row sortable selection model |
| * |
| * @param selectionLayer |
| * provider of cell information |
| * @param rowDataProvider |
| * provider of underlying row objects |
| * @param rowIdAccessor |
| * provider of unique IDs for the rows |
| */ |
| public PreserveSelectionModel( |
| IUniqueIndexLayer selectionLayer, |
| IRowDataProvider<T> rowDataProvider, |
| IRowIdAccessor<T> rowIdAccessor) { |
| this.selectionLayer = selectionLayer; |
| this.rowDataProvider = rowDataProvider; |
| this.rowIdAccessor = rowIdAccessor; |
| this.allowMultiSelection = true; |
| this.selectionsLock = new ReentrantReadWriteLock(); |
| } |
| |
| @Override |
| public boolean isMultipleSelectionAllowed() { |
| return this.allowMultiSelection; |
| } |
| |
| @Override |
| public void setMultipleSelectionAllowed(boolean multipleSelectionAllowed) { |
| this.allowMultiSelection = multipleSelectionAllowed; |
| } |
| |
| @Override |
| public void addSelection(int columnPosition, int rowPosition) { |
| this.selectionsLock.writeLock().lock(); |
| try { |
| if (!this.allowMultiSelection) { |
| clearSelection(); |
| } |
| |
| Serializable rowId = getRowIdByPosition(rowPosition); |
| if (rowId != null) { |
| T rowObject = getRowObjectByPosition(rowPosition); |
| this.selections.select(rowId, rowObject, columnPosition); |
| } |
| } finally { |
| this.selectionsLock.writeLock().unlock(); |
| } |
| } |
| |
| @Override |
| public void addSelection(Rectangle range) { |
| this.selectionsLock.writeLock().lock(); |
| try { |
| if (!this.allowMultiSelection) { |
| clearSelection(); |
| } |
| |
| int startColumnPosition = range.x; |
| int startRowPosition = range.y; |
| if (startColumnPosition < this.selectionLayer.getColumnCount() && startRowPosition < this.selectionLayer.getRowCount()) { |
| int numberOfVisibleColumnsToBeSelected = getNumberOfColumnsToBeSelected(range); |
| int numberOfVisibleRowsToBeSelected = getNumberOfRowsToBeSelected(range); |
| |
| for (int rowPosition = startRowPosition; rowPosition < startRowPosition + numberOfVisibleRowsToBeSelected; rowPosition++) { |
| Serializable rowId = getRowIdByPosition(rowPosition); |
| if (rowId != null) { |
| T rowObject = getRowObjectByPosition(rowPosition); |
| for (int columnPosition = startColumnPosition; columnPosition < (startColumnPosition + numberOfVisibleColumnsToBeSelected); columnPosition++) { |
| this.selections.select(rowId, rowObject, columnPosition); |
| } |
| } |
| } |
| } |
| |
| } finally { |
| this.selectionsLock.writeLock().unlock(); |
| } |
| } |
| |
| @Override |
| public void clearSelection() { |
| this.selectionsLock.writeLock().lock(); |
| try { |
| this.selections.clear(); |
| } finally { |
| this.selectionsLock.writeLock().unlock(); |
| } |
| } |
| |
| @Override |
| public void clearSelection(int columnPosition, int rowPosition) { |
| this.selectionsLock.writeLock().lock(); |
| try { |
| Serializable rowId = getRowIdByPosition(rowPosition); |
| if (rowId != null) { |
| this.selections.deselect(rowId, columnPosition); |
| } |
| } finally { |
| this.selectionsLock.writeLock().unlock(); |
| } |
| } |
| |
| @Override |
| public void clearSelection(Rectangle removedSelection) { |
| this.selectionsLock.writeLock().lock(); |
| try { |
| int startColumnPosition = removedSelection.x; |
| int startRowPosition = removedSelection.y; |
| if (startColumnPosition < this.selectionLayer.getColumnCount() && startRowPosition < this.selectionLayer.getRowCount()) { |
| int numberOfVisibleColumnsToBeSelected = getNumberOfColumnsToBeSelected(removedSelection); |
| int numberOfVisibleRowsToBeSelected = getNumberOfRowsToBeSelected(removedSelection); |
| |
| for (int rowPosition = startRowPosition; rowPosition < startRowPosition + numberOfVisibleRowsToBeSelected; rowPosition++) { |
| Serializable rowId = getRowIdByPosition(rowPosition); |
| if (rowId != null) { |
| for (int columnPosition = startColumnPosition; columnPosition < startColumnPosition + numberOfVisibleColumnsToBeSelected; columnPosition++) { |
| this.selections.deselect(rowId, columnPosition); |
| } |
| } |
| } |
| } |
| |
| } finally { |
| this.selectionsLock.writeLock().unlock(); |
| } |
| } |
| |
| /** |
| * Return the number of columns to select. Determines the number based on |
| * the columns that are known. Needed in case of full row selection as the |
| * range will be from 0 to {@link Integer#MAX_VALUE}, where only the range |
| * from 0 to total column count is needed. |
| * |
| * @param selection |
| * The rectangle that should be selected. |
| * @return The number of columns to select. |
| */ |
| private int getNumberOfColumnsToBeSelected(Rectangle selection) { |
| int columnCount = this.selectionLayer.getColumnCount(); |
| return (selection.x + selection.width <= columnCount) ? selection.width : columnCount; |
| } |
| |
| /** |
| * Return the number of rows to select. Determines the number based on the |
| * rows that are known. Needed in case of full column selection as the range |
| * will be from 0 to {@link Integer#MAX_VALUE}, where only the range from 0 |
| * to total row count is needed. |
| * |
| * @param selection |
| * The rectangle that should be selected. |
| * @return The number of rows to select. |
| */ |
| private int getNumberOfRowsToBeSelected(Rectangle selection) { |
| int rowCount = this.selectionLayer.getRowCount(); |
| return (selection.y + selection.height <= rowCount) ? selection.height : rowCount; |
| } |
| |
| @Override |
| public boolean isEmpty() { |
| this.selectionsLock.readLock().lock(); |
| try { |
| return this.selections.isEmpty(); |
| } finally { |
| this.selectionsLock.readLock().unlock(); |
| } |
| } |
| |
| @Override |
| public List<Rectangle> getSelections() { |
| ArrayList<Rectangle> selectedCells = null; |
| |
| this.selectionsLock.readLock().lock(); |
| try { |
| Collection<CellPosition<T>> selectedPositions = this.selections.getSelections(); |
| selectedCells = new ArrayList<Rectangle>(selectedPositions.size()); |
| for (CellPosition<T> cellPosition : selectedPositions) { |
| int rowPosition = getRowPositionByRowObject(cellPosition.getRowObject()); |
| if (isRowVisible(rowPosition)) { |
| Integer columnPosition = cellPosition.getColumnPosition(); |
| Rectangle selectedCell = new Rectangle(columnPosition, rowPosition, 1, 1); |
| selectedCells.add(selectedCell); |
| } |
| } |
| } finally { |
| this.selectionsLock.readLock().unlock(); |
| } |
| return selectedCells != null ? selectedCells : new ArrayList<Rectangle>(); |
| } |
| |
| /** |
| * Determines if rowPosition represents a visible row |
| * |
| * @param rowPosition |
| * position of row to inspect |
| * @return whether rowPosition represents a visible row |
| */ |
| private boolean isRowVisible(int rowPosition) { |
| return rowPosition != -1; |
| } |
| |
| @Override |
| public boolean isCellPositionSelected(int columnPosition, int rowPosition) { |
| this.selectionsLock.readLock().lock(); |
| |
| try { |
| ILayerCell cell = this.selectionLayer.getCellByPosition(columnPosition, rowPosition); |
| int cellOriginRowPosition = cell.getOriginRowPosition(); |
| |
| for (int candidateRowPosition = cellOriginRowPosition; candidateRowPosition < cellOriginRowPosition + cell.getRowSpan(); candidateRowPosition++) { |
| Serializable rowId = getRowIdByPosition(candidateRowPosition); |
| |
| int cellOriginColumnPosition = cell.getOriginColumnPosition(); |
| for (int candidateColumnPosition = cellOriginColumnPosition; candidateColumnPosition < cellOriginColumnPosition + cell.getColumnSpan(); candidateColumnPosition++) { |
| if (this.selections.isSelected(rowId, candidateColumnPosition)) { |
| return true; |
| } |
| |
| } |
| } |
| } finally { |
| this.selectionsLock.readLock().unlock(); |
| } |
| return false; |
| } |
| |
| @Override |
| public int[] getSelectedColumnPositions() { |
| this.selectionsLock.readLock().lock(); |
| try { |
| return this.selections.getColumnPositions(); |
| } finally { |
| this.selectionsLock.readLock().unlock(); |
| } |
| } |
| |
| @Override |
| public boolean isColumnPositionSelected(int columnPosition) { |
| this.selectionsLock.readLock().lock(); |
| try { |
| for (Selections.Row<T> row : this.selections.getRows()) { |
| if (row.contains(columnPosition)) { |
| return true; |
| } |
| } |
| |
| } finally { |
| this.selectionsLock.readLock().unlock(); |
| } |
| return false; |
| } |
| |
| @Override |
| public int[] getFullySelectedColumnPositions(int columnHeight) { |
| this.selectionsLock.readLock().lock(); |
| try { |
| return Arrays.stream(this.selections.getColumnPositions()) |
| .filter(selectedColumn -> isColumnPositionFullySelected(selectedColumn, columnHeight)) |
| .toArray(); |
| } finally { |
| this.selectionsLock.readLock().unlock(); |
| } |
| } |
| |
| @Override |
| public boolean isColumnPositionFullySelected(int columnPosition, int columnHeight) { |
| this.selectionsLock.readLock().lock(); |
| try { |
| Selections.Column selectedRowsInColumn = this.selections.getSelectedRows(columnPosition); |
| if (hasColumnsSelectedRows(selectedRowsInColumn)) { |
| return selectedRowsInColumn.getItems().size() >= columnHeight; |
| } |
| return false; |
| } finally { |
| this.selectionsLock.readLock().unlock(); |
| } |
| } |
| |
| /** |
| * Determines if there are selected cells in a column |
| * |
| * @param column |
| * collections of selected cells for a column |
| * @return whether there are selected cells in column |
| */ |
| private boolean hasColumnsSelectedRows(Selections.Column column) { |
| return column != null; |
| } |
| |
| @Override |
| public int getSelectedRowCount() { |
| this.selectionsLock.readLock().lock(); |
| try { |
| return this.selections.getRows().size(); |
| } finally { |
| this.selectionsLock.readLock().unlock(); |
| } |
| } |
| |
| @Override |
| public Set<Range> getSelectedRowPositions() { |
| HashSet<Range> visiblySelectedRowPositions = new HashSet<Range>(); |
| |
| this.selectionsLock.readLock().lock(); |
| try { |
| for (Selections.Row<T> row : this.selections.getRows()) { |
| int rowPosition = getRowPositionByRowObject(row.getRowObject()); |
| if (isRowVisible(rowPosition)) { |
| visiblySelectedRowPositions.add(new Range(rowPosition, rowPosition + 1)); |
| } |
| } |
| } finally { |
| this.selectionsLock.readLock().unlock(); |
| } |
| return visiblySelectedRowPositions; |
| } |
| |
| @Override |
| public boolean isRowPositionSelected(int rowPosition) { |
| this.selectionsLock.readLock().lock(); |
| try { |
| Serializable rowId = getRowIdByPosition(rowPosition); |
| return this.selections.isRowSelected(rowId); |
| } finally { |
| this.selectionsLock.readLock().unlock(); |
| } |
| } |
| |
| @Override |
| public int[] getFullySelectedRowPositions(int rowWidth) { |
| this.selectionsLock.readLock().lock(); |
| try { |
| return this.selections.getRows().stream() |
| .map(Selections.Row::getRowObject) |
| .mapToInt(this::getRowPositionByRowObject) |
| .filter(rowPosition -> (isRowVisible(rowPosition) |
| && isRowPositionFullySelected(rowPosition, rowWidth))) |
| .sorted() |
| .toArray(); |
| } finally { |
| this.selectionsLock.readLock().unlock(); |
| } |
| } |
| |
| @Override |
| public boolean isRowPositionFullySelected(int rowPosition, int rowWidth) { |
| int[] selectedColumnPositions = null; |
| |
| this.selectionsLock.readLock().lock(); |
| try { |
| Serializable rowId = getRowIdByPosition(rowPosition); |
| if (rowId != null) { |
| Selections.Row<T> selectedColumnsInRow = this.selections.getSelectedColumns(rowId); |
| if (hasRowSelectedColumns(selectedColumnsInRow)) { |
| selectedColumnPositions = selectedColumnsInRow.getItems().stream() |
| .mapToInt(Integer::intValue) |
| .toArray(); |
| } |
| } |
| } finally { |
| this.selectionsLock.readLock().unlock(); |
| } |
| |
| return (selectedColumnPositions == null || selectedColumnPositions.length < rowWidth) |
| ? false |
| : SelectionUtils.isConsecutive(selectedColumnPositions); |
| } |
| |
| /** |
| * Determines if there are selected cells in a row |
| * |
| * @param row |
| * collections of selected cells for a row |
| * @return whether there are selected cells in row |
| */ |
| private boolean hasRowSelectedColumns(Selections.Row<T> row) { |
| return row != null; |
| } |
| |
| /** |
| * Retrieves the row ID for a row position |
| * |
| * @param rowPosition |
| * row position for retrieving row ID |
| * @return row ID for rowPosition, or null if undefined |
| * |
| * @since 1.6 |
| */ |
| protected Serializable getRowIdByPosition(int rowPosition) { |
| T rowObject = getRowObjectByPosition(rowPosition); |
| if (rowObject != null) { |
| return this.rowIdAccessor.getRowId(rowObject); |
| } |
| return null; |
| } |
| |
| /** |
| * Retrieves the row object for a row position |
| * |
| * @param rowPosition |
| * row position for retrieving row object |
| * @return row object for rowPosition, or null if undefined |
| */ |
| private T getRowObjectByPosition(int rowPosition) { |
| int rowIndex = this.selectionLayer.getRowIndexByPosition(rowPosition); |
| if (rowIndex >= 0) { |
| try { |
| return this.rowDataProvider.getRowObject(rowIndex); |
| } catch (Exception e) { |
| // row index is invalid for the data provider |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Retrieves the row position for a row object |
| * |
| * @param rowObject |
| * row object for retrieving row position |
| * @return row position for rowObject, or -1 if undefined |
| * |
| * @since 1.6 |
| */ |
| protected int getRowPositionByRowObject(T rowObject) { |
| int rowIndex = this.rowDataProvider.indexOfRowObject(rowObject); |
| if (rowIndex == -1) { |
| return -1; |
| } |
| return this.selectionLayer.getRowPositionByIndex(rowIndex); |
| } |
| |
| @Override |
| public Point getSelectionAnchor() { |
| if (this.selectionAnchorPoint == null) { |
| this.selectionsLock.readLock().lock(); |
| try { |
| this.selectionAnchorPoint = createMarkerPoint(this.selectionAnchor); |
| } finally { |
| this.selectionsLock.readLock().unlock(); |
| } |
| } |
| return this.selectionAnchorPoint; |
| } |
| |
| @Override |
| public Point getLastSelectedCell() { |
| this.selectionsLock.readLock().lock(); |
| try { |
| return createMarkerPoint(this.lastSelectedCell); |
| } finally { |
| this.selectionsLock.readLock().unlock(); |
| } |
| } |
| |
| /** |
| * Creates a point from a cell position. The point is expressed in row |
| * position and column position. The row position is calculated by |
| * translating the row object of the cell position. It uses the column |
| * position of the cell position without translation. |
| * |
| * @param cellPosition |
| * cell position to translate into a point |
| * @return cellPosition expressed in row position and column position |
| */ |
| private Point createMarkerPoint(CellPosition<T> cellPosition) { |
| if (cellPosition == null |
| || (cellPosition.getColumnPosition() == SelectionLayer.NO_SELECTION && cellPosition.getRowObject() == null)) { |
| return createUndefinedPoint(); |
| } |
| int rowPosition = getRowPositionByRowObject(cellPosition.getRowObject()); |
| return new Point(cellPosition.getColumnPosition(), rowPosition); |
| } |
| |
| /** |
| * Creates an undefined point, using the SelectionLayer.NO_SELECTION |
| * constant. |
| * |
| * @return an undefined point |
| */ |
| private Point createUndefinedPoint() { |
| return new Point(SelectionLayer.NO_SELECTION, SelectionLayer.NO_SELECTION); |
| } |
| |
| @Override |
| public Rectangle getLastSelectedRegion() { |
| this.selectionsLock.readLock().lock(); |
| try { |
| if (this.lastSelectedRegion == null) { |
| return null; |
| } else { |
| correctLastSelectedRegion(); |
| return this.lastSelectedRegion; |
| } |
| } finally { |
| this.selectionsLock.readLock().unlock(); |
| } |
| } |
| |
| /** |
| * Corrects the last selected region by moving it so that it is originating |
| * on the row position identified by the last selected region origin row |
| * object. |
| */ |
| private void correctLastSelectedRegion() { |
| this.lastSelectedRegion.y = getRowPositionByRowObject(this.lastSelectedRegionOriginRowObject); |
| } |
| |
| @Override |
| public void setSelectionAnchor(Point coordinate) { |
| this.selectionsLock.writeLock().lock(); |
| try { |
| if (coordinate.x == SelectionLayer.NO_SELECTION |
| && coordinate.y == SelectionLayer.NO_SELECTION) { |
| this.selectionAnchor = null; |
| } else { |
| this.selectionAnchor = |
| new CellPosition<T>(getRowObjectByPosition(coordinate.y), coordinate.x); |
| } |
| } finally { |
| this.selectionsLock.writeLock().unlock(); |
| this.selectionAnchorPoint = null; |
| } |
| } |
| |
| @Override |
| public void setLastSelectedCell(Point coordinate) { |
| this.selectionsLock.writeLock().lock(); |
| try { |
| if (coordinate.x == SelectionLayer.NO_SELECTION |
| && coordinate.y == SelectionLayer.NO_SELECTION) { |
| this.lastSelectedCell = null; |
| } else { |
| this.lastSelectedCell = |
| new CellPosition<T>(getRowObjectByPosition(coordinate.y), coordinate.x); |
| } |
| } finally { |
| this.selectionsLock.writeLock().unlock(); |
| } |
| } |
| |
| @Override |
| public void setLastSelectedRegion(Rectangle region) { |
| // clear the selection in the current last selected region |
| if (region != null && this.lastSelectedRegion != null) { |
| clearSelection(this.lastSelectedRegion); |
| } |
| |
| this.selectionsLock.writeLock().lock(); |
| try { |
| |
| this.lastSelectedRegion = region; |
| |
| if (region != null) { |
| this.lastSelectedRegionOriginRowObject = getRowObjectByPosition(region.y); |
| } |
| } finally { |
| this.selectionsLock.writeLock().unlock(); |
| } |
| } |
| |
| @Override |
| public void setLastSelectedRegion(int x, int y, int width, int height) { |
| this.selectionsLock.writeLock().lock(); |
| try { |
| this.lastSelectedRegion.x = x; |
| this.lastSelectedRegion.y = y; |
| this.lastSelectedRegion.width = width; |
| this.lastSelectedRegion.height = height; |
| |
| this.lastSelectedRegionOriginRowObject = getRowObjectByPosition(y); |
| } finally { |
| this.selectionsLock.writeLock().unlock(); |
| } |
| } |
| |
| @Override |
| public void handleLayerEvent(IStructuralChangeEvent event) { |
| // we are not interested in resize events |
| if (event instanceof ResizeStructuralRefreshEvent |
| || event instanceof ColumnResizeEvent |
| || event instanceof RowResizeEvent) { |
| return; |
| } |
| |
| // ensure the selection anchor point is calculated on the next access |
| this.selectionAnchorPoint = null; |
| |
| // handling for deleting columns |
| if (event.isHorizontalStructureChanged()) { |
| Collection<StructuralDiff> diffs = event.getColumnDiffs(); |
| if (diffs != null) { |
| // first handle deletion, then handle insert |
| // this is to avoid mixed operations that might lead to |
| // confusing indexes |
| List<Integer> removed = new ArrayList<Integer>(); |
| for (StructuralDiff columnDiff : diffs) { |
| if (columnDiff.getDiffType() != null |
| && columnDiff.getDiffType().equals(DiffTypeEnum.DELETE)) { |
| Range beforePositionRange = columnDiff.getBeforePositionRange(); |
| // first de-select removed columns |
| for (int i = beforePositionRange.start; i < beforePositionRange.end; i++) { |
| if (!(event instanceof ColumnReorderEvent)) { |
| // in case the column was reordered we don't |
| // want to deselect the column |
| this.selections.deselectColumn(i); |
| } |
| removed.add(i); |
| } |
| } |
| } |
| // now update still visible column selections |
| Collections.sort(removed); |
| int mod = 0; |
| for (int i : removed) { |
| // ask for further column selections that need to be |
| // modified |
| this.selections.updateColumnsForRemoval(i - mod); |
| mod++; |
| } |
| |
| for (StructuralDiff columnDiff : diffs) { |
| if (columnDiff.getDiffType() != null |
| && columnDiff.getDiffType().equals(DiffTypeEnum.ADD)) { |
| Range afterPositionRange = columnDiff.getAfterPositionRange(); |
| for (int i = afterPositionRange.start; i < afterPositionRange.end; i++) { |
| // ask for column selections that need to be |
| // modified |
| this.selections.updateColumnsForAddition(i); |
| } |
| } |
| } |
| } |
| } |
| |
| // handling for deleting rows |
| if (event.isVerticalStructureChanged()) { |
| // the change is already done and we don't know about indexes, so we |
| // need to check if the selected objects still exist |
| Collection<Serializable> keysToRemove = new ArrayList<Serializable>(); |
| for (Selections.Row<T> row : this.selections.getRows()) { |
| if (!ignoreVerticalChange(row)) { |
| int rowIndex = this.rowDataProvider.indexOfRowObject(row.getRowObject()); |
| if (rowIndex == -1 || this.selectionLayer.getRowPositionByIndex(rowIndex) == -1) { |
| keysToRemove.add(row.getId()); |
| } |
| } |
| } |
| |
| for (Serializable toRemove : keysToRemove) { |
| this.selections.deselectRow(toRemove); |
| } |
| } |
| } |
| |
| /** |
| * Check if the default handling for vertical structure changes should be |
| * performed for the given {@link Row}, or if it should be skipped. Skipping |
| * for example would make sense for selections that are stored for rows that |
| * have no row data in the backing data structure, e.g. a summary row |
| * selection. |
| * |
| * @param row |
| * The internal selected row representation. |
| * @return <code>false</code> if the default handling for vertical changes |
| * should be performed, <code>true</code> if the default handling |
| * should be skipped. |
| * |
| * @since 1.6 |
| */ |
| protected boolean ignoreVerticalChange(Selections.Row<T> row) { |
| return false; |
| } |
| |
| @Override |
| public Class<IStructuralChangeEvent> getLayerEventClass() { |
| return IStructuralChangeEvent.class; |
| } |
| } |