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

net.sf.cuf.ui.table.SortingTable Maven / Gradle / Ivy

The newest version!
package net.sf.cuf.ui.table;

import java.awt.Cursor;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Enumeration;
import javax.swing.JTable;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableModelEvent;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;

/**
 * A {@link JTable} with capabilities for sorting the rows and
 * showing/hiding columns. Sorting is displayed in the table header. It can be
 * switched there by mouse clicking. With the SHIFT modifier sorting direction
 * can be changed to descending. With the CONTROL modifier further sorting
 * criteria can be added.
 * 

* Sorting support is achieved by using a {@link TableSorter}. In {@link #setModel(TableModel)} * the TableModel will be wrapped in a TableSorter if necessary. * Therefore {@link #getModel} will always return a TableSorter object. * If you need the original TableModel you have to use {@link TableSorter#getModel}, * e.g. table.getModel().getModel(). *

* The support for showing/hiding columns uses {@link ColumnVisibilitySupport}. * It assumes that there is a 1:1 mapping from model columns to view columns (each model column must be displayed in * exactly one view column). *
* Manipulations of the table column model have to be done very carefully since there * might be conflicts with the column showing/hiding. Especially two points have * to be observed: do not try to remove any hidden columns from the column model and do * not break the 1:1 mapping between model and view columns. * * @author Jörg Eichhorn, sd&m AG */ public class SortingTable extends JTable implements MouseListener, ColumnVisibilityChangeListener { /** * Our sorting wrapper around the table model. * The reference is stored in this attribute for convenience. Otherwise * we would have to get it via {@link JTable#getModel} and cast it. */ protected TableSorter mSorter = null; /** * Functionality to make columns invisible and visible again. */ protected ColumnVisibilitySupport mColumnVisibilitySupport = null; /** * Construct new SortingTable with default table model. */ public SortingTable() { this(null); } /** * Construct new SortingTable for given a table model. * The table model will be wrapped in a {@link TableSorter}. * * @param pTableModel ordinary table model, may be null */ public SortingTable(TableModel pTableModel) { super(); mColumnVisibilitySupport = new ColumnVisibilitySupport(this); mColumnVisibilitySupport.addColumnVisibilityChangeListener(this); if (pTableModel == null) { pTableModel = new DefaultTableModel(); } setModel(pTableModel); initialize(); } /** * Setup everyting. *

    *
  • Set special TableHeaderRenderer for every column.
  • *
  • Register ourselves as MouseListener at TableHeader.
  • *
*/ protected void initialize() { for (int i = 0; i < columnModel.getColumnCount(); i++) { // every column gets its own TableHeaderRenderer columnModel.getColumn(i).setHeaderRenderer(getTableHeaderRenderer()); } // register ourselves as MouseListner tableHeader.addMouseListener(this); } /** * Return the TableCellRenderer to use as TableHeaderRenderer. * Subclasses may override this method to return their own special Renderer. * @return a table header renderer */ protected TableHeaderRenderer getTableHeaderRenderer() { return new TableHeaderRenderer(); } /** * Redraw all TableHeaders according to current sorting. */ protected void redrawTableHeaders() { // fetch list containing TableSortInfo from model TableSortInfo sortedColumns = mSorter.getSortInfo(); // iterate over all table columns for (int i = 0; i < columnModel.getColumnCount(); i++) { // fetch reference to TableHeaderRenderer TableHeaderRenderer thr = ((TableHeaderRenderer)columnModel.getColumn(i).getHeaderRenderer()); // initially assume no sorting in this column thr.setDirection(TableHeaderRenderer.NONE); // our running index i is a view-column-index: get corresponding model-index int modelIndex = convertColumnIndexToModel(i); // try to find modelIndex in TableSortInfo int entryIndex = sortedColumns.find(modelIndex); if ( entryIndex >= 0 ) { // modelIndex in TableSortInfo found: set button in TableHeaderRenderer thr.setDirection(sortedColumns.isAscending(entryIndex) ? TableHeaderRenderer.ASCENDING : TableHeaderRenderer.DESCENDING); } } // IMPORTANT for proper redisplay of tableheaders if (tableHeader != null) { tableHeader.resizeAndRepaint(); } } /* --------------------------------------------------------------- Hiding and Showing table columns --------------------------------------------------------------- */ /** * Forget all invisible columns and (re)create the table columns from the model. * This implementation notifies the {@link ColumnVisibilitySupport} about all changes * to the table column model, even those for which no event is generated. */ public void createDefaultColumnsFromModel() { super.createDefaultColumnsFromModel(); if ( mColumnVisibilitySupport != null ) // this method is called in the constructor of our parent class at which point the columnVisibilitySupport is not yet initialised { mColumnVisibilitySupport.clearInvisibleColumns(); } } /** * This event is ignored. * * @param pEvent event information */ public void columnShown(final ColumnVisibilityChangeEvent pEvent) { // ignore this event } /** * Whenever a sorted column is hidden the whole sorting is dropped. * * @param pEvent event information */ public void columnHidden(final ColumnVisibilityChangeEvent pEvent) { int modelIndex = pEvent.getColumn().getModelIndex(); // remove sorting from table if sorted column is hidden if ( mSorter.isSorted(modelIndex) ) { mSorter.dropSorting(); } } /** * Test whether a table column is currently visible. * @param pModelIndex column index in the table model * @return true if table column is visible */ public boolean isColumnVisible(final int pModelIndex) { return mColumnVisibilitySupport.isColumnVisible(pModelIndex); } /** * Show or hide a table column. * @param pModelIndex column index in the table model * @param pVisible true to make the column visible */ public void setColumnVisible(final int pModelIndex, final boolean pVisible) { mColumnVisibilitySupport.setColumnVisible(pModelIndex, pVisible); } /** * Get the visibility of all table columns at once. * The visibility is modeled as a boolean[]. There is an * entry in this array for each table model column. true * means the respective column is visible. false makes the * column hidden. * @return visiblity information for each column in the table model */ public boolean[] getColumnsVisible() { return mColumnVisibilitySupport.getColumnsVisible(); } /** * Set the visibility of all table columns at once. * The visibility is modeled as a boolean[]. There must be an * entry in this array for each table model column. true * means the respective column is visible. false makes the * column hidden. * @param pVisible visiblity information for each column in the table model * @throws IllegalArgumentException if visible is null or has a different number of entries than the table model */ public void setColumnsVisible(final boolean[] pVisible) { mColumnVisibilitySupport.setColumnsVisible(pVisible); } /** * Make all table columns visible. */ public void setAllColumnsVisible() { mColumnVisibilitySupport.setAllColumnsVisible(); } /** * Test if all columns are visible. * @return true if there are currently no invisible columns */ public boolean isAllColumnsVisible() { return mColumnVisibilitySupport.isAllColumnsVisible(); } /** * Accessor to invisible columns for {@link TableProperties}. * @param pModelIndex the model index. * @return the matching table column */ /*package visible*/ TableColumn getInvisibleColumn(final int pModelIndex) { return mColumnVisibilitySupport.getInvisibleColumn(pModelIndex); } /* --------------------------------------------------------------- Overridden methods of JTable --------------------------------------------------------------- */ /** * Adds following behaviour and then calls implementation of super. *
    *
  • Sets TableHeaderRenderer as TableHeaderRenderer for added Column.
  • *
* * @param pColumn The TableColumn to be added */ public void addColumn(final TableColumn pColumn) { if ( !(pColumn.getHeaderRenderer() instanceof TableHeaderRenderer) ) { pColumn.setHeaderRenderer(getTableHeaderRenderer()); } super.addColumn(pColumn); } /** * Callback from TableColumnModel. * Gets called when a column was added, esp. when an invisible column becomes visible again. * * Adds following behaviour and then calls implementation of super. *
    *
  • Redraw all Tableheaders.
  • *
* * @param pEvent The TableColumnModelEvent */ public void columnAdded(final TableColumnModelEvent pEvent) { redrawTableHeaders(); super.columnAdded(pEvent); } /** * Adds following behaviour and then calls implementation of super. *
    *
  • Wraps given table model with {@link #wrapModel(TableModel)}, if given table * model is not already an instance of {@link TableSorter}.
  • *
* * @param pModel new TableModel to set for this table */ public void setModel(final TableModel pModel) { TableSorter lModel; if (pModel instanceof TableSorter) { lModel = (TableSorter)pModel; } else { lModel = wrapModel(pModel); } mSorter = lModel; super.setModel(lModel); } /** * Puts a wrapper around the given table model. * It is garanteed that the table model needs to be wraped when this method is called * (i.e. the table model is not already an instance of {@link TableSorter}). *

* This implementation wraps the table model in a {@link TableSorter}. * In derived classes this method can be overridden to provide different wrappings. * @param pTableModel table model to be wrapped * @return wrapped table model */ protected TableSorter wrapModel(final TableModel pTableModel) { return new TableSorter(pTableModel); } /** * Adds following behaviour and then calls implementation of super. *

    *
  • redraw sort-order buttons in TableHeaderRenderer, if ALL_COLUMNS are UPDATEed
  • *
* * @param pEvent the TableModelEvent */ public void tableChanged(final TableModelEvent pEvent) { // optimize: react only if ALL COLUMNS are UPDATEed if (pEvent.getType() == TableModelEvent.UPDATE && pEvent.getColumn() == TableModelEvent.ALL_COLUMNS) { redrawTableHeaders(); } // finally propagate to super super.tableChanged(pEvent); } /* --------------------------------------------------------------- Implementation of MouseListener interface --------------------------------------------------------------- */ /** * Callback for MouseEvents. * * The TableHeader calls this Method, when user clicks Mouse on TableHeader. * We do not react on mouse clicks that might trigger a context menu popup. * We also do not react on click in the column resizing area between the * column headers. * * @param pEvent the MouseEvent */ public void mouseClicked(final MouseEvent pEvent) { // ignore mouse clicks that are popup triggers if ((pEvent.getModifiers() & MouseEvent.BUTTON3_MASK) != 0) // BUTTON3 is hardcoded as popup trigger in Basic-, Metal-, Motif- und Windows-L&F { return; } // ignore mouse clicks when in column resizing mode if (getTableHeader().getCursor().getType() != Cursor.DEFAULT_CURSOR) // there are so many conditions for the resizing mode that we best check for the visual feedback to the user { return; } // try to stop cell-editing TableCellEditor ce = getCellEditor(); if (ce == null || ce.stopCellEditing()) { // get column user clicked on as viewindex and modelindex int viewIndex = columnModel.getColumnIndexAtX(pEvent.getX()); int column = convertColumnIndexToModel(viewIndex); if (pEvent.getClickCount() == 1 && column != -1) { // ok, user hits a column // check, if user pressed any modifier key boolean reset = ((pEvent.getModifiers() & InputEvent.CTRL_MASK) == 0); boolean ascending = ((pEvent.getModifiers() & InputEvent.SHIFT_MASK) == 0); // i got the inspiration for following code from DefaultTableColumnModel.getColumnIndexAtX() // other stuff i tried didn't work, because getBounds() for TableHeaderRenderer didn't contain // values i expected. // i really don't like this, but... // // calculate the width of columns from left side up to column containing user click Point aPoint = new Point(pEvent.getX(), 1); Rectangle columnRect = new Rectangle(0, 0, 0, 3); Enumeration enumeration = columnModel.getColumns(); while (enumeration.hasMoreElements()) { TableColumn aColumn = enumeration.nextElement(); columnRect.width = aColumn.getWidth(); // since JDK 1.4, it is wrong to add columnModel.getColumnMargin() if (columnRect.contains(aPoint)) break; columnRect.x += columnRect.width; } // Check, if user pressed a button in TableHeader. // This will override the SHIFT-modifier key. TableHeaderRenderer thr = (TableHeaderRenderer)columnModel.getColumn(viewIndex).getHeaderRenderer(); switch (thr.hitButton(pEvent.getX() - columnRect.x, pEvent.getY())) { case TableHeaderRenderer.ASCENDING: ascending = true; break; case TableHeaderRenderer.DESCENDING: ascending = false; break; } // for debugging purposes //System.out.println("clicked on column " + viewColumn + " reset = " + reset + " ascending = " + ascending); if (reset) { // reset any stored column-sorting-info mSorter.resetColumns(); } // finally sort columns mSorter.sortByColumn(column, ascending); } } } /** * Empty implementation * * @param pEvent the MouseEvent */ public void mouseExited(final MouseEvent pEvent) { } /** * Empty implementation * * @param pEvent the MouseEvent */ public void mouseEntered(final MouseEvent pEvent) { } /** * Empty implementation * * @param pEvent the MouseEvent */ public void mouseReleased(final MouseEvent pEvent) { } /** * Empty implementation * * @param pEvent the MouseEvent */ public void mousePressed(final MouseEvent pEvent) { } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy