package cooltable;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JViewport;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
/**
* A cool table with dynamic frozen columns.
*
* @author Kurt Riede
*/
public class CoolTable extends JScrollPane {
public static void main(String[] args) {
final CoolTable coolTable = new CoolTable(new DefaultTableModel(20, 10), 2);
JFrame frame = new JFrame("Cool Table Demo");
frame.getContentPane().setLayout(new GridLayout(2, 1));
JPanel buttonPanel = new JPanel();
buttonPanel.setLayout(new FlowLayout());
JButton button2 = new JButton("Freeze 2 columns");
JButton button4 = new JButton("Freeze 4 columns");
JButton button6 = new JButton("Freeze 6 columns");
button2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
coolTable.setFrozenColumns(2);
}
});
button4.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
coolTable.setFrozenColumns(4);
}
});
button6.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
coolTable.setFrozenColumns(6);
}
});
buttonPanel.add(button2);
buttonPanel.add(button4);
buttonPanel.add(button6);
frame.getContentPane().add(buttonPanel);
frame.getContentPane().add(coolTable);
frame.setSize(600, 300);
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
Dimension frameSize = frame.getSize();
if (frameSize.height > screenSize.height) {
frameSize.height = screenSize.height;
}
if (frameSize.width > screenSize.width) {
frameSize.width = screenSize.width;
}
frame.setLocation((screenSize.width - frameSize.width) / 2, (screenSize.height - frameSize.height) / 2);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
private final JTable lockedTable;
private final JTable scrollTable;
int frozenColumns = 0;
private final JScrollPaneAdjuster adjuster;
public CoolTable(TableModel model, int numFrozenColumns) {
super();
adjuster = new JScrollPaneAdjuster(this);
frozenColumns = numFrozenColumns;
// create the two tables
lockedTable = new JTable(model);
lockedTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
scrollTable = new JTable(model);
scrollTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
setViewportView(scrollTable);
// Put the locked-column table in the row header
JViewport viewport = new JViewport();
viewport.setBackground(Color.white);
viewport.setView(lockedTable);
setRowHeader(viewport);
// Put the header of the locked-column table in the top left corner
// of the scoll pane
JTableHeader lockedHeader = lockedTable.getTableHeader();
lockedHeader.setReorderingAllowed(false);
lockedHeader.setResizingAllowed(false);
setCorner(JScrollPane.UPPER_LEFT_CORNER, lockedHeader);
scrollTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
lockedTable.setSelectionModel(scrollTable.getSelectionModel());
lockedTable.getTableHeader().setReorderingAllowed(false);
lockedTable.getTableHeader().setResizingAllowed(false);
lockedTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
scrollTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
// Remove the fixed columns from the main table
TableColumnModel scrollColumnModel = scrollTable.getColumnModel();
for (int i = 0; i < frozenColumns; i++) {
scrollColumnModel.removeColumn(scrollColumnModel.getColumn(0));
}
// Remove the non-fixed columns from the fixed table
TableColumnModel lockedColumnModel = lockedTable.getColumnModel();
while (lockedTable.getColumnCount() > frozenColumns) {
lockedColumnModel.removeColumn(lockedColumnModel.getColumn(frozenColumns));
}
// Add the fixed table to the scroll pane
lockedTable.setPreferredScrollableViewportSize(lockedTable.getPreferredSize());
// set a new action for the tab key
// todo search actions by action name (not by KeyStroke)
final Action lockedTableNextColumnCellAction = getAction(lockedTable, KeyEvent.VK_TAB, 0);
final Action scrollTableNextColumnCellAction = getAction(scrollTable, KeyEvent.VK_TAB, 0);
final Action lockedTablePrevColumnCellAction = getAction(lockedTable, KeyEvent.VK_TAB, InputEvent.SHIFT_DOWN_MASK);
final Action scrollTablePrevColumnCellAction = getAction(scrollTable, KeyEvent.VK_TAB, InputEvent.SHIFT_DOWN_MASK);
setAction(lockedTable, "selectNextColumn", new LockedTableSelectNextColumnCellAction(lockedTableNextColumnCellAction));
setAction(scrollTable, "selectNextColumn", new ScrollTableSelectNextColumnCellAction(scrollTableNextColumnCellAction));
setAction(lockedTable, "selectPreviousColumn", new LockedTableSelectPreviousColumnCellAction(lockedTablePrevColumnCellAction));
setAction(scrollTable, "selectPreviousColumn", new ScrollTableSelectPreviousColumnCellAction(scrollTablePrevColumnCellAction));
setAction(lockedTable, "selectNextColumnCell", new LockedTableSelectNextColumnCellAction(lockedTableNextColumnCellAction));
setAction(scrollTable, "selectNextColumnCell", new ScrollTableSelectNextColumnCellAction(scrollTableNextColumnCellAction));
setAction(lockedTable, "selectPreviousColumnCell", new LockedTableSelectPreviousColumnCellAction(lockedTablePrevColumnCellAction));
setAction(scrollTable, "selectPreviousColumnCell", new ScrollTableSelectPreviousColumnCellAction(scrollTablePrevColumnCellAction));
setAction(scrollTable, "selectFirstColumn", new ScrollableSelectFirstColumnCellAction());
setAction(lockedTable, "selectLastColumn", new LockedTableSelectLastColumnCellAction());
}
private void setAction(JComponent component, String name, Action action) {
component.getActionMap().put(name, action);
}
private void setAction(JComponent component, String name, int keyCode, int modifiers, Action action) {
final int condition = JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT;
final KeyStroke keyStroke = KeyStroke.getKeyStroke(keyCode, modifiers);
component.getInputMap(condition).put(keyStroke, name);
component.getActionMap().put(name, action);
}
private Action getAction(JComponent component, int keyCode, int modifiers) {
final int condition = JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT;
final KeyStroke keyStroke = KeyStroke.getKeyStroke(keyCode, modifiers);
Object object = component.getInputMap(condition).get(keyStroke);
if (object == null) {
if (component.getParent() instanceof JComponent) {
return getAction((JComponent) component.getParent(), keyCode, modifiers);
} else {
return null;
}
} else {
return scrollTable.getActionMap().get(object);
}
}
protected int nextRow(JTable table) {
int row = table.getSelectedRow() + 1;
if (row == table.getRowCount()) {
row = 0;
}
return row;
}
private int previousRow(JTable table) {
int row = table.getSelectedRow() - 1;
if (row == -1) {
row = table.getRowCount() - 1;
}
return row;
}
public final int getFrozenColumns() {
return frozenColumns;
}
public final void setFrozenColumns(final int numFrozenColumns) {
rearrangeColumns(numFrozenColumns);
frozenColumns = numFrozenColumns;
}
private void rearrangeColumns(final int numFrozenColumns) {
TableColumnModel scrollColumnModel = scrollTable.getColumnModel();
TableColumnModel lockedColumnModel = lockedTable.getColumnModel();
if (frozenColumns < numFrozenColumns) {
// move columns from scrollable to fixed table
for (int i = frozenColumns; i < numFrozenColumns; i++) {
TableColumn column = scrollColumnModel.getColumn(0);
lockedColumnModel.addColumn(column);
scrollColumnModel.removeColumn(column);
}
lockedTable.setPreferredScrollableViewportSize(lockedTable.getPreferredSize());
} else if (frozenColumns > numFrozenColumns) {
// move columns from fixed to scrollable table
for (int i = numFrozenColumns; i < frozenColumns; i++) {
TableColumn column = lockedColumnModel.getColumn(lockedColumnModel.getColumnCount() - 1);
scrollColumnModel.addColumn(column);
scrollColumnModel.moveColumn(scrollColumnModel.getColumnCount() - 1, 0);
lockedColumnModel.removeColumn(column);
}
lockedTable.setPreferredScrollableViewportSize(lockedTable.getPreferredSize());
}
}
public class JScrollPaneAdjuster implements PropertyChangeListener, Serializable {
private JScrollPane pane;
private transient Adjuster x, y;
public JScrollPaneAdjuster(JScrollPane pane) {
this.pane = pane;
this.x = new Adjuster(pane.getViewport(), pane.getColumnHeader(), Adjuster.X);
this.y = new Adjuster(pane.getViewport(), pane.getRowHeader(), Adjuster.Y);
pane.addPropertyChangeListener(this);
}
public void dispose() {
x.dispose();
y.dispose();
pane.removePropertyChangeListener(this);
pane = null;
}
public void propertyChange(PropertyChangeEvent e) {
String name = e.getPropertyName();
if (name.equals("viewport")) {
x.setViewport((JViewport) e.getNewValue());
y.setViewport((JViewport) e.getNewValue());
} else if (name.equals("rowHeader")) {
y.setHeader((JViewport) e.getNewValue());
} else if (name.equals("columnHeader")) {
x.setHeader((JViewport) e.getNewValue());
}
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
x = new Adjuster(pane.getViewport(), pane.getColumnHeader(), Adjuster.X);
y = new Adjuster(pane.getViewport(), pane.getRowHeader(), Adjuster.Y);
}
private class Adjuster implements ChangeListener, Runnable {
public static final int X = 1, Y = 2;
private JViewport viewport, header;
private int type;
public Adjuster(JViewport viewport, JViewport header, int type) {
this.viewport = viewport;
this.header = header;
this.type = type;
if (header != null)
header.addChangeListener(this);
}
public void setViewport(JViewport newViewport) {
viewport = newViewport;
}
public void setHeader(JViewport newHeader) {
if (header != null)
header.removeChangeListener(this);
header = newHeader;
if (header != null)
header.addChangeListener(this);
}
public void stateChanged(ChangeEvent e) {
if (viewport == null || header == null)
return;
if (type == X) {
if (viewport.getViewPosition().x != header.getViewPosition().x)
SwingUtilities.invokeLater(this);
} else {
if (viewport.getViewPosition().y != header.getViewPosition().y)
SwingUtilities.invokeLater(this);
}
}
public void run() {
if (viewport == null || header == null)
return;
Point v = viewport.getViewPosition(), h = header.getViewPosition();
if (type == X) {
if (v.x != h.x)
viewport.setViewPosition(new Point(h.x, v.y));
} else {
if (v.y != h.y)
viewport.setViewPosition(new Point(v.x, h.y));
}
}
public void dispose() {
if (header != null)
header.removeChangeListener(this);
viewport = header = null;
}
}
}
private final class LockedTableSelectLastColumnCellAction extends AbstractAction {
private LockedTableSelectLastColumnCellAction() {
super();
}
public void actionPerformed(ActionEvent e) {
if (e.getSource() == lockedTable) {
lockedTable.transferFocus();
}
scrollTable.changeSelection(scrollTable.getSelectedRow(), scrollTable.getColumnCount() - 1, false, false);
}
}
private final class ScrollableSelectFirstColumnCellAction extends AbstractAction {
private ScrollableSelectFirstColumnCellAction() {
super();
}
public void actionPerformed(ActionEvent e) {
if (e.getSource() == scrollTable) {
scrollTable.transferFocusBackward();
}
lockedTable.changeSelection(lockedTable.getSelectedRow(), 0, false, false);
}
}
private final class LockedTableSelectNextColumnCellAction extends AbstractAction {
private final Action lockedTableNextColumnCellAction;
private LockedTableSelectNextColumnCellAction(Action lockedTableNextColumnCellAction) {
super();
this.lockedTableNextColumnCellAction = lockedTableNextColumnCellAction;
}
public void actionPerformed(ActionEvent e) {
if (lockedTable.getSelectedColumn() == lockedTable.getColumnCount() - 1) {
lockedTable.transferFocus();
scrollTable.changeSelection(lockedTable.getSelectedRow(), 0, false, false);
} else {
lockedTableNextColumnCellAction.actionPerformed(e);
}
}
}
private final class ScrollTableSelectNextColumnCellAction extends AbstractAction {
private final Action scrollTableNextColumnCellAction;
private ScrollTableSelectNextColumnCellAction(Action scrollTableNextColumnCellAction) {
super();
this.scrollTableNextColumnCellAction = scrollTableNextColumnCellAction;
}
public void actionPerformed(ActionEvent e) {
if (scrollTable.getSelectedColumn() == scrollTable.getColumnCount() - 1) {
scrollTable.transferFocusBackward();
lockedTable.changeSelection(nextRow(scrollTable), 0, false, false);
return;
} else {
scrollTableNextColumnCellAction.actionPerformed(e);
}
}
}
private final class ScrollTableSelectPreviousColumnCellAction extends AbstractAction {
private final Action scrollTablePrevColumnCellAction;
private ScrollTableSelectPreviousColumnCellAction(Action scrollTablePrevColumnCellAction) {
super();
this.scrollTablePrevColumnCellAction = scrollTablePrevColumnCellAction;
}
public void actionPerformed(ActionEvent e) {
if (scrollTable.getSelectedColumn() == 0) {
scrollTable.transferFocusBackward();
lockedTable.changeSelection(scrollTable.getSelectedRow(), lockedTable.getColumnCount() - 1, false, false);
return;
} else {
scrollTablePrevColumnCellAction.actionPerformed(e);
}
}
}
private final class LockedTableSelectPreviousColumnCellAction extends AbstractAction {
private final Action lockedTablePrevColumnCellAction;
private LockedTableSelectPreviousColumnCellAction(Action lockedTablePrevColumnCellAction) {
super();
this.lockedTablePrevColumnCellAction = lockedTablePrevColumnCellAction;
}
public void actionPerformed(ActionEvent e) {
if (lockedTable.getSelectedColumn() == 0) {
lockedTable.transferFocus();
scrollTable.changeSelection(previousRow(scrollTable), scrollTable.getColumnCount() - 1, false, false);
return;
} else {
lockedTablePrevColumnCellAction.actionPerformed(e);
}
}
}
}