1 /*******************************************************************************
\r
2 * Copyright (c) 2012 Original authors and others.
\r
3 * All rights reserved. This program and the accompanying materials
\r
4 * are made available under the terms of the Eclipse Public License v1.0
\r
5 * which accompanies this distribution, and is available at
\r
6 * http://www.eclipse.org/legal/epl-v10.html
\r
9 * Original authors and others - initial API and implementation
\r
10 ******************************************************************************/
\r
11 package org.simantics.browsing.ui.nattable.override;
\r
13 import java.util.ArrayList;
\r
14 import java.util.Collection;
\r
15 import java.util.List;
\r
17 import org.eclipse.nebula.widgets.nattable.command.ILayerCommand;
\r
18 import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
\r
19 import org.eclipse.nebula.widgets.nattable.hideshow.command.MultiRowHideCommand;
\r
20 import org.eclipse.nebula.widgets.nattable.hideshow.command.RowHideCommand;
\r
21 import org.eclipse.nebula.widgets.nattable.hideshow.event.HideRowPositionsEvent;
\r
22 import org.eclipse.nebula.widgets.nattable.hideshow.event.ShowRowPositionsEvent;
\r
23 import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer;
\r
24 import org.eclipse.nebula.widgets.nattable.layer.LabelStack;
\r
25 import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
\r
26 import org.eclipse.nebula.widgets.nattable.painter.cell.BackgroundPainter;
\r
27 import org.eclipse.nebula.widgets.nattable.painter.cell.CellPainterWrapper;
\r
28 import org.eclipse.nebula.widgets.nattable.painter.cell.ICellPainter;
\r
29 import org.eclipse.nebula.widgets.nattable.tree.ITreeRowModel;
\r
30 import org.eclipse.nebula.widgets.nattable.tree.TreeLayer;
\r
31 import org.eclipse.nebula.widgets.nattable.tree.config.DefaultTreeLayerConfiguration;
\r
32 import org.eclipse.nebula.widgets.nattable.tree.config.TreeConfigAttributes;
\r
33 import org.eclipse.nebula.widgets.nattable.tree.painter.IndentedTreeImagePainter;
\r
35 import it.unimi.dsi.fastutil.ints.IntRBTreeSet;
\r
37 public class TreeLayer2 extends AbstractRowHideShowLayer2 {
\r
39 //private static final Log log = LogFactory.getLog(TreeLayer.class);
\r
41 public static final String TREE_COLUMN_CELL = "TREE_COLUMN_CELL"; //$NON-NLS-1$
\r
43 public static final int TREE_COLUMN_NUMBER = 0;
\r
46 * Flag to configure whether the tree column should be identified by
\r
47 * position or by index. Default is position.
\r
49 private boolean useTreeColumnIndex = false;
\r
52 * The ITreeRowModelListener that is used to get information about the tree
\r
55 private final ITreeRowModel<?> treeRowModel;
\r
58 * Collection of all row indexes that are hidden if tree nodes are
\r
61 * Note: This collection is only in use if the used {@link ITreeRowModel}
\r
62 * implementation is returning the row indexes of affected rows on
\r
63 * expand/collapse. There are also implementations that use another approach
\r
64 * where the hide/show approach is not used (e.g. GlazedListTreeRowModel)
\r
67 private final IntRBTreeSet hiddenRowIndexes = new IntRBTreeSet();
\r
70 * The IndentedTreeImagePainter that paints indentation to the left of the
\r
71 * configured base painter and icons for expand/collapse if possible, to
\r
72 * render tree structure accordingly.
\r
74 private IndentedTreeImagePainter indentedTreeImagePainter;
\r
77 * Creates a TreeLayer instance based on the given information. Will use a
\r
78 * default IndentedTreeImagePainter that uses 10 pixels for indentation and
\r
79 * simple + and - icons for expand/collapse icons. It also uses the
\r
80 * DefaultTreeLayerConfiguration.
\r
82 * @param underlyingLayer
\r
83 * The underlying layer on whose top this layer will be set.
\r
84 * @param treeRowModel
\r
85 * The ITreeRowModelListener that is used to get information
\r
86 * about the tree structure.
\r
88 public TreeLayer2(IUniqueIndexLayer underlyingLayer, ITreeRowModel<?> treeRowModel) {
\r
89 this(underlyingLayer, treeRowModel, new IndentedTreeImagePainter());
\r
93 * Creates a TreeLayer instance based on the given information. Allows to
\r
94 * specify the IndentedTreeImagePainter while using the
\r
95 * DefaultTreeLayerConfiguration.
\r
97 * @param underlyingLayer
\r
98 * The underlying layer on whose top this layer will be set.
\r
99 * @param treeRowModel
\r
100 * The ITreeRowModelListener that is used to get information
\r
101 * about the tree structure.
\r
102 * @param indentedTreeImagePainter
\r
103 * The IndentedTreeImagePainter that paints indentation to the
\r
104 * left of the configured base painter and icons for
\r
105 * expand/collapse if possible, to render tree structure
\r
109 IUniqueIndexLayer underlyingLayer,
\r
110 ITreeRowModel<?> treeRowModel,
\r
111 IndentedTreeImagePainter indentedTreeImagePainter) {
\r
112 this(underlyingLayer, treeRowModel, indentedTreeImagePainter, true);
\r
116 * Creates a TreeLayer instance based on the given information. Will use a
\r
117 * default IndentedTreeImagePainter that uses 10 pixels for indentation and
\r
118 * simple + and - icons for expand/collapse icons.
\r
120 * @param underlyingLayer
\r
121 * The underlying layer on whose top this layer will be set.
\r
122 * @param treeRowModel
\r
123 * The ITreeRowModelListener that is used to get information
\r
124 * about the tree structure.
\r
125 * @param useDefaultConfiguration
\r
126 * <code>true</code> to use the DefaultTreeLayerConfiguration,
\r
127 * <code>false</code> if you want to specify your own
\r
131 IUniqueIndexLayer underlyingLayer,
\r
132 ITreeRowModel<?> treeRowModel,
\r
133 boolean useDefaultConfiguration) {
\r
134 this(underlyingLayer,
\r
136 new IndentedTreeImagePainter(),
\r
137 useDefaultConfiguration);
\r
141 * Creates a TreeLayer instance based on the given information.
\r
143 * @param underlyingLayer
\r
144 * The underlying layer on whose top this layer will be set.
\r
145 * @param treeRowModel
\r
146 * The ITreeRowModelListener that is used to get information
\r
147 * about the tree structure.
\r
148 * @param indentedTreeImagePainter
\r
149 * The IndentedTreeImagePainter that paints indentation to the
\r
150 * left of the configured base painter and icons for
\r
151 * expand/collapse if possible, to render tree structure
\r
153 * @param useDefaultConfiguration
\r
154 * <code>true</code> to use the DefaultTreeLayerConfiguration,
\r
155 * <code>false</code> if you want to specify your own
\r
159 IUniqueIndexLayer underlyingLayer,
\r
160 ITreeRowModel<?> treeRowModel,
\r
161 IndentedTreeImagePainter indentedTreeImagePainter,
\r
162 boolean useDefaultConfiguration) {
\r
164 super(underlyingLayer);
\r
165 this.treeRowModel = treeRowModel;
\r
167 if (useDefaultConfiguration) {
\r
168 addConfiguration(new DefaultTreeLayerConfiguration2(this));
\r
171 this.indentedTreeImagePainter = indentedTreeImagePainter;
\r
173 registerCommandHandler(new TreeExpandCollapseCommandHandler(this));
\r
174 registerCommandHandler(new TreeCollapseAllCommandHandler(this));
\r
175 registerCommandHandler(new TreeExpandAllCommandHandler(this));
\r
176 registerCommandHandler(new TreeExpandToLevelCommandHandler(this));
\r
180 public LabelStack getConfigLabelsByPosition(int columnPosition, int rowPosition) {
\r
181 LabelStack configLabels = super.getConfigLabelsByPosition(columnPosition, rowPosition);
\r
183 if (isTreeColumn(columnPosition)) {
\r
184 configLabels.addLabelOnTop(TREE_COLUMN_CELL);
\r
186 int rowIndex = getRowIndexByPosition(rowPosition);
\r
187 configLabels.addLabelOnTop(
\r
188 DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + this.treeRowModel.depth(rowIndex));
\r
189 if (!this.treeRowModel.hasChildren(rowIndex)) {
\r
190 configLabels.addLabelOnTop(DefaultTreeLayerConfiguration.TREE_LEAF_CONFIG_TYPE);
\r
192 if (this.treeRowModel.isCollapsed(rowIndex)) {
\r
193 configLabels.addLabelOnTop(DefaultTreeLayerConfiguration.TREE_COLLAPSED_CONFIG_TYPE);
\r
195 configLabels.addLabelOnTop(DefaultTreeLayerConfiguration.TREE_EXPANDED_CONFIG_TYPE);
\r
199 return configLabels;
\r
203 * @return The ITreeRowModelListener that is used to get information about
\r
204 * the tree structure.
\r
206 public ITreeRowModel<?> getModel() {
\r
207 return this.treeRowModel;
\r
211 * @return The IndentedTreeImagePainter that paints indentation to the left
\r
212 * of the configured base painter and icons for expand/collapse if
\r
213 * possible, to render tree structure accordingly.
\r
215 * @deprecated since 1.1 the configured TreeImagePainter should be used
\r
216 * instead of the hard referenced one
\r
219 public IndentedTreeImagePainter getIndentedTreeImagePainter() {
\r
220 return this.indentedTreeImagePainter;
\r
224 * @return The ICellPainter that is used to paint the images in the tree by
\r
225 * the IndentedTreeImagePainter. Usually it is some type of
\r
226 * TreeImagePainter that paints expand/collapse/leaf icons regarding
\r
227 * the node state.<br>
\r
228 * Can be <code>null</code> if set explicitly to the
\r
229 * IndentedTreeImagePainter!
\r
231 * @deprecated since 1.1 the configured TreeImagePainter should be used
\r
232 * instead of the hard referenced one
\r
235 public ICellPainter getTreeImagePainter() {
\r
236 return this.indentedTreeImagePainter != null ? this.indentedTreeImagePainter
\r
237 .getTreeImagePainter() : null;
\r
241 * @param columnPosition
\r
242 * The column position to check.
\r
243 * @return <code>true</code> if the given column position is the tree
\r
244 * column, <code>false</code> if not.
\r
246 private boolean isTreeColumn(int columnPosition) {
\r
247 if (this.useTreeColumnIndex)
\r
248 return getColumnIndexByPosition(columnPosition) == TREE_COLUMN_NUMBER;
\r
250 return columnPosition == TREE_COLUMN_NUMBER;
\r
254 public ICellPainter getCellPainter(
\r
255 int columnPosition, int rowPosition,
\r
256 ILayerCell cell, IConfigRegistry configRegistry) {
\r
257 ICellPainter cellPainter = super.getCellPainter(
\r
258 columnPosition, rowPosition, cell, configRegistry);
\r
260 if (cell.getConfigLabels().hasLabel(TREE_COLUMN_CELL)) {
\r
262 ICellPainter treeCellPainter = configRegistry.getConfigAttribute(
\r
263 TreeConfigAttributes.TREE_STRUCTURE_PAINTER,
\r
264 cell.getDisplayMode(),
\r
265 cell.getConfigLabels().getLabels());
\r
267 if (treeCellPainter != null) {
\r
268 ICellPainter innerWrapper = treeCellPainter;
\r
269 IndentedTreeImagePainter treePainter = null;
\r
270 if (innerWrapper instanceof IndentedTreeImagePainter) {
\r
271 treePainter = (IndentedTreeImagePainter) innerWrapper;
\r
273 while (treePainter == null
\r
274 && innerWrapper != null
\r
275 && innerWrapper instanceof CellPainterWrapper
\r
276 && ((CellPainterWrapper) innerWrapper).getWrappedPainter() != null) {
\r
278 innerWrapper = ((CellPainterWrapper) innerWrapper).getWrappedPainter();
\r
279 if (innerWrapper instanceof IndentedTreeImagePainter) {
\r
280 treePainter = (IndentedTreeImagePainter) innerWrapper;
\r
285 if (treePainter != null) {
\r
286 treePainter.setBaseCellPainter(cellPainter);
\r
287 cellPainter = treeCellPainter;
\r
290 // log.warn("There is no IndentedTreeImagePainter found for TREE_STRUCTURE_PAINTER, " //$NON-NLS-1$
\r
291 // + "using local configured IndentedTreeImagePainter as fallback"); //$NON-NLS-1$
\r
293 this.indentedTreeImagePainter.setBaseCellPainter(cellPainter);
\r
294 cellPainter = new BackgroundPainter(this.indentedTreeImagePainter);
\r
297 // backwards compatibility fallback
\r
298 this.indentedTreeImagePainter.setBaseCellPainter(cellPainter);
\r
299 cellPainter = new BackgroundPainter(this.indentedTreeImagePainter);
\r
303 return cellPainter;
\r
307 public boolean isRowIndexHidden(int rowIndex) {
\r
308 return this.hiddenRowIndexes.contains(rowIndex)
\r
309 || isHiddenInUnderlyingLayer(rowIndex);
\r
313 public Collection<Integer> getHiddenRowIndexes() {
\r
314 return this.hiddenRowIndexes;
\r
318 * Performs an expand/collapse action dependent on the current state of the
\r
319 * tree node at the given row index.
\r
321 * @param parentIndex
\r
322 * The index of the row that shows the tree node for which the
\r
323 * expand/collapse action should be performed.
\r
325 public void expandOrCollapseIndex(int parentIndex) {
\r
326 if (this.treeRowModel.isCollapsed(parentIndex)) {
\r
327 expandTreeRow(parentIndex);
\r
329 collapseTreeRow(parentIndex);
\r
334 * Collapses the tree node for the given row index.
\r
336 * @param parentIndex
\r
337 * The index of the row that shows the node that should be
\r
340 public void collapseTreeRow(int parentIndex) {
\r
341 List<Integer> rowIndexes = this.treeRowModel.collapse(parentIndex);
\r
342 List<Integer> rowPositions = new ArrayList<Integer>();
\r
343 for (Integer rowIndex : rowIndexes) {
\r
344 int rowPos = getRowPositionByIndex(rowIndex);
\r
345 // if the rowPos is negative, it is not visible because of hidden
\r
346 // state in an underlying layer
\r
348 rowPositions.add(rowPos);
\r
351 this.hiddenRowIndexes.addAll(rowIndexes);
\r
353 fireLayerEvent(new HideRowPositionsEvent(this, rowPositions));
\r
357 * Collapses all tree nodes in the tree.
\r
359 public void collapseAll() {
\r
360 List<Integer> rowIndexes = this.treeRowModel.collapseAll();
\r
361 List<Integer> rowPositions = new ArrayList<Integer>();
\r
362 for (Integer rowIndex : rowIndexes) {
\r
363 int rowPos = getRowPositionByIndex(rowIndex);
\r
364 // if the rowPos is negative, it is not visible because of hidden
\r
365 // state in an underlying layer
\r
367 rowPositions.add(rowPos);
\r
370 this.hiddenRowIndexes.addAll(rowIndexes);
\r
372 fireLayerEvent(new HideRowPositionsEvent(this, rowPositions));
\r
376 * Expands the tree node for the given row index.
\r
378 * @param parentIndex
\r
379 * The index of the row that shows the node that should be
\r
382 public void expandTreeRow(int parentIndex) {
\r
383 List<Integer> rowIndexes = this.treeRowModel.expand(parentIndex);
\r
384 // Bug 432865: iterating and removing every single item is faster than
\r
386 for (final Integer expandedChildRowIndex : rowIndexes) {
\r
387 this.hiddenRowIndexes.remove(expandedChildRowIndex);
\r
390 fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes));
\r
394 * Expands the tree node for the given row index in the tree to a certain
\r
397 * @param parentIndex
\r
398 * The index of the row that shows the node that should be
\r
401 * The level to which the tree node should be expanded.
\r
403 public void expandTreeRowToLevel(int parentIndex, int level) {
\r
404 List<Integer> rowIndexes = this.treeRowModel.expandToLevel(parentIndex, level);
\r
405 // Bug 432865: iterating and removing every single item is faster than
\r
407 for (final Integer expandedChildRowIndex : rowIndexes) {
\r
408 this.hiddenRowIndexes.remove(expandedChildRowIndex);
\r
411 fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes));
\r
415 * Expands all tree nodes in the tree.
\r
417 public void expandAll() {
\r
418 List<Integer> rowIndexes = this.treeRowModel.expandAll();
\r
419 this.hiddenRowIndexes.clear();
\r
421 fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes));
\r
425 * Expands all tree nodes in the tree to a certain level.
\r
428 * The level to which the tree node should be expanded.
\r
430 public void expandAllToLevel(int level) {
\r
431 List<Integer> rowIndexes = this.treeRowModel.expandToLevel(level);
\r
432 // Bug 432865: iterating and removing every single item is faster than
\r
434 // for (final Integer expandedChildRowIndex : rowIndexes) {
\r
435 // this.hiddenRowIndexes.remove(expandedChildRowIndex);
\r
437 if (rowIndexes == null)
\r
439 for (int i = rowIndexes.size()-1; i>=0; i--) {
\r
440 this.hiddenRowIndexes.remove(rowIndexes.get(i));
\r
443 fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes));
\r
447 * Checks the underlying layer if the row is hidden by another layer.
\r
450 * The index of the row whose hidden state should be checked
\r
451 * @return <code>true</code> if the row at the given index is hidden in the
\r
452 * underlying layer <code>false</code> if not.
\r
454 private boolean isHiddenInUnderlyingLayer(int rowIndex) {
\r
455 IUniqueIndexLayer underlyingLayer = (IUniqueIndexLayer) getUnderlyingLayer();
\r
456 return (underlyingLayer.getRowPositionByIndex(rowIndex) == -1);
\r
460 public boolean doCommand(ILayerCommand command) {
\r
461 // special command transformations are needed to hide also child nodes
\r
462 if (command instanceof RowHideCommand) {
\r
463 return handleRowHideCommand((RowHideCommand) command);
\r
464 } else if (command instanceof MultiRowHideCommand) {
\r
465 return handleMultiRowHideCommand((MultiRowHideCommand) command);
\r
467 return super.doCommand(command);
\r
471 * Checks if the given command tries to hide a row that is a node that is
\r
472 * not collapsed and has children. In that case also the child rows need to
\r
476 * The {@link RowHideCommand} to process
\r
477 * @return <code>true</code> if the command has been handled,
\r
478 * <code>false</code> otherwise
\r
480 protected boolean handleRowHideCommand(RowHideCommand command) {
\r
481 // transform position to index
\r
482 if (command.convertToTargetLayer(this)) {
\r
483 int rowIndex = getRowIndexByPosition(command.getRowPosition());
\r
484 if (this.treeRowModel.hasChildren(rowIndex)
\r
485 && !this.treeRowModel.isCollapsed(rowIndex)) {
\r
486 List<Integer> childIndexes = this.treeRowModel.getChildIndexes(rowIndex);
\r
487 int[] childPositions = new int[childIndexes.size() + 1];
\r
488 childPositions[0] = command.getRowPosition();
\r
489 for (int i = 1; i < childIndexes.size() + 1; i++) {
\r
490 int childPos = getRowPositionByIndex(childIndexes.get(i - 1));
\r
491 childPositions[i] = childPos;
\r
493 return super.doCommand(new MultiRowHideCommand(this, childPositions));
\r
496 return super.doCommand(command);
\r
500 * Checks if the given command tries to hide rows that are nodes that are
\r
501 * not collapsed and have children. In that case also the child rows need to
\r
505 * The {@link MultiRowHideCommand} to process
\r
506 * @return <code>true</code> if the command has been handled,
\r
507 * <code>false</code> otherwise
\r
509 protected boolean handleMultiRowHideCommand(MultiRowHideCommand command) {
\r
510 // transform position to index
\r
511 if (command.convertToTargetLayer(this)) {
\r
512 List<Integer> rowPositionsToHide = new ArrayList<Integer>();
\r
513 for (Integer rowPos : command.getRowPositions()) {
\r
514 rowPositionsToHide.add(rowPos);
\r
515 int rowIndex = getRowIndexByPosition(rowPos);
\r
516 if (this.treeRowModel.hasChildren(rowIndex)
\r
517 && !this.treeRowModel.isCollapsed(rowIndex)) {
\r
518 List<Integer> childIndexes = this.treeRowModel.getChildIndexes(rowIndex);
\r
519 for (Integer childIndex : childIndexes) {
\r
520 rowPositionsToHide.add(getRowPositionByIndex(childIndex));
\r
525 int[] childPositions = new int[rowPositionsToHide.size()];
\r
526 for (int i = 0; i < rowPositionsToHide.size(); i++) {
\r
527 childPositions[i] = rowPositionsToHide.get(i);
\r
529 return super.doCommand(new MultiRowHideCommand(this, childPositions));
\r
531 return super.doCommand(command);
\r
535 * @return <code>true</code> if the column index is used to determine the
\r
536 * tree column, <code>false</code> if the column position is used.
\r
537 * Default is <code>false</code>.
\r
539 public boolean isUseTreeColumnIndex() {
\r
540 return this.useTreeColumnIndex;
\r
544 * Configure whether (column index == 0) or (column position == 0) should be
\r
545 * performed to identify the tree column.
\r
547 * @param useTreeColumnIndex
\r
548 * <code>true</code> if the column index should be used to
\r
549 * determine the tree column, <code>false</code> if the column
\r
550 * position should be used.
\r
552 public void setUseTreeColumnIndex(boolean useTreeColumnIndex) {
\r
553 this.useTreeColumnIndex = useTreeColumnIndex;
\r
560 public Collection<String> getProvidedLabels() {
\r
561 Collection<String> result = super.getProvidedLabels();
\r
563 result.add(TreeLayer.TREE_COLUMN_CELL);
\r
564 result.add(DefaultTreeLayerConfiguration.TREE_LEAF_CONFIG_TYPE);
\r
565 result.add(DefaultTreeLayerConfiguration.TREE_COLLAPSED_CONFIG_TYPE);
\r
566 result.add(DefaultTreeLayerConfiguration.TREE_EXPANDED_CONFIG_TYPE);
\r
567 // configure 5 levels to be configurable via CSS
\r
568 // if you need more you need to override this method
\r
569 result.add(DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + "0"); //$NON-NLS-1$
\r
570 result.add(DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + "1"); //$NON-NLS-1$
\r
571 result.add(DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + "2"); //$NON-NLS-1$
\r
572 result.add(DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + "3"); //$NON-NLS-1$
\r
573 result.add(DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + "4"); //$NON-NLS-1$
\r