![JAR search and dependency download from the Maven repository](/logo.png)
net.sf.cuf.ui.table.SortingTable Maven / Gradle / Ivy
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)
{
}
}