blob: 05d43a5f26bde03230263482a112a6c8b910e0ce [file] [log] [blame]
/*
* 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;
import org.apache.pivot.util.Utils;
/**
* Container that arranges components in a two-dimensional grid, where every
* cell is the same size.
*/
@DefaultProperty("rows")
public class GridPane extends Container {
/**
* Represents a grid pane row.
*/
public static class Row implements Sequence<Component>, Iterable<Component> {
private ArrayList<Component> cells = new ArrayList<>();
private GridPane gridPane = null;
public Row() {
}
public Row(GridPane gridPane) {
if (gridPane != null) {
gridPane.getRows().add(this);
}
}
/**
* Returns the grid pane with which this row is associated.
*
* @return The row's grid pane, or <tt>null</tt> if the row does not
* currently belong to a grid.
*/
public GridPane getGridPane() {
return gridPane;
}
@Override
public int add(Component component) {
int index = getLength();
insert(component, index);
return index;
}
@Override
public void insert(Component component, int index) {
Utils.checkNull(component, "component");
if (component.getParent() != null) {
throw new IllegalArgumentException("Component already has a parent.");
}
cells.insert(component, index);
if (gridPane != null) {
gridPane.add(component);
gridPane.gridPaneListeners.cellInserted(this, index);
}
}
@Override
public Component update(int index, Component component) {
Component previousComponent = cells.get(index);
if (component != previousComponent) {
Utils.checkNull(component, "Component");
if (component.getParent() != null) {
throw new IllegalArgumentException("Component already has a parent.");
}
cells.update(index, component);
if (gridPane != null) {
gridPane.add(component);
gridPane.gridPaneListeners.cellUpdated(this, index, previousComponent);
gridPane.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<Component> remove(int index, int count) {
Sequence<Component> removed = cells.remove(index, count);
if (gridPane != null) {
gridPane.gridPaneListeners.cellsRemoved(this, index, removed);
for (int i = 0, n = removed.getLength(); i < n; i++) {
Component component = removed.get(i);
gridPane.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<Component> iterator() {
return new ImmutableIterator<>(cells.iterator());
}
}
/**
* Grid pane skin interface. Grid 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 grid pane's row list. Callers get access to the row
* sequence via {@link GridPane#getRows()}.
*/
public final class RowSequence implements Sequence<Row>, Iterable<Row> {
private RowSequence() {
}
@Override
public int add(Row row) {
int index = getLength();
insert(row, index);
return index;
}
@Override
public void insert(Row row, int index) {
Utils.checkNull(row, "Row");
if (row.getGridPane() != null) {
throw new IllegalArgumentException("Row is already in use by another grid pane.");
}
rows.insert(row, index);
row.gridPane = GridPane.this;
for (int i = 0, n = row.getLength(); i < n; i++) {
Component component = row.get(i);
GridPane.this.add(component);
}
// Notify listeners
gridPaneListeners.rowInserted(GridPane.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<Row> remove(int index, int count) {
Sequence<Row> removed = rows.remove(index, count);
if (count > 0) {
for (int i = 0, n = removed.getLength(); i < n; i++) {
Row row = removed.get(i);
row.gridPane = null;
for (int j = 0, m = row.getLength(); j < m; j++) {
Component component = row.get(j);
GridPane.this.remove(component);
}
}
gridPaneListeners.rowsRemoved(GridPane.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<Row> iterator() {
return new ImmutableIterator<>(rows.iterator());
}
}
/**
* Component that can be used as filler for empty cells.
*/
public static final class Filler extends Component {
public Filler() {
installSkin(Filler.class);
}
}
private static class GridPaneListenerList extends WTKListenerList<GridPaneListener> implements
GridPaneListener {
@Override
public void columnCountChanged(GridPane gridPane, int previousColumnCount) {
for (GridPaneListener listener : this) {
listener.columnCountChanged(gridPane, previousColumnCount);
}
}
@Override
public void rowInserted(GridPane gridPane, int index) {
for (GridPaneListener listener : this) {
listener.rowInserted(gridPane, index);
}
}
@Override
public void rowsRemoved(GridPane gridPane, int index, Sequence<GridPane.Row> rows) {
for (GridPaneListener listener : this) {
listener.rowsRemoved(gridPane, index, rows);
}
}
@Override
public void cellInserted(GridPane.Row row, int column) {
for (GridPaneListener listener : this) {
listener.cellInserted(row, column);
}
}
@Override
public void cellsRemoved(GridPane.Row row, int column, Sequence<Component> removed) {
for (GridPaneListener listener : this) {
listener.cellsRemoved(row, column, removed);
}
}
@Override
public void cellUpdated(GridPane.Row row, int column, Component previousComponent) {
for (GridPaneListener listener : this) {
listener.cellUpdated(row, column, previousComponent);
}
}
}
private int columnCount;
private ArrayList<Row> rows = new ArrayList<>();
private RowSequence rowSequence = new RowSequence();
private GridPaneListenerList gridPaneListeners = new GridPaneListenerList();
/**
* Creates a new grid pane.
*/
public GridPane() {
this(0);
}
/**
* Creates a new grid pane with the specified column count.
*
* @param columnCount Number of columns for this grid.
*/
public GridPane(int columnCount) {
if (columnCount < 0) {
throw new IllegalArgumentException("columnCount is negative.");
}
setColumnCount(columnCount);
installSkin(GridPane.class);
}
@Override
protected void setSkin(org.apache.pivot.wtk.Skin skin) {
if (!(skin instanceof GridPane.Skin)) {
throw new IllegalArgumentException("Skin class must implement "
+ GridPane.Skin.class.getName());
}
super.setSkin(skin);
}
/**
* @return The number of columns in the grid pane.
*/
public int getColumnCount() {
return columnCount;
}
/**
* Sets the number of columns in the grid pane.
*
* @param columnCount The new number of columns in the grid.
*/
public void setColumnCount(int columnCount) {
int previousColumnCount = this.columnCount;
if (previousColumnCount != columnCount) {
this.columnCount = columnCount;
gridPaneListeners.columnCountChanged(this, previousColumnCount);
}
}
/**
* @return The grid 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 <tt>-1</tt> if there is no row at the given
* y-coordinate.
*/
public int getRowAt(int y) {
GridPane.Skin gridPaneSkin = (GridPane.Skin) getSkin();
return gridPaneSkin.getRowAt(y);
}
/**
* Returns the bounds of a given row.
*
* @param row The row index.
* @return The bounds for the given row.
*/
public Bounds getRowBounds(int row) {
GridPane.Skin gridPaneSkin = (GridPane.Skin) getSkin();
return gridPaneSkin.getRowBounds(row);
}
/**
* 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 <tt>-1</tt> if there is no column at the
* given x-coordinate.
*/
public int getColumnAt(int x) {
GridPane.Skin gridPaneSkin = (GridPane.Skin) getSkin();
return gridPaneSkin.getColumnAt(x);
}
/**
* Returns the bounds of a given column.
*
* @param column The column index.
* @return The bounds of the given column.
*/
public Bounds getColumnBounds(int column) {
GridPane.Skin gridPaneSkin = (GridPane.Skin) getSkin();
return gridPaneSkin.getColumnBounds(column);
}
/**
* Gets the component at the specified cell in this grid 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 <tt>null</tt> 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<Component> 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);
}
/**
* @return The grid pane listener list.
*/
public ListenerList<GridPaneListener> getGridPaneListeners() {
return gridPaneListeners;
}
}