Subclassing DefaultRowSorter to allow tree-table sorting
Asked Answered
K

1

16

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)

Kala answered 4/4, 2012 at 12:11 Comment(3)
Have you managed to make it work? Any reasons why you kept the JXTreeTable code instead of using the aephyr.TreeTable class?Polychrome
Hi, I have worked only for hours and I'm stuck with a problem for which I don't see any solution. Basically, I can correctly sort all rows and even get the highlighters to display the correct line but it doesn't work for the hierarchical column. Even if the value is displayed correctly, the tree structure itself doesn't change. Say the tree has two children with 2 and 3 children respectively. If sorting the treetable makes the second child of root become the first, the values are ordered correctly but the first child of the tree has still 2 children where we would expect 3.Kala
Concerning the aephyr library, since I use some other things from Swingx and particularly the very handy highlighters, I didn't want to switch to another library. But it inspired me alot when trying to get the JXTreeTable to sort its elements and it seems very nice :)Kala
C
1

Based on aephyr library i've checked out, if you modify the node sorter like this it should sort only the first elements :

public class NodeSorter extends DefaultRowSorter<M, Integer> {

     [.....]

            private Comparator noopComparator = new Comparator(){
                @Override
                public int compare(Object o1, Object o2) {
                    return 0;
                }
            };

    @Override
    public Comparator<?> getComparator(int column) {
                 // if it has no parent then compare --> sort
                 // if it has a parent then do nothing
                if(parent != null)
                    return noopComparator;
                Comparator<?> c = super.getComparator(column);
                return c != null ? c : getMaster().getComparator(column);
    }
   [....]
Coal answered 25/7, 2012 at 15:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.