]> gerrit.simantics Code Review - simantics/platform.git/blob
a44944ce6a6792b9dbe36138168f2f4dd4bafdeb
[simantics/platform.git] /
1 /*******************************************************************************
2  * Copyright (c) 2012 Original authors and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  * 
8  * Contributors:
9  *     Original authors and others - initial API and implementation
10  ******************************************************************************/
11 package org.simantics.browsing.ui.nattable.override;
12
13 import java.util.ArrayList;
14 import java.util.Collection;
15 import java.util.List;
16
17 import org.eclipse.nebula.widgets.nattable.command.ILayerCommand;
18 import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
19 import org.eclipse.nebula.widgets.nattable.hideshow.command.MultiRowHideCommand;
20 import org.eclipse.nebula.widgets.nattable.hideshow.command.RowHideCommand;
21 import org.eclipse.nebula.widgets.nattable.hideshow.event.HideRowPositionsEvent;
22 import org.eclipse.nebula.widgets.nattable.hideshow.event.ShowRowPositionsEvent;
23 import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer;
24 import org.eclipse.nebula.widgets.nattable.layer.LabelStack;
25 import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
26 import org.eclipse.nebula.widgets.nattable.painter.cell.BackgroundPainter;
27 import org.eclipse.nebula.widgets.nattable.painter.cell.CellPainterWrapper;
28 import org.eclipse.nebula.widgets.nattable.painter.cell.ICellPainter;
29 import org.eclipse.nebula.widgets.nattable.tree.ITreeRowModel;
30 import org.eclipse.nebula.widgets.nattable.tree.TreeLayer;
31 import org.eclipse.nebula.widgets.nattable.tree.config.DefaultTreeLayerConfiguration;
32 import org.eclipse.nebula.widgets.nattable.tree.config.TreeConfigAttributes;
33 import org.eclipse.nebula.widgets.nattable.tree.painter.IndentedTreeImagePainter;
34
35 import it.unimi.dsi.fastutil.ints.IntRBTreeSet;
36
37 public class TreeLayer2 extends AbstractRowHideShowLayer2 {
38
39     //private static final Log log = LogFactory.getLog(TreeLayer.class);
40
41     public static final String TREE_COLUMN_CELL = "TREE_COLUMN_CELL"; //$NON-NLS-1$
42
43     public static final int TREE_COLUMN_NUMBER = 0;
44
45     /**
46      * Flag to configure whether the tree column should be identified by
47      * position or by index. Default is position.
48      */
49     private boolean useTreeColumnIndex = false;
50
51     /**
52      * The ITreeRowModelListener that is used to get information about the tree
53      * structure.
54      */
55     private final ITreeRowModel<?> treeRowModel;
56
57     /**
58      * Collection of all row indexes that are hidden if tree nodes are
59      * collapsed.
60      * <p>
61      * Note: This collection is only in use if the used {@link ITreeRowModel}
62      * implementation is returning the row indexes of affected rows on
63      * expand/collapse. There are also implementations that use another approach
64      * where the hide/show approach is not used (e.g. GlazedListTreeRowModel)
65      * </p>
66      */
67         private final IntRBTreeSet hiddenRowIndexes = new IntRBTreeSet();
68
69     /**
70      * The IndentedTreeImagePainter that paints indentation to the left of the
71      * configured base painter and icons for expand/collapse if possible, to
72      * render tree structure accordingly.
73      */
74     private IndentedTreeImagePainter indentedTreeImagePainter;
75
76     /**
77      * Creates a TreeLayer instance based on the given information. Will use a
78      * default IndentedTreeImagePainter that uses 10 pixels for indentation and
79      * simple + and - icons for expand/collapse icons. It also uses the
80      * DefaultTreeLayerConfiguration.
81      *
82      * @param underlyingLayer
83      *            The underlying layer on whose top this layer will be set.
84      * @param treeRowModel
85      *            The ITreeRowModelListener that is used to get information
86      *            about the tree structure.
87      */
88     public TreeLayer2(IUniqueIndexLayer underlyingLayer, ITreeRowModel<?> treeRowModel) {
89         this(underlyingLayer, treeRowModel, new IndentedTreeImagePainter());
90     }
91
92     /**
93      * Creates a TreeLayer instance based on the given information. Allows to
94      * specify the IndentedTreeImagePainter while using the
95      * DefaultTreeLayerConfiguration.
96      *
97      * @param underlyingLayer
98      *            The underlying layer on whose top this layer will be set.
99      * @param treeRowModel
100      *            The ITreeRowModelListener that is used to get information
101      *            about the tree structure.
102      * @param indentedTreeImagePainter
103      *            The IndentedTreeImagePainter that paints indentation to the
104      *            left of the configured base painter and icons for
105      *            expand/collapse if possible, to render tree structure
106      *            accordingly.
107      */
108     public TreeLayer2(
109             IUniqueIndexLayer underlyingLayer,
110             ITreeRowModel<?> treeRowModel,
111             IndentedTreeImagePainter indentedTreeImagePainter) {
112         this(underlyingLayer, treeRowModel, indentedTreeImagePainter, true);
113     }
114
115     /**
116      * Creates a TreeLayer instance based on the given information. Will use a
117      * default IndentedTreeImagePainter that uses 10 pixels for indentation and
118      * simple + and - icons for expand/collapse icons.
119      *
120      * @param underlyingLayer
121      *            The underlying layer on whose top this layer will be set.
122      * @param treeRowModel
123      *            The ITreeRowModelListener that is used to get information
124      *            about the tree structure.
125      * @param useDefaultConfiguration
126      *            <code>true</code> to use the DefaultTreeLayerConfiguration,
127      *            <code>false</code> if you want to specify your own
128      *            configuration.
129      */
130     public TreeLayer2(
131             IUniqueIndexLayer underlyingLayer,
132             ITreeRowModel<?> treeRowModel,
133             boolean useDefaultConfiguration) {
134         this(underlyingLayer,
135                 treeRowModel,
136                 new IndentedTreeImagePainter(),
137                 useDefaultConfiguration);
138     }
139
140     /**
141      * Creates a TreeLayer instance based on the given information.
142      *
143      * @param underlyingLayer
144      *            The underlying layer on whose top this layer will be set.
145      * @param treeRowModel
146      *            The ITreeRowModelListener that is used to get information
147      *            about the tree structure.
148      * @param indentedTreeImagePainter
149      *            The IndentedTreeImagePainter that paints indentation to the
150      *            left of the configured base painter and icons for
151      *            expand/collapse if possible, to render tree structure
152      *            accordingly.
153      * @param useDefaultConfiguration
154      *            <code>true</code> to use the DefaultTreeLayerConfiguration,
155      *            <code>false</code> if you want to specify your own
156      *            configuration.
157      */
158     public TreeLayer2(
159             IUniqueIndexLayer underlyingLayer,
160             ITreeRowModel<?> treeRowModel,
161             IndentedTreeImagePainter indentedTreeImagePainter,
162             boolean useDefaultConfiguration) {
163
164         super(underlyingLayer);
165         this.treeRowModel = treeRowModel;
166
167         if (useDefaultConfiguration) {
168             addConfiguration(new DefaultTreeLayerConfiguration2(this));
169         }
170
171         this.indentedTreeImagePainter = indentedTreeImagePainter;
172
173         registerCommandHandler(new TreeExpandCollapseCommandHandler(this));
174         registerCommandHandler(new TreeCollapseAllCommandHandler(this));
175         registerCommandHandler(new TreeExpandAllCommandHandler(this));
176         registerCommandHandler(new TreeExpandToLevelCommandHandler(this));
177     }
178
179     @Override
180     public LabelStack getConfigLabelsByPosition(int columnPosition, int rowPosition) {
181         LabelStack configLabels = super.getConfigLabelsByPosition(columnPosition, rowPosition);
182
183         if (isTreeColumn(columnPosition)) {
184             configLabels.addLabelOnTop(TREE_COLUMN_CELL);
185
186             int rowIndex = getRowIndexByPosition(rowPosition);
187             configLabels.addLabelOnTop(
188                     DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + this.treeRowModel.depth(rowIndex));
189             if (!this.treeRowModel.hasChildren(rowIndex)) {
190                 configLabels.addLabelOnTop(DefaultTreeLayerConfiguration.TREE_LEAF_CONFIG_TYPE);
191             } else {
192                 if (this.treeRowModel.isCollapsed(rowIndex)) {
193                     configLabels.addLabelOnTop(DefaultTreeLayerConfiguration.TREE_COLLAPSED_CONFIG_TYPE);
194                 } else {
195                     configLabels.addLabelOnTop(DefaultTreeLayerConfiguration.TREE_EXPANDED_CONFIG_TYPE);
196                 }
197             }
198         }
199         return configLabels;
200     }
201
202     /**
203      * @return The ITreeRowModelListener that is used to get information about
204      *         the tree structure.
205      */
206     public ITreeRowModel<?> getModel() {
207         return this.treeRowModel;
208     }
209
210     /**
211      * @return The IndentedTreeImagePainter that paints indentation to the left
212      *         of the configured base painter and icons for expand/collapse if
213      *         possible, to render tree structure accordingly.
214      *
215      * @deprecated since 1.1 the configured TreeImagePainter should be used
216      *             instead of the hard referenced one
217      */
218     @Deprecated
219     public IndentedTreeImagePainter getIndentedTreeImagePainter() {
220         return this.indentedTreeImagePainter;
221     }
222
223     /**
224      * @return The ICellPainter that is used to paint the images in the tree by
225      *         the IndentedTreeImagePainter. Usually it is some type of
226      *         TreeImagePainter that paints expand/collapse/leaf icons regarding
227      *         the node state.<br>
228      *         Can be <code>null</code> if set explicitly to the
229      *         IndentedTreeImagePainter!
230      *
231      * @deprecated since 1.1 the configured TreeImagePainter should be used
232      *             instead of the hard referenced one
233      */
234     @Deprecated
235     public ICellPainter getTreeImagePainter() {
236         return this.indentedTreeImagePainter != null ? this.indentedTreeImagePainter
237                 .getTreeImagePainter() : null;
238     }
239
240     /**
241      * @param columnPosition
242      *            The column position to check.
243      * @return <code>true</code> if the given column position is the tree
244      *         column, <code>false</code> if not.
245      */
246     private boolean isTreeColumn(int columnPosition) {
247         if (this.useTreeColumnIndex)
248             return getColumnIndexByPosition(columnPosition) == TREE_COLUMN_NUMBER;
249
250         return columnPosition == TREE_COLUMN_NUMBER;
251     }
252
253     @Override
254     public ICellPainter getCellPainter(
255             int columnPosition, int rowPosition,
256             ILayerCell cell, IConfigRegistry configRegistry) {
257         ICellPainter cellPainter = super.getCellPainter(
258                 columnPosition, rowPosition, cell, configRegistry);
259
260         if (cell.getConfigLabels().hasLabel(TREE_COLUMN_CELL)) {
261
262             ICellPainter treeCellPainter = configRegistry.getConfigAttribute(
263                     TreeConfigAttributes.TREE_STRUCTURE_PAINTER,
264                     cell.getDisplayMode(),
265                     cell.getConfigLabels().getLabels());
266
267             if (treeCellPainter != null) {
268                 ICellPainter innerWrapper = treeCellPainter;
269                 IndentedTreeImagePainter treePainter = null;
270                 if (innerWrapper instanceof IndentedTreeImagePainter) {
271                     treePainter = (IndentedTreeImagePainter) innerWrapper;
272                 } else {
273                     while (treePainter == null
274                             && innerWrapper != null
275                             && innerWrapper instanceof CellPainterWrapper
276                             && ((CellPainterWrapper) innerWrapper).getWrappedPainter() != null) {
277
278                         innerWrapper = ((CellPainterWrapper) innerWrapper).getWrappedPainter();
279                         if (innerWrapper instanceof IndentedTreeImagePainter) {
280                             treePainter = (IndentedTreeImagePainter) innerWrapper;
281                         }
282                     }
283                 }
284
285                 if (treePainter != null) {
286                     treePainter.setBaseCellPainter(cellPainter);
287                     cellPainter = treeCellPainter;
288                 } else {
289                     // log error
290 //                    log.warn("There is no IndentedTreeImagePainter found for TREE_STRUCTURE_PAINTER, " //$NON-NLS-1$
291 //                            + "using local configured IndentedTreeImagePainter as fallback"); //$NON-NLS-1$
292                     // fallback
293                     this.indentedTreeImagePainter.setBaseCellPainter(cellPainter);
294                     cellPainter = new BackgroundPainter(this.indentedTreeImagePainter);
295                 }
296             } else {
297                 // backwards compatibility fallback
298                 this.indentedTreeImagePainter.setBaseCellPainter(cellPainter);
299                 cellPainter = new BackgroundPainter(this.indentedTreeImagePainter);
300             }
301         }
302
303         return cellPainter;
304     }
305
306     @Override
307     public boolean isRowIndexHidden(int rowIndex) {
308         return this.hiddenRowIndexes.contains(rowIndex)
309                 || isHiddenInUnderlyingLayer(rowIndex);
310     }
311
312     @Override
313     public Collection<Integer> getHiddenRowIndexes() {
314         return this.hiddenRowIndexes;
315     }
316
317     /**
318      * Performs an expand/collapse action dependent on the current state of the
319      * tree node at the given row index.
320      *
321      * @param parentIndex
322      *            The index of the row that shows the tree node for which the
323      *            expand/collapse action should be performed.
324      */
325     public void expandOrCollapseIndex(int parentIndex) {
326         if (this.treeRowModel.isCollapsed(parentIndex)) {
327             expandTreeRow(parentIndex);
328         } else {
329             collapseTreeRow(parentIndex);
330         }
331     }
332
333     /**
334      * Collapses the tree node for the given row index.
335      *
336      * @param parentIndex
337      *            The index of the row that shows the node that should be
338      *            collapsed
339      */
340     public void collapseTreeRow(int parentIndex) {
341         List<Integer> rowIndexes = this.treeRowModel.collapse(parentIndex);
342         List<Integer> rowPositions = new ArrayList<Integer>();
343         for (Integer rowIndex : rowIndexes) {
344             int rowPos = getRowPositionByIndex(rowIndex);
345             // if the rowPos is negative, it is not visible because of hidden
346             // state in an underlying layer
347             if (rowPos >= 0) {
348                 rowPositions.add(rowPos);
349             }
350         }
351         this.hiddenRowIndexes.addAll(rowIndexes);
352         invalidateCache();
353         fireLayerEvent(new HideRowPositionsEvent(this, rowPositions));
354     }
355
356     /**
357      * Collapses all tree nodes in the tree.
358      */
359     public void collapseAll() {
360         List<Integer> rowIndexes = this.treeRowModel.collapseAll();
361         List<Integer> rowPositions = new ArrayList<Integer>();
362         for (Integer rowIndex : rowIndexes) {
363             int rowPos = getRowPositionByIndex(rowIndex);
364             // if the rowPos is negative, it is not visible because of hidden
365             // state in an underlying layer
366             if (rowPos >= 0) {
367                 rowPositions.add(rowPos);
368             }
369         }
370         this.hiddenRowIndexes.addAll(rowIndexes);
371         invalidateCache();
372         fireLayerEvent(new HideRowPositionsEvent(this, rowPositions));
373     }
374
375     /**
376      * Expands the tree node for the given row index.
377      *
378      * @param parentIndex
379      *            The index of the row that shows the node that should be
380      *            expanded
381      */
382     public void expandTreeRow(int parentIndex) {
383         List<Integer> rowIndexes = this.treeRowModel.expand(parentIndex);
384         // Bug 432865: iterating and removing every single item is faster than
385         // removeAll()
386         for (final Integer expandedChildRowIndex : rowIndexes) {
387             this.hiddenRowIndexes.remove(expandedChildRowIndex);
388         }
389         invalidateCache();
390         fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes));
391     }
392
393     /**
394      * Expands the tree node for the given row index in the tree to a certain
395      * level.
396      *
397      * @param parentIndex
398      *            The index of the row that shows the node that should be
399      *            expanded
400      * @param level
401      *            The level to which the tree node should be expanded.
402      */
403     public void expandTreeRowToLevel(int parentIndex, int level) {
404         List<Integer> rowIndexes = this.treeRowModel.expandToLevel(parentIndex, level);
405         // Bug 432865: iterating and removing every single item is faster than
406         // removeAll()
407         for (final Integer expandedChildRowIndex : rowIndexes) {
408             this.hiddenRowIndexes.remove(expandedChildRowIndex);
409         }
410         invalidateCache();
411         fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes));
412     }
413
414     /**
415      * Expands all tree nodes in the tree.
416      */
417     public void expandAll() {
418         List<Integer> rowIndexes = this.treeRowModel.expandAll();
419         this.hiddenRowIndexes.clear();
420         invalidateCache();
421         fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes));
422     }
423
424     /**
425      * Expands all tree nodes in the tree to a certain level.
426      *
427      * @param level
428      *            The level to which the tree node should be expanded.
429      */
430     public void expandAllToLevel(int level) {
431         List<Integer> rowIndexes = this.treeRowModel.expandToLevel(level);
432         // Bug 432865: iterating and removing every single item is faster than
433         // removeAll()
434 //        for (final Integer expandedChildRowIndex : rowIndexes) {
435 //            this.hiddenRowIndexes.remove(expandedChildRowIndex);
436 //        }
437         if (rowIndexes == null)
438                 return;
439         for (int i = rowIndexes.size()-1; i>=0; i--) {
440                 this.hiddenRowIndexes.remove(rowIndexes.get(i));
441         }
442         invalidateCache();
443         fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes));
444     }
445
446     /**
447      * Checks the underlying layer if the row is hidden by another layer.
448      *
449      * @param rowIndex
450      *            The index of the row whose hidden state should be checked
451      * @return <code>true</code> if the row at the given index is hidden in the
452      *         underlying layer <code>false</code> if not.
453      */
454     private boolean isHiddenInUnderlyingLayer(int rowIndex) {
455         IUniqueIndexLayer underlyingLayer = (IUniqueIndexLayer) getUnderlyingLayer();
456         return (underlyingLayer.getRowPositionByIndex(rowIndex) == -1);
457     }
458
459     @Override
460     public boolean doCommand(ILayerCommand command) {
461         // special command transformations are needed to hide also child nodes
462         if (command instanceof RowHideCommand) {
463             return handleRowHideCommand((RowHideCommand) command);
464         } else if (command instanceof MultiRowHideCommand) {
465             return handleMultiRowHideCommand((MultiRowHideCommand) command);
466         }
467         return super.doCommand(command);
468     }
469
470     /**
471      * Checks if the given command tries to hide a row that is a node that is
472      * not collapsed and has children. In that case also the child rows need to
473      * be hidden.
474      *
475      * @param command
476      *            The {@link RowHideCommand} to process
477      * @return <code>true</code> if the command has been handled,
478      *         <code>false</code> otherwise
479      */
480     protected boolean handleRowHideCommand(RowHideCommand command) {
481         // transform position to index
482         if (command.convertToTargetLayer(this)) {
483             int rowIndex = getRowIndexByPosition(command.getRowPosition());
484             if (this.treeRowModel.hasChildren(rowIndex)
485                     && !this.treeRowModel.isCollapsed(rowIndex)) {
486                 List<Integer> childIndexes = this.treeRowModel.getChildIndexes(rowIndex);
487                 int[] childPositions = new int[childIndexes.size() + 1];
488                 childPositions[0] = command.getRowPosition();
489                 for (int i = 1; i < childIndexes.size() + 1; i++) {
490                     int childPos = getRowPositionByIndex(childIndexes.get(i - 1));
491                     childPositions[i] = childPos;
492                 }
493                 return super.doCommand(new MultiRowHideCommand(this, childPositions));
494             }
495         }
496         return super.doCommand(command);
497     }
498
499     /**
500      * Checks if the given command tries to hide rows that are nodes that are
501      * not collapsed and have children. In that case also the child rows need to
502      * be hidden.
503      *
504      * @param command
505      *            The {@link MultiRowHideCommand} to process
506      * @return <code>true</code> if the command has been handled,
507      *         <code>false</code> otherwise
508      */
509     protected boolean handleMultiRowHideCommand(MultiRowHideCommand command) {
510         // transform position to index
511         if (command.convertToTargetLayer(this)) {
512             List<Integer> rowPositionsToHide = new ArrayList<Integer>();
513             for (Integer rowPos : command.getRowPositions()) {
514                 rowPositionsToHide.add(rowPos);
515                 int rowIndex = getRowIndexByPosition(rowPos);
516                 if (this.treeRowModel.hasChildren(rowIndex)
517                         && !this.treeRowModel.isCollapsed(rowIndex)) {
518                     List<Integer> childIndexes = this.treeRowModel.getChildIndexes(rowIndex);
519                     for (Integer childIndex : childIndexes) {
520                         rowPositionsToHide.add(getRowPositionByIndex(childIndex));
521                     }
522                 }
523             }
524
525             int[] childPositions = new int[rowPositionsToHide.size()];
526             for (int i = 0; i < rowPositionsToHide.size(); i++) {
527                 childPositions[i] = rowPositionsToHide.get(i);
528             }
529             return super.doCommand(new MultiRowHideCommand(this, childPositions));
530         }
531         return super.doCommand(command);
532     }
533
534     /**
535      * @return <code>true</code> if the column index is used to determine the
536      *         tree column, <code>false</code> if the column position is used.
537      *         Default is <code>false</code>.
538      */
539     public boolean isUseTreeColumnIndex() {
540         return this.useTreeColumnIndex;
541     }
542
543     /**
544      * Configure whether (column index == 0) or (column position == 0) should be
545      * performed to identify the tree column.
546      *
547      * @param useTreeColumnIndex
548      *            <code>true</code> if the column index should be used to
549      *            determine the tree column, <code>false</code> if the column
550      *            position should be used.
551      */
552     public void setUseTreeColumnIndex(boolean useTreeColumnIndex) {
553         this.useTreeColumnIndex = useTreeColumnIndex;
554     }
555
556     /**
557      * @since 1.4
558      */
559     @Override
560     public Collection<String> getProvidedLabels() {
561         Collection<String> result = super.getProvidedLabels();
562
563         result.add(TreeLayer.TREE_COLUMN_CELL);
564         result.add(DefaultTreeLayerConfiguration.TREE_LEAF_CONFIG_TYPE);
565         result.add(DefaultTreeLayerConfiguration.TREE_COLLAPSED_CONFIG_TYPE);
566         result.add(DefaultTreeLayerConfiguration.TREE_EXPANDED_CONFIG_TYPE);
567         // configure 5 levels to be configurable via CSS
568         // if you need more you need to override this method
569         result.add(DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + "0"); //$NON-NLS-1$
570         result.add(DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + "1"); //$NON-NLS-1$
571         result.add(DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + "2"); //$NON-NLS-1$
572         result.add(DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + "3"); //$NON-NLS-1$
573         result.add(DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + "4"); //$NON-NLS-1$
574
575         return result;
576     }
577 }