-/*******************************************************************************\r
- * Copyright (c) 2012 Original authors and others.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- * \r
- * Contributors:\r
- * Original authors and others - initial API and implementation\r
- ******************************************************************************/\r
-package org.simantics.browsing.ui.nattable.override;\r
-\r
-import java.util.ArrayList;\r
-import java.util.Collection;\r
-import java.util.List;\r
-\r
-import org.eclipse.nebula.widgets.nattable.command.ILayerCommand;\r
-import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;\r
-import org.eclipse.nebula.widgets.nattable.hideshow.command.MultiRowHideCommand;\r
-import org.eclipse.nebula.widgets.nattable.hideshow.command.RowHideCommand;\r
-import org.eclipse.nebula.widgets.nattable.hideshow.event.HideRowPositionsEvent;\r
-import org.eclipse.nebula.widgets.nattable.hideshow.event.ShowRowPositionsEvent;\r
-import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer;\r
-import org.eclipse.nebula.widgets.nattable.layer.LabelStack;\r
-import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;\r
-import org.eclipse.nebula.widgets.nattable.painter.cell.BackgroundPainter;\r
-import org.eclipse.nebula.widgets.nattable.painter.cell.CellPainterWrapper;\r
-import org.eclipse.nebula.widgets.nattable.painter.cell.ICellPainter;\r
-import org.eclipse.nebula.widgets.nattable.tree.ITreeRowModel;\r
-import org.eclipse.nebula.widgets.nattable.tree.TreeLayer;\r
-import org.eclipse.nebula.widgets.nattable.tree.config.DefaultTreeLayerConfiguration;\r
-import org.eclipse.nebula.widgets.nattable.tree.config.TreeConfigAttributes;\r
-import org.eclipse.nebula.widgets.nattable.tree.painter.IndentedTreeImagePainter;\r
-\r
-import it.unimi.dsi.fastutil.ints.IntRBTreeSet;\r
-\r
-public class TreeLayer2 extends AbstractRowHideShowLayer2 {\r
-\r
- //private static final Log log = LogFactory.getLog(TreeLayer.class);\r
-\r
- public static final String TREE_COLUMN_CELL = "TREE_COLUMN_CELL"; //$NON-NLS-1$\r
-\r
- public static final int TREE_COLUMN_NUMBER = 0;\r
-\r
- /**\r
- * Flag to configure whether the tree column should be identified by\r
- * position or by index. Default is position.\r
- */\r
- private boolean useTreeColumnIndex = false;\r
-\r
- /**\r
- * The ITreeRowModelListener that is used to get information about the tree\r
- * structure.\r
- */\r
- private final ITreeRowModel<?> treeRowModel;\r
-\r
- /**\r
- * Collection of all row indexes that are hidden if tree nodes are\r
- * collapsed.\r
- * <p>\r
- * Note: This collection is only in use if the used {@link ITreeRowModel}\r
- * implementation is returning the row indexes of affected rows on\r
- * expand/collapse. There are also implementations that use another approach\r
- * where the hide/show approach is not used (e.g. GlazedListTreeRowModel)\r
- * </p>\r
- */\r
- private final IntRBTreeSet hiddenRowIndexes = new IntRBTreeSet();\r
-\r
- /**\r
- * The IndentedTreeImagePainter that paints indentation to the left of the\r
- * configured base painter and icons for expand/collapse if possible, to\r
- * render tree structure accordingly.\r
- */\r
- private IndentedTreeImagePainter indentedTreeImagePainter;\r
-\r
- /**\r
- * Creates a TreeLayer instance based on the given information. Will use a\r
- * default IndentedTreeImagePainter that uses 10 pixels for indentation and\r
- * simple + and - icons for expand/collapse icons. It also uses the\r
- * DefaultTreeLayerConfiguration.\r
- *\r
- * @param underlyingLayer\r
- * The underlying layer on whose top this layer will be set.\r
- * @param treeRowModel\r
- * The ITreeRowModelListener that is used to get information\r
- * about the tree structure.\r
- */\r
- public TreeLayer2(IUniqueIndexLayer underlyingLayer, ITreeRowModel<?> treeRowModel) {\r
- this(underlyingLayer, treeRowModel, new IndentedTreeImagePainter());\r
- }\r
-\r
- /**\r
- * Creates a TreeLayer instance based on the given information. Allows to\r
- * specify the IndentedTreeImagePainter while using the\r
- * DefaultTreeLayerConfiguration.\r
- *\r
- * @param underlyingLayer\r
- * The underlying layer on whose top this layer will be set.\r
- * @param treeRowModel\r
- * The ITreeRowModelListener that is used to get information\r
- * about the tree structure.\r
- * @param indentedTreeImagePainter\r
- * The IndentedTreeImagePainter that paints indentation to the\r
- * left of the configured base painter and icons for\r
- * expand/collapse if possible, to render tree structure\r
- * accordingly.\r
- */\r
- public TreeLayer2(\r
- IUniqueIndexLayer underlyingLayer,\r
- ITreeRowModel<?> treeRowModel,\r
- IndentedTreeImagePainter indentedTreeImagePainter) {\r
- this(underlyingLayer, treeRowModel, indentedTreeImagePainter, true);\r
- }\r
-\r
- /**\r
- * Creates a TreeLayer instance based on the given information. Will use a\r
- * default IndentedTreeImagePainter that uses 10 pixels for indentation and\r
- * simple + and - icons for expand/collapse icons.\r
- *\r
- * @param underlyingLayer\r
- * The underlying layer on whose top this layer will be set.\r
- * @param treeRowModel\r
- * The ITreeRowModelListener that is used to get information\r
- * about the tree structure.\r
- * @param useDefaultConfiguration\r
- * <code>true</code> to use the DefaultTreeLayerConfiguration,\r
- * <code>false</code> if you want to specify your own\r
- * configuration.\r
- */\r
- public TreeLayer2(\r
- IUniqueIndexLayer underlyingLayer,\r
- ITreeRowModel<?> treeRowModel,\r
- boolean useDefaultConfiguration) {\r
- this(underlyingLayer,\r
- treeRowModel,\r
- new IndentedTreeImagePainter(),\r
- useDefaultConfiguration);\r
- }\r
-\r
- /**\r
- * Creates a TreeLayer instance based on the given information.\r
- *\r
- * @param underlyingLayer\r
- * The underlying layer on whose top this layer will be set.\r
- * @param treeRowModel\r
- * The ITreeRowModelListener that is used to get information\r
- * about the tree structure.\r
- * @param indentedTreeImagePainter\r
- * The IndentedTreeImagePainter that paints indentation to the\r
- * left of the configured base painter and icons for\r
- * expand/collapse if possible, to render tree structure\r
- * accordingly.\r
- * @param useDefaultConfiguration\r
- * <code>true</code> to use the DefaultTreeLayerConfiguration,\r
- * <code>false</code> if you want to specify your own\r
- * configuration.\r
- */\r
- public TreeLayer2(\r
- IUniqueIndexLayer underlyingLayer,\r
- ITreeRowModel<?> treeRowModel,\r
- IndentedTreeImagePainter indentedTreeImagePainter,\r
- boolean useDefaultConfiguration) {\r
-\r
- super(underlyingLayer);\r
- this.treeRowModel = treeRowModel;\r
-\r
- if (useDefaultConfiguration) {\r
- addConfiguration(new DefaultTreeLayerConfiguration2(this));\r
- }\r
-\r
- this.indentedTreeImagePainter = indentedTreeImagePainter;\r
-\r
- registerCommandHandler(new TreeExpandCollapseCommandHandler(this));\r
- registerCommandHandler(new TreeCollapseAllCommandHandler(this));\r
- registerCommandHandler(new TreeExpandAllCommandHandler(this));\r
- registerCommandHandler(new TreeExpandToLevelCommandHandler(this));\r
- }\r
-\r
- @Override\r
- public LabelStack getConfigLabelsByPosition(int columnPosition, int rowPosition) {\r
- LabelStack configLabels = super.getConfigLabelsByPosition(columnPosition, rowPosition);\r
-\r
- if (isTreeColumn(columnPosition)) {\r
- configLabels.addLabelOnTop(TREE_COLUMN_CELL);\r
-\r
- int rowIndex = getRowIndexByPosition(rowPosition);\r
- configLabels.addLabelOnTop(\r
- DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + this.treeRowModel.depth(rowIndex));\r
- if (!this.treeRowModel.hasChildren(rowIndex)) {\r
- configLabels.addLabelOnTop(DefaultTreeLayerConfiguration.TREE_LEAF_CONFIG_TYPE);\r
- } else {\r
- if (this.treeRowModel.isCollapsed(rowIndex)) {\r
- configLabels.addLabelOnTop(DefaultTreeLayerConfiguration.TREE_COLLAPSED_CONFIG_TYPE);\r
- } else {\r
- configLabels.addLabelOnTop(DefaultTreeLayerConfiguration.TREE_EXPANDED_CONFIG_TYPE);\r
- }\r
- }\r
- }\r
- return configLabels;\r
- }\r
-\r
- /**\r
- * @return The ITreeRowModelListener that is used to get information about\r
- * the tree structure.\r
- */\r
- public ITreeRowModel<?> getModel() {\r
- return this.treeRowModel;\r
- }\r
-\r
- /**\r
- * @return The IndentedTreeImagePainter that paints indentation to the left\r
- * of the configured base painter and icons for expand/collapse if\r
- * possible, to render tree structure accordingly.\r
- *\r
- * @deprecated since 1.1 the configured TreeImagePainter should be used\r
- * instead of the hard referenced one\r
- */\r
- @Deprecated\r
- public IndentedTreeImagePainter getIndentedTreeImagePainter() {\r
- return this.indentedTreeImagePainter;\r
- }\r
-\r
- /**\r
- * @return The ICellPainter that is used to paint the images in the tree by\r
- * the IndentedTreeImagePainter. Usually it is some type of\r
- * TreeImagePainter that paints expand/collapse/leaf icons regarding\r
- * the node state.<br>\r
- * Can be <code>null</code> if set explicitly to the\r
- * IndentedTreeImagePainter!\r
- *\r
- * @deprecated since 1.1 the configured TreeImagePainter should be used\r
- * instead of the hard referenced one\r
- */\r
- @Deprecated\r
- public ICellPainter getTreeImagePainter() {\r
- return this.indentedTreeImagePainter != null ? this.indentedTreeImagePainter\r
- .getTreeImagePainter() : null;\r
- }\r
-\r
- /**\r
- * @param columnPosition\r
- * The column position to check.\r
- * @return <code>true</code> if the given column position is the tree\r
- * column, <code>false</code> if not.\r
- */\r
- private boolean isTreeColumn(int columnPosition) {\r
- if (this.useTreeColumnIndex)\r
- return getColumnIndexByPosition(columnPosition) == TREE_COLUMN_NUMBER;\r
-\r
- return columnPosition == TREE_COLUMN_NUMBER;\r
- }\r
-\r
- @Override\r
- public ICellPainter getCellPainter(\r
- int columnPosition, int rowPosition,\r
- ILayerCell cell, IConfigRegistry configRegistry) {\r
- ICellPainter cellPainter = super.getCellPainter(\r
- columnPosition, rowPosition, cell, configRegistry);\r
-\r
- if (cell.getConfigLabels().hasLabel(TREE_COLUMN_CELL)) {\r
-\r
- ICellPainter treeCellPainter = configRegistry.getConfigAttribute(\r
- TreeConfigAttributes.TREE_STRUCTURE_PAINTER,\r
- cell.getDisplayMode(),\r
- cell.getConfigLabels().getLabels());\r
-\r
- if (treeCellPainter != null) {\r
- ICellPainter innerWrapper = treeCellPainter;\r
- IndentedTreeImagePainter treePainter = null;\r
- if (innerWrapper instanceof IndentedTreeImagePainter) {\r
- treePainter = (IndentedTreeImagePainter) innerWrapper;\r
- } else {\r
- while (treePainter == null\r
- && innerWrapper != null\r
- && innerWrapper instanceof CellPainterWrapper\r
- && ((CellPainterWrapper) innerWrapper).getWrappedPainter() != null) {\r
-\r
- innerWrapper = ((CellPainterWrapper) innerWrapper).getWrappedPainter();\r
- if (innerWrapper instanceof IndentedTreeImagePainter) {\r
- treePainter = (IndentedTreeImagePainter) innerWrapper;\r
- }\r
- }\r
- }\r
-\r
- if (treePainter != null) {\r
- treePainter.setBaseCellPainter(cellPainter);\r
- cellPainter = treeCellPainter;\r
- } else {\r
- // log error\r
-// log.warn("There is no IndentedTreeImagePainter found for TREE_STRUCTURE_PAINTER, " //$NON-NLS-1$\r
-// + "using local configured IndentedTreeImagePainter as fallback"); //$NON-NLS-1$\r
- // fallback\r
- this.indentedTreeImagePainter.setBaseCellPainter(cellPainter);\r
- cellPainter = new BackgroundPainter(this.indentedTreeImagePainter);\r
- }\r
- } else {\r
- // backwards compatibility fallback\r
- this.indentedTreeImagePainter.setBaseCellPainter(cellPainter);\r
- cellPainter = new BackgroundPainter(this.indentedTreeImagePainter);\r
- }\r
- }\r
-\r
- return cellPainter;\r
- }\r
-\r
- @Override\r
- public boolean isRowIndexHidden(int rowIndex) {\r
- return this.hiddenRowIndexes.contains(rowIndex)\r
- || isHiddenInUnderlyingLayer(rowIndex);\r
- }\r
-\r
- @Override\r
- public Collection<Integer> getHiddenRowIndexes() {\r
- return this.hiddenRowIndexes;\r
- }\r
-\r
- /**\r
- * Performs an expand/collapse action dependent on the current state of the\r
- * tree node at the given row index.\r
- *\r
- * @param parentIndex\r
- * The index of the row that shows the tree node for which the\r
- * expand/collapse action should be performed.\r
- */\r
- public void expandOrCollapseIndex(int parentIndex) {\r
- if (this.treeRowModel.isCollapsed(parentIndex)) {\r
- expandTreeRow(parentIndex);\r
- } else {\r
- collapseTreeRow(parentIndex);\r
- }\r
- }\r
-\r
- /**\r
- * Collapses the tree node for the given row index.\r
- *\r
- * @param parentIndex\r
- * The index of the row that shows the node that should be\r
- * collapsed\r
- */\r
- public void collapseTreeRow(int parentIndex) {\r
- List<Integer> rowIndexes = this.treeRowModel.collapse(parentIndex);\r
- List<Integer> rowPositions = new ArrayList<Integer>();\r
- for (Integer rowIndex : rowIndexes) {\r
- int rowPos = getRowPositionByIndex(rowIndex);\r
- // if the rowPos is negative, it is not visible because of hidden\r
- // state in an underlying layer\r
- if (rowPos >= 0) {\r
- rowPositions.add(rowPos);\r
- }\r
- }\r
- this.hiddenRowIndexes.addAll(rowIndexes);\r
- invalidateCache();\r
- fireLayerEvent(new HideRowPositionsEvent(this, rowPositions));\r
- }\r
-\r
- /**\r
- * Collapses all tree nodes in the tree.\r
- */\r
- public void collapseAll() {\r
- List<Integer> rowIndexes = this.treeRowModel.collapseAll();\r
- List<Integer> rowPositions = new ArrayList<Integer>();\r
- for (Integer rowIndex : rowIndexes) {\r
- int rowPos = getRowPositionByIndex(rowIndex);\r
- // if the rowPos is negative, it is not visible because of hidden\r
- // state in an underlying layer\r
- if (rowPos >= 0) {\r
- rowPositions.add(rowPos);\r
- }\r
- }\r
- this.hiddenRowIndexes.addAll(rowIndexes);\r
- invalidateCache();\r
- fireLayerEvent(new HideRowPositionsEvent(this, rowPositions));\r
- }\r
-\r
- /**\r
- * Expands the tree node for the given row index.\r
- *\r
- * @param parentIndex\r
- * The index of the row that shows the node that should be\r
- * expanded\r
- */\r
- public void expandTreeRow(int parentIndex) {\r
- List<Integer> rowIndexes = this.treeRowModel.expand(parentIndex);\r
- // Bug 432865: iterating and removing every single item is faster than\r
- // removeAll()\r
- for (final Integer expandedChildRowIndex : rowIndexes) {\r
- this.hiddenRowIndexes.remove(expandedChildRowIndex);\r
- }\r
- invalidateCache();\r
- fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes));\r
- }\r
-\r
- /**\r
- * Expands the tree node for the given row index in the tree to a certain\r
- * level.\r
- *\r
- * @param parentIndex\r
- * The index of the row that shows the node that should be\r
- * expanded\r
- * @param level\r
- * The level to which the tree node should be expanded.\r
- */\r
- public void expandTreeRowToLevel(int parentIndex, int level) {\r
- List<Integer> rowIndexes = this.treeRowModel.expandToLevel(parentIndex, level);\r
- // Bug 432865: iterating and removing every single item is faster than\r
- // removeAll()\r
- for (final Integer expandedChildRowIndex : rowIndexes) {\r
- this.hiddenRowIndexes.remove(expandedChildRowIndex);\r
- }\r
- invalidateCache();\r
- fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes));\r
- }\r
-\r
- /**\r
- * Expands all tree nodes in the tree.\r
- */\r
- public void expandAll() {\r
- List<Integer> rowIndexes = this.treeRowModel.expandAll();\r
- this.hiddenRowIndexes.clear();\r
- invalidateCache();\r
- fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes));\r
- }\r
-\r
- /**\r
- * Expands all tree nodes in the tree to a certain level.\r
- *\r
- * @param level\r
- * The level to which the tree node should be expanded.\r
- */\r
- public void expandAllToLevel(int level) {\r
- List<Integer> rowIndexes = this.treeRowModel.expandToLevel(level);\r
- // Bug 432865: iterating and removing every single item is faster than\r
- // removeAll()\r
-// for (final Integer expandedChildRowIndex : rowIndexes) {\r
-// this.hiddenRowIndexes.remove(expandedChildRowIndex);\r
-// }\r
- if (rowIndexes == null)\r
- return;\r
- for (int i = rowIndexes.size()-1; i>=0; i--) {\r
- this.hiddenRowIndexes.remove(rowIndexes.get(i));\r
- }\r
- invalidateCache();\r
- fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes));\r
- }\r
-\r
- /**\r
- * Checks the underlying layer if the row is hidden by another layer.\r
- *\r
- * @param rowIndex\r
- * The index of the row whose hidden state should be checked\r
- * @return <code>true</code> if the row at the given index is hidden in the\r
- * underlying layer <code>false</code> if not.\r
- */\r
- private boolean isHiddenInUnderlyingLayer(int rowIndex) {\r
- IUniqueIndexLayer underlyingLayer = (IUniqueIndexLayer) getUnderlyingLayer();\r
- return (underlyingLayer.getRowPositionByIndex(rowIndex) == -1);\r
- }\r
-\r
- @Override\r
- public boolean doCommand(ILayerCommand command) {\r
- // special command transformations are needed to hide also child nodes\r
- if (command instanceof RowHideCommand) {\r
- return handleRowHideCommand((RowHideCommand) command);\r
- } else if (command instanceof MultiRowHideCommand) {\r
- return handleMultiRowHideCommand((MultiRowHideCommand) command);\r
- }\r
- return super.doCommand(command);\r
- }\r
-\r
- /**\r
- * Checks if the given command tries to hide a row that is a node that is\r
- * not collapsed and has children. In that case also the child rows need to\r
- * be hidden.\r
- *\r
- * @param command\r
- * The {@link RowHideCommand} to process\r
- * @return <code>true</code> if the command has been handled,\r
- * <code>false</code> otherwise\r
- */\r
- protected boolean handleRowHideCommand(RowHideCommand command) {\r
- // transform position to index\r
- if (command.convertToTargetLayer(this)) {\r
- int rowIndex = getRowIndexByPosition(command.getRowPosition());\r
- if (this.treeRowModel.hasChildren(rowIndex)\r
- && !this.treeRowModel.isCollapsed(rowIndex)) {\r
- List<Integer> childIndexes = this.treeRowModel.getChildIndexes(rowIndex);\r
- int[] childPositions = new int[childIndexes.size() + 1];\r
- childPositions[0] = command.getRowPosition();\r
- for (int i = 1; i < childIndexes.size() + 1; i++) {\r
- int childPos = getRowPositionByIndex(childIndexes.get(i - 1));\r
- childPositions[i] = childPos;\r
- }\r
- return super.doCommand(new MultiRowHideCommand(this, childPositions));\r
- }\r
- }\r
- return super.doCommand(command);\r
- }\r
-\r
- /**\r
- * Checks if the given command tries to hide rows that are nodes that are\r
- * not collapsed and have children. In that case also the child rows need to\r
- * be hidden.\r
- *\r
- * @param command\r
- * The {@link MultiRowHideCommand} to process\r
- * @return <code>true</code> if the command has been handled,\r
- * <code>false</code> otherwise\r
- */\r
- protected boolean handleMultiRowHideCommand(MultiRowHideCommand command) {\r
- // transform position to index\r
- if (command.convertToTargetLayer(this)) {\r
- List<Integer> rowPositionsToHide = new ArrayList<Integer>();\r
- for (Integer rowPos : command.getRowPositions()) {\r
- rowPositionsToHide.add(rowPos);\r
- int rowIndex = getRowIndexByPosition(rowPos);\r
- if (this.treeRowModel.hasChildren(rowIndex)\r
- && !this.treeRowModel.isCollapsed(rowIndex)) {\r
- List<Integer> childIndexes = this.treeRowModel.getChildIndexes(rowIndex);\r
- for (Integer childIndex : childIndexes) {\r
- rowPositionsToHide.add(getRowPositionByIndex(childIndex));\r
- }\r
- }\r
- }\r
-\r
- int[] childPositions = new int[rowPositionsToHide.size()];\r
- for (int i = 0; i < rowPositionsToHide.size(); i++) {\r
- childPositions[i] = rowPositionsToHide.get(i);\r
- }\r
- return super.doCommand(new MultiRowHideCommand(this, childPositions));\r
- }\r
- return super.doCommand(command);\r
- }\r
-\r
- /**\r
- * @return <code>true</code> if the column index is used to determine the\r
- * tree column, <code>false</code> if the column position is used.\r
- * Default is <code>false</code>.\r
- */\r
- public boolean isUseTreeColumnIndex() {\r
- return this.useTreeColumnIndex;\r
- }\r
-\r
- /**\r
- * Configure whether (column index == 0) or (column position == 0) should be\r
- * performed to identify the tree column.\r
- *\r
- * @param useTreeColumnIndex\r
- * <code>true</code> if the column index should be used to\r
- * determine the tree column, <code>false</code> if the column\r
- * position should be used.\r
- */\r
- public void setUseTreeColumnIndex(boolean useTreeColumnIndex) {\r
- this.useTreeColumnIndex = useTreeColumnIndex;\r
- }\r
-\r
- /**\r
- * @since 1.4\r
- */\r
- @Override\r
- public Collection<String> getProvidedLabels() {\r
- Collection<String> result = super.getProvidedLabels();\r
-\r
- result.add(TreeLayer.TREE_COLUMN_CELL);\r
- result.add(DefaultTreeLayerConfiguration.TREE_LEAF_CONFIG_TYPE);\r
- result.add(DefaultTreeLayerConfiguration.TREE_COLLAPSED_CONFIG_TYPE);\r
- result.add(DefaultTreeLayerConfiguration.TREE_EXPANDED_CONFIG_TYPE);\r
- // configure 5 levels to be configurable via CSS\r
- // if you need more you need to override this method\r
- result.add(DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + "0"); //$NON-NLS-1$\r
- result.add(DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + "1"); //$NON-NLS-1$\r
- result.add(DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + "2"); //$NON-NLS-1$\r
- result.add(DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + "3"); //$NON-NLS-1$\r
- result.add(DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + "4"); //$NON-NLS-1$\r
-\r
- return result;\r
- }\r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2012 Original authors and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Original authors and others - initial API and implementation
+ ******************************************************************************/
+package org.simantics.browsing.ui.nattable.override;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.nebula.widgets.nattable.command.ILayerCommand;
+import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
+import org.eclipse.nebula.widgets.nattable.hideshow.command.MultiRowHideCommand;
+import org.eclipse.nebula.widgets.nattable.hideshow.command.RowHideCommand;
+import org.eclipse.nebula.widgets.nattable.hideshow.event.HideRowPositionsEvent;
+import org.eclipse.nebula.widgets.nattable.hideshow.event.ShowRowPositionsEvent;
+import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer;
+import org.eclipse.nebula.widgets.nattable.layer.LabelStack;
+import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
+import org.eclipse.nebula.widgets.nattable.painter.cell.BackgroundPainter;
+import org.eclipse.nebula.widgets.nattable.painter.cell.CellPainterWrapper;
+import org.eclipse.nebula.widgets.nattable.painter.cell.ICellPainter;
+import org.eclipse.nebula.widgets.nattable.tree.ITreeRowModel;
+import org.eclipse.nebula.widgets.nattable.tree.TreeLayer;
+import org.eclipse.nebula.widgets.nattable.tree.config.DefaultTreeLayerConfiguration;
+import org.eclipse.nebula.widgets.nattable.tree.config.TreeConfigAttributes;
+import org.eclipse.nebula.widgets.nattable.tree.painter.IndentedTreeImagePainter;
+
+import it.unimi.dsi.fastutil.ints.IntRBTreeSet;
+
+public class TreeLayer2 extends AbstractRowHideShowLayer2 {
+
+ //private static final Log log = LogFactory.getLog(TreeLayer.class);
+
+ public static final String TREE_COLUMN_CELL = "TREE_COLUMN_CELL"; //$NON-NLS-1$
+
+ public static final int TREE_COLUMN_NUMBER = 0;
+
+ /**
+ * Flag to configure whether the tree column should be identified by
+ * position or by index. Default is position.
+ */
+ private boolean useTreeColumnIndex = false;
+
+ /**
+ * The ITreeRowModelListener that is used to get information about the tree
+ * structure.
+ */
+ private final ITreeRowModel<?> treeRowModel;
+
+ /**
+ * Collection of all row indexes that are hidden if tree nodes are
+ * collapsed.
+ * <p>
+ * Note: This collection is only in use if the used {@link ITreeRowModel}
+ * implementation is returning the row indexes of affected rows on
+ * expand/collapse. There are also implementations that use another approach
+ * where the hide/show approach is not used (e.g. GlazedListTreeRowModel)
+ * </p>
+ */
+ private final IntRBTreeSet hiddenRowIndexes = new IntRBTreeSet();
+
+ /**
+ * The IndentedTreeImagePainter that paints indentation to the left of the
+ * configured base painter and icons for expand/collapse if possible, to
+ * render tree structure accordingly.
+ */
+ private IndentedTreeImagePainter indentedTreeImagePainter;
+
+ /**
+ * Creates a TreeLayer instance based on the given information. Will use a
+ * default IndentedTreeImagePainter that uses 10 pixels for indentation and
+ * simple + and - icons for expand/collapse icons. It also uses the
+ * DefaultTreeLayerConfiguration.
+ *
+ * @param underlyingLayer
+ * The underlying layer on whose top this layer will be set.
+ * @param treeRowModel
+ * The ITreeRowModelListener that is used to get information
+ * about the tree structure.
+ */
+ public TreeLayer2(IUniqueIndexLayer underlyingLayer, ITreeRowModel<?> treeRowModel) {
+ this(underlyingLayer, treeRowModel, new IndentedTreeImagePainter());
+ }
+
+ /**
+ * Creates a TreeLayer instance based on the given information. Allows to
+ * specify the IndentedTreeImagePainter while using the
+ * DefaultTreeLayerConfiguration.
+ *
+ * @param underlyingLayer
+ * The underlying layer on whose top this layer will be set.
+ * @param treeRowModel
+ * The ITreeRowModelListener that is used to get information
+ * about the tree structure.
+ * @param indentedTreeImagePainter
+ * The IndentedTreeImagePainter that paints indentation to the
+ * left of the configured base painter and icons for
+ * expand/collapse if possible, to render tree structure
+ * accordingly.
+ */
+ public TreeLayer2(
+ IUniqueIndexLayer underlyingLayer,
+ ITreeRowModel<?> treeRowModel,
+ IndentedTreeImagePainter indentedTreeImagePainter) {
+ this(underlyingLayer, treeRowModel, indentedTreeImagePainter, true);
+ }
+
+ /**
+ * Creates a TreeLayer instance based on the given information. Will use a
+ * default IndentedTreeImagePainter that uses 10 pixels for indentation and
+ * simple + and - icons for expand/collapse icons.
+ *
+ * @param underlyingLayer
+ * The underlying layer on whose top this layer will be set.
+ * @param treeRowModel
+ * The ITreeRowModelListener that is used to get information
+ * about the tree structure.
+ * @param useDefaultConfiguration
+ * <code>true</code> to use the DefaultTreeLayerConfiguration,
+ * <code>false</code> if you want to specify your own
+ * configuration.
+ */
+ public TreeLayer2(
+ IUniqueIndexLayer underlyingLayer,
+ ITreeRowModel<?> treeRowModel,
+ boolean useDefaultConfiguration) {
+ this(underlyingLayer,
+ treeRowModel,
+ new IndentedTreeImagePainter(),
+ useDefaultConfiguration);
+ }
+
+ /**
+ * Creates a TreeLayer instance based on the given information.
+ *
+ * @param underlyingLayer
+ * The underlying layer on whose top this layer will be set.
+ * @param treeRowModel
+ * The ITreeRowModelListener that is used to get information
+ * about the tree structure.
+ * @param indentedTreeImagePainter
+ * The IndentedTreeImagePainter that paints indentation to the
+ * left of the configured base painter and icons for
+ * expand/collapse if possible, to render tree structure
+ * accordingly.
+ * @param useDefaultConfiguration
+ * <code>true</code> to use the DefaultTreeLayerConfiguration,
+ * <code>false</code> if you want to specify your own
+ * configuration.
+ */
+ public TreeLayer2(
+ IUniqueIndexLayer underlyingLayer,
+ ITreeRowModel<?> treeRowModel,
+ IndentedTreeImagePainter indentedTreeImagePainter,
+ boolean useDefaultConfiguration) {
+
+ super(underlyingLayer);
+ this.treeRowModel = treeRowModel;
+
+ if (useDefaultConfiguration) {
+ addConfiguration(new DefaultTreeLayerConfiguration2(this));
+ }
+
+ this.indentedTreeImagePainter = indentedTreeImagePainter;
+
+ registerCommandHandler(new TreeExpandCollapseCommandHandler(this));
+ registerCommandHandler(new TreeCollapseAllCommandHandler(this));
+ registerCommandHandler(new TreeExpandAllCommandHandler(this));
+ registerCommandHandler(new TreeExpandToLevelCommandHandler(this));
+ }
+
+ @Override
+ public LabelStack getConfigLabelsByPosition(int columnPosition, int rowPosition) {
+ LabelStack configLabels = super.getConfigLabelsByPosition(columnPosition, rowPosition);
+
+ if (isTreeColumn(columnPosition)) {
+ configLabels.addLabelOnTop(TREE_COLUMN_CELL);
+
+ int rowIndex = getRowIndexByPosition(rowPosition);
+ configLabels.addLabelOnTop(
+ DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + this.treeRowModel.depth(rowIndex));
+ if (!this.treeRowModel.hasChildren(rowIndex)) {
+ configLabels.addLabelOnTop(DefaultTreeLayerConfiguration.TREE_LEAF_CONFIG_TYPE);
+ } else {
+ if (this.treeRowModel.isCollapsed(rowIndex)) {
+ configLabels.addLabelOnTop(DefaultTreeLayerConfiguration.TREE_COLLAPSED_CONFIG_TYPE);
+ } else {
+ configLabels.addLabelOnTop(DefaultTreeLayerConfiguration.TREE_EXPANDED_CONFIG_TYPE);
+ }
+ }
+ }
+ return configLabels;
+ }
+
+ /**
+ * @return The ITreeRowModelListener that is used to get information about
+ * the tree structure.
+ */
+ public ITreeRowModel<?> getModel() {
+ return this.treeRowModel;
+ }
+
+ /**
+ * @return The IndentedTreeImagePainter that paints indentation to the left
+ * of the configured base painter and icons for expand/collapse if
+ * possible, to render tree structure accordingly.
+ *
+ * @deprecated since 1.1 the configured TreeImagePainter should be used
+ * instead of the hard referenced one
+ */
+ @Deprecated
+ public IndentedTreeImagePainter getIndentedTreeImagePainter() {
+ return this.indentedTreeImagePainter;
+ }
+
+ /**
+ * @return The ICellPainter that is used to paint the images in the tree by
+ * the IndentedTreeImagePainter. Usually it is some type of
+ * TreeImagePainter that paints expand/collapse/leaf icons regarding
+ * the node state.<br>
+ * Can be <code>null</code> if set explicitly to the
+ * IndentedTreeImagePainter!
+ *
+ * @deprecated since 1.1 the configured TreeImagePainter should be used
+ * instead of the hard referenced one
+ */
+ @Deprecated
+ public ICellPainter getTreeImagePainter() {
+ return this.indentedTreeImagePainter != null ? this.indentedTreeImagePainter
+ .getTreeImagePainter() : null;
+ }
+
+ /**
+ * @param columnPosition
+ * The column position to check.
+ * @return <code>true</code> if the given column position is the tree
+ * column, <code>false</code> if not.
+ */
+ private boolean isTreeColumn(int columnPosition) {
+ if (this.useTreeColumnIndex)
+ return getColumnIndexByPosition(columnPosition) == TREE_COLUMN_NUMBER;
+
+ return columnPosition == TREE_COLUMN_NUMBER;
+ }
+
+ @Override
+ public ICellPainter getCellPainter(
+ int columnPosition, int rowPosition,
+ ILayerCell cell, IConfigRegistry configRegistry) {
+ ICellPainter cellPainter = super.getCellPainter(
+ columnPosition, rowPosition, cell, configRegistry);
+
+ if (cell.getConfigLabels().hasLabel(TREE_COLUMN_CELL)) {
+
+ ICellPainter treeCellPainter = configRegistry.getConfigAttribute(
+ TreeConfigAttributes.TREE_STRUCTURE_PAINTER,
+ cell.getDisplayMode(),
+ cell.getConfigLabels().getLabels());
+
+ if (treeCellPainter != null) {
+ ICellPainter innerWrapper = treeCellPainter;
+ IndentedTreeImagePainter treePainter = null;
+ if (innerWrapper instanceof IndentedTreeImagePainter) {
+ treePainter = (IndentedTreeImagePainter) innerWrapper;
+ } else {
+ while (treePainter == null
+ && innerWrapper != null
+ && innerWrapper instanceof CellPainterWrapper
+ && ((CellPainterWrapper) innerWrapper).getWrappedPainter() != null) {
+
+ innerWrapper = ((CellPainterWrapper) innerWrapper).getWrappedPainter();
+ if (innerWrapper instanceof IndentedTreeImagePainter) {
+ treePainter = (IndentedTreeImagePainter) innerWrapper;
+ }
+ }
+ }
+
+ if (treePainter != null) {
+ treePainter.setBaseCellPainter(cellPainter);
+ cellPainter = treeCellPainter;
+ } else {
+ // log error
+// log.warn("There is no IndentedTreeImagePainter found for TREE_STRUCTURE_PAINTER, " //$NON-NLS-1$
+// + "using local configured IndentedTreeImagePainter as fallback"); //$NON-NLS-1$
+ // fallback
+ this.indentedTreeImagePainter.setBaseCellPainter(cellPainter);
+ cellPainter = new BackgroundPainter(this.indentedTreeImagePainter);
+ }
+ } else {
+ // backwards compatibility fallback
+ this.indentedTreeImagePainter.setBaseCellPainter(cellPainter);
+ cellPainter = new BackgroundPainter(this.indentedTreeImagePainter);
+ }
+ }
+
+ return cellPainter;
+ }
+
+ @Override
+ public boolean isRowIndexHidden(int rowIndex) {
+ return this.hiddenRowIndexes.contains(rowIndex)
+ || isHiddenInUnderlyingLayer(rowIndex);
+ }
+
+ @Override
+ public Collection<Integer> getHiddenRowIndexes() {
+ return this.hiddenRowIndexes;
+ }
+
+ /**
+ * Performs an expand/collapse action dependent on the current state of the
+ * tree node at the given row index.
+ *
+ * @param parentIndex
+ * The index of the row that shows the tree node for which the
+ * expand/collapse action should be performed.
+ */
+ public void expandOrCollapseIndex(int parentIndex) {
+ if (this.treeRowModel.isCollapsed(parentIndex)) {
+ expandTreeRow(parentIndex);
+ } else {
+ collapseTreeRow(parentIndex);
+ }
+ }
+
+ /**
+ * Collapses the tree node for the given row index.
+ *
+ * @param parentIndex
+ * The index of the row that shows the node that should be
+ * collapsed
+ */
+ public void collapseTreeRow(int parentIndex) {
+ List<Integer> rowIndexes = this.treeRowModel.collapse(parentIndex);
+ List<Integer> rowPositions = new ArrayList<Integer>();
+ for (Integer rowIndex : rowIndexes) {
+ int rowPos = getRowPositionByIndex(rowIndex);
+ // if the rowPos is negative, it is not visible because of hidden
+ // state in an underlying layer
+ if (rowPos >= 0) {
+ rowPositions.add(rowPos);
+ }
+ }
+ this.hiddenRowIndexes.addAll(rowIndexes);
+ invalidateCache();
+ fireLayerEvent(new HideRowPositionsEvent(this, rowPositions));
+ }
+
+ /**
+ * Collapses all tree nodes in the tree.
+ */
+ public void collapseAll() {
+ List<Integer> rowIndexes = this.treeRowModel.collapseAll();
+ List<Integer> rowPositions = new ArrayList<Integer>();
+ for (Integer rowIndex : rowIndexes) {
+ int rowPos = getRowPositionByIndex(rowIndex);
+ // if the rowPos is negative, it is not visible because of hidden
+ // state in an underlying layer
+ if (rowPos >= 0) {
+ rowPositions.add(rowPos);
+ }
+ }
+ this.hiddenRowIndexes.addAll(rowIndexes);
+ invalidateCache();
+ fireLayerEvent(new HideRowPositionsEvent(this, rowPositions));
+ }
+
+ /**
+ * Expands the tree node for the given row index.
+ *
+ * @param parentIndex
+ * The index of the row that shows the node that should be
+ * expanded
+ */
+ public void expandTreeRow(int parentIndex) {
+ List<Integer> rowIndexes = this.treeRowModel.expand(parentIndex);
+ // Bug 432865: iterating and removing every single item is faster than
+ // removeAll()
+ if (rowIndexes.isEmpty())
+ return;
+ for (final Integer expandedChildRowIndex : rowIndexes) {
+ this.hiddenRowIndexes.remove(expandedChildRowIndex);
+ }
+ invalidateCache();
+ fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes));
+ }
+
+ /**
+ * Expands the tree node for the given row index in the tree to a certain
+ * level.
+ *
+ * @param parentIndex
+ * The index of the row that shows the node that should be
+ * expanded
+ * @param level
+ * The level to which the tree node should be expanded.
+ */
+ public void expandTreeRowToLevel(int parentIndex, int level) {
+ List<Integer> rowIndexes = this.treeRowModel.expandToLevel(parentIndex, level);
+ // Bug 432865: iterating and removing every single item is faster than
+ // removeAll()
+ for (final Integer expandedChildRowIndex : rowIndexes) {
+ this.hiddenRowIndexes.remove(expandedChildRowIndex);
+ }
+ invalidateCache();
+ fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes));
+ }
+
+ /**
+ * Expands all tree nodes in the tree.
+ */
+ public void expandAll() {
+ List<Integer> rowIndexes = this.treeRowModel.expandAll();
+ this.hiddenRowIndexes.clear();
+ invalidateCache();
+ fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes));
+ }
+
+ /**
+ * Expands all tree nodes in the tree to a certain level.
+ *
+ * @param level
+ * The level to which the tree node should be expanded.
+ */
+ public void expandAllToLevel(int level) {
+ List<Integer> rowIndexes = this.treeRowModel.expandToLevel(level);
+ // Bug 432865: iterating and removing every single item is faster than
+ // removeAll()
+// for (final Integer expandedChildRowIndex : rowIndexes) {
+// this.hiddenRowIndexes.remove(expandedChildRowIndex);
+// }
+ if (rowIndexes == null)
+ return;
+ for (int i = rowIndexes.size()-1; i>=0; i--) {
+ this.hiddenRowIndexes.remove(rowIndexes.get(i));
+ }
+ invalidateCache();
+ fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes));
+ }
+
+ /**
+ * Checks the underlying layer if the row is hidden by another layer.
+ *
+ * @param rowIndex
+ * The index of the row whose hidden state should be checked
+ * @return <code>true</code> if the row at the given index is hidden in the
+ * underlying layer <code>false</code> if not.
+ */
+ private boolean isHiddenInUnderlyingLayer(int rowIndex) {
+ IUniqueIndexLayer underlyingLayer = (IUniqueIndexLayer) getUnderlyingLayer();
+ return (underlyingLayer.getRowPositionByIndex(rowIndex) == -1);
+ }
+
+ @Override
+ public boolean doCommand(ILayerCommand command) {
+ // special command transformations are needed to hide also child nodes
+ if (command instanceof RowHideCommand) {
+ return handleRowHideCommand((RowHideCommand) command);
+ } else if (command instanceof MultiRowHideCommand) {
+ return handleMultiRowHideCommand((MultiRowHideCommand) command);
+ }
+ return super.doCommand(command);
+ }
+
+ /**
+ * Checks if the given command tries to hide a row that is a node that is
+ * not collapsed and has children. In that case also the child rows need to
+ * be hidden.
+ *
+ * @param command
+ * The {@link RowHideCommand} to process
+ * @return <code>true</code> if the command has been handled,
+ * <code>false</code> otherwise
+ */
+ protected boolean handleRowHideCommand(RowHideCommand command) {
+ // transform position to index
+ if (command.convertToTargetLayer(this)) {
+ int rowIndex = getRowIndexByPosition(command.getRowPosition());
+ if (this.treeRowModel.hasChildren(rowIndex)
+ && !this.treeRowModel.isCollapsed(rowIndex)) {
+ List<Integer> childIndexes = this.treeRowModel.getChildIndexes(rowIndex);
+ int[] childPositions = new int[childIndexes.size() + 1];
+ childPositions[0] = command.getRowPosition();
+ for (int i = 1; i < childIndexes.size() + 1; i++) {
+ int childPos = getRowPositionByIndex(childIndexes.get(i - 1));
+ childPositions[i] = childPos;
+ }
+ return super.doCommand(new MultiRowHideCommand(this, childPositions));
+ }
+ }
+ return super.doCommand(command);
+ }
+
+ /**
+ * Checks if the given command tries to hide rows that are nodes that are
+ * not collapsed and have children. In that case also the child rows need to
+ * be hidden.
+ *
+ * @param command
+ * The {@link MultiRowHideCommand} to process
+ * @return <code>true</code> if the command has been handled,
+ * <code>false</code> otherwise
+ */
+ protected boolean handleMultiRowHideCommand(MultiRowHideCommand command) {
+ // transform position to index
+ if (command.convertToTargetLayer(this)) {
+ List<Integer> rowPositionsToHide = new ArrayList<Integer>();
+ for (Integer rowPos : command.getRowPositions()) {
+ rowPositionsToHide.add(rowPos);
+ int rowIndex = getRowIndexByPosition(rowPos);
+ if (this.treeRowModel.hasChildren(rowIndex)
+ && !this.treeRowModel.isCollapsed(rowIndex)) {
+ List<Integer> childIndexes = this.treeRowModel.getChildIndexes(rowIndex);
+ for (Integer childIndex : childIndexes) {
+ rowPositionsToHide.add(getRowPositionByIndex(childIndex));
+ }
+ }
+ }
+
+ int[] childPositions = new int[rowPositionsToHide.size()];
+ for (int i = 0; i < rowPositionsToHide.size(); i++) {
+ childPositions[i] = rowPositionsToHide.get(i);
+ }
+ return super.doCommand(new MultiRowHideCommand(this, childPositions));
+ }
+ return super.doCommand(command);
+ }
+
+ /**
+ * @return <code>true</code> if the column index is used to determine the
+ * tree column, <code>false</code> if the column position is used.
+ * Default is <code>false</code>.
+ */
+ public boolean isUseTreeColumnIndex() {
+ return this.useTreeColumnIndex;
+ }
+
+ /**
+ * Configure whether (column index == 0) or (column position == 0) should be
+ * performed to identify the tree column.
+ *
+ * @param useTreeColumnIndex
+ * <code>true</code> if the column index should be used to
+ * determine the tree column, <code>false</code> if the column
+ * position should be used.
+ */
+ public void setUseTreeColumnIndex(boolean useTreeColumnIndex) {
+ this.useTreeColumnIndex = useTreeColumnIndex;
+ }
+
+ /**
+ * @since 1.4
+ */
+ @Override
+ public Collection<String> getProvidedLabels() {
+ Collection<String> result = super.getProvidedLabels();
+
+ result.add(TreeLayer.TREE_COLUMN_CELL);
+ result.add(DefaultTreeLayerConfiguration.TREE_LEAF_CONFIG_TYPE);
+ result.add(DefaultTreeLayerConfiguration.TREE_COLLAPSED_CONFIG_TYPE);
+ result.add(DefaultTreeLayerConfiguration.TREE_EXPANDED_CONFIG_TYPE);
+ // configure 5 levels to be configurable via CSS
+ // if you need more you need to override this method
+ result.add(DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + "0"); //$NON-NLS-1$
+ result.add(DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + "1"); //$NON-NLS-1$
+ result.add(DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + "2"); //$NON-NLS-1$
+ result.add(DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + "3"); //$NON-NLS-1$
+ result.add(DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + "4"); //$NON-NLS-1$
+
+ return result;
+ }
+}