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

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