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

ca.odell.glazedlists.swing.TableComparatorChooser Maven / Gradle / Ivy

There is a newer version: 1.9.1
Show newest version
/* Glazed Lists                                                 (c) 2003-2006 */
/* http://publicobject.com/glazedlists/                      publicobject.com,*/
/*                                                     O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.swing;

// the core Glazed Lists packages
import ca.odell.glazedlists.*;
// the Glazed Lists util and impl packages include default comparators
import ca.odell.glazedlists.gui.*;
import ca.odell.glazedlists.impl.SortIconFactory;
import ca.odell.glazedlists.impl.gui.SortingStrategy;
// Swing toolkit stuff for displaying widgets
import javax.swing.*;
import javax.swing.table.*;
// for responding to user actions
import java.awt.event.*;
import java.awt.*;
import javax.swing.event.*;
// for keeping lists of comparators
import java.util.*;

/**
 * A TableComparatorChooser is a tool that allows the user to sort a ListTable by clicking
 * on the table's headers. It requires that the {@link JTable}s model is an
 * {@link EventTableModel} with a {@link SortedList} as a source.
 *
 * 

This class includes custom arrow icons that indicate the sort * order. The icons used are chosen based on the current Swing look and feel. * Icons are available for the following look and feels: Mac OS X, Metal, Windows. * *

This class supports multiple sort strategies for each * column, specified by having muliple comparators for each column. This may * be useful when you want to sort a single column in either of two ways. For * example, when sorting movie names, "The Phantom Menace" may be sorted under * "T" for "The", or "P" for "Phantom". * *

This class supports sorting multiple columns simultaneously. * In this mode, the user clicks a first column to sort by, and then the user * clicks subsequent columns. The list is sorted by the first column and ties * are broken by the second column. * *

If the {@link EventTableModel} uses a {@link AdvancedTableFormat}, its * {@link AdvancedTableFormat#getColumnComparator} method will be used to * populate the initial column {@link Comparator}s. * * @see Bug 4 * @see Bug 31 * * @author Jesse Wilson */ public class TableComparatorChooser extends AbstractTableComparatorChooser { /** * the header renderer which decorates an underlying renderer * (the table header's default renderer) with a sort arrow icon. */ private final SortArrowHeaderRenderer sortArrowHeaderRenderer; /** listen for table and mouse events */ private final TableModelHandler tableModelHandler = new TableModelHandler(); /** the table being sorted */ private JTable table = null; /** listeners to sort change events */ private ActionListener sortListener = null; /** the sort icons to use */ private static Icon[] icons = SortIconFactory.loadIcons(); /** when somebody clicks on the header, update the sorting state */ private final HeaderClickHandler headerClickHandler; /** * Creates a new TableComparatorChooser that responds to clicks * on the specified table and uses them to sort the specified list. * * @param table the table with headers that can be clicked on * @param sortedList the sorted list to update * @param multipleColumnSort true to sort by multiple columns * at a time, or false to sort by a single column. Although * sorting by multiple columns is more powerful, the user interface is * not as simple and this strategy should only be used where necessary. */ public TableComparatorChooser(JTable table, SortedList sortedList, boolean multipleColumnSort) { this(table, sortedList, multipleColumnSort ? MULTIPLE_COLUMN_MOUSE : SINGLE_COLUMN); } /** * Creates a new TableComparatorChooser that responds to clicks * on the specified table and uses them to sort the specified list by * delegating to the given strategy. * * @param table the table with headers that can be clicked on * @param sortedList the sorted list to update * @param strategy an implementations of {@link SortingStrategy}, typically one of *

    *
  • {@link AbstractTableComparatorChooser#SINGLE_COLUMN} *
  • {@link AbstractTableComparatorChooser#MULTIPLE_COLUMN_MOUSE} *
  • {@link AbstractTableComparatorChooser#MULTIPLE_COLUMN_KEYBOARD} *
*/ public TableComparatorChooser(JTable table, SortedList sortedList, Object strategy) { super(sortedList, ((EventTableModel)table.getModel()).getTableFormat()); validateSortingStrategy(strategy); // save the Swing-specific state this.table = table; // build and set the table header renderer which decorates the existing renderer with sort arrows sortArrowHeaderRenderer = new SortArrowHeaderRenderer(table.getTableHeader().getDefaultRenderer()); table.getTableHeader().setDefaultRenderer(sortArrowHeaderRenderer); // listen for events on the specified table table.setColumnSelectionAllowed(false); table.getModel().addTableModelListener(tableModelHandler); // install the sorting strategy to interpret clicks this.headerClickHandler = new HeaderClickHandler(table, (SortingStrategy)strategy); } /** * Ensures the given strategy is an accepted value. It is * possible for people to define their own sorting strategies, so this * validation can only ensure that the given strategy * implements the {@link SortingStrategy} interface. * * @throws IllegalArgumentException if strategy is not an * accepted value */ private static void validateSortingStrategy(Object strategy) { if (!(strategy instanceof SortingStrategy)) throw new IllegalArgumentException("Unrecognized sorting strategy, \"" + strategy + "\", use one of AbstractTableComparatorChooser.SINGLE_COLUMN, AbstractTableComparatorChooser.MULTIPLE_COLUMN_MOUSE, or AbstractTableComparatorChooser.MULTIPLE_COLUMN_KEYBOARD"); } /** * Registers the specified {@link ActionListener} to receive notification whenever * the {@link JTable} is sorted by this {@link TableComparatorChooser}. */ public void addSortActionListener(final ActionListener sortActionListener) { sortListener = AWTEventMulticaster.add(sortListener, sortActionListener); } /** * Deregisters the specified {@link ActionListener} to no longer receive * action events. */ public void removeSortActionListener(final ActionListener sortActionListener) { sortListener = AWTEventMulticaster.remove(sortListener, sortActionListener); } /** * Examines the current {@link Comparator} of the SortedList and * adds icons to the table header renderers in response. * *

To do this, clicks are injected into each of the * corresponding ColumnClickTrackers. */ protected void redetectComparator(Comparator currentComparator) { super.redetectComparator(currentComparator); // force the table header to redraw itself table.getTableHeader().revalidate(); table.getTableHeader().repaint(); } /** * Updates the comparator in use and applies it to the table. */ protected final void rebuildComparator() { super.rebuildComparator(); // force the table header to redraw itself table.getTableHeader().revalidate(); table.getTableHeader().repaint(); // notify interested listeners that the sorting has changed if(sortListener != null) sortListener.actionPerformed(new ActionEvent(this, 0, "sort")); } /** * Gets the sorting style currently applied to the specified column. */ protected final int getSortingStyle(int column) { return super.getSortingStyle(table.convertColumnIndexToModel(column)); } /** * Determines if the specified mouse event shall be handled by this * {@link TableComparatorChooser}. The default implementation handles only clicks * with the left mouse button. Extending classes can customize which mouse * events the table comparator chooser responds to by overriding this method. * *

As of 2005/12/20, that this method is no longer called when the * corresponding mouse press event was a popup trigger. In effect, if this * is a right-click on Windows or a 'control-click' on the Mac. */ protected boolean isSortingMouseEvent(MouseEvent e) { // skip the sort if it's not button 1 if(e.getButton() != MouseEvent.BUTTON1) return false; // we have no reason to dislike this mouse event! return true; } /** * Set all {@link TableComparatorChooser}s to use the icons from the directory * specified. The folder should contain the following eight icon files: *

  • primary_sorted.png
  • secondary_sorted.png *
  • primary_sorted_alternate.png
  • secondary_sorted_alternate.png *
  • primary_sorted_alternate_reverse.png
  • secondary_sorted_alternate_reverse.png *
  • primary_sorted_reverse.png
  • secondary_sorted_reverse.png * *

    Note that this path must be on the system classpath. It may be within a * jar file. */ public static void setIconPath(String path) { icons = SortIconFactory.loadIcons(path); } /** * Releases the resources consumed by this {@link TableComparatorChooser} so that it * may eventually be garbage collected. * *

    A {@link TableComparatorChooser} will be garbage collected without a call to * {@link #dispose()}, but not before its source {@link EventList} is garbage * collected. By calling {@link #dispose()}, you allow the {@link TableComparatorChooser} * to be garbage collected before its source {@link EventList}. This is * necessary for situations where an {@link TableComparatorChooser} is short-lived but * its source {@link EventList} is long-lived. * *

    Warning: It is an error * to call any method on a {@link TableComparatorChooser} after it has been disposed. */ public void dispose() { super.dispose(); headerClickHandler.dispose(); // if the default renderer within the table header is our sort arrow renderer, // uninstall it by restoring the table header's original default renderer if (table.getTableHeader().getDefaultRenderer() == sortArrowHeaderRenderer) table.getTableHeader().setDefaultRenderer(sortArrowHeaderRenderer.getDelegateRenderer()); // remove our listeners from the table's header and model table.getModel().removeTableModelListener(tableModelHandler); // null out our table reference for safety's sake table = null; } /** * Nested Listener class handles table events and mouse events. */ private class TableModelHandler implements TableModelListener { /** * When the number of columns changes in the table, we need to * clear the comparators and columns. */ public void tableChanged(TableModelEvent event) { if(event.getFirstRow() == TableModelEvent.HEADER_ROW && event.getColumn() == TableModelEvent.ALL_COLUMNS) { // the table structure may have changed due to a change in the // table format so we conservatively reset the TableFormat setTableFormat(((EventTableModel)table.getModel()).getTableFormat()); } // if the comparator has changed final Comparator currentComparator = sortedList.getComparator(); if(currentComparator != sortedListComparator) { redetectComparator(currentComparator); } } } /** * The SortArrowHeaderRenderer simply delegates most of the rendering * to a given delegate renderer, and adds an icon to indicate sorting * direction. This allows TableComparatorChooser to work equally well * with any custom TableCellRenderers that are used as the default * table header renderer. * *

    This class fails to add indicator arrows on table headers where the * default table header render is not a DefaultTableCellRenderer or does * not implement {@link SortableRenderer}. */ class SortArrowHeaderRenderer implements TableCellRenderer { /** the renderer to which we delegate */ private TableCellRenderer delegateRenderer; /** * Creates a new SortArrowHeaderRenderer that attempts to decorate the * given delegateRenderer which a sorting icon. */ public SortArrowHeaderRenderer(TableCellRenderer delegateRenderer) { this.delegateRenderer = delegateRenderer; } /** * Returns the delegate renderer that is decorated with sort arrows. */ public TableCellRenderer getDelegateRenderer() { return this.delegateRenderer; } /** * Renders the header in the default way but with the addition of an icon. */ public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { final Icon sortIcon = icons[getSortingStyle(column)]; final Component rendered; // 1. look for our custom SortableRenderer interface if (delegateRenderer instanceof SortableRenderer) { ((SortableRenderer) delegateRenderer).setSortIcon(sortIcon); rendered = delegateRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); // 2. Otherwise check whether the rendered component is a JLabel (this is the case of the default header renderer) } else { rendered = delegateRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); // we check for a JLabel rather than a DefaultTableCellRenderer to support WinLAF, // which installs a decorator over the DefaultTableCellRenderer if (rendered instanceof JLabel) { final JLabel label = (JLabel) rendered; label.setIcon(sortIcon); label.setHorizontalTextPosition(SwingConstants.LEADING); } } return rendered; } } /** * Handle clicks to the table's header by adjusting the sorrting state. */ private class HeaderClickHandler extends MouseAdapter { private final JTable table; private final SortingStrategy delegate; private boolean mouseEventIsPerformingPopupTrigger = false; public HeaderClickHandler(JTable table, SortingStrategy delegate) { this.table = table; this.delegate = delegate; table.getTableHeader().addMouseListener(this); } public void mouseClicked(MouseEvent e) { if(mouseEventIsPerformingPopupTrigger) return; if(!isSortingMouseEvent(e)) return; boolean shift = e.isShiftDown(); boolean control = e.isControlDown() || e.isMetaDown(); TableColumnModel columnModel = table.getColumnModel(); int viewColumn = columnModel.getColumnIndexAtX(e.getX()); int column = table.convertColumnIndexToModel(viewColumn); int clicks = e.getClickCount(); if(clicks >= 1 && column != -1) { delegate.columnClicked(sortingState, column, clicks, shift, control); } } /** * Keep track of whether the mouse is triggering a popup, so we * can avoid sorting the table when the poor user just wants to show * a context menu. */ public void mousePressed(MouseEvent mouseEvent) { this.mouseEventIsPerformingPopupTrigger = mouseEvent.isPopupTrigger(); } public void dispose() { table.getTableHeader().removeMouseListener(this); } } }





  • © 2015 - 2024 Weber Informatics LLC | Privacy Policy