/**
* Copyright 2004-2016 Riccardo Solmi. All rights reserved.
* This file is part of the Whole Platform.
*
* The Whole Platform is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Whole Platform is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the Whole Platform. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whole.lang.ui.layout;
import java.util.List;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Rectangle;
import org.whole.lang.ui.figures.IEntityFigure;
import org.whole.lang.util.CompositeUtils;
/**
* @author Riccardo Solmi
*/
public class TableLayout extends AbstractCompositeEntityLayout implements ITabularLayoutServer, ITabularLayoutClient {
public static enum Placement {HEADER}
{
withMinorAlignment(Alignment.LEADING);
}
private IEntityFigure headerRowFigure;
private int columnSpacing;
private int columns;
private int[] columnWidth;
private int[] columnSpacingBefore;
private Alignment[] columnAlignment;
private Alignment defaultColumnAlignment = Alignment.LEADING;
public TableLayout() {
this(2);
}
public TableLayout(int initialColumns) {
columnAlignment = new Alignment[initialColumns];
}
@Override
public void remove(IFigure child) {
if (headerRowFigure == child)
headerRowFigure = null;
super.remove(child);
}
@Override
public void setConstraint(IFigure child, Object constraint) {
remove(child);
super.setConstraint(child, constraint);
if (constraint == null)
return;
switch ((Placement) constraint) {
case HEADER:
headerRowFigure = (IEntityFigure) child;
break;
}
}
@SuppressWarnings("unchecked")
@Override
protected IEntityFigure[] getChildren(IFigure container) {
if (headerRowFigure == null)
return super.getChildren(container);
else {
List<IEntityFigure> children = container.getChildren();
int size = children.size();
if (size == 1)
return new IEntityFigure[0];
IEntityFigure[] figures = new IEntityFigure[size];
figures[0] = headerRowFigure;
for (int i=0,j=1; i<size; i++)
if (headerRowFigure != children.get(i))
figures[j++] = children.get(i);
return figures;
}
}
public boolean hasHeaderRow() {
return headerRowFigure != null;
}
@Override
public int getMarginTop() {
return marginTop;
}
@Override
public int getMarginLeft() {
return marginLeft;
}
@Override
public int getMarginBottom() {
return !isHorizontal() && (getSpacedChild() == childFigure.length-(hasHeaderRow() ? 2 : 1) || getSpacedChild() == SPACED_ALL) ?
getSpacedSpacing(marginBottom) : marginBottom;
}
@Override
public int getMarginRight() {
return marginRight;
}
private int startingCellIndex;
public int getStartingCellIndex() {
return startingCellIndex;
}
public void setStartingCellIndex(int cellIndex) {
startingCellIndex = cellIndex;
}
public int getCells() {
return columns();
}
public void invalidateCells() {
invalidate();
}
public Dimension getPreferredCellSize(int cellIndex, int wHint, int hHint) {
Rectangle r = getCellBounds(cellIndex);
int columnIndex = cellIndex - getStartingCellIndex();
return columnIndex < getCells() && childFigure[columnIndex].isVisible() ? new Dimension(r.width, r.height): IEntityFigure.PLACE_HOLDER_DIMENSION;
}
public Rectangle getCellBounds(int cellIndex) {
int columnIndex = cellIndex - getStartingCellIndex();
return columnIndex < getCells() && childFigure[columnIndex].isVisible() ? getColumnBounds(columnIndex) : IEntityFigure.PLACE_HOLDER_BOUNDS;
}
public int getCellSpacingBefore(int cellIndex) {
int columnIndex = cellIndex - getStartingCellIndex();
return getColumnSpacingBefore(columnIndex);
}
public int getPreferredCellSpacingBefore(int cellIndex) {
int columnIndex = cellIndex - getStartingCellIndex();
return getPreferredColumnSpacingBefore(columnIndex);
}
public TableLayout withColumnAlignment(int columnIndex, Alignment alignment) {
columnAlignment = CompositeUtils.grow(columnAlignment, columnIndex+1, null);
columnAlignment[columnIndex] = alignment;
return this;
}
public TableLayout withColumnAlignment(Alignment defaultAlignment) {
defaultColumnAlignment = defaultAlignment;
return this;
}
public boolean isHorizontal() {
return false;
}
public int getRowSpacingBefore(int rowIndex) {
int childIndex = hasHeaderRow() ? rowIndex-1 : rowIndex;
return getSpacingBefore(childIndex);
}
public TableLayout withRowSpacing(int spacing) {
withSpacing(spacing);
return this;
}
public TableLayout withColumnSpacing(int spacing) {
this.columnSpacing = spacing;
return this;
}
public void updateColumns() {
int columns = 0;
for (int rowIndex=0; rowIndex<rows(); rowIndex++)
columns = Math.max(columns, getRowColumns(rowIndex));
this.columns = columns;
}
public int columns() {
return columns;
}
public int rows() {
return childFigure.length;
}
public boolean isRowVisible(int rowIndex) {
return getRow(rowIndex).isVisible();
}
public int firstRowVisible() {
for (int i=0, size=rows(); i<size; i++)
if (isRowVisible(i))
return i;
return 0;
}
public int lastRowVisible() {
for (int i=rows()-1; i>=0; i--)
if (isRowVisible(i))
return i;
return rows()-1;
}
private boolean tableInlining;
public TableLayout withTableInlining(boolean value) {
tableInlining = value;
return this;
}
@Override
public ITabularLayoutClient getTabularLayoutClient() {
return tableInlining ? this : ITabularLayoutClient.NULL_TABULAR_LAYOUT_CLIENT;
}
@Override
public ITabularLayoutServer getTabularLayoutServer() {
return this;
}
private ITabularLayoutServer myTabularLayoutServer;
public ITabularLayoutServer getMyTabularLayoutServer() {
if (myTabularLayoutServer == null) {
myTabularLayoutServer = new ITabularLayoutServer() {
public void invalidateTable() {
}
public int getColumnWidth(int columnIndex) {
return getPreferredColumnWidth(columnIndex - getStartingCellIndex());
}
public int getColumnSpacingBefore(int columnIndex) {
return getPreferredColumnSpacingBefore(columnIndex - getStartingCellIndex());
}
public Alignment getColumnAlignment(int columnIndex) {
int childIndex = columnIndex - getStartingCellIndex();
return childIndex < columnAlignment.length && columnAlignment[childIndex] != null ? columnAlignment[childIndex] : defaultColumnAlignment;
}
};
}
return myTabularLayoutServer;
}
public void updateMyTabularLayoutServer(IFigure container) {
if (tableInlining) {
IFigure parent = container.getParent();
ITabularLayoutServer tls = (parent instanceof IEntityFigure) ? ((IEntityFigure) parent).getTabularLayoutServer() : null;
if (tls != null)
myTabularLayoutServer = tls;
}
}
public void invalidateTable() {
invalidate();
getMyTabularLayoutServer().invalidateTable();
}
public int getColumnWidth(int columnIndex) {
return Math.max(getMyTabularLayoutServer().getColumnWidth(getStartingCellIndex()+columnIndex), getPreferredColumnWidth(columnIndex));
}
public int getColumnSpacingBefore(int columnIndex) {
return Math.max(getMyTabularLayoutServer().getColumnSpacingBefore(getStartingCellIndex()+columnIndex), getPreferredColumnSpacingBefore(columnIndex));
}
public Alignment getColumnAlignment(int columnIndex) {
return getMyTabularLayoutServer().getColumnAlignment(getStartingCellIndex()+columnIndex);
}
public int getPreferredColumnWidth(int columnIndex) {
return columnWidth == null || columnIndex >= columnWidth.length ? 0 : columnWidth[columnIndex];
}
public int getPreferredColumnSpacingBefore(int columnIndex) {
int sp = columnSpacingBefore != null && columnIndex < columnSpacingBefore.length ? columnSpacingBefore[columnIndex] : 0;
return sp != 0 ? sp : columnSpacing;
}
public IEntityFigure getRow(int rowIndex) {
return (IEntityFigure) childFigure[rowIndex];
}
public int getRowColumns(int rowIndex) {
return Math.max(getRow(rowIndex).getTabularLayoutClient().getCells(), 1);
}
public Rectangle getCellBounds(int columnIndex, int rowIndex) {
Rectangle bounds = getRow(rowIndex).getTabularLayoutClient().getCellBounds(columnIndex);
return bounds == IEntityFigure.PLACE_HOLDER_BOUNDS ? bounds : bounds.getTranslated(-getColumnSpacingBefore(columnIndex)/2, -getRowSpacingBefore(rowIndex)/2).resize((getColumnSpacingBefore(columnIndex)+getColumnSpacingBefore(columnIndex+1))/2, (getRowSpacingBefore(rowIndex)+getRowSpacingBefore(rowIndex+1))/2);
}
public Rectangle getColumnBounds(int columnIndex) {
Rectangle columnBounds = new Rectangle();
columnBounds.x = Integer.MAX_VALUE;
columnBounds.y = getRowBounds(firstRowVisible()).y;
columnBounds.height = getRowBounds(lastRowVisible()).bottom()-columnBounds.y;
for (int rowIndex=0; rowIndex<rows(); rowIndex++)
if (isRowVisible(rowIndex)) {
Rectangle cellBounds = getCellBounds(columnIndex, rowIndex);
if (cellBounds.width > 0) {
columnBounds.x = Math.min(columnBounds.x, cellBounds.x);
columnBounds.width = Math.max(columnBounds.width, cellBounds.width);
}
}
return columnBounds;
}
public Rectangle getRowBounds(int rowIndex) {
if (columns() == 1) {
Rectangle r = getRow(rowIndex).getBounds().getCopy();
for (int i=0; i<rows(); i++) {
Rectangle cellBounds = getCellBounds(0, i);
r.width = Math.max(r.width, cellBounds.width);
}
return r.translate(-getColumnSpacingBefore(1)/2, -getRowSpacingBefore(rowIndex)/2).resize(getColumnSpacingBefore(1), (getRowSpacingBefore(rowIndex)+getRowSpacingBefore(rowIndex+1))/2);
} else
return getRow(rowIndex).getBounds()
.getTranslated(-getColumnSpacingBefore(1)/2, -getRowSpacingBefore(rowIndex)/2).resize(getColumnSpacingBefore(1), (getRowSpacingBefore(rowIndex)+getRowSpacingBefore(rowIndex+1))/2);
}
protected void setPrefSize(int wHint, int hHint) {
updateColumns();
boolean isInvalid = false;
columnWidth = new int[columns()];
columnSpacingBefore = new int[columns()+1];
for (int rowIndex=0; rowIndex<rows(); rowIndex++) {
IEntityFigure row = getRow(rowIndex);
if (row.isVisible()) {
ITabularLayoutClient tabularLayoutClient = row.getTabularLayoutClient();
for (int columnIndex=0; columnIndex<columns(); columnIndex++) {
int width = tabularLayoutClient.getPreferredCellSize(columnIndex, wHint, hHint).width;
if (width > columnWidth[columnIndex]) {
columnWidth[columnIndex] = width;
if (rowIndex>0)
isInvalid = true;
}
int sp = tabularLayoutClient.getPreferredCellSpacingBefore(columnIndex);
if (sp > columnSpacingBefore[columnIndex]) {
columnSpacingBefore[columnIndex] = sp;
if (rowIndex>0)
isInvalid = true;
}
}
int sp = tabularLayoutClient.getPreferredCellSpacingBefore(columns());
if (sp > columnSpacingBefore[columns()]) {
columnSpacingBefore[columns()] = sp;
isInvalid = true;
}
}
}
for (int rowIndex=0; rowIndex<rows(); rowIndex++) {
IEntityFigure row = getRow(rowIndex);
if (row.isVisible()) {
ITabularLayoutClient tabularLayoutClient = row.getTabularLayoutClient();
tabularLayoutClient.invalidateCells();
}
}
}
protected BaselinedDimension calculateSize(IFigure container, int wHint, int hHint, boolean preferred) {
updateMyTabularLayoutServer(container);
return super.calculateSize(container, wHint, hHint, preferred);
}
@Override
protected boolean calculateChildrenSize(int wHint, int hHint, boolean preferred) {
super.calculateChildrenSize(wHint, hHint, preferred);
setPrefSize(wHint, hHint);
return super.calculateChildrenSize(wHint, hHint, preferred);
}
protected void setAscentDescentWidth(int wHint, int hHint) {
figWidth = 0;
boolean isFirst = true;
int figHeight = 0;
for (int i=0; i<rows(); i++)
if (isRowVisible(i)) {
figWidth = Math.max(figWidth, childSize[i].width);
figHeight += (isFirst ? 0 : getRowSpacingBefore(i)) + childSize[i].height;
isFirst = false;
}
figAscent = getAscent(figHeight);
figDescent = figHeight-figAscent;
}
protected int getAscent(int height) {
return height/2;
}
protected void setLocation(Rectangle area, int[] x, int[] y) {
int xi = calculateXLocation(area, getMinorAlignment());
int yi = calculateYLocation(area, getMajorAlignment());
for (int i=0; i<rows(); i++) {
x[i] = xi + (getMinorAlignment() == Alignment.CENTER ? -childSize[i].width/2 : 0);
y[i] = yi;
if (isRowVisible(i))
yi += childSize[i].height + getRowSpacingBefore(i+1);
}
}
}