From fe33fb6316a84b809ebc4cde6aef4a744e489972 Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Tue, 12 Nov 2013 10:37:27 +0200 Subject: [PATCH] Fixes insert/remove columns and testcase (#12645) This includes also other bugs that were found while creating a more suitable testcase for this Change-Id: I841e3643550b02d1ba16d2eee74deab9be15cc26 --- .../com/vaadin/client/ui/grid/Escalator.java | 213 +++++++++++------- .../tests/components/grid/GridTest.html | 67 ++++-- .../client/grid/TestGridConnector.java | 10 +- .../widgetset/client/grid/VTestGrid.java | 160 +++++++++---- 4 files changed, 291 insertions(+), 159 deletions(-) diff --git a/client/src/com/vaadin/client/ui/grid/Escalator.java b/client/src/com/vaadin/client/ui/grid/Escalator.java index 051203ecb6..d5311af3da 100644 --- a/client/src/com/vaadin/client/ui/grid/Escalator.java +++ b/client/src/com/vaadin/client/ui/grid/Escalator.java @@ -27,6 +27,7 @@ import java.util.logging.Logger; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Node; +import com.google.gwt.dom.client.NodeList; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.event.logical.shared.AttachEvent; @@ -215,6 +216,10 @@ public class Escalator extends Widget { * [[frozencol]]: This needs to be re-inspected once frozen columns are * being implemented. */ + /* + * [[widgets]]: This needs to be re-inspected once GWT/Vaadin widgets are + * being supported. + */ private static final int ROW_HEIGHT_PX = 20; private static final int COLUMN_WIDTH_PX = 100; @@ -689,23 +694,26 @@ public class Escalator extends Widget { return addedRows; } - Node referenceNode; + Node referenceRow; if (root.getChildCount() != 0 && visualIndex != 0) { // get the row node we're inserting stuff after - referenceNode = root.getChild(visualIndex - 1); + referenceRow = root.getChild(visualIndex - 1); } else { // index is 0, so just prepend. - referenceNode = null; + referenceRow = null; } for (int row = visualIndex; row < visualIndex + numberOfRows; row++) { final Element tr = DOM.createTR(); addedRows.add(tr); + tr.addClassName(CLASS_NAME + "-row"); + referenceRow = insertAfterReferenceAndUpdateIt(root, tr, + referenceRow); for (int col = 0; col < columnConfiguration.getColumnCount(); col++) { final Element cellElem = createCellElement(); - paintCell(cellElem, row, col); tr.appendChild(cellElem); + paintCell(cellElem, row, col); } /* @@ -715,27 +723,6 @@ public class Escalator extends Widget { * updated to reduce the number of reflows. */ recalculateRowWidth(tr); - tr.addClassName(CLASS_NAME + "-row"); - - if (referenceNode != null) { - root.insertAfter(tr, referenceNode); - } else { - /* - * referencenode being null means we have index 0, i.e. make - * it the first row - */ - /* - * TODO [[optimize]]: Is insertFirst or append faster for an - * empty root? - */ - root.insertFirst(tr); - } - - /* - * to get the rows to appear one after another in a logical - * order, update the reference - */ - referenceNode = tr; } recalculateSectionHeight(); @@ -743,6 +730,24 @@ public class Escalator extends Widget { return addedRows; } + private Node insertAfterReferenceAndUpdateIt(final Element parent, + final Element elem, final Node referenceNode) { + if (referenceNode != null) { + parent.insertAfter(elem, referenceNode); + } else { + /* + * referencenode being null means we have offset 0, i.e. make it + * the first row + */ + /* + * TODO [[optimize]]: Is insertFirst or append faster for an + * empty root? + */ + parent.insertFirst(elem); + } + return elem; + } + protected void recalculateSectionHeight() { final double newHeight = root.getChildCount() * ROW_HEIGHT_PX; if (newHeight != height) { @@ -844,6 +849,83 @@ public class Escalator extends Widget { */ abstract protected Element getTrByVisualIndex(int index) throws IndexOutOfBoundsException; + + abstract protected int getTopVisualRowLogicalIndex(); + + protected void paintRemoveColumns(final int offset, + final int numberOfColumns) { + final NodeList childNodes = root.getChildNodes(); + for (int visualRowIndex = 0; visualRowIndex < childNodes + .getLength(); visualRowIndex++) { + final Node tr = childNodes.getItem(visualRowIndex); + + for (int column = 0; column < numberOfColumns; column++) { + // TODO [[widgets]] + tr.getChild(offset).removeFromParent(); + } + recalculateRowWidth((Element) tr); + } + + final int firstRemovedColumnLeft = offset * COLUMN_WIDTH_PX; + final boolean columnsWereRemovedFromLeftOfTheViewport = scroller.lastScrollLeft > firstRemovedColumnLeft; + + if (columnsWereRemovedFromLeftOfTheViewport) { + final int removedColumnsPxAmount = numberOfColumns + * COLUMN_WIDTH_PX; + final int leftByDiff = (int) (scroller.lastScrollLeft - removedColumnsPxAmount); + final int newScrollLeft = Math.max(firstRemovedColumnLeft, + leftByDiff); + scrollerElem.setScrollLeft(newScrollLeft); + } + + // this needs to be after the scroll position adjustment above. + scroller.recalculateScrollbarsForVirtualViewport(); + + } + + protected void paintInsertColumns(final int offset, + final int numberOfColumns) { + final NodeList childNodes = root.getChildNodes(); + final int topVisualRowLogicalIndex = getTopVisualRowLogicalIndex(); + + for (int row = 0; row < childNodes.getLength(); row++) { + final Element tr = getTrByVisualIndex(row); + + Node referenceCell; + if (offset != 0) { + referenceCell = tr.getChild(offset - 1); + } else { + referenceCell = null; + } + + for (int col = offset; col < offset + numberOfColumns; col++) { + final Element cellElem = createCellElement(); + referenceCell = insertAfterReferenceAndUpdateIt(tr, + cellElem, referenceCell); + paintCell(cellElem, topVisualRowLogicalIndex + row, col); + } + + /* + * TODO [[optimize]] [[colwidth]]: When this method is updated + * to measure things instead of using hardcoded values, it would + * be better to do everything at once after all rows have been + * updated to reduce the number of reflows. + */ + recalculateRowWidth(tr); + } + + // this needs to be before the scrollbar adjustment. + scroller.recalculateScrollbarsForVirtualViewport(); + + final boolean columnsWereAddedToTheLeftOfViewport = scroller.lastScrollLeft > offset + * COLUMN_WIDTH_PX; + + if (columnsWereAddedToTheLeftOfViewport) { + scrollerElem + .setScrollLeft((int) (scroller.lastScrollLeft + numberOfColumns + * COLUMN_WIDTH_PX)); + } + } } private abstract class AbstractStaticRowContainer extends @@ -856,6 +938,7 @@ public class Escalator extends Widget { protected void paintRemoveRows(final int index, final int numberOfRows) { for (int i = index; i < index + numberOfRows; i++) { final Element tr = (Element) root.getChild(i); + // TODO [[widgets]] tr.removeFromParent(); } recalculateSectionHeight(); @@ -871,6 +954,11 @@ public class Escalator extends Widget { + index); } } + + @Override + protected int getTopVisualRowLogicalIndex() { + return 0; + } } private class HeaderRowContainer extends AbstractStaticRowContainer { @@ -1374,6 +1462,7 @@ public class Escalator extends Widget { for (int i = 0; i < escalatorRowsToRemove; i++) { final Element tr = visualRowOrder .remove(removedVisualInside.getStart()); + // TODO [[widgets]] tr.removeFromParent(); rowTopPosMap.remove(tr); } @@ -1733,6 +1822,15 @@ public class Escalator extends Widget { tBodyScrollTop = scrollTop; position.set(bodyElem, -tBodyScrollLeft, -tBodyScrollTop); } + + @Override + protected int getTopVisualRowLogicalIndex() { + if (!visualRowOrder.isEmpty()) { + return getLogicalRowIndex(visualRowOrder.getFirst()); + } else { + return 0; + } + } } private class ColumnConfigurationImpl implements ColumnConfiguration { @@ -1751,17 +1849,11 @@ public class Escalator extends Widget { public void removeColumns(final int index, final int numberOfColumns) { assertArgumentsAreValidAndWithinRange(index, numberOfColumns); - columns--; + columns -= numberOfColumns; - // FIXME [[escalator]]: broken on escalator if (hasSomethingInDom()) { for (final AbstractRowContainer rowContainer : rowContainers) { - for (int row = 0; row < rowContainer.getRowCount(); row++) { - final Node tr = rowContainer.root.getChild(row); - for (int col = 0; col < numberOfColumns; col++) { - tr.getChild(index).removeFromParent(); - } - } + rowContainer.paintRemoveColumns(index, numberOfColumns); } } } @@ -1807,58 +1899,9 @@ public class Escalator extends Widget { } columns += numberOfColumns; - if (!hasColumnAndRowData()) { - return; - } - - for (final AbstractRowContainer rowContainer : rowContainers) { - // FIXME: broken on escalator - final Element element = rowContainer.root; - - for (int row = 0; row < element.getChildCount(); row++) { - final Element tr = (Element) element.getChild(row); - - Node referenceElement; - if (index != 0) { - referenceElement = tr.getChild(index - 1); - } else { - referenceElement = null; - } - - for (int col = index; col < index + numberOfColumns; col++) { - final Element cellElem = rowContainer - .createCellElement(); - rowContainer.paintCell(cellElem, row, col); - - if (referenceElement != null) { - tr.insertAfter(cellElem, referenceElement); - } else { - /* - * referenceElement being null means we have index - * 0, make it the first cell. - */ - /* - * TODO [[optimize]]: Is insertFirst or append - * faster for an empty tr? - */ - tr.insertFirst(cellElem); - } - - /* - * update reference to insert cells in logical order, - * the latter after the former - */ - referenceElement = cellElem; - } - - /* - * TODO [[optimize]] [[colwidth]]: When this method is - * updated to measure things instead of using hardcoded - * values, it would be better to do everything at once after - * all rows have been updated to reduce the number of - * reflows. - */ - recalculateRowWidth(tr); + if (hasColumnAndRowData()) { + for (final AbstractRowContainer rowContainer : rowContainers) { + rowContainer.paintInsertColumns(index, numberOfColumns); } } } diff --git a/uitest/src/com/vaadin/tests/components/grid/GridTest.html b/uitest/src/com/vaadin/tests/components/grid/GridTest.html index 76ebbf1807..33dda17471 100644 --- a/uitest/src/com/vaadin/tests/components/grid/GridTest.html +++ b/uitest/src/com/vaadin/tests/components/grid/GridTest.html @@ -19,21 +19,21 @@ verifyText vaadin=runcomvaadintestscomponentsgridGridTest::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VTestGrid[0]/domChild[1]/domChild[0]/domChild[1]/domChild[0]/domChild[0] - Logical row 0/0 + Row 0: 0,0 (0) verifyText - vaadin=runcomvaadintestscomponentsgridGridTest::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VTestGrid[0]/domChild[1]/domChild[0]/domChild[1]/domChild[9]/domChild[0] - Logical row 9/9 + vaadin=runcomvaadintestscomponentsgridGridTest::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VTestGrid[0]/domChild[1]/domChild[0]/domChild[1]/domChild[17]/domChild[9] + Cell: 9,17 (179) verifyTextNotPresent - Logical row 0/10 + Cell: 0,100 verifyTextNotPresent - Logical row 11/11 + Cell: 0,101 @@ -53,8 +53,8 @@ verifyText - vaadin=runcomvaadintestscomponentsgridGridTest::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VTestGrid[0]/domChild[1]/domChild[0]/domChild[1]/domChild[0]/domChild[0] - Logical row 0/10 + vaadin=runcomvaadintestscomponentsgridGridTest::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VTestGrid[0]/domChild[1]/domChild[0]/domChild[1]/domChild[18]/domChild[0] + Row 0: 0,100 (190) type @@ -68,8 +68,8 @@ verifyText - vaadin=runcomvaadintestscomponentsgridGridTest::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VTestGrid[0]/domChild[1]/domChild[0]/domChild[1]/domChild[11]/domChild[0] - Logical row 11/11 + vaadin=runcomvaadintestscomponentsgridGridTest::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VTestGrid[0]/domChild[1]/domChild[0]/domChild[1]/domChild[17]/domChild[0] + Row 11: 0,101 (200) type @@ -88,13 +88,13 @@ verifyText - vaadin=runcomvaadintestscomponentsgridGridTest::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VTestGrid[0]/domChild[1]/domChild[0]/domChild[1]/domChild[0]/domChild[0] - Logical row 0/12 + vaadin=runcomvaadintestscomponentsgridGridTest::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VTestGrid[0]/domChild[1]/domChild[0]/domChild[1]/domChild[16]/domChild[0] + Row 0: 0,102 (210) verifyText - vaadin=runcomvaadintestscomponentsgridGridTest::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VTestGrid[0]/domChild[1]/domChild[0]/domChild[1]/domChild[10]/domChild[0] - Logical row 17/29 + vaadin=runcomvaadintestscomponentsgridGridTest::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VTestGrid[0]/domChild[1]/domChild[0]/domChild[1]/domChild[1]/domChild[0] + Row 16: 0,118 (370) scroll @@ -103,28 +103,28 @@ verifyTextPresent - Logical row 56/68 + Row 56: 0,158 verifyTextPresent - Logical row 72/84 + Row 72: 0,174 scroll vaadin=runcomvaadintestscomponentsgridGridTest::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VTestGrid[0]/domChild[0] - 1875 + 3690 verifyTextPresent - Logical row 111/ + Row 201: 0,99 type vaadin=runcomvaadintestscomponentsgridGridTest::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[2]/VHorizontalLayout[0]/Slot[0]/VTextField[0] - 111 + 201 type @@ -138,14 +138,39 @@ verifyText - vaadin=runcomvaadintestscomponentsgridGridTest::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VTestGrid[0]/domChild[1]/domChild[0]/domChild[1]/domChild[17]/domChild[0] - Logical row 110/144 + vaadin=runcomvaadintestscomponentsgridGridTest::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VTestGrid[0]/domChild[1]/domChild[0]/domChild[1]/domChild[1]/domChild[0] + Row 200: 0,98 (960) verifyTextNotPresent - Logical row 111/ + Row 201: + + type + vaadin=runcomvaadintestscomponentsgridGridTest::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[3]/VHorizontalLayout[0]/Slot[0]/VTextField[0] + 0 + + + type + vaadin=runcomvaadintestscomponentsgridGridTest::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[3]/VHorizontalLayout[0]/Slot[1]/VTextField[0] + 2 + + + click + vaadin=runcomvaadintestscomponentsgridGridTest::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[3]/VHorizontalLayout[0]/Slot[2]/VButton[0] + + + + verifyText + vaadin=runcomvaadintestscomponentsgridGridTest::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VTestGrid[0]/domChild[1]/domChild[0]/domChild[1]/domChild[16]/domChild[0] + Row 184: 10,82 (974) + + + verifyText + vaadin=runcomvaadintestscomponentsgridGridTest::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VTestGrid[0]/domChild[1]/domChild[0]/domChild[1]/domChild[1]/domChild[0] + Row 200: 10,98 (1006) + diff --git a/uitest/src/com/vaadin/tests/widgetset/client/grid/TestGridConnector.java b/uitest/src/com/vaadin/tests/widgetset/client/grid/TestGridConnector.java index 80eeeb6849..6a024b9d96 100644 --- a/uitest/src/com/vaadin/tests/widgetset/client/grid/TestGridConnector.java +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/TestGridConnector.java @@ -32,24 +32,22 @@ public class TestGridConnector extends AbstractComponentConnector { registerRpc(TestGridClientRpc.class, new TestGridClientRpc() { @Override public void insertRows(int offset, int amount) { - getWidget().getBody().insertRows(offset, amount); + getWidget().insertRows(offset, amount); } @Override public void removeRows(int offset, int amount) { - getWidget().getBody().removeRows(offset, amount); + getWidget().removeRows(offset, amount); } @Override public void removeColumns(int offset, int amount) { - getWidget().getColumnConfiguration().removeColumns(offset, - amount); + getWidget().removeColumns(offset, amount); } @Override public void insertColumns(int offset, int amount) { - getWidget().getColumnConfiguration().insertColumns(offset, - amount); + getWidget().insertColumns(offset, amount); } @Override diff --git a/uitest/src/com/vaadin/tests/widgetset/client/grid/VTestGrid.java b/uitest/src/com/vaadin/tests/widgetset/client/grid/VTestGrid.java index 04fe5561be..154b1d2bc9 100644 --- a/uitest/src/com/vaadin/tests/widgetset/client/grid/VTestGrid.java +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/VTestGrid.java @@ -1,5 +1,8 @@ package com.vaadin.tests.widgetset.client.grid; +import java.util.ArrayList; +import java.util.List; + import com.google.gwt.user.client.ui.Composite; import com.vaadin.client.ui.grid.Cell; import com.vaadin.client.ui.grid.CellRenderer; @@ -9,77 +12,130 @@ import com.vaadin.client.ui.grid.RowContainer; import com.vaadin.client.ui.grid.ScrollDestination; public class VTestGrid extends Composite { - public static class HeaderRenderer implements CellRenderer { - private int i = 0; - @Override - public void renderCell(final Cell cell) { - cell.getElement().setInnerText("Header " + (i++)); - } - } + private static class Data { + private int columnCounter = 0; + private int rowCounter = 0; + private final List columns = new ArrayList(); + private final List rows = new ArrayList(); - public static class BodyRenderer implements CellRenderer { - private int i = 0; - private int ri = 0; - - @Override - public void renderCell(final Cell cell) { - if (cell.getColumn() != 0) { - cell.getElement().setInnerText("Cell #" + (i++)); - } else { - cell.getElement().setInnerText( - "Logical row " + cell.getRow() + "/" + (ri++)); + public void insertRows(int offset, int amount) { + List newRows = new ArrayList(); + for (int i = 0; i < amount; i++) { + newRows.add(rowCounter++); } + rows.addAll(offset, newRows); + } - double c = i * .1; - int r = (int) ((Math.cos(c) + 1) * 128); - int g = (int) ((Math.cos(c / Math.PI) + 1) * 128); - int b = (int) ((Math.cos(c / (Math.PI * 2)) + 1) * 128); - cell.getElement().getStyle() - .setBackgroundColor("rgb(" + r + "," + g + "," + b + ")"); - if ((r * .8 + g * 1.3 + b * .9) / 3 < 127) { - cell.getElement().getStyle().setColor("white"); - } else { - cell.getElement().getStyle().clearColor(); + public void insertColumns(int offset, int amount) { + List newColumns = new ArrayList(); + for (int i = 0; i < amount; i++) { + newColumns.add(columnCounter++); } + columns.addAll(offset, newColumns); + } + + public CellRenderer createHeaderRenderer() { + return new CellRenderer() { + @Override + public void renderCell(Cell cell) { + int columnName = columns.get(cell.getColumn()); + cell.getElement().setInnerText("Header " + columnName); + } + }; + } + + public CellRenderer createFooterRenderer() { + return new CellRenderer() { + @Override + public void renderCell(Cell cell) { + int columnName = columns.get(cell.getColumn()); + cell.getElement().setInnerText("Footer " + columnName); + } + }; + } + + public CellRenderer createBodyRenderer() { + return new CellRenderer() { + int i = 0; + + @Override + public void renderCell(Cell cell) { + int columnName = columns.get(cell.getColumn()); + int rowName = rows.get(cell.getRow()); + String cellInfo = columnName + "," + rowName + " (" + i + + ")"; + + if (cell.getColumn() > 0) { + cell.getElement().setInnerText("Cell: " + cellInfo); + } else { + cell.getElement().setInnerText( + "Row " + cell.getRow() + ": " + cellInfo); + } + + double c = i * .1; + int r = (int) ((Math.cos(c) + 1) * 128); + int g = (int) ((Math.cos(c / Math.PI) + 1) * 128); + int b = (int) ((Math.cos(c / (Math.PI * 2)) + 1) * 128); + cell.getElement() + .getStyle() + .setBackgroundColor( + "rgb(" + r + "," + g + "," + b + ")"); + if ((r * .8 + g * 1.3 + b * .9) / 3 < 127) { + cell.getElement().getStyle().setColor("white"); + } else { + cell.getElement().getStyle().clearColor(); + } + + i++; + } + }; } - } - public static class FooterRenderer implements CellRenderer { - private int i = 0; + public void removeRows(int offset, int amount) { + for (int i = 0; i < amount; i++) { + rows.remove(offset); + } + } - @Override - public void renderCell(final Cell cell) { - cell.getElement().setInnerText("Footer " + (i++)); + public void removeColumns(int offset, int amount) { + for (int i = 0; i < amount; i++) { + columns.remove(offset); + } } } private Escalator escalator = new Escalator(); + private Data data = new Data(); public VTestGrid() { initWidget(escalator); - final ColumnConfiguration cConf = escalator.getColumnConfiguration(); - cConf.insertColumns(cConf.getColumnCount(), 10); + RowContainer header = escalator.getHeader(); + header.setCellRenderer(data.createHeaderRenderer()); + header.insertRows(0, 1); - final RowContainer h = escalator.getHeader(); - h.setCellRenderer(new HeaderRenderer()); - h.insertRows(0, 1); + RowContainer footer = escalator.getFooter(); + footer.setCellRenderer(data.createFooterRenderer()); + footer.insertRows(0, 1); - final RowContainer b = escalator.getBody(); - b.setCellRenderer(new BodyRenderer()); - b.insertRows(0, 10); + escalator.getBody().setCellRenderer(data.createBodyRenderer()); - final RowContainer f = escalator.getFooter(); - f.setCellRenderer(new FooterRenderer()); - f.insertRows(0, 1); + insertRows(0, 100); + insertColumns(0, 10); setWidth(TestGridState.DEFAULT_WIDTH); setHeight(TestGridState.DEFAULT_HEIGHT); } - public RowContainer getBody() { - return escalator.getBody(); + public void insertRows(int offset, int number) { + data.insertRows(offset, number); + escalator.getBody().insertRows(offset, number); + } + + public void insertColumns(int offset, int number) { + data.insertColumns(offset, number); + escalator.getColumnConfiguration().insertColumns(offset, number); } public ColumnConfiguration getColumnConfiguration() { @@ -103,4 +159,14 @@ public class VTestGrid extends Composite { escalator.scrollToColumn(index, destination); } } + + public void removeRows(int offset, int amount) { + data.removeRows(offset, amount); + escalator.getBody().removeRows(offset, amount); + } + + public void removeColumns(int offset, int amount) { + data.removeColumns(offset, amount); + escalator.getColumnConfiguration().removeColumns(offset, amount); + } } -- 2.39.5