/*
* FILE_NAME : TableFilter.java
*
* Copyright (c) 2006 EADS ASTRIUM
* All rights reserved
*
*/
package simtools.util;
import java.awt.Color;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.JOptionPane;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
/**
* Class TableFilter
* This tableModel permit to enable filtering on columns values.
*/
public class TableFilter extends AbstractTableModel {
/**(<b>TableModel</b>) tableModel: The table model managed by this filter.*/
private TableModel tableModel;
/**(<b>TableModelListener</b>) tableModelListener:The listener, registered on the table model managed that will handle data modifications.*/
private TableModelListener tableModelListener;
/**(<b>int[]</b>) modelToView: the arrayList that contains the filtered Rows.*/
private ArrayList modelToView;
/**(<b>JTableHeader</b>) tableHeader: The table header.*/
private JTableHeader tableHeader;
/**(<b>MouseListener</b>) mouseListener: The mouse listener, to activate filter on right click.*/
private MouseListener mouseListener;
/**(<b>String[]</b>) filter: The current filters array, for each column.*/
private String[] filter;
/**(<b>Pattern[]</b>) filterPattern: The current filter patterns, for each column.*/
private Pattern[] filterPattern;
/**(<b>Color</b>) defaultForegroundColor:The default foreground color of the table header.*/
private Color defaultForegroundColor;
/**(<b>Color</b>) filteredForegroundColor: the color to use for the filtered table header.*/
private final static Color FILTERED_FOREGROUND_COLOR = Color.BLUE;
private boolean activeFilter;
/**
* Contructor TableFilter
* <br><b>Summary:</b><br>
* The constructor of the class TableFilter.
* You must set a Table model using setTableModel.
* If not you will not have datas to filter.
*/
public TableFilter() {
this(null);
}
/**
* Contructor TableFilter
* <br><b>Summary:</b><br>
* The constructor of the class TableFilter.
* @param tableModel The tableModel to be managed.
*/
public TableFilter(TableModel tableModel) {
//create the modelToView data structure.
modelToView = new ArrayList();
//Create the listener that will handler data changes.
tableModelListener = new TableFilterHandler();
mouseListener = new MouseHandler();
activeFilter = false;
//set the tableModel
setTableModel(tableModel);
}
/**
* Method setTableModel
* <br><b>Summary:</b><br>
* This method sets the model that is managed by this tableFilter.
* @param tableModel The model to be managed by this tableFilter.
*/
public void setTableModel(TableModel tableModel) {
//remove this from previous table model listener, no to follow old data changes.
if (this.tableModel != null) {
this.tableModel.removeTableModelListener(tableModelListener);
//nullify filters
filter = null;
filterPattern = null;
}
//then set teh table model to the one given.
this.tableModel = tableModel;
if (this.tableModel != null) {
//Add the modelListener to the tableModel given.
this.tableModel.addTableModelListener(tableModelListener);
//create the filter.
int nbColumns = tableModel.getColumnCount();
filter = new String[nbColumns];
filterPattern = new Pattern[nbColumns];
}
fireTableStructureChanged();
}
/**
* Method updateFilteredElements
* <br><b>Summary:</b><br>
* This method update the filtered elements.
*/
protected void updateFilteredElements() {
modelToView.clear();
//Clear the modelToView
//We will parse all the rows of the model, and keep the one that match the filter.
int modelRowCount = tableModel.getRowCount();
for (int i = 0; i < modelRowCount; i++) {
Row currentRow = new Row(i);
if (matchFilter(currentRow)) {
modelToView.add(currentRow);
}
}
fireTableDataChanged();
}
/**
* Method matchFilter
* <br><b>Summary:</b><br>
* This method return true if the row match the current filter.
* @param currentRow The row to check.
* @return <b>(boolean)</b> True if the given row matches the current filter.
*/
private boolean matchFilter(Row currentRow) {
//the result of the method.
boolean result = true;
//Retrieve all the row values, till found one that match.
int index = 0;
int columnCount = tableModel.getColumnCount();
while (result == true && index < columnCount) {
//TODO: make filters
if (filterPattern[index] != null) {
String value = tableModel.getValueAt(currentRow.modelIndex, index).toString();
Matcher filterMatcher = filterPattern[index].matcher(value);
result = result && filterMatcher.matches();
}//if there is no filter, let result to true.
//move on to next column
index++;
}
//return the result
return result;
}
/* (non-Javadoc)
* @see javax.swing.table.TableModel#getColumnCount()
*/
public int getColumnCount() {
//The result of the method.
int result = 0;
if (tableModel != null) {
result = tableModel.getColumnCount();
}
//return the result
return result;
}
/* (non-Javadoc)
* @see javax.swing.table.TableModel#getRowCount()
*/
public int getRowCount() {
//The result of the method.
int result = 0;
if (tableModel != null) {
result = modelToView.size();
}
//return the result
return result;
}
/* (non-Javadoc)
* @see javax.swing.table.TableModel#getValueAt(int, int)
*/
public Object getValueAt(int rowIndex, int columnIndex) {
//the result of the method
Object result = null;
if (tableModel != null && modelToView != null && rowIndex < modelToView.size()) {
Row selectedRow = (Row) modelToView.get(rowIndex);
if(selectedRow != null){
result = tableModel.getValueAt(selectedRow.modelIndex, columnIndex);
}
}
if(result == null){
result = "";
}
return result;
}
/**
* Class Row
* The class that represent a JTable row
*/
private class Row {
/**(<b>int</b>) modelIndex: the index of the table in the model.*/
private int modelIndex;
/**
* Contructor Row
* <br><b>Summary:</b><br>
* The constructor of the class Row.
* @param index The index of the row in the model
*/
public Row(int index) {
modelIndex = index;
}
}
/* (non-Javadoc)
* @see javax.swing.table.AbstractTableModel#getColumnName(int)
*/
public String getColumnName(int column) {
return tableModel.getColumnName(column);
}
/* (non-Javadoc)
* @see javax.swing.table.AbstractTableModel#getColumnClass(int)
*/
public Class getColumnClass(int column) {
return tableModel.getColumnClass(column);
}
/* (non-Javadoc)
* @see javax.swing.table.AbstractTableModel#isCellEditable(int, int)
*/
public boolean isCellEditable(int row, int column) {
return tableModel.isCellEditable(((Row) modelToView.get(row)).modelIndex, column);
}
/**
* Method setTableHeader
* <br><b>Summary:</b><br>
* Permit to set the table header.
* This way, a right click on table header will enable the filter.
* @param tableHeader The table header to use.
*/
public void setTableHeader(JTableHeader tableHeader) {
if (this.tableHeader != null) {
//remove from previous tableHeader's mouse listener list.
this.tableHeader.removeMouseListener(mouseListener);
//remove the default table header color.
defaultForegroundColor = null;
}
this.tableHeader = tableHeader;
//Add in the new table header mous listener list.
if (this.tableHeader != null) {
this.tableHeader.addMouseListener(mouseListener);
//retrieve the default foreground color.
defaultForegroundColor = tableHeader.getForeground();
}
}
/**
* Method filterChanged
* <br><b>Summary:</b><br>
* This method is called when filter has changed.
* It update the table.
*/
private void filterChanged(int columnIndex) {
//If we control a table header, change color and column value.
if (tableHeader != null) {
//Need to get the column that is concerned by the filter changement.
//Because column order may have changed.
String columnName = tableModel.getColumnName(columnIndex);
String columnFilteredName = "*"+columnName+"*";
Enumeration columns = tableHeader.getColumnModel().getColumns();
TableColumn concernedColumn = null;
while (columns.hasMoreElements()) {
TableColumn column = (TableColumn) columns.nextElement();
String columnHeaderValue = (String) column.getHeaderValue();
if (columnHeaderValue.equals(columnName) || columnHeaderValue.equals(columnFilteredName)){
concernedColumn = column;
}
}
//change the header foreground with filtering status
if (filter[columnIndex] != null && filter[columnIndex].equals("")) {
//Set column header value to normal
concernedColumn.setHeaderValue(columnName);
//Check is there is at least a filter. If there is no more filter, return foreground to default.
if (!isTableFiltered()) {
tableHeader.setForeground(defaultForegroundColor);
activeFilter = false;
}
} else {
//Set column header value to filtered
concernedColumn.setHeaderValue(columnFilteredName);
//change the foreground color, if needed.
if (!tableHeader.getForeground().equals(FILTERED_FOREGROUND_COLOR)) {
tableHeader.setForeground(FILTERED_FOREGROUND_COLOR);
}
activeFilter = true;
}
//repaint the table header, to take count of table header name changes
tableHeader.repaint();
}
//Then update the filter
String newFilter = ".*" + filter[columnIndex] + ".*";
filterPattern[columnIndex] = Pattern.compile(newFilter);
Thread t = new Thread(new Runnable() {
public void run() {
updateFilteredElements();
}
}, "UpdatingFilteredTableThread");
t.start();
}
/**
* Method isTableFiltered
* <br><b>Summary:</b><br>
* return true if there is at least an active filter.
* @return <b>(boolean)</b> true if there is at least an active filter.
*/
private boolean isTableFiltered() {
//Parse all the filters, till found a filter.
boolean result = false;
int index = 0;
while (result != true && index < filter.length) {
if (filter[index] != null && !filter[index].equals("")) {
//If there is a non null filter, then the table is filtered, return true
result = true;
} else {
//else move on the next column.
index++;
}
}
//return the result
return result;
}
/**
* Class MouseHandler
* A mouse handler to ask fo the new filter.
* Wake up on a right click on table header.
*/
private class MouseHandler extends MouseAdapter {
public void mouseClicked(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON3) {
JTableHeader h = (JTableHeader) e.getSource();
TableColumnModel columnModel = h.getColumnModel();
int viewColumn = columnModel.getColumnIndexAtX(e.getX());
int column = columnModel.getColumn(viewColumn).getModelIndex();
if (column != -1) {
//We know we have clicked on a column.
//Show up a dialog to enter the new filter.
String newFilter = JOptionPane.showInputDialog("Enter filter for column "
+ tableModel.getColumnName(column), filter[column]);
if (newFilter != null) {
filter[column] = newFilter;
filterChanged(column);
}
}
}
}
}
/**
* Class TableFilterHandler
* This method permits to handle the table modifications that occurs.
* It specifically handle the rowInserted event by filtering only the newly inserted elements.
*/
private class TableFilterHandler implements TableModelListener{
/* (non-Javadoc)
* @see javax.swing.event.TableModelListener#tableChanged(javax.swing.event.TableModelEvent)
*/
public void tableChanged(TableModelEvent e) {
//If insert event, handle the inserted rows.
if (e.getType() == TableModelEvent.INSERT){
//get the range of rows to be processed
int startRow = e.getFirstRow();
int lastRow = e.getLastRow();
//keep in mind the first index in our filtered model.
int firstNewIndex = modelToView.size();
//Then process the inserted rows.
for(int i = startRow; i <= lastRow;i++){
Row newRow = new Row(i);
//If filter is not active, add the rows.
if(!activeFilter){
modelToView.add(newRow);
}else{
//Else, we have to check filter on the row.
if(matchFilter(newRow)){
modelToView.add(newRow);
}
}
}
//Then compute the last index.
int lastNewIndex = modelToView.size()-1;
//And inform the tableModelListener registered that new rows have been inserted.
fireTableRowsInserted(firstNewIndex, lastNewIndex);
}else{
//If it is a more serious update of the table, update all filtered elements.
updateFilteredElements();
}
}
}
}