1 package org.simantics.browsing.ui.nattable;
\r
3 import java.util.ArrayList;
\r
4 import java.util.Collection;
\r
5 import java.util.Collections;
\r
6 import java.util.Comparator;
\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
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
21 import it.unimi.dsi.fastutil.ints.IntRBTreeSet;
\r
23 * NatTable TreeLayer for IEcoReportTask tree.
\r
25 * Keeps track of collapsed nodes so that current sorting mechanism works.
\r
28 * @author Marko Luukkainen <marko.luukkainen@vtt.fi>
\r
31 public class GETreeLayer extends TreeLayer2 {
\r
33 //Set<IEcoReportTask> collapsed = new HashSet<IEcoReportTask>();
\r
34 Set<TreeNode> collapsed = new IdentityHashSet<TreeNode>();
\r
35 GETreeData treeData;
\r
36 Comparator<int[]> comparator = new FirstElementComparator();
\r
38 public GETreeLayer(IUniqueIndexLayer underlyingLayer, GETreeRowModel<TreeNode> treeRowModel,boolean useDefaultConfiguration) {
\r
39 super(underlyingLayer, treeRowModel, useDefaultConfiguration);
\r
41 if (underlyingLayer instanceof AbstractRowHideShowLayer) {
\r
42 throw new IllegalArgumentException("Cannot use treelayer above row hide layer");
\r
45 this.treeData = (GETreeData)treeRowModel.getTreeData();
\r
46 hiddenPos = new ArrayList<int[]>();
\r
47 hiddenPos.add(new int[]{0,0});
\r
52 public void collapseTreeRow(int parentIndex) {
\r
53 TreeNode task = treeData.getDataAtIndex(parentIndex);
\r
54 collapsed.add(task);
\r
55 task.setExpanded(false);
\r
56 super.collapseTreeRow(parentIndex);
\r
59 public void fullCollapseTreeRow(int parentIndex) {
\r
60 TreeNode task = treeData.getDataAtIndex(parentIndex);
\r
61 List<Integer> indices = new ArrayList<Integer>();
\r
63 Stack<TreeNode> stack = new Stack<TreeNode>();
\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
70 collapseTreeRow(indices);
\r
74 public void expandTreeRow(int parentIndex) {
\r
75 TreeNode task = treeData.getDataAtIndex(parentIndex);
\r
76 collapsed.remove(task);
\r
77 task.setExpanded(true);
\r
78 super.expandTreeRow(parentIndex);
\r
81 public void expandToTreeRow(int parentIndex) {
\r
82 TreeNode task = treeData.getDataAtIndex(parentIndex);
\r
83 List<TreeNode> ancestors = new ArrayList<TreeNode>();
\r
85 task = task.getParent();
\r
89 ancestors.add(0, task);
\r
91 for (TreeNode t : ancestors) {
\r
92 if (treeData.getDepthOfData(t) >= 0)
\r
93 expandTreeRow(treeData.indexOf(t));
\r
97 public void fullExpandTreeRow(int parentIndex) {
\r
98 TreeNode task = treeData.getDataAtIndex(parentIndex);
\r
99 List<Integer> indices = new ArrayList<Integer>();
\r
101 Stack<TreeNode> stack = new Stack<TreeNode>();
\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
108 expandTreeRow(indices);
\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 collapsed.add(task);
\r
122 rowIndexes.addAll(getModel().collapse(parentIndex));
\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
129 rowPositions.add(rowPos);
\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
137 fireLayerEvent(new HideRowPositionsEvent(this, rowPositions));
\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 collapsed.add(task);
\r
149 rowIndexes.addAll(getModel().collapse(parentIndex));
\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
156 rowPositions.add(rowPos);
\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
164 fireLayerEvent(new HideRowPositionsEvent(this, rowPositions));
\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
177 t.setExpanded(false);
\r
179 getModel().collapse(i);
\r
182 this.getHiddenRowIndexes().addAll(rowIndexes);
\r
184 fireLayerEvent(new HideRowPositionsEvent(this, rowIndexes));
\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 collapsed.remove(task);
\r
193 rowIndexes.addAll(getModel().expand(parentIndex));
\r
196 //Implementation uses tree set, so removing in reverse order is faster.
\r
197 for (int i = rowIndexes.size() -1 ; i >= 0; i--) {
\r
198 this.getHiddenRowIndexes().remove(rowIndexes.get(i));
\r
200 //this.getHiddenRowIndexes().removeAll(rowIndexes);
\r
202 fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes));
\r
205 public void expandTreeRow(List<Integer> parentIndices) {
\r
206 List<Integer> rowIndexes = new ArrayList<Integer>();
\r
207 for (int parentIndex : parentIndices) {
\r
208 TreeNode task = treeData.getDataAtIndex(parentIndex);
\r
209 task.setExpanded(true);
\r
210 collapsed.remove(task);
\r
211 rowIndexes.addAll(getModel().expand(parentIndex));
\r
214 //Implementation uses tree set, so removing in reverse order is faster.
\r
215 for (int i = rowIndexes.size() -1 ; i >= 0; i--) {
\r
216 this.getHiddenRowIndexes().remove(rowIndexes.get(i));
\r
218 //this.getHiddenRowIndexes().removeAll(rowIndexes);
\r
220 fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes));
\r
223 public void expandAllRows() {
\r
224 Collection<Integer> parentIndices = getHiddenRowIndexes();
\r
225 List<Integer> rowIndexes = new ArrayList<Integer>();
\r
226 for (int parentIndex : parentIndices) {
\r
227 rowIndexes.addAll(getModel().expand(parentIndex));
\r
229 for (TreeNode t : collapsed)
\r
230 t.setExpanded(true);
\r
232 getHiddenRowIndexes().clear();
\r
233 ((GETreeRowModel)getModel()).clear();
\r
235 fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes));
\r
239 protected void invalidateCache() {
\r
240 super.invalidateCache();
\r
242 hiddenPos.add(new int[]{0,0});
\r
246 public void handleLayerEvent(ILayerEvent event) {
\r
247 // Currently sorting is implemented by sorting the underlaying list.
\r
248 // Since all layers are storing just indices, we have to keep track the indices after sorting,
\r
249 // and refresh the layers accordingly.
\r
251 // Another option would use some sort of sorting layers, so that the original data is kept intact, and
\r
252 // sorting layer would map the row indexes to sorted row positions.
\r
254 // preserve collapsed nodes
\r
255 Set<TreeNode> coll = null;
\r
256 if (event instanceof IStructuralChangeEvent) {
\r
257 IStructuralChangeEvent structuralChangeEvent = (IStructuralChangeEvent) event;
\r
258 if (structuralChangeEvent.isVerticalStructureChanged()) {
\r
259 // expand old indices
\r
260 ((GETreeRowModel)getModel()).clear();
\r
261 getHiddenRowIndexes().clear();
\r
265 super.handleLayerEvent(event);
\r
266 if (coll != null) {
\r
267 // collapse new indices
\r
268 int ind[] = new int[coll.size()];
\r
269 Iterator<TreeNode> iter = coll.iterator();
\r
270 for (int i = 0; i < ind.length; i++) {
\r
271 ind[i] = treeData.indexOf(iter.next());
\r
273 collapseTreeRow(ind);
\r
277 public Set<TreeNode> getCollapsed() {
\r
281 List<int[]> hiddenPos;
\r
284 public int getStartYOfRowPosition(int localRowPosition) {
\r
285 Integer cachedStartY = startYCache.get(Integer.valueOf(localRowPosition));
\r
286 if (cachedStartY != null) {
\r
287 return cachedStartY.intValue();
\r
290 IUniqueIndexLayer underlyingLayer = (IUniqueIndexLayer) getUnderlyingLayer();
\r
291 int underlyingPosition = localToUnderlyingRowPosition(localRowPosition);
\r
292 int underlyingStartY = underlyingLayer.getStartYOfRowPosition(underlyingPosition);
\r
293 if (underlyingStartY < 0) {
\r
300 if (hiddenPos.size() < 2) { // is cache empty? (hiddenPos contains {0,0} element by default)
\r
301 if (getHiddenRowIndexes().size() > 0) // check if there are hidden rows.
\r
302 start = getHiddenRowIndexes().iterator().next();
\r
304 int[] d = hiddenPos.get(hiddenPos.size()-1); // take the last element of the cache.
\r
305 start = d[0]+1; // set to search from the next element.
\r
308 if (start < underlyingPosition) { // check if we can find the amount of hidden space from the cache.
\r
309 // cache positions of hidden nodes and hidden space.
\r
310 //for (Integer hiddenIndex : ((TreeSet<Integer>)getHiddenRowIndexes()).tailSet(start)) {
\r
311 for (int hiddenIndex : ((IntRBTreeSet)getHiddenRowIndexes()).tailSet(start)) {
\r
312 // safety check (could be disabled, but this does not seem to cause considerable performance hit)
\r
313 int hiddenPosition = underlyingLayer.getRowPositionByIndex(hiddenIndex);//.intValue());
\r
314 if (hiddenPosition != hiddenIndex)//.intValue())
\r
315 throw new RuntimeException("Underlying layer is swithing indices");
\r
316 if (hiddenPosition >= 0 && hiddenPosition <= underlyingPosition) {
\r
317 h += underlyingLayer.getRowHeightByPosition(hiddenPosition);
\r
318 hiddenPos.add(new int[]{hiddenPosition,h});
\r
319 } else if (hiddenPosition > underlyingPosition) {
\r
324 // use binary search to find hidden space.
\r
326 int index = Collections.binarySearch(hiddenPos, new int[]{underlyingPosition,0}, comparator);
\r
327 if (index < 0) { // exact element is not cached, but we can use the closest match.
\r
330 h = hiddenPos.get(index)[1];
\r
332 underlyingStartY -= h;
\r
333 startYCache.put(Integer.valueOf(localRowPosition), Integer.valueOf(underlyingStartY));
\r
334 return underlyingStartY;
\r
338 private static class FirstElementComparator implements Comparator<int[]> {
\r
340 public int compare(int[] o1, int[] o2) {
\r
341 return o1[0]-o2[0];
\r