ca.odell.glazedlists.swing.ListLayout Maven / Gradle / Ivy
Show all versions of glazedlists_java15 Show documentation
/* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.swing;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.ColumnSpec;
import com.jgoodies.forms.layout.FormLayout;
import com.jgoodies.forms.layout.RowSpec;
import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Layout list elements by delegating to a {@link FormLayout}. This layout manages
* the {@link ListLayout} by responding to list changes and shifting components
* around as elements are inserted or removed.
*
* Note that this class is subject to significant API changes and improvements,
* and that only the {@link JEventListPanel} class should be referenced in
* client source code.
*
* @author Jesse Wilson
*/
class ListLayout extends LayoutDecorator {
/** all components for the entire grid */
private final List gridComponents = new ArrayList();
/** the formlayout does the actual legwork */
private final FormLayout formLayout;
/** how to layout logical cells in the formlayout */
private final JEventListPanel.Format format;
/** the row and column styles */
private RowSpec[] rowSpecs;
private ColumnSpec[] columnSpecs;
/** spacers between element columns */
private RowSpec gapRow;
private ColumnSpec gapColumn;
private Container container;
private int elementColumns = 1;
private int elementRows = Integer.MAX_VALUE;
/**
* Layout the specified container using the specified format to manage
* rows, columns and constraints.
*/
public ListLayout(Container container, JEventListPanel.Format format) {
this.container = container;
this.format = format;
this.formLayout = new FormLayout("");
// prepare the empty panel
this.columnSpecs = format.getElementColumns();
this.rowSpecs = format.getElementRows();
this.gapRow = format.getGapRow();
this.gapColumn = format.getGapColumn();
// tell the superclass who to delegate methods to
super.delegateLayout = formLayout;
}
/**
* Set the number of columns the layout wraps over. If set, the layout will
* wrap from left to right, adding rows as necessary.
*/
public void setElementColumns(int elementColumns) {
if(elementColumns == this.elementColumns) return;
setElementRowsAndColumns(Integer.MAX_VALUE, elementColumns);
}
/**
* Set the number of rows the layout wraps over. If set, the layout will
* wrap from top to bottom, adding rows as necessary.
*/
public void setElementRows(int elementRows) {
if(elementRows == this.elementRows) return;
setElementRowsAndColumns(elementRows, Integer.MAX_VALUE);
}
/**
* Adjust the number of logical rows and columns and how elements are wrapped
* into those cells.
*/
private void setElementRowsAndColumns(int elementRows, int elementColumns) {
int logicalRowCountBefore = logicalRowCount();
int logicalColumnCountBefore = logicalColumnCount();
this.elementRows = elementRows;
this.elementColumns = elementColumns;
fixCellCount(logicalRowCountBefore, logicalColumnCountBefore);
}
/**
* We can improve this method by replacing the before counts as parameters
* by figuring out what the counts are by examining the layout.
*/
private void fixCellCount(int logicalRowCountBefore, int logicalColumnCountBefore) {
int logicalColumnCountAfter = logicalColumnCount();
int logicalRowCountAfter = logicalRowCount();
// make sure we have enough cells
for(int r = logicalRowCountBefore; r < logicalRowCountAfter; r++) {
insertLogicalRow(r);
}
for(int c = logicalColumnCountBefore; c < logicalColumnCountAfter; c++) {
insertLogicalColumn(c);
}
// we have too many cells in one dimension, the perfect time to
// reassign constraints safely
reassignConstraints();
// make sure we don't have too many cells
for(int r = logicalRowCountBefore - 1; r >= logicalRowCountAfter; r--) {
removeLogicalRow(r);
}
for(int c = logicalColumnCountBefore - 1; c >= logicalColumnCountAfter; c--) {
removeLogicalColumn(c);
}
}
private CellConstraints deriveCellConstraints(int component, int logicalColumn, int logicalRow) {
CellConstraints constraints = format.getConstraints(component);
constraints = (CellConstraints)constraints.clone();
constraints.gridX += logicalToLayoutColumn(logicalColumn) + (logicalColumn > 0 && gapColumn != null ? 1 : 0);
constraints.gridY += logicalToLayoutRow(logicalRow) + (logicalRow > 0 && gapRow != null ? 1 : 0);
return constraints;
}
/** {@inheritDoc} */
@Override
public void addLayoutComponent(Component component, Object constraints) {
Constraints listLayoutConstraints = (Constraints)constraints;
int index = listLayoutConstraints.getIndex();
int formConstraints = listLayoutConstraints.getFormConstraints();
CellComponents cellComponents = gridComponents.get(index);
cellComponents.components.add(component);
cellComponents.constraints.add(listLayoutConstraints);
CellConstraints cellConstraints = deriveCellConstraints(formConstraints, logicalColumn(index), logicalRow(index));
super.addLayoutComponent(component, cellConstraints);
}
private int logicalColumn(int index) {
if(elementRows == Integer.MAX_VALUE) {
return index % elementColumns;
} else {
return index / elementRows;
}
}
private int logicalRow(int index) {
if(elementRows == Integer.MAX_VALUE) {
return index / elementColumns;
} else {
return index % elementRows;
}
}
private int logicalRowCount() {
if(elementRows == Integer.MAX_VALUE) {
return (gridComponents.size() + elementColumns - 1) / elementColumns;
} else {
return Math.min(gridComponents.size(), elementRows);
}
}
private int logicalColumnCount() {
if(elementRows == Integer.MAX_VALUE) {
return Math.min(gridComponents.size(), elementColumns);
} else {
return (gridComponents.size() + elementRows - 1) / elementRows;
}
}
private void insertLogicalRow(int index) {
int baseRow = logicalToLayoutRow(index);
// insert the gap for all rows but the first
if(index != 0 && gapRow != null) {
insertRow(baseRow, gapRow);
baseRow += 1;
}
// insert the element rows
for(int r = 0; r < rowSpecs.length; r++) {
insertRow(baseRow + r, rowSpecs[r]);
}
}
private void insertLogicalColumn(int index) {
int baseColumn = logicalToLayoutColumn(index);
// insert the gap for all columns but the first
if(index != 0 && gapColumn != null) {
insertColumn(baseColumn, gapColumn);
baseColumn += 1;
}
// insert the element columns
for(int c = 0; c < columnSpecs.length; c++) {
insertColumn(baseColumn + c, columnSpecs[c]);
}
}
private void removeLogicalRow(int index) {
int baseRow = logicalToLayoutRow(index);
if(index != 0 && gapRow != null) {
formLayout.removeRow(baseRow + 1);
}
for(int r = 0; r < rowSpecs.length; r++) {
formLayout.removeRow(baseRow + 1);
}
}
private void removeLogicalColumn(int index) {
int baseColumn = logicalToLayoutColumn(index);
if(index != 0 && gapColumn != null) {
formLayout.removeColumn(baseColumn + 1);
}
for(int c = 0; c < columnSpecs.length; c++) {
formLayout.removeColumn(baseColumn + 1);
}
}
/**
* @return the row that the preceding gap for this row should be shown on,
* or the row that the element rows should be on if this is the first row.
*/
private int logicalToLayoutRow(int row) {
if(row == 0) return 0;
else return (row * rowSpecs.length) + (gapRow == null ? 0 : row - 1);
}
/**
* @return the column that the preceding gap for this column should be shown on,
* or the column that the element columns should be on if this is the first column.
*/
private int logicalToLayoutColumn(int column) {
if(column == 0) return 0;
else return (column * columnSpecs.length) + (gapColumn == null ? 0 : column - 1);
}
/**
* Handle a list element being inserted by adjusting the layout.
*/
public void insertIndex(int index) {
int logicalColumnCountBefore = logicalColumnCount();
int logicalRowCountBefore = logicalRowCount();
gridComponents.add(index, new CellComponents());
fixCellCount(logicalRowCountBefore, logicalColumnCountBefore);
}
/**
* Handle a list element being removed by adjusting the layout.
*/
public void removeIndex(int index) {
int logicalColumnCountBefore = logicalColumnCount();
int logicalRowCountBefore = logicalRowCount();
gridComponents.remove(index);
fixCellCount(logicalRowCountBefore, logicalColumnCountBefore);
}
/**
* Handle a list element being updated by adjusting the layout.
*/
public void updateIndex(int index) {
// remove all cached component constraints that aren't in the layout anymore
CellComponents cellComponents = gridComponents.get(index);
List activeComponents = Arrays.asList(container.getComponents());
for(int i = 0; i < cellComponents.components.size(); ) {
Component component = cellComponents.components.get(i);
if(!activeComponents.contains(component)) {
cellComponents.components.remove(i);
cellComponents.constraints.remove(i);
} else {
i++;
}
}
}
private void reassignConstraints() {
formLayout.invalidateLayout(container);
for(int i = 0; i < gridComponents.size(); i++) {
CellComponents cellComponents = gridComponents.get(i);
for(int j = 0; j < cellComponents.components.size(); j++) {
Component component = cellComponents.components.get(j);
Constraints constraints = cellComponents.constraints.get(j);
formLayout.addLayoutComponent(component, deriveCellConstraints(constraints.getFormConstraints(), logicalColumn(i), logicalRow(i)));
}
}
}
/**
* Count the cells in the grid.
*/
public int size() {
return gridComponents.size();
}
/**
* Insert the specified row into the layout. This accomodates
* for the appendColumn/insertColumn API weakness in FormLayout.
*/
private void insertRow(int index, RowSpec rowSpec) {
if(index == formLayout.getRowCount()) formLayout.appendRow(rowSpec);
else formLayout.insertRow(index + 1, rowSpec);
}
/**
* Insert the specified column into the layout. This accomodates
* for the appendRow/insertRow API weakness in FormLayout.
*/
private void insertColumn(int index, ColumnSpec columnSpec) {
if(index == formLayout.getColumnCount()) formLayout.appendColumn(columnSpec);
else formLayout.insertColumn(index + 1, columnSpec);
}
/**
* The components for a single cell.
*/
private static class CellComponents {
private List components = new ArrayList(1);
private List constraints = new ArrayList(1);
}
/**
* Map a {@link Component} to a list element and a location in the container.
*/
public static class Constraints {
private int formConstraints;
private int index;
public Constraints(int component, int index) {
this.formConstraints = component;
this.index = index;
}
/**
* The form constraints specifies which {@link CellConstraints}
* in the element cell belongs to this {@link Component}.
*/
public int getFormConstraints() {
return formConstraints;
}
/**
* The initial index of the element, it will become stale as other
* elements are added before it. It's only used the first time a
* component is used to find the appropriate {@link CellComponents}
* object.
*/
public int getIndex() {
return index;
}
}
}