package com.bc.ceres.swing; import com.bc.ceres.core.Assert; import javax.swing.AbstractButton; import javax.swing.BorderFactory; import javax.swing.JCheckBox; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.border.Border; import java.awt.Component; import java.awt.Graphics; import java.awt.Insets; import java.awt.LayoutManager; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; /** * A panel that contains other components arranged in form of a table. * It's layout manager is a {@link com.bc.ceres.swing.TableLayout}. * * @author Norman * @since Ceres 0.14 */ public class Grid extends JPanel implements GridSelectionModel.Listener { //private final List<RowSelector> rowSelectors; private final List<List<JComponent>> componentRows; private final JPanel filler; private GridSelectionModel selectionModel; private boolean showSelectionColumn; public Grid(int columnCount, boolean showSelectionColumn) { this(new TableLayout(columnCount), showSelectionColumn); } public Grid(TableLayout tableLayout, boolean showSelectionColumn) { super(tableLayout); this.showSelectionColumn = showSelectionColumn; //this.rowSelectors = new ArrayList<>(); this.componentRows = new ArrayList<>(); this.componentRows.add(new ArrayList<>(Arrays.asList(new JComponent[tableLayout.getColumnCount()]))); this.selectionModel = new DefaultGridSelectionModel(); this.selectionModel.addListener(this); filler = new JPanel(); addFiller(); } @Override public TableLayout getLayout() { return (TableLayout) super.getLayout(); } @Override public void setLayout(LayoutManager mgr) { TableLayout oldLayout = getLayout(); if (oldLayout == mgr) { return; } if (!(mgr instanceof TableLayout)) { throw new IllegalArgumentException(); } TableLayout tableLayout = (TableLayout) mgr; if (oldLayout != null) { if (oldLayout.getColumnCount() != tableLayout.getColumnCount() && getRowCount() > 0) { throw new IllegalArgumentException(); } } super.setLayout(tableLayout); } public GridSelectionModel getSelectionModel() { return selectionModel; } public void setSelectionModel(GridSelectionModel selectionModel) { GridSelectionModel oldSelectionModel = this.selectionModel; if (oldSelectionModel != selectionModel) { if (oldSelectionModel != null) { oldSelectionModel.removeListener(this); } this.selectionModel = selectionModel; if (this.selectionModel != null) { this.selectionModel.addListener(this); } firePropertyChange("selectionModel", oldSelectionModel, this.selectionModel); } } public boolean getShowSelectionColumn() { return showSelectionColumn; } public void setShowSelectionColumn(boolean showSelectionColumn) { boolean oldShowSelectionColumn = this.showSelectionColumn; if (oldShowSelectionColumn != showSelectionColumn) { this.showSelectionColumn = showSelectionColumn; for (List<JComponent> componentRow : componentRows) { JComponent component = componentRow.get(0); if (component != null) { component.setVisible(this.showSelectionColumn); } } fireComponentsChanged(); firePropertyChange("showSelectionColumn", oldShowSelectionColumn, showSelectionColumn); } } public int getColumnCount() { return getLayout().getColumnCount(); } public int getDataColumnCount() { return getColumnCount() - 1; } public int getRowCount() { return componentRows.size(); } public int getDataRowCount() { return getRowCount() - 1; } public JComponent getComponent(int rowIndex, int colIndex) { return componentRows.get(rowIndex).get(colIndex); } public JComponent setComponent(int rowIndex, int colIndex, JComponent component) { List<JComponent> componentRow = componentRows.get(rowIndex); JComponent oldComponent = componentRow.get(colIndex); if (oldComponent != null) { remove(oldComponent); } if (component != null) { add(component, TableLayout.cell(rowIndex, colIndex)); } componentRow.set(colIndex, component); fireComponentsChanged(); return oldComponent; } public int findRowIndex(JComponent component) { return findRowIndex(component, 0); } public int findDataRowIndex(JComponent component) { int rowIndex = findRowIndex(component, 1); return rowIndex >= 1 ? rowIndex - 1 : -1; } public void setHeaderRow(JComponent... components) { checkColumnCount(components); List<JComponent> headerRow = new ArrayList<>(components.length + 1); AbstractButton headerRowSelector = createHeaderRowSelector(); if (headerRowSelector != null) { headerRowSelector.setVisible(showSelectionColumn); headerRowSelector.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { onHeaderRowSelectorAction(); } }); } headerRow.add(headerRowSelector); Collections.addAll(headerRow, components); addComponentRowIntern(headerRow, 0); componentRows.set(0, headerRow); fireComponentsChanged(); adjustHeaderRowSelector(); } public JComponent[] getDataRow(int dataRowIndex) { Assert.argument(dataRowIndex >= 0 && dataRowIndex < getDataRowCount(), "dataRowIndex"); int rowIndex = dataRowIndex + 1; List<JComponent> componentRow = componentRows.get(rowIndex); JComponent[] dataRow = new JComponent[getDataColumnCount()]; for (int i = 0; i < dataRow.length; i++) { dataRow[i] = componentRow.get(i + 1); } return dataRow; } public void addDataRow(JComponent... components) { checkColumnCount(components); removeFiller(); List<JComponent> dataRow = new ArrayList<>(components.length + 1); AbstractButton dataRowSelector = createDataRowSelector(); if (dataRowSelector != null) { dataRowSelector.setVisible(showSelectionColumn); dataRowSelector.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { onDataRowSelectorAction(); } }); } dataRow.add(dataRowSelector); Collections.addAll(dataRow, components); addComponentRowIntern(dataRow, componentRows.size()); componentRows.add(dataRow); addFiller(); fireComponentsChanged(); adjustHeaderRowSelector(); } public void removeDataRow(int dataRowIndex) { Assert.argument(dataRowIndex >= 0, "dataRowIndex"); int rowIndex = dataRowIndex + 1; removeFiller(); boolean rowSelected = isDataRowSelected(rowIndex); List<JComponent> componentRow = componentRows.get(rowIndex); removeComponentRowIntern(componentRow); componentRows.remove(rowIndex); // Re-add remaining components, so that they are inserted at correct row positions for (int i = rowIndex; i < componentRows.size(); i++) { addComponentRowIntern(componentRows.get(i), i); } addFiller(); fireComponentsChanged(); if (rowSelected) { selectionModel.removeSelectedRowIndex(dataRowIndex); } } public void removeDataRows(int... dataRowIndexes) { if (dataRowIndexes.length == 0) { return; } removeFiller(); int offset = 0; int selectedCount = 0; for (int dataRowIndex : dataRowIndexes) { Assert.argument(dataRowIndex >= 0, "rowIndexes"); int rowIndex = dataRowIndex + 1 - offset; Assert.state(rowIndex >= 1, "rowIndex"); selectedCount += isDataRowSelected(dataRowIndex) ? 1 : 0; List<JComponent> componentRow = componentRows.get(rowIndex); removeComponentRowIntern(componentRow); componentRows.remove(rowIndex); offset++; } int rowIndex0 = dataRowIndexes[0] + 1; // Re-add remaining components, so that they are inserted at correct row positions for (int rowIndex = rowIndex0; rowIndex < componentRows.size(); rowIndex++) { addComponentRowIntern(componentRows.get(rowIndex), rowIndex); } addFiller(); fireComponentsChanged(); if (selectedCount > 0) { for (int dataRowIndex : dataRowIndexes) { selectionModel.removeSelectedRowIndex(dataRowIndex); } } } public void moveDataRowUp(int dataRowIndex) { Assert.argument(dataRowIndex >= 1, "dataRowIndex"); int rowIndex = dataRowIndex + 1; boolean selected = selectionModel.isRowSelected(dataRowIndex); List<JComponent> componentRow = componentRows.remove(rowIndex); componentRows.add(rowIndex - 1, componentRow); for (int i = rowIndex - 1; i < componentRows.size(); i++) { List<JComponent> componentRowToUpdate = componentRows.get(i); removeComponentRowIntern(componentRowToUpdate); addComponentRowIntern(componentRowToUpdate, i); } fireComponentsChanged(); if (selected) { selectionModel.removeSelectedRowIndex(dataRowIndex); selectionModel.addSelectedRowIndex(dataRowIndex - 1); } } public void moveDataRowDown(int dataRowIndex) { Assert.argument(dataRowIndex < getDataRowCount() - 1, "dataRowIndex"); int rowIndex = dataRowIndex + 1; boolean selected = selectionModel.isRowSelected(dataRowIndex); List<JComponent> componentRow = componentRows.remove(rowIndex); componentRows.add(rowIndex + 1, componentRow); for (int i = rowIndex; i < componentRows.size(); i++) { List<JComponent> componentRowToUpdate = componentRows.get(i); removeComponentRowIntern(componentRowToUpdate); addComponentRowIntern(componentRowToUpdate, i); } fireComponentsChanged(); if (selected) { selectionModel.removeSelectedRowIndex(dataRowIndex); selectionModel.addSelectedRowIndex(dataRowIndex + 1); } } public boolean isDataRowSelected(int dataRowIndex) { return selectionModel.isRowSelected(dataRowIndex); } public int getSelectedDataRowCount() { return selectionModel.getSelectedRowCount(); } public int getSelectedDataRowIndex() { return selectionModel.getMinSelectedRowIndex(); } public int[] getSelectedDataRowIndexes() { return selectionModel.getSelectedRowIndices(); } public void setSelectedDataRowIndexes(int... dataRowIndexes) { selectionModel.setSelectedRowIndices(dataRowIndexes); } @Override public void gridSelectionChanged(GridSelectionModel.Event event) { int[] selectedRowIndices = selectionModel.getSelectedRowIndices(); System.out.println("selectedRowIndices = " + Arrays.toString(selectedRowIndices)); adjustHeaderRowSelector(); adjustDataRowSelectors(); } protected void adjustHeaderRowSelector(AbstractButton headerRowSelector, int selectedDataRowCount) { int dataRowCount = getDataRowCount(); headerRowSelector.setSelected(dataRowCount > 0 && selectedDataRowCount == dataRowCount); headerRowSelector.setEnabled(dataRowCount > 0); } protected Border createHeaderCellBorder() { return new HeaderBorder(); } protected AbstractButton createHeaderRowSelector() { return new JCheckBox(); } protected AbstractButton createDataRowSelector() { return new JCheckBox(); } private void onHeaderRowSelectorAction() { AbstractButton button = (AbstractButton) componentRows.get(0).get(0); setAllDataRowsSelected(button.isSelected()); } private void onDataRowSelectorAction() { adjustSelectionModel(); } private void setAllDataRowsSelected(boolean selected) { if (selected) { int dataRowCount = getDataRowCount(); int[] dataRowIndices = new int[dataRowCount]; for (int i = 0; i < dataRowIndices.length; i++) { dataRowIndices[i] = i; } selectionModel.setSelectedRowIndices(dataRowIndices); } else { selectionModel.setSelectedRowIndices(); } } private void adjustHeaderRowSelector() { JComponent component = componentRows.get(0).get(0); if (component instanceof AbstractButton) { AbstractButton button = (AbstractButton) component; adjustHeaderRowSelector(button, getSelectedDataRowCount()); } } private void adjustDataRowSelectors() { for (int rowIndex = 1; rowIndex < componentRows.size(); rowIndex++) { Component component = componentRows.get(rowIndex).get(0); if (component instanceof AbstractButton) { AbstractButton button = (AbstractButton) component; int dataRowIndex = rowIndex - 1; button.setSelected(isDataRowSelected(dataRowIndex)); } } } private void adjustSelectionModel() { ArrayList<Integer> dataRowIndexList = new ArrayList<>(); for (int rowIndex = 1; rowIndex < componentRows.size(); rowIndex++) { Component component = componentRows.get(rowIndex).get(0); if (component instanceof AbstractButton) { AbstractButton button = (AbstractButton) component; if (button.isSelected()) { dataRowIndexList.add(rowIndex - 1); } } } int[] dataRowIndices = new int[dataRowIndexList.size()]; for (int i = 0; i < dataRowIndices.length; i++) { dataRowIndices[i] = dataRowIndexList.get(i); } selectionModel.setSelectedRowIndices(dataRowIndices); } private void addComponentRowIntern(List<JComponent> componentRow, int rowIndex) { for (int colIndex = 0; colIndex < componentRow.size(); colIndex++) { JComponent component = componentRow.get(colIndex); if (component != null) { remove(component); if (rowIndex == 0) { addHeaderBorder(component); } add(component, TableLayout.cell(rowIndex, colIndex)); //System.out.println("added at (" + rowIndex + "," + colIndex + "): " + component.getClass().getSimpleName()); } } } private void removeComponentRowIntern(List<JComponent> componentRow) { for (JComponent component : componentRow) { if (component != null) { remove(component); } } } private void fireComponentsChanged() { invalidate(); revalidate(); validate(); repaint(); } private void checkColumnCount(JComponent[] components) { if (components.length != getColumnCount() - 1) { throw new IllegalArgumentException("components"); } } private void addHeaderBorder(JComponent component) { Border oldBorder = component.getBorder(); Border newBorder = createHeaderCellBorder(); if (oldBorder != null) { newBorder = BorderFactory.createCompoundBorder(newBorder, oldBorder); } component.setBorder(newBorder); } private int lastFillerRow = -1; private void addFiller() { lastFillerRow = getRowCount(); getLayout().setCellWeightY(lastFillerRow, 0, 1.0); getLayout().setCellFill(lastFillerRow, 0, TableLayout.Fill.VERTICAL); add(filler, TableLayout.cell(lastFillerRow, 0)); } private void removeFiller() { if (lastFillerRow >= 0) { getLayout().setCellWeightY(lastFillerRow, 0, null); getLayout().setCellFill(lastFillerRow, 0, null); } remove(filler); } private int findRowIndex(JComponent component, int rowOffset) { for (int rowIndex = rowOffset; rowIndex < componentRows.size(); rowIndex++) { List<JComponent> componentRow = componentRows.get(rowIndex); for (JComponent jComponent : componentRow) { if (jComponent != null && jComponent == component) { return rowIndex; } } } return -1; } private static class HeaderBorder implements Border { @Override public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { g.setColor(c.getForeground().brighter()); g.drawLine(x, y + height - 1, x + width - 1, y + height - 1); } @Override public Insets getBorderInsets(Component c) { return new Insets(0, 0, 1, 0); } @Override public boolean isBorderOpaque() { return true; } } /* public interface RowSelector { boolean isSelected(); void setSelected(boolean selected); JComponent getEditorComponent(); } public static class DefaultRowSelector implements RowSelector { private AbstractButton button; public DefaultRowSelector() { this(new JCheckBox()); } public DefaultRowSelector(AbstractButton button) { this.button = button; } @Override public boolean isSelected() { return button.isSelected(); } @Override public void setSelected(boolean selected) { button.setSelected(selected); } @Override public AbstractButton getEditorComponent() { return button; } } */ public static class DefaultGridSelectionModel implements GridSelectionModel { private final SortedSet<Integer> rowIndices; private final List<Listener> listeners; public DefaultGridSelectionModel() { rowIndices = new TreeSet<>(); listeners = new ArrayList<>(); } @Override public int getSelectedRowCount() { return rowIndices.size(); } @Override public boolean isRowSelected(int rowIndex) { return rowIndices.contains(rowIndex); } @Override public int getMinSelectedRowIndex() { return rowIndices.isEmpty() ? -1 : rowIndices.first(); } @Override public int getMaxSelectedRowIndex() { return rowIndices.isEmpty() ? -1 : rowIndices.last(); } @Override public int[] getSelectedRowIndices() { Integer[] integers = rowIndices.toArray(new Integer[rowIndices.size()]); int[] indices = new int[integers.length]; for (int i = 0; i < integers.length; i++) { indices[i] = integers[i]; } return indices; } @Override public void setSelectedRowIndices(int... rowIndices) { Set<Integer> newRowIndices = new TreeSet<>(); for (int rowIndex : rowIndices) { newRowIndices.add(rowIndex); } if (!newRowIndices.equals(this.rowIndices)) { this.rowIndices.clear(); this.rowIndices.addAll(newRowIndices); fireChange(new Event(this)); } } @Override public void addSelectedRowIndex(int rowIndex) { if (!rowIndices.contains(rowIndex)) { rowIndices.add(rowIndex); fireChange(new Event(this)); } } @Override public void removeSelectedRowIndex(int rowIndex) { if (rowIndices.contains(rowIndex)) { rowIndices.remove(rowIndex); fireChange(new Event(this)); } } @Override public void addListener(Listener listener) { listeners.add(listener); } @Override public void removeListener(Listener listener) { listeners.remove(listener); } @Override public void fireChange(Event event) { for (GridSelectionModel.Listener listener : listeners) { listener.gridSelectionChanged(event); } } } }