/* @ITMillApache2LicenseForJavaFiles@ */ package com.vaadin.ui; import java.io.Serializable; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.Map; import java.util.Map.Entry; import com.vaadin.event.LayoutEvents.LayoutClickEvent; import com.vaadin.event.LayoutEvents.LayoutClickListener; import com.vaadin.event.LayoutEvents.LayoutClickNotifier; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.gwt.client.EventId; import com.vaadin.terminal.gwt.client.ui.VGridLayout; /** *
* A container that consists of components with certain coordinates (cell * position) on a grid. It also maintains cursor for adding component in left to * right, top to bottom order. *
* *
* Each component in a GridLayout
uses a certain
* {@link GridLayout.Area area} (column1,row1,column2,row2) from the grid. One
* should not add components that would overlap with the existing components
* because in such case an {@link OverlapsException} is thrown. Adding component
* with cursor automatically extends the grid by increasing the grid height.
*
* Adds a component with a specified area to the grid. The area the new * component should take is defined by specifying the upper left corner * (column1, row1) and the lower right corner (column2, row2) of the area. *
* ** If the new component overlaps with any of the existing components already * present in the grid the operation will fail and an * {@link OverlapsException} is thrown. *
* * @param c * the component to be added. * @param column1 * the column of the upper left corner of the areac
* is supposed to occupy.
* @param row1
* the row of the upper left corner of the area c
is
* supposed to occupy.
* @param column2
* the column of the lower right corner of the area
* c
is supposed to occupy.
* @param row2
* the row of the lower right corner of the area c
* is supposed to occupy.
* @throws OverlapsException
* if the new component overlaps with any of the components
* already in the grid.
* @throws OutOfBoundsException
* if the cells are outside the grid area.
*/
public void addComponent(Component component, int column1, int row1,
int column2, int row2) throws OverlapsException,
OutOfBoundsException {
if (component == null) {
throw new NullPointerException("Component must not be null");
}
// Checks that the component does not already exist in the container
if (components.contains(component)) {
throw new IllegalArgumentException(
"Component is already in the container");
}
// Creates the area
final Area area = new Area(component, column1, row1, column2, row2);
// Checks the validity of the coordinates
if (column2 < column1 || row2 < row1) {
throw new IllegalArgumentException(
"Illegal coordinates for the component");
}
if (column1 < 0 || row1 < 0 || column2 >= cols || row2 >= rows) {
throw new OutOfBoundsException(area);
}
// Checks that newItem does not overlap with existing items
checkExistingOverlaps(area);
// Inserts the component to right place at the list
// Respect top-down, left-right ordering
// component.setParent(this);
final Iterator i = areas.iterator();
int index = 0;
boolean done = false;
while (!done && i.hasNext()) {
final Area existingArea = i.next();
if ((existingArea.row1 >= row1 && existingArea.column1 > column1)
|| existingArea.row1 > row1) {
areas.add(index, area);
components.add(index, component);
done = true;
}
index++;
}
if (!done) {
areas.addLast(area);
components.addLast(component);
}
// Attempt to add to super
try {
super.addComponent(component);
} catch (IllegalArgumentException e) {
areas.remove(area);
components.remove(component);
throw e;
}
// update cursor position, if it's within this area; use first position
// outside this area, even if it's occupied
if (cursorX >= column1 && cursorX <= column2 && cursorY >= row1
&& cursorY <= row2) {
// cursor within area
cursorX = column2 + 1; // one right of area
if (cursorX >= cols) {
// overflowed columns
cursorX = 0; // first col
// move one row down, or one row under the area
cursorY = (column1 == 0 ? row2 : row1) + 1;
} else {
cursorY = row1;
}
}
requestRepaint();
}
/**
* Tests if the given area overlaps with any of the items already on the
* grid.
*
* @param area
* the Area to be checked for overlapping.
* @throws OverlapsException
* if area
overlaps with any existing area.
*/
private void checkExistingOverlaps(Area area) throws OverlapsException {
for (final Iterator i = areas.iterator(); i.hasNext();) {
final Area existingArea = i.next();
if (existingArea.overlaps(area)) {
// Component not added, overlaps with existing component
throw new OverlapsException(existingArea);
}
}
}
/**
* Adds the component into this container to cells column1,row1 (NortWest
* corner of the area.) End coordinates (SouthEast corner of the area) are
* the same as column1,row1. Component width and height is 1.
*
* @param c
* the component to be added.
* @param column
* the column index.
* @param row
* the row index.
* @throws OverlapsException
* if the new component overlaps with any of the components
* already in the grid.
* @throws OutOfBoundsException
* if the cell is outside the grid area.
*/
public void addComponent(Component c, int column, int row)
throws OverlapsException, OutOfBoundsException {
this.addComponent(c, column, row, column, row);
}
/**
* Force the next component to be added to the beginning of the next line.
* By calling this function user can ensure that no more components are
* added to the right of the previous component.
*
* @see #space()
*/
public void newLine() {
cursorX = 0;
cursorY++;
}
/**
* Moves the cursor forwards by one. If the cursor goes out of the right
* grid border, move it to next line.
*
* @see #newLine()
*/
public void space() {
cursorX++;
if (cursorX >= cols) {
cursorX = 0;
cursorY++;
}
}
/**
* Adds the component into this container to the cursor position. If the
* cursor position is already occupied, the cursor is moved forwards to find
* free position. If the cursor goes out from the bottom of the grid, the
* grid is automatically extended.
*
* @param c
* the component to be added.
*/
@Override
public void addComponent(Component component) {
// Finds first available place from the grid
Area area;
boolean done = false;
while (!done) {
try {
area = new Area(component, cursorX, cursorY, cursorX, cursorY);
checkExistingOverlaps(area);
done = true;
} catch (final OverlapsException e) {
space();
}
}
// Extends the grid if needed
cols = cursorX >= cols ? cursorX + 1 : cols;
rows = cursorY >= rows ? cursorY + 1 : rows;
addComponent(component, cursorX, cursorY);
}
/**
* Removes the given component from this container.
*
* @param c
* the component to be removed.
*/
@Override
public void removeComponent(Component component) {
// Check that the component is contained in the container
if (component == null || !components.contains(component)) {
return;
}
Area area = null;
for (final Iterator i = areas.iterator(); area == null
&& i.hasNext();) {
final Area a = i.next();
if (a.getComponent() == component) {
area = a;
}
}
components.remove(component);
if (area != null) {
areas.remove(area);
}
componentToAlignment.remove(component);
super.removeComponent(component);
requestRepaint();
}
/**
* Removes the component specified with it's cell index.
*
* @param column
* the Component's column.
* @param row
* the Component's row.
*/
public void removeComponent(int column, int row) {
// Finds the area
for (final Iterator i = areas.iterator(); i.hasNext();) {
final Area area = i.next();
if (area.getColumn1() == column && area.getRow1() == row) {
removeComponent(area.getComponent());
return;
}
}
}
/**
* Gets an Iterator to the component container contents. Using the Iterator
* it's possible to step through the contents of the container.
*
* @return the Iterator of the components inside the container.
*/
public Iterator* Construct a new area on a grid. *
* * @param component * the component connected to the area. * @param column1 * The column of the upper left corner cell of the area *c
is supposed to occupy.
* @param row1
* The row of the upper left corner cell of the area
* c
is supposed to occupy.
* @param column2
* The column of the lower right corner cell of the area
* c
is supposed to occupy.
* @param row2
* The row of the lower right corner cell of the area
* c
is supposed to occupy.
* @throws OverlapsException
* if the new component overlaps with any of the components
* already in the grid
*/
public Area(Component component, int column1, int row1, int column2,
int row2) {
this.column1 = column1;
this.row1 = row1;
this.column2 = column2;
this.row2 = row2;
this.component = component;
}
/**
* Tests if the given Area overlaps with an another Area.
*
* @param other
* the Another Area that's to be tested for overlap with this
* area.
* @return true
if other
overlaps with this
* area, false
if it doesn't.
*/
public boolean overlaps(Area other) {
return column1 <= other.getColumn2() && row1 <= other.getRow2()
&& column2 >= other.getColumn1() && row2 >= other.getRow1();
}
/**
* Gets the component connected to the area.
*
* @return the Component.
*/
public Component getComponent() {
return component;
}
/**
* Sets the component connected to the area.
*
* * This function only sets the value in the datastructure and does not * send any events or set parents. *
* * @param newComponent * the new connected overriding the existing one. */ protected void setComponent(Component newComponent) { component = newComponent; } /** * @deprecated Use getColumn1() instead. * * @see com.vaadin.ui.GridLayout#getColumn1() */ @Deprecated public int getX1() { return getColumn1(); } /** * Gets the column of the top-left corner cell. * * @return the column of the top-left corner cell. */ public int getColumn1() { return column1; } /** * @deprecated Use getColumn2() instead. * * @see com.vaadin.ui.GridLayout#getColumn2() */ @Deprecated public int getX2() { return getColumn2(); } /** * Gets the column of the bottom-right corner cell. * * @return the column of the bottom-right corner cell. */ public int getColumn2() { return column2; } /** * @deprecated Use getRow1() instead. * * @see com.vaadin.ui.GridLayout#getRow1() */ @Deprecated public int getY1() { return getRow1(); } /** * Gets the row of the top-left corner cell. * * @return the row of the top-left corner cell. */ public int getRow1() { return row1; } /** * @deprecated Use getRow2() instead. * * @see com.vaadin.ui.GridLayout#getRow2() */ @Deprecated public int getY2() { return getRow2(); } /** * Gets the row of the bottom-right corner cell. * * @return the row of the bottom-right corner cell. */ public int getRow2() { return row2; } } /** * Gridlayout does not support laying components on top of each other. An *OverlapsException
is thrown when a component already exists
* (even partly) at the same space on a grid with the new component.
*
* @author IT Mill Ltd.
* @version
* @VERSION@
* @since 3.0
*/
public class OverlapsException extends java.lang.RuntimeException {
private final Area existingArea;
/**
* Constructs an OverlapsException
.
*
* @param existingArea
*/
public OverlapsException(Area existingArea) {
this.existingArea = existingArea;
}
@Override
public String getMessage() {
StringBuilder sb = new StringBuilder();
Component component = existingArea.getComponent();
sb.append(component);
sb.append("( type = ");
sb.append(component.getClass().getName());
if (component.getCaption() != null) {
sb.append(", caption = \"");
sb.append(component.getCaption());
sb.append("\"");
}
sb.append(")");
sb.append(" is already added to ");
sb.append(existingArea.column1);
sb.append(",");
sb.append(existingArea.column1);
sb.append(",");
sb.append(existingArea.row1);
sb.append(",");
sb.append(existingArea.row2);
sb.append("(column1, column2, row1, row2).");
return sb.toString();
}
/**
* Gets the area .
*
* @return the existing area.
*/
public Area getArea() {
return existingArea;
}
}
/**
* An Exception
object which is thrown when an area exceeds the
* bounds of the grid.
*
* @author IT Mill Ltd.
* @version
* @VERSION@
* @since 3.0
*/
public class OutOfBoundsException extends java.lang.RuntimeException {
private final Area areaOutOfBounds;
/**
* Constructs an OoutOfBoundsException
with the specified
* detail message.
*
* @param areaOutOfBounds
*/
public OutOfBoundsException(Area areaOutOfBounds) {
this.areaOutOfBounds = areaOutOfBounds;
}
/**
* Gets the area that is out of bounds.
*
* @return the area out of Bound.
*/
public Area getArea() {
return areaOutOfBounds;
}
}
/**
* Sets the number of columns in the grid. The column count can not be
* reduced if there are any areas that would be outside of the shrunk grid.
*
* @param columns
* the new number of columns in the grid.
*/
public void setColumns(int columns) {
// The the param
if (columns < 1) {
throw new IllegalArgumentException(
"The number of columns and rows in the grid must be at least 1");
}
// In case of no change
if (cols == columns) {
return;
}
// Checks for overlaps
if (cols > columns) {
for (final Iterator i = areas.iterator(); i.hasNext();) {
final Area area = i.next();
if (area.column2 >= columns) {
throw new OutOfBoundsException(area);
}
}
}
cols = columns;
requestRepaint();
}
/**
* Get the number of columns in the grid.
*
* @return the number of columns in the grid.
*/
public int getColumns() {
return cols;
}
/**
* Sets the number of rows in the grid. The number of rows can not be
* reduced if there are any areas that would be outside of the shrunk grid.
*
* @param rows
* the new number of rows in the grid.
*/
public void setRows(int rows) {
// The the param
if (rows < 1) {
throw new IllegalArgumentException(
"The number of columns and rows in the grid must be at least 1");
}
// In case of no change
if (this.rows == rows) {
return;
}
// Checks for overlaps
if (this.rows > rows) {
for (final Iterator i = areas.iterator(); i.hasNext();) {
final Area area = i.next();
if (area.row2 >= rows) {
throw new OutOfBoundsException(area);
}
}
}
this.rows = rows;
requestRepaint();
}
/**
* Get the number of rows in the grid.
*
* @return the number of rows in the grid.
*/
public int getRows() {
return rows;
}
/**
* Gets the current cursor x-position. The cursor position points the
* position for the next component that is added without specifying its
* coordinates (grid cell). When the cursor position is occupied, the next
* component will be added to first free position after the cursor.
*
* @return the grid column the Cursor is on.
*/
public int getCursorX() {
return cursorX;
}
/**
* Sets the current cursor x-position. This is usually handled automatically
* by GridLayout.
*
* @param cursorX
*/
public void setCursorX(int cursorX) {
this.cursorX = cursorX;
}
/**
* Gets the current cursor y-position. The cursor position points the
* position for the next component that is added without specifying its
* coordinates (grid cell). When the cursor position is occupied, the next
* component will be added to first free position after the cursor.
*
* @return the grid row the Cursor is on.
*/
public int getCursorY() {
return cursorY;
}
/**
* Sets the current cursor y-position. This is usually handled automatically
* by GridLayout.
*
* @param cursorY
*/
public void setCursorY(int cursorY) {
this.cursorY = cursorY;
}
/* Documented in superclass */
public void replaceComponent(Component oldComponent, Component newComponent) {
// Gets the locations
Area oldLocation = null;
Area newLocation = null;
for (final Iterator i = areas.iterator(); i.hasNext();) {
final Area location = i.next();
final Component component = location.getComponent();
if (component == oldComponent) {
oldLocation = location;
}
if (component == newComponent) {
newLocation = location;
}
}
if (oldLocation == null) {
addComponent(newComponent);
} else if (newLocation == null) {
removeComponent(oldComponent);
addComponent(newComponent, oldLocation.getColumn1(),
oldLocation.getRow1(), oldLocation.getColumn2(),
oldLocation.getRow2());
} else {
oldLocation.setComponent(newComponent);
newLocation.setComponent(oldComponent);
requestRepaint();
}
}
/*
* Removes all components from this container.
*
* @see com.vaadin.ui.ComponentContainer#removeAllComponents()
*/
@Override
public void removeAllComponents() {
super.removeAllComponents();
componentToAlignment = new HashMap* If the last row is removed then all remaining components will be removed * and the grid will be reduced to one row. The cursor will be moved to the * upper left cell of the grid. *
* * @param row * The row number to remove */ public void removeRow(int row) { if (row >= rows) { throw new IllegalArgumentException("Cannot delete row " + row + " from a gridlayout with height " + rows); } // Remove all components in row for (int col = 0; col < getColumns(); col++) { removeComponent(col, row); } // Shrink or remove areas in the selected row for (Iterator i = areas.iterator(); i.hasNext();) { Area existingArea = i.next(); if (existingArea.row2 >= row) { existingArea.row2--; if (existingArea.row1 > row) { existingArea.row1--; } } } if (rows == 1) { /* * Removing the last row means that the dimensions of the Grid * layout will be truncated to 1 empty row and the cursor is moved * to the first cell */ cursorX = 0; cursorY = 0; } else { setRows(rows - 1); if (cursorY > row) { cursorY--; } } structuralChange = true; requestRepaint(); } /** * Sets the expand ratio of given column. Expand ratio defines how excess * space is distributed among columns. Excess space means the space not * consumed by non relatively sized components. * ** By default excess space is distributed evenly. * *
* Note, that width needs to be defined for this method to have any effect. * * @see #setWidth(float, int) * * @param columnIndex * @param ratio */ public void setColumnExpandRatio(int columnIndex, float ratio) { columnExpandRatio.put(columnIndex, ratio); requestRepaint(); } /** * Returns the expand ratio of given column * * @see #setColumnExpandRatio(int, float) * * @param columnIndex * @return the expand ratio, 0.0f by default */ public float getColumnExpandRatio(int columnIndex) { Float r = columnExpandRatio.get(columnIndex); return r == null ? 0 : r.floatValue(); } /** * Sets the expand ratio of given row. Expand ratio defines how excess space * is distributed among rows. Excess space means the space not consumed by * non relatively sized components. * *
* By default excess space is distributed evenly. * *
* Note, that height needs to be defined for this method to have any effect. * * @see #setHeight(float, int) * * @param rowIndex * @param ratio */ public void setRowExpandRatio(int rowIndex, float ratio) { rowExpandRatio.put(rowIndex, ratio); requestRepaint(); } /** * Returns the expand ratio of given row. * * @see #setRowExpandRatio(int, float) * * @param rowIndex * @return the expand ratio, 0.0f by default */ public float getRowExpandRatio(int rowIndex) { Float r = rowExpandRatio.get(rowIndex); return r == null ? 0 : r.floatValue(); } /** * Gets the Component at given index. * * @param x * x-index * @param y * y-index * @return Component in given cell or null if empty */ public Component getComponent(int x, int y) { for (final Iterator iterator = areas.iterator(); iterator .hasNext();) { final Area area = iterator.next(); if (area.getColumn1() <= x && x <= area.getColumn2() && area.getRow1() <= y && y <= area.getRow2()) { return area.getComponent(); } } return null; } /** * Returns information about the area where given component is layed in the * GridLayout. * * @param component * the component whose area information is requested. * @return an Area object that contains information how component is layed * in the grid */ public Area getComponentArea(Component component) { for (final Iterator iterator = areas.iterator(); iterator .hasNext();) { final Area area = iterator.next(); if (area.getComponent() == component) { return area; } } return null; } /** * Sets the component alignment using a short hand string notation. * * @deprecated Replaced by * {@link #setComponentAlignment(Component, Alignment)} * * @param component * A child component in this layout * @param alignment * A short hand notation described in {@link AlignmentUtils} */ @Deprecated public void setComponentAlignment(Component component, String alignment) { AlignmentUtils.setComponentAlignment(this, component, alignment); } public void addListener(LayoutClickListener listener) { addListener(CLICK_EVENT, LayoutClickEvent.class, listener, LayoutClickListener.clickMethod); } public void removeListener(LayoutClickListener listener) { removeListener(CLICK_EVENT, LayoutClickEvent.class, listener); } }