12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622 |
- /*
- * Copyright 2000-2016 Vaadin Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
- package com.vaadin.ui;
-
- import java.io.Serializable;
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.Collections;
- import java.util.HashMap;
- import java.util.HashSet;
- import java.util.Iterator;
- import java.util.LinkedList;
- import java.util.List;
- import java.util.Map;
- import java.util.Map.Entry;
- import java.util.Set;
-
- import org.jsoup.nodes.Attributes;
- import org.jsoup.nodes.Element;
- import org.jsoup.select.Elements;
-
- import com.vaadin.event.LayoutEvents.LayoutClickEvent;
- import com.vaadin.event.LayoutEvents.LayoutClickListener;
- import com.vaadin.event.LayoutEvents.LayoutClickNotifier;
- import com.vaadin.shared.Connector;
- import com.vaadin.shared.EventId;
- import com.vaadin.shared.MouseEventDetails;
- import com.vaadin.shared.Registration;
- import com.vaadin.shared.ui.MarginInfo;
- import com.vaadin.shared.ui.gridlayout.GridLayoutServerRpc;
- import com.vaadin.shared.ui.gridlayout.GridLayoutState;
- import com.vaadin.shared.ui.gridlayout.GridLayoutState.ChildComponentData;
- import com.vaadin.ui.declarative.DesignAttributeHandler;
- import com.vaadin.ui.declarative.DesignContext;
-
- /**
- * A layout where the components are laid out on a grid using cell coordinates.
- *
- * <p>
- * The GridLayout also maintains a cursor for adding components in
- * left-to-right, top-to-bottom order.
- * </p>
- *
- * <p>
- * Each component in a <code>GridLayout</code> uses a defined
- * {@link GridLayout.Area area} (column1,row1,column2,row2) from the grid. The
- * components may not overlap with the existing components - if you try to do so
- * you will get an {@link OverlapsException}. Adding a component with cursor
- * automatically extends the grid by increasing the grid height.
- * </p>
- *
- * <p>
- * The grid coordinates, which are specified by a row and column index, always
- * start from 0 for the topmost row and the leftmost column.
- * </p>
- *
- * @author Vaadin Ltd.
- * @since 3.0
- */
- @SuppressWarnings("serial")
- public class GridLayout extends AbstractLayout
- implements Layout.AlignmentHandler, Layout.SpacingHandler,
- Layout.MarginHandler, LayoutClickNotifier {
-
- private GridLayoutServerRpc rpc = (MouseEventDetails mouseDetails,
- Connector clickedConnector) -> {
- fireEvent(LayoutClickEvent.createEvent(GridLayout.this, mouseDetails,
- clickedConnector));
- };
- /**
- * Cursor X position: this is where the next component with unspecified x,y
- * is inserted
- */
- private int cursorX = 0;
-
- /**
- * Cursor Y position: this is where the next component with unspecified x,y
- * is inserted
- */
- private int cursorY = 0;
-
- private final LinkedList<Component> components = new LinkedList<>();
-
- private Map<Integer, Float> columnExpandRatio = new HashMap<>();
- private Map<Integer, Float> rowExpandRatio = new HashMap<>();
- private Alignment defaultComponentAlignment = Alignment.TOP_LEFT;
-
- /**
- * Constructor for a grid of given size (number of columns and rows).
- *
- * The grid may grow or shrink later. Grid grows automatically if you add
- * components outside its area.
- *
- * @param columns
- * Number of columns in the grid.
- * @param rows
- * Number of rows in the grid.
- */
- public GridLayout(int columns, int rows) {
- setColumns(columns);
- setRows(rows);
- registerRpc(rpc);
- }
-
- /**
- * Constructs an empty (1x1) grid layout that is extended as needed.
- */
- public GridLayout() {
- this(1, 1);
- }
-
- /**
- * Constructs a GridLayout of given size (number of columns and rows) and
- * adds the given components in order to the grid.
- *
- * @see #addComponents(Component...)
- *
- * @param columns
- * Number of columns in the grid.
- * @param rows
- * Number of rows in the grid.
- * @param children
- * Components to add to the grid.
- */
- public GridLayout(int columns, int rows, Component... children) {
- this(columns, rows);
- addComponents(children);
- }
-
- @Override
- protected GridLayoutState getState() {
- return (GridLayoutState) super.getState();
- }
-
- @Override
- protected GridLayoutState getState(boolean markAsDirty) {
- return (GridLayoutState) super.getState(markAsDirty);
- }
-
- /**
- * <p>
- * Adds a component to the grid in the specified area. The area is defined
- * by specifying the upper left corner (column1, row1) and the lower right
- * corner (column2, row2) of the area. The coordinates are zero-based.
- * </p>
- *
- * <p>
- * If the area overlaps with any of the existing components already present
- * in the grid, the operation will fail and an {@link OverlapsException} is
- * thrown.
- * </p>
- *
- * @param component
- * the component to be added, not <code>null</code>.
- * @param column1
- * the column of the upper left corner of the area <code>c</code>
- * is supposed to occupy. The leftmost column has index 0.
- * @param row1
- * the row of the upper left corner of the area <code>c</code> is
- * supposed to occupy. The topmost row has index 0.
- * @param column2
- * the column of the lower right corner of the area
- * <code>c</code> is supposed to occupy.
- * @param row2
- * the row of the lower right corner of the area <code>c</code>
- * 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 >= getColumns()
- || row2 >= getRows()) {
- 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 Map<Connector, ChildComponentData> childDataMap = getState().childData;
- int index = 0;
- boolean done = false;
- for (Component c : components) {
- final ChildComponentData existingArea = childDataMap.get(c);
- if ((existingArea.row1 >= row1 && existingArea.column1 > column1)
- || existingArea.row1 > row1) {
- components.add(index, component);
- done = true;
- break;
- }
- index++;
- }
- if (!done) {
- components.addLast(component);
- }
-
- childDataMap.put(component, area.childData);
-
- // Attempt to add to super
- try {
- super.addComponent(component);
- } catch (IllegalArgumentException e) {
- childDataMap.remove(component);
- 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 >= getColumns()) {
- // 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;
- }
- }
- }
-
- /**
- * 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 <code>area</code> overlaps with any existing area.
- */
- private void checkExistingOverlaps(Area area) throws OverlapsException {
- for (Entry<Connector, ChildComponentData> entry : getState().childData
- .entrySet()) {
- if (componentsOverlap(entry.getValue(), area.childData)) {
- // Component not added, overlaps with existing component
- throw new OverlapsException(
- new Area(entry.getValue(), (Component) entry.getKey()));
- }
- }
- }
-
- /**
- * Adds the component to the grid in cells column1,row1 (NortWest corner of
- * the area.) End coordinates (SouthEast corner of the area) are the same as
- * column1,row1. The coordinates are zero-based. Component width and height
- * is 1.
- *
- * @param component
- * the component to be added, not <code>null</code>.
- * @param column
- * the column index, starting from 0.
- * @param row
- * the row index, starting from 0.
- * @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 component, int column, int row)
- throws OverlapsException, OutOfBoundsException {
- this.addComponent(component, column, row, column, row);
- }
-
- /**
- * Forces the next component to be added at the beginning of the next line.
- *
- * <p>
- * Sets the cursor column to 0 and increments the cursor row by one.
- * </p>
- *
- * <p>
- * By calling this function you can ensure that no more components are added
- * right of the previous component.
- * </p>
- *
- * @see #space()
- */
- public void newLine() {
- cursorX = 0;
- cursorY++;
- }
-
- /**
- * Moves the cursor forward by one. If the cursor goes out of the right grid
- * border, it is moved to the first column of the next row.
- *
- * @see #newLine()
- */
- public void space() {
- cursorX++;
- if (cursorX >= getColumns()) {
- 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 component
- * the component to be added, not <code>null</code>.
- */
- @Override
- public void addComponent(Component component) {
- if (component == null) {
- throw new IllegalArgumentException("Component must not be null");
- }
-
- // 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
- if (cursorX >= getColumns()) {
- setColumns(cursorX + 1);
- }
- if (cursorY >= getRows()) {
- setRows(cursorY + 1);
- }
-
- addComponent(component, cursorX, cursorY);
- }
-
- /**
- * Removes the specified component from the layout.
- *
- * @param component
- * 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;
- }
-
- getState().childData.remove(component);
- components.remove(component);
- super.removeComponent(component);
- }
-
- /**
- * Removes the component specified by its cell coordinates.
- *
- * @param column
- * the component's column, starting from 0.
- * @param row
- * the component's row, starting from 0.
- */
- public void removeComponent(int column, int row) {
-
- // Finds the area
- for (final Component component : components) {
- final ChildComponentData childData = getState().childData
- .get(component);
- if (childData.column1 == column && childData.row1 == row) {
- removeComponent(component);
- return;
- }
- }
- }
-
- /**
- * Gets an Iterator for the components contained in the layout. By using the
- * Iterator it is possible to step through the contents of the layout.
- *
- * @return the Iterator of the components inside the layout.
- */
- @Override
- public Iterator<Component> iterator() {
- return Collections.unmodifiableCollection(components).iterator();
- }
-
- /**
- * Gets the number of components contained in the layout. Consistent with
- * the iterator returned by {@link #getComponentIterator()}.
- *
- * @return the number of contained components
- */
- @Override
- public int getComponentCount() {
- return components.size();
- }
-
- @Override
- public void beforeClientResponse(boolean initial) {
- super.beforeClientResponse(initial);
-
- getState().colExpand = new float[getColumns()];
- float colSum = getExpandRatioSum(columnExpandRatio);
- if (colSum == 0) {
- // no cols have been expanded
- for (int i = 0; i < getColumns(); i++) {
- getState().colExpand[i] = 1f;
- }
- } else {
- for (int i = 0; i < getColumns(); i++) {
- getState().colExpand[i] = getColumnExpandRatio(i);
- }
- }
-
- getState().rowExpand = new float[getRows()];
- float rowSum = getExpandRatioSum(rowExpandRatio);
- if (rowSum == 0) {
- // no rows have been expanded
- for (int i = 0; i < getRows(); i++) {
- getState().rowExpand[i] = 1f;
- }
- } else {
- for (int i = 0; i < getRows(); i++) {
- getState().rowExpand[i] = getRowExpandRatio(i);
- }
- }
-
- }
-
- private float getExpandRatioSum(Map<Integer, Float> ratioMap) {
- float sum = 0;
- for (Float expandRatio : ratioMap.values()) {
- sum += expandRatio;
- }
- return sum;
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.vaadin.ui.Layout.AlignmentHandler#getComponentAlignment(com
- * .vaadin.ui.Component)
- */
- @Override
- public Alignment getComponentAlignment(Component childComponent) {
- ChildComponentData childComponentData = getState(false).childData
- .get(childComponent);
- if (childComponentData == null) {
- throw new IllegalArgumentException(
- "The given component is not a child of this layout");
- } else {
- return new Alignment(childComponentData.alignment);
- }
- }
-
- /**
- * Defines a rectangular area of cells in a GridLayout.
- *
- * <p>
- * Also maintains a reference to the component contained in the area.
- * </p>
- *
- * <p>
- * The area is specified by the cell coordinates of its upper left corner
- * (column1,row1) and lower right corner (column2,row2). As otherwise with
- * GridLayout, the column and row coordinates start from zero.
- * </p>
- *
- * @author Vaadin Ltd.
- * @since 3.0
- */
- public class Area implements Serializable {
- private final ChildComponentData childData;
- private final Component component;
-
- /**
- * <p>
- * Construct a new area on a grid.
- * </p>
- *
- * @param component
- * the component connected to the area.
- * @param column1
- * The column of the upper left corner cell of the area. The
- * leftmost column has index 0.
- * @param row1
- * The row of the upper left corner cell of the area. The
- * topmost row has index 0.
- * @param column2
- * The column of the lower right corner cell of the area. The
- * leftmost column has index 0.
- * @param row2
- * The row of the lower right corner cell of the area. The
- * topmost row has index 0.
- */
- public Area(Component component, int column1, int row1, int column2,
- int row2) {
- this.component = component;
- childData = new ChildComponentData();
- childData.alignment = getDefaultComponentAlignment().getBitMask();
- childData.column1 = column1;
- childData.row1 = row1;
- childData.column2 = column2;
- childData.row2 = row2;
- }
-
- public Area(ChildComponentData childData, Component component) {
- this.childData = childData;
- this.component = component;
- }
-
- /**
- * Tests if this Area overlaps with another Area.
- *
- * @param other
- * the other Area that is to be tested for overlap with this
- * area
- * @return <code>true</code> if <code>other</code> area overlaps with
- * this on, <code>false</code> if it does not.
- */
- public boolean overlaps(Area other) {
- return componentsOverlap(childData, other.childData);
- }
-
- /**
- * Gets the component connected to the area.
- *
- * @return the Component.
- */
- public Component getComponent() {
- return component;
- }
-
- /**
- * Gets the column of the top-left corner cell.
- *
- * @return the column of the top-left corner cell.
- */
- public int getColumn1() {
- return childData.column1;
- }
-
- /**
- * Gets the column of the bottom-right corner cell.
- *
- * @return the column of the bottom-right corner cell.
- */
- public int getColumn2() {
- return childData.column2;
- }
-
- /**
- * Gets the row of the top-left corner cell.
- *
- * @return the row of the top-left corner cell.
- */
- public int getRow1() {
- return childData.row1;
- }
-
- /**
- * Gets the row of the bottom-right corner cell.
- *
- * @return the row of the bottom-right corner cell.
- */
- public int getRow2() {
- return childData.row2;
- }
-
- }
-
- private static boolean componentsOverlap(ChildComponentData a,
- ChildComponentData b) {
- return a.column1 <= b.column2 && a.row1 <= b.row2
- && a.column2 >= b.column1 && a.row2 >= b.row1;
- }
-
- /**
- * Gridlayout does not support laying components on top of each other. An
- * <code>OverlapsException</code> is thrown when a component already exists
- * (even partly) at the same space on a grid with the new component.
- *
- * @author Vaadin Ltd.
- * @since 3.0
- */
- public class OverlapsException extends RuntimeException {
-
- private final Area existingArea;
-
- /**
- * Constructs an <code>OverlapsException</code>.
- *
- * @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.childData.column1);
- sb.append(',');
- sb.append(existingArea.childData.column1);
- sb.append(',');
- sb.append(existingArea.childData.row1);
- sb.append(',');
- sb.append(existingArea.childData.row2);
- sb.append("(column1, column2, row1, row2).");
-
- return sb.toString();
- }
-
- /**
- * Gets the area .
- *
- * @return the existing area.
- */
- public Area getArea() {
- return existingArea;
- }
- }
-
- /**
- * An <code>Exception</code> object which is thrown when an area exceeds the
- * bounds of the grid.
- *
- * @author Vaadin Ltd.
- * @since 3.0
- */
- public class OutOfBoundsException extends RuntimeException {
-
- private final Area areaOutOfBounds;
-
- /**
- * Constructs an <code>OoutOfBoundsException</code> 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 (getColumns() == columns) {
- return;
- }
-
- // Checks for overlaps
- if (getColumns() > columns) {
- for (Entry<Connector, ChildComponentData> entry : getState().childData
- .entrySet()) {
- if (entry.getValue().column2 >= columns) {
- throw new OutOfBoundsException(new Area(entry.getValue(),
- (Component) entry.getKey()));
- }
- }
- }
-
- // Forget expands for removed columns
- if (columns < getColumns()) {
- for (int i = columns; i < getColumns(); i++) {
- columnExpandRatio.remove(i);
- getState().explicitColRatios.remove(i);
- }
- }
-
- getState().columns = columns;
- }
-
- /**
- * Get the number of columns in the grid.
- *
- * @return the number of columns in the grid.
- */
- public int getColumns() {
- return getState(false).columns;
- }
-
- /**
- * 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 (getRows() == rows) {
- return;
- }
-
- // Checks for overlaps
- if (getRows() > rows) {
- for (Entry<Connector, ChildComponentData> entry : getState().childData
- .entrySet()) {
- if (entry.getValue().row2 >= rows) {
- throw new OutOfBoundsException(new Area(entry.getValue(),
- (Component) entry.getKey()));
- }
- }
- }
- // Forget expands for removed rows
- if (rows < getRows()) {
- for (int i = rows; i < getRows(); i++) {
- rowExpandRatio.remove(i);
- getState().explicitRowRatios.remove(i);
- }
- }
-
- getState().rows = rows;
- }
-
- /**
- * Get the number of rows in the grid.
- *
- * @return the number of rows in the grid.
- */
- public int getRows() {
- return getState(false).rows;
- }
-
- /**
- * Gets the current x-position (column) of the cursor.
- *
- * <p>
- * 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.
- * </p>
- *
- * @return the grid column the cursor is on, starting from 0.
- */
- 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 y-position (row) of the cursor.
- *
- * <p>
- * 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 the first free
- * position after the cursor.
- * </p>
- *
- * @return the grid row the Cursor is on.
- */
- public int getCursorY() {
- return cursorY;
- }
-
- /**
- * Sets the current y-coordinate (row) of the cursor. This is usually
- * handled automatically by GridLayout.
- *
- * @param cursorY
- * the row number, starting from 0 for the topmost row.
- */
- public void setCursorY(int cursorY) {
- this.cursorY = cursorY;
- }
-
- /* Documented in superclass */
- @Override
- public void replaceComponent(Component oldComponent,
- Component newComponent) {
-
- // Gets the locations
- ChildComponentData oldLocation = getState().childData.get(oldComponent);
- ChildComponentData newLocation = getState().childData.get(newComponent);
-
- if (oldLocation == null) {
- addComponent(newComponent);
- } else if (newLocation == null) {
- removeComponent(oldComponent);
- addComponent(newComponent, oldLocation.column1, oldLocation.row1,
- oldLocation.column2, oldLocation.row2);
- } else {
- int oldAlignment = oldLocation.alignment;
- oldLocation.alignment = newLocation.alignment;
- newLocation.alignment = oldAlignment;
-
- getState().childData.put(newComponent, oldLocation);
- getState().childData.put(oldComponent, newLocation);
- }
- }
-
- /*
- * Removes all components from this container.
- *
- * @see com.vaadin.ui.ComponentContainer#removeAllComponents()
- */
- @Override
- public void removeAllComponents() {
- super.removeAllComponents();
- cursorX = 0;
- cursorY = 0;
- }
-
- @Override
- public void setComponentAlignment(Component childComponent,
- Alignment alignment) {
- ChildComponentData childComponentData = getState().childData
- .get(childComponent);
- if (childComponentData == null) {
- throw new IllegalArgumentException(
- "Component must be added to layout before using setComponentAlignment()");
- } else {
- if (alignment == null) {
- childComponentData.alignment = GridLayoutState.ALIGNMENT_DEFAULT
- .getBitMask();
- } else {
- childComponentData.alignment = alignment.getBitMask();
- }
- }
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.vaadin.ui.Layout.SpacingHandler#setSpacing(boolean)
- */
- @Override
- public void setSpacing(boolean spacing) {
- getState().spacing = spacing;
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.vaadin.ui.Layout.SpacingHandler#isSpacing()
- */
- @Override
- public boolean isSpacing() {
- return getState(false).spacing;
- }
-
- /**
- * Inserts an empty row at the specified position in the grid.
- *
- * @param row
- * Index of the row before which the new row will be inserted.
- * The leftmost row has index 0.
- */
- public void insertRow(int row) {
- if (row > getRows()) {
- throw new IllegalArgumentException("Cannot insert row at " + row
- + " in a gridlayout with height " + getRows());
- }
-
- for (ChildComponentData existingArea : getState().childData.values()) {
- // Areas ending below the row needs to be moved down or stretched
- if (existingArea.row2 >= row) {
- existingArea.row2++;
-
- // Stretch areas that span over the selected row
- if (existingArea.row1 >= row) {
- existingArea.row1++;
- }
-
- }
- }
-
- if (cursorY >= row) {
- cursorY++;
- }
-
- setRows(getRows() + 1);
- markAsDirty();
- }
-
- /**
- * Removes a row and all the components in the row.
- *
- * <p>
- * Components which span over several rows are removed if the selected row
- * is on the first row of such a component.
- * </p>
- *
- * <p>
- * 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.
- * </p>
- *
- * @param row
- * Index of the row to remove. The leftmost row has index 0.
- */
- public void removeRow(int row) {
- if (row >= getRows()) {
- throw new IllegalArgumentException("Cannot delete row " + row
- + " from a gridlayout with height " + getRows());
- }
-
- // Remove all components in row
- for (int col = 0; col < getColumns(); col++) {
- removeComponent(col, row);
- }
-
- // Shrink or remove areas in the selected row
- for (ChildComponentData existingArea : getState().childData.values()) {
- if (existingArea.row2 >= row) {
- existingArea.row2--;
-
- if (existingArea.row1 > row) {
- existingArea.row1--;
- }
- }
- }
-
- if (getRows() == 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(getRows() - 1);
- if (cursorY > row) {
- cursorY--;
- }
- }
-
- markAsDirty();
-
- }
-
- /**
- * Sets the expand ratio of given column.
- *
- * <p>
- * The expand ratio defines how excess space is distributed among columns.
- * Excess space means space that is left over from components that are not
- * sized relatively. By default, the excess space is distributed evenly.
- * </p>
- *
- * <p>
- * Note, that width of this GridLayout needs to be defined (fixed or
- * relative, as opposed to undefined height) for this method to have any
- * effect.
- * <p>
- * Note that checking for relative width for the child components is done on
- * the server so you cannot set a child component to have undefined width on
- * the server and set it to <code>100%</code> in CSS. You must set it to
- * <code>100%</code> on the server.
- *
- * @see #setWidth(float, Unit)
- *
- * @param columnIndex
- * @param ratio
- */
- public void setColumnExpandRatio(int columnIndex, float ratio) {
- columnExpandRatio.put(columnIndex, ratio);
- getState().explicitColRatios.add(columnIndex);
- markAsDirty();
- }
-
- /**
- * 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.
- *
- * <p>
- * Expand ratio defines how excess space is distributed among rows. Excess
- * space means the space left over from components that are not sized
- * relatively. By default, the excess space is distributed evenly.
- * </p>
- *
- * <p>
- * Note, that height of this GridLayout needs to be defined (fixed or
- * relative, as opposed to undefined height) for this method to have any
- * effect.
- * <p>
- * Note that checking for relative height for the child components is done
- * on the server so you cannot set a child component to have undefined
- * height on the server and set it to <code>100%</code> in CSS. You must set
- * it to <code>100%</code> on the server.
- *
- * @see #setHeight(float, Unit)
- *
- * @param rowIndex
- * The row index, starting from 0 for the topmost row.
- * @param ratio
- */
- public void setRowExpandRatio(int rowIndex, float ratio) {
- rowExpandRatio.put(rowIndex, ratio);
- getState().explicitRowRatios.add(rowIndex);
- markAsDirty();
- }
-
- /**
- * Returns the expand ratio of given row.
- *
- * @see #setRowExpandRatio(int, float)
- *
- * @param rowIndex
- * The row index, starting from 0 for the topmost row.
- * @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
- * The column index, starting from 0 for the leftmost column.
- * @param y
- * The row index, starting from 0 for the topmost row.
- * @return Component in given cell or null if empty
- */
- public Component getComponent(int x, int y) {
- for (Entry<Connector, ChildComponentData> entry : getState(
- false).childData.entrySet()) {
- ChildComponentData childData = entry.getValue();
- if (childData.column1 <= x && x <= childData.column2
- && childData.row1 <= y && y <= childData.row2) {
- return (Component) entry.getKey();
- }
- }
- return null;
- }
-
- /**
- * Returns information about the area where given component is laid in the
- * GridLayout.
- *
- * @param component
- * the component whose area information is requested.
- * @return an Area object that contains information how component is laid in
- * the grid
- */
- public Area getComponentArea(Component component) {
- ChildComponentData childComponentData = getState(false).childData
- .get(component);
- if (childComponentData == null) {
- return null;
- } else {
- return new Area(childComponentData, component);
- }
- }
-
- @Override
- public Registration addLayoutClickListener(LayoutClickListener listener) {
- return addListener(EventId.LAYOUT_CLICK_EVENT_IDENTIFIER,
- LayoutClickEvent.class, listener,
- LayoutClickListener.clickMethod);
- }
-
- @Override
- @Deprecated
- public void removeLayoutClickListener(LayoutClickListener listener) {
- removeListener(EventId.LAYOUT_CLICK_EVENT_IDENTIFIER,
- LayoutClickEvent.class, listener);
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.vaadin.ui.Layout.MarginHandler#setMargin(boolean)
- */
- @Override
- public void setMargin(boolean enabled) {
- setMargin(new MarginInfo(enabled));
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.vaadin.ui.Layout.MarginHandler#setMargin(com.vaadin.shared.ui.
- * MarginInfo )
- */
- @Override
- public void setMargin(MarginInfo marginInfo) {
- getState().marginsBitmask = marginInfo.getBitMask();
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.vaadin.ui.Layout.MarginHandler#getMargin()
- */
- @Override
- public MarginInfo getMargin() {
- return new MarginInfo(getState(false).marginsBitmask);
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.vaadin.ui.Layout.AlignmentHandler#getDefaultComponentAlignment()
- */
- @Override
- public Alignment getDefaultComponentAlignment() {
- return defaultComponentAlignment;
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.vaadin.ui.Layout.AlignmentHandler#setDefaultComponentAlignment(com
- * .vaadin.ui.Alignment)
- */
- @Override
- public void setDefaultComponentAlignment(Alignment defaultAlignment) {
- defaultComponentAlignment = defaultAlignment;
- }
-
- /**
- * Sets whether empty rows and columns should be considered as non-existent
- * when rendering or not. If this is set to true then the spacing between
- * multiple empty columns (or rows) will be collapsed.
- *
- * The default behavior is to consider all rows and columns as visible
- *
- * NOTE that this must be set before the initial rendering takes place.
- * Updating this on the fly is not supported.
- *
- * @since 7.3
- * @param hideEmptyRowsAndColumns
- * true to hide empty rows and columns, false to leave them as-is
- */
- public void setHideEmptyRowsAndColumns(boolean hideEmptyRowsAndColumns) {
- getState().hideEmptyRowsAndColumns = hideEmptyRowsAndColumns;
- }
-
- /**
- * Checks whether whether empty rows and columns should be considered as
- * non-existent when rendering or not.
- *
- * @see #setHideEmptyRowsAndColumns(boolean)
- * @since 7.3
- * @return true if empty rows and columns are hidden, false otherwise
- */
- public boolean isHideEmptyRowsAndColumns() {
- return getState(false).hideEmptyRowsAndColumns;
- }
-
- /**
- * {@inheritDoc}
- * <p>
- * After reading the design, cursorY is set to point to a row outside of the
- * GridLayout area. CursorX is reset to 0.
- */
- @Override
- public void readDesign(Element design, DesignContext designContext) {
- super.readDesign(design, designContext);
-
- setMargin(readMargin(design, getMargin(), designContext));
- if (design.childNodeSize() > 0) {
- // Touch content only if there is some content specified. This is
- // needed to be able to use extended GridLayouts which add
- // components in the constructor (e.g. Designs based on GridLayout).
- readChildComponents(design.children(), designContext);
- }
-
- // Set cursor position explicitly
- setCursorY(getRows());
- setCursorX(0);
- }
-
- private void readChildComponents(Elements childElements,
- DesignContext designContext) {
- List<Element> rowElements = new ArrayList<>();
- List<Map<Integer, Component>> rows = new ArrayList<>();
- // Prepare a 2D map for reading column contents
- for (Element e : childElements) {
- if (e.tagName().equalsIgnoreCase("row")) {
- rowElements.add(e);
- rows.add(new HashMap<>());
-
- }
- }
- setRows(Math.max(rows.size(), 1));
- Map<Component, Alignment> alignments = new HashMap<>();
- List<Float> columnExpandRatios = new ArrayList<>();
- for (int row = 0; row < rowElements.size(); ++row) {
- Element rowElement = rowElements.get(row);
-
- // Row Expand
- if (rowElement.hasAttr("expand")) {
- float expand = DesignAttributeHandler.readAttribute("expand",
- rowElement.attributes(), float.class);
- setRowExpandRatio(row, expand);
- }
-
- Elements cols = rowElement.children();
-
- // Amount of skipped columns due to spanned components
- int skippedColumns = 0;
-
- for (int column = 0; column < cols.size(); ++column) {
- while (rows.get(row).containsKey(column + skippedColumns)) {
- // Skip any spanned components
- skippedColumns++;
- }
-
- Element col = cols.get(column);
- Component child = null;
-
- if (!col.children().isEmpty()) {
- Element childElement = col.child(0);
- child = designContext.readDesign(childElement);
- alignments.put(child, DesignAttributeHandler
- .readAlignment(childElement.attributes()));
- // TODO: Currently ignoring any extra children.
- // Needs Error handling?
- } // Else: Empty placeholder. No child component.
-
- // Handle rowspan and colspan for this child component
- Attributes attr = col.attributes();
- int colspan = DesignAttributeHandler.readAttribute("colspan",
- attr, 1, int.class);
- int rowspan = DesignAttributeHandler.readAttribute("rowspan",
- attr, 1, int.class);
-
- for (int rowIndex = row; rowIndex < row + rowspan; ++rowIndex) {
- for (int colIndex = column; colIndex < column
- + colspan; ++colIndex) {
- if (rowIndex == rows.size()) {
- // Rowspan with not enough rows. Fix by adding rows.
- rows.add(new HashMap<>());
- }
- rows.get(rowIndex).put(colIndex + skippedColumns,
- child);
- }
- }
-
- // Read column expand ratios if handling the first row.
- if (row == 0) {
- if (col.hasAttr("expand")) {
- for (String expand : col.attr("expand").split(",")) {
- columnExpandRatios.add(Float.parseFloat(expand));
- }
- } else {
- for (int c = 0; c < colspan; ++c) {
- columnExpandRatios.add(0f);
- }
- }
- }
-
- skippedColumns += (colspan - 1);
- }
- }
-
- // Calculate highest column count and set columns
- int colMax = 0;
- for (Map<Integer, Component> cols : rows) {
- if (colMax < cols.size()) {
- colMax = cols.size();
- }
- }
- setColumns(Math.max(colMax, 1));
-
- for (int i = 0; i < columnExpandRatios.size(); ++i) {
- setColumnExpandRatio(i, columnExpandRatios.get(i));
- }
-
- // Reiterate through the 2D map and add components to GridLayout
- Set<Component> visited = new HashSet<>();
-
- // Ignore any missing components
- visited.add(null);
-
- for (int i = 0; i < rows.size(); ++i) {
- Map<Integer, Component> row = rows.get(i);
- for (int j = 0; j < colMax; ++j) {
- Component child = row.get(j);
- if (visited.contains(child)) {
- // Empty location or already handled child
- continue;
- }
- visited.add(child);
-
- // Figure out col and rowspan from 2D map
- int colspan = 0;
- while (j + colspan + 1 < row.size()
- && row.get(j + colspan + 1) == child) {
- ++colspan;
- }
-
- int rowspan = 0;
- while (i + rowspan + 1 < rows.size()
- && rows.get(i + rowspan + 1).get(j) == child) {
- ++rowspan;
- }
-
- // Add component with area
- addComponent(child, j, i, j + colspan, i + rowspan);
- setComponentAlignment(child, alignments.get(child));
- }
- }
- }
-
- @Override
- public void writeDesign(Element design, DesignContext designContext) {
- super.writeDesign(design, designContext);
-
- GridLayout def = designContext.getDefaultInstance(this);
-
- writeMargin(design, getMargin(), def.getMargin(), designContext);
-
- if (!designContext.shouldWriteChildren(this, def)) {
- return;
- }
-
- if (components.isEmpty()) {
- writeEmptyColsAndRows(design, designContext);
- return;
- }
-
- final Map<Connector, ChildComponentData> childData = getState().childData;
-
- // Make a 2D map of component areas.
- Component[][] componentMap = new Component[getState().rows][getState().columns];
- final Component dummyComponent = new Label("");
-
- for (Component component : components) {
- ChildComponentData coords = childData.get(component);
- for (int row = coords.row1; row <= coords.row2; ++row) {
- for (int col = coords.column1; col <= coords.column2; ++col) {
- componentMap[row][col] = component;
- }
- }
- }
-
- // Go through the map and write only needed column tags
- Set<Connector> visited = new HashSet<>();
-
- // Skip the dummy placeholder
- visited.add(dummyComponent);
-
- for (int i = 0; i < componentMap.length; ++i) {
- Element row = design.appendElement("row");
-
- // Row Expand
- DesignAttributeHandler.writeAttribute("expand", row.attributes(),
- getRowExpandRatio(i), 0.0f, float.class, designContext);
-
- int colspan = 1;
- Element col;
- for (int j = 0; j < componentMap[i].length; ++j) {
- Component child = componentMap[i][j];
- if (child != null) {
- if (visited.contains(child)) {
- // Child has already been written in the design
- continue;
- }
- visited.add(child);
-
- Element childElement = designContext.createElement(child);
- col = row.appendElement("column");
-
- // Write child data into design
- ChildComponentData coords = childData.get(child);
-
- Alignment alignment = getComponentAlignment(child);
- DesignAttributeHandler.writeAlignment(childElement,
- alignment);
-
- col.appendChild(childElement);
- if (coords.row1 != coords.row2) {
- col.attr("rowspan",
- "" + (1 + coords.row2 - coords.row1));
- }
-
- colspan = 1 + coords.column2 - coords.column1;
- if (colspan > 1) {
- col.attr("colspan", "" + colspan);
- }
-
- } else {
- boolean hasExpands = false;
- if (i == 0 && lastComponentOnRow(componentMap[i], j,
- visited)) {
- // A column with expand and no content in the end of
- // first row needs to be present.
- for (int c = j; c < componentMap[i].length; ++c) {
- if (getColumnExpandRatio(c) > 0) {
- hasExpands = true;
- }
- }
- }
-
- if (lastComponentOnRow(componentMap[i], j, visited)
- && !hasExpands) {
- continue;
- }
-
- // Empty placeholder tag.
- col = row.appendElement("column");
-
- // Use colspan to make placeholders more pleasant
- while (j + colspan < componentMap[i].length
- && componentMap[i][j + colspan] == child) {
- ++colspan;
- }
-
- int rowspan = getRowSpan(componentMap, i, j, colspan,
- child);
- if (colspan > 1) {
- col.attr("colspan", "" + colspan);
- }
- if (rowspan > 1) {
- col.attr("rowspan", "" + rowspan);
- }
- for (int x = 0; x < rowspan; ++x) {
- for (int y = 0; y < colspan; ++y) {
- // Mark handled columns
- componentMap[i + x][j + y] = dummyComponent;
- }
- }
- }
-
- // Column expands
- if (i == 0) {
- // Only do expands on first row
- String expands = "";
- boolean expandRatios = false;
- for (int c = 0; c < colspan; ++c) {
- float colExpand = getColumnExpandRatio(j + c);
- if (colExpand > 0) {
- expandRatios = true;
- }
- expands += (c > 0 ? "," : "") + colExpand;
- }
- if (expandRatios) {
- col.attr("expand", expands);
- }
- }
-
- j += colspan - 1;
- }
- }
- }
-
- /**
- * Fills in the design with rows and empty columns. This needs to be done
- * for empty {@link GridLayout}, because there's no other way to serialize
- * info about number of columns and rows if there are absolutely no
- * components in the {@link GridLayout}
- *
- * @param design
- * @param designContext
- */
- private void writeEmptyColsAndRows(Element design,
- DesignContext designContext) {
- int rowCount = getState(false).rows;
- int colCount = getState(false).columns;
-
- // only write cols and rows tags if size is not 1x1
- if (rowCount == 1 && colCount == 1) {
- return;
- }
-
- for (int i = 0; i < rowCount; i++) {
- Element row = design.appendElement("row");
- for (int j = 0; j < colCount; j++) {
- row.appendElement("column");
- }
- }
-
- }
-
- private int getRowSpan(Component[][] compMap, int i, int j, int colspan,
- Component child) {
- int rowspan = 1;
- while (i + rowspan < compMap.length
- && compMap[i + rowspan][j] == child) {
- for (int k = 0; k < colspan; ++k) {
- if (compMap[i + rowspan][j + k] != child) {
- return rowspan;
- }
- }
- rowspan++;
- }
- return rowspan;
- }
-
- private boolean lastComponentOnRow(Component[] componentArray, int j,
- Set<Connector> visited) {
- while ((++j) < componentArray.length) {
- Component child = componentArray[j];
- if (child != null && !visited.contains(child)) {
- return false;
- }
- }
- return true;
- }
-
- @Override
- protected Collection<String> getCustomAttributes() {
- Collection<String> result = super.getCustomAttributes();
- result.add("cursor-x");
- result.add("cursor-y");
- result.add("rows");
- result.add("columns");
- result.add("margin");
- result.add("margin-left");
- result.add("margin-right");
- result.add("margin-top");
- result.add("margin-bottom");
- return result;
- }
- }
|