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 44KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438
  1. /*
  2. * Copyright 2011 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.Collections;
  19. import java.util.HashMap;
  20. import java.util.Iterator;
  21. import java.util.LinkedList;
  22. import java.util.Map;
  23. import java.util.Map.Entry;
  24. import com.vaadin.event.LayoutEvents.LayoutClickEvent;
  25. import com.vaadin.event.LayoutEvents.LayoutClickListener;
  26. import com.vaadin.event.LayoutEvents.LayoutClickNotifier;
  27. import com.vaadin.server.LegacyPaint;
  28. import com.vaadin.server.PaintException;
  29. import com.vaadin.server.PaintTarget;
  30. import com.vaadin.shared.Connector;
  31. import com.vaadin.shared.EventId;
  32. import com.vaadin.shared.MouseEventDetails;
  33. import com.vaadin.shared.ui.MarginInfo;
  34. import com.vaadin.shared.ui.gridlayout.GridLayoutServerRpc;
  35. import com.vaadin.shared.ui.gridlayout.GridLayoutState;
  36. /**
  37. * A layout where the components are laid out on a grid using cell coordinates.
  38. *
  39. * <p>
  40. * The GridLayout also maintains a cursor for adding components in
  41. * left-to-right, top-to-bottom order.
  42. * </p>
  43. *
  44. * <p>
  45. * Each component in a <code>GridLayout</code> uses a defined
  46. * {@link GridLayout.Area area} (column1,row1,column2,row2) from the grid. The
  47. * components may not overlap with the existing components - if you try to do so
  48. * you will get an {@link OverlapsException}. Adding a component with cursor
  49. * automatically extends the grid by increasing the grid height.
  50. * </p>
  51. *
  52. * <p>
  53. * The grid coordinates, which are specified by a row and column index, always
  54. * start from 0 for the topmost row and the leftmost column.
  55. * </p>
  56. *
  57. * @author Vaadin Ltd.
  58. * @since 3.0
  59. */
  60. @SuppressWarnings("serial")
  61. public class GridLayout extends AbstractLayout implements
  62. Layout.AlignmentHandler, Layout.SpacingHandler, Layout.MarginHandler,
  63. LayoutClickNotifier, LegacyComponent {
  64. private GridLayoutServerRpc rpc = new GridLayoutServerRpc() {
  65. @Override
  66. public void layoutClick(MouseEventDetails mouseDetails,
  67. Connector clickedConnector) {
  68. fireEvent(LayoutClickEvent.createEvent(GridLayout.this,
  69. mouseDetails, clickedConnector));
  70. }
  71. };
  72. /**
  73. * Cursor X position: this is where the next component with unspecified x,y
  74. * is inserted
  75. */
  76. private int cursorX = 0;
  77. /**
  78. * Cursor Y position: this is where the next component with unspecified x,y
  79. * is inserted
  80. */
  81. private int cursorY = 0;
  82. /**
  83. * Contains all items that are placed on the grid. These are components with
  84. * grid area definition.
  85. */
  86. private final LinkedList<Area> areas = new LinkedList<Area>();
  87. /**
  88. * Mapping from components to their respective areas.
  89. */
  90. private final LinkedList<Component> components = new LinkedList<Component>();
  91. /**
  92. * Mapping from components to alignments (horizontal + vertical).
  93. */
  94. private Map<Component, Alignment> componentToAlignment = new HashMap<Component, Alignment>();
  95. private static final Alignment ALIGNMENT_DEFAULT = Alignment.TOP_LEFT;
  96. /**
  97. * Has there been rows inserted or deleted in the middle of the layout since
  98. * the last paint operation.
  99. */
  100. private boolean structuralChange = false;
  101. private Map<Integer, Float> columnExpandRatio = new HashMap<Integer, Float>();
  102. private Map<Integer, Float> rowExpandRatio = new HashMap<Integer, Float>();
  103. /**
  104. * Constructor for a grid of given size (number of columns and rows).
  105. *
  106. * The grid may grow or shrink later. Grid grows automatically if you add
  107. * components outside its area.
  108. *
  109. * @param columns
  110. * Number of columns in the grid.
  111. * @param rows
  112. * Number of rows in the grid.
  113. */
  114. public GridLayout(int columns, int rows) {
  115. setColumns(columns);
  116. setRows(rows);
  117. registerRpc(rpc);
  118. }
  119. /**
  120. * Constructs an empty (1x1) grid layout that is extended as needed.
  121. */
  122. public GridLayout() {
  123. this(1, 1);
  124. }
  125. /**
  126. * Constructs a GridLayout of given size (number of columns and rows) and
  127. * adds the given components in order to the grid.
  128. *
  129. * @see #addComponents(Component...)
  130. *
  131. * @param columns
  132. * Number of columns in the grid.
  133. * @param rows
  134. * Number of rows in the grid.
  135. * @param children
  136. * Components to add to the grid.
  137. */
  138. public GridLayout(int columns, int rows, Component... children) {
  139. this(columns, rows);
  140. addComponents(children);
  141. }
  142. @Override
  143. protected GridLayoutState getState() {
  144. return (GridLayoutState) super.getState();
  145. }
  146. /**
  147. * <p>
  148. * Adds a component to the grid in the specified area. The area is defined
  149. * by specifying the upper left corner (column1, row1) and the lower right
  150. * corner (column2, row2) of the area. The coordinates are zero-based.
  151. * </p>
  152. *
  153. * <p>
  154. * If the area overlaps with any of the existing components already present
  155. * in the grid, the operation will fail and an {@link OverlapsException} is
  156. * thrown.
  157. * </p>
  158. *
  159. * @param component
  160. * the component to be added.
  161. * @param column1
  162. * the column of the upper left corner of the area <code>c</code>
  163. * is supposed to occupy. The leftmost column has index 0.
  164. * @param row1
  165. * the row of the upper left corner of the area <code>c</code> is
  166. * supposed to occupy. The topmost row has index 0.
  167. * @param column2
  168. * the column of the lower right corner of the area
  169. * <code>c</code> is supposed to occupy.
  170. * @param row2
  171. * the row of the lower right corner of the area <code>c</code>
  172. * is supposed to occupy.
  173. * @throws OverlapsException
  174. * if the new component overlaps with any of the components
  175. * already in the grid.
  176. * @throws OutOfBoundsException
  177. * if the cells are outside the grid area.
  178. */
  179. public void addComponent(Component component, int column1, int row1,
  180. int column2, int row2) throws OverlapsException,
  181. OutOfBoundsException {
  182. if (component == null) {
  183. throw new NullPointerException("Component must not be null");
  184. }
  185. // Checks that the component does not already exist in the container
  186. if (components.contains(component)) {
  187. throw new IllegalArgumentException(
  188. "Component is already in the container");
  189. }
  190. // Creates the area
  191. final Area area = new Area(component, column1, row1, column2, row2);
  192. // Checks the validity of the coordinates
  193. if (column2 < column1 || row2 < row1) {
  194. throw new IllegalArgumentException(
  195. "Illegal coordinates for the component");
  196. }
  197. if (column1 < 0 || row1 < 0 || column2 >= getColumns()
  198. || row2 >= getRows()) {
  199. throw new OutOfBoundsException(area);
  200. }
  201. // Checks that newItem does not overlap with existing items
  202. checkExistingOverlaps(area);
  203. // Inserts the component to right place at the list
  204. // Respect top-down, left-right ordering
  205. // component.setParent(this);
  206. final Iterator<Area> i = areas.iterator();
  207. int index = 0;
  208. boolean done = false;
  209. while (!done && i.hasNext()) {
  210. final Area existingArea = i.next();
  211. if ((existingArea.row1 >= row1 && existingArea.column1 > column1)
  212. || existingArea.row1 > row1) {
  213. areas.add(index, area);
  214. components.add(index, component);
  215. done = true;
  216. }
  217. index++;
  218. }
  219. if (!done) {
  220. areas.addLast(area);
  221. components.addLast(component);
  222. }
  223. // Attempt to add to super
  224. try {
  225. super.addComponent(component);
  226. } catch (IllegalArgumentException e) {
  227. areas.remove(area);
  228. components.remove(component);
  229. throw e;
  230. }
  231. // update cursor position, if it's within this area; use first position
  232. // outside this area, even if it's occupied
  233. if (cursorX >= column1 && cursorX <= column2 && cursorY >= row1
  234. && cursorY <= row2) {
  235. // cursor within area
  236. cursorX = column2 + 1; // one right of area
  237. if (cursorX >= getColumns()) {
  238. // overflowed columns
  239. cursorX = 0; // first col
  240. // move one row down, or one row under the area
  241. cursorY = (column1 == 0 ? row2 : row1) + 1;
  242. } else {
  243. cursorY = row1;
  244. }
  245. }
  246. markAsDirty();
  247. }
  248. /**
  249. * Tests if the given area overlaps with any of the items already on the
  250. * grid.
  251. *
  252. * @param area
  253. * the Area to be checked for overlapping.
  254. * @throws OverlapsException
  255. * if <code>area</code> overlaps with any existing area.
  256. */
  257. private void checkExistingOverlaps(Area area) throws OverlapsException {
  258. for (final Iterator<Area> i = areas.iterator(); i.hasNext();) {
  259. final Area existingArea = i.next();
  260. if (existingArea.overlaps(area)) {
  261. // Component not added, overlaps with existing component
  262. throw new OverlapsException(existingArea);
  263. }
  264. }
  265. }
  266. /**
  267. * Adds the component to the grid in cells column1,row1 (NortWest corner of
  268. * the area.) End coordinates (SouthEast corner of the area) are the same as
  269. * column1,row1. The coordinates are zero-based. Component width and height
  270. * is 1.
  271. *
  272. * @param component
  273. * the component to be added.
  274. * @param column
  275. * the column index, starting from 0.
  276. * @param row
  277. * the row index, starting from 0.
  278. * @throws OverlapsException
  279. * if the new component overlaps with any of the components
  280. * already in the grid.
  281. * @throws OutOfBoundsException
  282. * if the cell is outside the grid area.
  283. */
  284. public void addComponent(Component component, int column, int row)
  285. throws OverlapsException, OutOfBoundsException {
  286. this.addComponent(component, column, row, column, row);
  287. }
  288. /**
  289. * Forces the next component to be added at the beginning of the next line.
  290. *
  291. * <p>
  292. * Sets the cursor column to 0 and increments the cursor row by one.
  293. * </p>
  294. *
  295. * <p>
  296. * By calling this function you can ensure that no more components are added
  297. * right of the previous component.
  298. * </p>
  299. *
  300. * @see #space()
  301. */
  302. public void newLine() {
  303. cursorX = 0;
  304. cursorY++;
  305. }
  306. /**
  307. * Moves the cursor forward by one. If the cursor goes out of the right grid
  308. * border, it is moved to the first column of the next row.
  309. *
  310. * @see #newLine()
  311. */
  312. public void space() {
  313. cursorX++;
  314. if (cursorX >= getColumns()) {
  315. cursorX = 0;
  316. cursorY++;
  317. }
  318. }
  319. /**
  320. * Adds the component into this container to the cursor position. If the
  321. * cursor position is already occupied, the cursor is moved forwards to find
  322. * free position. If the cursor goes out from the bottom of the grid, the
  323. * grid is automatically extended.
  324. *
  325. * @param component
  326. * the component to be added.
  327. */
  328. @Override
  329. public void addComponent(Component component) {
  330. // Finds first available place from the grid
  331. Area area;
  332. boolean done = false;
  333. while (!done) {
  334. try {
  335. area = new Area(component, cursorX, cursorY, cursorX, cursorY);
  336. checkExistingOverlaps(area);
  337. done = true;
  338. } catch (final OverlapsException e) {
  339. space();
  340. }
  341. }
  342. // Extends the grid if needed
  343. if (cursorX >= getColumns()) {
  344. setColumns(cursorX + 1);
  345. }
  346. if (cursorY >= getRows()) {
  347. setRows(cursorY + 1);
  348. }
  349. addComponent(component, cursorX, cursorY);
  350. }
  351. /**
  352. * Removes the specified component from the layout.
  353. *
  354. * @param component
  355. * the component to be removed.
  356. */
  357. @Override
  358. public void removeComponent(Component component) {
  359. // Check that the component is contained in the container
  360. if (component == null || !components.contains(component)) {
  361. return;
  362. }
  363. Area area = null;
  364. for (final Iterator<Area> i = areas.iterator(); area == null
  365. && i.hasNext();) {
  366. final Area a = i.next();
  367. if (a.getComponent() == component) {
  368. area = a;
  369. }
  370. }
  371. components.remove(component);
  372. if (area != null) {
  373. areas.remove(area);
  374. }
  375. componentToAlignment.remove(component);
  376. super.removeComponent(component);
  377. markAsDirty();
  378. }
  379. /**
  380. * Removes the component specified by its cell coordinates.
  381. *
  382. * @param column
  383. * the component's column, starting from 0.
  384. * @param row
  385. * the component's row, starting from 0.
  386. */
  387. public void removeComponent(int column, int row) {
  388. // Finds the area
  389. for (final Iterator<Area> i = areas.iterator(); i.hasNext();) {
  390. final Area area = i.next();
  391. if (area.getColumn1() == column && area.getRow1() == row) {
  392. removeComponent(area.getComponent());
  393. return;
  394. }
  395. }
  396. }
  397. /**
  398. * Gets an Iterator for the components contained in the layout. By using the
  399. * Iterator it is possible to step through the contents of the layout.
  400. *
  401. * @return the Iterator of the components inside the layout.
  402. */
  403. @Override
  404. public Iterator<Component> iterator() {
  405. return Collections.unmodifiableCollection(components).iterator();
  406. }
  407. /**
  408. * Gets the number of components contained in the layout. Consistent with
  409. * the iterator returned by {@link #getComponentIterator()}.
  410. *
  411. * @return the number of contained components
  412. */
  413. @Override
  414. public int getComponentCount() {
  415. return components.size();
  416. }
  417. @Override
  418. public void changeVariables(Object source, Map<String, Object> variables) {
  419. // TODO Remove once LegacyComponent is no longer implemented
  420. }
  421. /**
  422. * Paints the contents of this component.
  423. *
  424. * @param target
  425. * the Paint Event.
  426. * @throws PaintException
  427. * if the paint operation failed.
  428. */
  429. @Override
  430. public void paintContent(PaintTarget target) throws PaintException {
  431. // TODO refactor attribute names in future release.
  432. target.addAttribute("structuralChange", structuralChange);
  433. structuralChange = false;
  434. // Area iterator
  435. final Iterator<Area> areaiterator = areas.iterator();
  436. // Current item to be processed (fetch first item)
  437. Area area = areaiterator.hasNext() ? (Area) areaiterator.next() : null;
  438. // Collects rowspan related information here
  439. final HashMap<Integer, Integer> cellUsed = new HashMap<Integer, Integer>();
  440. // Empty cell collector
  441. int emptyCells = 0;
  442. final String[] alignmentsArray = new String[components.size()];
  443. final Integer[] columnExpandRatioArray = new Integer[getColumns()];
  444. final Integer[] rowExpandRatioArray = new Integer[getRows()];
  445. int realColExpandRatioSum = 0;
  446. float colSum = getExpandRatioSum(columnExpandRatio);
  447. if (colSum == 0) {
  448. // no columns has been expanded, all cols have same expand
  449. // rate
  450. float equalSize = 1 / (float) getColumns();
  451. int myRatio = Math.round(equalSize * 1000);
  452. for (int i = 0; i < getColumns(); i++) {
  453. columnExpandRatioArray[i] = myRatio;
  454. }
  455. realColExpandRatioSum = myRatio * getColumns();
  456. } else {
  457. for (int i = 0; i < getColumns(); i++) {
  458. int myRatio = Math
  459. .round((getColumnExpandRatio(i) / colSum) * 1000);
  460. columnExpandRatioArray[i] = myRatio;
  461. realColExpandRatioSum += myRatio;
  462. }
  463. }
  464. boolean equallyDividedRows = false;
  465. int realRowExpandRatioSum = 0;
  466. float rowSum = getExpandRatioSum(rowExpandRatio);
  467. if (rowSum == 0) {
  468. // no rows have been expanded
  469. equallyDividedRows = true;
  470. float equalSize = 1 / (float) getRows();
  471. int myRatio = Math.round(equalSize * 1000);
  472. for (int i = 0; i < getRows(); i++) {
  473. rowExpandRatioArray[i] = myRatio;
  474. }
  475. realRowExpandRatioSum = myRatio * getRows();
  476. }
  477. int index = 0;
  478. // Iterates every applicable row
  479. for (int cury = 0; cury < getRows(); cury++) {
  480. target.startTag("gr");
  481. if (!equallyDividedRows) {
  482. int myRatio = Math
  483. .round((getRowExpandRatio(cury) / rowSum) * 1000);
  484. rowExpandRatioArray[cury] = myRatio;
  485. realRowExpandRatioSum += myRatio;
  486. }
  487. // Iterates every applicable column
  488. for (int curx = 0; curx < getColumns(); curx++) {
  489. // Checks if current item is located at curx,cury
  490. if (area != null && (area.row1 == cury)
  491. && (area.column1 == curx)) {
  492. // First check if empty cell needs to be rendered
  493. if (emptyCells > 0) {
  494. target.startTag("gc");
  495. target.addAttribute("x", curx - emptyCells);
  496. target.addAttribute("y", cury);
  497. if (emptyCells > 1) {
  498. target.addAttribute("w", emptyCells);
  499. }
  500. target.endTag("gc");
  501. emptyCells = 0;
  502. }
  503. // Now proceed rendering current item
  504. final int cols = (area.column2 - area.column1) + 1;
  505. final int rows = (area.row2 - area.row1) + 1;
  506. target.startTag("gc");
  507. target.addAttribute("x", curx);
  508. target.addAttribute("y", cury);
  509. if (cols > 1) {
  510. target.addAttribute("w", cols);
  511. }
  512. if (rows > 1) {
  513. target.addAttribute("h", rows);
  514. }
  515. LegacyPaint.paint(area.getComponent(), target);
  516. alignmentsArray[index++] = String
  517. .valueOf(getComponentAlignment(area.getComponent())
  518. .getBitMask());
  519. target.endTag("gc");
  520. // Fetch next item
  521. if (areaiterator.hasNext()) {
  522. area = areaiterator.next();
  523. } else {
  524. area = null;
  525. }
  526. // Updates the cellUsed if rowspan needed
  527. if (rows > 1) {
  528. int spannedx = curx;
  529. for (int j = 1; j <= cols; j++) {
  530. cellUsed.put(new Integer(spannedx), new Integer(
  531. cury + rows - 1));
  532. spannedx++;
  533. }
  534. }
  535. // Skips the current item's spanned columns
  536. if (cols > 1) {
  537. curx += cols - 1;
  538. }
  539. } else {
  540. // Checks against cellUsed, render space or ignore cell
  541. if (cellUsed.containsKey(new Integer(curx))) {
  542. // Current column contains already an item,
  543. // check if rowspan affects at current x,y position
  544. final int rowspanDepth = cellUsed
  545. .get(new Integer(curx)).intValue();
  546. if (rowspanDepth >= cury) {
  547. // ignore cell
  548. // Check if empty cell needs to be rendered
  549. if (emptyCells > 0) {
  550. target.startTag("gc");
  551. target.addAttribute("x", curx - emptyCells);
  552. target.addAttribute("y", cury);
  553. if (emptyCells > 1) {
  554. target.addAttribute("w", emptyCells);
  555. }
  556. target.endTag("gc");
  557. emptyCells = 0;
  558. }
  559. } else {
  560. // empty cell is needed
  561. emptyCells++;
  562. // Removes the cellUsed key as it has become
  563. // obsolete
  564. cellUsed.remove(Integer.valueOf(curx));
  565. }
  566. } else {
  567. // empty cell is needed
  568. emptyCells++;
  569. }
  570. }
  571. } // iterates every column
  572. // Last column handled of current row
  573. // Checks if empty cell needs to be rendered
  574. if (emptyCells > 0) {
  575. target.startTag("gc");
  576. target.addAttribute("x", getColumns() - emptyCells);
  577. target.addAttribute("y", cury);
  578. if (emptyCells > 1) {
  579. target.addAttribute("w", emptyCells);
  580. }
  581. target.endTag("gc");
  582. emptyCells = 0;
  583. }
  584. target.endTag("gr");
  585. } // iterates every row
  586. // Last row handled
  587. // correct possible rounding error
  588. if (rowExpandRatioArray.length > 0) {
  589. rowExpandRatioArray[0] -= realRowExpandRatioSum - 1000;
  590. }
  591. if (columnExpandRatioArray.length > 0) {
  592. columnExpandRatioArray[0] -= realColExpandRatioSum - 1000;
  593. }
  594. target.addAttribute("colExpand", columnExpandRatioArray);
  595. target.addAttribute("rowExpand", rowExpandRatioArray);
  596. // Add child component alignment info to layout tag
  597. target.addAttribute("alignments", alignmentsArray);
  598. }
  599. private float getExpandRatioSum(Map<Integer, Float> ratioMap) {
  600. float sum = 0;
  601. for (Iterator<Entry<Integer, Float>> iterator = ratioMap.entrySet()
  602. .iterator(); iterator.hasNext();) {
  603. sum += iterator.next().getValue();
  604. }
  605. return sum;
  606. }
  607. /*
  608. * (non-Javadoc)
  609. *
  610. * @see com.vaadin.ui.Layout.AlignmentHandler#getComponentAlignment(com
  611. * .vaadin.ui.Component)
  612. */
  613. @Override
  614. public Alignment getComponentAlignment(Component childComponent) {
  615. Alignment alignment = componentToAlignment.get(childComponent);
  616. if (alignment == null) {
  617. return ALIGNMENT_DEFAULT;
  618. } else {
  619. return alignment;
  620. }
  621. }
  622. /**
  623. * Defines a rectangular area of cells in a GridLayout.
  624. *
  625. * <p>
  626. * Also maintains a reference to the component contained in the area.
  627. * </p>
  628. *
  629. * <p>
  630. * The area is specified by the cell coordinates of its upper left corner
  631. * (column1,row1) and lower right corner (column2,row2). As otherwise with
  632. * GridLayout, the column and row coordinates start from zero.
  633. * </p>
  634. *
  635. * @author Vaadin Ltd.
  636. * @since 3.0
  637. */
  638. public class Area implements Serializable {
  639. /**
  640. * The column of the upper left corner cell of the area.
  641. */
  642. private final int column1;
  643. /**
  644. * The row of the upper left corner cell of the area.
  645. */
  646. private int row1;
  647. /**
  648. * The column of the lower right corner cell of the area.
  649. */
  650. private final int column2;
  651. /**
  652. * The row of the lower right corner cell of the area.
  653. */
  654. private int row2;
  655. /**
  656. * Component painted in the area.
  657. */
  658. private Component component;
  659. /**
  660. * <p>
  661. * Construct a new area on a grid.
  662. * </p>
  663. *
  664. * @param component
  665. * the component connected to the area.
  666. * @param column1
  667. * The column of the upper left corner cell of the area. The
  668. * leftmost column has index 0.
  669. * @param row1
  670. * The row of the upper left corner cell of the area. The
  671. * topmost row has index 0.
  672. * @param column2
  673. * The column of the lower right corner cell of the area. The
  674. * leftmost column has index 0.
  675. * @param row2
  676. * The row of the lower right corner cell of the area. The
  677. * topmost row has index 0.
  678. */
  679. public Area(Component component, int column1, int row1, int column2,
  680. int row2) {
  681. this.column1 = column1;
  682. this.row1 = row1;
  683. this.column2 = column2;
  684. this.row2 = row2;
  685. this.component = component;
  686. }
  687. /**
  688. * Tests if this Area overlaps with another Area.
  689. *
  690. * @param other
  691. * the other Area that is to be tested for overlap with this
  692. * area
  693. * @return <code>true</code> if <code>other</code> area overlaps with
  694. * this on, <code>false</code> if it does not.
  695. */
  696. public boolean overlaps(Area other) {
  697. return column1 <= other.getColumn2() && row1 <= other.getRow2()
  698. && column2 >= other.getColumn1() && row2 >= other.getRow1();
  699. }
  700. /**
  701. * Gets the component connected to the area.
  702. *
  703. * @return the Component.
  704. */
  705. public Component getComponent() {
  706. return component;
  707. }
  708. /**
  709. * Sets the component connected to the area.
  710. *
  711. * <p>
  712. * This function only sets the value in the data structure and does not
  713. * send any events or set parents.
  714. * </p>
  715. *
  716. * @param newComponent
  717. * the new connected overriding the existing one.
  718. */
  719. protected void setComponent(Component newComponent) {
  720. component = newComponent;
  721. }
  722. /**
  723. * Gets the column of the top-left corner cell.
  724. *
  725. * @return the column of the top-left corner cell.
  726. */
  727. public int getColumn1() {
  728. return column1;
  729. }
  730. /**
  731. * Gets the column of the bottom-right corner cell.
  732. *
  733. * @return the column of the bottom-right corner cell.
  734. */
  735. public int getColumn2() {
  736. return column2;
  737. }
  738. /**
  739. * Gets the row of the top-left corner cell.
  740. *
  741. * @return the row of the top-left corner cell.
  742. */
  743. public int getRow1() {
  744. return row1;
  745. }
  746. /**
  747. * Gets the row of the bottom-right corner cell.
  748. *
  749. * @return the row of the bottom-right corner cell.
  750. */
  751. public int getRow2() {
  752. return row2;
  753. }
  754. }
  755. /**
  756. * Gridlayout does not support laying components on top of each other. An
  757. * <code>OverlapsException</code> is thrown when a component already exists
  758. * (even partly) at the same space on a grid with the new component.
  759. *
  760. * @author Vaadin Ltd.
  761. * @since 3.0
  762. */
  763. public class OverlapsException extends java.lang.RuntimeException {
  764. private final Area existingArea;
  765. /**
  766. * Constructs an <code>OverlapsException</code>.
  767. *
  768. * @param existingArea
  769. */
  770. public OverlapsException(Area existingArea) {
  771. this.existingArea = existingArea;
  772. }
  773. @Override
  774. public String getMessage() {
  775. StringBuilder sb = new StringBuilder();
  776. Component component = existingArea.getComponent();
  777. sb.append(component);
  778. sb.append("( type = ");
  779. sb.append(component.getClass().getName());
  780. if (component.getCaption() != null) {
  781. sb.append(", caption = \"");
  782. sb.append(component.getCaption());
  783. sb.append("\"");
  784. }
  785. sb.append(")");
  786. sb.append(" is already added to ");
  787. sb.append(existingArea.column1);
  788. sb.append(",");
  789. sb.append(existingArea.column1);
  790. sb.append(",");
  791. sb.append(existingArea.row1);
  792. sb.append(",");
  793. sb.append(existingArea.row2);
  794. sb.append("(column1, column2, row1, row2).");
  795. return sb.toString();
  796. }
  797. /**
  798. * Gets the area .
  799. *
  800. * @return the existing area.
  801. */
  802. public Area getArea() {
  803. return existingArea;
  804. }
  805. }
  806. /**
  807. * An <code>Exception</code> object which is thrown when an area exceeds the
  808. * bounds of the grid.
  809. *
  810. * @author Vaadin Ltd.
  811. * @since 3.0
  812. */
  813. public class OutOfBoundsException extends java.lang.RuntimeException {
  814. private final Area areaOutOfBounds;
  815. /**
  816. * Constructs an <code>OoutOfBoundsException</code> with the specified
  817. * detail message.
  818. *
  819. * @param areaOutOfBounds
  820. */
  821. public OutOfBoundsException(Area areaOutOfBounds) {
  822. this.areaOutOfBounds = areaOutOfBounds;
  823. }
  824. /**
  825. * Gets the area that is out of bounds.
  826. *
  827. * @return the area out of Bound.
  828. */
  829. public Area getArea() {
  830. return areaOutOfBounds;
  831. }
  832. }
  833. /**
  834. * Sets the number of columns in the grid. The column count can not be
  835. * reduced if there are any areas that would be outside of the shrunk grid.
  836. *
  837. * @param columns
  838. * the new number of columns in the grid.
  839. */
  840. public void setColumns(int columns) {
  841. // The the param
  842. if (columns < 1) {
  843. throw new IllegalArgumentException(
  844. "The number of columns and rows in the grid must be at least 1");
  845. }
  846. // In case of no change
  847. if (getColumns() == columns) {
  848. return;
  849. }
  850. // Checks for overlaps
  851. if (getColumns() > columns) {
  852. for (final Iterator<Area> i = areas.iterator(); i.hasNext();) {
  853. final Area area = i.next();
  854. if (area.column2 >= columns) {
  855. throw new OutOfBoundsException(area);
  856. }
  857. }
  858. }
  859. getState().columns = columns;
  860. }
  861. /**
  862. * Get the number of columns in the grid.
  863. *
  864. * @return the number of columns in the grid.
  865. */
  866. public int getColumns() {
  867. return getState().columns;
  868. }
  869. /**
  870. * Sets the number of rows in the grid. The number of rows can not be
  871. * reduced if there are any areas that would be outside of the shrunk grid.
  872. *
  873. * @param rows
  874. * the new number of rows in the grid.
  875. */
  876. public void setRows(int rows) {
  877. // The the param
  878. if (rows < 1) {
  879. throw new IllegalArgumentException(
  880. "The number of columns and rows in the grid must be at least 1");
  881. }
  882. // In case of no change
  883. if (getRows() == rows) {
  884. return;
  885. }
  886. // Checks for overlaps
  887. if (getRows() > rows) {
  888. for (final Iterator<Area> i = areas.iterator(); i.hasNext();) {
  889. final Area area = i.next();
  890. if (area.row2 >= rows) {
  891. throw new OutOfBoundsException(area);
  892. }
  893. }
  894. }
  895. getState().rows = rows;
  896. }
  897. /**
  898. * Get the number of rows in the grid.
  899. *
  900. * @return the number of rows in the grid.
  901. */
  902. public int getRows() {
  903. return getState().rows;
  904. }
  905. /**
  906. * Gets the current x-position (column) of the cursor.
  907. *
  908. * <p>
  909. * The cursor position points the position for the next component that is
  910. * added without specifying its coordinates (grid cell). When the cursor
  911. * position is occupied, the next component will be added to first free
  912. * position after the cursor.
  913. * </p>
  914. *
  915. * @return the grid column the cursor is on, starting from 0.
  916. */
  917. public int getCursorX() {
  918. return cursorX;
  919. }
  920. /**
  921. * Sets the current cursor x-position. This is usually handled automatically
  922. * by GridLayout.
  923. *
  924. * @param cursorX
  925. */
  926. public void setCursorX(int cursorX) {
  927. this.cursorX = cursorX;
  928. }
  929. /**
  930. * Gets the current y-position (row) of the cursor.
  931. *
  932. * <p>
  933. * The cursor position points the position for the next component that is
  934. * added without specifying its coordinates (grid cell). When the cursor
  935. * position is occupied, the next component will be added to the first free
  936. * position after the cursor.
  937. * </p>
  938. *
  939. * @return the grid row the Cursor is on.
  940. */
  941. public int getCursorY() {
  942. return cursorY;
  943. }
  944. /**
  945. * Sets the current y-coordinate (row) of the cursor. This is usually
  946. * handled automatically by GridLayout.
  947. *
  948. * @param cursorY
  949. * the row number, starting from 0 for the topmost row.
  950. */
  951. public void setCursorY(int cursorY) {
  952. this.cursorY = cursorY;
  953. }
  954. /* Documented in superclass */
  955. @Override
  956. public void replaceComponent(Component oldComponent, Component newComponent) {
  957. // Gets the locations
  958. Area oldLocation = null;
  959. Area newLocation = null;
  960. for (final Iterator<Area> i = areas.iterator(); i.hasNext();) {
  961. final Area location = i.next();
  962. final Component component = location.getComponent();
  963. if (component == oldComponent) {
  964. oldLocation = location;
  965. }
  966. if (component == newComponent) {
  967. newLocation = location;
  968. }
  969. }
  970. if (oldLocation == null) {
  971. addComponent(newComponent);
  972. } else if (newLocation == null) {
  973. removeComponent(oldComponent);
  974. addComponent(newComponent, oldLocation.getColumn1(),
  975. oldLocation.getRow1(), oldLocation.getColumn2(),
  976. oldLocation.getRow2());
  977. } else {
  978. oldLocation.setComponent(newComponent);
  979. newLocation.setComponent(oldComponent);
  980. markAsDirty();
  981. }
  982. }
  983. /*
  984. * Removes all components from this container.
  985. *
  986. * @see com.vaadin.ui.ComponentContainer#removeAllComponents()
  987. */
  988. @Override
  989. public void removeAllComponents() {
  990. super.removeAllComponents();
  991. componentToAlignment = new HashMap<Component, Alignment>();
  992. cursorX = 0;
  993. cursorY = 0;
  994. }
  995. @Override
  996. public void setComponentAlignment(Component childComponent,
  997. Alignment alignment) {
  998. componentToAlignment.put(childComponent, alignment);
  999. markAsDirty();
  1000. }
  1001. /*
  1002. * (non-Javadoc)
  1003. *
  1004. * @see com.vaadin.ui.Layout.SpacingHandler#setSpacing(boolean)
  1005. */
  1006. @Override
  1007. public void setSpacing(boolean spacing) {
  1008. getState().spacing = spacing;
  1009. }
  1010. /*
  1011. * (non-Javadoc)
  1012. *
  1013. * @see com.vaadin.ui.Layout.SpacingHandler#isSpacing()
  1014. */
  1015. @Override
  1016. public boolean isSpacing() {
  1017. return getState().spacing;
  1018. }
  1019. /**
  1020. * Inserts an empty row at the specified position in the grid.
  1021. *
  1022. * @param row
  1023. * Index of the row before which the new row will be inserted.
  1024. * The leftmost row has index 0.
  1025. */
  1026. public void insertRow(int row) {
  1027. if (row > getRows()) {
  1028. throw new IllegalArgumentException("Cannot insert row at " + row
  1029. + " in a gridlayout with height " + getRows());
  1030. }
  1031. for (Iterator<Area> i = areas.iterator(); i.hasNext();) {
  1032. Area existingArea = i.next();
  1033. // Areas ending below the row needs to be moved down or stretched
  1034. if (existingArea.row2 >= row) {
  1035. existingArea.row2++;
  1036. // Stretch areas that span over the selected row
  1037. if (existingArea.row1 >= row) {
  1038. existingArea.row1++;
  1039. }
  1040. }
  1041. }
  1042. if (cursorY >= row) {
  1043. cursorY++;
  1044. }
  1045. setRows(getRows() + 1);
  1046. structuralChange = true;
  1047. markAsDirty();
  1048. }
  1049. /**
  1050. * Removes a row and all the components in the row.
  1051. *
  1052. * <p>
  1053. * Components which span over several rows are removed if the selected row
  1054. * is on the first row of such a component.
  1055. * </p>
  1056. *
  1057. * <p>
  1058. * If the last row is removed then all remaining components will be removed
  1059. * and the grid will be reduced to one row. The cursor will be moved to the
  1060. * upper left cell of the grid.
  1061. * </p>
  1062. *
  1063. * @param row
  1064. * Index of the row to remove. The leftmost row has index 0.
  1065. */
  1066. public void removeRow(int row) {
  1067. if (row >= getRows()) {
  1068. throw new IllegalArgumentException("Cannot delete row " + row
  1069. + " from a gridlayout with height " + getRows());
  1070. }
  1071. // Remove all components in row
  1072. for (int col = 0; col < getColumns(); col++) {
  1073. removeComponent(col, row);
  1074. }
  1075. // Shrink or remove areas in the selected row
  1076. for (Iterator<Area> i = areas.iterator(); i.hasNext();) {
  1077. Area existingArea = i.next();
  1078. if (existingArea.row2 >= row) {
  1079. existingArea.row2--;
  1080. if (existingArea.row1 > row) {
  1081. existingArea.row1--;
  1082. }
  1083. }
  1084. }
  1085. if (getRows() == 1) {
  1086. /*
  1087. * Removing the last row means that the dimensions of the Grid
  1088. * layout will be truncated to 1 empty row and the cursor is moved
  1089. * to the first cell
  1090. */
  1091. cursorX = 0;
  1092. cursorY = 0;
  1093. } else {
  1094. setRows(getRows() - 1);
  1095. if (cursorY > row) {
  1096. cursorY--;
  1097. }
  1098. }
  1099. structuralChange = true;
  1100. markAsDirty();
  1101. }
  1102. /**
  1103. * Sets the expand ratio of given column.
  1104. *
  1105. * <p>
  1106. * The expand ratio defines how excess space is distributed among columns.
  1107. * Excess space means space that is left over from components that are not
  1108. * sized relatively. By default, the excess space is distributed evenly.
  1109. * </p>
  1110. *
  1111. * <p>
  1112. * Note that the component width of the GridLayout must be defined (fixed or
  1113. * relative, as opposed to undefined) for this method to have any effect.
  1114. * </p>
  1115. *
  1116. * @see #setWidth(float, int)
  1117. *
  1118. * @param columnIndex
  1119. * @param ratio
  1120. */
  1121. public void setColumnExpandRatio(int columnIndex, float ratio) {
  1122. columnExpandRatio.put(columnIndex, ratio);
  1123. markAsDirty();
  1124. }
  1125. /**
  1126. * Returns the expand ratio of given column
  1127. *
  1128. * @see #setColumnExpandRatio(int, float)
  1129. *
  1130. * @param columnIndex
  1131. * @return the expand ratio, 0.0f by default
  1132. */
  1133. public float getColumnExpandRatio(int columnIndex) {
  1134. Float r = columnExpandRatio.get(columnIndex);
  1135. return r == null ? 0 : r.floatValue();
  1136. }
  1137. /**
  1138. * Sets the expand ratio of given row.
  1139. *
  1140. * <p>
  1141. * Expand ratio defines how excess space is distributed among rows. Excess
  1142. * space means the space left over from components that are not sized
  1143. * relatively. By default, the excess space is distributed evenly.
  1144. * </p>
  1145. *
  1146. * <p>
  1147. * Note, that height needs to be defined (fixed or relative, as opposed to
  1148. * undefined height) for this method to have any effect.
  1149. * </p>
  1150. *
  1151. * @see #setHeight(float, int)
  1152. *
  1153. * @param rowIndex
  1154. * The row index, starting from 0 for the topmost row.
  1155. * @param ratio
  1156. */
  1157. public void setRowExpandRatio(int rowIndex, float ratio) {
  1158. rowExpandRatio.put(rowIndex, ratio);
  1159. markAsDirty();
  1160. }
  1161. /**
  1162. * Returns the expand ratio of given row.
  1163. *
  1164. * @see #setRowExpandRatio(int, float)
  1165. *
  1166. * @param rowIndex
  1167. * The row index, starting from 0 for the topmost row.
  1168. * @return the expand ratio, 0.0f by default
  1169. */
  1170. public float getRowExpandRatio(int rowIndex) {
  1171. Float r = rowExpandRatio.get(rowIndex);
  1172. return r == null ? 0 : r.floatValue();
  1173. }
  1174. /**
  1175. * Gets the Component at given index.
  1176. *
  1177. * @param x
  1178. * The column index, starting from 0 for the leftmost column.
  1179. * @param y
  1180. * The row index, starting from 0 for the topmost row.
  1181. * @return Component in given cell or null if empty
  1182. */
  1183. public Component getComponent(int x, int y) {
  1184. for (final Iterator<Area> iterator = areas.iterator(); iterator
  1185. .hasNext();) {
  1186. final Area area = iterator.next();
  1187. if (area.getColumn1() <= x && x <= area.getColumn2()
  1188. && area.getRow1() <= y && y <= area.getRow2()) {
  1189. return area.getComponent();
  1190. }
  1191. }
  1192. return null;
  1193. }
  1194. /**
  1195. * Returns information about the area where given component is laid in the
  1196. * GridLayout.
  1197. *
  1198. * @param component
  1199. * the component whose area information is requested.
  1200. * @return an Area object that contains information how component is laid in
  1201. * the grid
  1202. */
  1203. public Area getComponentArea(Component component) {
  1204. for (final Iterator<Area> iterator = areas.iterator(); iterator
  1205. .hasNext();) {
  1206. final Area area = iterator.next();
  1207. if (area.getComponent() == component) {
  1208. return area;
  1209. }
  1210. }
  1211. return null;
  1212. }
  1213. @Override
  1214. public void addLayoutClickListener(LayoutClickListener listener) {
  1215. addListener(EventId.LAYOUT_CLICK_EVENT_IDENTIFIER,
  1216. LayoutClickEvent.class, listener,
  1217. LayoutClickListener.clickMethod);
  1218. }
  1219. /**
  1220. * @deprecated As of 7.0, replaced by
  1221. * {@link #addLayoutClickListener(LayoutClickListener)}
  1222. **/
  1223. @Override
  1224. @Deprecated
  1225. public void addListener(LayoutClickListener listener) {
  1226. addLayoutClickListener(listener);
  1227. }
  1228. @Override
  1229. public void removeLayoutClickListener(LayoutClickListener listener) {
  1230. removeListener(EventId.LAYOUT_CLICK_EVENT_IDENTIFIER,
  1231. LayoutClickEvent.class, listener);
  1232. }
  1233. /**
  1234. * @deprecated As of 7.0, replaced by
  1235. * {@link #removeLayoutClickListener(LayoutClickListener)}
  1236. **/
  1237. @Override
  1238. @Deprecated
  1239. public void removeListener(LayoutClickListener listener) {
  1240. removeLayoutClickListener(listener);
  1241. }
  1242. /*
  1243. * (non-Javadoc)
  1244. *
  1245. * @see com.vaadin.ui.Layout.MarginHandler#setMargin(boolean)
  1246. */
  1247. @Override
  1248. public void setMargin(boolean enabled) {
  1249. setMargin(new MarginInfo(enabled));
  1250. }
  1251. /*
  1252. * (non-Javadoc)
  1253. *
  1254. * @see
  1255. * com.vaadin.ui.Layout.MarginHandler#setMargin(com.vaadin.shared.ui.MarginInfo
  1256. * )
  1257. */
  1258. @Override
  1259. public void setMargin(MarginInfo marginInfo) {
  1260. getState().marginsBitmask = marginInfo.getBitMask();
  1261. }
  1262. /*
  1263. * (non-Javadoc)
  1264. *
  1265. * @see com.vaadin.ui.Layout.MarginHandler#getMargin()
  1266. */
  1267. @Override
  1268. public MarginInfo getMargin() {
  1269. return new MarginInfo(getState().marginsBitmask);
  1270. }
  1271. }