package org.simantics.browsing.ui.nattable; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.Stack; import org.eclipse.nebula.widgets.nattable.hideshow.AbstractRowHideShowLayer; import org.eclipse.nebula.widgets.nattable.hideshow.event.HideRowPositionsEvent; import org.eclipse.nebula.widgets.nattable.hideshow.event.ShowRowPositionsEvent; import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer; import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent; import org.eclipse.nebula.widgets.nattable.layer.event.IStructuralChangeEvent; import org.simantics.browsing.ui.nattable.override.TreeLayer2; import org.simantics.databoard.util.IdentityHashSet; import it.unimi.dsi.fastutil.ints.IntRBTreeSet; /** * NatTable TreeLayer for IEcoReportTask tree. * * Keeps track of collapsed nodes so that current sorting mechanism works. * * * @author Marko Luukkainen * */ public class GETreeLayer extends TreeLayer2 { //Set collapsed = new HashSet(); Set expanded = new IdentityHashSet(); GETreeData treeData; Comparator comparator = new FirstElementComparator(); public GETreeLayer(IUniqueIndexLayer underlyingLayer, GETreeRowModel treeRowModel,boolean useDefaultConfiguration) { super(underlyingLayer, treeRowModel, useDefaultConfiguration); if (underlyingLayer instanceof AbstractRowHideShowLayer) { throw new IllegalArgumentException("Cannot use treelayer above row hide layer"); } this.treeData = (GETreeData)treeRowModel.getTreeData(); hiddenPos = new ArrayList(); hiddenPos.add(new int[]{0,0}); } @Override public void collapseTreeRow(int parentIndex) { TreeNode task = treeData.getDataAtIndex(parentIndex); expanded.remove(task); task.setExpanded(false); super.collapseTreeRow(parentIndex); } public void fullCollapseTreeRow(int parentIndex) { TreeNode task = treeData.getDataAtIndex(parentIndex); List indices = new ArrayList(); Stack stack = new Stack(); stack.add(task); while (!stack.isEmpty()) { TreeNode t = stack.pop(); indices.add(treeData.indexOf(t)); stack.addAll(t.getChildren()); } collapseTreeRow(indices); } @Override public void expandTreeRow(int parentIndex) { TreeNode task = treeData.getDataAtIndex(parentIndex); expanded.add(task); task.setExpanded(true); super.expandTreeRow(parentIndex); } public void expandToTreeRow(int parentIndex) { TreeNode task = treeData.getDataAtIndex(parentIndex); List ancestors = new ArrayList(); while (true) { task = task.getParent(); if (task == null) break; else ancestors.add(0, task); } for (TreeNode t : ancestors) { if (treeData.getDepthOfData(t) >= 0) expandTreeRow(treeData.indexOf(t)); } } public void fullExpandTreeRow(int parentIndex) { TreeNode task = treeData.getDataAtIndex(parentIndex); List indices = new ArrayList(); Stack stack = new Stack(); stack.add(task); while (!stack.isEmpty()) { TreeNode t = stack.pop(); indices.add(treeData.indexOf(t)); stack.addAll(t.getChildren()); } expandTreeRow(indices); } public void collapseTreeRow(int parentIndices[]) { List rowPositions = new ArrayList(); List rowIndexes = new ArrayList(); // while this approach may collect some of the row indices several times, it is faster than up-keeping hash set. for (int parentIndex : parentIndices) { if (parentIndex >= 0) { TreeNode task = treeData.getDataAtIndex(parentIndex); if (task != null) { task.setExpanded(false); expanded.remove(task); } rowIndexes.addAll(getModel().collapse(parentIndex)); } } for (Integer rowIndex : rowIndexes) { int rowPos = getRowPositionByIndex(rowIndex); //if the rowPos is negative, it is not visible because of hidden state in an underlying layer if (rowPos >= 0) { rowPositions.add(rowPos); } } //this.getHiddenRowIndexes().addAll(rowIndexes); for (int i = 0; i < rowIndexes.size(); i++) { this.getHiddenRowIndexes().add(rowIndexes.get(i)); } invalidateCache(); fireLayerEvent(new HideRowPositionsEvent(this, rowPositions)); } public void collapseTreeRow(List parentIndices) { List rowPositions = new ArrayList(); List rowIndexes = new ArrayList(); // while this approach may collect some of the row indices several times, it is faster than up-keeping hash set. for (int parentIndex : parentIndices) { if (parentIndex >= 0) { TreeNode task = treeData.getDataAtIndex(parentIndex); task.setExpanded(false); expanded.remove(task); rowIndexes.addAll(getModel().collapse(parentIndex)); } } for (Integer rowIndex : rowIndexes) { int rowPos = getRowPositionByIndex(rowIndex); //if the rowPos is negative, it is not visible because of hidden state in an underlying layer if (rowPos >= 0) { rowPositions.add(rowPos); } } //this.getHiddenRowIndexes().addAll(rowIndexes); for (int i = 0; i < rowIndexes.size(); i++) { this.getHiddenRowIndexes().add(rowIndexes.get(i)); } invalidateCache(); fireLayerEvent(new HideRowPositionsEvent(this, rowPositions)); } public void collapseAllRows() { int count = treeData.getElementCount(); List rowIndexes = new ArrayList(count); for (int i = 0; i < count; i++) { TreeNode t = treeData.getDataAtIndex(i); // we don't want to hide the roots of the tree if (!treeData.isRoot(t)) { rowIndexes.add(i); } t.setExpanded(false); expanded.remove(t); getModel().collapse(i); } this.getHiddenRowIndexes().addAll(rowIndexes); invalidateCache(); fireLayerEvent(new HideRowPositionsEvent(this, rowIndexes)); } public void expandTreeRow(int parentIndices[]) { List rowIndexes = new ArrayList(); for (int parentIndex : parentIndices) { TreeNode task = treeData.getDataAtIndex(parentIndex); task.setExpanded(true); expanded.add(task); rowIndexes.addAll(getModel().expand(parentIndex)); rowIndexes.add(parentIndex); } //Implementation uses tree set, so removing in reverse order is faster. for (int i = rowIndexes.size() -1 ; i >= 0; i--) { this.getHiddenRowIndexes().remove(rowIndexes.get(i)); } //this.getHiddenRowIndexes().removeAll(rowIndexes); invalidateCache(); fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes)); } public void expandTreeRow(List parentIndices) { List rowIndexes = new ArrayList(); for (int parentIndex : parentIndices) { TreeNode task = treeData.getDataAtIndex(parentIndex); task.setExpanded(true); expanded.add(task); rowIndexes.addAll(getModel().expand(parentIndex)); } //Implementation uses tree set, so removing in reverse order is faster. for (int i = rowIndexes.size() -1 ; i >= 0; i--) { this.getHiddenRowIndexes().remove(rowIndexes.get(i)); } //this.getHiddenRowIndexes().removeAll(rowIndexes); invalidateCache(); fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes)); } // public void expandAllRows() { // Collection parentIndices = getHiddenRowIndexes(); // List rowIndexes = new ArrayList(); // for (int parentIndex : parentIndices) { // rowIndexes.addAll(getModel().expand(parentIndex)); // } // for (TreeNode t : collapsed) // t.setExpanded(true); // collapsed.clear(); // getHiddenRowIndexes().clear(); // ((GETreeRowModel)getModel()).clear(); // invalidateCache(); // fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes)); // } @Override protected void invalidateCache() { super.invalidateCache(); hiddenPos.clear(); hiddenPos.add(new int[]{0,0}); } private void _collapseAllRows() { int count = treeData.getElementCount(); List rowIndexes = new ArrayList(count); for (int i = 0; i < count; i++) { TreeNode t = treeData.getDataAtIndex(i); // we don't want to hide the roots of the tree if (!treeData.isRoot(t)) { rowIndexes.add(i); } getModel().collapse(i); } this.getHiddenRowIndexes().addAll(rowIndexes); invalidateCache(); } @Override public void handleLayerEvent(ILayerEvent event) { // Currently sorting is implemented by sorting the underlaying list. // Since all layers are storing just indices, we have to keep track the indices after sorting, // and refresh the layers accordingly. // Another option would use some sort of sorting layers, so that the original data is kept intact, and // sorting layer would map the row indexes to sorted row positions. // preserve expanded nodes Set coll = null; // int hiddenCount = 0; if (event instanceof IStructuralChangeEvent) { IStructuralChangeEvent structuralChangeEvent = (IStructuralChangeEvent) event; if (structuralChangeEvent.isVerticalStructureChanged()) { // store old indices internalRefresh = true; ((GETreeRowModel)getModel()).clear(); // hiddenCount = getHiddenRowIndexes().size(); getHiddenRowIndexes().clear(); // store expanded nodes and clear disposed nodes. coll = new HashSet<>(); for (TreeNode n : expanded) if (!n.isDisposed()) coll.add(n); expanded.clear(); expanded.addAll(coll); // filter hidden nodes (nodes that have collapsed ancestors) coll.clear(); for (TreeNode n : expanded) if (!n.isHidden()) coll.add(n); } } super.handleLayerEvent(event); if (coll != null) { _collapseAllRows(); // expand new indices int ind[] = new int[coll.size()]; Iterator iter = coll.iterator(); for (int i = 0; i < ind.length; i++) { ind[i] = treeData.indexOf(iter.next()); } expandTreeRow(ind); // if (getHiddenRowIndexes().size() != hiddenCount) { // System.out.println(getHiddenRowIndexes().size() + " != " + hiddenCount); // ((GETreeRowModel)getModel()).clear(); // getHiddenRowIndexes().clear(); // _collapseAllRows(); // //collapseAll(); // // expand new indices // iter = coll.iterator(); // for (int i = 0; i < ind.length; i++) { // ind[i] = treeData.indexOf(iter.next()); // } // expandTreeRow(ind); // } internalRefresh = false; } } private boolean internalRefresh = false; public void fireLayerEvent(ILayerEvent event) { if (!internalRefresh) super.fireLayerEvent(event); } public Set getExpanded() { return expanded; } List hiddenPos; @Override public int getStartYOfRowPosition(int localRowPosition) { Integer cachedStartY = startYCache.get(Integer.valueOf(localRowPosition)); if (cachedStartY != null) { return cachedStartY.intValue(); } IUniqueIndexLayer underlyingLayer = (IUniqueIndexLayer) getUnderlyingLayer(); int underlyingPosition = localToUnderlyingRowPosition(localRowPosition); int underlyingStartY = underlyingLayer.getStartYOfRowPosition(underlyingPosition); if (underlyingStartY < 0) { return -1; } int h = 0; int start = 0; if (hiddenPos.size() < 2) { // is cache empty? (hiddenPos contains {0,0} element by default) if (getHiddenRowIndexes().size() > 0) // check if there are hidden rows. start = getHiddenRowIndexes().iterator().next(); } else { int[] d = hiddenPos.get(hiddenPos.size()-1); // take the last element of the cache. start = d[0]+1; // set to search from the next element. h = d[1]; } if (start < underlyingPosition) { // check if we can find the amount of hidden space from the cache. // cache positions of hidden nodes and hidden space. //for (Integer hiddenIndex : ((TreeSet)getHiddenRowIndexes()).tailSet(start)) { for (int hiddenIndex : ((IntRBTreeSet)getHiddenRowIndexes()).tailSet(start)) { // safety check (could be disabled, but this does not seem to cause considerable performance hit) int hiddenPosition = underlyingLayer.getRowPositionByIndex(hiddenIndex);//.intValue()); if (hiddenPosition != hiddenIndex)//.intValue()) throw new RuntimeException("Underlying layer is swithing indices"); if (hiddenPosition >= 0 && hiddenPosition <= underlyingPosition) { h += underlyingLayer.getRowHeightByPosition(hiddenPosition); hiddenPos.add(new int[]{hiddenPosition,h}); } else if (hiddenPosition > underlyingPosition) { break; } } } else { // use binary search to find hidden space. h = 0; int index = Collections.binarySearch(hiddenPos, new int[]{underlyingPosition,0}, comparator); if (index < 0) { // exact element is not cached, but we can use the closest match. index = -index-2; } h = hiddenPos.get(index)[1]; } underlyingStartY -= h; startYCache.put(Integer.valueOf(localRowPosition), Integer.valueOf(underlyingStartY)); return underlyingStartY; } private static class FirstElementComparator implements Comparator { @Override public int compare(int[] o1, int[] o2) { return o1[0]-o2[0]; } } }