| /******************************************************************************* |
| * 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.hideshow; |
| |
| import java.io.Serializable; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.Set; |
| import java.util.StringTokenizer; |
| import java.util.TreeMap; |
| import java.util.stream.Collectors; |
| |
| import org.eclipse.collections.api.set.primitive.MutableIntSet; |
| import org.eclipse.collections.impl.factory.primitive.IntSets; |
| import org.eclipse.nebula.widgets.nattable.command.ILayerCommand; |
| import org.eclipse.nebula.widgets.nattable.data.IRowDataProvider; |
| import org.eclipse.nebula.widgets.nattable.data.IRowIdAccessor; |
| import org.eclipse.nebula.widgets.nattable.data.convert.IDisplayConverter; |
| import org.eclipse.nebula.widgets.nattable.hideshow.command.HideRowByIndexCommandHandler; |
| import org.eclipse.nebula.widgets.nattable.hideshow.command.MultiRowHideCommandHandler; |
| import org.eclipse.nebula.widgets.nattable.hideshow.command.MultiRowShowCommandHandler; |
| import org.eclipse.nebula.widgets.nattable.hideshow.command.RowHideCommandHandler; |
| import org.eclipse.nebula.widgets.nattable.hideshow.command.RowPositionHideCommandHandler; |
| import org.eclipse.nebula.widgets.nattable.hideshow.command.RowShowCommandHandler; |
| import org.eclipse.nebula.widgets.nattable.hideshow.command.ShowAllRowsCommandHandler; |
| import org.eclipse.nebula.widgets.nattable.hideshow.event.HideRowPositionsEvent; |
| import org.eclipse.nebula.widgets.nattable.hideshow.event.ShowRowPositionsEvent; |
| import org.eclipse.nebula.widgets.nattable.hideshow.indicator.HideIndicatorConstants; |
| import org.eclipse.nebula.widgets.nattable.layer.ILayer; |
| import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer; |
| import org.eclipse.nebula.widgets.nattable.layer.LabelStack; |
| import org.eclipse.nebula.widgets.nattable.persistence.IPersistable; |
| import org.eclipse.nebula.widgets.nattable.sort.command.SortColumnCommand; |
| |
| /** |
| * {@link ILayer} that supports hiding of rows based on the row id. This way the |
| * hidden row is identified even on sorting and filtering. This is different to |
| * the {@link RowHideShowLayer} that handles row hiding via row index. |
| * |
| * @since 1.6 |
| */ |
| public class RowIdHideShowLayer<T> extends AbstractRowHideShowLayer implements IRowHideShowLayer { |
| |
| public static final String PERSISTENCE_KEY_HIDDEN_ROW_IDS = ".hiddenRowIDs"; //$NON-NLS-1$ |
| |
| protected final IRowDataProvider<T> rowDataProvider; |
| protected final IRowIdAccessor<T> rowIdAccessor; |
| |
| protected Map<Serializable, T> hiddenRows = new TreeMap<Serializable, T>(); |
| |
| protected IDisplayConverter idConverter; |
| |
| /** |
| * |
| * @param underlyingLayer |
| * The underlying layer. |
| * @param rowDataProvider |
| * The body data provider needed to get the row object by index |
| * to determine the row id. |
| * @param rowIdAccessor |
| * The {@link IRowIdAccessor} needed to extract the row id of a |
| * row object. |
| */ |
| public RowIdHideShowLayer(IUniqueIndexLayer underlyingLayer, IRowDataProvider<T> rowDataProvider, IRowIdAccessor<T> rowIdAccessor) { |
| super(underlyingLayer); |
| this.rowDataProvider = rowDataProvider; |
| this.rowIdAccessor = rowIdAccessor; |
| |
| registerCommandHandler(new MultiRowHideCommandHandler(this)); |
| registerCommandHandler(new RowHideCommandHandler(this)); |
| registerCommandHandler(new ShowAllRowsCommandHandler(this)); |
| registerCommandHandler(new MultiRowShowCommandHandler(this)); |
| registerCommandHandler(new RowPositionHideCommandHandler(this)); |
| registerCommandHandler(new RowShowCommandHandler(this)); |
| registerCommandHandler(new HideRowByIndexCommandHandler(this)); |
| } |
| |
| @Override |
| public boolean doCommand(ILayerCommand command) { |
| // in case we receive a SortColumnCommand we expect that the row |
| // ordering changes. We therefore pro-actively invalidate the cache to |
| // avoid flickering when the update is triggered with a 100ms delay by |
| // the GlazedListsEventLayer. |
| if (command instanceof SortColumnCommand) { |
| invalidateCache(); |
| } |
| return super.doCommand(command); |
| } |
| |
| // Persistence |
| |
| @Override |
| public void saveState(String prefix, Properties properties) { |
| if (this.hiddenRows.size() > 0) { |
| StringBuilder strBuilder = new StringBuilder(); |
| for (Serializable id : this.hiddenRows.keySet()) { |
| if (this.idConverter != null) { |
| strBuilder.append(this.idConverter.canonicalToDisplayValue(id)); |
| } else { |
| strBuilder.append(id.toString()); |
| } |
| strBuilder.append(IPersistable.VALUE_SEPARATOR); |
| } |
| properties.setProperty(prefix + PERSISTENCE_KEY_HIDDEN_ROW_IDS, strBuilder.toString()); |
| } |
| |
| super.saveState(prefix, properties); |
| } |
| |
| @Override |
| public void loadState(String prefix, Properties properties) { |
| this.hiddenRows.clear(); |
| String property = properties.getProperty(prefix + PERSISTENCE_KEY_HIDDEN_ROW_IDS); |
| if (property != null) { |
| StringTokenizer tok = new StringTokenizer(property, IPersistable.VALUE_SEPARATOR); |
| Set<Serializable> ids = new HashSet<Serializable>(); |
| while (tok.hasMoreTokens()) { |
| String id = tok.nextToken(); |
| if (this.idConverter != null) { |
| ids.add((Serializable) this.idConverter.displayToCanonicalValue(id)); |
| } else { |
| ids.add(id); |
| } |
| } |
| |
| for (int row = 0; row < this.rowDataProvider.getRowCount(); row++) { |
| T rowObject = this.rowDataProvider.getRowObject(row); |
| Serializable rowId = this.rowIdAccessor.getRowId(rowObject); |
| if (ids.contains(rowId)) { |
| ids.remove(rowId); |
| this.hiddenRows.put(rowId, rowObject); |
| } |
| if (ids.isEmpty()) { |
| // if no more row ids are hidden we do not need to iterate |
| // to the end |
| break; |
| } |
| } |
| } |
| |
| super.loadState(prefix, properties); |
| } |
| |
| @Override |
| public LabelStack getConfigLabelsByPosition(int columnPosition, int rowPosition) { |
| LabelStack configLabels = super.getConfigLabelsByPosition(columnPosition, rowPosition); |
| |
| // we need to check the hidden state of an adjacent position via the |
| // underlying layer as in the hide layer the position might be |
| // hidden |
| int underlyingPosition = localToUnderlyingRowPosition(rowPosition); |
| int upRowIndex = this.underlyingLayer.getRowIndexByPosition(underlyingPosition - 1); |
| if (isRowIndexHidden(upRowIndex)) { |
| configLabels.addLabel(HideIndicatorConstants.ROW_TOP_HIDDEN); |
| } |
| |
| int downRowIndex = this.underlyingLayer.getRowIndexByPosition(underlyingPosition + 1); |
| if (isRowIndexHidden(downRowIndex)) { |
| configLabels.addLabel(HideIndicatorConstants.ROW_BOTTOM_HIDDEN); |
| } |
| |
| return configLabels; |
| } |
| |
| @Override |
| public int getRowCount() { |
| // faster implementation than the super implementation because |
| // getHiddenRowIndexes() is calculating the row indexes everytime |
| return this.underlyingLayer.getRowCount() - this.hiddenRows.size(); |
| } |
| |
| // Hide/show |
| |
| @Override |
| public boolean isRowIndexHidden(int rowIndex) { |
| if (rowIndex >= 0) { |
| T rowObject = getRowObjectByIndex(rowIndex); |
| return this.hiddenRows.containsKey(this.rowIdAccessor.getRowId(rowObject)); |
| } |
| return false; |
| } |
| |
| @Override |
| public Collection<Integer> getHiddenRowIndexes() { |
| return this.hiddenRows.entrySet().stream() |
| .map(entry -> getRowIndexById(entry.getKey())) |
| .collect(Collectors.toSet()); |
| } |
| |
| @Override |
| public int[] getHiddenRowIndexesArray() { |
| return this.hiddenRows.entrySet().stream() |
| .mapToInt(entry -> getRowIndexById(entry.getKey())) |
| .sorted() |
| .toArray(); |
| } |
| |
| @Override |
| public boolean hasHiddenRows() { |
| return !this.hiddenRows.isEmpty(); |
| } |
| |
| @Override |
| public void hideRowPositions(int... rowPositions) { |
| Map<Serializable, T> toHide = new HashMap<Serializable, T>(); |
| for (int rowPosition : rowPositions) { |
| T rowObject = getRowObjectByPosition(rowPosition); |
| toHide.put(this.rowIdAccessor.getRowId(rowObject), rowObject); |
| } |
| this.hiddenRows.putAll(toHide); |
| invalidateCache(); |
| fireLayerEvent(new HideRowPositionsEvent(this, rowPositions)); |
| } |
| |
| @Override |
| public void hideRowPositions(Collection<Integer> rowPositions) { |
| hideRowPositions(rowPositions.stream().mapToInt(Integer::intValue).toArray()); |
| } |
| |
| @Override |
| public void hideRowIndexes(int... rowIndexes) { |
| MutableIntSet rowPositions = IntSets.mutable.empty(); |
| for (int rowIndex : rowIndexes) { |
| rowPositions.add(getRowPositionByIndex(rowIndex)); |
| T rowObject = getRowObjectByIndex(rowIndex); |
| this.hiddenRows.put(this.rowIdAccessor.getRowId(rowObject), rowObject); |
| } |
| invalidateCache(); |
| fireLayerEvent(new HideRowPositionsEvent(this, rowPositions.toArray())); |
| } |
| |
| @Override |
| public void hideRowIndexes(Collection<Integer> rowIndexes) { |
| hideRowIndexes(rowIndexes.stream().mapToInt(Integer::intValue).toArray()); |
| } |
| |
| @Override |
| public void showRowIndexes(int... rowIndexes) { |
| for (int rowIndex : rowIndexes) { |
| T rowObject = getRowObjectByIndex(rowIndex); |
| this.hiddenRows.remove(this.rowIdAccessor.getRowId(rowObject)); |
| } |
| invalidateCache(); |
| fireLayerEvent(new ShowRowPositionsEvent(this, getRowPositionsByIndexes(rowIndexes))); |
| } |
| |
| @Override |
| public void showRowIndexes(Collection<Integer> rowIndexes) { |
| showRowIndexes(rowIndexes.stream().mapToInt(Integer::intValue).toArray()); |
| } |
| |
| @Override |
| public void showRowPosition(int rowPosition, boolean showToTop, boolean showAll) { |
| MutableIntSet rowIndexes = IntSets.mutable.empty(); |
| int underlyingPosition = localToUnderlyingRowPosition(rowPosition); |
| if (showToTop) { |
| int topRowIndex = this.underlyingLayer.getRowIndexByPosition(underlyingPosition - 1); |
| if (showAll) { |
| int move = 1; |
| while (isRowIndexHidden(topRowIndex)) { |
| rowIndexes.add(topRowIndex); |
| move++; |
| topRowIndex = this.underlyingLayer.getRowIndexByPosition(underlyingPosition - move); |
| } |
| } else if (isRowIndexHidden(topRowIndex)) { |
| rowIndexes.add(topRowIndex); |
| } |
| } else { |
| int bottomRowIndex = this.underlyingLayer.getRowIndexByPosition(underlyingPosition + 1); |
| if (showAll) { |
| int move = 1; |
| while (isRowIndexHidden(bottomRowIndex)) { |
| rowIndexes.add(bottomRowIndex); |
| move++; |
| bottomRowIndex = this.underlyingLayer.getRowIndexByPosition(underlyingPosition + move); |
| } |
| } else if (isRowIndexHidden(bottomRowIndex)) { |
| rowIndexes.add(bottomRowIndex); |
| } |
| } |
| |
| if (!rowIndexes.isEmpty()) { |
| showRowIndexes(rowIndexes.toArray()); |
| } |
| } |
| |
| @Override |
| public void showAllRows() { |
| int[] hidden = getHiddenRowIndexesArray(); |
| this.hiddenRows.clear(); |
| invalidateCache(); |
| fireLayerEvent(new ShowRowPositionsEvent(this, hidden)); |
| } |
| |
| @Override |
| public Collection<String> getProvidedLabels() { |
| Collection<String> result = super.getProvidedLabels(); |
| result.add(HideIndicatorConstants.ROW_TOP_HIDDEN); |
| result.add(HideIndicatorConstants.ROW_BOTTOM_HIDDEN); |
| return result; |
| } |
| |
| private T getRowObjectByPosition(int rowPosition) { |
| int rowIndex = getRowIndexByPosition(rowPosition); |
| return getRowObjectByIndex(rowIndex); |
| } |
| |
| private T getRowObjectByIndex(int rowIndex) { |
| if (rowIndex >= 0) { |
| try { |
| T rowObject = this.rowDataProvider.getRowObject(rowIndex); |
| return rowObject; |
| } catch (Exception e) { |
| // row index is invalid for the data provider |
| } |
| } |
| |
| return null; |
| } |
| |
| private int getRowIndexById(Serializable rowId) { |
| T rowObject = this.hiddenRows.get(rowId); |
| int rowIndex = this.rowDataProvider.indexOfRowObject(rowObject); |
| if (rowIndex == -1) { |
| return -1; |
| } |
| return rowIndex; |
| } |
| |
| /** |
| * |
| * @return The converter used for id conversion. |
| */ |
| public IDisplayConverter getIdConverter() { |
| return this.idConverter; |
| } |
| |
| /** |
| * Set the {@link IDisplayConverter} that is used for conversion of the row |
| * id needed on {@link #loadState(String, Properties)} and |
| * {@link #saveState(String, Properties)}. |
| * |
| * @param idConverter |
| * The converter that should be used for id conversion. |
| */ |
| public void setIdConverter(IDisplayConverter idConverter) { |
| this.idConverter = idConverter; |
| } |
| |
| } |