/*
* This file is part of lanterna (http://code.google.com/p/lanterna/).
*
* lanterna 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.
*
* This program 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 this program. If not, see
* You use the {@code GridLayout} by specifying a number of columns you want your grid to have and then when you add
* components, you assign {@code LayoutData} to these components using the different static methods in this class
* ({@code createLayoutData(..)}). You can set components to span both rows and columns, as well as defining how to
* distribute the available space.
*/
public class GridLayout implements LayoutManager {
/**
* The enum is used to specify where in a grid cell a component should be placed, in the case that the preferred
* size of the component is smaller than the space in the cell. This class will generally use two alignments, one
* for horizontal and one for vertical.
*/
public enum Alignment {
/**
* Place the component at the start of the cell (horizontally or vertically) and leave whatever space is left
* after the preferred size empty.
*/
BEGINNING,
/**
* Place the component at the middle of the cell (horizontally or vertically) and leave the space before and
* after empty.
*/
CENTER,
/**
* Place the component at the end of the cell (horizontally or vertically) and leave whatever space is left
* before the preferred size empty.
*/
END,
/**
* Force the component to be the same size as the table cell
*/
FILL,
;
}
static class GridLayoutData implements LayoutData {
final Alignment horizontalAlignment;
final Alignment verticalAlignment;
final boolean grabExtraHorizontalSpace;
final boolean grabExtraVerticalSpace;
final int horizontalSpan;
final int verticalSpan;
private GridLayoutData(
Alignment horizontalAlignment,
Alignment verticalAlignment,
boolean grabExtraHorizontalSpace,
boolean grabExtraVerticalSpace,
int horizontalSpan,
int verticalSpan) {
if(horizontalSpan < 1 || verticalSpan < 1) {
throw new IllegalArgumentException("Horizontal/Vertical span must be 1 or greater");
}
this.horizontalAlignment = horizontalAlignment;
this.verticalAlignment = verticalAlignment;
this.grabExtraHorizontalSpace = grabExtraHorizontalSpace;
this.grabExtraVerticalSpace = grabExtraVerticalSpace;
this.horizontalSpan = horizontalSpan;
this.verticalSpan = verticalSpan;
}
}
private static GridLayoutData DEFAULT = new GridLayoutData(
Alignment.BEGINNING,
Alignment.BEGINNING,
false,
false,
1,
1);
/**
* Creates a layout data object for {@code GridLayout}:s that specify the horizontal and vertical alignment for the
* component in case the cell space is larger than the preferred size of the component
* @param horizontalAlignment Horizontal alignment strategy
* @param verticalAlignment Vertical alignment strategy
* @return The layout data object containing the specified alignments
*/
public static LayoutData createLayoutData(Alignment horizontalAlignment, Alignment verticalAlignment) {
return createLayoutData(horizontalAlignment, verticalAlignment, false, false);
}
/**
* Creates a layout data object for {@code GridLayout}:s that specify the horizontal and vertical alignment for the
* component in case the cell space is larger than the preferred size of the component. This method also has fields
* for indicating that the component would like to take more space if available to the container. For example, if
* the container is assigned is assigned an area of 50x15, but all the child components in the grid together only
* asks for 40x10, the remaining 10 columns and 5 rows will be empty. If just a single component asks for extra
* space horizontally and/or vertically, the grid will expand out to fill the entire area and the text space will be
* assigned to the component that asked for it.
*
* @param horizontalAlignment Horizontal alignment strategy
* @param verticalAlignment Vertical alignment strategy
* @param grabExtraHorizontalSpace If set to {@code true}, this component will ask to be assigned extra horizontal
* space if there is any to assign
* @param grabExtraVerticalSpace If set to {@code true}, this component will ask to be assigned extra vertical
* space if there is any to assign
* @return The layout data object containing the specified alignments and size requirements
*/
public static LayoutData createLayoutData(
Alignment horizontalAlignment,
Alignment verticalAlignment,
boolean grabExtraHorizontalSpace,
boolean grabExtraVerticalSpace) {
return createLayoutData(horizontalAlignment, verticalAlignment, grabExtraHorizontalSpace, grabExtraVerticalSpace, 1, 1);
}
/**
* Creates a layout data object for {@code GridLayout}:s that specify the horizontal and vertical alignment for the
* component in case the cell space is larger than the preferred size of the component. This method also has fields
* for indicating that the component would like to take more space if available to the container. For example, if
* the container is assigned is assigned an area of 50x15, but all the child components in the grid together only
* asks for 40x10, the remaining 10 columns and 5 rows will be empty. If just a single component asks for extra
* space horizontally and/or vertically, the grid will expand out to fill the entire area and the text space will be
* assigned to the component that asked for it. It also puts in data on how many rows and/or columns the component
* should span.
*
* @param horizontalAlignment Horizontal alignment strategy
* @param verticalAlignment Vertical alignment strategy
* @param grabExtraHorizontalSpace If set to {@code true}, this component will ask to be assigned extra horizontal
* space if there is any to assign
* @param grabExtraVerticalSpace If set to {@code true}, this component will ask to be assigned extra vertical
* space if there is any to assign
* @param horizontalSpan How many "cells" this component wants to span horizontally
* @param verticalSpan How many "cells" this component wants to span vertically
* @return The layout data object containing the specified alignments, size requirements and cell spanning
*/
public static LayoutData createLayoutData(
Alignment horizontalAlignment,
Alignment verticalAlignment,
boolean grabExtraHorizontalSpace,
boolean grabExtraVerticalSpace,
int horizontalSpan,
int verticalSpan) {
return new GridLayoutData(
horizontalAlignment,
verticalAlignment,
grabExtraHorizontalSpace,
grabExtraVerticalSpace,
horizontalSpan,
verticalSpan);
}
/**
* This is a shortcut method that will create a grid layout data object that will expand its cell as much as is can
* horizontally and make the component occupy the whole area horizontally and center it vertically
* @param horizontalSpan How many cells to span horizontally
* @return Layout data object with the specified span and horizontally expanding as much as it can
*/
public static LayoutData createHorizontallyFilledLayoutData(int horizontalSpan) {
return createLayoutData(
Alignment.FILL,
Alignment.CENTER,
true,
false,
horizontalSpan,
1);
}
/**
* This is a shortcut method that will create a grid layout data object that will expand its cell as much as is can
* vertically and make the component occupy the whole area vertically and center it horizontally
* @param horizontalSpan How many cells to span vertically
* @return Layout data object with the specified span and vertically expanding as much as it can
*/
public static LayoutData createHorizontallyEndAlignedLayoutData(int horizontalSpan) {
return createLayoutData(
Alignment.END,
Alignment.CENTER,
true,
false,
horizontalSpan,
1);
}
private final int numberOfColumns;
private int horizontalSpacing;
private int verticalSpacing;
private int topMarginSize;
private int bottomMarginSize;
private int leftMarginSize;
private int rightMarginSize;
private boolean changed;
/**
* Creates a new {@code GridLayout} with the specified number of columns. Initially, this layout will have a
* horizontal spacing of 1 and vertical spacing of 0, with a left and right margin of 1.
* @param numberOfColumns Number of columns in this grid
*/
public GridLayout(int numberOfColumns) {
this.numberOfColumns = numberOfColumns;
this.horizontalSpacing = 1;
this.verticalSpacing = 0;
this.topMarginSize = 0;
this.bottomMarginSize = 0;
this.leftMarginSize = 1;
this.rightMarginSize = 1;
this.changed = true;
}
/**
* Returns the horizontal spacing, i.e. the number of empty columns between each cell
* @return Horizontal spacing
*/
public int getHorizontalSpacing() {
return horizontalSpacing;
}
/**
* Sets the horizontal spacing, i.e. the number of empty columns between each cell
* @param horizontalSpacing New horizontal spacing
* @return Itself
*/
public GridLayout setHorizontalSpacing(int horizontalSpacing) {
if(horizontalSpacing < 0) {
throw new IllegalArgumentException("Horizontal spacing cannot be less than 0");
}
this.horizontalSpacing = horizontalSpacing;
this.changed = true;
return this;
}
/**
* Returns the vertical spacing, i.e. the number of empty columns between each row
* @return Vertical spacing
*/
public int getVerticalSpacing() {
return verticalSpacing;
}
/**
* Sets the vertical spacing, i.e. the number of empty columns between each row
* @param verticalSpacing New vertical spacing
* @return Itself
*/
public GridLayout setVerticalSpacing(int verticalSpacing) {
if(verticalSpacing < 0) {
throw new IllegalArgumentException("Vertical spacing cannot be less than 0");
}
this.verticalSpacing = verticalSpacing;
this.changed = true;
return this;
}
/**
* Returns the top margin, i.e. number of empty rows above the first row in the grid
* @return Top margin, in number of rows
*/
public int getTopMarginSize() {
return topMarginSize;
}
/**
* Sets the top margin, i.e. number of empty rows above the first row in the grid
* @param topMarginSize Top margin, in number of rows
* @return Itself
*/
public GridLayout setTopMarginSize(int topMarginSize) {
if(topMarginSize < 0) {
throw new IllegalArgumentException("Top margin size cannot be less than 0");
}
this.topMarginSize = topMarginSize;
this.changed = true;
return this;
}
/**
* Returns the bottom margin, i.e. number of empty rows below the last row in the grid
* @return Bottom margin, in number of rows
*/
public int getBottomMarginSize() {
return bottomMarginSize;
}
/**
* Sets the bottom margin, i.e. number of empty rows below the last row in the grid
* @param bottomMarginSize Bottom margin, in number of rows
* @return Itself
*/
public GridLayout setBottomMarginSize(int bottomMarginSize) {
if(bottomMarginSize < 0) {
throw new IllegalArgumentException("Bottom margin size cannot be less than 0");
}
this.bottomMarginSize = bottomMarginSize;
this.changed = true;
return this;
}
/**
* Returns the left margin, i.e. number of empty columns left of the first column in the grid
* @return Left margin, in number of columns
*/
public int getLeftMarginSize() {
return leftMarginSize;
}
/**
* Sets the left margin, i.e. number of empty columns left of the first column in the grid
* @param leftMarginSize Left margin, in number of columns
* @return Itself
*/
public GridLayout setLeftMarginSize(int leftMarginSize) {
if(leftMarginSize < 0) {
throw new IllegalArgumentException("Left margin size cannot be less than 0");
}
this.leftMarginSize = leftMarginSize;
this.changed = true;
return this;
}
/**
* Returns the right margin, i.e. number of empty columns right of the last column in the grid
* @return Right margin, in number of columns
*/
public int getRightMarginSize() {
return rightMarginSize;
}
/**
* Sets the right margin, i.e. number of empty columns right of the last column in the grid
* @param rightMarginSize Right margin, in number of columns
* @return Itself
*/
public GridLayout setRightMarginSize(int rightMarginSize) {
if(rightMarginSize < 0) {
throw new IllegalArgumentException("Right margin size cannot be less than 0");
}
this.rightMarginSize = rightMarginSize;
this.changed = true;
return this;
}
@Override
public boolean hasChanged() {
return this.changed;
}
@Override
public TerminalSize getPreferredSize(List