package org.limewire.ui.swing.util; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.jdesktop.swingx.JXTable; import org.jdesktop.swingx.decorator.SortController; import org.jdesktop.swingx.decorator.SortKey; import org.jdesktop.swingx.decorator.SortOrder; import org.jdesktop.swingx.table.TableColumnExt; import org.limewire.ui.swing.table.GlazedJXTable; import ca.odell.glazedlists.GlazedLists; import ca.odell.glazedlists.SortedList; import ca.odell.glazedlists.gui.AdvancedTableFormat; import ca.odell.glazedlists.gui.TableFormat; import ca.odell.glazedlists.impl.sort.TableColumnComparator; import ca.odell.glazedlists.swing.DefaultEventTableModel; /** * This is essentially a copy of glazedLists.EventListJXTableSorting class. The * main difference is this class enforces the column comparators within * AdvancedTableFormat if the table is constructed with an AdvancedTableFormat. * Otherwise is reverts to the default GlazedListComparator. * * <p>This class supports multiple column sorts with stable results so that * equal elements are not reordered by sorts on successive columns. In * addition, any column may have secondary sort columns associated with it - * a single sort request on such a column will result in a multiple-column * sort. Secondary sort columns are specified by providing a non-null instance * of EventListTableSortFormat in the <code>install()</code> method.</p> */ public class EventListJXTableSorting { /** Maximum number of sorted columns. */ private static final int MAX_SORT_COLUMNS = 5; /** the sorted list behind the table being sorted */ private final SortedList sortedList; private final GlazedJXTable table; /** adapters between SortedList and JXTable */ private final SortController sortController; /** the original filter pipeline, used in {@link #uninstall} only */ private final SortController originalSortController; /** Format for table sorts on event list. */ private final EventListTableSortFormat tableSortFormat; /** * Usually, constructors shouldn't supposed to have side-effects, but this one * changes the table's filter pipeline. Therefore we use this private * constructor and call through it from the {@link #install} method. */ private EventListJXTableSorting(GlazedJXTable table, SortedList sortedList, EventListTableSortFormat tableSortFormat) { this.table = table; this.sortedList = sortedList; this.tableSortFormat = tableSortFormat; this.originalSortController = table.getSortController(); this.sortController = new EventListSortController(); table.setSortController(sortController); // Apply default sort keys. if (tableSortFormat != null) { List<SortKey> sortKeys = tableSortFormat.getDefaultSortKeys(); this.sortController.setSortKeys(sortKeys); } } /** * Install this {@link EventListJXTableSorting} to provide the sorting * behaviour for the specified {@link JXTable}. */ public static EventListJXTableSorting install(GlazedJXTable table, SortedList sortedList) { return new EventListJXTableSorting(table, sortedList, null); } /** * Install this {@link EventListJXTableSorting} to provide the sorting * behaviour for the specified {@link JXTable}. A non-null value for * <code>tableSortFormat</code> provides support for secondary sort columns. */ public static EventListJXTableSorting install(GlazedJXTable table, SortedList sortedList, EventListTableSortFormat tableSortFormat) { return new EventListJXTableSorting(table, sortedList, tableSortFormat); } /** * Remove this {@link EventListJXTableSorting} from the {@link JXTable}. */ public void uninstall() { table.setSortController(originalSortController); } /** * Implement {@link SortController} to provide sorting for {@link JXTable}. */ private class EventListSortController implements SortController { /** the active sort columns */ private final List<SortKey> sortKeys = new ArrayList<SortKey>(MAX_SORT_COLUMNS); private final List<SortKey> sortKeysReadOnly = Collections.unmodifiableList(sortKeys); /** active sort columns that do not contain any preSort columns */ private final List<SortKey> nonHiddenSortKeys = new ArrayList<SortKey>(MAX_SORT_COLUMNS); private final List<SortKey> nonHiddenKeysReadOnly = Collections.unmodifiableList(nonHiddenSortKeys); /** {@inheritDoc} */ public void toggleSortOrder(int columnIndex) { toggleSortOrder(columnIndex, GlazedLists.comparableComparator()); } /** {@inheritDoc} */ @SuppressWarnings("cast") public void toggleSortOrder(int columnIndex, Comparator comparator) { List<? extends SortKey> sortKeys = getSortKeys(); // see if we're already sorting with this column SortKey columnSortKey = null; for(Iterator<? extends SortKey> s = sortKeys.iterator(); s.hasNext(); ) { SortKey sortKey = (SortKey)s.next(); if(sortKey.getSortOrder() == SortOrder.UNSORTED) continue; if(sortKey.getColumn() == columnIndex) { columnSortKey = sortKey; break; } } // create the new sort key if(columnSortKey == null) { columnSortKey = new SortKey(SortOrder.ASCENDING, columnIndex); } else { SortOrder sortOrder = columnSortKey.getSortOrder() == SortOrder.ASCENDING ? SortOrder.DESCENDING : SortOrder.ASCENDING; columnSortKey = new SortKey(sortOrder, columnIndex); } // Create new list of sort keys based on existing list. List<? extends SortKey> newSortKeys = sortKeys; // Apply secondary sort columns if available. if (tableSortFormat != null) { List<Integer> columnList = tableSortFormat.getSecondarySortColumns(columnIndex); // Apply secondary sort columns in reverse order, from least // significant to most significant. If possible, we use the // existing sort key for the column. for (int i = columnList.size(); i > 0; i--) { Integer secondaryColumn = columnList.get(i - 1); SortKey sortKey = findSortKey(secondaryColumn, sortKeys); newSortKeys = buildSortKeys(sortKey, newSortKeys); } } // Apply primary sort column, and set sort keys to perform sort. setSortKeys(buildSortKeys(columnSortKey, newSortKeys)); } /** * Returns the SortKey corresponding to the specified column from the * specified list. If a meaningful SortKey is not found, then a * default SortKey with ascending order is returned. */ private SortKey findSortKey(int column, List<? extends SortKey> sortKeys) { // Search for sort key with matching column. for (SortKey sortKey : sortKeys) { if ((sortKey.getSortOrder() != SortOrder.UNSORTED) && (sortKey.getColumn() == column)) { return new SortKey(sortKey.getSortOrder(), column); } } // Not found so return default sort key. return new SortKey(SortOrder.ASCENDING, column); } /** * Returns a list of sort keys containing the specified first key, * followed by the specified list of additional keys. This method * provides support for sorting over multiple columns. */ private List<? extends SortKey> buildSortKeys(SortKey firstKey, List<? extends SortKey> sortKeys) { // Create SortKey list and unique column set. List<SortKey> newSortKeys = new ArrayList<SortKey>(); Set<Integer> columnSet = new HashSet<Integer>(); // Add first sort key. newSortKeys.add(firstKey); columnSet.add(firstKey.getColumn()); // Add remaining sort keys. Only keys with a meaningful sort order // are added, and duplicate columns are omitted. for (SortKey sortKey : sortKeys) { if (newSortKeys.size() >= MAX_SORT_COLUMNS) { break; } if ((sortKey.getSortOrder() != SortOrder.UNSORTED) && !columnSet.contains(sortKey.getColumn())) { newSortKeys.add(sortKey); columnSet.add(sortKey.getColumn()); } } // Return new list. return newSortKeys; } /** {@inheritDoc} */ @SuppressWarnings({ "unchecked", "cast" }) public void setSortKeys(List<? extends SortKey> sortKeys) { if(sortKeys == sortKeysReadOnly) return; if(sortKeys == null) sortKeys = Collections.emptyList(); this.sortKeys.clear(); this.nonHiddenSortKeys.clear(); if(tableSortFormat != null && tableSortFormat.getPreSortColumns() != null && tableSortFormat.getPreSortColumns().size() > 0) { this.sortKeys.addAll(tableSortFormat.getPreSortColumns()); } this.nonHiddenSortKeys.addAll(sortKeys); this.sortKeys.addAll(sortKeys); // rebuild the SortedList's comparator List<Comparator> comparators = new ArrayList<Comparator>(this.sortKeys.size()); for(int k = 0; k < this.sortKeys.size(); k++) { SortKey sortKey = (SortKey)this.sortKeys.get(k); if(sortKey.getSortOrder() == SortOrder.UNSORTED) continue; Comparator comparator = getComparator(sortKey.getColumn()); if(sortKey.getSortOrder() == SortOrder.DESCENDING) comparator = GlazedLists.reverseComparator(comparator); comparators.add(comparator); } // figure out the final comparator final Comparator comparator; if(comparators.isEmpty()) { comparator = null; } else if(comparators.size() == 1) { comparator = comparators.get(0); } else { comparator = GlazedLists.chainComparators((List)comparators); } // apply this comparator to the sortedlist sortedList.getReadWriteLock().writeLock().lock(); try { sortedList.setComparator(comparator); } finally { sortedList.getReadWriteLock().writeLock().unlock(); } } /** * We need to fix this implementation so it looks into the {@link JXTable}'s * {@link TableColumnExt} to find the appropriate column comparator. Failing * that, it could look for an {@link AdvancedTableFormat} and the column's * Comparator. */ @SuppressWarnings("unchecked") private Comparator getComparator(int modelIndex) { DefaultEventTableModel tableModel = (DefaultEventTableModel)table.getModel(); TableFormat tableFormat = tableModel.getTableFormat(); // TODO: local changes compared to GlazedLists implementation // If the tableFormat is an AdvancedTableFormat, use the table column comparators // found in the AdvancedTableFormat for sorting. if(tableFormat instanceof AdvancedTableFormat) { return new EventListColumnComparator(tableFormat, modelIndex, ((AdvancedTableFormat)tableFormat).getColumnComparator(modelIndex)); } else return new TableColumnComparator(tableFormat, modelIndex); } /** {@inheritDoc} */ public List<? extends SortKey> getSortKeys() { // return the sort keys which do not contain preSort columns. This ensures // the correct table header is used when displaying sort to the user. return nonHiddenKeysReadOnly; } /** {@inheritDoc} */ public SortOrder getSortOrder(int columnIndex) { for(SortKey s : sortKeys) { if(s.getColumn() == columnIndex) return s.getSortOrder(); } return SortOrder.UNSORTED; } } }