Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

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