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.

VGridLayout.java 38KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.client.ui;
  5. import java.util.ArrayList;
  6. import java.util.HashMap;
  7. import java.util.HashSet;
  8. import java.util.Iterator;
  9. import java.util.LinkedList;
  10. import java.util.List;
  11. import java.util.Set;
  12. import com.google.gwt.dom.client.DivElement;
  13. import com.google.gwt.dom.client.Document;
  14. import com.google.gwt.event.dom.client.DomEvent.Type;
  15. import com.google.gwt.event.shared.EventHandler;
  16. import com.google.gwt.event.shared.HandlerRegistration;
  17. import com.google.gwt.user.client.Element;
  18. import com.google.gwt.user.client.ui.AbsolutePanel;
  19. import com.google.gwt.user.client.ui.SimplePanel;
  20. import com.google.gwt.user.client.ui.Widget;
  21. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  22. import com.vaadin.terminal.gwt.client.Container;
  23. import com.vaadin.terminal.gwt.client.Paintable;
  24. import com.vaadin.terminal.gwt.client.RenderSpace;
  25. import com.vaadin.terminal.gwt.client.StyleConstants;
  26. import com.vaadin.terminal.gwt.client.UIDL;
  27. import com.vaadin.terminal.gwt.client.Util;
  28. import com.vaadin.terminal.gwt.client.ui.layout.CellBasedLayout;
  29. import com.vaadin.terminal.gwt.client.ui.layout.ChildComponentContainer;
  30. public class VGridLayout extends SimplePanel implements Paintable, Container {
  31. public static final String CLASSNAME = "v-gridlayout";
  32. public static final String CLICK_EVENT_IDENTIFIER = "click";
  33. private DivElement margin = Document.get().createDivElement();
  34. private final AbsolutePanel canvas = new AbsolutePanel();
  35. private ApplicationConnection client;
  36. protected HashMap<Widget, ChildComponentContainer> widgetToComponentContainer = new HashMap<Widget, ChildComponentContainer>();
  37. private HashMap<Paintable, Cell> paintableToCell = new HashMap<Paintable, Cell>();
  38. private int spacingPixelsHorizontal;
  39. private int spacingPixelsVertical;
  40. private int[] columnWidths;
  41. private int[] rowHeights;
  42. private String height;
  43. private String width;
  44. private int[] colExpandRatioArray;
  45. private int[] rowExpandRatioArray;
  46. private int[] minColumnWidths;
  47. private int[] minRowHeights;
  48. private boolean rendering;
  49. private HashMap<Widget, ChildComponentContainer> nonRenderedWidgets;
  50. private boolean sizeChangedDuringRendering = false;
  51. private LayoutClickEventHandler clickEventHandler = new LayoutClickEventHandler(
  52. this, CLICK_EVENT_IDENTIFIER) {
  53. @Override
  54. protected Paintable getChildComponent(Element element) {
  55. return getComponent(element);
  56. }
  57. @Override
  58. protected <H extends EventHandler> HandlerRegistration registerHandler(
  59. H handler, Type<H> type) {
  60. return addDomHandler(handler, type);
  61. }
  62. };
  63. public VGridLayout() {
  64. super();
  65. getElement().appendChild(margin);
  66. setStyleName(CLASSNAME);
  67. setWidget(canvas);
  68. }
  69. @Override
  70. protected Element getContainerElement() {
  71. return margin.cast();
  72. }
  73. public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
  74. rendering = true;
  75. this.client = client;
  76. if (client.updateComponent(this, uidl, true)) {
  77. rendering = false;
  78. return;
  79. }
  80. clickEventHandler.handleEventHandlerRegistration(client);
  81. canvas.setWidth("0px");
  82. handleMargins(uidl);
  83. detectSpacing(uidl);
  84. int cols = uidl.getIntAttribute("w");
  85. int rows = uidl.getIntAttribute("h");
  86. columnWidths = new int[cols];
  87. rowHeights = new int[rows];
  88. if (cells == null) {
  89. cells = new Cell[cols][rows];
  90. } else if (cells.length != cols || cells[0].length != rows) {
  91. Cell[][] newCells = new Cell[cols][rows];
  92. for (int i = 0; i < cells.length; i++) {
  93. for (int j = 0; j < cells[i].length; j++) {
  94. if (i < cols && j < rows) {
  95. newCells[i][j] = cells[i][j];
  96. }
  97. }
  98. }
  99. cells = newCells;
  100. }
  101. nonRenderedWidgets = (HashMap<Widget, ChildComponentContainer>) widgetToComponentContainer
  102. .clone();
  103. final int[] alignments = uidl.getIntArrayAttribute("alignments");
  104. int alignmentIndex = 0;
  105. LinkedList<Cell> pendingCells = new LinkedList<Cell>();
  106. LinkedList<Cell> relativeHeighted = new LinkedList<Cell>();
  107. for (final Iterator i = uidl.getChildIterator(); i.hasNext();) {
  108. final UIDL r = (UIDL) i.next();
  109. if ("gr".equals(r.getTag())) {
  110. for (final Iterator j = r.getChildIterator(); j.hasNext();) {
  111. final UIDL c = (UIDL) j.next();
  112. if ("gc".equals(c.getTag())) {
  113. Cell cell = getCell(c);
  114. if (cell.hasContent()) {
  115. boolean rendered = cell.renderIfNoRelativeWidth();
  116. cell.alignment = alignments[alignmentIndex++];
  117. if (!rendered) {
  118. pendingCells.add(cell);
  119. }
  120. if (cell.colspan > 1) {
  121. storeColSpannedCell(cell);
  122. } else if (rendered) {
  123. // strore non-colspanned widths to columnWidth
  124. // array
  125. if (columnWidths[cell.col] < cell.getWidth()) {
  126. columnWidths[cell.col] = cell.getWidth();
  127. }
  128. }
  129. if (cell.hasRelativeHeight()) {
  130. relativeHeighted.add(cell);
  131. }
  132. }
  133. }
  134. }
  135. }
  136. }
  137. distributeColSpanWidths();
  138. colExpandRatioArray = uidl.getIntArrayAttribute("colExpand");
  139. rowExpandRatioArray = uidl.getIntArrayAttribute("rowExpand");
  140. minColumnWidths = cloneArray(columnWidths);
  141. expandColumns();
  142. renderRemainingComponentsWithNoRelativeHeight(pendingCells);
  143. detectRowHeights();
  144. expandRows();
  145. renderRemainingComponents(pendingCells);
  146. for (Cell cell : relativeHeighted) {
  147. // rendering done above so cell.cc should not be null
  148. Widget widget2 = cell.cc.getWidget();
  149. client.handleComponentRelativeSize(widget2);
  150. cell.cc.updateWidgetSize();
  151. }
  152. layoutCells();
  153. // clean non rendered components
  154. for (Widget w : nonRenderedWidgets.keySet()) {
  155. ChildComponentContainer childComponentContainer = widgetToComponentContainer
  156. .get(w);
  157. paintableToCell.remove(w);
  158. widgetToComponentContainer.remove(w);
  159. childComponentContainer.removeFromParent();
  160. client.unregisterPaintable((Paintable) w);
  161. }
  162. nonRenderedWidgets = null;
  163. rendering = false;
  164. sizeChangedDuringRendering = false;
  165. }
  166. private static int[] cloneArray(int[] toBeCloned) {
  167. int[] clone = new int[toBeCloned.length];
  168. for (int i = 0; i < clone.length; i++) {
  169. clone[i] = toBeCloned[i] * 1;
  170. }
  171. return clone;
  172. }
  173. private void expandRows() {
  174. if (!"".equals(height)) {
  175. int usedSpace = minRowHeights[0];
  176. for (int i = 1; i < minRowHeights.length; i++) {
  177. usedSpace += spacingPixelsVertical + minRowHeights[i];
  178. }
  179. int availableSpace = getOffsetHeight() - marginTopAndBottom;
  180. int excessSpace = availableSpace - usedSpace;
  181. int distributed = 0;
  182. if (excessSpace > 0) {
  183. for (int i = 0; i < rowHeights.length; i++) {
  184. int ew = excessSpace * rowExpandRatioArray[i] / 1000;
  185. rowHeights[i] = minRowHeights[i] + ew;
  186. distributed += ew;
  187. }
  188. excessSpace -= distributed;
  189. int c = 0;
  190. while (excessSpace > 0) {
  191. rowHeights[c % rowHeights.length]++;
  192. excessSpace--;
  193. c++;
  194. }
  195. }
  196. }
  197. }
  198. @Override
  199. public void setHeight(String height) {
  200. super.setHeight(height);
  201. if (!height.equals(this.height)) {
  202. this.height = height;
  203. if (rendering) {
  204. sizeChangedDuringRendering = true;
  205. } else {
  206. expandRows();
  207. layoutCells();
  208. for (Paintable c : paintableToCell.keySet()) {
  209. client.handleComponentRelativeSize((Widget) c);
  210. }
  211. }
  212. }
  213. }
  214. @Override
  215. public void setWidth(String width) {
  216. super.setWidth(width);
  217. if (!width.equals(this.width)) {
  218. this.width = width;
  219. if (rendering) {
  220. sizeChangedDuringRendering = true;
  221. } else {
  222. int[] oldWidths = cloneArray(columnWidths);
  223. expandColumns();
  224. boolean heightChanged = false;
  225. HashSet<Integer> dirtyRows = null;
  226. for (int i = 0; i < oldWidths.length; i++) {
  227. if (columnWidths[i] != oldWidths[i]) {
  228. Cell[] column = cells[i];
  229. for (int j = 0; j < column.length; j++) {
  230. Cell c = column[j];
  231. if (c != null && c.cc != null
  232. && c.widthCanAffectHeight()) {
  233. c.cc.setContainerSize(c.getAvailableWidth(),
  234. c.getAvailableHeight());
  235. client.handleComponentRelativeSize(c.cc
  236. .getWidget());
  237. c.cc.updateWidgetSize();
  238. int newHeight = c.getHeight();
  239. if (columnWidths[i] < oldWidths[i]
  240. && newHeight > minRowHeights[j]
  241. && c.rowspan == 1) {
  242. /*
  243. * The width of this column was reduced and
  244. * this affected the height. The height is
  245. * now greater than the previously
  246. * calculated minHeight for the row.
  247. */
  248. minRowHeights[j] = newHeight;
  249. if (newHeight > rowHeights[j]) {
  250. /*
  251. * The new height is greater than the
  252. * previously calculated rowHeight -> we
  253. * need to recalculate heights later on
  254. */
  255. rowHeights[j] = newHeight;
  256. heightChanged = true;
  257. }
  258. } else if (newHeight < minRowHeights[j]) {
  259. /*
  260. * The new height of the component is less
  261. * than the previously calculated min row
  262. * height. The min row height may be
  263. * affected and must thus be recalculated
  264. */
  265. if (dirtyRows == null) {
  266. dirtyRows = new HashSet<Integer>();
  267. }
  268. dirtyRows.add(j);
  269. }
  270. }
  271. }
  272. }
  273. }
  274. if (dirtyRows != null) {
  275. /* flag indicating that there is a potential row shrinking */
  276. boolean rowMayShrink = false;
  277. for (Integer rowIndex : dirtyRows) {
  278. int oldMinimum = minRowHeights[rowIndex];
  279. int newMinimum = 0;
  280. for (int colIndex = 0; colIndex < columnWidths.length; colIndex++) {
  281. Cell cell = cells[colIndex][rowIndex];
  282. if (cell != null && !cell.hasRelativeHeight()
  283. && cell.getHeight() > newMinimum) {
  284. newMinimum = cell.getHeight();
  285. }
  286. }
  287. if (newMinimum < oldMinimum) {
  288. minRowHeights[rowIndex] = rowHeights[rowIndex] = newMinimum;
  289. rowMayShrink = true;
  290. }
  291. }
  292. if (rowMayShrink) {
  293. distributeRowSpanHeights();
  294. minRowHeights = cloneArray(rowHeights);
  295. heightChanged = true;
  296. }
  297. }
  298. layoutCells();
  299. for (Paintable c : paintableToCell.keySet()) {
  300. client.handleComponentRelativeSize((Widget) c);
  301. }
  302. if (heightChanged && "".equals(height)) {
  303. Util.notifyParentOfSizeChange(this, false);
  304. }
  305. }
  306. }
  307. }
  308. private void expandColumns() {
  309. if (!"".equals(width)) {
  310. int usedSpace = minColumnWidths[0];
  311. for (int i = 1; i < minColumnWidths.length; i++) {
  312. usedSpace += spacingPixelsHorizontal + minColumnWidths[i];
  313. }
  314. canvas.setWidth("");
  315. int availableSpace = canvas.getOffsetWidth();
  316. int excessSpace = availableSpace - usedSpace;
  317. int distributed = 0;
  318. if (excessSpace > 0) {
  319. for (int i = 0; i < columnWidths.length; i++) {
  320. int ew = excessSpace * colExpandRatioArray[i] / 1000;
  321. columnWidths[i] = minColumnWidths[i] + ew;
  322. distributed += ew;
  323. }
  324. excessSpace -= distributed;
  325. int c = 0;
  326. while (excessSpace > 0) {
  327. columnWidths[c % columnWidths.length]++;
  328. excessSpace--;
  329. c++;
  330. }
  331. }
  332. }
  333. }
  334. private void layoutCells() {
  335. int x = 0;
  336. int y = 0;
  337. for (int i = 0; i < cells.length; i++) {
  338. y = 0;
  339. for (int j = 0; j < cells[i].length; j++) {
  340. Cell cell = cells[i][j];
  341. if (cell != null) {
  342. cell.layout(x, y);
  343. }
  344. y += rowHeights[j] + spacingPixelsVertical;
  345. }
  346. x += columnWidths[i] + spacingPixelsHorizontal;
  347. }
  348. if ("".equals(width)) {
  349. canvas.setWidth((x - spacingPixelsHorizontal) + "px");
  350. } else {
  351. // main element defines width
  352. canvas.setWidth("");
  353. }
  354. int canvasHeight;
  355. if ("".equals(height)) {
  356. canvasHeight = y - spacingPixelsVertical;
  357. } else {
  358. canvasHeight = getOffsetHeight() - marginTopAndBottom;
  359. }
  360. canvas.setHeight(canvasHeight + "px");
  361. }
  362. private void renderRemainingComponents(LinkedList<Cell> pendingCells) {
  363. for (Cell cell : pendingCells) {
  364. cell.render();
  365. }
  366. }
  367. private void detectRowHeights() {
  368. // collect min rowheight from non-rowspanned cells
  369. for (int i = 0; i < cells.length; i++) {
  370. for (int j = 0; j < cells[i].length; j++) {
  371. Cell cell = cells[i][j];
  372. if (cell != null) {
  373. /*
  374. * Setting fixing container width may in some situations
  375. * affect height. Example: Label with wrapping text without
  376. * or with relative width.
  377. */
  378. if (cell.cc != null && cell.widthCanAffectHeight()) {
  379. cell.cc.setWidth(cell.getAvailableWidth() + "px");
  380. cell.cc.updateWidgetSize();
  381. }
  382. if (cell.rowspan == 1) {
  383. if (!cell.hasRelativeHeight()
  384. && rowHeights[j] < cell.getHeight()) {
  385. rowHeights[j] = cell.getHeight();
  386. }
  387. } else {
  388. storeRowSpannedCell(cell);
  389. }
  390. }
  391. }
  392. }
  393. distributeRowSpanHeights();
  394. minRowHeights = cloneArray(rowHeights);
  395. }
  396. private void storeRowSpannedCell(Cell cell) {
  397. SpanList l = null;
  398. for (SpanList list : rowSpans) {
  399. if (list.span < cell.rowspan) {
  400. continue;
  401. } else {
  402. // insert before this
  403. l = list;
  404. break;
  405. }
  406. }
  407. if (l == null) {
  408. l = new SpanList(cell.rowspan);
  409. rowSpans.add(l);
  410. } else if (l.span != cell.rowspan) {
  411. SpanList newL = new SpanList(cell.rowspan);
  412. rowSpans.add(rowSpans.indexOf(l), newL);
  413. l = newL;
  414. }
  415. l.cells.add(cell);
  416. }
  417. private void renderRemainingComponentsWithNoRelativeHeight(
  418. LinkedList<Cell> pendingCells) {
  419. for (Iterator iterator = pendingCells.iterator(); iterator.hasNext();) {
  420. Cell cell = (Cell) iterator.next();
  421. if (!cell.hasRelativeHeight()) {
  422. cell.render();
  423. iterator.remove();
  424. }
  425. }
  426. }
  427. /**
  428. * Iterates colspanned cells, ensures cols have enough space to accommodate
  429. * them
  430. */
  431. private void distributeColSpanWidths() {
  432. for (SpanList list : colSpans) {
  433. for (Cell cell : list.cells) {
  434. // cells with relative content may return non 0 here if on
  435. // subsequent renders
  436. int width = cell.hasRelativeWidth() ? 0 : cell.getWidth();
  437. int allocated = columnWidths[cell.col];
  438. for (int i = 1; i < cell.colspan; i++) {
  439. allocated += spacingPixelsHorizontal
  440. + columnWidths[cell.col + i];
  441. }
  442. if (allocated < width) {
  443. // columnWidths needs to be expanded due colspanned cell
  444. int neededExtraSpace = width - allocated;
  445. int spaceForColunms = neededExtraSpace / cell.colspan;
  446. for (int i = 0; i < cell.colspan; i++) {
  447. int col = cell.col + i;
  448. columnWidths[col] += spaceForColunms;
  449. neededExtraSpace -= spaceForColunms;
  450. }
  451. if (neededExtraSpace > 0) {
  452. for (int i = 0; i < cell.colspan; i++) {
  453. int col = cell.col + i;
  454. columnWidths[col] += 1;
  455. neededExtraSpace -= 1;
  456. if (neededExtraSpace == 0) {
  457. break;
  458. }
  459. }
  460. }
  461. }
  462. }
  463. }
  464. }
  465. /**
  466. * Iterates rowspanned cells, ensures rows have enough space to accommodate
  467. * them
  468. */
  469. private void distributeRowSpanHeights() {
  470. for (SpanList list : rowSpans) {
  471. for (Cell cell : list.cells) {
  472. // cells with relative content may return non 0 here if on
  473. // subsequent renders
  474. int height = cell.hasRelativeHeight() ? 0 : cell.getHeight();
  475. int allocated = rowHeights[cell.row];
  476. for (int i = 1; i < cell.rowspan; i++) {
  477. allocated += spacingPixelsVertical
  478. + rowHeights[cell.row + i];
  479. }
  480. if (allocated < height) {
  481. // columnWidths needs to be expanded due colspanned cell
  482. int neededExtraSpace = height - allocated;
  483. int spaceForColunms = neededExtraSpace / cell.rowspan;
  484. for (int i = 0; i < cell.rowspan; i++) {
  485. int row = cell.row + i;
  486. rowHeights[row] += spaceForColunms;
  487. neededExtraSpace -= spaceForColunms;
  488. }
  489. if (neededExtraSpace > 0) {
  490. for (int i = 0; i < cell.rowspan; i++) {
  491. int row = cell.row + i;
  492. rowHeights[row] += 1;
  493. neededExtraSpace -= 1;
  494. if (neededExtraSpace == 0) {
  495. break;
  496. }
  497. }
  498. }
  499. }
  500. }
  501. }
  502. }
  503. private LinkedList<SpanList> colSpans = new LinkedList<SpanList>();
  504. private LinkedList<SpanList> rowSpans = new LinkedList<SpanList>();
  505. private int marginTopAndBottom;
  506. private class SpanList {
  507. final int span;
  508. List<Cell> cells = new LinkedList<Cell>();
  509. public SpanList(int span) {
  510. this.span = span;
  511. }
  512. }
  513. private void storeColSpannedCell(Cell cell) {
  514. SpanList l = null;
  515. for (SpanList list : colSpans) {
  516. if (list.span < cell.colspan) {
  517. continue;
  518. } else {
  519. // insert before this
  520. l = list;
  521. break;
  522. }
  523. }
  524. if (l == null) {
  525. l = new SpanList(cell.colspan);
  526. colSpans.add(l);
  527. } else if (l.span != cell.colspan) {
  528. SpanList newL = new SpanList(cell.colspan);
  529. colSpans.add(colSpans.indexOf(l), newL);
  530. l = newL;
  531. }
  532. l.cells.add(cell);
  533. }
  534. private void detectSpacing(UIDL uidl) {
  535. DivElement spacingmeter = Document.get().createDivElement();
  536. spacingmeter.setClassName(CLASSNAME + "-" + "spacing-"
  537. + (uidl.getBooleanAttribute("spacing") ? "on" : "off"));
  538. spacingmeter.getStyle().setProperty("width", "0");
  539. spacingmeter.getStyle().setProperty("height", "0");
  540. canvas.getElement().appendChild(spacingmeter);
  541. spacingPixelsHorizontal = spacingmeter.getOffsetWidth();
  542. spacingPixelsVertical = spacingmeter.getOffsetHeight();
  543. canvas.getElement().removeChild(spacingmeter);
  544. }
  545. private void handleMargins(UIDL uidl) {
  546. final VMarginInfo margins = new VMarginInfo(
  547. uidl.getIntAttribute("margins"));
  548. String styles = CLASSNAME + "-margin";
  549. if (margins.hasTop()) {
  550. styles += " " + CLASSNAME + "-" + StyleConstants.MARGIN_TOP;
  551. }
  552. if (margins.hasRight()) {
  553. styles += " " + CLASSNAME + "-" + StyleConstants.MARGIN_RIGHT;
  554. }
  555. if (margins.hasBottom()) {
  556. styles += " " + CLASSNAME + "-" + StyleConstants.MARGIN_BOTTOM;
  557. }
  558. if (margins.hasLeft()) {
  559. styles += " " + CLASSNAME + "-" + StyleConstants.MARGIN_LEFT;
  560. }
  561. margin.setClassName(styles);
  562. marginTopAndBottom = margin.getOffsetHeight()
  563. - canvas.getOffsetHeight();
  564. }
  565. public boolean hasChildComponent(Widget component) {
  566. return paintableToCell.containsKey(component);
  567. }
  568. public void replaceChildComponent(Widget oldComponent, Widget newComponent) {
  569. ChildComponentContainer componentContainer = widgetToComponentContainer
  570. .remove(oldComponent);
  571. if (componentContainer == null) {
  572. return;
  573. }
  574. componentContainer.setWidget(newComponent);
  575. widgetToComponentContainer.put(newComponent, componentContainer);
  576. paintableToCell.put((Paintable) newComponent,
  577. paintableToCell.get(oldComponent));
  578. }
  579. public void updateCaption(Paintable component, UIDL uidl) {
  580. ChildComponentContainer cc = widgetToComponentContainer.get(component);
  581. if (cc != null) {
  582. cc.updateCaption(uidl, client);
  583. }
  584. if (!rendering) {
  585. // ensure rel size details are updated
  586. paintableToCell.get(component).updateRelSizeStatus(uidl);
  587. }
  588. }
  589. public boolean requestLayout(final Set<Paintable> changedChildren) {
  590. boolean needsLayout = false;
  591. boolean reDistributeColSpanWidths = false;
  592. boolean reDistributeRowSpanHeights = false;
  593. int offsetHeight = canvas.getOffsetHeight();
  594. int offsetWidth = canvas.getOffsetWidth();
  595. if ("".equals(width) || "".equals(height)) {
  596. needsLayout = true;
  597. }
  598. ArrayList<Integer> dirtyColumns = new ArrayList<Integer>();
  599. ArrayList<Integer> dirtyRows = new ArrayList<Integer>();
  600. for (Paintable paintable : changedChildren) {
  601. Cell cell = paintableToCell.get(paintable);
  602. if (!cell.hasRelativeHeight() || !cell.hasRelativeWidth()) {
  603. // cell sizes will only stay still if only relatively
  604. // sized components
  605. // check if changed child affects min col widths
  606. assert cell.cc != null;
  607. cell.cc.setWidth("");
  608. cell.cc.setHeight("");
  609. cell.cc.updateWidgetSize();
  610. /*
  611. * If this is the result of an caption icon onload event the
  612. * caption size may have changed
  613. */
  614. cell.cc.updateCaptionSize();
  615. int width = cell.getWidth();
  616. int allocated = columnWidths[cell.col];
  617. for (int i = 1; i < cell.colspan; i++) {
  618. allocated += spacingPixelsHorizontal
  619. + columnWidths[cell.col + i];
  620. }
  621. if (allocated < width) {
  622. needsLayout = true;
  623. if (cell.colspan == 1) {
  624. // do simple column width expansion
  625. columnWidths[cell.col] = minColumnWidths[cell.col] = width;
  626. } else {
  627. // mark that col span expansion is needed
  628. reDistributeColSpanWidths = true;
  629. }
  630. } else if (allocated != width) {
  631. // size is smaller thant allocated, column might
  632. // shrink
  633. dirtyColumns.add(cell.col);
  634. }
  635. int height = cell.getHeight();
  636. allocated = rowHeights[cell.row];
  637. for (int i = 1; i < cell.rowspan; i++) {
  638. allocated += spacingPixelsVertical
  639. + rowHeights[cell.row + i];
  640. }
  641. if (allocated < height) {
  642. needsLayout = true;
  643. if (cell.rowspan == 1) {
  644. // do simple row expansion
  645. rowHeights[cell.row] = minRowHeights[cell.row] = height;
  646. } else {
  647. // mark that row span expansion is needed
  648. reDistributeRowSpanHeights = true;
  649. }
  650. } else if (allocated != height) {
  651. // size is smaller than allocated, row might shrink
  652. dirtyRows.add(cell.row);
  653. }
  654. }
  655. }
  656. if (dirtyColumns.size() > 0) {
  657. for (Integer colIndex : dirtyColumns) {
  658. int colW = 0;
  659. for (int i = 0; i < rowHeights.length; i++) {
  660. Cell cell = cells[colIndex][i];
  661. if (cell != null && cell.getChildUIDL() != null
  662. && !cell.hasRelativeWidth() && cell.colspan == 1) {
  663. int width = cell.getWidth();
  664. if (width > colW) {
  665. colW = width;
  666. }
  667. }
  668. }
  669. minColumnWidths[colIndex] = colW;
  670. }
  671. needsLayout = true;
  672. // ensure colspanned columns have enough space
  673. columnWidths = cloneArray(minColumnWidths);
  674. distributeColSpanWidths();
  675. reDistributeColSpanWidths = false;
  676. }
  677. if (reDistributeColSpanWidths) {
  678. distributeColSpanWidths();
  679. }
  680. if (dirtyRows.size() > 0) {
  681. needsLayout = true;
  682. for (Integer rowIndex : dirtyRows) {
  683. // recalculate min row height
  684. int rowH = minRowHeights[rowIndex] = 0;
  685. // loop all columns on row rowIndex
  686. for (int i = 0; i < columnWidths.length; i++) {
  687. Cell cell = cells[i][rowIndex];
  688. if (cell != null && cell.getChildUIDL() != null
  689. && !cell.hasRelativeHeight() && cell.rowspan == 1) {
  690. int h = cell.getHeight();
  691. if (h > rowH) {
  692. rowH = h;
  693. }
  694. }
  695. }
  696. minRowHeights[rowIndex] = rowH;
  697. }
  698. // TODO could check only some row spans
  699. rowHeights = cloneArray(minRowHeights);
  700. distributeRowSpanHeights();
  701. reDistributeRowSpanHeights = false;
  702. }
  703. if (reDistributeRowSpanHeights) {
  704. distributeRowSpanHeights();
  705. }
  706. if (needsLayout) {
  707. expandColumns();
  708. expandRows();
  709. layoutCells();
  710. // loop all relative sized components and update their size
  711. for (int i = 0; i < cells.length; i++) {
  712. for (int j = 0; j < cells[i].length; j++) {
  713. Cell cell = cells[i][j];
  714. if (cell != null
  715. && cell.cc != null
  716. && (cell.hasRelativeHeight() || cell
  717. .hasRelativeWidth())) {
  718. client.handleComponentRelativeSize(cell.cc.getWidget());
  719. }
  720. }
  721. }
  722. }
  723. if (canvas.getOffsetHeight() != offsetHeight
  724. || canvas.getOffsetWidth() != offsetWidth) {
  725. return false;
  726. } else {
  727. return true;
  728. }
  729. }
  730. public RenderSpace getAllocatedSpace(Widget child) {
  731. Cell cell = paintableToCell.get(child);
  732. assert cell != null;
  733. return cell.getAllocatedSpace();
  734. }
  735. private Cell[][] cells;
  736. /**
  737. * Private helper class.
  738. */
  739. private class Cell {
  740. private boolean relHeight = false;
  741. private boolean relWidth = false;
  742. private boolean widthCanAffectHeight = false;
  743. public Cell(UIDL c) {
  744. row = c.getIntAttribute("y");
  745. col = c.getIntAttribute("x");
  746. setUidl(c);
  747. }
  748. public boolean widthCanAffectHeight() {
  749. return widthCanAffectHeight;
  750. }
  751. public boolean hasRelativeHeight() {
  752. return relHeight;
  753. }
  754. public RenderSpace getAllocatedSpace() {
  755. return new RenderSpace(getAvailableWidth()
  756. - cc.getCaptionWidthAfterComponent(), getAvailableHeight()
  757. - cc.getCaptionHeightAboveComponent());
  758. }
  759. public boolean hasContent() {
  760. return childUidl != null;
  761. }
  762. /**
  763. * @return total of spanned cols
  764. */
  765. private int getAvailableWidth() {
  766. int width = columnWidths[col];
  767. for (int i = 1; i < colspan; i++) {
  768. width += spacingPixelsHorizontal + columnWidths[col + i];
  769. }
  770. return width;
  771. }
  772. /**
  773. * @return total of spanned rows
  774. */
  775. private int getAvailableHeight() {
  776. int height = rowHeights[row];
  777. for (int i = 1; i < rowspan; i++) {
  778. height += spacingPixelsVertical + rowHeights[row + i];
  779. }
  780. return height;
  781. }
  782. public void layout(int x, int y) {
  783. if (cc != null && cc.isAttached()) {
  784. canvas.setWidgetPosition(cc, x, y);
  785. cc.setContainerSize(getAvailableWidth(), getAvailableHeight());
  786. cc.setAlignment(new AlignmentInfo(alignment));
  787. cc.updateAlignments(getAvailableWidth(), getAvailableHeight());
  788. }
  789. }
  790. public int getWidth() {
  791. if (cc != null) {
  792. int w = cc.getWidgetSize().getWidth()
  793. + cc.getCaptionWidthAfterComponent();
  794. return w;
  795. } else {
  796. return 0;
  797. }
  798. }
  799. public int getHeight() {
  800. if (cc != null) {
  801. return cc.getWidgetSize().getHeight()
  802. + cc.getCaptionHeightAboveComponent();
  803. } else {
  804. return 0;
  805. }
  806. }
  807. public boolean renderIfNoRelativeWidth() {
  808. if (childUidl == null) {
  809. return false;
  810. }
  811. if (!hasRelativeWidth()) {
  812. render();
  813. return true;
  814. } else {
  815. return false;
  816. }
  817. }
  818. protected boolean hasRelativeWidth() {
  819. return relWidth;
  820. }
  821. protected void render() {
  822. assert childUidl != null;
  823. Paintable paintable = client.getPaintable(childUidl);
  824. assert paintable != null;
  825. if (cc == null || cc.getWidget() != paintable) {
  826. if (widgetToComponentContainer.containsKey(paintable)) {
  827. cc = widgetToComponentContainer.get(paintable);
  828. cc.setWidth("");
  829. cc.setHeight("");
  830. } else {
  831. cc = new ChildComponentContainer((Widget) paintable,
  832. CellBasedLayout.ORIENTATION_VERTICAL);
  833. widgetToComponentContainer.put((Widget) paintable, cc);
  834. paintableToCell.put(paintable, this);
  835. cc.setWidth("");
  836. canvas.add(cc, 0, 0);
  837. }
  838. }
  839. cc.renderChild(childUidl, client, -1);
  840. if (sizeChangedDuringRendering && Util.isCached(childUidl)) {
  841. client.handleComponentRelativeSize(cc.getWidget());
  842. }
  843. cc.updateWidgetSize();
  844. nonRenderedWidgets.remove(paintable);
  845. }
  846. public UIDL getChildUIDL() {
  847. return childUidl;
  848. }
  849. final int row;
  850. final int col;
  851. int colspan = 1;
  852. int rowspan = 1;
  853. UIDL childUidl;
  854. int alignment;
  855. // may be null after setUidl() if content has vanished or changed, set
  856. // in render()
  857. ChildComponentContainer cc;
  858. public void setUidl(UIDL c) {
  859. // Set cell width
  860. colspan = c.hasAttribute("w") ? c.getIntAttribute("w") : 1;
  861. // Set cell height
  862. rowspan = c.hasAttribute("h") ? c.getIntAttribute("h") : 1;
  863. // ensure we will lose reference to old cells, now overlapped by
  864. // this cell
  865. for (int i = 0; i < colspan; i++) {
  866. for (int j = 0; j < rowspan; j++) {
  867. if (i > 0 || j > 0) {
  868. cells[col + i][row + j] = null;
  869. }
  870. }
  871. }
  872. c = c.getChildUIDL(0); // we are interested about childUidl
  873. if (childUidl != null) {
  874. if (c == null) {
  875. // content has vanished, old content will be removed from
  876. // canvas later during the render phase
  877. cc = null;
  878. } else if (cc != null
  879. && cc.getWidget() != client.getPaintable(c)) {
  880. // content has changed
  881. cc = null;
  882. Paintable paintable = client.getPaintable(c);
  883. if (widgetToComponentContainer.containsKey(paintable)) {
  884. // cc exist for this component (moved) use that for this
  885. // cell
  886. cc = widgetToComponentContainer.get(paintable);
  887. cc.setWidth("");
  888. cc.setHeight("");
  889. paintableToCell.put(paintable, this);
  890. }
  891. }
  892. }
  893. childUidl = c;
  894. updateRelSizeStatus(c);
  895. }
  896. protected void updateRelSizeStatus(UIDL uidl) {
  897. if (uidl != null && !uidl.getBooleanAttribute("cached")) {
  898. if (uidl.hasAttribute("height")
  899. && uidl.getStringAttribute("height").contains("%")) {
  900. relHeight = true;
  901. } else {
  902. relHeight = false;
  903. }
  904. if (uidl.hasAttribute("width")) {
  905. widthCanAffectHeight = relWidth = uidl.getStringAttribute(
  906. "width").contains("%");
  907. if (uidl.hasAttribute("height")) {
  908. widthCanAffectHeight = false;
  909. }
  910. } else {
  911. widthCanAffectHeight = !uidl.hasAttribute("height");
  912. relWidth = false;
  913. }
  914. }
  915. }
  916. }
  917. private Cell getCell(UIDL c) {
  918. int row = c.getIntAttribute("y");
  919. int col = c.getIntAttribute("x");
  920. Cell cell = cells[col][row];
  921. if (cell == null) {
  922. cell = new Cell(c);
  923. cells[col][row] = cell;
  924. } else {
  925. cell.setUidl(c);
  926. }
  927. return cell;
  928. }
  929. /**
  930. * Returns the child component which contains "element". The child component
  931. * is also returned if "element" is part of its caption.
  932. *
  933. * @param element
  934. * An element that is a sub element of the root element in this
  935. * layout
  936. * @return The Paintable which the element is a part of. Null if the element
  937. * belongs to the layout and not to a child.
  938. */
  939. private Paintable getComponent(Element element) {
  940. return Util.getChildPaintableForElement(client, this, element);
  941. }
  942. }