1 package org.simantics.browsing.ui.nattable;
3 import java.util.ArrayList;
4 import java.util.Collections;
5 import java.util.Comparator;
6 import java.util.HashSet;
7 import java.util.Iterator;
10 import java.util.Stack;
12 import org.eclipse.nebula.widgets.nattable.hideshow.AbstractRowHideShowLayer;
13 import org.eclipse.nebula.widgets.nattable.hideshow.event.HideRowPositionsEvent;
14 import org.eclipse.nebula.widgets.nattable.hideshow.event.ShowRowPositionsEvent;
15 import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer;
16 import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent;
17 import org.eclipse.nebula.widgets.nattable.layer.event.IStructuralChangeEvent;
18 import org.simantics.browsing.ui.nattable.override.TreeLayer2;
19 import org.simantics.databoard.util.IdentityHashSet;
21 import it.unimi.dsi.fastutil.ints.IntRBTreeSet;
23 * NatTable TreeLayer for IEcoReportTask tree.
25 * Keeps track of collapsed nodes so that current sorting mechanism works.
28 * @author Marko Luukkainen <marko.luukkainen@vtt.fi>
31 public class GETreeLayer extends TreeLayer2 {
33 //Set<IEcoReportTask> collapsed = new HashSet<IEcoReportTask>();
34 Set<TreeNode> expanded = new IdentityHashSet<TreeNode>();
36 Comparator<int[]> comparator = new FirstElementComparator();
38 public GETreeLayer(IUniqueIndexLayer underlyingLayer, GETreeRowModel<TreeNode> treeRowModel,boolean useDefaultConfiguration) {
39 super(underlyingLayer, treeRowModel, useDefaultConfiguration);
41 if (underlyingLayer instanceof AbstractRowHideShowLayer) {
42 throw new IllegalArgumentException("Cannot use treelayer above row hide layer");
45 this.treeData = (GETreeData)treeRowModel.getTreeData();
46 hiddenPos = new ArrayList<int[]>();
47 hiddenPos.add(new int[]{0,0});
52 public void collapseTreeRow(int parentIndex) {
53 TreeNode task = treeData.getDataAtIndex(parentIndex);
54 expanded.remove(task);
55 task.setExpanded(false);
56 super.collapseTreeRow(parentIndex);
59 public void fullCollapseTreeRow(int parentIndex) {
60 TreeNode task = treeData.getDataAtIndex(parentIndex);
61 List<Integer> indices = new ArrayList<Integer>();
63 Stack<TreeNode> stack = new Stack<TreeNode>();
65 while (!stack.isEmpty()) {
66 TreeNode t = stack.pop();
67 indices.add(treeData.indexOf(t));
68 stack.addAll(t.getChildren());
70 collapseTreeRow(indices);
74 public void expandTreeRow(int parentIndex) {
75 TreeNode task = treeData.getDataAtIndex(parentIndex);
77 task.setExpanded(true);
78 super.expandTreeRow(parentIndex);
81 public void expandToTreeRow(int parentIndex) {
82 TreeNode task = treeData.getDataAtIndex(parentIndex);
83 List<TreeNode> ancestors = new ArrayList<TreeNode>();
85 task = task.getParent();
89 ancestors.add(0, task);
91 for (TreeNode t : ancestors) {
92 if (treeData.getDepthOfData(t) >= 0)
93 expandTreeRow(treeData.indexOf(t));
97 public void fullExpandTreeRow(int parentIndex) {
98 TreeNode task = treeData.getDataAtIndex(parentIndex);
99 List<Integer> indices = new ArrayList<Integer>();
101 Stack<TreeNode> stack = new Stack<TreeNode>();
103 while (!stack.isEmpty()) {
104 TreeNode t = stack.pop();
105 indices.add(treeData.indexOf(t));
106 stack.addAll(t.getChildren());
108 expandTreeRow(indices);
111 public void collapseTreeRow(int parentIndices[]) {
112 List<Integer> rowPositions = new ArrayList<Integer>();
113 List<Integer> rowIndexes = new ArrayList<Integer>();
114 // while this approach may collect some of the row indices several times, it is faster than up-keeping hash set.
115 for (int parentIndex : parentIndices) {
116 if (parentIndex >= 0) {
117 TreeNode task = treeData.getDataAtIndex(parentIndex);
119 task.setExpanded(false);
120 expanded.remove(task);
122 rowIndexes.addAll(getModel().collapse(parentIndex));
125 for (Integer rowIndex : rowIndexes) {
126 int rowPos = getRowPositionByIndex(rowIndex);
127 //if the rowPos is negative, it is not visible because of hidden state in an underlying layer
129 rowPositions.add(rowPos);
132 //this.getHiddenRowIndexes().addAll(rowIndexes);
133 for (int i = 0; i < rowIndexes.size(); i++) {
134 this.getHiddenRowIndexes().add(rowIndexes.get(i));
137 fireLayerEvent(new HideRowPositionsEvent(this, rowPositions));
140 public void collapseTreeRow(List<Integer> parentIndices) {
141 List<Integer> rowPositions = new ArrayList<Integer>();
142 List<Integer> rowIndexes = new ArrayList<Integer>();
143 // while this approach may collect some of the row indices several times, it is faster than up-keeping hash set.
144 for (int parentIndex : parentIndices) {
145 if (parentIndex >= 0) {
146 TreeNode task = treeData.getDataAtIndex(parentIndex);
147 task.setExpanded(false);
148 expanded.remove(task);
149 rowIndexes.addAll(getModel().collapse(parentIndex));
152 for (Integer rowIndex : rowIndexes) {
153 int rowPos = getRowPositionByIndex(rowIndex);
154 //if the rowPos is negative, it is not visible because of hidden state in an underlying layer
156 rowPositions.add(rowPos);
159 //this.getHiddenRowIndexes().addAll(rowIndexes);
160 for (int i = 0; i < rowIndexes.size(); i++) {
161 this.getHiddenRowIndexes().add(rowIndexes.get(i));
164 fireLayerEvent(new HideRowPositionsEvent(this, rowPositions));
167 public void collapseAllRows() {
168 int count = treeData.getElementCount();
169 List <Integer> rowIndexes = new ArrayList<Integer>(count);
170 for (int i = 0; i < count; i++) {
171 TreeNode t = treeData.getDataAtIndex(i);
172 // we don't want to hide the roots of the tree
173 if (!treeData.isRoot(t)) {
177 t.setExpanded(false);
179 getModel().collapse(i);
182 this.getHiddenRowIndexes().addAll(rowIndexes);
184 fireLayerEvent(new HideRowPositionsEvent(this, rowIndexes));
187 public void expandTreeRow(int parentIndices[]) {
188 List<Integer> rowIndexes = new ArrayList<Integer>();
189 for (int parentIndex : parentIndices) {
190 TreeNode task = treeData.getDataAtIndex(parentIndex);
191 task.setExpanded(true);
193 rowIndexes.addAll(getModel().expand(parentIndex));
194 rowIndexes.add(parentIndex);
197 //Implementation uses tree set, so removing in reverse order is faster.
198 for (int i = rowIndexes.size() -1 ; i >= 0; i--) {
199 this.getHiddenRowIndexes().remove(rowIndexes.get(i));
201 //this.getHiddenRowIndexes().removeAll(rowIndexes);
203 fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes));
206 public void expandTreeRow(List<Integer> parentIndices) {
207 List<Integer> rowIndexes = new ArrayList<Integer>();
208 for (int parentIndex : parentIndices) {
209 TreeNode task = treeData.getDataAtIndex(parentIndex);
210 task.setExpanded(true);
212 rowIndexes.addAll(getModel().expand(parentIndex));
215 //Implementation uses tree set, so removing in reverse order is faster.
216 for (int i = rowIndexes.size() -1 ; i >= 0; i--) {
217 this.getHiddenRowIndexes().remove(rowIndexes.get(i));
219 //this.getHiddenRowIndexes().removeAll(rowIndexes);
221 fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes));
224 // public void expandAllRows() {
225 // Collection<Integer> parentIndices = getHiddenRowIndexes();
226 // List<Integer> rowIndexes = new ArrayList<Integer>();
227 // for (int parentIndex : parentIndices) {
228 // rowIndexes.addAll(getModel().expand(parentIndex));
230 // for (TreeNode t : collapsed)
231 // t.setExpanded(true);
232 // collapsed.clear();
233 // getHiddenRowIndexes().clear();
234 // ((GETreeRowModel)getModel()).clear();
235 // invalidateCache();
236 // fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes));
240 protected void invalidateCache() {
241 super.invalidateCache();
243 hiddenPos.add(new int[]{0,0});
246 private void _collapseAllRows() {
247 int count = treeData.getElementCount();
248 List <Integer> rowIndexes = new ArrayList<Integer>(count);
249 for (int i = 0; i < count; i++) {
250 TreeNode t = treeData.getDataAtIndex(i);
251 // we don't want to hide the roots of the tree
252 if (!treeData.isRoot(t)) {
256 getModel().collapse(i);
259 this.getHiddenRowIndexes().addAll(rowIndexes);
264 public void handleLayerEvent(ILayerEvent event) {
265 // Currently sorting is implemented by sorting the underlaying list.
266 // Since all layers are storing just indices, we have to keep track the indices after sorting,
267 // and refresh the layers accordingly.
269 // Another option would use some sort of sorting layers, so that the original data is kept intact, and
270 // sorting layer would map the row indexes to sorted row positions.
272 // preserve expanded nodes
273 Set<TreeNode> coll = null;
274 // int hiddenCount = 0;
275 if (event instanceof IStructuralChangeEvent) {
276 IStructuralChangeEvent structuralChangeEvent = (IStructuralChangeEvent) event;
277 if (structuralChangeEvent.isVerticalStructureChanged()) {
279 internalRefresh = true;
280 ((GETreeRowModel<?>)getModel()).clear();
281 // hiddenCount = getHiddenRowIndexes().size();
282 getHiddenRowIndexes().clear();
283 // store expanded nodes and clear disposed nodes.
284 coll = new HashSet<>();
285 for (TreeNode n : expanded)
289 expanded.addAll(coll);
290 // filter hidden nodes (nodes that have collapsed ancestors)
292 for (TreeNode n : expanded)
297 super.handleLayerEvent(event);
300 // expand new indices
301 int ind[] = new int[coll.size()];
302 Iterator<TreeNode> iter = coll.iterator();
303 for (int i = 0; i < ind.length; i++) {
304 ind[i] = treeData.indexOf(iter.next());
307 // if (getHiddenRowIndexes().size() != hiddenCount) {
308 // System.out.println(getHiddenRowIndexes().size() + " != " + hiddenCount);
309 // ((GETreeRowModel<?>)getModel()).clear();
310 // getHiddenRowIndexes().clear();
311 // _collapseAllRows();
313 // // expand new indices
314 // iter = coll.iterator();
315 // for (int i = 0; i < ind.length; i++) {
316 // ind[i] = treeData.indexOf(iter.next());
318 // expandTreeRow(ind);
320 internalRefresh = false;
324 private boolean internalRefresh = false;
326 public void fireLayerEvent(ILayerEvent event) {
327 if (!internalRefresh)
328 super.fireLayerEvent(event);
332 public Set<TreeNode> getExpanded() {
336 List<int[]> hiddenPos;
339 public int getStartYOfRowPosition(int localRowPosition) {
340 Integer cachedStartY = startYCache.get(Integer.valueOf(localRowPosition));
341 if (cachedStartY != null) {
342 return cachedStartY.intValue();
345 IUniqueIndexLayer underlyingLayer = (IUniqueIndexLayer) getUnderlyingLayer();
346 int underlyingPosition = localToUnderlyingRowPosition(localRowPosition);
347 int underlyingStartY = underlyingLayer.getStartYOfRowPosition(underlyingPosition);
348 if (underlyingStartY < 0) {
355 if (hiddenPos.size() < 2) { // is cache empty? (hiddenPos contains {0,0} element by default)
356 if (getHiddenRowIndexes().size() > 0) // check if there are hidden rows.
357 start = getHiddenRowIndexes().iterator().next();
359 int[] d = hiddenPos.get(hiddenPos.size()-1); // take the last element of the cache.
360 start = d[0]+1; // set to search from the next element.
363 if (start < underlyingPosition) { // check if we can find the amount of hidden space from the cache.
364 // cache positions of hidden nodes and hidden space.
365 //for (Integer hiddenIndex : ((TreeSet<Integer>)getHiddenRowIndexes()).tailSet(start)) {
366 for (int hiddenIndex : ((IntRBTreeSet)getHiddenRowIndexes()).tailSet(start)) {
367 // safety check (could be disabled, but this does not seem to cause considerable performance hit)
368 int hiddenPosition = underlyingLayer.getRowPositionByIndex(hiddenIndex);//.intValue());
369 if (hiddenPosition != hiddenIndex)//.intValue())
370 throw new RuntimeException("Underlying layer is swithing indices");
371 if (hiddenPosition >= 0 && hiddenPosition <= underlyingPosition) {
372 h += underlyingLayer.getRowHeightByPosition(hiddenPosition);
373 hiddenPos.add(new int[]{hiddenPosition,h});
374 } else if (hiddenPosition > underlyingPosition) {
379 // use binary search to find hidden space.
381 int index = Collections.binarySearch(hiddenPos, new int[]{underlyingPosition,0}, comparator);
382 if (index < 0) { // exact element is not cached, but we can use the closest match.
385 h = hiddenPos.get(index)[1];
387 underlyingStartY -= h;
388 startYCache.put(Integer.valueOf(localRowPosition), Integer.valueOf(underlyingStartY));
389 return underlyingStartY;
393 private static class FirstElementComparator implements Comparator<int[]> {
395 public int compare(int[] o1, int[] o2) {