package org.openslx.dozmod.gui.control.table; import java.awt.Component; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import javax.swing.Icon; import javax.swing.JTable; import javax.swing.ListSelectionModel; import javax.swing.RowSorter; import javax.swing.SortOrder; import javax.swing.event.RowSorterEvent; import javax.swing.event.RowSorterListener; 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 extends JTable { private final ListModel model; private final TableRowSorter> sorter; private List sortKeys = new ArrayList<>(); private final TableColumnAdjuster adjuster; private final Comparator itemComparator; public ListTable(ListTableColumn... columns) { this(null, columns); } public ListTable(Comparator 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(this, columns); this.sorter = new TableRowSorter>(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.adjuster.setOnlyAdjustLarger(true); 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.setDefaultRenderer(Icon.class, new IconRenderer()); this.setDefaultEditor(Boolean.class, getDefaultEditor(Boolean.class)); this.setRowSelectionAllowed(true); this.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); sorter.addRowSorterListener(new RowSorterListener() { @Override public void sorterChanged(RowSorterEvent e) { adjuster.adjustColumns(); } }); } 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 List getSelectedItems() { int[] rows = getSelectedRows(); if (rows.length == 0) return null; List itemList = new ArrayList(); for (int i : rows) { itemList.add(getViewRow(i)); } return itemList; } public T getSelectedItem() { int rowIndex = getSelectedRow(); if (rowIndex == -1) return null; return getViewRow(rowIndex); } public List getData() { if (model.data == null) return null; return Collections.unmodifiableList(model.data); } 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 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> getRowSorter() { return sorter; } @Override public ListModel getModel() { return model; } /** * Callback when a cell is edited. */ public void setValueAt(Object newValue, T row, ListTableColumn column) { // Nothing by default } /** * 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 * @return */ public Component prepareRenderHook(Component component, T row, ListTableColumn listTableColumn, boolean isSelected) { // Nothing by default return component; } /** * 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); if (c != null && item != null) { c = prepareRenderHook(c, item, model.getColumn(convertColumnIndexToModel(column)), isRowSelected(row)); } return c; } /** * Model for our table */ public static class ListModel extends AbstractTableModel { private final ColumnState[] columns; private ArrayList data = null; private final ListTable table; public ListModel(ListTable 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 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 boolean isCellEditable(int row, int col) { return columns[col].column.isEditable; } @Override public void setValueAt(Object aValue, int row, int col) { if (isCellEditable(row, col)) { T item = table.getModelRow(row); if (item == null) return; table.setValueAt(aValue, item, columns[col].column); fireTableCellUpdated(row, col); } } @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); if (item == null) return null; return table.getValueAtInternal(item, columns[columnIndex].column); } public T getModelRow(int rowIndex) { return table.getModelRow(rowIndex); } 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) { if (value != null) { value = modelValueToDisplayFormat(value, model.getColumn(convertColumnIndexToModel(column))); } super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); setBorder(null); return this; } } private class IconRenderer extends DefaultTableCellRenderer { @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { if (value != null) { value = modelValueToDisplayFormat(value, model.getColumn(convertColumnIndexToModel(column))); } setIcon((Icon)value); return this; } } public static class ListTableColumn { public final boolean isEditable; 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, colClass, sortComparator, false); } public ListTableColumn(String colName, Class colClass, Comparator sortComparator, boolean isEditable) { this.isEditable = isEditable; this.colName = colName; this.colClass = colClass; this.sortComparator = sortComparator; } public ListTableColumn(String colName, Comparator sortComparator) { this(colName, String.class, sortComparator); } } }