package com.onaro.util.jfc.grouping; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.BitSet; import java.util.Stack; import javax.swing.DefaultListSelectionModel; import javax.swing.JTree; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.TreeModel; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; /** * A selection model that recursively selects the rows of the selection model of the * {@link GroupingTable} (the selection of the GroupingTable may contain * groups and rows, this model will select only the rows in all the selected groups and * sub-groups). */ public class SourceTableGroupSelectionModel extends DefaultListSelectionModel implements TreeSelectionListener { private static final long serialVersionUID = 1L; /** * The tree-model of the {@link GroupingTable}. Used for finding all the rows * in a group. */ private TreeModel treeModel; private boolean tableRebuilding = false; private TreeSelectionModel treeSelectionModel; /** * Initialize the selection model for the given {@link GroupingTable}. Register * for selection model changes in the GroupingTable. * * @param groupingTable the GroupingTable who's selection model * is being translated */ public SourceTableGroupSelectionModel(GroupingTable groupingTable) { JTree tree = groupingTable.getTree(); treeModel = tree.getModel(); treeSelectionModel = tree.getSelectionModel(); tree.addTreeSelectionListener(this); groupingTable.addPropertyChangeListener(GroupingTable.REBUILDING_PROPERTY, new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { if (GroupingTable.REBUILDING_PROPERTY.equals(evt.getPropertyName())) { tableRebuilding = ((GroupingTable)evt.getSource()).isRebuilding(); if (!tableRebuilding) rebuildFromTree(); } } }); } /** * Whenever selection change in the tree, update this model, except if the grouping table is now rebuilding. * @param e */ public void valueChanged(TreeSelectionEvent e) { if (tableRebuilding) return; BitSet addedRows = new BitSet(); BitSet removedRows = new BitSet(); TreePath[] paths = e.getPaths(); for (int i = 0; i < paths.length; ++i) { translatePathToRow(paths[i], e.isAddedPath(i) ? addedRows : removedRows); } /** * Remove paths that are both "removed" and "added". */ BitSet tmpAdded = new BitSet(); tmpAdded.or(addedRows); addedRows.andNot(removedRows); removedRows.andNot(tmpAdded); /** * Notify the listeners of this selection model of the change that was * detected. */ if (addedRows.cardinality() > 0 || removedRows.cardinality() > 0) { setValueIsAdjusting(true); int min = addedRows.nextSetBit(0); while (min >= 0) { int max = addedRows.nextClearBit(min); addSelectionInterval(min, max-1); min = addedRows.nextSetBit(max); } min = removedRows.nextSetBit(0); while (min >= 0) { int max = removedRows.nextClearBit(min); removeSelectionInterval(min, max-1); min = removedRows.nextSetBit(max); } setValueIsAdjusting(false); } } /** * Find all the rows that descend from the nodes of the path and register them with the addedRows/removedRows. * @param path the tree-selection-path to convert to row * @param rows holds the converted row numbers */ private void translatePathToRow(TreePath path, BitSet rows) { Stack searchStack = new Stack(); searchStack.push(path.getLastPathComponent()); while (!searchStack.isEmpty()) { Object node = searchStack.pop(); if (node instanceof GroupingModel.State.Row) { GroupingModel.State.Row row = (GroupingModel.State.Row) node; rows.set(row.row); } else { int childCount = treeModel.getChildCount(node); for (int child = 0; child < childCount; ++child) { searchStack.push(treeModel.getChild(node, child)); } } } } private void rebuildFromTree() { BitSet addedRows = new BitSet(); TreePath[] selectionPaths = treeSelectionModel.getSelectionPaths(); if (selectionPaths != null) { for (TreePath path: selectionPaths) { translatePathToRow(path, addedRows); } } setValueIsAdjusting(true); clearSelection(); if (addedRows.cardinality() > 0) { setValueIsAdjusting(true); int min = addedRows.nextSetBit(0); while (min >= 0) { int max = addedRows.nextClearBit(min); addSelectionInterval(min, max-1); min = addedRows.nextSetBit(max); } } setValueIsAdjusting(false); } }