org.apache.pivot.wtk.TablePane Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.pivot.wtk;
import java.util.Iterator;
import org.apache.pivot.beans.DefaultProperty;
import org.apache.pivot.collections.ArrayList;
import org.apache.pivot.collections.Sequence;
import org.apache.pivot.util.ImmutableIterator;
import org.apache.pivot.util.ListenerList;
/**
* Container that arranges components in a two-dimensional grid, optionally
* spanning multiple rows and columns, much like an HTML <table>
* element.
*
* Note that unlike an HTML <table>, components that span
* multiple rows or columns will not "push" other components out of their way.
* Instead, the spanning components will simply overlay the cells into which
* they span. This means that application developers may have to use
* {@link Filler filler cells} in the cells that are spanned.
*/
@DefaultProperty("rows")
public class TablePane extends Container {
/**
* Represents a table pane row.
*/
public static class Row implements Sequence, Iterable {
private int height;
private boolean relative;
private boolean highlighted;
private ArrayList cells = new ArrayList();
private TablePane tablePane = null;
public Row() {
this(-1, false, false);
}
public Row(int height) {
this(height, false, false);
}
public Row(int height, boolean relative) {
this(height, relative, false);
}
public Row(int height, boolean relative, boolean highlighted) {
this.height = height;
this.relative = relative;
this.highlighted = highlighted;
}
/**
* Returns the table pane with which this row is associated.
*
* @return
* The row's table pane, or null if the row does not
* currently belong to a table.
*/
public TablePane getTablePane() {
return tablePane;
}
/**
* Returns the row height.
*
* @return
* The height of the row.
*/
public int getHeight() {
return height;
}
/**
* Returns the relative flag.
*
* @return
* true if the row height is relative, false if it
* is fixed.
*/
public boolean isRelative() {
return relative;
}
/**
* Set the row height.
*
* @param height
* The absolute height of the row.
*/
public void setHeight(int height) {
setHeight(height, false);
}
/**
* Set the row height.
*
* @param height
* The encoded height of the row. If the string ends with the '*'
* character, it is treated as a relative value. Otherwise, it is
* considered an absolute value.
*/
public void setHeight(String height) {
boolean relativeLocal = false;
if (height.endsWith(RELATIVE_SIZE_INDICATOR)) {
relativeLocal = true;
setHeight(Integer.parseInt(height.substring(0, height.length() - 1)), relativeLocal);
} else {
setHeight(Integer.parseInt(height), relativeLocal);
}
}
/**
* Sets the row height.
*
* @param height
* The height of the row.
*
* @param relative
* true if the row height is relative, false if it
* is fixed.
*/
public void setHeight(int height, boolean relative) {
int previousHeight = this.height;
boolean previousRelative = this.relative;
if (previousHeight != height
|| previousRelative != relative) {
this.height = height;
this.relative = relative;
if (tablePane != null) {
tablePane.tablePaneListeners.rowHeightChanged(this,
previousHeight, previousRelative);
}
}
}
/**
* Returns the highlighted flag.
*
* @return
* true if the row is highlighted, false if it is not
*/
public boolean isHighlighted() {
return highlighted;
}
/**
* Sets the highlighted flag.
*
* @param highlighted
* true to set the row as highlighted, false to set
* it as not highlighted
*/
public void setHighlighted(boolean highlighted) {
if (highlighted != this.highlighted) {
this.highlighted = highlighted;
if (tablePane != null) {
tablePane.tablePaneListeners.rowHighlightedChanged(this);
}
}
}
@Override
public int add(Component component) {
int index = getLength();
insert(component, index);
return index;
}
@Override
public void insert(Component component, int index) {
if (component == null) {
throw new IllegalArgumentException("Component is null.");
}
if (component.getParent() != null) {
throw new IllegalArgumentException("Component already has a parent.");
}
cells.insert(component, index);
if (tablePane != null) {
tablePane.add(component);
tablePane.tablePaneListeners.cellInserted(this, index);
}
}
@Override
public Component update(int index, Component component) {
Component previousComponent = cells.get(index);
if (component != previousComponent) {
if (component == null) {
throw new IllegalArgumentException("Component is null.");
}
if (component.getParent() != null) {
throw new IllegalArgumentException("Component already has a parent.");
}
cells.update(index, component);
if (tablePane != null) {
tablePane.add(component);
tablePane.tablePaneListeners.cellUpdated(this, index, previousComponent);
tablePane.remove(previousComponent);
}
}
return previousComponent;
}
@Override
public int remove(Component component) {
int index = indexOf(component);
if (index != -1) {
remove(index, 1);
}
return index;
}
@Override
public Sequence remove(int index, int count) {
Sequence removed = cells.remove(index, count);
if (tablePane != null) {
tablePane.tablePaneListeners.cellsRemoved(this, index, removed);
for (int i = 0, n = removed.getLength(); i < n; i++) {
Component component = removed.get(i);
tablePane.remove(component);
}
}
return removed;
}
@Override
public Component get(int index) {
return cells.get(index);
}
@Override
public int indexOf(Component component) {
return cells.indexOf(component);
}
@Override
public int getLength() {
return cells.getLength();
}
@Override
public Iterator iterator() {
return new ImmutableIterator(cells.iterator());
}
}
/**
* Represents a table pane column.
*/
public static class Column {
private TablePane tablePane = null;
private int width;
private boolean relative;
private boolean highlighted;
public Column() {
this(-1, false, false);
}
public Column(int width) {
this(width, false, false);
}
public Column(int width, boolean relative) {
this(width, relative, false);
}
public Column(int width, boolean relative, boolean highlighted) {
this.width = width;
this.relative = relative;
this.highlighted = highlighted;
}
/**
* Returns the table pane with which this column is associated.
*
* @return
* The column's table pane, or null if the column does not
* currently belong to a table.
*/
public TablePane getTablePane() {
return tablePane;
}
/**
* Returns the column width.
*
* @return
* The width of the column.
*/
public int getWidth() {
return width;
}
/**
* Returns the relative flag.
*
* @return
* true if the column width is relative, false if it
* is fixed.
*/
public boolean isRelative() {
return relative;
}
/**
* Set the column width.
*
* @param width
* The absolute width of the column.
*/
public void setWidth(int width) {
setWidth(width, false);
}
/**
* Set the column width.
*
* @param width
* The encoded width of the row. If the string ends with the '*'
* character, it is treated as a relative value. Otherwise, it is
* considered an absolute value.
*/
public void setWidth(String width) {
boolean relativeLocal = false;
if (width.endsWith(RELATIVE_SIZE_INDICATOR)) {
relativeLocal = true;
setWidth(Integer.parseInt(width.substring(0, width.length() - 1)), relativeLocal);
} else {
setWidth(Integer.parseInt(width), relativeLocal);
}
}
/**
* Sets the column width.
*
* @param width
* The width of the column.
*
* @param relative
* true if the column width is relative, false if it
* is fixed.
*/
public void setWidth(int width, boolean relative) {
int previousWidth = this.width;
boolean previousRelative = this.relative;
if (previousWidth != width
|| previousRelative != relative) {
this.width = width;
this.relative = relative;
if (tablePane != null) {
tablePane.tablePaneListeners.columnWidthChanged(this,
previousWidth, previousRelative);
}
}
}
/**
* Returns the highlighted flag.
*
* @return
* true if the column is highlighted, false if it is not
*/
public boolean isHighlighted() {
return highlighted;
}
/**
* Sets the highlighted flag.
*
* @param highlighted
* true to set the column as highlighted, false to set
* it as not highlighted
*/
public void setHighlighted(boolean highlighted) {
if (highlighted != this.highlighted) {
this.highlighted = highlighted;
if (tablePane != null) {
tablePane.tablePaneListeners.columnHighlightedChanged(this);
}
}
}
}
/**
* Table pane skin interface. Table pane skins must implement
* this interface to facilitate additional communication between the
* component and the skin.
*/
public interface Skin {
public int getRowAt(int y);
public Bounds getRowBounds(int row);
public int getColumnAt(int x);
public Bounds getColumnBounds(int column);
}
/**
* Class that manages a table pane's row list. Callers get access to the
* row sequence via {@link TablePane#getRows()}.
*/
public final class RowSequence implements Sequence, Iterable {
private RowSequence() {
}
@Override
public int add(Row row) {
int index = getLength();
insert(row, index);
return index;
}
@Override
public void insert(Row row, int index) {
if (row == null) {
throw new IllegalArgumentException("row is null.");
}
if (row.tablePane != null) {
throw new IllegalArgumentException
("row is already in use by another table pane.");
}
rows.insert(row, index);
row.tablePane = TablePane.this;
for (int i = 0, n = row.getLength(); i < n; i++) {
Component component = row.get(i);
TablePane.this.add(component);
}
// Notify listeners
tablePaneListeners.rowInserted(TablePane.this, index);
}
@Override
public Row update(int index, Row row) {
throw new UnsupportedOperationException();
}
@Override
public int remove(Row row) {
int index = indexOf(row);
if (index != -1) {
remove(index, 1);
}
return index;
}
@Override
public Sequence remove(int index, int count) {
Sequence removed = rows.remove(index, count);
if (count > 0) {
for (int i = 0, n = removed.getLength(); i < n; i++) {
Row row = removed.get(i);
row.tablePane = null;
for (int j = 0, m = row.getLength(); j < m; j++) {
Component component = row.get(j);
TablePane.this.remove(component);
}
}
tablePaneListeners.rowsRemoved(TablePane.this, index, removed);
}
return removed;
}
@Override
public Row get(int index) {
return rows.get(index);
}
@Override
public int indexOf(Row row) {
return rows.indexOf(row);
}
@Override
public int getLength() {
return rows.getLength();
}
@Override
public Iterator iterator() {
return new ImmutableIterator(rows.iterator());
}
}
/**
* Class that manages a table pane's column list. Callers get access to the
* column sequence via {@link TablePane#getColumns()}.
*/
public final class ColumnSequence implements Sequence, Iterable {
private ColumnSequence() {
}
@Override
public int add(Column column) {
int index = getLength();
insert(column, index);
return index;
}
@Override
public void insert(Column column, int index) {
if (column == null) {
throw new IllegalArgumentException("column is null.");
}
if (column.tablePane != null) {
throw new IllegalArgumentException
("column is already in use by another table pane.");
}
columns.insert(column, index);
column.tablePane = TablePane.this;
// Notify listeners
tablePaneListeners.columnInserted(TablePane.this, index);
}
@Override
public Column update(int index, Column column) {
throw new UnsupportedOperationException();
}
@Override
public int remove(Column column) {
int index = indexOf(column);
if (index != -1) {
remove(index, 1);
}
return index;
}
@Override
public Sequence remove(int index, int count) {
Sequence removed = columns.remove(index, count);
if (count > 0) {
for (int i = 0, n = removed.getLength(); i < n; i++) {
Column column = removed.get(i);
column.tablePane = null;
}
tablePaneListeners.columnsRemoved(TablePane.this, index, removed);
}
return removed;
}
@Override
public Column get(int index) {
return columns.get(index);
}
@Override
public int indexOf(Column column) {
return columns.indexOf(column);
}
@Override
public int getLength() {
return columns.getLength();
}
@Override
public Iterator iterator() {
return new ImmutableIterator(columns.iterator());
}
}
/**
* Component that can be used as filler for empty cells.
*/
public static final class Filler extends Component {
public Filler() {
installSkin(Filler.class);
}
}
private enum Attribute {
ROW_SPAN,
COLUMN_SPAN;
}
private static class TablePaneListenerList extends WTKListenerList
implements TablePaneListener {
@Override
public void rowInserted(TablePane tablePane, int index) {
for (TablePaneListener listener : this) {
listener.rowInserted(tablePane, index);
}
}
@Override
public void rowsRemoved(TablePane tablePane, int index,
Sequence rows) {
for (TablePaneListener listener : this) {
listener.rowsRemoved(tablePane, index, rows);
}
}
@Override
public void rowHeightChanged(TablePane.Row row, int previousHeight,
boolean previousRelative) {
for (TablePaneListener listener : this) {
listener.rowHeightChanged(row, previousHeight, previousRelative);
}
}
@Override
public void rowHighlightedChanged(TablePane.Row row) {
for (TablePaneListener listener : this) {
listener.rowHighlightedChanged(row);
}
}
@Override
public void columnInserted(TablePane tablePane, int index) {
for (TablePaneListener listener : this) {
listener.columnInserted(tablePane, index);
}
}
@Override
public void columnsRemoved(TablePane tablePane, int index,
Sequence columns) {
for (TablePaneListener listener : this) {
listener.columnsRemoved(tablePane, index, columns);
}
}
@Override
public void columnWidthChanged(TablePane.Column column, int previousWidth,
boolean previousRelative) {
for (TablePaneListener listener : this) {
listener.columnWidthChanged(column, previousWidth, previousRelative);
}
}
@Override
public void columnHighlightedChanged(TablePane.Column column) {
for (TablePaneListener listener : this) {
listener.columnHighlightedChanged(column);
}
}
@Override
public void cellInserted(TablePane.Row row, int column) {
for (TablePaneListener listener : this) {
listener.cellInserted(row, column);
}
}
@Override
public void cellsRemoved(TablePane.Row row, int column,
Sequence removed) {
for (TablePaneListener listener : this) {
listener.cellsRemoved(row, column, removed);
}
}
@Override
public void cellUpdated(TablePane.Row row, int column,
Component previousComponent) {
for (TablePaneListener listener : this) {
listener.cellUpdated(row, column, previousComponent);
}
}
}
private static class TablePaneAttributeListenerList extends WTKListenerList
implements TablePaneAttributeListener {
@Override
public void rowSpanChanged(TablePane tablePane, Component component,
int previousRowSpan) {
for (TablePaneAttributeListener listener : this) {
listener.rowSpanChanged(tablePane, component, previousRowSpan);
}
}
@Override
public void columnSpanChanged(TablePane tablePane, Component component,
int previousColumnSpan) {
for (TablePaneAttributeListener listener : this) {
listener.columnSpanChanged(tablePane, component, previousColumnSpan);
}
}
}
private ArrayList rows = null;
private RowSequence rowSequence = new RowSequence();
private ArrayList columns = null;
private ColumnSequence columnSequence = new ColumnSequence();
private TablePaneListenerList tablePaneListeners = new TablePaneListenerList();
private TablePaneAttributeListenerList tablePaneAttributeListeners = new TablePaneAttributeListenerList();
public static final String RELATIVE_SIZE_INDICATOR = "*";
/**
* Creates a new TablePane with empty row and column sequences.
*/
public TablePane() {
this(new ArrayList());
}
/**
* Creates a new TablePane with the specified columns.
*
* @param columns
* The column sequence to use. A copy of this sequence will be made
*/
public TablePane(Sequence columns) {
if (columns == null) {
throw new IllegalArgumentException("columns is null");
}
this.rows = new ArrayList();
this.columns = new ArrayList(columns);
installSkin(TablePane.class);
}
@Override
protected void setSkin(org.apache.pivot.wtk.Skin skin) {
if (!(skin instanceof TablePane.Skin)) {
throw new IllegalArgumentException("Skin class must implement "
+ TablePane.Skin.class.getName());
}
super.setSkin(skin);
}
/**
* Returns the table pane row sequence.
*
* @return
* The table pane row sequence
*/
public RowSequence getRows() {
return rowSequence;
}
/**
* Returns the index of the row at a given location.
*
* @param y
* The y-coordinate of the row to identify.
*
* @return
* The row index, or -1 if there is no row at the given
* y-coordinate.
*/
public int getRowAt(int y) {
TablePane.Skin tablePaneSkin = (TablePane.Skin)getSkin();
return tablePaneSkin.getRowAt(y);
}
/**
* Returns the bounds of a given row.
*
* @param row
* The row index.
*/
public Bounds getRowBounds(int row) {
TablePane.Skin tablePaneSkin = (TablePane.Skin)getSkin();
return tablePaneSkin.getRowBounds(row);
}
/**
* Returns the table pane column sequence.
*
* @return
* The table pane column sequence
*/
public ColumnSequence getColumns() {
return columnSequence;
}
/**
* Returns the index of the column at a given location.
*
* @param x
* The x-coordinate of the column to identify.
*
* @return
* The column index, or -1 if there is no column at the given
* x-coordinate.
*/
public int getColumnAt(int x) {
TablePane.Skin tablePaneSkin = (TablePane.Skin)getSkin();
return tablePaneSkin.getColumnAt(x);
}
/**
* Returns the bounds of a given column.
*
* @param column
* The column index.
*/
public Bounds getColumnBounds(int column) {
TablePane.Skin tablePaneSkin = (TablePane.Skin)getSkin();
return tablePaneSkin.getColumnBounds(column);
}
/**
* Gets the component at the specified cell in this table pane.
*
* @param rowIndex
* The row index of the cell
*
* @param columnIndex
* The column index of the cell
*
* @return
* The component in the specified cell, or null if the cell is
* empty
*/
public Component getCellComponent(int rowIndex, int columnIndex) {
Row row = rows.get(rowIndex);
Component component = null;
if (row.getLength() > columnIndex) {
component = row.get(columnIndex);
}
return component;
}
/**
* Overrides the base method to check whether or not a cell component is
* being removed, and fires the appropriate event in that case.
*
* @param index
* The index at which components were removed
*
* @param count
* The number of components removed
*
* @return
* The sequence of components that were removed
*/
@Override
public Sequence remove(int index, int count) {
for (int i = index, n = index + count; i < n; i++) {
Component component = get(i);
for (Row row : rows) {
if (row.indexOf(component) >= 0) {
throw new UnsupportedOperationException();
}
}
}
// Call the base method to remove the components
return super.remove(index, count);
}
/**
* Returns the table pane listener list.
*/
public ListenerList getTablePaneListeners() {
return tablePaneListeners;
}
/**
* Returns the table pane attribute listener list.
*/
public ListenerList getTablePaneAttributeListeners() {
return tablePaneAttributeListeners;
}
public static int getRowSpan(Component component) {
Integer value = (Integer)component.getAttribute(Attribute.ROW_SPAN);
return (value == null) ? 1 : value;
}
public static void setRowSpan(Component component, int rowSpan) {
Integer previousValue = (Integer)component.setAttribute(Attribute.ROW_SPAN, rowSpan);
int previousRowSpan = (previousValue == null) ? 1 : previousValue;
if (previousRowSpan != rowSpan) {
Container parent = component.getParent();
if (parent instanceof TablePane) {
TablePane tablePane = (TablePane)parent;
tablePane.tablePaneAttributeListeners.rowSpanChanged(tablePane,
component, previousRowSpan);
}
}
}
public static int getColumnSpan(Component component) {
Integer value = (Integer)component.getAttribute(Attribute.COLUMN_SPAN);
return (value == null) ? 1 : value;
}
public static void setColumnSpan(Component component, int columnSpan) {
Integer previousValue = (Integer)component.setAttribute(Attribute.COLUMN_SPAN, columnSpan);
int previousColumnSpan = (previousValue == null) ? 1 : previousValue;
if (previousColumnSpan != columnSpan) {
Container parent = component.getParent();
if (parent instanceof TablePane) {
TablePane tablePane = (TablePane)parent;
tablePane.tablePaneAttributeListeners.columnSpanChanged(tablePane,
component, previousColumnSpan);
}
}
}
}