package ch.sahits.game.graphic.layout;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.Rectangle;
import java.util.Hashtable;
import ch.sahits.game.graphic.display.ISahitsComponent;
import ch.sahits.game.graphic.display.ISahitsContainer;
import ch.sahits.game.graphic.display.ISahitsLayoutConstraint;
import ch.sahits.game.graphic.display.ISahitsLayoutManager;
/**
* Grid layout that sizes the cells according the the biggest
* sized ISahitsComponent in the same row/column. If the ISahitsComponent does
* not fill the whole size of the cell it is by default positioned in the
* center.
* @author Andi Hotz, (c) Sahits GmbH, 2011
* Created on May 27, 2011
*
*/
public class SahitsMaximalGridLayout implements ISahitsLayoutManager {
private final int nbCols;
private final int nbRows;
private final int[] maxWidth;
private final int[] maxHight;
private final int gap;
private final ECellPosition position;
private final Hashtable<ISahitsComponent, MaximalGridLayoutConstraints> compTable;
/**
* Initialize the layout with the number of columns and rows
* @param cols
* @param rows
*/
public SahitsMaximalGridLayout(int cols, int rows) {
this(cols, rows, 0, ECellPosition.TOP_LEFT);
}
/**
* Initialize the layout with the number of columns and rows and the gaping between them as
* well as the positioning of the whole if there is any space.
* @param cols
* @param rows
* @param gapWidth
* @param positioning
*/
public SahitsMaximalGridLayout(int cols, int rows,int gapWidth, ECellPosition positioning) {
super();
this.nbCols = cols;
this.nbRows = rows;
gap=gapWidth;
position=positioning;
maxWidth = new int[nbCols];
maxHight = new int[nbRows];
resetSizeCache();
compTable = new Hashtable<ISahitsComponent, MaximalGridLayoutConstraints>();
}
/**
* Default constructor initializing a grid of size 1x1.
*/
public SahitsMaximalGridLayout(){
this(1,1);
}
private void resetSizeCache(){
for (int i = 0; i < maxWidth.length; i++) {
maxWidth[i]=0;
}
for (int i = 0; i < maxWidth.length; i++) {
maxWidth[i]=0;
}
}
/**
* Compute the width of the widest ISahitsComponent in the specified
* column.
* @param column number of the column (starting with index 1)
* @param parent parent ISahitsContainer
* @return
*/
final int computeWidth(int column, ISahitsContainer parent){
if (column>nbCols){
throw new IllegalArgumentException("Layout has only "+nbCols+" columns");
}
if (this.maxWidth[column-1]>0){
return this.maxWidth[column-1];
}
int nComps = parent.getComponentCount();
Dimension d = null;
int maxWidth=0;
for (int i = 0; i < nComps; i++) {
ISahitsComponent c = parent.get(i);
if ( computeColumnIndex(i)==column) {
d = c.getPreferredSize();
if (d.width>maxWidth){
maxWidth=d.width;
}
}
}
this.maxWidth[column-1] = maxWidth;
return maxWidth;
}
/**
* Compute the height of the highest ISahitsComponent in the specified
* row.
* @param row number of the row (starting with index 1)
* @param parent parent ISahitsContainer
* @return
*/
final int computeHeight(int row, ISahitsContainer parent){
if (row>nbRows){
throw new IllegalArgumentException("Layout has only "+nbCols+" columns");
}
if (this.maxHight[row-1]>0){
return this.maxHight[row-1];
}
int nComps = parent.getComponentCount();
Dimension d = null;
int maxHeigth=0;
for (int i = 0; i < nComps; i++) {
ISahitsComponent c = parent.get(i);
if (computeRowIndex(i)==row) {
d = c.getPreferredSize();
if (d.height>maxHeigth){
maxHeigth=d.height;
}
}
}
this.maxHight[row-1] = maxHeigth;
return maxHeigth;
}
/**
* Computing the prefered size by adding the sizes of the columns and rows together with the insets
*/
public Dimension preferredLayoutSize(ISahitsContainer parent) {
Dimension dim = new Dimension(0, 0);
//Always add the ISahitsContainer's insets!
Insets insets = parent.getInsets();
int preferredWidth = 0;
int preferredHeight = 0;
for (int i=1;i<=nbCols;i++){
preferredWidth+=computeWidth(i, parent);
}
for (int i=1;i<=nbRows;i++){
preferredHeight+=computeHeight(i, parent);
}
dim.width = preferredWidth
+ insets.left + insets.right;
dim.height = preferredHeight
+ insets.top + insets.bottom;
return dim;
}
@Override
public void layoutContainer(ISahitsContainer parent) {
final Rectangle bounds = parent.getBounds();
Insets insets = parent.getInsets();
int nComps = parent.getComponentCount();
final double widthScale;
int totalWidth;
if (checkWidth(parent)){
widthScale=1;
totalWidth = addUpColumnWidth(parent);
} else {
widthScale = computeWidthScale(parent);
totalWidth = parent.getWidth();
}
final double heightScale;
int totalHeight;
if (checkHeight(parent)){
heightScale=1;
totalHeight = addUpRowHeight(parent);
} else {
heightScale = computeHeightScale(parent);
totalHeight = parent.getHeight();
}
int w = parent.getWidth();
int h = parent.getHeight();
int globalXPadding = computeXPadding(w, totalWidth, new MaximalGridLayoutConstraints(position));
globalXPadding += bounds.x;
int globalYPadding = computeYPadding(h, totalHeight, new MaximalGridLayoutConstraints(position));
globalYPadding += bounds.y;
for (int i = 0 ; i < nComps ; i++) {
ISahitsComponent c = parent.get(i);
final int columnIndex = computeColumnIndex(i);
final int rowIndex = computeRowIndex(i);
int xColumn = (int) Math.rint(getColumnStart(columnIndex,parent)*widthScale);
xColumn += (columnIndex-1)*gap;
int yRow = (int) Math.rint(getRowStart(rowIndex,parent)*heightScale);
yRow += (rowIndex-1)*gap;
final int columnWidth = (int) Math.rint(computeWidth(columnIndex, parent)*widthScale);
final int rowHeight = (int) Math.rint(computeHeight(rowIndex, parent)*heightScale);
Dimension d = c.getPreferredSize();
d.width = Math.min(d.width,columnWidth);
d.height = Math.min(d.height,rowHeight);
final int xPadding = computeXPadding(columnWidth,d.width,getConstraints(c));
final int yPadding = computeYPadding(rowHeight,d.height,getConstraints(c));
// increase x and y, if appropriate
final int x = insets.left+xColumn+xPadding+globalXPadding;
final int y = insets.top+yRow+yPadding+globalYPadding;
// Set the ISahitsComponent's size and position.
c.setBounds(x, y, d.width, d.height);
}
resetSizeCache();
}
/**
* Compute the scale factor that is needed on the ISahitsComponents to resize them to
* the heigth of the ISahitsContainer
* @param parent ISahitsContainer
* @return scale factor
*/
private double computeHeightScale(ISahitsContainer parent) {
Insets insets = parent.getInsets();
int hight = parent.getHeight()-insets.top-insets.bottom;
int addHeigth = addUpRowHeight(parent);
int constHeight = insets.top+insets.bottom+(nbRows-1)*gap;
return 1.0*hight/(addHeigth-constHeight); // compute the scale factor without the gaps
}
/**
* Compute the scale factor that is needed on the ISahitsComponents to resize them to
* the width of the ISahitsContainer
* @param parent ISahitsContainer
* @return scale factor
*/
private double computeWidthScale(ISahitsContainer parent) {
Insets insets = parent.getInsets();
int width = parent.getWidth()-insets.left-insets.right;
int addWidth = addUpColumnWidth(parent);
int constWidth = insets.left+insets.right+(nbCols-1)*gap;
return 1.0*width/(addWidth-constWidth); // compute the scale factor without the gaps
}
/**
* check if the size of the children add up to more than the parents size
* @param parent ISahitsContainer
* @return true if the height of the parent ISahitsContainer is larger or equal than the sum of
* all the rows plus the parents insets.
*/
private boolean checkHeight(ISahitsContainer parent) {
int heigth = parent.getHeight();
int addHeigth = addUpRowHeight(parent);
return addHeigth<=heigth;
}
/**
* Adding up the row height of a ISahitsContainer. Between each row the gap height is added
* @param parent ISahitsContainer
* @return height of all child ISahitsComponents plus the parents insets
*/
private int addUpRowHeight(ISahitsContainer parent) {
Insets insets = parent.getInsets();
int addHeigth=0;
for (int i=1;i<=nbRows;i++){
addHeigth += computeHeight(i, parent);
if (i<nbRows){
addHeigth += gap;
}
}
addHeigth += insets.top+insets.bottom;
return addHeigth;
}
/**
* check if the size of the children add up to more than the parents size
* @param parent ISahitsContainer
* @return true if the width of the parent ISahitsContainer is larger or equal than the sum of
* all the columns plus the parents insets.
*/
private boolean checkWidth(ISahitsContainer parent) {
int width = parent.getWidth();
int addWidth = addUpColumnWidth(parent);
return addWidth<=width;
}
/**
* Adding up the column width of a ISahitsContainer. Between each column the gap width is added
* @param parent ISahitsContainer
* @return width of all child ISahitsComponents plus the parents insets
*/
private int addUpColumnWidth(ISahitsContainer parent) {
Insets insets = parent.getInsets();
int addWidth=0;
for (int i=1;i<=nbCols;i++){
addWidth += computeWidth(i, parent);
if (i<nbCols){
addWidth += gap;
}
}
addWidth += insets.left+insets.right;
return addWidth;
}
/**
* Compute the vertical padding within a cell based upon the alignment of the ISahitsComponent
* @param rowHeight height of the cell
* @param height of the ISahitsComponent
* @param constraints Objects
* @return padding from the top
*/
final int computeYPadding(int rowHeight, int height,
MaximalGridLayoutConstraints constraints) {
switch (constraints.getAnchor()) {
case TOP_LEFT:
case TOP:
case TOP_RIGHT:
return 0;
case LEFT:
case CENTER:
case RIGHT:
return (rowHeight-height)/2;
default: // bottom
return rowHeight-height;
}
}
/**
* Compute the horizontal padding within a cell based upon the alignment of the ISahitsComponent
* @param columnWidth width of the cell
* @param width of the ISahitsComponent
* @param constraints Object
* @return padding from the left
*/
final int computeXPadding(int columnWidth, int width,
MaximalGridLayoutConstraints constraints) {
switch (constraints.getAnchor()) {
case TOP_LEFT:
case LEFT:
case BOTTOM_LEFT:
return 0;
case TOP:
case CENTER:
case BOTTOM:
return (columnWidth-width)/2;
default: // the right side
return columnWidth-width;
}
}
/**
* Compute the y position where the row with the given index starts
* @param index of the row
* @param parent ISahitsContainer
* @return
*/
final int getRowStart(int index, ISahitsContainer parent) {
int yPadding = 0;
for (int i=1;i<index;i++){
yPadding+=computeHeight(i, parent);
}
return yPadding;
}
/**
* Compute the x position where the column with the given index starts
* @param index of the column
* @param parent ISahitsContainer
* @return
*/
final int getColumnStart(int index, ISahitsContainer parent) {
int xPadding = 0;
for (int i=1;i<index;i++){
xPadding+=computeWidth(i, parent);
}
return xPadding;
}
/**
* Compute the row based on the index of the ISahitsComponent
* @param i index of the ISahitsComponent
* @return
*/
final int computeRowIndex(int i) {
return (i)/nbCols+1;
}
/**
* Compute the column based on the index of the ISahitsComponent
* @param i index of the ISahitsComponent
* @return
*/
final int computeColumnIndex(int i) {
int inter = (i)%nbCols;
return inter+1;
}
/**
* Sets the constraints for the specified ISahitsComponent in this layout.
* @param comp the ISahitsComponent to be modified
* @param constraints the constraints to be applied
*/
public void setConstraints(ISahitsComponent comp,
MaximalGridLayoutConstraints constraints) {
compTable.put(comp, (MaximalGridLayoutConstraints) constraints);
}
/**
* Gets the constraints for the specified ISahitsComponent. A copy of
* the actual <code>GridBagConstraints</code> object is returned.
* @param comp the ISahitsComponent to be queried
* @return the constraint for the specified ISahitsComponent in this
* grid bag layout; a copy of the actual constraint
* object is returned
*/
public MaximalGridLayoutConstraints getConstraints(ISahitsComponent comp) {
MaximalGridLayoutConstraints constraints = compTable.get(comp);
if (constraints == null) {
setConstraints(comp, new MaximalGridLayoutConstraints());
constraints = compTable.get(comp);
}
return (MaximalGridLayoutConstraints) constraints;
}
/**
* Adding a ISahitsComponent with a {@link MaximalGridLayoutConstraints}
*/
@Override
public void addLayoutComponent(ISahitsComponent comp, ISahitsLayoutConstraint constraints) {
if (!(constraints instanceof MaximalGridLayoutConstraints)){
throw new IllegalArgumentException("The constraint must be of type MaximalGridLayoutConstraints");
}
setConstraints(comp, (MaximalGridLayoutConstraints) constraints);
}
}