package org.openslx.dozmod.gui.control.table;
import java.awt.Component;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.RowSorter;
import javax.swing.SortOrder;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableRowSorter;
import org.openslx.dozmod.gui.helper.TableColumnAdjuster;
@SuppressWarnings("serial")
public abstract class ListTable<T> extends JTable {
private final ListModel<T> model;
private final TableRowSorter<ListModel<T>> sorter;
private List<RowSorter.SortKey> sortKeys = new ArrayList<>();
private final TableColumnAdjuster adjuster;
private final Comparator<T> itemComparator;
public ListTable(ListTableColumn... columns) {
this(null, columns);
}
public ListTable(Comparator<T> itemComparator, ListTableColumn... columns) {
super();
for (int i = 0; i < columns.length; i++) {
TableColumn tc = new TableColumn(i);
tc.setHeaderValue(columns[i].colName);
addColumn(tc);
}
this.model = new ListModel<T>(this, columns);
this.sorter = new TableRowSorter<ListModel<T>>(model);
for (int i = 0; i < columns.length; ++i) {
if (columns[i].sortComparator != null) {
sorter.setComparator(i, columns[i].sortComparator);
}
}
this.adjuster = new TableColumnAdjuster(this, 5);
this.itemComparator = itemComparator;
this.setModel(model);
this.setRowSorter(sorter);
this.setShowGrid(false);
this.setCellSelectionEnabled(false);
this.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
this.getTableHeader().setReorderingAllowed(false);
ListTableRenderer listTableRenderer = new ListTableRenderer();
this.setDefaultRenderer(Object.class, listTableRenderer);
this.setDefaultRenderer(Integer.class, listTableRenderer);
this.setDefaultRenderer(Long.class, listTableRenderer);
this.setDefaultRenderer(Boolean.class, getDefaultRenderer(Boolean.class));
this.setDefaultEditor(Boolean.class, getDefaultEditor(Boolean.class));
this.setRowSelectionAllowed(true);
this.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
}
protected abstract Object getValueAtInternal(T item, ListTableColumn column);
public T getModelRow(int rowIndex) {
if (rowIndex < 0 || rowIndex >= model.getRowCount())
throw new IndexOutOfBoundsException();
return model.data.get(rowIndex);
}
public T getViewRow(int rowIndex) {
return getModelRow(convertRowIndexToModel(rowIndex));
}
public T getSelectedItem() {
int rowIndex = getSelectedRow();
if (rowIndex == -1)
return null;
return getViewRow(rowIndex);
}
public boolean setSelectedItem(T item) {
int selectionIndex = -1;
for (int i = 0; i < model.data.size(); ++i) {
T rowItem = model.data.get(i);
if (itemComparator == null) {
if (rowItem.equals(item)) {
selectionIndex = i;
break;
}
} else {
if (itemComparator.compare(item, rowItem) == 0) {
selectionIndex = i;
break;
}
}
}
if (selectionIndex != -1) {
selectionIndex = convertRowIndexToView(selectionIndex);
}
if (selectionIndex == -1) {
getSelectionModel().clearSelection();
return false;
}
setRowSelectionInterval(selectionIndex, selectionIndex);
return true;
}
public void setData(List<T> data, boolean sort) {
T oldSelection = getSelectedItem();
model.setData(data);
adjuster.adjustColumns();
// handle sorting
if (!sort)
return;
if (sortKeys.isEmpty()) {
// sort by the first column by default
sortKeys.add(new RowSorter.SortKey(0, SortOrder.ASCENDING));
sorter.setSortKeys(sortKeys);
}
// always sort
sorter.sort();
if (oldSelection != null) {
setSelectedItem(oldSelection);
}
}
/**
* Show or hide the given column of the table
*
* @param column
* @param visible
*/
public void setColumnVisible(ListTableColumn column, boolean visible) {
model.setVisible(column, visible);
}
@Override
public TableRowSorter<ListModel<T>> getRowSorter() {
return sorter;
}
@Override
public ListModel<T> getModel() {
return model;
}
/**
* Called when rendering a column is being prepared. This is a good time to
* change the color or font for the given cell.
*
* @param component The component representing the cell being rendered
* @param row item of the row being rendered
* @param listTableColumn column (model-based) of the cell being rendered
* @param isSelected whether the row is currently selected
*/
public void prepareRenderHook(Component component, T row, ListTableColumn listTableColumn,
boolean isSelected) {
// Nothing by default
}
/**
* Called when the value of the given column needs to be transformed into a
* displayable item. By default this is the identity function, returning the
* value as-is.
*
* @param value Value to render
* @param column Column index (model-based) being rendered
* @return Rendered version of value. This should match the column class
*/
public Object modelValueToDisplayFormat(Object value, ListTableColumn column) {
return value;
}
@Override
public final Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
Component c = super.prepareRenderer(renderer, row, column);
T item = getViewRow(row);
prepareRenderHook(c, item, model.getColumn(convertColumnIndexToModel(column)), isRowSelected(row));
return c;
}
/**
* Model for our table
*/
public static class ListModel<T> extends AbstractTableModel {
private final ColumnState[] columns;
private ArrayList<T> data = null;
private final ListTable<T> table;
public ListModel(ListTable<T> table, ListTableColumn... columns) {
this.table = table;
this.columns = new ColumnState[columns.length];
for (int i = 0; i < columns.length; ++i) {
this.columns[i] = new ColumnState(columns[i]);
}
}
public void setData(List<T> list) {
this.data = new ArrayList<>(list);
fireTableDataChanged();
}
public ListTableColumn getColumn(int modelColumnIndex) {
return columns[modelColumnIndex].column;
}
public void setVisible(ListTableColumn column, boolean visible) {
TableColumnModel cm = table.getColumnModel();
for (int i = 0; i < columns.length; ++i) {
ColumnState c = columns[i];
if (c.column != column)
continue;
if ((c.hiddenColumn == null && visible) || (c.hiddenColumn != null && !visible))
return;
if (visible) {
cm.addColumn(c.hiddenColumn);
cm.moveColumn(cm.getColumnCount() - 1, c.oldIndex);
c.hiddenColumn = null;
} else {
c.oldIndex = table.convertColumnIndexToView(i);
c.hiddenColumn = cm.getColumn(c.oldIndex);
cm.removeColumn(c.hiddenColumn);
}
break;
}
}
@Override
public int getRowCount() {
return data == null ? 0 : data.size();
}
@Override
public int getColumnCount() {
return columns.length;
}
@Override
public String getColumnName(int col) {
return columns[col].column.colName;
}
@Override
public Class<?> getColumnClass(int col) {
return columns[col].column.colClass;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
T item = table.getModelRow(rowIndex);
return table.getValueAtInternal(item, columns[columnIndex].column);
}
private static class ColumnState {
int oldIndex;
final ListTableColumn column;
TableColumn hiddenColumn = null;
ColumnState(ListTableColumn column) {
this.column = column;
}
}
}
/**
* This renderer simply removes the default dotted border when
* a cell is selected
*
*/
private class ListTableRenderer extends DefaultTableCellRenderer {
public ListTableRenderer() {
putClientProperty("html.disable", Boolean.TRUE);
putClientProperty("html", null);
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column) {
super.getTableCellRendererComponent(table,
modelValueToDisplayFormat(value, model.getColumn(convertColumnIndexToModel(column))),
isSelected, hasFocus, row, column);
setBorder(null);
return this;
}
}
public static class ListTableColumn {
public final Class<?> colClass;
public final String colName;
public final Comparator<?> sortComparator;
public ListTableColumn(String colName) {
this(colName, String.class, null);
}
public ListTableColumn(String colName, Class<?> colClass) {
this(colName, colClass, null);
}
public ListTableColumn(String colName, Class<?> colClass, Comparator<?> sortComparator) {
this.colName = colName;
this.colClass = colClass;
this.sortComparator = sortComparator;
}
public ListTableColumn(String colName, Comparator<?> sortComparator) {
this(colName, String.class, sortComparator);
}
}
}