After much research, I came up with this (untested, hackish code):
public class CellSelectionTable extends JTable {
CellSelectionModel cellSelectionModel;
public CellSelectionTable(TableModel tableModel) {
super(tableModel);
cellSelectionModel = new CellSelectionModel(getRowCount(), getColumnCount());
}
@Override
public void changeSelection(int rowIndex, int columnIndex, boolean toggle, boolean extend) {
cellSelectionModel.changeSelection(rowIndex, columnIndex, toggle, extend);
super.changeSelection(rowIndex, columnIndex, toggle, extend);
}
private void addCellSelection(int row, int col) {
cellSelectionModel.addSelection(row, col);
}
@Override
public void selectAll() {
cellSelectionModel.selectAll();
super.selectAll();
}
@Override
public void clearSelection() {
if (cellSelectionModel != null) {
cellSelectionModel.clearSelection();
}
super.clearSelection();
}
@Override
public boolean isCellSelected(int row, int column) {
return cellSelectionModel.isCellSelected(row, column);
}
}
/**
* 2D selection model (e.g. 2D table)
*
* @author RadianceOng
*/
public class CellSelectionModel {
private boolean[][] cellSelected;
int rows;
int cols;
Point anchor;
List<ChangeListener> changeListeners;
public CellSelectionModel(int row, int col) {
cellSelected = new boolean[row][col];
anchor = new Point(0, 0);
rows = row;
cols = col;
changeListeners = new ArrayList<> ();
clearSelection();
}
public void addSelection(int row, int col) {
cellSelected[row][col] = true;
setAnchor(row, col);
}
public void removeSelection(int row, int col) {
cellSelected[row][col] = false;
setAnchor(row, col);
}
public void toggleSelection(int row, int col) {
cellSelected[row][col] = !cellSelected[row][col];
setAnchor(row, col);
}
public void changeSelection(int row, int col, boolean toggle, boolean extend) {
if (extend) {
extendSelection(row, col, toggle);
} else {
if (toggle) {
toggleSelection(row, col);
} else {
clearSelection();
addSelection(row, col);
}
}
stateChange();
}
public void extendSelection(int row, int col, boolean toggle) {
boolean anchorState = true;
if (toggle) {
anchorState = isCellSelected(row, col);
}
Point firstPoint = new Point(Math.min(anchor.x, col), Math.min(anchor.y, row));
Point lastPoint = new Point(Math.max(anchor.x, col), Math.max(anchor.y, row));
for (int i = firstPoint.y; i <= lastPoint.y; i++) {
for (int j = firstPoint.x; j <= lastPoint.x; j++) {
cellSelected[i][j] = anchorState;
}
}
//setAnchor(lastPoint.y, lastPoint.x);
}
public void selectAll() {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
cellSelected[i][j] = true;
}
}
setAnchor(rows - 1, cols - 1);
}
public void clearSelection() {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
cellSelected[i][j] = false;
}
}
setAnchor(rows - 1, cols - 1);
}
public boolean isCellSelected(int row, int col) {
return cellSelected[row][col];
}
/**
* Visits the entire selection, calling the visitor on every cell
* @param v
*/
public void visitCellSelection(CellSelectionVisitor v) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
v.isSelected(cellSelected[i][j], i, j);
}
}
}
public void addChangeListener(ChangeListener l) {
changeListeners.add(l);
}
public void removeChangeListener(ChangeListener l) {
changeListeners.remove(l);
}
private void setAnchor(int row, int col) {
anchor.x = col;
anchor.y = row;
}
private void stateChange() {
ChangeEvent e = new ChangeEvent(this);
for(ChangeListener l : changeListeners) {
l.stateChanged(e);
}
}
public interface CellSelectionVisitor {
/**
* Called with state of cell
* @param selected
* @param row
* @param col
*/
void isSelected(boolean selected, int row, int col);
}
}