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