All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.sciss.treetable.j.DefaultTreeTableSorter Maven / Gradle / Ivy

There is a newer version: 1.2.1
Show newest version
/*
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Lesser General Public License as published
 *    by the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public License
 *    along with this program.  If not, see .
 */
package de.sciss.treetable.j;

import java.text.Collator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;

import javax.swing.DefaultRowSorter;
import javax.swing.RowFilter;
import javax.swing.SortOrder;
import javax.swing.RowSorter.SortKey;
import javax.swing.event.EventListenerList;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;

import de.sciss.treetable.j.event.TreeTableSorterEvent;
import de.sciss.treetable.j.event.TreeTableSorterListener;


public class DefaultTreeTableSorter
		implements TreeTableSorter, TreeTableSorter.SortCycle {
	
	public static final List ASCENDING_DESCENDING =
		Collections.unmodifiableList(Arrays.asList(
				SortOrder.ASCENDING, SortOrder.DESCENDING));
	
	public static final List ASCENDING_DESCENDING_UNSORTED =
		Collections.unmodifiableList(Arrays.asList(
				SortOrder.ASCENDING, SortOrder.DESCENDING, SortOrder.UNSORTED));
	
	public static final Comparator COMPARABLE_COMPARATOR =
		new Comparator() {
			@SuppressWarnings("unchecked")
			public int compare(Object a, Object b) {
				return ((Comparable)a).compareTo((Comparable)b);
			}
		};
	
	public DefaultTreeTableSorter(T tm, C cm) {
		treeModel = tm;
		columnModel = cm;
		sorters = new IdentityHashMap();
		sorters.put(tm.getRoot(), new NodeSorter(tm.getRoot()));
	}
	
	protected EventListenerList listenerList = new EventListenerList();
	
	private T treeModel;
	
	private C columnModel;
	
	private IdentityHashMap sorters;
	
	private List sortKeys = Collections.emptyList();
	
	private boolean[] isSortable;
	
	@SuppressWarnings("unchecked")
	private Comparator[] comparators;
	
	private RowFilter rowFilter;
	
	private List sortCycle = ASCENDING_DESCENDING;
	
	private int maxSortKeys = 3;
	
	private boolean sortsOnUpdates;
	
	@Override
	public NodeSorter getRowSorter(Object node) {
		return sorters.get(node);
	}
	
	@Override
	public NodeSorter getRowSorter(TreePath path) {
		Map sorterMap = sorters;
		NodeSorter sorter = sorterMap.get(path.getPathComponent(0));
		for (int idx=1, count=path.getPathCount(); idx comparator) {
    	checkColumn(column);
    	if (comparators == null) {
    		if (comparator == null)
    			return;
    		comparators = new Comparator[columnModel.getColumnCount()];
    	}
    	comparators[column] = comparator;
    }
    
    boolean isComparatorSet(int column) {
    	return comparators != null && comparators[column] != null;
    }
    
    public Comparator getComparator(int column) {
    	if (isComparatorSet(column))
    		return comparators[column];
    	Class cls = columnModel.getColumnClass(column);
    	if (cls == String.class)
    		return Collator.getInstance();
    	if (Comparable.class.isAssignableFrom(cls))
    		return COMPARABLE_COMPARATOR;
    	return Collator.getInstance();
    }

    public void setRowFilter(RowFilter filter) {
    	if (filter == null && rowFilter == null)
    		return;
    	rowFilter = filter;
    	sort();
    }
    
    public RowFilter getRowFilter() {
    	return rowFilter;
    }

	@Override
	public List getSortKeys() {
		return sortKeys;
	}

	@Override
	public void setSortKeys(List keys) {
		List old = sortKeys;
		if (keys != null && !keys.isEmpty()) {
			sortKeys = Collections.unmodifiableList(
					new ArrayList(keys));
		} else {
			sortKeys = Collections.emptyList();
		}
		if (!sortKeys.equals(old)) {
			fireSortOrderChanged();
			sort();
		}
	}

	@Override
	public void toggleSortOrder(int column) {
		checkColumn(column);
		if (isSortable(column)) {
			List keys = toggleSortOrder(
					getSortKeys(), getSortCycle(), column, getMaxSortKeys());
			setSortKeys(keys);
		}
	}
	
	// adapted from DefaultRowSorter.toggleSortOrder
	static List toggleSortOrder(List sortKeys,
			List sortCycle, int column, int maxSortKeys) {
		List keys = new ArrayList(sortKeys);
		SortKey sortKey;
		int sortIndex;
		for (sortIndex = keys.size() - 1; sortIndex >= 0; sortIndex--) {
			if (keys.get(sortIndex).getColumn() == column) {
				break;
			}
		}
		if (sortIndex == -1) {
			// Key doesn't exist
			sortKey = new SortKey(column, sortCycle.get(0));
			keys.add(0, sortKey);
		} else if (sortIndex == 0) {
			// It's the primary sorting key, toggle it
			SortKey key = keys.get(0);
			int idx = sortCycle.indexOf(key.getSortOrder());
			if (idx < 0 || ++idx >= sortCycle.size())
				idx = 0;
			keys.set(0, new SortKey(key.getColumn(), sortCycle.get(idx)));
		} else {
			// It's not the first, but was sorted on, remove old
			// entry, insert as first with ascending.
			keys.remove(sortIndex);
			keys.add(0, new SortKey(column, sortCycle.get(0)));
		}
		if (keys.size() > maxSortKeys) {
			keys = keys.subList(0, maxSortKeys);
		}
		return keys;
	}

	public List getSortCycle() {
		return sortCycle;
	}
	
	public void setSortCycle(List sortCycle) {
		if (sortCycle.isEmpty())
			throw new IllegalArgumentException();
		this.sortCycle = sortCycle;
	}


	private void checkColumn(int column) {
		if (column < 0 || column >= columnModel.getColumnCount())
			throw new IndexOutOfBoundsException();
	}
	
	
	public void sort() {
		getRowSorter(treeModel.getRoot()).sort(true);
		fireSorterChanged();
	}
	
	
	public void addTreeTableSorterListener(TreeTableSorterListener l) {
		listenerList.add(TreeTableSorterListener.class, l);
	}
	
	public void removeTreeTableSorterListener(TreeTableSorterListener l) {
		listenerList.remove(TreeTableSorterListener.class, l);
	}

	protected void fireSortOrderChanged() {
		fire(new TreeTableSorterEvent(this));
	}

	protected void fireSorterChanged() {
		fire(new TreeTableSorterEvent(this, null));
	}
	
	protected void fireRowSorterChanged(TreePath path) {
		fire(new TreeTableSorterEvent(this, path));
	}
	
	private void fire(TreeTableSorterEvent e) {
		Object[] listeners = listenerList.getListenerList();
		for (int i = listeners.length - 2; i >= 0; i -= 2) {
			if (listeners[i] == TreeTableSorterListener.class) {
				((TreeTableSorterListener)listeners[i + 1]).sorterChanged(e);
			}
		}
	}
	
	@Override
	public void structureChanged(TreePath path, boolean newRoot) {
		if (newRoot) {
			sorters.clear();
			sorters.put(treeModel.getRoot(), new NodeSorter(treeModel.getRoot()));
		} else {
			NodeSorter s = getRowSorter(path.getLastPathComponent());
			s.removeAllChildren(sorters);
			// TODO: rebuild here?
		}
	}
	
	@Override
	public void nodesRemoved(TreePath path, Object[] childNodes) {
		NodeSorter sorter = getRowSorter(path.getLastPathComponent());
		if (sorter != null)
			sorter.remove(childNodes, sorters);
	}
	
	@Override
	public void setVisible(TreePath path, List subPaths, boolean visible) {
		NodeSorter sorter = getRowSorter(path);
		sorter.setVisible(visible);
		if (visible) {
			for (TreePath p : subPaths) {
				NodeSorter s = sorter;
				for (int idx=path.getPathCount(), count=p.getPathCount(); idx implements SortCycle {

		public NodeSorter(Object root) {
			this(null, root);
			setVisible(true);
		}

		public NodeSorter(NodeSorter par, Object node) {
			parent = par;
			setModelWrapper(new TreeTableWrapper(node));
			children = createChildren();
			if (parent != null)
				setMaxSortKeys(Integer.MAX_VALUE);
		}

		private NodeSorter parent;

		private Map children;
		
		private List sortCycle = ASCENDING_DESCENDING_UNSORTED;

		private boolean visible;

		protected Map createChildren() {
			return new IdentityHashMap(
					getModel().getChildCount(getNode()));
		}

		public NodeSorter getParent() {
			return parent;
		}

		DefaultTreeTableSorter getMaster() {
			return DefaultTreeTableSorter.this;
		}

		NodeSorter getChildSorter(Object node, Map 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 TreeTableWrapper getTreeTableModelWrapper() {
			return (TreeTableWrapper)getModelWrapper();
		}

		public Object getNode() {
			return getTreeTableModelWrapper().getNode();
		}

		public C getColumnModel() {
			return getTreeTableModelWrapper().getColumnModel();
		}

		@Override
		public Comparator getComparator(int column) {
			Comparator c = super.getComparator(column);
			return c != null ? c : getMaster().getComparator(column);
		}
		
		@Override
		protected boolean useToString(int column) {
			if (super.getComparator(column) != null
					|| getMaster().isComparatorSet(column))
				return false;
	        Class columnClass = getColumnModel().getColumnClass(column);
	        if (columnClass == String.class)
	            return false;
            return !Comparable.class.isAssignableFrom(columnClass);
        }

		@Override
		public List getSortKeys() {
			List k = super.getSortKeys();
			return !k.isEmpty() ? k : getMaster().getSortKeys();
		}

		@Override
		public int getMaxSortKeys() {
			int m = super.getMaxSortKeys();
			return m < Integer.MAX_VALUE ? m : getMaster().getMaxSortKeys();
		}
		
		@Override
		public RowFilter getRowFilter() {
			RowFilter f = super.getRowFilter();
			if (f != null)
				return f;
			return getMaster().getRowFilter();
		}

		@Override
		public boolean getSortsOnUpdates() {
			return getMaster().getSortsOnUpdates();
		}
		
		@Override
		public boolean isSortable(int column) {
			return getMaster().isSortable(column);
		}
		
		public void setSortCycle(List sortCycle) {
			if (sortCycle.isEmpty())
				throw new IllegalArgumentException();
			this.sortCycle = sortCycle;
		}
		
		public List getSortCycle() {
			return sortCycle;
		}
		
		@Override
		public void toggleSortOrder(int column) {
			checkColumn(column);
			if (isSortable(column)) {
				List keys = DefaultTreeTableSorter.toggleSortOrder(
						super.getSortKeys(), getSortCycle(), column, getMaxSortKeys());
				setSortKeys(keys);
			}
		}

		
		@Override
		public void setSortsOnUpdates(boolean sortsOnUpdates) {
			throw new UnsupportedOperationException();
		}
		
		@Override
		public void setSortable(int column, boolean sortable) {
			throw new UnsupportedOperationException();
		}

		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);
		}

		@Override
		protected void fireRowSorterChanged(int[] lastRowIndexToModel) {
			super.fireRowSorterChanged(lastRowIndexToModel);
			if (firePathEvent)
				getMaster().fireRowSorterChanged(getPathToRoot());
		}

		private TreePath getPathToRoot() {
			if (parent == null)
				return new TreePath(getNode());
			return parent.getPathToRoot()
				.pathByAddingChild(getNode());
		}


		public void allRowsChanged() {
			getTreeTableModelWrapper().updateRowCount();
			super.allRowsChanged();
		}

		public void rowsDeleted(int firstRow, int endRow) {
			getTreeTableModelWrapper().updateRowCount();
			super.rowsDeleted(firstRow, endRow);
		}

		public void rowsInserted(int firstRow, int endRow) {
			getTreeTableModelWrapper().updateRowCount();
			super.rowsInserted(firstRow, endRow);
		}


		public void setVisible(boolean vis) {
			if (visible != vis) {
				visible = vis;
				if (vis)
					sort(true);
			}
		}

		public boolean isVisible() {
			return visible;
		}

		
		void removeAllChildren(Map map) {
			for (Map.Entry entry : children.entrySet()) {
				map.remove(entry.getKey());
				entry.getValue().removeAllChildren(map);
			}
			children.clear();
		}
		
		void remove(Object[] childNodes, Map map) {
			for (Object node : childNodes) {
				NodeSorter s = children.remove(node);
				if (s != null)
					s.removeAllChildren(map);
			}
		}
		
		

		protected class TreeTableWrapper extends ModelWrapper {

			public TreeTableWrapper(Object n) {
				node = n;
				updateRowCount();
			}

			private Object node;

			private int rowCount;

			public Object getNode() {
				return node;
			}

			public C getColumnModel() {
				return columnModel;
			}

			@Override
			public int getColumnCount() {
				return columnModel.getColumnCount();
			}

			@Override
			public I getIdentifier(int row) {
				return (I)treeModel.getChild(node, row);
			}

			@Override
			public T getModel() {
				return treeModel;
			}

			@Override
			public int getRowCount() {
				return rowCount;
			}

			/**
			 * The last row count must be cached until
			 * this method is called to update it.
			 */
			public void updateRowCount() {
				rowCount = treeModel.getChildCount(node);
			}

			@Override
			public Object getValueAt(int row, int column) {
				return columnModel.getValueAt(treeModel.getChild(node, row), column);
			}

		}

	}
	

}