Sync git svn branch with SVN repository r33334.
[simantics/platform.git] / bundles / org.simantics.browsing.ui.nattable / src / org / simantics / browsing / ui / nattable / GETreeLayer.java
1 package org.simantics.browsing.ui.nattable;\r
2 \r
3 import java.util.ArrayList;\r
4 import java.util.Collections;\r
5 import java.util.Comparator;\r
6 import java.util.HashSet;\r
7 import java.util.Iterator;\r
8 import java.util.List;\r
9 import java.util.Set;\r
10 import java.util.Stack;\r
11 \r
12 import org.eclipse.nebula.widgets.nattable.hideshow.AbstractRowHideShowLayer;\r
13 import org.eclipse.nebula.widgets.nattable.hideshow.event.HideRowPositionsEvent;\r
14 import org.eclipse.nebula.widgets.nattable.hideshow.event.ShowRowPositionsEvent;\r
15 import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer;\r
16 import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent;\r
17 import org.eclipse.nebula.widgets.nattable.layer.event.IStructuralChangeEvent;\r
18 import org.simantics.browsing.ui.nattable.override.TreeLayer2;\r
19 import org.simantics.databoard.util.IdentityHashSet;\r
20 \r
21 import it.unimi.dsi.fastutil.ints.IntRBTreeSet;\r
22 /**\r
23  * NatTable TreeLayer for IEcoReportTask tree.\r
24  * \r
25  * Keeps track of collapsed nodes so that current sorting mechanism works.\r
26  * \r
27  * \r
28  * @author Marko Luukkainen <marko.luukkainen@vtt.fi>\r
29  *\r
30  */\r
31 public class GETreeLayer  extends TreeLayer2 {\r
32 \r
33         //Set<IEcoReportTask> collapsed = new HashSet<IEcoReportTask>();\r
34         Set<TreeNode> expanded = new IdentityHashSet<TreeNode>();\r
35         GETreeData treeData;\r
36         Comparator<int[]> comparator = new FirstElementComparator();\r
37         \r
38         public GETreeLayer(IUniqueIndexLayer underlyingLayer, GETreeRowModel<TreeNode> treeRowModel,boolean useDefaultConfiguration) {\r
39                 super(underlyingLayer, treeRowModel, useDefaultConfiguration);\r
40                 \r
41                 if (underlyingLayer instanceof AbstractRowHideShowLayer) {\r
42                         throw new IllegalArgumentException("Cannot use treelayer above row hide layer");\r
43                 }\r
44                 \r
45                 this.treeData = (GETreeData)treeRowModel.getTreeData();\r
46                 hiddenPos = new ArrayList<int[]>();\r
47                 hiddenPos.add(new int[]{0,0});\r
48         }\r
49 \r
50         \r
51         @Override\r
52         public void collapseTreeRow(int parentIndex) {\r
53                 TreeNode task = treeData.getDataAtIndex(parentIndex);\r
54                 expanded.remove(task);\r
55                 task.setExpanded(false);\r
56                 super.collapseTreeRow(parentIndex);\r
57         }\r
58         \r
59         public void fullCollapseTreeRow(int parentIndex) {\r
60                 TreeNode task = treeData.getDataAtIndex(parentIndex);\r
61                 List<Integer> indices = new ArrayList<Integer>();\r
62                 \r
63                 Stack<TreeNode> stack = new Stack<TreeNode>();\r
64                 stack.add(task);\r
65                 while (!stack.isEmpty()) {\r
66                         TreeNode t = stack.pop();\r
67                         indices.add(treeData.indexOf(t));\r
68                         stack.addAll(t.getChildren());\r
69                 }\r
70                 collapseTreeRow(indices);\r
71         }\r
72         \r
73         @Override\r
74         public void expandTreeRow(int parentIndex) {\r
75                 TreeNode task = treeData.getDataAtIndex(parentIndex);\r
76                 expanded.add(task);\r
77                 task.setExpanded(true);\r
78                 super.expandTreeRow(parentIndex);\r
79         }\r
80         \r
81         public void expandToTreeRow(int parentIndex) {\r
82                 TreeNode task = treeData.getDataAtIndex(parentIndex);\r
83                 List<TreeNode> ancestors = new ArrayList<TreeNode>();\r
84                 while (true) {\r
85                         task = task.getParent();\r
86                         if (task == null)\r
87                                 break;\r
88                         else\r
89                                 ancestors.add(0, task);\r
90                 }\r
91                 for (TreeNode t : ancestors) {\r
92                         if (treeData.getDepthOfData(t) >= 0)\r
93                                 expandTreeRow(treeData.indexOf(t));\r
94                 }\r
95         }\r
96         \r
97         public void fullExpandTreeRow(int parentIndex) {\r
98                 TreeNode task = treeData.getDataAtIndex(parentIndex);\r
99                 List<Integer> indices = new ArrayList<Integer>();\r
100                 \r
101                 Stack<TreeNode> stack = new Stack<TreeNode>();\r
102                 stack.add(task);\r
103                 while (!stack.isEmpty()) {\r
104                         TreeNode t = stack.pop();\r
105                         indices.add(treeData.indexOf(t));\r
106                         stack.addAll(t.getChildren());\r
107                 }\r
108                 expandTreeRow(indices);\r
109         }\r
110         \r
111         public void collapseTreeRow(int parentIndices[]) {\r
112                 List<Integer> rowPositions = new ArrayList<Integer>();\r
113                 List<Integer> rowIndexes = new ArrayList<Integer>();\r
114                 // while this approach may collect some of the row indices several times, it is faster than up-keeping hash set.\r
115                 for (int parentIndex : parentIndices) {\r
116                         if (parentIndex >= 0) {\r
117                                 TreeNode task = treeData.getDataAtIndex(parentIndex);\r
118                                 if (task != null) {\r
119                                         task.setExpanded(false);\r
120                                         expanded.remove(task);\r
121                                 }\r
122                                 rowIndexes.addAll(getModel().collapse(parentIndex));\r
123                         }\r
124                 }\r
125                 for (Integer rowIndex : rowIndexes) {\r
126                         int rowPos = getRowPositionByIndex(rowIndex);\r
127                         //if the rowPos is negative, it is not visible because of hidden state in an underlying layer\r
128                         if (rowPos >= 0) {\r
129                                 rowPositions.add(rowPos);\r
130                         }\r
131                 }\r
132                 //this.getHiddenRowIndexes().addAll(rowIndexes);\r
133                 for (int i = 0; i < rowIndexes.size(); i++) {\r
134                         this.getHiddenRowIndexes().add(rowIndexes.get(i));\r
135                 }\r
136                 invalidateCache();\r
137                 fireLayerEvent(new HideRowPositionsEvent(this, rowPositions));\r
138         }\r
139         \r
140         public void collapseTreeRow(List<Integer> parentIndices) {\r
141                 List<Integer> rowPositions = new ArrayList<Integer>();\r
142                 List<Integer> rowIndexes = new ArrayList<Integer>();\r
143                 // while this approach may collect some of the row indices several times, it is faster than up-keeping hash set.\r
144                 for (int parentIndex : parentIndices) {\r
145                         if (parentIndex >= 0) {\r
146                                 TreeNode task = treeData.getDataAtIndex(parentIndex);\r
147                                 task.setExpanded(false);\r
148                                 expanded.remove(task);\r
149                                 rowIndexes.addAll(getModel().collapse(parentIndex));\r
150                         }\r
151                 }\r
152                 for (Integer rowIndex : rowIndexes) {\r
153                         int rowPos = getRowPositionByIndex(rowIndex);\r
154                         //if the rowPos is negative, it is not visible because of hidden state in an underlying layer\r
155                         if (rowPos >= 0) {\r
156                                 rowPositions.add(rowPos);\r
157                         }\r
158                 }\r
159                 //this.getHiddenRowIndexes().addAll(rowIndexes);\r
160                 for (int i = 0; i < rowIndexes.size(); i++) {\r
161                         this.getHiddenRowIndexes().add(rowIndexes.get(i));\r
162                 }\r
163                 invalidateCache();\r
164                 fireLayerEvent(new HideRowPositionsEvent(this, rowPositions));\r
165         }\r
166         \r
167         public void collapseAllRows() {\r
168                 int count = treeData.getElementCount();\r
169                 List <Integer> rowIndexes = new ArrayList<Integer>(count);\r
170                 for (int i = 0; i < count; i++) {\r
171                         TreeNode t = treeData.getDataAtIndex(i);\r
172                         // we don't want to hide the roots of the tree\r
173                         if (!treeData.isRoot(t)) { \r
174                                 rowIndexes.add(i);\r
175                                 \r
176                         } \r
177                         t.setExpanded(false);\r
178                         expanded.remove(t);\r
179                         getModel().collapse(i);\r
180                         \r
181                 }\r
182                 this.getHiddenRowIndexes().addAll(rowIndexes);\r
183                 invalidateCache();\r
184                 fireLayerEvent(new HideRowPositionsEvent(this, rowIndexes));\r
185         }\r
186         \r
187         public void expandTreeRow(int parentIndices[]) {\r
188                 List<Integer> rowIndexes = new ArrayList<Integer>();\r
189                 for (int parentIndex : parentIndices) {\r
190                         TreeNode task = treeData.getDataAtIndex(parentIndex);\r
191                         task.setExpanded(true);\r
192                         expanded.add(task);\r
193                         rowIndexes.addAll(getModel().expand(parentIndex));\r
194                         rowIndexes.add(parentIndex);\r
195                 }\r
196                 \r
197                 //Implementation uses tree set, so removing in reverse order is faster.\r
198                 for (int i = rowIndexes.size() -1 ; i >= 0; i--) {\r
199                         this.getHiddenRowIndexes().remove(rowIndexes.get(i));\r
200                 }\r
201                 //this.getHiddenRowIndexes().removeAll(rowIndexes);\r
202                 invalidateCache();\r
203                 fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes));\r
204         }\r
205         \r
206         public void expandTreeRow(List<Integer> parentIndices) {\r
207                 List<Integer> rowIndexes = new ArrayList<Integer>();\r
208                 for (int parentIndex : parentIndices) {\r
209                         TreeNode task = treeData.getDataAtIndex(parentIndex);\r
210                         task.setExpanded(true);\r
211                         expanded.add(task);\r
212                         rowIndexes.addAll(getModel().expand(parentIndex));\r
213                 }\r
214                 \r
215                 //Implementation uses tree set, so removing in reverse order is faster.\r
216                 for (int i = rowIndexes.size() -1 ; i >= 0; i--) {\r
217                         this.getHiddenRowIndexes().remove(rowIndexes.get(i));\r
218                 }\r
219                 //this.getHiddenRowIndexes().removeAll(rowIndexes);\r
220                 invalidateCache();\r
221                 fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes));\r
222         }\r
223         \r
224 //      public void expandAllRows() {\r
225 //              Collection<Integer> parentIndices = getHiddenRowIndexes();\r
226 //              List<Integer> rowIndexes = new ArrayList<Integer>();\r
227 //              for (int parentIndex : parentIndices) {\r
228 //                      rowIndexes.addAll(getModel().expand(parentIndex));\r
229 //              }\r
230 //              for (TreeNode t : collapsed)\r
231 //                      t.setExpanded(true);\r
232 //              collapsed.clear();\r
233 //              getHiddenRowIndexes().clear();\r
234 //              ((GETreeRowModel)getModel()).clear();\r
235 //              invalidateCache();\r
236 //              fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes));\r
237 //      }\r
238         \r
239         @Override\r
240         protected void invalidateCache() {\r
241                 super.invalidateCache();\r
242                 hiddenPos.clear();\r
243                 hiddenPos.add(new int[]{0,0});\r
244         }\r
245         \r
246         private void _collapseAllRows() {\r
247                 int count = treeData.getElementCount();\r
248                 List <Integer> rowIndexes = new ArrayList<Integer>(count);\r
249                 for (int i = 0; i < count; i++) {\r
250                         TreeNode t = treeData.getDataAtIndex(i);\r
251                         // we don't want to hide the roots of the tree\r
252                         if (!treeData.isRoot(t)) { \r
253                                 rowIndexes.add(i);\r
254                                 \r
255                         } \r
256                         getModel().collapse(i);\r
257                         \r
258                 }\r
259                 this.getHiddenRowIndexes().addAll(rowIndexes);\r
260                 invalidateCache();\r
261         }\r
262         \r
263         @Override\r
264         public void handleLayerEvent(ILayerEvent event) {\r
265                 // Currently sorting is implemented by sorting the underlaying list.\r
266                 // Since all layers are storing just indices, we have to keep track the indices after sorting,\r
267                 // and refresh the layers accordingly.\r
268                 \r
269                 // Another option would use some sort of sorting layers, so that the original data is kept intact, and\r
270                 // sorting layer would map the row indexes to sorted row positions.\r
271                 \r
272                 // preserve expanded nodes \r
273                 Set<TreeNode> coll = null;\r
274 //              int hiddenCount = 0;\r
275                 if (event instanceof IStructuralChangeEvent) {\r
276                         IStructuralChangeEvent structuralChangeEvent = (IStructuralChangeEvent) event;\r
277                         if (structuralChangeEvent.isVerticalStructureChanged()) {\r
278                                 // store old indices\r
279                                 internalRefresh = true;\r
280                                 ((GETreeRowModel<?>)getModel()).clear();\r
281 //                              hiddenCount = getHiddenRowIndexes().size();\r
282                                 getHiddenRowIndexes().clear();\r
283                                 // store expanded nodes and clear disposed nodes.\r
284                                 coll = new HashSet<>();\r
285                                 for (TreeNode n : expanded)\r
286                                         if (!n.isDisposed())\r
287                                                 coll.add(n);\r
288                                 expanded.clear();\r
289                                 expanded.addAll(coll);\r
290                                 // filter hidden nodes (nodes that have collapsed ancestors)\r
291                                 coll.clear();\r
292                                 for (TreeNode n : expanded)\r
293                                         if (!n.isHidden())\r
294                                                 coll.add(n);\r
295                         }\r
296                 }\r
297                 super.handleLayerEvent(event);\r
298                 if (coll != null) {\r
299                         _collapseAllRows();\r
300                         // expand new indices\r
301                         int ind[] = new int[coll.size()];\r
302                         Iterator<TreeNode> iter = coll.iterator();\r
303                         for (int i = 0; i < ind.length; i++) {\r
304                                 ind[i] = treeData.indexOf(iter.next());\r
305                         }\r
306                         expandTreeRow(ind);\r
307 //                      if (getHiddenRowIndexes().size() != hiddenCount) {\r
308 //                              System.out.println(getHiddenRowIndexes().size() + " != " + hiddenCount);\r
309 //                              ((GETreeRowModel<?>)getModel()).clear();\r
310 //                              getHiddenRowIndexes().clear();\r
311 //                              _collapseAllRows();\r
312 //                              //collapseAll();\r
313 //                              // expand new indices\r
314 //                              iter = coll.iterator();\r
315 //                              for (int i = 0; i < ind.length; i++) {\r
316 //                                      ind[i] = treeData.indexOf(iter.next());\r
317 //                              }\r
318 //                              expandTreeRow(ind);\r
319 //                      }\r
320                         internalRefresh = false;\r
321                 }\r
322         }\r
323         \r
324         private boolean internalRefresh = false;\r
325         \r
326         public void fireLayerEvent(ILayerEvent event) {\r
327                 if (!internalRefresh)\r
328                         super.fireLayerEvent(event);\r
329                 \r
330         }\r
331         \r
332         public Set<TreeNode> getExpanded() {\r
333                 return expanded;\r
334         }\r
335         \r
336         List<int[]> hiddenPos;\r
337         \r
338         @Override\r
339         public int getStartYOfRowPosition(int localRowPosition) {\r
340                 Integer cachedStartY = startYCache.get(Integer.valueOf(localRowPosition));\r
341                 if (cachedStartY != null) {\r
342                         return cachedStartY.intValue();\r
343                 }\r
344                 \r
345                 IUniqueIndexLayer underlyingLayer = (IUniqueIndexLayer) getUnderlyingLayer();\r
346                 int underlyingPosition = localToUnderlyingRowPosition(localRowPosition);\r
347                 int underlyingStartY = underlyingLayer.getStartYOfRowPosition(underlyingPosition);\r
348                 if (underlyingStartY < 0) {\r
349                         return -1;\r
350                 }\r
351 \r
352                 int h = 0;\r
353                 int start = 0;\r
354                 \r
355                 if (hiddenPos.size() < 2) {                       // is cache empty? (hiddenPos contains {0,0} element by default) \r
356                         if (getHiddenRowIndexes().size() > 0)         // check if there are hidden rows.\r
357                                 start = getHiddenRowIndexes().iterator().next();\r
358                 } else {\r
359                         int[] d =  hiddenPos.get(hiddenPos.size()-1); // take the last element of the cache.\r
360                         start = d[0]+1;                               // set to search from the next element.\r
361                         h = d[1];\r
362                 }\r
363                 if (start < underlyingPosition) {                 // check if we can find the amount of hidden space from the cache.\r
364                                                                       // cache positions of hidden nodes and hidden space.\r
365                         //for (Integer hiddenIndex : ((TreeSet<Integer>)getHiddenRowIndexes()).tailSet(start)) {\r
366                         for (int hiddenIndex : ((IntRBTreeSet)getHiddenRowIndexes()).tailSet(start)) {\r
367                                                                           // safety check (could be disabled, but this does not seem to cause considerable performance hit)\r
368                                 int hiddenPosition = underlyingLayer.getRowPositionByIndex(hiddenIndex);//.intValue());\r
369                                 if (hiddenPosition != hiddenIndex)//.intValue())\r
370                                         throw new RuntimeException("Underlying layer is swithing indices");\r
371                                 if (hiddenPosition >= 0 && hiddenPosition <= underlyingPosition) {\r
372                                         h += underlyingLayer.getRowHeightByPosition(hiddenPosition); \r
373                                         hiddenPos.add(new int[]{hiddenPosition,h});\r
374                                 } else if (hiddenPosition > underlyingPosition) {\r
375                                         break;\r
376                                 }\r
377                         }\r
378                 } else {\r
379                         // use binary search to find hidden space.\r
380                         h = 0;\r
381                         int index = Collections.binarySearch(hiddenPos, new int[]{underlyingPosition,0}, comparator);\r
382                         if (index < 0) { // exact element is not cached, but we can use the closest match.\r
383                                 index = -index-2;\r
384                         }  \r
385                         h = hiddenPos.get(index)[1];\r
386                 }\r
387                 underlyingStartY -= h;\r
388                 startYCache.put(Integer.valueOf(localRowPosition), Integer.valueOf(underlyingStartY));\r
389                 return underlyingStartY;\r
390         }\r
391         \r
392         \r
393         private static class FirstElementComparator implements Comparator<int[]> {\r
394                 @Override\r
395                 public int compare(int[] o1, int[] o2) {\r
396                         return o1[0]-o2[0];\r
397                 }\r
398         }\r
399         \r
400 }