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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649
  1. /*
  2. @VaadinApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.client.ui;
  5. import java.util.HashMap;
  6. import java.util.LinkedList;
  7. import java.util.List;
  8. import com.google.gwt.dom.client.DivElement;
  9. import com.google.gwt.dom.client.Document;
  10. import com.google.gwt.dom.client.Style;
  11. import com.google.gwt.dom.client.Style.Position;
  12. import com.google.gwt.dom.client.Style.Unit;
  13. import com.google.gwt.user.client.Element;
  14. import com.google.gwt.user.client.ui.ComplexPanel;
  15. import com.google.gwt.user.client.ui.Widget;
  16. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  17. import com.vaadin.terminal.gwt.client.LayoutManager;
  18. import com.vaadin.terminal.gwt.client.UIDL;
  19. import com.vaadin.terminal.gwt.client.Util;
  20. import com.vaadin.terminal.gwt.client.VCaption;
  21. import com.vaadin.terminal.gwt.client.ConnectorMap;
  22. import com.vaadin.terminal.gwt.client.ComponentConnector;
  23. import com.vaadin.terminal.gwt.client.ui.layout.VLayoutSlot;
  24. import com.vaadin.terminal.gwt.client.ui.layout.VPaintableLayoutSlot;
  25. public class VGridLayout extends ComplexPanel {
  26. public static final String CLASSNAME = "v-gridlayout";
  27. ApplicationConnection client;
  28. HashMap<Widget, Cell> widgetToCell = new HashMap<Widget, Cell>();
  29. int[] columnWidths;
  30. int[] rowHeights;
  31. int[] colExpandRatioArray;
  32. int[] rowExpandRatioArray;
  33. int[] minColumnWidths;
  34. private int[] minRowHeights;
  35. DivElement spacingMeasureElement;
  36. public VGridLayout() {
  37. super();
  38. setElement(Document.get().createDivElement());
  39. spacingMeasureElement = Document.get().createDivElement();
  40. Style spacingStyle = spacingMeasureElement.getStyle();
  41. spacingStyle.setPosition(Position.ABSOLUTE);
  42. getElement().appendChild(spacingMeasureElement);
  43. setStyleName(CLASSNAME);
  44. }
  45. private ComponentConnector getPaintable() {
  46. return ConnectorMap.get(client).getConnector(this);
  47. }
  48. /**
  49. * Returns the column widths measured in pixels
  50. *
  51. * @return
  52. */
  53. protected int[] getColumnWidths() {
  54. return columnWidths;
  55. }
  56. /**
  57. * Returns the row heights measured in pixels
  58. *
  59. * @return
  60. */
  61. protected int[] getRowHeights() {
  62. return rowHeights;
  63. }
  64. /**
  65. * Returns the spacing between the cells horizontally in pixels
  66. *
  67. * @return
  68. */
  69. protected int getHorizontalSpacing() {
  70. return LayoutManager.get(client).getOuterWidth(spacingMeasureElement);
  71. }
  72. /**
  73. * Returns the spacing between the cells vertically in pixels
  74. *
  75. * @return
  76. */
  77. protected int getVerticalSpacing() {
  78. return LayoutManager.get(client).getOuterHeight(spacingMeasureElement);
  79. }
  80. static int[] cloneArray(int[] toBeCloned) {
  81. int[] clone = new int[toBeCloned.length];
  82. for (int i = 0; i < clone.length; i++) {
  83. clone[i] = toBeCloned[i] * 1;
  84. }
  85. return clone;
  86. }
  87. void expandRows() {
  88. if (!isUndefinedHeight()) {
  89. int usedSpace = minRowHeights[0];
  90. int verticalSpacing = getVerticalSpacing();
  91. for (int i = 1; i < minRowHeights.length; i++) {
  92. usedSpace += verticalSpacing + minRowHeights[i];
  93. }
  94. int availableSpace = LayoutManager.get(client).getInnerHeight(
  95. getElement());
  96. int excessSpace = availableSpace - usedSpace;
  97. int distributed = 0;
  98. if (excessSpace > 0) {
  99. for (int i = 0; i < rowHeights.length; i++) {
  100. int ew = excessSpace * rowExpandRatioArray[i] / 1000;
  101. rowHeights[i] = minRowHeights[i] + ew;
  102. distributed += ew;
  103. }
  104. excessSpace -= distributed;
  105. int c = 0;
  106. while (excessSpace > 0) {
  107. rowHeights[c % rowHeights.length]++;
  108. excessSpace--;
  109. c++;
  110. }
  111. }
  112. }
  113. }
  114. void updateHeight() {
  115. // Detect minimum heights & calculate spans
  116. detectRowHeights();
  117. // Expand
  118. expandRows();
  119. // Position
  120. layoutCellsVertically();
  121. }
  122. void updateWidth() {
  123. // Detect widths & calculate spans
  124. detectColWidths();
  125. // Expand
  126. expandColumns();
  127. // Position
  128. layoutCellsHorizontally();
  129. }
  130. void expandColumns() {
  131. if (!isUndefinedWidth()) {
  132. int usedSpace = minColumnWidths[0];
  133. int horizontalSpacing = getHorizontalSpacing();
  134. for (int i = 1; i < minColumnWidths.length; i++) {
  135. usedSpace += horizontalSpacing + minColumnWidths[i];
  136. }
  137. int availableSpace = LayoutManager.get(client).getInnerWidth(
  138. getElement());
  139. int excessSpace = availableSpace - usedSpace;
  140. int distributed = 0;
  141. if (excessSpace > 0) {
  142. for (int i = 0; i < columnWidths.length; i++) {
  143. int ew = excessSpace * colExpandRatioArray[i] / 1000;
  144. columnWidths[i] = minColumnWidths[i] + ew;
  145. distributed += ew;
  146. }
  147. excessSpace -= distributed;
  148. int c = 0;
  149. while (excessSpace > 0) {
  150. columnWidths[c % columnWidths.length]++;
  151. excessSpace--;
  152. c++;
  153. }
  154. }
  155. }
  156. }
  157. void layoutCellsVertically() {
  158. int verticalSpacing = getVerticalSpacing();
  159. LayoutManager layoutManager = LayoutManager.get(client);
  160. Element element = getElement();
  161. int paddingTop = layoutManager.getPaddingTop(element);
  162. int y = paddingTop;
  163. for (int i = 0; i < cells.length; i++) {
  164. y = paddingTop;
  165. for (int j = 0; j < cells[i].length; j++) {
  166. Cell cell = cells[i][j];
  167. if (cell != null) {
  168. cell.layoutVertically(y);
  169. }
  170. y += rowHeights[j] + verticalSpacing;
  171. }
  172. }
  173. if (isUndefinedHeight()) {
  174. int outerHeight = y - verticalSpacing
  175. + layoutManager.getPaddingBottom(element)
  176. + layoutManager.getBorderHeight(element);
  177. element.getStyle().setHeight(outerHeight, Unit.PX);
  178. }
  179. }
  180. void layoutCellsHorizontally() {
  181. LayoutManager layoutManager = LayoutManager.get(client);
  182. Element element = getElement();
  183. int x = layoutManager.getPaddingLeft(element);
  184. int horizontalSpacing = getHorizontalSpacing();
  185. for (int i = 0; i < cells.length; i++) {
  186. for (int j = 0; j < cells[i].length; j++) {
  187. Cell cell = cells[i][j];
  188. if (cell != null) {
  189. cell.layoutHorizontally(x);
  190. }
  191. }
  192. x += columnWidths[i] + horizontalSpacing;
  193. }
  194. if (isUndefinedWidth()) {
  195. int outerWidth = x - horizontalSpacing
  196. + layoutManager.getPaddingRight(element)
  197. + layoutManager.getBorderWidth(element);
  198. element.getStyle().setWidth(outerWidth, Unit.PX);
  199. }
  200. }
  201. private boolean isUndefinedHeight() {
  202. return getPaintable().isUndefinedHeight();
  203. }
  204. private boolean isUndefinedWidth() {
  205. return getPaintable().isUndefinedWidth();
  206. }
  207. private void detectRowHeights() {
  208. for (int i = 0; i < rowHeights.length; i++) {
  209. rowHeights[i] = 0;
  210. }
  211. // collect min rowheight from non-rowspanned cells
  212. for (int i = 0; i < cells.length; i++) {
  213. for (int j = 0; j < cells[i].length; j++) {
  214. Cell cell = cells[i][j];
  215. if (cell != null) {
  216. if (cell.rowspan == 1) {
  217. if (!cell.hasRelativeHeight()
  218. && rowHeights[j] < cell.getHeight()) {
  219. rowHeights[j] = cell.getHeight();
  220. }
  221. } else {
  222. storeRowSpannedCell(cell);
  223. }
  224. }
  225. }
  226. }
  227. distributeRowSpanHeights();
  228. minRowHeights = cloneArray(rowHeights);
  229. }
  230. private void detectColWidths() {
  231. // collect min colwidths from non-colspanned cells
  232. for (int i = 0; i < columnWidths.length; i++) {
  233. columnWidths[i] = 0;
  234. }
  235. for (int i = 0; i < cells.length; i++) {
  236. for (int j = 0; j < cells[i].length; j++) {
  237. Cell cell = cells[i][j];
  238. if (cell != null) {
  239. if (cell.colspan == 1) {
  240. if (!cell.hasRelativeWidth()
  241. && columnWidths[i] < cell.getWidth()) {
  242. columnWidths[i] = cell.getWidth();
  243. }
  244. } else {
  245. storeColSpannedCell(cell);
  246. }
  247. }
  248. }
  249. }
  250. distributeColSpanWidths();
  251. minColumnWidths = cloneArray(columnWidths);
  252. }
  253. private void storeRowSpannedCell(Cell cell) {
  254. SpanList l = null;
  255. for (SpanList list : rowSpans) {
  256. if (list.span < cell.rowspan) {
  257. continue;
  258. } else {
  259. // insert before this
  260. l = list;
  261. break;
  262. }
  263. }
  264. if (l == null) {
  265. l = new SpanList(cell.rowspan);
  266. rowSpans.add(l);
  267. } else if (l.span != cell.rowspan) {
  268. SpanList newL = new SpanList(cell.rowspan);
  269. rowSpans.add(rowSpans.indexOf(l), newL);
  270. l = newL;
  271. }
  272. l.cells.add(cell);
  273. }
  274. /**
  275. * Iterates colspanned cells, ensures cols have enough space to accommodate
  276. * them
  277. */
  278. void distributeColSpanWidths() {
  279. for (SpanList list : colSpans) {
  280. for (Cell cell : list.cells) {
  281. // cells with relative content may return non 0 here if on
  282. // subsequent renders
  283. int width = cell.hasRelativeWidth() ? 0 : cell.getWidth();
  284. distributeSpanSize(columnWidths, cell.col, cell.colspan,
  285. getHorizontalSpacing(), width, colExpandRatioArray);
  286. }
  287. }
  288. }
  289. /**
  290. * Iterates rowspanned cells, ensures rows have enough space to accommodate
  291. * them
  292. */
  293. private void distributeRowSpanHeights() {
  294. for (SpanList list : rowSpans) {
  295. for (Cell cell : list.cells) {
  296. // cells with relative content may return non 0 here if on
  297. // subsequent renders
  298. int height = cell.hasRelativeHeight() ? 0 : cell.getHeight();
  299. distributeSpanSize(rowHeights, cell.row, cell.rowspan,
  300. getVerticalSpacing(), height, rowExpandRatioArray);
  301. }
  302. }
  303. }
  304. private static void distributeSpanSize(int[] dimensions,
  305. int spanStartIndex, int spanSize, int spacingSize, int size,
  306. int[] expansionRatios) {
  307. int allocated = dimensions[spanStartIndex];
  308. for (int i = 1; i < spanSize; i++) {
  309. allocated += spacingSize + dimensions[spanStartIndex + i];
  310. }
  311. if (allocated < size) {
  312. // dimensions needs to be expanded due spanned cell
  313. int neededExtraSpace = size - allocated;
  314. int allocatedExtraSpace = 0;
  315. // Divide space according to expansion ratios if any span has a
  316. // ratio
  317. int totalExpansion = 0;
  318. for (int i = 0; i < spanSize; i++) {
  319. int itemIndex = spanStartIndex + i;
  320. totalExpansion += expansionRatios[itemIndex];
  321. }
  322. for (int i = 0; i < spanSize; i++) {
  323. int itemIndex = spanStartIndex + i;
  324. int expansion;
  325. if (totalExpansion == 0) {
  326. // Divide equally among all cells if there are no
  327. // expansion ratios
  328. expansion = neededExtraSpace / spanSize;
  329. } else {
  330. expansion = neededExtraSpace * expansionRatios[itemIndex]
  331. / totalExpansion;
  332. }
  333. dimensions[itemIndex] += expansion;
  334. allocatedExtraSpace += expansion;
  335. }
  336. // We might still miss a couple of pixels because of
  337. // rounding errors...
  338. if (neededExtraSpace > allocatedExtraSpace) {
  339. for (int i = 0; i < spanSize; i++) {
  340. // Add one pixel to every cell until we have
  341. // compensated for any rounding error
  342. int itemIndex = spanStartIndex + i;
  343. dimensions[itemIndex] += 1;
  344. allocatedExtraSpace += 1;
  345. if (neededExtraSpace == allocatedExtraSpace) {
  346. break;
  347. }
  348. }
  349. }
  350. }
  351. }
  352. private LinkedList<SpanList> colSpans = new LinkedList<SpanList>();
  353. private LinkedList<SpanList> rowSpans = new LinkedList<SpanList>();
  354. private class SpanList {
  355. final int span;
  356. List<Cell> cells = new LinkedList<Cell>();
  357. public SpanList(int span) {
  358. this.span = span;
  359. }
  360. }
  361. void storeColSpannedCell(Cell cell) {
  362. SpanList l = null;
  363. for (SpanList list : colSpans) {
  364. if (list.span < cell.colspan) {
  365. continue;
  366. } else {
  367. // insert before this
  368. l = list;
  369. break;
  370. }
  371. }
  372. if (l == null) {
  373. l = new SpanList(cell.colspan);
  374. colSpans.add(l);
  375. } else if (l.span != cell.colspan) {
  376. SpanList newL = new SpanList(cell.colspan);
  377. colSpans.add(colSpans.indexOf(l), newL);
  378. l = newL;
  379. }
  380. l.cells.add(cell);
  381. }
  382. Cell[][] cells;
  383. /**
  384. * Private helper class.
  385. */
  386. class Cell {
  387. public Cell(UIDL c) {
  388. row = c.getIntAttribute("y");
  389. col = c.getIntAttribute("x");
  390. updateFromUidl(c);
  391. }
  392. public boolean hasContent() {
  393. return hasContent;
  394. }
  395. public boolean hasRelativeHeight() {
  396. if (slot != null) {
  397. return slot.getPaintable().isRelativeHeight();
  398. } else {
  399. return true;
  400. }
  401. }
  402. /**
  403. * @return total of spanned cols
  404. */
  405. private int getAvailableWidth() {
  406. int width = columnWidths[col];
  407. for (int i = 1; i < colspan; i++) {
  408. width += getHorizontalSpacing() + columnWidths[col + i];
  409. }
  410. return width;
  411. }
  412. /**
  413. * @return total of spanned rows
  414. */
  415. private int getAvailableHeight() {
  416. int height = rowHeights[row];
  417. for (int i = 1; i < rowspan; i++) {
  418. height += getVerticalSpacing() + rowHeights[row + i];
  419. }
  420. return height;
  421. }
  422. public void layoutHorizontally(int x) {
  423. if (slot != null) {
  424. slot.positionHorizontally(x, getAvailableWidth());
  425. }
  426. }
  427. public void layoutVertically(int y) {
  428. if (slot != null) {
  429. slot.positionVertically(y, getAvailableHeight());
  430. }
  431. }
  432. public int getWidth() {
  433. if (slot != null) {
  434. return slot.getUsedWidth();
  435. } else {
  436. return 0;
  437. }
  438. }
  439. public int getHeight() {
  440. if (slot != null) {
  441. return slot.getUsedHeight();
  442. } else {
  443. return 0;
  444. }
  445. }
  446. protected boolean hasRelativeWidth() {
  447. if (slot != null) {
  448. return slot.getPaintable().isRelativeWidth();
  449. } else {
  450. return true;
  451. }
  452. }
  453. final int row;
  454. final int col;
  455. int colspan = 1;
  456. int rowspan = 1;
  457. private boolean hasContent;
  458. private AlignmentInfo alignment;
  459. VPaintableLayoutSlot slot;
  460. public void updateFromUidl(UIDL cellUidl) {
  461. // Set cell width
  462. colspan = cellUidl.hasAttribute("w") ? cellUidl
  463. .getIntAttribute("w") : 1;
  464. // Set cell height
  465. rowspan = cellUidl.hasAttribute("h") ? cellUidl
  466. .getIntAttribute("h") : 1;
  467. // ensure we will lose reference to old cells, now overlapped by
  468. // this cell
  469. for (int i = 0; i < colspan; i++) {
  470. for (int j = 0; j < rowspan; j++) {
  471. if (i > 0 || j > 0) {
  472. cells[col + i][row + j] = null;
  473. }
  474. }
  475. }
  476. UIDL childUidl = cellUidl.getChildUIDL(0); // we are interested
  477. // about childUidl
  478. hasContent = childUidl != null;
  479. if (hasContent) {
  480. ComponentConnector paintable = client.getPaintable(childUidl);
  481. if (slot == null || slot.getPaintable() != paintable) {
  482. slot = new VPaintableLayoutSlot(CLASSNAME, paintable);
  483. Element slotWrapper = slot.getWrapperElement();
  484. getElement().appendChild(slotWrapper);
  485. Widget widget = paintable.getWidget();
  486. insert(widget, slotWrapper, getWidgetCount(), false);
  487. Cell oldCell = widgetToCell.put(widget, this);
  488. if (oldCell != null) {
  489. oldCell.slot.getWrapperElement().removeFromParent();
  490. oldCell.slot = null;
  491. }
  492. }
  493. paintable.updateFromUIDL(childUidl, client);
  494. }
  495. }
  496. public void setAlignment(AlignmentInfo alignmentInfo) {
  497. slot.setAlignment(alignmentInfo);
  498. }
  499. }
  500. Cell getCell(UIDL c) {
  501. int row = c.getIntAttribute("y");
  502. int col = c.getIntAttribute("x");
  503. Cell cell = cells[col][row];
  504. if (cell == null) {
  505. cell = new Cell(c);
  506. cells[col][row] = cell;
  507. } else {
  508. cell.updateFromUidl(c);
  509. }
  510. return cell;
  511. }
  512. /**
  513. * Returns the deepest nested child component which contains "element". The
  514. * child component is also returned if "element" is part of its caption.
  515. *
  516. * @param element
  517. * An element that is a nested sub element of the root element in
  518. * this layout
  519. * @return The Paintable which the element is a part of. Null if the element
  520. * belongs to the layout and not to a child.
  521. */
  522. ComponentConnector getComponent(Element element) {
  523. return Util.getPaintableForElement(client, this, element);
  524. }
  525. void setCaption(Widget widget, VCaption caption) {
  526. VLayoutSlot slot = widgetToCell.get(widget).slot;
  527. if (caption != null) {
  528. // Logical attach.
  529. getChildren().add(caption);
  530. }
  531. // Physical attach if not null, also removes old caption
  532. slot.setCaption(caption);
  533. if (caption != null) {
  534. // Adopt.
  535. adopt(caption);
  536. }
  537. }
  538. private void togglePrefixedStyleName(String name, boolean enabled) {
  539. if (enabled) {
  540. addStyleDependentName(name);
  541. } else {
  542. removeStyleDependentName(name);
  543. }
  544. }
  545. void updateMarginStyleNames(VMarginInfo marginInfo) {
  546. togglePrefixedStyleName("margin-top", marginInfo.hasTop());
  547. togglePrefixedStyleName("margin-right", marginInfo.hasRight());
  548. togglePrefixedStyleName("margin-bottom", marginInfo.hasBottom());
  549. togglePrefixedStyleName("margin-left", marginInfo.hasLeft());
  550. }
  551. void updateSpacingStyleName(boolean spacingEnabled) {
  552. String styleName = getStylePrimaryName();
  553. if (spacingEnabled) {
  554. spacingMeasureElement.addClassName(styleName + "-spacing-on");
  555. spacingMeasureElement.removeClassName(styleName + "-spacing-off");
  556. } else {
  557. spacingMeasureElement.removeClassName(styleName + "-spacing-on");
  558. spacingMeasureElement.addClassName(styleName + "-spacing-off");
  559. }
  560. }
  561. }