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

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