You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

GridLayout.java 53KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622
  1. /*
  2. * Copyright 2000-2016 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.ui;
  17. import java.io.Serializable;
  18. import java.util.ArrayList;
  19. import java.util.Collection;
  20. import java.util.Collections;
  21. import java.util.HashMap;
  22. import java.util.HashSet;
  23. import java.util.Iterator;
  24. import java.util.LinkedList;
  25. import java.util.List;
  26. import java.util.Map;
  27. import java.util.Map.Entry;
  28. import java.util.Set;
  29. import org.jsoup.nodes.Attributes;
  30. import org.jsoup.nodes.Element;
  31. import org.jsoup.select.Elements;
  32. import com.vaadin.event.LayoutEvents.LayoutClickEvent;
  33. import com.vaadin.event.LayoutEvents.LayoutClickListener;
  34. import com.vaadin.event.LayoutEvents.LayoutClickNotifier;
  35. import com.vaadin.shared.Connector;
  36. import com.vaadin.shared.EventId;
  37. import com.vaadin.shared.MouseEventDetails;
  38. import com.vaadin.shared.Registration;
  39. import com.vaadin.shared.ui.MarginInfo;
  40. import com.vaadin.shared.ui.gridlayout.GridLayoutServerRpc;
  41. import com.vaadin.shared.ui.gridlayout.GridLayoutState;
  42. import com.vaadin.shared.ui.gridlayout.GridLayoutState.ChildComponentData;
  43. import com.vaadin.ui.declarative.DesignAttributeHandler;
  44. import com.vaadin.ui.declarative.DesignContext;
  45. /**
  46. * A layout where the components are laid out on a grid using cell coordinates.
  47. *
  48. * <p>
  49. * The GridLayout also maintains a cursor for adding components in
  50. * left-to-right, top-to-bottom order.
  51. * </p>
  52. *
  53. * <p>
  54. * Each component in a <code>GridLayout</code> uses a defined
  55. * {@link GridLayout.Area area} (column1,row1,column2,row2) from the grid. The
  56. * components may not overlap with the existing components - if you try to do so
  57. * you will get an {@link OverlapsException}. Adding a component with cursor
  58. * automatically extends the grid by increasing the grid height.
  59. * </p>
  60. *
  61. * <p>
  62. * The grid coordinates, which are specified by a row and column index, always
  63. * start from 0 for the topmost row and the leftmost column.
  64. * </p>
  65. *
  66. * @author Vaadin Ltd.
  67. * @since 3.0
  68. */
  69. @SuppressWarnings("serial")
  70. public class GridLayout extends AbstractLayout
  71. implements Layout.AlignmentHandler, Layout.SpacingHandler,
  72. Layout.MarginHandler, LayoutClickNotifier {
  73. private GridLayoutServerRpc rpc = (MouseEventDetails mouseDetails,
  74. Connector clickedConnector) -> {
  75. fireEvent(LayoutClickEvent.createEvent(GridLayout.this, mouseDetails,
  76. clickedConnector));
  77. };
  78. /**
  79. * Cursor X position: this is where the next component with unspecified x,y
  80. * is inserted
  81. */
  82. private int cursorX = 0;
  83. /**
  84. * Cursor Y position: this is where the next component with unspecified x,y
  85. * is inserted
  86. */
  87. private int cursorY = 0;
  88. private final LinkedList<Component> components = new LinkedList<>();
  89. private Map<Integer, Float> columnExpandRatio = new HashMap<>();
  90. private Map<Integer, Float> rowExpandRatio = new HashMap<>();
  91. private Alignment defaultComponentAlignment = Alignment.TOP_LEFT;
  92. /**
  93. * Constructor for a grid of given size (number of columns and rows).
  94. *
  95. * The grid may grow or shrink later. Grid grows automatically if you add
  96. * components outside its area.
  97. *
  98. * @param columns
  99. * Number of columns in the grid.
  100. * @param rows
  101. * Number of rows in the grid.
  102. */
  103. public GridLayout(int columns, int rows) {
  104. setColumns(columns);
  105. setRows(rows);
  106. registerRpc(rpc);
  107. }
  108. /**
  109. * Constructs an empty (1x1) grid layout that is extended as needed.
  110. */
  111. public GridLayout() {
  112. this(1, 1);
  113. }
  114. /**
  115. * Constructs a GridLayout of given size (number of columns and rows) and
  116. * adds the given components in order to the grid.
  117. *
  118. * @see #addComponents(Component...)
  119. *
  120. * @param columns
  121. * Number of columns in the grid.
  122. * @param rows
  123. * Number of rows in the grid.
  124. * @param children
  125. * Components to add to the grid.
  126. */
  127. public GridLayout(int columns, int rows, Component... children) {
  128. this(columns, rows);
  129. addComponents(children);
  130. }
  131. @Override
  132. protected GridLayoutState getState() {
  133. return (GridLayoutState) super.getState();
  134. }
  135. @Override
  136. protected GridLayoutState getState(boolean markAsDirty) {
  137. return (GridLayoutState) super.getState(markAsDirty);
  138. }
  139. /**
  140. * <p>
  141. * Adds a component to the grid in the specified area. The area is defined
  142. * by specifying the upper left corner (column1, row1) and the lower right
  143. * corner (column2, row2) of the area. The coordinates are zero-based.
  144. * </p>
  145. *
  146. * <p>
  147. * If the area overlaps with any of the existing components already present
  148. * in the grid, the operation will fail and an {@link OverlapsException} is
  149. * thrown.
  150. * </p>
  151. *
  152. * @param component
  153. * the component to be added, not <code>null</code>.
  154. * @param column1
  155. * the column of the upper left corner of the area <code>c</code>
  156. * is supposed to occupy. The leftmost column has index 0.
  157. * @param row1
  158. * the row of the upper left corner of the area <code>c</code> is
  159. * supposed to occupy. The topmost row has index 0.
  160. * @param column2
  161. * the column of the lower right corner of the area
  162. * <code>c</code> is supposed to occupy.
  163. * @param row2
  164. * the row of the lower right corner of the area <code>c</code>
  165. * is supposed to occupy.
  166. * @throws OverlapsException
  167. * if the new component overlaps with any of the components
  168. * already in the grid.
  169. * @throws OutOfBoundsException
  170. * if the cells are outside the grid area.
  171. */
  172. public void addComponent(Component component, int column1, int row1,
  173. int column2, int row2)
  174. throws OverlapsException, OutOfBoundsException {
  175. if (component == null) {
  176. throw new NullPointerException("Component must not be null");
  177. }
  178. // Checks that the component does not already exist in the container
  179. if (components.contains(component)) {
  180. throw new IllegalArgumentException(
  181. "Component is already in the container");
  182. }
  183. // Creates the area
  184. final Area area = new Area(component, column1, row1, column2, row2);
  185. // Checks the validity of the coordinates
  186. if (column2 < column1 || row2 < row1) {
  187. throw new IllegalArgumentException(
  188. "Illegal coordinates for the component");
  189. }
  190. if (column1 < 0 || row1 < 0 || column2 >= getColumns()
  191. || row2 >= getRows()) {
  192. throw new OutOfBoundsException(area);
  193. }
  194. // Checks that newItem does not overlap with existing items
  195. checkExistingOverlaps(area);
  196. // Inserts the component to right place at the list
  197. // Respect top-down, left-right ordering
  198. // component.setParent(this);
  199. final Map<Connector, ChildComponentData> childDataMap = getState().childData;
  200. int index = 0;
  201. boolean done = false;
  202. for (Component c : components) {
  203. final ChildComponentData existingArea = childDataMap.get(c);
  204. if ((existingArea.row1 >= row1 && existingArea.column1 > column1)
  205. || existingArea.row1 > row1) {
  206. components.add(index, component);
  207. done = true;
  208. break;
  209. }
  210. index++;
  211. }
  212. if (!done) {
  213. components.addLast(component);
  214. }
  215. childDataMap.put(component, area.childData);
  216. // Attempt to add to super
  217. try {
  218. super.addComponent(component);
  219. } catch (IllegalArgumentException e) {
  220. childDataMap.remove(component);
  221. components.remove(component);
  222. throw e;
  223. }
  224. // update cursor position, if it's within this area; use first position
  225. // outside this area, even if it's occupied
  226. if (cursorX >= column1 && cursorX <= column2 && cursorY >= row1
  227. && cursorY <= row2) {
  228. // cursor within area
  229. cursorX = column2 + 1; // one right of area
  230. if (cursorX >= getColumns()) {
  231. // overflowed columns
  232. cursorX = 0; // first col
  233. // move one row down, or one row under the area
  234. cursorY = (column1 == 0 ? row2 : row1) + 1;
  235. } else {
  236. cursorY = row1;
  237. }
  238. }
  239. }
  240. /**
  241. * Tests if the given area overlaps with any of the items already on the
  242. * grid.
  243. *
  244. * @param area
  245. * the Area to be checked for overlapping.
  246. * @throws OverlapsException
  247. * if <code>area</code> overlaps with any existing area.
  248. */
  249. private void checkExistingOverlaps(Area area) throws OverlapsException {
  250. for (Entry<Connector, ChildComponentData> entry : getState().childData
  251. .entrySet()) {
  252. if (componentsOverlap(entry.getValue(), area.childData)) {
  253. // Component not added, overlaps with existing component
  254. throw new OverlapsException(
  255. new Area(entry.getValue(), (Component) entry.getKey()));
  256. }
  257. }
  258. }
  259. /**
  260. * Adds the component to the grid in cells column1,row1 (NortWest corner of
  261. * the area.) End coordinates (SouthEast corner of the area) are the same as
  262. * column1,row1. The coordinates are zero-based. Component width and height
  263. * is 1.
  264. *
  265. * @param component
  266. * the component to be added, not <code>null</code>.
  267. * @param column
  268. * the column index, starting from 0.
  269. * @param row
  270. * the row index, starting from 0.
  271. * @throws OverlapsException
  272. * if the new component overlaps with any of the components
  273. * already in the grid.
  274. * @throws OutOfBoundsException
  275. * if the cell is outside the grid area.
  276. */
  277. public void addComponent(Component component, int column, int row)
  278. throws OverlapsException, OutOfBoundsException {
  279. this.addComponent(component, column, row, column, row);
  280. }
  281. /**
  282. * Forces the next component to be added at the beginning of the next line.
  283. *
  284. * <p>
  285. * Sets the cursor column to 0 and increments the cursor row by one.
  286. * </p>
  287. *
  288. * <p>
  289. * By calling this function you can ensure that no more components are added
  290. * right of the previous component.
  291. * </p>
  292. *
  293. * @see #space()
  294. */
  295. public void newLine() {
  296. cursorX = 0;
  297. cursorY++;
  298. }
  299. /**
  300. * Moves the cursor forward by one. If the cursor goes out of the right grid
  301. * border, it is moved to the first column of the next row.
  302. *
  303. * @see #newLine()
  304. */
  305. public void space() {
  306. cursorX++;
  307. if (cursorX >= getColumns()) {
  308. cursorX = 0;
  309. cursorY++;
  310. }
  311. }
  312. /**
  313. * Adds the component into this container to the cursor position. If the
  314. * cursor position is already occupied, the cursor is moved forwards to find
  315. * free position. If the cursor goes out from the bottom of the grid, the
  316. * grid is automatically extended.
  317. *
  318. * @param component
  319. * the component to be added, not <code>null</code>.
  320. */
  321. @Override
  322. public void addComponent(Component component) {
  323. if (component == null) {
  324. throw new IllegalArgumentException("Component must not be null");
  325. }
  326. // Finds first available place from the grid
  327. Area area;
  328. boolean done = false;
  329. while (!done) {
  330. try {
  331. area = new Area(component, cursorX, cursorY, cursorX, cursorY);
  332. checkExistingOverlaps(area);
  333. done = true;
  334. } catch (final OverlapsException e) {
  335. space();
  336. }
  337. }
  338. // Extends the grid if needed
  339. if (cursorX >= getColumns()) {
  340. setColumns(cursorX + 1);
  341. }
  342. if (cursorY >= getRows()) {
  343. setRows(cursorY + 1);
  344. }
  345. addComponent(component, cursorX, cursorY);
  346. }
  347. /**
  348. * Removes the specified component from the layout.
  349. *
  350. * @param component
  351. * the component to be removed.
  352. */
  353. @Override
  354. public void removeComponent(Component component) {
  355. // Check that the component is contained in the container
  356. if (component == null || !components.contains(component)) {
  357. return;
  358. }
  359. getState().childData.remove(component);
  360. components.remove(component);
  361. super.removeComponent(component);
  362. }
  363. /**
  364. * Removes the component specified by its cell coordinates.
  365. *
  366. * @param column
  367. * the component's column, starting from 0.
  368. * @param row
  369. * the component's row, starting from 0.
  370. */
  371. public void removeComponent(int column, int row) {
  372. // Finds the area
  373. for (final Component component : components) {
  374. final ChildComponentData childData = getState().childData
  375. .get(component);
  376. if (childData.column1 == column && childData.row1 == row) {
  377. removeComponent(component);
  378. return;
  379. }
  380. }
  381. }
  382. /**
  383. * Gets an Iterator for the components contained in the layout. By using the
  384. * Iterator it is possible to step through the contents of the layout.
  385. *
  386. * @return the Iterator of the components inside the layout.
  387. */
  388. @Override
  389. public Iterator<Component> iterator() {
  390. return Collections.unmodifiableCollection(components).iterator();
  391. }
  392. /**
  393. * Gets the number of components contained in the layout. Consistent with
  394. * the iterator returned by {@link #getComponentIterator()}.
  395. *
  396. * @return the number of contained components
  397. */
  398. @Override
  399. public int getComponentCount() {
  400. return components.size();
  401. }
  402. @Override
  403. public void beforeClientResponse(boolean initial) {
  404. super.beforeClientResponse(initial);
  405. getState().colExpand = new float[getColumns()];
  406. float colSum = getExpandRatioSum(columnExpandRatio);
  407. if (colSum == 0) {
  408. // no cols have been expanded
  409. for (int i = 0; i < getColumns(); i++) {
  410. getState().colExpand[i] = 1f;
  411. }
  412. } else {
  413. for (int i = 0; i < getColumns(); i++) {
  414. getState().colExpand[i] = getColumnExpandRatio(i);
  415. }
  416. }
  417. getState().rowExpand = new float[getRows()];
  418. float rowSum = getExpandRatioSum(rowExpandRatio);
  419. if (rowSum == 0) {
  420. // no rows have been expanded
  421. for (int i = 0; i < getRows(); i++) {
  422. getState().rowExpand[i] = 1f;
  423. }
  424. } else {
  425. for (int i = 0; i < getRows(); i++) {
  426. getState().rowExpand[i] = getRowExpandRatio(i);
  427. }
  428. }
  429. }
  430. private float getExpandRatioSum(Map<Integer, Float> ratioMap) {
  431. float sum = 0;
  432. for (Float expandRatio : ratioMap.values()) {
  433. sum += expandRatio;
  434. }
  435. return sum;
  436. }
  437. /*
  438. * (non-Javadoc)
  439. *
  440. * @see com.vaadin.ui.Layout.AlignmentHandler#getComponentAlignment(com
  441. * .vaadin.ui.Component)
  442. */
  443. @Override
  444. public Alignment getComponentAlignment(Component childComponent) {
  445. ChildComponentData childComponentData = getState(false).childData
  446. .get(childComponent);
  447. if (childComponentData == null) {
  448. throw new IllegalArgumentException(
  449. "The given component is not a child of this layout");
  450. } else {
  451. return new Alignment(childComponentData.alignment);
  452. }
  453. }
  454. /**
  455. * Defines a rectangular area of cells in a GridLayout.
  456. *
  457. * <p>
  458. * Also maintains a reference to the component contained in the area.
  459. * </p>
  460. *
  461. * <p>
  462. * The area is specified by the cell coordinates of its upper left corner
  463. * (column1,row1) and lower right corner (column2,row2). As otherwise with
  464. * GridLayout, the column and row coordinates start from zero.
  465. * </p>
  466. *
  467. * @author Vaadin Ltd.
  468. * @since 3.0
  469. */
  470. public class Area implements Serializable {
  471. private final ChildComponentData childData;
  472. private final Component component;
  473. /**
  474. * <p>
  475. * Construct a new area on a grid.
  476. * </p>
  477. *
  478. * @param component
  479. * the component connected to the area.
  480. * @param column1
  481. * The column of the upper left corner cell of the area. The
  482. * leftmost column has index 0.
  483. * @param row1
  484. * The row of the upper left corner cell of the area. The
  485. * topmost row has index 0.
  486. * @param column2
  487. * The column of the lower right corner cell of the area. The
  488. * leftmost column has index 0.
  489. * @param row2
  490. * The row of the lower right corner cell of the area. The
  491. * topmost row has index 0.
  492. */
  493. public Area(Component component, int column1, int row1, int column2,
  494. int row2) {
  495. this.component = component;
  496. childData = new ChildComponentData();
  497. childData.alignment = getDefaultComponentAlignment().getBitMask();
  498. childData.column1 = column1;
  499. childData.row1 = row1;
  500. childData.column2 = column2;
  501. childData.row2 = row2;
  502. }
  503. public Area(ChildComponentData childData, Component component) {
  504. this.childData = childData;
  505. this.component = component;
  506. }
  507. /**
  508. * Tests if this Area overlaps with another Area.
  509. *
  510. * @param other
  511. * the other Area that is to be tested for overlap with this
  512. * area
  513. * @return <code>true</code> if <code>other</code> area overlaps with
  514. * this on, <code>false</code> if it does not.
  515. */
  516. public boolean overlaps(Area other) {
  517. return componentsOverlap(childData, other.childData);
  518. }
  519. /**
  520. * Gets the component connected to the area.
  521. *
  522. * @return the Component.
  523. */
  524. public Component getComponent() {
  525. return component;
  526. }
  527. /**
  528. * Gets the column of the top-left corner cell.
  529. *
  530. * @return the column of the top-left corner cell.
  531. */
  532. public int getColumn1() {
  533. return childData.column1;
  534. }
  535. /**
  536. * Gets the column of the bottom-right corner cell.
  537. *
  538. * @return the column of the bottom-right corner cell.
  539. */
  540. public int getColumn2() {
  541. return childData.column2;
  542. }
  543. /**
  544. * Gets the row of the top-left corner cell.
  545. *
  546. * @return the row of the top-left corner cell.
  547. */
  548. public int getRow1() {
  549. return childData.row1;
  550. }
  551. /**
  552. * Gets the row of the bottom-right corner cell.
  553. *
  554. * @return the row of the bottom-right corner cell.
  555. */
  556. public int getRow2() {
  557. return childData.row2;
  558. }
  559. }
  560. private static boolean componentsOverlap(ChildComponentData a,
  561. ChildComponentData b) {
  562. return a.column1 <= b.column2 && a.row1 <= b.row2
  563. && a.column2 >= b.column1 && a.row2 >= b.row1;
  564. }
  565. /**
  566. * Gridlayout does not support laying components on top of each other. An
  567. * <code>OverlapsException</code> is thrown when a component already exists
  568. * (even partly) at the same space on a grid with the new component.
  569. *
  570. * @author Vaadin Ltd.
  571. * @since 3.0
  572. */
  573. public class OverlapsException extends RuntimeException {
  574. private final Area existingArea;
  575. /**
  576. * Constructs an <code>OverlapsException</code>.
  577. *
  578. * @param existingArea
  579. */
  580. public OverlapsException(Area existingArea) {
  581. this.existingArea = existingArea;
  582. }
  583. @Override
  584. public String getMessage() {
  585. StringBuilder sb = new StringBuilder();
  586. Component component = existingArea.getComponent();
  587. sb.append(component);
  588. sb.append("( type = ");
  589. sb.append(component.getClass().getName());
  590. if (component.getCaption() != null) {
  591. sb.append(", caption = \"");
  592. sb.append(component.getCaption());
  593. sb.append("\"");
  594. }
  595. sb.append(')');
  596. sb.append(" is already added to ");
  597. sb.append(existingArea.childData.column1);
  598. sb.append(',');
  599. sb.append(existingArea.childData.column1);
  600. sb.append(',');
  601. sb.append(existingArea.childData.row1);
  602. sb.append(',');
  603. sb.append(existingArea.childData.row2);
  604. sb.append("(column1, column2, row1, row2).");
  605. return sb.toString();
  606. }
  607. /**
  608. * Gets the area .
  609. *
  610. * @return the existing area.
  611. */
  612. public Area getArea() {
  613. return existingArea;
  614. }
  615. }
  616. /**
  617. * An <code>Exception</code> object which is thrown when an area exceeds the
  618. * bounds of the grid.
  619. *
  620. * @author Vaadin Ltd.
  621. * @since 3.0
  622. */
  623. public class OutOfBoundsException extends RuntimeException {
  624. private final Area areaOutOfBounds;
  625. /**
  626. * Constructs an <code>OoutOfBoundsException</code> with the specified
  627. * detail message.
  628. *
  629. * @param areaOutOfBounds
  630. */
  631. public OutOfBoundsException(Area areaOutOfBounds) {
  632. this.areaOutOfBounds = areaOutOfBounds;
  633. }
  634. /**
  635. * Gets the area that is out of bounds.
  636. *
  637. * @return the area out of Bound.
  638. */
  639. public Area getArea() {
  640. return areaOutOfBounds;
  641. }
  642. }
  643. /**
  644. * Sets the number of columns in the grid. The column count can not be
  645. * reduced if there are any areas that would be outside of the shrunk grid.
  646. *
  647. * @param columns
  648. * the new number of columns in the grid.
  649. */
  650. public void setColumns(int columns) {
  651. // The the param
  652. if (columns < 1) {
  653. throw new IllegalArgumentException(
  654. "The number of columns and rows in the grid must be at least 1");
  655. }
  656. // In case of no change
  657. if (getColumns() == columns) {
  658. return;
  659. }
  660. // Checks for overlaps
  661. if (getColumns() > columns) {
  662. for (Entry<Connector, ChildComponentData> entry : getState().childData
  663. .entrySet()) {
  664. if (entry.getValue().column2 >= columns) {
  665. throw new OutOfBoundsException(new Area(entry.getValue(),
  666. (Component) entry.getKey()));
  667. }
  668. }
  669. }
  670. // Forget expands for removed columns
  671. if (columns < getColumns()) {
  672. for (int i = columns; i < getColumns(); i++) {
  673. columnExpandRatio.remove(i);
  674. getState().explicitColRatios.remove(i);
  675. }
  676. }
  677. getState().columns = columns;
  678. }
  679. /**
  680. * Get the number of columns in the grid.
  681. *
  682. * @return the number of columns in the grid.
  683. */
  684. public int getColumns() {
  685. return getState(false).columns;
  686. }
  687. /**
  688. * Sets the number of rows in the grid. The number of rows can not be
  689. * reduced if there are any areas that would be outside of the shrunk grid.
  690. *
  691. * @param rows
  692. * the new number of rows in the grid.
  693. */
  694. public void setRows(int rows) {
  695. // The the param
  696. if (rows < 1) {
  697. throw new IllegalArgumentException(
  698. "The number of columns and rows in the grid must be at least 1");
  699. }
  700. // In case of no change
  701. if (getRows() == rows) {
  702. return;
  703. }
  704. // Checks for overlaps
  705. if (getRows() > rows) {
  706. for (Entry<Connector, ChildComponentData> entry : getState().childData
  707. .entrySet()) {
  708. if (entry.getValue().row2 >= rows) {
  709. throw new OutOfBoundsException(new Area(entry.getValue(),
  710. (Component) entry.getKey()));
  711. }
  712. }
  713. }
  714. // Forget expands for removed rows
  715. if (rows < getRows()) {
  716. for (int i = rows; i < getRows(); i++) {
  717. rowExpandRatio.remove(i);
  718. getState().explicitRowRatios.remove(i);
  719. }
  720. }
  721. getState().rows = rows;
  722. }
  723. /**
  724. * Get the number of rows in the grid.
  725. *
  726. * @return the number of rows in the grid.
  727. */
  728. public int getRows() {
  729. return getState(false).rows;
  730. }
  731. /**
  732. * Gets the current x-position (column) of the cursor.
  733. *
  734. * <p>
  735. * The cursor position points the position for the next component that is
  736. * added without specifying its coordinates (grid cell). When the cursor
  737. * position is occupied, the next component will be added to first free
  738. * position after the cursor.
  739. * </p>
  740. *
  741. * @return the grid column the cursor is on, starting from 0.
  742. */
  743. public int getCursorX() {
  744. return cursorX;
  745. }
  746. /**
  747. * Sets the current cursor x-position. This is usually handled automatically
  748. * by GridLayout.
  749. *
  750. * @param cursorX
  751. */
  752. public void setCursorX(int cursorX) {
  753. this.cursorX = cursorX;
  754. }
  755. /**
  756. * Gets the current y-position (row) of the cursor.
  757. *
  758. * <p>
  759. * The cursor position points the position for the next component that is
  760. * added without specifying its coordinates (grid cell). When the cursor
  761. * position is occupied, the next component will be added to the first free
  762. * position after the cursor.
  763. * </p>
  764. *
  765. * @return the grid row the Cursor is on.
  766. */
  767. public int getCursorY() {
  768. return cursorY;
  769. }
  770. /**
  771. * Sets the current y-coordinate (row) of the cursor. This is usually
  772. * handled automatically by GridLayout.
  773. *
  774. * @param cursorY
  775. * the row number, starting from 0 for the topmost row.
  776. */
  777. public void setCursorY(int cursorY) {
  778. this.cursorY = cursorY;
  779. }
  780. /* Documented in superclass */
  781. @Override
  782. public void replaceComponent(Component oldComponent,
  783. Component newComponent) {
  784. // Gets the locations
  785. ChildComponentData oldLocation = getState().childData.get(oldComponent);
  786. ChildComponentData newLocation = getState().childData.get(newComponent);
  787. if (oldLocation == null) {
  788. addComponent(newComponent);
  789. } else if (newLocation == null) {
  790. removeComponent(oldComponent);
  791. addComponent(newComponent, oldLocation.column1, oldLocation.row1,
  792. oldLocation.column2, oldLocation.row2);
  793. } else {
  794. int oldAlignment = oldLocation.alignment;
  795. oldLocation.alignment = newLocation.alignment;
  796. newLocation.alignment = oldAlignment;
  797. getState().childData.put(newComponent, oldLocation);
  798. getState().childData.put(oldComponent, newLocation);
  799. }
  800. }
  801. /*
  802. * Removes all components from this container.
  803. *
  804. * @see com.vaadin.ui.ComponentContainer#removeAllComponents()
  805. */
  806. @Override
  807. public void removeAllComponents() {
  808. super.removeAllComponents();
  809. cursorX = 0;
  810. cursorY = 0;
  811. }
  812. @Override
  813. public void setComponentAlignment(Component childComponent,
  814. Alignment alignment) {
  815. ChildComponentData childComponentData = getState().childData
  816. .get(childComponent);
  817. if (childComponentData == null) {
  818. throw new IllegalArgumentException(
  819. "Component must be added to layout before using setComponentAlignment()");
  820. } else {
  821. if (alignment == null) {
  822. childComponentData.alignment = GridLayoutState.ALIGNMENT_DEFAULT
  823. .getBitMask();
  824. } else {
  825. childComponentData.alignment = alignment.getBitMask();
  826. }
  827. }
  828. }
  829. /*
  830. * (non-Javadoc)
  831. *
  832. * @see com.vaadin.ui.Layout.SpacingHandler#setSpacing(boolean)
  833. */
  834. @Override
  835. public void setSpacing(boolean spacing) {
  836. getState().spacing = spacing;
  837. }
  838. /*
  839. * (non-Javadoc)
  840. *
  841. * @see com.vaadin.ui.Layout.SpacingHandler#isSpacing()
  842. */
  843. @Override
  844. public boolean isSpacing() {
  845. return getState(false).spacing;
  846. }
  847. /**
  848. * Inserts an empty row at the specified position in the grid.
  849. *
  850. * @param row
  851. * Index of the row before which the new row will be inserted.
  852. * The leftmost row has index 0.
  853. */
  854. public void insertRow(int row) {
  855. if (row > getRows()) {
  856. throw new IllegalArgumentException("Cannot insert row at " + row
  857. + " in a gridlayout with height " + getRows());
  858. }
  859. for (ChildComponentData existingArea : getState().childData.values()) {
  860. // Areas ending below the row needs to be moved down or stretched
  861. if (existingArea.row2 >= row) {
  862. existingArea.row2++;
  863. // Stretch areas that span over the selected row
  864. if (existingArea.row1 >= row) {
  865. existingArea.row1++;
  866. }
  867. }
  868. }
  869. if (cursorY >= row) {
  870. cursorY++;
  871. }
  872. setRows(getRows() + 1);
  873. markAsDirty();
  874. }
  875. /**
  876. * Removes a row and all the components in the row.
  877. *
  878. * <p>
  879. * Components which span over several rows are removed if the selected row
  880. * is on the first row of such a component.
  881. * </p>
  882. *
  883. * <p>
  884. * If the last row is removed then all remaining components will be removed
  885. * and the grid will be reduced to one row. The cursor will be moved to the
  886. * upper left cell of the grid.
  887. * </p>
  888. *
  889. * @param row
  890. * Index of the row to remove. The leftmost row has index 0.
  891. */
  892. public void removeRow(int row) {
  893. if (row >= getRows()) {
  894. throw new IllegalArgumentException("Cannot delete row " + row
  895. + " from a gridlayout with height " + getRows());
  896. }
  897. // Remove all components in row
  898. for (int col = 0; col < getColumns(); col++) {
  899. removeComponent(col, row);
  900. }
  901. // Shrink or remove areas in the selected row
  902. for (ChildComponentData existingArea : getState().childData.values()) {
  903. if (existingArea.row2 >= row) {
  904. existingArea.row2--;
  905. if (existingArea.row1 > row) {
  906. existingArea.row1--;
  907. }
  908. }
  909. }
  910. if (getRows() == 1) {
  911. /*
  912. * Removing the last row means that the dimensions of the Grid
  913. * layout will be truncated to 1 empty row and the cursor is moved
  914. * to the first cell
  915. */
  916. cursorX = 0;
  917. cursorY = 0;
  918. } else {
  919. setRows(getRows() - 1);
  920. if (cursorY > row) {
  921. cursorY--;
  922. }
  923. }
  924. markAsDirty();
  925. }
  926. /**
  927. * Sets the expand ratio of given column.
  928. *
  929. * <p>
  930. * The expand ratio defines how excess space is distributed among columns.
  931. * Excess space means space that is left over from components that are not
  932. * sized relatively. By default, the excess space is distributed evenly.
  933. * </p>
  934. *
  935. * <p>
  936. * Note, that width of this GridLayout needs to be defined (fixed or
  937. * relative, as opposed to undefined height) for this method to have any
  938. * effect.
  939. * <p>
  940. * Note that checking for relative width for the child components is done on
  941. * the server so you cannot set a child component to have undefined width on
  942. * the server and set it to <code>100%</code> in CSS. You must set it to
  943. * <code>100%</code> on the server.
  944. *
  945. * @see #setWidth(float, Unit)
  946. *
  947. * @param columnIndex
  948. * @param ratio
  949. */
  950. public void setColumnExpandRatio(int columnIndex, float ratio) {
  951. columnExpandRatio.put(columnIndex, ratio);
  952. getState().explicitColRatios.add(columnIndex);
  953. markAsDirty();
  954. }
  955. /**
  956. * Returns the expand ratio of given column.
  957. *
  958. * @see #setColumnExpandRatio(int, float)
  959. *
  960. * @param columnIndex
  961. * @return the expand ratio, 0.0f by default
  962. */
  963. public float getColumnExpandRatio(int columnIndex) {
  964. Float r = columnExpandRatio.get(columnIndex);
  965. return r == null ? 0 : r.floatValue();
  966. }
  967. /**
  968. * Sets the expand ratio of given row.
  969. *
  970. * <p>
  971. * Expand ratio defines how excess space is distributed among rows. Excess
  972. * space means the space left over from components that are not sized
  973. * relatively. By default, the excess space is distributed evenly.
  974. * </p>
  975. *
  976. * <p>
  977. * Note, that height of this GridLayout needs to be defined (fixed or
  978. * relative, as opposed to undefined height) for this method to have any
  979. * effect.
  980. * <p>
  981. * Note that checking for relative height for the child components is done
  982. * on the server so you cannot set a child component to have undefined
  983. * height on the server and set it to <code>100%</code> in CSS. You must set
  984. * it to <code>100%</code> on the server.
  985. *
  986. * @see #setHeight(float, Unit)
  987. *
  988. * @param rowIndex
  989. * The row index, starting from 0 for the topmost row.
  990. * @param ratio
  991. */
  992. public void setRowExpandRatio(int rowIndex, float ratio) {
  993. rowExpandRatio.put(rowIndex, ratio);
  994. getState().explicitRowRatios.add(rowIndex);
  995. markAsDirty();
  996. }
  997. /**
  998. * Returns the expand ratio of given row.
  999. *
  1000. * @see #setRowExpandRatio(int, float)
  1001. *
  1002. * @param rowIndex
  1003. * The row index, starting from 0 for the topmost row.
  1004. * @return the expand ratio, 0.0f by default
  1005. */
  1006. public float getRowExpandRatio(int rowIndex) {
  1007. Float r = rowExpandRatio.get(rowIndex);
  1008. return r == null ? 0 : r.floatValue();
  1009. }
  1010. /**
  1011. * Gets the Component at given index.
  1012. *
  1013. * @param x
  1014. * The column index, starting from 0 for the leftmost column.
  1015. * @param y
  1016. * The row index, starting from 0 for the topmost row.
  1017. * @return Component in given cell or null if empty
  1018. */
  1019. public Component getComponent(int x, int y) {
  1020. for (Entry<Connector, ChildComponentData> entry : getState(
  1021. false).childData.entrySet()) {
  1022. ChildComponentData childData = entry.getValue();
  1023. if (childData.column1 <= x && x <= childData.column2
  1024. && childData.row1 <= y && y <= childData.row2) {
  1025. return (Component) entry.getKey();
  1026. }
  1027. }
  1028. return null;
  1029. }
  1030. /**
  1031. * Returns information about the area where given component is laid in the
  1032. * GridLayout.
  1033. *
  1034. * @param component
  1035. * the component whose area information is requested.
  1036. * @return an Area object that contains information how component is laid in
  1037. * the grid
  1038. */
  1039. public Area getComponentArea(Component component) {
  1040. ChildComponentData childComponentData = getState(false).childData
  1041. .get(component);
  1042. if (childComponentData == null) {
  1043. return null;
  1044. } else {
  1045. return new Area(childComponentData, component);
  1046. }
  1047. }
  1048. @Override
  1049. public Registration addLayoutClickListener(LayoutClickListener listener) {
  1050. return addListener(EventId.LAYOUT_CLICK_EVENT_IDENTIFIER,
  1051. LayoutClickEvent.class, listener,
  1052. LayoutClickListener.clickMethod);
  1053. }
  1054. @Override
  1055. @Deprecated
  1056. public void removeLayoutClickListener(LayoutClickListener listener) {
  1057. removeListener(EventId.LAYOUT_CLICK_EVENT_IDENTIFIER,
  1058. LayoutClickEvent.class, listener);
  1059. }
  1060. /*
  1061. * (non-Javadoc)
  1062. *
  1063. * @see com.vaadin.ui.Layout.MarginHandler#setMargin(boolean)
  1064. */
  1065. @Override
  1066. public void setMargin(boolean enabled) {
  1067. setMargin(new MarginInfo(enabled));
  1068. }
  1069. /*
  1070. * (non-Javadoc)
  1071. *
  1072. * @see com.vaadin.ui.Layout.MarginHandler#setMargin(com.vaadin.shared.ui.
  1073. * MarginInfo )
  1074. */
  1075. @Override
  1076. public void setMargin(MarginInfo marginInfo) {
  1077. getState().marginsBitmask = marginInfo.getBitMask();
  1078. }
  1079. /*
  1080. * (non-Javadoc)
  1081. *
  1082. * @see com.vaadin.ui.Layout.MarginHandler#getMargin()
  1083. */
  1084. @Override
  1085. public MarginInfo getMargin() {
  1086. return new MarginInfo(getState(false).marginsBitmask);
  1087. }
  1088. /*
  1089. * (non-Javadoc)
  1090. *
  1091. * @see com.vaadin.ui.Layout.AlignmentHandler#getDefaultComponentAlignment()
  1092. */
  1093. @Override
  1094. public Alignment getDefaultComponentAlignment() {
  1095. return defaultComponentAlignment;
  1096. }
  1097. /*
  1098. * (non-Javadoc)
  1099. *
  1100. * @see
  1101. * com.vaadin.ui.Layout.AlignmentHandler#setDefaultComponentAlignment(com
  1102. * .vaadin.ui.Alignment)
  1103. */
  1104. @Override
  1105. public void setDefaultComponentAlignment(Alignment defaultAlignment) {
  1106. defaultComponentAlignment = defaultAlignment;
  1107. }
  1108. /**
  1109. * Sets whether empty rows and columns should be considered as non-existent
  1110. * when rendering or not. If this is set to true then the spacing between
  1111. * multiple empty columns (or rows) will be collapsed.
  1112. *
  1113. * The default behavior is to consider all rows and columns as visible
  1114. *
  1115. * NOTE that this must be set before the initial rendering takes place.
  1116. * Updating this on the fly is not supported.
  1117. *
  1118. * @since 7.3
  1119. * @param hideEmptyRowsAndColumns
  1120. * true to hide empty rows and columns, false to leave them as-is
  1121. */
  1122. public void setHideEmptyRowsAndColumns(boolean hideEmptyRowsAndColumns) {
  1123. getState().hideEmptyRowsAndColumns = hideEmptyRowsAndColumns;
  1124. }
  1125. /**
  1126. * Checks whether whether empty rows and columns should be considered as
  1127. * non-existent when rendering or not.
  1128. *
  1129. * @see #setHideEmptyRowsAndColumns(boolean)
  1130. * @since 7.3
  1131. * @return true if empty rows and columns are hidden, false otherwise
  1132. */
  1133. public boolean isHideEmptyRowsAndColumns() {
  1134. return getState(false).hideEmptyRowsAndColumns;
  1135. }
  1136. /**
  1137. * {@inheritDoc}
  1138. * <p>
  1139. * After reading the design, cursorY is set to point to a row outside of the
  1140. * GridLayout area. CursorX is reset to 0.
  1141. */
  1142. @Override
  1143. public void readDesign(Element design, DesignContext designContext) {
  1144. super.readDesign(design, designContext);
  1145. setMargin(readMargin(design, getMargin(), designContext));
  1146. if (design.childNodeSize() > 0) {
  1147. // Touch content only if there is some content specified. This is
  1148. // needed to be able to use extended GridLayouts which add
  1149. // components in the constructor (e.g. Designs based on GridLayout).
  1150. readChildComponents(design.children(), designContext);
  1151. }
  1152. // Set cursor position explicitly
  1153. setCursorY(getRows());
  1154. setCursorX(0);
  1155. }
  1156. private void readChildComponents(Elements childElements,
  1157. DesignContext designContext) {
  1158. List<Element> rowElements = new ArrayList<>();
  1159. List<Map<Integer, Component>> rows = new ArrayList<>();
  1160. // Prepare a 2D map for reading column contents
  1161. for (Element e : childElements) {
  1162. if (e.tagName().equalsIgnoreCase("row")) {
  1163. rowElements.add(e);
  1164. rows.add(new HashMap<>());
  1165. }
  1166. }
  1167. setRows(Math.max(rows.size(), 1));
  1168. Map<Component, Alignment> alignments = new HashMap<>();
  1169. List<Float> columnExpandRatios = new ArrayList<>();
  1170. for (int row = 0; row < rowElements.size(); ++row) {
  1171. Element rowElement = rowElements.get(row);
  1172. // Row Expand
  1173. if (rowElement.hasAttr("expand")) {
  1174. float expand = DesignAttributeHandler.readAttribute("expand",
  1175. rowElement.attributes(), float.class);
  1176. setRowExpandRatio(row, expand);
  1177. }
  1178. Elements cols = rowElement.children();
  1179. // Amount of skipped columns due to spanned components
  1180. int skippedColumns = 0;
  1181. for (int column = 0; column < cols.size(); ++column) {
  1182. while (rows.get(row).containsKey(column + skippedColumns)) {
  1183. // Skip any spanned components
  1184. skippedColumns++;
  1185. }
  1186. Element col = cols.get(column);
  1187. Component child = null;
  1188. if (!col.children().isEmpty()) {
  1189. Element childElement = col.child(0);
  1190. child = designContext.readDesign(childElement);
  1191. alignments.put(child, DesignAttributeHandler
  1192. .readAlignment(childElement.attributes()));
  1193. // TODO: Currently ignoring any extra children.
  1194. // Needs Error handling?
  1195. } // Else: Empty placeholder. No child component.
  1196. // Handle rowspan and colspan for this child component
  1197. Attributes attr = col.attributes();
  1198. int colspan = DesignAttributeHandler.readAttribute("colspan",
  1199. attr, 1, int.class);
  1200. int rowspan = DesignAttributeHandler.readAttribute("rowspan",
  1201. attr, 1, int.class);
  1202. for (int rowIndex = row; rowIndex < row + rowspan; ++rowIndex) {
  1203. for (int colIndex = column; colIndex < column
  1204. + colspan; ++colIndex) {
  1205. if (rowIndex == rows.size()) {
  1206. // Rowspan with not enough rows. Fix by adding rows.
  1207. rows.add(new HashMap<>());
  1208. }
  1209. rows.get(rowIndex).put(colIndex + skippedColumns,
  1210. child);
  1211. }
  1212. }
  1213. // Read column expand ratios if handling the first row.
  1214. if (row == 0) {
  1215. if (col.hasAttr("expand")) {
  1216. for (String expand : col.attr("expand").split(",")) {
  1217. columnExpandRatios.add(Float.parseFloat(expand));
  1218. }
  1219. } else {
  1220. for (int c = 0; c < colspan; ++c) {
  1221. columnExpandRatios.add(0f);
  1222. }
  1223. }
  1224. }
  1225. skippedColumns += (colspan - 1);
  1226. }
  1227. }
  1228. // Calculate highest column count and set columns
  1229. int colMax = 0;
  1230. for (Map<Integer, Component> cols : rows) {
  1231. if (colMax < cols.size()) {
  1232. colMax = cols.size();
  1233. }
  1234. }
  1235. setColumns(Math.max(colMax, 1));
  1236. for (int i = 0; i < columnExpandRatios.size(); ++i) {
  1237. setColumnExpandRatio(i, columnExpandRatios.get(i));
  1238. }
  1239. // Reiterate through the 2D map and add components to GridLayout
  1240. Set<Component> visited = new HashSet<>();
  1241. // Ignore any missing components
  1242. visited.add(null);
  1243. for (int i = 0; i < rows.size(); ++i) {
  1244. Map<Integer, Component> row = rows.get(i);
  1245. for (int j = 0; j < colMax; ++j) {
  1246. Component child = row.get(j);
  1247. if (visited.contains(child)) {
  1248. // Empty location or already handled child
  1249. continue;
  1250. }
  1251. visited.add(child);
  1252. // Figure out col and rowspan from 2D map
  1253. int colspan = 0;
  1254. while (j + colspan + 1 < row.size()
  1255. && row.get(j + colspan + 1) == child) {
  1256. ++colspan;
  1257. }
  1258. int rowspan = 0;
  1259. while (i + rowspan + 1 < rows.size()
  1260. && rows.get(i + rowspan + 1).get(j) == child) {
  1261. ++rowspan;
  1262. }
  1263. // Add component with area
  1264. addComponent(child, j, i, j + colspan, i + rowspan);
  1265. setComponentAlignment(child, alignments.get(child));
  1266. }
  1267. }
  1268. }
  1269. @Override
  1270. public void writeDesign(Element design, DesignContext designContext) {
  1271. super.writeDesign(design, designContext);
  1272. GridLayout def = designContext.getDefaultInstance(this);
  1273. writeMargin(design, getMargin(), def.getMargin(), designContext);
  1274. if (!designContext.shouldWriteChildren(this, def)) {
  1275. return;
  1276. }
  1277. if (components.isEmpty()) {
  1278. writeEmptyColsAndRows(design, designContext);
  1279. return;
  1280. }
  1281. final Map<Connector, ChildComponentData> childData = getState().childData;
  1282. // Make a 2D map of component areas.
  1283. Component[][] componentMap = new Component[getState().rows][getState().columns];
  1284. final Component dummyComponent = new Label("");
  1285. for (Component component : components) {
  1286. ChildComponentData coords = childData.get(component);
  1287. for (int row = coords.row1; row <= coords.row2; ++row) {
  1288. for (int col = coords.column1; col <= coords.column2; ++col) {
  1289. componentMap[row][col] = component;
  1290. }
  1291. }
  1292. }
  1293. // Go through the map and write only needed column tags
  1294. Set<Connector> visited = new HashSet<>();
  1295. // Skip the dummy placeholder
  1296. visited.add(dummyComponent);
  1297. for (int i = 0; i < componentMap.length; ++i) {
  1298. Element row = design.appendElement("row");
  1299. // Row Expand
  1300. DesignAttributeHandler.writeAttribute("expand", row.attributes(),
  1301. getRowExpandRatio(i), 0.0f, float.class, designContext);
  1302. int colspan = 1;
  1303. Element col;
  1304. for (int j = 0; j < componentMap[i].length; ++j) {
  1305. Component child = componentMap[i][j];
  1306. if (child != null) {
  1307. if (visited.contains(child)) {
  1308. // Child has already been written in the design
  1309. continue;
  1310. }
  1311. visited.add(child);
  1312. Element childElement = designContext.createElement(child);
  1313. col = row.appendElement("column");
  1314. // Write child data into design
  1315. ChildComponentData coords = childData.get(child);
  1316. Alignment alignment = getComponentAlignment(child);
  1317. DesignAttributeHandler.writeAlignment(childElement,
  1318. alignment);
  1319. col.appendChild(childElement);
  1320. if (coords.row1 != coords.row2) {
  1321. col.attr("rowspan",
  1322. "" + (1 + coords.row2 - coords.row1));
  1323. }
  1324. colspan = 1 + coords.column2 - coords.column1;
  1325. if (colspan > 1) {
  1326. col.attr("colspan", "" + colspan);
  1327. }
  1328. } else {
  1329. boolean hasExpands = false;
  1330. if (i == 0 && lastComponentOnRow(componentMap[i], j,
  1331. visited)) {
  1332. // A column with expand and no content in the end of
  1333. // first row needs to be present.
  1334. for (int c = j; c < componentMap[i].length; ++c) {
  1335. if (getColumnExpandRatio(c) > 0) {
  1336. hasExpands = true;
  1337. }
  1338. }
  1339. }
  1340. if (lastComponentOnRow(componentMap[i], j, visited)
  1341. && !hasExpands) {
  1342. continue;
  1343. }
  1344. // Empty placeholder tag.
  1345. col = row.appendElement("column");
  1346. // Use colspan to make placeholders more pleasant
  1347. while (j + colspan < componentMap[i].length
  1348. && componentMap[i][j + colspan] == child) {
  1349. ++colspan;
  1350. }
  1351. int rowspan = getRowSpan(componentMap, i, j, colspan,
  1352. child);
  1353. if (colspan > 1) {
  1354. col.attr("colspan", "" + colspan);
  1355. }
  1356. if (rowspan > 1) {
  1357. col.attr("rowspan", "" + rowspan);
  1358. }
  1359. for (int x = 0; x < rowspan; ++x) {
  1360. for (int y = 0; y < colspan; ++y) {
  1361. // Mark handled columns
  1362. componentMap[i + x][j + y] = dummyComponent;
  1363. }
  1364. }
  1365. }
  1366. // Column expands
  1367. if (i == 0) {
  1368. // Only do expands on first row
  1369. String expands = "";
  1370. boolean expandRatios = false;
  1371. for (int c = 0; c < colspan; ++c) {
  1372. float colExpand = getColumnExpandRatio(j + c);
  1373. if (colExpand > 0) {
  1374. expandRatios = true;
  1375. }
  1376. expands += (c > 0 ? "," : "") + colExpand;
  1377. }
  1378. if (expandRatios) {
  1379. col.attr("expand", expands);
  1380. }
  1381. }
  1382. j += colspan - 1;
  1383. }
  1384. }
  1385. }
  1386. /**
  1387. * Fills in the design with rows and empty columns. This needs to be done
  1388. * for empty {@link GridLayout}, because there's no other way to serialize
  1389. * info about number of columns and rows if there are absolutely no
  1390. * components in the {@link GridLayout}
  1391. *
  1392. * @param design
  1393. * @param designContext
  1394. */
  1395. private void writeEmptyColsAndRows(Element design,
  1396. DesignContext designContext) {
  1397. int rowCount = getState(false).rows;
  1398. int colCount = getState(false).columns;
  1399. // only write cols and rows tags if size is not 1x1
  1400. if (rowCount == 1 && colCount == 1) {
  1401. return;
  1402. }
  1403. for (int i = 0; i < rowCount; i++) {
  1404. Element row = design.appendElement("row");
  1405. for (int j = 0; j < colCount; j++) {
  1406. row.appendElement("column");
  1407. }
  1408. }
  1409. }
  1410. private int getRowSpan(Component[][] compMap, int i, int j, int colspan,
  1411. Component child) {
  1412. int rowspan = 1;
  1413. while (i + rowspan < compMap.length
  1414. && compMap[i + rowspan][j] == child) {
  1415. for (int k = 0; k < colspan; ++k) {
  1416. if (compMap[i + rowspan][j + k] != child) {
  1417. return rowspan;
  1418. }
  1419. }
  1420. rowspan++;
  1421. }
  1422. return rowspan;
  1423. }
  1424. private boolean lastComponentOnRow(Component[] componentArray, int j,
  1425. Set<Connector> visited) {
  1426. while ((++j) < componentArray.length) {
  1427. Component child = componentArray[j];
  1428. if (child != null && !visited.contains(child)) {
  1429. return false;
  1430. }
  1431. }
  1432. return true;
  1433. }
  1434. @Override
  1435. protected Collection<String> getCustomAttributes() {
  1436. Collection<String> result = super.getCustomAttributes();
  1437. result.add("cursor-x");
  1438. result.add("cursor-y");
  1439. result.add("rows");
  1440. result.add("columns");
  1441. result.add("margin");
  1442. result.add("margin-left");
  1443. result.add("margin-right");
  1444. result.add("margin-top");
  1445. result.add("margin-bottom");
  1446. return result;
  1447. }
  1448. }