Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

VGridLayout.java 34KB

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