/**
* 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.Arrays;
import org.eclipse.draw2d.geometry.Rectangle;
import org.whole.lang.util.CompositeUtils;
/**
* @author Riccardo Solmi
*/
public class GridLayout extends AbstractCompositeEntityLayout {
int figIndent;
{
withMajorAlignment(Alignment.MATHLINE);
withMinorAlignment(Alignment.LEADING);
}
private int maxRows = -1;
private int maxColumns = -1;
private int[] cellX;
private int[] cellY;
private int[] columnIndent;
private int[] columnRightIndent;
private int[] rowAscent;
private int[] rowDescent;
private Alignment[] rowAlignment = new Alignment[0];
private Alignment[] columnAlignment = new Alignment[0];
private int[] rowSpacing = new int[0];
private int[] columnSpacing = new int[0];
private int defaultColumnSpacing;
public GridLayout withMaxRows(int maxRows) {
this.maxRows = maxRows;
return this;
}
public GridLayout withMaxColumns(int maxColumns) {
this.maxColumns = maxColumns;
return this;
}
public GridLayout withRowAlignment(int rowIndex, Alignment alignment) {
rowAlignment = CompositeUtils.grow(rowAlignment, rowIndex+1, null);
rowAlignment[rowIndex] = alignment;
return this;
}
public GridLayout withRowAlignment(Alignment defaultAlignment) {
withMajorAlignment(defaultAlignment);
return this;
}
public Alignment getRowAlignment(int rowIndex) {
return rowIndex < rowAlignment.length && rowAlignment[rowIndex] != null ?
rowAlignment[rowIndex] : getRowAlignment();
}
public Alignment getRowAlignment() {
return getMajorAlignment();
}
public GridLayout withColumnAlignment(int columnIndex, Alignment alignment) {
columnAlignment = CompositeUtils.grow(columnAlignment, columnIndex+1, null);
columnAlignment[columnIndex] = alignment;
return this;
}
public GridLayout withColumnAlignment(Alignment defaultAlignment) {
withMinorAlignment(defaultAlignment);
return this;
}
public Alignment getColumnAlignment(int columnIndex) {
return columnIndex < columnAlignment.length && columnAlignment[columnIndex] != null ?
columnAlignment[columnIndex] : getColumnAlignment();
}
public Alignment getColumnAlignment() {
return getMinorAlignment();
}
public GridLayout withRowSpacingAfter(int rowIndex, int spacing) {
rowSpacing = CompositeUtils.grow(rowSpacing, rowIndex+1, -1);
rowSpacing[rowIndex] = spacing;
return this;
}
public GridLayout withRowSpacing(int spacing) {
withSpacing(spacing);
return this;
}
public int getRowSpacingAfter(int rowIndex) {
return rowIndex < rowSpacing.length && rowSpacing[rowIndex] != -1 ?
rowSpacing[rowIndex] : getRowSpacing();
}
public int getRowSpacing() {
return getSpacing();
}
public GridLayout withColumnSpacingAfter(int columnIndex, int spacing) {
columnSpacing = CompositeUtils.grow(columnSpacing, columnIndex+1, -1);
columnSpacing[columnIndex] = spacing;
return this;
}
public GridLayout withColumnSpacing(int spacing) {
this.defaultColumnSpacing = spacing;
return this;
}
public int getColumnSpacingAfter(int columnIndex) {
return columnIndex < columnSpacing.length && columnSpacing[columnIndex] != -1 ?
columnSpacing[columnIndex] : getColumnSpacing();
}
public int getColumnSpacing() {
return defaultColumnSpacing;
}
public int rows() {
return rowAscent != null ? rowAscent.length : 0;
}
public int columns() {
return columnIndent != null ? columnIndent.length : 0;
}
public Rectangle cellBounds(int c, int r) {
Rectangle rectangle = Rectangle.SINGLETON;
rectangle.setLocation(cellX[c], cellY[r]);
rectangle.setSize(
columnIndent[c]+columnRightIndent[c],
rowAscent[r]+rowDescent[r]);
return rectangle;
}
public Rectangle columnBounds(int c) {
Rectangle rectangle = Rectangle.SINGLETON;
rectangle.setLocation(cellX[c], cellY[0]);
rectangle.setSize(
columnIndent[c]+columnRightIndent[c],
figAscent+figDescent);
return rectangle;
}
public Rectangle rowBounds(int r) {
Rectangle rectangle = Rectangle.SINGLETON;
rectangle.setLocation(cellX[0], cellY[r]);
rectangle.setSize(
figWidth,//TODO figIndent + figRightIndent
rowAscent[r]+rowDescent[r]);
return rectangle;
}
protected void initData(int childrenSize) {
int rows = 0;
int columns = 0;
//FIXME visibility of rows and columns
if (childrenSize > 0) {
if (maxRows > 0) {
rows = Math.min(maxRows, childrenSize);
columns = childrenSize / rows + (childrenSize % rows == 0 ? 0 : 1);
} else if (maxColumns > 0) {
columns = Math.min(maxColumns, childrenSize);
rows = childrenSize / columns + (childrenSize % columns == 0 ? 0 : 1);
} else {
rows = (int) Math.sqrt(childrenSize);
columns = childrenSize / rows + (childrenSize % rows == 0 ? 0 : 1);
}
}
if (cellX == null || cellY == null) {
cellX = new int[columns];
cellY = new int[rows];
}
columnIndent = new int[columns];
columnRightIndent = new int[columns];
rowAscent = new int[rows];
rowDescent = new int[rows];
}
public boolean isHorizontal() {
return false;
}
@Override
protected int getIndent() {
return figIndent;
}
@Override
protected boolean calculateChildrenSize(int wHint, int hHint, boolean preferred) {
int figHeight = 0;
boolean hintsSensitive = false;
int size = childFigure.length;
initData(size);
childSize = new BaselinedDimension[size];
for (int i=0,c=0,r=0; i<size; i++)
if (childFigure[i].isVisible()) {
c = i / rows();
r = i % rows();
childSize[i] = getChildSize(childFigure[i], wHint,
hHint < 0 ? hHint : Math.max(0, hHint-figHeight), preferred);
hintsSensitive |= childSize[i].hintsSensitive;
int ci = childSize[i].getIndent();
//FIXME f(alignment)
columnIndent[c] = Math.max(columnIndent[c], ci);
columnRightIndent[c] = Math.max(columnRightIndent[c], childSize[i].width-ci);
rowAscent[r] = Math.max(rowAscent[r], childSize[i].getAscent());
rowDescent[r] = Math.max(rowDescent[r], childSize[i].getDescent());
}
figWidth =
Arrays.stream(columnIndent).sum() +
Arrays.stream(columnRightIndent).sum() +
getColumnSpacing()*(columns()-1);//FIXME
figHeight =
Arrays.stream(rowAscent).sum() +
Arrays.stream(rowDescent).sum() +
getRowSpacing()*(rows()-1);//FIXME
//TODO add withAscentAlignment, withAscentTarget and withIndentAlignment
figAscent = figHeight / 2;
figDescent = figHeight - figAscent;
figIndent = figWidth / 2;
//FIXME hints
// if (hHint >= 0 && figHeight > hHint)
// calculateChildrenSize(wHint, figHeight, preferred);
return hintsSensitive;
}
@Override
protected void setAscentDescentWidth(int wHint, int hHint) {}
protected void setLocation(Rectangle area, int[] x, int[] y) {
int children = childSize.length;
assert children == x.length;
cellX = new int[columns()];
cellY = new int[rows()];
for (int i=0,c=0,r=0; i<children; i++)
if (isChildVisible(i)) {
c = i / rows();
r = i % rows();
int columnIndentSum=0;
int columnRightIndentSum=0;
int columnSpacingSum=0;
int rowAscentSum=0;
int rowDescentSum=0;
int rowSpacingSum=0;
//FIXME visibility row/column
for (int j=0; j<c; j++) {
columnIndentSum += columnIndent[j];
columnRightIndentSum += columnRightIndent[j];
columnSpacingSum += getColumnSpacingAfter(j);
}
for (int j=0; j<r; j++) {
rowAscentSum += rowAscent[j];
rowDescentSum += rowDescent[j];
rowSpacingSum += getRowSpacingAfter(j);
}
cellX[c] = area.x + columnIndentSum + columnRightIndentSum + columnSpacingSum;
// Arrays.stream(columnIndent).limit(c).sum() +
// Arrays.stream(columnRightIndent).limit(c).sum() +
// getSpacing()*c;
cellY[r] = area.y + rowAscentSum + rowDescentSum + rowSpacingSum;
// Arrays.stream(rowAscent).limit(r).sum() +
// Arrays.stream(rowDescent).limit(r).sum() +
// getSpacing()*r;
switch (getColumnAlignment(c)) {
case FILL:
childSize[i].width = columnIndent[c]+columnRightIndent[c];
case LEADING:
x[i] = cellX[c];
break;
case MATHLINE:
x[i] = cellX[c] + columnIndent[c]-indent(i);
break;
case CENTER:
x[i] = cellX[c] + (columnIndent[c]+columnRightIndent[c] - childSize[i].width)/2;
break;
case TRAILING:
x[i] = cellX[c] + columnIndent[c]+columnRightIndent[c] - childSize[i].width;
break;
}
switch (getRowAlignment(r)) {
case FILL:
childSize[i].height = rowAscent[r]+rowDescent[r];
case LEADING:
y[i] = cellY[r];
break;
case MATHLINE:
y[i] = cellY[r] + rowAscent[r]-ascent(i);
break;
case CENTER:
y[i] = cellY[r] + (rowAscent[r]+rowDescent[r] - childSize[i].height)/2;
break;
case TRAILING:
y[i] = cellY[r] + rowAscent[r]+rowDescent[r] -ascent(i)-descent(i);
break;
}
}
}
}