package com.explodingpixels.widgets; import java.awt.Cursor; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import javax.swing.JTable; import javax.swing.table.TableColumnModel; /** * A collection of utility methods to be used with {@link JTable}. */ public class TableUtils { private TableUtils() { // no constructor - utility class. } // Sort support. ////////////////////////////////////////////////////////////////////////////// /** * An enumeration representing the sort order of a table column. */ public enum SortDirection { NONE(""), ASCENDING("ascending"), DESCENDING("descending"); private final String fValue; SortDirection(String value) { fValue = value; } String getValue() { return fValue; } static SortDirection find(String value) { for (SortDirection sortDirection : values()) { if (sortDirection.getValue().equals(value)) { return sortDirection; } } throw new IllegalArgumentException("No sort direction found for " + value); } } /** * An interface that will be notified when sorting of a {@link JTable} should occur. * * @see TableUtils#makeSortable(javax.swing.JTable, com.explodingpixels.widgets.TableUtils.SortDelegate) */ public interface SortDelegate { /** * Called when a table should sort its' rows based on the given column. * * @param columnModelIndex the column model index to base the sorting on. * @param sortDirection the direction to sort the table's rows in. */ void sort(int columnModelIndex, SortDirection sortDirection); } /** * Installs a listener on the given {@link JTable}'s {@link javax.swing.table.JTableHeader}, * which will notify the given {@link SortDelegate} when the user clicks the header * and thus wishes to sort. The listener will also call * {@link com.explodingpixels.widgets.TableHeaderUtils#toggleSortDirection(javax.swing.table.JTableHeader, int)} * and {@link TableHeaderUtils#setPressedColumn(javax.swing.table.JTableHeader, int)} * which will install hints for header renders to render the column headers in the * appropriate state. * * @param table the table so install the {@code SortDelegate} on. * @param sortDelegate the delegate to notify when sorting should be performed. */ public static void makeSortable(JTable table, SortDelegate sortDelegate) { validateSortDelegate(sortDelegate); MouseListener mouseListener = new ColumnHeaderMouseListener(table, sortDelegate); table.getTableHeader().addMouseListener(mouseListener); } private static void validateSortDelegate(SortDelegate sortDelegate) { if (sortDelegate == null) { throw new IllegalArgumentException("The given SortDelegate cannot be null."); } } private static class ColumnHeaderMouseListener extends MouseAdapter { private final JTable fTable; private final TableUtils.SortDelegate fSortDelegate; private boolean fMouseEventIsPerformingPopupTrigger = false; private ColumnHeaderMouseListener(JTable fTable, TableUtils.SortDelegate fSortDelegate) { this.fTable = fTable; this.fSortDelegate = fSortDelegate; } public void mouseClicked(MouseEvent mouseEvent) { if (shouldProcessMouseClicked()) { final TableColumnModel columnModel = fTable.getColumnModel(); int columnViewIndex = columnModel.getColumnIndexAtX(mouseEvent.getX()); int columnModelIndex = fTable.convertColumnIndexToModel(columnViewIndex); TableUtils.SortDirection sortDirection = TableHeaderUtils.toggleSortDirection(fTable.getTableHeader(), columnModelIndex); fSortDelegate.sort(columnModelIndex, sortDirection); fTable.getTableHeader().repaint(); } } private boolean shouldProcessMouseClicked() { return !fMouseEventIsPerformingPopupTrigger && isNotResizeCursor(); } private boolean isNotResizeCursor() { return fTable.getTableHeader().getCursor() != Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR); } public void mousePressed(MouseEvent mouseEvent) { fMouseEventIsPerformingPopupTrigger = mouseEvent.isPopupTrigger(); if (isNotResizeCursor()) { final TableColumnModel columnModel = fTable.getColumnModel(); int viewColumnIndex = columnModel.getColumnIndexAtX(mouseEvent.getX()); int columnModelIndex = fTable.convertColumnIndexToModel(viewColumnIndex); TableHeaderUtils.setPressedColumn(fTable.getTableHeader(), columnModelIndex); fTable.getTableHeader().repaint(); } } @Override public void mouseReleased(MouseEvent e) { TableHeaderUtils.setPressedColumn(fTable.getTableHeader(), TableHeaderUtils.NO_COLUMN); fTable.getTableHeader().repaint(); } } }