// Copyright 2004, FreeHEP.
package org.freehep.swing.table;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.BitSet;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JPopupMenu;
import javax.swing.event.EventListenerList;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;
/**
* Allows the user to select which columns are visible in a table.
* <p>
* Example of usage:
* <pre>
* TableModel model = ...
* TableColumnSelector selector = new TableColumnSelector(model);
* JTable table = new JTable(selector.getFilteredTableModel());
* table.addMouseListener(new PopupListener(selector.createPopupMenu()));
* </pre>
* @author Tony Johnson
*/
public class TableColumnSelector
{
private BitSet hidden = new BitSet();
private TableModel source;
private TableModel result;
private EventListenerList listeners = new EventListenerList();
private TableModelListener internalListener = new InternalTableModelListener();
/**
* Create a TableColumnSelector.
* @param model The source table model.
*/
public TableColumnSelector(TableModel model)
{
this.source = model;
this.result = new InternalTableModel();
}
private int mapFromFilter(int columnIndex)
{
int result = columnIndex;
if (!hidden.isEmpty())
{
for (int i=0; columnIndex >= 0 ;i++ )
{
if (!hidden.get(i)) columnIndex--;
else result++;
}
}
return result;
}
private int mapToFilter(int columnIndex)
{
int result = columnIndex;
if (!hidden.isEmpty())
{
for (int i=0; i<columnIndex; i++)
{
if (hidden.get(i)) result--;
}
}
return result;
}
/**
* Can be used to fill a JMenu or JPopupMenu with appropriate JCheckBoxMenuItems.
* @param menu The JMenu or JPopupMenu to populate.
*/
public void populateMenu(JComponent menu)
{
ActionListener l = new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
int i = Integer.parseInt(e.getActionCommand());
boolean hide = !((JCheckBoxMenuItem) e.getSource()).isSelected();
setHideColumn(i,hide);
}
};
for (int i=0; i<source.getColumnCount(); i++)
{
JCheckBoxMenuItem item = new JCheckBoxMenuItem(source.getColumnName(i));
item.setActionCommand(String.valueOf(i));
item.setSelected(!hidden.get(i));
item.addActionListener(l);
menu.add(item);
}
}
/**
* Creates a JPopupMenu filled with appropriate JCheckBoxMenuItems. Can be used as the popup menu for the JTable.
* @return The created menu.
*/
public JPopupMenu createPopupMenu()
{
JPopupMenu result = new JPopupMenu()
{
protected void firePopupMenuWillBecomeVisible()
{
populateMenu(this);
}
protected void firePopupMenuWillBecomeInvisible()
{
removeAll();
}
};
return result;
}
/**
* Show or Hide the specied column.
* @param columnIndex The columnIndex in the source TableModel
* @param hide if <CODE>true</CODE> hides this column
*/
public void setHideColumn(int columnIndex, boolean hide)
{
if (hide != hidden.get(columnIndex))
{
hidden.set(columnIndex,hide);
if (listeners.getListenerCount() != 0)
{
int type = hide ? TableModelEvent.DELETE : TableModelEvent.INSERT;
int column = mapToFilter(columnIndex);
TableModelEvent event = new TableModelEvent(result,TableModelEvent.HEADER_ROW,TableModelEvent.HEADER_ROW,column,type);
fireTableChanged(event);
}
}
}
/**
* Notifies all listeners of a change to the filtered TableModel.
* @param event The event to be sent to the listeners.
*/
protected void fireTableChanged(TableModelEvent event)
{
TableModelListener[] l = (TableModelListener[]) listeners.getListeners(TableModelListener.class);
for (int i=0; i<l.length; i++)
{
l[i].tableChanged(event);
}
}
/**
* Test if a column is hidden.
* @param columnIndex The columnIndex in the source TableModel.
* @return <CODE>true</CODE> if this column is hidden
*/
public boolean isHideColumn(int columnIndex)
{
return hidden.get(columnIndex);
}
/**
* Get the resulting table model. This is the table model that should actually be installed.
* @return The filtered table mode.
*/
public TableModel getFilteredTableModel()
{
return result;
}
private class InternalTableModel implements TableModel
{
public void addTableModelListener(TableModelListener l)
{
if (listeners.getListenerCount() == 0) source.addTableModelListener(internalListener);
listeners.add(TableModelListener.class, l);
}
public Class getColumnClass(int columnIndex)
{
return source.getColumnClass(mapFromFilter(columnIndex));
}
public int getColumnCount()
{
return source.getColumnCount() - hidden.cardinality();
}
public String getColumnName(int columnIndex)
{
return source.getColumnName(mapFromFilter(columnIndex));
}
public int getRowCount()
{
return source.getRowCount();
}
public Object getValueAt(int rowIndex, int columnIndex)
{
return source.getValueAt(rowIndex,mapFromFilter(columnIndex));
}
public boolean isCellEditable(int rowIndex, int columnIndex)
{
return source.isCellEditable(rowIndex,mapFromFilter(columnIndex));
}
public void removeTableModelListener(TableModelListener l)
{
listeners.remove(TableModelListener.class, l);
if (listeners.getListenerCount() == 0) source.removeTableModelListener(internalListener);
}
public void setValueAt(Object aValue, int rowIndex, int columnIndex)
{
source.setValueAt(aValue, rowIndex, mapFromFilter(columnIndex));
}
}
private class InternalTableModelListener implements TableModelListener
{
public void tableChanged(TableModelEvent e)
{
int column = e.getColumn();
if (column == TableModelEvent.ALL_COLUMNS || !hidden.get(column))
{
int first = e.getFirstRow();
int last = e.getLastRow();
int type = e.getType();
if (column != TableModelEvent.ALL_COLUMNS) column = mapToFilter(column);
TableModelEvent ee = new TableModelEvent(result,first,last,column,type);
fireTableChanged(ee);
}
}
}
}