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