In this question, I asked how it was possible to make a JXTreeTable (SwingX) sort its top element.
I took a look at the library (aephyr) suggested by mKorbel and tried to combine it with JXTreeTable (I created a new class named JXSortableTreeTable by copying the source code of JXTreeTable).
So far, I was able to implement a mechanism to sort the nodes of the tree-table i.e. when convertRowIndexToModel
of my custom sorter (see below) is called, the indices that it returns are correct.
I thus have a class that looks like that:
public class TreeTableRowSorter <M extends TreeTableModelAdapter> extends DefaultRowSorter {
private M treeTableModel; // model
private List<Integer> indices; // list where each entry is the model index
private IdentityHashMap<Object, NodeSorter> sorters;
private class IndicesMapFiller { // class that fills the "map" this.indices
public void fillIndicesMap(Object node) { // recursive
// Fills the indices "map"
}
}
@Override
public int convertRowIndexToModel(int index) {
return indices.get(index);
}
@Override
public int convertRowIndexToView(int index) {
return indices.indexOf(index);
}
@Override
public void toggleSortOrder(int columnIndex) {
sorters.get(treeTableModel.getRoot()).toggleSortOrder(columnIndex);
super.toggleSortOrder(columnIndex);
}
@Override
public void sort() {
getRowSorter(treeTableModel.getRoot()).sort(true);
indices = new ArrayList<Integer>();
for (int i = 0; i < getViewRowCount(); i++)
indices.add(-1);
IndicesMapFiller filler = new IndicesMapFiller(indices);
filler.fillIndicesMap(treeTableModel.getRoot());
System.out.println("INDICES: " + indices);
}
private class TreeTableRowSorterModelWrapper extends ModelWrapper<M, Integer> {
private final Object node;
private final TreeTableRowSorterModelWrapper parent;
public TreeTableRowSorterModelWrapper(Object node, TreeTableRowSorterModelWrapper parent) {
this.node = node;
this.parent = parent;
}
@Override
public M getModel() {
return treeTableModel;
}
@Override
public int getColumnCount() {
return (treeTableModel == null) ? 0 : treeTableModel.getColumnCount();
}
@Override
public int getRowCount() {
// Returns only the number of direct visible children in order for
// each NodeSorter to only sort its children (and not its children's
// children)
int rowCount = treeTableModel.getDirectVisibleChildrenCount(node, getParentPath());
return rowCount;
}
public TreePath getParentPath() {
Object root = treeTableModel.getRoot();
if (root == null || node == root)
return null;
if (parent.getNode() == root)
return new TreePath(root);
return parent.getParentPath().pathByAddingChild(parent);
}
@Override
public Object getValueAt(int row, int column) {
Object node = treeTableModel.getChild(getNode(), row);
return treeTableModel.getValue(node, column);
}
public Object getNode() {
return node;
}
}
public class NodeSorter extends DefaultRowSorter<M, Integer> {
private NodeSorter parent;
private Map<Object, NodeSorter> children;
public NodeSorter(Object root) {
this(null, root);
}
public NodeSorter(NodeSorter par, Object node) {
parent = par;
TreeTableRowSorterModelWrapper parentWrapper =
(parent == null) ? null : (TreeTableRowSorterModelWrapper) parent.getModelWrapper();
TreeTableRowSorterModelWrapper wrapper =
new TreeTableRowSorterModelWrapper(node, parentWrapper);
setModelWrapper(wrapper);
children = createChildren();
if (parent != null)
setMaxSortKeys(Integer.MAX_VALUE);
if (node == null)
return;
// Create the sorters for all children (even those not yet shown)
int childCount = getModel().getChildCount(node);
for (int i = 0; i < childCount; i++) {
Object childNode = getModel().getChild(node, i);
getChildSorter(childNode, sorters);
}
}
protected Map<Object, NodeSorter> createChildren() {
int childCount = getModel().getChildCount(getNode());
IdentityHashMap<Object, NodeSorter> map = new IdentityHashMap<Object, NodeSorter>(childCount);
return map;
}
public NodeSorter getParent() {
return parent;
}
TreeTableSortController<M> getMaster() {
return TreeTableSortController.this;
}
NodeSorter getChildSorter(Object node, Map<Object, NodeSorter> map) {
NodeSorter s = children.get(node);
if (s == null && map != null) {
s = new NodeSorter(this, node);
children.put(node, s);
map.put(node, s);
}
return s;
}
protected TreeTableRowSorterModelWrapper getTreeTableModelWrapper() {
return (TreeTableRowSorterModelWrapper) getModelWrapper();
}
public Object getNode() {
return ((TreeTableRowSorterModelWrapper) getModelWrapper()).getNode();
}
@Override
public void toggleSortOrder(int columnIndex) {
for (NodeSorter childSorter : children.values()) {
childSorter.toggleSortOrder(columnIndex);
}
super.toggleSortOrder(columnIndex);
}
private boolean firePathEvent = true;
void sort(boolean sortChildren) {
if (!isVisible())
return;
firePathEvent = false;
try {
super.sort();
} finally {
firePathEvent = true;
}
if (!sortChildren)
return;
for (NodeSorter sorter : children.values())
sorter.sort(sortChildren);
}
private TreePath getPathToRoot() {
if (parent == null)
return new TreePath(getNode());
return parent.getPathToRoot()
.pathByAddingChild(getNode());
}
}
If the model data is organised like this (with each global node's index in the model shown in the last column):
Name / Date / File UID | Glob. View Idx | Glob. Model Idx
(Root)
|- Mr. X / 1996/10/22 / AE123F6D | 0 | 0
|--- File1 / 2012/01/10 / DFC2345Q | 1 | 1
|--- File2 / 2012/01/11 / D321FEC6 | 2 | 2
|- Mrs. Y / 1975/03/03 / G2GF35EZ | 3 | 3
|--- File3 / 2012/02/29 / 35GERR32 | 4 | 4
|--- File4 / 2012/01/22 / LKJY2342 | 5 | 5
.
.
.
If I sort the 2nd column, I would like to get this output:
Name / Date / File UID | Glob. View Idx | Glob. Model Idx
(Root)
|- Mrs. Y / 1975/03/03 / G2GF35EZ | 0 | 3
|--- File4 / 2012/01/22 / LKJY2342 | 1 | 5
|--- File3 / 2012/02/29 / 35GERR32 | 2 | 4
|- Mr. X / 1996/10/22 / AE123F6D | 3 | 0
|--- File1 / 2012/01/10 / DFC2345Q | 4 | 1
|--- File2 / 2012/01/11 / D321FEC6 | 5 | 2
.
.
.
What I actually get is exactly the same as the non-sorted version except that the result of convertRowIndexToModel is correct because if I call it for each row of the model, I get:
V -> M
0 -> 3
1 -> 5
2 -> 4
3 -> 0
4 -> 1
5 -> 2
My question thus boils down to:
When subclassing DefaultRowSorter to implement the sorting of a tree-table like JXTreeTable what methods should I override? Since I cannot override viewToModel of the rowSorter (private) or any function that modifies it (also private), I implemented my own V->M map creation and made sure it is used when calling convertRowIndexTo(Model|View) of my sorter.
I feel like there's something missing like a call to the conversion method somewhere either in JXSortableTreeTable or maybe in the custom sorter.
Thank you very much for your help and insightful comments!
Edit> After testing the result a little bit more, it seems to rather be related to hierarchical column of the JXTreeTable for which the data isn't updated as it works perfectly when sorting on the other columns (I also tried to change the hierarchical column (set to another column) and the former works once it's not hierarchical anymore)